@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
@@ -191,6 +191,443 @@ var init_employees = __esm({
191
191
  }
192
192
  });
193
193
 
194
+ // src/lib/exe-daemon-client.ts
195
+ import net from "net";
196
+ import { spawn } from "child_process";
197
+ import { randomUUID } from "crypto";
198
+ import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
199
+ import path3 from "path";
200
+ import { fileURLToPath } from "url";
201
+ function handleData(chunk) {
202
+ _buffer += chunk.toString();
203
+ if (_buffer.length > MAX_BUFFER) {
204
+ _buffer = "";
205
+ return;
206
+ }
207
+ let newlineIdx;
208
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
209
+ const line = _buffer.slice(0, newlineIdx).trim();
210
+ _buffer = _buffer.slice(newlineIdx + 1);
211
+ if (!line) continue;
212
+ try {
213
+ const response = JSON.parse(line);
214
+ const id = response.id;
215
+ if (!id) continue;
216
+ const entry = _pending.get(id);
217
+ if (entry) {
218
+ clearTimeout(entry.timer);
219
+ _pending.delete(id);
220
+ entry.resolve(response);
221
+ }
222
+ } catch {
223
+ }
224
+ }
225
+ }
226
+ function cleanupStaleFiles() {
227
+ if (existsSync3(PID_PATH)) {
228
+ try {
229
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
230
+ if (pid > 0) {
231
+ try {
232
+ process.kill(pid, 0);
233
+ return;
234
+ } catch {
235
+ }
236
+ }
237
+ } catch {
238
+ }
239
+ try {
240
+ unlinkSync2(PID_PATH);
241
+ } catch {
242
+ }
243
+ try {
244
+ unlinkSync2(SOCKET_PATH);
245
+ } catch {
246
+ }
247
+ }
248
+ }
249
+ function findPackageRoot() {
250
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
251
+ const { root } = path3.parse(dir);
252
+ while (dir !== root) {
253
+ if (existsSync3(path3.join(dir, "package.json"))) return dir;
254
+ dir = path3.dirname(dir);
255
+ }
256
+ return null;
257
+ }
258
+ function spawnDaemon() {
259
+ const pkgRoot = findPackageRoot();
260
+ if (!pkgRoot) {
261
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
262
+ return;
263
+ }
264
+ const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
265
+ if (!existsSync3(daemonPath)) {
266
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
267
+ `);
268
+ return;
269
+ }
270
+ const resolvedPath = daemonPath;
271
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
272
+ `);
273
+ const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
274
+ let stderrFd = "ignore";
275
+ try {
276
+ stderrFd = openSync(logPath, "a");
277
+ } catch {
278
+ }
279
+ const child = spawn(process.execPath, [resolvedPath], {
280
+ detached: true,
281
+ stdio: ["ignore", "ignore", stderrFd],
282
+ env: {
283
+ ...process.env,
284
+ TMUX: void 0,
285
+ // Daemon is global — must not inherit session scope
286
+ TMUX_PANE: void 0,
287
+ // Prevents resolveExeSession() from scoping to one session
288
+ EXE_DAEMON_SOCK: SOCKET_PATH,
289
+ EXE_DAEMON_PID: PID_PATH
290
+ }
291
+ });
292
+ child.unref();
293
+ if (typeof stderrFd === "number") {
294
+ try {
295
+ closeSync(stderrFd);
296
+ } catch {
297
+ }
298
+ }
299
+ }
300
+ function acquireSpawnLock() {
301
+ try {
302
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
303
+ closeSync(fd);
304
+ return true;
305
+ } catch {
306
+ try {
307
+ const stat = statSync(SPAWN_LOCK_PATH);
308
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
309
+ try {
310
+ unlinkSync2(SPAWN_LOCK_PATH);
311
+ } catch {
312
+ }
313
+ try {
314
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
315
+ closeSync(fd);
316
+ return true;
317
+ } catch {
318
+ }
319
+ }
320
+ } catch {
321
+ }
322
+ return false;
323
+ }
324
+ }
325
+ function releaseSpawnLock() {
326
+ try {
327
+ unlinkSync2(SPAWN_LOCK_PATH);
328
+ } catch {
329
+ }
330
+ }
331
+ function connectToSocket() {
332
+ return new Promise((resolve) => {
333
+ if (_socket && _connected) {
334
+ resolve(true);
335
+ return;
336
+ }
337
+ const socket = net.createConnection({ path: SOCKET_PATH });
338
+ const connectTimeout = setTimeout(() => {
339
+ socket.destroy();
340
+ resolve(false);
341
+ }, 2e3);
342
+ socket.on("connect", () => {
343
+ clearTimeout(connectTimeout);
344
+ _socket = socket;
345
+ _connected = true;
346
+ _buffer = "";
347
+ socket.on("data", handleData);
348
+ socket.on("close", () => {
349
+ _connected = false;
350
+ _socket = null;
351
+ for (const [id, entry] of _pending) {
352
+ clearTimeout(entry.timer);
353
+ _pending.delete(id);
354
+ entry.resolve({ error: "Connection closed" });
355
+ }
356
+ });
357
+ socket.on("error", () => {
358
+ _connected = false;
359
+ _socket = null;
360
+ });
361
+ resolve(true);
362
+ });
363
+ socket.on("error", () => {
364
+ clearTimeout(connectTimeout);
365
+ resolve(false);
366
+ });
367
+ });
368
+ }
369
+ async function connectEmbedDaemon() {
370
+ if (_socket && _connected) return true;
371
+ if (await connectToSocket()) return true;
372
+ if (acquireSpawnLock()) {
373
+ try {
374
+ cleanupStaleFiles();
375
+ spawnDaemon();
376
+ } finally {
377
+ releaseSpawnLock();
378
+ }
379
+ }
380
+ const start = Date.now();
381
+ let delay2 = 100;
382
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
383
+ await new Promise((r) => setTimeout(r, delay2));
384
+ if (await connectToSocket()) return true;
385
+ delay2 = Math.min(delay2 * 2, 3e3);
386
+ }
387
+ return false;
388
+ }
389
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
390
+ return new Promise((resolve) => {
391
+ if (!_socket || !_connected) {
392
+ resolve({ error: "Not connected" });
393
+ return;
394
+ }
395
+ const id = randomUUID();
396
+ const timer = setTimeout(() => {
397
+ _pending.delete(id);
398
+ resolve({ error: "Request timeout" });
399
+ }, timeoutMs);
400
+ _pending.set(id, { resolve, timer });
401
+ try {
402
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
403
+ } catch {
404
+ clearTimeout(timer);
405
+ _pending.delete(id);
406
+ resolve({ error: "Write failed" });
407
+ }
408
+ });
409
+ }
410
+ function isClientConnected() {
411
+ return _connected;
412
+ }
413
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
414
+ var init_exe_daemon_client = __esm({
415
+ "src/lib/exe-daemon-client.ts"() {
416
+ "use strict";
417
+ init_config();
418
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
419
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
420
+ SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
421
+ SPAWN_LOCK_STALE_MS = 3e4;
422
+ CONNECT_TIMEOUT_MS = 15e3;
423
+ REQUEST_TIMEOUT_MS = 3e4;
424
+ _socket = null;
425
+ _connected = false;
426
+ _buffer = "";
427
+ _pending = /* @__PURE__ */ new Map();
428
+ MAX_BUFFER = 1e7;
429
+ }
430
+ });
431
+
432
+ // src/lib/daemon-protocol.ts
433
+ function serializeValue(v) {
434
+ if (v === null || v === void 0) return null;
435
+ if (typeof v === "bigint") return Number(v);
436
+ if (typeof v === "boolean") return v ? 1 : 0;
437
+ if (v instanceof Uint8Array) {
438
+ return { __blob: Buffer.from(v).toString("base64") };
439
+ }
440
+ if (ArrayBuffer.isView(v)) {
441
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
442
+ }
443
+ if (v instanceof ArrayBuffer) {
444
+ return { __blob: Buffer.from(v).toString("base64") };
445
+ }
446
+ if (typeof v === "string" || typeof v === "number") return v;
447
+ return String(v);
448
+ }
449
+ function deserializeValue(v) {
450
+ if (v === null) return null;
451
+ if (typeof v === "object" && v !== null && "__blob" in v) {
452
+ const buf = Buffer.from(v.__blob, "base64");
453
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
454
+ }
455
+ return v;
456
+ }
457
+ function deserializeResultSet(srs) {
458
+ const rows = srs.rows.map((obj) => {
459
+ const values = srs.columns.map(
460
+ (col) => deserializeValue(obj[col] ?? null)
461
+ );
462
+ const row = values;
463
+ for (let i = 0; i < srs.columns.length; i++) {
464
+ const col = srs.columns[i];
465
+ if (col !== void 0) {
466
+ row[col] = values[i] ?? null;
467
+ }
468
+ }
469
+ Object.defineProperty(row, "length", {
470
+ value: values.length,
471
+ enumerable: false
472
+ });
473
+ return row;
474
+ });
475
+ return {
476
+ columns: srs.columns,
477
+ columnTypes: srs.columnTypes ?? [],
478
+ rows,
479
+ rowsAffected: srs.rowsAffected,
480
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
481
+ toJSON: () => ({
482
+ columns: srs.columns,
483
+ columnTypes: srs.columnTypes ?? [],
484
+ rows: srs.rows,
485
+ rowsAffected: srs.rowsAffected,
486
+ lastInsertRowid: srs.lastInsertRowid
487
+ })
488
+ };
489
+ }
490
+ var init_daemon_protocol = __esm({
491
+ "src/lib/daemon-protocol.ts"() {
492
+ "use strict";
493
+ }
494
+ });
495
+
496
+ // src/lib/db-daemon-client.ts
497
+ var db_daemon_client_exports = {};
498
+ __export(db_daemon_client_exports, {
499
+ createDaemonDbClient: () => createDaemonDbClient,
500
+ initDaemonDbClient: () => initDaemonDbClient
501
+ });
502
+ function normalizeStatement(stmt) {
503
+ if (typeof stmt === "string") {
504
+ return { sql: stmt, args: [] };
505
+ }
506
+ const sql = stmt.sql;
507
+ let args = [];
508
+ if (Array.isArray(stmt.args)) {
509
+ args = stmt.args.map((v) => serializeValue(v));
510
+ } else if (stmt.args && typeof stmt.args === "object") {
511
+ const named = {};
512
+ for (const [key, val] of Object.entries(stmt.args)) {
513
+ named[key] = serializeValue(val);
514
+ }
515
+ return { sql, args: named };
516
+ }
517
+ return { sql, args };
518
+ }
519
+ function createDaemonDbClient(fallbackClient) {
520
+ let _useDaemon = false;
521
+ const client = {
522
+ async execute(stmt) {
523
+ if (!_useDaemon || !isClientConnected()) {
524
+ return fallbackClient.execute(stmt);
525
+ }
526
+ const { sql, args } = normalizeStatement(stmt);
527
+ const response = await sendDaemonRequest({
528
+ type: "db-execute",
529
+ sql,
530
+ args
531
+ });
532
+ if (response.error) {
533
+ const errMsg = String(response.error);
534
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
535
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
536
+ `);
537
+ return fallbackClient.execute(stmt);
538
+ }
539
+ throw new Error(errMsg);
540
+ }
541
+ if (response.db) {
542
+ return deserializeResultSet(response.db);
543
+ }
544
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
545
+ return fallbackClient.execute(stmt);
546
+ },
547
+ async batch(stmts, mode) {
548
+ if (!_useDaemon || !isClientConnected()) {
549
+ return fallbackClient.batch(stmts, mode);
550
+ }
551
+ const statements = stmts.map(normalizeStatement);
552
+ const response = await sendDaemonRequest({
553
+ type: "db-batch",
554
+ statements,
555
+ mode: mode ?? "deferred"
556
+ });
557
+ if (response.error) {
558
+ const errMsg = String(response.error);
559
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
560
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
561
+ `);
562
+ return fallbackClient.batch(stmts, mode);
563
+ }
564
+ throw new Error(errMsg);
565
+ }
566
+ const batchResults = response["db-batch"];
567
+ if (batchResults) {
568
+ return batchResults.map(deserializeResultSet);
569
+ }
570
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
571
+ return fallbackClient.batch(stmts, mode);
572
+ },
573
+ // Transaction support — delegate to fallback (transactions need direct connection)
574
+ async transaction(mode) {
575
+ return fallbackClient.transaction(mode);
576
+ },
577
+ // executeMultiple — delegate to fallback (used only for schema migrations)
578
+ async executeMultiple(sql) {
579
+ return fallbackClient.executeMultiple(sql);
580
+ },
581
+ // migrate — delegate to fallback
582
+ async migrate(stmts) {
583
+ return fallbackClient.migrate(stmts);
584
+ },
585
+ // Sync mode — delegate to fallback
586
+ sync() {
587
+ return fallbackClient.sync();
588
+ },
589
+ close() {
590
+ _useDaemon = false;
591
+ },
592
+ get closed() {
593
+ return fallbackClient.closed;
594
+ },
595
+ get protocol() {
596
+ return fallbackClient.protocol;
597
+ }
598
+ };
599
+ return {
600
+ ...client,
601
+ /** Enable daemon routing (call after confirming daemon is connected) */
602
+ _enableDaemon() {
603
+ _useDaemon = true;
604
+ },
605
+ /** Check if daemon routing is active */
606
+ _isDaemonActive() {
607
+ return _useDaemon && isClientConnected();
608
+ }
609
+ };
610
+ }
611
+ async function initDaemonDbClient(fallbackClient) {
612
+ if (process.env.EXE_IS_DAEMON === "1") return null;
613
+ const connected = await connectEmbedDaemon();
614
+ if (!connected) {
615
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
616
+ return null;
617
+ }
618
+ const client = createDaemonDbClient(fallbackClient);
619
+ client._enableDaemon();
620
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
621
+ return client;
622
+ }
623
+ var init_db_daemon_client = __esm({
624
+ "src/lib/db-daemon-client.ts"() {
625
+ "use strict";
626
+ init_exe_daemon_client();
627
+ init_daemon_protocol();
628
+ }
629
+ });
630
+
194
631
  // src/lib/database.ts
