From ca2ea81f82e7e44c72fc66e40254521712e66460 Mon Sep 17 00:00:00 2001 From: haikdc Date: Sat, 18 Apr 2026 07:21:41 -0700 Subject: [PATCH] [hAIk]: refactor backend settings and tools routes to use explicit endpoint names (/get_settings, /update_settings, /get_builtin_permissions, /update_builtin_permissions) with matching frontend updates; simplify base_routes.ts to relative /api path; add new appsSlice Redux slice and register in store; remove unused GET_HISTORY reducer; rename structlint to lint across CI workflow, local.sh, and publish.sh; strip ANSI escape codes from NineRouter forwarded output; add swarm-debug as backend dependency --- .github/workflows/lint.yml | 2 +- backend/apps/settings/settings.py | 4 +- .../helpers/forward_output.py | 6 +- backend/pyproject.toml | 1 + backend/uv.lock | 19 ++++++ .../shared/backend-bridge/apps/settings.ts | 4 +- .../src/shared/backend-bridge/apps/tools.ts | 4 +- .../src/shared/backend-bridge/base_routes.ts | 6 +- .../src/shared/state/agentsExtraReducers.ts | 5 -- frontend/src/shared/state/appsSlice.ts | 66 +++++++++++++++++++ frontend/src/shared/state/store.ts | 2 + run/local.sh | 4 +- run/publish.sh | 4 +- 13 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 frontend/src/shared/state/appsSlice.ts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b55cfa3c..06db2baa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: with: python-version: "3.11" - - run: python3 linter/structlint.py --root . + - run: python3 linter/lint.py --root . typecheck: name: Type check (backend) diff --git a/backend/apps/settings/settings.py b/backend/apps/settings/settings.py index 444b5109..edecf298 100644 --- a/backend/apps/settings/settings.py +++ b/backend/apps/settings/settings.py @@ -30,12 +30,12 @@ def load_settings() -> AppSettings: return AppSettings() -@settings.router.get("") +@settings.router.get("/get_settings") async def get_settings(): return load_settings().model_dump() -@settings.router.put("") +@settings.router.put("/update_settings") async def update_settings(body: AppSettings): os.makedirs(SETTINGS_DIR, exist_ok=True) with open(SETTINGS_FILE, "w") as f: diff --git a/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/helpers/forward_output.py b/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/helpers/forward_output.py index e0eb2395..ac0a3986 100644 --- a/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/helpers/forward_output.py +++ b/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/helpers/forward_output.py @@ -1,12 +1,16 @@ +import re + from typeguard import typechecked +_ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]|\x1b[c78]") + # TODO: type spec this entirely @typechecked def forward_output(pipe) -> None: try: for line in iter(pipe.readline, b""): - text = line.decode("utf-8", errors="replace").rstrip() + text = _ANSI_RE.sub("", line.decode("utf-8", errors="replace")).rstrip() if text: print(f"[9router] {text}", flush=True) except Exception: diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 61fe7b55..cf2bde59 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "posthog", "pydantic==2.10.5", "watchfiles", + "swarm-debug" ] [dependency-groups] diff --git a/backend/uv.lock b/backend/uv.lock index 1896bae4..b20d2690 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -885,6 +885,7 @@ dependencies = [ { name = "pillow" }, { name = "posthog" }, { name = "pydantic" }, + { name = "swarm-debug" }, { name = "watchfiles" }, ] @@ -907,6 +908,7 @@ requires-dist = [ { name = "pillow" }, { name = "posthog" }, { name = "pydantic", specifier = "==2.10.5" }, + { name = "swarm-debug" }, { name = "watchfiles" }, ] @@ -1705,6 +1707,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] +[[package]] +name = "swarm-debug" +version = "0.1.47" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi", extra = ["standard"] }, + { name = "packaging" }, + { name = "rich" }, + { name = "typeguard" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/9c/bd571dea78033dfea387f626de983115b8a96d9dd3478166119f38a115e3/swarm_debug-0.1.47.tar.gz", hash = "sha256:b3261dd675f3b4ca1046a6d293169e0dcb3f827660a2c747103b527bbb0e712c", size = 397465, upload-time = "2026-04-17T00:48:29.047Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/16/62716f8f0cdd9e4e3ff75ed4ae034dbfcf3a349d65a1fb1f3894bb686c11/swarm_debug-0.1.47-py3-none-any.whl", hash = "sha256:c137635a1425adfddc8147766c62dd2e7f811e4911682bcdacce496144a56d0a", size = 399393, upload-time = "2026-04-17T00:48:27.469Z" }, +] + [[package]] name = "tomli" version = "2.4.1" diff --git a/frontend/src/shared/backend-bridge/apps/settings.ts b/frontend/src/shared/backend-bridge/apps/settings.ts index afcdc022..8cacd37f 100644 --- a/frontend/src/shared/backend-bridge/apps/settings.ts +++ b/frontend/src/shared/backend-bridge/apps/settings.ts @@ -40,7 +40,7 @@ export interface AppSettings { // --------------------------------------------------------------------------- -const get_settings_endpoint: string = SETTINGS_API; +const get_settings_endpoint: string = `${SETTINGS_API}/get_settings`; async function get_settings_function(): Promise { const res = await fetch(get_settings_endpoint, { method: 'GET', @@ -55,7 +55,7 @@ export const GET_SETTINGS = createAsyncThunk( ); -const update_settings_endpoint: string = SETTINGS_API; +const update_settings_endpoint: string = `${SETTINGS_API}/update_settings`; async function update_settings_function(body: Partial): Promise<{ ok: boolean; settings: AppSettings }> { const res = await fetch(update_settings_endpoint, { method: 'PUT', diff --git a/frontend/src/shared/backend-bridge/apps/tools.ts b/frontend/src/shared/backend-bridge/apps/tools.ts index 9139633b..390d0451 100644 --- a/frontend/src/shared/backend-bridge/apps/tools.ts +++ b/frontend/src/shared/backend-bridge/apps/tools.ts @@ -23,7 +23,7 @@ export const LIST_BUILTIN_TOOLS = createAsyncThunk( ); -const get_builtin_permissions_endpoint: string = `${TOOLS_API}/builtin/permissions`; +const get_builtin_permissions_endpoint: string = `${TOOLS_API}/get_builtin_permissions`; async function get_builtin_permissions_function(): Promise<{ permissions: Record }> { const res = await fetch(get_builtin_permissions_endpoint, { method: 'GET', @@ -38,7 +38,7 @@ export const GET_BUILTIN_PERMISSIONS = createAsyncThunk( ); -const update_builtin_permissions_endpoint: string = `${TOOLS_API}/builtin/permissions`; +const update_builtin_permissions_endpoint: string = `${TOOLS_API}/update_builtin_permissions`; async function update_builtin_permissions_function(permissions: Record): Promise<{ permissions: Record }> { const res = await fetch(update_builtin_permissions_endpoint, { method: 'PUT', diff --git a/frontend/src/shared/backend-bridge/base_routes.ts b/frontend/src/shared/backend-bridge/base_routes.ts index 5ed54bcd..2e876b74 100644 --- a/frontend/src/shared/backend-bridge/base_routes.ts +++ b/frontend/src/shared/backend-bridge/base_routes.ts @@ -1,5 +1 @@ -const port: number = (window as any).__OPENSWARM_PORT__ || 8325; -const host: string = window.location.hostname || 'localhost'; - -export const API_BASE: string = `http://${host}:${port}/api`; -export const WS_BASE: string = `ws://${host}:${port}`; +export const API_BASE: string = `/api`; \ No newline at end of file diff --git a/frontend/src/shared/state/agentsExtraReducers.ts b/frontend/src/shared/state/agentsExtraReducers.ts index bb314c8d..66dc993a 100644 --- a/frontend/src/shared/state/agentsExtraReducers.ts +++ b/frontend/src/shared/state/agentsExtraReducers.ts @@ -173,11 +173,6 @@ export function buildExtraReducers(builder: ActionReducerMapBuilder state.expandedSessionIds = state.expandedSessionIds.filter((id) => id !== sessionId); state.trackedNotificationIds = state.trackedNotificationIds.filter((id) => id !== sessionId); }) - .addCase(GET_HISTORY.fulfilled, (state, action) => { - const history: Record = {}; - for (const s of action.payload.sessions) history[s.id] = s; - state.history = history; - }) .addCase(RESUME_SESSION.fulfilled, (state, action) => { const session = action.payload; state.sessions[session.id] = { ...session, streamingMessage: null, tool_group_meta: session.tool_group_meta ?? {} }; diff --git a/frontend/src/shared/state/appsSlice.ts b/frontend/src/shared/state/appsSlice.ts new file mode 100644 index 00000000..b843c242 --- /dev/null +++ b/frontend/src/shared/state/appsSlice.ts @@ -0,0 +1,66 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { + LIST_APPS, + CREATE_APP, + UPDATE_APP, + DELETE_APP, + GET_APP, +} from '@/shared/backend-bridge/apps/app_builder'; +import type { App } from '@/shared/backend-bridge/apps/app_builder'; + +interface AppsState { + items: Record; + loading: boolean; + loaded: boolean; +} + +const initialState: AppsState = { + items: {}, + loading: false, + loaded: false, +}; + +const appsSlice = createSlice({ + name: 'apps', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(LIST_APPS.pending, (state) => { + state.loading = true; + }) + .addCase(LIST_APPS.fulfilled, (state, action) => { + state.loading = false; + state.loaded = true; + const items: Record = {}; + for (const a of action.payload.apps) { + items[a.id] = a; + } + state.items = items; + }) + .addCase(LIST_APPS.rejected, (state) => { + state.loading = false; + state.loaded = true; + }) + .addCase(CREATE_APP.fulfilled, (state, action) => { + state.items[action.payload.id] = action.payload; + }) + .addCase(UPDATE_APP.fulfilled, (state, action) => { + const a = action.payload; + if (a?.id) { + state.items[a.id] = { ...state.items[a.id], ...a }; + } + }) + .addCase(DELETE_APP.fulfilled, (state, action) => { + delete state.items[action.payload]; + }) + .addCase(GET_APP.fulfilled, (state, action) => { + const a = action.payload; + if (a?.id) { + state.items[a.id] = a; + } + }); + }, +}); + +export default appsSlice.reducer; diff --git a/frontend/src/shared/state/store.ts b/frontend/src/shared/state/store.ts index a27fd919..a31e07d1 100644 --- a/frontend/src/shared/state/store.ts +++ b/frontend/src/shared/state/store.ts @@ -9,6 +9,7 @@ import settingsReducer from './settingsSlice'; import skillRegistryReducer from './skillRegistrySlice'; import dashboardLayoutReducer from './dashboardLayoutSlice'; import dashboardsReducer from './dashboardsSlice'; +import appsReducer from './appsSlice'; import updateReducer from './updateSlice'; // import modelsReducer from './modelsSlice'; @@ -24,6 +25,7 @@ export const store = configureStore({ skillRegistry: skillRegistryReducer, dashboardLayout: dashboardLayoutReducer, dashboards: dashboardsReducer, + apps: appsReducer, update: updateReducer, // models: modelsReducer, }, diff --git a/run/local.sh b/run/local.sh index 51f04f97..3c281b6b 100755 --- a/run/local.sh +++ b/run/local.sh @@ -90,7 +90,7 @@ BACKEND_PORT=$(python3 -c "import json; print(json.load(open('$PROJECT_ROOT/port FRONTEND_PORT=$(python3 -c "import json; print(json.load(open('$PROJECT_ROOT/ports.config.json'))['frontend']['dev'])") # --- Run structural linter (warnings only, non-blocking) --- -LINT_OUTPUT=$(python3 "$PROJECT_ROOT/linter/structlint.py" --root "$PROJECT_ROOT" 2>&1) +LINT_OUTPUT=$(python3 "$PROJECT_ROOT/linter/lint.py" --root "$PROJECT_ROOT" 2>&1) LINT_EXIT=$? if [ $LINT_EXIT -ne 0 ]; then echo "" @@ -99,7 +99,7 @@ if [ $LINT_EXIT -ne 0 ]; then echo -e "${YELLOW} $line${RESET}" done LINT_COUNT=$(echo "$LINT_OUTPUT" | grep -oE '[0-9]+ error' | head -1 | grep -oE '[0-9]+') - echo -e "${YELLOW}${BOLD} ${LINT_COUNT} violation(s) — fix or add exceptions in linter/structlint.json${RESET}" + echo -e "${YELLOW}${BOLD} ${LINT_COUNT} violation(s) — fix or add exceptions in linter/config/config.json${RESET}" echo "" fi diff --git a/run/publish.sh b/run/publish.sh index 0fb562ef..b527b0d7 100755 --- a/run/publish.sh +++ b/run/publish.sh @@ -13,9 +13,9 @@ PROJECT_ROOT="$(dirname "$RUN_DIR_ROOT")" cd "$PROJECT_ROOT" echo "Running structural linter..." -if ! python3 "$PROJECT_ROOT/linter/structlint.py" --root "$PROJECT_ROOT"; then +if ! python3 "$PROJECT_ROOT/linter/lint.py" --root "$PROJECT_ROOT"; then echo "" - echo "Publish blocked: structlint found violations. Fix them or add exceptions in linter/structlint.json." + echo "Publish blocked: linter found violations. Fix them or add exceptions in linter/config/config.json." exit 1 fi echo ""