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:
+17
-17
@@ -31,7 +31,7 @@
|
||||
*/
|
||||
|
||||
const tmux = require("./tmux");
|
||||
const { resolveRouter, routerForSpawn } = require("./router-apply");
|
||||
const { resolveProxy, proxyForSpawn } = require("./proxy-apply");
|
||||
const { desktopEnv } = require("./desktop-env");
|
||||
const { getCodexHome } = require("./codex-home");
|
||||
const aiTools = require("./ai-tools");
|
||||
@@ -131,7 +131,7 @@ function buildClaudeArgv({
|
||||
initialPrompt,
|
||||
addDirs = [],
|
||||
systemPromptFile = null,
|
||||
routerArgv = [],
|
||||
proxyArgv = [],
|
||||
}) {
|
||||
const argv = ["claude"];
|
||||
// `--add-dir` is VARIADIC (`<directories...>`): it swallows every following
|
||||
@@ -139,8 +139,8 @@ function buildClaudeArgv({
|
||||
// always ends it — after the prompt it would eat the prompt as a directory.
|
||||
if (addDirs.length) argv.push("--add-dir", ...addDirs);
|
||||
argv.push("--permission-mode", permissionMode || "auto");
|
||||
// The session's AI Router: `--settings=<file>` (router-apply.js).
|
||||
argv.push(...routerArgv);
|
||||
// The session's AI Proxy: `--settings=<file>` (proxy-apply.js).
|
||||
argv.push(...proxyArgv);
|
||||
// Claude Code makes the worktree itself (`.claude/worktrees/<name>`, branch
|
||||
// `worktree-<name>`, `.worktreeinclude`, the user's WorktreeCreate hook).
|
||||
// `=` form: `-w [name]` takes an OPTIONAL value and would otherwise swallow
|
||||
@@ -182,7 +182,7 @@ function buildCodexArgv({
|
||||
resumeLast,
|
||||
bypass,
|
||||
initialPrompt,
|
||||
routerArgv = [],
|
||||
proxyArgv = [],
|
||||
addDirs = [],
|
||||
systemPrompt = null,
|
||||
systemPromptFile = null,
|
||||
@@ -191,7 +191,7 @@ function buildCodexArgv({
|
||||
// Before the subcommand and before the prompt: `-p` layers
|
||||
// `$CODEX_HOME/<name>.config.toml` over the base config, and an option that
|
||||
// lands after the positional prompt is not an option any more.
|
||||
argv.push(...routerArgv);
|
||||
argv.push(...proxyArgv);
|
||||
if (resumeSessionId) argv.push("resume", resumeSessionId);
|
||||
else if (resumeLast) argv.push("resume", "--last");
|
||||
if (model) argv.push("-m", model);
|
||||
@@ -266,7 +266,7 @@ function buildArgv(args, entry) {
|
||||
* @param {boolean} [args.bypass] the user ticked "bypass permissions" for THIS
|
||||
* run; appends the catalog's bypass args/env. Claude has no catalog bypass.
|
||||
* @param {string} [args.initialPrompt]
|
||||
* @param {string} [args.router] AI Router profile THIS session runs on — a
|
||||
* @param {string} [args.proxy] AI Proxy profile THIS session runs on — a
|
||||
* name only, its key and endpoint come from the store; null/"native" = the
|
||||
* vendor's own API. Claude and Codex only (`EUNSUPPORTED`).
|
||||
*/
|
||||
@@ -288,7 +288,7 @@ function spawnRun(args) {
|
||||
resumeLast,
|
||||
bypass,
|
||||
initialPrompt,
|
||||
router,
|
||||
proxy,
|
||||
} = args || {};
|
||||
if (typeof laneId !== "number" || !Number.isInteger(laneId)) {
|
||||
throw makeErr("EBADLANE", "laneId must be an integer");
|
||||
@@ -345,7 +345,7 @@ function spawnRun(args) {
|
||||
if (worktree === true && entry.launcher !== "claude") {
|
||||
throw makeErr("EUNSUPPORTED", `${tool} cannot start a session in its own worktree`);
|
||||
}
|
||||
const routerProfile = resolveRouter(tool, router); // ENOTFOUND, EUNSUPPORTED
|
||||
const proxyProfile = resolveProxy(tool, proxy); // ENOTFOUND, EUNSUPPORTED
|
||||
if (forkSession === true) {
|
||||
if (!resumeSessionId) throw makeErr("EBADSESSION", "a fork needs resumeSessionId");
|
||||
if (entry.launcher !== "claude") {
|
||||
@@ -382,7 +382,7 @@ function spawnRun(args) {
|
||||
laneId,
|
||||
tool
|
||||
);
|
||||
const routerSpawn = routerForSpawn(tool, routerProfile);
|
||||
const proxySpawn = proxyForSpawn(tool, proxyProfile);
|
||||
// A new Claude session gets its id from us, so the run knows the session it
|
||||
// holds from the start — a hook that lost AGENTHUB_RUN_ID (an older hook
|
||||
// handler) still finds its pane by it (routes/hooks.js → bindRunSession).
|
||||
@@ -406,7 +406,7 @@ function spawnRun(args) {
|
||||
resumeLast,
|
||||
bypass: bypass === true,
|
||||
initialPrompt,
|
||||
routerArgv: routerSpawn.argv,
|
||||
proxyArgv: proxySpawn.argv,
|
||||
},
|
||||
entry
|
||||
);
|
||||
@@ -432,7 +432,7 @@ function spawnRun(args) {
|
||||
effort: effort || null,
|
||||
resumeSessionId: resumeSessionId || null,
|
||||
worktree: worktree === true || inWorktree === true,
|
||||
router: routerProfile ? routerProfile.name : null,
|
||||
proxy: proxyProfile ? proxyProfile.name : null,
|
||||
prompt: initialPrompt || "",
|
||||
status: "running",
|
||||
startedAt,
|
||||
@@ -446,9 +446,9 @@ function spawnRun(args) {
|
||||
// from inside it then resolves the lane exactly, instead of matching on cwd —
|
||||
// which is ambiguous now that one directory can back both a `claude` lane and
|
||||
// a `codex` lane (see bin/agenthub.js's resolveLaneArg).
|
||||
// A codex session on an AI Router also needs the key its layer's `env_key`
|
||||
// A codex session on an AI Proxy also needs the key its layer's `env_key`
|
||||
// names. A claude one needs nothing here — its `--settings` file carries the
|
||||
// router — and a session on the vendor's own API needs nothing either.
|
||||
// proxy — and a session on the vendor's own API needs nothing either.
|
||||
// The graphical session's own DISPLAY/WAYLAND_DISPLAY goes in too: the
|
||||
// server normally runs as a systemd user service with neither, so a pane it
|
||||
// starts has no clipboard at all — pasting an image into `claude` there
|
||||
@@ -466,14 +466,14 @@ function spawnRun(args) {
|
||||
...(process.env.PATH ? { PATH: process.env.PATH } : {}),
|
||||
...desktopEnv(),
|
||||
// A codex pane must read the same home the dashboard reads — its rollout
|
||||
// journals and the router layer `-p <name>` loads live there — and the
|
||||
// journals and the proxy layer `-p <name>` loads live there — and the
|
||||
// tmux server's own env may predate, or never have had, CODEX_HOME.
|
||||
...(tool === "codex" ? { CODEX_HOME: getCodexHome() } : {}),
|
||||
// No CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN: an inline claude duplicated its
|
||||
// output whenever the pane narrowed (tmux re-wraps the full-width rules and
|
||||
// pushes lines into history its redraw cannot clear). The user's own
|
||||
// `"tui"` setting decides; under fullscreen the pane scrolls in Claude.
|
||||
...routerSpawn.env,
|
||||
...proxySpawn.env,
|
||||
...(bypass === true && entry.bypassEnv ? entry.bypassEnv : {}),
|
||||
};
|
||||
|
||||
@@ -571,7 +571,7 @@ function spawnShell({ laneId, slot = 0, cwd }) {
|
||||
effort: null,
|
||||
resumeSessionId: null,
|
||||
worktree: false,
|
||||
router: null,
|
||||
proxy: null,
|
||||
prompt: "",
|
||||
status: "running",
|
||||
startedAt: Date.now(),
|
||||
|
||||
Reference in New Issue
Block a user