@askexenow/exe-os 0.8.83 → 0.8.86

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 (103) 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 +154 -21
  5. package/dist/bin/cli.js +14678 -12676
  6. package/dist/bin/exe-agent-config.js +242 -0
  7. package/dist/bin/exe-agent.js +100 -91
  8. package/dist/bin/exe-assign.js +1003 -854
  9. package/dist/bin/exe-boot.js +1420 -485
  10. package/dist/bin/exe-call.js +10 -0
  11. package/dist/bin/exe-cloud.js +29 -6
  12. package/dist/bin/exe-dispatch.js +572 -271
  13. package/dist/bin/exe-doctor.js +403 -6
  14. package/dist/bin/exe-export-behaviors.js +175 -72
  15. package/dist/bin/exe-forget.js +102 -3
  16. package/dist/bin/exe-gateway.js +796 -292
  17. package/dist/bin/exe-healthcheck.js +134 -1
  18. package/dist/bin/exe-heartbeat.js +172 -36
  19. package/dist/bin/exe-kill.js +175 -72
  20. package/dist/bin/exe-launch-agent.js +189 -76
  21. package/dist/bin/exe-link.js +927 -82
  22. package/dist/bin/exe-new-employee.js +60 -8
  23. package/dist/bin/exe-pending-messages.js +151 -19
  24. package/dist/bin/exe-pending-notifications.js +97 -2
  25. package/dist/bin/exe-pending-reviews.js +155 -22
  26. package/dist/bin/exe-rename.js +564 -23
  27. package/dist/bin/exe-review.js +231 -73
  28. package/dist/bin/exe-search.js +995 -228
  29. package/dist/bin/exe-session-cleanup.js +4930 -1664
  30. package/dist/bin/exe-settings.js +20 -5
  31. package/dist/bin/exe-start-codex.js +2598 -0
  32. package/dist/bin/exe-start.sh +15 -3
  33. package/dist/bin/exe-status.js +154 -21
  34. package/dist/bin/exe-team.js +97 -2
  35. package/dist/bin/git-sweep.js +1180 -363
  36. package/dist/bin/graph-backfill.js +175 -72
  37. package/dist/bin/graph-export.js +175 -72
  38. package/dist/bin/install.js +60 -7
  39. package/dist/bin/list-providers.js +1 -0
  40. package/dist/bin/scan-tasks.js +1185 -367
  41. package/dist/bin/setup.js +914 -270
  42. package/dist/bin/shard-migrate.js +175 -72
  43. package/dist/bin/update.js +1 -0
  44. package/dist/bin/wiki-sync.js +175 -72
  45. package/dist/gateway/index.js +792 -285
  46. package/dist/hooks/bug-report-worker.js +445 -135
  47. package/dist/hooks/commit-complete.js +1178 -361
  48. package/dist/hooks/error-recall.js +994 -228
  49. package/dist/hooks/ingest-worker.js +1799 -1234
  50. package/dist/hooks/ingest.js +3 -0
  51. package/dist/hooks/instructions-loaded.js +707 -97
  52. package/dist/hooks/notification.js +699 -89
  53. package/dist/hooks/post-compact.js +757 -109
  54. package/dist/hooks/pre-compact.js +1061 -244
  55. package/dist/hooks/pre-tool-use.js +787 -130
  56. package/dist/hooks/prompt-ingest-worker.js +242 -101
  57. package/dist/hooks/prompt-submit.js +1121 -299
  58. package/dist/hooks/response-ingest-worker.js +242 -101
  59. package/dist/hooks/session-end.js +4063 -397
  60. package/dist/hooks/session-start.js +1071 -254
  61. package/dist/hooks/stop.js +768 -120
  62. package/dist/hooks/subagent-stop.js +757 -109
  63. package/dist/hooks/summary-worker.js +1706 -1011
  64. package/dist/index.js +1821 -1098
  65. package/dist/lib/agent-config.js +167 -0
  66. package/dist/lib/cloud-sync.js +932 -88
  67. package/dist/lib/consolidation.js +2 -1
  68. package/dist/lib/database.js +642 -87
  69. package/dist/lib/db-daemon-client.js +503 -0
  70. package/dist/lib/device-registry.js +547 -7
  71. package/dist/lib/embedder.js +14 -28
  72. package/dist/lib/employee-templates.js +84 -74
  73. package/dist/lib/employees.js +9 -0
  74. package/dist/lib/exe-daemon-client.js +16 -29
  75. package/dist/lib/exe-daemon.js +2733 -1575
  76. package/dist/lib/hybrid-search.js +995 -228
  77. package/dist/lib/identity.js +87 -67
  78. package/dist/lib/keychain.js +9 -1
  79. package/dist/lib/messaging.js +103 -40
  80. package/dist/lib/reminders.js +91 -74
  81. package/dist/lib/runtime-table.js +16 -0
  82. package/dist/lib/schedules.js +96 -2
  83. package/dist/lib/session-wrappers.js +22 -0
  84. package/dist/lib/skill-learning.js +103 -85
  85. package/dist/lib/store.js +234 -73
  86. package/dist/lib/tasks.js +348 -134
  87. package/dist/lib/tmux-routing.js +422 -208
  88. package/dist/lib/token-spend.js +273 -0
  89. package/dist/lib/ws-client.js +11 -0
  90. package/dist/mcp/server.js +5742 -696
  91. package/dist/mcp/tools/complete-reminder.js +94 -77
  92. package/dist/mcp/tools/create-reminder.js +94 -77
  93. package/dist/mcp/tools/create-task.js +375 -152
  94. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  95. package/dist/mcp/tools/list-reminders.js +94 -77
  96. package/dist/mcp/tools/list-tasks.js +99 -31
  97. package/dist/mcp/tools/send-message.js +108 -45
  98. package/dist/mcp/tools/update-task.js +162 -77
  99. package/dist/runtime/index.js +1075 -258
  100. package/dist/tui/App.js +1333 -506
  101. package/package.json +6 -1
  102. package/src/commands/exe/agent-config.md +27 -0
  103. package/src/commands/exe/cc-doctor.md +10 -0
@@ -428,6 +428,539 @@ var init_employees = __esm({
428
428
  }
429
429
  });
