- Windows panes: a WinGet tmux runs native programs and there is no `sh`, so
every pane's `sh -c` wrapper died at once ("Command failed: tmux set-option
… mouse on"). paneArgv runs the CLI itself on win32, and a Terminal gets
tmux's own default shell (newSession drops the `--` for an empty argv).
- Plugin UI build: client deps install with --ignore-scripts and tsc/vite run
by the running node instead of `npm run build`. On Windows esbuild's
postinstall went through cmd.exe with no `node` on the server's PATH, both
installs failed, and Update now kept the previous UI. A test keeps the two
build steps equal to client/package.json's build script.
- Update all: Atomics.wait instead of a `sleep` binary Windows lacks.
A Windows self-update failed at "bootstrap": 'npm.cmd' is not recognized.
The update chain runs with the dashboard server's environment, whose PATH
need not reach npm.cmd, and the bootstrap looked npm up through cmd.exe
(shell: true, which also printed DEP0190).
- npmCommand() in scripts/plugin-bootstrap.js runs the npm bundled with the
running node (npm-cli.js beside process.execPath, Windows and Unix
layouts) with no shell, and puts that node's directory first on PATH (kept
under the env's own Path/PATH key) so npm scripts find the same node; npm
on PATH only when that node ships none
- Used by the runtime install, the UI build (plugin-open.js) and the
doctor's npm row (plugin-doctor.js)
- /api/workspaces and `agenthub workspace(s)` / `--workspace` as aliases of
/api/lanes and `agenthub lanes` / `--lane`; old names print one stderr note.
REPL `help lanes|workspace|workspaces` resolves through the aliases.
- A workspace holds up to ten sessions (slots 0-9); every console tab is a
group of 1-4 split panes (PaneGroups, agenthub.workspace.paneLayout);
parseGroups keeps the stored active tab when an earlier one is dropped.
- Browse button beside path fields: POST /api/lanes/pick-path opens the OS
picker on the server's desktop — loopback only and no forwarding header
(EREMOTE), a display failure is ENODIALOG not a Cancel, the dialog is
killed when the request closes, a folder field falls back to the in-app
browser on any failure.
- Changes rail counts chip, image viewer (/git/raw), status bar session
counts and per-day cost (`date=` on /api/pricing/cost).
- /agenthub-open and /agenthub-doctor run from the INSTALLED build
(supersededBy), doctor offers fixes behind two confirmations; plugin-open
treats a locked bootstrap as non-fatal.
- New-terminal shell waits for the first attach (issue #55).
- repo-onboarding / debug-live-issue skills removed.
- OpenAPI: pick-path, lane/date on /pricing/cost, tool on /run/history,
/api/workspaces mount.
Right-click menus: one portal shell (components/ContextMenu.tsx) for every
item with actions - sidebar/Recents/archived session rows (SessionMenu),
console tabs, pane headers, the Changes panel's files/tree/commits, the
workspace home, sessions outside a workspace (SessionRowMenu: Sessions
table, Dashboard, Activity, Workflow runs), prompt images, every Settings
list and every Claude Code/Codex config list (FileItemMenu). Buttons stay;
a delete keeps its button's confirm; the shell stops portal clicks from
reaching the card that opened it.
Status bar: Workspace $ and Session $ on the Workspace page, from
?lane= on GET /api/pricing/cost and the focused/replayed session's cost.
Database, no data dropped: synchronous=NORMAL, journal_size_limit 64 MB,
hourly PASSIVE checkpoint + PRAGMA optimize, idx_events_session_created
(replacing the redundant idx_events_session), watchdog reads
sessions.transcript_path, transcript parsed before the hook's write
transaction, GET /api/sessions/:id?events=0 for the web page (reloads only
on its own session, debounced), streamed export, transactional clear-data
and chunked cleanup, subagent import reads tool_use_ids once, runtime logs
rotated past 5 MB.
After the 1.5.2 -> 1.5.3 self-update the server ran 1.5.3 while the dashboard
still served the 1.5.2 UI. The client build had failed, and `runtime/client-dist`
still held the previous version's bundle, so nothing looked broken — the only
signal was the Settings page's "UI build v1.5.2 (rebuild client to match)".
Cause: the whole client build toolchain (vite, typescript,
@vitejs/plugin-react, tailwindcss, @types/*) is a devDependency, and npm reads
NODE_ENV=production as --omit=dev (`NODE_ENV=production npm config get omit` ->
`dev`). The dashboard server runs with NODE_ENV=production, and every bootstrap
descended from it inherits that: the plugin-update chain, and any SessionStart
fired by a `claude` running in a tmux pane the server started. So `npm ci`
dropped vite, `tsc -b` died on missing type declarations, and buildClient
returned "failed" with the stale bundle left in place.
`--include=dev` on both install paths — a CLI flag outranks the env, which is
the point. Verified by running buildClient with NODE_ENV=production against the
installed plugin root: previously "failed", now "built", and the served bundle
carries 1.5.3.
Also: plugin-update-helper writes its verdict BEFORE the bootstrap handover as
well as after. That step retires the process tree the helper hangs off, and the
observed run reached the new server with no verdict file at all — the dashboard
reported `last_plugin_update: null` for an update that had actually succeeded.
npm install reuses whatever the cache and an earlier run left behind and still
reports success, so a truncated cache entry produced a node_modules that was
missing typescript's lib/*.d.ts — the build then died with TS2318, every global
type undefined. npm ci starts from an empty tree and verifies each tarball
against the lockfile's integrity hash. Falls back to install when the copied
client has no usable lockfile.
Two leftovers from the rebrand, done together because they touch the same
docs and manifests.
**Data directory.** `agent-dashboard` could not simply be renamed in the
source: it names a real directory holding the live SQLite database. Startup
now moves it once, via `migrateLegacyDataDir()` in `server/lib/claude-home.js`
— a same-filesystem rename, so it is instant even at multiple gigabytes and
cannot leave two divergent copies. `getDataDir()` stays PURE and resolves to
the legacy path until the move happens; the first version of this put the
rename inside the getter, which meant `npm run test:server` relocated the
developer's real database as a side effect. The call sits behind
`require.main === module` and ahead of the router requires, because those pull
in db.js, which opens SQLite at module load. A failed rename is silent and the
old directory keeps being used: an unfashionable directory name beats a blank
database beside an unreachable one. Same treatment for the port-discovery file
(`.agent-dashboard.json` → `.agenthub.json`, old one dropped on write).
Not renamed on purpose: the Sagi localStorage keys (renaming silently resets
everyone's prefs) and the PWA manifest `id` (changing it orphans an installed
app).
**MCP server.** 4 tool calls in 46 days across 577,132 events, against 4,556
lines of TypeScript, a 2 MB committed build artifact, and a pre-commit guard
that had already blocked commits twice. Deleted: `mcp/`, the maintainer skills
and `mcp-reviewer` agent, the build check, `plugins/agenthub/.mcp.json`,
`docs/MCP.md`, 12 npm scripts, 5 Makefile targets, and every doc section
describing it.
Kept, because they are unrelated things sharing the name: the Config
Explorer's MCP tab and its API, per-lane Playwright MCP, the agenthub-config
plugin's MCP audit, and the historical `mcp__plugin_agenthub_*` events that
analytics must keep attributing.
Verified: server 1432/1432, client 459/459, tsc clean, prettier clean. Both
directions of the migration proven — a sandbox-HOME test run leaves the legacy
directory untouched, and a real boot moves it with its contents intact.
Clean break, no compatibility alias. Renames the product name, the CLI
binary (`ccam` -> `agenthub`), the env var prefix (`CCAM_` ->
`AGENTHUB_`), the Prometheus metric namespace (`ccam_*` ->
`agenthub_*`), the tmux session prefix (`ccam-lane-<id>` ->
`agenthub-lane-<id>`), the per-repo runtime profile directory
(`.ccam/profile` -> `.agenthub/profile`), the lanes state root
(`~/.claude/ccam-lanes` -> `~/.claude/agenthub-lanes`), the bundled
plugins (`plugins/ccam*` -> `plugins/agenthub*`) and their slash
commands (`/ccam-doctor` -> `/agenthub-doctor`, `/ccam:*` ->
`/agenthub:*`).
Because there is no alias, an existing install has to be updated by
hand: rename its `.ccam/profile` directory and any `CCAM_*` variables,
and reinstall the plugin under its new name.
Two other name families are deliberately left alone here, since they are
not just strings: the `Claude-Code-Monitor` repository/marketplace name,
and the `agent-dashboard` npm package plus its `~/.claude/agent-dashboard`
runtime directory, which holds the live database and needs a migration
rather than a rename.
Verified: server 1406/1406, client 459/459, `grep -i ccam` clean across
tracked files, `openapi.yaml` regenerated (95 paths, unchanged count).
c017f6d made /ccam-open call bootstrap() when the live server belongs to a
retired plugin root, which closed a require cycle that had never been closed
before: bootstrap() lazily requires plugin-open back for buildClient, and when
plugin-open is itself the entry point that require resolves to whatever sits on
its module.exports at that instant. The assignment was the last statement in the
file, below the entry block, so it was still empty and /ccam-open died with
"TypeError: buildClient is not a function" before building anything.
Move the assignment above the entry block. The test asserts that ordering
directly — it fails against the previous layout — alongside a check that the
module a re-entrant bootstrap sees really does carry buildClient.
A plugin update applied mid-session (/plugin followed by /reload-plugins)
fires no SessionStart, so the bootstrap's version handover never runs and the
dashboard server keeps executing a cache directory Claude Code has already
discarded. /ccam-open then rebuilt the bundle from the new root against that
old server, and the new bundle called routes the old server never had:
/api/lanes/pipeline-stats fell through to /:id and rendered "lane not found",
/api/workflows/skill-usage fell through to the SPA catch-all and broke
JSON.parse on the returned HTML. Both Workflows panels showed an error while
the code they needed was sitting on disk.
plugin-open now re-runs bootstrap when the live server's recorded pluginRoot
differs from the current one, or when no server is live at all. Bootstrap
already owns the stop/start and the dependency install the new root may need,
so this only restores the trigger that a mid-session update skips.
Adds a root `ccam` plugin (`.claude-plugin/plugin.json`, `"source": "./"`) so
`/plugin marketplace add` + `/plugin install ccam@...` is enough on a machine
with nothing but Claude Code: no clone, no npm run setup, no manual npm start.
- scripts/plugin-bootstrap.js: SessionStart hook. Fast-path exit, Node >=22.5
gate (node:sqlite), mkdir lock with stale reclaim, deps installed into
~/.claude/agent-dashboard/runtime/ (never the plugin cache), legacy
checkout-hook cleanup (backed up), ~/.local/bin/ccam launcher, eager UI
build so client routes like /run work immediately, detached server spawn.
- scripts/plugin-open.js, scripts/plugin-doctor.js: /ccam-open, /ccam-doctor.
- server/index.js: DASHBOARD_CLIENT_DIST override (plugin cache is read-only).
- mcp/build/ is committed (plugin MCP servers start before any bootstrap could
build them) and kept honest by scripts/check-mcp-build.js (content hash,
not mtime), enforced by pre-commit when mcp/src changes.
- plugins/ccam-dashboard/.mcp.json moved under plugins/ccam/ with a working
${CLAUDE_PLUGIN_ROOT} path (the old relative path never resolved from a
marketplace-cached subdir).
- Docs: README, INSTALL, SETUP, ARCHITECTURE, CLAUDE.md, docs/PLUGINS.md,
docs/MCP.md, docs/CLI.md, docs/HOOKS.md.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>