nodebench-mcp 2.34.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmarks/ambientBench.d.ts +27 -0
- package/dist/benchmarks/ambientBench.js +900 -0
- package/dist/benchmarks/ambientBench.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -0
- package/dist/benchmarks/index.js +2 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/dashboard/operatingDashboardHtml.d.ts +23 -0
- package/dist/dashboard/operatingDashboardHtml.js +2036 -0
- package/dist/dashboard/operatingDashboardHtml.js.map +1 -0
- package/dist/dashboard/operatingServer.d.ts +23 -0
- package/dist/dashboard/operatingServer.js +704 -0
- package/dist/dashboard/operatingServer.js.map +1 -0
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/dogfoodJudgeTools.d.ts +13 -0
- package/dist/tools/dogfoodJudgeTools.js +809 -0
- package/dist/tools/dogfoodJudgeTools.js.map +1 -0
- package/dist/tools/localDashboardTools.js +36 -0
- package/dist/tools/localDashboardTools.js.map +1 -1
- package/dist/tools/progressiveDiscoveryTools.js +1 -1
- package/dist/tools/progressiveDiscoveryTools.js.map +1 -1
- package/dist/tools/toolRegistry.js +166 -0
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/toolsetRegistry.js +2 -0
- package/dist/toolsetRegistry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeBench MCP — Operating Dashboard Server (Phase 10/11)
|
|
3
|
+
*
|
|
4
|
+
* Serves the ambient intelligence dashboard at port 6276 showing:
|
|
5
|
+
* 1. Session Delta ("Since Your Last Session")
|
|
6
|
+
* 2. Trajectory Scores (sparkline + dimensions)
|
|
7
|
+
* 3. Event Ledger (causal events)
|
|
8
|
+
* 4. Important Changes (with resolution)
|
|
9
|
+
* 5. Path Replay (session navigation)
|
|
10
|
+
* 6. Time Rollups (day/week/month/quarter/year)
|
|
11
|
+
* 7. Packet Readiness (staleness tracking)
|
|
12
|
+
* 8. Recent Actions (from tracking tools)
|
|
13
|
+
*
|
|
14
|
+
* All data from local SQLite (~/.nodebench/nodebench.db).
|
|
15
|
+
* Zero external dependency at runtime — works fully offline.
|
|
16
|
+
*/
|
|
17
|
+
import { createServer } from "node:http";
|
|
18
|
+
// ── Founder Business Schema ───────────────────────────────────────────
|
|
19
|
+
function ensureFounderSchema(db) {
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS founder_company (
|
|
22
|
+
id TEXT PRIMARY KEY DEFAULT 'default',
|
|
23
|
+
name TEXT NOT NULL,
|
|
24
|
+
canonicalMission TEXT,
|
|
25
|
+
wedge TEXT,
|
|
26
|
+
companyState TEXT DEFAULT 'operating',
|
|
27
|
+
identityConfidence REAL DEFAULT 0.7,
|
|
28
|
+
updatedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS founder_initiatives (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
title TEXT NOT NULL,
|
|
34
|
+
objective TEXT,
|
|
35
|
+
status TEXT DEFAULT 'active',
|
|
36
|
+
ownerType TEXT DEFAULT 'founder',
|
|
37
|
+
riskLevel TEXT DEFAULT 'medium',
|
|
38
|
+
priorityScore REAL DEFAULT 5,
|
|
39
|
+
latestSummary TEXT,
|
|
40
|
+
updatedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS founder_interventions (
|
|
44
|
+
id TEXT PRIMARY KEY,
|
|
45
|
+
title TEXT NOT NULL,
|
|
46
|
+
description TEXT,
|
|
47
|
+
priorityScore REAL DEFAULT 5,
|
|
48
|
+
confidence REAL DEFAULT 0.5,
|
|
49
|
+
status TEXT DEFAULT 'suggested',
|
|
50
|
+
expectedImpact TEXT,
|
|
51
|
+
updatedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS founder_agents (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
name TEXT NOT NULL,
|
|
57
|
+
agentType TEXT DEFAULT 'claude_code',
|
|
58
|
+
status TEXT DEFAULT 'healthy',
|
|
59
|
+
currentGoal TEXT,
|
|
60
|
+
lastHeartbeatAt INTEGER,
|
|
61
|
+
updatedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS founder_competitors (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
name TEXT NOT NULL,
|
|
67
|
+
description TEXT,
|
|
68
|
+
threat TEXT,
|
|
69
|
+
opportunity TEXT,
|
|
70
|
+
lastSignalAt INTEGER,
|
|
71
|
+
updatedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS founder_contradictions (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
title TEXT NOT NULL,
|
|
77
|
+
description TEXT,
|
|
78
|
+
severity TEXT DEFAULT 'medium',
|
|
79
|
+
affectedEntities TEXT,
|
|
80
|
+
status TEXT DEFAULT 'active',
|
|
81
|
+
detectedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
CREATE TABLE IF NOT EXISTS founder_decisions (
|
|
85
|
+
id TEXT PRIMARY KEY,
|
|
86
|
+
title TEXT NOT NULL,
|
|
87
|
+
rationale TEXT,
|
|
88
|
+
status TEXT DEFAULT 'accepted',
|
|
89
|
+
decidedAt INTEGER DEFAULT (strftime('%s','now') * 1000)
|
|
90
|
+
);
|
|
91
|
+
`);
|
|
92
|
+
}
|
|
93
|
+
function seedDemoDataIfEmpty(db) {
|
|
94
|
+
const row = safeGet(db, `SELECT COUNT(*) as count FROM founder_company`);
|
|
95
|
+
if (row && row.count > 0)
|
|
96
|
+
return;
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
// Company
|
|
99
|
+
db.prepare(`INSERT INTO founder_company (id, name, canonicalMission, wedge, companyState, identityConfidence, updatedAt)
|
|
100
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run('default', 'NodeBench AI', 'Ambient operating intelligence for founders', 'Entity-context layer for agent-native businesses', 'operating', 0.78, now);
|
|
101
|
+
// Initiatives
|
|
102
|
+
const initiatives = [
|
|
103
|
+
{ id: 'init-causal-memory', title: 'Phase 10: Causal Memory', objective: 'Build event-driven causal graph for entity state tracking', status: 'completed', ownerType: 'founder', riskLevel: 'low', priorityScore: 9.2, latestSummary: 'Shipped: causal events, state diffs, path replay, time rollups all live in local SQLite' },
|
|
104
|
+
{ id: 'init-ambient-intel', title: 'Phase 11: Ambient Intelligence', objective: 'Session delta, packet readiness, trajectory scoring from local data', status: 'active', ownerType: 'founder', riskLevel: 'medium', priorityScore: 9.0, latestSummary: 'Dashboard live at :6276, auto-refresh working, needs business layer' },
|
|
105
|
+
{ id: 'init-mcp-distribution', title: 'MCP Distribution', objective: 'Publish to npm, MCP Registry, cursor.directory, mcpservers.org', status: 'active', ownerType: 'founder', riskLevel: 'medium', priorityScore: 8.5, latestSummary: 'npm 2.31.0 published, Registry submission pending, SEO infra done' },
|
|
106
|
+
{ id: 'init-benchmark-suite', title: 'Benchmark Suite', objective: 'Comprehensive eval harness: SWE-bench, BFCL, GAIA, HumanEval, MCP-AgentBench', status: 'active', ownerType: 'founder', riskLevel: 'low', priorityScore: 7.8, latestSummary: '1510+ tests passing, 5 dataset adapters, comparative bench operational' },
|
|
107
|
+
{ id: 'init-dashboard-polish', title: 'Local Dashboard Polish', objective: 'Add business intelligence layer to operating dashboard', status: 'in_progress', ownerType: 'founder', riskLevel: 'low', priorityScore: 7.5, latestSummary: 'System intelligence sections complete, business layer in progress' },
|
|
108
|
+
];
|
|
109
|
+
const initStmt = db.prepare(`INSERT INTO founder_initiatives (id, title, objective, status, ownerType, riskLevel, priorityScore, latestSummary, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
110
|
+
for (const i of initiatives) {
|
|
111
|
+
initStmt.run(i.id, i.title, i.objective, i.status, i.ownerType, i.riskLevel, i.priorityScore, i.latestSummary, now);
|
|
112
|
+
}
|
|
113
|
+
// Interventions
|
|
114
|
+
const interventions = [
|
|
115
|
+
{ id: 'intv-live-demo', title: 'Ship live investigation on first click', description: 'Run Live Demo should trigger a real DeepTrace investigation, not navigate to static receipts', priorityScore: 9.5, confidence: 0.82, status: 'suggested', expectedImpact: 'Time-to-value drops from 30s to 5s, unlocks viral screenshot moments' },
|
|
116
|
+
{ id: 'intv-share-memo', title: 'One-click shareable Decision Memos', description: 'Generate a public URL that renders the memo without auth — the viral artifact', priorityScore: 8.8, confidence: 0.75, status: 'in_progress', expectedImpact: 'Each shared memo becomes a distribution channel, est. 3-5x referral lift' },
|
|
117
|
+
{ id: 'intv-daily-brief', title: 'Connect Research Hub to live signals', description: 'Daily brief with real news/signal feeds so there is always fresh content on return', priorityScore: 8.2, confidence: 0.68, status: 'suggested', expectedImpact: 'Return hook: users check daily, DAU/MAU ratio improves 2-3x' },
|
|
118
|
+
{ id: 'intv-voice-commands', title: 'Voice-first core actions', description: 'Investigate Acme AI or Run diligence on this company via voice — the OpenClaw pattern', priorityScore: 7.5, confidence: 0.55, status: 'suggested', expectedImpact: 'Hands-free use case opens mobile-first segment, differentiates from text-only tools' },
|
|
119
|
+
];
|
|
120
|
+
const intvStmt = db.prepare(`INSERT INTO founder_interventions (id, title, description, priorityScore, confidence, status, expectedImpact, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
121
|
+
for (const v of interventions) {
|
|
122
|
+
intvStmt.run(v.id, v.title, v.description, v.priorityScore, v.confidence, v.status, v.expectedImpact, now);
|
|
123
|
+
}
|
|
124
|
+
// Agents
|
|
125
|
+
const agents = [
|
|
126
|
+
{ id: 'agent-claude-code', name: 'Claude Code', agentType: 'claude_code', status: 'healthy', currentGoal: 'Building business intelligence dashboard layer', lastHeartbeatAt: now },
|
|
127
|
+
{ id: 'agent-background', name: 'Background Jobs', agentType: 'background_worker', status: 'healthy', currentGoal: 'Syncing daily brief and narrative from Convex', lastHeartbeatAt: now - 120_000 },
|
|
128
|
+
{ id: 'agent-openclaw', name: 'OpenClaw Bridge', agentType: 'openclaw', status: 'waiting', currentGoal: 'Awaiting command bridge connection', lastHeartbeatAt: now - 3_600_000 },
|
|
129
|
+
];
|
|
130
|
+
const agentStmt = db.prepare(`INSERT INTO founder_agents (id, name, agentType, status, currentGoal, lastHeartbeatAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
131
|
+
for (const a of agents) {
|
|
132
|
+
agentStmt.run(a.id, a.name, a.agentType, a.status, a.currentGoal, a.lastHeartbeatAt, now);
|
|
133
|
+
}
|
|
134
|
+
// Competitors
|
|
135
|
+
const competitors = [
|
|
136
|
+
{ id: 'comp-supermemory', name: 'Supermemory', description: 'Memory substrate for AI agents — persistent context across sessions', threat: 'Direct competitor on memory layer positioning, well-funded', opportunity: 'Their focus is generic memory; NodeBench is founder-specific operating intelligence', lastSignalAt: now - 86_400_000 * 3 },
|
|
137
|
+
{ id: 'comp-mastra', name: 'Mastra', description: 'Observational memory and workflow orchestration for agents', threat: 'Strong developer community, good DX, TypeScript-native', opportunity: 'Mastra is workflow-first; NodeBench is judgment-first with entity context', lastSignalAt: now - 86_400_000 * 5 },
|
|
138
|
+
{ id: 'comp-agentchattr', name: 'AgentChattr', description: 'Multi-agent coordination bus with shared state', threat: 'Novel coordination primitives, growing mindshare in multi-agent space', opportunity: 'Coordination without judgment is noise; NodeBench adds the decision layer', lastSignalAt: now - 86_400_000 * 7 },
|
|
139
|
+
];
|
|
140
|
+
const compStmt = db.prepare(`INSERT INTO founder_competitors (id, name, description, threat, opportunity, lastSignalAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
141
|
+
for (const c of competitors) {
|
|
142
|
+
compStmt.run(c.id, c.name, c.description, c.threat, c.opportunity, c.lastSignalAt, now);
|
|
143
|
+
}
|
|
144
|
+
// Contradictions
|
|
145
|
+
const contradictions = [
|
|
146
|
+
{ id: 'contra-positioning', title: 'Positioning vs build priorities', description: 'Marketing says Operating Intelligence for Founders but current build focuses on MCP tooling and benchmarks — the founder surface has demo data only', severity: 'high', affectedEntities: JSON.stringify(['founder_platform', 'mcp_distribution', 'landing_page']), status: 'active' },
|
|
147
|
+
{ id: 'contra-memory-scope', title: 'Memory layer scope creep', description: 'Entity-context layer keeps expanding (causal memory, tracking, packets, session delta) — risk of building infrastructure nobody uses', severity: 'medium', affectedEntities: JSON.stringify(['causal_memory', 'tracking', 'ambient_intelligence']), status: 'resolved' },
|
|
148
|
+
];
|
|
149
|
+
const contraStmt = db.prepare(`INSERT INTO founder_contradictions (id, title, description, severity, affectedEntities, status, detectedAt) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
150
|
+
for (const x of contradictions) {
|
|
151
|
+
contraStmt.run(x.id, x.title, x.description, x.severity, x.affectedEntities, x.status, now);
|
|
152
|
+
}
|
|
153
|
+
// Decisions
|
|
154
|
+
const decisions = [
|
|
155
|
+
{ id: 'dec-local-first', title: 'Local-first SQLite over cloud-only Convex', rationale: 'Zero-dependency offline operation is the wedge — cloud sync is additive, not required', status: 'accepted' },
|
|
156
|
+
{ id: 'dec-304-tools', title: 'Ship 304 tools with progressive discovery', rationale: 'Large tool count signals depth; progressive discovery prevents overwhelm; toolset gating lets agents see only what they need', status: 'accepted' },
|
|
157
|
+
{ id: 'dec-founder-positioning', title: 'Reposition from Agent Trust to Operating Intelligence', rationale: 'Agent Trust Infrastructure is too abstract — Operating Intelligence for Founders is specific, testable, and resonates with ICP', status: 'accepted' },
|
|
158
|
+
{ id: 'dec-glass-card-dna', title: 'Glass card design DNA as brand signature', rationale: 'Consistent visual identity across all surfaces — dark bg, glass cards, terracotta accent — creates instant recognition', status: 'accepted' },
|
|
159
|
+
];
|
|
160
|
+
const decStmt = db.prepare(`INSERT INTO founder_decisions (id, title, rationale, status, decidedAt) VALUES (?, ?, ?, ?, ?)`);
|
|
161
|
+
for (const d of decisions) {
|
|
162
|
+
decStmt.run(d.id, d.title, d.rationale, d.status, now - Math.floor(Math.random() * 86_400_000 * 14));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let _server = null;
|
|
166
|
+
let _port = 0;
|
|
167
|
+
let _getHtml = null;
|
|
168
|
+
/** Start the operating dashboard server. Returns the port. */
|
|
169
|
+
export function startOperatingDashboardServer(db, preferredPort = 6276) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
if (_server) {
|
|
172
|
+
resolve(_port);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
_server = createServer((req, res) => handleRequest(db, req, res));
|
|
176
|
+
let attempts = 0;
|
|
177
|
+
const maxRetries = 5;
|
|
178
|
+
function tryListen(port) {
|
|
179
|
+
_server.once("error", (err) => {
|
|
180
|
+
if (err.code === "EADDRINUSE" && attempts < maxRetries) {
|
|
181
|
+
attempts++;
|
|
182
|
+
tryListen(port + 1);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
reject(err);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
_server.listen(port, "127.0.0.1", () => {
|
|
189
|
+
_port = port;
|
|
190
|
+
resolve(port);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
tryListen(preferredPort);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** Stop the operating dashboard server. */
|
|
197
|
+
export function stopOperatingDashboardServer() {
|
|
198
|
+
if (_server) {
|
|
199
|
+
_server.close();
|
|
200
|
+
_server = null;
|
|
201
|
+
_port = 0;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Get the current operating dashboard URL. */
|
|
205
|
+
export function getOperatingDashboardUrl() {
|
|
206
|
+
return _port ? `http://127.0.0.1:${_port}` : null;
|
|
207
|
+
}
|
|
208
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
209
|
+
function json(res, data) {
|
|
210
|
+
res.writeHead(200, {
|
|
211
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
212
|
+
"Access-Control-Allow-Origin": "*",
|
|
213
|
+
});
|
|
214
|
+
res.end(JSON.stringify(data));
|
|
215
|
+
}
|
|
216
|
+
function safeQuery(db, sql, params = []) {
|
|
217
|
+
try {
|
|
218
|
+
return db.prepare(sql).all(...params);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function safeGet(db, sql, params = []) {
|
|
225
|
+
try {
|
|
226
|
+
return db.prepare(sql).get(...params) ?? null;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ── Request Router ─────────────────────────────────────────────────────
|
|
233
|
+
let _founderSchemaReady = false;
|
|
234
|
+
function handleRequest(db, req, res) {
|
|
235
|
+
// Ensure founder business tables exist (once per server lifetime)
|
|
236
|
+
if (!_founderSchemaReady) {
|
|
237
|
+
try {
|
|
238
|
+
ensureFounderSchema(db);
|
|
239
|
+
seedDemoDataIfEmpty(db);
|
|
240
|
+
_founderSchemaReady = true;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Schema creation failed — continue serving system routes
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${_port}`);
|
|
247
|
+
const path = url.pathname;
|
|
248
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
249
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
250
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
251
|
+
if (req.method === "OPTIONS") {
|
|
252
|
+
res.writeHead(204);
|
|
253
|
+
res.end();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
// ── HTML ──
|
|
258
|
+
if (path === "/" || path === "/index.html") {
|
|
259
|
+
serveHtml(res);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// ── Causal Memory API ──
|
|
263
|
+
if (path === "/api/causal/events")
|
|
264
|
+
return apiCausalEvents(db, url, res);
|
|
265
|
+
if (path === "/api/causal/trajectory")
|
|
266
|
+
return apiCausalTrajectory(db, res);
|
|
267
|
+
if (path === "/api/causal/changes")
|
|
268
|
+
return apiCausalChanges(db, url, res);
|
|
269
|
+
if (path === "/api/causal/path")
|
|
270
|
+
return apiCausalPath(db, url, res);
|
|
271
|
+
if (path === "/api/causal/diffs")
|
|
272
|
+
return apiCausalDiffs(db, url, res);
|
|
273
|
+
if (path === "/api/causal/rollups")
|
|
274
|
+
return apiCausalRollups(db, url, res);
|
|
275
|
+
// ── Tracking API ──
|
|
276
|
+
if (path === "/api/tracking/actions")
|
|
277
|
+
return apiTrackingActions(db, url, res);
|
|
278
|
+
if (path === "/api/tracking/milestones")
|
|
279
|
+
return apiTrackingMilestones(db, res);
|
|
280
|
+
// ── Ambient API ──
|
|
281
|
+
if (path === "/api/ambient/session-delta")
|
|
282
|
+
return apiSessionDelta(db, res);
|
|
283
|
+
if (path === "/api/ambient/packets")
|
|
284
|
+
return apiPacketReadiness(db, res);
|
|
285
|
+
if (path === "/api/ambient/stats")
|
|
286
|
+
return apiStats(db, res);
|
|
287
|
+
// ── Business Intelligence API ──
|
|
288
|
+
if (path === "/api/business/company")
|
|
289
|
+
return apiBusinessCompany(db, res);
|
|
290
|
+
if (path === "/api/business/initiatives")
|
|
291
|
+
return apiBusinessInitiatives(db, res);
|
|
292
|
+
if (path === "/api/business/interventions")
|
|
293
|
+
return apiBusinessInterventions(db, res);
|
|
294
|
+
if (path === "/api/business/agents")
|
|
295
|
+
return apiBusinessAgents(db, res);
|
|
296
|
+
if (path === "/api/business/competitors")
|
|
297
|
+
return apiBusinessCompetitors(db, res);
|
|
298
|
+
if (path === "/api/business/contradictions")
|
|
299
|
+
return apiBusinessContradictions(db, res);
|
|
300
|
+
if (path === "/api/business/decisions")
|
|
301
|
+
return apiBusinessDecisions(db, res);
|
|
302
|
+
if (path === "/api/business/summary")
|
|
303
|
+
return apiBusinessSummary(db, res);
|
|
304
|
+
// ── 404 ──
|
|
305
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
306
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
310
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ── HTML Serving ───────────────────────────────────────────────────────
|
|
314
|
+
async function serveHtml(res) {
|
|
315
|
+
if (!_getHtml) {
|
|
316
|
+
try {
|
|
317
|
+
const mod = await import("./operatingDashboardHtml.js");
|
|
318
|
+
_getHtml = mod.getOperatingDashboardHtml;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
322
|
+
res.end("Dashboard HTML module not found");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
res.writeHead(200, {
|
|
327
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
328
|
+
"Cache-Control": "no-cache",
|
|
329
|
+
});
|
|
330
|
+
res.end(_getHtml());
|
|
331
|
+
}
|
|
332
|
+
// ── Causal Events API ──────────────────────────────────────────────────
|
|
333
|
+
function apiCausalEvents(db, url, res) {
|
|
334
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "30"), 100);
|
|
335
|
+
const eventType = url.searchParams.get("eventType");
|
|
336
|
+
let rows;
|
|
337
|
+
if (eventType) {
|
|
338
|
+
rows = safeQuery(db, `SELECT * FROM causal_events WHERE eventType = ? ORDER BY timestamp DESC LIMIT ?`, [eventType, limit]);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
rows = safeQuery(db, `SELECT * FROM causal_events ORDER BY timestamp DESC LIMIT ?`, [limit]);
|
|
342
|
+
}
|
|
343
|
+
json(res, {
|
|
344
|
+
events: rows.map((r) => ({
|
|
345
|
+
id: r.eventId,
|
|
346
|
+
eventType: r.eventType,
|
|
347
|
+
actorType: r.actorType,
|
|
348
|
+
entityType: r.entityType,
|
|
349
|
+
entityId: r.entityId,
|
|
350
|
+
summary: r.summary,
|
|
351
|
+
details: r.details ? tryParseJson(r.details) : null,
|
|
352
|
+
causedByEventId: r.causedByEventId,
|
|
353
|
+
correlationId: r.correlationId,
|
|
354
|
+
createdAt: r.timestamp,
|
|
355
|
+
})),
|
|
356
|
+
total: rows.length,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// ── Trajectory API ─────────────────────────────────────────────────────
|
|
360
|
+
function apiCausalTrajectory(db, res) {
|
|
361
|
+
// Get trajectory from tracking milestones and actions as proxy
|
|
362
|
+
const actions = safeQuery(db, `SELECT * FROM tracking_actions ORDER BY timestamp DESC LIMIT 30`);
|
|
363
|
+
const milestones = safeQuery(db, `SELECT * FROM tracking_milestones ORDER BY timestamp DESC LIMIT 10`);
|
|
364
|
+
// Build trajectory from action data
|
|
365
|
+
const byDay = {};
|
|
366
|
+
for (const a of actions) {
|
|
367
|
+
const day = new Date(a.timestamp).toISOString().slice(0, 10);
|
|
368
|
+
if (!byDay[day])
|
|
369
|
+
byDay[day] = { count: 0, categories: {} };
|
|
370
|
+
byDay[day].count++;
|
|
371
|
+
const cat = a.category || "unknown";
|
|
372
|
+
byDay[day].categories[cat] = (byDay[day].categories[cat] || 0) + 1;
|
|
373
|
+
}
|
|
374
|
+
const scores = Object.entries(byDay)
|
|
375
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
376
|
+
.map(([date, data]) => ({
|
|
377
|
+
date,
|
|
378
|
+
actionsCount: data.count,
|
|
379
|
+
categories: data.categories,
|
|
380
|
+
// Heuristic score based on activity level
|
|
381
|
+
score: Math.min(1, data.count / 10),
|
|
382
|
+
}));
|
|
383
|
+
json(res, { scores, milestones: milestones.length, totalActions: actions.length });
|
|
384
|
+
}
|
|
385
|
+
// ── Important Changes API ──────────────────────────────────────────────
|
|
386
|
+
function apiCausalChanges(db, url, res) {
|
|
387
|
+
const status = url.searchParams.get("status");
|
|
388
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "20"), 50);
|
|
389
|
+
let rows;
|
|
390
|
+
if (status) {
|
|
391
|
+
rows = safeQuery(db, `SELECT * FROM causal_important_changes WHERE status = ? ORDER BY timestamp DESC LIMIT ?`, [status, limit]);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
rows = safeQuery(db, `SELECT * FROM causal_important_changes ORDER BY timestamp DESC LIMIT ?`, [limit]);
|
|
395
|
+
}
|
|
396
|
+
// Count by status
|
|
397
|
+
const statusCounts = safeQuery(db, `SELECT status, COUNT(*) as count FROM causal_important_changes GROUP BY status`);
|
|
398
|
+
json(res, {
|
|
399
|
+
changes: rows.map((r) => ({
|
|
400
|
+
id: r.changeId,
|
|
401
|
+
changeCategory: r.changeCategory,
|
|
402
|
+
impactScore: r.impactScore,
|
|
403
|
+
impactReason: r.impactReason,
|
|
404
|
+
affectedEntities: tryParseJson(r.affectedEntities) || [],
|
|
405
|
+
suggestedAction: r.suggestedAction,
|
|
406
|
+
status: r.status,
|
|
407
|
+
createdAt: r.timestamp,
|
|
408
|
+
})),
|
|
409
|
+
statusCounts: Object.fromEntries(statusCounts.map((r) => [r.status, r.count])),
|
|
410
|
+
total: rows.length,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// ── Path Replay API ────────────────────────────────────────────────────
|
|
414
|
+
function apiCausalPath(db, url, res) {
|
|
415
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
416
|
+
let rows;
|
|
417
|
+
if (sessionId) {
|
|
418
|
+
rows = safeQuery(db, `SELECT * FROM causal_path_steps WHERE sessionId = ? ORDER BY stepIndex ASC`, [sessionId]);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// Get latest session
|
|
422
|
+
const latestSession = safeGet(db, `SELECT sessionId FROM causal_path_steps ORDER BY timestamp DESC LIMIT 1`);
|
|
423
|
+
if (latestSession) {
|
|
424
|
+
rows = safeQuery(db, `SELECT * FROM causal_path_steps WHERE sessionId = ? ORDER BY stepIndex ASC`, [latestSession.sessionId]);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
rows = [];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
json(res, {
|
|
431
|
+
path: rows.map((r) => ({
|
|
432
|
+
stepIndex: r.stepIndex,
|
|
433
|
+
surfaceType: r.surfaceType,
|
|
434
|
+
surfaceRef: r.surfaceRef,
|
|
435
|
+
surfaceLabel: r.surfaceLabel,
|
|
436
|
+
entityType: r.entityType,
|
|
437
|
+
entityId: r.entityId,
|
|
438
|
+
durationMs: r.durationMs,
|
|
439
|
+
transitionFrom: r.transitionFrom,
|
|
440
|
+
createdAt: r.timestamp,
|
|
441
|
+
})),
|
|
442
|
+
sessionId: rows.length > 0 ? rows[0].sessionId : null,
|
|
443
|
+
totalSteps: rows.length,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
// ── State Diffs API ────────────────────────────────────────────────────
|
|
447
|
+
function apiCausalDiffs(db, url, res) {
|
|
448
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "20"), 50);
|
|
449
|
+
const entityType = url.searchParams.get("entityType");
|
|
450
|
+
let rows;
|
|
451
|
+
if (entityType) {
|
|
452
|
+
rows = safeQuery(db, `SELECT * FROM causal_state_diffs WHERE entityType = ? ORDER BY timestamp DESC LIMIT ?`, [entityType, limit]);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
rows = safeQuery(db, `SELECT * FROM causal_state_diffs ORDER BY timestamp DESC LIMIT ?`, [limit]);
|
|
456
|
+
}
|
|
457
|
+
json(res, {
|
|
458
|
+
diffs: rows.map((r) => ({
|
|
459
|
+
id: r.diffId,
|
|
460
|
+
entityType: r.entityType,
|
|
461
|
+
entityId: r.entityId,
|
|
462
|
+
changeType: r.changeType,
|
|
463
|
+
beforeState: tryParseJson(r.beforeState),
|
|
464
|
+
afterState: tryParseJson(r.afterState),
|
|
465
|
+
changedFields: tryParseJson(r.changedFields) || [],
|
|
466
|
+
reason: r.reason,
|
|
467
|
+
createdAt: r.timestamp,
|
|
468
|
+
})),
|
|
469
|
+
total: rows.length,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
// ── Time Rollups API ───────────────────────────────────────────────────
|
|
473
|
+
function apiCausalRollups(db, url, res) {
|
|
474
|
+
// Compute rollups from tracking data
|
|
475
|
+
const now = Date.now();
|
|
476
|
+
const dayMs = 86_400_000;
|
|
477
|
+
// Today's actions
|
|
478
|
+
const todayStart = new Date().setHours(0, 0, 0, 0);
|
|
479
|
+
const todayActions = safeQuery(db, `SELECT category, COUNT(*) as count FROM tracking_actions WHERE timestamp >= ? GROUP BY category`, [todayStart]);
|
|
480
|
+
// Yesterday's actions
|
|
481
|
+
const yesterdayActions = safeQuery(db, `SELECT category, COUNT(*) as count FROM tracking_actions WHERE timestamp >= ? AND timestamp < ? GROUP BY category`, [todayStart - dayMs, todayStart]);
|
|
482
|
+
// This week (last 7 days)
|
|
483
|
+
const weekActions = safeQuery(db, `SELECT category, COUNT(*) as count FROM tracking_actions WHERE timestamp >= ? GROUP BY category`, [now - 7 * dayMs]);
|
|
484
|
+
// Events today
|
|
485
|
+
const todayEvents = safeGet(db, `SELECT COUNT(*) as count FROM causal_events WHERE timestamp >= ?`, [todayStart]);
|
|
486
|
+
// Events yesterday
|
|
487
|
+
const yesterdayEvents = safeGet(db, `SELECT COUNT(*) as count FROM causal_events WHERE timestamp >= ? AND timestamp < ?`, [todayStart - dayMs, todayStart]);
|
|
488
|
+
// Changes today
|
|
489
|
+
const todayChanges = safeGet(db, `SELECT COUNT(*) as count FROM causal_important_changes WHERE timestamp >= ?`, [todayStart]);
|
|
490
|
+
// Diffs today
|
|
491
|
+
const todayDiffs = safeGet(db, `SELECT COUNT(*) as count FROM causal_state_diffs WHERE timestamp >= ?`, [todayStart]);
|
|
492
|
+
json(res, {
|
|
493
|
+
daily: {
|
|
494
|
+
current: {
|
|
495
|
+
key: new Date().toISOString().slice(0, 10),
|
|
496
|
+
actions: Object.fromEntries(todayActions.map((r) => [r.category, r.count])),
|
|
497
|
+
totalActions: todayActions.reduce((s, r) => s + r.count, 0),
|
|
498
|
+
events: todayEvents?.count ?? 0,
|
|
499
|
+
changes: todayChanges?.count ?? 0,
|
|
500
|
+
diffs: todayDiffs?.count ?? 0,
|
|
501
|
+
},
|
|
502
|
+
prior: {
|
|
503
|
+
key: new Date(todayStart - dayMs).toISOString().slice(0, 10),
|
|
504
|
+
actions: Object.fromEntries(yesterdayActions.map((r) => [r.category, r.count])),
|
|
505
|
+
totalActions: yesterdayActions.reduce((s, r) => s + r.count, 0),
|
|
506
|
+
events: yesterdayEvents?.count ?? 0,
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
weekly: {
|
|
510
|
+
actions: Object.fromEntries(weekActions.map((r) => [r.category, r.count])),
|
|
511
|
+
totalActions: weekActions.reduce((s, r) => s + r.count, 0),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// ── Tracking Actions API ───────────────────────────────────────────────
|
|
516
|
+
function apiTrackingActions(db, url, res) {
|
|
517
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "15"), 50);
|
|
518
|
+
const rows = safeQuery(db, `SELECT * FROM tracking_actions ORDER BY timestamp DESC LIMIT ?`, [limit]);
|
|
519
|
+
json(res, {
|
|
520
|
+
actions: rows.map((r) => ({
|
|
521
|
+
id: r.actionId,
|
|
522
|
+
action: r.action,
|
|
523
|
+
category: r.category,
|
|
524
|
+
beforeState: r.beforeState,
|
|
525
|
+
afterState: r.afterState,
|
|
526
|
+
reasoning: r.reasoning,
|
|
527
|
+
filesChanged: r.filesChanged,
|
|
528
|
+
impactLevel: r.impactLevel,
|
|
529
|
+
sessionId: r.sessionId,
|
|
530
|
+
createdAt: r.timestamp,
|
|
531
|
+
})),
|
|
532
|
+
total: rows.length,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
// ── Tracking Milestones API ────────────────────────────────────────────
|
|
536
|
+
function apiTrackingMilestones(db, res) {
|
|
537
|
+
const rows = safeQuery(db, `SELECT * FROM tracking_milestones ORDER BY timestamp DESC LIMIT 10`);
|
|
538
|
+
json(res, {
|
|
539
|
+
milestones: rows.map((r) => ({
|
|
540
|
+
id: r.milestoneId,
|
|
541
|
+
milestone: r.milestone,
|
|
542
|
+
category: r.category,
|
|
543
|
+
evidence: r.evidence,
|
|
544
|
+
metrics: r.metrics,
|
|
545
|
+
createdAt: r.timestamp,
|
|
546
|
+
})),
|
|
547
|
+
total: rows.length,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// ── Session Delta API ──────────────────────────────────────────────────
|
|
551
|
+
function apiSessionDelta(db, res) {
|
|
552
|
+
const now = Date.now();
|
|
553
|
+
const eightHoursAgo = now - 8 * 3_600_000;
|
|
554
|
+
// Recent events
|
|
555
|
+
const recentEvents = safeQuery(db, `SELECT eventType, COUNT(*) as count FROM causal_events WHERE timestamp >= ? GROUP BY eventType`, [eightHoursAgo]);
|
|
556
|
+
// Recent changes
|
|
557
|
+
const recentChanges = safeQuery(db, `SELECT * FROM causal_important_changes WHERE timestamp >= ? ORDER BY impactScore DESC`, [eightHoursAgo]);
|
|
558
|
+
// Recent actions
|
|
559
|
+
const recentActions = safeQuery(db, `SELECT category, COUNT(*) as count FROM tracking_actions WHERE timestamp >= ? GROUP BY category`, [eightHoursAgo]);
|
|
560
|
+
// Recent diffs
|
|
561
|
+
const recentDiffs = safeGet(db, `SELECT COUNT(*) as count FROM causal_state_diffs WHERE timestamp >= ?`, [eightHoursAgo]);
|
|
562
|
+
// Total counts
|
|
563
|
+
const totalEvents = recentEvents.reduce((s, r) => s + r.count, 0);
|
|
564
|
+
const totalActions = recentActions.reduce((s, r) => s + r.count, 0);
|
|
565
|
+
json(res, {
|
|
566
|
+
sinceTimestamp: eightHoursAgo,
|
|
567
|
+
totalChanges: totalEvents + totalActions + (recentDiffs?.count ?? 0),
|
|
568
|
+
eventsByType: Object.fromEntries(recentEvents.map((r) => [r.eventType, r.count])),
|
|
569
|
+
actionsByCategory: Object.fromEntries(recentActions.map((r) => [r.category, r.count])),
|
|
570
|
+
importantChanges: recentChanges.map((r) => ({
|
|
571
|
+
id: r.changeId,
|
|
572
|
+
category: r.changeCategory,
|
|
573
|
+
impactScore: r.impactScore,
|
|
574
|
+
reason: r.impactReason,
|
|
575
|
+
status: r.status,
|
|
576
|
+
})),
|
|
577
|
+
diffsCount: recentDiffs?.count ?? 0,
|
|
578
|
+
eventsCount: totalEvents,
|
|
579
|
+
actionsCount: totalActions,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// ── Packet Readiness API ───────────────────────────────────────────────
|
|
583
|
+
function apiPacketReadiness(db, res) {
|
|
584
|
+
// Compute readiness from action/event counts
|
|
585
|
+
const now = Date.now();
|
|
586
|
+
const weekMs = 7 * 86_400_000;
|
|
587
|
+
const weeklyActions = safeGet(db, `SELECT COUNT(*) as count FROM tracking_actions WHERE timestamp >= ?`, [now - weekMs]);
|
|
588
|
+
const weeklyEvents = safeGet(db, `SELECT COUNT(*) as count FROM causal_events WHERE timestamp >= ?`, [now - weekMs]);
|
|
589
|
+
const weeklyChanges = safeGet(db, `SELECT COUNT(*) as count FROM causal_important_changes WHERE timestamp >= ?`, [now - weekMs]);
|
|
590
|
+
const actionCount = weeklyActions?.count ?? 0;
|
|
591
|
+
const eventCount = weeklyEvents?.count ?? 0;
|
|
592
|
+
const changeCount = weeklyChanges?.count ?? 0;
|
|
593
|
+
const packets = [
|
|
594
|
+
{
|
|
595
|
+
type: "weekly_reset",
|
|
596
|
+
changeCount: actionCount + eventCount,
|
|
597
|
+
threshold: 10,
|
|
598
|
+
readiness: Math.min(1, (actionCount + eventCount) / 10),
|
|
599
|
+
reason: actionCount + eventCount > 5
|
|
600
|
+
? `${actionCount + eventCount} actions/events this week — time for a weekly reset`
|
|
601
|
+
: null,
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
type: "agent_brief",
|
|
605
|
+
changeCount: eventCount,
|
|
606
|
+
threshold: 5,
|
|
607
|
+
readiness: Math.min(1, eventCount / 5),
|
|
608
|
+
reason: eventCount > 3 ? `${eventCount} events since last brief` : null,
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
type: "competitor_readout",
|
|
612
|
+
changeCount: changeCount,
|
|
613
|
+
threshold: 8,
|
|
614
|
+
readiness: Math.min(1, changeCount / 8),
|
|
615
|
+
reason: changeCount > 3 ? `${changeCount} important changes detected` : null,
|
|
616
|
+
},
|
|
617
|
+
];
|
|
618
|
+
json(res, { packets });
|
|
619
|
+
}
|
|
620
|
+
// ── Stats API ──────────────────────────────────────────────────────────
|
|
621
|
+
function apiStats(db, res) {
|
|
622
|
+
const eventCount = safeGet(db, `SELECT COUNT(*) as count FROM causal_events`);
|
|
623
|
+
const actionCount = safeGet(db, `SELECT COUNT(*) as count FROM tracking_actions`);
|
|
624
|
+
const milestoneCount = safeGet(db, `SELECT COUNT(*) as count FROM tracking_milestones`);
|
|
625
|
+
const diffCount = safeGet(db, `SELECT COUNT(*) as count FROM causal_state_diffs`);
|
|
626
|
+
const changeCount = safeGet(db, `SELECT COUNT(*) as count FROM causal_important_changes`);
|
|
627
|
+
const pathCount = safeGet(db, `SELECT COUNT(*) as count FROM causal_path_steps`);
|
|
628
|
+
json(res, {
|
|
629
|
+
events: eventCount?.count ?? 0,
|
|
630
|
+
actions: actionCount?.count ?? 0,
|
|
631
|
+
milestones: milestoneCount?.count ?? 0,
|
|
632
|
+
diffs: diffCount?.count ?? 0,
|
|
633
|
+
changes: changeCount?.count ?? 0,
|
|
634
|
+
pathSteps: pathCount?.count ?? 0,
|
|
635
|
+
dashboardUrl: getOperatingDashboardUrl(),
|
|
636
|
+
version: "2.34.0",
|
|
637
|
+
toolCount: 325,
|
|
638
|
+
tableCount: 30,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
// ── Business Intelligence API ──────────────────────────────────────────
|
|
642
|
+
function apiBusinessCompany(db, res) {
|
|
643
|
+
const row = safeGet(db, `SELECT * FROM founder_company WHERE id = 'default'`);
|
|
644
|
+
json(res, { company: row || null });
|
|
645
|
+
}
|
|
646
|
+
function apiBusinessInitiatives(db, res) {
|
|
647
|
+
const rows = safeQuery(db, `SELECT * FROM founder_initiatives ORDER BY priorityScore DESC`);
|
|
648
|
+
json(res, { initiatives: rows, total: rows.length });
|
|
649
|
+
}
|
|
650
|
+
function apiBusinessInterventions(db, res) {
|
|
651
|
+
const rows = safeQuery(db, `SELECT * FROM founder_interventions ORDER BY priorityScore DESC`);
|
|
652
|
+
json(res, { interventions: rows, total: rows.length });
|
|
653
|
+
}
|
|
654
|
+
function apiBusinessAgents(db, res) {
|
|
655
|
+
const rows = safeQuery(db, `SELECT * FROM founder_agents ORDER BY updatedAt DESC`);
|
|
656
|
+
json(res, { agents: rows, total: rows.length });
|
|
657
|
+
}
|
|
658
|
+
function apiBusinessCompetitors(db, res) {
|
|
659
|
+
const rows = safeQuery(db, `SELECT * FROM founder_competitors ORDER BY lastSignalAt DESC`);
|
|
660
|
+
json(res, { competitors: rows, total: rows.length });
|
|
661
|
+
}
|
|
662
|
+
function apiBusinessContradictions(db, res) {
|
|
663
|
+
const rows = safeQuery(db, `SELECT * FROM founder_contradictions ORDER BY CASE WHEN status = 'active' THEN 0 ELSE 1 END, detectedAt DESC`);
|
|
664
|
+
json(res, { contradictions: rows, total: rows.length });
|
|
665
|
+
}
|
|
666
|
+
function apiBusinessDecisions(db, res) {
|
|
667
|
+
const rows = safeQuery(db, `SELECT * FROM founder_decisions ORDER BY decidedAt DESC`);
|
|
668
|
+
json(res, { decisions: rows, total: rows.length });
|
|
669
|
+
}
|
|
670
|
+
function apiBusinessSummary(db, res) {
|
|
671
|
+
const company = safeGet(db, `SELECT * FROM founder_company WHERE id = 'default'`);
|
|
672
|
+
// Initiative counts by status
|
|
673
|
+
const initCounts = safeQuery(db, `SELECT status, COUNT(*) as count FROM founder_initiatives GROUP BY status`);
|
|
674
|
+
const initiativesByStatus = Object.fromEntries(initCounts.map((r) => [r.status, r.count]));
|
|
675
|
+
// Top 3 interventions
|
|
676
|
+
const topInterventions = safeQuery(db, `SELECT id, title, priorityScore, confidence, status FROM founder_interventions ORDER BY priorityScore DESC LIMIT 3`);
|
|
677
|
+
// Agent health
|
|
678
|
+
const agents = safeQuery(db, `SELECT id, name, status FROM founder_agents`);
|
|
679
|
+
const healthyCount = agents.filter((a) => a.status === "healthy").length;
|
|
680
|
+
// Active contradictions
|
|
681
|
+
const activeContradictions = safeGet(db, `SELECT COUNT(*) as count FROM founder_contradictions WHERE status = 'active'`);
|
|
682
|
+
// Competitor count
|
|
683
|
+
const competitorCount = safeGet(db, `SELECT COUNT(*) as count FROM founder_competitors`);
|
|
684
|
+
json(res, {
|
|
685
|
+
company: company || null,
|
|
686
|
+
initiativesByStatus,
|
|
687
|
+
topInterventions,
|
|
688
|
+
agentHealth: { total: agents.length, healthy: healthyCount },
|
|
689
|
+
activeContradictions: activeContradictions?.count ?? 0,
|
|
690
|
+
competitorCount: competitorCount?.count ?? 0,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
// ── Utilities ──────────────────────────────────────────────────────────
|
|
694
|
+
function tryParseJson(str) {
|
|
695
|
+
if (!str)
|
|
696
|
+
return null;
|
|
697
|
+
try {
|
|
698
|
+
return JSON.parse(str);
|
|
699
|
+
}
|
|
700
|
+
catch {
|
|
701
|
+
return str;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=operatingServer.js.map
|