430
430
 
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;
443
+ }
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
+ }
461
+ }
462
+ }
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
+ }
484
+ }
485
+ }
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);
492
+ }
493
+ return null;
494
+ }
495
+ function spawnDaemon() {
496
+ const pkgRoot = findPackageRoot();
497
+ if (!pkgRoot) {
498
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
499
+ return;
500
+ }
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;
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";
512
+ try {
513
+ stderrFd = openSync(logPath, "a");
514
+ } catch {
515
+ }
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
+ }
535
+ }
536
+ }
537
+ function acquireSpawnLock() {
538
+ try {
539
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
540
+ closeSync(fd);
541
+ return true;
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;
560
+ }
561
+ }
562
+ function releaseSpawnLock() {
563
+ try {
564
+ unlinkSync2(SPAWN_LOCK_PATH);
565
+ } catch {
566
+ }
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);
599
+ });
600
+ socket.on("error", () => {
601
+ clearTimeout(connectTimeout);
602
+ resolve(false);
603
+ });
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
+ }
616
+ }
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);
623
+ }
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;
655
+ }
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
+ }
671
+ }
672
+ if (_socket) {
673
+ _socket.destroy();
674
+ _socket = null;
675
+ }
676
+ _connected = false;
677
+ _buffer = "";
678
+ try {
679
+ unlinkSync2(PID_PATH);
680
+ } catch {
681
+ }
682
+ try {
683
+ unlinkSync2(SOCKET_PATH);
684
+ } catch {
685
+ }
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
+ }
706
+ }
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
+ `);
725
+ }
726
+ return null;
727
+ }
728
+ function disconnectClient() {
729
+ if (_socket) {
730
+ _socket.destroy();
731
+ _socket = null;
732
+ }
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" });
739
+ }
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
+
431
964
  // src/lib/database.ts
432
965
  var database_exports = {};
433
966
  __export(database_exports, {
@@ -436,6 +969,7 @@ __export(database_exports, {
436
969
  ensureSchema: () => ensureSchema,
437
970
  getClient: () => getClient,
438
971
  getRawClient: () => getRawClient,
972
+ initDaemonClient: () => initDaemonClient,
439
973
  initDatabase: () => initDatabase,
440
974
  initTurso: () => initTurso,
441
975
  isInitialized: () => isInitialized
@@ -463,8 +997,27 @@ function getClient() {
463
997
  if (!_resilientClient) {
464
998
  throw new Error("Database client not initialized. Call initDatabase() first.");
465
999
  }
1000
+ if (process.env.EXE_IS_DAEMON === "1") {
1001
+ return _resilientClient;
1002
+ }
1003
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1004
+ return _daemonClient;
1005
+ }
466
1006
  return _resilientClient;
467
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
+ }
468
1021
  function getRawClient() {
469
1022
  if (!_client) {
470
1023
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -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,6 +1766,33 @@ async function ensureSchema() {
1184
1766
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1185
1767
  ON conversations(channel_id);
1186
1768
  `);
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
+ }
1187
1796
  try {
1188
1797
  await client.execute({
1189
1798
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -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
 
@@ -2158,17 +2802,53 @@ var init_provider_table = __esm({
2158
2802
  }
2159
2803
  });
2160
2804
 
2805
+ // src/lib/runtime-table.ts
2806
+ var RUNTIME_TABLE;
2807
+ var init_runtime_table = __esm({
2808
+ "src/lib/runtime-table.ts"() {
2809
+ "use strict";
2810
+ RUNTIME_TABLE = {
2811
+ codex: {
2812
+ binary: "codex",
2813
+ launchMode: "exec",
2814
+ autoApproveFlag: "--full-auto",
2815
+ inlineFlag: "--no-alt-screen",
2816
+ apiKeyEnv: "OPENAI_API_KEY",
2817
+ defaultModel: "gpt-5.4"
2818
+ }
2819
+ };
2820
+ }
2821
+ });
2822
+
2823
+ // src/lib/agent-config.ts
2824
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
2825
+ import path8 from "path";
2826
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
2827
+ var init_agent_config = __esm({
2828
+ "src/lib/agent-config.ts"() {
2829
+ "use strict";
2830
+ init_config();
2831
+ init_runtime_table();
2832
+ AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
2833
+ DEFAULT_MODELS = {
2834
+ claude: "claude-opus-4",
2835
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
2836
+ opencode: "minimax-m2.7"
2837
+ };
2838
+ }
2839
+ });
2840
+
2161
2841
  // 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";
2842
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
2843
+ import path9 from "path";
2164
2844
  import os6 from "os";
2165
2845
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2166
2846
  var init_intercom_queue = __esm({
2167
2847
  "src/lib/intercom-queue.ts"() {
2168
2848
  "use strict";
2169
- QUEUE_PATH = path7.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2849
+ QUEUE_PATH = path9.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2170
2850
  TTL_MS = 60 * 60 * 1e3;
2171
- INTERCOM_LOG = path7.join(os6.homedir(), ".exe-os", "intercom.log");
2851
+ INTERCOM_LOG = path9.join(os6.homedir(), ".exe-os", "intercom.log");
2172
2852
  }
2173
2853
  });
2174
2854
 
@@ -2189,923 +2869,580 @@ __export(license_exports, {
2189
2869
  stopLicenseRevalidation: () => stopLicenseRevalidation,
2190
2870
  validateLicense: () => validateLicense
2191
2871
  });
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";
2872
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2873
+ import { randomUUID as randomUUID3 } from "crypto";
2874
+ import path10 from "path";
2195
2875
  import { jwtVerify, importSPKI } from "jose";
2196
2876
  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
- );
2877
+ try {
2878
+ return await fetch(url, init);
2879
+ } catch {
2880
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
2881
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
2467
2882
  }
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;
2883
+ }
2884
+ function loadDeviceId() {
2885
+ const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
2472
2886
  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
- }
2887
+ if (existsSync9(deviceJsonPath)) {
2888
+ const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
2889
+ if (data.deviceId) return data.deviceId;
2498
2890
  }
2499
2891
  } catch {
2500
2892
  }
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 {
2893
+ try {
2894
+ if (existsSync9(DEVICE_ID_PATH)) {
2895
+ const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
2896
+ if (id2) return id2;
2514
2897
  }
2515
- }, intervalMs);
2516
- if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
2517
- _revalTimer.unref();
2898
+ } catch {
2518
2899
  }
2900
+ const id = randomUUID3();
2901
+ mkdirSync4(EXE_AI_DIR, { recursive: true });
2902
+ writeFileSync4(DEVICE_ID_PATH, id, "utf8");
2903
+ return id;
2519
2904
  }
2520
- function stopLicenseRevalidation() {
2521
- if (_revalTimer) {
2522
- clearInterval(_revalTimer);
2523
- _revalTimer = null;
2905
+ function loadLicense() {
2906
+ try {
2907
+ if (!existsSync9(LICENSE_PATH)) return null;
2908
+ return readFileSync7(LICENSE_PATH, "utf8").trim();
2909
+ } catch {
2910
+ return null;
2524
2911
  }
2525
2912
  }
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: 5e3 },
2543
- pro: { devices: 3, employees: 5, memories: 1e5 },
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: 5e3
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() {
2913
+ function saveLicense(apiKey) {
2914
+ mkdirSync4(EXE_AI_DIR, { recursive: true });
2915
+ writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2916
+ }
2917
+ async function verifyLicenseJwt(token) {
2576
2918
  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());
2919
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
2920
+ const { payload } = await jwtVerify(token, key, {
2921
+ algorithms: [LICENSE_JWT_ALG]
2922
+ });
2583
2923
  const plan = payload.plan ?? "free";
2924
+ const email = payload.sub ?? "";
2584
2925
  const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2585
2926
  return {
2586
2927
  valid: true,
2587
2928
  plan,
2588
- email: payload.sub ?? "",
2929
+ email,
2589
2930
  expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
2590
2931
  deviceLimit: limits.devices,
2591
2932
  employeeLimit: limits.employees,
2592
2933
  memoryLimit: limits.memories
2593
2934
  };
2594
2935
  } 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
- );
2936
+ return null;
2637
2937
  }
2638
2938
  }
2639
- function assertEmployeeLimitSync(rosterPath) {
2640
- const license = getLicenseSync();
2641
- if (license.employeeLimit < 0) return;
2642
- const filePath = rosterPath ?? EMPLOYEES_PATH;
2643
- let count = 0;
2939
+ async function getCachedLicense() {
2644
2940
  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
- }
2941
+ if (!existsSync9(CACHE_PATH)) return null;
2942
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
2943
+ if (!raw.token || typeof raw.token !== "string") return null;
2944
+ return await verifyLicenseJwt(raw.token);
2650
2945
  } 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
- );
2667
- }
2668
- }
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");
2946
+ return null;
2684
2947
  }
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
2948
  }
