feat: AI Router per session; Settings at the sidebar's foot; shipped tool icons
AI Router is a session's choice, not a workspace's: - an AI Router select beside the model picker in the workspace home and a pane's run form (RouterPicker.tsx; hidden off Claude/Codex or with only Native; last pick remembered in agenthub.session.router) - `router` on POST /api/lanes/:id/start and POST /api/run; Claude gets --settings=<data dir>/router-settings/<name>.json (0600, outranks project and user settings, key never in argv), Codex -p <name> + key via tmux -e - dashboard_runs.router; a resume naming none goes back to it - profile PATCH rewrites the session files, DELETE removes them - gone: POST /api/lanes/:id/router/apply, LaneRouterRow, the Add-workspace router radio, the claude-/codex-home `routers` answers; clearLaneRouters takes the old keys out of each workspace's settings.local.json at start Sidebar: Settings pinned to the foot, the collapse toggle beside it (stacked on the rail) instead of in the brand's corner. Icons: shipped SVG marks for claude, codex, openclaw and hermes (a black/white pair swapped by theme) in client/public/ai-tools/, used by AiToolIcon before the server's favicon.
This commit is contained in:
@@ -1,11 +1,7 @@
|
||||
/**
|
||||
* @file Tests GET/PUT /api/settings/codex-home and setCodexHome: the shared
|
||||
* home-dir guard, persistence to the settings file, the `env_owned` flag, and
|
||||
* the AI Router layer being re-written into the NEW home for every codex lane
|
||||
* on a router (a `codex -p <name>` against a home without `<name>.config.toml`
|
||||
* would fail at start); a home change from another site refused, and a
|
||||
* CLAUDE_HOME change naming the lanes whose router profile the new home's
|
||||
* store lacks. Everything lives in a temp dir.
|
||||
* a home change from another site refused. Everything lives in a temp dir.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -25,9 +21,6 @@ const { describe, it, before, after } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const express = require("express");
|
||||
|
||||
const routers = require("../lib/ai-routers");
|
||||
const lanesLib = require("../lib/lanes");
|
||||
const { applyRouterToLane } = require("../lib/router-apply");
|
||||
const { envOwned } = require("../lib/settings-env");
|
||||
const settingsRouter = require("../routes/settings");
|
||||
|
||||
@@ -97,27 +90,13 @@ describe("/api/settings/codex-home", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("switches, persists, and re-writes every codex lane's router layer into the new home", async () => {
|
||||
const cwd = path.join(ROOT, "lane");
|
||||
fs.mkdirSync(cwd, { recursive: true });
|
||||
const { name } = routers.createRouter({
|
||||
label: "9Router",
|
||||
baseUrl: "http://localhost:20128/v1",
|
||||
apiKey: "sk-secret-value",
|
||||
model: "Highest",
|
||||
});
|
||||
const lane = lanesLib.createLane({ title: "c", cwd, tool: "codex", router_profile: name });
|
||||
applyRouterToLane(lane, name);
|
||||
assert.ok(fs.existsSync(path.join(process.env.CODEX_HOME, `${name}.config.toml`)));
|
||||
|
||||
it("switches and persists", async () => {
|
||||
const next = path.join(ROOT, "codex-b");
|
||||
fs.mkdirSync(next);
|
||||
const res = await request("PUT", "/api/settings/codex-home", { path: next });
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.codex_home, next);
|
||||
assert.deepEqual(res.body.routers, { updated: 1, failed: [] });
|
||||
assert.equal(process.env.CODEX_HOME, next);
|
||||
assert.ok(fs.existsSync(path.join(next, `${name}.config.toml`)));
|
||||
assert.match(
|
||||
fs.readFileSync(process.env.AGENTHUB_SETTINGS_FILE, "utf8"),
|
||||
new RegExp(`^CODEX_HOME=${next}$`, "m")
|
||||
@@ -136,13 +115,4 @@ describe("/api/settings/codex-home", () => {
|
||||
assert.equal(res.status, 403);
|
||||
assert.notEqual(process.env.CODEX_HOME, next);
|
||||
});
|
||||
|
||||
it("a CLAUDE_HOME change names the lanes whose router the new store lacks", async () => {
|
||||
const next = path.join(ROOT, "claude-b");
|
||||
fs.mkdirSync(next);
|
||||
const res = await request("PUT", "/api/settings/claude-home", { path: next });
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.routers.missing.length, 1);
|
||||
assert.match(res.body.routers.missing[0], /\(c\): 9router/i);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user