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

Commit a6427ce

Browse files
committed
FEA: Replace agent-monitor sidecar with in-process SQLite database + React renderer
- Replace agent-dashboard sidecar (external process) with in-process node:sqlite database - Create functional repository layer matching symphony-alpha patterns (sessions, agents, events, dashboard stores) - Add Vite + React 19 renderer with DesignSystemProvider shell and DS components - Add hook ingestion endpoint (POST /api/hooks/event) writing directly to local SQLite - Extend preload bridge with typed db IPC channels (db.getSessions, db.getAgents, db.getEvents, db.getDashboardSummary) - Update window.ts to load Vite-built renderer (dev server in dev, file in prod) - Vendor @closedloop-ai/design-system as file: dep with subpath component exports - Preserve legacy HTML renderer as legacy.html for strangler-fig migration via iframe - Add DashboardPage using DS Card/Badge/Button components with real-time summary queries Testing: pnpm build:renderer (Vite) and npx tsc (main process) both compile clean; all DB-layer errors resolved; pre-existing tsc errors (node-pty, ws, react) unchanged. Risks: Sidecar not yet disabled — this adds parallel in-process DB alongside existing agent-monitor; legacy HTML still works via iframe fallback.
1 parent d297752 commit a6427ce

40 files changed

Lines changed: 11060 additions & 10867 deletions