2701
- function getParentExe(sessionKey) {
2949
+ function readCachedToken() {
2702
2950
  try {
2703
- const data = JSON.parse(readFileSync7(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2704
- return data.parentExe || null;
2951
+ if (!existsSync9(CACHE_PATH)) return null;
2952
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
2953
+ return typeof raw.token === "string" ? raw.token : null;
2705
2954
  } catch {
2706
2955
  return null;
2707
2956
  }
2708
2957
  }
2709
- function resolveExeSession() {
2710
- const mySession = getMySession();
2711
- if (!mySession) return null;
2958
+ function getRawCachedPlan() {
2712
2959
  try {
2713
- const key = getSessionKey();
2714
- const parentExe = getParentExe(key);
2715
- if (parentExe) {
2716
- return extractRootExe(parentExe) ?? parentExe;
2717
- }
2960
+ const token = readCachedToken();
2961
+ if (!token) return null;
2962
+ const parts = token.split(".");
2963
+ if (parts.length !== 3) return null;
2964
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2965
+ const plan = payload.plan ?? "free";
2966
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2967
+ process.stderr.write(
2968
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
2969
+ `
2970
+ );
2971
+ return {
2972
+ valid: true,
2973
+ plan,
2974
+ email: payload.sub ?? "",
2975
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
2976
+ deviceLimit: limits.devices,
2977
+ employeeLimit: limits.employees,
2978
+ memoryLimit: limits.memories
2979
+ };
2718
2980
  } catch {
2981
+ return null;
2719
2982
  }
2720
- return extractRootExe(mySession) ?? mySession;
2721
2983
  }
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() {
2984
+ function cacheResponse(token) {
2745
2985
  try {
2746
- return resolveExeSession();
2986
+ writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
2747
2987
  } catch {
2748
- return null;
2749
2988
  }
2750
2989
  }
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);
2990
+ async function validateLicense(apiKey, deviceId) {
2991
+ const did = deviceId ?? loadDeviceId();
2992
+ try {
2993
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2994
+ method: "POST",
2995
+ headers: { "Content-Type": "application/json" },
2996
+ body: JSON.stringify({ apiKey, deviceId: did }),
2997
+ signal: AbortSignal.timeout(1e4)
2998
+ });
2999
+ if (res.ok) {
3000
+ const data = await res.json();
3001
+ if (data.error === "device_limit_exceeded") {
3002
+ const cached2 = await getCachedLicense();
3003
+ if (cached2) return cached2;
3004
+ const raw2 = getRawCachedPlan();
3005
+ if (raw2) return { ...raw2, valid: false };
3006
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
2792
3007
  }
2793
- } catch {
3008
+ if (data.token) {
3009
+ cacheResponse(data.token);
3010
+ const verified = await verifyLicenseJwt(data.token);
3011
+ if (verified) return verified;
3012
+ }
3013
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3014
+ return {
3015
+ valid: data.valid,
3016
+ plan: data.plan,
3017
+ email: data.email,
3018
+ expiresAt: data.expiresAt,
3019
+ deviceLimit: limits.devices,
3020
+ employeeLimit: limits.employees,
3021
+ memoryLimit: limits.memories
3022
+ };
2794
3023
  }
3024
+ const cached = await getCachedLicense();
3025
+ if (cached) return cached;
3026
+ const raw = getRawCachedPlan();
3027
+ if (raw) return raw;
3028
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
3029
+ } catch {
3030
+ const cached = await getCachedLicense();
3031
+ if (cached) return cached;
3032
+ const rawFallback = getRawCachedPlan();
3033
+ if (rawFallback) return rawFallback;
3034
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
2795
3035
  }
2796
3036
  }
2797
- function cleanupStaleFiles() {
2798
- if (existsSync10(PID_PATH)) {
3037
+ function getCacheAgeMs() {
3038
+ try {
3039
+ const { statSync: statSync2 } = __require("fs");
3040
+ const s = statSync2(CACHE_PATH);
3041
+ return Date.now() - s.mtimeMs;
3042
+ } catch {
3043
+ return Infinity;
3044
+ }
3045
+ }
3046
+ async function checkLicense() {
3047
+ let key = loadLicense();
3048
+ if (!key) {
2799
3049
  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 {
3050
+ const configPath = path10.join(EXE_AI_DIR, "config.json");
3051
+ if (existsSync9(configPath)) {
3052
+ const raw = JSON.parse(readFileSync7(configPath, "utf8"));
3053
+ const cloud = raw.cloud;
3054
+ if (cloud?.apiKey) {
3055
+ key = cloud.apiKey;
3056
+ saveLicense(key);
2806
3057
  }
2807
3058
  }
2808
3059
  } catch {
2809
3060
  }
2810
- try {
2811
- unlinkSync3(PID_PATH);
2812
- } catch {
2813
- }
2814
- try {
2815
- unlinkSync3(SOCKET_PATH);
2816
- } catch {
2817
- }
2818
3061
  }
3062
+ if (!key) return FREE_LICENSE;
3063
+ const cached = await getCachedLicense();
3064
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
3065
+ const deviceId = loadDeviceId();
3066
+ return validateLicense(key, deviceId);
2819
3067
  }
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);
3068
+ function isFeatureAllowed(license, feature) {
3069
+ switch (feature) {
3070
+ case "cloud_sync":
3071
+ case "external_agents":
3072
+ case "wiki":
3073
+ return license.plan !== "free";
3074
+ case "unlimited_employees":
3075
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
2826
3076
  }
2827
- return null;
2828
3077
  }
2829
- function spawnDaemon() {
2830
- const pkgRoot = findPackageRoot();
2831
- if (!pkgRoot) {
2832
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
2833
- return;
3078
+ function mirrorLicenseKey(apiKey) {
3079
+ const trimmed = apiKey.trim();
3080
+ if (!trimmed) return;
3081
+ saveLicense(trimmed);
3082
+ }
3083
+ async function assertVpsLicense(opts) {
3084
+ const env = opts?.env ?? process.env;
3085
+ const inProduction = env.NODE_ENV === "production";
3086
+ if (!opts?.force && !inProduction) {
3087
+ return { ...FREE_LICENSE, plan: "free" };
2834
3088
  }
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;
3089
+ const envKey = env.EXE_LICENSE_KEY?.trim();
3090
+ if (envKey) {
3091
+ saveLicense(envKey);
2840
3092
  }
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";
3093
+ const apiKey = envKey ?? loadLicense();
3094
+ if (!apiKey) {
3095
+ throw new Error(
3096
+ "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."
3097
+ );
3098
+ }
3099
+ const deviceId = loadDeviceId();
3100
+ let backendResponse = null;
3101
+ let explicitRejection = false;
3102
+ let transientFailure = false;
2846
3103
  try {
2847
- stderrFd = openSync(logPath, "a");
3104
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3105
+ method: "POST",
3106
+ headers: { "Content-Type": "application/json" },
3107
+ body: JSON.stringify({ apiKey, deviceId }),
3108
+ signal: AbortSignal.timeout(1e4)
3109
+ });
3110
+ if (res.ok) {
3111
+ backendResponse = await res.json();
3112
+ if (!backendResponse.valid) explicitRejection = true;
3113
+ } else if (res.status === 401 || res.status === 403) {
3114
+ explicitRejection = true;
3115
+ } else {
3116
+ transientFailure = true;
3117
+ }
2848
3118
  } catch {
3119
+ transientFailure = true;
2849
3120
  }
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
3121
+ if (backendResponse?.valid) {
3122
+ if (backendResponse.token) {
3123
+ cacheResponse(backendResponse.token);
3124
+ const verified = await verifyLicenseJwt(backendResponse.token);
3125
+ if (verified) return verified;
2861
3126
  }
2862
- });
2863
- child.unref();
2864
- if (typeof stderrFd === "number") {
2865
- try {
2866
- closeSync(stderrFd);
2867
- } catch {
3127
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
3128
+ return {
3129
+ valid: true,
3130
+ plan: backendResponse.plan,
3131
+ email: backendResponse.email,
3132
+ expiresAt: backendResponse.expiresAt,
3133
+ deviceLimit: limits.devices,
3134
+ employeeLimit: limits.employees,
3135
+ memoryLimit: limits.memories
3136
+ };
3137
+ }
3138
+ if (explicitRejection) {
3139
+ throw new Error(
3140
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
3141
+ );
3142
+ }
3143
+ if (!transientFailure) {
3144
+ throw new Error(
3145
+ "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
3146
+ );
3147
+ }
3148
+ const fresh = await getCachedLicense();
3149
+ if (fresh && fresh.valid) return fresh;
3150
+ const graceDays = opts?.offlineGraceDays ?? 7;
3151
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
3152
+ try {
3153
+ const token = readCachedToken();
3154
+ if (token) {
3155
+ const payloadB64 = token.split(".")[1];
3156
+ if (payloadB64) {
3157
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
3158
+ const expMs = (payload.exp ?? 0) * 1e3;
3159
+ if (Date.now() < expMs + graceMs) {
3160
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
3161
+ const { payload: verified } = await jwtVerify(token, key, {
3162
+ algorithms: [LICENSE_JWT_ALG],
3163
+ clockTolerance: graceDays * 24 * 60 * 60
3164
+ });
3165
+ const plan = verified.plan ?? "free";
3166
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3167
+ return {
3168
+ valid: true,
3169
+ plan,
3170
+ email: verified.sub ?? "",
3171
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
3172
+ deviceLimit: limits.devices,
3173
+ employeeLimit: limits.employees,
3174
+ memoryLimit: limits.memories
3175
+ };
3176
+ }
3177
+ }
2868
3178
  }
3179
+ } catch {
2869
3180
  }
3181
+ throw new Error(
3182
+ `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.`
3183
+ );
2870
3184
  }
2871
- function acquireSpawnLock() {
2872
- try {
2873
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
2874
- closeSync(fd);
2875
- return true;
2876
- } catch {
3185
+ function startLicenseRevalidation(intervalMs = 36e5) {
3186
+ if (_revalTimer) return;
3187
+ _revalTimer = setInterval(async () => {
2877
3188
  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
- }
3189
+ const license = await checkLicense();
3190
+ if (!license.valid) {
3191
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
2890
3192
  }
2891
3193
  } catch {
2892
3194
  }
2893
- return false;
3195
+ }, intervalMs);
3196
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
3197
+ _revalTimer.unref();
2894
3198
  }
2895
3199
  }
2896
- function releaseSpawnLock() {
2897
- try {
2898
- unlinkSync3(SPAWN_LOCK_PATH);
2899
- } catch {
3200
+ function stopLicenseRevalidation() {
3201
+ if (_revalTimer) {
3202
+ clearInterval(_revalTimer);
3203
+ _revalTimer = null;
2900
3204
  }
2901
3205
  }
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
- }
3206
+ 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;
3207
+ var init_license = __esm({
3208
+ "src/lib/license.ts"() {
3209
+ "use strict";
3210
+ init_config();
3211
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3212
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3213
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3214
+ API_BASE = "https://askexe.com/cloud";
3215
+ RETRY_DELAY_MS = 500;
3216
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
3217
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3218
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
3219
+ -----END PUBLIC KEY-----`;
3220
+ LICENSE_JWT_ALG = "ES256";
3221
+ PLAN_LIMITS = {
3222
+ free: { devices: 1, employees: 1, memories: 5e3 },
3223
+ pro: { devices: 3, employees: 5, memories: 1e5 },
3224
+ team: { devices: 10, employees: 20, memories: 1e6 },
3225
+ agency: { devices: 50, employees: 100, memories: 1e7 },
3226
+ enterprise: { devices: -1, employees: -1, memories: -1 }
3227
+ };
3228
+ FREE_LICENSE = {
3229
+ valid: true,
3230
+ plan: "free",
3231
+ email: "",
3232
+ expiresAt: null,
3233
+ deviceLimit: 1,
3234
+ employeeLimit: 1,
3235
+ memoryLimit: 5e3
3236
+ };
3237
+ CACHE_MAX_AGE_MS = 36e5;
3238
+ _revalTimer = null;
2950
3239
  }
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);
3240
+ });
3241
+
3242
+ // src/lib/plan-limits.ts
3243
+ var plan_limits_exports = {};
3244
+ __export(plan_limits_exports, {
3245
+ PlanLimitError: () => PlanLimitError,
3246
+ assertEmployeeLimit: () => assertEmployeeLimit,
3247
+ assertEmployeeLimitSync: () => assertEmployeeLimitSync,
3248
+ assertFeature: () => assertFeature,
3249
+ assertMemoryLimit: () => assertMemoryLimit,
3250
+ countActiveMemories: () => countActiveMemories,
3251
+ getLicenseSync: () => getLicenseSync
3252
+ });
3253
+ import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3254
+ import path11 from "path";
3255
+ function getLicenseSync() {
3256
+ try {
3257
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3258
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3259
+ if (!raw.token || typeof raw.token !== "string") return freeLicense();
3260
+ const parts = raw.token.split(".");
3261
+ if (parts.length !== 3) return freeLicense();
3262
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3263
+ const plan = payload.plan ?? "free";
3264
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3265
+ return {
3266
+ valid: true,
3267
+ plan,
3268
+ email: payload.sub ?? "",
3269
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3270
+ deviceLimit: limits.devices,
3271
+ employeeLimit: limits.employees,
3272
+ memoryLimit: limits.memories
3273
+ };
3274
+ } catch {
3275
+ return freeLicense();
2957
3276
  }
2958
- return false;
2959
3277
  }
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
- });
3278
+ function freeLicense() {
3279
+ const limits = PLAN_LIMITS.free;
3280
+ return {
3281
+ valid: true,
3282
+ plan: "free",
3283
+ email: "",
3284
+ expiresAt: null,
3285
+ deviceLimit: limits.devices,
3286
+ employeeLimit: limits.employees,
3287
+ memoryLimit: limits.memories
3288
+ };
2980
3289
  }
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
- });
3290
+ async function countActiveMemories() {
3291
+ if (!isInitialized()) return 0;
3292
+ const client = getClient();
3293
+ const result = await client.execute(
3294
+ "SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
3295
+ );
3296
+ const row = result.rows[0];
3297
+ return Number(row?.cnt ?? 0);
3007
3298
  }
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
- }
3299
+ async function assertMemoryLimit() {
3300
+ const license = await checkLicense();
3301
+ if (license.memoryLimit < 0) return;
3302
+ const count = await countActiveMemories();
3303
+ if (count >= license.memoryLimit) {
3304
+ throw new PlanLimitError(
3305
+ `Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
3306
+ );
3021
3307
  }
3022
- if (_socket) {
3023
- _socket.destroy();
3024
- _socket = null;
3308
+ }
3309
+ async function assertEmployeeLimit(license, rosterPath) {
3310
+ const lic = license ?? await checkLicense();
3311
+ if (lic.employeeLimit < 0) return;
3312
+ const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
3313
+ if (employees.length >= lic.employeeLimit) {
3314
+ throw new PlanLimitError(
3315
+ `Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
3316
+ );
3025
3317
  }
3026
- _connected = false;
3027
- _buffer = "";
3318
+ }
3319
+ function assertEmployeeLimitSync(rosterPath) {
3320
+ const license = getLicenseSync();
3321
+ if (license.employeeLimit < 0) return;
3322
+ const filePath = rosterPath ?? EMPLOYEES_PATH;
3323
+ let count = 0;
3028
3324
  try {
3029
- unlinkSync3(PID_PATH);
3325
+ if (existsSync10(filePath)) {
3326
+ const raw = readFileSync8(filePath, "utf8");
3327
+ const employees = JSON.parse(raw);
3328
+ count = Array.isArray(employees) ? employees.length : 0;
3329
+ }
3030
3330
  } catch {
3331
+ throw new PlanLimitError(
3332
+ `Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
3333
+ );
3031
3334
  }
3032
- try {
3033
- unlinkSync3(SOCKET_PATH);
3034
- } catch {
3335
+ if (count >= license.employeeLimit) {
3336
+ throw new PlanLimitError(
3337
+ `Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
3338
+ );
3035
3339
  }
3036
- spawnDaemon();
3037
3340
  }
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);
3341
+ async function assertFeature(feature) {
3342
+ const license = await checkLicense();
3343
+ if (!isFeatureAllowed(license, feature)) {
3344
+ throw new PlanLimitError(
3345
+ `Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
3346
+ );
3347
+ }
3348
+ }
3349
+ var PlanLimitError, CACHE_PATH2;
3350
+ var init_plan_limits = __esm({
3351
+ "src/lib/plan-limits.ts"() {
3352
+ "use strict";
3353
+ init_database();
3354
+ init_employees();
3355
+ init_license();
3356
+ init_config();
3357
+ PlanLimitError = class extends Error {
3358
+ constructor(message) {
3359
+ super(message);
3360
+ this.name = "PlanLimitError";
3053
3361
  }
3054
- if (!_connected) return null;
3055
- }
3362
+ };
3363
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
3056
3364
  }
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);
3365
+ });
3366
+
3367
+ // src/lib/tmux-routing.ts
3368
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
3369
+ import path12 from "path";
3370
+ import os7 from "os";
3371
+ import { fileURLToPath as fileURLToPath2 } from "url";
3372
+ function getMySession() {
3373
+ return getTransport().getMySession();
3374
+ }
3375
+ function extractRootExe(name) {
3376
+ if (!name) return null;
3377
+ if (!name.includes("-")) return name;
3378
+ const parts = name.split("-").filter(Boolean);
3379
+ return parts.length > 0 ? parts[parts.length - 1] : null;
3380
+ }
3381
+ function getParentExe(sessionKey) {
3382
+ try {
3383
+ const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3384
+ return data.parentExe || null;
3385
+ } catch {
3386
+ return null;
3387
+ }
3388
+ }
3389
+ function resolveExeSession() {
3390
+ const mySession = getMySession();
3391
+ if (!mySession) return null;
3392
+ try {
3393
+ const key = getSessionKey();
3394
+ const parentExe = getParentExe(key);
3395
+ if (parentExe) {
3396
+ return extractRootExe(parentExe) ?? parentExe;
3069
3397
  }
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
- `);
3398
+ } catch {
3075
3399
  }
3076
- return null;
3400
+ return extractRootExe(mySession) ?? mySession;
3077
3401
  }
