quoroom 0.1.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.
@@ -0,0 +1,2855 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // package.json
34
+ var require_package = __commonJS({
35
+ "package.json"(exports2, module2) {
36
+ module2.exports = {
37
+ name: "quoroom",
38
+ version: "0.1.0",
39
+ description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
40
+ main: "./out/mcp/server.js",
41
+ bin: {
42
+ quoroom: "./out/mcp/cli.js"
43
+ },
44
+ author: "Quoroom <hello@quoroom.ai>",
45
+ homepage: "https://quoroom.ai",
46
+ repository: {
47
+ type: "git",
48
+ url: "https://github.com/quoroom-ai/room.git"
49
+ },
50
+ license: "MIT",
51
+ files: [
52
+ "out/mcp/cli.js",
53
+ "out/mcp/server.js",
54
+ "out/mcp/api-server.js"
55
+ ],
56
+ scripts: {
57
+ build: "npm run typecheck && npm run build:mcp && npm run build:ui",
58
+ "build:mcp": "node scripts/build-mcp.js",
59
+ "build:ui": "vite build --config src/ui/vite.config.ts",
60
+ "dev:ui": "vite --config src/ui/vite.config.ts",
61
+ typecheck: "tsc --noEmit",
62
+ test: "npm run rebuild:native:node && vitest run --pool=forks",
63
+ "test:watch": "npm run rebuild:native:node && vitest --pool=forks",
64
+ "test:e2e": "npm run build && npx playwright test",
65
+ "test:e2e:ui": "npm run build && npx playwright test e2e/ui.test.ts",
66
+ "rebuild:native:node": `node -e "try{require('better-sqlite3')(':memory:').close()}catch{process.exit(1)}" || npx --yes node-gyp rebuild --directory=node_modules/better-sqlite3`,
67
+ prepublishOnly: "npm run build:mcp"
68
+ },
69
+ dependencies: {
70
+ "@huggingface/transformers": "^3.4.1",
71
+ "@modelcontextprotocol/sdk": "^1.12.0",
72
+ "better-sqlite3": "^11.10.0",
73
+ "node-cron": "^3.0.3",
74
+ "sqlite-vec": "^0.1.7-alpha.2",
75
+ viem: "^2.46.2",
76
+ ws: "^8.16.0",
77
+ zod: "^3.24.0"
78
+ },
79
+ devDependencies: {
80
+ "@electron-toolkit/tsconfig": "^2.0.0",
81
+ "@playwright/test": "^1.58.2",
82
+ "@tailwindcss/vite": "^4.2.0",
83
+ "@types/better-sqlite3": "^7.6.13",
84
+ "@types/node": "^20.11.0",
85
+ "@types/node-cron": "^3.0.11",
86
+ "@types/react": "^19.2.14",
87
+ "@types/react-dom": "^19.2.3",
88
+ "@types/ws": "^8.5.10",
89
+ "@vitejs/plugin-react": "^5.1.4",
90
+ esbuild: "^0.24.0",
91
+ react: "^19.2.4",
92
+ "react-dom": "^19.2.4",
93
+ tailwindcss: "^4.2.0",
94
+ typescript: "^5.9.3",
95
+ vite: "^7.3.1",
96
+ vitest: "^1.6.1"
97
+ }
98
+ };
99
+ }
100
+ });
101
+
102
+ // src/server/index.ts
103
+ var index_exports = {};
104
+ __export(index_exports, {
105
+ createApiServer: () => createApiServer,
106
+ startServer: () => startServer
107
+ });
108
+ module.exports = __toCommonJS(index_exports);
109
+ var import_node_http = __toESM(require("node:http"));
110
+ var import_node_url = require("node:url");
111
+ var import_node_fs2 = __toESM(require("node:fs"));
112
+ var import_node_path2 = __toESM(require("node:path"));
113
+
114
+ // src/server/router.ts
115
+ var Router = class {
116
+ routes = [];
117
+ get(path2, handler) {
118
+ this.add("GET", path2, handler);
119
+ }
120
+ post(path2, handler) {
121
+ this.add("POST", path2, handler);
122
+ }
123
+ patch(path2, handler) {
124
+ this.add("PATCH", path2, handler);
125
+ }
126
+ put(path2, handler) {
127
+ this.add("PUT", path2, handler);
128
+ }
129
+ delete(path2, handler) {
130
+ this.add("DELETE", path2, handler);
131
+ }
132
+ add(method, path2, handler) {
133
+ const paramNames = [];
134
+ const patternStr = path2.replace(/:(\w+)/g, (_, name) => {
135
+ paramNames.push(name);
136
+ return "([^/]+)";
137
+ });
138
+ this.routes.push({
139
+ method,
140
+ pattern: new RegExp(`^${patternStr}$`),
141
+ paramNames,
142
+ handler
143
+ });
144
+ }
145
+ match(method, pathname) {
146
+ for (const route of this.routes) {
147
+ if (route.method !== method) continue;
148
+ const match = pathname.match(route.pattern);
149
+ if (match) {
150
+ const params = {};
151
+ route.paramNames.forEach((name, i) => {
152
+ params[name] = decodeURIComponent(match[i + 1]);
153
+ });
154
+ return { handler: route.handler, params };
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ };
160
+
161
+ // src/server/auth.ts
162
+ var import_node_crypto = __toESM(require("node:crypto"));
163
+ var import_node_fs = require("node:fs");
164
+ var import_node_path = require("node:path");
165
+ var serverToken = null;
166
+ function generateToken() {
167
+ serverToken = import_node_crypto.default.randomBytes(32).toString("hex");
168
+ return serverToken;
169
+ }
170
+ function getToken() {
171
+ if (!serverToken) throw new Error("Token not yet generated");
172
+ return serverToken;
173
+ }
174
+ function validateToken(authHeader) {
175
+ if (!serverToken) return false;
176
+ if (!authHeader?.startsWith("Bearer ")) return false;
177
+ const provided = Buffer.from(authHeader.slice(7));
178
+ const expected = Buffer.from(serverToken);
179
+ if (provided.length !== expected.length) return false;
180
+ return import_node_crypto.default.timingSafeEqual(provided, expected);
181
+ }
182
+ function writeTokenFile(dataDir, token, port) {
183
+ (0, import_node_fs.mkdirSync)(dataDir, { recursive: true });
184
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(dataDir, "api.token"), token, { mode: 384 });
185
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(dataDir, "api.port"), String(port));
186
+ }
187
+ var ALLOWED_REMOTE_ORIGINS = [
188
+ "https://app.quoroom.ai",
189
+ "https://quoroom-ai.github.io"
190
+ ];
191
+ function isAllowedOrigin(origin) {
192
+ if (!origin) return true;
193
+ try {
194
+ const url = new URL(origin);
195
+ if (url.hostname === "localhost" || url.hostname === "127.0.0.1") return true;
196
+ return ALLOWED_REMOTE_ORIGINS.includes(origin);
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ function setCorsHeaders(origin, headers) {
202
+ if (origin && isAllowedOrigin(origin)) {
203
+ headers["Access-Control-Allow-Origin"] = origin;
204
+ }
205
+ headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
206
+ headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization";
207
+ headers["Access-Control-Max-Age"] = "86400";
208
+ }
209
+
210
+ // src/shared/constants.ts
211
+ var DEFAULT_ROOM_CONFIG = {
212
+ threshold: "majority",
213
+ timeoutMinutes: 60,
214
+ keeperWeight: "dynamic",
215
+ tieBreaker: "queen",
216
+ autoApprove: ["low_impact"],
217
+ minCycleGapMs: 1e3
218
+ };
219
+
220
+ // src/shared/db-queries.ts
221
+ function createEntity(db2, name, type = "fact", category) {
222
+ const result = db2.prepare("INSERT INTO entities (name, type, category) VALUES (?, ?, ?)").run(name, type, category ?? null);
223
+ return getEntity(db2, result.lastInsertRowid);
224
+ }
225
+ function getEntity(db2, id) {
226
+ const row = db2.prepare("SELECT * FROM entities WHERE id = ?").get(id);
227
+ return row ?? null;
228
+ }
229
+ function listEntities(db2, category) {
230
+ if (category) {
231
+ return db2.prepare("SELECT * FROM entities WHERE category = ? ORDER BY updated_at DESC").all(category);
232
+ }
233
+ return db2.prepare("SELECT * FROM entities ORDER BY updated_at DESC").all();
234
+ }
235
+ function updateEntity(db2, id, updates) {
236
+ const fields = [];
237
+ const values = [];
238
+ if (updates.name !== void 0) {
239
+ fields.push("name = ?");
240
+ values.push(updates.name);
241
+ }
242
+ if (updates.type !== void 0) {
243
+ fields.push("type = ?");
244
+ values.push(updates.type);
245
+ }
246
+ if (updates.category !== void 0) {
247
+ fields.push("category = ?");
248
+ values.push(updates.category);
249
+ }
250
+ if (fields.length === 0) return;
251
+ fields.push("updated_at = datetime('now','localtime')");
252
+ values.push(id);
253
+ db2.prepare(`UPDATE entities SET ${fields.join(", ")} WHERE id = ?`).run(...values);
254
+ }
255
+ function deleteEntity(db2, id) {
256
+ db2.prepare("DELETE FROM entities WHERE id = ?").run(id);
257
+ }
258
+ function searchEntities(db2, query) {
259
+ try {
260
+ const ftsResults = db2.prepare(
261
+ `SELECT e.* FROM entities e
262
+ INNER JOIN memory_fts fts ON e.id = fts.rowid
263
+ WHERE memory_fts MATCH ?
264
+ ORDER BY rank`
265
+ ).all(query);
266
+ if (ftsResults.length > 0) return ftsResults;
267
+ } catch {
268
+ }
269
+ const escaped = query.replace(/[%_]/g, "\\$&");
270
+ return db2.prepare("SELECT * FROM entities WHERE name LIKE ? ESCAPE '\\' OR category LIKE ? ESCAPE '\\' ORDER BY updated_at DESC").all(`%${escaped}%`, `%${escaped}%`);
271
+ }
272
+ function addObservation(db2, entityId, content, source = "claude") {
273
+ const result = db2.prepare("INSERT INTO observations (entity_id, content, source) VALUES (?, ?, ?)").run(entityId, content, source);
274
+ db2.prepare("UPDATE entities SET embedded_at = NULL, updated_at = datetime('now','localtime') WHERE id = ?").run(entityId);
275
+ return getObservation(db2, result.lastInsertRowid);
276
+ }
277
+ function getObservation(db2, id) {
278
+ const row = db2.prepare("SELECT * FROM observations WHERE id = ?").get(id);
279
+ return row ?? null;
280
+ }
281
+ function getObservations(db2, entityId) {
282
+ return db2.prepare("SELECT * FROM observations WHERE entity_id = ? ORDER BY id DESC").all(entityId);
283
+ }
284
+ function deleteObservation(db2, id) {
285
+ db2.prepare("DELETE FROM observations WHERE id = ?").run(id);
286
+ }
287
+ function addRelation(db2, fromEntity, toEntity, relationType) {
288
+ const result = db2.prepare("INSERT INTO relations (from_entity, to_entity, relation_type) VALUES (?, ?, ?)").run(fromEntity, toEntity, relationType);
289
+ return getRelation(db2, result.lastInsertRowid);
290
+ }
291
+ function getRelation(db2, id) {
292
+ const row = db2.prepare("SELECT * FROM relations WHERE id = ?").get(id);
293
+ return row ?? null;
294
+ }
295
+ function getRelations(db2, entityId) {
296
+ return db2.prepare("SELECT * FROM relations WHERE from_entity = ? OR to_entity = ? ORDER BY created_at DESC").all(entityId, entityId);
297
+ }
298
+ function deleteRelation(db2, id) {
299
+ db2.prepare("DELETE FROM relations WHERE id = ?").run(id);
300
+ }
301
+ function getMemoryStats(db2) {
302
+ const entities = db2.prepare("SELECT COUNT(*) as count FROM entities").get();
303
+ const observations = db2.prepare("SELECT COUNT(*) as count FROM observations").get();
304
+ const relations = db2.prepare("SELECT COUNT(*) as count FROM relations").get();
305
+ return {
306
+ entityCount: entities.count,
307
+ observationCount: observations.count,
308
+ relationCount: relations.count
309
+ };
310
+ }
311
+ function createWorker(db2, input) {
312
+ if (input.isDefault) {
313
+ db2.prepare("UPDATE workers SET is_default = 0 WHERE is_default = 1").run();
314
+ }
315
+ const result = db2.prepare(
316
+ `INSERT INTO workers (name, role, system_prompt, description, model, is_default, room_id, agent_state)
317
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
318
+ ).run(input.name, input.role ?? null, input.systemPrompt, input.description ?? null, input.model ?? null, input.isDefault ? 1 : 0, input.roomId ?? null, input.agentState ?? "idle");
319
+ return getWorker(db2, result.lastInsertRowid);
320
+ }
321
+ function getWorker(db2, id) {
322
+ const row = db2.prepare("SELECT * FROM workers WHERE id = ?").get(id);
323
+ return row ? mapWorkerRow(row) : null;
324
+ }
325
+ function listWorkers(db2) {
326
+ const rows = db2.prepare("SELECT * FROM workers ORDER BY is_default DESC, name ASC").all();
327
+ return rows.map(mapWorkerRow);
328
+ }
329
+ function updateWorker(db2, id, updates) {
330
+ if (updates.isDefault === true) {
331
+ db2.prepare("UPDATE workers SET is_default = 0 WHERE is_default = 1").run();
332
+ }
333
+ const fieldMap = {
334
+ name: "name",
335
+ role: "role",
336
+ systemPrompt: "system_prompt",
337
+ description: "description",
338
+ model: "model",
339
+ isDefault: "is_default",
340
+ roomId: "room_id",
341
+ agentState: "agent_state"
342
+ };
343
+ const fields = [];
344
+ const values = [];
345
+ for (const [key, value] of Object.entries(updates)) {
346
+ const dbField = fieldMap[key];
347
+ if (dbField) {
348
+ fields.push(`${dbField} = ?`);
349
+ values.push(key === "isDefault" ? value ? 1 : 0 : value);
350
+ }
351
+ }
352
+ if (fields.length === 0) return;
353
+ fields.push("updated_at = datetime('now','localtime')");
354
+ values.push(id);
355
+ db2.prepare(`UPDATE workers SET ${fields.join(", ")} WHERE id = ?`).run(...values);
356
+ }
357
+ function deleteWorker(db2, id) {
358
+ db2.prepare("DELETE FROM workers WHERE id = ?").run(id);
359
+ }
360
+ function refreshWorkerTaskCount(db2, workerId) {
361
+ const row = db2.prepare("SELECT COUNT(*) as count FROM tasks WHERE worker_id = ?").get(workerId);
362
+ db2.prepare("UPDATE workers SET task_count = ? WHERE id = ?").run(row.count, workerId);
363
+ }
364
+ function mapWorkerRow(row) {
365
+ return {
366
+ id: row.id,
367
+ name: row.name,
368
+ role: row.role ?? null,
369
+ systemPrompt: row.system_prompt,
370
+ description: row.description,
371
+ model: row.model,
372
+ isDefault: row.is_default === 1,
373
+ taskCount: row.task_count ?? 0,
374
+ roomId: row.room_id ?? null,
375
+ agentState: row.agent_state ?? "idle",
376
+ createdAt: row.created_at,
377
+ updatedAt: row.updated_at
378
+ };
379
+ }
380
+ function createTask(db2, input) {
381
+ const result = db2.prepare(
382
+ `INSERT INTO tasks (name, description, prompt, cron_expression, trigger_type, trigger_config, scheduled_at, executor, max_runs, worker_id, session_continuity, timeout_minutes, max_turns, allowed_tools, disallowed_tools, nudge_mode)
383
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
384
+ ).run(
385
+ input.name,
386
+ input.description ?? null,
387
+ input.prompt,
388
+ input.cronExpression ?? null,
389
+ input.triggerType ?? "cron",
390
+ input.triggerConfig ?? null,
391
+ input.scheduledAt ?? null,
392
+ input.executor ?? "claude_code",
393
+ input.maxRuns ?? null,
394
+ input.workerId ?? null,
395
+ input.sessionContinuity ? 1 : 0,
396
+ input.timeoutMinutes ?? null,
397
+ input.maxTurns ?? null,
398
+ input.allowedTools ?? null,
399
+ input.disallowedTools ?? null,
400
+ input.nudgeMode ?? "always"
401
+ );
402
+ const task = getTask(db2, result.lastInsertRowid);
403
+ if (input.workerId) refreshWorkerTaskCount(db2, input.workerId);
404
+ return task;
405
+ }
406
+ function getTask(db2, id) {
407
+ const row = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
408
+ return row ? mapTaskRow(row) : null;
409
+ }
410
+ function listTasks(db2, status) {
411
+ const rows = status ? db2.prepare("SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC").all(status) : db2.prepare("SELECT * FROM tasks ORDER BY created_at DESC").all();
412
+ return rows.map(mapTaskRow);
413
+ }
414
+ function updateTask(db2, id, updates) {
415
+ const fieldMap = {
416
+ name: "name",
417
+ description: "description",
418
+ prompt: "prompt",
419
+ cronExpression: "cron_expression",
420
+ triggerType: "trigger_type",
421
+ triggerConfig: "trigger_config",
422
+ scheduledAt: "scheduled_at",
423
+ executor: "executor",
424
+ status: "status",
425
+ lastRun: "last_run",
426
+ lastResult: "last_result",
427
+ errorCount: "error_count",
428
+ maxRuns: "max_runs",
429
+ runCount: "run_count",
430
+ memoryEntityId: "memory_entity_id",
431
+ workerId: "worker_id",
432
+ sessionContinuity: "session_continuity",
433
+ sessionId: "session_id",
434
+ timeoutMinutes: "timeout_minutes",
435
+ maxTurns: "max_turns",
436
+ allowedTools: "allowed_tools",
437
+ disallowedTools: "disallowed_tools",
438
+ nudgeMode: "nudge_mode",
439
+ learnedContext: "learned_context"
440
+ };
441
+ const fields = [];
442
+ const values = [];
443
+ for (const [key, value] of Object.entries(updates)) {
444
+ const dbField = fieldMap[key];
445
+ if (dbField) {
446
+ fields.push(`${dbField} = ?`);
447
+ values.push(key === "sessionContinuity" ? value ? 1 : 0 : value);
448
+ }
449
+ }
450
+ if (fields.length === 0) return;
451
+ fields.push("updated_at = datetime('now','localtime')");
452
+ values.push(id);
453
+ db2.prepare(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`).run(...values);
454
+ }
455
+ function deleteTask(db2, id) {
456
+ const task = getTask(db2, id);
457
+ db2.prepare("DELETE FROM tasks WHERE id = ?").run(id);
458
+ if (task?.workerId) refreshWorkerTaskCount(db2, task.workerId);
459
+ }
460
+ function pauseTask(db2, id) {
461
+ updateTask(db2, id, { status: "paused" });
462
+ }
463
+ function resumeTask(db2, id) {
464
+ updateTask(db2, id, { status: "active" });
465
+ }
466
+ function createWatch(db2, path2, description, actionPrompt) {
467
+ const result = db2.prepare("INSERT INTO watches (path, description, action_prompt) VALUES (?, ?, ?)").run(path2, description ?? null, actionPrompt ?? null);
468
+ return getWatch(db2, result.lastInsertRowid);
469
+ }
470
+ function getWatch(db2, id) {
471
+ const row = db2.prepare("SELECT * FROM watches WHERE id = ?").get(id);
472
+ return row ? mapWatchRow(row) : null;
473
+ }
474
+ function listWatches(db2, status) {
475
+ const rows = status ? db2.prepare("SELECT * FROM watches WHERE status = ? ORDER BY created_at DESC").all(status) : db2.prepare("SELECT * FROM watches ORDER BY created_at DESC").all();
476
+ return rows.map(mapWatchRow);
477
+ }
478
+ function deleteWatch(db2, id) {
479
+ db2.prepare("DELETE FROM watches WHERE id = ?").run(id);
480
+ }
481
+ function pauseWatch(db2, id) {
482
+ db2.prepare("UPDATE watches SET status = 'paused' WHERE id = ?").run(id);
483
+ }
484
+ function resumeWatch(db2, id) {
485
+ db2.prepare("UPDATE watches SET status = 'active' WHERE id = ?").run(id);
486
+ }
487
+ function getSetting(db2, key) {
488
+ const row = db2.prepare("SELECT value FROM settings WHERE key = ?").get(key);
489
+ return row?.value ?? null;
490
+ }
491
+ function setSetting(db2, key, value) {
492
+ db2.prepare(
493
+ `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now','localtime'))
494
+ ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now','localtime')`
495
+ ).run(key, value, value);
496
+ }
497
+ function getAllSettings(db2) {
498
+ const rows = db2.prepare("SELECT key, value FROM settings").all();
499
+ const result = {};
500
+ for (const row of rows) result[row.key] = row.value;
501
+ return result;
502
+ }
503
+ function createTaskRun(db2, taskId) {
504
+ const result = db2.prepare("INSERT INTO task_runs (task_id, started_at) VALUES (?, datetime('now','localtime'))").run(taskId);
505
+ return getTaskRun(db2, result.lastInsertRowid);
506
+ }
507
+ function getTaskRun(db2, id) {
508
+ const row = db2.prepare("SELECT * FROM task_runs WHERE id = ?").get(id);
509
+ return row ? mapTaskRunRow(row) : null;
510
+ }
511
+ function getTaskRuns(db2, taskId, limit = 20) {
512
+ const rows = db2.prepare("SELECT * FROM task_runs WHERE task_id = ? ORDER BY started_at DESC LIMIT ?").all(taskId, limit);
513
+ return rows.map(mapTaskRunRow);
514
+ }
515
+ function listAllRuns(db2, limit = 20) {
516
+ const rows = db2.prepare("SELECT * FROM task_runs ORDER BY started_at DESC LIMIT ?").all(limit);
517
+ return rows.map(mapTaskRunRow);
518
+ }
519
+ function cleanupAllRunningRuns(db2) {
520
+ const result = db2.prepare(`
521
+ UPDATE task_runs SET
522
+ status = 'failed',
523
+ finished_at = datetime('now','localtime'),
524
+ error_message = 'Stale run: process restarted'
525
+ WHERE status = 'running'
526
+ `).run();
527
+ return result.changes;
528
+ }
529
+ var PRUNE_INTERVAL_MS = 60 * 6e4;
530
+ function getConsoleLogs(db2, runId, afterSeq = 0, limit = 100) {
531
+ const rows = db2.prepare("SELECT * FROM console_logs WHERE run_id = ? AND seq > ? ORDER BY seq ASC LIMIT ?").all(runId, afterSeq, limit);
532
+ return rows.map(mapConsoleLogRow);
533
+ }
534
+ function mapConsoleLogRow(row) {
535
+ return {
536
+ id: row.id,
537
+ runId: row.run_id,
538
+ seq: row.seq,
539
+ entryType: row.entry_type,
540
+ content: row.content,
541
+ createdAt: row.created_at
542
+ };
543
+ }
544
+ function mapTaskRow(row) {
545
+ return {
546
+ id: row.id,
547
+ name: row.name,
548
+ description: row.description,
549
+ prompt: row.prompt,
550
+ cronExpression: row.cron_expression,
551
+ triggerType: row.trigger_type,
552
+ triggerConfig: row.trigger_config,
553
+ scheduledAt: row.scheduled_at,
554
+ executor: row.executor,
555
+ status: row.status,
556
+ lastRun: row.last_run,
557
+ lastResult: row.last_result,
558
+ errorCount: row.error_count,
559
+ maxRuns: row.max_runs,
560
+ runCount: row.run_count ?? 0,
561
+ memoryEntityId: row.memory_entity_id,
562
+ workerId: row.worker_id,
563
+ sessionContinuity: row.session_continuity === 1,
564
+ sessionId: row.session_id,
565
+ timeoutMinutes: row.timeout_minutes,
566
+ maxTurns: row.max_turns,
567
+ allowedTools: row.allowed_tools,
568
+ disallowedTools: row.disallowed_tools,
569
+ nudgeMode: row.nudge_mode ?? "always",
570
+ learnedContext: row.learned_context,
571
+ createdAt: row.created_at,
572
+ updatedAt: row.updated_at
573
+ };
574
+ }
575
+ function mapWatchRow(row) {
576
+ return {
577
+ id: row.id,
578
+ path: row.path,
579
+ description: row.description,
580
+ actionPrompt: row.action_prompt,
581
+ status: row.status,
582
+ lastTriggered: row.last_triggered,
583
+ triggerCount: row.trigger_count,
584
+ createdAt: row.created_at
585
+ };
586
+ }
587
+ function mapTaskRunRow(row) {
588
+ return {
589
+ id: row.id,
590
+ taskId: row.task_id,
591
+ startedAt: row.started_at,
592
+ finishedAt: row.finished_at,
593
+ status: row.status,
594
+ result: row.result,
595
+ resultFile: row.result_file,
596
+ errorMessage: row.error_message,
597
+ durationMs: row.duration_ms,
598
+ progress: row.progress,
599
+ progressMessage: row.progress_message,
600
+ sessionId: row.session_id
601
+ };
602
+ }
603
+ function clearTaskSession(db2, taskId) {
604
+ db2.prepare("UPDATE tasks SET session_id = NULL, updated_at = datetime('now','localtime') WHERE id = ?").run(taskId);
605
+ }
606
+ function mapRoomRow(row) {
607
+ let config = { ...DEFAULT_ROOM_CONFIG };
608
+ try {
609
+ if (row.config) config = { ...DEFAULT_ROOM_CONFIG, ...JSON.parse(row.config) };
610
+ } catch {
611
+ }
612
+ return {
613
+ id: row.id,
614
+ name: row.name,
615
+ queenWorkerId: row.queen_worker_id ?? null,
616
+ goal: row.goal ?? null,
617
+ status: row.status,
618
+ visibility: row.visibility,
619
+ config,
620
+ createdAt: row.created_at,
621
+ updatedAt: row.updated_at
622
+ };
623
+ }
624
+ function createRoom(db2, name, goal, config) {
625
+ const configJson = config ? JSON.stringify({ ...DEFAULT_ROOM_CONFIG, ...config }) : JSON.stringify(DEFAULT_ROOM_CONFIG);
626
+ const result = db2.prepare("INSERT INTO rooms (name, goal, config) VALUES (?, ?, ?)").run(name, goal ?? null, configJson);
627
+ return getRoom(db2, result.lastInsertRowid);
628
+ }
629
+ function getRoom(db2, id) {
630
+ const row = db2.prepare("SELECT * FROM rooms WHERE id = ?").get(id);
631
+ return row ? mapRoomRow(row) : null;
632
+ }
633
+ function listRooms(db2, status) {
634
+ if (status) {
635
+ const rows2 = db2.prepare("SELECT * FROM rooms WHERE status = ? ORDER BY created_at DESC").all(status);
636
+ return rows2.map(mapRoomRow);
637
+ }
638
+ const rows = db2.prepare("SELECT * FROM rooms ORDER BY created_at DESC").all();
639
+ return rows.map(mapRoomRow);
640
+ }
641
+ function updateRoom(db2, id, updates) {
642
+ const fieldMap = {
643
+ name: "name",
644
+ queenWorkerId: "queen_worker_id",
645
+ goal: "goal",
646
+ status: "status",
647
+ visibility: "visibility",
648
+ config: "config"
649
+ };
650
+ const fields = [];
651
+ const values = [];
652
+ for (const [key, value] of Object.entries(updates)) {
653
+ const dbField = fieldMap[key];
654
+ if (dbField) {
655
+ fields.push(`${dbField} = ?`);
656
+ values.push(key === "config" ? JSON.stringify(value) : value);
657
+ }
658
+ }
659
+ if (fields.length === 0) return;
660
+ fields.push("updated_at = datetime('now','localtime')");
661
+ values.push(id);
662
+ db2.prepare(`UPDATE rooms SET ${fields.join(", ")} WHERE id = ?`).run(...values);
663
+ }
664
+ function deleteRoom(db2, id) {
665
+ db2.prepare("DELETE FROM rooms WHERE id = ?").run(id);
666
+ }
667
+ function mapRoomActivityRow(row) {
668
+ return {
669
+ id: row.id,
670
+ roomId: row.room_id,
671
+ eventType: row.event_type,
672
+ actorId: row.actor_id ?? null,
673
+ summary: row.summary,
674
+ details: row.details ?? null,
675
+ isPublic: row.is_public === 1,
676
+ createdAt: row.created_at
677
+ };
678
+ }
679
+ function logRoomActivity(db2, roomId, eventType, summary, details, actorId, isPublic = true) {
680
+ const result = db2.prepare("INSERT INTO room_activity (room_id, event_type, actor_id, summary, details, is_public) VALUES (?, ?, ?, ?, ?, ?)").run(roomId, eventType, actorId ?? null, summary, details ?? null, isPublic ? 1 : 0);
681
+ const row = db2.prepare("SELECT * FROM room_activity WHERE id = ?").get(result.lastInsertRowid);
682
+ return mapRoomActivityRow(row);
683
+ }
684
+ function getRoomActivity(db2, roomId, limit = 50, eventTypes) {
685
+ if (eventTypes && eventTypes.length > 0) {
686
+ const placeholders = eventTypes.map(() => "?").join(", ");
687
+ const rows2 = db2.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC LIMIT ?`).all(roomId, ...eventTypes, limit);
688
+ return rows2.map(mapRoomActivityRow);
689
+ }
690
+ const rows = db2.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC LIMIT ?").all(roomId, limit);
691
+ return rows.map(mapRoomActivityRow);
692
+ }
693
+ function mapDecisionRow(row) {
694
+ return {
695
+ id: row.id,
696
+ roomId: row.room_id,
697
+ proposerId: row.proposer_id ?? null,
698
+ proposal: row.proposal,
699
+ decisionType: row.decision_type,
700
+ status: row.status,
701
+ result: row.result ?? null,
702
+ threshold: row.threshold,
703
+ timeoutAt: row.timeout_at ?? null,
704
+ createdAt: row.created_at,
705
+ resolvedAt: row.resolved_at ?? null
706
+ };
707
+ }
708
+ function createDecision(db2, roomId, proposerId, proposal, decisionType, threshold = "majority", timeoutAt) {
709
+ const result = db2.prepare("INSERT INTO quorum_decisions (room_id, proposer_id, proposal, decision_type, threshold, timeout_at) VALUES (?, ?, ?, ?, ?, ?)").run(roomId, proposerId, proposal, decisionType, threshold, timeoutAt ?? null);
710
+ return getDecision(db2, result.lastInsertRowid);
711
+ }
712
+ function getDecision(db2, id) {
713
+ const row = db2.prepare("SELECT * FROM quorum_decisions WHERE id = ?").get(id);
714
+ return row ? mapDecisionRow(row) : null;
715
+ }
716
+ function listDecisions(db2, roomId, status) {
717
+ if (status) {
718
+ const rows2 = db2.prepare("SELECT * FROM quorum_decisions WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
719
+ return rows2.map(mapDecisionRow);
720
+ }
721
+ const rows = db2.prepare("SELECT * FROM quorum_decisions WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
722
+ return rows.map(mapDecisionRow);
723
+ }
724
+ function resolveDecision(db2, id, status, result) {
725
+ db2.prepare("UPDATE quorum_decisions SET status = ?, result = ?, resolved_at = datetime('now','localtime') WHERE id = ?").run(status, result ?? null, id);
726
+ }
727
+ function mapVoteRow(row) {
728
+ return {
729
+ id: row.id,
730
+ decisionId: row.decision_id,
731
+ workerId: row.worker_id,
732
+ vote: row.vote,
733
+ reasoning: row.reasoning ?? null,
734
+ createdAt: row.created_at
735
+ };
736
+ }
737
+ function castVote(db2, decisionId, workerId, vote, reasoning) {
738
+ const result = db2.prepare("INSERT INTO quorum_votes (decision_id, worker_id, vote, reasoning) VALUES (?, ?, ?, ?)").run(decisionId, workerId, vote, reasoning ?? null);
739
+ const row = db2.prepare("SELECT * FROM quorum_votes WHERE id = ?").get(result.lastInsertRowid);
740
+ return mapVoteRow(row);
741
+ }
742
+ function getVotes(db2, decisionId) {
743
+ const rows = db2.prepare("SELECT * FROM quorum_votes WHERE decision_id = ? ORDER BY created_at ASC").all(decisionId);
744
+ return rows.map(mapVoteRow);
745
+ }
746
+ function mapGoalRow(row) {
747
+ return {
748
+ id: row.id,
749
+ roomId: row.room_id,
750
+ description: row.description,
751
+ status: row.status,
752
+ parentGoalId: row.parent_goal_id ?? null,
753
+ assignedWorkerId: row.assigned_worker_id ?? null,
754
+ progress: row.progress ?? 0,
755
+ createdAt: row.created_at,
756
+ updatedAt: row.updated_at
757
+ };
758
+ }
759
+ function createGoal(db2, roomId, description, parentGoalId, assignedWorkerId) {
760
+ const result = db2.prepare("INSERT INTO goals (room_id, description, parent_goal_id, assigned_worker_id) VALUES (?, ?, ?, ?)").run(roomId, description, parentGoalId ?? null, assignedWorkerId ?? null);
761
+ return getGoal(db2, result.lastInsertRowid);
762
+ }
763
+ function getGoal(db2, id) {
764
+ const row = db2.prepare("SELECT * FROM goals WHERE id = ?").get(id);
765
+ return row ? mapGoalRow(row) : null;
766
+ }
767
+ function listGoals(db2, roomId, status) {
768
+ if (status) {
769
+ const rows2 = db2.prepare("SELECT * FROM goals WHERE room_id = ? AND status = ? ORDER BY created_at ASC").all(roomId, status);
770
+ return rows2.map(mapGoalRow);
771
+ }
772
+ const rows = db2.prepare("SELECT * FROM goals WHERE room_id = ? ORDER BY created_at ASC").all(roomId);
773
+ return rows.map(mapGoalRow);
774
+ }
775
+ function getSubGoals(db2, parentGoalId) {
776
+ const rows = db2.prepare("SELECT * FROM goals WHERE parent_goal_id = ? ORDER BY created_at ASC").all(parentGoalId);
777
+ return rows.map(mapGoalRow);
778
+ }
779
+ function updateGoal(db2, id, updates) {
780
+ const fieldMap = {
781
+ description: "description",
782
+ status: "status",
783
+ assignedWorkerId: "assigned_worker_id",
784
+ progress: "progress"
785
+ };
786
+ const fields = [];
787
+ const values = [];
788
+ for (const [key, value] of Object.entries(updates)) {
789
+ const dbField = fieldMap[key];
790
+ if (dbField) {
791
+ fields.push(`${dbField} = ?`);
792
+ values.push(value);
793
+ }
794
+ }
795
+ if (fields.length === 0) return;
796
+ fields.push("updated_at = datetime('now','localtime')");
797
+ values.push(id);
798
+ db2.prepare(`UPDATE goals SET ${fields.join(", ")} WHERE id = ?`).run(...values);
799
+ }
800
+ function deleteGoal(db2, id) {
801
+ db2.prepare("DELETE FROM goals WHERE id = ?").run(id);
802
+ }
803
+ function mapGoalUpdateRow(row) {
804
+ return {
805
+ id: row.id,
806
+ goalId: row.goal_id,
807
+ workerId: row.worker_id ?? null,
808
+ observation: row.observation,
809
+ metricValue: row.metric_value ?? null,
810
+ createdAt: row.created_at
811
+ };
812
+ }
813
+ function logGoalUpdate(db2, goalId, observation, metricValue, workerId) {
814
+ const result = db2.prepare("INSERT INTO goal_updates (goal_id, worker_id, observation, metric_value) VALUES (?, ?, ?, ?)").run(goalId, workerId ?? null, observation, metricValue ?? null);
815
+ const row = db2.prepare("SELECT * FROM goal_updates WHERE id = ?").get(result.lastInsertRowid);
816
+ return mapGoalUpdateRow(row);
817
+ }
818
+ function getGoalUpdates(db2, goalId, limit = 50) {
819
+ const rows = db2.prepare("SELECT * FROM goal_updates WHERE goal_id = ? ORDER BY created_at DESC LIMIT ?").all(goalId, limit);
820
+ return rows.map(mapGoalUpdateRow);
821
+ }
822
+ function mapSkillRow(row) {
823
+ let activationContext = null;
824
+ try {
825
+ if (row.activation_context) activationContext = JSON.parse(row.activation_context);
826
+ } catch {
827
+ }
828
+ return {
829
+ id: row.id,
830
+ roomId: row.room_id ?? null,
831
+ name: row.name,
832
+ content: row.content,
833
+ activationContext,
834
+ autoActivate: row.auto_activate === 1,
835
+ agentCreated: row.agent_created === 1,
836
+ createdByWorkerId: row.created_by_worker_id ?? null,
837
+ version: row.version ?? 1,
838
+ createdAt: row.created_at,
839
+ updatedAt: row.updated_at
840
+ };
841
+ }
842
+ function createSkill(db2, roomId, name, content, opts) {
843
+ const result = db2.prepare("INSERT INTO skills (room_id, name, content, activation_context, auto_activate, agent_created, created_by_worker_id) VALUES (?, ?, ?, ?, ?, ?, ?)").run(
844
+ roomId,
845
+ name,
846
+ content,
847
+ opts?.activationContext ? JSON.stringify(opts.activationContext) : null,
848
+ opts?.autoActivate ? 1 : 0,
849
+ opts?.agentCreated ? 1 : 0,
850
+ opts?.createdByWorkerId ?? null
851
+ );
852
+ return getSkill(db2, result.lastInsertRowid);
853
+ }
854
+ function getSkill(db2, id) {
855
+ const row = db2.prepare("SELECT * FROM skills WHERE id = ?").get(id);
856
+ return row ? mapSkillRow(row) : null;
857
+ }
858
+ function listSkills(db2, roomId) {
859
+ if (roomId != null) {
860
+ const rows2 = db2.prepare("SELECT * FROM skills WHERE room_id = ? ORDER BY name ASC").all(roomId);
861
+ return rows2.map(mapSkillRow);
862
+ }
863
+ const rows = db2.prepare("SELECT * FROM skills ORDER BY name ASC").all();
864
+ return rows.map(mapSkillRow);
865
+ }
866
+ function updateSkill(db2, id, updates) {
867
+ const fields = [];
868
+ const values = [];
869
+ if (updates.name !== void 0) {
870
+ fields.push("name = ?");
871
+ values.push(updates.name);
872
+ }
873
+ if (updates.content !== void 0) {
874
+ fields.push("content = ?");
875
+ values.push(updates.content);
876
+ }
877
+ if (updates.activationContext !== void 0) {
878
+ fields.push("activation_context = ?");
879
+ values.push(updates.activationContext ? JSON.stringify(updates.activationContext) : null);
880
+ }
881
+ if (updates.autoActivate !== void 0) {
882
+ fields.push("auto_activate = ?");
883
+ values.push(updates.autoActivate ? 1 : 0);
884
+ }
885
+ if (updates.version !== void 0) {
886
+ fields.push("version = ?");
887
+ values.push(updates.version);
888
+ }
889
+ if (fields.length === 0) return;
890
+ fields.push("updated_at = datetime('now','localtime')");
891
+ values.push(id);
892
+ db2.prepare(`UPDATE skills SET ${fields.join(", ")} WHERE id = ?`).run(...values);
893
+ }
894
+ function deleteSkill(db2, id) {
895
+ db2.prepare("DELETE FROM skills WHERE id = ?").run(id);
896
+ }
897
+ function mapSelfModRow(row) {
898
+ return {
899
+ id: row.id,
900
+ roomId: row.room_id ?? null,
901
+ workerId: row.worker_id ?? null,
902
+ filePath: row.file_path,
903
+ oldHash: row.old_hash ?? null,
904
+ newHash: row.new_hash ?? null,
905
+ reason: row.reason ?? null,
906
+ reversible: row.reversible === 1,
907
+ reverted: row.reverted === 1,
908
+ createdAt: row.created_at
909
+ };
910
+ }
911
+ function getSelfModHistory(db2, roomId, limit = 50) {
912
+ const rows = db2.prepare("SELECT * FROM self_mod_audit WHERE room_id = ? ORDER BY created_at DESC LIMIT ?").all(roomId, limit);
913
+ return rows.map(mapSelfModRow);
914
+ }
915
+ function mapEscalationRow(row) {
916
+ return {
917
+ id: row.id,
918
+ roomId: row.room_id,
919
+ fromAgentId: row.from_agent_id ?? null,
920
+ toAgentId: row.to_agent_id ?? null,
921
+ question: row.question,
922
+ answer: row.answer ?? null,
923
+ status: row.status,
924
+ createdAt: row.created_at,
925
+ resolvedAt: row.resolved_at ?? null
926
+ };
927
+ }
928
+ function createEscalation(db2, roomId, fromAgentId, question, toAgentId) {
929
+ const result = db2.prepare("INSERT INTO escalations (room_id, from_agent_id, to_agent_id, question) VALUES (?, ?, ?, ?)").run(roomId, fromAgentId, toAgentId ?? null, question);
930
+ return getEscalation(db2, result.lastInsertRowid);
931
+ }
932
+ function getEscalation(db2, id) {
933
+ const row = db2.prepare("SELECT * FROM escalations WHERE id = ?").get(id);
934
+ return row ? mapEscalationRow(row) : null;
935
+ }
936
+ function getPendingEscalations(db2, roomId, toAgentId) {
937
+ if (toAgentId != null) {
938
+ const rows2 = db2.prepare("SELECT * FROM escalations WHERE room_id = ? AND status = 'pending' AND (to_agent_id = ? OR to_agent_id IS NULL) ORDER BY created_at ASC").all(roomId, toAgentId);
939
+ return rows2.map(mapEscalationRow);
940
+ }
941
+ const rows = db2.prepare("SELECT * FROM escalations WHERE room_id = ? AND status = 'pending' ORDER BY created_at ASC").all(roomId);
942
+ return rows.map(mapEscalationRow);
943
+ }
944
+ function resolveEscalation(db2, id, answer) {
945
+ db2.prepare("UPDATE escalations SET answer = ?, status = 'resolved', resolved_at = datetime('now','localtime') WHERE id = ?").run(answer, id);
946
+ }
947
+ function listRoomWorkers(db2, roomId) {
948
+ const rows = db2.prepare("SELECT * FROM workers WHERE room_id = ? ORDER BY name ASC").all(roomId);
949
+ return rows.map(mapWorkerRow);
950
+ }
951
+ function updateAgentState(db2, workerId, state) {
952
+ db2.prepare("UPDATE workers SET agent_state = ?, updated_at = datetime('now','localtime') WHERE id = ?").run(state, workerId);
953
+ }
954
+
955
+ // src/shared/goals.ts
956
+ function setRoomObjective(db2, roomId, description) {
957
+ return createGoal(db2, roomId, description);
958
+ }
959
+
960
+ // src/shared/room.ts
961
+ var DEFAULT_QUEEN_SYSTEM_PROMPT = `You are the Queen agent of this Room \u2014 the strategic coordinator.
962
+ Your role is to pursue the room's objectives by:
963
+ - Decomposing goals into actionable sub-goals
964
+ - Creating and delegating to worker agents
965
+ - Proposing decisions to the quorum
966
+ - Self-improving your strategies and skills based on results
967
+ - Managing resources efficiently
968
+
969
+ You have access to all room MCP tools. Use them to manage goals, workers, skills, and decisions.`;
970
+ function createRoom2(db2, input) {
971
+ const config = { ...DEFAULT_ROOM_CONFIG, ...input.config };
972
+ const room = createRoom(db2, input.name, input.goal, config);
973
+ const queen = createWorker(db2, {
974
+ name: `${input.name} Queen`,
975
+ systemPrompt: input.queenSystemPrompt ?? DEFAULT_QUEEN_SYSTEM_PROMPT,
976
+ roomId: room.id,
977
+ agentState: "idle"
978
+ });
979
+ updateRoom(db2, room.id, { queenWorkerId: queen.id });
980
+ let rootGoal = null;
981
+ if (input.goal) {
982
+ rootGoal = setRoomObjective(db2, room.id, input.goal);
983
+ }
984
+ logRoomActivity(
985
+ db2,
986
+ room.id,
987
+ "system",
988
+ `Room "${input.name}" created${input.goal ? ` with objective: ${input.goal}` : ""}`,
989
+ void 0,
990
+ queen.id
991
+ );
992
+ return {
993
+ room: getRoom(db2, room.id),
994
+ queen,
995
+ rootGoal
996
+ };
997
+ }
998
+ function pauseRoom(db2, roomId) {
999
+ const room = getRoom(db2, roomId);
1000
+ if (!room) throw new Error(`Room ${roomId} not found`);
1001
+ updateRoom(db2, roomId, { status: "paused" });
1002
+ const workers = listRoomWorkers(db2, roomId);
1003
+ for (const w of workers) {
1004
+ updateAgentState(db2, w.id, "idle");
1005
+ }
1006
+ logRoomActivity(db2, roomId, "system", "Room paused");
1007
+ }
1008
+ function restartRoom(db2, roomId, newGoal) {
1009
+ const room = getRoom(db2, roomId);
1010
+ if (!room) throw new Error(`Room ${roomId} not found`);
1011
+ db2.prepare("DELETE FROM goals WHERE room_id = ?").run(roomId);
1012
+ db2.prepare("DELETE FROM quorum_decisions WHERE room_id = ?").run(roomId);
1013
+ db2.prepare("DELETE FROM escalations WHERE room_id = ?").run(roomId);
1014
+ const workers = listRoomWorkers(db2, roomId);
1015
+ for (const w of workers) {
1016
+ updateAgentState(db2, w.id, "idle");
1017
+ }
1018
+ updateRoom(db2, roomId, { status: "active", goal: newGoal ?? room.goal });
1019
+ if (newGoal) {
1020
+ setRoomObjective(db2, roomId, newGoal);
1021
+ }
1022
+ logRoomActivity(
1023
+ db2,
1024
+ roomId,
1025
+ "system",
1026
+ `Room restarted${newGoal ? ` with new objective: ${newGoal}` : ""}`
1027
+ );
1028
+ }
1029
+ function deleteRoom2(db2, roomId) {
1030
+ const room = getRoom(db2, roomId);
1031
+ if (!room) throw new Error(`Room ${roomId} not found`);
1032
+ const workers = listRoomWorkers(db2, roomId);
1033
+ for (const w of workers) {
1034
+ deleteWorker(db2, w.id);
1035
+ }
1036
+ deleteRoom(db2, roomId);
1037
+ }
1038
+ function getRoomStatus(db2, roomId) {
1039
+ const room = getRoom(db2, roomId);
1040
+ if (!room) throw new Error(`Room ${roomId} not found`);
1041
+ const workers = listRoomWorkers(db2, roomId);
1042
+ const activeGoals = listGoals(db2, roomId).filter(
1043
+ (g) => g.status === "active" || g.status === "in_progress"
1044
+ );
1045
+ const pendingDecisions = listDecisions(db2, roomId, "voting").length;
1046
+ return { room, workers, activeGoals, pendingDecisions };
1047
+ }
1048
+
1049
+ // src/server/event-bus.ts
1050
+ var EventBus = class {
1051
+ handlers = /* @__PURE__ */ new Map();
1052
+ wildcardHandlers = /* @__PURE__ */ new Set();
1053
+ emit(channel, type, data) {
1054
+ const event = {
1055
+ type,
1056
+ channel,
1057
+ data,
1058
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1059
+ };
1060
+ const channelHandlers = this.handlers.get(channel);
1061
+ if (channelHandlers) {
1062
+ for (const handler of channelHandlers) handler(event);
1063
+ }
1064
+ for (const handler of this.wildcardHandlers) handler(event);
1065
+ }
1066
+ on(channel, handler) {
1067
+ let set = this.handlers.get(channel);
1068
+ if (!set) {
1069
+ set = /* @__PURE__ */ new Set();
1070
+ this.handlers.set(channel, set);
1071
+ }
1072
+ set.add(handler);
1073
+ return () => {
1074
+ set.delete(handler);
1075
+ if (set.size === 0) this.handlers.delete(channel);
1076
+ };
1077
+ }
1078
+ onAny(handler) {
1079
+ this.wildcardHandlers.add(handler);
1080
+ return () => {
1081
+ this.wildcardHandlers.delete(handler);
1082
+ };
1083
+ }
1084
+ /** Remove all handlers (useful for tests) */
1085
+ clear() {
1086
+ this.handlers.clear();
1087
+ this.wildcardHandlers.clear();
1088
+ }
1089
+ };
1090
+ var eventBus = new EventBus();
1091
+
1092
+ // src/server/routes/rooms.ts
1093
+ function registerRoomRoutes(router) {
1094
+ router.post("/api/rooms", (ctx) => {
1095
+ const { name, goal, queenSystemPrompt, config } = ctx.body || {};
1096
+ if (!name || typeof name !== "string") return { status: 400, error: "name is required" };
1097
+ const result = createRoom2(ctx.db, {
1098
+ name,
1099
+ goal,
1100
+ queenSystemPrompt,
1101
+ config
1102
+ });
1103
+ eventBus.emit(`room:${result.room.id}`, "room:created", result.room);
1104
+ return { status: 201, data: result };
1105
+ });
1106
+ router.get("/api/rooms", (ctx) => {
1107
+ const rooms = listRooms(ctx.db, ctx.query.status);
1108
+ return { data: rooms };
1109
+ });
1110
+ router.get("/api/rooms/:id", (ctx) => {
1111
+ const room = getRoom(ctx.db, Number(ctx.params.id));
1112
+ if (!room) return { status: 404, error: "Room not found" };
1113
+ return { data: room };
1114
+ });
1115
+ router.get("/api/rooms/:id/status", (ctx) => {
1116
+ try {
1117
+ const status = getRoomStatus(ctx.db, Number(ctx.params.id));
1118
+ return { data: status };
1119
+ } catch (e) {
1120
+ return { status: 404, error: e.message };
1121
+ }
1122
+ });
1123
+ router.get("/api/rooms/:id/activity", (ctx) => {
1124
+ const roomId = Number(ctx.params.id);
1125
+ const limit = ctx.query.limit ? Number(ctx.query.limit) : void 0;
1126
+ const activity = getRoomActivity(ctx.db, roomId, limit);
1127
+ return { data: activity };
1128
+ });
1129
+ router.post("/api/rooms/:id/pause", (ctx) => {
1130
+ const roomId = Number(ctx.params.id);
1131
+ try {
1132
+ pauseRoom(ctx.db, roomId);
1133
+ eventBus.emit(`room:${roomId}`, "room:paused", { roomId });
1134
+ return { data: { ok: true } };
1135
+ } catch (e) {
1136
+ return { status: 404, error: e.message };
1137
+ }
1138
+ });
1139
+ router.post("/api/rooms/:id/restart", (ctx) => {
1140
+ const roomId = Number(ctx.params.id);
1141
+ const { goal } = ctx.body || {};
1142
+ try {
1143
+ restartRoom(ctx.db, roomId, goal);
1144
+ eventBus.emit(`room:${roomId}`, "room:restarted", { roomId });
1145
+ return { data: { ok: true } };
1146
+ } catch (e) {
1147
+ return { status: 404, error: e.message };
1148
+ }
1149
+ });
1150
+ router.delete("/api/rooms/:id", (ctx) => {
1151
+ const roomId = Number(ctx.params.id);
1152
+ try {
1153
+ deleteRoom2(ctx.db, roomId);
1154
+ eventBus.emit(`room:${roomId}`, "room:deleted", { roomId });
1155
+ return { data: { ok: true } };
1156
+ } catch (e) {
1157
+ return { status: 404, error: e.message };
1158
+ }
1159
+ });
1160
+ }
1161
+
1162
+ // src/server/routes/workers.ts
1163
+ function registerWorkerRoutes(router) {
1164
+ router.post("/api/workers", (ctx) => {
1165
+ const body = ctx.body || {};
1166
+ if (!body.name || typeof body.name !== "string") return { status: 400, error: "name is required" };
1167
+ if (!body.systemPrompt || typeof body.systemPrompt !== "string") return { status: 400, error: "systemPrompt is required" };
1168
+ const worker = createWorker(ctx.db, {
1169
+ name: body.name,
1170
+ systemPrompt: body.systemPrompt,
1171
+ description: body.description,
1172
+ role: body.role,
1173
+ isDefault: body.isDefault,
1174
+ roomId: body.roomId,
1175
+ agentState: body.agentState
1176
+ });
1177
+ eventBus.emit("workers", "worker:created", worker);
1178
+ return { status: 201, data: worker };
1179
+ });
1180
+ router.get("/api/workers", (ctx) => {
1181
+ const workers = listWorkers(ctx.db);
1182
+ return { data: workers };
1183
+ });
1184
+ router.get("/api/workers/:id", (ctx) => {
1185
+ const worker = getWorker(ctx.db, Number(ctx.params.id));
1186
+ if (!worker) return { status: 404, error: "Worker not found" };
1187
+ return { data: worker };
1188
+ });
1189
+ router.patch("/api/workers/:id", (ctx) => {
1190
+ const id = Number(ctx.params.id);
1191
+ const worker = getWorker(ctx.db, id);
1192
+ if (!worker) return { status: 404, error: "Worker not found" };
1193
+ const body = ctx.body || {};
1194
+ updateWorker(ctx.db, id, body);
1195
+ const updated = getWorker(ctx.db, id);
1196
+ eventBus.emit("workers", "worker:updated", updated);
1197
+ return { data: updated };
1198
+ });
1199
+ router.delete("/api/workers/:id", (ctx) => {
1200
+ const id = Number(ctx.params.id);
1201
+ const worker = getWorker(ctx.db, id);
1202
+ if (!worker) return { status: 404, error: "Worker not found" };
1203
+ deleteWorker(ctx.db, id);
1204
+ eventBus.emit("workers", "worker:deleted", { id });
1205
+ return { data: { ok: true } };
1206
+ });
1207
+ router.get("/api/rooms/:roomId/workers", (ctx) => {
1208
+ const workers = listRoomWorkers(ctx.db, Number(ctx.params.roomId));
1209
+ return { data: workers };
1210
+ });
1211
+ }
1212
+
1213
+ // src/server/routes/goals.ts
1214
+ function registerGoalRoutes(router) {
1215
+ router.post("/api/rooms/:roomId/goals", (ctx) => {
1216
+ const roomId = Number(ctx.params.roomId);
1217
+ const body = ctx.body || {};
1218
+ if (!body.description || typeof body.description !== "string") {
1219
+ return { status: 400, error: "description is required" };
1220
+ }
1221
+ const goal = createGoal(
1222
+ ctx.db,
1223
+ roomId,
1224
+ body.description,
1225
+ body.parentGoalId,
1226
+ body.assignedWorkerId
1227
+ );
1228
+ eventBus.emit(`room:${roomId}`, "goal:created", goal);
1229
+ return { status: 201, data: goal };
1230
+ });
1231
+ router.get("/api/rooms/:roomId/goals", (ctx) => {
1232
+ const roomId = Number(ctx.params.roomId);
1233
+ const goals = listGoals(ctx.db, roomId, ctx.query.status);
1234
+ return { data: goals };
1235
+ });
1236
+ router.get("/api/goals/:id", (ctx) => {
1237
+ const goal = getGoal(ctx.db, Number(ctx.params.id));
1238
+ if (!goal) return { status: 404, error: "Goal not found" };
1239
+ return { data: goal };
1240
+ });
1241
+ router.get("/api/goals/:id/subgoals", (ctx) => {
1242
+ const subgoals = getSubGoals(ctx.db, Number(ctx.params.id));
1243
+ return { data: subgoals };
1244
+ });
1245
+ router.patch("/api/goals/:id", (ctx) => {
1246
+ const id = Number(ctx.params.id);
1247
+ const goal = getGoal(ctx.db, id);
1248
+ if (!goal) return { status: 404, error: "Goal not found" };
1249
+ const body = ctx.body || {};
1250
+ updateGoal(ctx.db, id, body);
1251
+ const updated = getGoal(ctx.db, id);
1252
+ eventBus.emit(`room:${goal.roomId}`, "goal:updated", updated);
1253
+ return { data: updated };
1254
+ });
1255
+ router.delete("/api/goals/:id", (ctx) => {
1256
+ const id = Number(ctx.params.id);
1257
+ const goal = getGoal(ctx.db, id);
1258
+ if (!goal) return { status: 404, error: "Goal not found" };
1259
+ deleteGoal(ctx.db, id);
1260
+ eventBus.emit(`room:${goal.roomId}`, "goal:deleted", { id });
1261
+ return { data: { ok: true } };
1262
+ });
1263
+ router.post("/api/goals/:id/updates", (ctx) => {
1264
+ const id = Number(ctx.params.id);
1265
+ const goal = getGoal(ctx.db, id);
1266
+ if (!goal) return { status: 404, error: "Goal not found" };
1267
+ const body = ctx.body || {};
1268
+ if (!body.observation || typeof body.observation !== "string") {
1269
+ return { status: 400, error: "observation is required" };
1270
+ }
1271
+ const update = logGoalUpdate(
1272
+ ctx.db,
1273
+ id,
1274
+ body.observation,
1275
+ body.metricValue,
1276
+ body.workerId
1277
+ );
1278
+ eventBus.emit(`room:${goal.roomId}`, "goal:progress", {
1279
+ goalId: id,
1280
+ update
1281
+ });
1282
+ return { status: 201, data: update };
1283
+ });
1284
+ router.get("/api/goals/:id/updates", (ctx) => {
1285
+ const limit = ctx.query.limit ? Number(ctx.query.limit) : void 0;
1286
+ const updates = getGoalUpdates(ctx.db, Number(ctx.params.id), limit);
1287
+ return { data: updates };
1288
+ });
1289
+ }
1290
+
1291
+ // src/server/routes/decisions.ts
1292
+ function registerDecisionRoutes(router) {
1293
+ router.post("/api/rooms/:roomId/decisions", (ctx) => {
1294
+ const roomId = Number(ctx.params.roomId);
1295
+ const body = ctx.body || {};
1296
+ if (!body.proposerId || typeof body.proposerId !== "number") {
1297
+ return { status: 400, error: "proposerId is required" };
1298
+ }
1299
+ if (!body.proposal || typeof body.proposal !== "string") {
1300
+ return { status: 400, error: "proposal is required" };
1301
+ }
1302
+ if (!body.decisionType || typeof body.decisionType !== "string") {
1303
+ return { status: 400, error: "decisionType is required" };
1304
+ }
1305
+ const decision = createDecision(
1306
+ ctx.db,
1307
+ roomId,
1308
+ body.proposerId,
1309
+ body.proposal,
1310
+ body.decisionType,
1311
+ body.threshold,
1312
+ body.timeoutAt
1313
+ );
1314
+ eventBus.emit(`room:${roomId}`, "decision:created", decision);
1315
+ return { status: 201, data: decision };
1316
+ });
1317
+ router.get("/api/rooms/:roomId/decisions", (ctx) => {
1318
+ const roomId = Number(ctx.params.roomId);
1319
+ const decisions = listDecisions(ctx.db, roomId, ctx.query.status);
1320
+ return { data: decisions };
1321
+ });
1322
+ router.get("/api/decisions/:id", (ctx) => {
1323
+ const decision = getDecision(ctx.db, Number(ctx.params.id));
1324
+ if (!decision) return { status: 404, error: "Decision not found" };
1325
+ return { data: decision };
1326
+ });
1327
+ router.post("/api/decisions/:id/resolve", (ctx) => {
1328
+ const id = Number(ctx.params.id);
1329
+ const decision = getDecision(ctx.db, id);
1330
+ if (!decision) return { status: 404, error: "Decision not found" };
1331
+ const body = ctx.body || {};
1332
+ if (!body.status || typeof body.status !== "string") {
1333
+ return { status: 400, error: "status is required (approved/rejected)" };
1334
+ }
1335
+ resolveDecision(ctx.db, id, body.status, body.result);
1336
+ const updated = getDecision(ctx.db, id);
1337
+ eventBus.emit(`room:${decision.roomId}`, "decision:resolved", updated);
1338
+ return { data: updated };
1339
+ });
1340
+ router.post("/api/decisions/:id/vote", (ctx) => {
1341
+ const id = Number(ctx.params.id);
1342
+ const decision = getDecision(ctx.db, id);
1343
+ if (!decision) return { status: 404, error: "Decision not found" };
1344
+ const body = ctx.body || {};
1345
+ if (!body.workerId || typeof body.workerId !== "number") {
1346
+ return { status: 400, error: "workerId is required" };
1347
+ }
1348
+ if (!body.vote || typeof body.vote !== "string") {
1349
+ return { status: 400, error: "vote is required (yes/no/abstain)" };
1350
+ }
1351
+ const vote = castVote(ctx.db, id, body.workerId, body.vote, body.reasoning);
1352
+ eventBus.emit(`room:${decision.roomId}`, "decision:vote_cast", vote);
1353
+ return { status: 201, data: vote };
1354
+ });
1355
+ router.get("/api/decisions/:id/votes", (ctx) => {
1356
+ const votes = getVotes(ctx.db, Number(ctx.params.id));
1357
+ return { data: votes };
1358
+ });
1359
+ }
1360
+
1361
+ // src/server/routes/tasks.ts
1362
+ function registerTaskRoutes(router) {
1363
+ router.post("/api/tasks", (ctx) => {
1364
+ const body = ctx.body || {};
1365
+ if (!body.prompt || typeof body.prompt !== "string") {
1366
+ return { status: 400, error: "prompt is required" };
1367
+ }
1368
+ const task = createTask(ctx.db, {
1369
+ name: body.name || body.prompt.slice(0, 50),
1370
+ prompt: body.prompt,
1371
+ description: body.description,
1372
+ triggerType: body.triggerType || "manual",
1373
+ cronExpression: body.cronExpression,
1374
+ scheduledAt: body.scheduledAt,
1375
+ workerId: body.workerId,
1376
+ maxRuns: body.maxRuns,
1377
+ maxTurns: body.maxTurns,
1378
+ timeoutMinutes: body.timeout,
1379
+ allowedTools: body.allowedTools,
1380
+ disallowedTools: body.disallowedTools,
1381
+ sessionContinuity: body.sessionContinuity
1382
+ });
1383
+ eventBus.emit("tasks", "task:created", task);
1384
+ return { status: 201, data: task };
1385
+ });
1386
+ router.get("/api/tasks", (ctx) => {
1387
+ const tasks = listTasks(ctx.db, ctx.query.status);
1388
+ return { data: tasks };
1389
+ });
1390
+ router.get("/api/tasks/:id", (ctx) => {
1391
+ const task = getTask(ctx.db, Number(ctx.params.id));
1392
+ if (!task) return { status: 404, error: "Task not found" };
1393
+ return { data: task };
1394
+ });
1395
+ router.patch("/api/tasks/:id", (ctx) => {
1396
+ const id = Number(ctx.params.id);
1397
+ const task = getTask(ctx.db, id);
1398
+ if (!task) return { status: 404, error: "Task not found" };
1399
+ const body = ctx.body || {};
1400
+ updateTask(ctx.db, id, body);
1401
+ const updated = getTask(ctx.db, id);
1402
+ return { data: updated };
1403
+ });
1404
+ router.delete("/api/tasks/:id", (ctx) => {
1405
+ const id = Number(ctx.params.id);
1406
+ const task = getTask(ctx.db, id);
1407
+ if (!task) return { status: 404, error: "Task not found" };
1408
+ deleteTask(ctx.db, id);
1409
+ return { data: { ok: true } };
1410
+ });
1411
+ router.post("/api/tasks/:id/pause", (ctx) => {
1412
+ const id = Number(ctx.params.id);
1413
+ const task = getTask(ctx.db, id);
1414
+ if (!task) return { status: 404, error: "Task not found" };
1415
+ pauseTask(ctx.db, id);
1416
+ return { data: { ok: true } };
1417
+ });
1418
+ router.post("/api/tasks/:id/resume", (ctx) => {
1419
+ const id = Number(ctx.params.id);
1420
+ const task = getTask(ctx.db, id);
1421
+ if (!task) return { status: 404, error: "Task not found" };
1422
+ resumeTask(ctx.db, id);
1423
+ return { data: { ok: true } };
1424
+ });
1425
+ router.get("/api/tasks/:id/runs", (ctx) => {
1426
+ const limit = ctx.query.limit ? Number(ctx.query.limit) : void 0;
1427
+ const runs = getTaskRuns(ctx.db, Number(ctx.params.id), limit);
1428
+ return { data: runs };
1429
+ });
1430
+ router.post("/api/tasks/:id/run", (ctx) => {
1431
+ const id = Number(ctx.params.id);
1432
+ const task = getTask(ctx.db, id);
1433
+ if (!task) return { status: 404, error: "Task not found" };
1434
+ const run = createTaskRun(ctx.db, id);
1435
+ eventBus.emit("runs", "run:created", { taskId: id, runId: run.id });
1436
+ return { status: 201, data: { ok: true, runId: run.id } };
1437
+ });
1438
+ router.post("/api/tasks/:id/reset-session", (ctx) => {
1439
+ const id = Number(ctx.params.id);
1440
+ const task = getTask(ctx.db, id);
1441
+ if (!task) return { status: 404, error: "Task not found" };
1442
+ clearTaskSession(ctx.db, id);
1443
+ return { data: { ok: true } };
1444
+ });
1445
+ }
1446
+
1447
+ // src/server/routes/runs.ts
1448
+ function registerRunRoutes(router) {
1449
+ router.get("/api/runs", (ctx) => {
1450
+ const limit = ctx.query.limit ? Number(ctx.query.limit) : void 0;
1451
+ const runs = listAllRuns(ctx.db, limit);
1452
+ return { data: runs };
1453
+ });
1454
+ router.get("/api/runs/:id", (ctx) => {
1455
+ const run = getTaskRun(ctx.db, Number(ctx.params.id));
1456
+ if (!run) return { status: 404, error: "Run not found" };
1457
+ return { data: run };
1458
+ });
1459
+ router.get("/api/runs/:id/logs", (ctx) => {
1460
+ const afterSeq = ctx.query.afterSeq ? Number(ctx.query.afterSeq) : void 0;
1461
+ const limit = ctx.query.limit ? Number(ctx.query.limit) : void 0;
1462
+ const logs = getConsoleLogs(ctx.db, Number(ctx.params.id), afterSeq, limit);
1463
+ return { data: logs };
1464
+ });
1465
+ }
1466
+
1467
+ // src/server/routes/memory.ts
1468
+ function registerMemoryRoutes(router) {
1469
+ router.post("/api/memory/entities", (ctx) => {
1470
+ const body = ctx.body || {};
1471
+ if (!body.name || typeof body.name !== "string") {
1472
+ return { status: 400, error: "name is required" };
1473
+ }
1474
+ const entity = createEntity(
1475
+ ctx.db,
1476
+ body.name,
1477
+ body.type,
1478
+ body.category
1479
+ );
1480
+ eventBus.emit("memory", "entity:created", entity);
1481
+ return { status: 201, data: entity };
1482
+ });
1483
+ router.get("/api/memory/entities", (ctx) => {
1484
+ const entities = listEntities(ctx.db, ctx.query.category);
1485
+ return { data: entities };
1486
+ });
1487
+ router.get("/api/memory/entities/:id", (ctx) => {
1488
+ const entity = getEntity(ctx.db, Number(ctx.params.id));
1489
+ if (!entity) return { status: 404, error: "Entity not found" };
1490
+ return { data: entity };
1491
+ });
1492
+ router.patch("/api/memory/entities/:id", (ctx) => {
1493
+ const id = Number(ctx.params.id);
1494
+ const entity = getEntity(ctx.db, id);
1495
+ if (!entity) return { status: 404, error: "Entity not found" };
1496
+ const body = ctx.body || {};
1497
+ updateEntity(ctx.db, id, body);
1498
+ const updated = getEntity(ctx.db, id);
1499
+ eventBus.emit("memory", "entity:updated", updated);
1500
+ return { data: updated };
1501
+ });
1502
+ router.delete("/api/memory/entities/:id", (ctx) => {
1503
+ const id = Number(ctx.params.id);
1504
+ const entity = getEntity(ctx.db, id);
1505
+ if (!entity) return { status: 404, error: "Entity not found" };
1506
+ deleteEntity(ctx.db, id);
1507
+ eventBus.emit("memory", "entity:deleted", { id });
1508
+ return { data: { ok: true } };
1509
+ });
1510
+ router.get("/api/memory/search", (ctx) => {
1511
+ const q = ctx.query.q;
1512
+ if (!q) return { status: 400, error: "q query parameter is required" };
1513
+ const results = searchEntities(ctx.db, q);
1514
+ return { data: results };
1515
+ });
1516
+ router.get("/api/memory/stats", (ctx) => {
1517
+ const stats = getMemoryStats(ctx.db);
1518
+ return { data: stats };
1519
+ });
1520
+ router.post("/api/memory/entities/:entityId/observations", (ctx) => {
1521
+ const entityId = Number(ctx.params.entityId);
1522
+ const body = ctx.body || {};
1523
+ if (!body.content || typeof body.content !== "string") {
1524
+ return { status: 400, error: "content is required" };
1525
+ }
1526
+ const observation = addObservation(
1527
+ ctx.db,
1528
+ entityId,
1529
+ body.content,
1530
+ body.source
1531
+ );
1532
+ eventBus.emit("memory", "observation:added", observation);
1533
+ return { status: 201, data: observation };
1534
+ });
1535
+ router.get("/api/memory/entities/:entityId/observations", (ctx) => {
1536
+ const observations = getObservations(ctx.db, Number(ctx.params.entityId));
1537
+ return { data: observations };
1538
+ });
1539
+ router.delete("/api/memory/observations/:id", (ctx) => {
1540
+ deleteObservation(ctx.db, Number(ctx.params.id));
1541
+ return { data: { ok: true } };
1542
+ });
1543
+ router.post("/api/memory/relations", (ctx) => {
1544
+ const body = ctx.body || {};
1545
+ if (!body.fromEntityId || typeof body.fromEntityId !== "number") {
1546
+ return { status: 400, error: "fromEntityId is required" };
1547
+ }
1548
+ if (!body.toEntityId || typeof body.toEntityId !== "number") {
1549
+ return { status: 400, error: "toEntityId is required" };
1550
+ }
1551
+ if (!body.relationType || typeof body.relationType !== "string") {
1552
+ return { status: 400, error: "relationType is required" };
1553
+ }
1554
+ const relation = addRelation(
1555
+ ctx.db,
1556
+ body.fromEntityId,
1557
+ body.toEntityId,
1558
+ body.relationType
1559
+ );
1560
+ return { status: 201, data: relation };
1561
+ });
1562
+ router.get("/api/memory/entities/:entityId/relations", (ctx) => {
1563
+ const relations = getRelations(ctx.db, Number(ctx.params.entityId));
1564
+ return { data: relations };
1565
+ });
1566
+ router.delete("/api/memory/relations/:id", (ctx) => {
1567
+ deleteRelation(ctx.db, Number(ctx.params.id));
1568
+ return { data: { ok: true } };
1569
+ });
1570
+ }
1571
+
1572
+ // src/server/routes/skills.ts
1573
+ function registerSkillRoutes(router) {
1574
+ router.post("/api/skills", (ctx) => {
1575
+ const body = ctx.body || {};
1576
+ if (!body.roomId || typeof body.roomId !== "number") {
1577
+ return { status: 400, error: "roomId is required" };
1578
+ }
1579
+ if (!body.name || typeof body.name !== "string") {
1580
+ return { status: 400, error: "name is required" };
1581
+ }
1582
+ if (!body.content || typeof body.content !== "string") {
1583
+ return { status: 400, error: "content is required" };
1584
+ }
1585
+ const skill = createSkill(ctx.db, body.roomId, body.name, body.content, {
1586
+ autoActivate: body.autoActivate,
1587
+ activationContext: body.activationContext,
1588
+ agentCreated: body.agentCreated
1589
+ });
1590
+ eventBus.emit(`room:${body.roomId}`, "skill:created", skill);
1591
+ return { status: 201, data: skill };
1592
+ });
1593
+ router.get("/api/skills", (ctx) => {
1594
+ const roomId = ctx.query.roomId ? Number(ctx.query.roomId) : void 0;
1595
+ const skills = listSkills(ctx.db, roomId);
1596
+ return { data: skills };
1597
+ });
1598
+ router.get("/api/skills/:id", (ctx) => {
1599
+ const skill = getSkill(ctx.db, Number(ctx.params.id));
1600
+ if (!skill) return { status: 404, error: "Skill not found" };
1601
+ return { data: skill };
1602
+ });
1603
+ router.patch("/api/skills/:id", (ctx) => {
1604
+ const id = Number(ctx.params.id);
1605
+ const skill = getSkill(ctx.db, id);
1606
+ if (!skill) return { status: 404, error: "Skill not found" };
1607
+ const body = ctx.body || {};
1608
+ updateSkill(ctx.db, id, body);
1609
+ const updated = getSkill(ctx.db, id);
1610
+ eventBus.emit(`room:${skill.roomId}`, "skill:updated", updated);
1611
+ return { data: updated };
1612
+ });
1613
+ router.delete("/api/skills/:id", (ctx) => {
1614
+ const id = Number(ctx.params.id);
1615
+ const skill = getSkill(ctx.db, id);
1616
+ if (!skill) return { status: 404, error: "Skill not found" };
1617
+ deleteSkill(ctx.db, id);
1618
+ eventBus.emit(`room:${skill.roomId}`, "skill:deleted", { id });
1619
+ return { data: { ok: true } };
1620
+ });
1621
+ }
1622
+
1623
+ // src/server/routes/watches.ts
1624
+ function registerWatchRoutes(router) {
1625
+ router.post("/api/watches", (ctx) => {
1626
+ const body = ctx.body || {};
1627
+ if (!body.path || typeof body.path !== "string") {
1628
+ return { status: 400, error: "path is required" };
1629
+ }
1630
+ const watch = createWatch(
1631
+ ctx.db,
1632
+ body.path,
1633
+ body.description,
1634
+ body.actionPrompt
1635
+ );
1636
+ return { status: 201, data: watch };
1637
+ });
1638
+ router.get("/api/watches", (ctx) => {
1639
+ const watches = listWatches(ctx.db, ctx.query.status);
1640
+ return { data: watches };
1641
+ });
1642
+ router.get("/api/watches/:id", (ctx) => {
1643
+ const watch = getWatch(ctx.db, Number(ctx.params.id));
1644
+ if (!watch) return { status: 404, error: "Watch not found" };
1645
+ return { data: watch };
1646
+ });
1647
+ router.delete("/api/watches/:id", (ctx) => {
1648
+ const id = Number(ctx.params.id);
1649
+ const watch = getWatch(ctx.db, id);
1650
+ if (!watch) return { status: 404, error: "Watch not found" };
1651
+ deleteWatch(ctx.db, id);
1652
+ return { data: { ok: true } };
1653
+ });
1654
+ router.post("/api/watches/:id/pause", (ctx) => {
1655
+ const id = Number(ctx.params.id);
1656
+ const watch = getWatch(ctx.db, id);
1657
+ if (!watch) return { status: 404, error: "Watch not found" };
1658
+ pauseWatch(ctx.db, id);
1659
+ return { data: { ok: true } };
1660
+ });
1661
+ router.post("/api/watches/:id/resume", (ctx) => {
1662
+ const id = Number(ctx.params.id);
1663
+ const watch = getWatch(ctx.db, id);
1664
+ if (!watch) return { status: 404, error: "Watch not found" };
1665
+ resumeWatch(ctx.db, id);
1666
+ return { data: { ok: true } };
1667
+ });
1668
+ }
1669
+
1670
+ // src/server/routes/settings.ts
1671
+ function registerSettingRoutes(router) {
1672
+ router.get("/api/settings", (ctx) => {
1673
+ const settings = getAllSettings(ctx.db);
1674
+ return { data: settings };
1675
+ });
1676
+ router.get("/api/settings/:key", (ctx) => {
1677
+ const value = getSetting(ctx.db, ctx.params.key);
1678
+ return { data: { key: ctx.params.key, value } };
1679
+ });
1680
+ router.put("/api/settings/:key", (ctx) => {
1681
+ const body = ctx.body || {};
1682
+ if (body.value === void 0) {
1683
+ return { status: 400, error: "value is required" };
1684
+ }
1685
+ setSetting(ctx.db, ctx.params.key, String(body.value));
1686
+ return { data: { key: ctx.params.key, value: String(body.value) } };
1687
+ });
1688
+ }
1689
+
1690
+ // src/server/routes/escalations.ts
1691
+ function registerEscalationRoutes(router) {
1692
+ router.post("/api/rooms/:roomId/escalations", (ctx) => {
1693
+ const roomId = Number(ctx.params.roomId);
1694
+ const body = ctx.body || {};
1695
+ if (!body.fromAgentId || typeof body.fromAgentId !== "number") {
1696
+ return { status: 400, error: "fromAgentId is required" };
1697
+ }
1698
+ if (!body.question || typeof body.question !== "string") {
1699
+ return { status: 400, error: "question is required" };
1700
+ }
1701
+ const escalation = createEscalation(
1702
+ ctx.db,
1703
+ roomId,
1704
+ body.fromAgentId,
1705
+ body.question,
1706
+ body.toAgentId
1707
+ );
1708
+ eventBus.emit(`room:${roomId}`, "escalation:created", escalation);
1709
+ return { status: 201, data: escalation };
1710
+ });
1711
+ router.get("/api/rooms/:roomId/escalations", (ctx) => {
1712
+ const roomId = Number(ctx.params.roomId);
1713
+ const toAgentId = ctx.query.toAgentId ? Number(ctx.query.toAgentId) : void 0;
1714
+ const escalations = getPendingEscalations(ctx.db, roomId, toAgentId);
1715
+ return { data: escalations };
1716
+ });
1717
+ router.post("/api/escalations/:id/resolve", (ctx) => {
1718
+ const id = Number(ctx.params.id);
1719
+ const escalation = getEscalation(ctx.db, id);
1720
+ if (!escalation) return { status: 404, error: "Escalation not found" };
1721
+ const body = ctx.body || {};
1722
+ if (!body.answer || typeof body.answer !== "string") {
1723
+ return { status: 400, error: "answer is required" };
1724
+ }
1725
+ resolveEscalation(ctx.db, id, body.answer);
1726
+ const updated = getEscalation(ctx.db, id);
1727
+ eventBus.emit(`room:${escalation.roomId}`, "escalation:resolved", updated);
1728
+ return { data: updated };
1729
+ });
1730
+ }
1731
+
1732
+ // src/server/routes/self-mod.ts
1733
+ function registerSelfModRoutes(router) {
1734
+ router.get("/api/rooms/:roomId/self-mod", (ctx) => {
1735
+ const roomId = Number(ctx.params.roomId);
1736
+ const limit = ctx.query.limit ? Number(ctx.query.limit) : void 0;
1737
+ const history = getSelfModHistory(ctx.db, roomId, limit);
1738
+ return { data: history };
1739
+ });
1740
+ }
1741
+
1742
+ // src/server/routes/status.ts
1743
+ var import_node_child_process = require("node:child_process");
1744
+
1745
+ // src/server/db.ts
1746
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
1747
+ var import_os = require("os");
1748
+ var import_path = require("path");
1749
+ var import_fs = require("fs");
1750
+
1751
+ // src/shared/schema.ts
1752
+ var SCHEMA = `
1753
+ PRAGMA journal_mode = WAL;
1754
+ PRAGMA foreign_keys = ON;
1755
+
1756
+ -- Memory: entities, observations, relations
1757
+ CREATE TABLE IF NOT EXISTS entities (
1758
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1759
+ name TEXT NOT NULL,
1760
+ type TEXT NOT NULL DEFAULT 'fact',
1761
+ category TEXT,
1762
+ embedded_at DATETIME,
1763
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1764
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
1765
+ );
1766
+ CREATE INDEX IF NOT EXISTS idx_entities_category ON entities(category);
1767
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
1768
+
1769
+ CREATE TABLE IF NOT EXISTS observations (
1770
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1771
+ entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
1772
+ content TEXT NOT NULL,
1773
+ source TEXT NOT NULL DEFAULT 'claude',
1774
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1775
+ );
1776
+ CREATE INDEX IF NOT EXISTS idx_observations_entity_id ON observations(entity_id);
1777
+
1778
+ CREATE TABLE IF NOT EXISTS relations (
1779
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1780
+ from_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
1781
+ to_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
1782
+ relation_type TEXT NOT NULL,
1783
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1784
+ );
1785
+ CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
1786
+ CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
1787
+
1788
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
1789
+ name, content, category, content='entities', content_rowid='id'
1790
+ );
1791
+
1792
+ CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
1793
+ INSERT INTO memory_fts(rowid, name, content, category) VALUES (new.id, new.name, '', new.category);
1794
+ END;
1795
+ CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
1796
+ INSERT INTO memory_fts(memory_fts, rowid, name, content, category) VALUES ('delete', old.id, old.name, '', old.category);
1797
+ END;
1798
+ CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
1799
+ INSERT INTO memory_fts(memory_fts, rowid, name, content, category) VALUES ('delete', old.id, old.name, '', old.category);
1800
+ INSERT INTO memory_fts(rowid, name, content, category) VALUES (new.id, new.name, '', new.category);
1801
+ END;
1802
+
1803
+ -- Embeddings (semantic search)
1804
+ CREATE TABLE IF NOT EXISTS embeddings (
1805
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1806
+ entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
1807
+ source_type TEXT NOT NULL DEFAULT 'entity',
1808
+ source_id INTEGER NOT NULL,
1809
+ text_hash TEXT NOT NULL,
1810
+ vector BLOB NOT NULL,
1811
+ model TEXT NOT NULL DEFAULT 'all-MiniLM-L6-v2',
1812
+ dimensions INTEGER NOT NULL DEFAULT 384,
1813
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1814
+ );
1815
+ CREATE INDEX IF NOT EXISTS idx_embeddings_entity_id ON embeddings(entity_id);
1816
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_embeddings_source ON embeddings(source_type, source_id, model);
1817
+
1818
+ -- Workers
1819
+ CREATE TABLE IF NOT EXISTS workers (
1820
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1821
+ name TEXT NOT NULL,
1822
+ role TEXT,
1823
+ system_prompt TEXT NOT NULL,
1824
+ description TEXT,
1825
+ model TEXT,
1826
+ is_default INTEGER NOT NULL DEFAULT 0,
1827
+ task_count INTEGER NOT NULL DEFAULT 0,
1828
+ room_id INTEGER,
1829
+ agent_state TEXT NOT NULL DEFAULT 'idle',
1830
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1831
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
1832
+ );
1833
+ CREATE INDEX IF NOT EXISTS idx_workers_name ON workers(name);
1834
+
1835
+ -- Tasks
1836
+ CREATE TABLE IF NOT EXISTS tasks (
1837
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1838
+ name TEXT NOT NULL,
1839
+ description TEXT,
1840
+ prompt TEXT NOT NULL,
1841
+ cron_expression TEXT,
1842
+ trigger_type TEXT NOT NULL DEFAULT 'cron',
1843
+ trigger_config TEXT,
1844
+ executor TEXT NOT NULL DEFAULT 'claude_code',
1845
+ status TEXT NOT NULL DEFAULT 'active',
1846
+ last_run DATETIME,
1847
+ last_result TEXT,
1848
+ error_count INTEGER NOT NULL DEFAULT 0,
1849
+ scheduled_at DATETIME,
1850
+ max_runs INTEGER,
1851
+ run_count INTEGER NOT NULL DEFAULT 0,
1852
+ memory_entity_id INTEGER REFERENCES entities(id) ON DELETE SET NULL,
1853
+ worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
1854
+ session_continuity INTEGER NOT NULL DEFAULT 0,
1855
+ session_id TEXT,
1856
+ timeout_minutes INTEGER,
1857
+ max_turns INTEGER,
1858
+ allowed_tools TEXT,
1859
+ disallowed_tools TEXT,
1860
+ nudge_mode TEXT NOT NULL DEFAULT 'always',
1861
+ learned_context TEXT,
1862
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1863
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
1864
+ );
1865
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
1866
+ CREATE INDEX IF NOT EXISTS idx_tasks_scheduled_at ON tasks(scheduled_at);
1867
+ CREATE INDEX IF NOT EXISTS idx_tasks_trigger_type ON tasks(trigger_type);
1868
+
1869
+ -- Task runs
1870
+ CREATE TABLE IF NOT EXISTS task_runs (
1871
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1872
+ task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1873
+ started_at DATETIME DEFAULT (datetime('now','localtime')),
1874
+ finished_at DATETIME,
1875
+ status TEXT NOT NULL DEFAULT 'running',
1876
+ result TEXT,
1877
+ result_file TEXT,
1878
+ error_message TEXT,
1879
+ duration_ms INTEGER,
1880
+ progress REAL,
1881
+ progress_message TEXT,
1882
+ session_id TEXT
1883
+ );
1884
+ CREATE INDEX IF NOT EXISTS idx_task_runs_task_id ON task_runs(task_id);
1885
+ CREATE INDEX IF NOT EXISTS idx_task_runs_started_at ON task_runs(started_at);
1886
+ CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status);
1887
+
1888
+ -- Console logs
1889
+ CREATE TABLE IF NOT EXISTS console_logs (
1890
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1891
+ run_id INTEGER NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
1892
+ seq INTEGER NOT NULL,
1893
+ entry_type TEXT NOT NULL,
1894
+ content TEXT NOT NULL,
1895
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1896
+ );
1897
+ CREATE INDEX IF NOT EXISTS idx_console_logs_run_seq ON console_logs(run_id, seq);
1898
+
1899
+ -- File watchers
1900
+ CREATE TABLE IF NOT EXISTS watches (
1901
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1902
+ path TEXT NOT NULL,
1903
+ description TEXT,
1904
+ action_prompt TEXT,
1905
+ status TEXT NOT NULL DEFAULT 'active',
1906
+ last_triggered DATETIME,
1907
+ trigger_count INTEGER NOT NULL DEFAULT 0,
1908
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1909
+ );
1910
+
1911
+ -- Settings
1912
+ CREATE TABLE IF NOT EXISTS settings (
1913
+ key TEXT PRIMARY KEY,
1914
+ value TEXT,
1915
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
1916
+ );
1917
+
1918
+ -- Rooms
1919
+ CREATE TABLE IF NOT EXISTS rooms (
1920
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1921
+ name TEXT NOT NULL,
1922
+ queen_worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
1923
+ goal TEXT,
1924
+ status TEXT NOT NULL DEFAULT 'active',
1925
+ visibility TEXT NOT NULL DEFAULT 'private',
1926
+ config TEXT,
1927
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1928
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
1929
+ );
1930
+ CREATE INDEX IF NOT EXISTS idx_rooms_status ON rooms(status);
1931
+
1932
+ -- Room activity
1933
+ CREATE TABLE IF NOT EXISTS room_activity (
1934
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1935
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1936
+ event_type TEXT NOT NULL,
1937
+ actor_id INTEGER,
1938
+ summary TEXT NOT NULL,
1939
+ details TEXT,
1940
+ is_public INTEGER NOT NULL DEFAULT 1,
1941
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1942
+ );
1943
+ CREATE INDEX IF NOT EXISTS idx_room_activity_room ON room_activity(room_id);
1944
+ CREATE INDEX IF NOT EXISTS idx_room_activity_type ON room_activity(event_type);
1945
+
1946
+ -- Quorum decisions
1947
+ CREATE TABLE IF NOT EXISTS quorum_decisions (
1948
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1949
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1950
+ proposer_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
1951
+ proposal TEXT NOT NULL,
1952
+ decision_type TEXT NOT NULL DEFAULT 'low_impact',
1953
+ status TEXT NOT NULL DEFAULT 'voting',
1954
+ result TEXT,
1955
+ threshold TEXT NOT NULL DEFAULT 'majority',
1956
+ timeout_at DATETIME,
1957
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1958
+ resolved_at DATETIME
1959
+ );
1960
+ CREATE INDEX IF NOT EXISTS idx_quorum_decisions_room ON quorum_decisions(room_id);
1961
+ CREATE INDEX IF NOT EXISTS idx_quorum_decisions_status ON quorum_decisions(status);
1962
+
1963
+ -- Quorum votes
1964
+ CREATE TABLE IF NOT EXISTS quorum_votes (
1965
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1966
+ decision_id INTEGER NOT NULL REFERENCES quorum_decisions(id) ON DELETE CASCADE,
1967
+ worker_id INTEGER NOT NULL REFERENCES workers(id) ON DELETE CASCADE,
1968
+ vote TEXT NOT NULL,
1969
+ reasoning TEXT,
1970
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1971
+ UNIQUE(decision_id, worker_id)
1972
+ );
1973
+ CREATE INDEX IF NOT EXISTS idx_quorum_votes_decision ON quorum_votes(decision_id);
1974
+
1975
+ -- Goals
1976
+ CREATE TABLE IF NOT EXISTS goals (
1977
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1978
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1979
+ description TEXT NOT NULL,
1980
+ status TEXT NOT NULL DEFAULT 'active',
1981
+ parent_goal_id INTEGER REFERENCES goals(id) ON DELETE CASCADE,
1982
+ assigned_worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
1983
+ progress REAL NOT NULL DEFAULT 0.0,
1984
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
1985
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
1986
+ );
1987
+ CREATE INDEX IF NOT EXISTS idx_goals_room ON goals(room_id);
1988
+ CREATE INDEX IF NOT EXISTS idx_goals_parent ON goals(parent_goal_id);
1989
+ CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(status);
1990
+
1991
+ -- Goal updates
1992
+ CREATE TABLE IF NOT EXISTS goal_updates (
1993
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1994
+ goal_id INTEGER NOT NULL REFERENCES goals(id) ON DELETE CASCADE,
1995
+ worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
1996
+ observation TEXT NOT NULL,
1997
+ metric_value REAL,
1998
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
1999
+ );
2000
+ CREATE INDEX IF NOT EXISTS idx_goal_updates_goal ON goal_updates(goal_id);
2001
+
2002
+ -- Skills
2003
+ CREATE TABLE IF NOT EXISTS skills (
2004
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2005
+ room_id INTEGER REFERENCES rooms(id) ON DELETE CASCADE,
2006
+ name TEXT NOT NULL,
2007
+ content TEXT NOT NULL,
2008
+ activation_context TEXT,
2009
+ auto_activate INTEGER NOT NULL DEFAULT 0,
2010
+ agent_created INTEGER NOT NULL DEFAULT 0,
2011
+ created_by_worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2012
+ version INTEGER NOT NULL DEFAULT 1,
2013
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2014
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2015
+ );
2016
+ CREATE INDEX IF NOT EXISTS idx_skills_room ON skills(room_id);
2017
+ CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
2018
+
2019
+ -- Self-modification audit
2020
+ CREATE TABLE IF NOT EXISTS self_mod_audit (
2021
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2022
+ room_id INTEGER REFERENCES rooms(id) ON DELETE CASCADE,
2023
+ worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2024
+ file_path TEXT NOT NULL,
2025
+ old_hash TEXT,
2026
+ new_hash TEXT,
2027
+ reason TEXT,
2028
+ reversible INTEGER NOT NULL DEFAULT 1,
2029
+ reverted INTEGER NOT NULL DEFAULT 0,
2030
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2031
+ );
2032
+ CREATE INDEX IF NOT EXISTS idx_self_mod_audit_room ON self_mod_audit(room_id);
2033
+
2034
+ -- Escalations
2035
+ CREATE TABLE IF NOT EXISTS escalations (
2036
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2037
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2038
+ from_agent_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2039
+ to_agent_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2040
+ question TEXT NOT NULL,
2041
+ answer TEXT,
2042
+ status TEXT NOT NULL DEFAULT 'pending',
2043
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2044
+ resolved_at DATETIME
2045
+ );
2046
+ CREATE INDEX IF NOT EXISTS idx_escalations_room ON escalations(room_id);
2047
+ CREATE INDEX IF NOT EXISTS idx_escalations_status ON escalations(status);
2048
+
2049
+ -- Credentials
2050
+ CREATE TABLE IF NOT EXISTS credentials (
2051
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2052
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2053
+ name TEXT NOT NULL,
2054
+ type TEXT NOT NULL DEFAULT 'other',
2055
+ value_encrypted TEXT NOT NULL,
2056
+ provided_by TEXT NOT NULL DEFAULT 'keeper',
2057
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2058
+ );
2059
+ CREATE INDEX IF NOT EXISTS idx_credentials_room ON credentials(room_id);
2060
+
2061
+ -- Wallets
2062
+ CREATE TABLE IF NOT EXISTS wallets (
2063
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2064
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2065
+ address TEXT NOT NULL,
2066
+ private_key_encrypted TEXT NOT NULL,
2067
+ chain TEXT NOT NULL DEFAULT 'base',
2068
+ erc8004_agent_id TEXT,
2069
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2070
+ );
2071
+ CREATE INDEX IF NOT EXISTS idx_wallets_room ON wallets(room_id);
2072
+
2073
+ -- Wallet transactions
2074
+ CREATE TABLE IF NOT EXISTS wallet_transactions (
2075
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2076
+ wallet_id INTEGER NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
2077
+ type TEXT NOT NULL,
2078
+ amount TEXT NOT NULL,
2079
+ counterparty TEXT,
2080
+ tx_hash TEXT,
2081
+ description TEXT,
2082
+ status TEXT NOT NULL DEFAULT 'confirmed',
2083
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2084
+ );
2085
+ CREATE INDEX IF NOT EXISTS idx_wallet_tx_wallet ON wallet_transactions(wallet_id);
2086
+
2087
+ -- Stations
2088
+ CREATE TABLE IF NOT EXISTS stations (
2089
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2090
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2091
+ name TEXT NOT NULL,
2092
+ provider TEXT NOT NULL,
2093
+ external_id TEXT,
2094
+ tier TEXT NOT NULL,
2095
+ region TEXT,
2096
+ status TEXT NOT NULL DEFAULT 'provisioning',
2097
+ monthly_cost REAL NOT NULL DEFAULT 0,
2098
+ config TEXT,
2099
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2100
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2101
+ );
2102
+ CREATE INDEX IF NOT EXISTS idx_stations_room ON stations(room_id);
2103
+
2104
+ -- Schema version tracking
2105
+ CREATE TABLE IF NOT EXISTS schema_version (
2106
+ version INTEGER PRIMARY KEY,
2107
+ applied_at DATETIME DEFAULT (datetime('now','localtime'))
2108
+ );
2109
+ INSERT OR IGNORE INTO schema_version (version) VALUES (1);
2110
+ INSERT OR IGNORE INTO schema_version (version) VALUES (2);
2111
+ INSERT OR IGNORE INTO schema_version (version) VALUES (3);
2112
+ INSERT OR IGNORE INTO schema_version (version) VALUES (4);
2113
+ INSERT OR IGNORE INTO schema_version (version) VALUES (5);
2114
+ INSERT OR IGNORE INTO schema_version (version) VALUES (6);
2115
+ INSERT OR IGNORE INTO schema_version (version) VALUES (7);
2116
+ INSERT OR IGNORE INTO schema_version (version) VALUES (8);
2117
+ INSERT OR IGNORE INTO schema_version (version) VALUES (9);
2118
+ INSERT OR IGNORE INTO schema_version (version) VALUES (10);
2119
+ INSERT OR IGNORE INTO schema_version (version) VALUES (11);
2120
+ INSERT OR IGNORE INTO schema_version (version) VALUES (12);
2121
+ INSERT OR IGNORE INTO schema_version (version) VALUES (13);
2122
+ INSERT OR IGNORE INTO schema_version (version) VALUES (14);
2123
+ INSERT OR IGNORE INTO schema_version (version) VALUES (15);
2124
+ INSERT OR IGNORE INTO schema_version (version) VALUES (16);
2125
+ INSERT OR IGNORE INTO schema_version (version) VALUES (17);
2126
+ INSERT OR IGNORE INTO schema_version (version) VALUES (18);
2127
+ INSERT OR IGNORE INTO schema_version (version) VALUES (19);
2128
+ INSERT OR IGNORE INTO schema_version (version) VALUES (20);
2129
+ `;
2130
+ var MIGRATIONS = [
2131
+ {
2132
+ version: 2,
2133
+ label: "one-time tasks + progress tracking",
2134
+ sql: `
2135
+ ALTER TABLE tasks ADD COLUMN scheduled_at DATETIME;
2136
+ ALTER TABLE task_runs ADD COLUMN progress REAL;
2137
+ ALTER TABLE task_runs ADD COLUMN progress_message TEXT;
2138
+ CREATE INDEX IF NOT EXISTS idx_tasks_scheduled_at ON tasks(scheduled_at);
2139
+ CREATE INDEX IF NOT EXISTS idx_tasks_trigger_type ON tasks(trigger_type);
2140
+ INSERT OR IGNORE INTO schema_version (version) VALUES (2);`
2141
+ },
2142
+ {
2143
+ version: 3,
2144
+ label: "max runs",
2145
+ sql: `
2146
+ ALTER TABLE tasks ADD COLUMN max_runs INTEGER;
2147
+ ALTER TABLE tasks ADD COLUMN run_count INTEGER NOT NULL DEFAULT 0;
2148
+ INSERT OR IGNORE INTO schema_version (version) VALUES (3);`
2149
+ },
2150
+ {
2151
+ version: 4,
2152
+ label: "memory-task integration",
2153
+ sql: `
2154
+ ALTER TABLE tasks ADD COLUMN memory_entity_id INTEGER REFERENCES entities(id) ON DELETE SET NULL;
2155
+ INSERT OR IGNORE INTO schema_version (version) VALUES (4);`
2156
+ },
2157
+ {
2158
+ version: 5,
2159
+ label: "workers, sessions, embeddings",
2160
+ sql: `
2161
+ CREATE TABLE IF NOT EXISTS workers (
2162
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2163
+ name TEXT NOT NULL,
2164
+ system_prompt TEXT NOT NULL,
2165
+ description TEXT,
2166
+ model TEXT,
2167
+ is_default INTEGER NOT NULL DEFAULT 0,
2168
+ task_count INTEGER NOT NULL DEFAULT 0,
2169
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2170
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2171
+ );
2172
+ CREATE INDEX IF NOT EXISTS idx_workers_name ON workers(name);
2173
+ ALTER TABLE tasks ADD COLUMN worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL;
2174
+ ALTER TABLE tasks ADD COLUMN session_continuity INTEGER NOT NULL DEFAULT 0;
2175
+ ALTER TABLE tasks ADD COLUMN session_id TEXT;
2176
+ ALTER TABLE task_runs ADD COLUMN session_id TEXT;
2177
+ CREATE TABLE IF NOT EXISTS embeddings (
2178
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2179
+ entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
2180
+ source_type TEXT NOT NULL DEFAULT 'entity',
2181
+ source_id INTEGER NOT NULL,
2182
+ text_hash TEXT NOT NULL,
2183
+ vector BLOB NOT NULL,
2184
+ model TEXT NOT NULL DEFAULT 'all-MiniLM-L6-v2',
2185
+ dimensions INTEGER NOT NULL DEFAULT 384,
2186
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2187
+ );
2188
+ CREATE INDEX IF NOT EXISTS idx_embeddings_entity_id ON embeddings(entity_id);
2189
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_embeddings_source ON embeddings(source_type, source_id, model);
2190
+ ALTER TABLE entities ADD COLUMN embedded_at DATETIME;
2191
+ INSERT OR IGNORE INTO schema_version (version) VALUES (5);`
2192
+ },
2193
+ {
2194
+ version: 6,
2195
+ label: "task timeout",
2196
+ sql: `
2197
+ ALTER TABLE tasks ADD COLUMN timeout_minutes INTEGER;
2198
+ INSERT OR IGNORE INTO schema_version (version) VALUES (6);`
2199
+ },
2200
+ {
2201
+ version: 7,
2202
+ label: "task_runs status index",
2203
+ sql: `
2204
+ CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status);
2205
+ INSERT OR IGNORE INTO schema_version (version) VALUES (7);`
2206
+ },
2207
+ {
2208
+ version: 8,
2209
+ label: "console logs for task runs",
2210
+ sql: `
2211
+ CREATE TABLE IF NOT EXISTS console_logs (
2212
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2213
+ run_id INTEGER NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
2214
+ seq INTEGER NOT NULL,
2215
+ entry_type TEXT NOT NULL,
2216
+ content TEXT NOT NULL,
2217
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2218
+ );
2219
+ CREATE INDEX IF NOT EXISTS idx_console_logs_run_seq ON console_logs(run_id, seq);
2220
+ INSERT OR IGNORE INTO schema_version (version) VALUES (8);`
2221
+ },
2222
+ {
2223
+ version: 9,
2224
+ label: "task max turns",
2225
+ sql: `
2226
+ ALTER TABLE tasks ADD COLUMN max_turns INTEGER;
2227
+ INSERT OR IGNORE INTO schema_version (version) VALUES (9);`
2228
+ },
2229
+ {
2230
+ version: 10,
2231
+ label: "task tool restrictions",
2232
+ sql: `
2233
+ ALTER TABLE tasks ADD COLUMN allowed_tools TEXT;
2234
+ ALTER TABLE tasks ADD COLUMN disallowed_tools TEXT;
2235
+ INSERT OR IGNORE INTO schema_version (version) VALUES (10);`
2236
+ },
2237
+ {
2238
+ version: 11,
2239
+ label: "task learned context",
2240
+ sql: `
2241
+ ALTER TABLE tasks ADD COLUMN learned_context TEXT;
2242
+ INSERT OR IGNORE INTO schema_version (version) VALUES (11);`
2243
+ },
2244
+ {
2245
+ version: 12,
2246
+ label: "per-task nudge mode",
2247
+ sql: `
2248
+ ALTER TABLE tasks ADD COLUMN nudge_mode TEXT NOT NULL DEFAULT 'always';
2249
+ INSERT OR IGNORE INTO schema_version (version) VALUES (12);`
2250
+ },
2251
+ {
2252
+ version: 13,
2253
+ label: "worker role field",
2254
+ sql: `
2255
+ ALTER TABLE workers ADD COLUMN role TEXT;
2256
+ INSERT OR IGNORE INTO schema_version (version) VALUES (13);`
2257
+ },
2258
+ {
2259
+ version: 14,
2260
+ label: "rooms + room activity",
2261
+ sql: `
2262
+ CREATE TABLE IF NOT EXISTS rooms (
2263
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2264
+ name TEXT NOT NULL,
2265
+ queen_worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2266
+ goal TEXT,
2267
+ status TEXT NOT NULL DEFAULT 'active',
2268
+ visibility TEXT NOT NULL DEFAULT 'private',
2269
+ config TEXT,
2270
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2271
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2272
+ );
2273
+ CREATE INDEX IF NOT EXISTS idx_rooms_status ON rooms(status);
2274
+ CREATE TABLE IF NOT EXISTS room_activity (
2275
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2276
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2277
+ event_type TEXT NOT NULL,
2278
+ actor_id INTEGER,
2279
+ summary TEXT NOT NULL,
2280
+ details TEXT,
2281
+ is_public INTEGER NOT NULL DEFAULT 1,
2282
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2283
+ );
2284
+ CREATE INDEX IF NOT EXISTS idx_room_activity_room ON room_activity(room_id);
2285
+ CREATE INDEX IF NOT EXISTS idx_room_activity_type ON room_activity(event_type);
2286
+ INSERT OR IGNORE INTO schema_version (version) VALUES (14);`
2287
+ },
2288
+ {
2289
+ version: 15,
2290
+ label: "quorum decisions + votes",
2291
+ sql: `
2292
+ CREATE TABLE IF NOT EXISTS quorum_decisions (
2293
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2294
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2295
+ proposer_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2296
+ proposal TEXT NOT NULL,
2297
+ decision_type TEXT NOT NULL DEFAULT 'low_impact',
2298
+ status TEXT NOT NULL DEFAULT 'voting',
2299
+ result TEXT,
2300
+ threshold TEXT NOT NULL DEFAULT 'majority',
2301
+ timeout_at DATETIME,
2302
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2303
+ resolved_at DATETIME
2304
+ );
2305
+ CREATE INDEX IF NOT EXISTS idx_quorum_decisions_room ON quorum_decisions(room_id);
2306
+ CREATE INDEX IF NOT EXISTS idx_quorum_decisions_status ON quorum_decisions(status);
2307
+ CREATE TABLE IF NOT EXISTS quorum_votes (
2308
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2309
+ decision_id INTEGER NOT NULL REFERENCES quorum_decisions(id) ON DELETE CASCADE,
2310
+ worker_id INTEGER NOT NULL REFERENCES workers(id) ON DELETE CASCADE,
2311
+ vote TEXT NOT NULL,
2312
+ reasoning TEXT,
2313
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2314
+ UNIQUE(decision_id, worker_id)
2315
+ );
2316
+ CREATE INDEX IF NOT EXISTS idx_quorum_votes_decision ON quorum_votes(decision_id);
2317
+ INSERT OR IGNORE INTO schema_version (version) VALUES (15);`
2318
+ },
2319
+ {
2320
+ version: 16,
2321
+ label: "goals + goal updates",
2322
+ sql: `
2323
+ CREATE TABLE IF NOT EXISTS goals (
2324
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2325
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2326
+ description TEXT NOT NULL,
2327
+ status TEXT NOT NULL DEFAULT 'active',
2328
+ parent_goal_id INTEGER REFERENCES goals(id) ON DELETE CASCADE,
2329
+ assigned_worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2330
+ progress REAL NOT NULL DEFAULT 0.0,
2331
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2332
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2333
+ );
2334
+ CREATE INDEX IF NOT EXISTS idx_goals_room ON goals(room_id);
2335
+ CREATE INDEX IF NOT EXISTS idx_goals_parent ON goals(parent_goal_id);
2336
+ CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(status);
2337
+ CREATE TABLE IF NOT EXISTS goal_updates (
2338
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2339
+ goal_id INTEGER NOT NULL REFERENCES goals(id) ON DELETE CASCADE,
2340
+ worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2341
+ observation TEXT NOT NULL,
2342
+ metric_value REAL,
2343
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2344
+ );
2345
+ CREATE INDEX IF NOT EXISTS idx_goal_updates_goal ON goal_updates(goal_id);
2346
+ INSERT OR IGNORE INTO schema_version (version) VALUES (16);`
2347
+ },
2348
+ {
2349
+ version: 17,
2350
+ label: "skills",
2351
+ sql: `
2352
+ CREATE TABLE IF NOT EXISTS skills (
2353
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2354
+ room_id INTEGER REFERENCES rooms(id) ON DELETE CASCADE,
2355
+ name TEXT NOT NULL,
2356
+ content TEXT NOT NULL,
2357
+ activation_context TEXT,
2358
+ auto_activate INTEGER NOT NULL DEFAULT 0,
2359
+ agent_created INTEGER NOT NULL DEFAULT 0,
2360
+ created_by_worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2361
+ version INTEGER NOT NULL DEFAULT 1,
2362
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2363
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2364
+ );
2365
+ CREATE INDEX IF NOT EXISTS idx_skills_room ON skills(room_id);
2366
+ CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
2367
+ INSERT OR IGNORE INTO schema_version (version) VALUES (17);`
2368
+ },
2369
+ {
2370
+ version: 18,
2371
+ label: "self-mod audit + escalations",
2372
+ sql: `
2373
+ CREATE TABLE IF NOT EXISTS self_mod_audit (
2374
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2375
+ room_id INTEGER REFERENCES rooms(id) ON DELETE CASCADE,
2376
+ worker_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2377
+ file_path TEXT NOT NULL,
2378
+ old_hash TEXT,
2379
+ new_hash TEXT,
2380
+ reason TEXT,
2381
+ reversible INTEGER NOT NULL DEFAULT 1,
2382
+ reverted INTEGER NOT NULL DEFAULT 0,
2383
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2384
+ );
2385
+ CREATE INDEX IF NOT EXISTS idx_self_mod_audit_room ON self_mod_audit(room_id);
2386
+ CREATE TABLE IF NOT EXISTS escalations (
2387
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2388
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2389
+ from_agent_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2390
+ to_agent_id INTEGER REFERENCES workers(id) ON DELETE SET NULL,
2391
+ question TEXT NOT NULL,
2392
+ answer TEXT,
2393
+ status TEXT NOT NULL DEFAULT 'pending',
2394
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2395
+ resolved_at DATETIME
2396
+ );
2397
+ CREATE INDEX IF NOT EXISTS idx_escalations_room ON escalations(room_id);
2398
+ CREATE INDEX IF NOT EXISTS idx_escalations_status ON escalations(status);
2399
+ INSERT OR IGNORE INTO schema_version (version) VALUES (18);`
2400
+ },
2401
+ {
2402
+ version: 19,
2403
+ label: "credentials + worker room extensions",
2404
+ sql: `
2405
+ CREATE TABLE IF NOT EXISTS credentials (
2406
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2407
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2408
+ name TEXT NOT NULL,
2409
+ type TEXT NOT NULL DEFAULT 'other',
2410
+ value_encrypted TEXT NOT NULL,
2411
+ provided_by TEXT NOT NULL DEFAULT 'keeper',
2412
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2413
+ );
2414
+ CREATE INDEX IF NOT EXISTS idx_credentials_room ON credentials(room_id);
2415
+ ALTER TABLE workers ADD COLUMN room_id INTEGER;
2416
+ ALTER TABLE workers ADD COLUMN agent_state TEXT NOT NULL DEFAULT 'idle';
2417
+ INSERT OR IGNORE INTO schema_version (version) VALUES (19);`
2418
+ },
2419
+ {
2420
+ version: 20,
2421
+ label: "wallets, wallet transactions, stations",
2422
+ sql: `
2423
+ CREATE TABLE IF NOT EXISTS wallets (
2424
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2425
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2426
+ address TEXT NOT NULL,
2427
+ private_key_encrypted TEXT NOT NULL,
2428
+ chain TEXT NOT NULL DEFAULT 'base',
2429
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2430
+ );
2431
+ CREATE INDEX IF NOT EXISTS idx_wallets_room ON wallets(room_id);
2432
+ CREATE TABLE IF NOT EXISTS wallet_transactions (
2433
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2434
+ wallet_id INTEGER NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
2435
+ type TEXT NOT NULL,
2436
+ amount TEXT NOT NULL,
2437
+ counterparty TEXT,
2438
+ tx_hash TEXT,
2439
+ description TEXT,
2440
+ status TEXT NOT NULL DEFAULT 'confirmed',
2441
+ created_at DATETIME DEFAULT (datetime('now','localtime'))
2442
+ );
2443
+ CREATE INDEX IF NOT EXISTS idx_wallet_tx_wallet ON wallet_transactions(wallet_id);
2444
+ CREATE TABLE IF NOT EXISTS stations (
2445
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2446
+ room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
2447
+ name TEXT NOT NULL,
2448
+ provider TEXT NOT NULL,
2449
+ external_id TEXT,
2450
+ tier TEXT NOT NULL,
2451
+ region TEXT,
2452
+ status TEXT NOT NULL DEFAULT 'provisioning',
2453
+ monthly_cost REAL NOT NULL DEFAULT 0,
2454
+ config TEXT,
2455
+ created_at DATETIME DEFAULT (datetime('now','localtime')),
2456
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
2457
+ );
2458
+ CREATE INDEX IF NOT EXISTS idx_stations_room ON stations(room_id);
2459
+ INSERT OR IGNORE INTO schema_version (version) VALUES (20);`
2460
+ },
2461
+ {
2462
+ version: 21,
2463
+ label: "ERC-8004 on-chain identity",
2464
+ sql: `
2465
+ ALTER TABLE wallets ADD COLUMN erc8004_agent_id TEXT;
2466
+ INSERT OR IGNORE INTO schema_version (version) VALUES (21);`
2467
+ }
2468
+ ];
2469
+
2470
+ // src/shared/db-migrations.ts
2471
+ function runMigrations(database, log = console.log) {
2472
+ const hasVersionTable = database.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'").get();
2473
+ if (!hasVersionTable) {
2474
+ database.exec(SCHEMA);
2475
+ log("Database schema initialized (fresh install)");
2476
+ return;
2477
+ }
2478
+ const row = database.prepare("SELECT MAX(version) as version FROM schema_version").get();
2479
+ const currentVersion = row?.version ?? 0;
2480
+ for (const migration of MIGRATIONS) {
2481
+ if (currentVersion < migration.version) {
2482
+ database.transaction(() => {
2483
+ database.exec(migration.sql);
2484
+ })();
2485
+ log(`Database schema v${migration.version} applied (${migration.label})`);
2486
+ }
2487
+ }
2488
+ }
2489
+
2490
+ // src/shared/embeddings.ts
2491
+ var sqliteVecLoaded = false;
2492
+ function loadSqliteVec(db2) {
2493
+ if (sqliteVecLoaded) return true;
2494
+ try {
2495
+ const sqliteVec = require("sqlite-vec");
2496
+ sqliteVec.load(db2);
2497
+ sqliteVecLoaded = true;
2498
+ return true;
2499
+ } catch {
2500
+ return false;
2501
+ }
2502
+ }
2503
+
2504
+ // src/server/db.ts
2505
+ var db = null;
2506
+ function expandTilde(p) {
2507
+ if (p.startsWith("~/") || p === "~") {
2508
+ return p.replace("~", (0, import_os.homedir)());
2509
+ }
2510
+ return p;
2511
+ }
2512
+ function getDefaultDataDir() {
2513
+ return (0, import_path.join)((0, import_os.homedir)(), ".quoroom");
2514
+ }
2515
+ function getDataDir() {
2516
+ const raw = process.env.QUOROOM_DATA_DIR || getDefaultDataDir();
2517
+ return expandTilde(raw);
2518
+ }
2519
+ function getServerDatabase() {
2520
+ if (db) return db;
2521
+ const dataDir = getDataDir();
2522
+ (0, import_fs.mkdirSync)(dataDir, { recursive: true });
2523
+ const rawPath = process.env.QUOROOM_DB_PATH || process.env.DAYMON_DB_PATH || (0, import_path.join)(dataDir, "data.db");
2524
+ const dbPath = expandTilde(rawPath);
2525
+ db = new import_better_sqlite3.default(dbPath);
2526
+ db.pragma("journal_mode = WAL");
2527
+ db.pragma("foreign_keys = ON");
2528
+ db.pragma("busy_timeout = 5000");
2529
+ loadSqliteVec(db);
2530
+ runMigrations(db, (msg) => console.error(`API server: ${msg}`));
2531
+ const cleaned = cleanupAllRunningRuns(db);
2532
+ if (cleaned > 0) {
2533
+ console.error(`API server: Cleaned up ${cleaned} stale task run(s)`);
2534
+ }
2535
+ return db;
2536
+ }
2537
+ function closeServerDatabase() {
2538
+ if (db) {
2539
+ db.close();
2540
+ db = null;
2541
+ }
2542
+ }
2543
+
2544
+ // src/server/routes/status.ts
2545
+ var startedAt = Date.now();
2546
+ var cachedVersion = null;
2547
+ function getVersion() {
2548
+ if (cachedVersion) return cachedVersion;
2549
+ try {
2550
+ cachedVersion = require_package().version;
2551
+ } catch {
2552
+ cachedVersion = "unknown";
2553
+ }
2554
+ return cachedVersion;
2555
+ }
2556
+ var cachedClaudeCheck = null;
2557
+ function checkClaude() {
2558
+ if (cachedClaudeCheck) return cachedClaudeCheck;
2559
+ try {
2560
+ const out = (0, import_node_child_process.execSync)("claude --version 2>/dev/null", { timeout: 5e3 }).toString().trim();
2561
+ cachedClaudeCheck = { available: true, version: out };
2562
+ } catch {
2563
+ cachedClaudeCheck = { available: false };
2564
+ }
2565
+ return cachedClaudeCheck;
2566
+ }
2567
+ function registerStatusRoutes(router) {
2568
+ router.get("/api/status", (ctx) => {
2569
+ const dataDir = getDataDir();
2570
+ const dbPath = ctx.db.name;
2571
+ const claude = checkClaude();
2572
+ return {
2573
+ data: {
2574
+ version: getVersion(),
2575
+ uptime: Math.floor((Date.now() - startedAt) / 1e3),
2576
+ dataDir,
2577
+ dbPath,
2578
+ claude
2579
+ }
2580
+ };
2581
+ });
2582
+ }
2583
+
2584
+ // src/server/routes/index.ts
2585
+ function registerAllRoutes(router) {
2586
+ registerRoomRoutes(router);
2587
+ registerWorkerRoutes(router);
2588
+ registerGoalRoutes(router);
2589
+ registerDecisionRoutes(router);
2590
+ registerTaskRoutes(router);
2591
+ registerRunRoutes(router);
2592
+ registerMemoryRoutes(router);
2593
+ registerSkillRoutes(router);
2594
+ registerWatchRoutes(router);
2595
+ registerSettingRoutes(router);
2596
+ registerEscalationRoutes(router);
2597
+ registerSelfModRoutes(router);
2598
+ registerStatusRoutes(router);
2599
+ }
2600
+
2601
+ // src/server/ws.ts
2602
+ var import_ws = require("ws");
2603
+ function createWsServer(server) {
2604
+ const wss = new import_ws.WebSocketServer({ noServer: true });
2605
+ const clients = /* @__PURE__ */ new Map();
2606
+ server.on("upgrade", (req, socket, head) => {
2607
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
2608
+ if (url.pathname !== "/ws") {
2609
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
2610
+ socket.destroy();
2611
+ return;
2612
+ }
2613
+ const token = url.searchParams.get("token");
2614
+ if (!validateToken(token ? `Bearer ${token}` : void 0)) {
2615
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
2616
+ socket.destroy();
2617
+ return;
2618
+ }
2619
+ wss.handleUpgrade(req, socket, head, (ws) => {
2620
+ wss.emit("connection", ws, req);
2621
+ });
2622
+ });
2623
+ wss.on("connection", (ws) => {
2624
+ clients.set(ws, { channels: /* @__PURE__ */ new Set() });
2625
+ ws.on("message", (data) => {
2626
+ try {
2627
+ const msg = JSON.parse(data.toString());
2628
+ handleClientMessage(ws, clients.get(ws), msg);
2629
+ } catch {
2630
+ sendError(ws, "Invalid JSON");
2631
+ }
2632
+ });
2633
+ ws.on("close", () => {
2634
+ clients.delete(ws);
2635
+ });
2636
+ });
2637
+ const unsub = eventBus.onAny((event) => {
2638
+ for (const [ws, state] of clients) {
2639
+ if (ws.readyState !== import_ws.WebSocket.OPEN) continue;
2640
+ if (state.channels.has(event.channel)) {
2641
+ ws.send(JSON.stringify(event));
2642
+ }
2643
+ }
2644
+ });
2645
+ wss.on("close", () => {
2646
+ unsub();
2647
+ });
2648
+ return wss;
2649
+ }
2650
+ function handleClientMessage(ws, state, msg) {
2651
+ if (msg.type === "subscribe" && Array.isArray(msg.channels)) {
2652
+ for (const ch of msg.channels) {
2653
+ if (typeof ch === "string") state.channels.add(ch);
2654
+ }
2655
+ ws.send(JSON.stringify({
2656
+ type: "subscribed",
2657
+ channels: [...state.channels]
2658
+ }));
2659
+ return;
2660
+ }
2661
+ if (msg.type === "unsubscribe" && Array.isArray(msg.channels)) {
2662
+ for (const ch of msg.channels) {
2663
+ state.channels.delete(ch);
2664
+ }
2665
+ ws.send(JSON.stringify({
2666
+ type: "unsubscribed",
2667
+ channels: [...state.channels]
2668
+ }));
2669
+ return;
2670
+ }
2671
+ if (msg.type === "ping") {
2672
+ ws.send(JSON.stringify({ type: "pong" }));
2673
+ return;
2674
+ }
2675
+ sendError(ws, `Unknown message type: ${msg.type}`);
2676
+ }
2677
+ function sendError(ws, message) {
2678
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
2679
+ ws.send(JSON.stringify({ type: "error", message }));
2680
+ }
2681
+ }
2682
+
2683
+ // src/server/index.ts
2684
+ var DEFAULT_PORT = 3700;
2685
+ function parseBody(req) {
2686
+ return new Promise((resolve, reject) => {
2687
+ const chunks = [];
2688
+ req.on("data", (chunk) => chunks.push(chunk));
2689
+ req.on("end", () => {
2690
+ const raw = Buffer.concat(chunks).toString();
2691
+ if (!raw) return resolve(void 0);
2692
+ try {
2693
+ resolve(JSON.parse(raw));
2694
+ } catch {
2695
+ reject(new Error("Invalid JSON body"));
2696
+ }
2697
+ });
2698
+ req.on("error", reject);
2699
+ });
2700
+ }
2701
+ var MIME_TYPES = {
2702
+ ".html": "text/html; charset=utf-8",
2703
+ ".js": "application/javascript; charset=utf-8",
2704
+ ".css": "text/css; charset=utf-8",
2705
+ ".json": "application/json; charset=utf-8",
2706
+ ".png": "image/png",
2707
+ ".jpg": "image/jpeg",
2708
+ ".jpeg": "image/jpeg",
2709
+ ".svg": "image/svg+xml",
2710
+ ".ico": "image/x-icon",
2711
+ ".woff": "font/woff",
2712
+ ".woff2": "font/woff2",
2713
+ ".webp": "image/webp",
2714
+ ".webmanifest": "application/manifest+json"
2715
+ };
2716
+ function serveStatic(staticDir, pathname, res) {
2717
+ const safePath = import_node_path2.default.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
2718
+ let filePath = import_node_path2.default.join(staticDir, safePath);
2719
+ try {
2720
+ const stat = import_node_fs2.default.statSync(filePath);
2721
+ if (stat.isDirectory()) {
2722
+ filePath = import_node_path2.default.join(filePath, "index.html");
2723
+ }
2724
+ } catch {
2725
+ filePath = import_node_path2.default.join(staticDir, "index.html");
2726
+ }
2727
+ try {
2728
+ const data = import_node_fs2.default.readFileSync(filePath);
2729
+ const ext = import_node_path2.default.extname(filePath).toLowerCase();
2730
+ const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2731
+ res.writeHead(200, { "Content-Type": contentType });
2732
+ res.end(data);
2733
+ } catch {
2734
+ res.writeHead(404, { "Content-Type": "text/plain" });
2735
+ res.end("Not Found");
2736
+ }
2737
+ }
2738
+ function createApiServer(options = {}) {
2739
+ const db2 = options.db ?? getServerDatabase();
2740
+ const port = options.port ?? DEFAULT_PORT;
2741
+ const dataDir = options.dataDir ?? getDataDir();
2742
+ const router = new Router();
2743
+ registerAllRoutes(router);
2744
+ const token = generateToken();
2745
+ if (!options.skipTokenFile) {
2746
+ writeTokenFile(dataDir, token, port);
2747
+ }
2748
+ const server = import_node_http.default.createServer(async (req, res) => {
2749
+ const url = new import_node_url.URL(req.url, `http://${req.headers.host || "localhost"}`);
2750
+ const pathname = url.pathname;
2751
+ const origin = req.headers.origin;
2752
+ if (req.method === "OPTIONS") {
2753
+ const headers = {};
2754
+ setCorsHeaders(origin, headers);
2755
+ res.writeHead(204, headers);
2756
+ res.end();
2757
+ return;
2758
+ }
2759
+ const responseHeaders = {
2760
+ "Content-Type": "application/json"
2761
+ };
2762
+ setCorsHeaders(origin, responseHeaders);
2763
+ if (pathname.startsWith("/api/") && !isAllowedOrigin(origin)) {
2764
+ res.writeHead(403, responseHeaders);
2765
+ res.end(JSON.stringify({ error: "Forbidden origin" }));
2766
+ return;
2767
+ }
2768
+ if (pathname === "/api/auth/handshake" && req.method === "GET") {
2769
+ res.writeHead(200, responseHeaders);
2770
+ res.end(JSON.stringify({ token: getToken() }));
2771
+ return;
2772
+ }
2773
+ if (pathname === "/api/auth/verify" && req.method === "GET") {
2774
+ if (!validateToken(req.headers.authorization)) {
2775
+ res.writeHead(401, responseHeaders);
2776
+ res.end(JSON.stringify({ error: "Unauthorized" }));
2777
+ return;
2778
+ }
2779
+ res.writeHead(200, responseHeaders);
2780
+ res.end(JSON.stringify({ ok: true }));
2781
+ return;
2782
+ }
2783
+ if (pathname.startsWith("/api/")) {
2784
+ if (!validateToken(req.headers.authorization)) {
2785
+ res.writeHead(401, responseHeaders);
2786
+ res.end(JSON.stringify({ error: "Unauthorized" }));
2787
+ return;
2788
+ }
2789
+ const matched = router.match(req.method, pathname);
2790
+ if (!matched) {
2791
+ res.writeHead(404, responseHeaders);
2792
+ res.end(JSON.stringify({ error: "Not found" }));
2793
+ return;
2794
+ }
2795
+ try {
2796
+ const body = await parseBody(req);
2797
+ const query = Object.fromEntries(url.searchParams);
2798
+ const ctx = {
2799
+ params: matched.params,
2800
+ query,
2801
+ body,
2802
+ db: db2
2803
+ };
2804
+ const result = await matched.handler(ctx);
2805
+ const status = result.error ? result.status || 400 : result.status || 200;
2806
+ res.writeHead(status, responseHeaders);
2807
+ res.end(JSON.stringify(result.error ? { error: result.error } : result.data));
2808
+ } catch (err) {
2809
+ const message = err instanceof Error ? err.message : "Internal error";
2810
+ res.writeHead(500, responseHeaders);
2811
+ res.end(JSON.stringify({ error: message }));
2812
+ }
2813
+ return;
2814
+ }
2815
+ if (options.staticDir) {
2816
+ serveStatic(options.staticDir, pathname, res);
2817
+ } else {
2818
+ res.writeHead(404, { "Content-Type": "text/plain" });
2819
+ res.end("Not Found");
2820
+ }
2821
+ });
2822
+ createWsServer(server);
2823
+ return { server, token, db: db2 };
2824
+ }
2825
+ function startServer(options = {}) {
2826
+ const port = options.port ?? DEFAULT_PORT;
2827
+ if (!options.staticDir) {
2828
+ const defaultUiDir = import_node_path2.default.join(__dirname, "../ui");
2829
+ if (import_node_fs2.default.existsSync(defaultUiDir)) {
2830
+ options.staticDir = defaultUiDir;
2831
+ }
2832
+ }
2833
+ const { server, token } = createApiServer(options);
2834
+ server.listen(port, "127.0.0.1", () => {
2835
+ console.error(`Quoroom API server started on http://localhost:${port}`);
2836
+ console.error(`Auth token: ${token.slice(0, 8)}...`);
2837
+ console.error(`WebSocket: ws://localhost:${port}/ws?token=<token>`);
2838
+ });
2839
+ process.on("SIGINT", () => {
2840
+ console.error("Shutting down...");
2841
+ server.close();
2842
+ closeServerDatabase();
2843
+ process.exit(0);
2844
+ });
2845
+ process.on("SIGTERM", () => {
2846
+ server.close();
2847
+ closeServerDatabase();
2848
+ process.exit(0);
2849
+ });
2850
+ }
2851
+ // Annotate the CommonJS export names for ESM import in node:
2852
+ 0 && (module.exports = {
2853
+ createApiServer,
2854
+ startServer
2855
+ });