@askexenow/exe-os 0.8.83 → 0.8.85

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.
Files changed (95) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +97 -2
  5. package/dist/bin/cli.js +14350 -12518
  6. package/dist/bin/exe-agent.js +97 -88
  7. package/dist/bin/exe-assign.js +1003 -854
  8. package/dist/bin/exe-boot.js +1257 -320
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +29 -6
  11. package/dist/bin/exe-dispatch.js +210 -34
  12. package/dist/bin/exe-doctor.js +403 -6
  13. package/dist/bin/exe-export-behaviors.js +175 -72
  14. package/dist/bin/exe-forget.js +97 -2
  15. package/dist/bin/exe-gateway.js +550 -171
  16. package/dist/bin/exe-healthcheck.js +1 -0
  17. package/dist/bin/exe-heartbeat.js +100 -5
  18. package/dist/bin/exe-kill.js +175 -72
  19. package/dist/bin/exe-launch-agent.js +189 -76
  20. package/dist/bin/exe-link.js +902 -80
  21. package/dist/bin/exe-new-employee.js +38 -8
  22. package/dist/bin/exe-pending-messages.js +96 -2
  23. package/dist/bin/exe-pending-notifications.js +97 -2
  24. package/dist/bin/exe-pending-reviews.js +98 -3
  25. package/dist/bin/exe-rename.js +564 -23
  26. package/dist/bin/exe-review.js +231 -73
  27. package/dist/bin/exe-search.js +989 -226
  28. package/dist/bin/exe-session-cleanup.js +4806 -1665
  29. package/dist/bin/exe-settings.js +20 -5
  30. package/dist/bin/exe-status.js +97 -2
  31. package/dist/bin/exe-team.js +97 -2
  32. package/dist/bin/git-sweep.js +899 -207
  33. package/dist/bin/graph-backfill.js +175 -72
  34. package/dist/bin/graph-export.js +175 -72
  35. package/dist/bin/install.js +38 -7
  36. package/dist/bin/list-providers.js +1 -0
  37. package/dist/bin/scan-tasks.js +904 -211
  38. package/dist/bin/setup.js +867 -268
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +1 -0
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +548 -166
  43. package/dist/hooks/bug-report-worker.js +208 -23
  44. package/dist/hooks/commit-complete.js +897 -205
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1638 -1194
  47. package/dist/hooks/ingest.js +3 -0
  48. package/dist/hooks/instructions-loaded.js +707 -97
  49. package/dist/hooks/notification.js +699 -89
  50. package/dist/hooks/post-compact.js +714 -104
  51. package/dist/hooks/pre-compact.js +897 -205
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +242 -101
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +242 -101
  56. package/dist/hooks/session-end.js +3941 -400
  57. package/dist/hooks/session-start.js +1001 -226
  58. package/dist/hooks/stop.js +725 -115
  59. package/dist/hooks/subagent-stop.js +714 -104
  60. package/dist/hooks/summary-worker.js +1964 -1330
  61. package/dist/index.js +1651 -1053
  62. package/dist/lib/cloud-sync.js +907 -86
  63. package/dist/lib/consolidation.js +2 -1
  64. package/dist/lib/database.js +642 -87
  65. package/dist/lib/db-daemon-client.js +503 -0
  66. package/dist/lib/device-registry.js +547 -7
  67. package/dist/lib/embedder.js +14 -28
  68. package/dist/lib/employee-templates.js +84 -74
  69. package/dist/lib/employees.js +9 -0
  70. package/dist/lib/exe-daemon-client.js +16 -29
  71. package/dist/lib/exe-daemon.js +1955 -922
  72. package/dist/lib/hybrid-search.js +988 -226
  73. package/dist/lib/identity.js +87 -67
  74. package/dist/lib/keychain.js +9 -1
  75. package/dist/lib/messaging.js +8 -1
  76. package/dist/lib/reminders.js +91 -74
  77. package/dist/lib/schedules.js +96 -2
  78. package/dist/lib/skill-learning.js +103 -85
  79. package/dist/lib/store.js +234 -73
  80. package/dist/lib/tasks.js +111 -22
  81. package/dist/lib/tmux-routing.js +120 -31
  82. package/dist/lib/token-spend.js +273 -0
  83. package/dist/lib/ws-client.js +11 -0
  84. package/dist/mcp/server.js +5222 -475
  85. package/dist/mcp/tools/complete-reminder.js +94 -77
  86. package/dist/mcp/tools/create-reminder.js +94 -77
  87. package/dist/mcp/tools/create-task.js +120 -22
  88. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  89. package/dist/mcp/tools/list-reminders.js +94 -77
  90. package/dist/mcp/tools/list-tasks.js +31 -1
  91. package/dist/mcp/tools/send-message.js +8 -1
  92. package/dist/mcp/tools/update-task.js +39 -10
  93. package/dist/runtime/index.js +911 -219
  94. package/dist/tui/App.js +997 -295
  95. package/package.json +6 -1
@@ -243,369 +243,693 @@ var init_employees = __esm({
243
243
  }
244
244
  });
245
245
 