3078
- function disconnectClient() {
3079
- if (_socket) {
3080
- _socket.destroy();
3081
- _socket = null;
3402
+ var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3403
+ var init_tmux_routing = __esm({
3404
+ "src/lib/tmux-routing.ts"() {
3405
+ "use strict";
3406
+ init_session_registry();
3407
+ init_session_key();
3408
+ init_transport();
3409
+ init_cc_agent_support();
3410
+ init_mcp_prefix();
3411
+ init_provider_table();
3412
+ init_agent_config();
3413
+ init_runtime_table();
3414
+ init_intercom_queue();
3415
+ init_plan_limits();
3416
+ init_employees();
3417
+ SPAWN_LOCK_DIR = path12.join(os7.homedir(), ".exe-os", "spawn-locks");
3418
+ SESSION_CACHE = path12.join(os7.homedir(), ".exe-os", "session-cache");
3419
+ INTERCOM_LOG2 = path12.join(os7.homedir(), ".exe-os", "intercom.log");
3420
+ DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
3421
+ DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3082
3422
  }
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" });
3423
+ });
3424
+
3425
+ // src/lib/task-scope.ts
3426
+ function getCurrentSessionScope() {
3427
+ try {
3428
+ return resolveExeSession();
3429
+ } catch {
3430
+ return null;
3089
3431
  }
3090
3432
  }
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"() {
3433
+ function sessionScopeFilter(sessionScope, tableAlias) {
3434
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3435
+ if (!scope) return { sql: "", args: [] };
3436
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3437
+ return {
3438
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3439
+ args: [scope]
3440
+ };
3441
+ }
3442
+ var init_task_scope = __esm({
3443
+ "src/lib/task-scope.ts"() {
3094
3444
  "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;
3445
+ init_tmux_routing();
3109
3446
  }
3110
3447
  });
3111
3448
 
@@ -3146,10 +3483,10 @@ async function disposeEmbedder() {
3146
3483
  async function embedDirect(text) {
3147
3484
  const llamaCpp = await import("node-llama-cpp");
3148
3485
  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)) {
3486
+ const { existsSync: existsSync16 } = await import("fs");
3487
+ const path17 = await import("path");
3488
+ const modelPath = path17.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3489
+ if (!existsSync16(modelPath)) {
3153
3490
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3154
3491
  }
3155
3492
  const llama = await llamaCpp.getLlama();
@@ -3187,14 +3524,14 @@ __export(worker_gate_exports, {
3187
3524
  tryAcquireBackfillLock: () => tryAcquireBackfillLock,
3188
3525
  tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
3189
3526
  });
3190
- import { readdirSync as readdirSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, mkdirSync as mkdirSync5, existsSync as existsSync11 } from "fs";
3191
- import path12 from "path";
3527
+ import { readdirSync as readdirSync3, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync12 } from "fs";
3528
+ import path13 from "path";
3192
3529
  function tryAcquireWorkerSlot() {
3193
3530
  try {
3194
- mkdirSync5(WORKER_PID_DIR, { recursive: true });
3531
+ mkdirSync6(WORKER_PID_DIR, { recursive: true });
3195
3532
  const reservationId = `res-${process.pid}-${Date.now()}`;
3196
- const reservationPath = path12.join(WORKER_PID_DIR, `${reservationId}.pid`);
3197
- writeFileSync5(reservationPath, String(process.pid));
3533
+ const reservationPath = path13.join(WORKER_PID_DIR, `${reservationId}.pid`);
3534
+ writeFileSync6(reservationPath, String(process.pid));
3198
3535
  const files = readdirSync3(WORKER_PID_DIR);
3199
3536
  let alive = 0;
3200
3537
  for (const f of files) {
@@ -3211,7 +3548,7 @@ function tryAcquireWorkerSlot() {
3211
3548
  alive++;
3212
3549
  } catch {
3213
3550
  try {
3214
- unlinkSync4(path12.join(WORKER_PID_DIR, f));
3551
+ unlinkSync4(path13.join(WORKER_PID_DIR, f));
3215
3552
  } catch {
3216
3553
  }
3217
3554
  }
@@ -3234,21 +3571,21 @@ function tryAcquireWorkerSlot() {
3234
3571
  }
3235
3572
  function registerWorkerPid(pid) {
3236
3573
  try {
3237
- mkdirSync5(WORKER_PID_DIR, { recursive: true });
3238
- writeFileSync5(path12.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3574
+ mkdirSync6(WORKER_PID_DIR, { recursive: true });
3575
+ writeFileSync6(path13.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3239
3576
  } catch {
3240
3577
  }
3241
3578
  }
3242
3579
  function cleanupWorkerPid() {
3243
3580
  try {
3244
- unlinkSync4(path12.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3581
+ unlinkSync4(path13.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3245
3582
  } catch {
3246
3583
  }
3247
3584
  }
3248
3585
  function tryAcquireBackfillLock() {
3249
3586
  try {
3250
- mkdirSync5(WORKER_PID_DIR, { recursive: true });
3251
- if (existsSync11(BACKFILL_LOCK)) {
3587
+ mkdirSync6(WORKER_PID_DIR, { recursive: true });
3588
+ if (existsSync12(BACKFILL_LOCK)) {
3252
3589
  try {
3253
3590
  const pid = parseInt(
3254
3591
  __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
@@ -3264,7 +3601,7 @@ function tryAcquireBackfillLock() {
3264
3601
  } catch {
3265
3602
  }
3266
3603
  }
3267
- writeFileSync5(BACKFILL_LOCK, String(process.pid));
3604
+ writeFileSync6(BACKFILL_LOCK, String(process.pid));
3268
3605
  return true;
3269
3606
  } catch {
3270
3607
  return true;
@@ -3281,9 +3618,9 @@ var init_worker_gate = __esm({
3281
3618
  "src/lib/worker-gate.ts"() {
3282
3619
  "use strict";
3283
3620
  init_config();
3284
- WORKER_PID_DIR = path12.join(EXE_AI_DIR, "worker-pids");
3621
+ WORKER_PID_DIR = path13.join(EXE_AI_DIR, "worker-pids");
3285
3622
  MAX_CONCURRENT_WORKERS = 3;
3286
- BACKFILL_LOCK = path12.join(WORKER_PID_DIR, "backfill.lock");
3623
+ BACKFILL_LOCK = path13.join(WORKER_PID_DIR, "backfill.lock");
3287
3624
  }
3288
3625
  });
3289
3626
 
@@ -3366,6 +3703,232 @@ var init_compress = __esm({
3366
3703
  }
3367
3704
  });
3368
3705
 
3706
+ // src/lib/crdt-sync.ts
3707
+ var crdt_sync_exports = {};
3708
+ __export(crdt_sync_exports, {
3709
+ _setStatePath: () => _setStatePath,
3710
+ applyRemoteUpdate: () => applyRemoteUpdate,
3711
+ destroyCrdtDoc: () => destroyCrdtDoc,
3712
+ getDiffUpdate: () => getDiffUpdate,
3713
+ getFullState: () => getFullState,
3714
+ getStateVector: () => getStateVector,
3715
+ importExistingBehaviors: () => importExistingBehaviors,
3716
+ importExistingMemories: () => importExistingMemories,
3717
+ initCrdtDoc: () => initCrdtDoc,
3718
+ isCrdtSyncEnabled: () => isCrdtSyncEnabled,
3719
+ onUpdate: () => onUpdate,
3720
+ readAllBehaviors: () => readAllBehaviors,
3721
+ readAllMemories: () => readAllMemories,
3722
+ rebuildFromDb: () => rebuildFromDb
3723
+ });
3724
+ import * as Y from "yjs";
3725
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
3726
+ import path14 from "path";
3727
+ import { homedir } from "os";
3728
+ function getStatePath() {
3729
+ return _statePathOverride ?? DEFAULT_STATE_PATH;
3730
+ }
3731
+ function _setStatePath(p) {
3732
+ _statePathOverride = p;
3733
+ }
3734
+ function initCrdtDoc() {
3735
+ if (doc) return doc;
3736
+ doc = new Y.Doc();
3737
+ const sp = getStatePath();
3738
+ if (existsSync13(sp)) {
3739
+ try {
3740
+ const state = readFileSync10(sp);
3741
+ Y.applyUpdate(doc, new Uint8Array(state));
3742
+ } catch {
3743
+ console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
3744
+ try {
3745
+ unlinkSync5(sp);
3746
+ } catch {
3747
+ }
3748
+ rebuildFromDb().catch((err) => {
3749
+ console.warn("[crdt-sync] rebuild from DB failed:", err);
3750
+ });
3751
+ }
3752
+ }
3753
+ doc.on("update", () => {
3754
+ persistState();
3755
+ });
3756
+ return doc;
3757
+ }
3758
+ function getMemoriesMap() {
3759
+ const d = initCrdtDoc();
3760
+ return d.getMap("memories");
3761
+ }
3762
+ function getBehaviorsMap() {
3763
+ const d = initCrdtDoc();
3764
+ return d.getMap("behaviors");
3765
+ }
3766
+ function applyRemoteUpdate(update) {
3767
+ const d = initCrdtDoc();
3768
+ Y.applyUpdate(d, update);
3769
+ }
3770
+ function getFullState() {
3771
+ const d = initCrdtDoc();
3772
+ return Y.encodeStateAsUpdate(d);
3773
+ }
3774
+ function getDiffUpdate(remoteStateVector) {
3775
+ const d = initCrdtDoc();
3776
+ return Y.encodeStateAsUpdate(d, remoteStateVector);
3777
+ }
3778
+ function getStateVector() {
3779
+ const d = initCrdtDoc();
3780
+ return Y.encodeStateVector(d);
3781
+ }
3782
+ function importExistingMemories(memories) {
3783
+ const map = getMemoriesMap();
3784
+ const d = initCrdtDoc();
3785
+ let imported = 0;
3786
+ d.transact(() => {
3787
+ for (const mem of memories) {
3788
+ if (!mem.id) continue;
3789
+ if (map.has(mem.id)) continue;
3790
+ const entry = new Y.Map();
3791
+ entry.set("id", mem.id);
3792
+ entry.set("agent_id", mem.agent_id ?? null);
3793
+ entry.set("agent_role", mem.agent_role ?? null);
3794
+ entry.set("session_id", mem.session_id ?? null);
3795
+ entry.set("timestamp", mem.timestamp ?? null);
3796
+ entry.set("tool_name", mem.tool_name ?? null);
3797
+ entry.set("project_name", mem.project_name ?? null);
3798
+ entry.set("has_error", mem.has_error ?? 0);
3799
+ entry.set("raw_text", mem.raw_text ?? "");
3800
+ entry.set("version", mem.version ?? 0);
3801
+ entry.set("author_device_id", mem.author_device_id ?? null);
3802
+ entry.set("scope", mem.scope ?? "business");
3803
+ map.set(mem.id, entry);
3804
+ imported++;
3805
+ }
3806
+ });
3807
+ return imported;
3808
+ }
3809
+ function importExistingBehaviors(behaviors) {
3810
+ const map = getBehaviorsMap();
3811
+ const d = initCrdtDoc();
3812
+ let imported = 0;
3813
+ d.transact(() => {
3814
+ for (const beh of behaviors) {
3815
+ if (!beh.id) continue;
3816
+ if (map.has(beh.id)) continue;
3817
+ const entry = new Y.Map();
3818
+ entry.set("id", beh.id);
3819
+ entry.set("agent_id", beh.agent_id ?? null);
3820
+ entry.set("project_name", beh.project_name ?? null);
3821
+ entry.set("domain", beh.domain ?? null);
3822
+ entry.set("content", beh.content ?? null);
3823
+ entry.set("active", beh.active ?? 1);
3824
+ entry.set("priority", beh.priority ?? "p1");
3825
+ entry.set("created_at", beh.created_at ?? null);
3826
+ entry.set("updated_at", beh.updated_at ?? null);
3827
+ map.set(beh.id, entry);
3828
+ imported++;
3829
+ }
3830
+ });
3831
+ return imported;
3832
+ }
3833
+ function readAllMemories() {
3834
+ const map = getMemoriesMap();
3835
+ const records = [];
3836
+ map.forEach((entry, id) => {
3837
+ records.push({
3838
+ id,
3839
+ agent_id: entry.get("agent_id"),
3840
+ agent_role: entry.get("agent_role"),
3841
+ session_id: entry.get("session_id"),
3842
+ timestamp: entry.get("timestamp"),
3843
+ tool_name: entry.get("tool_name"),
3844
+ project_name: entry.get("project_name"),
3845
+ has_error: entry.get("has_error"),
3846
+ raw_text: entry.get("raw_text"),
3847
+ version: entry.get("version"),
3848
+ author_device_id: entry.get("author_device_id"),
3849
+ scope: entry.get("scope")
3850
+ });
3851
+ });
3852
+ return records;
3853
+ }
3854
+ function readAllBehaviors() {
3855
+ const map = getBehaviorsMap();
3856
+ const records = [];
3857
+ map.forEach((entry, id) => {
3858
+ records.push({
3859
+ id,
3860
+ agent_id: entry.get("agent_id"),
3861
+ project_name: entry.get("project_name"),
3862
+ domain: entry.get("domain"),
3863
+ content: entry.get("content"),
3864
+ active: entry.get("active"),
3865
+ priority: entry.get("priority"),
3866
+ created_at: entry.get("created_at"),
3867
+ updated_at: entry.get("updated_at")
3868
+ });
3869
+ });
3870
+ return records;
3871
+ }
3872
+ function onUpdate(callback) {
3873
+ const d = initCrdtDoc();
3874
+ const handler = (update) => callback(update);
3875
+ d.on("update", handler);
3876
+ return () => d.off("update", handler);
3877
+ }
3878
+ function persistState() {
3879
+ if (!doc) return;
3880
+ try {
3881
+ const sp = getStatePath();
3882
+ const dir = path14.dirname(sp);
3883
+ if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
3884
+ const state = Y.encodeStateAsUpdate(doc);
3885
+ writeFileSync7(sp, Buffer.from(state));
3886
+ } catch {
3887
+ }
3888
+ }
3889
+ function isCrdtSyncEnabled() {
3890
+ return process.env.EXE_CRDT_SYNC !== "0";
3891
+ }
3892
+ async function rebuildFromDb() {
3893
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3894
+ const client = getClient2();
3895
+ const result = await client.execute(
3896
+ "SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
3897
+ );
3898
+ const memories = result.rows.map((row) => ({
3899
+ id: String(row.id),
3900
+ agent_id: row.agent_id,
3901
+ agent_role: row.agent_role,
3902
+ session_id: row.session_id,
3903
+ timestamp: row.timestamp,
3904
+ tool_name: row.tool_name,
3905
+ project_name: row.project_name,
3906
+ has_error: row.has_error,
3907
+ raw_text: row.raw_text,
3908
+ version: row.version,
3909
+ author_device_id: row.author_device_id,
3910
+ scope: row.scope
3911
+ }));
3912
+ const count = importExistingMemories(memories);
3913
+ persistState();
3914
+ return count;
3915
+ }
3916
+ function destroyCrdtDoc() {
3917
+ if (doc) {
3918
+ doc.destroy();
3919
+ doc = null;
3920
+ }
3921
+ }
3922
+ var DEFAULT_STATE_PATH, _statePathOverride, doc;
3923
+ var init_crdt_sync = __esm({
3924
+ "src/lib/crdt-sync.ts"() {
3925
+ "use strict";
3926
+ DEFAULT_STATE_PATH = path14.join(homedir(), ".exe-os", "crdt-state.bin");
3927
+ _statePathOverride = null;
3928
+ doc = null;
3929
+ }
3930
+ });
3931
+
3369
3932
  // src/lib/cloud-sync.ts
3370
3933
  var cloud_sync_exports = {};
3371
3934
  __export(cloud_sync_exports, {
@@ -3394,16 +3957,16 @@ __export(cloud_sync_exports, {
3394
3957
  mergeRosterFromRemote: () => mergeRosterFromRemote,
3395
3958
  recordRosterDeletion: () => recordRosterDeletion
3396
3959
  });
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";
3960
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync14, readdirSync as readdirSync4, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
3398
3961
  import crypto3 from "crypto";
3399
- import path13 from "path";
3400
- import { homedir } from "os";
3962
+ import path15 from "path";
3963
+ import { homedir as homedir2 } from "os";
3401
3964
  function sqlSafe(v) {
3402
3965
  return v === void 0 ? null : v;
3403
3966
  }
3404
3967
  function logError(msg) {
3405
3968
  try {
3406
- const logPath = path13.join(homedir(), ".exe-os", "workers.log");
3969
+ const logPath = path15.join(homedir2(), ".exe-os", "workers.log");
3407
3970
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
3408
3971
  `);
3409
3972
  } catch {
@@ -3413,18 +3976,18 @@ async function withRosterLock(fn) {
3413
3976
  try {
3414
3977
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3415
3978
  closeSync2(fd);
3416
- writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
3979
+ writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
3417
3980
  } catch (err) {
3418
3981
  if (err.code === "EEXIST") {
3419
3982
  try {
3420
- const ts = parseInt(readFileSync9(ROSTER_LOCK_PATH, "utf-8"), 10);
3983
+ const ts = parseInt(readFileSync11(ROSTER_LOCK_PATH, "utf-8"), 10);
3421
3984
  if (Date.now() - ts < LOCK_STALE_MS) {
3422
3985
  throw new Error("Roster merge already in progress \u2014 another sync is running");
3423
3986
  }
3424
- unlinkSync5(ROSTER_LOCK_PATH);
3987
+ unlinkSync6(ROSTER_LOCK_PATH);
3425
3988
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3426
3989
  closeSync2(fd);
3427
- writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
3990
+ writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
3428
3991
  } catch (retryErr) {
3429
3992
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
3430
3993
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -3437,7 +4000,7 @@ async function withRosterLock(fn) {
3437
4000
  return await fn();
3438
4001
  } finally {
3439
4002
  try {
3440
- unlinkSync5(ROSTER_LOCK_PATH);
4003
+ unlinkSync6(ROSTER_LOCK_PATH);
3441
4004
  } catch {
3442
4005
  }
3443
4006
  }
@@ -3582,29 +4145,75 @@ async function cloudSync(config) {
3582
4145
  const pullResult = await cloudPull(lastPullVersion, config);
3583
4146
  let pulled = 0;
3584
4147
  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;
4148
+ if (isCrdtSyncEnabled()) {
4149
+ const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
4150
+ initCrdtDoc2();
4151
+ importExistingMemories2(
4152
+ pullResult.records.map((rec) => ({
4153
+ id: String(rec.id ?? ""),
4154
+ agent_id: rec.agent_id,
4155
+ agent_role: rec.agent_role,
4156
+ session_id: rec.session_id,
4157
+ timestamp: rec.timestamp,
4158
+ tool_name: rec.tool_name,
4159
+ project_name: rec.project_name,
4160
+ has_error: rec.has_error ?? 0,
4161
+ raw_text: rec.raw_text ?? "",
4162
+ version: rec.version ?? 0,
4163
+ author_device_id: rec.author_device_id,
4164
+ scope: rec.scope ?? "business"
4165
+ }))
4166
+ );
4167
+ const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
4168
+ const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
4169
+ const stmts = merged.map((rec) => ({
4170
+ sql: `INSERT OR REPLACE INTO memories
4171
+ (id, agent_id, agent_role, session_id, timestamp,
4172
+ tool_name, project_name, has_error, raw_text, version,
4173
+ author_device_id, scope)
4174
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4175
+ args: [
4176
+ sqlSafe(rec.id),
4177
+ sqlSafe(rec.agent_id),
4178
+ sqlSafe(rec.agent_role),
4179
+ sqlSafe(rec.session_id),
4180
+ sqlSafe(rec.timestamp),
4181
+ sqlSafe(rec.tool_name),
4182
+ sqlSafe(rec.project_name),
4183
+ sqlSafe(rec.has_error ?? 0),
4184
+ sqlSafe(rec.raw_text ?? ""),
4185
+ sqlSafe(rec.version ?? 0),
4186
+ sqlSafe(rec.author_device_id),
4187
+ sqlSafe(rec.scope ?? "business")
4188
+ ]
4189
+ }));
4190
+ if (stmts.length > 0) await client.batch(stmts, "write");
4191
+ pulled = pullResult.records.length;
4192
+ } else {
4193
+ const stmts = pullResult.records.map((rec) => ({
4194
+ sql: `INSERT OR REPLACE INTO memories
4195
+ (id, agent_id, agent_role, session_id, timestamp,
4196
+ tool_name, project_name, has_error, raw_text, version,
4197
+ author_device_id, scope)
4198
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4199
+ args: [
4200
+ sqlSafe(rec.id),
4201
+ sqlSafe(rec.agent_id),
4202
+ sqlSafe(rec.agent_role),
4203
+ sqlSafe(rec.session_id),
4204
+ sqlSafe(rec.timestamp),
4205
+ sqlSafe(rec.tool_name),
4206
+ sqlSafe(rec.project_name),
4207
+ sqlSafe(rec.has_error ?? 0),
4208
+ sqlSafe(rec.raw_text ?? ""),
4209
+ sqlSafe(rec.version ?? 0),
4210
+ sqlSafe(rec.author_device_id),
4211
+ sqlSafe(rec.scope ?? "business")
4212
+ ]
4213
+ }));
4214
+ await client.batch(stmts, "write");
4215
+ pulled = pullResult.records.length;
4216
+ }
3608
4217
  }
3609
4218
  if (pullResult.maxVersion > lastPullVersion) {
3610
4219
  await client.execute({
@@ -3752,8 +4361,8 @@ async function cloudSync(config) {
3752
4361
  try {
3753
4362
  const employees = await loadEmployees();
3754
4363
  rosterResult.employees = employees.length;
3755
- const idDir = path13.join(EXE_AI_DIR, "identity");
3756
- if (existsSync12(idDir)) {
4364
+ const idDir = path15.join(EXE_AI_DIR, "identity");
4365
+ if (existsSync14(idDir)) {
3757
4366
  rosterResult.identities = readdirSync4(idDir).filter((f) => f.endsWith(".md")).length;
3758
4367
  }
3759
4368
  } catch {
@@ -3774,55 +4383,63 @@ async function cloudSync(config) {
3774
4383
  function recordRosterDeletion(name) {
3775
4384
  let deletions = [];
3776
4385
  try {
3777
- if (existsSync12(ROSTER_DELETIONS_PATH)) {
3778
- deletions = JSON.parse(readFileSync9(ROSTER_DELETIONS_PATH, "utf-8"));
4386
+ if (existsSync14(ROSTER_DELETIONS_PATH)) {
4387
+ deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
3779
4388
  }
3780
4389
  } catch {
3781
4390
  }
3782
4391
  if (!deletions.includes(name)) deletions.push(name);
3783
- writeFileSync6(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
4392
+ writeFileSync8(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3784
4393
  }
3785
4394
  function consumeRosterDeletions() {
3786
4395
  try {
3787
- if (!existsSync12(ROSTER_DELETIONS_PATH)) return [];
3788
- const deletions = JSON.parse(readFileSync9(ROSTER_DELETIONS_PATH, "utf-8"));
3789
- writeFileSync6(ROSTER_DELETIONS_PATH, "[]");
4396
+ if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
4397
+ const deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
4398
+ writeFileSync8(ROSTER_DELETIONS_PATH, "[]");
3790
4399
  return deletions;
3791
4400
  } catch {
3792
4401
  return [];
3793
4402
  }
3794
4403
  }
3795
4404
  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");
4405
+ const rosterPath = paths?.rosterPath ?? path15.join(EXE_AI_DIR, "exe-employees.json");
4406
+ const identityDir = paths?.identityDir ?? path15.join(EXE_AI_DIR, "identity");
4407
+ const configPath = paths?.configPath ?? path15.join(EXE_AI_DIR, "config.json");
3799
4408
  let roster = [];
3800
- if (existsSync12(rosterPath)) {
4409
+ if (existsSync14(rosterPath)) {
3801
4410
  try {
3802
- roster = JSON.parse(readFileSync9(rosterPath, "utf-8"));
4411
+ roster = JSON.parse(readFileSync11(rosterPath, "utf-8"));
3803
4412
  } catch {
3804
4413
  }
3805
4414
  }
3806
4415
  const identities = {};
3807
- if (existsSync12(identityDir)) {
4416
+ if (existsSync14(identityDir)) {
3808
4417
  for (const file of readdirSync4(identityDir).filter((f) => f.endsWith(".md"))) {
3809
4418
  try {
3810
- identities[file] = readFileSync9(path13.join(identityDir, file), "utf-8");
4419
+ identities[file] = readFileSync11(path15.join(identityDir, file), "utf-8");
3811
4420
  } catch {
3812
4421
  }
3813
4422
  }
3814
4423
  }
3815
4424
  let config;
3816
- if (existsSync12(configPath)) {
4425
+ if (existsSync14(configPath)) {
3817
4426
  try {
3818
- config = JSON.parse(readFileSync9(configPath, "utf-8"));
4427
+ config = JSON.parse(readFileSync11(configPath, "utf-8"));
4428
+ } catch {
4429
+ }
4430
+ }
4431
+ let agentConfig;
4432
+ const agentConfigPath = path15.join(EXE_AI_DIR, "agent-config.json");
4433
+ if (existsSync14(agentConfigPath)) {
4434
+ try {
4435
+ agentConfig = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
3819
4436
  } catch {
3820
4437
  }
3821
4438
  }
3822
4439
  const deletedNames = consumeRosterDeletions();
3823
- const content = JSON.stringify({ roster, identities, config, deletedNames });
4440
+ const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
3824
4441
  const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
3825
- return { roster, identities, config, deletedNames, version: hash };
4442
+ return { roster, identities, config, agentConfig, deletedNames, version: hash };
3826
4443
  }
3827
4444
  async function cloudPushRoster(config) {
3828
4445
  assertSecureEndpoint(config.endpoint);
@@ -3891,23 +4508,23 @@ async function cloudPullRoster(config) {
3891
4508
  }
3892
4509
  }
3893
4510
  function mergeConfig(remoteConfig, configPath) {
3894
- const cfgPath = configPath ?? path13.join(EXE_AI_DIR, "config.json");
4511
+ const cfgPath = configPath ?? path15.join(EXE_AI_DIR, "config.json");
3895
4512
  let local = {};
3896
- if (existsSync12(cfgPath)) {
4513
+ if (existsSync14(cfgPath)) {
3897
4514
  try {
3898
- local = JSON.parse(readFileSync9(cfgPath, "utf-8"));
4515
+ local = JSON.parse(readFileSync11(cfgPath, "utf-8"));
3899
4516
  } catch {
3900
4517
  }
3901
4518
  }
3902
4519
  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");
4520
+ const dir = path15.dirname(cfgPath);
4521
+ if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
4522
+ writeFileSync8(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3906
4523
  }
3907
4524
  async function mergeRosterFromRemote(remote, paths) {
3908
4525
  return withRosterLock(async () => {
3909
4526
  const rosterPath = paths?.rosterPath ?? void 0;
3910
- const identityDir = paths?.identityDir ?? path13.join(EXE_AI_DIR, "identity");
4527
+ const identityDir = paths?.identityDir ?? path15.join(EXE_AI_DIR, "identity");
3911
4528
  const localEmployees = await loadEmployees(rosterPath);
3912
4529
  const localNames = new Set(localEmployees.map((e) => e.name));
3913
4530
  let added = 0;
@@ -3928,15 +4545,15 @@ async function mergeRosterFromRemote(remote, paths) {
3928
4545
  ) ?? lookupKey;
3929
4546
  const remoteIdentity = remote.identities[matchedKey];
3930
4547
  if (remoteIdentity) {
3931
- if (!existsSync12(identityDir)) mkdirSync6(identityDir, { recursive: true });
3932
- const idPath = path13.join(identityDir, `${remoteEmp.name}.md`);
4548
+ if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
4549
+ const idPath = path15.join(identityDir, `${remoteEmp.name}.md`);
3933
4550
  let localIdentity = null;
3934
4551
  try {
3935
- localIdentity = existsSync12(idPath) ? readFileSync9(idPath, "utf-8") : null;
4552
+ localIdentity = existsSync14(idPath) ? readFileSync11(idPath, "utf-8") : null;
3936
4553
  } catch {
3937
4554
  }
3938
4555
  if (localIdentity !== remoteIdentity) {
3939
- writeFileSync6(idPath, remoteIdentity, "utf-8");
4556
+ writeFileSync8(idPath, remoteIdentity, "utf-8");
3940
4557
  identitiesUpdated++;
3941
4558
  }
3942
4559
  }
@@ -3960,6 +4577,21 @@ async function mergeRosterFromRemote(remote, paths) {
3960
4577
  } catch {
3961
4578
  }
3962
4579
  }
4580
+ if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
4581
+ try {
4582
+ const agentConfigPath = path15.join(EXE_AI_DIR, "agent-config.json");
4583
+ let local = {};
4584
+ if (existsSync14(agentConfigPath)) {
4585
+ try {
4586
+ local = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
4587
+ } catch {
4588
+ }
4589
+ }
4590
+ const merged = { ...remote.agentConfig, ...local };
4591
+ writeFileSync8(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
4592
+ } catch {
4593
+ }
4594
+ }
3963
4595
  return { added, identitiesUpdated };
3964
4596
  });
3965
4597
  }
@@ -4389,13 +5021,14 @@ var init_cloud_sync = __esm({
4389
5021
  init_compress();
4390
5022
  init_license();
4391
5023
  init_config();
5024
+ init_crdt_sync();
4392
5025
  init_employees();
4393
5026
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
4394
5027
  FETCH_TIMEOUT_MS = 3e4;
4395
5028
  PUSH_BATCH_SIZE = 5e3;
4396
- ROSTER_LOCK_PATH = path13.join(EXE_AI_DIR, "roster-merge.lock");
5029
+ ROSTER_LOCK_PATH = path15.join(EXE_AI_DIR, "roster-merge.lock");
4397
5030
  LOCK_STALE_MS = 3e4;
4398
- ROSTER_DELETIONS_PATH = path13.join(EXE_AI_DIR, "roster-deletions.json");
5031
+ ROSTER_DELETIONS_PATH = path15.join(EXE_AI_DIR, "roster-deletions.json");
4399
5032
  }
4400
5033
  });
4401
5034
 
@@ -4405,6 +5038,7 @@ init_database();
4405
5038
  init_keychain();
4406
5039
  init_config();
4407
5040
  init_state_bus();
5041
+ import { createHash } from "crypto";
4408
5042
  var INIT_MAX_RETRIES = 3;
4409
5043
  var INIT_RETRY_DELAY_MS = 1e3;
4410
5044
  function isBusyError2(err) {
@@ -4486,12 +5120,52 @@ function classifyTier(record) {
4486
5120
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4487
5121
  return 3;
4488
5122
  }
5123
+ function inferFilePaths(record) {
5124
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
5125
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
5126
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
5127
+ return match ? JSON.stringify([match[1]]) : null;
5128
+ }
5129
+ function inferCommitHash(record) {
5130
+ if (record.tool_name !== "Bash") return null;
5131
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
5132
+ return match ? match[1] : null;
5133
+ }
5134
+ function inferLanguageType(record) {
5135
+ const text = record.raw_text;
5136
+ if (!text || text.length < 10) return null;
5137
+ const trimmed = text.trimStart();
5138
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
5139
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
5140
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
5141
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
5142
+ return "mixed";
5143
+ }
5144
+ function inferDomain(record) {
5145
+ const proj = (record.project_name ?? "").toLowerCase();
5146
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
5147
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
5148
+ return null;
5149
+ }
4489
5150
  async function writeMemory(record) {
4490
5151
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4491
5152
  throw new Error(
4492
5153
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4493
5154
  );
4494
5155
  }
5156
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
5157
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
5158
+ return;
5159
+ }
5160
+ try {
5161
+ const client = getClient();
5162
+ const existing = await client.execute({
5163
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
5164
+ args: [contentHash, record.agent_id]
5165
+ });
5166
+ if (existing.rows.length > 0) return;
5167
+ } catch {
5168
+ }
4495
5169
  const dbRow = {
4496
5170
  id: record.id,
4497
5171
  agent_id: record.agent_id,
@@ -4521,7 +5195,23 @@ async function writeMemory(record) {
4521
5195
  supersedes_id: record.supersedes_id ?? null,
4522
5196
  draft: record.draft ? 1 : 0,
4523
5197
  memory_type: record.memory_type ?? "raw",
4524
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
5198
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5199
+ content_hash: contentHash,
5200
+ intent: record.intent ?? null,
5201
+ outcome: record.outcome ?? null,
5202
+ domain: record.domain ?? inferDomain(record),
5203
+ referenced_entities: record.referenced_entities ?? null,
5204
+ retrieval_count: record.retrieval_count ?? 0,
5205
+ chain_position: record.chain_position ?? null,
5206
+ review_status: record.review_status ?? null,
5207
+ context_window_pct: record.context_window_pct ?? null,
5208
+ file_paths: record.file_paths ?? inferFilePaths(record),
5209
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
5210
+ duration_ms: record.duration_ms ?? null,
5211
+ token_cost: record.token_cost ?? null,
5212
+ audience: record.audience ?? null,
5213
+ language_type: record.language_type ?? inferLanguageType(record),
5214
+ parent_memory_id: record.parent_memory_id ?? null
4525
5215
  };
4526
5216
  _pendingRecords.push(dbRow);
4527
5217
  orgBus.emit({
@@ -4579,80 +5269,85 @@ async function flushBatch() {
4579
5269
  const draft = row.draft ? 1 : 0;
4580
5270
  const memoryType = row.memory_type ?? "raw";
4581
5271
  const trajectory = row.trajectory ?? null;
4582
- return {
4583
- sql: hasVector ? `INSERT OR IGNORE INTO memories
4584
- (id, agent_id, agent_role, session_id, timestamp,
5272
+ const contentHash = row.content_hash ?? null;
5273
+ const intent = row.intent ?? null;
5274
+ const outcome = row.outcome ?? null;
5275
+ const domain = row.domain ?? null;
5276
+ const referencedEntities = row.referenced_entities ?? null;
5277
+ const retrievalCount = row.retrieval_count ?? 0;
5278
+ const chainPosition = row.chain_position ?? null;
5279
+ const reviewStatus = row.review_status ?? null;
5280
+ const contextWindowPct = row.context_window_pct ?? null;
5281
+ const filePaths = row.file_paths ?? null;
5282
+ const commitHash = row.commit_hash ?? null;
5283
+ const durationMs = row.duration_ms ?? null;
5284
+ const tokenCost = row.token_cost ?? null;
5285
+ const audience = row.audience ?? null;
5286
+ const languageType = row.language_type ?? null;
5287
+ const parentMemoryId = row.parent_memory_id ?? null;
5288
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4585
5289
  tool_name, project_name,
4586
5290
  has_error, raw_text, vector, version, task_id, importance, status,
4587
5291
  confidence, last_accessed,
4588
5292
  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
- ]
5293
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5294
+ intent, outcome, domain, referenced_entities, retrieval_count,
5295
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
5296
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
5297
+ const metaArgs = [
5298
+ intent,
5299
+ outcome,
5300
+ domain,
5301
+ referencedEntities,
5302
+ retrievalCount,
5303
+ chainPosition,
5304
+ reviewStatus,
5305
+ contextWindowPct,
5306
+ filePaths,
5307
+ commitHash,
5308
+ durationMs,
5309
+ tokenCost,
5310
+ audience,
5311
+ languageType,
5312
+ parentMemoryId
5313
+ ];
5314
+ const baseArgs = [
5315
+ row.id,
5316
+ row.agent_id,
5317
+ row.agent_role,
5318
+ row.session_id,
5319
+ row.timestamp,
5320
+ row.tool_name,
5321
+ row.project_name,
5322
+ row.has_error,
5323
+ row.raw_text
5324
+ ];
5325
+ const sharedArgs = [
5326
+ row.version,
5327
+ taskId,
5328
+ importance,
5329
+ status,
5330
+ confidence,
5331
+ lastAccessed,
5332
+ workspaceId,
5333
+ documentId,
5334
+ userId,
5335
+ charOffset,
5336
+ pageNumber,
5337
+ sourcePath,
5338
+ sourceType,
5339
+ tier,
5340
+ supersedesId,
5341
+ draft,
5342
+ memoryType,
5343
+ trajectory,
5344
+ contentHash
5345
+ ];
5346
+ return {
5347
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5348
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5349
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5350
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4656
5351
  };
4657
5352
  };
4658
5353
  const globalClient = getClient();
@@ -4700,8 +5395,8 @@ init_task_scope();
4700
5395
  init_employees();
4701
5396
  import crypto4 from "crypto";
4702
5397
  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";
5398
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
5399
+ import path16 from "path";
4705
5400
  async function main() {
4706
5401
  const agentId = process.env.AGENT_ID ?? "default";
4707
5402
  const agentRole = process.env.AGENT_ROLE ?? "employee";
@@ -4836,8 +5531,8 @@ async function main() {
4836
5531
  }
4837
5532
  try {
4838
5533
  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)) {
5534
+ const flagPath = path16.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
5535
+ if (existsSync15(flagPath)) {
4841
5536
  const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
4842
5537
  if (!tryAcquireWorkerSlot2()) {
4843
5538
  process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
@@ -4845,11 +5540,11 @@ async function main() {
4845
5540
  const { spawn: spawn2 } = await import("child_process");
4846
5541
  const { fileURLToPath: fileURLToPath3 } = await import("url");
4847
5542
  const thisFile = fileURLToPath3(import.meta.url);
4848
- const backfillPath = path14.resolve(path14.dirname(thisFile), "backfill-vectors.js");
4849
- if (existsSync13(backfillPath)) {
5543
+ const backfillPath = path16.resolve(path16.dirname(thisFile), "backfill-vectors.js");
5544
+ if (existsSync15(backfillPath)) {
4850
5545
  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 });
5546
+ const bLogPath = path16.join(exeDir2, "workers.log");
5547
+ mkdirSync9(path16.dirname(bLogPath), { recursive: true });
4853
5548
  const bLogFd = openSync3(bLogPath, "a");
4854
5549
  const child = spawn2(process.execPath, [backfillPath], {
4855
5550
  detached: true,