@askexenow/exe-os 0.9.7 → 0.9.9

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 (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -392,6 +392,44 @@ var init_db_retry = __esm({
392
392
  }
393
393
  });
394
394
 
395
+ // src/lib/secure-files.ts
396
+ import { chmodSync, existsSync, mkdirSync } from "fs";
397
+ import { chmod, mkdir } from "fs/promises";
398
+ async function ensurePrivateDir(dirPath) {
399
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
400
+ try {
401
+ await chmod(dirPath, PRIVATE_DIR_MODE);
402
+ } catch {
403
+ }
404
+ }
405
+ function ensurePrivateDirSync(dirPath) {
406
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
407
+ try {
408
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
409
+ } catch {
410
+ }
411
+ }
412
+ async function enforcePrivateFile(filePath) {
413
+ try {
414
+ await chmod(filePath, PRIVATE_FILE_MODE);
415
+ } catch {
416
+ }
417
+ }
418
+ function enforcePrivateFileSync(filePath) {
419
+ try {
420
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
421
+ } catch {
422
+ }
423
+ }
424
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
425
+ var init_secure_files = __esm({
426
+ "src/lib/secure-files.ts"() {
427
+ "use strict";
428
+ PRIVATE_DIR_MODE = 448;
429
+ PRIVATE_FILE_MODE = 384;
430
+ }
431
+ });
432
+
395
433
  // src/lib/config.ts
396
434
  var config_exports = {};