246
- // src/lib/db-retry.ts
247
- function isBusyError(err) {
248
- if (err instanceof Error) {
249
- const msg = err.message.toLowerCase();
250
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
246
+ // src/lib/exe-daemon-client.ts
247
+ import net from "net";
248
+ import { spawn } from "child_process";
249
+ import { randomUUID as randomUUID2 } from "crypto";
250
+ import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
251
+ import path3 from "path";
252
+ import { fileURLToPath } from "url";
253
+ function handleData(chunk) {
254
+ _buffer += chunk.toString();
255
+ if (_buffer.length > MAX_BUFFER) {
256
+ _buffer = "";
257
+ return;
251
258
  }
252
- return false;
253
- }
254
- function delay(ms) {
255
- return new Promise((resolve) => setTimeout(resolve, ms));
256
- }
257
- async function retryOnBusy(fn, label) {
258
- let lastError;
259
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
259
+ let newlineIdx;
260
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
261
+ const line = _buffer.slice(0, newlineIdx).trim();
262
+ _buffer = _buffer.slice(newlineIdx + 1);
263
+ if (!line) continue;
260
264
  try {
261
- return await fn();
262
- } catch (err) {
263
- lastError = err;
264
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
265
- throw err;
265
+ const response = JSON.parse(line);
266
+ const id = response.id;
267
+ if (!id) continue;
268
+ const entry = _pending.get(id);
269
+ if (entry) {
270
+ clearTimeout(entry.timer);
271
+ _pending.delete(id);
272
+ entry.resolve(response);
266
273
  }
267
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
268
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
269
- process.stderr.write(
270
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
271
- `
272
- );
273
- await delay(backoff + jitter);
274
+ } catch {
274
275
  }
275
276
  }
276
- throw lastError;
277
277
  }
278
- function wrapWithRetry(client) {
279
- return new Proxy(client, {
280
- get(target, prop, receiver) {
281
- if (prop === "execute") {
282
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
283
- }
284
- if (prop === "batch") {
285
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
278
+ function cleanupStaleFiles() {
279
+ if (existsSync3(PID_PATH)) {
280
+ try {
281
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
282
+ if (pid > 0) {
283
+ try {
284
+ process.kill(pid, 0);
285
+ return;
286
+ } catch {
287
+ }
286
288
  }
287
- return Reflect.get(target, prop, receiver);
289
+ } catch {
288
290
  }
289
- });
291
+ try {
292
+ unlinkSync2(PID_PATH);
293
+ } catch {
294
+ }
295
+ try {
296
+ unlinkSync2(SOCKET_PATH);
297
+ } catch {
298
+ }
299
+ }
290
300
  }
291
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
292
- var init_db_retry = __esm({
293
- "src/lib/db-retry.ts"() {
294
- "use strict";
295
- MAX_RETRIES = 3;
296
- BASE_DELAY_MS = 200;
297
- MAX_JITTER_MS = 300;
301
+ function findPackageRoot() {
302
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
303
+ const { root } = path3.parse(dir);
304
+ while (dir !== root) {
305
+ if (existsSync3(path3.join(dir, "package.json"))) return dir;
306
+ dir = path3.dirname(dir);
298
307
  }
299
- });
300
-
301
- // src/lib/database.ts
302
- import { createClient } from "@libsql/client";
303
- async function initDatabase(config) {
304
- if (_client) {
305
- _client.close();
306
- _client = null;
307
- _resilientClient = null;
308
+ return null;
309
+ }
310
+ function spawnDaemon() {
311
+ const pkgRoot = findPackageRoot();
312
+ if (!pkgRoot) {
313
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
314
+ return;
308
315
  }
309
- const opts = {
310
- url: `file:${config.dbPath}`
311
- };
312
- if (config.encryptionKey) {
313
- opts.encryptionKey = config.encryptionKey;
316
+ const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
317
+ if (!existsSync3(daemonPath)) {
318
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
319
+ `);
320
+ return;
314
321
  }
315
- _client = createClient(opts);
316
- _resilientClient = wrapWithRetry(_client);
317
- }
318
- function getClient() {
319
- if (!_resilientClient) {
320
- throw new Error("Database client not initialized. Call initDatabase() first.");
322
+ const resolvedPath = daemonPath;
323
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
324
+ `);
325
+ const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
326
+ let stderrFd = "ignore";
327
+ try {
328
+ stderrFd = openSync(logPath, "a");
329
+ } catch {
330
+ }
331
+ const child = spawn(process.execPath, [resolvedPath], {
332
+ detached: true,
333
+ stdio: ["ignore", "ignore", stderrFd],
334
+ env: {
335
+ ...process.env,
336
+ TMUX: void 0,
337
+ // Daemon is global — must not inherit session scope
338
+ TMUX_PANE: void 0,
339
+ // Prevents resolveExeSession() from scoping to one session
340
+ EXE_DAEMON_SOCK: SOCKET_PATH,
341
+ EXE_DAEMON_PID: PID_PATH
342
+ }
343
+ });
344
+ child.unref();
345
+ if (typeof stderrFd === "number") {
346
+ try {
347
+ closeSync(stderrFd);
348
+ } catch {
349
+ }
321
350
  }
322
- return _resilientClient;
323
351
  }
324
- function getRawClient() {
325
- if (!_client) {
326
- throw new Error("Database client not initialized. Call initDatabase() first.");
352
+ function acquireSpawnLock() {
353
+ try {
354
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
355
+ closeSync(fd);
356
+ return true;
357
+ } catch {
358
+ try {
359
+ const stat = statSync(SPAWN_LOCK_PATH);
360
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
361
+ try {
362
+ unlinkSync2(SPAWN_LOCK_PATH);
363
+ } catch {
364
+ }
365
+ try {
366
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
367
+ closeSync(fd);
368
+ return true;
369
+ } catch {
370
+ }
371
+ }
372
+ } catch {
373
+ }
374
+ return false;
327
375
  }
328
- return _client;
329
376
  }
330
- async function ensureSchema() {
331
- const client = getRawClient();
332
- await client.execute("PRAGMA journal_mode = WAL");
333
- await client.execute("PRAGMA busy_timeout = 30000");
334
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
377
+ function releaseSpawnLock() {
335
378
  try {
336
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
379
+ unlinkSync2(SPAWN_LOCK_PATH);
337
380
  } catch {
338
381
  }
339
- await client.executeMultiple(`
340
- CREATE TABLE IF NOT EXISTS memories (
341
- id TEXT PRIMARY KEY,
342
- agent_id TEXT NOT NULL,
343
- agent_role TEXT NOT NULL,
344
- session_id TEXT NOT NULL,
345
- timestamp TEXT NOT NULL,
346
- tool_name TEXT NOT NULL,
347
- project_name TEXT NOT NULL,
348
- has_error INTEGER NOT NULL DEFAULT 0,
349
- raw_text TEXT NOT NULL,
350
- vector F32_BLOB(1024),
351
- version INTEGER NOT NULL DEFAULT 0
352
- );
353
-
354
- CREATE INDEX IF NOT EXISTS idx_memories_agent
355
- ON memories(agent_id);
356
-
357
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
358
- ON memories(timestamp);
359
-
360
- CREATE INDEX IF NOT EXISTS idx_memories_session
361
- ON memories(session_id);
362
-
363
- CREATE INDEX IF NOT EXISTS idx_memories_project
364
- ON memories(project_name);
365
-
366
- CREATE INDEX IF NOT EXISTS idx_memories_tool
367
- ON memories(tool_name);
368
-
369
- CREATE INDEX IF NOT EXISTS idx_memories_version
370
- ON memories(version);
371
-
372
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
373
- ON memories(agent_id, project_name);
374
- `);
375
- await client.executeMultiple(`
376
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
377
- raw_text,
378
- content='memories',
379
- content_rowid='rowid'
380
- );
381
-
382
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
383
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
384
- END;
385
-
386
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
387
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
388
- END;
389
-
390
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
391
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
392
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
393
- END;
394
- `);
395
- await client.executeMultiple(`
396
- CREATE TABLE IF NOT EXISTS sync_meta (
397
- key TEXT PRIMARY KEY,
398
- value TEXT NOT NULL
399
- );
400
- `);
401
- await client.executeMultiple(`
402
- CREATE TABLE IF NOT EXISTS tasks (
403
- id TEXT PRIMARY KEY,
404
- title TEXT NOT NULL,
405
- assigned_to TEXT NOT NULL,
406
- assigned_by TEXT NOT NULL,
407
- project_name TEXT NOT NULL,
408
- priority TEXT NOT NULL DEFAULT 'p1',
409
- status TEXT NOT NULL DEFAULT 'open',
410
- task_file TEXT,
411
- created_at TEXT NOT NULL,
412
- updated_at TEXT NOT NULL
413
- );
414
-
415
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
416
- ON tasks(assigned_to, status);
417
- `);
418
- await client.executeMultiple(`
419
- CREATE TABLE IF NOT EXISTS behaviors (
420
- id TEXT PRIMARY KEY,
421
- agent_id TEXT NOT NULL,
422
- project_name TEXT,
423
- domain TEXT,
424
- content TEXT NOT NULL,
425
- active INTEGER NOT NULL DEFAULT 1,
426
- created_at TEXT NOT NULL,
427
- updated_at TEXT NOT NULL
428
- );
429
-
430
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
431
- ON behaviors(agent_id, active);
432
- `);
433
- try {
434
- const coordinatorName = getCoordinatorName();
435
- const existing = await client.execute({
436
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
437
- args: [coordinatorName]
438
- });
439
- if (Number(existing.rows[0]?.cnt) === 0) {
440
- const seededAt = "2026-03-25T00:00:00Z";
441
- for (const [domain, content] of [
442
- ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
443
- ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
444
- ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
445
- ]) {
446
- await client.execute({
447
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
448
- VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
449
- args: [coordinatorName, domain, content, seededAt, seededAt]
450
- });
451
- }
382
+ }
383
+ function connectToSocket() {
384
+ return new Promise((resolve) => {
385
+ if (_socket && _connected) {
386
+ resolve(true);
387
+ return;
452
388
  }
453
- } catch {
454
- }
455
- try {
456
- await client.execute({
457
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
458
- args: []
389
+ const socket = net.createConnection({ path: SOCKET_PATH });
390
+ const connectTimeout = setTimeout(() => {
391
+ socket.destroy();
392
+ resolve(false);
393
+ }, 2e3);
394
+ socket.on("connect", () => {
395
+ clearTimeout(connectTimeout);
396
+ _socket = socket;
397
+ _connected = true;
398
+ _buffer = "";
399
+ socket.on("data", handleData);
400
+ socket.on("close", () => {
401
+ _connected = false;
402
+ _socket = null;
403
+ for (const [id, entry] of _pending) {
404
+ clearTimeout(entry.timer);
405
+ _pending.delete(id);
406
+ entry.resolve({ error: "Connection closed" });
407
+ }
408
+ });
409
+ socket.on("error", () => {
410
+ _connected = false;
411
+ _socket = null;
412
+ });
413
+ resolve(true);
459
414
  });
460
- } catch {
461
- }
462
- try {
463
- await client.execute({
464
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
465
- args: []
415
+ socket.on("error", () => {
416
+ clearTimeout(connectTimeout);
417
+ resolve(false);
466
418
  });
467
- } catch {
419
+ });
420
+ }
421
+ async function connectEmbedDaemon() {
422
+ if (_socket && _connected) return true;
423
+ if (await connectToSocket()) return true;
424
+ if (acquireSpawnLock()) {
425
+ try {
426
+ cleanupStaleFiles();
427
+ spawnDaemon();
428
+ } finally {
429
+ releaseSpawnLock();
430
+ }
468
431
  }
469
- try {
470
- await client.execute({
471
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
472
- args: []
473
- });
474
- } catch {
432
+ const start = Date.now();
433
+ let delay2 = 100;
434
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
435
+ await new Promise((r) => setTimeout(r, delay2));
436
+ if (await connectToSocket()) return true;
437
+ delay2 = Math.min(delay2 * 2, 3e3);
475
438
  }
476
- try {
477
- await client.execute({
478
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
479
- ON tasks(parent_task_id)
480
- WHERE parent_task_id IS NOT NULL`,
481
- args: []
482
- });
483
- } catch {
439
+ return false;
440
+ }
441
+ function sendRequest(texts, priority) {
442
+ return sendDaemonRequest({ texts, priority });
443
+ }
444
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
445
+ return new Promise((resolve) => {
446
+ if (!_socket || !_connected) {
447
+ resolve({ error: "Not connected" });
448
+ return;
449
+ }
450
+ const id = randomUUID2();
451
+ const timer = setTimeout(() => {
452
+ _pending.delete(id);
453
+ resolve({ error: "Request timeout" });
454
+ }, timeoutMs);
455
+ _pending.set(id, { resolve, timer });
456
+ try {
457
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
458
+ } catch {
459
+ clearTimeout(timer);
460
+ _pending.delete(id);
461
+ resolve({ error: "Write failed" });
462
+ }
463
+ });
464
+ }
465
+ async function pingDaemon() {
466
+ if (!_socket || !_connected) return null;
467
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
468
+ if (response.health) {
469
+ return response.health;
484
470
  }
485
- try {
486
- await client.execute({
487
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
488
- args: []
489
- });
490
- } catch {
471
+ return null;
472
+ }
473
+ function killAndRespawnDaemon() {
474
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
475
+ if (existsSync3(PID_PATH)) {
476
+ try {
477
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
478
+ if (pid > 0) {
479
+ try {
480
+ process.kill(pid, "SIGKILL");
481
+ } catch {
482
+ }
483
+ }
484
+ } catch {
485
+ }
491
486
  }
492
- try {
493
- await client.execute({
494
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
495
- args: []
496
- });
497
- } catch {
487
+ if (_socket) {
488
+ _socket.destroy();
489
+ _socket = null;
498
490
  }
491
+ _connected = false;
492
+ _buffer = "";
499
493
  try {
500
- await client.execute({
501
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
502
- args: []
503
- });
494
+ unlinkSync2(PID_PATH);
504
495
  } catch {
505
496
  }
506
497
  try {
507
- await client.execute({
508
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
509
- args: []
510
- });
498
+ unlinkSync2(SOCKET_PATH);
511
499
  } catch {
512
500
  }
513
- try {
514
- await client.execute({
515
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
516
- args: []
517
- });
518
- } catch {
501
+ spawnDaemon();
502
+ }
503
+ async function embedViaClient(text, priority = "high") {
504
+ if (!_connected && !await connectEmbedDaemon()) return null;
505
+ _requestCount++;
506
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
507
+ const health = await pingDaemon();
508
+ if (!health) {
509
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
510
+ `);
511
+ killAndRespawnDaemon();
512
+ const start = Date.now();
513
+ let delay2 = 200;
514
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
515
+ await new Promise((r) => setTimeout(r, delay2));
516
+ if (await connectToSocket()) break;
517
+ delay2 = Math.min(delay2 * 2, 3e3);
518
+ }
519
+ if (!_connected) return null;
520
+ }
519
521
  }
520
- try {
521
- await client.execute({
522
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
523
- args: []
524
- });
525
- } catch {
522
+ const result = await sendRequest([text], priority);
523
+ if (!result.error && result.vectors?.[0]) return result.vectors[0];
524
+ if (result.error) {
525
+ process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
526
+ `);
527
+ killAndRespawnDaemon();
528
+ const start = Date.now();
529
+ let delay2 = 200;
530
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
531
+ await new Promise((r) => setTimeout(r, delay2));
532
+ if (await connectToSocket()) break;
533
+ delay2 = Math.min(delay2 * 2, 3e3);
534
+ }
535
+ if (!_connected) return null;
536
+ const retry = await sendRequest([text], priority);
537
+ if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
538
+ process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
539
+ `);
526
540
  }
527
- try {
528
- await client.execute({
529
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
530
- args: []
531
- });
532
- } catch {
541
+ return null;
542
+ }
543
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
544
+ var init_exe_daemon_client = __esm({
545
+ "src/lib/exe-daemon-client.ts"() {
546
+ "use strict";
547
+ init_config();
548
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
549
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
550
+ SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
551
+ SPAWN_LOCK_STALE_MS = 3e4;
552
+ CONNECT_TIMEOUT_MS = 15e3;
553
+ REQUEST_TIMEOUT_MS = 3e4;
554
+ _socket = null;
555
+ _connected = false;
556
+ _buffer = "";
557
+ _requestCount = 0;
558
+ HEALTH_CHECK_INTERVAL = 100;
559
+ _pending = /* @__PURE__ */ new Map();
560
+ MAX_BUFFER = 1e7;
533
561
  }
534
- try {
535
- await client.execute({
536
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
537
- args: []
538
- });
562
+ });
563
+
564
+ // src/lib/db-retry.ts
565
+ function isBusyError(err) {
566
+ if (err instanceof Error) {
567
+ const msg = err.message.toLowerCase();
568
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
569
+ }
570
+ return false;
571
+ }
572
+ function delay(ms) {
573
+ return new Promise((resolve) => setTimeout(resolve, ms));
574
+ }
575
+ async function retryOnBusy(fn, label) {
576
+ let lastError;
577
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
578
+ try {
579
+ return await fn();
580
+ } catch (err) {
581
+ lastError = err;
582
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
583
+ throw err;
584
+ }
585
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
586
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
587
+ process.stderr.write(
588
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
589
+ `
590
+ );
591
+ await delay(backoff + jitter);
592
+ }
593
+ }
594
+ throw lastError;
595
+ }
596
+ function wrapWithRetry(client) {
597
+ return new Proxy(client, {
598
+ get(target, prop, receiver) {
599
+ if (prop === "execute") {
600
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
601
+ }
602
+ if (prop === "batch") {
603
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
604
+ }
605
+ return Reflect.get(target, prop, receiver);
606
+ }
607
+ });
608
+ }
609
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
610
+ var init_db_retry = __esm({
611
+ "src/lib/db-retry.ts"() {
612
+ "use strict";
613
+ MAX_RETRIES = 3;
614
+ BASE_DELAY_MS = 200;
615
+ MAX_JITTER_MS = 300;
616
+ }
617
+ });
618
+
619
+ // src/lib/database.ts
620
+ import { createClient } from "@libsql/client";
621
+ async function initDatabase(config) {
622
+ if (_client) {
623
+ _client.close();
624
+ _client = null;
625
+ _resilientClient = null;
626
+ }
627
+ const opts = {
628
+ url: `file:${config.dbPath}`
629
+ };
630
+ if (config.encryptionKey) {
631
+ opts.encryptionKey = config.encryptionKey;
632
+ }
633
+ _client = createClient(opts);
634
+ _resilientClient = wrapWithRetry(_client);
635
+ }
636
+ function getClient() {
637
+ if (!_resilientClient) {
638
+ throw new Error("Database client not initialized. Call initDatabase() first.");
639
+ }
640
+ if (process.env.EXE_IS_DAEMON === "1") {
641
+ return _resilientClient;
642
+ }
643
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
644
+ return _daemonClient;
645
+ }
646
+ return _resilientClient;
647
+ }
648
+ function getRawClient() {
649
+ if (!_client) {
650
+ throw new Error("Database client not initialized. Call initDatabase() first.");
651
+ }
652
+ return _client;
653
+ }
654
+ async function ensureSchema() {
655
+ const client = getRawClient();
656
+ await client.execute("PRAGMA journal_mode = WAL");
657
+ await client.execute("PRAGMA busy_timeout = 30000");
658
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
659
+ try {
660
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
661
+ } catch {
662
+ }
663
+ await client.executeMultiple(`
664
+ CREATE TABLE IF NOT EXISTS memories (
665
+ id TEXT PRIMARY KEY,
666
+ agent_id TEXT NOT NULL,
667
+ agent_role TEXT NOT NULL,
668
+ session_id TEXT NOT NULL,
669
+ timestamp TEXT NOT NULL,
670
+ tool_name TEXT NOT NULL,
671
+ project_name TEXT NOT NULL,
672
+ has_error INTEGER NOT NULL DEFAULT 0,
673
+ raw_text TEXT NOT NULL,
674
+ vector F32_BLOB(1024),
675
+ version INTEGER NOT NULL DEFAULT 0
676
+ );
677
+
678
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
679
+ ON memories(agent_id);
680
+
681
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
682
+ ON memories(timestamp);
683
+
684
+ CREATE INDEX IF NOT EXISTS idx_memories_session
685
+ ON memories(session_id);
686
+
687
+ CREATE INDEX IF NOT EXISTS idx_memories_project
688
+ ON memories(project_name);
689
+
690
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
691
+ ON memories(tool_name);
692
+
693
+ CREATE INDEX IF NOT EXISTS idx_memories_version
694
+ ON memories(version);
695
+
696
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
697
+ ON memories(agent_id, project_name);
698
+ `);
699
+ await client.executeMultiple(`
700
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
701
+ raw_text,
702
+ content='memories',
703
+ content_rowid='rowid'
704
+ );
705
+
706
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
707
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
708
+ END;
709
+
710
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
711
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
712
+ END;
713
+
714
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
715
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
716
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
717
+ END;
718
+ `);
719
+ await client.executeMultiple(`
720
+ CREATE TABLE IF NOT EXISTS sync_meta (
721
+ key TEXT PRIMARY KEY,
722
+ value TEXT NOT NULL
723
+ );
724
+ `);
725
+ await client.executeMultiple(`
726
+ CREATE TABLE IF NOT EXISTS tasks (
727
+ id TEXT PRIMARY KEY,
728
+ title TEXT NOT NULL,
729
+ assigned_to TEXT NOT NULL,
730
+ assigned_by TEXT NOT NULL,
731
+ project_name TEXT NOT NULL,
732
+ priority TEXT NOT NULL DEFAULT 'p1',
733
+ status TEXT NOT NULL DEFAULT 'open',
734
+ task_file TEXT,
735
+ created_at TEXT NOT NULL,
736
+ updated_at TEXT NOT NULL
737
+ );
738
+
739
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
740
+ ON tasks(assigned_to, status);
741
+ `);
742
+ await client.executeMultiple(`
743
+ CREATE TABLE IF NOT EXISTS behaviors (
744
+ id TEXT PRIMARY KEY,
745
+ agent_id TEXT NOT NULL,
746
+ project_name TEXT,
747
+ domain TEXT,
748
+ content TEXT NOT NULL,
749
+ active INTEGER NOT NULL DEFAULT 1,
750
+ created_at TEXT NOT NULL,
751
+ updated_at TEXT NOT NULL
752
+ );
753
+
754
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
755
+ ON behaviors(agent_id, active);
756
+ `);
757
+ try {
758
+ const coordinatorName = getCoordinatorName();
759
+ const existing = await client.execute({
760
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
761
+ args: [coordinatorName]
762
+ });
763
+ if (Number(existing.rows[0]?.cnt) === 0) {
764
+ const seededAt = "2026-03-25T00:00:00Z";
765
+ for (const [domain, content] of [
766
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
767
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
768
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
769
+ ]) {
770
+ await client.execute({
771
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
772
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
773
+ args: [coordinatorName, domain, content, seededAt, seededAt]
774
+ });
775
+ }
776
+ }
539
777
  } catch {
540
778
  }
541
779
  try {
542
780
  await client.execute({
543
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
781
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
544
782
  args: []
545
783
  });
546
784
  } catch {
547
785
  }
548
786
  try {
549
787
  await client.execute({
550
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
788
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
551
789
  args: []
552
790
  });
553
791
  } catch {
554
792
  }
555
793
  try {
556
794
  await client.execute({
557
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
795
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
558
796
  args: []
559
797
  });
560
798
  } catch {
561
799
  }
562
800
  try {
563
801
  await client.execute({
564
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
802
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
803
+ ON tasks(parent_task_id)
804
+ WHERE parent_task_id IS NOT NULL`,
565
805
  args: []
566
806
  });
567
807
  } catch {
568
808
  }
569
809
  try {
570
810
  await client.execute({
571
- sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
811
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
572
812
  args: []
573
813
  });
574
814
  } catch {
575
815
  }
576
- await client.executeMultiple(`
577
- CREATE TABLE IF NOT EXISTS consolidations (
578
- id TEXT PRIMARY KEY,
579
- consolidated_memory_id TEXT NOT NULL,
580
- source_memory_id TEXT NOT NULL,
581
- created_at TEXT NOT NULL
582
- );
583
-
584
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
585
- ON consolidations(source_memory_id);
586
-
587
- CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
588
- ON consolidations(consolidated_memory_id);
589
- `);
590
- await client.executeMultiple(`
591
- CREATE TABLE IF NOT EXISTS reminders (
592
- id TEXT PRIMARY KEY,
593
- text TEXT NOT NULL,
594
- created_at TEXT NOT NULL,
595
- due_date TEXT,
596
- completed_at TEXT
597
- );
598
- `);
599
- await client.executeMultiple(`
600
- CREATE TABLE IF NOT EXISTS notifications (
601
- id TEXT PRIMARY KEY,
602
- agent_id TEXT NOT NULL,
603
- agent_role TEXT NOT NULL,
604
- event TEXT NOT NULL,
605
- project TEXT NOT NULL,
606
- summary TEXT NOT NULL,
607
- task_file TEXT,
608
- read INTEGER NOT NULL DEFAULT 0,
816
+ try {
817
+ await client.execute({
818
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
819
+ args: []
820
+ });
821
+ } catch {
822
+ }
823
+ try {
824
+ await client.execute({
825
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
826
+ args: []
827
+ });
828
+ } catch {
829
+ }
830
+ try {
831
+ await client.execute({
832
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
833
+ args: []
834
+ });
835
+ } catch {
836
+ }
837
+ try {
838
+ await client.execute({
839
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
840
+ args: []
841
+ });
842
+ } catch {
843
+ }
844
+ try {
845
+ await client.execute({
846
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
847
+ args: []
848
+ });
849
+ } catch {
850
+ }
851
+ try {
852
+ await client.execute({
853
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
854
+ args: []
855
+ });
856
+ } catch {
857
+ }
858
+ try {
859
+ await client.execute({
860
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
861
+ args: []
862
+ });
863
+ } catch {
864
+ }
865
+ try {
866
+ await client.execute({
867
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
868
+ args: []
869
+ });
870
+ } catch {
871
+ }
872
+ try {
873
+ await client.execute({
874
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
875
+ args: []
876
+ });
877
+ } catch {
878
+ }
879
+ try {
880
+ await client.execute({
881
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
882
+ args: []
883
+ });
884
+ } catch {
885
+ }
886
+ try {
887
+ await client.execute({
888
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
889
+ args: []
890
+ });
891
+ } catch {
892
+ }
893
+ try {
894
+ await client.execute({
895
+ sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
896
+ args: []
897
+ });
898
+ } catch {
899
+ }
900
+ await client.executeMultiple(`
901
+ CREATE TABLE IF NOT EXISTS consolidations (
902
+ id TEXT PRIMARY KEY,
903
+ consolidated_memory_id TEXT NOT NULL,
904
+ source_memory_id TEXT NOT NULL,
905
+ created_at TEXT NOT NULL
906
+ );
907
+
908
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
909
+ ON consolidations(source_memory_id);
910
+
911
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
912
+ ON consolidations(consolidated_memory_id);
913
+ `);
914
+ await client.executeMultiple(`
915
+ CREATE TABLE IF NOT EXISTS reminders (
916
+ id TEXT PRIMARY KEY,
917
+ text TEXT NOT NULL,
918
+ created_at TEXT NOT NULL,
919
+ due_date TEXT,
920
+ completed_at TEXT
921
+ );
922
+ `);
923
+ await client.executeMultiple(`
924
+ CREATE TABLE IF NOT EXISTS notifications (
925
+ id TEXT PRIMARY KEY,
926
+ agent_id TEXT NOT NULL,
927
+ agent_role TEXT NOT NULL,
928
+ event TEXT NOT NULL,
929
+ project TEXT NOT NULL,
930
+ summary TEXT NOT NULL,
931
+ task_file TEXT,
932
+ read INTEGER NOT NULL DEFAULT 0,
609
933
  created_at TEXT NOT NULL
610
934
  );
611
935
 
@@ -807,6 +1131,12 @@ async function ensureSchema() {
807
1131
  } catch {
808
1132
  }
809
1133
  }
1134
+ try {
1135
+ await client.execute(
1136
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1137
+ );
1138
+ } catch {
1139
+ }
810
1140
  await client.executeMultiple(`
811
1141
  CREATE TABLE IF NOT EXISTS entities (
812
1142
  id TEXT PRIMARY KEY,
@@ -859,7 +1189,30 @@ async function ensureSchema() {
859
1189
  entity_id TEXT NOT NULL,
860
1190
  PRIMARY KEY (hyperedge_id, entity_id)
861
1191
  );
1192
+
1193
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1194
+ name,
1195
+ content=entities,
1196
+ content_rowid=rowid
1197
+ );
1198
+
1199
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1200
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1201
+ END;
1202
+
1203
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1204
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1205
+ END;
1206
+
1207
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1208
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1209
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1210
+ END;
862
1211
  `);
1212
+ try {
1213
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1214
+ } catch {
1215
+ }
863
1216
  await client.executeMultiple(`
864
1217
  CREATE TABLE IF NOT EXISTS entity_aliases (
865
1218
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1040,6 +1393,33 @@ async function ensureSchema() {
1040
1393
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1041
1394
  ON conversations(channel_id);
1042
1395
  `);
1396
+ await client.executeMultiple(`
1397
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1398
+ session_uuid TEXT PRIMARY KEY,
1399
+ agent_id TEXT NOT NULL,
1400
+ session_name TEXT,
1401
+ task_id TEXT,
1402
+ project_name TEXT,
1403
+ started_at TEXT NOT NULL
1404
+ );
1405
+
1406
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1407
+ ON session_agent_map(agent_id);
1408
+ `);
1409
+ try {
1410
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1411
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1412
+ await client.execute({
1413
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1414
+ SELECT session_id, agent_id, '', MIN(timestamp)
1415
+ FROM memories
1416
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1417
+ GROUP BY session_id, agent_id`,
1418
+ args: []
1419
+ });
1420
+ }
1421
+ } catch {
1422
+ }
1043
1423
  try {
1044
1424
  await client.execute({
1045
1425
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1173,8 +1553,30 @@ async function ensureSchema() {
1173
1553
  });
1174
1554
  } catch {
1175
1555
  }
1556
+ for (const col of [
1557
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1558
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1559
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1560
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1561
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1562
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1563
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1564
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1565
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1566
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1567
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1568
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1569
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1570
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1571
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1572
+ ]) {
1573
+ try {
1574
+ await client.execute(col);
1575
+ } catch {
1576
+ }
1577
+ }
1176
1578
  }
1177
- var _client, _resilientClient, initTurso;
1579
+ var _client, _resilientClient, _daemonClient, initTurso;
1178
1580
  var init_database = __esm({
1179
1581
  "src/lib/database.ts"() {
1180
1582
  "use strict";
@@ -1182,6 +1584,7 @@ var init_database = __esm({
1182
1584
  init_employees();
1183
1585
  _client = null;
1184
1586
  _resilientClient = null;
1587
+ _daemonClient = null;
1185
1588
  initTurso = initDatabase;
1186
1589
  }
1187
1590
  });
@@ -1566,509 +1969,184 @@ ${p.content}`).join("\n\n");
1566
1969
  }
1567
1970
  _cacheLoaded = true;
1568
1971
  return customerOnly;
1569
- }
1570
- function getGlobalProceduresBlock() {
1571
- const sections = [];
1572
- if (_platformCache) sections.push(_platformCache);
1573
- if (_cacheLoaded && _customerCache) sections.push(_customerCache);
1574
- if (sections.length === 0) return "";
1575
- return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
1576
-
1577
- ${sections.join("\n\n")}
1578
- `;
1579
- }
1580
- async function storeGlobalProcedure(input) {
1581
- const id = randomUUID3();
1582
- const now = (/* @__PURE__ */ new Date()).toISOString();
1583
- const client = getClient();
1584
- await client.execute({
1585
- sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
1586
- VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
1587
- args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
1588
- });
1589
- await loadGlobalProcedures();
1590
- return id;
1591
- }
1592
- async function deactivateGlobalProcedure(id) {
1593
- const now = (/* @__PURE__ */ new Date()).toISOString();
1594
- const client = getClient();
1595
- const result = await client.execute({
1596
- sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
1597
- args: [now, id]
1598
- });
1599
- await loadGlobalProcedures();
1600
- return result.rowsAffected > 0;
1601
- }
1602
- var _customerCache, _cacheLoaded, _platformCache;
1603
- var init_global_procedures = __esm({
1604
- "src/lib/global-procedures.ts"() {
1605
- "use strict";
1606
- init_database();
1607
- init_platform_procedures();
1608
- _customerCache = "";
1609
- _cacheLoaded = false;
1610
- _platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
1611
- ${p.content}`).join("\n\n");
1612
- }
1613
- });
1614
-
1615
- // src/bin/exe-assign.ts
1616
- init_employees();
1617
-
1618
- // src/lib/task-router.ts
1619
- init_employees();
1620
- import { randomUUID } from "crypto";
1621
- var DEFAULT_BLOOM_CONFIG = {
1622
- complexityToTier: {
1623
- routine: "junior",
1624
- standard: "standard",
1625
- complex: "senior",
1626
- critical: "specialist"
1627
- },
1628
- tierRules: {
1629
- junior: {
1630
- eligible: [],
1631
- // resolved dynamically from roster (Principal Engineer role)
1632
- reviewRequired: false,
1633
- manualOnly: false
1634
- },
1635
- standard: {
1636
- eligible: [],
1637
- // resolved dynamically from roster (Principal Engineer role)
1638
- reviewRequired: false,
1639
- manualOnly: false
1640
- },
1641
- senior: {
1642
- eligible: [],
1643
- // any specialist, but review required
1644
- reviewRequired: true,
1645
- manualOnly: false
1646
- },
1647
- specialist: {
1648
- eligible: [],
1649
- reviewRequired: true,
1650
- manualOnly: true
1651
- }
1652
- }
1653
- };
1654
- function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
1655
- const tier = config.complexityToTier[complexity];
1656
- const rule = config.tierRules[tier];
1657
- return {
1658
- tier,
1659
- reviewRequired: rule.reviewRequired,
1660
- manualOnly: rule.manualOnly,
1661
- eligible: rule.eligible
1662
- };
1663
- }
1664
- async function scoreEmployee(taskVector, agentId, searchFn) {
1665
- const results = await searchFn(taskVector, agentId, { limit: 5 });
1666
- if (results.length === 0) {
1667
- return { agentId, score: 0 };
1668
- }
1669
- const distances = results.map((r) => r["_distance"]).filter((d) => typeof d === "number");
1670
- if (distances.length > 0) {
1671
- const avgDistance = distances.reduce((sum, d) => sum + d, 0) / distances.length;
1672
- return { agentId, score: 1 / (1 + avgDistance) };
1673
- }
1674
- return { agentId, score: results.length / 5 };
1675
- }
1676
- async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
1677
- let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
1678
- if (specialists.length === 0) {
1679
- throw new Error(
1680
- "No specialist employees available. Create one with /exe-new-employee."
1681
- );
1682
- }
1683
- let bloomRouting;
1684
- if (options?.complexity) {
1685
- bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
1686
- if (bloomRouting.manualOnly) {
1687
- throw new Error(
1688
- `Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
1689
- );
1690
- }
1691
- if (bloomRouting.eligible.length > 0) {
1692
- const eligible = new Set(bloomRouting.eligible);
1693
- const filtered = specialists.filter((e) => eligible.has(e.name));
1694
- if (filtered.length > 0) {
1695
- specialists = filtered;
1696
- }
1697
- }
1698
- }
1699
- const taskVector = await embedFn(taskDescription);
1700
- const scored = await Promise.all(
1701
- specialists.map(async (emp) => {
1702
- const { score } = await scoreEmployee(taskVector, emp.name, searchFn);
1703
- return { employee: emp, score };
1704
- })
1705
- );
1706
- scored.sort((a, b) => b.score - a.score);
1707
- return { ...scored[0], bloomRouting };
1708
- }
1709
- function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
1710
- return {
1711
- id: randomUUID(),
1712
- agent_id: assignedTo.name,
1713
- agent_role: assignedTo.role,
1714
- session_id: `assign-${Date.now()}`,
1715
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1716
- tool_name: "assignment",
1717
- project_name: "",
1718
- has_error: false,
1719
- raw_text: `Task assigned by ${assignedBy}: ${taskDescription}`,
1720
- vector: null
1721
- // Will be backfilled
1722
- };
1723
- }
1724
- function formatAssignmentOutput(employee, task, score, mode, bloomRouting) {
1725
- const modeLabel = mode === "auto" ? "auto-routed" : "direct";
1726
- const percent = Math.round(score * 100);
1727
- const lines = [
1728
- `Assigned to ${employee.name} (${employee.role})`,
1729
- `Mode: ${modeLabel}`,
1730
- `Relevance: ${percent}%`,
1731
- `Task: ${task}`
1732
- ];
1733
- if (bloomRouting) {
1734
- lines.push(`Complexity tier: ${bloomRouting.tier}`);
1735
- if (bloomRouting.reviewRequired) {
1736
- lines.push(`Review: required`);
1737
- }
1738
- }
1739
- return lines.join("\n");
1740
- }
1741
-
1742
- // src/types/memory.ts
1743
- var EMBEDDING_DIM = 1024;
1744
-
1745
- // src/lib/exe-daemon-client.ts
1746
- init_config();
1747
- import net from "net";
1748
- import { spawn } from "child_process";
1749
- import { randomUUID as randomUUID2 } from "crypto";
1750
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1751
- import path3 from "path";
1752
- import { fileURLToPath } from "url";
1753
- var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
1754
- var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
1755
- var SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1756
- var SPAWN_LOCK_STALE_MS = 3e4;
1757
- var CONNECT_TIMEOUT_MS = 15e3;
1758
- var REQUEST_TIMEOUT_MS = 3e4;
1759
- var _socket = null;
1760
- var _connected = false;
1761
- var _buffer = "";
1762
- var _requestCount = 0;
1763
- var HEALTH_CHECK_INTERVAL = 100;
1764
- var _pending = /* @__PURE__ */ new Map();
1765
- var MAX_BUFFER = 1e7;
1766
- function handleData(chunk) {
1767
- _buffer += chunk.toString();
1768
- if (_buffer.length > MAX_BUFFER) {
1769
- _buffer = "";
1770
- return;
1771
- }
1772
- let newlineIdx;
1773
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1774
- const line = _buffer.slice(0, newlineIdx).trim();
1775
- _buffer = _buffer.slice(newlineIdx + 1);
1776
- if (!line) continue;
1777
- try {
1778
- const response = JSON.parse(line);
1779
- const entry = _pending.get(response.id);
1780
- if (entry) {
1781
- clearTimeout(entry.timer);
1782
- _pending.delete(response.id);
1783
- entry.resolve(response);
1784
- }
1785
- } catch {
1786
- }
1787
- }
1788
- }
1789
- function cleanupStaleFiles() {
1790
- if (existsSync3(PID_PATH)) {
1791
- try {
1792
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1793
- if (pid > 0) {
1794
- try {
1795
- process.kill(pid, 0);
1796
- return;
1797
- } catch {
1798
- }
1799
- }
1800
- } catch {
1801
- }
1802
- try {
1803
- unlinkSync2(PID_PATH);
1804
- } catch {
1805
- }
1806
- try {
1807
- unlinkSync2(SOCKET_PATH);
1808
- } catch {
1809
- }
1810
- }
1811
- }
1812
- function findPackageRoot() {
1813
- let dir = path3.dirname(fileURLToPath(import.meta.url));
1814
- const { root } = path3.parse(dir);
1815
- while (dir !== root) {
1816
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
1817
- dir = path3.dirname(dir);
1818
- }
1819
- return null;
1820
- }
1821
- function spawnDaemon() {
1822
- const pkgRoot = findPackageRoot();
1823
- if (!pkgRoot) {
1824
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
1825
- return;
1826
- }
1827
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1828
- if (!existsSync3(daemonPath)) {
1829
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1830
- `);
1831
- return;
1832
- }
1833
- const resolvedPath = daemonPath;
1834
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1835
- `);
1836
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
1837
- let stderrFd = "ignore";
1838
- try {
1839
- stderrFd = openSync(logPath, "a");
1840
- } catch {
1841
- }
1842
- const child = spawn(process.execPath, [resolvedPath], {
1843
- detached: true,
1844
- stdio: ["ignore", "ignore", stderrFd],
1845
- env: {
1846
- ...process.env,
1847
- TMUX: void 0,
1848
- // Daemon is global — must not inherit session scope
1849
- TMUX_PANE: void 0,
1850
- // Prevents resolveExeSession() from scoping to one session
1851
- EXE_DAEMON_SOCK: SOCKET_PATH,
1852
- EXE_DAEMON_PID: PID_PATH
1853
- }
1854
- });
1855
- child.unref();
1856
- if (typeof stderrFd === "number") {
1857
- try {
1858
- closeSync(stderrFd);
1859
- } catch {
1860
- }
1861
- }
1862
- }
1863
- function acquireSpawnLock() {
1864
- try {
1865
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
1866
- closeSync(fd);
1867
- return true;
1868
- } catch {
1869
- try {
1870
- const stat = statSync(SPAWN_LOCK_PATH);
1871
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1872
- try {
1873
- unlinkSync2(SPAWN_LOCK_PATH);
1874
- } catch {
1875
- }
1876
- try {
1877
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
1878
- closeSync(fd);
1879
- return true;
1880
- } catch {
1881
- }
1882
- }
1883
- } catch {
1884
- }
1885
- return false;
1886
- }
1887
- }
1888
- function releaseSpawnLock() {
1889
- try {
1890
- unlinkSync2(SPAWN_LOCK_PATH);
1891
- } catch {
1892
- }
1893
- }
1894
- function connectToSocket() {
1895
- return new Promise((resolve) => {
1896
- if (_socket && _connected) {
1897
- resolve(true);
1898
- return;
1899
- }
1900
- const socket = net.createConnection({ path: SOCKET_PATH });
1901
- const connectTimeout = setTimeout(() => {
1902
- socket.destroy();
1903
- resolve(false);
1904
- }, 2e3);
1905
- socket.on("connect", () => {
1906
- clearTimeout(connectTimeout);
1907
- _socket = socket;
1908
- _connected = true;
1909
- _buffer = "";
1910
- socket.on("data", handleData);
1911
- socket.on("close", () => {
1912
- _connected = false;
1913
- _socket = null;
1914
- for (const [id, entry] of _pending) {
1915
- clearTimeout(entry.timer);
1916
- _pending.delete(id);
1917
- entry.resolve({ error: "Connection closed" });
1918
- }
1919
- });
1920
- socket.on("error", () => {
1921
- _connected = false;
1922
- _socket = null;
1923
- });
1924
- resolve(true);
1925
- });
1926
- socket.on("error", () => {
1927
- clearTimeout(connectTimeout);
1928
- resolve(false);
1929
- });
1930
- });
1931
- }
1932
- async function connectEmbedDaemon() {
1933
- if (_socket && _connected) return true;
1934
- if (await connectToSocket()) return true;
1935
- if (acquireSpawnLock()) {
1936
- try {
1937
- cleanupStaleFiles();
1938
- spawnDaemon();
1939
- } finally {
1940
- releaseSpawnLock();
1941
- }
1942
- }
1943
- const start = Date.now();
1944
- let delay2 = 100;
1945
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1946
- await new Promise((r) => setTimeout(r, delay2));
1947
- if (await connectToSocket()) return true;
1948
- delay2 = Math.min(delay2 * 2, 3e3);
1949
- }
1950
- return false;
1951
- }
1952
- function sendRequest(texts, priority) {
1953
- return new Promise((resolve) => {
1954
- if (!_socket || !_connected) {
1955
- resolve({ error: "Not connected" });
1956
- return;
1957
- }
1958
- const id = randomUUID2();
1959
- const timer = setTimeout(() => {
1960
- _pending.delete(id);
1961
- resolve({ error: "Request timeout" });
1962
- }, REQUEST_TIMEOUT_MS);
1963
- _pending.set(id, { resolve, timer });
1964
- try {
1965
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
1966
- } catch {
1967
- clearTimeout(timer);
1968
- _pending.delete(id);
1969
- resolve({ error: "Write failed" });
1970
- }
1972
+ }
1973
+ function getGlobalProceduresBlock() {
1974
+ const sections = [];
1975
+ if (_platformCache) sections.push(_platformCache);
1976
+ if (_cacheLoaded && _customerCache) sections.push(_customerCache);
1977
+ if (sections.length === 0) return "";
1978
+ return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
1979
+
1980
+ ${sections.join("\n\n")}
1981
+ `;
1982
+ }
1983
+ async function storeGlobalProcedure(input) {
1984
+ const id = randomUUID3();
1985
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1986
+ const client = getClient();
1987
+ await client.execute({
1988
+ sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
1989
+ VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
1990
+ args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
1971
1991
  });
1992
+ await loadGlobalProcedures();
1993
+ return id;
1972
1994
  }
1973
- async function pingDaemon() {
1974
- if (!_socket || !_connected) return null;
1975
- return new Promise((resolve) => {
1976
- const id = randomUUID2();
1977
- const timer = setTimeout(() => {
1978
- _pending.delete(id);
1979
- resolve(null);
1980
- }, 5e3);
1981
- _pending.set(id, {
1982
- resolve: (data) => {
1983
- if (data.health) {
1984
- resolve(data.health);
1985
- } else {
1986
- resolve(null);
1987
- }
1988
- },
1989
- timer
1990
- });
1991
- try {
1992
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
1993
- } catch {
1994
- clearTimeout(timer);
1995
- _pending.delete(id);
1996
- resolve(null);
1997
- }
1995
+ async function deactivateGlobalProcedure(id) {
1996
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1997
+ const client = getClient();
1998
+ const result = await client.execute({
1999
+ sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
2000
+ args: [now, id]
1998
2001
  });
2002
+ await loadGlobalProcedures();
2003
+ return result.rowsAffected > 0;
1999
2004
  }
2000
- function killAndRespawnDaemon() {
2001
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
2002
- if (existsSync3(PID_PATH)) {
2003
- try {
2004
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2005
- if (pid > 0) {
2006
- try {
2007
- process.kill(pid, "SIGKILL");
2008
- } catch {
2009
- }
2010
- }
2011
- } catch {
2012
- }
2005
+ var _customerCache, _cacheLoaded, _platformCache;
2006
+ var init_global_procedures = __esm({
2007
+ "src/lib/global-procedures.ts"() {
2008
+ "use strict";
2009
+ init_database();
2010
+ init_platform_procedures();
2011
+ _customerCache = "";
2012
+ _cacheLoaded = false;
2013
+ _platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
2014
+ ${p.content}`).join("\n\n");
2013
2015
  }
2014
- if (_socket) {
2015
- _socket.destroy();
2016
- _socket = null;
2016
+ });
2017
+
2018
+ // src/bin/exe-assign.ts
2019
+ init_employees();
2020
+
2021
+ // src/lib/task-router.ts
2022
+ init_employees();
2023
+ import { randomUUID } from "crypto";
2024
+ var DEFAULT_BLOOM_CONFIG = {
2025
+ complexityToTier: {
2026
+ routine: "junior",
2027
+ standard: "standard",
2028
+ complex: "senior",
2029
+ critical: "specialist"
2030
+ },
2031
+ tierRules: {
2032
+ junior: {
2033
+ eligible: [],
2034
+ // resolved dynamically from roster (Principal Engineer role)
2035
+ reviewRequired: false,
2036
+ manualOnly: false
2037
+ },
2038
+ standard: {
2039
+ eligible: [],
2040
+ // resolved dynamically from roster (Principal Engineer role)
2041
+ reviewRequired: false,
2042
+ manualOnly: false
2043
+ },
2044
+ senior: {
2045
+ eligible: [],
2046
+ // any specialist, but review required
2047
+ reviewRequired: true,
2048
+ manualOnly: false
2049
+ },
2050
+ specialist: {
2051
+ eligible: [],
2052
+ reviewRequired: true,
2053
+ manualOnly: true
2054
+ }
2017
2055
  }
2018
- _connected = false;
2019
- _buffer = "";
2020
- try {
2021
- unlinkSync2(PID_PATH);
2022
- } catch {
2056
+ };
2057
+ function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
2058
+ const tier = config.complexityToTier[complexity];
2059
+ const rule = config.tierRules[tier];
2060
+ return {
2061
+ tier,
2062
+ reviewRequired: rule.reviewRequired,
2063
+ manualOnly: rule.manualOnly,
2064
+ eligible: rule.eligible
2065
+ };
2066
+ }
2067
+ async function scoreEmployee(taskVector, agentId, searchFn) {
2068
+ const results = await searchFn(taskVector, agentId, { limit: 5 });
2069
+ if (results.length === 0) {
2070
+ return { agentId, score: 0 };
2023
2071
  }
2024
- try {
2025
- unlinkSync2(SOCKET_PATH);
2026
- } catch {
2072
+ const distances = results.map((r) => r["_distance"]).filter((d) => typeof d === "number");
2073
+ if (distances.length > 0) {
2074
+ const avgDistance = distances.reduce((sum, d) => sum + d, 0) / distances.length;
2075
+ return { agentId, score: 1 / (1 + avgDistance) };
2027
2076
  }
2028
- spawnDaemon();
2077
+ return { agentId, score: results.length / 5 };
2029
2078
  }
2030
- async function embedViaClient(text, priority = "high") {
2031
- if (!_connected && !await connectEmbedDaemon()) return null;
2032
- _requestCount++;
2033
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2034
- const health = await pingDaemon();
2035
- if (!health) {
2036
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2037
- `);
2038
- killAndRespawnDaemon();
2039
- const start = Date.now();
2040
- let delay2 = 200;
2041
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2042
- await new Promise((r) => setTimeout(r, delay2));
2043
- if (await connectToSocket()) break;
2044
- delay2 = Math.min(delay2 * 2, 3e3);
2079
+ async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
2080
+ let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
2081
+ if (specialists.length === 0) {
2082
+ throw new Error(
2083
+ "No specialist employees available. Create one with /exe-new-employee."
2084
+ );
2085
+ }
2086
+ let bloomRouting;
2087
+ if (options?.complexity) {
2088
+ bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
2089
+ if (bloomRouting.manualOnly) {
2090
+ throw new Error(
2091
+ `Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
2092
+ );
2093
+ }
2094
+ if (bloomRouting.eligible.length > 0) {
2095
+ const eligible = new Set(bloomRouting.eligible);
2096
+ const filtered = specialists.filter((e) => eligible.has(e.name));
2097
+ if (filtered.length > 0) {
2098
+ specialists = filtered;
2045
2099
  }
2046
- if (!_connected) return null;
2047
2100
  }
2048
2101
  }
