- Changes panel: Files and Changes merged into one panel; the overview's
groups drawn as a folder tree (compacted chains, file counts, fold,
stage/unstage a whole folder)
- AI-suggested commit message for what is staged
(POST /api/lanes/:id/git/commit-message, claude -p --safe-mode, haiku)
- AI Router: optional "Behaves as" Claude model id, written as
modelPicker options into the router's --settings file (issue #56)
- Sidebar: session rows show last activity, live rows their start time;
a closed Terminal row can be removed (DELETE /api/run/history/:id)
- Every pane's CLI starts only once a client is attached; claude panes
no longer forced inline; tmux scrollbar one column wide
- Update notifier: "Check for updates" reopens a closed notice
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.
Copy-only rename (workspace redesign, phase 0). The word a user reads
changes — "Lane" → "Workspace", "Add lane" → "Add workspace" — in both
locales, in agenthub CLI prose and help, in the server's error messages
and in the docs. Every identifier stays: /api/lanes, `agenthub lanes`,
--lane, AGENTHUB_LANE_ID, error codes, tmux names, DB tables, file,
component and i18n key names. docs/LANES.md keeps its name, retitled
"Workspaces (lanes)".
Part B: Model Pricing, Hook Configuration and CLAUDE_HOME move from
Settings → Dashboard to the Claude Code tab's AgentHub sub-tab
(ClaudeAgentHubSettings.tsx); CODEX_HOME is editable in Codex → AgentHub.
A home that is or contains the OS home is refused (EBADHOME).
Settings persistence: dashboard-saved settings go to
~/.config/agenthub/settings.env (AGENTHUB_SETTINGS_FILE), outside the plugin
cache an update wipes. Load order: real env > the checkout's .env > that file.
The first CLAUDE_HOME change pins DASHBOARD_DATA_DIR so the DB stays put.
C1: codex-usage.js reads Codex rollout journals into sessions (new column
tool) and token_usage, tracked per file in codex_rollouts; forks deduped by
total vector, sub-agent threads folded into the root; pricing-sync also
prices OpenAI models; PricingSettings with a vendor filter.
C2: codex-hooks.js installs AgentHub's hooks into $CODEX_HOME/hooks.json on
a click, through a stable forwarder; Codex trust is never written. The hook
route creates tool='codex' sessions and folds sub-agent hooks into the root.
D: antigravity-usage.js reads agy's per-conversation SQLite files (tokens
from ModelUsageStats) into tool='antigravity' sessions; antigravity-hooks.js
installs the named hook `agenthub` (PreInvocation, PostToolUse, Stop — never
PreToolUse) into ~/.gemini/config/hooks.json; Settings → Antigravity tab;
Gemini pricing.
Whole-change review fixes: sameOriginGuard on both home PUTs, routers.missing
on a CLAUDE_HOME change, reset-pricing scope, GET /api/sessions?tool=,
clear-data covering the new tables, Codex child-thread events moving no
status, hooks owning status only while current, EFOREIGN for a forwarder
that is not ours, agy quiet window and turn rules, AGENTHUB_SETTINGS_FILE
honoured from .env.
Docs, OpenAPI and the CLAUDE.md marker updated. Server 2141, client 779.
server/lib/ai-tools.js loads server/data/ai-tools/*.json (38 tools, data from
Orca, MIT) plus DASHBOARD_AI_TOOLS_DIR, detects each CLI by walking PATH, and
keeps enable/default in <data dir>/ai-tools.json. shell-path.js merges the
login shell's PATH at startup and on Refresh. /api/ai-tools lists, refreshes
and toggles; /api/run/binary resolves through the catalog.
Lanes accept any catalog id (EDISABLED for a disabled one on creation, ENOTOOL
at start once a tool left the catalog). pty-run keeps the Claude/Codex
builders and builds every other tool's argv from its catalog file, pasting an
after-start prompt through a tmux buffer; every pane gets the hydrated PATH.
A per-run bypass switch appends the catalog's bypass args (Codex included).
POST /api/run now forwards sandbox/approval/resumeLast, which it dropped.
agenthub lanes add gains --tool.
- antigravity: a third lane tool running Antigravity's `agy` CLI. `-i`
carries the first prompt, `--continue` resumes; `agy` has no model,
effort or permission flag, and `--dangerously-skip-permissions` is never
emitted. No pipeline, no agents install, and no AI Router (native only,
else EUNSUPPORTED). Add lane gains an Antigravity choice and the binary
probe maps it to `agy`.
- UI: the lane card no longer renders a frame holding only a "Codex" chip,
and the prompt box names the lane's CLI ("Ask Codex anything…"), via
LANE_TOOL_LABEL.
- runs: a pane that exits is released on a 10 s server beat
(healAllRunIds, DASHBOARD_RUN_HEAL_MS). Codex and Antigravity fire no
hook, so their lanes stayed "running" until something re-read them.
- codex: Start no longer sends resumeLast — only "Resume last session"
continues the previous thread.
- git (#50): stage-hunk/discard-hunk take an optional `block` — one run of
consecutive +/- lines inside an @@ hunk. `onlyBlock` (server/lib/git-diff.js)
cuts git's fresh hunk down to it, folding the other blocks into the side
being checked; LaneDiffView hangs one review bar per block (unified + split)
and its arrows walk blocks.
- git exclude: entries AgentHub writes for a lane go to the COMMON git dir with
the lane's path prefix (server/lib/git-exclude.js, used by router-apply and
lane-mcp) — a linked worktree's own info/exclude is never read by git, and a
subdir lane has no .git. MCP sync no longer fails with ENOTDIR on a worktree
lane or creates a stray .git in a non-repo lane.
- codex lanes carry no pipeline: codex-ingest mirrors only idle/running,
codex-nudge is removed, agents install writes nothing for codex, the
Workspace hides stage/pipeline/feature pickers/PipelineMap for them, and
every server start clears leftover codex stage data and the old AGENTS.md
stage block.
- Changes panel: a whole file opens for reading and enters the editor on a
click on its text (caret at the clicked line), not automatically.
- macOS: LaunchAgent start-at-login (scripts/launchd-service.js, picked by
scripts/service-manager.js).
- Adds the AgentHub introduction slides and their image-embedding script.
Closes#19, #20, #21, #27.
#19 — a saved auth profile now reports how much of its account's quota is
already spent, which is the only number that decides which one is worth
switching to. Nothing under CLAUDE_HOME records it, so it comes from the same
endpoint the CLI's own /usage reads, answered for whichever account's OAuth
token is presented. It is a POST and a button, never a poll: every profile that
is not the active one is normally expired, so answering means refreshing it
first, and a refresh ROTATES the stored refresh token. The rotated pair is
written straight back — dropping it would leave the profile holding a
credential Anthropic has already retired, i.e. an account needing a fresh
login. The token is read, put in an Authorization header, and never returned,
logged or echoed; what leaves the server is two percentages and two reset
times.
#20 — a settings patch that changes nothing wrote nothing but still took a
timestamped backup, so opening the config panel quietly grew a backup
directory inside every lane. patchSettings now compares the rendered output
with what is already on disk and returns early, fixed once where all callers
route through. A lane's git exclude also picks up .claude/.cc-config-backups/
beside settings.local.json.
#21 — the router apply route was the only mutating lane route that never
broadcast its lane. The write reached disk and the column, but the client's
cached row kept the old router_profile forever: the picker looked right until
the user switched lanes and back, at which point the effect re-read the stale
row and flipped the selection to the previous profile. One broadcastLane, plus
a lane-switch guard so an apply that resolves after the user has moved on no
longer paints its result onto whichever lane is now on screen.
#27 — Add-lane in worktree mode left the dialog up after a clean setup with a
dead confirm button and no obvious way out. It closes itself when nothing
failed, and when something did, the summary stays and the confirm becomes a
real Close.
A new **AI Router** tab in Settings, next to Codex, stores named router
endpoints (9Router, OpenRouter, a self-hosted gateway) as profiles: a base
URL, an API key, a default model and an optional per-tier override. The tab
only stores them; a lane CHOOSES one in the Add-lane modal, via a Native /
AI Router radio, and that choice is written into the lane's own config.
The two CLIs are configured differently, and the difference is theirs:
- a claude lane gets eight `env` keys merged into its own
`.claude/settings.local.json` (`.local` because the value holds a key);
- a codex lane gets `$CODEX_HOME/<name>.config.toml` — the layer
`codex -p <name>` loads — because Codex has NO project-level config.
Its base `config.toml` is never touched.
Native is a built-in profile rather than a code branch, so "go back to
native" is the same write path. It blanks the two connection variables
instead of removing them (an absent key at project scope lets a USER-level
router apply) and removes the model names instead of writing them (writing
them would override the user's own global model choice).
Key material never leaves the server: profiles live one file each under
`<CLAUDE_HOME>/agenthub-routers/` (0700/0600), `GET /api/ai-routers`
reports only `hasKey`, and there is deliberately no route returning one
profile in full.
Three things this had to get right, each found by review and verified
against the real tools rather than assumed:
- `wire_api = "chat"` is rejected outright by codex-cli 0.149 ("no longer
supported"), so every codex lane on a router would have failed to start.
The layer writes "responses", and writes `env_key` only when the profile
actually has a key — naming an unset variable is itself a startup error.
- The key must never be TYPED into a pane. `tmux.sendCommand` used
`send-keys` with a `KEY='…' cmd` prefix, which the dashboard streams to
every attached browser and bash appends to history; it now takes over the
idle pane with `respawn-pane -k -e`, passing env through argv the way
`newSession` always has. Verified on tmux 3.4: multi-element argv arrives
intact and the pane renders no trace of the value.
- Attaching COPIES a profile, so editing or deleting one in Settings has to
reach the lanes already using it. An edit re-applies it (a rotated key
would otherwise never reach the lane still holding the revoked one); a
delete resets those lanes to native and removes the Codex layer file.
Both report per-lane failures, which the tab surfaces.
`.claude/settings.local.json` is added to the lane's git exclude, for both
native and router lanes: `agenthub lanes reset` runs `git clean -fd` without
`-x`, so an un-excluded settings file is deleted on reset — and untracked
meant the key was committable.
DB: `lanes.router_profile`, nullable, additive. Written only by
`setRouterProfile` beside the disk write, never through `PATCHABLE`.