195
632
  var database_exports = {};
196
633
  __export(database_exports, {
@@ -199,6 +636,7 @@ __export(database_exports, {
199
636
  ensureSchema: () => ensureSchema,
200
637
  getClient: () => getClient,
201
638
  getRawClient: () => getRawClient,
639
+ initDaemonClient: () => initDaemonClient,
202
640
  initDatabase: () => initDatabase,
203
641
  initTurso: () => initTurso,
204
642
  isInitialized: () => isInitialized
@@ -226,8 +664,27 @@ function getClient() {
226
664
  if (!_resilientClient) {
227
665
  throw new Error("Database client not initialized. Call initDatabase() first.");
228
666
  }
667
+ if (process.env.EXE_IS_DAEMON === "1") {
668
+ return _resilientClient;
669
+ }
670
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
671
+ return _daemonClient;
672
+ }
229
673
  return _resilientClient;
230
674
  }
675
+ async function initDaemonClient() {
676
+ if (process.env.EXE_IS_DAEMON === "1") return;
677
+ if (!_resilientClient) return;
678
+ try {
679
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
680
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
681
+ } catch (err) {
682
+ process.stderr.write(
683
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
684
+ `
685
+ );
686
+ }
687
+ }
231
688
  function getRawClient() {
232
689
  if (!_client) {
233
690
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -714,6 +1171,12 @@ async function ensureSchema() {
714
1171
  } catch {
715
1172
  }
716
1173
  }
1174
+ try {
1175
+ await client.execute(
1176
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1177
+ );
1178
+ } catch {
1179
+ }
717
1180
  await client.executeMultiple(`
718
1181
  CREATE TABLE IF NOT EXISTS entities (
719
1182
  id TEXT PRIMARY KEY,
@@ -766,7 +1229,30 @@ async function ensureSchema() {
766
1229
  entity_id TEXT NOT NULL,
767
1230
  PRIMARY KEY (hyperedge_id, entity_id)
768
1231
  );
1232
+
1233
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1234
+ name,
1235
+ content=entities,
1236
+ content_rowid=rowid
1237
+ );
1238
+
1239
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1240
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1241
+ END;
1242
+
1243
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1244
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1245
+ END;
1246
+
1247
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1248
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1249
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1250
+ END;
769
1251
  `);
1252
+ try {
1253
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1254
+ } catch {
1255
+ }
770
1256
  await client.executeMultiple(`
771
1257
  CREATE TABLE IF NOT EXISTS entity_aliases (
772
1258
  alias TEXT NOT NULL PRIMARY KEY,
@@ -947,6 +1433,33 @@ async function ensureSchema() {
947
1433
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
948
1434
  ON conversations(channel_id);
949
1435
  `);
1436
+ await client.executeMultiple(`
1437
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1438
+ session_uuid TEXT PRIMARY KEY,
1439
+ agent_id TEXT NOT NULL,
1440
+ session_name TEXT,
1441
+ task_id TEXT,
1442
+ project_name TEXT,
1443
+ started_at TEXT NOT NULL
1444
+ );
1445
+
1446
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1447
+ ON session_agent_map(agent_id);
1448
+ `);
1449
+ try {
1450
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1451
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1452
+ await client.execute({
1453
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1454
+ SELECT session_id, agent_id, '', MIN(timestamp)
1455
+ FROM memories
1456
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1457
+ GROUP BY session_id, agent_id`,
1458
+ args: []
1459
+ });
1460
+ }
1461
+ } catch {
1462
+ }
950
1463
  try {
951
1464
  await client.execute({
952
1465
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1080,15 +1593,41 @@ async function ensureSchema() {
1080
1593
  });
1081
1594
  } catch {
1082
1595
  }
1596
+ for (const col of [
1597
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1598
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1599
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1600
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1601
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1602
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1603
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1604
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1605
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1606
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1607
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1608
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1609
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1610
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1611
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1612
+ ]) {
1613
+ try {
1614
+ await client.execute(col);
1615
+ } catch {
1616
+ }
1617
+ }
1083
1618
  }
1084
1619
  async function disposeDatabase() {
1620
+ if (_daemonClient) {
1621
+ _daemonClient.close();
1622
+ _daemonClient = null;
1623
+ }
1085
1624
  if (_client) {
1086
1625
  _client.close();
1087
1626
  _client = null;
1088
1627
  _resilientClient = null;
1089
1628
  }
1090
1629
  }
1091
- var _client, _resilientClient, initTurso, disposeTurso;
1630
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1092
1631
  var init_database = __esm({
1093
1632
  "src/lib/database.ts"() {
1094
1633
  "use strict";
@@ -1096,6 +1635,7 @@ var init_database = __esm({
1096
1635
  init_employees();
1097
1636
  _client = null;
1098
1637
  _resilientClient = null;
1638
+ _daemonClient = null;
1099
1639
  initTurso = initDatabase;
1100
1640
  disposeTurso = disposeDatabase;
1101
1641
  }
@@ -1105,13 +1645,13 @@ var init_database = __esm({
1105
1645
  init_config();
1106
1646
  import crypto from "crypto";
1107
1647
  import os3 from "os";
1108
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
1109
- import path3 from "path";
1110
- var DEVICE_JSON_PATH = path3.join(EXE_AI_DIR, "device.json");
1648
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync4 } from "fs";
1649
+ import path4 from "path";
1650
+ var DEVICE_JSON_PATH = path4.join(EXE_AI_DIR, "device.json");
1111
1651
  function getDeviceInfo() {
1112
- if (existsSync3(DEVICE_JSON_PATH)) {
1652
+ if (existsSync4(DEVICE_JSON_PATH)) {
1113
1653
  try {
1114
- const raw = readFileSync3(DEVICE_JSON_PATH, "utf8");
1654
+ const raw = readFileSync4(DEVICE_JSON_PATH, "utf8");
1115
1655
  const data = JSON.parse(raw);
1116
1656
  if (data.deviceId && data.friendlyName && data.hostname) {
1117
1657
  return data;
@@ -1125,7 +1665,7 @@ function getDeviceInfo() {
1125
1665
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
1126
1666
  hostname
1127
1667
  };
1128
- mkdirSync(path3.dirname(DEVICE_JSON_PATH), { recursive: true });
1668
+ mkdirSync(path4.dirname(DEVICE_JSON_PATH), { recursive: true });
1129
1669
  writeFileSync2(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
1130
1670
  return info;
1131
1671
  }
@@ -285,10 +285,12 @@ function handleData(chunk) {
285
285
  if (!line) continue;
286
286
  try {
287
287
  const response = JSON.parse(line);
288
- const entry = _pending.get(response.id);
288
+ const id = response.id;
289
+ if (!id) continue;
290
+ const entry = _pending.get(id);
289
291
  if (entry) {
290
292
  clearTimeout(entry.timer);
291
- _pending.delete(response.id);
293
+ _pending.delete(id);
292
294
  entry.resolve(response);
293
295
  }
294
296
  } catch {
@@ -459,6 +461,9 @@ async function connectEmbedDaemon() {
459
461
  return false;
460
462
  }
461
463
  function sendRequest(texts, priority) {
464
+ return sendDaemonRequest({ texts, priority });
465
+ }
466
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
462
467
  return new Promise((resolve) => {
463
468
  if (!_socket || !_connected) {
464
469
  resolve({ error: "Not connected" });
@@ -468,10 +473,10 @@ function sendRequest(texts, priority) {
468
473
  const timer = setTimeout(() => {
469
474
  _pending.delete(id);
470
475
  resolve({ error: "Request timeout" });
471
- }, REQUEST_TIMEOUT_MS);
476
+ }, timeoutMs);
472
477
  _pending.set(id, { resolve, timer });
473
478
  try {
474
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
479
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
475
480
  } catch {
476
481
  clearTimeout(timer);
477
482
  _pending.delete(id);
@@ -481,30 +486,11 @@ function sendRequest(texts, priority) {
481
486
  }
482
487
  async function pingDaemon() {
483
488
  if (!_socket || !_connected) return null;
484
- return new Promise((resolve) => {
485
- const id = randomUUID();
486
- const timer = setTimeout(() => {
487
- _pending.delete(id);
488
- resolve(null);
489
- }, 5e3);
490
- _pending.set(id, {
491
- resolve: (data) => {
492
- if (data.health) {
493
- resolve(data.health);
494
- } else {
495
- resolve(null);
496
- }
497
- },
498
- timer
499
- });
500
- try {
501
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
502
- } catch {
503
- clearTimeout(timer);
504
- _pending.delete(id);
505
- resolve(null);
506
- }
507
- });
489
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
490
+ if (response.health) {
491
+ return response.health;
492
+ }
493
+ return null;
508
494
  }
509
495
  function killAndRespawnDaemon() {
510
496
  process.stderr.write("[exed-client] Killing daemon for restart...\n");