Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Commit 0372f26

Browse files
committed
FEA-1504: Finalize gated agent dashboard
Keep the legacy monitor as the default boot path, put the design-system dashboard runtime and storage behind the explicit Labs flag, and package the embedded agent dashboard assets and tests. Validation: pnpm -r prebuild; pnpm -C apps/desktop lint; pnpm typecheck; pnpm -C apps/desktop assert:design-system-boundary; pnpm -C apps/desktop test:boot:design-system-off; pnpm -C apps/desktop measure:agent-dashboard-storage; pnpm test; pnpm -C apps/desktop build.
1 parent 143dc11 commit 0372f26

221 files changed

Lines changed: 53970 additions & 673 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,14 @@ jobs:
4545
- name: Typecheck
4646
run: pnpm typecheck
4747

48+
- name: Assert design-system boundary
49+
run: pnpm -C apps/desktop assert:design-system-boundary
50+
51+
- name: Assert design-system flag-off boot
52+
run: pnpm -C apps/desktop test:boot:design-system-off
53+
54+
- name: Measure Agent Dashboard storage
55+
run: pnpm -C apps/desktop measure:agent-dashboard-storage
56+
4857
- name: Run tests
4958
run: pnpm test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ apps/desktop/.generated/
2626
apps/desktop/test-results/
2727
apps/desktop/playwright-report/
2828

29+
# Desktop-local workflow/debug artifacts
30+
apps/desktop/.closedloop-ai/
31+
2932
# UI Numbers Audit generated reports (FEA-1415 / PLN-738)
3033
# These embed a timestamp; regenerate locally via `pnpm --filter desktop audit:report`
3134
# or `node apps/desktop/test-e2e/agent-monitor/inventory/scan-tiles.mjs`.

THIRD_PARTY_NOTICES.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,51 @@
11
# Third-Party Notices
22

33
This product bundles third-party open-source software pinned in
4-
`apps/desktop/package.json`.
4+
`apps/desktop/package.json`. Some upstream source is fetched by `pnpm` during
5+
development/build, and the desktop build generates a runtime tree under
6+
`apps/desktop/.generated/agent-monitor/` for packaging.
7+
8+
---
9+
10+
## Claude-Code-Agent-Monitor
11+
12+
- **Upstream:** https://github.com/hoangsonww/Claude-Code-Agent-Monitor
13+
- **Pinned commit:** `840c518d7fa69231de049e41b893938228b67e40`
14+
- **Imported via:** pnpm dependencies `agent-dashboard` and
15+
`agent-dashboard-client`
16+
- **Usage:** Bundled and run as the default local `127.0.0.1` legacy sidecar
17+
dashboard. The desktop build applies local host patches while generating
18+
`apps/desktop/.generated/agent-monitor/`.
19+
- **License:** MIT — © 2026 Son Nguyen.
20+
21+
Bundled runtime dependencies remain pure JS. The generated sidecar runtime uses
22+
Node's built-in `node:sqlite`; `better-sqlite3` is not used by the shipped
23+
server, and the packaged desktop runtime strips the hoisted `better-sqlite3`
24+
module from the staged app tree.
25+
26+
```
27+
MIT License
28+
29+
Copyright (c) 2026 Son Nguyen
30+
31+
Permission is hereby granted, free of charge, to any person obtaining a copy
32+
of this software and associated documentation files (the "Software"), to deal
33+
in the Software without restriction, including without limitation the rights
34+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35+
copies of the Software, and to permit persons to whom the Software is
36+
furnished to do so, subject to the following conditions:
37+
38+
The above copyright notice and this permission notice shall be included in all
39+
copies or substantial portions of the Software.
40+
41+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47+
SOFTWARE.
48+
```
549

650
---
751

apps/desktop/CLAUDE.md

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -136,20 +136,17 @@ Typical log locations:
136136

137137
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.
138138