397
435
  __export(config_exports, {
@@ -408,8 +446,8 @@ __export(config_exports, {
408
446
  migrateConfig: () => migrateConfig,
409
447
  saveConfig: () => saveConfig
410
448
  });
411
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
412
- import { readFileSync, existsSync, renameSync } from "fs";
449
+ import { readFile, writeFile } from "fs/promises";
450
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
413
451
  import path from "path";
414
452
  import os from "os";
415
453
  function resolveDataDir() {
@@ -417,7 +455,7 @@ function resolveDataDir() {
417
455
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
418
456
  const newDir = path.join(os.homedir(), ".exe-os");
419
457
  const legacyDir = path.join(os.homedir(), ".exe-mem");
420
- if (!existsSync(newDir) && existsSync(legacyDir)) {
458
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
421
459
  try {
422
460
  renameSync(legacyDir, newDir);
423
461
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -480,9 +518,9 @@ function normalizeAutoUpdate(raw) {
480
518
  }
481
519
  async function loadConfig() {
482
520
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
483
- await mkdir(dir, { recursive: true });
521
+ await ensurePrivateDir(dir);
484
522
  const configPath = path.join(dir, "config.json");
485
- if (!existsSync(configPath)) {
523
+ if (!existsSync2(configPath)) {
486
524
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
487
525
  }
488
526
  const raw = await readFile(configPath, "utf-8");
@@ -495,6 +533,7 @@ async function loadConfig() {
495
533
  `);
496
534
  try {
497
535
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
536
+ await enforcePrivateFile(configPath);
498
537
  } catch {
499
538
  }
500
539
  }
@@ -513,7 +552,7 @@ async function loadConfig() {
513
552
  function loadConfigSync() {
514
553
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
515
554
  const configPath = path.join(dir, "config.json");
516
- if (!existsSync(configPath)) {
555
+ if (!existsSync2(configPath)) {
517
556
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
518
557
  }
519
558
  try {
@@ -531,12 +570,10 @@ function loadConfigSync() {
531
570
  }
532
571
  async function saveConfig(config2) {
533
572
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
534
- await mkdir(dir, { recursive: true });
573
+ await ensurePrivateDir(dir);
535
574
  const configPath = path.join(dir, "config.json");
536
575
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
537
- if (config2.cloud?.apiKey) {
538
- await chmod(configPath, 384);
539
- }
576
+ await enforcePrivateFile(configPath);
540
577
  }
541
578
  async function loadConfigFrom(configPath) {
542
579
  const raw = await readFile(configPath, "utf-8");
@@ -556,6 +593,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
556
593
  var init_config = __esm({
557
594
  "src/lib/config.ts"() {
558
595
  "use strict";
596
+ init_secure_files();
559
597
  EXE_AI_DIR = resolveDataDir();
560
598
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
561
599
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -632,6 +670,120 @@ var init_config = __esm({
632
670
  }
633
671
  });
634
672
 
673
+ // src/lib/runtime-table.ts
674
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
675
+ var init_runtime_table = __esm({
676
+ "src/lib/runtime-table.ts"() {
677
+ "use strict";
678
+ RUNTIME_TABLE = {
679
+ codex: {
680
+ binary: "codex",
681
+ launchMode: "interactive",
682
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
683
+ inlineFlag: "--no-alt-screen",
684
+ apiKeyEnv: "OPENAI_API_KEY",
685
+ defaultModel: "gpt-5.4"
686
+ },
687
+ opencode: {
688
+ binary: "opencode",
689
+ launchMode: "exec",
690
+ autoApproveFlag: "--dangerously-skip-permissions",
691
+ inlineFlag: "",
692
+ apiKeyEnv: "ANTHROPIC_API_KEY",
693
+ defaultModel: "anthropic/claude-sonnet-4-6"
694
+ }
695
+ };
696
+ DEFAULT_RUNTIME = "claude";
697
+ }
698
+ });
699
+
700
+ // src/lib/agent-config.ts
701
+ var agent_config_exports = {};
702
+ __export(agent_config_exports, {
703
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
704
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
705
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
706
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
707
+ clearAgentRuntime: () => clearAgentRuntime,
708
+ getAgentRuntime: () => getAgentRuntime,
709
+ loadAgentConfig: () => loadAgentConfig,
710
+ saveAgentConfig: () => saveAgentConfig,
711
+ setAgentRuntime: () => setAgentRuntime
712
+ });
713
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
714
+ import path2 from "path";
715
+ function loadAgentConfig() {
716
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
717
+ try {
718
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
719
+ } catch {
720
+ return {};
721
+ }
722
+ }
723
+ function saveAgentConfig(config2) {
724
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
725
+ ensurePrivateDirSync(dir);
726
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
727
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
728
+ }
729
+ function getAgentRuntime(agentId) {
730
+ const config2 = loadAgentConfig();
731
+ const entry = config2[agentId];
732
+ if (entry) return entry;
733
+ const orgDefault = config2["default"];
734
+ if (orgDefault) return orgDefault;
735
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
736
+ }
737
+ function setAgentRuntime(agentId, runtime, model) {
738
+ const knownModels = KNOWN_RUNTIMES[runtime];
739
+ if (!knownModels) {
740
+ return {
741
+ ok: false,
742
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
743
+ };
744
+ }
745
+ if (!knownModels.includes(model)) {
746
+ return {
747
+ ok: false,
748
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
749
+ };
750
+ }
751
+ const config2 = loadAgentConfig();
752
+ config2[agentId] = { runtime, model };
753
+ saveAgentConfig(config2);
754
+ return { ok: true };
755
+ }
756
+ function clearAgentRuntime(agentId) {
757
+ const config2 = loadAgentConfig();
758
+ delete config2[agentId];
759
+ saveAgentConfig(config2);
760
+ }
761
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
762
+ var init_agent_config = __esm({
763
+ "src/lib/agent-config.ts"() {
764
+ "use strict";
765
+ init_config();
766
+ init_runtime_table();
767
+ init_secure_files();
768
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
769
+ KNOWN_RUNTIMES = {
770
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
771
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
772
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
773
+ };
774
+ RUNTIME_LABELS = {
775
+ claude: "Claude Code (Anthropic)",
776
+ codex: "Codex (OpenAI)",
777
+ opencode: "OpenCode (open source)"
778
+ };
779
+ DEFAULT_MODELS = {
780
+ claude: "claude-opus-4",
781
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
782
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
783
+ };
784
+ }
785
+ });
786
+
635
787
  // src/lib/employees.ts
636
788
  var employees_exports = {};
637
789
  __export(employees_exports, {
@@ -647,6 +799,7 @@ __export(employees_exports, {
647
799
  getEmployeeByRole: () => getEmployeeByRole,
648
800
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
649
801
  hasRole: () => hasRole,
802
+ hireEmployee: () => hireEmployee,
650
803
  isCoordinatorName: () => isCoordinatorName,
651
804
  isCoordinatorRole: () => isCoordinatorRole,
652
805
  isMultiInstance: () => isMultiInstance,
@@ -659,9 +812,9 @@ __export(employees_exports, {
659
812
  validateEmployeeName: () => validateEmployeeName
660
813
  });
661
814
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
662
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
815
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
663
816
  import { execSync } from "child_process";
664
- import path2 from "path";
817
+ import path3 from "path";
665
818
  import os2 from "os";
666
819
  function normalizeRole(role) {
667
820
  return (role ?? "").trim().toLowerCase();
@@ -698,7 +851,7 @@ function validateEmployeeName(name) {
698
851
  return { valid: true };
699
852
  }
700
853
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
701
- if (!existsSync2(employeesPath)) {
854
+ if (!existsSync4(employeesPath)) {
702
855
  return [];
703
856
  }
704
857
  const raw = await readFile2(employeesPath, "utf-8");
@@ -709,13 +862,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
709
862
  }
710
863
  }
711
864
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
712
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
865
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
713
866
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
714
867
  }
715
868
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
716
- if (!existsSync2(employeesPath)) return [];
869
+ if (!existsSync4(employeesPath)) return [];
717
870
  try {
718
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
871
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
719
872
  } catch {
720
873
  return [];
721
874
  }
@@ -757,6 +910,52 @@ function addEmployee(employees, employee) {
757
910
  }
758
911
  return [...employees, normalized];
759
912
  }
913
+ function appendToCoordinatorTeam(employee) {
914
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
915
+ if (!coordinator) return;
916
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
917
+ if (!existsSync4(idPath)) return;
918
+ const content = readFileSync3(idPath, "utf-8");
919
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
920
+ const teamMatch = content.match(TEAM_SECTION_RE);
921
+ if (!teamMatch || teamMatch.index === void 0) return;
922
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
923
+ const nextHeading = afterTeam.match(/\n## /);
924
+ const entry = `
925
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
926
+ `;
927
+ let updated;
928
+ if (nextHeading && nextHeading.index !== void 0) {
929
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
930
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
931
+ } else {
932
+ updated = content.trimEnd() + "\n" + entry;
933
+ }
934
+ writeFileSync2(idPath, updated, "utf-8");
935
+ }
936
+ function capitalize(s) {
937
+ return s.charAt(0).toUpperCase() + s.slice(1);
938
+ }
939
+ async function hireEmployee(employee) {
940
+ const employees = await loadEmployees();
941
+ const updated = addEmployee(employees, employee);
942
+ await saveEmployees(updated);
943
+ try {
944
+ appendToCoordinatorTeam(employee);
945
+ } catch {
946
+ }
947
+ try {
948
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
949
+ const config2 = loadAgentConfig2();
950
+ const name = employee.name.toLowerCase();
951
+ if (!config2[name] && config2["default"]) {
952
+ config2[name] = { ...config2["default"] };
953
+ saveAgentConfig2(config2);
954
+ }
955
+ } catch {
956
+ }
957
+ return updated;
958
+ }
760
959
  async function normalizeRosterCase(rosterPath) {
761
960
  const employees = await loadEmployees(rosterPath);
762
961
  let changed = false;
@@ -766,14 +965,14 @@ async function normalizeRosterCase(rosterPath) {
766
965
  emp.name = emp.name.toLowerCase();
767
966
  changed = true;
768
967
  try {
769
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
770
- const oldPath = path2.join(identityDir, `${oldName}.md`);
771
- const newPath = path2.join(identityDir, `${emp.name}.md`);
772
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
968
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
969
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
970
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
971
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
773
972
  renameSync2(oldPath, newPath);
774
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
775
- const content = readFileSync2(oldPath, "utf-8");
776
- writeFileSync(newPath, content, "utf-8");
973
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
974
+ const content = readFileSync3(oldPath, "utf-8");
975
+ writeFileSync2(newPath, content, "utf-8");
777
976
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
778
977
  unlinkSync(oldPath);
779
978
  }
@@ -803,7 +1002,7 @@ function registerBinSymlinks(name) {
803
1002
  errors.push("Could not find 'exe-os' in PATH");
804
1003
  return { created, skipped, errors };
805
1004
  }
806
- const binDir = path2.dirname(exeBinPath);
1005
+ const binDir = path3.dirname(exeBinPath);
807
1006
  let target;
808
1007
  try {
809
1008
  target = readlinkSync(exeBinPath);
@@ -813,8 +1012,8 @@ function registerBinSymlinks(name) {
813
1012
  }
814
1013
  for (const suffix of ["", "-opencode"]) {
815
1014
  const linkName = `${name}${suffix}`;
816
- const linkPath = path2.join(binDir, linkName);
817
- if (existsSync2(linkPath)) {
1015
+ const linkPath = path3.join(binDir, linkName);
1016
+ if (existsSync4(linkPath)) {
818
1017
  skipped.push(linkName);
819
1018
  continue;
820
1019
  }
@@ -827,21 +1026,619 @@ function registerBinSymlinks(name) {
827
1026
  }
828
1027
  return { created, skipped, errors };
829
1028
  }
830
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
1029
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
831
1030
  var init_employees = __esm({
832
1031
  "src/lib/employees.ts"() {
833
1032
  "use strict";
834
1033
  init_config();
835
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
1034
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
836
1035
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
837
1036
  COORDINATOR_ROLE = "COO";
838
1037
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1038
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
1039
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
1040
+ }
1041
+ });
1042
+
1043
+ // src/lib/database-adapter.ts
1044
+ import os3 from "os";
1045
+ import path4 from "path";
1046
+ import { createRequire } from "module";
1047
+ import { pathToFileURL } from "url";
1048
+ function quotedIdentifier(identifier) {
1049
+ return `"${identifier.replace(/"/g, '""')}"`;
1050
+ }
1051
+ function unqualifiedTableName(name) {
1052
+ const raw = name.trim().replace(/^"|"$/g, "");
1053
+ const parts = raw.split(".");
1054
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1055
+ }
1056
+ function stripTrailingSemicolon(sql) {
1057
+ return sql.trim().replace(/;+\s*$/u, "");
1058
+ }
1059
+ function appendClause(sql, clause) {
1060
+ const trimmed = stripTrailingSemicolon(sql);
1061
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1062
+ if (!returningMatch) {
1063
+ return `${trimmed}${clause}`;
1064
+ }
1065
+ const idx = returningMatch.index;
1066
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1067
+ }
1068
+ function normalizeStatement(stmt) {
1069
+ if (typeof stmt === "string") {
1070
+ return { kind: "positional", sql: stmt, args: [] };
1071
+ }
1072
+ const sql = stmt.sql;
1073
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1074
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1075
+ }
1076
+ return { kind: "named", sql, args: stmt.args };
1077
+ }
1078
+ function rewriteBooleanLiterals(sql) {
1079
+ let out = sql;
1080
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1081
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1082
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1083
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1084
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1085
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1086
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1087
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1088
+ }
1089
+ return out;
1090
+ }
1091
+ function rewriteInsertOrIgnore(sql) {
1092
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1093
+ return sql;
1094
+ }
1095
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1096
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1097
+ }
1098
+ function rewriteInsertOrReplace(sql) {
1099
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1100
+ if (!match) {
1101
+ return sql;
1102
+ }
1103
+ const rawTable = match[1];
1104
+ const rawColumns = match[2];
1105
+ const remainder = match[3];
1106
+ const tableName = unqualifiedTableName(rawTable);
1107
+ const conflictKeys = UPSERT_KEYS[tableName];
1108
+ if (!conflictKeys?.length) {
1109
+ return sql;
1110
+ }
1111
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1112
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1113
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1114
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1115
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1116
+ }
1117
+ function rewriteSql(sql) {
1118
+ let out = sql;
1119
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1120
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1121
+ out = rewriteBooleanLiterals(out);
1122
+ out = rewriteInsertOrReplace(out);
1123
+ out = rewriteInsertOrIgnore(out);
1124
+ return stripTrailingSemicolon(out);
1125
+ }
1126
+ function toBoolean(value) {
1127
+ if (value === null || value === void 0) return value;
1128
+ if (typeof value === "boolean") return value;
1129
+ if (typeof value === "number") return value !== 0;
1130
+ if (typeof value === "bigint") return value !== 0n;
1131
+ if (typeof value === "string") {
1132
+ const normalized = value.trim().toLowerCase();
1133
+ if (normalized === "0" || normalized === "false") return false;
1134
+ if (normalized === "1" || normalized === "true") return true;
1135
+ }
1136
+ return Boolean(value);
1137
+ }
1138
+ function countQuestionMarks(sql, end) {
1139
+ let count = 0;
1140
+ let inSingle = false;
1141
+ let inDouble = false;
1142
+ let inLineComment = false;
1143
+ let inBlockComment = false;
1144
+ for (let i = 0; i < end; i++) {
1145
+ const ch = sql[i];
1146
+ const next = sql[i + 1];
1147
+ if (inLineComment) {
1148
+ if (ch === "\n") inLineComment = false;
1149
+ continue;
1150
+ }
1151
+ if (inBlockComment) {
1152
+ if (ch === "*" && next === "/") {
1153
+ inBlockComment = false;
1154
+ i += 1;
1155
+ }
1156
+ continue;
1157
+ }
1158
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1159
+ inLineComment = true;
1160
+ i += 1;
1161
+ continue;
1162
+ }
1163
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1164
+ inBlockComment = true;
1165
+ i += 1;
1166
+ continue;
1167
+ }
1168
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1169
+ inSingle = !inSingle;
1170
+ continue;
1171
+ }
1172
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1173
+ inDouble = !inDouble;
1174
+ continue;
1175
+ }
1176
+ if (!inSingle && !inDouble && ch === "?") {
1177
+ count += 1;
1178
+ }
1179
+ }
1180
+ return count;
1181
+ }
1182
+ function findBooleanPlaceholderIndexes(sql) {
1183
+ const indexes = /* @__PURE__ */ new Set();
1184
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1185
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1186
+ for (const match of sql.matchAll(pattern)) {
1187
+ const matchText = match[0];
1188
+ const qIndex = match.index + matchText.lastIndexOf("?");
1189
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1190
+ }
1191
+ }
1192
+ return indexes;
1193
+ }
1194
+ function coerceInsertBooleanArgs(sql, args) {
1195
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1196
+ if (!match) return;
1197
+ const rawTable = match[1];
1198
+ const rawColumns = match[2];
1199
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1200
+ if (!boolColumns?.size) return;
1201
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1202
+ for (const [index, column] of columns.entries()) {
1203
+ if (boolColumns.has(column) && index < args.length) {
1204
+ args[index] = toBoolean(args[index]);
1205
+ }
1206
+ }
1207
+ }
1208
+ function coerceUpdateBooleanArgs(sql, args) {
1209
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1210
+ if (!match) return;
1211
+ const rawTable = match[1];
1212
+ const setClause = match[2];
1213
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1214
+ if (!boolColumns?.size) return;
1215
+ const assignments = setClause.split(",");
1216
+ let placeholderIndex = 0;
1217
+ for (const assignment of assignments) {
1218
+ if (!assignment.includes("?")) continue;
1219
+ placeholderIndex += 1;
1220
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1221
+ if (colMatch && boolColumns.has(colMatch[1])) {
1222
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1223
+ }
1224
+ }
1225
+ }
1226
+ function coerceBooleanArgs(sql, args) {
1227
+ const nextArgs = [...args];
1228
+ coerceInsertBooleanArgs(sql, nextArgs);
1229
+ coerceUpdateBooleanArgs(sql, nextArgs);
1230
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1231
+ for (const index of placeholderIndexes) {
1232
+ if (index > 0 && index <= nextArgs.length) {
1233
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1234
+ }
1235
+ }
1236
+ return nextArgs;
1237
+ }
1238
+ function convertQuestionMarksToDollarParams(sql) {
1239
+ let out = "";
1240
+ let placeholder = 0;
1241
+ let inSingle = false;
1242
+ let inDouble = false;
1243
+ let inLineComment = false;
1244
+ let inBlockComment = false;
1245
+ for (let i = 0; i < sql.length; i++) {
1246
+ const ch = sql[i];
1247
+ const next = sql[i + 1];
1248
+ if (inLineComment) {
1249
+ out += ch;
1250
+ if (ch === "\n") inLineComment = false;
1251
+ continue;
1252
+ }
1253
+ if (inBlockComment) {
1254
+ out += ch;
1255
+ if (ch === "*" && next === "/") {
1256
+ out += next;
1257
+ inBlockComment = false;
1258
+ i += 1;
1259
+ }
1260
+ continue;
1261
+ }
1262
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1263
+ out += ch + next;
1264
+ inLineComment = true;
1265
+ i += 1;
1266
+ continue;
1267
+ }
1268
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1269
+ out += ch + next;
1270
+ inBlockComment = true;
1271
+ i += 1;
1272
+ continue;
1273
+ }
1274
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1275
+ inSingle = !inSingle;
1276
+ out += ch;
1277
+ continue;
1278
+ }
1279
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1280
+ inDouble = !inDouble;
1281
+ out += ch;
1282
+ continue;
1283
+ }
1284
+ if (!inSingle && !inDouble && ch === "?") {
1285
+ placeholder += 1;
1286
+ out += `$${placeholder}`;
1287
+ continue;
1288
+ }
1289
+ out += ch;
1290
+ }
1291
+ return out;
1292
+ }
1293
+ function translateStatementForPostgres(stmt) {
1294
+ const normalized = normalizeStatement(stmt);
1295
+ if (normalized.kind === "named") {
1296
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1297
+ }
1298
+ const rewrittenSql = rewriteSql(normalized.sql);
1299
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1300
+ return {
1301
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1302
+ args: coercedArgs
1303
+ };
1304
+ }
1305
+ function shouldBypassPostgres(stmt) {
1306
+ const normalized = normalizeStatement(stmt);
1307
+ if (normalized.kind === "named") {
1308
+ return true;
1309
+ }
1310
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1311
+ }
1312
+ function shouldFallbackOnError(error) {
1313
+ const message = error instanceof Error ? error.message : String(error);
1314
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1315
+ }
1316
+ function isReadQuery(sql) {
1317
+ const trimmed = sql.trimStart();
1318
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1319
+ }
1320
+ function buildRow(row, columns) {
1321
+ const values = columns.map((column) => row[column]);
1322
+ return Object.assign(values, row);
1323
+ }
1324
+ function buildResultSet(rows, rowsAffected = 0) {
1325
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1326
+ const resultRows = rows.map((row) => buildRow(row, columns));
1327
+ return {
1328
+ columns,
1329
+ columnTypes: columns.map(() => ""),
1330
+ rows: resultRows,
1331
+ rowsAffected,
1332
+ lastInsertRowid: void 0,
1333
+ toJSON() {
1334
+ return {
1335
+ columns,
1336
+ columnTypes: columns.map(() => ""),
1337
+ rows,
1338
+ rowsAffected,
1339
+ lastInsertRowid: void 0
1340
+ };
1341
+ }
1342
+ };
1343
+ }
1344
+ async function loadPrismaClient() {
1345
+ if (!prismaClientPromise) {
1346
+ prismaClientPromise = (async () => {
1347
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1348
+ if (explicitPath) {
1349
+ const module2 = await import(pathToFileURL(explicitPath).href);
1350
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1351
+ if (!PrismaClient2) {
1352
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1353
+ }
1354
+ return new PrismaClient2();
1355
+ }
1356
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1357
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
1358
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1359
+ const module = await import(pathToFileURL(prismaEntry).href);
1360
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1361
+ if (!PrismaClient) {
1362
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1363
+ }
1364
+ return new PrismaClient();
1365
+ })();
1366
+ }
1367
+ return prismaClientPromise;
1368
+ }
1369
+ async function ensureCompatibilityViews(prisma) {
1370
+ if (!compatibilityBootstrapPromise) {
1371
+ compatibilityBootstrapPromise = (async () => {
1372
+ for (const mapping of VIEW_MAPPINGS) {
1373
+ const relation = mapping.source.replace(/"/g, "");
1374
+ const rows = await prisma.$queryRawUnsafe(
1375
+ "SELECT to_regclass($1) AS regclass",
1376
+ relation
1377
+ );
1378
+ if (!rows[0]?.regclass) {
1379
+ continue;
1380
+ }
1381
+ await prisma.$executeRawUnsafe(
1382
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1383
+ );
1384
+ }
1385
+ })();
1386
+ }
1387
+ return compatibilityBootstrapPromise;
1388
+ }
1389
+ async function executeOnPrisma(executor, stmt) {
1390
+ const translated = translateStatementForPostgres(stmt);
1391
+ if (isReadQuery(translated.sql)) {
1392
+ const rows = await executor.$queryRawUnsafe(
1393
+ translated.sql,
1394
+ ...translated.args
1395
+ );
1396
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1397
+ }
1398
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1399
+ return buildResultSet([], rowsAffected);
1400
+ }
1401
+ function splitSqlStatements(sql) {
1402
+ const parts = [];
1403
+ let current = "";
1404
+ let inSingle = false;
1405
+ let inDouble = false;
1406
+ let inLineComment = false;
1407
+ let inBlockComment = false;
1408
+ for (let i = 0; i < sql.length; i++) {
1409
+ const ch = sql[i];
1410
+ const next = sql[i + 1];
1411
+ if (inLineComment) {
1412
+ current += ch;
1413
+ if (ch === "\n") inLineComment = false;
1414
+ continue;
1415
+ }
1416
+ if (inBlockComment) {
1417
+ current += ch;
1418
+ if (ch === "*" && next === "/") {
1419
+ current += next;
1420
+ inBlockComment = false;
1421
+ i += 1;
1422
+ }
1423
+ continue;
1424
+ }
1425
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1426
+ current += ch + next;
1427
+ inLineComment = true;
1428
+ i += 1;
1429
+ continue;
1430
+ }
1431
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1432
+ current += ch + next;
1433
+ inBlockComment = true;
1434
+ i += 1;
1435
+ continue;
1436
+ }
1437
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1438
+ inSingle = !inSingle;
1439
+ current += ch;
1440
+ continue;
1441
+ }
1442
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1443
+ inDouble = !inDouble;
1444
+ current += ch;
1445
+ continue;
1446
+ }
1447
+ if (!inSingle && !inDouble && ch === ";") {
1448
+ if (current.trim()) {
1449
+ parts.push(current.trim());
1450
+ }
1451
+ current = "";
1452
+ continue;
1453
+ }
1454
+ current += ch;
1455
+ }
1456
+ if (current.trim()) {
1457
+ parts.push(current.trim());
1458
+ }
1459
+ return parts;
1460
+ }
1461
+ async function createPrismaDbAdapter(fallbackClient) {
1462
+ const prisma = await loadPrismaClient();
1463
+ await ensureCompatibilityViews(prisma);
1464
+ let closed = false;
1465
+ let adapter;
1466
+ const fallbackExecute = async (stmt, error) => {
1467
+ if (!fallbackClient) {
1468
+ if (error) throw error;
1469
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1470
+ }
1471
+ if (error) {
1472
+ process.stderr.write(
1473
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1474
+ `
1475
+ );
1476
+ }
1477
+ return fallbackClient.execute(stmt);
1478
+ };
1479
+ adapter = {
1480
+ async execute(stmt) {
1481
+ if (shouldBypassPostgres(stmt)) {
1482
+ return fallbackExecute(stmt);
1483
+ }
1484
+ try {
1485
+ return await executeOnPrisma(prisma, stmt);
1486
+ } catch (error) {
1487
+ if (shouldFallbackOnError(error)) {
1488
+ return fallbackExecute(stmt, error);
1489
+ }
1490
+ throw error;
1491
+ }
1492
+ },
1493
+ async batch(stmts, mode) {
1494
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1495
+ if (!fallbackClient) {
1496
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1497
+ }
1498
+ return fallbackClient.batch(stmts, mode);
1499
+ }
1500
+ try {
1501
+ if (prisma.$transaction) {
1502
+ return await prisma.$transaction(async (tx) => {
1503
+ const results2 = [];
1504
+ for (const stmt of stmts) {
1505
+ results2.push(await executeOnPrisma(tx, stmt));
1506
+ }
1507
+ return results2;
1508
+ });
1509
+ }
1510
+ const results = [];
1511
+ for (const stmt of stmts) {
1512
+ results.push(await executeOnPrisma(prisma, stmt));
1513
+ }
1514
+ return results;
1515
+ } catch (error) {
1516
+ if (fallbackClient && shouldFallbackOnError(error)) {
1517
+ process.stderr.write(
1518
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1519
+ `
1520
+ );
1521
+ return fallbackClient.batch(stmts, mode);
1522
+ }
1523
+ throw error;
1524
+ }
1525
+ },
1526
+ async migrate(stmts) {
1527
+ if (fallbackClient) {
1528
+ return fallbackClient.migrate(stmts);
1529
+ }
1530
+ return adapter.batch(stmts, "deferred");
1531
+ },
1532
+ async transaction(mode) {
1533
+ if (!fallbackClient) {
1534
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1535
+ }
1536
+ return fallbackClient.transaction(mode);
1537
+ },
1538
+ async executeMultiple(sql) {
1539
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1540
+ return fallbackClient.executeMultiple(sql);
1541
+ }
1542
+ for (const statement of splitSqlStatements(sql)) {
1543
+ await adapter.execute(statement);
1544
+ }
1545
+ },
1546
+ async sync() {
1547
+ if (fallbackClient) {
1548
+ return fallbackClient.sync();
1549
+ }
1550
+ return { frame_no: 0, frames_synced: 0 };
1551
+ },
1552
+ close() {
1553
+ closed = true;
1554
+ prismaClientPromise = null;
1555
+ compatibilityBootstrapPromise = null;
1556
+ void prisma.$disconnect?.();
1557
+ },
1558
+ get closed() {
1559
+ return closed;
1560
+ },
1561
+ get protocol() {
1562
+ return "prisma-postgres";
1563
+ }
1564
+ };
1565
+ return adapter;
1566
+ }
1567
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1568
+ var init_database_adapter = __esm({
1569
+ "src/lib/database-adapter.ts"() {
1570
+ "use strict";
1571
+ VIEW_MAPPINGS = [
1572
+ { view: "memories", source: "memory.memory_records" },
1573
+ { view: "tasks", source: "memory.tasks" },
1574
+ { view: "behaviors", source: "memory.behaviors" },
1575
+ { view: "entities", source: "memory.entities" },
1576
+ { view: "relationships", source: "memory.relationships" },
1577
+ { view: "entity_memories", source: "memory.entity_memories" },
1578
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1579
+ { view: "notifications", source: "memory.notifications" },
1580
+ { view: "messages", source: "memory.messages" },
1581
+ { view: "users", source: "wiki.users" },
1582
+ { view: "workspaces", source: "wiki.workspaces" },
1583
+ { view: "workspace_users", source: "wiki.workspace_users" },
1584
+ { view: "documents", source: "wiki.workspace_documents" },
1585
+ { view: "chats", source: "wiki.workspace_chats" }
1586
+ ];
1587
+ UPSERT_KEYS = {
1588
+ memories: ["id"],
1589
+ tasks: ["id"],
1590
+ behaviors: ["id"],
1591
+ entities: ["id"],
1592
+ relationships: ["id"],
1593
+ entity_aliases: ["alias"],
1594
+ notifications: ["id"],
1595
+ messages: ["id"],
1596
+ users: ["id"],
1597
+ workspaces: ["id"],
1598
+ workspace_users: ["id"],
1599
+ documents: ["id"],
1600
+ chats: ["id"]
1601
+ };
1602
+ BOOLEAN_COLUMNS_BY_TABLE = {
1603
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1604
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1605
+ notifications: /* @__PURE__ */ new Set(["read"]),
1606
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1607
+ };
1608
+ BOOLEAN_COLUMN_NAMES = new Set(
1609
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1610
+ );
1611
+ IMMEDIATE_FALLBACK_PATTERNS = [
1612
+ /\bPRAGMA\b/i,
1613
+ /\bsqlite_master\b/i,
1614
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1615
+ /\bMATCH\b/i,
1616
+ /\bvector_distance_cos\s*\(/i,
1617
+ /\bjson_extract\s*\(/i,
1618
+ /\bjulianday\s*\(/i,
1619
+ /\bstrftime\s*\(/i,
1620
+ /\blast_insert_rowid\s*\(/i
1621
+ ];
1622
+ prismaClientPromise = null;
1623
+ compatibilityBootstrapPromise = null;
839
1624
  }
840
1625
  });
841
1626
 
842
1627
  // src/lib/database.ts
843
1628
  import { createClient } from "@libsql/client";
844
1629
  async function initDatabase(config2) {
1630
+ if (_walCheckpointTimer) {
1631
+ clearInterval(_walCheckpointTimer);
1632
+ _walCheckpointTimer = null;
1633
+ }
1634
+ if (_daemonClient) {
1635
+ _daemonClient.close();
1636
+ _daemonClient = null;
1637
+ }
1638
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1639
+ _adapterClient.close();
1640
+ }
1641
+ _adapterClient = null;
845
1642
  if (_client) {
846
1643
  _client.close();
847
1644
  _client = null;
@@ -855,6 +1652,7 @@ async function initDatabase(config2) {
855
1652
  }
856
1653
  _client = createClient(opts);
857
1654
  _resilientClient = wrapWithRetry(_client);
1655
+ _adapterClient = _resilientClient;
858
1656
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
859
1657
  });
860
1658
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -865,11 +1663,17 @@ async function initDatabase(config2) {
865
1663
  });
866
1664
  }, 3e4);
867
1665
  _walCheckpointTimer.unref();
1666
+ if (process.env.DATABASE_URL) {
1667
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1668
+ }
868
1669
  }