apps/desktop/package.json

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
"type": "module",
88
"main": "dist/main/index.js",
99
"scripts": {
10-
"dev": "pnpm build && ELECTRON_BIN=$(bash scripts/patch-electron-plist.sh | tail -1) && \"$ELECTRON_BIN\" .",
10+
"dev": "pnpm build:renderer && pnpm build && ELECTRON_BIN=$(bash scripts/patch-electron-plist.sh | tail -1) && \"$ELECTRON_BIN\" .",
1111
"start": "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})\"",
12+
"clean:dist": "node -e \"require('fs').rmSync('dist/main',{recursive:true,force:true});require('fs').rmSync('dist/server',{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:agent-monitor",
15+
"build": "pnpm build:renderer && pnpm clean:dist && pnpm prebuild && tsc -p tsconfig.json",
1616
"build:agent-monitor": "node scripts/build-agent-monitor.mjs",
17+
"build:renderer": "node scripts/patch-vendor-ds-react.mjs && vite build --config vite.renderer.config.ts",
1718
"dashboard:reset": "node scripts/reset-dashboard-db.mjs",
1819
"dashboard:reset-packs": "node scripts/reset-dashboard-db.mjs --packs-only",
1920
"stage:package": "node scripts/stage-packaging-app.mjs",
@@ -24,29 +25,37 @@
2425
"release": "pnpm clean:package && pnpm build && pnpm stage:package && node scripts/run-electron-builder.mjs --publish always"
2526
},
2627
"dependencies": {
28+
"@closedloop-ai/design-system": "file:vendor/design-system",
2729
"@closedloop-ai/loops-api": "0.2.11",
2830
"agent-dashboard": "github:hoangsonww/Claude-Code-Agent-Monitor#840c518d7fa69231de049e41b893938228b67e40",
2931
"busboy": "^1.6.0",
3032
"electron-log": "^5.4.3",
3133
"electron-store": "^8.2.0",
3234
"electron-updater": "^6.8.3",
3335
"glob": "^11.0.1",
36+
"next-themes": "^0.4.6",
37+
"react": "^19.2.6",
38+
"react-day-picker": "^9.14.0",
39+
"react-dom": "^19.2.6",
40+
"react-hook-form": "^7.76.1",
41+
"react-resizable-panels": "^3.0.6",
3442
"socket.io-client": "^4.8.1",
3543
"zod": "^4.0.0"
3644
},
3745
"devDependencies": {
38-
"@vitejs/plugin-react": "4.3.4",
46+
"@tailwindcss/postcss": "^4.3.0",
3947
"@types/busboy": "^1.5.4",
4048
"@types/node": "^22.13.8",
4149
"@typescript-eslint/eslint-plugin": "^8.57.1",
4250
"@typescript-eslint/parser": "^8.57.1",
51+
"@vitejs/plugin-react": "4.3.4",
4352
"agent-dashboard-client": "github:hoangsonww/Claude-Code-Agent-Monitor#840c518d7fa69231de049e41b893938228b67e40&path:/client",
4453
"autoprefixer": "10.4.20",
4554
"electron": "^35.0.2",
4655
"electron-builder": "^26.8.1",
4756
"eslint": "^10.0.3",
48-
"postcss": "8.5.1",
49-
"tailwindcss": "3.4.17",
57+
"postcss": "8.5.15",
58+
"tailwindcss": "4.3.0",
5059
"tsx": "^4.19.3",
5160
"typescript": "^5.8.2",
5261
"typescript-eslint": "^8.57.1",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
plugins: {
3+
"@tailwindcss/postcss": {},
4+
},
5+
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Patches vendored @closedloop-ai/design-system dist files that reference
2+
// `React` as a bare global (missing/incorrect import/require statements).
3+
// Run after pnpm install or by build:renderer.
4+
5+
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
6+
import { join, extname } from "node:path";
7+
8+
const VENDOR_DIST = new URL(
9+
"../vendor/design-system/dist",
10+
import.meta.url,
11+
).pathname;
12+
13+
const REACT_CJS_LINE = 'var React = require("react");\n';
14+
const REACT_ESM_LINE = 'import React from "react";\n';
15+
16+
function hasScopeImport(code) {
17+
return (
18+
/\bvar\s+React\b\s*=/.test(code) ||
19+
/import\s+(?:\*\s+as\s+)?React\s+from\s/.test(code) ||
20+
/\bimport\s+\{[^}]*\bReact\b[^}]*\}\s+from\s/.test(code)
21+
);
22+
}
23+
24+
function walk(dir) {
25+
const entries = readdirSync(dir);
26+
for (const entry of entries) {
27+
const full = join(dir, entry);
28+
if (statSync(full).isDirectory()) {
29+
walk(full);
30+
} else if (entry.endsWith(".js") || entry.endsWith(".cjs") || entry.endsWith(".mjs")) {
31+
patch(full);
32+
}
33+
}
34+
}
35+
36+
function patch(file) {
37+
let code = readFileSync(file, "utf-8");
38+
const ext = extname(file);
39+
40+
// --- Step 1: Deduplicate stale patches from earlier tool runs ---
41+
// If our patch line is present AND there's already a proper React import,
42+
// remove the stale patch line.
43+
const lines = code.split("\n");
44+
let changed = false;
45+
46+
// For ESM: check if line 1 is our patch and another React import exists
47+
if (
48+
ext === ".mjs" &&
49+
(lines[0] === 'import React from "react"' || lines[0] === 'import React from "react";') &&
50+
hasScopeImport(code.slice(code.indexOf("\n") + 1))
51+
) {
52+
code = lines.slice(1).join("\n");
53+
changed = true;
54+
}
55+
56+
// For CJS: check if line 2 (after "use strict") is our patch
57+
if (
58+
ext !== ".mjs" &&
59+
lines[0] === '"use strict"' &&
60+
(lines[1] === 'var React = require("react")' || lines[1] === 'var React = require("react");') &&
61+
hasScopeImport(code.slice(code.indexOf("\n", code.indexOf("\n") + 1) + 1))
62+
) {
63+
code = '"use strict";\n' + lines.slice(2).join("\n");
64+
changed = true;
65+
}
66+
67+
// --- Step 2: Re-check if already patched (dedup may have just cleaned it) ---
68+
if (code.includes(REACT_ESM_LINE) || code.includes(REACT_CJS_LINE)) {
69+
if (changed) {
70+
writeFileSync(file, code);
71+
console.log(` dedup: ${file.replace(VENDOR_DIST, "")}`);
72+
}
73+
return;
74+
}
75+
76+
// If dedup removed our patch but the file already has its own React import, we're done.
77+
if (changed && hasScopeImport(code)) {
78+
writeFileSync(file, code);
79+
console.log(` dedup: ${file.replace(VENDOR_DIST, "")}`);
80+
return;
81+
}
82+
83+
// --- Step 3: Check if file needs patching ---
84+
if (!/\bReact\s*[\.\(]/.test(code)) {
85+
return;
86+
}
87+
88+
// Skip if React is already properly in scope
89+
if (hasScopeImport(code)) {
90+
return;
91+
}
92+
93+
const line = ext === ".mjs" ? REACT_ESM_LINE : REACT_CJS_LINE;
94+
if (code.startsWith('"use strict";\n')) {
95+
code = code.replace('"use strict";\n', '"use strict";\n' + line);
96+
} else {
97+
code = line + code;
98+
}
99+
writeFileSync(file, code);
100+
console.log(` patched: ${file.replace(VENDOR_DIST, "")}`);
101+
}
102+
103+
walk(VENDOR_DIST);

apps/desktop/src/main/app.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { SettingsStore, type SavedConfigManagedPatch } from "./settings-store.js
6565
import { DesktopTray } from "./tray.js";
6666
import { DesktopWindow } from "./window.js";
6767
import { AgentMonitorSidecar } from "./agent-monitor-sidecar.js";
68+
import { openAgentDatabase } from "./database/index.js";
6869
import { AgentSessionSyncService } from "./agent-session-sync-service.js";
6970
import {
7071
isAgentMonitorHooksEnabled,
@@ -196,6 +197,7 @@ export class DesktopApplication {
196197
private readonly cloudSocket: CloudSocketService;
197198
private readonly commandExecutor: CloudCommandExecutor;
198199
private readonly agentMonitor: AgentMonitorSidecar;
200+
private readonly agentDatabase: ReturnType<typeof openAgentDatabase>;
199201
private readonly agentSessionSync: AgentSessionSyncService;
200202
private readonly activityLog: ActivityLogStore;
201203
private readonly approvalStore: ApprovalStore;
@@ -284,6 +286,9 @@ export class DesktopApplication {
284286
this.tray = new DesktopTray();
285287
this.desktopWindow = new DesktopWindow();
286288
this.agentMonitor = new AgentMonitorSidecar();
289+
this.agentDatabase = openAgentDatabase(
290+
path.join(app.getPath("userData"), "agent-dashboard.sqlite"),
291+
);
287292
this.activityLog = new ActivityLogStore();
288293
this.jobStore = new JobStore();
289294
this.approvalStore = new ApprovalStore({
@@ -3190,6 +3195,59 @@ export class DesktopApplication {
31903195
this.restartCloudSocket();
31913196
return appliedConfig;
31923197
});
3198+
3199+
ipcMain.handle("desktop:db:get-sessions", () => {
3200+
return this.agentDatabase.sessions.getAll();
3201+
});
3202+
3203+
ipcMain.handle("desktop:db:get-session", (_event, id: string) => {
3204+
return this.agentDatabase.sessions.getById(id);
3205+
});
3206+
3207+
ipcMain.handle("desktop:db:get-agents", (_event, sessionId: string) => {
3208+
return this.agentDatabase.agents.getBySession(sessionId);
3209+
});
3210+
3211+
ipcMain.handle(
3212+
"desktop:db:get-events",
3213+
(_event, sessionId: string, agentId?: string) => {
3214+
if (agentId) {
3215+
return this.agentDatabase.events.getBySessionAndAgent(
3216+
sessionId,
3217+
agentId,
3218+
);
3219+
}
3220+
return this.agentDatabase.events.getBySession(sessionId);
3221+
},
3222+
);
3223+
3224+
ipcMain.handle("desktop:db:get-dashboard-summary", () => {
3225+
return this.agentDatabase.getSummary();
3226+
});
3227+
3228+
ipcMain.handle("desktop:db:get-sessions-with-details", () => {
3229+
return this.agentDatabase.sessions.getAllWithDetails();
3230+
});
3231+
3232+
ipcMain.handle("desktop:db:get-event-feed", () => {
3233+
return this.agentDatabase.events.getAll();
3234+
});
3235+
3236+
ipcMain.handle("desktop:db:get-events-with-session", (_event, sessionId: string) => {
3237+
return this.agentDatabase.events.getWithSession(sessionId);
3238+
});
3239+
3240+
ipcMain.handle("desktop:db:get-event-count-by-type", () => {
3241+
return this.agentDatabase.events.getCountByType();
3242+
});
3243+
3244+
ipcMain.handle("desktop:db:get-token-analytics", () => {
3245+
return this.agentDatabase.dashboard.getTokenAnalytics();
3246+
});
3247+
3248+
ipcMain.handle("desktop:db:get-agent-hierarchy", (_event, sessionId: string) => {
3249+
return this.agentDatabase.agents.getBySessionWithChildren(sessionId, this.agentDatabase.events);
3250+
});
31933251
}
31943252

31953253
private signDesktopRequest(

0 commit comments

Comments
 (0)