Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a6427ce
FEA: Replace agent-monitor sidecar with in-process SQLite database + …
mikeangstadt Jun 1, 2026
de59efd
FEA: Address PR #264 review feedback + feature parity + performance
mikeangstadt Jun 1, 2026
132c2d3
FEA: Hoist prepared statements, fix nav labels, reorder nav
mikeangstadt Jun 1, 2026
6d4b957
FEA: Fix query perf on large databases (2.7M events)
mikeangstadt Jun 1, 2026
cd526af
FEA: Fix rowid index crash + add manual chunk splitting
mikeangstadt Jun 1, 2026
bfd25a0
FEA: Add covering indexes for 2.8M event table + decouple dashboard load
mikeangstadt Jun 1, 2026
9272a7e
FEA: Consolidate nav — merge Analytics+Workflows into Agents, Activit…
mikeangstadt Jun 1, 2026
d623034
FEA-1497: Merge main + fix build (Phase 0)
thadeusb Jun 2, 2026
5342e2b
FEA-1497: Green the test suite after main-merge (Phase 0)
thadeusb Jun 2, 2026
3911766
FEA-1497: Re-merge main (#265, #266) to clear PR conflict (Phase 0)
thadeusb Jun 2, 2026
09e9df4
FEA-1497: Fix pre-existing lint errors blocking CI (Phase 0)
thadeusb Jun 2, 2026
617b9a7
FEA-1497: In-process hook listener + write engine (Phase 1 S1+S2)
thadeusb Jun 2, 2026
e9972e7
FEA-1497: Codex hook ingestion opt-in (Phase 1 S3)
thadeusb Jun 2, 2026
5165ecf
FEA-1497: Renderer typed IPC + session drill-down + live updates (Pha…
thadeusb Jun 2, 2026
1b6230d
FEA-1497: Atomic sidecar cutover + relay/recon repoint + boot migrati…
thadeusb Jun 2, 2026
a0a7b2b
FEA-1497: Address Phase 1 review findings (migration FK abort + harde…
thadeusb Jun 2, 2026
9f357c4
FEA-1497: Reference breaking-change ticket IDs (FEA-1500, FEA-1501) i…
thadeusb Jun 2, 2026
cd8e6d5
FEA-1497: Address PR #264 review findings (validation, packaging, typ…
thadeusb Jun 2, 2026
26259a7
FEA: consume published design-system package
mikeangstadt Jun 3, 2026
d589c41
FEA: avoid broken design-system root provider
mikeangstadt Jun 3, 2026
e366306
FEA: patch published design-system bundle in vite
mikeangstadt Jun 3, 2026
fd3c992
FEA: switch desktop to published design-system package
mikeangstadt Jun 3, 2026
0280eed
FEA: avoid analytics fetch on dashboard load
mikeangstadt Jun 3, 2026
846bb40
FEA: tighten sessions dashboard layout
mikeangstadt Jun 3, 2026
5c47688
FEA: defer to design-system renderer globals
mikeangstadt Jun 3, 2026
3a20dad
FEA: cache historical sessions and preserve views
mikeangstadt Jun 3, 2026
eb8157a
FEA: defer heavy analytics sections
mikeangstadt Jun 3, 2026
a876313
FEA-1503: In-process multi-harness collection + vendor removal
thadeusb Jun 3, 2026
ca3855c
Merge remote-tracking branch 'origin/feat/in-process-agent-database' …
thadeusb Jun 3, 2026
1da1146
FEA-1503: Fix CI 403 on @closedloop-ai/design-system via org-scoped a…
thadeusb Jun 3, 2026
6847025
FEA-1503: Revert CI app-token plumbing; grant-based package access
thadeusb Jun 3, 2026
b4ba906
FEA-1497: Address PR #264 review findings (DS pinning, Topbar labels,…
mikeangstadt Jun 4, 2026
f40b4d1
FEA-1497: Address remaining PR #264 review findings (settings parity,…
shafty023 Jun 4, 2026
143dc11
Merge remote-tracking branch 'origin/feat/in-process-agent-database' …
shafty023 Jun 4, 2026
0372f26
FEA-1504: Finalize gated agent dashboard
shafty023 Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 3 additions & 50 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,7 @@
# Third-Party Notices

This product bundles third-party open-source software pinned in
`apps/desktop/package.json`. Some upstream source is fetched by `pnpm` during
development/build, and the desktop build generates a runtime tree under
`apps/desktop/.generated/agent-monitor/` for packaging.

---

## Claude-Code-Agent-Monitor

- **Upstream:** https://github.com/hoangsonww/Claude-Code-Agent-Monitor
- **Pinned commit:** `840c518d7fa69231de049e41b893938228b67e40`
- **Imported via:** pnpm dependencies `agent-dashboard` and
`agent-dashboard-client`
- **Usage:** Bundled and run as a local `127.0.0.1` sidecar process by the
desktop app (the embedded "Claude Dashboard" tab). The desktop build applies
three local host patches while generating
`apps/desktop/.generated/agent-monitor/`: loopback-only bind, explicit
`CCAM_AUTO_INSTALL_HOOKS` gating, and a hook uninstall script.
- **License:** MIT — © 2026 Son Nguyen.

Bundled runtime dependencies remain pure JS. The generated sidecar runtime uses
Node's built-in `node:sqlite`; `better-sqlite3` is not used by the shipped
server, and the packaged desktop runtime strips the hoisted `better-sqlite3`
module from the staged app tree.

```
MIT License

Copyright (c) 2026 Son Nguyen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
`apps/desktop/package.json`.

---

Expand All @@ -57,9 +11,8 @@ SOFTWARE.
- **Used as:** documentation/algorithm reference for the OpenAI **Codex** CLI
rollout JSONL format — the cumulative→session token semantics and the
`turn_context.model` attribution rule (`docs/codex.md`). No CodexBar source
is bundled. Our own Codex ingestion modules
(`apps/desktop/scripts/agent-monitor-codex/codex-*.js`, copied into the
generated runtime tree at build time) were merely informed by it.
is bundled. Our own first-party Codex ingestion modules
(`apps/desktop/src/main/collectors/codex/`) were merely informed by it.
- **License:** MIT — © 2026 Peter Steinberger.

```
Expand Down
192 changes: 88 additions & 104 deletions apps/desktop/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,108 +136,92 @@ Typical log locations:

The 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.

## Agent Monitor Sidecar

The desktop app bundles the MIT-licensed `Claude-Code-Agent-Monitor`
(`agent-dashboard` + `agent-dashboard-client`, pinned in
`apps/desktop/package.json`) and runs a generated runtime tree as a managed
localhost **sidecar** for local Claude Code session/agent observability. It is
the single embedded observability tool. It powers the **Dashboard** and the
agent nav items (Sessions, Kanban, Activity Feed, etc.) in the desktop left
sidebar. The feature is gated by the persisted `agentMonitorEnabled` desktop
setting, which **defaults ON**; when disabled, the agent nav items are hidden
and only the Gateway section remains.

- **Process model:** `src/main/agent-monitor-sidecar.ts` spawns the generated
`server/index.js` from `apps/desktop/.generated/agent-monitor/` (packaged:
unpacked `extraResources/agent-monitor`) using the Electron binary as Node
(`ELECTRON_RUN_AS_NODE=1`, `process.execPath`) — a packaged app ships no
standalone `node`. Started fire-and-forget from `boot()` **only when
`agentMonitorEnabled` is true**, and still before the gateway-start try-block
so a gateway-start failure never prevents it from running and a sidecar
failure never blocks or fails app boot.
## Agent Monitor (in-process)

> **Status (FEA-1503):** the agent monitor is FULLY FIRST-PARTY and IN-PROCESS.
> The third-party agent-monitor vendor tool is GONE — no sidecar, no generated
> tree, no vendor dependency, no vendor-generated hook handler.
> `src/main/agent-monitor-listener.ts` (`AgentHookListener`) owns
> `127.0.0.1:4820` in the main process and writes through the `node:sqlite`
> repository (`src/main/database/`) via the hook lifecycle state machine
> (`database/lifecycle.ts`). The renderer is a first-party React app
> (`src/renderer/`) — there is NO iframe. The first-party collection layer
> (`src/main/collectors/`) imports historical sessions on boot and watches the
> live transcript files of all five agent CLIs, writing through the same DB. The
> cloud relay and cost-reconciliation worker read that DB through the shared
> connection.

The desktop app provides local Claude Code (and opt-in Codex) session/agent
observability. It powers the **Dashboard** and the agent nav items (Sessions,
Activity, Analytics, Workflows, Kanban) in the desktop left sidebar. The feature
is gated by the persisted `agentMonitorEnabled` desktop setting, which
**defaults ON**; when disabled, the agent nav items are hidden and only the
Gateway section remains.

- **Hook listener:** `src/main/agent-monitor-listener.ts` binds `127.0.0.1:4820`
in the main process and accepts the hook payload (`POST /api/hooks/event`,
`GET /api/health`). Each event is gated by the FEA-1407 sandbox check,
harness-stamped from `__provider`, and applied by the lifecycle state machine
in one `BEGIN IMMEDIATE` transaction. Started from `startAgentCapture()` (boot
+ the enable path) **only when `agentMonitorEnabled` is true**, before the
gateway-start try-block; a bind failure (EADDRINUSE) degrades to "no monitor"
rather than blocking boot.
- **Collection layer (`src/main/collectors/`):** `CollectorManager` runs a
best-effort boot bulk import and live file watchers for all five agent CLIs,
writing through the first-party `importSession` into the same in-process DB.
It is started/stopped alongside the listener (and stopped in `shutdown()`
BEFORE `agentDatabase.close()` so a late fs-watch import can't hit a closed
DB). Every parsed session is sandbox-gated (FEA-1407, fail-closed) before any
write. Import is idempotent via a per-(session, event_type) high-water-mark on
`created_at`. Watchers self-heal if a data dir doesn't exist at boot.
- **Fixed port (differs from the gateway):** `127.0.0.1:4820`
(`AGENT_MONITOR_PORT` in `src/shared/contracts.ts`), passed via
`DASHBOARD_PORT`. It MUST be fixed — Claude Code hooks bake a port at install
time and the hook handler POSTs to `127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820}`,
so 4820 (upstream's default) means hooks need zero per-hook env. 4820 is
outside `PORT_PROBE_ORDER`, so it never collides with the gateway.
- **Durable DB:** `DASHBOARD_DB_PATH` is set to
`app.getPath("userData")/agent-monitor/dashboard.db` (the packaged app dir is
read-only). Uses Node's built-in `node:sqlite`; the generated `server/db.js`
is patched to prefer `./compat-sqlite`, and staged packaging removes the
hoisted `better-sqlite3` module as a belt-and-suspenders guard.
- **UI:** embedded in the main window (`src/renderer/index.html`) as a plain
`<iframe>` pointed at the sidecar URL fetched via
`desktop:get-agent-monitor-url` (renderer polls until `ready`, then sets
`src` once). No separate window. The host left sidebar drives it: the
**Dashboard** + agent nav items each map to a sidecar route. The first load
bakes the route + `embed=1` into the iframe `src`; later agent-nav clicks
are a `postMessage` (`{ type: "closedloop:navigate", path }`) so there is no
reload. In embed mode the sidecar's own `Layout` drops its internal sidebar
(see `scripts/agent-monitor-embed/Layout.tsx`) so the host shell is the only
chrome. The agent nav items are hidden when the feature is disabled. The
embed depends on the renderer having **no CSP** — if a CSP is ever added it
must include `frame-src http://127.0.0.1:*`. Iframes in a `display:none`
panel collapse to 0px, so an explicit px height is set via JS *after* the
panel is `.active`, re-applied on `resize`.
- **Hooks are explicit opt-in (consent-bearing).** Upstream silently writes 8
hooks into `~/.claude/settings.json` on every startup — the generated
`server/index.js` gates that behind `CCAM_AUTO_INSTALL_HOOKS` (which the
sidecar sets to `"0"`).
The user enables/disables tracking via the toggle on the Agent Dashboard
view → `src/main/agent-monitor-hooks.ts` writes/removes the 8 hook entries. The
hook command runs the Electron binary as Node against a **userData copy** of
`hook-handler.js` (location-independent across app moves/updates), at the
fixed port 4820. Default is OFF; disabling fully removes the entries;
re-enabling is idempotent and self-heals a stale path (also repaired at boot
via `syncAgentMonitorHooksOnBoot()`). Disk state: a dedicated electron-store
(`agent-monitor-hooks`, key `enabled`).
- **Lifecycle:** health-checked readiness on `GET /api/health` (60s ready
timeout — first run synchronously imports legacy `~/.claude` sessions; ready
≠ import-complete, the iframe populates progressively), crash-restart with
exponential backoff (hard cap; a fixed-port `EADDRINUSE` degrades to "no
monitor", never blocks boot or Claude Code), process-group SIGTERM→SIGKILL
stop wired into `runShutdownSequence` (`agentMonitor.stop`, before
`server.stop`).
- **Security model (by design):** the sidecar reads `~/.claude` **directly**,
*outside* the gateway `isPathAllowed` sandbox. Acceptable and intentional:
bound to `127.0.0.1` only (patched at build time; verified the LAN interface
is refused), the user's own local data, no cloud egress, no auth (consistent
with the unauthenticated `/health` precedent). Hooks only mutate global
Claude config on explicit user opt-in and are fully reversible.
- **Build/packaging:** `scripts/build-agent-monitor.mjs` (run via
`pnpm build:agent-monitor`, chained into `build`) resolves the pnpm-managed
upstream packages, builds the client with Vite, generates
`apps/desktop/.generated/agent-monitor/`, applies the ClosedLoop host
patches (loopback bind, `CCAM_AUTO_INSTALL_HOOKS` gate, uninstall script,
`compat-sqlite` bootstrap), and hard-gates the build on the generated
`compat-sqlite.js` working under Electron-as-Node. Shipped via
`electron-builder.yml` `extraResources` (unpacked, outside the asar)
preserving the `server/` ↔ `client/dist/` relative layout.
- **Multi-harness support (5 agent tools):** the same dashboard ingests
sessions from **Claude Code** (via hooks), **OpenAI Codex** (rollout JSONL
under `~/.codex/sessions/`), **Cursor** (agent transcripts under
`~/.cursor/projects/`), **GitHub Copilot** (chat JSON under VS Code
`workspaceStorage/` + CLI JSONL under `~/.copilot/session-state/`), and
**OpenCode** (per-message JSON under `~/.local/share/opencode/storage/`).
All non-Claude tools have **no hook system** — their data comes from
file-based importing/watching. Proven, architecture-independent modules
live in-repo at `apps/desktop/scripts/agent-monitor-{codex,cursor,copilot,
opencode}/{tool}-{home,parser,import,watcher}.js` and are copied into the
generated `server/lib/` at materialize time. Each parser emits the same
normalized shape as the upstream Claude importer so the shared
`importSession()` renders all harnesses through the unchanged UI; all
watchers self-heal if data directories don't exist at boot (no app restart
needed for a first-ever session with any tool). All non-Claude paths are
best-effort and never block boot or the Claude path. The build hard-gates
all watcher/import wiring so a future upstream bump can't silently drop
any harness. The user-facing nav/tray label is **"Agent Dashboard"**
(internal ids/IPC channels unchanged). Environment variable overrides:
`$CODEX_HOME`, `$CURSOR_HOME`, `$COPILOT_HOME`, `$OPENCODE_DATA_DIR`.
- **Update procedure:** bump the git dependency commit(s) in
`apps/desktop/package.json`, regenerate the lockfile, and rerun
`pnpm -C apps/desktop build:agent-monitor`. Any change here requires the
`apps/desktop/package.json` version bump (CI-enforced) and a clean-machine
packaged-DMG smoke test (the highest-risk path: `node:sqlite` from the
asar-external, universal-merged binary).
(`AGENT_MONITOR_PORT` in `src/shared/contracts.ts`). It MUST be fixed — the
hook handler POSTs to `127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820}`, baked into
`~/.claude/settings.json` at install time, so 4820 means hooks need zero
per-hook env. 4820 is outside `PORT_PROBE_ORDER`, so it never collides with
the gateway. (FEA-1500 tracks migrating this transport later.)
- **Durable DB:** `app.getPath("userData")/agent-dashboard.sqlite` (schema in
`src/main/database/schema.ts`), Node's built-in `node:sqlite`. Persisted
collector caches live under `<userData>/agent-monitor/`.
- **UI:** a first-party React app in the main window (`src/renderer/`) — NO
iframe. The left sidebar drives the **Dashboard** + agent nav items; live
updates arrive via the `desktop:db:changed` IPC push after each write.
- **Hooks are explicit opt-in (consent-bearing).** The user enables/disables
tracking via the toggle → `src/main/agent-monitor-hooks.ts` writes/removes the
hook entries in `~/.claude/settings.json` (and, opt-in, `~/.codex/hooks.json`).
The hook command runs the Electron binary as Node against a **userData copy**
of the first-party `hook-handler.js` (location-independent across app
moves/updates), at the fixed port 4820. Default is OFF; disabling fully removes
the entries; re-enabling is idempotent and self-heals a stale path (also
repaired at boot via `syncAgentMonitorHooksOnBoot()`). When hooks are ON they
own live Claude capture, so the Claude **file watcher** is gated off (boot
historical import still runs); the four non-Claude tools always file-watch.
Disk state: a dedicated electron-store (`agent-monitor-hooks`, key `enabled`).
- **First-party hook handlers:** `resources/hooks/{hook-handler,codex-hook-handler}.js`
— zero-dependency CommonJS scripts that POST `{ hook_type, data }` to
`:4820`. Shipped via `electron-builder.yml` `extraResources` (`to: hooks`,
unpacked) and resolved by `agent-monitor-path.ts`. No build step, no generated
tree.
- **Security model (by design):** the collectors + listener read the agent-CLI
home dirs (`~/.claude`, `~/.codex`, …) **directly**, outside the gateway
`isPathAllowed` sandbox, but every captured session is dropped unless its
`cwd` is inside the FEA-1407 sandbox base directory (fail-closed). The listener
is bound to `127.0.0.1` only; no cloud egress from collectors. Hooks only
mutate global Claude/Codex config on explicit user opt-in and are reversible.
- **Multi-harness support (5 agent tools):** ingests sessions from **Claude
Code** (hooks live + file historical), **OpenAI Codex** (rollout JSONL under
`~/.codex/sessions/`), **Cursor** (agent transcripts under `~/.cursor/projects/`),
**GitHub Copilot** (chat JSON under VS Code `workspaceStorage/` + CLI JSONL
under `~/.copilot/session-state/`), and **OpenCode** (the `opencode.db` SQLite
store under `~/.local/share/opencode/`). The four non-Claude tools have **no
hook system** — file import/watching is the only capture path. Each
harness's parser (`src/main/collectors/<tool>/`) emits the same normalized
session shape so `importSession` renders all harnesses through the unchanged
UI. Environment variable overrides: `$CODEX_HOME`, `$CURSOR_HOME`,
`$COPILOT_HOME`, `$OPENCODE_DATA_DIR` (Claude uses `$CLAUDE_HOME`).
- **Build/packaging:** the main process is plain `tsc` → `dist/`; there is no
agent-monitor generative build step. Packaging ships `dist/` (via
`stage-packaging-app.mjs`) plus the unpacked `resources/hooks` handlers. Any
change to `apps/desktop/` requires the `package.json` version bump
(CI-enforced) and a clean-machine packaged-DMG smoke test (the highest-risk
path: `node:sqlite` from the asar-external, universal-merged binary).
46 changes: 0 additions & 46 deletions apps/desktop/ci/README.md

This file was deleted.

Loading
Loading