[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

This commit is contained in:
haikdc
2026-04-18 07:21:41 -07:00
parent ad8add82fd
commit ca2ea81f82
13 changed files with 105 additions and 22 deletions
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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:
@@ -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:
+1
View File
@@ -12,6 +12,7 @@ dependencies = [
"posthog",
"pydantic==2.10.5",
"watchfiles",
"swarm-debug"
]
[dependency-groups]
+19
View File
@@ -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"
@@ -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<AppSettings> {
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<AppSettings>): Promise<{ ok: boolean; settings: AppSettings }> {
const res = await fetch(update_settings_endpoint, {
method: 'PUT',
@@ -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<string, string> }> {
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<string, string>): Promise<{ permissions: Record<string, string> }> {
const res = await fetch(update_builtin_permissions_endpoint, {
method: 'PUT',
@@ -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`;
@@ -173,11 +173,6 @@ export function buildExtraReducers(builder: ActionReducerMapBuilder<AgentsState>
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<string, HistorySession> = {};
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 ?? {} };
+66
View File
@@ -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<string, App>;
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<string, App> = {};
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;
+2
View File
@@ -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,
},
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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 ""