feat: v2.5.5 — AI Router renamed AI Proxy, reload onto new builds, update no longer spins

AI Proxy (was AI Router):
- server/lib/ai-proxies.js, proxy-apply.js, routes/ai-proxies.js mounted at
  /api/ai-proxies and the old /api/ai-routers; `router` still accepted on the
  start routes; profiles move to <CLAUDE_HOME>/agenthub-proxies/
- dashboard_runs.router renamed in place to proxy: an older build on the same
  DB fails on that column, so dev and prod sharing one DB update together
- a proxy's baseUrl stored as its root (a typed /v1 made /v1/v1/messages);
  Test probes Claude /v1/messages and Codex /v1/responses
- Settings → AI Proxy (/settings/ai-router redirects), ProxyPicker

Updates:
- every tab reloads onto the new UI build when its WebSocket reconnects to a
  server of another version (lib/newBuildReload.ts, once per version)
- Update now stops waiting on a plugin chain that failed or pulled nothing
  (the old server never goes down), and reloads as soon as a new version answers

Also:
- a pane whose command exits at once says so (EPANEEXITED) instead of
  "Command failed: tmux set-option … mouse on"
- the pane drag preview draws where the pane actually lands
This commit is contained in:
2026-10-06 22:48:09 +07:00
parent dd9b23106c
commit 5fe5d51b1b
82 changed files with 1792 additions and 1412 deletions
+11 -11
View File
@@ -32,11 +32,11 @@ const insertStmt = db.prepare(`
INSERT OR REPLACE INTO dashboard_runs (
id, session_id, mode, cwd, model, permission_mode, effort,
resume_session_id, prompt_preview, status, exit_code, started_at, ended_at,
lane_id, tool, worktree, router
lane_id, tool, worktree, proxy
) VALUES (
@id, @session_id, @mode, @cwd, @model, @permission_mode, @effort,
@resume_session_id, @prompt_preview, @status, @exit_code, @started_at, @ended_at,
@lane_id, @tool, @worktree, @router
@lane_id, @tool, @worktree, @proxy
)
`);
@@ -51,7 +51,7 @@ const updateStmt = db.prepare(`
const RUN_COLUMNS = `id, session_id, mode, cwd, model, permission_mode, effort,
resume_session_id, prompt_preview, status, exit_code,
started_at, ended_at, lane_id, tool, worktree, router`;
started_at, ended_at, lane_id, tool, worktree, proxy`;
const listStmt = db.prepare(`
SELECT ${RUN_COLUMNS}
@@ -123,7 +123,7 @@ function recordRun(handle) {
lane_id: typeof handle.laneId === "number" ? handle.laneId : null,
tool: typeof handle.tool === "string" && handle.tool ? handle.tool : "claude",
worktree: handle.worktree ? 1 : 0,
router: handle.router || null,
proxy: handle.proxy || null,
});
} catch (err) {
// Best-effort by design (a live run must never fail because sqlite did),
@@ -181,19 +181,19 @@ function getRun(id) {
}
}
const sessionRouterStmt = db.prepare(`
SELECT router FROM dashboard_runs
const sessionProxyStmt = db.prepare(`
SELECT proxy FROM dashboard_runs
WHERE session_id = @sessionId OR resume_session_id = @sessionId
ORDER BY started_at DESC LIMIT 1
`);
/**
* The AI Router the newest run holding `sessionId` started on (null = native,
* or no run known) — what a resume that names no router goes back to.
* The AI Proxy the newest run holding `sessionId` started on (null = native,
* or no run known) — what a resume that names no proxy goes back to.
*/
function routerForSession(sessionId) {
function proxyForSession(sessionId) {
try {
return sessionRouterStmt.get({ sessionId })?.router || null;
return sessionProxyStmt.get({ sessionId })?.proxy || null;
} catch {
return null;
}
@@ -308,7 +308,7 @@ module.exports = {
getRun,
forgetShellRun,
reconcileOrphans,
routerForSession,
proxyForSession,
laneForSession,
claimToolSessions,
};