- A session picks its own AI tool: `tool` on POST /api/lanes/:id/start
(a catalog id, known + enabled, else 400 EBADTOOL/EDISABLED); a resume
keeps the tool its session ran on (`sessions.tool`). The workspace's
`lanes.tool` is now only the default.
- The workspace home's prompt box is the one place a session starts. Its
fields run in order: AI → AI Router (Native or a profile) → Model (the
tool's own list, or the router's models) → Permission (Claude mode /
Codex sandbox+approval / catalog bypass) → Effort.
- The pane's run form (RunSetup) is gone: New run, a single pane's exit and
an empty split pane's New session all go to the home. RunSetup.tsx keeps
PromptEditor, CwdAutocomplete and ModelPicker (new `choices` prop).
- The tool icon moved from the sidebar's workspace row to each session row
(right edge, `session.tool`); the split-pane header shows the run's tool.
- An ended session opens in its workspace (`&session=`, SessionReplay.tsx):
its conversation read-only plus a prompt box; only a SENT message resumes
it (resumeSessionId + prompt, first free slot). The console is only
hidden behind it.
- Analyst button beside Files/Changes in the right panel opens
/sessions/:id for the opened session.
- Gemini CLI ships its own icon (client/public/ai-tools/gemini.svg, from
zonalogo.com).
- RouterPicker always shows (Native disabled for tools without router
support); useAiRouters() exported.
- Tests: server start-with-tool cases; SessionReplay, home field order/tool/
router models, empty pane, Analyst; dropped the cwd-field suites
(Run.defaultCwd, Workspace.laneCwd). Docs: CLAUDE.md rules, API.md,
LANES.md, README, ARCHITECTURE, INSTALL.
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.
Workspace sessions (docs/plans/desktop-parity, phases 1-5 and 7):
- sessions.lane_id: a workspace lists only the sessions its panes held,
stamped by bindRunSession (first writer wins, nested `claude -p`
excluded); GET /api/sessions?lane=&archived=
- session row state dot, double-click rename, archive (sessions.archived_at,
hides only); the activity bar's duplicate Sessions tab removed
- Fork beside Resume (--fork-session, Claude only), Ctrl/Cmd+click a live
session opens it in a second pane, a diff line's comment is pasted into
the workspace's last-focused pane (lib/panePaste.ts)
dashboard_runs stability:
- a pane's previous run is archived as <pane>@<started_at> instead of being
overwritten by INSERT OR REPLACE
- a run is flagged abandoned only once its tmux session is gone, at boot and
on the run-heal beat (panes outlive a server restart)
UI:
- sidebar: no footer, language or theme block; small collapse button in the
brand corner; version + update check in the connection details modal, a
status-bar dot while a release is out; language in Settings -> General
- one Switch component for every switch, the themed Checkbox for every
checkbox (no native checkboxes)
Also: AI Tools catalog trimmed to 10 built-ins (custom-file prompt modes
tested through fixtures), and the workspace plans under docs/plans/ (the
multi-user server plan lives in the AgentHub-Server repo).
/workspace?lane=<id> opens on the workspace's home (WorkspaceHome): a prompt
box whose Start opens a new session in the first free pane slot, Recents (a
live session attaches its pane, any other opens its transcript, nothing
resumes), and Instructions / Memory / Context / Folder. Instructions live in
lanes.instructions, extra folders and uploaded files in the new lane_sources
table (copies under <data dir>/workspace-context/<lane id>/); spawnRun reads
them from the lane's rows and hands Claude --add-dir + an appended system
prompt file, Codex --add-dir + developer_instructions. Memory is read-only.
Every dashboard pane carries AGENTHUB_RUN_ID; hooks forward it and the run
records which session the pane holds (a Claude pane follows /clear and
/resume but not a nested claude -p), so the sidebar and notifications open
the right pane (&run=). Settings loses its Projects group; Worktrees now
decides which worktrees Add folder suggests.
Two optional named themes from Orca's neutral palette join the Appearance
picker (Orca Dark keeps a blue accent so white-on-accent buttons stay
legible; Orca Light's status colours are darkened to clear 4.5:1 on their
chips). A shown notification can play a sound — None (default), Chime, Pop
or Alert — chosen globally and per notification type in Settings →
Notifications; the WAVs are generated by scripts/gen-notification-sounds.js.
Settings moves from a tab bar to /settings/<section>: a left nav grouped
General / Agents / Routing / Git / Projects (a select on narrow screens), a
search box over every section (Ctrl/Cmd+F, Enter opens the first match), and
row primitives. Each AI tool has its page under agents/<id>, the AI Tools
modal becomes the Agents page, Pricing shows every vendor, Git → Worktrees
sets which untracked worktrees the sidebar lists, and each project gets a
page with its own worktree override and Forget all. ?tool= and #ai-tools
links redirect to the path form.
Pricing: the LiteLLM sync now adds every current Claude / GPT (>= 5) /
Gemini (>= 3) chat model before it is used and keeps its own rows
(model_pricing.source = 'sync', the Auto badge) at the published rate. A
user's row (any PUT) and a deleted pattern (model_pricing_dismissed) are
never touched; a deleted built-in row is not re-seeded at the next start; a
reset re-applies the list at once; retired Claude models are priced only
once used.
Models: GET /api/ai-tools/:id/models reads each tool's own list (Codex's
models_cache.json, Claude's aliases named after the newest version plus
priced versions, a catalog tool's vendor rows plus the models its sessions
used). A catalog `model: {flag, vendor?}` field passes `<flag> <id>`
(validated: never a flag or shell text); a per-tool default model starts
each run, and a resume keeps the session's own model.
Settings: every installed, enabled catalog tool without its own tab gets
one (AiToolConfig.tsx): path, homepage, Update, sessions/cost/lanes,
default model, vendor pricing, and the config files its release catalog
lists, edited over the opened sha, syntax-checked, backed up, written
atomically; credential-looking names refused. Antigravity keeps its hooks
card there.
Lanes: a just-started run no longer reads "dead" (age counts the run's
start while the old session is still bound); a second dashboard on the
same database re-broadcasts the lanes the other changed (PRAGMA
data_version, every 2 s); a lane start with a bad model answers 400.
DB: every migration runs under a cross-process lock (<db>-migrate.lock), so
two dashboards started at once on a fresh database both come up.
- AI Tools moves from a Settings section into a modal behind a button at the
Settings tab bar's right edge (AiToolsModal, `actions` on PageTabs,
/settings#ai-tools); rows get an enable switch, Default badge, and an
Update button for every tool whose catalog names an updater (no version
lookup on open); not-installed tools fold behind a disclosure, a finished
job's log folds to one line; shared AiToolIcon
- codex updates through its own `codex update`, openclaw/hermes gain
`update --yes`; update-all runs every installed updater and skips an npm
one only when npm does not manage the binary
- Add-lane tool picker is one button showing the chosen tool that opens the
enabled tools as buttons
- Claude Code / Codex AgentHub sub-tab folded into Overview (home editor,
hooks, pricing); the path list drops CLAUDE_HOME/CODEX_HOME;
HomeDirSetting shows the saved value only while the field differs
- Preview/Raw toggle for markdown files in the config FileViewer and the
Changes panel's whole-file view (MarkdownPreview: react-markdown +
remark-gfm, raw HTML never run, frontmatter as a code block)
- docs and the CLAUDE.md marker updated
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.
Settings → Dashboard gains an AI Tools section (installed tools with
Enabled/Disabled and Set default, available ones with a homepage link,
Refresh). useAiTools shares one catalog copy across the app and replaces
LANE_TOOL_LABEL. The Add-lane modal picks Default (<tool>) or Other…; the
router row reads Native (<tool>) and disables AI Router profiles for tools
that cannot use one. The run form gives catalog tools resume and a red
per-run Bypass permissions switch (Codex too). Claude Code / Codex settings
tabs hide while that tool is disabled. The Workspace probes each lane tool's
CLI fresh.
Docs: CLAUDE.md tool/bypass rule, AGENTS.md, LANES.md, API.md, CLI.md,
server/README.md, OpenAPI fragment + regenerated openapi.yaml.
- Settings → Appearance: mode Dark / Light / System (follows the OS live) and
a named theme remembered per mode — dark: Default, Darcula, Apple Dark;
light: Default, Apple Light, IntelliJ Light, Canvas. Each theme is a
CSS-variable override block keyed by `data-theme` on <html>; the sidebar
quick toggle gains System and swaps between the two remembered themes.
- An inline boot script in index.html applies the stored mode and theme
before first paint, so a reload no longer flashes the default colours.
- Diff +/- rows are easier to tell apart on every theme: a /20 status tint,
a 3px status bar and a bold coloured marker; the changed-span emphasis no
longer vanishes on light themes.
- #42: the Add-lane modal (Repo mode / "Current branch") asks
POST /api/lanes/ensure with `exact: true`. Without it a folder nested inside
an existing lane resolved to that parent lane with created:false, so the
modal closed and no new card appeared. When the folder really already has a
lane, the modal now says which one instead of closing.
Closes#42