869
1670
  function getClient() {
870
- if (!_resilientClient) {
1671
+ if (!_adapterClient) {
871
1672
  throw new Error("Database client not initialized. Call initDatabase() first.");
872
1673
  }
1674
+ if (process.env.DATABASE_URL) {
1675
+ return _adapterClient;
1676
+ }
873
1677
  if (process.env.EXE_IS_DAEMON === "1") {
874
1678
  return _resilientClient;
875
1679
  }
@@ -1162,6 +1966,7 @@ async function ensureSchema() {
1162
1966
  project TEXT NOT NULL,
1163
1967
  summary TEXT NOT NULL,
1164
1968
  task_file TEXT,
1969
+ session_scope TEXT,
1165
1970
  read INTEGER NOT NULL DEFAULT 0,
1166
1971
  created_at TEXT NOT NULL
1167
1972
  );
@@ -1170,7 +1975,7 @@ async function ensureSchema() {
1170
1975
  ON notifications(read);
1171
1976
 
1172
1977
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1173
- ON notifications(agent_id);
1978
+ ON notifications(agent_id, session_scope);
1174
1979
 
1175
1980
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1176
1981
  ON notifications(task_file);
@@ -1208,6 +2013,7 @@ async function ensureSchema() {
1208
2013
  target_agent TEXT NOT NULL,
1209
2014
  target_project TEXT,
1210
2015
  target_device TEXT NOT NULL DEFAULT 'local',
2016
+ session_scope TEXT,
1211
2017
  content TEXT NOT NULL,
1212
2018
  priority TEXT DEFAULT 'normal',
1213
2019
  status TEXT DEFAULT 'pending',
@@ -1221,10 +2027,31 @@ async function ensureSchema() {
1221
2027
  );
1222
2028
 
1223
2029
  CREATE INDEX IF NOT EXISTS idx_messages_target
1224
- ON messages(target_agent, status);
2030
+ ON messages(target_agent, session_scope, status);
1225
2031
 
1226
2032
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1227
- ON messages(target_agent, from_agent, server_seq);
2033
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2034
+ `);
2035
+ try {
2036
+ await client.execute({
2037
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2038
+ args: []
2039
+ });
2040
+ } catch {
2041
+ }
2042
+ try {
2043
+ await client.execute({
2044
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2045
+ args: []
2046
+ });
2047
+ } catch {
2048
+ }
2049
+ await client.executeMultiple(`
2050
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2051
+ ON notifications(agent_id, session_scope, read, created_at);
2052
+
2053
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2054
+ ON messages(target_agent, session_scope, status, created_at);
1228
2055
  `);
1229
2056
  try {
1230
2057
  await client.execute({
@@ -1808,28 +2635,45 @@ async function ensureSchema() {
1808
2635
  } catch {
1809
2636
  }
1810
2637
  }
2638
+ try {
2639
+ await client.execute({
2640
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2641
+ args: []
2642
+ });
2643
+ } catch {
2644
+ }
1811
2645
  }
1812
2646
  async function disposeDatabase() {
2647
+ if (_walCheckpointTimer) {
2648
+ clearInterval(_walCheckpointTimer);
2649
+ _walCheckpointTimer = null;
2650
+ }
1813
2651
  if (_daemonClient) {
1814
2652
  _daemonClient.close();
1815
2653
  _daemonClient = null;
1816
2654
  }
2655
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2656
+ _adapterClient.close();
2657
+ }
2658
+ _adapterClient = null;
1817
2659
  if (_client) {
1818
2660
  _client.close();
1819
2661
  _client = null;
1820
2662
  _resilientClient = null;
1821
2663
  }
1822
2664
  }
1823
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2665
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1824
2666
  var init_database = __esm({
1825
2667
  "src/lib/database.ts"() {
1826
2668
  "use strict";
1827
2669
  init_db_retry();
1828
2670
  init_employees();
2671
+ init_database_adapter();
1829
2672
  _client = null;
1830
2673
  _resilientClient = null;
1831
2674
  _walCheckpointTimer = null;
1832
2675
  _daemonClient = null;
2676
+ _adapterClient = null;
1833
2677
  initTurso = initDatabase;
1834
2678
  disposeTurso = disposeDatabase;
1835
2679
  }
@@ -1844,13 +2688,50 @@ var init_memory = __esm({
1844
2688
  }
1845
2689
  });
1846
2690
 
2691
+ // src/lib/daemon-auth.ts
2692
+ import crypto from "crypto";
2693
+ import path5 from "path";
2694
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2695
+ function normalizeToken(token) {
2696
+ if (!token) return null;
2697
+ const trimmed = token.trim();
2698
+ return trimmed.length > 0 ? trimmed : null;
2699
+ }
2700
+ function readDaemonToken() {
2701
+ try {
2702
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
2703
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
2704
+ } catch {
2705
+ return null;
2706
+ }
2707
+ }
2708
+ function ensureDaemonToken(seed) {
2709
+ const existing = readDaemonToken();
2710
+ if (existing) return existing;
2711
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
2712
+ ensurePrivateDirSync(EXE_AI_DIR);
2713
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
2714
+ `, "utf8");
2715
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
2716
+ return token;
2717
+ }
2718
+ var DAEMON_TOKEN_PATH;
2719
+ var init_daemon_auth = __esm({
2720
+ "src/lib/daemon-auth.ts"() {
2721
+ "use strict";
2722
+ init_config();
2723
+ init_secure_files();
2724
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
2725
+ }
2726
+ });
2727
+
1847
2728
  // src/lib/exe-daemon-client.ts
1848
2729
  import net from "net";
1849
- import os3 from "os";
2730
+ import os4 from "os";
1850
2731
  import { spawn } from "child_process";
1851
2732
  import { randomUUID } from "crypto";
1852
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1853
- import path3 from "path";
2733
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
2734
+ import path6 from "path";
1854
2735
  import { fileURLToPath } from "url";
1855
2736
  function handleData(chunk) {
1856
2737
  _buffer += chunk.toString();
@@ -1878,9 +2759,9 @@ function handleData(chunk) {
1878
2759
  }
1879
2760
  }
1880
2761
  function cleanupStaleFiles() {
1881
- if (existsSync3(PID_PATH)) {
2762
+ if (existsSync6(PID_PATH)) {
1882
2763
  try {
1883
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2764
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1884
2765
  if (pid > 0) {
1885
2766
  try {
1886
2767
  process.kill(pid, 0);
@@ -1901,17 +2782,17 @@ function cleanupStaleFiles() {
1901
2782
  }
1902
2783
  }
1903
2784
  function findPackageRoot() {
1904
- let dir = path3.dirname(fileURLToPath(import.meta.url));
1905
- const { root } = path3.parse(dir);
2785
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2786
+ const { root } = path6.parse(dir);
1906
2787
  while (dir !== root) {
1907
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
1908
- dir = path3.dirname(dir);
2788
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2789
+ dir = path6.dirname(dir);
1909
2790
  }
1910
2791
  return null;
1911
2792
  }
1912
2793
  function spawnDaemon() {
1913
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
1914
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
2794
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
2795
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1915
2796
  if (totalGB <= 8) {
1916
2797
  process.stderr.write(
1917
2798
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1931,16 +2812,17 @@ function spawnDaemon() {
1931
2812
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1932
2813
  return;
1933
2814
  }
1934
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1935
- if (!existsSync3(daemonPath)) {
2815
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2816
+ if (!existsSync6(daemonPath)) {
1936
2817
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1937
2818
  `);
1938
2819
  return;
1939
2820
  }
1940
2821
  const resolvedPath = daemonPath;
2822
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1941
2823
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1942
2824
  `);
1943
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
2825
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1944
2826
  let stderrFd = "ignore";
1945
2827
  try {
1946
2828
  stderrFd = openSync(logPath, "a");
@@ -1958,7 +2840,8 @@ function spawnDaemon() {
1958
2840
  TMUX_PANE: void 0,
1959
2841
  // Prevents resolveExeSession() from scoping to one session
1960
2842
  EXE_DAEMON_SOCK: SOCKET_PATH,
1961
- EXE_DAEMON_PID: PID_PATH
2843
+ EXE_DAEMON_PID: PID_PATH,
2844
+ [DAEMON_TOKEN_ENV]: daemonToken
1962
2845
  }
1963
2846
  });
1964
2847
  child.unref();
@@ -2068,13 +2951,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2068
2951
  return;
2069
2952
  }
2070
2953
  const id = randomUUID();
2954
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2071
2955
  const timer = setTimeout(() => {
2072
2956
  _pending.delete(id);
2073
2957
  resolve({ error: "Request timeout" });
2074
2958
  }, timeoutMs);
2075
2959
  _pending.set(id, { resolve, timer });
2076
2960
  try {
2077
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2961
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
2078
2962
  } catch {
2079
2963
  clearTimeout(timer);
2080
2964
  _pending.delete(id);
@@ -2091,74 +2975,123 @@ async function pingDaemon() {
2091
2975
  return null;
2092
2976
  }
2093
2977
  function killAndRespawnDaemon() {
2094
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
2095
- if (existsSync3(PID_PATH)) {
2096
- try {
2097
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2098
- if (pid > 0) {
2099
- try {
2100
- process.kill(pid, "SIGKILL");
2101
- } catch {
2978
+ if (!acquireSpawnLock()) {
2979
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2980
+ if (_socket) {
2981
+ _socket.destroy();
2982
+ _socket = null;
2983
+ }
2984
+ _connected = false;
2985
+ _buffer = "";
2986
+ return;
2987
+ }
2988
+ try {
2989
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2990
+ if (existsSync6(PID_PATH)) {
2991
+ try {
2992
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2993
+ if (pid > 0) {
2994
+ try {
2995
+ process.kill(pid, "SIGKILL");
2996
+ } catch {
2997
+ }
2102
2998
  }
2999
+ } catch {
2103
3000
  }
3001
+ }
3002
+ if (_socket) {
3003
+ _socket.destroy();
3004
+ _socket = null;
3005
+ }
3006
+ _connected = false;
3007
+ _buffer = "";
3008
+ try {
3009
+ unlinkSync2(PID_PATH);
2104
3010
  } catch {
2105
3011
  }
3012
+ try {
3013
+ unlinkSync2(SOCKET_PATH);
3014
+ } catch {
3015
+ }
3016
+ spawnDaemon();
3017
+ } finally {
3018
+ releaseSpawnLock();
2106
3019
  }
2107
- if (_socket) {
2108
- _socket.destroy();
2109
- _socket = null;
2110
- }
2111
- _connected = false;
2112
- _buffer = "";
3020
+ }
3021
+ function isDaemonTooYoung() {
2113
3022
  try {
2114
- unlinkSync2(PID_PATH);
3023
+ const stat = statSync(PID_PATH);
3024
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
2115
3025
  } catch {
3026
+ return false;
2116
3027
  }
2117
- try {
2118
- unlinkSync2(SOCKET_PATH);
2119
- } catch {
3028
+ }
3029
+ async function retryThenRestart(doRequest, label) {
3030
+ const result = await doRequest();
3031
+ if (!result.error) {
3032
+ _consecutiveFailures = 0;
3033
+ return result;
3034
+ }
3035
+ _consecutiveFailures++;
3036
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
3037
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
3038
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
3039
+ `);
3040
+ await new Promise((r) => setTimeout(r, delayMs));
3041
+ if (!_connected) {
3042
+ if (!await connectToSocket()) continue;
3043
+ }
3044
+ const retry = await doRequest();
3045
+ if (!retry.error) {
3046
+ _consecutiveFailures = 0;
3047
+ return retry;
3048
+ }
3049
+ _consecutiveFailures++;
2120
3050
  }
2121
- spawnDaemon();
3051
+ if (isDaemonTooYoung()) {
3052
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
3053
+ `);
3054
+ return { error: result.error };
3055
+ }
3056
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
3057
+ `);
3058
+ killAndRespawnDaemon();
3059
+ const start = Date.now();
3060
+ let delay2 = 200;
3061
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3062
+ await new Promise((r) => setTimeout(r, delay2));
3063
+ if (await connectToSocket()) break;
3064
+ delay2 = Math.min(delay2 * 2, 3e3);
3065
+ }
3066
+ if (!_connected) return { error: "Daemon restart failed" };
3067
+ const final = await doRequest();
3068
+ if (!final.error) _consecutiveFailures = 0;
3069
+ return final;
2122
3070
  }
2123
3071
  async function embedViaClient(text, priority = "high") {
2124
3072
  if (!_connected && !await connectEmbedDaemon()) return null;
2125
3073
  _requestCount++;
2126
3074
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2127
3075
  const health = await pingDaemon();
2128
- if (!health) {
3076
+ if (!health && !isDaemonTooYoung()) {
2129
3077
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2130
3078
  `);
2131
3079
  killAndRespawnDaemon();
2132
3080
  const start = Date.now();
2133
- let delay2 = 200;
3081
+ let d = 200;
2134
3082
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2135
- await new Promise((r) => setTimeout(r, delay2));
3083
+ await new Promise((r) => setTimeout(r, d));
2136
3084
  if (await connectToSocket()) break;
2137
- delay2 = Math.min(delay2 * 2, 3e3);
3085
+ d = Math.min(d * 2, 3e3);
2138
3086
  }
2139
3087
  if (!_connected) return null;
2140
3088
  }
2141
3089
  }
2142
- const result = await sendRequest([text], priority);
2143
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2144
- if (result.error) {
2145
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2146
- `);
2147
- killAndRespawnDaemon();
2148
- const start = Date.now();
2149
- let delay2 = 200;
2150
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2151
- await new Promise((r) => setTimeout(r, delay2));
2152
- if (await connectToSocket()) break;
2153
- delay2 = Math.min(delay2 * 2, 3e3);
2154
- }
2155
- if (!_connected) return null;
2156
- const retry = await sendRequest([text], priority);
2157
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2158
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2159
- `);
2160
- }
2161
- return null;
3090
+ const result = await retryThenRestart(
3091
+ () => sendRequest([text], priority),
3092
+ "Embed"
3093
+ );
3094
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
2162
3095
  }
2163
3096
  function disconnectClient() {
2164
3097
  if (_socket) {
@@ -2173,22 +3106,28 @@ function disconnectClient() {
2173
3106
  entry.resolve({ error: "Client disconnected" });
2174
3107
  }
2175
3108
  }
2176
- 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;
3109
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
2177
3110
  var init_exe_daemon_client = __esm({
2178
3111
  "src/lib/exe-daemon-client.ts"() {
2179
3112
  "use strict";
2180
3113
  init_config();
2181
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
2182
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
2183
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
3114
+ init_daemon_auth();
3115
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3116
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3117
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2184
3118
  SPAWN_LOCK_STALE_MS = 3e4;
2185
3119
  CONNECT_TIMEOUT_MS = 15e3;
2186
3120
  REQUEST_TIMEOUT_MS = 3e4;
3121
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
2187
3122
  _socket = null;
2188
3123
  _connected = false;
2189
3124
  _buffer = "";
2190
3125
  _requestCount = 0;
3126
+ _consecutiveFailures = 0;
2191
3127
  HEALTH_CHECK_INTERVAL = 100;
3128
+ MAX_RETRIES_BEFORE_RESTART = 3;
3129
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
3130
+ MIN_DAEMON_AGE_MS = 3e4;
2192
3131
  _pending = /* @__PURE__ */ new Map();
2193
3132
  MAX_BUFFER = 1e7;
2194
3133
  }
@@ -2231,10 +3170,10 @@ async function disposeEmbedder() {
2231
3170
  async function embedDirect(text) {
2232
3171
  const llamaCpp = await import("node-llama-cpp");
2233
3172
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2234
- const { existsSync: existsSync17 } = await import("fs");
2235
- const path21 = await import("path");
2236
- const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2237
- if (!existsSync17(modelPath)) {
3173
+ const { existsSync: existsSync19 } = await import("fs");
3174
+ const path23 = await import("path");
3175
+ const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3176
+ if (!existsSync19(modelPath)) {
2238
3177
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
2239
3178
  }
2240
3179
  const llama = await llamaCpp.getLlama();
@@ -2264,14 +3203,14 @@ var init_embedder = __esm({
2264
3203
 
2265
3204
  // src/lib/keychain.ts
2266
3205
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2267
- import { existsSync as existsSync4 } from "fs";
2268
- import path4 from "path";
2269
- import os4 from "os";
3206
+ import { existsSync as existsSync7 } from "fs";
3207
+ import path7 from "path";
3208
+ import os5 from "os";
2270
3209
  function getKeyDir() {
2271
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
3210
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
2272
3211
  }
2273
3212
  function getKeyPath() {
2274
- return path4.join(getKeyDir(), "master.key");
3213
+ return path7.join(getKeyDir(), "master.key");
2275
3214
  }
2276
3215
  async function tryKeytar() {
2277
3216
  try {
@@ -2292,9 +3231,9 @@ async function getMasterKey() {
2292
3231
  }
2293
3232
  }
2294
3233
  const keyPath = getKeyPath();
2295
- if (!existsSync4(keyPath)) {
3234
+ if (!existsSync7(keyPath)) {
2296
3235
  process.stderr.write(
2297
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3236
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2298
3237
  `
2299
3238
  );
2300
3239
  return null;
@@ -2324,6 +3263,7 @@ var shard_manager_exports = {};
2324
3263
  __export(shard_manager_exports, {
2325
3264
  disposeShards: () => disposeShards,
2326
3265
  ensureShardSchema: () => ensureShardSchema,
3266
+ getOpenShardCount: () => getOpenShardCount,
2327
3267
  getReadyShardClient: () => getReadyShardClient,
2328
3268
  getShardClient: () => getShardClient,
2329
3269
  getShardsDir: () => getShardsDir,
@@ -2332,15 +3272,18 @@ __export(shard_manager_exports, {
2332
3272
  listShards: () => listShards,
2333
3273
  shardExists: () => shardExists
2334
3274
  });
2335
- import path5 from "path";
2336
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
3275
+ import path8 from "path";
3276
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
2337
3277
  import { createClient as createClient2 } from "@libsql/client";
2338
3278
  function initShardManager(encryptionKey) {
2339
3279
  _encryptionKey = encryptionKey;
2340
- if (!existsSync5(SHARDS_DIR)) {
2341
- mkdirSync(SHARDS_DIR, { recursive: true });
3280
+ if (!existsSync8(SHARDS_DIR)) {
3281
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2342
3282
  }
2343
3283
  _shardingEnabled = true;
3284
+ if (_evictionTimer) clearInterval(_evictionTimer);
3285
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3286
+ _evictionTimer.unref();
2344
3287
  }
2345
3288
  function isShardingEnabled() {
2346
3289
  return _shardingEnabled;
@@ -2357,21 +3300,28 @@ function getShardClient(projectName) {
2357
3300
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2358
3301
  }
2359
3302
  const cached = _shards.get(safeName);
2360
- if (cached) return cached;
2361
- const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
3303
+ if (cached) {
3304
+ _shardLastAccess.set(safeName, Date.now());
3305
+ return cached;
3306
+ }
3307
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3308
+ evictLRU();
3309
+ }
3310
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
2362
3311
  const client = createClient2({
2363
3312
  url: `file:${dbPath}`,
2364
3313
  encryptionKey: _encryptionKey
2365
3314
  });
2366
3315
  _shards.set(safeName, client);
3316
+ _shardLastAccess.set(safeName, Date.now());
2367
3317
  return client;
2368
3318
  }
2369
3319
  function shardExists(projectName) {
2370
3320
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2371
- return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
3321
+ return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
2372
3322
  }
2373
3323
  function listShards() {
2374
- if (!existsSync5(SHARDS_DIR)) return [];
3324
+ if (!existsSync8(SHARDS_DIR)) return [];
2375
3325
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2376
3326
  }
2377
3327
  async function ensureShardSchema(client) {
@@ -2423,6 +3373,8 @@ async function ensureShardSchema(client) {
2423
3373
  for (const col of [
2424
3374
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2425
3375
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3376
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3377
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2426
3378
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2427
3379
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2428
3380
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2445,7 +3397,23 @@ async function ensureShardSchema(client) {
2445
3397
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2446
3398
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2447
3399
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2448
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3400
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3401
+ // Metadata enrichment columns (must match database.ts)
3402
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3403
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3404
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3405
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3406
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3407
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3408
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3409
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3410
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3411
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3412
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3413
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3414
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3415
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3416
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2449
3417
  ]) {
2450
3418
  try {
2451
3419
  await client.execute(col);
@@ -2544,21 +3512,69 @@ async function getReadyShardClient(projectName) {
2544
3512
  await ensureShardSchema(client);
2545
3513
  return client;
2546
3514
  }
3515
+ function evictLRU() {
3516
+ let oldest = null;
3517
+ let oldestTime = Infinity;
3518
+ for (const [name, time] of _shardLastAccess) {
3519
+ if (time < oldestTime) {
3520
+ oldestTime = time;
3521
+ oldest = name;
3522
+ }
3523
+ }
3524
+ if (oldest) {
3525
+ const client = _shards.get(oldest);
3526
+ if (client) {
3527
+ client.close();
3528
+ }
3529
+ _shards.delete(oldest);
3530
+ _shardLastAccess.delete(oldest);
3531
+ }
3532
+ }
3533
+ function evictIdleShards() {
3534
+ const now = Date.now();
3535
+ const toEvict = [];
3536
+ for (const [name, lastAccess] of _shardLastAccess) {
3537
+ if (now - lastAccess > SHARD_IDLE_MS) {
3538
+ toEvict.push(name);
3539
+ }
3540
+ }
3541
+ for (const name of toEvict) {
3542
+ const client = _shards.get(name);
3543
+ if (client) {
3544
+ client.close();
3545
+ }
3546
+ _shards.delete(name);
3547
+ _shardLastAccess.delete(name);
3548
+ }
3549
+ }
3550
+ function getOpenShardCount() {
3551
+ return _shards.size;
3552
+ }
2547
3553
  function disposeShards() {
3554
+ if (_evictionTimer) {
3555
+ clearInterval(_evictionTimer);
3556
+ _evictionTimer = null;
3557
+ }
2548
3558
  for (const [, client] of _shards) {
2549
3559
  client.close();
2550
3560
  }
2551
3561
  _shards.clear();
3562
+ _shardLastAccess.clear();
2552
3563
  _shardingEnabled = false;
2553
3564
  _encryptionKey = null;
2554
3565
  }
2555
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3566
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2556
3567
  var init_shard_manager = __esm({
2557
3568
  "src/lib/shard-manager.ts"() {
2558
3569
  "use strict";
2559
3570
  init_config();
2560
- SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
3571
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
3572
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3573
+ MAX_OPEN_SHARDS = 10;
3574
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2561
3575
  _shards = /* @__PURE__ */ new Map();
3576
+ _shardLastAccess = /* @__PURE__ */ new Map();
3577
+ _evictionTimer = null;
2562
3578
  _encryptionKey = null;
2563
3579
  _shardingEnabled = false;
2564
3580
  }
@@ -3330,8 +4346,8 @@ __export(wiki_client_exports, {
3330
4346
  listDocuments: () => listDocuments,
3331
4347
  listWorkspaces: () => listWorkspaces
3332
4348
  });
3333
- async function wikiFetch(config2, path21, method = "GET", body) {
3334
- const url = `${config2.baseUrl}/api/v1${path21}`;
4349
+ async function wikiFetch(config2, path23, method = "GET", body) {
4350
+ const url = `${config2.baseUrl}/api/v1${path23}`;
3335
4351
  const headers = {
3336
4352
  Authorization: `Bearer ${config2.apiKey}`,
3337
4353
  "Content-Type": "application/json"
@@ -3364,7 +4380,7 @@ async function wikiFetch(config2, path21, method = "GET", body) {
3364
4380
  }
3365
4381
  }
3366
4382
  if (!response.ok) {
3367
- throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
4383
+ throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
3368
4384
  }
3369
4385
  return response.json();
3370
4386
  } finally {
@@ -3657,13 +4673,13 @@ __export(graph_rag_exports, {
3657
4673
  resolveAlias: () => resolveAlias,
3658
4674
  storeExtraction: () => storeExtraction
3659
4675
  });
3660
- import crypto from "crypto";
4676
+ import crypto2 from "crypto";
3661
4677
  function normalizeEntityName(name) {
3662
4678
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
3663
4679
  }
3664
4680
  function entityId(name, type) {
3665
4681
  const normalized = normalizeEntityName(name);
3666
- return crypto.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
4682
+ return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
3667
4683
  }
3668
4684
  async function resolveAlias(client, name) {
3669
4685
  const normalized = normalizeEntityName(name);
@@ -3913,7 +4929,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
3913
4929
  const targetAlias = await resolveAlias(client, r.target);
3914
4930
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
3915
4931
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
3916
- const relId = crypto.randomUUID().slice(0, 16);
4932
+ const relId = crypto2.randomUUID().slice(0, 16);
3917
4933
  try {
3918
4934
  await client.execute({
3919
4935
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -3976,7 +4992,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
3976
4992
  }
3977
4993
  }
3978
4994
  for (const h of extraction.hyperedges) {
3979
- const hId = crypto.randomUUID().slice(0, 16);
4995
+ const hId = crypto2.randomUUID().slice(0, 16);
3980
4996
  try {
3981
4997
  await client.execute({
3982
4998
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -4040,7 +5056,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
4040
5056
  totalEntities += stored.entitiesStored;
4041
5057
  totalRelationships += stored.relationshipsStored;
4042
5058
  }
4043
- const contentHash = crypto.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
5059
+ const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
4044
5060
  await client.execute({
4045
5061
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
4046
5062
  args: [contentHash, contentHash, memoryId]
@@ -4393,9 +5409,12 @@ __export(license_exports, {
4393
5409
  stopLicenseRevalidation: () => stopLicenseRevalidation,
4394
5410
  validateLicense: () => validateLicense
4395
5411
  });
4396
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
5412
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
4397
5413
  import { randomUUID as randomUUID3 } from "crypto";
4398
- import path6 from "path";
5414
+ import { createRequire as createRequire2 } from "module";
5415
+ import { pathToFileURL as pathToFileURL2 } from "url";
5416
+ import os6 from "os";
5417
+ import path9 from "path";
4399
5418
  import { jwtVerify, importSPKI } from "jose";
4400
5419
  async function fetchRetry(url, init) {
4401
5420
  try {
@@ -4406,37 +5425,37 @@ async function fetchRetry(url, init) {
4406
5425
  }
4407
5426
  }
4408
5427
  function loadDeviceId() {
4409
- const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
5428
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
4410
5429
  try {
4411
- if (existsSync6(deviceJsonPath)) {
4412
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
5430
+ if (existsSync9(deviceJsonPath)) {
5431
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
4413
5432
  if (data.deviceId) return data.deviceId;
4414
5433
  }
4415
5434
  } catch {
4416
5435
  }
4417
5436
  try {
4418
- if (existsSync6(DEVICE_ID_PATH)) {
4419
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
5437
+ if (existsSync9(DEVICE_ID_PATH)) {
5438
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
4420
5439
  if (id2) return id2;
4421
5440
  }
4422
5441
  } catch {
4423
5442
  }
4424
5443
  const id = randomUUID3();
4425
- mkdirSync2(EXE_AI_DIR, { recursive: true });
4426
- writeFileSync2(DEVICE_ID_PATH, id, "utf8");
5444
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
5445
+ writeFileSync4(DEVICE_ID_PATH, id, "utf8");
4427
5446
  return id;
4428
5447
  }
4429
5448
  function loadLicense() {
4430
5449
  try {
4431
- if (!existsSync6(LICENSE_PATH)) return null;
4432
- return readFileSync4(LICENSE_PATH, "utf8").trim();
5450
+ if (!existsSync9(LICENSE_PATH)) return null;
5451
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
4433
5452
  } catch {
4434
5453
  return null;
4435
5454
  }
4436
5455
  }
4437
5456
  function saveLicense(apiKey) {
4438
- mkdirSync2(EXE_AI_DIR, { recursive: true });
4439
- writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
5457
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
5458
+ writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
4440
5459
  }
4441
5460
  async function verifyLicenseJwt(token) {
4442
5461
  try {
@@ -4462,8 +5481,8 @@ async function verifyLicenseJwt(token) {
4462
5481
  }
4463
5482
  async function getCachedLicense() {
4464
5483
  try {
4465
- if (!existsSync6(CACHE_PATH)) return null;
4466
- const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
5484
+ if (!existsSync9(CACHE_PATH)) return null;
5485
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
4467
5486
  if (!raw.token || typeof raw.token !== "string") return null;
4468
5487
  return await verifyLicenseJwt(raw.token);
4469
5488
  } catch {
@@ -4472,8 +5491,8 @@ async function getCachedLicense() {
4472
5491
  }
4473
5492
  function readCachedToken() {
4474
5493
  try {
4475
- if (!existsSync6(CACHE_PATH)) return null;
4476
- const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
5494
+ if (!existsSync9(CACHE_PATH)) return null;
5495
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
4477
5496
  return typeof raw.token === "string" ? raw.token : null;
4478
5497
  } catch {
4479
5498
  return null;
@@ -4495,68 +5514,142 @@ function getRawCachedPlan() {
4495
5514
  return {
4496
5515
  valid: true,
4497
5516
  plan,
4498
- email: payload.sub ?? "",
4499
- expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4500
- deviceLimit: limits.devices,
4501
- employeeLimit: limits.employees,
4502
- memoryLimit: limits.memories
5517
+ email: payload.sub ?? "",
5518
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
5519
+ deviceLimit: limits.devices,
5520
+ employeeLimit: limits.employees,
5521
+ memoryLimit: limits.memories
5522
+ };
5523
+ } catch {
5524
+ return null;
5525
+ }
5526
+ }
5527
+ function cacheResponse(token) {
5528
+ try {
5529
+ writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
5530
+ } catch {
5531
+ }
5532
+ }
5533
+ function loadPrismaForLicense() {
5534
+ if (_prismaFailed) return null;
5535
+ const dbUrl = process.env.DATABASE_URL;
5536
+ if (!dbUrl) {
5537
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
5538
+ if (!existsSync9(path9.join(exeDbRoot, "package.json"))) {
5539
+ _prismaFailed = true;
5540
+ return null;
5541
+ }
5542
+ }
5543
+ if (!_prismaPromise) {
5544
+ _prismaPromise = (async () => {
5545
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
5546
+ if (explicitPath) {
5547
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
5548
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
5549
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
5550
+ return new Ctor2();
5551
+ }
5552
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
5553
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
5554
+ const entry = req.resolve("@prisma/client");
5555
+ const mod = await import(pathToFileURL2(entry).href);
5556
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
5557
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
5558
+ return new Ctor();
5559
+ })().catch((err) => {
5560
+ _prismaFailed = true;
5561
+ _prismaPromise = null;
5562
+ throw err;
5563
+ });
5564
+ }
5565
+ return _prismaPromise;
5566
+ }
5567
+ async function validateViaPostgres(apiKey) {
5568
+ const loader = loadPrismaForLicense();
5569
+ if (!loader) return null;
5570
+ try {
5571
+ const prisma = await loader;
5572
+ const rows = await prisma.$queryRawUnsafe(
5573
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
5574
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
5575
+ apiKey
5576
+ );
5577
+ if (!rows || rows.length === 0) return null;
5578
+ const row = rows[0];
5579
+ if (row.status !== "active") return null;
5580
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
5581
+ const plan = row.plan;
5582
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
5583
+ return {
5584
+ valid: true,
5585
+ plan,
5586
+ email: row.email,
5587
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
5588
+ deviceLimit: row.device_limit ?? limits.devices,
5589
+ employeeLimit: row.employee_limit ?? limits.employees,
5590
+ memoryLimit: row.memory_limit ?? limits.memories
4503
5591
  };
4504
5592
  } catch {
4505
5593
  return null;
4506
5594
  }
4507
5595
  }
4508
- function cacheResponse(token) {
5596
+ async function validateViaCFWorker(apiKey, deviceId) {
4509
5597
  try {
4510
- writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
5598
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
5599
+ method: "POST",
5600
+ headers: { "Content-Type": "application/json" },
5601
+ body: JSON.stringify({ apiKey, deviceId }),
5602
+ signal: AbortSignal.timeout(1e4)
5603
+ });
5604
+ if (!res.ok) return null;
5605
+ const data = await res.json();
5606
+ if (data.error === "device_limit_exceeded") return null;
5607
+ if (!data.valid) return null;
5608
+ if (data.token) {
5609
+ cacheResponse(data.token);
5610
+ const verified = await verifyLicenseJwt(data.token);
5611
+ if (verified) return verified;
5612
+ }
5613
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
5614
+ return {
5615
+ valid: data.valid,
5616
+ plan: data.plan,
5617
+ email: data.email,
5618
+ expiresAt: data.expiresAt,
5619
+ deviceLimit: limits.devices,
5620
+ employeeLimit: limits.employees,
5621
+ memoryLimit: limits.memories
5622
+ };
4511
5623
  } catch {
5624
+ return null;
4512
5625
  }
4513
5626
  }
4514
5627
  async function validateLicense(apiKey, deviceId) {
4515
5628
  const did = deviceId ?? loadDeviceId();
5629
+ const pgResult = await validateViaPostgres(apiKey);
5630
+ if (pgResult) {
5631
+ try {
5632
+ writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
5633
+ } catch {
5634
+ }
5635
+ return pgResult;
5636
+ }
5637
+ const cfResult = await validateViaCFWorker(apiKey, did);
5638
+ if (cfResult) return cfResult;
5639
+ const cached = await getCachedLicense();
5640
+ if (cached) return cached;
4516
5641
  try {
4517
- const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4518
- method: "POST",
4519
- headers: { "Content-Type": "application/json" },
4520
- body: JSON.stringify({ apiKey, deviceId: did }),
4521
- signal: AbortSignal.timeout(1e4)
4522
- });
4523
- if (res.ok) {
4524
- const data = await res.json();
4525
- if (data.error === "device_limit_exceeded") {
4526
- const cached2 = await getCachedLicense();
4527
- if (cached2) return cached2;
4528
- const raw2 = getRawCachedPlan();
4529
- if (raw2) return { ...raw2, valid: false };
4530
- return { ...FREE_LICENSE, valid: false, plan: "free" };
4531
- }
4532
- if (data.token) {
4533
- cacheResponse(data.token);
4534
- const verified = await verifyLicenseJwt(data.token);
4535
- if (verified) return verified;
4536
- }
4537
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4538
- return {
4539
- valid: data.valid,
4540
- plan: data.plan,
4541
- email: data.email,
4542
- expiresAt: data.expiresAt,
4543
- deviceLimit: limits.devices,
4544
- employeeLimit: limits.employees,
4545
- memoryLimit: limits.memories
4546
- };
5642
+ if (existsSync9(CACHE_PATH)) {
5643
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
5644
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
5645
+ return raw.pgLicense;
5646
+ }
4547
5647
  }
4548
- const cached = await getCachedLicense();
4549
- if (cached) return cached;
4550
- const raw = getRawCachedPlan();
4551
- if (raw) return raw;
4552
- return { ...FREE_LICENSE, valid: false, plan: "free" };
4553
5648
  } catch {
4554
- const cached = await getCachedLicense();
4555
- if (cached) return cached;
4556
- const rawFallback = getRawCachedPlan();
4557
- if (rawFallback) return rawFallback;
4558
- return { ...FREE_LICENSE, valid: false, error: "offline" };
4559
5649
  }
5650
+ const rawFallback = getRawCachedPlan();
5651
+ if (rawFallback) return rawFallback;
5652
+ return { ...FREE_LICENSE, valid: false };
4560
5653
  }
4561
5654
  function getCacheAgeMs() {
4562
5655
  try {
@@ -4571,9 +5664,9 @@ async function checkLicense() {
4571
5664
  let key = loadLicense();
4572
5665
  if (!key) {
4573
5666
  try {
4574
- const configPath = path6.join(EXE_AI_DIR, "config.json");
4575
- if (existsSync6(configPath)) {
4576
- const raw = JSON.parse(readFileSync4(configPath, "utf8"));
5667
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
5668
+ if (existsSync9(configPath)) {
5669
+ const raw = JSON.parse(readFileSync6(configPath, "utf8"));
4577
5670
  const cloud = raw.cloud;
4578
5671
  if (cloud?.apiKey) {
4579
5672
  key = cloud.apiKey;
@@ -4727,14 +5820,14 @@ function stopLicenseRevalidation() {
4727
5820
  _revalTimer = null;
4728
5821
  }
4729
5822
  }
4730
- 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;
5823
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
4731
5824
  var init_license = __esm({
4732
5825
  "src/lib/license.ts"() {
4733
5826
  "use strict";
4734
5827
  init_config();
4735
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
4736
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
4737
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
5828
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
5829
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
5830
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4738
5831
  API_BASE = "https://askexe.com/cloud";
4739
5832
  RETRY_DELAY_MS = 500;
4740
5833
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -4758,6 +5851,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4758
5851
  employeeLimit: 1,
4759
5852
  memoryLimit: 5e3
4760
5853
  };
5854
+ _prismaPromise = null;
5855
+ _prismaFailed = false;
4761
5856
  CACHE_MAX_AGE_MS = 36e5;
4762
5857
  _revalTimer = null;
4763
5858
  }
@@ -4772,7 +5867,7 @@ __export(whatsapp_exports, {
4772
5867
  import { randomUUID as randomUUID4 } from "crypto";
4773
5868
  import { homedir } from "os";
4774
5869
  import { join } from "path";
4775
- import { mkdirSync as mkdirSync3 } from "fs";
5870
+ import { mkdirSync as mkdirSync4 } from "fs";
4776
5871
  function calculateBackoff(retryCount) {
4777
5872
  const base = Math.min(
4778
5873
  INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
@@ -4802,7 +5897,7 @@ var init_whatsapp = __esm({
4802
5897
  disconnectedAt = 0;
4803
5898
  async connect(config2) {
4804
5899
  this.authDir = config2.credentials.authDir ?? AUTH_DIR;
4805
- mkdirSync3(this.authDir, { recursive: true });
5900
+ mkdirSync4(this.authDir, { recursive: true });
4806
5901
  const baileys = await import("@whiskeysockets/baileys");
4807
5902
  const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = baileys;
4808
5903
  const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
@@ -5632,16 +6727,16 @@ __export(imessage_exports, {
5632
6727
  });
5633
6728
  import { execFile } from "child_process";
5634
6729
  import { promisify } from "util";
5635
- import os5 from "os";
5636
- import path7 from "path";
6730
+ import os7 from "os";
6731
+ import path10 from "path";
5637
6732
  var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
5638
6733
  var init_imessage = __esm({
5639
6734
  "src/gateway/adapters/imessage.ts"() {
5640
6735
  "use strict";
5641
6736
  execFileAsync = promisify(execFile);
5642
6737
  POLL_INTERVAL_MS = 5e3;
5643
- MESSAGES_DB_PATH = path7.join(
5644
- process.env.HOME ?? os5.homedir(),
6738
+ MESSAGES_DB_PATH = path10.join(
6739
+ process.env.HOME ?? os7.homedir(),
5645
6740
  "Library/Messages/chat.db"
5646
6741
  );
5647
6742
  IMessageAdapter = class {
@@ -5954,9 +7049,9 @@ __export(webhook_exports, {
5954
7049
  WebhookAdapter: () => WebhookAdapter
5955
7050
  });
5956
7051
  import { randomUUID as randomUUID7 } from "crypto";
5957
- function resolvePath(obj, path21) {
7052
+ function resolvePath(obj, path23) {
5958
7053
  let current = obj;
5959
- for (const segment of path21.split(".")) {
7054
+ for (const segment of path23.split(".")) {
5960
7055
  if (current == null || typeof current !== "object") return void 0;
5961
7056
  current = current[segment];
5962
7057
  }
@@ -6059,13 +7154,13 @@ __export(whatsapp_accounts_exports, {
6059
7154
  getDefaultAccount: () => getDefaultAccount,
6060
7155
  loadAccounts: () => loadAccounts
6061
7156
  });
6062
- import { readFileSync as readFileSync5 } from "fs";
7157
+ import { readFileSync as readFileSync7 } from "fs";
6063
7158
  import { join as join2 } from "path";
6064
7159
  import { homedir as homedir2 } from "os";
6065
7160
  function loadAccounts() {
6066
7161
  if (cachedAccounts !== null) return cachedAccounts;
6067
7162
  try {
6068
- const raw = readFileSync5(CONFIG_PATH2, "utf8");
7163
+ const raw = readFileSync7(CONFIG_PATH2, "utf8");
6069
7164
  const parsed = JSON.parse(raw);
6070
7165
  if (!Array.isArray(parsed)) {
6071
7166
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -6105,13 +7200,13 @@ var init_whatsapp_accounts = __esm({
6105
7200
  });
6106
7201
 
6107
7202
  // src/lib/session-registry.ts
6108
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync7 } from "fs";
6109
- import path8 from "path";
6110
- import os6 from "os";
7203
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
7204
+ import path11 from "path";
7205
+ import os8 from "os";
6111
7206
  function registerSession(entry) {
6112
- const dir = path8.dirname(REGISTRY_PATH);
6113
- if (!existsSync7(dir)) {
6114
- mkdirSync4(dir, { recursive: true });
7207
+ const dir = path11.dirname(REGISTRY_PATH);
7208
+ if (!existsSync10(dir)) {
7209
+ mkdirSync5(dir, { recursive: true });
6115
7210
  }
6116
7211
  const sessions = listSessions();
6117
7212
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -6120,11 +7215,11 @@ function registerSession(entry) {
6120
7215
  } else {
6121
7216
  sessions.push(entry);
6122
7217
  }
6123
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
7218
+ writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
6124
7219
  }
6125
7220
  function listSessions() {
6126
7221
  try {
6127
- const raw = readFileSync6(REGISTRY_PATH, "utf8");
7222
+ const raw = readFileSync8(REGISTRY_PATH, "utf8");
6128
7223
  return JSON.parse(raw);
6129
7224
  } catch {
6130
7225
  return [];
@@ -6134,7 +7229,7 @@ var REGISTRY_PATH;
6134
7229
  var init_session_registry = __esm({
6135
7230
  "src/lib/session-registry.ts"() {
6136
7231
  "use strict";
6137
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
7232
+ REGISTRY_PATH = path11.join(os8.homedir(), ".exe-os", "session-registry.json");
6138
7233
  }
6139
7234
  });
6140
7235
 
@@ -6386,67 +7481,6 @@ var init_provider_table = __esm({
6386
7481
  }
6387
7482
  });
6388
7483
 
6389
- // src/lib/runtime-table.ts
6390
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
6391
- var init_runtime_table = __esm({
6392
- "src/lib/runtime-table.ts"() {
6393
- "use strict";
6394
- RUNTIME_TABLE = {
6395
- codex: {
6396
- binary: "codex",
6397
- launchMode: "interactive",
6398
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
6399
- inlineFlag: "--no-alt-screen",
6400
- apiKeyEnv: "OPENAI_API_KEY",
6401
- defaultModel: "gpt-5.4"
6402
- },
6403
- opencode: {
6404
- binary: "opencode",
6405
- launchMode: "exec",
6406
- autoApproveFlag: "--dangerously-skip-permissions",
6407
- inlineFlag: "",
6408
- apiKeyEnv: "ANTHROPIC_API_KEY",
6409
- defaultModel: "anthropic/claude-sonnet-4-6"
6410
- }
6411
- };
6412
- DEFAULT_RUNTIME = "claude";
6413
- }
6414
- });
6415
-
6416
- // src/lib/agent-config.ts
6417
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
6418
- import path9 from "path";
6419
- function loadAgentConfig() {
6420
- if (!existsSync8(AGENT_CONFIG_PATH)) return {};
6421
- try {
6422
- return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
6423
- } catch {
6424
- return {};
6425
- }
6426
- }
6427
- function getAgentRuntime(agentId) {
6428
- const config2 = loadAgentConfig();
6429
- const entry = config2[agentId];
6430
- if (entry) return entry;
6431
- const orgDefault = config2["default"];
6432
- if (orgDefault) return orgDefault;
6433
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
6434
- }
6435
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
6436
- var init_agent_config = __esm({
6437
- "src/lib/agent-config.ts"() {
6438
- "use strict";
6439
- init_config();
6440
- init_runtime_table();
6441
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
6442
- DEFAULT_MODELS = {
6443
- claude: "claude-opus-4",
6444
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
6445
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
6446
- };
6447
- }
6448
- });
6449
-
6450
7484
  // src/lib/intercom-queue.ts
6451
7485
  var intercom_queue_exports = {};
6452
7486
  __export(intercom_queue_exports, {
@@ -6456,17 +7490,17 @@ __export(intercom_queue_exports, {
6456
7490
  queueIntercom: () => queueIntercom,
6457
7491
  readQueue: () => readQueue
6458
7492
  });
6459
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
6460
- import path10 from "path";
6461
- import os7 from "os";
7493
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
7494
+ import path12 from "path";
7495
+ import os9 from "os";
6462
7496
  function ensureDir() {
6463
- const dir = path10.dirname(QUEUE_PATH);
6464
- if (!existsSync9(dir)) mkdirSync6(dir, { recursive: true });
7497
+ const dir = path12.dirname(QUEUE_PATH);
7498
+ if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true });
6465
7499
  }
6466
7500
  function readQueue() {
6467
7501
  try {
6468
- if (!existsSync9(QUEUE_PATH)) return [];
6469
- return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
7502
+ if (!existsSync11(QUEUE_PATH)) return [];
7503
+ return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
6470
7504
  } catch {
6471
7505
  return [];
6472
7506
  }
@@ -6474,7 +7508,7 @@ function readQueue() {
6474
7508
  function writeQueue(queue) {
6475
7509
  ensureDir();
6476
7510
  const tmp = `${QUEUE_PATH}.tmp`;
6477
- writeFileSync5(tmp, JSON.stringify(queue, null, 2));
7511
+ writeFileSync6(tmp, JSON.stringify(queue, null, 2));
6478
7512
  renameSync3(tmp, QUEUE_PATH);
6479
7513
  }
6480
7514
  function queueIntercom(targetSession, reason) {
@@ -6566,20 +7600,20 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
6566
7600
  var init_intercom_queue = __esm({
6567
7601
  "src/lib/intercom-queue.ts"() {
6568
7602
  "use strict";
6569
- QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
7603
+ QUEUE_PATH = path12.join(os9.homedir(), ".exe-os", "intercom-queue.json");
6570
7604
  MAX_RETRIES2 = 5;
6571
7605
  TTL_MS = 60 * 60 * 1e3;
6572
- INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
7606
+ INTERCOM_LOG = path12.join(os9.homedir(), ".exe-os", "intercom.log");
6573
7607
  }
6574
7608
  });
6575
7609
 
6576
7610
  // src/lib/plan-limits.ts
6577
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
6578
- import path11 from "path";
7611
+ import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
7612
+ import path13 from "path";
6579
7613
  function getLicenseSync() {
6580
7614
  try {
6581
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
6582
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
7615
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
7616
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
6583
7617
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
6584
7618
  const parts = raw.token.split(".");
6585
7619
  if (parts.length !== 3) return freeLicense();
@@ -6617,8 +7651,8 @@ function assertEmployeeLimitSync(rosterPath) {
6617
7651
  const filePath = rosterPath ?? EMPLOYEES_PATH;
6618
7652
  let count = 0;
6619
7653
  try {
6620
- if (existsSync10(filePath)) {
6621
- const raw = readFileSync9(filePath, "utf8");
7654
+ if (existsSync12(filePath)) {
7655
+ const raw = readFileSync10(filePath, "utf8");
6622
7656
  const employees = JSON.parse(raw);
6623
7657
  count = Array.isArray(employees) ? employees.length : 0;
6624
7658
  }
@@ -6647,29 +7681,63 @@ var init_plan_limits = __esm({
6647
7681
  this.name = "PlanLimitError";
6648
7682
  }
6649
7683
  };
6650
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
7684
+ CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
7685
+ }
7686
+ });
7687
+
7688
+ // src/lib/task-scope.ts
7689
+ function getCurrentSessionScope() {
7690
+ try {
7691
+ return resolveExeSession();
7692
+ } catch {
7693
+ return null;
7694
+ }
7695
+ }
7696
+ function sessionScopeFilter(sessionScope, tableAlias) {
7697
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7698
+ if (!scope) return { sql: "", args: [] };
7699
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7700
+ return {
7701
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
7702
+ args: [scope]
7703
+ };
7704
+ }
7705
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
7706
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7707
+ if (!scope) return { sql: "", args: [] };
7708
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7709
+ return {
7710
+ sql: ` AND ${col} = ?`,
7711
+ args: [scope]
7712
+ };
7713
+ }
7714
+ var init_task_scope = __esm({
7715
+ "src/lib/task-scope.ts"() {
7716
+ "use strict";
7717
+ init_tmux_routing();
6651
7718
  }
6652
7719
  });
6653
7720
 
6654
7721
  // src/lib/notifications.ts
6655
- import crypto3 from "crypto";
6656
- import path12 from "path";
6657
- import os8 from "os";
7722
+ import crypto4 from "crypto";
7723
+ import path14 from "path";
7724
+ import os10 from "os";
6658
7725
  import {
6659
- readFileSync as readFileSync10,
7726
+ readFileSync as readFileSync11,
6660
7727
  readdirSync as readdirSync2,
6661
7728
  unlinkSync as unlinkSync3,
6662
- existsSync as existsSync11,
7729
+ existsSync as existsSync13,
6663
7730
  rmdirSync
6664
7731
  } from "fs";
6665
7732
  async function writeNotification(notification) {
6666
7733
  try {
6667
7734
  const client = getClient();
6668
- const id = crypto3.randomUUID();
7735
+ const id = crypto4.randomUUID();
6669
7736
  const now = (/* @__PURE__ */ new Date()).toISOString();
7737
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
6670
7738
  await client.execute({
6671
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
6672
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
7739
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
7740
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
6673
7741
  args: [
6674
7742
  id,
6675
7743
  notification.agentId,
@@ -6678,6 +7746,7 @@ async function writeNotification(notification) {
6678
7746
  notification.project,
6679
7747
  notification.summary,
6680
7748
  notification.taskFile ?? null,
7749
+ sessionScope,
6681
7750
  now
6682
7751
  ]
6683
7752
  });
@@ -6686,12 +7755,14 @@ async function writeNotification(notification) {
6686
7755
  `);
6687
7756
  }
6688
7757
  }
6689
- async function markAsReadByTaskFile(taskFile) {
7758
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
6690
7759
  try {
6691
7760
  const client = getClient();
7761
+ const scope = strictSessionScopeFilter(sessionScope);
6692
7762
  await client.execute({
6693
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
6694
- args: [taskFile]
7763
+ sql: `UPDATE notifications SET read = 1
7764
+ WHERE task_file = ? AND read = 0${scope.sql}`,
7765
+ args: [taskFile, ...scope.args]
6695
7766
  });
6696
7767
  } catch {
6697
7768
  }
@@ -6700,11 +7771,12 @@ var init_notifications = __esm({
6700
7771
  "src/lib/notifications.ts"() {
6701
7772
  "use strict";
6702
7773
  init_database();
7774
+ init_task_scope();
6703
7775
  }
6704
7776
  });
6705
7777
 
6706
7778
  // src/lib/session-kill-telemetry.ts
6707
- import crypto4 from "crypto";
7779
+ import crypto5 from "crypto";
6708
7780
  async function recordSessionKill(input) {
6709
7781
  try {
6710
7782
  const client = getClient();
@@ -6714,7 +7786,7 @@ async function recordSessionKill(input) {
6714
7786
  ticks_idle, estimated_tokens_saved)
6715
7787
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
6716
7788
  args: [
6717
- crypto4.randomUUID(),
7789
+ crypto5.randomUUID(),
6718
7790
  input.sessionName,
6719
7791
  input.agentId,
6720
7792
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -6737,37 +7809,13 @@ var init_session_kill_telemetry = __esm({
6737
7809
  }
6738
7810
  });
6739
7811
 
6740
- // src/lib/task-scope.ts
6741
- function getCurrentSessionScope() {
6742
- try {
6743
- return resolveExeSession();
6744
- } catch {
6745
- return null;
6746
- }
6747
- }
6748
- function sessionScopeFilter(sessionScope, tableAlias) {
6749
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
6750
- if (!scope) return { sql: "", args: [] };
6751
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
6752
- return {
6753
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
6754
- args: [scope]
6755
- };
6756
- }
6757
- var init_task_scope = __esm({
6758
- "src/lib/task-scope.ts"() {
6759
- "use strict";
6760
- init_tmux_routing();
6761
- }
6762
- });
6763
-
6764
7812
  // src/lib/tasks-crud.ts
6765
- import crypto5 from "crypto";
6766
- import path13 from "path";
6767
- import os9 from "os";
7813
+ import crypto6 from "crypto";
7814
+ import path15 from "path";
7815
+ import os11 from "os";
6768
7816
  import { execSync as execSync4 } from "child_process";
6769
7817
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
6770
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
7818
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
6771
7819
  async function writeCheckpoint(input) {
6772
7820
  const client = getClient();
6773
7821
  const row = await resolveTask(client, input.taskId);
@@ -6883,7 +7931,7 @@ async function resolveTask(client, identifier, scopeSession) {
6883
7931
  }
6884
7932
  async function createTaskCore(input) {
6885
7933
  const client = getClient();
6886
- const id = crypto5.randomUUID();
7934
+ const id = crypto6.randomUUID();
6887
7935
  const now = (/* @__PURE__ */ new Date()).toISOString();
6888
7936
  const slug = slugify(input.title);
6889
7937
  let earlySessionScope = null;
@@ -6942,8 +7990,8 @@ ${laneWarning}` : laneWarning;
6942
7990
  }
6943
7991
  if (input.baseDir) {
6944
7992
  try {
6945
- await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
6946
- await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
7993
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
7994
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
6947
7995
  await ensureArchitectureDoc(input.baseDir, input.projectName);
6948
7996
  await ensureGitignoreExe(input.baseDir);
6949
7997
  } catch {
@@ -6979,13 +8027,19 @@ ${laneWarning}` : laneWarning;
6979
8027
  });
6980
8028
  if (input.baseDir) {
6981
8029
  try {
6982
- const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
6983
- const mdPath = path13.join(EXE_OS_DIR, taskFile);
6984
- const mdDir = path13.dirname(mdPath);
6985
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
8030
+ const EXE_OS_DIR = path15.join(os11.homedir(), ".exe-os");
8031
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
8032
+ const mdDir = path15.dirname(mdPath);
8033
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
6986
8034
  const reviewer = input.reviewer ?? input.assignedBy;
6987
8035
  const mdContent = `# ${input.title}
6988
8036
 
8037
+ ## MANDATORY: When done
8038
+
8039
+ You MUST call update_task with status "done" and a result summary when finished.
8040
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
8041
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
8042
+
6989
8043
  **ID:** ${id}
6990
8044
  **Status:** ${initialStatus}
6991
8045
  **Priority:** ${input.priority}
@@ -6999,12 +8053,6 @@ ${laneWarning}` : laneWarning;
6999
8053
  ## Context
7000
8054
 
7001
8055
  ${input.context}
7002
-
7003
- ## MANDATORY: When done
7004
-
7005
- You MUST call update_task with status "done" and a result summary when finished.
7006
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
7007
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
7008
8056
  `;
7009
8057
  await writeFile4(mdPath, mdContent, "utf-8");
7010
8058
  } catch (err) {
@@ -7253,7 +8301,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
7253
8301
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
7254
8302
  } catch {
7255
8303
  }
7256
- if (input.status === "done" || input.status === "cancelled") {
8304
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
7257
8305
  try {
7258
8306
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
7259
8307
  clearQueueForAgent2(String(row.assigned_to));
@@ -7282,9 +8330,9 @@ async function deleteTaskCore(taskId, _baseDir) {
7282
8330
  return { taskFile, assignedTo, assignedBy, taskSlug };
7283
8331
  }
7284
8332
  async function ensureArchitectureDoc(baseDir, projectName) {
7285
- const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
8333
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
7286
8334
  try {
7287
- if (existsSync12(archPath)) return;
8335
+ if (existsSync14(archPath)) return;
7288
8336
  const template = [
7289
8337
  `# ${projectName} \u2014 System Architecture`,
7290
8338
  "",
@@ -7317,10 +8365,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
7317
8365
  }
7318
8366
  }
7319
8367
  async function ensureGitignoreExe(baseDir) {
7320
- const gitignorePath = path13.join(baseDir, ".gitignore");
8368
+ const gitignorePath = path15.join(baseDir, ".gitignore");
7321
8369
  try {
7322
- if (existsSync12(gitignorePath)) {
7323
- const content = readFileSync11(gitignorePath, "utf-8");
8370
+ if (existsSync14(gitignorePath)) {
8371
+ const content = readFileSync12(gitignorePath, "utf-8");
7324
8372
  if (/^\/?exe\/?$/m.test(content)) return;
7325
8373
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
7326
8374
  } else {
@@ -7351,58 +8399,42 @@ var init_tasks_crud = __esm({
7351
8399
  });
7352
8400
 
7353
8401
  // src/lib/tasks-review.ts
7354
- import path14 from "path";
7355
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
8402
+ import path16 from "path";
8403
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
7356
8404
  async function countPendingReviews(sessionScope) {
7357
8405
  const client = getClient();
7358
- if (sessionScope) {
7359
- const result2 = await client.execute({
7360
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
7361
- args: [sessionScope]
7362
- });
7363
- return Number(result2.rows[0]?.cnt) || 0;
7364
- }
8406
+ const scope = strictSessionScopeFilter(
8407
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8408
+ );
7365
8409
  const result = await client.execute({
7366
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
7367
- args: []
8410
+ sql: `SELECT COUNT(*) as cnt FROM tasks
8411
+ WHERE status = 'needs_review'${scope.sql}`,
8412
+ args: [...scope.args]
7368
8413
  });
7369
8414
  return Number(result.rows[0]?.cnt) || 0;
7370
8415
  }
7371
8416
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
7372
8417
  const client = getClient();
7373
- if (sessionScope) {
7374
- const result2 = await client.execute({
7375
- sql: `SELECT COUNT(*) as cnt FROM tasks
7376
- WHERE status = 'needs_review' AND updated_at > ?
7377
- AND session_scope = ?`,
7378
- args: [sinceIso, sessionScope]
7379
- });
7380
- return Number(result2.rows[0]?.cnt) || 0;
7381
- }
8418
+ const scope = strictSessionScopeFilter(
8419
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8420
+ );
7382
8421
  const result = await client.execute({
7383
8422
  sql: `SELECT COUNT(*) as cnt FROM tasks
7384
- WHERE status = 'needs_review' AND updated_at > ?`,
7385
- args: [sinceIso]
8423
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
8424
+ args: [sinceIso, ...scope.args]
7386
8425
  });
7387
8426
  return Number(result.rows[0]?.cnt) || 0;
7388
8427
  }
7389
8428
  async function listPendingReviews(limit, sessionScope) {
7390
8429
  const client = getClient();
7391
- if (sessionScope) {
7392
- const result2 = await client.execute({
7393
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
7394
- WHERE status = 'needs_review'
7395
- AND session_scope = ?
7396
- ORDER BY updated_at ASC LIMIT ?`,
7397
- args: [sessionScope, limit]
7398
- });
7399
- return result2.rows;
7400
- }
8430
+ const scope = strictSessionScopeFilter(
8431
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8432
+ );
7401
8433
  const result = await client.execute({
7402
8434
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
7403
- WHERE status = 'needs_review'
8435
+ WHERE status = 'needs_review'${scope.sql}
7404
8436
  ORDER BY updated_at ASC LIMIT ?`,
7405
- args: [limit]
8437
+ args: [...scope.args, limit]
7406
8438
  });
7407
8439
  return result.rows;
7408
8440
  }
@@ -7414,7 +8446,7 @@ async function cleanupOrphanedReviews() {
7414
8446
  WHERE status IN ('open', 'needs_review', 'in_progress')
7415
8447
  AND assigned_by = 'system'
7416
8448
  AND title LIKE 'Review:%'
7417
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
8449
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
7418
8450
  args: [now]
7419
8451
  });
7420
8452
  const r1b = await client.execute({
@@ -7533,11 +8565,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
7533
8565
  );
7534
8566
  }
7535
8567
  try {
7536
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
7537
- if (existsSync13(cacheDir)) {
8568
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
8569
+ if (existsSync15(cacheDir)) {
7538
8570
  for (const f of readdirSync3(cacheDir)) {
7539
8571
  if (f.startsWith("review-notified-")) {
7540
- unlinkSync4(path14.join(cacheDir, f));
8572
+ unlinkSync4(path16.join(cacheDir, f));
7541
8573
  }
7542
8574
  }
7543
8575
  }
@@ -7554,11 +8586,12 @@ var init_tasks_review = __esm({
7554
8586
  init_tmux_routing();
7555
8587
  init_session_key();
7556
8588
  init_state_bus();
8589
+ init_task_scope();
7557
8590
  }
7558
8591
  });
7559
8592
 
7560
8593
  // src/lib/tasks-chain.ts
7561
- import path15 from "path";
8594
+ import path17 from "path";
7562
8595
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
7563
8596
  async function cascadeUnblock(taskId, baseDir, now) {
7564
8597
  const client = getClient();
@@ -7575,7 +8608,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
7575
8608
  });
7576
8609
  for (const ur of unblockedRows.rows) {
7577
8610
  try {
7578
- const ubFile = path15.join(baseDir, String(ur.task_file));
8611
+ const ubFile = path17.join(baseDir, String(ur.task_file));
7579
8612
  let ubContent = await readFile4(ubFile, "utf-8");
7580
8613
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
7581
8614
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -7610,7 +8643,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
7610
8643
  const scScope = sessionScopeFilter();
7611
8644
  const remaining = await client.execute({
7612
8645
  sql: `SELECT COUNT(*) as cnt FROM tasks
7613
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
8646
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
7614
8647
  args: [parentTaskId, ...scScope.args]
7615
8648
  });
7616
8649
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -7644,7 +8677,7 @@ var init_tasks_chain = __esm({
7644
8677
 
7645
8678
  // src/lib/project-name.ts
7646
8679
  import { execSync as execSync5 } from "child_process";
7647
- import path16 from "path";
8680
+ import path18 from "path";
7648
8681
  function getProjectName(cwd) {
7649
8682
  const dir = cwd ?? process.cwd();
7650
8683
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -7657,7 +8690,7 @@ function getProjectName(cwd) {
7657
8690
  timeout: 2e3,
7658
8691
  stdio: ["pipe", "pipe", "pipe"]
7659
8692
  }).trim();
7660
- repoRoot = path16.dirname(gitCommonDir);
8693
+ repoRoot = path18.dirname(gitCommonDir);
7661
8694
  } catch {
7662
8695
  repoRoot = execSync5("git rev-parse --show-toplevel", {
7663
8696
  cwd: dir,
@@ -7666,11 +8699,11 @@ function getProjectName(cwd) {
7666
8699
  stdio: ["pipe", "pipe", "pipe"]
7667
8700
  }).trim();
7668
8701
  }
7669
- _cached2 = path16.basename(repoRoot);
8702
+ _cached2 = path18.basename(repoRoot);
7670
8703
  _cachedCwd = dir;
7671
8704
  return _cached2;
7672
8705
  } catch {
7673
- _cached2 = path16.basename(dir);
8706
+ _cached2 = path18.basename(dir);
7674
8707
  _cachedCwd = dir;
7675
8708
  return _cached2;
7676
8709
  }
@@ -7813,10 +8846,10 @@ var init_tasks_notify = __esm({
7813
8846
  });
7814
8847
 
7815
8848
  // src/lib/behaviors.ts
7816
- import crypto6 from "crypto";
8849
+ import crypto7 from "crypto";
7817
8850
  async function storeBehavior(opts) {
7818
8851
  const client = getClient();
7819
- const id = crypto6.randomUUID();
8852
+ const id = crypto7.randomUUID();
7820
8853
  const now = (/* @__PURE__ */ new Date()).toISOString();
7821
8854
  await client.execute({
7822
8855
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -7845,7 +8878,7 @@ __export(skill_learning_exports, {
7845
8878
  storeTrajectory: () => storeTrajectory,
7846
8879
  sweepTrajectories: () => sweepTrajectories
7847
8880
  });
7848
- import crypto7 from "crypto";
8881
+ import crypto8 from "crypto";
7849
8882
  async function extractTrajectory(taskId, agentId) {
7850
8883
  const client = getClient();
7851
8884
  const result = await client.execute({
@@ -7874,11 +8907,11 @@ async function extractTrajectory(taskId, agentId) {
7874
8907
  return signature;
7875
8908
  }
7876
8909
  function hashSignature(signature) {
7877
- return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
8910
+ return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
7878
8911
  }
7879
8912
  async function storeTrajectory(opts) {
7880
8913
  const client = getClient();
7881
- const id = crypto7.randomUUID();
8914
+ const id = crypto8.randomUUID();
7882
8915
  const now = (/* @__PURE__ */ new Date()).toISOString();
7883
8916
  const signatureHash = hashSignature(opts.signature);
7884
8917
  await client.execute({
@@ -8143,8 +9176,8 @@ __export(tasks_exports, {
8143
9176
  updateTaskStatus: () => updateTaskStatus,
8144
9177
  writeCheckpoint: () => writeCheckpoint
8145
9178
  });
8146
- import path17 from "path";
8147
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
9179
+ import path19 from "path";
9180
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
8148
9181
  async function createTask(input) {
8149
9182
  const result = await createTaskCore(input);
8150
9183
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -8163,12 +9196,12 @@ async function updateTask(input) {
8163
9196
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
8164
9197
  try {
8165
9198
  const agent = String(row.assigned_to);
8166
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
8167
- const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
9199
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
9200
+ const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
8168
9201
  if (input.status === "in_progress") {
8169
9202
  mkdirSync7(cacheDir, { recursive: true });
8170
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
8171
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
9203
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
9204
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
8172
9205
  try {
8173
9206
  unlinkSync5(cachePath);
8174
9207
  } catch {
@@ -8176,10 +9209,10 @@ async function updateTask(input) {
8176
9209
  }
8177
9210
  } catch {
8178
9211
  }
8179
- if (input.status === "done") {
9212
+ if (input.status === "done" || input.status === "closed") {
8180
9213
  await cleanupReviewFile(row, taskFile, input.baseDir);
8181
9214
  }
8182
- if (input.status === "done" || input.status === "cancelled") {
9215
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
8183
9216
  try {
8184
9217
  const client = getClient();
8185
9218
  const taskTitle = String(row.title);
@@ -8195,7 +9228,7 @@ async function updateTask(input) {
8195
9228
  if (!isCoordinatorName(assignedAgent)) {
8196
9229
  try {
8197
9230
  const draftClient = getClient();
8198
- if (input.status === "done") {
9231
+ if (input.status === "done" || input.status === "closed") {
8199
9232
  await draftClient.execute({
8200
9233
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
8201
9234
  args: [assignedAgent]
@@ -8212,7 +9245,7 @@ async function updateTask(input) {
8212
9245
  try {
8213
9246
  const client = getClient();
8214
9247
  const cascaded = await client.execute({
8215
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
9248
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
8216
9249
  WHERE parent_task_id = ? AND status = 'needs_review'`,
8217
9250
  args: [now, taskId]
8218
9251
  });
@@ -8225,14 +9258,14 @@ async function updateTask(input) {
8225
9258
  } catch {
8226
9259
  }
8227
9260
  }
8228
- const isTerminal = input.status === "done" || input.status === "needs_review";
9261
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
8229
9262
  if (isTerminal) {
8230
9263
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
8231
9264
  if (!isCoordinator) {
8232
9265
  notifyTaskDone();
8233
9266
  }
8234
9267
  await markTaskNotificationsRead(taskFile);
8235
- if (input.status === "done") {
9268
+ if (input.status === "done" || input.status === "closed") {
8236
9269
  try {
8237
9270
  await cascadeUnblock(taskId, input.baseDir, now);
8238
9271
  } catch {
@@ -8252,7 +9285,7 @@ async function updateTask(input) {
8252
9285
  }
8253
9286
  }
8254
9287
  }
8255
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
9288
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8256
9289
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8257
9290
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
8258
9291
  taskId,
@@ -8624,6 +9657,7 @@ __export(tmux_routing_exports, {
8624
9657
  isEmployeeAlive: () => isEmployeeAlive,
8625
9658
  isExeSession: () => isExeSession,
8626
9659
  isSessionBusy: () => isSessionBusy,
9660
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
8627
9661
  notifyParentExe: () => notifyParentExe,
8628
9662
  parseParentExe: () => parseParentExe,
8629
9663
  registerParentExe: () => registerParentExe,
@@ -8634,13 +9668,13 @@ __export(tmux_routing_exports, {
8634
9668
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
8635
9669
  });
8636
9670
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
8637
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
8638
- import path18 from "path";
8639
- import os10 from "os";
9671
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
9672
+ import path20 from "path";
9673
+ import os12 from "os";
8640
9674
  import { fileURLToPath as fileURLToPath2 } from "url";
8641
9675
  import { unlinkSync as unlinkSync6 } from "fs";
8642
9676
  function spawnLockPath(sessionName) {
8643
- return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
9677
+ return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
8644
9678
  }
8645
9679
  function isProcessAlive(pid) {
8646
9680
  try {
@@ -8651,13 +9685,13 @@ function isProcessAlive(pid) {
8651
9685
  }
8652
9686
  }
8653
9687
  function acquireSpawnLock2(sessionName) {
8654
- if (!existsSync14(SPAWN_LOCK_DIR)) {
9688
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
8655
9689
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
8656
9690
  }
8657
9691
  const lockFile = spawnLockPath(sessionName);
8658
- if (existsSync14(lockFile)) {
9692
+ if (existsSync16(lockFile)) {
8659
9693
  try {
8660
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
9694
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
8661
9695
  const age = Date.now() - lock.timestamp;
8662
9696
  if (isProcessAlive(lock.pid) && age < 6e4) {
8663
9697
  return false;
@@ -8665,7 +9699,7 @@ function acquireSpawnLock2(sessionName) {
8665
9699
  } catch {
8666
9700
  }
8667
9701
  }
8668
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
9702
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
8669
9703
  return true;
8670
9704
  }
8671
9705
  function releaseSpawnLock2(sessionName) {
@@ -8677,13 +9711,13 @@ function releaseSpawnLock2(sessionName) {
8677
9711
  function resolveBehaviorsExporterScript() {
8678
9712
  try {
8679
9713
  const thisFile = fileURLToPath2(import.meta.url);
8680
- const scriptPath = path18.join(
8681
- path18.dirname(thisFile),
9714
+ const scriptPath = path20.join(
9715
+ path20.dirname(thisFile),
8682
9716
  "..",
8683
9717
  "bin",
8684
9718
  "exe-export-behaviors.js"
8685
9719
  );
8686
- return existsSync14(scriptPath) ? scriptPath : null;
9720
+ return existsSync16(scriptPath) ? scriptPath : null;
8687
9721
  } catch {
8688
9722
  return null;
8689
9723
  }
@@ -8749,12 +9783,12 @@ function extractRootExe(name) {
8749
9783
  return parts.length > 0 ? parts[parts.length - 1] : null;
8750
9784
  }
8751
9785
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
8752
- if (!existsSync14(SESSION_CACHE)) {
9786
+ if (!existsSync16(SESSION_CACHE)) {
8753
9787
  mkdirSync8(SESSION_CACHE, { recursive: true });
8754
9788
  }
8755
9789
  const rootExe = extractRootExe(parentExe) ?? parentExe;
8756
- const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
8757
- writeFileSync7(filePath, JSON.stringify({
9790
+ const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
9791
+ writeFileSync8(filePath, JSON.stringify({
8758
9792
  parentExe: rootExe,
8759
9793
  dispatchedBy: dispatchedBy || rootExe,
8760
9794
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8762,7 +9796,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
8762
9796
  }
8763
9797
  function getParentExe(sessionKey) {
8764
9798
  try {
8765
- const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
9799
+ const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
8766
9800
  return data.parentExe || null;
8767
9801
  } catch {
8768
9802
  return null;
@@ -8770,8 +9804,8 @@ function getParentExe(sessionKey) {
8770
9804
  }
8771
9805
  function getDispatchedBy(sessionKey) {
8772
9806
  try {
8773
- const data = JSON.parse(readFileSync12(
8774
- path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
9807
+ const data = JSON.parse(readFileSync13(
9808
+ path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
8775
9809
  "utf8"
8776
9810
  ));
8777
9811
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -8841,8 +9875,8 @@ async function verifyPaneAtCapacity(sessionName) {
8841
9875
  }
8842
9876
  function readDebounceState() {
8843
9877
  try {
8844
- if (!existsSync14(DEBOUNCE_FILE)) return {};
8845
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
9878
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
9879
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
8846
9880
  const state = {};
8847
9881
  for (const [key, val] of Object.entries(raw)) {
8848
9882
  if (typeof val === "number") {
@@ -8858,8 +9892,8 @@ function readDebounceState() {
8858
9892
  }
8859
9893
  function writeDebounceState(state) {
8860
9894
  try {
8861
- if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
8862
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
9895
+ if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
9896
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
8863
9897
  } catch {
8864
9898
  }
8865
9899
  }
@@ -8957,8 +9991,8 @@ function sendIntercom(targetSession) {
8957
9991
  try {
8958
9992
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8959
9993
  const agent = baseAgentName(rawAgent);
8960
- const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
8961
- if (existsSync14(markerPath)) {
9994
+ const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
9995
+ if (existsSync16(markerPath)) {
8962
9996
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
8963
9997
  return "debounced";
8964
9998
  }
@@ -8967,8 +10001,8 @@ function sendIntercom(targetSession) {
8967
10001
  try {
8968
10002
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8969
10003
  const agent = baseAgentName(rawAgent);
8970
- const taskDir = path18.join(process.cwd(), "exe", agent);
8971
- if (existsSync14(taskDir)) {
10004
+ const taskDir = path20.join(process.cwd(), "exe", agent);
10005
+ if (existsSync16(taskDir)) {
8972
10006
  const files = readdirSync4(taskDir).filter(
8973
10007
  (f) => f.endsWith(".md") && f !== "DONE.txt"
8974
10008
  );
@@ -9028,6 +10062,21 @@ function notifyParentExe(sessionKey) {
9028
10062
  }
9029
10063
  return true;
9030
10064
  }
10065
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
10066
+ const transport = getTransport();
10067
+ try {
10068
+ const sessions = transport.listSessions();
10069
+ if (!sessions.includes(coordinatorSession)) return false;
10070
+ execSync6(
10071
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
10072
+ { timeout: 3e3 }
10073
+ );
10074
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
10075
+ return true;
10076
+ } catch {
10077
+ return false;
10078
+ }
10079
+ }
9031
10080
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
9032
10081
  if (isCoordinatorName(employeeName)) {
9033
10082
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -9101,26 +10150,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9101
10150
  const transport = getTransport();
9102
10151
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
9103
10152
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
9104
- const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
9105
- const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
9106
- if (!existsSync14(logDir)) {
10153
+ const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
10154
+ const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
10155
+ if (!existsSync16(logDir)) {
9107
10156
  mkdirSync8(logDir, { recursive: true });
9108
10157
  }
9109
10158
  transport.kill(sessionName);
9110
10159
  let cleanupSuffix = "";
9111
10160
  try {
9112
10161
  const thisFile = fileURLToPath2(import.meta.url);
9113
- const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
9114
- if (existsSync14(cleanupScript)) {
10162
+ const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
10163
+ if (existsSync16(cleanupScript)) {
9115
10164
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
9116
10165
  }
9117
10166
  } catch {
9118
10167
  }
9119
10168
  try {
9120
- const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
10169
+ const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
9121
10170
  let claudeJson = {};
9122
10171
  try {
9123
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
10172
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
9124
10173
  } catch {
9125
10174
  }
9126
10175
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -9128,17 +10177,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9128
10177
  const trustDir = opts?.cwd ?? projectDir;
9129
10178
  if (!projects[trustDir]) projects[trustDir] = {};
9130
10179
  projects[trustDir].hasTrustDialogAccepted = true;
9131
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
10180
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
9132
10181
  } catch {
9133
10182
  }
9134
10183
  try {
9135
- const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
10184
+ const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
9136
10185
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
9137
- const projSettingsDir = path18.join(settingsDir, normalizedKey);
9138
- const settingsPath = path18.join(projSettingsDir, "settings.json");
10186
+ const projSettingsDir = path20.join(settingsDir, normalizedKey);
10187
+ const settingsPath = path20.join(projSettingsDir, "settings.json");
9139
10188
  let settings = {};
9140
10189
  try {
9141
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
10190
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
9142
10191
  } catch {
9143
10192
  }
9144
10193
  const perms = settings.permissions ?? {};
@@ -9167,7 +10216,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9167
10216
  perms.allow = allow;
9168
10217
  settings.permissions = perms;
9169
10218
  mkdirSync8(projSettingsDir, { recursive: true });
9170
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
10219
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
9171
10220
  }
9172
10221
  } catch {
9173
10222
  }
@@ -9182,8 +10231,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9182
10231
  let behaviorsFlag = "";
9183
10232
  let legacyFallbackWarned = false;
9184
10233
  if (!useExeAgent && !useBinSymlink) {
9185
- const identityPath = path18.join(
9186
- os10.homedir(),
10234
+ const identityPath = path20.join(
10235
+ os12.homedir(),
9187
10236
  ".exe-os",
9188
10237
  "identity",
9189
10238
  `${employeeName}.md`
@@ -9192,13 +10241,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9192
10241
  const hasAgentFlag = claudeSupportsAgentFlag();
9193
10242
  if (hasAgentFlag) {
9194
10243
  identityFlag = ` --agent ${employeeName}`;
9195
- } else if (existsSync14(identityPath)) {
10244
+ } else if (existsSync16(identityPath)) {
9196
10245
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
9197
10246
  legacyFallbackWarned = true;
9198
10247
  }
9199
10248
  const behaviorsFile = exportBehaviorsSync(
9200
10249
  employeeName,
9201
- path18.basename(spawnCwd),
10250
+ path20.basename(spawnCwd),
9202
10251
  sessionName
9203
10252
  );
9204
10253
  if (behaviorsFile) {
@@ -9213,16 +10262,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9213
10262
  }
9214
10263
  let sessionContextFlag = "";
9215
10264
  try {
9216
- const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
10265
+ const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
9217
10266
  mkdirSync8(ctxDir, { recursive: true });
9218
- const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
10267
+ const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
9219
10268
  const ctxContent = [
9220
10269
  `## Session Context`,
9221
10270
  `You are running in tmux session: ${sessionName}.`,
9222
10271
  `Your parent coordinator session is ${exeSession}.`,
9223
10272
  `Your employees (if any) use the -${exeSession} suffix.`
9224
10273
  ].join("\n");
9225
- writeFileSync7(ctxFile, ctxContent);
10274
+ writeFileSync8(ctxFile, ctxContent);
9226
10275
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
9227
10276
  } catch {
9228
10277
  }
@@ -9299,8 +10348,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9299
10348
  transport.pipeLog(sessionName, logFile);
9300
10349
  try {
9301
10350
  const mySession = getMySession();
9302
- const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
9303
- writeFileSync7(dispatchInfo, JSON.stringify({
10351
+ const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
10352
+ writeFileSync8(dispatchInfo, JSON.stringify({
9304
10353
  dispatchedBy: mySession,
9305
10354
  rootExe: exeSession,
9306
10355
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -9374,15 +10423,15 @@ var init_tmux_routing = __esm({
9374
10423
  init_intercom_queue();
9375
10424
  init_plan_limits();
9376
10425
  init_employees();
9377
- SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
9378
- SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
10426
+ SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
10427
+ SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
9379
10428
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
9380
10429
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
9381
10430
  VERIFY_PANE_LINES = 200;
9382
10431
  INTERCOM_DEBOUNCE_MS = 3e4;
9383
10432
  CODEX_DEBOUNCE_MS = 12e4;
9384
- INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
9385
- DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
10433
+ INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
10434
+ DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
9386
10435
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
9387
10436
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
9388
10437
  }
@@ -9405,10 +10454,10 @@ __export(messaging_exports, {
9405
10454
  sendMessage: () => sendMessage,
9406
10455
  setWsClientSend: () => setWsClientSend
9407
10456
  });
9408
- import crypto8 from "crypto";
10457
+ import crypto9 from "crypto";
9409
10458
  function generateUlid() {
9410
10459
  const timestamp = Date.now().toString(36).padStart(10, "0");
9411
- const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
10460
+ const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
9412
10461
  return (timestamp + random).toUpperCase();
9413
10462
  }
9414
10463
  function rowToMessage(row) {
@@ -9419,6 +10468,7 @@ function rowToMessage(row) {
9419
10468
  targetAgent: row.target_agent,
9420
10469
  targetProject: row.target_project ?? null,
9421
10470
  targetDevice: row.target_device,
10471
+ sessionScope: row.session_scope ?? null,
9422
10472
  content: row.content,
9423
10473
  priority: row.priority ?? "normal",
9424
10474
  status: row.status ?? "pending",
@@ -9436,15 +10486,17 @@ async function sendMessage(input) {
9436
10486
  const id = generateUlid();
9437
10487
  const now = (/* @__PURE__ */ new Date()).toISOString();
9438
10488
  const targetDevice = input.targetDevice ?? "local";
10489
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
9439
10490
  await client.execute({
9440
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
9441
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
10491
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
10492
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
9442
10493
  args: [
9443
10494
  id,
9444
10495
  input.fromAgent,
9445
10496
  input.targetAgent,
9446
10497
  input.targetProject ?? null,
9447
10498
  targetDevice,
10499
+ sessionScope,
9448
10500
  input.content,
9449
10501
  input.priority ?? "normal",
9450
10502
  now
@@ -9458,9 +10510,10 @@ async function sendMessage(input) {
9458
10510
  }
9459
10511
  } catch {
9460
10512
  }
10513
+ const sentScope = strictSessionScopeFilter(sessionScope);
9461
10514
  const result = await client.execute({
9462
- sql: "SELECT * FROM messages WHERE id = ?",
9463
- args: [id]
10515
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
10516
+ args: [id, ...sentScope.args]
9464
10517
  });
9465
10518
  return rowToMessage(result.rows[0]);
9466
10519
  }
@@ -9484,6 +10537,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
9484
10537
  fromAgent: msg.fromAgent,
9485
10538
  targetAgent: msg.targetAgent,
9486
10539
  targetProject: msg.targetProject,
10540
+ sessionScope: msg.sessionScope,
9487
10541
  content: msg.content,
9488
10542
  priority: msg.priority,
9489
10543
  createdAt: msg.createdAt
@@ -9527,7 +10581,7 @@ async function deliverLocalMessage(messageId) {
9527
10581
  } catch {
9528
10582
  const newRetryCount = msg.retryCount + 1;
9529
10583
  if (newRetryCount >= MAX_RETRIES3) {
9530
- await markFailed(messageId, "session unavailable after 10 retries");
10584
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
9531
10585
  } else {
9532
10586
  await client.execute({
9533
10587
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -9537,85 +10591,101 @@ async function deliverLocalMessage(messageId) {
9537
10591
  return false;
9538
10592
  }
9539
10593
  }
9540
- async function getPendingMessages(targetAgent) {
10594
+ async function getPendingMessages(targetAgent, sessionScope) {
9541
10595
  const client = getClient();
10596
+ const scope = strictSessionScopeFilter(sessionScope);
9542
10597
  const result = await client.execute({
9543
10598
  sql: `SELECT * FROM messages
9544
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
10599
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
9545
10600
  ORDER BY id`,
9546
- args: [targetAgent]
10601
+ args: [targetAgent, ...scope.args]
9547
10602
  });
9548
10603
  return result.rows.map((row) => rowToMessage(row));
9549
10604
  }
9550
- async function markRead(messageId) {
10605
+ async function markRead(messageId, sessionScope) {
9551
10606
  const client = getClient();
10607
+ const scope = strictSessionScopeFilter(sessionScope);
9552
10608
  await client.execute({
9553
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
9554
- args: [messageId]
10609
+ sql: `UPDATE messages SET status = 'read'
10610
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
10611
+ args: [messageId, ...scope.args]
9555
10612
  });
9556
10613
  }
9557
- async function markAcknowledged(messageId) {
10614
+ async function markAcknowledged(messageId, sessionScope) {
9558
10615
  const client = getClient();
10616
+ const scope = strictSessionScopeFilter(sessionScope);
9559
10617
  await client.execute({
9560
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
9561
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10618
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
10619
+ WHERE id = ? AND status = 'read'${scope.sql}`,
10620
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9562
10621
  });
9563
10622
  }
9564
- async function markProcessed(messageId) {
10623
+ async function markProcessed(messageId, sessionScope) {
9565
10624
  const client = getClient();
10625
+ const scope = strictSessionScopeFilter(sessionScope);
9566
10626
  await client.execute({
9567
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
9568
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10627
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
10628
+ WHERE id = ?${scope.sql}`,
10629
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9569
10630
  });
9570
10631
  }
9571
- async function getMessageStatus(messageId) {
10632
+ async function getMessageStatus(messageId, sessionScope) {
9572
10633
  const client = getClient();
10634
+ const scope = strictSessionScopeFilter(sessionScope);
9573
10635
  const result = await client.execute({
9574
- sql: "SELECT status FROM messages WHERE id = ?",
9575
- args: [messageId]
10636
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
10637
+ args: [messageId, ...scope.args]
9576
10638
  });
9577
10639
  return result.rows[0]?.status ?? null;
9578
10640
  }
9579
- async function getUnacknowledgedMessages(targetAgent) {
10641
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
9580
10642
  const client = getClient();
10643
+ const scope = strictSessionScopeFilter(sessionScope);
9581
10644
  const result = await client.execute({
9582
10645
  sql: `SELECT * FROM messages
9583
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
10646
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
9584
10647
  ORDER BY id`,
9585
- args: [targetAgent]
10648
+ args: [targetAgent, ...scope.args]
9586
10649
  });
9587
10650
  return result.rows.map((row) => rowToMessage(row));
9588
10651
  }
9589
- async function getReadMessages(targetAgent) {
10652
+ async function getReadMessages(targetAgent, sessionScope) {
9590
10653
  const client = getClient();
10654
+ const scope = strictSessionScopeFilter(sessionScope);
9591
10655
  const result = await client.execute({
9592
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
9593
- args: [targetAgent]
10656
+ sql: `SELECT * FROM messages
10657
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
10658
+ ORDER BY id`,
10659
+ args: [targetAgent, ...scope.args]
9594
10660
  });
9595
10661
  return result.rows.map((row) => rowToMessage(row));
9596
10662
  }
9597
- async function markFailed(messageId, reason) {
10663
+ async function markFailed(messageId, reason, sessionScope) {
9598
10664
  const client = getClient();
10665
+ const scope = strictSessionScopeFilter(sessionScope);
9599
10666
  await client.execute({
9600
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
9601
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
10667
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
10668
+ WHERE id = ?${scope.sql}`,
10669
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
9602
10670
  });
9603
10671
  }
9604
- async function getFailedMessages() {
10672
+ async function getFailedMessages(sessionScope) {
9605
10673
  const client = getClient();
10674
+ const scope = strictSessionScopeFilter(sessionScope);
9606
10675
  const result = await client.execute({
9607
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
9608
- args: []
10676
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
10677
+ args: [...scope.args]
9609
10678
  });
9610
10679
  return result.rows.map((row) => rowToMessage(row));
9611
10680
  }
9612
- async function retryPendingMessages() {
10681
+ async function retryPendingMessages(sessionScope) {
9613
10682
  const client = getClient();
10683
+ const scope = strictSessionScopeFilter(sessionScope);
9614
10684
  const result = await client.execute({
9615
10685
  sql: `SELECT * FROM messages
9616
- WHERE status = 'pending' AND retry_count < ?
10686
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
9617
10687
  ORDER BY id`,
9618
- args: [MAX_RETRIES3]
10688
+ args: [MAX_RETRIES3, ...scope.args]
9619
10689
  });
9620
10690
  let delivered = 0;
9621
10691
  for (const row of result.rows) {
@@ -9634,16 +10704,17 @@ var init_messaging = __esm({
9634
10704
  "use strict";
9635
10705
  init_database();
9636
10706
  init_tmux_routing();
10707
+ init_task_scope();
9637
10708
  MAX_RETRIES3 = 10;
9638
10709
  _wsClientSend = null;
9639
10710
  }
9640
10711
  });
9641
10712
 
9642
10713
  // src/automation/trigger-engine.ts
9643
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
10714
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
9644
10715
  import { randomUUID as randomUUID8 } from "crypto";
9645
- import path19 from "path";
9646
- import os11 from "os";
10716
+ import path21 from "path";
10717
+ import os13 from "os";
9647
10718
  function substituteTemplate(template, record) {
9648
10719
  return template.replace(
9649
10720
  /\{\{(\w+(?:\.\w+)*)\}\}/g,
@@ -9696,9 +10767,9 @@ function evaluateConditions(conditions, record) {
9696
10767
  return conditions.every((c) => evaluateCondition(c, record));
9697
10768
  }
9698
10769
  function loadTriggers(project) {
9699
- if (!existsSync15(TRIGGERS_PATH)) return [];
10770
+ if (!existsSync17(TRIGGERS_PATH)) return [];
9700
10771
  try {
9701
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
10772
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
9702
10773
  const all = JSON.parse(raw);
9703
10774
  if (!Array.isArray(all)) return [];
9704
10775
  if (project) {
@@ -9938,7 +11009,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
9938
11009
  var init_trigger_engine = __esm({
9939
11010
  "src/automation/trigger-engine.ts"() {
9940
11011
  "use strict";
9941
- TRIGGERS_PATH = path19.join(os11.homedir(), ".exe-os", "triggers.json");
11012
+ TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
9942
11013
  GRAPH_API_VERSION = "v21.0";
9943
11014
  }
9944
11015
  });
@@ -10001,9 +11072,9 @@ var init_crm_webhook = __esm({
10001
11072
  });
10002
11073
 
10003
11074
  // src/bin/exe-gateway.ts
10004
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
10005
- import path20 from "path";
10006
- import os12 from "os";
11075
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
11076
+ import path22 from "path";
11077
+ import os14 from "os";
10007
11078
 
10008
11079
  // src/gateway/webhook-server.ts
10009
11080
  import {
@@ -10245,11 +11316,11 @@ init_crm_bridge();
10245
11316
 
10246
11317
  // src/lib/pipeline-router.ts
10247
11318
  init_database();
10248
- import crypto2 from "crypto";
11319
+ import crypto3 from "crypto";
10249
11320
  async function sinkConversationStore(msg, agentResponse, agentName) {
10250
11321
  try {
10251
11322
  const client = getClient();
10252
- const id = crypto2.randomUUID();
11323
+ const id = crypto3.randomUUID();
10253
11324
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
10254
11325
  await client.execute({
10255
11326
  sql: `INSERT INTO conversations
@@ -10299,7 +11370,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
10299
11370
  ].filter(Boolean).join("\n");
10300
11371
  const vector = await embed2(rawText);
10301
11372
  await writeMemory2({
10302
- id: crypto2.randomUUID(),
11373
+ id: crypto3.randomUUID(),
10303
11374
  agent_id: agentName ?? "gateway",
10304
11375
  agent_role: "gateway",
10305
11376
  session_id: `gateway-${msg.platform}`,
@@ -10969,18 +12040,18 @@ var BotRegistry = class {
10969
12040
 
10970
12041
  // src/bin/exe-gateway.ts
10971
12042
  init_employees();
10972
- var CONFIG_DIR = path20.join(os12.homedir(), ".exe-os");
10973
- var CONFIG_PATH3 = path20.join(CONFIG_DIR, "gateway.json");
12043
+ var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path22.join(os14.homedir(), ".exe-os");
12044
+ var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path22.join(CONFIG_DIR, "gateway.json");
10974
12045
  var DEFAULT_PORT = 3100;
10975
12046
  function loadConfig2() {
10976
- if (!existsSync16(CONFIG_PATH3)) {
12047
+ if (!existsSync18(CONFIG_PATH3)) {
10977
12048
  console.log(
10978
12049
  `[exe-gateway] No config at ${CONFIG_PATH3} \u2014 using defaults (port ${DEFAULT_PORT})`
10979
12050
  );
10980
12051
  return {};
10981
12052
  }
10982
12053
  try {
10983
- const raw = readFileSync14(CONFIG_PATH3, "utf-8");
12054
+ const raw = readFileSync15(CONFIG_PATH3, "utf-8");
10984
12055
  return JSON.parse(raw);
10985
12056
  } catch (err) {
10986
12057
  console.error(