139-
## Agent Monitor (in-process)
140-
141-
> **Status (FEA-1503):** the agent monitor is FULLY FIRST-PARTY and IN-PROCESS.
142-
> The third-party agent-monitor vendor tool is GONE — no sidecar, no generated
143-
> tree, no vendor dependency, no vendor-generated hook handler.
144-
> `src/main/agent-monitor-listener.ts` (`AgentHookListener`) owns
145-
> `127.0.0.1:4820` in the main process and writes through the `node:sqlite`
146-
> repository (`src/main/database/`) via the hook lifecycle state machine
147-
> (`database/lifecycle.ts`). The renderer is a first-party React app
148-
> (`src/renderer/`) — there is NO iframe. The first-party collection layer
149-
> (`src/main/collectors/`) imports historical sessions on boot and watches the
150-
> live transcript files of all five agent CLIs, writing through the same DB. The
151-
> cloud relay and cost-reconciliation worker read that DB through the shared
152-
> connection.
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.
153150
154151
The desktop app provides local Claude Code (and opt-in Codex) session/agent
155152
observability. It powers the **Dashboard** and the agent nav items (Sessions,
@@ -158,22 +155,27 @@ is gated by the persisted `agentMonitorEnabled` desktop setting, which
158155
**defaults ON**; when disabled, the agent nav items are hidden and only the
159156
Gateway section remains.
160157

161-
- **Hook listener:** `src/main/agent-monitor-listener.ts` binds `127.0.0.1:4820`
162-
in the main process and accepts the hook payload (`POST /api/hooks/event`,
163-
`GET /api/health`). Each event is gated by the FEA-1407 sandbox check,
164-
harness-stamped from `__provider`, and applied by the lifecycle state machine
165-
in one `BEGIN IMMEDIATE` transaction. Started from `startAgentCapture()` (boot
166-
+ the enable path) **only when `agentMonitorEnabled` is true**, before the
167-
gateway-start try-block; a bind failure (EADDRINUSE) degrades to "no monitor"
168-
rather than blocking boot.
169-
- **Collection layer (`src/main/collectors/`):** `CollectorManager` runs a
170-
best-effort boot bulk import and live file watchers for all five agent CLIs,
171-
writing through the first-party `importSession` into the same in-process DB.
172-
It is started/stopped alongside the listener (and stopped in `shutdown()`
173-
BEFORE `agentDatabase.close()` so a late fs-watch import can't hit a closed
174-
DB). Every parsed session is sandbox-gated (FEA-1407, fail-closed) before any
175-
write. Import is idempotent via a per-(session, event_type) high-water-mark on
176-
`created_at`. Watchers self-heal if a data dir doesn't exist at boot.
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.
177179
- **Fixed port (differs from the gateway):** `127.0.0.1:4820`
178180
(`AGENT_MONITOR_PORT` in `src/shared/contracts.ts`). It MUST be fixed — the
179181
hook handler POSTs to `127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820}`, baked into

apps/desktop/ci/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Desktop CI workflow staging
2+
3+
## `audit-gate.yml` — UI Numbers Audit Gate (FEA-1437 Phase 6)
4+
5+
This is the Phase 6 CI gate for the UI Numbers Audit. It lives here, **not** in
6+
`.github/workflows/`, only because the automation token that opened this PR
7+
lacks the GitHub `workflow` OAuth scope (pushing a file under
8+
`.github/workflows/` is rejected without it).
9+
10+
**To activate** (one step, needs a token/UI with `workflow` scope):
11+
12+
```bash
13+
git mv apps/desktop/ci/audit-gate.yml .github/workflows/audit-gate.yml
14+
git commit -m "ci: activate UI Numbers Audit gate"
15+
```
16+
17+
Until moved, the gate does not run automatically, but every check it performs is
18+
runnable locally and is identical to the existing `test:audit` suite:
19+
20+
```bash
21+
pnpm -C apps/desktop audit:coverage # static coverage gate (fast, no build)
22+
pnpm -C apps/desktop test:audit # full node-side audit
23+
pnpm -C apps/desktop test:audit:ui # headless Playwright tile audit
24+
```
25+
26+
The workflow has two jobs:
27+
28+
- **coverage** — runs `audit:coverage`: regenerates the tile scan + coverage
29+
classification, asserts every scanner detection is classified (no
30+
`needs_review`), asserts every harness parser has a `*-parser.contract.test.mjs`,
31+
and posts the `by_status` breakdown to the job summary. This is the merge gate
32+
that fires when a new route / tile / parser lands without manifest coverage.
33+
- **ui-audit** — builds the agent-monitor sidecar and runs the headless
34+
Playwright tile audits (rendered DOM == oracle).
35+
36+
Real-Electron and visual-regression suites are intentionally excluded (they run
37+
elsewhere as non-blocking, flaky-by-nature per PLN-760).
38+
39+
### Branch-protection guidance (read before marking required)
40+
41+
Mark **only the `coverage` job** as a required status check. Leave `ui-audit`
42+
optional (it's heavier — builds the sidecar + Playwright — and shouldn't block on
43+
infra flake). The workflow has **no `paths:` filter** by design: a required check
44+
combined with a paths filter deadlocks any PR that doesn't touch `apps/desktop/**`
45+
(the job never triggers, so the required status sits pending forever). Triggering
46+
on every PR keeps the required `coverage` check from hanging; the job is fast.

apps/desktop/ci/audit-gate.yml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
name: UI Numbers Audit Gate
2+
3+
# FEA-1437 Phase 6 — gate PRs on the UI Numbers Audit. Two jobs:
4+
# coverage — fast, static: every scanner detection is classified (no
5+
# needs_review), every harness parser has a contract test. This
6+
# is the merge gate that fires when a new route / tile / parser
7+
# lands without manifest coverage.
8+
# ui-audit — heavier: builds the agent-monitor sidecar and runs the headless
9+
# Playwright tile audits (rendered DOM == oracle).
10+
# Real-Electron + visual-regression suites are intentionally NOT here; they run
11+
# elsewhere as non-blocking (flaky-by-nature) per PLN-760.
12+
#
13+
# NO `paths:` filter on purpose. The `coverage` job is intended to be a REQUIRED
14+
# status check, and "required check + paths filter" is a classic deadlock: a PR
15+
# that doesn't touch apps/desktop/** never triggers the job, so the required
16+
# status sits pending forever and the PR can't merge. Triggering on every PR
17+
# keeps the required check from hanging. The `coverage` job is fast; only mark
18+
# `coverage` as required (leave `ui-audit` optional — see apps/desktop/ci/README.md).
19+
20+
on:
21+
pull_request:
22+
types: [opened, synchronize, reopened]
23+
push:
24+
branches: [main]
25+
26+
concurrency:
27+
group: audit-gate-${{ github.event.pull_request.number || github.ref }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
coverage:
32+
name: Coverage gate (static)
33+
runs-on: ubuntu-latest
34+
timeout-minutes: 10
35+
permissions:
36+
contents: read
37+
packages: read
38+
steps:
39+
- uses: actions/checkout@v4
40+
- uses: pnpm/action-setup@v4
41+
- uses: actions/setup-node@v4
42+
with:
43+
node-version: 22
44+
cache: pnpm
45+
- name: Install dependencies
46+
run: pnpm install --frozen-lockfile
47+
env:
48+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49+
- name: Generate build info
50+
run: pnpm -r prebuild
51+
- name: Audit coverage gate
52+
run: pnpm -C apps/desktop audit:coverage
53+
- name: Coverage summary -> job summary
54+
if: always()
55+
# Reporting only — never let a summary glitch fail the gate job.
56+
continue-on-error: true
57+
run: |
58+
COV=apps/desktop/test-e2e/agent-monitor/inventory/coverage.json
59+
if [ -f "$COV" ]; then
60+
{
61+
echo "### UI Numbers Audit — coverage"
62+
echo ""
63+
echo "| status | count |"
64+
echo "|---|---|"
65+
# Resolve to an absolute path: require() treats a bare relative
66+
# string as a node_modules specifier and would throw.
67+
node -e '
68+
const c = require(require("path").resolve(process.env.COV));
69+
for (const [k, v] of Object.entries(c.by_status)) console.log(`| ${k} | ${v} |`);
70+
console.log(`| **total** | **${c.total_detections}** |`);
71+
'
72+
} >> "$GITHUB_STEP_SUMMARY"
73+
fi
74+
env:
75+
COV: apps/desktop/test-e2e/agent-monitor/inventory/coverage.json
76+
77+
ui-audit:
78+
name: UI tile audit (headless Playwright)
79+
runs-on: ubuntu-latest
80+
timeout-minutes: 25
81+
permissions:
82+
contents: read
83+
packages: read
84+
steps:
85+
- uses: actions/checkout@v4
86+
- uses: pnpm/action-setup@v4
87+
- uses: actions/setup-node@v4
88+
with:
89+
node-version: 22
90+
cache: pnpm
91+
- name: Install dependencies
92+
run: pnpm install --frozen-lockfile
93+
env:
94+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95+
- name: Generate build info
96+
run: pnpm -r prebuild
97+
- name: Install Playwright Chromium
98+
run: pnpm -C apps/desktop exec playwright install --with-deps chromium
99+
- name: Run headless tile audit
100+
run: pnpm -C apps/desktop test:audit:ui
101+
- name: Upload Playwright report on failure
102+
if: failure()
103+
uses: actions/upload-artifact@v4
104+
with:
105+
name: playwright-audit-report
106+
path: apps/desktop/test-results/
107+
retention-days: 7
108+
if-no-files-found: ignore

apps/desktop/electron-builder.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ extraResources:
1414
to: trayIconTemplate.png
1515
- from: resources/trayIconTemplate@2x.png
1616
to: trayIconTemplate@2x.png
17+
# Generated Claude-Code-Agent-Monitor runtime tree, shipped unpacked
18+
# (outside the asar) so the spawned Node server, the built client, and the
19+
# hook scripts resolve as real files. Built by scripts/build-agent-monitor.mjs
20+
# before packaging (chained into `build`). `client/dist/**/*` (NOT
21+
# `client/**/*`) — the server resolves ../client/dist relative to server/, so
22+
# the server/ <-> client/dist/ layout must be preserved.
23+
- from: .generated/agent-monitor
24+
to: agent-monitor
25+
filter:
26+
- server/**/*
27+
- client/dist/**/*
28+
- scripts/**/*
29+
- package.json
30+
- LICENSE
1731
# First-party agent-monitor hook handlers (FEA-1503), shipped unpacked outside
1832
# the asar so they resolve as real files. agent-monitor-hooks.ts copies them
1933
# into userData at install time; the installed hook command runs them via the

apps/desktop/package.json

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,44 @@
88
"main": "dist/main/index.js",
99
"scripts": {
1010
"dev": "pnpm build && ELECTRON_BIN=$(bash scripts/patch-electron-plist.sh | tail -1) && \"$ELECTRON_BIN\" .",
11-
"start": "ELECTRON_BIN=$(bash scripts/patch-electron-plist.sh | tail -1) && \"$ELECTRON_BIN\" .",
12-
"clean:dist": "node -e \"require('fs').rmSync('dist/main',{recursive:true,force:true});require('fs').rmSync('dist/server',{recursive:true,force:true})\"",
11+
"start": "pnpm build:agent-monitor && ELECTRON_BIN=$(bash scripts/patch-electron-plist.sh | tail -1) && \"$ELECTRON_BIN\" .",
12+
"clean:dist": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
1313
"clean:package": "node -e \"require('fs').rmSync('dist-dmg',{recursive:true,force:true})\"",
1414
"prebuild": "node -e \"const{execSync:e}=require('child_process'),{writeFileSync:w}=require('fs');const h=e('git rev-parse HEAD').toString().trim();w('src/shared/build-info.ts','// AUTO-GENERATED — do not edit\\nexport const BUILD_COMMIT_HASH = \\\"'+h+'\\\";\\n');\"",
15-
"build": "pnpm clean:dist && pnpm prebuild && tsc -p tsconfig.json && pnpm build:renderer",
15+
"build": "pnpm clean:dist && pnpm prebuild && tsc -p tsconfig.json && pnpm build:renderer && pnpm build:agent-monitor",
1616
"build:renderer": "vite build --config vite.renderer.config.ts",
17+
"build:agent-monitor": "node scripts/build-agent-monitor.mjs",
1718
"dashboard:reset": "node scripts/reset-dashboard-db.mjs",
19+
"dashboard:reset-packs": "node scripts/reset-dashboard-db.mjs --packs-only",
1820
"stage:package": "node scripts/stage-packaging-app.mjs",
1921
"typecheck": "tsc -p tsconfig.json --noEmit && pnpm typecheck:renderer",
2022
"typecheck:renderer": "tsc -p tsconfig.renderer.json --noEmit",
2123
"lint": "eslint src/",
22-
"test": "tsx --test test/*.test.ts",
24+
"assert:design-system-boundary": "tsx --test test/agent-dashboard-boundary.test.ts",
25+
"test:boot:design-system-off": "node --import tsx scripts/assert-design-system-boot-off.mjs",
26+
"measure:agent-dashboard-storage": "node scripts/measure-agent-dashboard-storage.mjs",
27+
"test": "tsx --test --test-concurrency=1 test/*.test.ts && node --test \"scripts/agent-monitor-packs/__tests__/*.test.js\" \"scripts/agent-monitor-pull-requests/__tests__/*.test.js\"",
28+
"pretest:contract": "pnpm build:agent-monitor",
29+
"test:contract": "node --test \"test-e2e/agent-monitor/specs/api-contract/*.test.mjs\"",
30+
"pretest:e2e": "pnpm build:agent-monitor",
31+
"test:e2e": "playwright test --config test-e2e/agent-monitor/playwright.config.ts",
32+
"pretest:audit": "pnpm build:agent-monitor && pnpm audit:scan && pnpm audit:classify",
33+
"audit:scan": "node test-e2e/agent-monitor/inventory/scan-tiles.mjs",
34+
"audit:classify": "node test-e2e/agent-monitor/inventory/coverage-classifier.mjs",
35+
"audit:coverage": "bash scripts/check-audit-coverage.sh",
36+
"test:audit": "node --test \"test-e2e/agent-monitor/specs/audit/*.test.mjs\"",
37+
"pretest:audit:ui": "pnpm build:agent-monitor",
38+
"test:audit:ui": "playwright test --config test-e2e/agent-monitor/playwright.audit.config.ts",
39+
"preaudit:report": "pnpm build:agent-monitor",
40+
"audit:report": "node test-e2e/agent-monitor/inventory/run-report.mjs",
2341
"package": "pnpm clean:package && pnpm build && pnpm stage:package && node scripts/run-electron-builder.mjs",
2442
"release": "pnpm clean:package && pnpm build && pnpm stage:package && node scripts/run-electron-builder.mjs --publish always"
2543
},
2644
"dependencies": {
2745
"@closedloop-ai/design-system": "0.1.1-dev.26892521643.1",
2846
"@closedloop-ai/loops-api": ">=0.3.1",
2947
"@pydantic/genai-prices": "0.0.62",
48+
"agent-dashboard": "github:hoangsonww/Claude-Code-Agent-Monitor#840c518d7fa69231de049e41b893938228b67e40",
3049
"busboy": "^1.6.0",
3150
"electron-log": "^5.4.3",
3251
"electron-store": "^8.2.0",
@@ -52,10 +71,14 @@
5271
"@typescript-eslint/eslint-plugin": "^8.57.1",
5372
"@typescript-eslint/parser": "^8.57.1",
5473
"@vitejs/plugin-react": "4.3.4",
74+
"agent-dashboard-client": "github:hoangsonww/Claude-Code-Agent-Monitor#840c518d7fa69231de049e41b893938228b67e40&path:/client",
75+
"autoprefixer": "10.4.20",
5576
"electron": "^35.0.2",
5677
"electron-builder": "^26.8.1",
5778
"eslint": "^10.0.3",
79+
"postcss": "8.5.1",
5880
"tailwindcss": "4.3.0",
81+
"tailwindcss-legacy": "npm:tailwindcss@3.4.17",
5982
"tsx": "^4.19.3",
6083
"tw-animate-css": "^1.4.0",
6184
"typescript": "^5.8.2",

0 commit comments

Comments
 (0)