2049
- const result = await sendRequest([text], priority);
2050
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2051
- if (result.error) {
2052
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2053
- `);
2054
- killAndRespawnDaemon();
2055
- const start = Date.now();
2056
- let delay2 = 200;
2057
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2058
- await new Promise((r) => setTimeout(r, delay2));
2059
- if (await connectToSocket()) break;
2060
- delay2 = Math.min(delay2 * 2, 3e3);
2102
+ const taskVector = await embedFn(taskDescription);
2103
+ const scored = await Promise.all(
2104
+ specialists.map(async (emp) => {
2105
+ const { score } = await scoreEmployee(taskVector, emp.name, searchFn);
2106
+ return { employee: emp, score };
2107
+ })
2108
+ );
2109
+ scored.sort((a, b) => b.score - a.score);
2110
+ return { ...scored[0], bloomRouting };
2111
+ }
2112
+ function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
2113
+ return {
2114
+ id: randomUUID(),
2115
+ agent_id: assignedTo.name,
2116
+ agent_role: assignedTo.role,
2117
+ session_id: `assign-${Date.now()}`,
2118
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2119
+ tool_name: "assignment",
2120
+ project_name: "",
2121
+ has_error: false,
2122
+ raw_text: `Task assigned by ${assignedBy}: ${taskDescription}`,
2123
+ vector: null
2124
+ // Will be backfilled
2125
+ };
2126
+ }
2127
+ function formatAssignmentOutput(employee, task, score, mode, bloomRouting) {
2128
+ const modeLabel = mode === "auto" ? "auto-routed" : "direct";
2129
+ const percent = Math.round(score * 100);
2130
+ const lines = [
2131
+ `Assigned to ${employee.name} (${employee.role})`,
2132
+ `Mode: ${modeLabel}`,
2133
+ `Relevance: ${percent}%`,
2134
+ `Task: ${task}`
2135
+ ];
2136
+ if (bloomRouting) {
2137
+ lines.push(`Complexity tier: ${bloomRouting.tier}`);
2138
+ if (bloomRouting.reviewRequired) {
2139
+ lines.push(`Review: required`);
2061
2140
  }
2062
- if (!_connected) return null;
2063
- const retry = await sendRequest([text], priority);
2064
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2065
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2066
- `);
2067
2141
  }
