@askexenow/exe-os 0.8.82 → 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 (97) 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 +14360 -12525
  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 +1260 -323
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +32 -9
  11. package/dist/bin/exe-dispatch.js +212 -36
  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 +553 -174
  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 +41 -11
  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 +577 -33
  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 +901 -209
  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 +906 -213
  38. package/dist/bin/setup.js +870 -271
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +4 -3
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +550 -168
  43. package/dist/hooks/bug-report-worker.js +210 -25
  44. package/dist/hooks/commit-complete.js +899 -207
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1639 -1195
  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 +899 -207
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +245 -104
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +245 -104
  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 +1970 -1336
  61. package/dist/index.js +1653 -1055
  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 +1957 -924
  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/license.js +3 -3
  76. package/dist/lib/messaging.js +8 -1
  77. package/dist/lib/reminders.js +91 -74
  78. package/dist/lib/schedules.js +96 -2
  79. package/dist/lib/skill-learning.js +103 -85
  80. package/dist/lib/store.js +234 -73
  81. package/dist/lib/tasks.js +113 -24
  82. package/dist/lib/tmux-routing.js +122 -33
  83. package/dist/lib/token-spend.js +273 -0
  84. package/dist/lib/ws-client.js +11 -0
  85. package/dist/mcp/server.js +10874 -5546
  86. package/dist/mcp/tools/complete-reminder.js +94 -77
  87. package/dist/mcp/tools/create-reminder.js +94 -77
  88. package/dist/mcp/tools/create-task.js +810 -27
  89. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  90. package/dist/mcp/tools/list-reminders.js +94 -77
  91. package/dist/mcp/tools/list-tasks.js +31 -1
  92. package/dist/mcp/tools/send-message.js +8 -1
  93. package/dist/mcp/tools/update-task.js +39 -10
  94. package/dist/runtime/index.js +913 -221
  95. package/dist/tui/App.js +1000 -298
  96. package/package.json +6 -1
  97. package/src/commands/exe/build-adv.md +2 -2
@@ -428,387 +428,940 @@ var init_employees = __esm({
428
428
  }
429
429
  });
430
430
 
