@@ -136,108 +136,94 @@ Typical log locations:
136136
137137The Diagnostics tab shows the current in-memory gateway log plus a bounded previous-session tail read from ` main.log ` at startup. First-run or unreadable log files must not block boot; return an empty previous-session tail and continue.
138138
139- ## Agent Monitor Sidecar
140-
141- The desktop app bundles the MIT-licensed ` Claude-Code-Agent-Monitor `
142- (` agent-dashboard ` + ` agent-dashboard-client ` , pinned in
143- ` apps/desktop/package.json ` ) and runs a generated runtime tree as a managed
144- localhost ** sidecar** for local Claude Code session/agent observability. It is
145- the single embedded observability tool. It powers the ** Dashboard** and the
146- agent nav items (Sessions, Kanban, Activity Feed, etc.) in the desktop left
147- sidebar. The feature is gated by the persisted ` agentMonitorEnabled ` desktop
148- setting, which ** defaults ON** ; when disabled, the agent nav items are hidden
149- and only the Gateway section remains.
150-
151- - ** Process model:** ` src/main/agent-monitor-sidecar.ts ` spawns the generated
152- ` server/index.js ` from ` apps/desktop/.generated/agent-monitor/ ` (packaged:
153- unpacked ` extraResources/agent-monitor ` ) using the Electron binary as Node
154- (` ELECTRON_RUN_AS_NODE=1 ` , ` process.execPath ` ) — a packaged app ships no
155- standalone ` node ` . Started fire-and-forget from ` boot() ` ** only when
156- ` agentMonitorEnabled ` is true** , and still before the gateway-start try-block
157- so a gateway-start failure never prevents it from running and a sidecar
158- failure never blocks or fails app boot.
139+ ## Agent Monitor
140+
141+ > ** Status (FEA-1504):** Agent Monitor has three boot modes. The default user
142+ > experience is the legacy sidecar-backed dashboard (` agentMonitorEnabled=true ` ,
143+ > ` agentDashboardDesignSystemEnabled=false ` ): pnpm-managed upstream packages are
144+ > materialized into ` .generated/agent-monitor ` , shipped unpacked, and rendered in
145+ > the legacy iframe shell. The in-process design-system dashboard is a Labs
146+ > opt-in only. When ` agentDashboardDesignSystemEnabled ` is not the literal
147+ > boolean ` true ` , the main process must not load ` src/main/database/ ` ,
148+ > ` src/main/collectors/ ` , ` AgentHookListener ` , ` desktop:db:* ` , or the ` app:// `
149+ > design renderer path.
150+
151+ The desktop app provides local Claude Code (and opt-in Codex) session/agent
152+ observability. It powers the ** Dashboard** and the agent nav items (Sessions,
153+ Activity, Analytics, Workflows, Kanban) in the desktop left sidebar. The feature
154+ is gated by the persisted ` agentMonitorEnabled ` desktop setting, which
155+ ** defaults ON** ; when disabled, the agent nav items are hidden and only the
156+ Gateway section remains.
157+
158+ - ** Legacy sidecar (default):** ` src/main/agent-monitor-sidecar.ts ` launches the
159+ generated Claude-Code-Agent-Monitor runtime tree. ` build:agent-monitor `
160+ materializes the tree from pnpm-managed upstream packages; package/stage logic
161+ must keep ` .generated/agent-monitor ` available for default users.
162+ - ** Design-system runtime (Labs opt-in):** ` src/main/agent-dashboard-design-system-runtime.ts `
163+ is the only module allowed to import ` src/main/database/ ` ,
164+ ` src/main/collectors/ ` , ` AgentHookListener ` , or register ` desktop:db:* ` . It is
165+ reached only through ` await import() ` after boot mode resolves to
166+ ` design-system ` .
167+ - ** Disabled mode:** ` agentMonitorEnabled=false ` starts no sidecar, no
168+ design-system runtime, no dashboard-derived sync source, and no
169+ dashboard-derived cost source.
170+ - ** Hook listener:** in design-system mode, ` src/main/agent-monitor-listener.ts `
171+ binds ` 127.0.0.1:4820 ` in the main process and accepts the hook payload
172+ (` POST /api/hooks/event ` , ` GET /api/health ` ). Each event is gated by the
173+ FEA-1407 sandbox check, harness-stamped from ` __provider ` , and applied by the
174+ lifecycle state machine.
175+ - ** Collection layer (` src/main/collectors/ ` ):** design-system mode uses
176+ ` CollectorManager ` for best-effort boot bulk import and live file watchers for
177+ all five agent CLIs, writing through the first-party ` importSession ` into the
178+ same in-process DB.
159179- ** Fixed port (differs from the gateway):** ` 127.0.0.1:4820 `
160- (` AGENT_MONITOR_PORT ` in ` src/shared/contracts.ts ` ), passed via
161- ` DASHBOARD_PORT ` . It MUST be fixed — Claude Code hooks bake a port at install
162- time and the hook handler POSTs to ` 127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820} ` ,
163- so 4820 (upstream's default) means hooks need zero per-hook env. 4820 is
164- outside ` PORT_PROBE_ORDER ` , so it never collides with the gateway.
165- - ** Durable DB:** ` DASHBOARD_DB_PATH ` is set to
166- ` app.getPath("userData")/agent-monitor/dashboard.db ` (the packaged app dir is
167- read-only). Uses Node's built-in ` node:sqlite ` ; the generated ` server/db.js `
168- is patched to prefer ` ./compat-sqlite ` , and staged packaging removes the
169- hoisted ` better-sqlite3 ` module as a belt-and-suspenders guard.
170- - ** UI:** embedded in the main window (` src/renderer/index.html ` ) as a plain
171- ` <iframe> ` pointed at the sidecar URL fetched via
172- ` desktop:get-agent-monitor-url ` (renderer polls until ` ready ` , then sets
173- ` src ` once). No separate window. The host left sidebar drives it: the
174- ** Dashboard** + agent nav items each map to a sidecar route. The first load
175- bakes the route + ` embed=1 ` into the iframe ` src ` ; later agent-nav clicks
176- are a ` postMessage ` (` { type: "closedloop:navigate", path } ` ) so there is no
177- reload. In embed mode the sidecar's own ` Layout ` drops its internal sidebar
178- (see ` scripts/agent-monitor-embed/Layout.tsx ` ) so the host shell is the only
179- chrome. The agent nav items are hidden when the feature is disabled. The
180- embed depends on the renderer having ** no CSP** — if a CSP is ever added it
181- must include ` frame-src http://127.0.0.1:* ` . Iframes in a ` display:none `
182- panel collapse to 0px, so an explicit px height is set via JS * after* the
183- panel is ` .active ` , re-applied on ` resize ` .
184- - ** Hooks are explicit opt-in (consent-bearing).** Upstream silently writes 8
185- hooks into ` ~/.claude/settings.json ` on every startup — the generated
186- ` server/index.js ` gates that behind ` CCAM_AUTO_INSTALL_HOOKS ` (which the
187- sidecar sets to ` "0" ` ).
188- The user enables/disables tracking via the toggle on the Agent Dashboard
189- view → ` src/main/agent-monitor-hooks.ts ` writes/removes the 8 hook entries. The
190- hook command runs the Electron binary as Node against a ** userData copy** of
191- ` hook-handler.js ` (location-independent across app moves/updates), at the
192- fixed port 4820. Default is OFF; disabling fully removes the entries;
193- re-enabling is idempotent and self-heals a stale path (also repaired at boot
194- via ` syncAgentMonitorHooksOnBoot() ` ). Disk state: a dedicated electron-store
195- (` agent-monitor-hooks ` , key ` enabled ` ).
196- - ** Lifecycle:** health-checked readiness on ` GET /api/health ` (60s ready
197- timeout — first run synchronously imports legacy ` ~/.claude ` sessions; ready
198- ≠ import-complete, the iframe populates progressively), crash-restart with
199- exponential backoff (hard cap; a fixed-port ` EADDRINUSE ` degrades to "no
200- monitor", never blocks boot or Claude Code), process-group SIGTERM→SIGKILL
201- stop wired into ` runShutdownSequence ` (` agentMonitor.stop ` , before
202- ` server.stop ` ).
203- - ** Security model (by design):** the sidecar reads ` ~/.claude ` ** directly** ,
204- * outside* the gateway ` isPathAllowed ` sandbox. Acceptable and intentional:
205- bound to ` 127.0.0.1 ` only (patched at build time; verified the LAN interface
206- is refused), the user's own local data, no cloud egress, no auth (consistent
207- with the unauthenticated ` /health ` precedent). Hooks only mutate global
208- Claude config on explicit user opt-in and are fully reversible.
209- - ** Build/packaging:** ` scripts/build-agent-monitor.mjs ` (run via
210- ` pnpm build:agent-monitor ` , chained into ` build ` ) resolves the pnpm-managed
211- upstream packages, builds the client with Vite, generates
212- ` apps/desktop/.generated/agent-monitor/ ` , applies the ClosedLoop host
213- patches (loopback bind, ` CCAM_AUTO_INSTALL_HOOKS ` gate, uninstall script,
214- ` compat-sqlite ` bootstrap), and hard-gates the build on the generated
215- ` compat-sqlite.js ` working under Electron-as-Node. Shipped via
216- ` electron-builder.yml ` ` extraResources ` (unpacked, outside the asar)
217- preserving the ` server/ ` ↔ ` client/dist/ ` relative layout.
218- - ** Multi-harness support (5 agent tools):** the same dashboard ingests
219- sessions from ** Claude Code** (via hooks), ** OpenAI Codex** (rollout JSONL
220- under ` ~/.codex/sessions/ ` ), ** Cursor** (agent transcripts under
221- ` ~/.cursor/projects/ ` ), ** GitHub Copilot** (chat JSON under VS Code
222- ` workspaceStorage/ ` + CLI JSONL under ` ~/.copilot/session-state/ ` ), and
223- ** OpenCode** (per-message JSON under ` ~/.local/share/opencode/storage/ ` ).
224- All non-Claude tools have ** no hook system** — their data comes from
225- file-based importing/watching. Proven, architecture-independent modules
226- live in-repo at `apps/desktop/scripts/agent-monitor-{codex,cursor,copilot,
227- opencode}/{tool}-{home,parser,import,watcher}.js` and are copied into the
228- generated ` server/lib/ ` at materialize time. Each parser emits the same
229- normalized shape as the upstream Claude importer so the shared
230- ` importSession() ` renders all harnesses through the unchanged UI; all
231- watchers self-heal if data directories don't exist at boot (no app restart
232- needed for a first-ever session with any tool). All non-Claude paths are
233- best-effort and never block boot or the Claude path. The build hard-gates
234- all watcher/import wiring so a future upstream bump can't silently drop
235- any harness. The user-facing nav/tray label is ** "Agent Dashboard"**
236- (internal ids/IPC channels unchanged). Environment variable overrides:
237- ` $CODEX_HOME ` , ` $CURSOR_HOME ` , ` $COPILOT_HOME ` , ` $OPENCODE_DATA_DIR ` .
238- - ** Update procedure:** bump the git dependency commit(s) in
239- ` apps/desktop/package.json ` , regenerate the lockfile, and rerun
240- ` pnpm -C apps/desktop build:agent-monitor ` . Any change here requires the
241- ` apps/desktop/package.json ` version bump (CI-enforced) and a clean-machine
242- packaged-DMG smoke test (the highest-risk path: ` node:sqlite ` from the
243- asar-external, universal-merged binary).
180+ (` AGENT_MONITOR_PORT ` in ` src/shared/contracts.ts ` ). It MUST be fixed — the
181+ hook handler POSTs to ` 127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820} ` , baked into
182+ ` ~/.claude/settings.json ` at install time, so 4820 means hooks need zero
183+ per-hook env. 4820 is outside ` PORT_PROBE_ORDER ` , so it never collides with
184+ the gateway. (FEA-1500 tracks migrating this transport later.)
185+ - ** Durable DB:** ` app.getPath("userData")/agent-dashboard.sqlite ` (schema in
186+ ` src/main/database/schema.ts ` ), Node's built-in ` node:sqlite ` . Persisted
187+ collector caches live under ` <userData>/agent-monitor/ ` .
188+ - ** UI:** a first-party React app in the main window (` src/renderer/ ` ) — NO
189+ iframe. The left sidebar drives the ** Dashboard** + agent nav items; live
190+ updates arrive via the ` desktop:db:changed ` IPC push after each write.
191+ - ** Hooks are explicit opt-in (consent-bearing).** The user enables/disables
192+ tracking via the toggle → ` src/main/agent-monitor-hooks.ts ` writes/removes the
193+ hook entries in ` ~/.claude/settings.json ` (and, opt-in, ` ~/.codex/hooks.json ` ).
194+ The hook command runs the Electron binary as Node against a ** userData copy**
195+ of the first-party ` hook-handler.js ` (location-independent across app
196+ moves/updates), at the fixed port 4820. Default is OFF; disabling fully removes
197+ the entries; re-enabling is idempotent and self-heals a stale path (also
198+ repaired at boot via ` syncAgentMonitorHooksOnBoot() ` ). When hooks are ON they
199+ own live Claude capture, so the Claude ** file watcher** is gated off (boot
200+ historical import still runs); the four non-Claude tools always file-watch.
201+ Disk state: a dedicated electron-store (` agent-monitor-hooks ` , key ` enabled ` ).
202+ - ** First-party hook handlers:** ` resources/hooks/{hook-handler,codex-hook-handler}.js `
203+ — zero-dependency CommonJS scripts that POST ` { hook_type, data } ` to
204+ ` :4820 ` . Shipped via ` electron-builder.yml ` ` extraResources ` (` to: hooks ` ,
205+ unpacked) and resolved by ` agent-monitor-path.ts ` . No build step, no generated
206+ tree.
207+ - ** Security model (by design):** the collectors + listener read the agent-CLI
208+ home dirs (` ~/.claude ` , ` ~/.codex ` , …) ** directly** , outside the gateway
209+ ` isPathAllowed ` sandbox, but every captured session is dropped unless its
210+ ` cwd ` is inside the FEA-1407 sandbox base directory (fail-closed). The listener
211+ is bound to ` 127.0.0.1 ` only; no cloud egress from collectors. Hooks only
212+ mutate global Claude/Codex config on explicit user opt-in and are reversible.
213+ - ** Multi-harness support (5 agent tools):** ingests sessions from ** Claude
214+ Code** (hooks live + file historical), ** OpenAI Codex** (rollout JSONL under
215+ ` ~/.codex/sessions/ ` ), ** Cursor** (agent transcripts under ` ~/.cursor/projects/ ` ),
216+ ** GitHub Copilot** (chat JSON under VS Code ` workspaceStorage/ ` + CLI JSONL
217+ under ` ~/.copilot/session-state/ ` ), and ** OpenCode** (the ` opencode.db ` SQLite
218+ store under ` ~/.local/share/opencode/ ` ). The four non-Claude tools have ** no
219+ hook system** — file import/watching is the only capture path. Each
220+ harness's parser (` src/main/collectors/<tool>/ ` ) emits the same normalized
221+ session shape so ` importSession ` renders all harnesses through the unchanged
222+ UI. Environment variable overrides: ` $CODEX_HOME ` , ` $CURSOR_HOME ` ,
223+ ` $COPILOT_HOME ` , ` $OPENCODE_DATA_DIR ` (Claude uses ` $CLAUDE_HOME ` ).
224+ - ** Build/packaging:** the main process is plain ` tsc ` → ` dist/ ` ; there is no
225+ agent-monitor generative build step. Packaging ships ` dist/ ` (via
226+ ` stage-packaging-app.mjs ` ) plus the unpacked ` resources/hooks ` handlers. Any
227+ change to ` apps/desktop/ ` requires the ` package.json ` version bump
228+ (CI-enforced) and a clean-machine packaged-DMG smoke test (the highest-risk
229+ path: ` node:sqlite ` from the asar-external, universal-merged binary).
0 commit comments