2068
- return null;
2142
+ return lines.join("\n");
2069
2143
  }
2070
2144
 
2145
+ // src/types/memory.ts
2146
+ var EMBEDDING_DIM = 1024;
2147
+
2071
2148
  // src/lib/embedder.ts
2149
+ init_exe_daemon_client();
2072
2150
  async function embed(text) {
2073
2151
  const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
2074
2152
  const vector = await embedViaClient(text, priority);
@@ -2086,6 +2164,7 @@ async function embed(text) {
2086
2164
  }
2087
2165
 
2088
2166
  // src/lib/store.ts
2167
+ import { createHash } from "crypto";
2089
2168
  init_database();
2090
2169
 
2091
2170
  // src/lib/keychain.ts
@@ -2121,12 +2200,20 @@ async function getMasterKey() {
2121
2200
  }
2122
2201
  const keyPath = getKeyPath();
2123
2202
  if (!existsSync4(keyPath)) {
2203
+ process.stderr.write(
2204
+ `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2205
+ `
2206
+ );
2124
2207
  return null;
2125
2208
  }
2126
2209
  try {
2127
2210
  const content = await readFile3(keyPath, "utf-8");
2128
2211
  return Buffer.from(content.trim(), "base64");
2129
- } catch {
2212
+ } catch (err) {
2213
+ process.stderr.write(
2214
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
2215
+ `
2216
+ );
2130
2217
  return null;
2131
2218
  }
2132
2219
  }
@@ -2265,12 +2352,52 @@ function classifyTier(record) {
2265
2352
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
2266
2353
  return 3;
2267
2354
  }
2355
+ function inferFilePaths(record) {
2356
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
2357
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
2358
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
2359
+ return match ? JSON.stringify([match[1]]) : null;
2360
+ }
2361
+ function inferCommitHash(record) {
2362
+ if (record.tool_name !== "Bash") return null;
2363
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
2364
+ return match ? match[1] : null;
2365
+ }
2366
+ function inferLanguageType(record) {
2367
+ const text = record.raw_text;
2368
+ if (!text || text.length < 10) return null;
2369
+ const trimmed = text.trimStart();
2370
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
2371
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
2372
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
2373
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
2374
+ return "mixed";
2375
+ }
2376
+ function inferDomain(record) {
2377
+ const proj = (record.project_name ?? "").toLowerCase();
2378
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
2379
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
2380
+ return null;
2381
+ }
2268
2382
  async function writeMemory(record) {
2269
2383
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
2270
2384
  throw new Error(
2271
2385
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
2272
2386
  );
2273
2387
  }
2388
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
2389
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
2390
+ return;
2391
+ }
2392
+ try {
2393
+ const client = getClient();
2394
+ const existing = await client.execute({
2395
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
2396
+ args: [contentHash, record.agent_id]
2397
+ });
2398
+ if (existing.rows.length > 0) return;
2399
+ } catch {
2400
+ }
2274
2401
  const dbRow = {
2275
2402
  id: record.id,
2276
2403
  agent_id: record.agent_id,
@@ -2300,7 +2427,23 @@ async function writeMemory(record) {
2300
2427
  supersedes_id: record.supersedes_id ?? null,
2301
2428
  draft: record.draft ? 1 : 0,
2302
2429
  memory_type: record.memory_type ?? "raw",
2303
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
2430
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
2431
+ content_hash: contentHash,
2432
+ intent: record.intent ?? null,
2433
+ outcome: record.outcome ?? null,
2434
+ domain: record.domain ?? inferDomain(record),
2435
+ referenced_entities: record.referenced_entities ?? null,
2436
+ retrieval_count: record.retrieval_count ?? 0,
2437
+ chain_position: record.chain_position ?? null,
2438
+ review_status: record.review_status ?? null,
2439
+ context_window_pct: record.context_window_pct ?? null,
2440
+ file_paths: record.file_paths ?? inferFilePaths(record),
2441
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
2442
+ duration_ms: record.duration_ms ?? null,
2443
+ token_cost: record.token_cost ?? null,
2444
+ audience: record.audience ?? null,
2445
+ language_type: record.language_type ?? inferLanguageType(record),
2446
+ parent_memory_id: record.parent_memory_id ?? null
2304
2447
  };
2305
2448
  _pendingRecords.push(dbRow);
2306
2449
  orgBus.emit({
@@ -2358,80 +2501,85 @@ async function flushBatch() {
2358
2501
  const draft = row.draft ? 1 : 0;
2359
2502
  const memoryType = row.memory_type ?? "raw";
2360
2503
  const trajectory = row.trajectory ?? null;
2361
- return {
2362
- sql: hasVector ? `INSERT OR IGNORE INTO memories
2363
- (id, agent_id, agent_role, session_id, timestamp,
2364
- tool_name, project_name,
2365
- has_error, raw_text, vector, version, task_id, importance, status,
2366
- confidence, last_accessed,
2367
- workspace_id, document_id, user_id, char_offset, page_number,
2368
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2369
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
2370
- (id, agent_id, agent_role, session_id, timestamp,
2504
+ const contentHash = row.content_hash ?? null;
2505
+ const intent = row.intent ?? null;
2506
+ const outcome = row.outcome ?? null;
2507
+ const domain = row.domain ?? null;
2508
+ const referencedEntities = row.referenced_entities ?? null;
2509
+ const retrievalCount = row.retrieval_count ?? 0;
2510
+ const chainPosition = row.chain_position ?? null;
2511
+ const reviewStatus = row.review_status ?? null;
2512
+ const contextWindowPct = row.context_window_pct ?? null;
2513
+ const filePaths = row.file_paths ?? null;
2514
+ const commitHash = row.commit_hash ?? null;
2515
+ const durationMs = row.duration_ms ?? null;
2516
+ const tokenCost = row.token_cost ?? null;
2517
+ const audience = row.audience ?? null;
2518
+ const languageType = row.language_type ?? null;
2519
+ const parentMemoryId = row.parent_memory_id ?? null;
2520
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
2371
2521
  tool_name, project_name,
2372
2522
  has_error, raw_text, vector, version, task_id, importance, status,
2373
2523
  confidence, last_accessed,
2374
2524
  workspace_id, document_id, user_id, char_offset, page_number,
2375
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2376
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2377
- args: hasVector ? [
2378
- row.id,
2379
- row.agent_id,
2380
- row.agent_role,
2381
- row.session_id,
2382
- row.timestamp,
2383
- row.tool_name,
2384
- row.project_name,
2385
- row.has_error,
2386
- row.raw_text,
2387
- vectorToBlob(row.vector),
2388
- row.version,
2389
- taskId,
2390
- importance,
2391
- status,
2392
- confidence,
2393
- lastAccessed,
2394
- workspaceId,
2395
- documentId,
2396
- userId,
2397
- charOffset,
2398
- pageNumber,
2399
- sourcePath,
2400
- sourceType,
2401
- tier,
2402
- supersedesId,
2403
- draft,
2404
- memoryType,
2405
- trajectory
2406
- ] : [
2407
- row.id,
2408
- row.agent_id,
2409
- row.agent_role,
2410
- row.session_id,
2411
- row.timestamp,
2412
- row.tool_name,
2413
- row.project_name,
2414
- row.has_error,
2415
- row.raw_text,
2416
- row.version,
2417
- taskId,
2418
- importance,
2419
- status,
2420
- confidence,
2421
- lastAccessed,
2422
- workspaceId,
2423
- documentId,
2424
- userId,
2425
- charOffset,
2426
- pageNumber,
2427
- sourcePath,
2428
- sourceType,
2429
- tier,
2430
- supersedesId,
2431
- draft,
2432
- memoryType,
2433
- trajectory
2434
- ]
2525
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
2526
+ intent, outcome, domain, referenced_entities, retrieval_count,
2527
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
2528
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
2529
+ const metaArgs = [
2530
+ intent,
2531
+ outcome,
2532
+ domain,
2533
+ referencedEntities,
2534
+ retrievalCount,
2535
+ chainPosition,
2536
+ reviewStatus,
2537
+ contextWindowPct,
2538
+ filePaths,
2539
+ commitHash,
2540
+ durationMs,
2541
+ tokenCost,
2542
+ audience,
2543
+ languageType,
2544
+ parentMemoryId
2545
+ ];
2546
+ const baseArgs = [
2547
+ row.id,
2548
+ row.agent_id,
2549
+ row.agent_role,
2550
+ row.session_id,
2551
+ row.timestamp,
2552
+ row.tool_name,
2553
+ row.project_name,
2554
+ row.has_error,
2555
+ row.raw_text
2556
+ ];
2557
+ const sharedArgs = [
2558
+ row.version,
2559
+ taskId,
2560
+ importance,
2561
+ status,
2562
+ confidence,
2563
+ lastAccessed,
2564
+ workspaceId,
2565
+ documentId,
2566
+ userId,
2567
+ charOffset,
2568
+ pageNumber,
2569
+ sourcePath,
2570
+ sourceType,
2571
+ tier,
2572
+ supersedesId,
2573
+ draft,
2574
+ memoryType,
2575
+ trajectory,
2576
+ contentHash
2577
+ ];
2578
+ return {
2579
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
2580
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
2581
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2582
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
2435
2583
  };
2436
2584
  };
2437
2585
  const globalClient = getClient();
@@ -2575,6 +2723,7 @@ import { realpathSync } from "fs";
2575
2723
  import { fileURLToPath as fileURLToPath2 } from "url";
2576
2724
  function isMainModule(importMetaUrl) {
2577
2725
  if (process.argv[1] == null) return false;
2726
+ if (process.argv[1].includes("mcp/server")) return false;
2578
2727
  try {
2579
2728
  const scriptPath = realpathSync(process.argv[1]);
2580
2729
  const modulePath = realpathSync(fileURLToPath2(importMetaUrl));