431
- // src/lib/database.ts
432
- var database_exports = {};
433
- __export(database_exports, {
434
- disposeDatabase: () => disposeDatabase,
435
- disposeTurso: () => disposeTurso,
436
- ensureSchema: () => ensureSchema,
437
- getClient: () => getClient,
438
- getRawClient: () => getRawClient,
439
- initDatabase: () => initDatabase,
440
- initTurso: () => initTurso,
441
- isInitialized: () => isInitialized
442
- });
443
- import { createClient } from "@libsql/client";
444
- async function initDatabase(config) {
445
- if (_client) {
446
- _client.close();
447
- _client = null;
448
- _resilientClient = null;
431
+ // src/lib/exe-daemon-client.ts
432
+ import net from "net";
433
+ import { spawn } from "child_process";
434
+ import { randomUUID } from "crypto";
435
+ import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
436
+ import path3 from "path";
437
+ import { fileURLToPath } from "url";
438
+ function handleData(chunk) {
439
+ _buffer += chunk.toString();
440
+ if (_buffer.length > MAX_BUFFER) {
441
+ _buffer = "";
442
+ return;
449
443
  }
450
- const opts = {
451
- url: `file:${config.dbPath}`
452
- };
453
- if (config.encryptionKey) {
454
- opts.encryptionKey = config.encryptionKey;
444
+ let newlineIdx;
445
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
446
+ const line = _buffer.slice(0, newlineIdx).trim();
447
+ _buffer = _buffer.slice(newlineIdx + 1);
448
+ if (!line) continue;
449
+ try {
450
+ const response = JSON.parse(line);
451
+ const id = response.id;
452
+ if (!id) continue;
453
+ const entry = _pending.get(id);
454
+ if (entry) {
455
+ clearTimeout(entry.timer);
456
+ _pending.delete(id);
457
+ entry.resolve(response);
458
+ }
459
+ } catch {
460
+ }
455
461
  }
456
- _client = createClient(opts);
457
- _resilientClient = wrapWithRetry(_client);
458
- }
459
- function isInitialized() {
460
- return _client !== null;
461
462
  }
462
- function getClient() {
463
- if (!_resilientClient) {
464
- throw new Error("Database client not initialized. Call initDatabase() first.");
463
+ function cleanupStaleFiles() {
464
+ if (existsSync3(PID_PATH)) {
465
+ try {
466
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
467
+ if (pid > 0) {
468
+ try {
469
+ process.kill(pid, 0);
470
+ return;
471
+ } catch {
472
+ }
473
+ }
474
+ } catch {
475
+ }
476
+ try {
477
+ unlinkSync2(PID_PATH);
478
+ } catch {
479
+ }
480
+ try {
481
+ unlinkSync2(SOCKET_PATH);
482
+ } catch {
483
+ }
465
484
  }
466
- return _resilientClient;
467
485
  }
468
- function getRawClient() {
469
- if (!_client) {
470
- throw new Error("Database client not initialized. Call initDatabase() first.");
486
+ function findPackageRoot() {
487
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
488
+ const { root } = path3.parse(dir);
489
+ while (dir !== root) {
490
+ if (existsSync3(path3.join(dir, "package.json"))) return dir;
491
+ dir = path3.dirname(dir);
471
492
  }
472
- return _client;
493
+ return null;
473
494
  }
474
- async function ensureSchema() {
475
- const client = getRawClient();
476
- await client.execute("PRAGMA journal_mode = WAL");
477
- await client.execute("PRAGMA busy_timeout = 30000");
478
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
479
- try {
480
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
481
- } catch {
495
+ function spawnDaemon() {
496
+ const pkgRoot = findPackageRoot();
497
+ if (!pkgRoot) {
498
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
499
+ return;
482
500
  }
483
- await client.executeMultiple(`
484
- CREATE TABLE IF NOT EXISTS memories (
485
- id TEXT PRIMARY KEY,
486
- agent_id TEXT NOT NULL,
487
- agent_role TEXT NOT NULL,
488
- session_id TEXT NOT NULL,
489
- timestamp TEXT NOT NULL,
490
- tool_name TEXT NOT NULL,
491
- project_name TEXT NOT NULL,
492
- has_error INTEGER NOT NULL DEFAULT 0,
493
- raw_text TEXT NOT NULL,
494
- vector F32_BLOB(1024),
495
- version INTEGER NOT NULL DEFAULT 0
496
- );
497
-
498
- CREATE INDEX IF NOT EXISTS idx_memories_agent
499
- ON memories(agent_id);
500
-
501
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
502
- ON memories(timestamp);
503
-
504
- CREATE INDEX IF NOT EXISTS idx_memories_session
505
- ON memories(session_id);
506
-
507
- CREATE INDEX IF NOT EXISTS idx_memories_project
508
- ON memories(project_name);
509
-
510
- CREATE INDEX IF NOT EXISTS idx_memories_tool
511
- ON memories(tool_name);
512
-
513
- CREATE INDEX IF NOT EXISTS idx_memories_version
514
- ON memories(version);
515
-
516
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
517
- ON memories(agent_id, project_name);
518
- `);
519
- await client.executeMultiple(`
520
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
521
- raw_text,
522
- content='memories',
523
- content_rowid='rowid'
524
- );
525
-
526
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
527
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
528
- END;
529
-
530
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
531
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
532
- END;
533
-
534
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
535
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
536
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
537
- END;
538
- `);
539
- await client.executeMultiple(`
540
- CREATE TABLE IF NOT EXISTS sync_meta (
541
- key TEXT PRIMARY KEY,
542
- value TEXT NOT NULL
543
- );
544
- `);
545
- await client.executeMultiple(`
546
- CREATE TABLE IF NOT EXISTS tasks (
547
- id TEXT PRIMARY KEY,
548
- title TEXT NOT NULL,
549
- assigned_to TEXT NOT NULL,
550
- assigned_by TEXT NOT NULL,
551
- project_name TEXT NOT NULL,
552
- priority TEXT NOT NULL DEFAULT 'p1',
553
- status TEXT NOT NULL DEFAULT 'open',
554
- task_file TEXT,
555
- created_at TEXT NOT NULL,
556
- updated_at TEXT NOT NULL
557
- );
558
-
559
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
560
- ON tasks(assigned_to, status);
561
- `);
562
- await client.executeMultiple(`
563
- CREATE TABLE IF NOT EXISTS behaviors (
564
- id TEXT PRIMARY KEY,
565
- agent_id TEXT NOT NULL,
566
- project_name TEXT,
567
- domain TEXT,
568
- content TEXT NOT NULL,
569
- active INTEGER NOT NULL DEFAULT 1,
570
- created_at TEXT NOT NULL,
571
- updated_at TEXT NOT NULL
572
- );
573
-
574
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
575
- ON behaviors(agent_id, active);
576
- `);
577
- try {
578
- const coordinatorName = getCoordinatorName();
579
- const existing = await client.execute({
580
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
581
- args: [coordinatorName]
582
- });
583
- if (Number(existing.rows[0]?.cnt) === 0) {
584
- const seededAt = "2026-03-25T00:00:00Z";
585
- for (const [domain, content] of [
586
- ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
587
- ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
588
- ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
589
- ]) {
590
- await client.execute({
591
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
592
- VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
593
- args: [coordinatorName, domain, content, seededAt, seededAt]
594
- });
595
- }
596
- }
597
- } catch {
501
+ const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
502
+ if (!existsSync3(daemonPath)) {
503
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
504
+ `);
505
+ return;
598
506
  }
507
+ const resolvedPath = daemonPath;
508
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
509
+ `);
510
+ const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
511
+ let stderrFd = "ignore";
599
512
  try {
600
- await client.execute({
601
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
602
- args: []
603
- });
513
+ stderrFd = openSync(logPath, "a");
604
514
  } catch {
605
515
  }
606
- try {
607
- await client.execute({
608
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
609
- args: []
610
- });
611
- } catch {
516
+ const child = spawn(process.execPath, [resolvedPath], {
517
+ detached: true,
518
+ stdio: ["ignore", "ignore", stderrFd],
519
+ env: {
520
+ ...process.env,
521
+ TMUX: void 0,
522
+ // Daemon is global — must not inherit session scope
523
+ TMUX_PANE: void 0,
524
+ // Prevents resolveExeSession() from scoping to one session
525
+ EXE_DAEMON_SOCK: SOCKET_PATH,
526
+ EXE_DAEMON_PID: PID_PATH
527
+ }
528
+ });
529
+ child.unref();
530
+ if (typeof stderrFd === "number") {
531
+ try {
532
+ closeSync(stderrFd);
533
+ } catch {
534
+ }
612
535
  }
536
+ }
537
+ function acquireSpawnLock() {
613
538
  try {
614
- await client.execute({
615
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
616
- args: []
617
- });
618
- } catch {
619
- }
620
- try {
621
- await client.execute({
622
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
623
- ON tasks(parent_task_id)
624
- WHERE parent_task_id IS NOT NULL`,
625
- args: []
626
- });
539
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
540
+ closeSync(fd);
541
+ return true;
627
542
  } catch {
543
+ try {
544
+ const stat = statSync(SPAWN_LOCK_PATH);
545
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
546
+ try {
547
+ unlinkSync2(SPAWN_LOCK_PATH);
548
+ } catch {
549
+ }
550
+ try {
551
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
552
+ closeSync(fd);
553
+ return true;
554
+ } catch {
555
+ }
556
+ }
557
+ } catch {
558
+ }
559
+ return false;
628
560
  }
561
+ }
562
+ function releaseSpawnLock() {
629
563
  try {
630
- await client.execute({
631
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
632
- args: []
633
- });
564
+ unlinkSync2(SPAWN_LOCK_PATH);
634
565
  } catch {
635
566
  }
636
- try {
637
- await client.execute({
638
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
639
- args: []
567
+ }
568
+ function connectToSocket() {
569
+ return new Promise((resolve) => {
570
+ if (_socket && _connected) {
571
+ resolve(true);
572
+ return;
573
+ }
574
+ const socket = net.createConnection({ path: SOCKET_PATH });
575
+ const connectTimeout = setTimeout(() => {
576
+ socket.destroy();
577
+ resolve(false);
578
+ }, 2e3);
579
+ socket.on("connect", () => {
580
+ clearTimeout(connectTimeout);
581
+ _socket = socket;
582
+ _connected = true;
583
+ _buffer = "";
584
+ socket.on("data", handleData);
585
+ socket.on("close", () => {
586
+ _connected = false;
587
+ _socket = null;
588
+ for (const [id, entry] of _pending) {
589
+ clearTimeout(entry.timer);
590
+ _pending.delete(id);
591
+ entry.resolve({ error: "Connection closed" });
592
+ }
593
+ });
594
+ socket.on("error", () => {
595
+ _connected = false;
596
+ _socket = null;
597
+ });
598
+ resolve(true);
640
599
  });
641
- } catch {
642
- }
643
- try {
644
- await client.execute({
645
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
646
- args: []
600
+ socket.on("error", () => {
601
+ clearTimeout(connectTimeout);
602
+ resolve(false);
647
603
  });
648
- } catch {
604
+ });
605
+ }
606
+ async function connectEmbedDaemon() {
607
+ if (_socket && _connected) return true;
608
+ if (await connectToSocket()) return true;
609
+ if (acquireSpawnLock()) {
610
+ try {
611
+ cleanupStaleFiles();
612
+ spawnDaemon();
613
+ } finally {
614
+ releaseSpawnLock();
615
+ }
649
616
  }
650
- try {
651
- await client.execute({
652
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
653
- args: []
654
- });
655
- } catch {
617
+ const start = Date.now();
618
+ let delay2 = 100;
619
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
620
+ await new Promise((r) => setTimeout(r, delay2));
621
+ if (await connectToSocket()) return true;
622
+ delay2 = Math.min(delay2 * 2, 3e3);
656
623
  }
657
- try {
658
- await client.execute({
659
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
660
- args: []
661
- });
662
- } catch {
624
+ return false;
625
+ }
626
+ function sendRequest(texts, priority) {
627
+ return sendDaemonRequest({ texts, priority });
628
+ }
629
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
630
+ return new Promise((resolve) => {
631
+ if (!_socket || !_connected) {
632
+ resolve({ error: "Not connected" });
633
+ return;
634
+ }
635
+ const id = randomUUID();
636
+ const timer = setTimeout(() => {
637
+ _pending.delete(id);
638
+ resolve({ error: "Request timeout" });
639
+ }, timeoutMs);
640
+ _pending.set(id, { resolve, timer });
641
+ try {
642
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
643
+ } catch {
644
+ clearTimeout(timer);
645
+ _pending.delete(id);
646
+ resolve({ error: "Write failed" });
647
+ }
648
+ });
649
+ }
650
+ async function pingDaemon() {
651
+ if (!_socket || !_connected) return null;
652
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
653
+ if (response.health) {
654
+ return response.health;
663
655
  }
664
- try {
665
- await client.execute({
666
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
667
- args: []
668
- });
669
- } catch {
656
+ return null;
657
+ }
658
+ function killAndRespawnDaemon() {
659
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
660
+ if (existsSync3(PID_PATH)) {
661
+ try {
662
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
663
+ if (pid > 0) {
664
+ try {
665
+ process.kill(pid, "SIGKILL");
666
+ } catch {
667
+ }
668
+ }
669
+ } catch {
670
+ }
670
671
  }
671
- try {
672
- await client.execute({
673
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
674
- args: []
675
- });
676
- } catch {
672
+ if (_socket) {
673
+ _socket.destroy();
674
+ _socket = null;
677
675
  }
676
+ _connected = false;
677
+ _buffer = "";
678
678
  try {
679
- await client.execute({
680
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
681
- args: []
682
- });
679
+ unlinkSync2(PID_PATH);
683
680
  } catch {
684
681
  }
685
682
  try {
686
- await client.execute({
687
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
688
- args: []
689
- });
683
+ unlinkSync2(SOCKET_PATH);
690
684
  } catch {
691
685
  }
692
- try {
693
- await client.execute({
694
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
695
- args: []
696
- });
697
- } catch {
686
+ spawnDaemon();
687
+ }
688
+ async function embedViaClient(text, priority = "high") {
689
+ if (!_connected && !await connectEmbedDaemon()) return null;
690
+ _requestCount++;
691
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
692
+ const health = await pingDaemon();
693
+ if (!health) {
694
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
695
+ `);
696
+ killAndRespawnDaemon();
697
+ const start = Date.now();
698
+ let delay2 = 200;
699
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
700
+ await new Promise((r) => setTimeout(r, delay2));
701
+ if (await connectToSocket()) break;
702
+ delay2 = Math.min(delay2 * 2, 3e3);
703
+ }
704
+ if (!_connected) return null;
705
+ }
698
706
  }
699
- try {
700
- await client.execute({
701
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
702
- args: []
703
- });
704
- } catch {
707
+ const result = await sendRequest([text], priority);
708
+ if (!result.error && result.vectors?.[0]) return result.vectors[0];
709
+ if (result.error) {
710
+ process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
711
+ `);
712
+ killAndRespawnDaemon();
713
+ const start = Date.now();
714
+ let delay2 = 200;
715
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
716
+ await new Promise((r) => setTimeout(r, delay2));
717
+ if (await connectToSocket()) break;
718
+ delay2 = Math.min(delay2 * 2, 3e3);
719
+ }
720
+ if (!_connected) return null;
721
+ const retry = await sendRequest([text], priority);
722
+ if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
723
+ process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
724
+ `);
705
725
  }
706
- try {
707
- await client.execute({
708
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
709
- args: []
710
- });
711
- } catch {
726
+ return null;
727
+ }
728
+ function disconnectClient() {
729
+ if (_socket) {
730
+ _socket.destroy();
731
+ _socket = null;
712
732
  }
713
- try {
714
- await client.execute({
715
- sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
716
- args: []
717
- });
718
- } catch {
733
+ _connected = false;
734
+ _buffer = "";
735
+ for (const [id, entry] of _pending) {
736
+ clearTimeout(entry.timer);
737
+ _pending.delete(id);
738
+ entry.resolve({ error: "Client disconnected" });
719
739
  }
720
- await client.executeMultiple(`
721
- CREATE TABLE IF NOT EXISTS consolidations (
722
- id TEXT PRIMARY KEY,
723
- consolidated_memory_id TEXT NOT NULL,
724
- source_memory_id TEXT NOT NULL,
725
- created_at TEXT NOT NULL
726
- );
727
-
728
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
729
- ON consolidations(source_memory_id);
730
-
731
- CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
732
- ON consolidations(consolidated_memory_id);
733
- `);
734
- await client.executeMultiple(`
735
- CREATE TABLE IF NOT EXISTS reminders (
740
+ }
741
+ function isClientConnected() {
742
+ return _connected;
743
+ }
744
+ 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;
745
+ var init_exe_daemon_client = __esm({
746
+ "src/lib/exe-daemon-client.ts"() {
747
+ "use strict";
748
+ init_config();
749
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
750
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
751
+ SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
752
+ SPAWN_LOCK_STALE_MS = 3e4;
753
+ CONNECT_TIMEOUT_MS = 15e3;
754
+ REQUEST_TIMEOUT_MS = 3e4;
755
+ _socket = null;
756
+ _connected = false;
757
+ _buffer = "";
758
+ _requestCount = 0;
759
+ HEALTH_CHECK_INTERVAL = 100;
760
+ _pending = /* @__PURE__ */ new Map();
761
+ MAX_BUFFER = 1e7;
762
+ }
763
+ });
764
+
765
+ // src/lib/daemon-protocol.ts
766
+ function serializeValue(v) {
767
+ if (v === null || v === void 0) return null;
768
+ if (typeof v === "bigint") return Number(v);
769
+ if (typeof v === "boolean") return v ? 1 : 0;
770
+ if (v instanceof Uint8Array) {
771
+ return { __blob: Buffer.from(v).toString("base64") };
772
+ }
773
+ if (ArrayBuffer.isView(v)) {
774
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
775
+ }
776
+ if (v instanceof ArrayBuffer) {
777
+ return { __blob: Buffer.from(v).toString("base64") };
778
+ }
779
+ if (typeof v === "string" || typeof v === "number") return v;
780
+ return String(v);
781
+ }
782
+ function deserializeValue(v) {
783
+ if (v === null) return null;
784
+ if (typeof v === "object" && v !== null && "__blob" in v) {
785
+ const buf = Buffer.from(v.__blob, "base64");
786
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
787
+ }
788
+ return v;
789
+ }
790
+ function deserializeResultSet(srs) {
791
+ const rows = srs.rows.map((obj) => {
792
+ const values = srs.columns.map(
793
+ (col) => deserializeValue(obj[col] ?? null)
794
+ );
795
+ const row = values;
796
+ for (let i = 0; i < srs.columns.length; i++) {
797
+ const col = srs.columns[i];
798
+ if (col !== void 0) {
799
+ row[col] = values[i] ?? null;
800
+ }
801
+ }
802
+ Object.defineProperty(row, "length", {
803
+ value: values.length,
804
+ enumerable: false
805
+ });
806
+ return row;
807
+ });
808
+ return {
809
+ columns: srs.columns,
810
+ columnTypes: srs.columnTypes ?? [],
811
+ rows,
812
+ rowsAffected: srs.rowsAffected,
813
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
814
+ toJSON: () => ({
815
+ columns: srs.columns,
816
+ columnTypes: srs.columnTypes ?? [],
817
+ rows: srs.rows,
818
+ rowsAffected: srs.rowsAffected,
819
+ lastInsertRowid: srs.lastInsertRowid
820
+ })
821
+ };
822
+ }
823
+ var init_daemon_protocol = __esm({
824
+ "src/lib/daemon-protocol.ts"() {
825
+ "use strict";
826
+ }
827
+ });
828
+
829
+ // src/lib/db-daemon-client.ts
830
+ var db_daemon_client_exports = {};
831
+ __export(db_daemon_client_exports, {
832
+ createDaemonDbClient: () => createDaemonDbClient,
833
+ initDaemonDbClient: () => initDaemonDbClient
834
+ });
835
+ function normalizeStatement(stmt) {
836
+ if (typeof stmt === "string") {
837
+ return { sql: stmt, args: [] };
838
+ }
839
+ const sql = stmt.sql;
840
+ let args = [];
841
+ if (Array.isArray(stmt.args)) {
842
+ args = stmt.args.map((v) => serializeValue(v));
843
+ } else if (stmt.args && typeof stmt.args === "object") {
844
+ const named = {};
845
+ for (const [key, val] of Object.entries(stmt.args)) {
846
+ named[key] = serializeValue(val);
847
+ }
848
+ return { sql, args: named };
849
+ }
850
+ return { sql, args };
851
+ }
852
+ function createDaemonDbClient(fallbackClient) {
853
+ let _useDaemon = false;
854
+ const client = {
855
+ async execute(stmt) {
856
+ if (!_useDaemon || !isClientConnected()) {
857
+ return fallbackClient.execute(stmt);
858
+ }
859
+ const { sql, args } = normalizeStatement(stmt);
860
+ const response = await sendDaemonRequest({
861
+ type: "db-execute",
862
+ sql,
863
+ args
864
+ });
865
+ if (response.error) {
866
+ const errMsg = String(response.error);
867
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
868
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
869
+ `);
870
+ return fallbackClient.execute(stmt);
871
+ }
872
+ throw new Error(errMsg);
873
+ }
874
+ if (response.db) {
875
+ return deserializeResultSet(response.db);
876
+ }
877
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
878
+ return fallbackClient.execute(stmt);
879
+ },
880
+ async batch(stmts, mode) {
881
+ if (!_useDaemon || !isClientConnected()) {
882
+ return fallbackClient.batch(stmts, mode);
883
+ }
884
+ const statements = stmts.map(normalizeStatement);
885
+ const response = await sendDaemonRequest({
886
+ type: "db-batch",
887
+ statements,
888
+ mode: mode ?? "deferred"
889
+ });
890
+ if (response.error) {
891
+ const errMsg = String(response.error);
892
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
893
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
894
+ `);
895
+ return fallbackClient.batch(stmts, mode);
896
+ }
897
+ throw new Error(errMsg);
898
+ }
899
+ const batchResults = response["db-batch"];
900
+ if (batchResults) {
901
+ return batchResults.map(deserializeResultSet);
902
+ }
903
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
904
+ return fallbackClient.batch(stmts, mode);
905
+ },
906
+ // Transaction support — delegate to fallback (transactions need direct connection)
907
+ async transaction(mode) {
908
+ return fallbackClient.transaction(mode);
909
+ },
910
+ // executeMultiple — delegate to fallback (used only for schema migrations)
911
+ async executeMultiple(sql) {
912
+ return fallbackClient.executeMultiple(sql);
913
+ },
914
+ // migrate — delegate to fallback
915
+ async migrate(stmts) {
916
+ return fallbackClient.migrate(stmts);
917
+ },
918
+ // Sync mode — delegate to fallback
919
+ sync() {
920
+ return fallbackClient.sync();
921
+ },
922
+ close() {
923
+ _useDaemon = false;
924
+ },
925
+ get closed() {
926
+ return fallbackClient.closed;
927
+ },
928
+ get protocol() {
929
+ return fallbackClient.protocol;
930
+ }
931
+ };
932
+ return {
933
+ ...client,
934
+ /** Enable daemon routing (call after confirming daemon is connected) */
935
+ _enableDaemon() {
936
+ _useDaemon = true;
937
+ },
938
+ /** Check if daemon routing is active */
939
+ _isDaemonActive() {
940
+ return _useDaemon && isClientConnected();
941
+ }
942
+ };
943
+ }
944
+ async function initDaemonDbClient(fallbackClient) {
945
+ if (process.env.EXE_IS_DAEMON === "1") return null;
946
+ const connected = await connectEmbedDaemon();
947
+ if (!connected) {
948
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
949
+ return null;
950
+ }
951
+ const client = createDaemonDbClient(fallbackClient);
952
+ client._enableDaemon();
953
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
954
+ return client;
955
+ }
956
+ var init_db_daemon_client = __esm({
957
+ "src/lib/db-daemon-client.ts"() {
958
+ "use strict";
959
+ init_exe_daemon_client();
960
+ init_daemon_protocol();
961
+ }
962
+ });
963
+
964
+ // src/lib/database.ts
965
+ var database_exports = {};
966
+ __export(database_exports, {
967
+ disposeDatabase: () => disposeDatabase,
968
+ disposeTurso: () => disposeTurso,
969
+ ensureSchema: () => ensureSchema,
970
+ getClient: () => getClient,
971
+ getRawClient: () => getRawClient,
972
+ initDaemonClient: () => initDaemonClient,
973
+ initDatabase: () => initDatabase,
974
+ initTurso: () => initTurso,
975
+ isInitialized: () => isInitialized
976
+ });
977
+ import { createClient } from "@libsql/client";
978
+ async function initDatabase(config) {
979
+ if (_client) {
980
+ _client.close();
981
+ _client = null;
982
+ _resilientClient = null;
983
+ }
984
+ const opts = {
985
+ url: `file:${config.dbPath}`
986
+ };
987
+ if (config.encryptionKey) {
988
+ opts.encryptionKey = config.encryptionKey;
989
+ }
990
+ _client = createClient(opts);
991
+ _resilientClient = wrapWithRetry(_client);
992
+ }
993
+ function isInitialized() {
994
+ return _client !== null;
995
+ }
996
+ function getClient() {
997
+ if (!_resilientClient) {
998
+ throw new Error("Database client not initialized. Call initDatabase() first.");
999
+ }
1000
+ if (process.env.EXE_IS_DAEMON === "1") {
1001
+ return _resilientClient;
1002
+ }
1003
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1004
+ return _daemonClient;
1005
+ }
1006
+ return _resilientClient;
1007
+ }
1008
+ async function initDaemonClient() {
1009
+ if (process.env.EXE_IS_DAEMON === "1") return;
1010
+ if (!_resilientClient) return;
1011
+ try {
1012
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1013
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1014
+ } catch (err) {
1015
+ process.stderr.write(
1016
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1017
+ `
1018
+ );
1019
+ }
1020
+ }
1021
+ function getRawClient() {
1022
+ if (!_client) {
1023
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1024
+ }
1025
+ return _client;
1026
+ }
1027
+ async function ensureSchema() {
1028
+ const client = getRawClient();
1029
+ await client.execute("PRAGMA journal_mode = WAL");
1030
+ await client.execute("PRAGMA busy_timeout = 30000");
1031
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
1032
+ try {
1033
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
1034
+ } catch {
1035
+ }
1036
+ await client.executeMultiple(`
1037
+ CREATE TABLE IF NOT EXISTS memories (
736
1038
  id TEXT PRIMARY KEY,
737
- text TEXT NOT NULL,
738
- created_at TEXT NOT NULL,
739
- due_date TEXT,
740
- completed_at TEXT
1039
+ agent_id TEXT NOT NULL,
1040
+ agent_role TEXT NOT NULL,
1041
+ session_id TEXT NOT NULL,
1042
+ timestamp TEXT NOT NULL,
1043
+ tool_name TEXT NOT NULL,
1044
+ project_name TEXT NOT NULL,
1045
+ has_error INTEGER NOT NULL DEFAULT 0,
1046
+ raw_text TEXT NOT NULL,
1047
+ vector F32_BLOB(1024),
1048
+ version INTEGER NOT NULL DEFAULT 0
741
1049
  );
1050
+
1051
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
1052
+ ON memories(agent_id);
1053
+
1054
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
1055
+ ON memories(timestamp);
1056
+
1057
+ CREATE INDEX IF NOT EXISTS idx_memories_session
1058
+ ON memories(session_id);
1059
+
1060
+ CREATE INDEX IF NOT EXISTS idx_memories_project
1061
+ ON memories(project_name);
1062
+
1063
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
1064
+ ON memories(tool_name);
1065
+
1066
+ CREATE INDEX IF NOT EXISTS idx_memories_version
1067
+ ON memories(version);
1068
+
1069
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
1070
+ ON memories(agent_id, project_name);
742
1071
  `);
743
1072
  await client.executeMultiple(`
744
- CREATE TABLE IF NOT EXISTS notifications (
745
- id TEXT PRIMARY KEY,
746
- agent_id TEXT NOT NULL,
747
- agent_role TEXT NOT NULL,
748
- event TEXT NOT NULL,
749
- project TEXT NOT NULL,
750
- summary TEXT NOT NULL,
751
- task_file TEXT,
752
- read INTEGER NOT NULL DEFAULT 0,
753
- created_at TEXT NOT NULL
1073
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1074
+ raw_text,
1075
+ content='memories',
1076
+ content_rowid='rowid'
754
1077
  );
755
1078
 
756
- CREATE INDEX IF NOT EXISTS idx_notifications_read
757
- ON notifications(read);
1079
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1080
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1081
+ END;
758
1082
 
759
- CREATE INDEX IF NOT EXISTS idx_notifications_agent
760
- ON notifications(agent_id);
1083
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1084
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1085
+ END;
761
1086
 
762
- CREATE INDEX IF NOT EXISTS idx_notifications_task_file
763
- ON notifications(task_file);
1087
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1088
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1089
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1090
+ END;
764
1091
  `);
765
1092
  await client.executeMultiple(`
766
- CREATE TABLE IF NOT EXISTS schedules (
767
- id TEXT PRIMARY KEY,
768
- cron TEXT NOT NULL,
769
- description TEXT NOT NULL,
770
- job_type TEXT NOT NULL DEFAULT 'report',
771
- prompt TEXT,
772
- assigned_to TEXT,
773
- project_name TEXT,
774
- active INTEGER NOT NULL DEFAULT 1,
775
- use_crontab INTEGER NOT NULL DEFAULT 0,
776
- created_at TEXT NOT NULL
1093
+ CREATE TABLE IF NOT EXISTS sync_meta (
1094
+ key TEXT PRIMARY KEY,
1095
+ value TEXT NOT NULL
777
1096
  );
778
1097
  `);
779
1098
  await client.executeMultiple(`
780
- CREATE TABLE IF NOT EXISTS device_registry (
781
- device_id TEXT PRIMARY KEY,
782
- friendly_name TEXT NOT NULL,
783
- hostname TEXT NOT NULL,
784
- projects TEXT NOT NULL DEFAULT '[]',
785
- agents TEXT NOT NULL DEFAULT '[]',
786
- connected INTEGER DEFAULT 0,
787
- last_seen TEXT NOT NULL
1099
+ CREATE TABLE IF NOT EXISTS tasks (
1100
+ id TEXT PRIMARY KEY,
1101
+ title TEXT NOT NULL,
1102
+ assigned_to TEXT NOT NULL,
1103
+ assigned_by TEXT NOT NULL,
1104
+ project_name TEXT NOT NULL,
1105
+ priority TEXT NOT NULL DEFAULT 'p1',
1106
+ status TEXT NOT NULL DEFAULT 'open',
1107
+ task_file TEXT,
1108
+ created_at TEXT NOT NULL,
1109
+ updated_at TEXT NOT NULL
788
1110
  );
1111
+
1112
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
1113
+ ON tasks(assigned_to, status);
789
1114
  `);
790
1115
  await client.executeMultiple(`
791
- CREATE TABLE IF NOT EXISTS messages (
792
- id TEXT PRIMARY KEY,
793
- from_agent TEXT NOT NULL,
794
- from_device TEXT NOT NULL DEFAULT 'local',
795
- target_agent TEXT NOT NULL,
796
- target_project TEXT,
797
- target_device TEXT NOT NULL DEFAULT 'local',
798
- content TEXT NOT NULL,
799
- priority TEXT DEFAULT 'normal',
800
- status TEXT DEFAULT 'pending',
801
- server_seq INTEGER,
802
- retry_count INTEGER DEFAULT 0,
803
- created_at TEXT NOT NULL,
804
- delivered_at TEXT,
805
- processed_at TEXT,
806
- failed_at TEXT,
807
- failure_reason TEXT
1116
+ CREATE TABLE IF NOT EXISTS behaviors (
1117
+ id TEXT PRIMARY KEY,
1118
+ agent_id TEXT NOT NULL,
1119
+ project_name TEXT,
1120
+ domain TEXT,
1121
+ content TEXT NOT NULL,
1122
+ active INTEGER NOT NULL DEFAULT 1,
1123
+ created_at TEXT NOT NULL,
1124
+ updated_at TEXT NOT NULL
808
1125
  );
809
1126
 
810
- CREATE INDEX IF NOT EXISTS idx_messages_target
811
- ON messages(target_agent, status);
1127
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1128
+ ON behaviors(agent_id, active);
1129
+ `);
1130
+ try {
1131
+ const coordinatorName = getCoordinatorName();
1132
+ const existing = await client.execute({
1133
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1134
+ args: [coordinatorName]
1135
+ });
1136
+ if (Number(existing.rows[0]?.cnt) === 0) {
1137
+ const seededAt = "2026-03-25T00:00:00Z";
1138
+ for (const [domain, content] of [
1139
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1140
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1141
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1142
+ ]) {
1143
+ await client.execute({
1144
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1145
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1146
+ args: [coordinatorName, domain, content, seededAt, seededAt]
1147
+ });
1148
+ }
1149
+ }
1150
+ } catch {
1151
+ }
1152
+ try {
1153
+ await client.execute({
1154
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1155
+ args: []
1156
+ });
1157
+ } catch {
1158
+ }
1159
+ try {
1160
+ await client.execute({
1161
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1162
+ args: []
1163
+ });
1164
+ } catch {
1165
+ }
1166
+ try {
1167
+ await client.execute({
1168
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1169
+ args: []
1170
+ });
1171
+ } catch {
1172
+ }
1173
+ try {
1174
+ await client.execute({
1175
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1176
+ ON tasks(parent_task_id)
1177
+ WHERE parent_task_id IS NOT NULL`,
1178
+ args: []
1179
+ });
1180
+ } catch {
1181
+ }
1182
+ try {
1183
+ await client.execute({
1184
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1185
+ args: []
1186
+ });
1187
+ } catch {
1188
+ }
1189
+ try {
1190
+ await client.execute({
1191
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1192
+ args: []
1193
+ });
1194
+ } catch {
1195
+ }
1196
+ try {
1197
+ await client.execute({
1198
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1199
+ args: []
1200
+ });
1201
+ } catch {
1202
+ }
1203
+ try {
1204
+ await client.execute({
1205
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1206
+ args: []
1207
+ });
1208
+ } catch {
1209
+ }
1210
+ try {
1211
+ await client.execute({
1212
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1213
+ args: []
1214
+ });
1215
+ } catch {
1216
+ }
1217
+ try {
1218
+ await client.execute({
1219
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1220
+ args: []
1221
+ });
1222
+ } catch {
1223
+ }
1224
+ try {
1225
+ await client.execute({
1226
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1227
+ args: []
1228
+ });
1229
+ } catch {
1230
+ }
1231
+ try {
1232
+ await client.execute({
1233
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1234
+ args: []
1235
+ });
1236
+ } catch {
1237
+ }
1238
+ try {
1239
+ await client.execute({
1240
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1241
+ args: []
1242
+ });
1243
+ } catch {
1244
+ }
1245
+ try {
1246
+ await client.execute({
1247
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1248
+ args: []
1249
+ });
1250
+ } catch {
1251
+ }
1252
+ try {
1253
+ await client.execute({
1254
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1255
+ args: []
1256
+ });
1257
+ } catch {
1258
+ }
1259
+ try {
1260
+ await client.execute({
1261
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1262
+ args: []
1263
+ });
1264
+ } catch {
1265
+ }
1266
+ try {
1267
+ await client.execute({
1268
+ sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1269
+ args: []
1270
+ });
1271
+ } catch {
1272
+ }
1273
+ await client.executeMultiple(`
1274
+ CREATE TABLE IF NOT EXISTS consolidations (
1275
+ id TEXT PRIMARY KEY,
1276
+ consolidated_memory_id TEXT NOT NULL,
1277
+ source_memory_id TEXT NOT NULL,
1278
+ created_at TEXT NOT NULL
1279
+ );
1280
+
1281
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
1282
+ ON consolidations(source_memory_id);
1283
+
1284
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1285
+ ON consolidations(consolidated_memory_id);
1286
+ `);
1287
+ await client.executeMultiple(`
1288
+ CREATE TABLE IF NOT EXISTS reminders (
1289
+ id TEXT PRIMARY KEY,
1290
+ text TEXT NOT NULL,
1291
+ created_at TEXT NOT NULL,
1292
+ due_date TEXT,
1293
+ completed_at TEXT
1294
+ );
1295
+ `);
1296
+ await client.executeMultiple(`
1297
+ CREATE TABLE IF NOT EXISTS notifications (
1298
+ id TEXT PRIMARY KEY,
1299
+ agent_id TEXT NOT NULL,
1300
+ agent_role TEXT NOT NULL,
1301
+ event TEXT NOT NULL,
1302
+ project TEXT NOT NULL,
1303
+ summary TEXT NOT NULL,
1304
+ task_file TEXT,
1305
+ read INTEGER NOT NULL DEFAULT 0,
1306
+ created_at TEXT NOT NULL
1307
+ );
1308
+
1309
+ CREATE INDEX IF NOT EXISTS idx_notifications_read
1310
+ ON notifications(read);
1311
+
1312
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent
1313
+ ON notifications(agent_id);
1314
+
1315
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1316
+ ON notifications(task_file);
1317
+ `);
1318
+ await client.executeMultiple(`
1319
+ CREATE TABLE IF NOT EXISTS schedules (
1320
+ id TEXT PRIMARY KEY,
1321
+ cron TEXT NOT NULL,
1322
+ description TEXT NOT NULL,
1323
+ job_type TEXT NOT NULL DEFAULT 'report',
1324
+ prompt TEXT,
1325
+ assigned_to TEXT,
1326
+ project_name TEXT,
1327
+ active INTEGER NOT NULL DEFAULT 1,
1328
+ use_crontab INTEGER NOT NULL DEFAULT 0,
1329
+ created_at TEXT NOT NULL
1330
+ );
1331
+ `);
1332
+ await client.executeMultiple(`
1333
+ CREATE TABLE IF NOT EXISTS device_registry (
1334
+ device_id TEXT PRIMARY KEY,
1335
+ friendly_name TEXT NOT NULL,
1336
+ hostname TEXT NOT NULL,
1337
+ projects TEXT NOT NULL DEFAULT '[]',
1338
+ agents TEXT NOT NULL DEFAULT '[]',
1339
+ connected INTEGER DEFAULT 0,
1340
+ last_seen TEXT NOT NULL
1341
+ );
1342
+ `);
1343
+ await client.executeMultiple(`
1344
+ CREATE TABLE IF NOT EXISTS messages (
1345
+ id TEXT PRIMARY KEY,
1346
+ from_agent TEXT NOT NULL,
1347
+ from_device TEXT NOT NULL DEFAULT 'local',
1348
+ target_agent TEXT NOT NULL,
1349
+ target_project TEXT,
1350
+ target_device TEXT NOT NULL DEFAULT 'local',
1351
+ content TEXT NOT NULL,
1352
+ priority TEXT DEFAULT 'normal',
1353
+ status TEXT DEFAULT 'pending',
1354
+ server_seq INTEGER,
1355
+ retry_count INTEGER DEFAULT 0,
1356
+ created_at TEXT NOT NULL,
1357
+ delivered_at TEXT,
1358
+ processed_at TEXT,
1359
+ failed_at TEXT,
1360
+ failure_reason TEXT
1361
+ );
1362
+
1363
+ CREATE INDEX IF NOT EXISTS idx_messages_target
1364
+ ON messages(target_agent, status);
812
1365
 
813
1366
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
814
1367
  ON messages(target_agent, from_agent, server_seq);
@@ -951,6 +1504,12 @@ async function ensureSchema() {
951
1504
  } catch {
952
1505
  }
953
1506
  }
1507
+ try {
1508
+ await client.execute(
1509
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1510
+ );
1511
+ } catch {
1512
+ }
954
1513
  await client.executeMultiple(`
955
1514
  CREATE TABLE IF NOT EXISTS entities (
956
1515
  id TEXT PRIMARY KEY,
@@ -1003,7 +1562,30 @@ async function ensureSchema() {
1003
1562
  entity_id TEXT NOT NULL,
1004
1563
  PRIMARY KEY (hyperedge_id, entity_id)
1005
1564
  );
1565
+
1566
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1567
+ name,
1568
+ content=entities,
1569
+ content_rowid=rowid
1570
+ );
1571
+
1572
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1573
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1574
+ END;
1575
+
1576
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1577
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1578
+ END;
1579
+
1580
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1581
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1582
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1583
+ END;
1006
1584
  `);
1585
+ try {
1586
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1587
+ } catch {
1588
+ }
1007
1589
  await client.executeMultiple(`
1008
1590
  CREATE TABLE IF NOT EXISTS entity_aliases (
1009
1591
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1184,8 +1766,35 @@ async function ensureSchema() {
1184
1766
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1185
1767
  ON conversations(channel_id);
1186
1768
  `);
1187
- try {
1188
- await client.execute({
1769
+ await client.executeMultiple(`
1770
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1771
+ session_uuid TEXT PRIMARY KEY,
1772
+ agent_id TEXT NOT NULL,
1773
+ session_name TEXT,
1774
+ task_id TEXT,
1775
+ project_name TEXT,
1776
+ started_at TEXT NOT NULL
1777
+ );
1778
+
1779
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1780
+ ON session_agent_map(agent_id);
1781
+ `);
1782
+ try {
1783
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1784
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1785
+ await client.execute({
1786
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1787
+ SELECT session_id, agent_id, '', MIN(timestamp)
1788
+ FROM memories
1789
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1790
+ GROUP BY session_id, agent_id`,
1791
+ args: []
1792
+ });
1793
+ }
1794
+ } catch {
1795
+ }
1796
+ try {
1797
+ await client.execute({
1189
1798
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
1190
1799
  args: []
1191
1800
  });
@@ -1317,15 +1926,41 @@ async function ensureSchema() {
1317
1926
  });
1318
1927
  } catch {
1319
1928
  }
1929
+ for (const col of [
1930
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1931
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1932
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1933
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1934
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1935
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1936
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1937
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1938
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1939
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1940
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1941
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1942
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1943
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1944
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1945
+ ]) {
1946
+ try {
1947
+ await client.execute(col);
1948
+ } catch {
1949
+ }
1950
+ }
1320
1951
  }
1321
1952
  async function disposeDatabase() {
1953
+ if (_daemonClient) {
1954
+ _daemonClient.close();
1955
+ _daemonClient = null;
1956
+ }
1322
1957
  if (_client) {
1323
1958
  _client.close();
1324
1959
  _client = null;
1325
1960
  _resilientClient = null;
1326
1961
  }
1327
1962
  }
1328
- var _client, _resilientClient, initTurso, disposeTurso;
1963
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1329
1964
  var init_database = __esm({
1330
1965
  "src/lib/database.ts"() {
1331
1966
  "use strict";
@@ -1333,6 +1968,7 @@ var init_database = __esm({
1333
1968
  init_employees();
1334
1969
  _client = null;
1335
1970
  _resilientClient = null;
1971
+ _daemonClient = null;
1336
1972
  initTurso = initDatabase;
1337
1973
  disposeTurso = disposeDatabase;
1338
1974
  }
@@ -1348,14 +1984,14 @@ __export(keychain_exports, {
1348
1984
  setMasterKey: () => setMasterKey
1349
1985
  });
1350
1986
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1351
- import { existsSync as existsSync3 } from "fs";
1352
- import path3 from "path";
1987
+ import { existsSync as existsSync4 } from "fs";
1988
+ import path4 from "path";
1353
1989
  import os3 from "os";
1354
1990
  function getKeyDir() {
1355
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
1991
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os3.homedir(), ".exe-os");
1356
1992
  }
1357
1993
  function getKeyPath() {
1358
- return path3.join(getKeyDir(), "master.key");
1994
+ return path4.join(getKeyDir(), "master.key");
1359
1995
  }
1360
1996
  async function tryKeytar() {
1361
1997
  try {
@@ -1376,13 +2012,21 @@ async function getMasterKey() {
1376
2012
  }
1377
2013
  }
1378
2014
  const keyPath = getKeyPath();
1379
- if (!existsSync3(keyPath)) {
2015
+ if (!existsSync4(keyPath)) {
2016
+ process.stderr.write(
2017
+ `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2018
+ `
2019
+ );
1380
2020
  return null;
1381
2021
  }
1382
2022
  try {
1383
2023
  const content = await readFile3(keyPath, "utf-8");
1384
2024
  return Buffer.from(content.trim(), "base64");
1385
- } catch {
2025
+ } catch (err) {
2026
+ process.stderr.write(
2027
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
2028
+ `
2029
+ );
1386
2030
  return null;
1387
2031
  }
1388
2032
  }
@@ -1411,7 +2055,7 @@ async function deleteMasterKey() {
1411
2055
  }
1412
2056
  }
1413
2057
  const keyPath = getKeyPath();
1414
- if (existsSync3(keyPath)) {
2058
+ if (existsSync4(keyPath)) {
1415
2059
  await unlink(keyPath);
1416
2060
  }
1417
2061
  }
@@ -1521,12 +2165,12 @@ __export(shard_manager_exports, {
1521
2165
  listShards: () => listShards,
1522
2166
  shardExists: () => shardExists
1523
2167
  });
1524
- import path4 from "path";
1525
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2168
+ import path5 from "path";
2169
+ import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
1526
2170
  import { createClient as createClient2 } from "@libsql/client";
1527
2171
  function initShardManager(encryptionKey) {
1528
2172
  _encryptionKey = encryptionKey;
1529
- if (!existsSync4(SHARDS_DIR)) {
2173
+ if (!existsSync5(SHARDS_DIR)) {
1530
2174
  mkdirSync(SHARDS_DIR, { recursive: true });
1531
2175
  }
1532
2176
  _shardingEnabled = true;
@@ -1547,7 +2191,7 @@ function getShardClient(projectName) {
1547
2191
  }
1548
2192
  const cached = _shards.get(safeName);
1549
2193
  if (cached) return cached;
1550
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2194
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1551
2195
  const client = createClient2({
1552
2196
  url: `file:${dbPath}`,
1553
2197
  encryptionKey: _encryptionKey
@@ -1557,10 +2201,10 @@ function getShardClient(projectName) {
1557
2201
  }
1558
2202
  function shardExists(projectName) {
1559
2203
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1560
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2204
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1561
2205
  }
1562
2206
  function listShards() {
1563
- if (!existsSync4(SHARDS_DIR)) return [];
2207
+ if (!existsSync5(SHARDS_DIR)) return [];
1564
2208
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1565
2209
  }
1566
2210
  async function ensureShardSchema(client) {
@@ -1746,7 +2390,7 @@ var init_shard_manager = __esm({
1746
2390
  "src/lib/shard-manager.ts"() {
1747
2391
  "use strict";
1748
2392
  init_config();
1749
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2393
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
1750
2394
  _shards = /* @__PURE__ */ new Map();
1751
2395
  _encryptionKey = null;
1752
2396
  _shardingEnabled = false;
@@ -1871,7 +2515,7 @@ __export(global_procedures_exports, {
1871
2515
  loadGlobalProcedures: () => loadGlobalProcedures,
1872
2516
  storeGlobalProcedure: () => storeGlobalProcedure
1873
2517
  });
1874
- import { randomUUID } from "crypto";
2518
+ import { randomUUID as randomUUID2 } from "crypto";
1875
2519
  async function loadGlobalProcedures() {
1876
2520
  const client = getClient();
1877
2521
  const result = await client.execute({
@@ -1900,7 +2544,7 @@ ${sections.join("\n\n")}
1900
2544
  `;
1901
2545
  }
1902
2546
  async function storeGlobalProcedure(input) {
1903
- const id = randomUUID();
2547
+ const id = randomUUID2();
1904
2548
  const now = (/* @__PURE__ */ new Date()).toISOString();
1905
2549
  const client = getClient();
1906
2550
  await client.execute({
@@ -1936,13 +2580,13 @@ ${p.content}`).join("\n\n");
1936
2580
 
1937
2581
  // src/lib/notifications.ts
1938
2582
  import crypto from "crypto";
1939
- import path5 from "path";
2583
+ import path6 from "path";
1940
2584
  import os4 from "os";
1941
2585
  import {
1942
- readFileSync as readFileSync3,
2586
+ readFileSync as readFileSync4,
1943
2587
  readdirSync as readdirSync2,
1944
- unlinkSync as unlinkSync2,
1945
- existsSync as existsSync5,
2588
+ unlinkSync as unlinkSync3,
2589
+ existsSync as existsSync6,
1946
2590
  rmdirSync
1947
2591
  } from "fs";
1948
2592
  async function writeNotification(notification) {
@@ -1977,13 +2621,13 @@ var init_notifications = __esm({
1977
2621
  });
1978
2622
 
1979
2623
  // src/lib/session-registry.ts
1980
- import path6 from "path";
2624
+ import path7 from "path";
1981
2625
  import os5 from "os";
1982
2626
  var REGISTRY_PATH;
1983
2627
  var init_session_registry = __esm({
1984
2628
  "src/lib/session-registry.ts"() {
1985
2629
  "use strict";
1986
- REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
2630
+ REGISTRY_PATH = path7.join(os5.homedir(), ".exe-os", "session-registry.json");
1987
2631
  }
1988
2632
  });
1989
2633
 
@@ -2159,16 +2803,16 @@ var init_provider_table = __esm({
2159
2803
  });
2160
2804
 
2161
2805
  // src/lib/intercom-queue.ts
2162
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
2163
- import path7 from "path";
2806
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
2807
+ import path8 from "path";
2164
2808
  import os6 from "os";
2165
2809
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2166
2810
  var init_intercom_queue = __esm({
2167
2811
  "src/lib/intercom-queue.ts"() {
2168
2812
  "use strict";
2169
- QUEUE_PATH = path7.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2813
+ QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2170
2814
  TTL_MS = 60 * 60 * 1e3;
2171
- INTERCOM_LOG = path7.join(os6.homedir(), ".exe-os", "intercom.log");
2815
+ INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
2172
2816
  }
2173
2817
  });
2174
2818
 
@@ -2179,933 +2823,588 @@ __export(license_exports, {
2179
2823
  PLAN_LIMITS: () => PLAN_LIMITS,
2180
2824
  assertVpsLicense: () => assertVpsLicense,
2181
2825
  checkLicense: () => checkLicense,
2182
- getCachedLicense: () => getCachedLicense,
2183
- isFeatureAllowed: () => isFeatureAllowed,
2184
- loadDeviceId: () => loadDeviceId,
2185
- loadLicense: () => loadLicense,
2186
- mirrorLicenseKey: () => mirrorLicenseKey,
2187
- saveLicense: () => saveLicense,
2188
- startLicenseRevalidation: () => startLicenseRevalidation,
2189
- stopLicenseRevalidation: () => stopLicenseRevalidation,
2190
- validateLicense: () => validateLicense
2191
- });
2192
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2193
- import { randomUUID as randomUUID2 } from "crypto";
2194
- import path8 from "path";
2195
- import { jwtVerify, importSPKI } from "jose";
2196
- async function fetchRetry(url, init) {
2197
- try {
2198
- return await fetch(url, init);
2199
- } catch {
2200
- await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
2201
- return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
2202
- }
2203
- }
2204
- function loadDeviceId() {
2205
- const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
2206
- try {
2207
- if (existsSync7(deviceJsonPath)) {
2208
- const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
2209
- if (data.deviceId) return data.deviceId;
2210
- }
2211
- } catch {
2212
- }
2213
- try {
2214
- if (existsSync7(DEVICE_ID_PATH)) {
2215
- const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
2216
- if (id2) return id2;
2217
- }
2218
- } catch {
2219
- }
2220
- const id = randomUUID2();
2221
- mkdirSync3(EXE_AI_DIR, { recursive: true });
2222
- writeFileSync3(DEVICE_ID_PATH, id, "utf8");
2223
- return id;
2224
- }
2225
- function loadLicense() {
2226
- try {
2227
- if (!existsSync7(LICENSE_PATH)) return null;
2228
- return readFileSync5(LICENSE_PATH, "utf8").trim();
2229
- } catch {
2230
- return null;
2231
- }
2232
- }
2233
- function saveLicense(apiKey) {
2234
- mkdirSync3(EXE_AI_DIR, { recursive: true });
2235
- writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2236
- }
2237
- async function verifyLicenseJwt(token) {
2238
- try {
2239
- const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
2240
- const { payload } = await jwtVerify(token, key, {
2241
- algorithms: [LICENSE_JWT_ALG]
2242
- });
2243
- const plan = payload.plan ?? "free";
2244
- const email = payload.sub ?? "";
2245
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2246
- return {
2247
- valid: true,
2248
- plan,
2249
- email,
2250
- expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
2251
- deviceLimit: limits.devices,
2252
- employeeLimit: limits.employees,
2253
- memoryLimit: limits.memories
2254
- };
2255
- } catch {
2256
- return null;
2257
- }
2258
- }
2259
- async function getCachedLicense() {
2260
- try {
2261
- if (!existsSync7(CACHE_PATH)) return null;
2262
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
2263
- if (!raw.token || typeof raw.token !== "string") return null;
2264
- return await verifyLicenseJwt(raw.token);
2265
- } catch {
2266
- return null;
2267
- }
2268
- }
2269
- function readCachedToken() {
2270
- try {
2271
- if (!existsSync7(CACHE_PATH)) return null;
2272
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
2273
- return typeof raw.token === "string" ? raw.token : null;
2274
- } catch {
2275
- return null;
2276
- }
2277
- }
2278
- function getRawCachedPlan() {
2279
- try {
2280
- const token = readCachedToken();
2281
- if (!token) return null;
2282
- const parts = token.split(".");
2283
- if (parts.length !== 3) return null;
2284
- const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2285
- const plan = payload.plan ?? "free";
2286
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2287
- process.stderr.write(
2288
- `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
2289
- `
2290
- );
2291
- return {
2292
- valid: true,
2293
- plan,
2294
- email: payload.sub ?? "",
2295
- expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
2296
- deviceLimit: limits.devices,
2297
- employeeLimit: limits.employees,
2298
- memoryLimit: limits.memories
2299
- };
2300
- } catch {
2301
- return null;
2302
- }
2303
- }
2304
- function cacheResponse(token) {
2305
- try {
2306
- writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
2307
- } catch {
2308
- }
2309
- }
2310
- async function validateLicense(apiKey, deviceId) {
2311
- const did = deviceId ?? loadDeviceId();
2312
- try {
2313
- const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2314
- method: "POST",
2315
- headers: { "Content-Type": "application/json" },
2316
- body: JSON.stringify({ apiKey, deviceId: did }),
2317
- signal: AbortSignal.timeout(1e4)
2318
- });
2319
- if (res.ok) {
2320
- const data = await res.json();
2321
- if (data.error === "device_limit_exceeded") {
2322
- const cached2 = await getCachedLicense();
2323
- if (cached2) return cached2;
2324
- const raw2 = getRawCachedPlan();
2325
- if (raw2) return { ...raw2, valid: false };
2326
- return { ...FREE_LICENSE, valid: false, plan: "free" };
2327
- }
2328
- if (data.token) {
2329
- cacheResponse(data.token);
2330
- const verified = await verifyLicenseJwt(data.token);
2331
- if (verified) return verified;
2332
- }
2333
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
2334
- return {
2335
- valid: data.valid,
2336
- plan: data.plan,
2337
- email: data.email,
2338
- expiresAt: data.expiresAt,
2339
- deviceLimit: limits.devices,
2340
- employeeLimit: limits.employees,
2341
- memoryLimit: limits.memories
2342
- };
2343
- }
2344
- const cached = await getCachedLicense();
2345
- if (cached) return cached;
2346
- const raw = getRawCachedPlan();
2347
- if (raw) return raw;
2348
- return { ...FREE_LICENSE, valid: false, plan: "free" };
2349
- } catch {
2350
- const cached = await getCachedLicense();
2351
- if (cached) return cached;
2352
- const rawFallback = getRawCachedPlan();
2353
- if (rawFallback) return rawFallback;
2354
- return { ...FREE_LICENSE, valid: false, error: "offline" };
2355
- }
2356
- }
2357
- function getCacheAgeMs() {
2358
- try {
2359
- const { statSync: statSync2 } = __require("fs");
2360
- const s = statSync2(CACHE_PATH);
2361
- return Date.now() - s.mtimeMs;
2362
- } catch {
2363
- return Infinity;
2364
- }
2365
- }
2366
- async function checkLicense() {
2367
- let key = loadLicense();
2368
- if (!key) {
2369
- try {
2370
- const configPath = path8.join(EXE_AI_DIR, "config.json");
2371
- if (existsSync7(configPath)) {
2372
- const raw = JSON.parse(readFileSync5(configPath, "utf8"));
2373
- const cloud = raw.cloud;
2374
- if (cloud?.apiKey) {
2375
- key = cloud.apiKey;
2376
- saveLicense(key);
2377
- }
2378
- }
2379
- } catch {
2380
- }
2381
- }
2382
- if (!key) return FREE_LICENSE;
2383
- const cached = await getCachedLicense();
2384
- if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
2385
- const deviceId = loadDeviceId();
2386
- return validateLicense(key, deviceId);
2387
- }
2388
- function isFeatureAllowed(license, feature) {
2389
- switch (feature) {
2390
- case "cloud_sync":
2391
- case "external_agents":
2392
- case "wiki":
2393
- return license.plan !== "free";
2394
- case "unlimited_employees":
2395
- return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
2396
- }
2397
- }
2398
- function mirrorLicenseKey(apiKey) {
2399
- const trimmed = apiKey.trim();
2400
- if (!trimmed) return;
2401
- saveLicense(trimmed);
2402
- }
2403
- async function assertVpsLicense(opts) {
2404
- const env = opts?.env ?? process.env;
2405
- const inProduction = env.NODE_ENV === "production";
2406
- if (!opts?.force && !inProduction) {
2407
- return { ...FREE_LICENSE, plan: "free" };
2408
- }
2409
- const envKey = env.EXE_LICENSE_KEY?.trim();
2410
- if (envKey) {
2411
- saveLicense(envKey);
2412
- }
2413
- const apiKey = envKey ?? loadLicense();
2414
- if (!apiKey) {
2415
- throw new Error(
2416
- "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
2417
- );
2418
- }
2419
- const deviceId = loadDeviceId();
2420
- let backendResponse = null;
2421
- let explicitRejection = false;
2422
- let transientFailure = false;
2423
- try {
2424
- const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2425
- method: "POST",
2426
- headers: { "Content-Type": "application/json" },
2427
- body: JSON.stringify({ apiKey, deviceId }),
2428
- signal: AbortSignal.timeout(1e4)
2429
- });
2430
- if (res.ok) {
2431
- backendResponse = await res.json();
2432
- if (!backendResponse.valid) explicitRejection = true;
2433
- } else if (res.status === 401 || res.status === 403) {
2434
- explicitRejection = true;
2435
- } else {
2436
- transientFailure = true;
2437
- }
2438
- } catch {
2439
- transientFailure = true;
2440
- }
2441
- if (backendResponse?.valid) {
2442
- if (backendResponse.token) {
2443
- cacheResponse(backendResponse.token);
2444
- const verified = await verifyLicenseJwt(backendResponse.token);
2445
- if (verified) return verified;
2446
- }
2447
- const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
2448
- return {
2449
- valid: true,
2450
- plan: backendResponse.plan,
2451
- email: backendResponse.email,
2452
- expiresAt: backendResponse.expiresAt,
2453
- deviceLimit: limits.devices,
2454
- employeeLimit: limits.employees,
2455
- memoryLimit: limits.memories
2456
- };
2457
- }
2458
- if (explicitRejection) {
2459
- throw new Error(
2460
- `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
2461
- );
2462
- }
2463
- if (!transientFailure) {
2464
- throw new Error(
2465
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
2466
- );
2826
+ getCachedLicense: () => getCachedLicense,
2827
+ isFeatureAllowed: () => isFeatureAllowed,
2828
+ loadDeviceId: () => loadDeviceId,
2829
+ loadLicense: () => loadLicense,
2830
+ mirrorLicenseKey: () => mirrorLicenseKey,
2831
+ saveLicense: () => saveLicense,
2832
+ startLicenseRevalidation: () => startLicenseRevalidation,
2833
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
2834
+ validateLicense: () => validateLicense
2835
+ });
2836
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
2837
+ import { randomUUID as randomUUID3 } from "crypto";
2838
+ import path9 from "path";
2839
+ import { jwtVerify, importSPKI } from "jose";
2840
+ async function fetchRetry(url, init) {
2841
+ try {
2842
+ return await fetch(url, init);
2843
+ } catch {
2844
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
2845
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
2467
2846
  }
2468
- const fresh = await getCachedLicense();
2469
- if (fresh && fresh.valid) return fresh;
2470
- const graceDays = opts?.offlineGraceDays ?? 7;
2471
- const graceMs = graceDays * 24 * 60 * 60 * 1e3;
2847
+ }
2848
+ function loadDeviceId() {
2849
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
2472
2850
  try {
2473
- const token = readCachedToken();
2474
- if (token) {
2475
- const payloadB64 = token.split(".")[1];
2476
- if (payloadB64) {
2477
- const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
2478
- const expMs = (payload.exp ?? 0) * 1e3;
2479
- if (Date.now() < expMs + graceMs) {
2480
- const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
2481
- const { payload: verified } = await jwtVerify(token, key, {
2482
- algorithms: [LICENSE_JWT_ALG],
2483
- clockTolerance: graceDays * 24 * 60 * 60
2484
- });
2485
- const plan = verified.plan ?? "free";
2486
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2487
- return {
2488
- valid: true,
2489
- plan,
2490
- email: verified.sub ?? "",
2491
- expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
2492
- deviceLimit: limits.devices,
2493
- employeeLimit: limits.employees,
2494
- memoryLimit: limits.memories
2495
- };
2496
- }
2497
- }
2851
+ if (existsSync8(deviceJsonPath)) {
2852
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
2853
+ if (data.deviceId) return data.deviceId;
2498
2854
  }
2499
2855
  } catch {
2500
2856
  }
2501
- throw new Error(
2502
- `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
2503
- );
2504
- }
2505
- function startLicenseRevalidation(intervalMs = 36e5) {
2506
- if (_revalTimer) return;
2507
- _revalTimer = setInterval(async () => {
2508
- try {
2509
- const license = await checkLicense();
2510
- if (!license.valid) {
2511
- process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
2512
- }
2513
- } catch {
2857
+ try {
2858
+ if (existsSync8(DEVICE_ID_PATH)) {
2859
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
2860
+ if (id2) return id2;
2514
2861
  }
2515
- }, intervalMs);
2516
- if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
2517
- _revalTimer.unref();
2862
+ } catch {
2518
2863
  }
2864
+ const id = randomUUID3();
2865
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
2866
+ writeFileSync3(DEVICE_ID_PATH, id, "utf8");
2867
+ return id;
2519
2868
  }
2520
- function stopLicenseRevalidation() {
2521
- if (_revalTimer) {
2522
- clearInterval(_revalTimer);
2523
- _revalTimer = null;
2869
+ function loadLicense() {
2870
+ try {
2871
+ if (!existsSync8(LICENSE_PATH)) return null;
2872
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
2873
+ } catch {
2874
+ return null;
2524
2875
  }
2525
2876
  }
2526
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
2527
- var init_license = __esm({
2528
- "src/lib/license.ts"() {
2529
- "use strict";
2530
- init_config();
2531
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2532
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2533
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2534
- API_BASE = "https://askexe.com/cloud";
2535
- RETRY_DELAY_MS = 500;
2536
- LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
2537
- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2538
- 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
2539
- -----END PUBLIC KEY-----`;
2540
- LICENSE_JWT_ALG = "ES256";
2541
- PLAN_LIMITS = {
2542
- free: { devices: 1, employees: 1, memories: 5e4 },
2543
- pro: { devices: 2, employees: 5, memories: 25e4 },
2544
- team: { devices: 10, employees: 20, memories: 1e6 },
2545
- agency: { devices: 50, employees: 100, memories: 1e7 },
2546
- enterprise: { devices: -1, employees: -1, memories: -1 }
2547
- };
2548
- FREE_LICENSE = {
2549
- valid: true,
2550
- plan: "free",
2551
- email: "",
2552
- expiresAt: null,
2553
- deviceLimit: 1,
2554
- employeeLimit: 1,
2555
- memoryLimit: 5e4
2556
- };
2557
- CACHE_MAX_AGE_MS = 36e5;
2558
- _revalTimer = null;
2559
- }
2560
- });
2561
-
2562
- // src/lib/plan-limits.ts
2563
- var plan_limits_exports = {};
2564
- __export(plan_limits_exports, {
2565
- PlanLimitError: () => PlanLimitError,
2566
- assertEmployeeLimit: () => assertEmployeeLimit,
2567
- assertEmployeeLimitSync: () => assertEmployeeLimitSync,
2568
- assertFeature: () => assertFeature,
2569
- assertMemoryLimit: () => assertMemoryLimit,
2570
- countActiveMemories: () => countActiveMemories,
2571
- getLicenseSync: () => getLicenseSync
2572
- });
2573
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
2574
- import path9 from "path";
2575
- function getLicenseSync() {
2877
+ function saveLicense(apiKey) {
2878
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
2879
+ writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2880
+ }
2881
+ async function verifyLicenseJwt(token) {
2576
2882
  try {
2577
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2578
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2579
- if (!raw.token || typeof raw.token !== "string") return freeLicense();
2580
- const parts = raw.token.split(".");
2581
- if (parts.length !== 3) return freeLicense();
2582
- const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2883
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
2884
+ const { payload } = await jwtVerify(token, key, {
2885
+ algorithms: [LICENSE_JWT_ALG]
2886
+ });
2583
2887
  const plan = payload.plan ?? "free";
2888
+ const email = payload.sub ?? "";
2584
2889
  const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2585
2890
  return {
2586
2891
  valid: true,
2587
2892
  plan,
2588
- email: payload.sub ?? "",
2893
+ email,
2589
2894
  expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
2590
2895
  deviceLimit: limits.devices,
2591
2896
  employeeLimit: limits.employees,
2592
2897
  memoryLimit: limits.memories
2593
2898
  };
2594
2899
  } catch {
2595
- return freeLicense();
2596
- }
2597
- }
2598
- function freeLicense() {
2599
- const limits = PLAN_LIMITS.free;
2600
- return {
2601
- valid: true,
2602
- plan: "free",
2603
- email: "",
2604
- expiresAt: null,
2605
- deviceLimit: limits.devices,
2606
- employeeLimit: limits.employees,
2607
- memoryLimit: limits.memories
2608
- };
2609
- }
2610
- async function countActiveMemories() {
2611
- if (!isInitialized()) return 0;
2612
- const client = getClient();
2613
- const result = await client.execute(
2614
- "SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
2615
- );
2616
- const row = result.rows[0];
2617
- return Number(row?.cnt ?? 0);
2618
- }
2619
- async function assertMemoryLimit() {
2620
- const license = await checkLicense();
2621
- if (license.memoryLimit < 0) return;
2622
- const count = await countActiveMemories();
2623
- if (count >= license.memoryLimit) {
2624
- throw new PlanLimitError(
2625
- `Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
2626
- );
2627
- }
2628
- }
2629
- async function assertEmployeeLimit(license, rosterPath) {
2630
- const lic = license ?? await checkLicense();
2631
- if (lic.employeeLimit < 0) return;
2632
- const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
2633
- if (employees.length >= lic.employeeLimit) {
2634
- throw new PlanLimitError(
2635
- `Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
2636
- );
2900
+ return null;
2637
2901
  }
2638
2902
  }
2639
- function assertEmployeeLimitSync(rosterPath) {
2640
- const license = getLicenseSync();
2641
- if (license.employeeLimit < 0) return;
2642
- const filePath = rosterPath ?? EMPLOYEES_PATH;
2643
- let count = 0;
2903
+ async function getCachedLicense() {
2644
2904
  try {
2645
- if (existsSync8(filePath)) {
2646
- const raw = readFileSync6(filePath, "utf8");
2647
- const employees = JSON.parse(raw);
2648
- count = Array.isArray(employees) ? employees.length : 0;
2649
- }
2905
+ if (!existsSync8(CACHE_PATH)) return null;
2906
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
2907
+ if (!raw.token || typeof raw.token !== "string") return null;
2908
+ return await verifyLicenseJwt(raw.token);
2650
2909
  } catch {
2651
- throw new PlanLimitError(
2652
- `Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
2653
- );
2654
- }
2655
- if (count >= license.employeeLimit) {
2656
- throw new PlanLimitError(
2657
- `Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
2658
- );
2659
- }
2660
- }
2661
- async function assertFeature(feature) {
2662
- const license = await checkLicense();
2663
- if (!isFeatureAllowed(license, feature)) {
2664
- throw new PlanLimitError(
2665
- `Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
2666
- );
2910
+ return null;
2667
2911
  }
2668
2912
  }
2669
- var PlanLimitError, CACHE_PATH2;
2670
- var init_plan_limits = __esm({
2671
- "src/lib/plan-limits.ts"() {
2672
- "use strict";
2673
- init_database();
2674
- init_employees();
2675
- init_license();
2676
- init_config();
2677
- PlanLimitError = class extends Error {
2678
- constructor(message) {
2679
- super(message);
2680
- this.name = "PlanLimitError";
2681
- }
2682
- };
2683
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2913
+ function readCachedToken() {
2914
+ try {
2915
+ if (!existsSync8(CACHE_PATH)) return null;
2916
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
2917
+ return typeof raw.token === "string" ? raw.token : null;
2918
+ } catch {
2919
+ return null;
2684
2920
  }
2685
- });
2686
-
2687
- // src/lib/tmux-routing.ts
2688
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9, appendFileSync } from "fs";
2689
- import path10 from "path";
2690
- import os7 from "os";
2691
- import { fileURLToPath } from "url";
2692
- function getMySession() {
2693
- return getTransport().getMySession();
2694
- }
2695
- function extractRootExe(name) {
2696
- if (!name) return null;
2697
- if (!name.includes("-")) return name;
2698
- const parts = name.split("-").filter(Boolean);
2699
- return parts.length > 0 ? parts[parts.length - 1] : null;
2700
2921
  }
2701
- function getParentExe(sessionKey) {
2922
+ function getRawCachedPlan() {
2702
2923
  try {
2703
- const data = JSON.parse(readFileSync7(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2704
- return data.parentExe || null;
2924
+ const token = readCachedToken();
2925
+ if (!token) return null;
2926
+ const parts = token.split(".");
2927
+ if (parts.length !== 3) return null;
2928
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2929
+ const plan = payload.plan ?? "free";
2930
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2931
+ process.stderr.write(
2932
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
2933
+ `
2934
+ );
2935
+ return {
2936
+ valid: true,
2937
+ plan,
2938
+ email: payload.sub ?? "",
2939
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
2940
+ deviceLimit: limits.devices,
2941
+ employeeLimit: limits.employees,
2942
+ memoryLimit: limits.memories
2943
+ };
2705
2944
  } catch {
2706
2945
  return null;
2707
2946
  }
2708
2947
  }
2709
- function resolveExeSession() {
2710
- const mySession = getMySession();
2711
- if (!mySession) return null;
2948
+ function cacheResponse(token) {
2712
2949
  try {
2713
- const key = getSessionKey();
2714
- const parentExe = getParentExe(key);
2715
- if (parentExe) {
2716
- return extractRootExe(parentExe) ?? parentExe;
2717
- }
2950
+ writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
2718
2951
  } catch {
2719
2952
  }
2720
- return extractRootExe(mySession) ?? mySession;
2721
2953
  }
2722
- var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
2723
- var init_tmux_routing = __esm({
2724
- "src/lib/tmux-routing.ts"() {
2725
- "use strict";
2726
- init_session_registry();
2727
- init_session_key();
2728
- init_transport();
2729
- init_cc_agent_support();
2730
- init_mcp_prefix();
2731
- init_provider_table();
2732
- init_intercom_queue();
2733
- init_plan_limits();
2734
- init_employees();
2735
- SPAWN_LOCK_DIR = path10.join(os7.homedir(), ".exe-os", "spawn-locks");
2736
- SESSION_CACHE = path10.join(os7.homedir(), ".exe-os", "session-cache");
2737
- INTERCOM_LOG2 = path10.join(os7.homedir(), ".exe-os", "intercom.log");
2738
- DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
2739
- DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2740
- }
2741
- });
2742
-
2743
- // src/lib/task-scope.ts
2744
- function getCurrentSessionScope() {
2954
+ async function validateLicense(apiKey, deviceId) {
2955
+ const did = deviceId ?? loadDeviceId();
2745
2956
  try {
2746
- return resolveExeSession();
2957
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2958
+ method: "POST",
2959
+ headers: { "Content-Type": "application/json" },
2960
+ body: JSON.stringify({ apiKey, deviceId: did }),
2961
+ signal: AbortSignal.timeout(1e4)
2962
+ });
2963
+ if (res.ok) {
2964
+ const data = await res.json();
2965
+ if (data.error === "device_limit_exceeded") {
2966
+ const cached2 = await getCachedLicense();
2967
+ if (cached2) return cached2;
2968
+ const raw2 = getRawCachedPlan();
2969
+ if (raw2) return { ...raw2, valid: false };
2970
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
2971
+ }
2972
+ if (data.token) {
2973
+ cacheResponse(data.token);
2974
+ const verified = await verifyLicenseJwt(data.token);
2975
+ if (verified) return verified;
2976
+ }
2977
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
2978
+ return {
2979
+ valid: data.valid,
2980
+ plan: data.plan,
2981
+ email: data.email,
2982
+ expiresAt: data.expiresAt,
2983
+ deviceLimit: limits.devices,
2984
+ employeeLimit: limits.employees,
2985
+ memoryLimit: limits.memories
2986
+ };
2987
+ }
2988
+ const cached = await getCachedLicense();
2989
+ if (cached) return cached;
2990
+ const raw = getRawCachedPlan();
2991
+ if (raw) return raw;
2992
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
2747
2993
  } catch {
2748
- return null;
2994
+ const cached = await getCachedLicense();
2995
+ if (cached) return cached;
2996
+ const rawFallback = getRawCachedPlan();
2997
+ if (rawFallback) return rawFallback;
2998
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
2749
2999
  }
2750
3000
  }
2751
- function sessionScopeFilter(sessionScope, tableAlias) {
2752
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2753
- if (!scope) return { sql: "", args: [] };
2754
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2755
- return {
2756
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2757
- args: [scope]
2758
- };
2759
- }
2760
- var init_task_scope = __esm({
2761
- "src/lib/task-scope.ts"() {
2762
- "use strict";
2763
- init_tmux_routing();
2764
- }
2765
- });
2766
-
2767
- // src/lib/exe-daemon-client.ts
2768
- import net from "net";
2769
- import { spawn } from "child_process";
2770
- import { randomUUID as randomUUID3 } from "crypto";
2771
- import { existsSync as existsSync10, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
2772
- import path11 from "path";
2773
- import { fileURLToPath as fileURLToPath2 } from "url";
2774
- function handleData(chunk) {
2775
- _buffer += chunk.toString();
2776
- if (_buffer.length > MAX_BUFFER) {
2777
- _buffer = "";
2778
- return;
2779
- }
2780
- let newlineIdx;
2781
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
2782
- const line = _buffer.slice(0, newlineIdx).trim();
2783
- _buffer = _buffer.slice(newlineIdx + 1);
2784
- if (!line) continue;
2785
- try {
2786
- const response = JSON.parse(line);
2787
- const entry = _pending.get(response.id);
2788
- if (entry) {
2789
- clearTimeout(entry.timer);
2790
- _pending.delete(response.id);
2791
- entry.resolve(response);
2792
- }
2793
- } catch {
2794
- }
3001
+ function getCacheAgeMs() {
3002
+ try {
3003
+ const { statSync: statSync2 } = __require("fs");
3004
+ const s = statSync2(CACHE_PATH);
3005
+ return Date.now() - s.mtimeMs;
3006
+ } catch {
3007
+ return Infinity;
2795
3008
  }
2796
3009
  }
2797
- function cleanupStaleFiles() {
2798
- if (existsSync10(PID_PATH)) {
3010
+ async function checkLicense() {
3011
+ let key = loadLicense();
3012
+ if (!key) {
2799
3013
  try {
2800
- const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
2801
- if (pid > 0) {
2802
- try {
2803
- process.kill(pid, 0);
2804
- return;
2805
- } catch {
3014
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
3015
+ if (existsSync8(configPath)) {
3016
+ const raw = JSON.parse(readFileSync6(configPath, "utf8"));
3017
+ const cloud = raw.cloud;
3018
+ if (cloud?.apiKey) {
3019
+ key = cloud.apiKey;
3020
+ saveLicense(key);
2806
3021
  }
2807
3022
  }
2808
3023
  } catch {
2809
3024
  }
2810
- try {
2811
- unlinkSync3(PID_PATH);
2812
- } catch {
2813
- }
2814
- try {
2815
- unlinkSync3(SOCKET_PATH);
2816
- } catch {
2817
- }
2818
3025
  }
3026
+ if (!key) return FREE_LICENSE;
3027
+ const cached = await getCachedLicense();
3028
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
3029
+ const deviceId = loadDeviceId();
3030
+ return validateLicense(key, deviceId);
2819
3031
  }
2820
- function findPackageRoot() {
2821
- let dir = path11.dirname(fileURLToPath2(import.meta.url));
2822
- const { root } = path11.parse(dir);
2823
- while (dir !== root) {
2824
- if (existsSync10(path11.join(dir, "package.json"))) return dir;
2825
- dir = path11.dirname(dir);
3032
+ function isFeatureAllowed(license, feature) {
3033
+ switch (feature) {
3034
+ case "cloud_sync":
3035
+ case "external_agents":
3036
+ case "wiki":
3037
+ return license.plan !== "free";
3038
+ case "unlimited_employees":
3039
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
2826
3040
  }
2827
- return null;
2828
3041
  }
2829
- function spawnDaemon() {
2830
- const pkgRoot = findPackageRoot();
2831
- if (!pkgRoot) {
2832
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
2833
- return;
3042
+ function mirrorLicenseKey(apiKey) {
3043
+ const trimmed = apiKey.trim();
3044
+ if (!trimmed) return;
3045
+ saveLicense(trimmed);
3046
+ }
3047
+ async function assertVpsLicense(opts) {
3048
+ const env = opts?.env ?? process.env;
3049
+ const inProduction = env.NODE_ENV === "production";
3050
+ if (!opts?.force && !inProduction) {
3051
+ return { ...FREE_LICENSE, plan: "free" };
2834
3052
  }
2835
- const daemonPath = path11.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2836
- if (!existsSync10(daemonPath)) {
2837
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2838
- `);
2839
- return;
3053
+ const envKey = env.EXE_LICENSE_KEY?.trim();
3054
+ if (envKey) {
3055
+ saveLicense(envKey);
2840
3056
  }
2841
- const resolvedPath = daemonPath;
2842
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2843
- `);
2844
- const logPath = path11.join(path11.dirname(SOCKET_PATH), "exed.log");
2845
- let stderrFd = "ignore";
3057
+ const apiKey = envKey ?? loadLicense();
3058
+ if (!apiKey) {
3059
+ throw new Error(
3060
+ "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
3061
+ );
3062
+ }
3063
+ const deviceId = loadDeviceId();
3064
+ let backendResponse = null;
3065
+ let explicitRejection = false;
3066
+ let transientFailure = false;
2846
3067
  try {
2847
- stderrFd = openSync(logPath, "a");
3068
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3069
+ method: "POST",
3070
+ headers: { "Content-Type": "application/json" },
3071
+ body: JSON.stringify({ apiKey, deviceId }),
3072
+ signal: AbortSignal.timeout(1e4)
3073
+ });
3074
+ if (res.ok) {
3075
+ backendResponse = await res.json();
3076
+ if (!backendResponse.valid) explicitRejection = true;
3077
+ } else if (res.status === 401 || res.status === 403) {
3078
+ explicitRejection = true;
3079
+ } else {
3080
+ transientFailure = true;
3081
+ }
2848
3082
  } catch {
3083
+ transientFailure = true;
2849
3084
  }
2850
- const child = spawn(process.execPath, [resolvedPath], {
2851
- detached: true,
2852
- stdio: ["ignore", "ignore", stderrFd],
2853
- env: {
2854
- ...process.env,
2855
- TMUX: void 0,
2856
- // Daemon is global — must not inherit session scope
2857
- TMUX_PANE: void 0,
2858
- // Prevents resolveExeSession() from scoping to one session
2859
- EXE_DAEMON_SOCK: SOCKET_PATH,
2860
- EXE_DAEMON_PID: PID_PATH
2861
- }
2862
- });
2863
- child.unref();
2864
- if (typeof stderrFd === "number") {
2865
- try {
2866
- closeSync(stderrFd);
2867
- } catch {
3085
+ if (backendResponse?.valid) {
3086
+ if (backendResponse.token) {
3087
+ cacheResponse(backendResponse.token);
3088
+ const verified = await verifyLicenseJwt(backendResponse.token);
3089
+ if (verified) return verified;
2868
3090
  }
3091
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
3092
+ return {
3093
+ valid: true,
3094
+ plan: backendResponse.plan,
3095
+ email: backendResponse.email,
3096
+ expiresAt: backendResponse.expiresAt,
3097
+ deviceLimit: limits.devices,
3098
+ employeeLimit: limits.employees,
3099
+ memoryLimit: limits.memories
3100
+ };
3101
+ }
3102
+ if (explicitRejection) {
3103
+ throw new Error(
3104
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
3105
+ );
2869
3106
  }
2870
- }
2871
- function acquireSpawnLock() {
3107
+ if (!transientFailure) {
3108
+ throw new Error(
3109
+ "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
3110
+ );
3111
+ }
3112
+ const fresh = await getCachedLicense();
3113
+ if (fresh && fresh.valid) return fresh;
3114
+ const graceDays = opts?.offlineGraceDays ?? 7;
3115
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
2872
3116
  try {
2873
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
2874
- closeSync(fd);
2875
- return true;
3117
+ const token = readCachedToken();
3118
+ if (token) {
3119
+ const payloadB64 = token.split(".")[1];
3120
+ if (payloadB64) {
3121
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
3122
+ const expMs = (payload.exp ?? 0) * 1e3;
3123
+ if (Date.now() < expMs + graceMs) {
3124
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
3125
+ const { payload: verified } = await jwtVerify(token, key, {
3126
+ algorithms: [LICENSE_JWT_ALG],
3127
+ clockTolerance: graceDays * 24 * 60 * 60
3128
+ });
3129
+ const plan = verified.plan ?? "free";
3130
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3131
+ return {
3132
+ valid: true,
3133
+ plan,
3134
+ email: verified.sub ?? "",
3135
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
3136
+ deviceLimit: limits.devices,
3137
+ employeeLimit: limits.employees,
3138
+ memoryLimit: limits.memories
3139
+ };
3140
+ }
3141
+ }
3142
+ }
2876
3143
  } catch {
3144
+ }
3145
+ throw new Error(
3146
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
3147
+ );
3148
+ }
3149
+ function startLicenseRevalidation(intervalMs = 36e5) {
3150
+ if (_revalTimer) return;
3151
+ _revalTimer = setInterval(async () => {
2877
3152
  try {
2878
- const stat = statSync(SPAWN_LOCK_PATH);
2879
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
2880
- try {
2881
- unlinkSync3(SPAWN_LOCK_PATH);
2882
- } catch {
2883
- }
2884
- try {
2885
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
2886
- closeSync(fd);
2887
- return true;
2888
- } catch {
2889
- }
3153
+ const license = await checkLicense();
3154
+ if (!license.valid) {
3155
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
2890
3156
  }
2891
3157
  } catch {
2892
3158
  }
2893
- return false;
3159
+ }, intervalMs);
3160
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
3161
+ _revalTimer.unref();
2894
3162
  }
2895
3163
  }
2896
- function releaseSpawnLock() {
2897
- try {
2898
- unlinkSync3(SPAWN_LOCK_PATH);
2899
- } catch {
3164
+ function stopLicenseRevalidation() {
3165
+ if (_revalTimer) {
3166
+ clearInterval(_revalTimer);
3167
+ _revalTimer = null;
2900
3168
  }
2901
3169
  }
2902
- function connectToSocket() {
2903
- return new Promise((resolve) => {
2904
- if (_socket && _connected) {
2905
- resolve(true);
2906
- return;
2907
- }
2908
- const socket = net.createConnection({ path: SOCKET_PATH });
2909
- const connectTimeout = setTimeout(() => {
2910
- socket.destroy();
2911
- resolve(false);
2912
- }, 2e3);
2913
- socket.on("connect", () => {
2914
- clearTimeout(connectTimeout);
2915
- _socket = socket;
2916
- _connected = true;
2917
- _buffer = "";
2918
- socket.on("data", handleData);
2919
- socket.on("close", () => {
2920
- _connected = false;
2921
- _socket = null;
2922
- for (const [id, entry] of _pending) {
2923
- clearTimeout(entry.timer);
2924
- _pending.delete(id);
2925
- entry.resolve({ error: "Connection closed" });
2926
- }
2927
- });
2928
- socket.on("error", () => {
2929
- _connected = false;
2930
- _socket = null;
2931
- });
2932
- resolve(true);
2933
- });
2934
- socket.on("error", () => {
2935
- clearTimeout(connectTimeout);
2936
- resolve(false);
2937
- });
2938
- });
2939
- }
2940
- async function connectEmbedDaemon() {
2941
- if (_socket && _connected) return true;
2942
- if (await connectToSocket()) return true;
2943
- if (acquireSpawnLock()) {
2944
- try {
2945
- cleanupStaleFiles();
2946
- spawnDaemon();
2947
- } finally {
2948
- releaseSpawnLock();
2949
- }
3170
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
3171
+ var init_license = __esm({
3172
+ "src/lib/license.ts"() {
3173
+ "use strict";
3174
+ init_config();
3175
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3176
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3177
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3178
+ API_BASE = "https://askexe.com/cloud";
3179
+ RETRY_DELAY_MS = 500;
3180
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
3181
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3182
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
3183
+ -----END PUBLIC KEY-----`;
3184
+ LICENSE_JWT_ALG = "ES256";
3185
+ PLAN_LIMITS = {
3186
+ free: { devices: 1, employees: 1, memories: 5e3 },
3187
+ pro: { devices: 3, employees: 5, memories: 1e5 },
3188
+ team: { devices: 10, employees: 20, memories: 1e6 },
3189
+ agency: { devices: 50, employees: 100, memories: 1e7 },
3190
+ enterprise: { devices: -1, employees: -1, memories: -1 }
3191
+ };
3192
+ FREE_LICENSE = {
3193
+ valid: true,
3194
+ plan: "free",
3195
+ email: "",
3196
+ expiresAt: null,
3197
+ deviceLimit: 1,
3198
+ employeeLimit: 1,
3199
+ memoryLimit: 5e3
3200
+ };
3201
+ CACHE_MAX_AGE_MS = 36e5;
3202
+ _revalTimer = null;
2950
3203
  }
2951
- const start = Date.now();
2952
- let delay2 = 100;
2953
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2954
- await new Promise((r) => setTimeout(r, delay2));
2955
- if (await connectToSocket()) return true;
2956
- delay2 = Math.min(delay2 * 2, 3e3);
3204
+ });
3205
+
3206
+ // src/lib/plan-limits.ts
3207
+ var plan_limits_exports = {};
3208
+ __export(plan_limits_exports, {
3209
+ PlanLimitError: () => PlanLimitError,
3210
+ assertEmployeeLimit: () => assertEmployeeLimit,
3211
+ assertEmployeeLimitSync: () => assertEmployeeLimitSync,
3212
+ assertFeature: () => assertFeature,
3213
+ assertMemoryLimit: () => assertMemoryLimit,
3214
+ countActiveMemories: () => countActiveMemories,
3215
+ getLicenseSync: () => getLicenseSync
3216
+ });
3217
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
3218
+ import path10 from "path";
3219
+ function getLicenseSync() {
3220
+ try {
3221
+ if (!existsSync9(CACHE_PATH2)) return freeLicense();
3222
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
3223
+ if (!raw.token || typeof raw.token !== "string") return freeLicense();
3224
+ const parts = raw.token.split(".");
3225
+ if (parts.length !== 3) return freeLicense();
3226
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3227
+ const plan = payload.plan ?? "free";
3228
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3229
+ return {
3230
+ valid: true,
3231
+ plan,
3232
+ email: payload.sub ?? "",
3233
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3234
+ deviceLimit: limits.devices,
3235
+ employeeLimit: limits.employees,
3236
+ memoryLimit: limits.memories
3237
+ };
3238
+ } catch {
3239
+ return freeLicense();
2957
3240
  }
2958
- return false;
2959
3241
  }
2960
- function sendRequest(texts, priority) {
2961
- return new Promise((resolve) => {
2962
- if (!_socket || !_connected) {
2963
- resolve({ error: "Not connected" });
2964
- return;
2965
- }
2966
- const id = randomUUID3();
2967
- const timer = setTimeout(() => {
2968
- _pending.delete(id);
2969
- resolve({ error: "Request timeout" });
2970
- }, REQUEST_TIMEOUT_MS);
2971
- _pending.set(id, { resolve, timer });
2972
- try {
2973
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
2974
- } catch {
2975
- clearTimeout(timer);
2976
- _pending.delete(id);
2977
- resolve({ error: "Write failed" });
2978
- }
2979
- });
3242
+ function freeLicense() {
3243
+ const limits = PLAN_LIMITS.free;
3244
+ return {
3245
+ valid: true,
3246
+ plan: "free",
3247
+ email: "",
3248
+ expiresAt: null,
3249
+ deviceLimit: limits.devices,
3250
+ employeeLimit: limits.employees,
3251
+ memoryLimit: limits.memories
3252
+ };
2980
3253
  }
2981
- async function pingDaemon() {
2982
- if (!_socket || !_connected) return null;
2983
- return new Promise((resolve) => {
2984
- const id = randomUUID3();
2985
- const timer = setTimeout(() => {
2986
- _pending.delete(id);
2987
- resolve(null);
2988
- }, 5e3);
2989
- _pending.set(id, {
2990
- resolve: (data) => {
2991
- if (data.health) {
2992
- resolve(data.health);
2993
- } else {
2994
- resolve(null);
2995
- }
2996
- },
2997
- timer
2998
- });
2999
- try {
3000
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
3001
- } catch {
3002
- clearTimeout(timer);
3003
- _pending.delete(id);
3004
- resolve(null);
3005
- }
3006
- });
3254
+ async function countActiveMemories() {
3255
+ if (!isInitialized()) return 0;
3256
+ const client = getClient();
3257
+ const result = await client.execute(
3258
+ "SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
3259
+ );
3260
+ const row = result.rows[0];
3261
+ return Number(row?.cnt ?? 0);
3007
3262
  }
3008
- function killAndRespawnDaemon() {
3009
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
3010
- if (existsSync10(PID_PATH)) {
3011
- try {
3012
- const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
3013
- if (pid > 0) {
3014
- try {
3015
- process.kill(pid, "SIGKILL");
3016
- } catch {
3017
- }
3018
- }
3019
- } catch {
3020
- }
3263
+ async function assertMemoryLimit() {
3264
+ const license = await checkLicense();
3265
+ if (license.memoryLimit < 0) return;
3266
+ const count = await countActiveMemories();
3267
+ if (count >= license.memoryLimit) {
3268
+ throw new PlanLimitError(
3269
+ `Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
3270
+ );
3021
3271
  }
3022
- if (_socket) {
3023
- _socket.destroy();
3024
- _socket = null;
3272
+ }
3273
+ async function assertEmployeeLimit(license, rosterPath) {
3274
+ const lic = license ?? await checkLicense();
3275
+ if (lic.employeeLimit < 0) return;
3276
+ const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
3277
+ if (employees.length >= lic.employeeLimit) {
3278
+ throw new PlanLimitError(
3279
+ `Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
3280
+ );
3025
3281
  }
3026
- _connected = false;
3027
- _buffer = "";
3282
+ }
3283
+ function assertEmployeeLimitSync(rosterPath) {
3284
+ const license = getLicenseSync();
3285
+ if (license.employeeLimit < 0) return;
3286
+ const filePath = rosterPath ?? EMPLOYEES_PATH;
3287
+ let count = 0;
3028
3288
  try {
3029
- unlinkSync3(PID_PATH);
3289
+ if (existsSync9(filePath)) {
3290
+ const raw = readFileSync7(filePath, "utf8");
3291
+ const employees = JSON.parse(raw);
3292
+ count = Array.isArray(employees) ? employees.length : 0;
3293
+ }
3030
3294
  } catch {
3295
+ throw new PlanLimitError(
3296
+ `Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
3297
+ );
3031
3298
  }
3032
- try {
3033
- unlinkSync3(SOCKET_PATH);
3034
- } catch {
3299
+ if (count >= license.employeeLimit) {
3300
+ throw new PlanLimitError(
3301
+ `Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
3302
+ );
3035
3303
  }
3036
- spawnDaemon();
3037
3304
  }
3038
- async function embedViaClient(text, priority = "high") {
3039
- if (!_connected && !await connectEmbedDaemon()) return null;
3040
- _requestCount++;
3041
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
3042
- const health = await pingDaemon();
3043
- if (!health) {
3044
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
3045
- `);
3046
- killAndRespawnDaemon();
3047
- const start = Date.now();
3048
- let delay2 = 200;
3049
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3050
- await new Promise((r) => setTimeout(r, delay2));
3051
- if (await connectToSocket()) break;
3052
- delay2 = Math.min(delay2 * 2, 3e3);
3305
+ async function assertFeature(feature) {
3306
+ const license = await checkLicense();
3307
+ if (!isFeatureAllowed(license, feature)) {
3308
+ throw new PlanLimitError(
3309
+ `Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
3310
+ );
3311
+ }
3312
+ }
3313
+ var PlanLimitError, CACHE_PATH2;
3314
+ var init_plan_limits = __esm({
3315
+ "src/lib/plan-limits.ts"() {
3316
+ "use strict";
3317
+ init_database();
3318
+ init_employees();
3319
+ init_license();
3320
+ init_config();
3321
+ PlanLimitError = class extends Error {
3322
+ constructor(message) {
3323
+ super(message);
3324
+ this.name = "PlanLimitError";
3053
3325
  }
3054
- if (!_connected) return null;
3055
- }
3326
+ };
3327
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3056
3328
  }
3057
- const result = await sendRequest([text], priority);
3058
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
3059
- if (result.error) {
3060
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
3061
- `);
3062
- killAndRespawnDaemon();
3063
- const start = Date.now();
3064
- let delay2 = 200;
3065
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3066
- await new Promise((r) => setTimeout(r, delay2));
3067
- if (await connectToSocket()) break;
3068
- delay2 = Math.min(delay2 * 2, 3e3);
3329
+ });
3330
+
3331
+ // src/lib/tmux-routing.ts
3332
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10, appendFileSync } from "fs";
3333
+ import path11 from "path";
3334
+ import os7 from "os";
3335
+ import { fileURLToPath as fileURLToPath2 } from "url";
3336
+ function getMySession() {
3337
+ return getTransport().getMySession();
3338
+ }
3339
+ function extractRootExe(name) {
3340
+ if (!name) return null;
3341
+ if (!name.includes("-")) return name;
3342
+ const parts = name.split("-").filter(Boolean);
3343
+ return parts.length > 0 ? parts[parts.length - 1] : null;
3344
+ }
3345
+ function getParentExe(sessionKey) {
3346
+ try {
3347
+ const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3348
+ return data.parentExe || null;
3349
+ } catch {
3350
+ return null;
3351
+ }
3352
+ }
3353
+ function resolveExeSession() {
3354
+ const mySession = getMySession();
3355
+ if (!mySession) return null;
3356
+ try {
3357
+ const key = getSessionKey();
3358
+ const parentExe = getParentExe(key);
3359
+ if (parentExe) {
3360
+ return extractRootExe(parentExe) ?? parentExe;
3069
3361
  }
3070
- if (!_connected) return null;
3071
- const retry = await sendRequest([text], priority);
3072
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
3073
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
3074
- `);
3362
+ } catch {
3075
3363
  }
3076
- return null;
3364
+ return extractRootExe(mySession) ?? mySession;
3077
3365
  }
3078
- function disconnectClient() {
3079
- if (_socket) {
3080
- _socket.destroy();
3081
- _socket = null;
3366
+ var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3367
+ var init_tmux_routing = __esm({
3368
+ "src/lib/tmux-routing.ts"() {
3369
+ "use strict";
3370
+ init_session_registry();
3371
+ init_session_key();
3372
+ init_transport();
3373
+ init_cc_agent_support();
3374
+ init_mcp_prefix();
3375
+ init_provider_table();
3376
+ init_intercom_queue();
3377
+ init_plan_limits();
3378
+ init_employees();
3379
+ SPAWN_LOCK_DIR = path11.join(os7.homedir(), ".exe-os", "spawn-locks");
3380
+ SESSION_CACHE = path11.join(os7.homedir(), ".exe-os", "session-cache");
3381
+ INTERCOM_LOG2 = path11.join(os7.homedir(), ".exe-os", "intercom.log");
3382
+ DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
3383
+ DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3082
3384
  }
3083
- _connected = false;
3084
- _buffer = "";
3085
- for (const [id, entry] of _pending) {
3086
- clearTimeout(entry.timer);
3087
- _pending.delete(id);
3088
- entry.resolve({ error: "Client disconnected" });
3385
+ });
3386
+
3387
+ // src/lib/task-scope.ts
3388
+ function getCurrentSessionScope() {
3389
+ try {
3390
+ return resolveExeSession();
3391
+ } catch {
3392
+ return null;
3089
3393
  }
3090
3394
  }
3091
- 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;
3092
- var init_exe_daemon_client = __esm({
3093
- "src/lib/exe-daemon-client.ts"() {
3395
+ function sessionScopeFilter(sessionScope, tableAlias) {
3396
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3397
+ if (!scope) return { sql: "", args: [] };
3398
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3399
+ return {
3400
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3401
+ args: [scope]
3402
+ };
3403
+ }
3404
+ var init_task_scope = __esm({
3405
+ "src/lib/task-scope.ts"() {
3094
3406
  "use strict";
3095
- init_config();
3096
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path11.join(EXE_AI_DIR, "exed.sock");
3097
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path11.join(EXE_AI_DIR, "exed.pid");
3098
- SPAWN_LOCK_PATH = path11.join(EXE_AI_DIR, "exed-spawn.lock");
3099
- SPAWN_LOCK_STALE_MS = 3e4;
3100
- CONNECT_TIMEOUT_MS = 15e3;
3101
- REQUEST_TIMEOUT_MS = 3e4;
3102
- _socket = null;
3103
- _connected = false;
3104
- _buffer = "";
3105
- _requestCount = 0;
3106
- HEALTH_CHECK_INTERVAL = 100;
3107
- _pending = /* @__PURE__ */ new Map();
3108
- MAX_BUFFER = 1e7;
3407
+ init_tmux_routing();
3109
3408
  }
3110
3409
  });
3111
3410
 
@@ -3146,10 +3445,10 @@ async function disposeEmbedder() {
3146
3445
  async function embedDirect(text) {
3147
3446
  const llamaCpp = await import("node-llama-cpp");
3148
3447
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3149
- const { existsSync: existsSync14 } = await import("fs");
3150
- const path15 = await import("path");
3151
- const modelPath = path15.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3152
- if (!existsSync14(modelPath)) {
3448
+ const { existsSync: existsSync15 } = await import("fs");
3449
+ const path16 = await import("path");
3450
+ const modelPath = path16.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3451
+ if (!existsSync15(modelPath)) {
3153
3452
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3154
3453
  }
3155
3454
  const llama = await llamaCpp.getLlama();
@@ -3366,6 +3665,232 @@ var init_compress = __esm({
3366
3665
  }
3367
3666
  });
3368
3667
 
3668
+ // src/lib/crdt-sync.ts
3669
+ var crdt_sync_exports = {};
3670
+ __export(crdt_sync_exports, {
3671
+ _setStatePath: () => _setStatePath,
3672
+ applyRemoteUpdate: () => applyRemoteUpdate,
3673
+ destroyCrdtDoc: () => destroyCrdtDoc,
3674
+ getDiffUpdate: () => getDiffUpdate,
3675
+ getFullState: () => getFullState,
3676
+ getStateVector: () => getStateVector,
3677
+ importExistingBehaviors: () => importExistingBehaviors,
3678
+ importExistingMemories: () => importExistingMemories,
3679
+ initCrdtDoc: () => initCrdtDoc,
3680
+ isCrdtSyncEnabled: () => isCrdtSyncEnabled,
3681
+ onUpdate: () => onUpdate,
3682
+ readAllBehaviors: () => readAllBehaviors,
3683
+ readAllMemories: () => readAllMemories,
3684
+ rebuildFromDb: () => rebuildFromDb
3685
+ });
3686
+ import * as Y from "yjs";
3687
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
3688
+ import path13 from "path";
3689
+ import { homedir } from "os";
3690
+ function getStatePath() {
3691
+ return _statePathOverride ?? DEFAULT_STATE_PATH;
3692
+ }
3693
+ function _setStatePath(p) {
3694
+ _statePathOverride = p;
3695
+ }
3696
+ function initCrdtDoc() {
3697
+ if (doc) return doc;
3698
+ doc = new Y.Doc();
3699
+ const sp = getStatePath();
3700
+ if (existsSync12(sp)) {
3701
+ try {
3702
+ const state = readFileSync9(sp);
3703
+ Y.applyUpdate(doc, new Uint8Array(state));
3704
+ } catch {
3705
+ console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
3706
+ try {
3707
+ unlinkSync5(sp);
3708
+ } catch {
3709
+ }
3710
+ rebuildFromDb().catch((err) => {
3711
+ console.warn("[crdt-sync] rebuild from DB failed:", err);
3712
+ });
3713
+ }
3714
+ }
3715
+ doc.on("update", () => {
3716
+ persistState();
3717
+ });
3718
+ return doc;
3719
+ }
3720
+ function getMemoriesMap() {
3721
+ const d = initCrdtDoc();
3722
+ return d.getMap("memories");
3723
+ }
3724
+ function getBehaviorsMap() {
3725
+ const d = initCrdtDoc();
3726
+ return d.getMap("behaviors");
3727
+ }
3728
+ function applyRemoteUpdate(update) {
3729
+ const d = initCrdtDoc();
3730
+ Y.applyUpdate(d, update);
3731
+ }
3732
+ function getFullState() {
3733
+ const d = initCrdtDoc();
3734
+ return Y.encodeStateAsUpdate(d);
3735
+ }
3736
+ function getDiffUpdate(remoteStateVector) {
3737
+ const d = initCrdtDoc();
3738
+ return Y.encodeStateAsUpdate(d, remoteStateVector);
3739
+ }
3740
+ function getStateVector() {
3741
+ const d = initCrdtDoc();
3742
+ return Y.encodeStateVector(d);
3743
+ }
3744
+ function importExistingMemories(memories) {
3745
+ const map = getMemoriesMap();
3746
+ const d = initCrdtDoc();
3747
+ let imported = 0;
3748
+ d.transact(() => {
3749
+ for (const mem of memories) {
3750
+ if (!mem.id) continue;
3751
+ if (map.has(mem.id)) continue;
3752
+ const entry = new Y.Map();
3753
+ entry.set("id", mem.id);
3754
+ entry.set("agent_id", mem.agent_id ?? null);
3755
+ entry.set("agent_role", mem.agent_role ?? null);
3756
+ entry.set("session_id", mem.session_id ?? null);
3757
+ entry.set("timestamp", mem.timestamp ?? null);
3758
+ entry.set("tool_name", mem.tool_name ?? null);
3759
+ entry.set("project_name", mem.project_name ?? null);
3760
+ entry.set("has_error", mem.has_error ?? 0);
3761
+ entry.set("raw_text", mem.raw_text ?? "");
3762
+ entry.set("version", mem.version ?? 0);
3763
+ entry.set("author_device_id", mem.author_device_id ?? null);
3764
+ entry.set("scope", mem.scope ?? "business");
3765
+ map.set(mem.id, entry);
3766
+ imported++;
3767
+ }
3768
+ });
3769
+ return imported;
3770
+ }
3771
+ function importExistingBehaviors(behaviors) {
3772
+ const map = getBehaviorsMap();
3773
+ const d = initCrdtDoc();
3774
+ let imported = 0;
3775
+ d.transact(() => {
3776
+ for (const beh of behaviors) {
3777
+ if (!beh.id) continue;
3778
+ if (map.has(beh.id)) continue;
3779
+ const entry = new Y.Map();
3780
+ entry.set("id", beh.id);
3781
+ entry.set("agent_id", beh.agent_id ?? null);
3782
+ entry.set("project_name", beh.project_name ?? null);
3783
+ entry.set("domain", beh.domain ?? null);
3784
+ entry.set("content", beh.content ?? null);
3785
+ entry.set("active", beh.active ?? 1);
3786
+ entry.set("priority", beh.priority ?? "p1");
3787
+ entry.set("created_at", beh.created_at ?? null);
3788
+ entry.set("updated_at", beh.updated_at ?? null);
3789
+ map.set(beh.id, entry);
3790
+ imported++;
3791
+ }
3792
+ });
3793
+ return imported;
3794
+ }
3795
+ function readAllMemories() {
3796
+ const map = getMemoriesMap();
3797
+ const records = [];
3798
+ map.forEach((entry, id) => {
3799
+ records.push({
3800
+ id,
3801
+ agent_id: entry.get("agent_id"),
3802
+ agent_role: entry.get("agent_role"),
3803
+ session_id: entry.get("session_id"),
3804
+ timestamp: entry.get("timestamp"),
3805
+ tool_name: entry.get("tool_name"),
3806
+ project_name: entry.get("project_name"),
3807
+ has_error: entry.get("has_error"),
3808
+ raw_text: entry.get("raw_text"),
3809
+ version: entry.get("version"),
3810
+ author_device_id: entry.get("author_device_id"),
3811
+ scope: entry.get("scope")
3812
+ });
3813
+ });
3814
+ return records;
3815
+ }
3816
+ function readAllBehaviors() {
3817
+ const map = getBehaviorsMap();
3818
+ const records = [];
3819
+ map.forEach((entry, id) => {
3820
+ records.push({
3821
+ id,
3822
+ agent_id: entry.get("agent_id"),
3823
+ project_name: entry.get("project_name"),
3824
+ domain: entry.get("domain"),
3825
+ content: entry.get("content"),
3826
+ active: entry.get("active"),
3827
+ priority: entry.get("priority"),
3828
+ created_at: entry.get("created_at"),
3829
+ updated_at: entry.get("updated_at")
3830
+ });
3831
+ });
3832
+ return records;
3833
+ }
3834
+ function onUpdate(callback) {
3835
+ const d = initCrdtDoc();
3836
+ const handler = (update) => callback(update);
3837
+ d.on("update", handler);
3838
+ return () => d.off("update", handler);
3839
+ }
3840
+ function persistState() {
3841
+ if (!doc) return;
3842
+ try {
3843
+ const sp = getStatePath();
3844
+ const dir = path13.dirname(sp);
3845
+ if (!existsSync12(dir)) mkdirSync6(dir, { recursive: true });
3846
+ const state = Y.encodeStateAsUpdate(doc);
3847
+ writeFileSync6(sp, Buffer.from(state));
3848
+ } catch {
3849
+ }
3850
+ }
3851
+ function isCrdtSyncEnabled() {
3852
+ return process.env.EXE_CRDT_SYNC !== "0";
3853
+ }
3854
+ async function rebuildFromDb() {
3855
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3856
+ const client = getClient2();
3857
+ const result = await client.execute(
3858
+ "SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
3859
+ );
3860
+ const memories = result.rows.map((row) => ({
3861
+ id: String(row.id),
3862
+ agent_id: row.agent_id,
3863
+ agent_role: row.agent_role,
3864
+ session_id: row.session_id,
3865
+ timestamp: row.timestamp,
3866
+ tool_name: row.tool_name,
3867
+ project_name: row.project_name,
3868
+ has_error: row.has_error,
3869
+ raw_text: row.raw_text,
3870
+ version: row.version,
3871
+ author_device_id: row.author_device_id,
3872
+ scope: row.scope
3873
+ }));
3874
+ const count = importExistingMemories(memories);
3875
+ persistState();
3876
+ return count;
3877
+ }
3878
+ function destroyCrdtDoc() {
3879
+ if (doc) {
3880
+ doc.destroy();
3881
+ doc = null;
3882
+ }
3883
+ }
3884
+ var DEFAULT_STATE_PATH, _statePathOverride, doc;
3885
+ var init_crdt_sync = __esm({
3886
+ "src/lib/crdt-sync.ts"() {
3887
+ "use strict";
3888
+ DEFAULT_STATE_PATH = path13.join(homedir(), ".exe-os", "crdt-state.bin");
3889
+ _statePathOverride = null;
3890
+ doc = null;
3891
+ }
3892
+ });
3893
+
3369
3894
  // src/lib/cloud-sync.ts
3370
3895
  var cloud_sync_exports = {};
3371
3896
  __export(cloud_sync_exports, {
@@ -3394,16 +3919,16 @@ __export(cloud_sync_exports, {
3394
3919
  mergeRosterFromRemote: () => mergeRosterFromRemote,
3395
3920
  recordRosterDeletion: () => recordRosterDeletion
3396
3921
  });
3397
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync12, readdirSync as readdirSync4, mkdirSync as mkdirSync6, appendFileSync as appendFileSync2, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2 } from "fs";
3922
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, readdirSync as readdirSync4, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
3398
3923
  import crypto3 from "crypto";
3399
- import path13 from "path";
3400
- import { homedir } from "os";
3924
+ import path14 from "path";
3925
+ import { homedir as homedir2 } from "os";
3401
3926
  function sqlSafe(v) {
3402
3927
  return v === void 0 ? null : v;
3403
3928
  }
3404
3929
  function logError(msg) {
3405
3930
  try {
3406
- const logPath = path13.join(homedir(), ".exe-os", "workers.log");
3931
+ const logPath = path14.join(homedir2(), ".exe-os", "workers.log");
3407
3932
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
3408
3933
  `);
3409
3934
  } catch {
@@ -3413,18 +3938,18 @@ async function withRosterLock(fn) {
3413
3938
  try {
3414
3939
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3415
3940
  closeSync2(fd);
3416
- writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
3941
+ writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
3417
3942
  } catch (err) {
3418
3943
  if (err.code === "EEXIST") {
3419
3944
  try {
3420
- const ts = parseInt(readFileSync9(ROSTER_LOCK_PATH, "utf-8"), 10);
3945
+ const ts = parseInt(readFileSync10(ROSTER_LOCK_PATH, "utf-8"), 10);
3421
3946
  if (Date.now() - ts < LOCK_STALE_MS) {
3422
3947
  throw new Error("Roster merge already in progress \u2014 another sync is running");
3423
3948
  }
3424
- unlinkSync5(ROSTER_LOCK_PATH);
3949
+ unlinkSync6(ROSTER_LOCK_PATH);
3425
3950
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3426
3951
  closeSync2(fd);
3427
- writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
3952
+ writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
3428
3953
  } catch (retryErr) {
3429
3954
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
3430
3955
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -3437,7 +3962,7 @@ async function withRosterLock(fn) {
3437
3962
  return await fn();
3438
3963
  } finally {
3439
3964
  try {
3440
- unlinkSync5(ROSTER_LOCK_PATH);
3965
+ unlinkSync6(ROSTER_LOCK_PATH);
3441
3966
  } catch {
3442
3967
  }
3443
3968
  }
@@ -3582,29 +4107,75 @@ async function cloudSync(config) {
3582
4107
  const pullResult = await cloudPull(lastPullVersion, config);
3583
4108
  let pulled = 0;
3584
4109
  if (pullResult.records.length > 0) {
3585
- const stmts = pullResult.records.map((rec) => ({
3586
- sql: `INSERT OR REPLACE INTO memories
3587
- (id, agent_id, agent_role, session_id, timestamp,
3588
- tool_name, project_name, has_error, raw_text, version,
3589
- author_device_id, scope)
3590
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3591
- args: [
3592
- sqlSafe(rec.id),
3593
- sqlSafe(rec.agent_id),
3594
- sqlSafe(rec.agent_role),
3595
- sqlSafe(rec.session_id),
3596
- sqlSafe(rec.timestamp),
3597
- sqlSafe(rec.tool_name),
3598
- sqlSafe(rec.project_name),
3599
- sqlSafe(rec.has_error ?? 0),
3600
- sqlSafe(rec.raw_text ?? ""),
3601
- sqlSafe(rec.version ?? 0),
3602
- sqlSafe(rec.author_device_id),
3603
- sqlSafe(rec.scope ?? "business")
3604
- ]
3605
- }));
3606
- await client.batch(stmts, "write");
3607
- pulled = pullResult.records.length;
4110
+ if (isCrdtSyncEnabled()) {
4111
+ const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
4112
+ initCrdtDoc2();
4113
+ importExistingMemories2(
4114
+ pullResult.records.map((rec) => ({
4115
+ id: String(rec.id ?? ""),
4116
+ agent_id: rec.agent_id,
4117
+ agent_role: rec.agent_role,
4118
+ session_id: rec.session_id,
4119
+ timestamp: rec.timestamp,
4120
+ tool_name: rec.tool_name,
4121
+ project_name: rec.project_name,
4122
+ has_error: rec.has_error ?? 0,
4123
+ raw_text: rec.raw_text ?? "",
4124
+ version: rec.version ?? 0,
4125
+ author_device_id: rec.author_device_id,
4126
+ scope: rec.scope ?? "business"
4127
+ }))
4128
+ );
4129
+ const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
4130
+ const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
4131
+ const stmts = merged.map((rec) => ({
4132
+ sql: `INSERT OR REPLACE INTO memories
4133
+ (id, agent_id, agent_role, session_id, timestamp,
4134
+ tool_name, project_name, has_error, raw_text, version,
4135
+ author_device_id, scope)
4136
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4137
+ args: [
4138
+ sqlSafe(rec.id),
4139
+ sqlSafe(rec.agent_id),
4140
+ sqlSafe(rec.agent_role),
4141
+ sqlSafe(rec.session_id),
4142
+ sqlSafe(rec.timestamp),
4143
+ sqlSafe(rec.tool_name),
4144
+ sqlSafe(rec.project_name),
4145
+ sqlSafe(rec.has_error ?? 0),
4146
+ sqlSafe(rec.raw_text ?? ""),
4147
+ sqlSafe(rec.version ?? 0),
4148
+ sqlSafe(rec.author_device_id),
4149
+ sqlSafe(rec.scope ?? "business")
4150
+ ]
4151
+ }));
4152
+ if (stmts.length > 0) await client.batch(stmts, "write");
4153
+ pulled = pullResult.records.length;
4154
+ } else {
4155
+ const stmts = pullResult.records.map((rec) => ({
4156
+ sql: `INSERT OR REPLACE INTO memories
4157
+ (id, agent_id, agent_role, session_id, timestamp,
4158
+ tool_name, project_name, has_error, raw_text, version,
4159
+ author_device_id, scope)
4160
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4161
+ args: [
4162
+ sqlSafe(rec.id),
4163
+ sqlSafe(rec.agent_id),
4164
+ sqlSafe(rec.agent_role),
4165
+ sqlSafe(rec.session_id),
4166
+ sqlSafe(rec.timestamp),
4167
+ sqlSafe(rec.tool_name),
4168
+ sqlSafe(rec.project_name),
4169
+ sqlSafe(rec.has_error ?? 0),
4170
+ sqlSafe(rec.raw_text ?? ""),
4171
+ sqlSafe(rec.version ?? 0),
4172
+ sqlSafe(rec.author_device_id),
4173
+ sqlSafe(rec.scope ?? "business")
4174
+ ]
4175
+ }));
4176
+ await client.batch(stmts, "write");
4177
+ pulled = pullResult.records.length;
4178
+ }
3608
4179
  }
3609
4180
  if (pullResult.maxVersion > lastPullVersion) {
3610
4181
  await client.execute({
@@ -3752,8 +4323,8 @@ async function cloudSync(config) {
3752
4323
  try {
3753
4324
  const employees = await loadEmployees();
3754
4325
  rosterResult.employees = employees.length;
3755
- const idDir = path13.join(EXE_AI_DIR, "identity");
3756
- if (existsSync12(idDir)) {
4326
+ const idDir = path14.join(EXE_AI_DIR, "identity");
4327
+ if (existsSync13(idDir)) {
3757
4328
  rosterResult.identities = readdirSync4(idDir).filter((f) => f.endsWith(".md")).length;
3758
4329
  }
3759
4330
  } catch {
@@ -3774,48 +4345,48 @@ async function cloudSync(config) {
3774
4345
  function recordRosterDeletion(name) {
3775
4346
  let deletions = [];
3776
4347
  try {
3777
- if (existsSync12(ROSTER_DELETIONS_PATH)) {
3778
- deletions = JSON.parse(readFileSync9(ROSTER_DELETIONS_PATH, "utf-8"));
4348
+ if (existsSync13(ROSTER_DELETIONS_PATH)) {
4349
+ deletions = JSON.parse(readFileSync10(ROSTER_DELETIONS_PATH, "utf-8"));
3779
4350
  }
3780
4351
  } catch {
3781
4352
  }
3782
4353
  if (!deletions.includes(name)) deletions.push(name);
3783
- writeFileSync6(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
4354
+ writeFileSync7(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3784
4355
  }
3785
4356
  function consumeRosterDeletions() {
3786
4357
  try {
3787
- if (!existsSync12(ROSTER_DELETIONS_PATH)) return [];
3788
- const deletions = JSON.parse(readFileSync9(ROSTER_DELETIONS_PATH, "utf-8"));
3789
- writeFileSync6(ROSTER_DELETIONS_PATH, "[]");
4358
+ if (!existsSync13(ROSTER_DELETIONS_PATH)) return [];
4359
+ const deletions = JSON.parse(readFileSync10(ROSTER_DELETIONS_PATH, "utf-8"));
4360
+ writeFileSync7(ROSTER_DELETIONS_PATH, "[]");
3790
4361
  return deletions;
3791
4362
  } catch {
3792
4363
  return [];
3793
4364
  }
3794
4365
  }
3795
4366
  function buildRosterBlob(paths) {
3796
- const rosterPath = paths?.rosterPath ?? path13.join(EXE_AI_DIR, "exe-employees.json");
3797
- const identityDir = paths?.identityDir ?? path13.join(EXE_AI_DIR, "identity");
3798
- const configPath = paths?.configPath ?? path13.join(EXE_AI_DIR, "config.json");
4367
+ const rosterPath = paths?.rosterPath ?? path14.join(EXE_AI_DIR, "exe-employees.json");
4368
+ const identityDir = paths?.identityDir ?? path14.join(EXE_AI_DIR, "identity");
4369
+ const configPath = paths?.configPath ?? path14.join(EXE_AI_DIR, "config.json");
3799
4370
  let roster = [];
3800
- if (existsSync12(rosterPath)) {
4371
+ if (existsSync13(rosterPath)) {
3801
4372
  try {
3802
- roster = JSON.parse(readFileSync9(rosterPath, "utf-8"));
4373
+ roster = JSON.parse(readFileSync10(rosterPath, "utf-8"));
3803
4374
  } catch {
3804
4375
  }
3805
4376
  }
3806
4377
  const identities = {};
3807
- if (existsSync12(identityDir)) {
4378
+ if (existsSync13(identityDir)) {
3808
4379
  for (const file of readdirSync4(identityDir).filter((f) => f.endsWith(".md"))) {
3809
4380
  try {
3810
- identities[file] = readFileSync9(path13.join(identityDir, file), "utf-8");
4381
+ identities[file] = readFileSync10(path14.join(identityDir, file), "utf-8");
3811
4382
  } catch {
3812
4383
  }
3813
4384
  }
3814
4385
  }
3815
4386
  let config;
3816
- if (existsSync12(configPath)) {
4387
+ if (existsSync13(configPath)) {
3817
4388
  try {
3818
- config = JSON.parse(readFileSync9(configPath, "utf-8"));
4389
+ config = JSON.parse(readFileSync10(configPath, "utf-8"));
3819
4390
  } catch {
3820
4391
  }
3821
4392
  }
@@ -3891,23 +4462,23 @@ async function cloudPullRoster(config) {
3891
4462
  }
3892
4463
  }
3893
4464
  function mergeConfig(remoteConfig, configPath) {
3894
- const cfgPath = configPath ?? path13.join(EXE_AI_DIR, "config.json");
4465
+ const cfgPath = configPath ?? path14.join(EXE_AI_DIR, "config.json");
3895
4466
  let local = {};
3896
- if (existsSync12(cfgPath)) {
4467
+ if (existsSync13(cfgPath)) {
3897
4468
  try {
3898
- local = JSON.parse(readFileSync9(cfgPath, "utf-8"));
4469
+ local = JSON.parse(readFileSync10(cfgPath, "utf-8"));
3899
4470
  } catch {
3900
4471
  }
3901
4472
  }
3902
4473
  const merged = { ...remoteConfig, ...local };
3903
- const dir = path13.dirname(cfgPath);
3904
- if (!existsSync12(dir)) mkdirSync6(dir, { recursive: true });
3905
- writeFileSync6(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
4474
+ const dir = path14.dirname(cfgPath);
4475
+ if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
4476
+ writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3906
4477
  }
3907
4478
  async function mergeRosterFromRemote(remote, paths) {
3908
4479
  return withRosterLock(async () => {
3909
4480
  const rosterPath = paths?.rosterPath ?? void 0;
3910
- const identityDir = paths?.identityDir ?? path13.join(EXE_AI_DIR, "identity");
4481
+ const identityDir = paths?.identityDir ?? path14.join(EXE_AI_DIR, "identity");
3911
4482
  const localEmployees = await loadEmployees(rosterPath);
3912
4483
  const localNames = new Set(localEmployees.map((e) => e.name));
3913
4484
  let added = 0;
@@ -3928,15 +4499,15 @@ async function mergeRosterFromRemote(remote, paths) {
3928
4499
  ) ?? lookupKey;
3929
4500
  const remoteIdentity = remote.identities[matchedKey];
3930
4501
  if (remoteIdentity) {
3931
- if (!existsSync12(identityDir)) mkdirSync6(identityDir, { recursive: true });
3932
- const idPath = path13.join(identityDir, `${remoteEmp.name}.md`);
4502
+ if (!existsSync13(identityDir)) mkdirSync7(identityDir, { recursive: true });
4503
+ const idPath = path14.join(identityDir, `${remoteEmp.name}.md`);
3933
4504
  let localIdentity = null;
3934
4505
  try {
3935
- localIdentity = existsSync12(idPath) ? readFileSync9(idPath, "utf-8") : null;
4506
+ localIdentity = existsSync13(idPath) ? readFileSync10(idPath, "utf-8") : null;
3936
4507
  } catch {
3937
4508
  }
3938
4509
  if (localIdentity !== remoteIdentity) {
3939
- writeFileSync6(idPath, remoteIdentity, "utf-8");
4510
+ writeFileSync7(idPath, remoteIdentity, "utf-8");
3940
4511
  identitiesUpdated++;
3941
4512
  }
3942
4513
  }
@@ -4389,13 +4960,14 @@ var init_cloud_sync = __esm({
4389
4960
  init_compress();
4390
4961
  init_license();
4391
4962
  init_config();
4963
+ init_crdt_sync();
4392
4964
  init_employees();
4393
4965
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
4394
4966
  FETCH_TIMEOUT_MS = 3e4;
4395
4967
  PUSH_BATCH_SIZE = 5e3;
4396
- ROSTER_LOCK_PATH = path13.join(EXE_AI_DIR, "roster-merge.lock");
4968
+ ROSTER_LOCK_PATH = path14.join(EXE_AI_DIR, "roster-merge.lock");
4397
4969
  LOCK_STALE_MS = 3e4;
4398
- ROSTER_DELETIONS_PATH = path13.join(EXE_AI_DIR, "roster-deletions.json");
4970
+ ROSTER_DELETIONS_PATH = path14.join(EXE_AI_DIR, "roster-deletions.json");
4399
4971
  }
4400
4972
  });
4401
4973
 
@@ -4405,6 +4977,7 @@ init_database();
4405
4977
  init_keychain();
4406
4978
  init_config();
4407
4979
  init_state_bus();
4980
+ import { createHash } from "crypto";
4408
4981
  var INIT_MAX_RETRIES = 3;
4409
4982
  var INIT_RETRY_DELAY_MS = 1e3;
4410
4983
  function isBusyError2(err) {
@@ -4486,12 +5059,52 @@ function classifyTier(record) {
4486
5059
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4487
5060
  return 3;
4488
5061
  }
5062
+ function inferFilePaths(record) {
5063
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
5064
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
5065
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
5066
+ return match ? JSON.stringify([match[1]]) : null;
5067
+ }
5068
+ function inferCommitHash(record) {
5069
+ if (record.tool_name !== "Bash") return null;
5070
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
5071
+ return match ? match[1] : null;
5072
+ }
5073
+ function inferLanguageType(record) {
5074
+ const text = record.raw_text;
5075
+ if (!text || text.length < 10) return null;
5076
+ const trimmed = text.trimStart();
5077
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
5078
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
5079
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
5080
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
5081
+ return "mixed";
5082
+ }
5083
+ function inferDomain(record) {
5084
+ const proj = (record.project_name ?? "").toLowerCase();
5085
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
5086
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
5087
+ return null;
5088
+ }
4489
5089
  async function writeMemory(record) {
4490
5090
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4491
5091
  throw new Error(
4492
5092
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4493
5093
  );
4494
5094
  }
5095
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
5096
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
5097
+ return;
5098
+ }
5099
+ try {
5100
+ const client = getClient();
5101
+ const existing = await client.execute({
5102
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
5103
+ args: [contentHash, record.agent_id]
5104
+ });
5105
+ if (existing.rows.length > 0) return;
5106
+ } catch {
5107
+ }
4495
5108
  const dbRow = {
4496
5109
  id: record.id,
4497
5110
  agent_id: record.agent_id,
@@ -4521,7 +5134,23 @@ async function writeMemory(record) {
4521
5134
  supersedes_id: record.supersedes_id ?? null,
4522
5135
  draft: record.draft ? 1 : 0,
4523
5136
  memory_type: record.memory_type ?? "raw",
4524
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
5137
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5138
+ content_hash: contentHash,
5139
+ intent: record.intent ?? null,
5140
+ outcome: record.outcome ?? null,
5141
+ domain: record.domain ?? inferDomain(record),
5142
+ referenced_entities: record.referenced_entities ?? null,
5143
+ retrieval_count: record.retrieval_count ?? 0,
5144
+ chain_position: record.chain_position ?? null,
5145
+ review_status: record.review_status ?? null,
5146
+ context_window_pct: record.context_window_pct ?? null,
5147
+ file_paths: record.file_paths ?? inferFilePaths(record),
5148
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
5149
+ duration_ms: record.duration_ms ?? null,
5150
+ token_cost: record.token_cost ?? null,
5151
+ audience: record.audience ?? null,
5152
+ language_type: record.language_type ?? inferLanguageType(record),
5153
+ parent_memory_id: record.parent_memory_id ?? null
4525
5154
  };
4526
5155
  _pendingRecords.push(dbRow);
4527
5156
  orgBus.emit({
@@ -4579,80 +5208,85 @@ async function flushBatch() {
4579
5208
  const draft = row.draft ? 1 : 0;
4580
5209
  const memoryType = row.memory_type ?? "raw";
4581
5210
  const trajectory = row.trajectory ?? null;
4582
- return {
4583
- sql: hasVector ? `INSERT OR IGNORE INTO memories
4584
- (id, agent_id, agent_role, session_id, timestamp,
5211
+ const contentHash = row.content_hash ?? null;
5212
+ const intent = row.intent ?? null;
5213
+ const outcome = row.outcome ?? null;
5214
+ const domain = row.domain ?? null;
5215
+ const referencedEntities = row.referenced_entities ?? null;
5216
+ const retrievalCount = row.retrieval_count ?? 0;
5217
+ const chainPosition = row.chain_position ?? null;
5218
+ const reviewStatus = row.review_status ?? null;
5219
+ const contextWindowPct = row.context_window_pct ?? null;
5220
+ const filePaths = row.file_paths ?? null;
5221
+ const commitHash = row.commit_hash ?? null;
5222
+ const durationMs = row.duration_ms ?? null;
5223
+ const tokenCost = row.token_cost ?? null;
5224
+ const audience = row.audience ?? null;
5225
+ const languageType = row.language_type ?? null;
5226
+ const parentMemoryId = row.parent_memory_id ?? null;
5227
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4585
5228
  tool_name, project_name,
4586
5229
  has_error, raw_text, vector, version, task_id, importance, status,
4587
5230
  confidence, last_accessed,
4588
5231
  workspace_id, document_id, user_id, char_offset, page_number,
4589
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4590
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4591
- (id, agent_id, agent_role, session_id, timestamp,
4592
- tool_name, project_name,
4593
- has_error, raw_text, vector, version, task_id, importance, status,
4594
- confidence, last_accessed,
4595
- workspace_id, document_id, user_id, char_offset, page_number,
4596
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4597
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4598
- args: hasVector ? [
4599
- row.id,
4600
- row.agent_id,
4601
- row.agent_role,
4602
- row.session_id,
4603
- row.timestamp,
4604
- row.tool_name,
4605
- row.project_name,
4606
- row.has_error,
4607
- row.raw_text,
4608
- vectorToBlob(row.vector),
4609
- row.version,
4610
- taskId,
4611
- importance,
4612
- status,
4613
- confidence,
4614
- lastAccessed,
4615
- workspaceId,
4616
- documentId,
4617
- userId,
4618
- charOffset,
4619
- pageNumber,
4620
- sourcePath,
4621
- sourceType,
4622
- tier,
4623
- supersedesId,
4624
- draft,
4625
- memoryType,
4626
- trajectory
4627
- ] : [
4628
- row.id,
4629
- row.agent_id,
4630
- row.agent_role,
4631
- row.session_id,
4632
- row.timestamp,
4633
- row.tool_name,
4634
- row.project_name,
4635
- row.has_error,
4636
- row.raw_text,
4637
- row.version,
4638
- taskId,
4639
- importance,
4640
- status,
4641
- confidence,
4642
- lastAccessed,
4643
- workspaceId,
4644
- documentId,
4645
- userId,
4646
- charOffset,
4647
- pageNumber,
4648
- sourcePath,
4649
- sourceType,
4650
- tier,
4651
- supersedesId,
4652
- draft,
4653
- memoryType,
4654
- trajectory
4655
- ]
5232
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5233
+ intent, outcome, domain, referenced_entities, retrieval_count,
5234
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
5235
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
5236
+ const metaArgs = [
5237
+ intent,
5238
+ outcome,
5239
+ domain,
5240
+ referencedEntities,
5241
+ retrievalCount,
5242
+ chainPosition,
5243
+ reviewStatus,
5244
+ contextWindowPct,
5245
+ filePaths,
5246
+ commitHash,
5247
+ durationMs,
5248
+ tokenCost,
5249
+ audience,
5250
+ languageType,
5251
+ parentMemoryId
5252
+ ];
5253
+ const baseArgs = [
5254
+ row.id,
5255
+ row.agent_id,
5256
+ row.agent_role,
5257
+ row.session_id,
5258
+ row.timestamp,
5259
+ row.tool_name,
5260
+ row.project_name,
5261
+ row.has_error,
5262
+ row.raw_text
5263
+ ];
5264
+ const sharedArgs = [
5265
+ row.version,
5266
+ taskId,
5267
+ importance,
5268
+ status,
5269
+ confidence,
5270
+ lastAccessed,
5271
+ workspaceId,
5272
+ documentId,
5273
+ userId,
5274
+ charOffset,
5275
+ pageNumber,
5276
+ sourcePath,
5277
+ sourceType,
5278
+ tier,
5279
+ supersedesId,
5280
+ draft,
5281
+ memoryType,
5282
+ trajectory,
5283
+ contentHash
5284
+ ];
5285
+ return {
5286
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5287
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5288
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5289
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4656
5290
  };
4657
5291
  };
4658
5292
  const globalClient = getClient();
@@ -4700,8 +5334,8 @@ init_task_scope();
4700
5334
  init_employees();
4701
5335
  import crypto4 from "crypto";
4702
5336
  import { execSync as execSync4 } from "child_process";
4703
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, openSync as openSync3, closeSync as closeSync3 } from "fs";
4704
- import path14 from "path";
5337
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, openSync as openSync3, closeSync as closeSync3 } from "fs";
5338
+ import path15 from "path";
4705
5339
  async function main() {
4706
5340
  const agentId = process.env.AGENT_ID ?? "default";
4707
5341
  const agentRole = process.env.AGENT_ROLE ?? "employee";
@@ -4836,8 +5470,8 @@ async function main() {
4836
5470
  }
4837
5471
  try {
4838
5472
  const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4839
- const flagPath = path14.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
4840
- if (existsSync13(flagPath)) {
5473
+ const flagPath = path15.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
5474
+ if (existsSync14(flagPath)) {
4841
5475
  const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
4842
5476
  if (!tryAcquireWorkerSlot2()) {
4843
5477
  process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
@@ -4845,11 +5479,11 @@ async function main() {
4845
5479
  const { spawn: spawn2 } = await import("child_process");
4846
5480
  const { fileURLToPath: fileURLToPath3 } = await import("url");
4847
5481
  const thisFile = fileURLToPath3(import.meta.url);
4848
- const backfillPath = path14.resolve(path14.dirname(thisFile), "backfill-vectors.js");
4849
- if (existsSync13(backfillPath)) {
5482
+ const backfillPath = path15.resolve(path15.dirname(thisFile), "backfill-vectors.js");
5483
+ if (existsSync14(backfillPath)) {
4850
5484
  const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4851
- const bLogPath = path14.join(exeDir2, "workers.log");
4852
- mkdirSync7(path14.dirname(bLogPath), { recursive: true });
5485
+ const bLogPath = path15.join(exeDir2, "workers.log");
5486
+ mkdirSync8(path15.dirname(bLogPath), { recursive: true });
4853
5487
  const bLogFd = openSync3(bLogPath, "a");
4854
5488
  const child = spawn2(process.execPath, [backfillPath], {
4855
5489
  detached: true,