@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
@@ -391,6 +391,44 @@ var init_db_retry = __esm({
391
391
  }
392
392
  });
393
393
 
394
+ // src/lib/secure-files.ts
395
+ import { chmodSync, existsSync, mkdirSync } from "fs";
396
+ import { chmod, mkdir } from "fs/promises";
397
+ async function ensurePrivateDir(dirPath) {
398
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
399
+ try {
400
+ await chmod(dirPath, PRIVATE_DIR_MODE);
401
+ } catch {
402
+ }
403
+ }
404
+ function ensurePrivateDirSync(dirPath) {
405
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
406
+ try {
407
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
408
+ } catch {
409
+ }
410
+ }
411
+ async function enforcePrivateFile(filePath) {
412
+ try {
413
+ await chmod(filePath, PRIVATE_FILE_MODE);
414
+ } catch {
415
+ }
416
+ }
417
+ function enforcePrivateFileSync(filePath) {
418
+ try {
419
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
420
+ } catch {
421
+ }
422
+ }
423
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
424
+ var init_secure_files = __esm({
425
+ "src/lib/secure-files.ts"() {
426
+ "use strict";
427
+ PRIVATE_DIR_MODE = 448;
428
+ PRIVATE_FILE_MODE = 384;
429
+ }
430
+ });
431
+
394
432
  // src/lib/config.ts
395
433
  var config_exports = {};
396
434
  __export(config_exports, {
@@ -407,8 +445,8 @@ __export(config_exports, {
407
445
  migrateConfig: () => migrateConfig,
408
446
  saveConfig: () => saveConfig
409
447
  });
410
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
411
- import { readFileSync, existsSync, renameSync } from "fs";
448
+ import { readFile, writeFile } from "fs/promises";
449
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
412
450
  import path from "path";
413
451
  import os from "os";
414
452
  function resolveDataDir() {
@@ -416,7 +454,7 @@ function resolveDataDir() {
416
454
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
417
455
  const newDir = path.join(os.homedir(), ".exe-os");
418
456
  const legacyDir = path.join(os.homedir(), ".exe-mem");
419
- if (!existsSync(newDir) && existsSync(legacyDir)) {
457
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
420
458
  try {
421
459
  renameSync(legacyDir, newDir);
422
460
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -479,9 +517,9 @@ function normalizeAutoUpdate(raw) {
479
517
  }
480
518
  async function loadConfig() {
481
519
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
482
- await mkdir(dir, { recursive: true });
520
+ await ensurePrivateDir(dir);
483
521
  const configPath = path.join(dir, "config.json");
484
- if (!existsSync(configPath)) {
522
+ if (!existsSync2(configPath)) {
485
523
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
486
524
  }
487
525
  const raw = await readFile(configPath, "utf-8");
@@ -494,6 +532,7 @@ async function loadConfig() {
494
532
  `);
495
533
  try {
496
534
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
535
+ await enforcePrivateFile(configPath);
497
536
  } catch {
498
537
  }
499
538
  }
@@ -512,7 +551,7 @@ async function loadConfig() {
512
551
  function loadConfigSync() {
513
552
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
514
553
  const configPath = path.join(dir, "config.json");
515
- if (!existsSync(configPath)) {
554
+ if (!existsSync2(configPath)) {
516
555
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
517
556
  }
518
557
  try {
@@ -530,12 +569,10 @@ function loadConfigSync() {
530
569
  }
531
570
  async function saveConfig(config2) {
532
571
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
533
- await mkdir(dir, { recursive: true });
572
+ await ensurePrivateDir(dir);
534
573
  const configPath = path.join(dir, "config.json");
535
574
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
536
- if (config2.cloud?.apiKey) {
537
- await chmod(configPath, 384);
538
- }
575
+ await enforcePrivateFile(configPath);
539
576
  }
540
577
  async function loadConfigFrom(configPath) {
541
578
  const raw = await readFile(configPath, "utf-8");
@@ -555,6 +592,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
555
592
  var init_config = __esm({
556
593
  "src/lib/config.ts"() {
557
594
  "use strict";
595
+ init_secure_files();
558
596
  EXE_AI_DIR = resolveDataDir();
559
597
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
560
598
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -631,6 +669,120 @@ var init_config = __esm({
631
669
  }
632
670
  });
633
671
 
672
+ // src/lib/runtime-table.ts
673
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
674
+ var init_runtime_table = __esm({
675
+ "src/lib/runtime-table.ts"() {
676
+ "use strict";
677
+ RUNTIME_TABLE = {
678
+ codex: {
679
+ binary: "codex",
680
+ launchMode: "interactive",
681
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
682
+ inlineFlag: "--no-alt-screen",
683
+ apiKeyEnv: "OPENAI_API_KEY",
684
+ defaultModel: "gpt-5.4"
685
+ },
686
+ opencode: {
687
+ binary: "opencode",
688
+ launchMode: "exec",
689
+ autoApproveFlag: "--dangerously-skip-permissions",
690
+ inlineFlag: "",
691
+ apiKeyEnv: "ANTHROPIC_API_KEY",
692
+ defaultModel: "anthropic/claude-sonnet-4-6"
693
+ }
694
+ };
695
+ DEFAULT_RUNTIME = "claude";
696
+ }
697
+ });
698
+
699
+ // src/lib/agent-config.ts
700
+ var agent_config_exports = {};
701
+ __export(agent_config_exports, {
702
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
703
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
704
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
705
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
706
+ clearAgentRuntime: () => clearAgentRuntime,
707
+ getAgentRuntime: () => getAgentRuntime,
708
+ loadAgentConfig: () => loadAgentConfig,
709
+ saveAgentConfig: () => saveAgentConfig,
710
+ setAgentRuntime: () => setAgentRuntime
711
+ });
712
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
713
+ import path2 from "path";
714
+ function loadAgentConfig() {
715
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
716
+ try {
717
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
718
+ } catch {
719
+ return {};
720
+ }
721
+ }
722
+ function saveAgentConfig(config2) {
723
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
724
+ ensurePrivateDirSync(dir);
725
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
726
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
727
+ }
728
+ function getAgentRuntime(agentId) {
729
+ const config2 = loadAgentConfig();
730
+ const entry = config2[agentId];
731
+ if (entry) return entry;
732
+ const orgDefault = config2["default"];
733
+ if (orgDefault) return orgDefault;
734
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
735
+ }
736
+ function setAgentRuntime(agentId, runtime, model) {
737
+ const knownModels = KNOWN_RUNTIMES[runtime];
738
+ if (!knownModels) {
739
+ return {
740
+ ok: false,
741
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
742
+ };
743
+ }
744
+ if (!knownModels.includes(model)) {
745
+ return {
746
+ ok: false,
747
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
748
+ };
749
+ }
750
+ const config2 = loadAgentConfig();
751
+ config2[agentId] = { runtime, model };
752
+ saveAgentConfig(config2);
753
+ return { ok: true };
754
+ }
755
+ function clearAgentRuntime(agentId) {
756
+ const config2 = loadAgentConfig();
757
+ delete config2[agentId];
758
+ saveAgentConfig(config2);
759
+ }
760
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
761
+ var init_agent_config = __esm({
762
+ "src/lib/agent-config.ts"() {
763
+ "use strict";
764
+ init_config();
765
+ init_runtime_table();
766
+ init_secure_files();
767
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
768
+ KNOWN_RUNTIMES = {
769
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
770
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
771
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
772
+ };
773
+ RUNTIME_LABELS = {
774
+ claude: "Claude Code (Anthropic)",
775
+ codex: "Codex (OpenAI)",
776
+ opencode: "OpenCode (open source)"
777
+ };
778
+ DEFAULT_MODELS = {
779
+ claude: "claude-opus-4",
780
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
781
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
782
+ };
783
+ }
784
+ });
785
+
634
786
  // src/lib/employees.ts
635
787
  var employees_exports = {};
636
788
  __export(employees_exports, {
@@ -646,6 +798,7 @@ __export(employees_exports, {
646
798
  getEmployeeByRole: () => getEmployeeByRole,
647
799
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
648
800
  hasRole: () => hasRole,
801
+ hireEmployee: () => hireEmployee,
649
802
  isCoordinatorName: () => isCoordinatorName,
650
803
  isCoordinatorRole: () => isCoordinatorRole,
651
804
  isMultiInstance: () => isMultiInstance,
@@ -658,9 +811,9 @@ __export(employees_exports, {
658
811
  validateEmployeeName: () => validateEmployeeName
659
812
  });
660
813
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
661
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
814
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
662
815
  import { execSync } from "child_process";
663
- import path2 from "path";
816
+ import path3 from "path";
664
817
  import os2 from "os";
665
818
  function normalizeRole(role) {
666
819
  return (role ?? "").trim().toLowerCase();
@@ -697,7 +850,7 @@ function validateEmployeeName(name) {
697
850
  return { valid: true };
698
851
  }
699
852
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
700
- if (!existsSync2(employeesPath)) {
853
+ if (!existsSync4(employeesPath)) {
701
854
  return [];
702
855
  }
703
856
  const raw = await readFile2(employeesPath, "utf-8");
@@ -708,13 +861,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
708
861
  }
709
862
  }
710
863
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
711
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
864
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
712
865
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
713
866
  }
714
867
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
715
- if (!existsSync2(employeesPath)) return [];
868
+ if (!existsSync4(employeesPath)) return [];
716
869
  try {
717
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
870
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
718
871
  } catch {
719
872
  return [];
720
873
  }
@@ -756,6 +909,52 @@ function addEmployee(employees, employee) {
756
909
  }
757
910
  return [...employees, normalized];
758
911
  }
912
+ function appendToCoordinatorTeam(employee) {
913
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
914
+ if (!coordinator) return;
915
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
916
+ if (!existsSync4(idPath)) return;
917
+ const content = readFileSync3(idPath, "utf-8");
918
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
919
+ const teamMatch = content.match(TEAM_SECTION_RE);
920
+ if (!teamMatch || teamMatch.index === void 0) return;
921
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
922
+ const nextHeading = afterTeam.match(/\n## /);
923
+ const entry = `
924
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
925
+ `;
926
+ let updated;
927
+ if (nextHeading && nextHeading.index !== void 0) {
928
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
929
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
930
+ } else {
931
+ updated = content.trimEnd() + "\n" + entry;
932
+ }
933
+ writeFileSync2(idPath, updated, "utf-8");
934
+ }
935
+ function capitalize(s) {
936
+ return s.charAt(0).toUpperCase() + s.slice(1);
937
+ }
938
+ async function hireEmployee(employee) {
939
+ const employees = await loadEmployees();
940
+ const updated = addEmployee(employees, employee);
941
+ await saveEmployees(updated);
942
+ try {
943
+ appendToCoordinatorTeam(employee);
944
+ } catch {
945
+ }
946
+ try {
947
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
948
+ const config2 = loadAgentConfig2();
949
+ const name = employee.name.toLowerCase();
950
+ if (!config2[name] && config2["default"]) {
951
+ config2[name] = { ...config2["default"] };
952
+ saveAgentConfig2(config2);
953
+ }
954
+ } catch {
955
+ }
956
+ return updated;
957
+ }
759
958
  async function normalizeRosterCase(rosterPath) {
760
959
  const employees = await loadEmployees(rosterPath);
761
960
  let changed = false;
@@ -765,14 +964,14 @@ async function normalizeRosterCase(rosterPath) {
765
964
  emp.name = emp.name.toLowerCase();
766
965
  changed = true;
767
966
  try {
768
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
769
- const oldPath = path2.join(identityDir, `${oldName}.md`);
770
- const newPath = path2.join(identityDir, `${emp.name}.md`);
771
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
967
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
968
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
969
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
970
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
772
971
  renameSync2(oldPath, newPath);
773
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
774
- const content = readFileSync2(oldPath, "utf-8");
775
- writeFileSync(newPath, content, "utf-8");
972
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
973
+ const content = readFileSync3(oldPath, "utf-8");
974
+ writeFileSync2(newPath, content, "utf-8");
776
975
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
777
976
  unlinkSync(oldPath);
778
977
  }
@@ -802,7 +1001,7 @@ function registerBinSymlinks(name) {
802
1001
  errors.push("Could not find 'exe-os' in PATH");
803
1002
  return { created, skipped, errors };
804
1003
  }
805
- const binDir = path2.dirname(exeBinPath);
1004
+ const binDir = path3.dirname(exeBinPath);
806
1005
  let target;
807
1006
  try {
808
1007
  target = readlinkSync(exeBinPath);
@@ -812,8 +1011,8 @@ function registerBinSymlinks(name) {
812
1011
  }
813
1012
  for (const suffix of ["", "-opencode"]) {
814
1013
  const linkName = `${name}${suffix}`;
815
- const linkPath = path2.join(binDir, linkName);
816
- if (existsSync2(linkPath)) {
1014
+ const linkPath = path3.join(binDir, linkName);
1015
+ if (existsSync4(linkPath)) {
817
1016
  skipped.push(linkName);
818
1017
  continue;
819
1018
  }
@@ -826,21 +1025,619 @@ function registerBinSymlinks(name) {
826
1025
  }
827
1026
  return { created, skipped, errors };
828
1027
  }
829
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
1028
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
830
1029
  var init_employees = __esm({
831
1030
  "src/lib/employees.ts"() {
832
1031
  "use strict";
833
1032
  init_config();
834
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
1033
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
835
1034
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
836
1035
  COORDINATOR_ROLE = "COO";
837
1036
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1037
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
1038
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
1039
+ }
1040
+ });
1041
+
1042
+ // src/lib/database-adapter.ts
1043
+ import os3 from "os";
1044
+ import path4 from "path";
1045
+ import { createRequire } from "module";
1046
+ import { pathToFileURL } from "url";
1047
+ function quotedIdentifier(identifier) {
1048
+ return `"${identifier.replace(/"/g, '""')}"`;
1049
+ }
1050
+ function unqualifiedTableName(name) {
1051
+ const raw = name.trim().replace(/^"|"$/g, "");
1052
+ const parts = raw.split(".");
1053
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1054
+ }
1055
+ function stripTrailingSemicolon(sql) {
1056
+ return sql.trim().replace(/;+\s*$/u, "");
1057
+ }
1058
+ function appendClause(sql, clause) {
1059
+ const trimmed = stripTrailingSemicolon(sql);
1060
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1061
+ if (!returningMatch) {
1062
+ return `${trimmed}${clause}`;
1063
+ }
1064
+ const idx = returningMatch.index;
1065
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1066
+ }
1067
+ function normalizeStatement(stmt) {
1068
+ if (typeof stmt === "string") {
1069
+ return { kind: "positional", sql: stmt, args: [] };
1070
+ }
1071
+ const sql = stmt.sql;
1072
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1073
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1074
+ }
1075
+ return { kind: "named", sql, args: stmt.args };
1076
+ }
1077
+ function rewriteBooleanLiterals(sql) {
1078
+ let out = sql;
1079
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1080
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1081
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1082
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1083
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1084
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1085
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1086
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1087
+ }
1088
+ return out;
1089
+ }
1090
+ function rewriteInsertOrIgnore(sql) {
1091
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1092
+ return sql;
1093
+ }
1094
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1095
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1096
+ }
1097
+ function rewriteInsertOrReplace(sql) {
1098
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1099
+ if (!match) {
1100
+ return sql;
1101
+ }
1102
+ const rawTable = match[1];
1103
+ const rawColumns = match[2];
1104
+ const remainder = match[3];
1105
+ const tableName = unqualifiedTableName(rawTable);
1106
+ const conflictKeys = UPSERT_KEYS[tableName];
1107
+ if (!conflictKeys?.length) {
1108
+ return sql;
1109
+ }
1110
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1111
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1112
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1113
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1114
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1115
+ }
1116
+ function rewriteSql(sql) {
1117
+ let out = sql;
1118
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1119
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1120
+ out = rewriteBooleanLiterals(out);
1121
+ out = rewriteInsertOrReplace(out);
1122
+ out = rewriteInsertOrIgnore(out);
1123
+ return stripTrailingSemicolon(out);
1124
+ }
1125
+ function toBoolean(value) {
1126
+ if (value === null || value === void 0) return value;
1127
+ if (typeof value === "boolean") return value;
1128
+ if (typeof value === "number") return value !== 0;
1129
+ if (typeof value === "bigint") return value !== 0n;
1130
+ if (typeof value === "string") {
1131
+ const normalized = value.trim().toLowerCase();
1132
+ if (normalized === "0" || normalized === "false") return false;
1133
+ if (normalized === "1" || normalized === "true") return true;
1134
+ }
1135
+ return Boolean(value);
1136
+ }
1137
+ function countQuestionMarks(sql, end) {
1138
+ let count = 0;
1139
+ let inSingle = false;
1140
+ let inDouble = false;
1141
+ let inLineComment = false;
1142
+ let inBlockComment = false;
1143
+ for (let i = 0; i < end; i++) {
1144
+ const ch = sql[i];
1145
+ const next = sql[i + 1];
1146
+ if (inLineComment) {
1147
+ if (ch === "\n") inLineComment = false;
1148
+ continue;
1149
+ }
1150
+ if (inBlockComment) {
1151
+ if (ch === "*" && next === "/") {
1152
+ inBlockComment = false;
1153
+ i += 1;
1154
+ }
1155
+ continue;
1156
+ }
1157
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1158
+ inLineComment = true;
1159
+ i += 1;
1160
+ continue;
1161
+ }
1162
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1163
+ inBlockComment = true;
1164
+ i += 1;
1165
+ continue;
1166
+ }
1167
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1168
+ inSingle = !inSingle;
1169
+ continue;
1170
+ }
1171
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1172
+ inDouble = !inDouble;
1173
+ continue;
1174
+ }
1175
+ if (!inSingle && !inDouble && ch === "?") {
1176
+ count += 1;
1177
+ }
1178
+ }
1179
+ return count;
1180
+ }
1181
+ function findBooleanPlaceholderIndexes(sql) {
1182
+ const indexes = /* @__PURE__ */ new Set();
1183
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1184
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1185
+ for (const match of sql.matchAll(pattern)) {
1186
+ const matchText = match[0];
1187
+ const qIndex = match.index + matchText.lastIndexOf("?");
1188
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1189
+ }
1190
+ }
1191
+ return indexes;
1192
+ }
1193
+ function coerceInsertBooleanArgs(sql, args) {
1194
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1195
+ if (!match) return;
1196
+ const rawTable = match[1];
1197
+ const rawColumns = match[2];
1198
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1199
+ if (!boolColumns?.size) return;
1200
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1201
+ for (const [index, column] of columns.entries()) {
1202
+ if (boolColumns.has(column) && index < args.length) {
1203
+ args[index] = toBoolean(args[index]);
1204
+ }
1205
+ }
1206
+ }
1207
+ function coerceUpdateBooleanArgs(sql, args) {
1208
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1209
+ if (!match) return;
1210
+ const rawTable = match[1];
1211
+ const setClause = match[2];
1212
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1213
+ if (!boolColumns?.size) return;
1214
+ const assignments = setClause.split(",");
1215
+ let placeholderIndex = 0;
1216
+ for (const assignment of assignments) {
1217
+ if (!assignment.includes("?")) continue;
1218
+ placeholderIndex += 1;
1219
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1220
+ if (colMatch && boolColumns.has(colMatch[1])) {
1221
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1222
+ }
1223
+ }
1224
+ }
1225
+ function coerceBooleanArgs(sql, args) {
1226
+ const nextArgs = [...args];
1227
+ coerceInsertBooleanArgs(sql, nextArgs);
1228
+ coerceUpdateBooleanArgs(sql, nextArgs);
1229
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1230
+ for (const index of placeholderIndexes) {
1231
+ if (index > 0 && index <= nextArgs.length) {
1232
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1233
+ }
1234
+ }
1235
+ return nextArgs;
1236
+ }
1237
+ function convertQuestionMarksToDollarParams(sql) {
1238
+ let out = "";
1239
+ let placeholder = 0;
1240
+ let inSingle = false;
1241
+ let inDouble = false;
1242
+ let inLineComment = false;
1243
+ let inBlockComment = false;
1244
+ for (let i = 0; i < sql.length; i++) {
1245
+ const ch = sql[i];
1246
+ const next = sql[i + 1];
1247
+ if (inLineComment) {
1248
+ out += ch;
1249
+ if (ch === "\n") inLineComment = false;
1250
+ continue;
1251
+ }
1252
+ if (inBlockComment) {
1253
+ out += ch;
1254
+ if (ch === "*" && next === "/") {
1255
+ out += next;
1256
+ inBlockComment = false;
1257
+ i += 1;
1258
+ }
1259
+ continue;
1260
+ }
1261
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1262
+ out += ch + next;
1263
+ inLineComment = true;
1264
+ i += 1;
1265
+ continue;
1266
+ }
1267
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1268
+ out += ch + next;
1269
+ inBlockComment = true;
1270
+ i += 1;
1271
+ continue;
1272
+ }
1273
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1274
+ inSingle = !inSingle;
1275
+ out += ch;
1276
+ continue;
1277
+ }
1278
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1279
+ inDouble = !inDouble;
1280
+ out += ch;
1281
+ continue;
1282
+ }
1283
+ if (!inSingle && !inDouble && ch === "?") {
1284
+ placeholder += 1;
1285
+ out += `$${placeholder}`;
1286
+ continue;
1287
+ }
1288
+ out += ch;
1289
+ }
1290
+ return out;
1291
+ }
1292
+ function translateStatementForPostgres(stmt) {
1293
+ const normalized = normalizeStatement(stmt);
1294
+ if (normalized.kind === "named") {
1295
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1296
+ }
1297
+ const rewrittenSql = rewriteSql(normalized.sql);
1298
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1299
+ return {
1300
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1301
+ args: coercedArgs
1302
+ };
1303
+ }
1304
+ function shouldBypassPostgres(stmt) {
1305
+ const normalized = normalizeStatement(stmt);
1306
+ if (normalized.kind === "named") {
1307
+ return true;
1308
+ }
1309
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1310
+ }
1311
+ function shouldFallbackOnError(error) {
1312
+ const message = error instanceof Error ? error.message : String(error);
1313
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1314
+ }
1315
+ function isReadQuery(sql) {
1316
+ const trimmed = sql.trimStart();
1317
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1318
+ }
1319
+ function buildRow(row, columns) {
1320
+ const values = columns.map((column) => row[column]);
1321
+ return Object.assign(values, row);
1322
+ }
1323
+ function buildResultSet(rows, rowsAffected = 0) {
1324
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1325
+ const resultRows = rows.map((row) => buildRow(row, columns));
1326
+ return {
1327
+ columns,
1328
+ columnTypes: columns.map(() => ""),
1329
+ rows: resultRows,
1330
+ rowsAffected,
1331
+ lastInsertRowid: void 0,
1332
+ toJSON() {
1333
+ return {
1334
+ columns,
1335
+ columnTypes: columns.map(() => ""),
1336
+ rows,
1337
+ rowsAffected,
1338
+ lastInsertRowid: void 0
1339
+ };
1340
+ }
1341
+ };
1342
+ }
1343
+ async function loadPrismaClient() {
1344
+ if (!prismaClientPromise) {
1345
+ prismaClientPromise = (async () => {
1346
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1347
+ if (explicitPath) {
1348
+ const module2 = await import(pathToFileURL(explicitPath).href);
1349
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1350
+ if (!PrismaClient2) {
1351
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1352
+ }
1353
+ return new PrismaClient2();
1354
+ }
1355
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1356
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
1357
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1358
+ const module = await import(pathToFileURL(prismaEntry).href);
1359
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1360
+ if (!PrismaClient) {
1361
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1362
+ }
1363
+ return new PrismaClient();
1364
+ })();
1365
+ }
1366
+ return prismaClientPromise;
1367
+ }
1368
+ async function ensureCompatibilityViews(prisma) {
1369
+ if (!compatibilityBootstrapPromise) {
1370
+ compatibilityBootstrapPromise = (async () => {
1371
+ for (const mapping of VIEW_MAPPINGS) {
1372
+ const relation = mapping.source.replace(/"/g, "");
1373
+ const rows = await prisma.$queryRawUnsafe(
1374
+ "SELECT to_regclass($1) AS regclass",
1375
+ relation
1376
+ );
1377
+ if (!rows[0]?.regclass) {
1378
+ continue;
1379
+ }
1380
+ await prisma.$executeRawUnsafe(
1381
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1382
+ );
1383
+ }
1384
+ })();
1385
+ }
1386
+ return compatibilityBootstrapPromise;
1387
+ }
1388
+ async function executeOnPrisma(executor, stmt) {
1389
+ const translated = translateStatementForPostgres(stmt);
1390
+ if (isReadQuery(translated.sql)) {
1391
+ const rows = await executor.$queryRawUnsafe(
1392
+ translated.sql,
1393
+ ...translated.args
1394
+ );
1395
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1396
+ }
1397
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1398
+ return buildResultSet([], rowsAffected);
1399
+ }
1400
+ function splitSqlStatements(sql) {
1401
+ const parts = [];
1402
+ let current = "";
1403
+ let inSingle = false;
1404
+ let inDouble = false;
1405
+ let inLineComment = false;
1406
+ let inBlockComment = false;
1407
+ for (let i = 0; i < sql.length; i++) {
1408
+ const ch = sql[i];
1409
+ const next = sql[i + 1];
1410
+ if (inLineComment) {
1411
+ current += ch;
1412
+ if (ch === "\n") inLineComment = false;
1413
+ continue;
1414
+ }
1415
+ if (inBlockComment) {
1416
+ current += ch;
1417
+ if (ch === "*" && next === "/") {
1418
+ current += next;
1419
+ inBlockComment = false;
1420
+ i += 1;
1421
+ }
1422
+ continue;
1423
+ }
1424
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1425
+ current += ch + next;
1426
+ inLineComment = true;
1427
+ i += 1;
1428
+ continue;
1429
+ }
1430
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1431
+ current += ch + next;
1432
+ inBlockComment = true;
1433
+ i += 1;
1434
+ continue;
1435
+ }
1436
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1437
+ inSingle = !inSingle;
1438
+ current += ch;
1439
+ continue;
1440
+ }
1441
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1442
+ inDouble = !inDouble;
1443
+ current += ch;
1444
+ continue;
1445
+ }
1446
+ if (!inSingle && !inDouble && ch === ";") {
1447
+ if (current.trim()) {
1448
+ parts.push(current.trim());
1449
+ }
1450
+ current = "";
1451
+ continue;
1452
+ }
1453
+ current += ch;
1454
+ }
1455
+ if (current.trim()) {
1456
+ parts.push(current.trim());
1457
+ }
1458
+ return parts;
1459
+ }
1460
+ async function createPrismaDbAdapter(fallbackClient) {
1461
+ const prisma = await loadPrismaClient();
1462
+ await ensureCompatibilityViews(prisma);
1463
+ let closed = false;
1464
+ let adapter;
1465
+ const fallbackExecute = async (stmt, error) => {
1466
+ if (!fallbackClient) {
1467
+ if (error) throw error;
1468
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1469
+ }
1470
+ if (error) {
1471
+ process.stderr.write(
1472
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1473
+ `
1474
+ );
1475
+ }
1476
+ return fallbackClient.execute(stmt);
1477
+ };
1478
+ adapter = {
1479
+ async execute(stmt) {
1480
+ if (shouldBypassPostgres(stmt)) {
1481
+ return fallbackExecute(stmt);
1482
+ }
1483
+ try {
1484
+ return await executeOnPrisma(prisma, stmt);
1485
+ } catch (error) {
1486
+ if (shouldFallbackOnError(error)) {
1487
+ return fallbackExecute(stmt, error);
1488
+ }
1489
+ throw error;
1490
+ }
1491
+ },
1492
+ async batch(stmts, mode) {
1493
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1494
+ if (!fallbackClient) {
1495
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1496
+ }
1497
+ return fallbackClient.batch(stmts, mode);
1498
+ }
1499
+ try {
1500
+ if (prisma.$transaction) {
1501
+ return await prisma.$transaction(async (tx) => {
1502
+ const results2 = [];
1503
+ for (const stmt of stmts) {
1504
+ results2.push(await executeOnPrisma(tx, stmt));
1505
+ }
1506
+ return results2;
1507
+ });
1508
+ }
1509
+ const results = [];
1510
+ for (const stmt of stmts) {
1511
+ results.push(await executeOnPrisma(prisma, stmt));
1512
+ }
1513
+ return results;
1514
+ } catch (error) {
1515
+ if (fallbackClient && shouldFallbackOnError(error)) {
1516
+ process.stderr.write(
1517
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1518
+ `
1519
+ );
1520
+ return fallbackClient.batch(stmts, mode);
1521
+ }
1522
+ throw error;
1523
+ }
1524
+ },
1525
+ async migrate(stmts) {
1526
+ if (fallbackClient) {
1527
+ return fallbackClient.migrate(stmts);
1528
+ }
1529
+ return adapter.batch(stmts, "deferred");
1530
+ },
1531
+ async transaction(mode) {
1532
+ if (!fallbackClient) {
1533
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1534
+ }
1535
+ return fallbackClient.transaction(mode);
1536
+ },
1537
+ async executeMultiple(sql) {
1538
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1539
+ return fallbackClient.executeMultiple(sql);
1540
+ }
1541
+ for (const statement of splitSqlStatements(sql)) {
1542
+ await adapter.execute(statement);
1543
+ }
1544
+ },
1545
+ async sync() {
1546
+ if (fallbackClient) {
1547
+ return fallbackClient.sync();
1548
+ }
1549
+ return { frame_no: 0, frames_synced: 0 };
1550
+ },
1551
+ close() {
1552
+ closed = true;
1553
+ prismaClientPromise = null;
1554
+ compatibilityBootstrapPromise = null;
1555
+ void prisma.$disconnect?.();
1556
+ },
1557
+ get closed() {
1558
+ return closed;
1559
+ },
1560
+ get protocol() {
1561
+ return "prisma-postgres";
1562
+ }
1563
+ };
1564
+ return adapter;
1565
+ }
1566
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1567
+ var init_database_adapter = __esm({
1568
+ "src/lib/database-adapter.ts"() {
1569
+ "use strict";
1570
+ VIEW_MAPPINGS = [
1571
+ { view: "memories", source: "memory.memory_records" },
1572
+ { view: "tasks", source: "memory.tasks" },
1573
+ { view: "behaviors", source: "memory.behaviors" },
1574
+ { view: "entities", source: "memory.entities" },
1575
+ { view: "relationships", source: "memory.relationships" },
1576
+ { view: "entity_memories", source: "memory.entity_memories" },
1577
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1578
+ { view: "notifications", source: "memory.notifications" },
1579
+ { view: "messages", source: "memory.messages" },
1580
+ { view: "users", source: "wiki.users" },
1581
+ { view: "workspaces", source: "wiki.workspaces" },
1582
+ { view: "workspace_users", source: "wiki.workspace_users" },
1583
+ { view: "documents", source: "wiki.workspace_documents" },
1584
+ { view: "chats", source: "wiki.workspace_chats" }
1585
+ ];
1586
+ UPSERT_KEYS = {
1587
+ memories: ["id"],
1588
+ tasks: ["id"],
1589
+ behaviors: ["id"],
1590
+ entities: ["id"],
1591
+ relationships: ["id"],
1592
+ entity_aliases: ["alias"],
1593
+ notifications: ["id"],
1594
+ messages: ["id"],
1595
+ users: ["id"],
1596
+ workspaces: ["id"],
1597
+ workspace_users: ["id"],
1598
+ documents: ["id"],
1599
+ chats: ["id"]
1600
+ };
1601
+ BOOLEAN_COLUMNS_BY_TABLE = {
1602
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1603
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1604
+ notifications: /* @__PURE__ */ new Set(["read"]),
1605
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1606
+ };
1607
+ BOOLEAN_COLUMN_NAMES = new Set(
1608
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1609
+ );
1610
+ IMMEDIATE_FALLBACK_PATTERNS = [
1611
+ /\bPRAGMA\b/i,
1612
+ /\bsqlite_master\b/i,
1613
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1614
+ /\bMATCH\b/i,
1615
+ /\bvector_distance_cos\s*\(/i,
1616
+ /\bjson_extract\s*\(/i,
1617
+ /\bjulianday\s*\(/i,
1618
+ /\bstrftime\s*\(/i,
1619
+ /\blast_insert_rowid\s*\(/i
1620
+ ];
1621
+ prismaClientPromise = null;
1622
+ compatibilityBootstrapPromise = null;
838
1623
  }
839
1624
  });
840
1625
 
841
1626
  // src/lib/database.ts
842
1627
  import { createClient } from "@libsql/client";
843
1628
  async function initDatabase(config2) {
1629
+ if (_walCheckpointTimer) {
1630
+ clearInterval(_walCheckpointTimer);
1631
+ _walCheckpointTimer = null;
1632
+ }
1633
+ if (_daemonClient) {
1634
+ _daemonClient.close();
1635
+ _daemonClient = null;
1636
+ }
1637
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1638
+ _adapterClient.close();
1639
+ }
1640
+ _adapterClient = null;
844
1641
  if (_client) {
845
1642
  _client.close();
846
1643
  _client = null;
@@ -854,6 +1651,7 @@ async function initDatabase(config2) {
854
1651
  }
855
1652
  _client = createClient(opts);
856
1653
  _resilientClient = wrapWithRetry(_client);
1654
+ _adapterClient = _resilientClient;
857
1655
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
858
1656
  });
859
1657
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -864,11 +1662,17 @@ async function initDatabase(config2) {
864
1662
  });
865
1663
  }, 3e4);
866
1664
  _walCheckpointTimer.unref();
1665
+ if (process.env.DATABASE_URL) {
1666
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1667
+ }
867
1668
  }
868
1669
  function getClient() {
869
- if (!_resilientClient) {
1670
+ if (!_adapterClient) {
870
1671
  throw new Error("Database client not initialized. Call initDatabase() first.");
871
1672
  }
1673
+ if (process.env.DATABASE_URL) {
1674
+ return _adapterClient;
1675
+ }
872
1676
  if (process.env.EXE_IS_DAEMON === "1") {
873
1677
  return _resilientClient;
874
1678
  }
@@ -1161,6 +1965,7 @@ async function ensureSchema() {
1161
1965
  project TEXT NOT NULL,
1162
1966
  summary TEXT NOT NULL,
1163
1967
  task_file TEXT,
1968
+ session_scope TEXT,
1164
1969
  read INTEGER NOT NULL DEFAULT 0,
1165
1970
  created_at TEXT NOT NULL
1166
1971
  );
@@ -1169,7 +1974,7 @@ async function ensureSchema() {
1169
1974
  ON notifications(read);
1170
1975
 
1171
1976
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1172
- ON notifications(agent_id);
1977
+ ON notifications(agent_id, session_scope);
1173
1978
 
1174
1979
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1175
1980
  ON notifications(task_file);
@@ -1207,6 +2012,7 @@ async function ensureSchema() {
1207
2012
  target_agent TEXT NOT NULL,
1208
2013
  target_project TEXT,
1209
2014
  target_device TEXT NOT NULL DEFAULT 'local',
2015
+ session_scope TEXT,
1210
2016
  content TEXT NOT NULL,
1211
2017
  priority TEXT DEFAULT 'normal',
1212
2018
  status TEXT DEFAULT 'pending',
@@ -1220,10 +2026,31 @@ async function ensureSchema() {
1220
2026
  );
1221
2027
 
1222
2028
  CREATE INDEX IF NOT EXISTS idx_messages_target
1223
- ON messages(target_agent, status);
2029
+ ON messages(target_agent, session_scope, status);
1224
2030
 
1225
2031
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1226
- ON messages(target_agent, from_agent, server_seq);
2032
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2033
+ `);
2034
+ try {
2035
+ await client.execute({
2036
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2037
+ args: []
2038
+ });
2039
+ } catch {
2040
+ }
2041
+ try {
2042
+ await client.execute({
2043
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2044
+ args: []
2045
+ });
2046
+ } catch {
2047
+ }
2048
+ await client.executeMultiple(`
2049
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2050
+ ON notifications(agent_id, session_scope, read, created_at);
2051
+
2052
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2053
+ ON messages(target_agent, session_scope, status, created_at);
1227
2054
  `);
1228
2055
  try {
1229
2056
  await client.execute({
@@ -1807,28 +2634,45 @@ async function ensureSchema() {
1807
2634
  } catch {
1808
2635
  }
1809
2636
  }
2637
+ try {
2638
+ await client.execute({
2639
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2640
+ args: []
2641
+ });
2642
+ } catch {
2643
+ }
1810
2644
  }
1811
2645
  async function disposeDatabase() {
2646
+ if (_walCheckpointTimer) {
2647
+ clearInterval(_walCheckpointTimer);
2648
+ _walCheckpointTimer = null;
2649
+ }
1812
2650
  if (_daemonClient) {
1813
2651
  _daemonClient.close();
1814
2652
  _daemonClient = null;
1815
2653
  }
2654
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2655
+ _adapterClient.close();
2656
+ }
2657
+ _adapterClient = null;
1816
2658
  if (_client) {
1817
2659
  _client.close();
1818
2660
  _client = null;
1819
2661
  _resilientClient = null;
1820
2662
  }
1821
2663
  }
1822
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2664
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1823
2665
  var init_database = __esm({
1824
2666
  "src/lib/database.ts"() {
1825
2667
  "use strict";
1826
2668
  init_db_retry();
1827
2669
  init_employees();
2670
+ init_database_adapter();
1828
2671
  _client = null;
1829
2672
  _resilientClient = null;
1830
2673
  _walCheckpointTimer = null;
1831
2674
  _daemonClient = null;
2675
+ _adapterClient = null;
1832
2676
  initTurso = initDatabase;
1833
2677
  disposeTurso = disposeDatabase;
1834
2678
  }
@@ -1843,13 +2687,50 @@ var init_memory = __esm({
1843
2687
  }
1844
2688
  });
1845
2689
 
2690
+ // src/lib/daemon-auth.ts
2691
+ import crypto from "crypto";
2692
+ import path5 from "path";
2693
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2694
+ function normalizeToken(token) {
2695
+ if (!token) return null;
2696
+ const trimmed = token.trim();
2697
+ return trimmed.length > 0 ? trimmed : null;
2698
+ }
2699
+ function readDaemonToken() {
2700
+ try {
2701
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
2702
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
2703
+ } catch {
2704
+ return null;
2705
+ }
2706
+ }
2707
+ function ensureDaemonToken(seed) {
2708
+ const existing = readDaemonToken();
2709
+ if (existing) return existing;
2710
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
2711
+ ensurePrivateDirSync(EXE_AI_DIR);
2712
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
2713
+ `, "utf8");
2714
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
2715
+ return token;
2716
+ }
2717
+ var DAEMON_TOKEN_PATH;
2718
+ var init_daemon_auth = __esm({
2719
+ "src/lib/daemon-auth.ts"() {
2720
+ "use strict";
2721
+ init_config();
2722
+ init_secure_files();
2723
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
2724
+ }
2725
+ });
2726
+
1846
2727
  // src/lib/exe-daemon-client.ts
1847
2728
  import net from "net";
1848
- import os3 from "os";
2729
+ import os4 from "os";
1849
2730
  import { spawn } from "child_process";
1850
2731
  import { randomUUID } from "crypto";
1851
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1852
- import path3 from "path";
2732
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
2733
+ import path6 from "path";
1853
2734
  import { fileURLToPath } from "url";
1854
2735
  function handleData(chunk) {
1855
2736
  _buffer += chunk.toString();
@@ -1877,9 +2758,9 @@ function handleData(chunk) {
1877
2758
  }
1878
2759
  }
1879
2760
  function cleanupStaleFiles() {
1880
- if (existsSync3(PID_PATH)) {
2761
+ if (existsSync6(PID_PATH)) {
1881
2762
  try {
1882
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2763
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1883
2764
  if (pid > 0) {
1884
2765
  try {
1885
2766
  process.kill(pid, 0);
@@ -1900,17 +2781,17 @@ function cleanupStaleFiles() {
1900
2781
  }
1901
2782
  }
1902
2783
  function findPackageRoot() {
1903
- let dir = path3.dirname(fileURLToPath(import.meta.url));
1904
- const { root } = path3.parse(dir);
2784
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2785
+ const { root } = path6.parse(dir);
1905
2786
  while (dir !== root) {
1906
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
1907
- dir = path3.dirname(dir);
2787
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2788
+ dir = path6.dirname(dir);
1908
2789
  }
1909
2790
  return null;
1910
2791
  }
1911
2792
  function spawnDaemon() {
1912
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
1913
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
2793
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
2794
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1914
2795
  if (totalGB <= 8) {
1915
2796
  process.stderr.write(
1916
2797
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1930,16 +2811,17 @@ function spawnDaemon() {
1930
2811
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1931
2812
  return;
1932
2813
  }
1933
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1934
- if (!existsSync3(daemonPath)) {
2814
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2815
+ if (!existsSync6(daemonPath)) {
1935
2816
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1936
2817
  `);
1937
2818
  return;
1938
2819
  }
1939
2820
  const resolvedPath = daemonPath;
2821
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1940
2822
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1941
2823
  `);
1942
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
2824
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1943
2825
  let stderrFd = "ignore";
1944
2826
  try {
1945
2827
  stderrFd = openSync(logPath, "a");
@@ -1957,7 +2839,8 @@ function spawnDaemon() {
1957
2839
  TMUX_PANE: void 0,
1958
2840
  // Prevents resolveExeSession() from scoping to one session
1959
2841
  EXE_DAEMON_SOCK: SOCKET_PATH,
1960
- EXE_DAEMON_PID: PID_PATH
2842
+ EXE_DAEMON_PID: PID_PATH,
2843
+ [DAEMON_TOKEN_ENV]: daemonToken
1961
2844
  }
1962
2845
  });
1963
2846
  child.unref();
@@ -2067,13 +2950,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2067
2950
  return;
2068
2951
  }
2069
2952
  const id = randomUUID();
2953
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2070
2954
  const timer = setTimeout(() => {
2071
2955
  _pending.delete(id);
2072
2956
  resolve({ error: "Request timeout" });
2073
2957
  }, timeoutMs);
2074
2958
  _pending.set(id, { resolve, timer });
2075
2959
  try {
2076
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2960
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
2077
2961
  } catch {
2078
2962
  clearTimeout(timer);
2079
2963
  _pending.delete(id);
@@ -2090,74 +2974,123 @@ async function pingDaemon() {
2090
2974
  return null;
2091
2975
  }
2092
2976
  function killAndRespawnDaemon() {
2093
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
2094
- if (existsSync3(PID_PATH)) {
2095
- try {
2096
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2097
- if (pid > 0) {
2098
- try {
2099
- process.kill(pid, "SIGKILL");
2100
- } catch {
2977
+ if (!acquireSpawnLock()) {
2978
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2979
+ if (_socket) {
2980
+ _socket.destroy();
2981
+ _socket = null;
2982
+ }
2983
+ _connected = false;
2984
+ _buffer = "";
2985
+ return;
2986
+ }
2987
+ try {
2988
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2989
+ if (existsSync6(PID_PATH)) {
2990
+ try {
2991
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2992
+ if (pid > 0) {
2993
+ try {
2994
+ process.kill(pid, "SIGKILL");
2995
+ } catch {
2996
+ }
2101
2997
  }
2998
+ } catch {
2102
2999
  }
3000
+ }
3001
+ if (_socket) {
3002
+ _socket.destroy();
3003
+ _socket = null;
3004
+ }
3005
+ _connected = false;
3006
+ _buffer = "";
3007
+ try {
3008
+ unlinkSync2(PID_PATH);
2103
3009
  } catch {
2104
3010
  }
3011
+ try {
3012
+ unlinkSync2(SOCKET_PATH);
3013
+ } catch {
3014
+ }
3015
+ spawnDaemon();
3016
+ } finally {
3017
+ releaseSpawnLock();
2105
3018
  }
2106
- if (_socket) {
2107
- _socket.destroy();
2108
- _socket = null;
2109
- }
2110
- _connected = false;
2111
- _buffer = "";
3019
+ }
3020
+ function isDaemonTooYoung() {
2112
3021
  try {
2113
- unlinkSync2(PID_PATH);
3022
+ const stat = statSync(PID_PATH);
3023
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
2114
3024
  } catch {
3025
+ return false;
2115
3026
  }
2116
- try {
2117
- unlinkSync2(SOCKET_PATH);
2118
- } catch {
3027
+ }
3028
+ async function retryThenRestart(doRequest, label) {
3029
+ const result = await doRequest();
3030
+ if (!result.error) {
3031
+ _consecutiveFailures = 0;
3032
+ return result;
2119
3033
  }
2120
- spawnDaemon();
3034
+ _consecutiveFailures++;
3035
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
3036
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
3037
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
3038
+ `);
3039
+ await new Promise((r) => setTimeout(r, delayMs));
3040
+ if (!_connected) {
3041
+ if (!await connectToSocket()) continue;
3042
+ }
3043
+ const retry = await doRequest();
3044
+ if (!retry.error) {
3045
+ _consecutiveFailures = 0;
3046
+ return retry;
3047
+ }
3048
+ _consecutiveFailures++;
3049
+ }
3050
+ if (isDaemonTooYoung()) {
3051
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
3052
+ `);
3053
+ return { error: result.error };
3054
+ }
3055
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
3056
+ `);
3057
+ killAndRespawnDaemon();
3058
+ const start = Date.now();
3059
+ let delay2 = 200;
3060
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3061
+ await new Promise((r) => setTimeout(r, delay2));
3062
+ if (await connectToSocket()) break;
3063
+ delay2 = Math.min(delay2 * 2, 3e3);
3064
+ }
3065
+ if (!_connected) return { error: "Daemon restart failed" };
3066
+ const final = await doRequest();
3067
+ if (!final.error) _consecutiveFailures = 0;
3068
+ return final;
2121
3069
  }
2122
3070
  async function embedViaClient(text, priority = "high") {
2123
3071
  if (!_connected && !await connectEmbedDaemon()) return null;
2124
3072
  _requestCount++;
2125
3073
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2126
3074
  const health = await pingDaemon();
2127
- if (!health) {
3075
+ if (!health && !isDaemonTooYoung()) {
2128
3076
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2129
3077
  `);
2130
3078
  killAndRespawnDaemon();
2131
3079
  const start = Date.now();
2132
- let delay2 = 200;
3080
+ let d = 200;
2133
3081
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2134
- await new Promise((r) => setTimeout(r, delay2));
3082
+ await new Promise((r) => setTimeout(r, d));
2135
3083
  if (await connectToSocket()) break;
2136
- delay2 = Math.min(delay2 * 2, 3e3);
2137
- }
2138
- if (!_connected) return null;
2139
- }
2140
- }
2141
- const result = await sendRequest([text], priority);
2142
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2143
- if (result.error) {
2144
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2145
- `);
2146
- killAndRespawnDaemon();
2147
- const start = Date.now();
2148
- let delay2 = 200;
2149
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2150
- await new Promise((r) => setTimeout(r, delay2));
2151
- if (await connectToSocket()) break;
2152
- delay2 = Math.min(delay2 * 2, 3e3);
2153
- }
2154
- if (!_connected) return null;
2155
- const retry = await sendRequest([text], priority);
2156
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2157
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2158
- `);
3084
+ d = Math.min(d * 2, 3e3);
3085
+ }
3086
+ if (!_connected) return null;
3087
+ }
2159
3088
  }
2160
- return null;
3089
+ const result = await retryThenRestart(
3090
+ () => sendRequest([text], priority),
3091
+ "Embed"
3092
+ );
3093
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
2161
3094
  }
2162
3095
  function disconnectClient() {
2163
3096
  if (_socket) {
@@ -2172,22 +3105,28 @@ function disconnectClient() {
2172
3105
  entry.resolve({ error: "Client disconnected" });
2173
3106
  }
2174
3107
  }
2175
- 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;
3108
+ 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;
2176
3109
  var init_exe_daemon_client = __esm({
2177
3110
  "src/lib/exe-daemon-client.ts"() {
2178
3111
  "use strict";
2179
3112
  init_config();
2180
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
2181
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
2182
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
3113
+ init_daemon_auth();
3114
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3115
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3116
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2183
3117
  SPAWN_LOCK_STALE_MS = 3e4;
2184
3118
  CONNECT_TIMEOUT_MS = 15e3;
2185
3119
  REQUEST_TIMEOUT_MS = 3e4;
3120
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
2186
3121
  _socket = null;
2187
3122
  _connected = false;
2188
3123
  _buffer = "";
2189
3124
  _requestCount = 0;
3125
+ _consecutiveFailures = 0;
2190
3126
  HEALTH_CHECK_INTERVAL = 100;
3127
+ MAX_RETRIES_BEFORE_RESTART = 3;
3128
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
3129
+ MIN_DAEMON_AGE_MS = 3e4;
2191
3130
  _pending = /* @__PURE__ */ new Map();
2192
3131
  MAX_BUFFER = 1e7;
2193
3132
  }
@@ -2230,10 +3169,10 @@ async function disposeEmbedder() {
2230
3169
  async function embedDirect(text) {
2231
3170
  const llamaCpp = await import("node-llama-cpp");
2232
3171
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2233
- const { existsSync: existsSync16 } = await import("fs");
2234
- const path20 = await import("path");
2235
- const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2236
- if (!existsSync16(modelPath)) {
3172
+ const { existsSync: existsSync18 } = await import("fs");
3173
+ const path22 = await import("path");
3174
+ const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3175
+ if (!existsSync18(modelPath)) {
2237
3176
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
2238
3177
  }
2239
3178
  const llama = await llamaCpp.getLlama();
@@ -2263,14 +3202,14 @@ var init_embedder = __esm({
2263
3202
 
2264
3203
  // src/lib/keychain.ts
2265
3204
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2266
- import { existsSync as existsSync4 } from "fs";
2267
- import path4 from "path";
2268
- import os4 from "os";
3205
+ import { existsSync as existsSync7 } from "fs";
3206
+ import path7 from "path";
3207
+ import os5 from "os";
2269
3208
  function getKeyDir() {
2270
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
3209
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
2271
3210
  }
2272
3211
  function getKeyPath() {
2273
- return path4.join(getKeyDir(), "master.key");
3212
+ return path7.join(getKeyDir(), "master.key");
2274
3213
  }
2275
3214
  async function tryKeytar() {
2276
3215
  try {
@@ -2291,9 +3230,9 @@ async function getMasterKey() {
2291
3230
  }
2292
3231
  }
2293
3232
  const keyPath = getKeyPath();
2294
- if (!existsSync4(keyPath)) {
3233
+ if (!existsSync7(keyPath)) {
2295
3234
  process.stderr.write(
2296
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3235
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2297
3236
  `
2298
3237
  );
2299
3238
  return null;
@@ -2323,6 +3262,7 @@ var shard_manager_exports = {};
2323
3262
  __export(shard_manager_exports, {
2324
3263
  disposeShards: () => disposeShards,
2325
3264
  ensureShardSchema: () => ensureShardSchema,
3265
+ getOpenShardCount: () => getOpenShardCount,
2326
3266
  getReadyShardClient: () => getReadyShardClient,
2327
3267
  getShardClient: () => getShardClient,
2328
3268
  getShardsDir: () => getShardsDir,
@@ -2331,15 +3271,18 @@ __export(shard_manager_exports, {
2331
3271
  listShards: () => listShards,
2332
3272
  shardExists: () => shardExists
2333
3273
  });
2334
- import path5 from "path";
2335
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
3274
+ import path8 from "path";
3275
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
2336
3276
  import { createClient as createClient2 } from "@libsql/client";
2337
3277
  function initShardManager(encryptionKey) {
2338
3278
  _encryptionKey = encryptionKey;
2339
- if (!existsSync5(SHARDS_DIR)) {
2340
- mkdirSync(SHARDS_DIR, { recursive: true });
3279
+ if (!existsSync8(SHARDS_DIR)) {
3280
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2341
3281
  }
2342
3282
  _shardingEnabled = true;
3283
+ if (_evictionTimer) clearInterval(_evictionTimer);
3284
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3285
+ _evictionTimer.unref();
2343
3286
  }
2344
3287
  function isShardingEnabled() {
2345
3288
  return _shardingEnabled;
@@ -2356,21 +3299,28 @@ function getShardClient(projectName) {
2356
3299
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2357
3300
  }
2358
3301
  const cached = _shards.get(safeName);
2359
- if (cached) return cached;
2360
- const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
3302
+ if (cached) {
3303
+ _shardLastAccess.set(safeName, Date.now());
3304
+ return cached;
3305
+ }
3306
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3307
+ evictLRU();
3308
+ }
3309
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
2361
3310
  const client = createClient2({
2362
3311
  url: `file:${dbPath}`,
2363
3312
  encryptionKey: _encryptionKey
2364
3313
  });
2365
3314
  _shards.set(safeName, client);
3315
+ _shardLastAccess.set(safeName, Date.now());
2366
3316
  return client;
2367
3317
  }
2368
3318
  function shardExists(projectName) {
2369
3319
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2370
- return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
3320
+ return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
2371
3321
  }
2372
3322
  function listShards() {
2373
- if (!existsSync5(SHARDS_DIR)) return [];
3323
+ if (!existsSync8(SHARDS_DIR)) return [];
2374
3324
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2375
3325
  }
2376
3326
  async function ensureShardSchema(client) {
@@ -2422,6 +3372,8 @@ async function ensureShardSchema(client) {
2422
3372
  for (const col of [
2423
3373
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2424
3374
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3375
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3376
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2425
3377
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2426
3378
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2427
3379
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2444,7 +3396,23 @@ async function ensureShardSchema(client) {
2444
3396
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2445
3397
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2446
3398
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2447
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3399
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3400
+ // Metadata enrichment columns (must match database.ts)
3401
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3402
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3403
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3404
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3405
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3406
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3407
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3408
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3409
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3410
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3411
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3412
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3413
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3414
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3415
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2448
3416
  ]) {
2449
3417
  try {
2450
3418
  await client.execute(col);
@@ -2543,21 +3511,69 @@ async function getReadyShardClient(projectName) {
2543
3511
  await ensureShardSchema(client);
2544
3512
  return client;
2545
3513
  }
3514
+ function evictLRU() {
3515
+ let oldest = null;
3516
+ let oldestTime = Infinity;
3517
+ for (const [name, time] of _shardLastAccess) {
3518
+ if (time < oldestTime) {
3519
+ oldestTime = time;
3520
+ oldest = name;
3521
+ }
3522
+ }
3523
+ if (oldest) {
3524
+ const client = _shards.get(oldest);
3525
+ if (client) {
3526
+ client.close();
3527
+ }
3528
+ _shards.delete(oldest);
3529
+ _shardLastAccess.delete(oldest);
3530
+ }
3531
+ }
3532
+ function evictIdleShards() {
3533
+ const now = Date.now();
3534
+ const toEvict = [];
3535
+ for (const [name, lastAccess] of _shardLastAccess) {
3536
+ if (now - lastAccess > SHARD_IDLE_MS) {
3537
+ toEvict.push(name);
3538
+ }
3539
+ }
3540
+ for (const name of toEvict) {
3541
+ const client = _shards.get(name);
3542
+ if (client) {
3543
+ client.close();
3544
+ }
3545
+ _shards.delete(name);
3546
+ _shardLastAccess.delete(name);
3547
+ }
3548
+ }
3549
+ function getOpenShardCount() {
3550
+ return _shards.size;
3551
+ }
2546
3552
  function disposeShards() {
3553
+ if (_evictionTimer) {
3554
+ clearInterval(_evictionTimer);
3555
+ _evictionTimer = null;
3556
+ }
2547
3557
  for (const [, client] of _shards) {
2548
3558
  client.close();
2549
3559
  }
2550
3560
  _shards.clear();
3561
+ _shardLastAccess.clear();
2551
3562
  _shardingEnabled = false;
2552
3563
  _encryptionKey = null;
2553
3564
  }
2554
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3565
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2555
3566
  var init_shard_manager = __esm({
2556
3567
  "src/lib/shard-manager.ts"() {
2557
3568
  "use strict";
2558
3569
  init_config();
2559
- SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
3570
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
3571
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3572
+ MAX_OPEN_SHARDS = 10;
3573
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2560
3574
  _shards = /* @__PURE__ */ new Map();
3575
+ _shardLastAccess = /* @__PURE__ */ new Map();
3576
+ _evictionTimer = null;
2561
3577
  _encryptionKey = null;
2562
3578
  _shardingEnabled = false;
2563
3579
  }
@@ -3329,8 +4345,8 @@ __export(wiki_client_exports, {
3329
4345
  listDocuments: () => listDocuments,
3330
4346
  listWorkspaces: () => listWorkspaces
3331
4347
  });
3332
- async function wikiFetch(config2, path20, method = "GET", body) {
3333
- const url = `${config2.baseUrl}/api/v1${path20}`;
4348
+ async function wikiFetch(config2, path22, method = "GET", body) {
4349
+ const url = `${config2.baseUrl}/api/v1${path22}`;
3334
4350
  const headers = {
3335
4351
  Authorization: `Bearer ${config2.apiKey}`,
3336
4352
  "Content-Type": "application/json"
@@ -3363,7 +4379,7 @@ async function wikiFetch(config2, path20, method = "GET", body) {
3363
4379
  }
3364
4380
  }
3365
4381
  if (!response.ok) {
3366
- throw new Error(`Wiki API ${method} ${path20}: ${response.status} ${response.statusText}`);
4382
+ throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
3367
4383
  }
3368
4384
  return response.json();
3369
4385
  } finally {
@@ -3656,13 +4672,13 @@ __export(graph_rag_exports, {
3656
4672
  resolveAlias: () => resolveAlias,
3657
4673
  storeExtraction: () => storeExtraction
3658
4674
  });
3659
- import crypto from "crypto";
4675
+ import crypto2 from "crypto";
3660
4676
  function normalizeEntityName(name) {
3661
4677
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
3662
4678
  }
3663
4679
  function entityId(name, type) {
3664
4680
  const normalized = normalizeEntityName(name);
3665
- return crypto.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
4681
+ return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
3666
4682
  }
3667
4683
  async function resolveAlias(client, name) {
3668
4684
  const normalized = normalizeEntityName(name);
@@ -3912,7 +4928,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
3912
4928
  const targetAlias = await resolveAlias(client, r.target);
3913
4929
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
3914
4930
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
3915
- const relId = crypto.randomUUID().slice(0, 16);
4931
+ const relId = crypto2.randomUUID().slice(0, 16);
3916
4932
  try {
3917
4933
  await client.execute({
3918
4934
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -3975,7 +4991,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
3975
4991
  }
3976
4992
  }
3977
4993
  for (const h of extraction.hyperedges) {
3978
- const hId = crypto.randomUUID().slice(0, 16);
4994
+ const hId = crypto2.randomUUID().slice(0, 16);
3979
4995
  try {
3980
4996
  await client.execute({
3981
4997
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -4039,7 +5055,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
4039
5055
  totalEntities += stored.entitiesStored;
4040
5056
  totalRelationships += stored.relationshipsStored;
4041
5057
  }
4042
- const contentHash = crypto.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
5058
+ const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
4043
5059
  await client.execute({
4044
5060
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
4045
5061
  args: [contentHash, contentHash, memoryId]
@@ -4384,13 +5400,13 @@ __export(whatsapp_accounts_exports, {
4384
5400
  getDefaultAccount: () => getDefaultAccount,
4385
5401
  loadAccounts: () => loadAccounts
4386
5402
  });
4387
- import { readFileSync as readFileSync4 } from "fs";
5403
+ import { readFileSync as readFileSync6 } from "fs";
4388
5404
  import { join as join2 } from "path";
4389
5405
  import { homedir as homedir2 } from "os";
4390
5406
  function loadAccounts() {
4391
5407
  if (cachedAccounts !== null) return cachedAccounts;
4392
5408
  try {
4393
- const raw = readFileSync4(CONFIG_PATH2, "utf8");
5409
+ const raw = readFileSync6(CONFIG_PATH2, "utf8");
4394
5410
  const parsed = JSON.parse(raw);
4395
5411
  if (!Array.isArray(parsed)) {
4396
5412
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -4430,13 +5446,13 @@ var init_whatsapp_accounts = __esm({
4430
5446
  });
4431
5447
 
4432
5448
  // src/lib/session-registry.ts
4433
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
4434
- import path7 from "path";
4435
- import os6 from "os";
5449
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5450
+ import path10 from "path";
5451
+ import os7 from "os";
4436
5452
  function registerSession(entry) {
4437
- const dir = path7.dirname(REGISTRY_PATH);
4438
- if (!existsSync6(dir)) {
4439
- mkdirSync3(dir, { recursive: true });
5453
+ const dir = path10.dirname(REGISTRY_PATH);
5454
+ if (!existsSync9(dir)) {
5455
+ mkdirSync4(dir, { recursive: true });
4440
5456
  }
4441
5457
  const sessions = listSessions();
4442
5458
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -4445,11 +5461,11 @@ function registerSession(entry) {
4445
5461
  } else {
4446
5462
  sessions.push(entry);
4447
5463
  }
4448
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5464
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4449
5465
  }
4450
5466
  function listSessions() {
4451
5467
  try {
4452
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
5468
+ const raw = readFileSync7(REGISTRY_PATH, "utf8");
4453
5469
  return JSON.parse(raw);
4454
5470
  } catch {
4455
5471
  return [];
@@ -4459,7 +5475,7 @@ var REGISTRY_PATH;
4459
5475
  var init_session_registry = __esm({
4460
5476
  "src/lib/session-registry.ts"() {
4461
5477
  "use strict";
4462
- REGISTRY_PATH = path7.join(os6.homedir(), ".exe-os", "session-registry.json");
5478
+ REGISTRY_PATH = path10.join(os7.homedir(), ".exe-os", "session-registry.json");
4463
5479
  }
4464
5480
  });
4465
5481
 
@@ -4711,67 +5727,6 @@ var init_provider_table = __esm({
4711
5727
  }
4712
5728
  });
4713
5729
 
4714
- // src/lib/runtime-table.ts
4715
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
4716
- var init_runtime_table = __esm({
4717
- "src/lib/runtime-table.ts"() {
4718
- "use strict";
4719
- RUNTIME_TABLE = {
4720
- codex: {
4721
- binary: "codex",
4722
- launchMode: "interactive",
4723
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
4724
- inlineFlag: "--no-alt-screen",
4725
- apiKeyEnv: "OPENAI_API_KEY",
4726
- defaultModel: "gpt-5.4"
4727
- },
4728
- opencode: {
4729
- binary: "opencode",
4730
- launchMode: "exec",
4731
- autoApproveFlag: "--dangerously-skip-permissions",
4732
- inlineFlag: "",
4733
- apiKeyEnv: "ANTHROPIC_API_KEY",
4734
- defaultModel: "anthropic/claude-sonnet-4-6"
4735
- }
4736
- };
4737
- DEFAULT_RUNTIME = "claude";
4738
- }
4739
- });
4740
-
4741
- // src/lib/agent-config.ts
4742
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
4743
- import path8 from "path";
4744
- function loadAgentConfig() {
4745
- if (!existsSync7(AGENT_CONFIG_PATH)) return {};
4746
- try {
4747
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
4748
- } catch {
4749
- return {};
4750
- }
4751
- }
4752
- function getAgentRuntime(agentId) {
4753
- const config2 = loadAgentConfig();
4754
- const entry = config2[agentId];
4755
- if (entry) return entry;
4756
- const orgDefault = config2["default"];
4757
- if (orgDefault) return orgDefault;
4758
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
4759
- }
4760
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
4761
- var init_agent_config = __esm({
4762
- "src/lib/agent-config.ts"() {
4763
- "use strict";
4764
- init_config();
4765
- init_runtime_table();
4766
- AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
4767
- DEFAULT_MODELS = {
4768
- claude: "claude-opus-4",
4769
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
4770
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
4771
- };
4772
- }
4773
- });
4774
-
4775
5730
  // src/lib/intercom-queue.ts
4776
5731
  var intercom_queue_exports = {};
4777
5732
  __export(intercom_queue_exports, {
@@ -4781,17 +5736,17 @@ __export(intercom_queue_exports, {
4781
5736
  queueIntercom: () => queueIntercom,
4782
5737
  readQueue: () => readQueue
4783
5738
  });
4784
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
4785
- import path9 from "path";
4786
- import os7 from "os";
5739
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
5740
+ import path11 from "path";
5741
+ import os8 from "os";
4787
5742
  function ensureDir() {
4788
- const dir = path9.dirname(QUEUE_PATH);
4789
- if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
5743
+ const dir = path11.dirname(QUEUE_PATH);
5744
+ if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
4790
5745
  }
4791
5746
  function readQueue() {
4792
5747
  try {
4793
- if (!existsSync8(QUEUE_PATH)) return [];
4794
- return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
5748
+ if (!existsSync10(QUEUE_PATH)) return [];
5749
+ return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
4795
5750
  } catch {
4796
5751
  return [];
4797
5752
  }
@@ -4799,7 +5754,7 @@ function readQueue() {
4799
5754
  function writeQueue(queue) {
4800
5755
  ensureDir();
4801
5756
  const tmp = `${QUEUE_PATH}.tmp`;
4802
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
5757
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
4803
5758
  renameSync3(tmp, QUEUE_PATH);
4804
5759
  }
4805
5760
  function queueIntercom(targetSession, reason) {
@@ -4891,26 +5846,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
4891
5846
  var init_intercom_queue = __esm({
4892
5847
  "src/lib/intercom-queue.ts"() {
4893
5848
  "use strict";
4894
- QUEUE_PATH = path9.join(os7.homedir(), ".exe-os", "intercom-queue.json");
5849
+ QUEUE_PATH = path11.join(os8.homedir(), ".exe-os", "intercom-queue.json");
4895
5850
  MAX_RETRIES2 = 5;
4896
5851
  TTL_MS = 60 * 60 * 1e3;
4897
- INTERCOM_LOG = path9.join(os7.homedir(), ".exe-os", "intercom.log");
5852
+ INTERCOM_LOG = path11.join(os8.homedir(), ".exe-os", "intercom.log");
4898
5853
  }
4899
5854
  });
4900
5855
 
4901
5856
  // src/lib/license.ts
4902
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
5857
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
4903
5858
  import { randomUUID as randomUUID11 } from "crypto";
4904
- import path10 from "path";
5859
+ import { createRequire as createRequire2 } from "module";
5860
+ import { pathToFileURL as pathToFileURL2 } from "url";
5861
+ import os9 from "os";
5862
+ import path12 from "path";
4905
5863
  import { jwtVerify, importSPKI } from "jose";
4906
5864
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
4907
5865
  var init_license = __esm({
4908
5866
  "src/lib/license.ts"() {
4909
5867
  "use strict";
4910
5868
  init_config();
4911
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
4912
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
4913
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
5869
+ LICENSE_PATH = path12.join(EXE_AI_DIR, "license.key");
5870
+ CACHE_PATH = path12.join(EXE_AI_DIR, "license-cache.json");
5871
+ DEVICE_ID_PATH = path12.join(EXE_AI_DIR, "device-id");
4914
5872
  PLAN_LIMITS = {
4915
5873
  free: { devices: 1, employees: 1, memories: 5e3 },
4916
5874
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4922,12 +5880,12 @@ var init_license = __esm({
4922
5880
  });
4923
5881
 
4924
5882
  // src/lib/plan-limits.ts
4925
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
4926
- import path11 from "path";
5883
+ import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
5884
+ import path13 from "path";
4927
5885
  function getLicenseSync() {
4928
5886
  try {
4929
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
4930
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
5887
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
5888
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
4931
5889
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
4932
5890
  const parts = raw.token.split(".");
4933
5891
  if (parts.length !== 3) return freeLicense();
@@ -4965,8 +5923,8 @@ function assertEmployeeLimitSync(rosterPath) {
4965
5923
  const filePath = rosterPath ?? EMPLOYEES_PATH;
4966
5924
  let count = 0;
4967
5925
  try {
4968
- if (existsSync10(filePath)) {
4969
- const raw = readFileSync9(filePath, "utf8");
5926
+ if (existsSync12(filePath)) {
5927
+ const raw = readFileSync10(filePath, "utf8");
4970
5928
  const employees = JSON.parse(raw);
4971
5929
  count = Array.isArray(employees) ? employees.length : 0;
4972
5930
  }
@@ -4995,29 +5953,63 @@ var init_plan_limits = __esm({
4995
5953
  this.name = "PlanLimitError";
4996
5954
  }
4997
5955
  };
4998
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
5956
+ CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
5957
+ }
5958
+ });
5959
+
5960
+ // src/lib/task-scope.ts
5961
+ function getCurrentSessionScope() {
5962
+ try {
5963
+ return resolveExeSession();
5964
+ } catch {
5965
+ return null;
5966
+ }
5967
+ }
5968
+ function sessionScopeFilter(sessionScope, tableAlias) {
5969
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5970
+ if (!scope) return { sql: "", args: [] };
5971
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5972
+ return {
5973
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
5974
+ args: [scope]
5975
+ };
5976
+ }
5977
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5978
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5979
+ if (!scope) return { sql: "", args: [] };
5980
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5981
+ return {
5982
+ sql: ` AND ${col} = ?`,
5983
+ args: [scope]
5984
+ };
5985
+ }
5986
+ var init_task_scope = __esm({
5987
+ "src/lib/task-scope.ts"() {
5988
+ "use strict";
5989
+ init_tmux_routing();
4999
5990
  }
5000
5991
  });
5001
5992
 
5002
5993
  // src/lib/notifications.ts
5003
- import crypto3 from "crypto";
5004
- import path12 from "path";
5005
- import os8 from "os";
5994
+ import crypto4 from "crypto";
5995
+ import path14 from "path";
5996
+ import os10 from "os";
5006
5997
  import {
5007
- readFileSync as readFileSync10,
5998
+ readFileSync as readFileSync11,
5008
5999
  readdirSync as readdirSync2,
5009
6000
  unlinkSync as unlinkSync3,
5010
- existsSync as existsSync11,
6001
+ existsSync as existsSync13,
5011
6002
  rmdirSync
5012
6003
  } from "fs";
5013
6004
  async function writeNotification(notification) {
5014
6005
  try {
5015
6006
  const client = getClient();
5016
- const id = crypto3.randomUUID();
6007
+ const id = crypto4.randomUUID();
5017
6008
  const now = (/* @__PURE__ */ new Date()).toISOString();
6009
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
5018
6010
  await client.execute({
5019
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
5020
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
6011
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
6012
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
5021
6013
  args: [
5022
6014
  id,
5023
6015
  notification.agentId,
@@ -5026,6 +6018,7 @@ async function writeNotification(notification) {
5026
6018
  notification.project,
5027
6019
  notification.summary,
5028
6020
  notification.taskFile ?? null,
6021
+ sessionScope,
5029
6022
  now
5030
6023
  ]
5031
6024
  });
@@ -5034,12 +6027,14 @@ async function writeNotification(notification) {
5034
6027
  `);
5035
6028
  }
5036
6029
  }
5037
- async function markAsReadByTaskFile(taskFile) {
6030
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
5038
6031
  try {
5039
6032
  const client = getClient();
6033
+ const scope = strictSessionScopeFilter(sessionScope);
5040
6034
  await client.execute({
5041
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
5042
- args: [taskFile]
6035
+ sql: `UPDATE notifications SET read = 1
6036
+ WHERE task_file = ? AND read = 0${scope.sql}`,
6037
+ args: [taskFile, ...scope.args]
5043
6038
  });
5044
6039
  } catch {
5045
6040
  }
@@ -5048,11 +6043,12 @@ var init_notifications = __esm({
5048
6043
  "src/lib/notifications.ts"() {
5049
6044
  "use strict";
5050
6045
  init_database();
6046
+ init_task_scope();
5051
6047
  }
5052
6048
  });
5053
6049
 
5054
6050
  // src/lib/session-kill-telemetry.ts
5055
- import crypto4 from "crypto";
6051
+ import crypto5 from "crypto";
5056
6052
  async function recordSessionKill(input) {
5057
6053
  try {
5058
6054
  const client = getClient();
@@ -5062,7 +6058,7 @@ async function recordSessionKill(input) {
5062
6058
  ticks_idle, estimated_tokens_saved)
5063
6059
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
5064
6060
  args: [
5065
- crypto4.randomUUID(),
6061
+ crypto5.randomUUID(),
5066
6062
  input.sessionName,
5067
6063
  input.agentId,
5068
6064
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -5085,37 +6081,13 @@ var init_session_kill_telemetry = __esm({
5085
6081
  }
5086
6082
  });
5087
6083
 
5088
- // src/lib/task-scope.ts
5089
- function getCurrentSessionScope() {
5090
- try {
5091
- return resolveExeSession();
5092
- } catch {
5093
- return null;
5094
- }
5095
- }
5096
- function sessionScopeFilter(sessionScope, tableAlias) {
5097
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5098
- if (!scope) return { sql: "", args: [] };
5099
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5100
- return {
5101
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
5102
- args: [scope]
5103
- };
5104
- }
5105
- var init_task_scope = __esm({
5106
- "src/lib/task-scope.ts"() {
5107
- "use strict";
5108
- init_tmux_routing();
5109
- }
5110
- });
5111
-
5112
6084
  // src/lib/tasks-crud.ts
5113
- import crypto5 from "crypto";
5114
- import path13 from "path";
5115
- import os9 from "os";
6085
+ import crypto6 from "crypto";
6086
+ import path15 from "path";
6087
+ import os11 from "os";
5116
6088
  import { execSync as execSync4 } from "child_process";
5117
6089
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5118
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
6090
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
5119
6091
  async function writeCheckpoint(input) {
5120
6092
  const client = getClient();
5121
6093
  const row = await resolveTask(client, input.taskId);
@@ -5231,7 +6203,7 @@ async function resolveTask(client, identifier, scopeSession) {
5231
6203
  }
5232
6204
  async function createTaskCore(input) {
5233
6205
  const client = getClient();
5234
- const id = crypto5.randomUUID();
6206
+ const id = crypto6.randomUUID();
5235
6207
  const now = (/* @__PURE__ */ new Date()).toISOString();
5236
6208
  const slug = slugify(input.title);
5237
6209
  let earlySessionScope = null;
@@ -5290,8 +6262,8 @@ ${laneWarning}` : laneWarning;
5290
6262
  }
5291
6263
  if (input.baseDir) {
5292
6264
  try {
5293
- await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
5294
- await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
6265
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
6266
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
5295
6267
  await ensureArchitectureDoc(input.baseDir, input.projectName);
5296
6268
  await ensureGitignoreExe(input.baseDir);
5297
6269
  } catch {
@@ -5327,13 +6299,19 @@ ${laneWarning}` : laneWarning;
5327
6299
  });
5328
6300
  if (input.baseDir) {
5329
6301
  try {
5330
- const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
5331
- const mdPath = path13.join(EXE_OS_DIR, taskFile);
5332
- const mdDir = path13.dirname(mdPath);
5333
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
6302
+ const EXE_OS_DIR = path15.join(os11.homedir(), ".exe-os");
6303
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
6304
+ const mdDir = path15.dirname(mdPath);
6305
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
5334
6306
  const reviewer = input.reviewer ?? input.assignedBy;
5335
6307
  const mdContent = `# ${input.title}
5336
6308
 
6309
+ ## MANDATORY: When done
6310
+
6311
+ You MUST call update_task with status "done" and a result summary when finished.
6312
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
6313
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
6314
+
5337
6315
  **ID:** ${id}
5338
6316
  **Status:** ${initialStatus}
5339
6317
  **Priority:** ${input.priority}
@@ -5347,12 +6325,6 @@ ${laneWarning}` : laneWarning;
5347
6325
  ## Context
5348
6326
 
5349
6327
  ${input.context}
5350
-
5351
- ## MANDATORY: When done
5352
-
5353
- You MUST call update_task with status "done" and a result summary when finished.
5354
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
5355
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
5356
6328
  `;
5357
6329
  await writeFile4(mdPath, mdContent, "utf-8");
5358
6330
  } catch (err) {
@@ -5601,7 +6573,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
5601
6573
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
5602
6574
  } catch {
5603
6575
  }
5604
- if (input.status === "done" || input.status === "cancelled") {
6576
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5605
6577
  try {
5606
6578
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
5607
6579
  clearQueueForAgent2(String(row.assigned_to));
@@ -5630,9 +6602,9 @@ async function deleteTaskCore(taskId, _baseDir) {
5630
6602
  return { taskFile, assignedTo, assignedBy, taskSlug };
5631
6603
  }
5632
6604
  async function ensureArchitectureDoc(baseDir, projectName) {
5633
- const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
6605
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
5634
6606
  try {
5635
- if (existsSync12(archPath)) return;
6607
+ if (existsSync14(archPath)) return;
5636
6608
  const template = [
5637
6609
  `# ${projectName} \u2014 System Architecture`,
5638
6610
  "",
@@ -5665,10 +6637,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5665
6637
  }
5666
6638
  }
5667
6639
  async function ensureGitignoreExe(baseDir) {
5668
- const gitignorePath = path13.join(baseDir, ".gitignore");
6640
+ const gitignorePath = path15.join(baseDir, ".gitignore");
5669
6641
  try {
5670
- if (existsSync12(gitignorePath)) {
5671
- const content = readFileSync11(gitignorePath, "utf-8");
6642
+ if (existsSync14(gitignorePath)) {
6643
+ const content = readFileSync12(gitignorePath, "utf-8");
5672
6644
  if (/^\/?exe\/?$/m.test(content)) return;
5673
6645
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
5674
6646
  } else {
@@ -5699,58 +6671,42 @@ var init_tasks_crud = __esm({
5699
6671
  });
5700
6672
 
5701
6673
  // src/lib/tasks-review.ts
5702
- import path14 from "path";
5703
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
6674
+ import path16 from "path";
6675
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5704
6676
  async function countPendingReviews(sessionScope) {
5705
6677
  const client = getClient();
5706
- if (sessionScope) {
5707
- const result2 = await client.execute({
5708
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
5709
- args: [sessionScope]
5710
- });
5711
- return Number(result2.rows[0]?.cnt) || 0;
5712
- }
6678
+ const scope = strictSessionScopeFilter(
6679
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6680
+ );
5713
6681
  const result = await client.execute({
5714
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
5715
- args: []
6682
+ sql: `SELECT COUNT(*) as cnt FROM tasks
6683
+ WHERE status = 'needs_review'${scope.sql}`,
6684
+ args: [...scope.args]
5716
6685
  });
5717
6686
  return Number(result.rows[0]?.cnt) || 0;
5718
6687
  }
5719
6688
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
5720
6689
  const client = getClient();
5721
- if (sessionScope) {
5722
- const result2 = await client.execute({
5723
- sql: `SELECT COUNT(*) as cnt FROM tasks
5724
- WHERE status = 'needs_review' AND updated_at > ?
5725
- AND session_scope = ?`,
5726
- args: [sinceIso, sessionScope]
5727
- });
5728
- return Number(result2.rows[0]?.cnt) || 0;
5729
- }
6690
+ const scope = strictSessionScopeFilter(
6691
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6692
+ );
5730
6693
  const result = await client.execute({
5731
6694
  sql: `SELECT COUNT(*) as cnt FROM tasks
5732
- WHERE status = 'needs_review' AND updated_at > ?`,
5733
- args: [sinceIso]
6695
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
6696
+ args: [sinceIso, ...scope.args]
5734
6697
  });
5735
6698
  return Number(result.rows[0]?.cnt) || 0;
5736
6699
  }
5737
6700
  async function listPendingReviews(limit, sessionScope) {
5738
6701
  const client = getClient();
5739
- if (sessionScope) {
5740
- const result2 = await client.execute({
5741
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5742
- WHERE status = 'needs_review'
5743
- AND session_scope = ?
5744
- ORDER BY updated_at ASC LIMIT ?`,
5745
- args: [sessionScope, limit]
5746
- });
5747
- return result2.rows;
5748
- }
6702
+ const scope = strictSessionScopeFilter(
6703
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6704
+ );
5749
6705
  const result = await client.execute({
5750
6706
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5751
- WHERE status = 'needs_review'
6707
+ WHERE status = 'needs_review'${scope.sql}
5752
6708
  ORDER BY updated_at ASC LIMIT ?`,
5753
- args: [limit]
6709
+ args: [...scope.args, limit]
5754
6710
  });
5755
6711
  return result.rows;
5756
6712
  }
@@ -5762,7 +6718,7 @@ async function cleanupOrphanedReviews() {
5762
6718
  WHERE status IN ('open', 'needs_review', 'in_progress')
5763
6719
  AND assigned_by = 'system'
5764
6720
  AND title LIKE 'Review:%'
5765
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
6721
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
5766
6722
  args: [now]
5767
6723
  });
5768
6724
  const r1b = await client.execute({
@@ -5881,11 +6837,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5881
6837
  );
5882
6838
  }
5883
6839
  try {
5884
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
5885
- if (existsSync13(cacheDir)) {
6840
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
6841
+ if (existsSync15(cacheDir)) {
5886
6842
  for (const f of readdirSync3(cacheDir)) {
5887
6843
  if (f.startsWith("review-notified-")) {
5888
- unlinkSync4(path14.join(cacheDir, f));
6844
+ unlinkSync4(path16.join(cacheDir, f));
5889
6845
  }
5890
6846
  }
5891
6847
  }
@@ -5902,11 +6858,12 @@ var init_tasks_review = __esm({
5902
6858
  init_tmux_routing();
5903
6859
  init_session_key();
5904
6860
  init_state_bus();
6861
+ init_task_scope();
5905
6862
  }
5906
6863
  });
5907
6864
 
5908
6865
  // src/lib/tasks-chain.ts
5909
- import path15 from "path";
6866
+ import path17 from "path";
5910
6867
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5911
6868
  async function cascadeUnblock(taskId, baseDir, now) {
5912
6869
  const client = getClient();
@@ -5923,7 +6880,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5923
6880
  });
5924
6881
  for (const ur of unblockedRows.rows) {
5925
6882
  try {
5926
- const ubFile = path15.join(baseDir, String(ur.task_file));
6883
+ const ubFile = path17.join(baseDir, String(ur.task_file));
5927
6884
  let ubContent = await readFile4(ubFile, "utf-8");
5928
6885
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5929
6886
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -5958,7 +6915,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
5958
6915
  const scScope = sessionScopeFilter();
5959
6916
  const remaining = await client.execute({
5960
6917
  sql: `SELECT COUNT(*) as cnt FROM tasks
5961
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
6918
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
5962
6919
  args: [parentTaskId, ...scScope.args]
5963
6920
  });
5964
6921
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -5992,7 +6949,7 @@ var init_tasks_chain = __esm({
5992
6949
 
5993
6950
  // src/lib/project-name.ts
5994
6951
  import { execSync as execSync5 } from "child_process";
5995
- import path16 from "path";
6952
+ import path18 from "path";
5996
6953
  function getProjectName(cwd) {
5997
6954
  const dir = cwd ?? process.cwd();
5998
6955
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -6005,7 +6962,7 @@ function getProjectName(cwd) {
6005
6962
  timeout: 2e3,
6006
6963
  stdio: ["pipe", "pipe", "pipe"]
6007
6964
  }).trim();
6008
- repoRoot = path16.dirname(gitCommonDir);
6965
+ repoRoot = path18.dirname(gitCommonDir);
6009
6966
  } catch {
6010
6967
  repoRoot = execSync5("git rev-parse --show-toplevel", {
6011
6968
  cwd: dir,
@@ -6014,11 +6971,11 @@ function getProjectName(cwd) {
6014
6971
  stdio: ["pipe", "pipe", "pipe"]
6015
6972
  }).trim();
6016
6973
  }
6017
- _cached2 = path16.basename(repoRoot);
6974
+ _cached2 = path18.basename(repoRoot);
6018
6975
  _cachedCwd = dir;
6019
6976
  return _cached2;
6020
6977
  } catch {
6021
- _cached2 = path16.basename(dir);
6978
+ _cached2 = path18.basename(dir);
6022
6979
  _cachedCwd = dir;
6023
6980
  return _cached2;
6024
6981
  }
@@ -6161,10 +7118,10 @@ var init_tasks_notify = __esm({
6161
7118
  });
6162
7119
 
6163
7120
  // src/lib/behaviors.ts
6164
- import crypto6 from "crypto";
7121
+ import crypto7 from "crypto";
6165
7122
  async function storeBehavior(opts) {
6166
7123
  const client = getClient();
6167
- const id = crypto6.randomUUID();
7124
+ const id = crypto7.randomUUID();
6168
7125
  const now = (/* @__PURE__ */ new Date()).toISOString();
6169
7126
  await client.execute({
6170
7127
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -6193,7 +7150,7 @@ __export(skill_learning_exports, {
6193
7150
  storeTrajectory: () => storeTrajectory,
6194
7151
  sweepTrajectories: () => sweepTrajectories
6195
7152
  });
6196
- import crypto7 from "crypto";
7153
+ import crypto8 from "crypto";
6197
7154
  async function extractTrajectory(taskId, agentId) {
6198
7155
  const client = getClient();
6199
7156
  const result = await client.execute({
@@ -6222,11 +7179,11 @@ async function extractTrajectory(taskId, agentId) {
6222
7179
  return signature;
6223
7180
  }
6224
7181
  function hashSignature(signature) {
6225
- return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
7182
+ return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
6226
7183
  }
6227
7184
  async function storeTrajectory(opts) {
6228
7185
  const client = getClient();
6229
- const id = crypto7.randomUUID();
7186
+ const id = crypto8.randomUUID();
6230
7187
  const now = (/* @__PURE__ */ new Date()).toISOString();
6231
7188
  const signatureHash = hashSignature(opts.signature);
6232
7189
  await client.execute({
@@ -6491,8 +7448,8 @@ __export(tasks_exports, {
6491
7448
  updateTaskStatus: () => updateTaskStatus,
6492
7449
  writeCheckpoint: () => writeCheckpoint
6493
7450
  });
6494
- import path17 from "path";
6495
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
7451
+ import path19 from "path";
7452
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
6496
7453
  async function createTask(input) {
6497
7454
  const result = await createTaskCore(input);
6498
7455
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -6511,12 +7468,12 @@ async function updateTask(input) {
6511
7468
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
6512
7469
  try {
6513
7470
  const agent = String(row.assigned_to);
6514
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
6515
- const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
7471
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
7472
+ const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
6516
7473
  if (input.status === "in_progress") {
6517
7474
  mkdirSync7(cacheDir, { recursive: true });
6518
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6519
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
7475
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7476
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
6520
7477
  try {
6521
7478
  unlinkSync5(cachePath);
6522
7479
  } catch {
@@ -6524,10 +7481,10 @@ async function updateTask(input) {
6524
7481
  }
6525
7482
  } catch {
6526
7483
  }
6527
- if (input.status === "done") {
7484
+ if (input.status === "done" || input.status === "closed") {
6528
7485
  await cleanupReviewFile(row, taskFile, input.baseDir);
6529
7486
  }
6530
- if (input.status === "done" || input.status === "cancelled") {
7487
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
6531
7488
  try {
6532
7489
  const client = getClient();
6533
7490
  const taskTitle = String(row.title);
@@ -6543,7 +7500,7 @@ async function updateTask(input) {
6543
7500
  if (!isCoordinatorName(assignedAgent)) {
6544
7501
  try {
6545
7502
  const draftClient = getClient();
6546
- if (input.status === "done") {
7503
+ if (input.status === "done" || input.status === "closed") {
6547
7504
  await draftClient.execute({
6548
7505
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
6549
7506
  args: [assignedAgent]
@@ -6560,7 +7517,7 @@ async function updateTask(input) {
6560
7517
  try {
6561
7518
  const client = getClient();
6562
7519
  const cascaded = await client.execute({
6563
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
7520
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
6564
7521
  WHERE parent_task_id = ? AND status = 'needs_review'`,
6565
7522
  args: [now, taskId]
6566
7523
  });
@@ -6573,14 +7530,14 @@ async function updateTask(input) {
6573
7530
  } catch {
6574
7531
  }
6575
7532
  }
6576
- const isTerminal = input.status === "done" || input.status === "needs_review";
7533
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
6577
7534
  if (isTerminal) {
6578
7535
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
6579
7536
  if (!isCoordinator) {
6580
7537
  notifyTaskDone();
6581
7538
  }
6582
7539
  await markTaskNotificationsRead(taskFile);
6583
- if (input.status === "done") {
7540
+ if (input.status === "done" || input.status === "closed") {
6584
7541
  try {
6585
7542
  await cascadeUnblock(taskId, input.baseDir, now);
6586
7543
  } catch {
@@ -6600,7 +7557,7 @@ async function updateTask(input) {
6600
7557
  }
6601
7558
  }
6602
7559
  }
6603
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
7560
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6604
7561
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6605
7562
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
6606
7563
  taskId,
@@ -6972,6 +7929,7 @@ __export(tmux_routing_exports, {
6972
7929
  isEmployeeAlive: () => isEmployeeAlive,
6973
7930
  isExeSession: () => isExeSession,
6974
7931
  isSessionBusy: () => isSessionBusy,
7932
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
6975
7933
  notifyParentExe: () => notifyParentExe,
6976
7934
  parseParentExe: () => parseParentExe,
6977
7935
  registerParentExe: () => registerParentExe,
@@ -6982,13 +7940,13 @@ __export(tmux_routing_exports, {
6982
7940
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
6983
7941
  });
6984
7942
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
6985
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
6986
- import path18 from "path";
6987
- import os10 from "os";
7943
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
7944
+ import path20 from "path";
7945
+ import os12 from "os";
6988
7946
  import { fileURLToPath as fileURLToPath2 } from "url";
6989
7947
  import { unlinkSync as unlinkSync6 } from "fs";
6990
7948
  function spawnLockPath(sessionName) {
6991
- return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7949
+ return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6992
7950
  }
6993
7951
  function isProcessAlive(pid) {
6994
7952
  try {
@@ -6999,13 +7957,13 @@ function isProcessAlive(pid) {
6999
7957
  }
7000
7958
  }
7001
7959
  function acquireSpawnLock2(sessionName) {
7002
- if (!existsSync14(SPAWN_LOCK_DIR)) {
7960
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
7003
7961
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
7004
7962
  }
7005
7963
  const lockFile = spawnLockPath(sessionName);
7006
- if (existsSync14(lockFile)) {
7964
+ if (existsSync16(lockFile)) {
7007
7965
  try {
7008
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
7966
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
7009
7967
  const age = Date.now() - lock.timestamp;
7010
7968
  if (isProcessAlive(lock.pid) && age < 6e4) {
7011
7969
  return false;
@@ -7013,7 +7971,7 @@ function acquireSpawnLock2(sessionName) {
7013
7971
  } catch {
7014
7972
  }
7015
7973
  }
7016
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7974
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7017
7975
  return true;
7018
7976
  }
7019
7977
  function releaseSpawnLock2(sessionName) {
@@ -7025,13 +7983,13 @@ function releaseSpawnLock2(sessionName) {
7025
7983
  function resolveBehaviorsExporterScript() {
7026
7984
  try {
7027
7985
  const thisFile = fileURLToPath2(import.meta.url);
7028
- const scriptPath = path18.join(
7029
- path18.dirname(thisFile),
7986
+ const scriptPath = path20.join(
7987
+ path20.dirname(thisFile),
7030
7988
  "..",
7031
7989
  "bin",
7032
7990
  "exe-export-behaviors.js"
7033
7991
  );
7034
- return existsSync14(scriptPath) ? scriptPath : null;
7992
+ return existsSync16(scriptPath) ? scriptPath : null;
7035
7993
  } catch {
7036
7994
  return null;
7037
7995
  }
@@ -7097,12 +8055,12 @@ function extractRootExe(name) {
7097
8055
  return parts.length > 0 ? parts[parts.length - 1] : null;
7098
8056
  }
7099
8057
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7100
- if (!existsSync14(SESSION_CACHE)) {
8058
+ if (!existsSync16(SESSION_CACHE)) {
7101
8059
  mkdirSync8(SESSION_CACHE, { recursive: true });
7102
8060
  }
7103
8061
  const rootExe = extractRootExe(parentExe) ?? parentExe;
7104
- const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7105
- writeFileSync7(filePath, JSON.stringify({
8062
+ const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
8063
+ writeFileSync8(filePath, JSON.stringify({
7106
8064
  parentExe: rootExe,
7107
8065
  dispatchedBy: dispatchedBy || rootExe,
7108
8066
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7110,7 +8068,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7110
8068
  }
7111
8069
  function getParentExe(sessionKey) {
7112
8070
  try {
7113
- const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
8071
+ const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
7114
8072
  return data.parentExe || null;
7115
8073
  } catch {
7116
8074
  return null;
@@ -7118,8 +8076,8 @@ function getParentExe(sessionKey) {
7118
8076
  }
7119
8077
  function getDispatchedBy(sessionKey) {
7120
8078
  try {
7121
- const data = JSON.parse(readFileSync12(
7122
- path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
8079
+ const data = JSON.parse(readFileSync13(
8080
+ path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
7123
8081
  "utf8"
7124
8082
  ));
7125
8083
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -7189,8 +8147,8 @@ async function verifyPaneAtCapacity(sessionName) {
7189
8147
  }
7190
8148
  function readDebounceState() {
7191
8149
  try {
7192
- if (!existsSync14(DEBOUNCE_FILE)) return {};
7193
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
8150
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
8151
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
7194
8152
  const state = {};
7195
8153
  for (const [key, val] of Object.entries(raw)) {
7196
8154
  if (typeof val === "number") {
@@ -7206,8 +8164,8 @@ function readDebounceState() {
7206
8164
  }
7207
8165
  function writeDebounceState(state) {
7208
8166
  try {
7209
- if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
7210
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
8167
+ if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
8168
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
7211
8169
  } catch {
7212
8170
  }
7213
8171
  }
@@ -7305,8 +8263,8 @@ function sendIntercom(targetSession) {
7305
8263
  try {
7306
8264
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
7307
8265
  const agent = baseAgentName(rawAgent);
7308
- const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
7309
- if (existsSync14(markerPath)) {
8266
+ const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
8267
+ if (existsSync16(markerPath)) {
7310
8268
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
7311
8269
  return "debounced";
7312
8270
  }
@@ -7315,8 +8273,8 @@ function sendIntercom(targetSession) {
7315
8273
  try {
7316
8274
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
7317
8275
  const agent = baseAgentName(rawAgent);
7318
- const taskDir = path18.join(process.cwd(), "exe", agent);
7319
- if (existsSync14(taskDir)) {
8276
+ const taskDir = path20.join(process.cwd(), "exe", agent);
8277
+ if (existsSync16(taskDir)) {
7320
8278
  const files = readdirSync4(taskDir).filter(
7321
8279
  (f) => f.endsWith(".md") && f !== "DONE.txt"
7322
8280
  );
@@ -7376,6 +8334,21 @@ function notifyParentExe(sessionKey) {
7376
8334
  }
7377
8335
  return true;
7378
8336
  }
8337
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
8338
+ const transport = getTransport();
8339
+ try {
8340
+ const sessions = transport.listSessions();
8341
+ if (!sessions.includes(coordinatorSession)) return false;
8342
+ execSync6(
8343
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8344
+ { timeout: 3e3 }
8345
+ );
8346
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
8347
+ return true;
8348
+ } catch {
8349
+ return false;
8350
+ }
8351
+ }
7379
8352
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
7380
8353
  if (isCoordinatorName(employeeName)) {
7381
8354
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -7449,26 +8422,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7449
8422
  const transport = getTransport();
7450
8423
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
7451
8424
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
7452
- const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
7453
- const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
7454
- if (!existsSync14(logDir)) {
8425
+ const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
8426
+ const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8427
+ if (!existsSync16(logDir)) {
7455
8428
  mkdirSync8(logDir, { recursive: true });
7456
8429
  }
7457
8430
  transport.kill(sessionName);
7458
8431
  let cleanupSuffix = "";
7459
8432
  try {
7460
8433
  const thisFile = fileURLToPath2(import.meta.url);
7461
- const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7462
- if (existsSync14(cleanupScript)) {
8434
+ const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8435
+ if (existsSync16(cleanupScript)) {
7463
8436
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
7464
8437
  }
7465
8438
  } catch {
7466
8439
  }
7467
8440
  try {
7468
- const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
8441
+ const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
7469
8442
  let claudeJson = {};
7470
8443
  try {
7471
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
8444
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
7472
8445
  } catch {
7473
8446
  }
7474
8447
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -7476,17 +8449,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7476
8449
  const trustDir = opts?.cwd ?? projectDir;
7477
8450
  if (!projects[trustDir]) projects[trustDir] = {};
7478
8451
  projects[trustDir].hasTrustDialogAccepted = true;
7479
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8452
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
7480
8453
  } catch {
7481
8454
  }
7482
8455
  try {
7483
- const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
8456
+ const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
7484
8457
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
7485
- const projSettingsDir = path18.join(settingsDir, normalizedKey);
7486
- const settingsPath = path18.join(projSettingsDir, "settings.json");
8458
+ const projSettingsDir = path20.join(settingsDir, normalizedKey);
8459
+ const settingsPath = path20.join(projSettingsDir, "settings.json");
7487
8460
  let settings = {};
7488
8461
  try {
7489
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
8462
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
7490
8463
  } catch {
7491
8464
  }
7492
8465
  const perms = settings.permissions ?? {};
@@ -7515,7 +8488,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7515
8488
  perms.allow = allow;
7516
8489
  settings.permissions = perms;
7517
8490
  mkdirSync8(projSettingsDir, { recursive: true });
7518
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8491
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
7519
8492
  }
7520
8493
  } catch {
7521
8494
  }
@@ -7530,8 +8503,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7530
8503
  let behaviorsFlag = "";
7531
8504
  let legacyFallbackWarned = false;
7532
8505
  if (!useExeAgent && !useBinSymlink) {
7533
- const identityPath = path18.join(
7534
- os10.homedir(),
8506
+ const identityPath = path20.join(
8507
+ os12.homedir(),
7535
8508
  ".exe-os",
7536
8509
  "identity",
7537
8510
  `${employeeName}.md`
@@ -7540,13 +8513,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7540
8513
  const hasAgentFlag = claudeSupportsAgentFlag();
7541
8514
  if (hasAgentFlag) {
7542
8515
  identityFlag = ` --agent ${employeeName}`;
7543
- } else if (existsSync14(identityPath)) {
8516
+ } else if (existsSync16(identityPath)) {
7544
8517
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
7545
8518
  legacyFallbackWarned = true;
7546
8519
  }
7547
8520
  const behaviorsFile = exportBehaviorsSync(
7548
8521
  employeeName,
7549
- path18.basename(spawnCwd),
8522
+ path20.basename(spawnCwd),
7550
8523
  sessionName
7551
8524
  );
7552
8525
  if (behaviorsFile) {
@@ -7561,16 +8534,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7561
8534
  }
7562
8535
  let sessionContextFlag = "";
7563
8536
  try {
7564
- const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
8537
+ const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
7565
8538
  mkdirSync8(ctxDir, { recursive: true });
7566
- const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
8539
+ const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
7567
8540
  const ctxContent = [
7568
8541
  `## Session Context`,
7569
8542
  `You are running in tmux session: ${sessionName}.`,
7570
8543
  `Your parent coordinator session is ${exeSession}.`,
7571
8544
  `Your employees (if any) use the -${exeSession} suffix.`
7572
8545
  ].join("\n");
7573
- writeFileSync7(ctxFile, ctxContent);
8546
+ writeFileSync8(ctxFile, ctxContent);
7574
8547
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
7575
8548
  } catch {
7576
8549
  }
@@ -7647,8 +8620,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7647
8620
  transport.pipeLog(sessionName, logFile);
7648
8621
  try {
7649
8622
  const mySession = getMySession();
7650
- const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7651
- writeFileSync7(dispatchInfo, JSON.stringify({
8623
+ const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8624
+ writeFileSync8(dispatchInfo, JSON.stringify({
7652
8625
  dispatchedBy: mySession,
7653
8626
  rootExe: exeSession,
7654
8627
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -7722,15 +8695,15 @@ var init_tmux_routing = __esm({
7722
8695
  init_intercom_queue();
7723
8696
  init_plan_limits();
7724
8697
  init_employees();
7725
- SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
7726
- SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
8698
+ SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
8699
+ SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
7727
8700
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7728
8701
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
7729
8702
  VERIFY_PANE_LINES = 200;
7730
8703
  INTERCOM_DEBOUNCE_MS = 3e4;
7731
8704
  CODEX_DEBOUNCE_MS = 12e4;
7732
- INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
7733
- DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
8705
+ INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
8706
+ DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
7734
8707
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
7735
8708
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
7736
8709
  }
@@ -7753,10 +8726,10 @@ __export(messaging_exports, {
7753
8726
  sendMessage: () => sendMessage,
7754
8727
  setWsClientSend: () => setWsClientSend
7755
8728
  });
7756
- import crypto8 from "crypto";
8729
+ import crypto9 from "crypto";
7757
8730
  function generateUlid() {
7758
8731
  const timestamp = Date.now().toString(36).padStart(10, "0");
7759
- const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
8732
+ const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
7760
8733
  return (timestamp + random).toUpperCase();
7761
8734
  }
7762
8735
  function rowToMessage(row) {
@@ -7767,6 +8740,7 @@ function rowToMessage(row) {
7767
8740
  targetAgent: row.target_agent,
7768
8741
  targetProject: row.target_project ?? null,
7769
8742
  targetDevice: row.target_device,
8743
+ sessionScope: row.session_scope ?? null,
7770
8744
  content: row.content,
7771
8745
  priority: row.priority ?? "normal",
7772
8746
  status: row.status ?? "pending",
@@ -7784,15 +8758,17 @@ async function sendMessage(input) {
7784
8758
  const id = generateUlid();
7785
8759
  const now = (/* @__PURE__ */ new Date()).toISOString();
7786
8760
  const targetDevice = input.targetDevice ?? "local";
8761
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
7787
8762
  await client.execute({
7788
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
7789
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
8763
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
8764
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
7790
8765
  args: [
7791
8766
  id,
7792
8767
  input.fromAgent,
7793
8768
  input.targetAgent,
7794
8769
  input.targetProject ?? null,
7795
8770
  targetDevice,
8771
+ sessionScope,
7796
8772
  input.content,
7797
8773
  input.priority ?? "normal",
7798
8774
  now
@@ -7806,9 +8782,10 @@ async function sendMessage(input) {
7806
8782
  }
7807
8783
  } catch {
7808
8784
  }
8785
+ const sentScope = strictSessionScopeFilter(sessionScope);
7809
8786
  const result = await client.execute({
7810
- sql: "SELECT * FROM messages WHERE id = ?",
7811
- args: [id]
8787
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
8788
+ args: [id, ...sentScope.args]
7812
8789
  });
7813
8790
  return rowToMessage(result.rows[0]);
7814
8791
  }
@@ -7832,6 +8809,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
7832
8809
  fromAgent: msg.fromAgent,
7833
8810
  targetAgent: msg.targetAgent,
7834
8811
  targetProject: msg.targetProject,
8812
+ sessionScope: msg.sessionScope,
7835
8813
  content: msg.content,
7836
8814
  priority: msg.priority,
7837
8815
  createdAt: msg.createdAt
@@ -7875,7 +8853,7 @@ async function deliverLocalMessage(messageId) {
7875
8853
  } catch {
7876
8854
  const newRetryCount = msg.retryCount + 1;
7877
8855
  if (newRetryCount >= MAX_RETRIES3) {
7878
- await markFailed(messageId, "session unavailable after 10 retries");
8856
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
7879
8857
  } else {
7880
8858
  await client.execute({
7881
8859
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -7885,85 +8863,101 @@ async function deliverLocalMessage(messageId) {
7885
8863
  return false;
7886
8864
  }
7887
8865
  }
7888
- async function getPendingMessages(targetAgent) {
8866
+ async function getPendingMessages(targetAgent, sessionScope) {
7889
8867
  const client = getClient();
8868
+ const scope = strictSessionScopeFilter(sessionScope);
7890
8869
  const result = await client.execute({
7891
8870
  sql: `SELECT * FROM messages
7892
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
8871
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
7893
8872
  ORDER BY id`,
7894
- args: [targetAgent]
8873
+ args: [targetAgent, ...scope.args]
7895
8874
  });
7896
8875
  return result.rows.map((row) => rowToMessage(row));
7897
8876
  }
7898
- async function markRead(messageId) {
8877
+ async function markRead(messageId, sessionScope) {
7899
8878
  const client = getClient();
8879
+ const scope = strictSessionScopeFilter(sessionScope);
7900
8880
  await client.execute({
7901
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
7902
- args: [messageId]
8881
+ sql: `UPDATE messages SET status = 'read'
8882
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
8883
+ args: [messageId, ...scope.args]
7903
8884
  });
7904
8885
  }
7905
- async function markAcknowledged(messageId) {
8886
+ async function markAcknowledged(messageId, sessionScope) {
7906
8887
  const client = getClient();
8888
+ const scope = strictSessionScopeFilter(sessionScope);
7907
8889
  await client.execute({
7908
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
7909
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8890
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
8891
+ WHERE id = ? AND status = 'read'${scope.sql}`,
8892
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
7910
8893
  });
7911
8894
  }
7912
- async function markProcessed(messageId) {
8895
+ async function markProcessed(messageId, sessionScope) {
7913
8896
  const client = getClient();
8897
+ const scope = strictSessionScopeFilter(sessionScope);
7914
8898
  await client.execute({
7915
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
7916
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8899
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
8900
+ WHERE id = ?${scope.sql}`,
8901
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
7917
8902
  });
7918
8903
  }
7919
- async function getMessageStatus(messageId) {
8904
+ async function getMessageStatus(messageId, sessionScope) {
7920
8905
  const client = getClient();
8906
+ const scope = strictSessionScopeFilter(sessionScope);
7921
8907
  const result = await client.execute({
7922
- sql: "SELECT status FROM messages WHERE id = ?",
7923
- args: [messageId]
8908
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
8909
+ args: [messageId, ...scope.args]
7924
8910
  });
7925
8911
  return result.rows[0]?.status ?? null;
7926
8912
  }
7927
- async function getUnacknowledgedMessages(targetAgent) {
8913
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
7928
8914
  const client = getClient();
8915
+ const scope = strictSessionScopeFilter(sessionScope);
7929
8916
  const result = await client.execute({
7930
8917
  sql: `SELECT * FROM messages
7931
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
8918
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
7932
8919
  ORDER BY id`,
7933
- args: [targetAgent]
8920
+ args: [targetAgent, ...scope.args]
7934
8921
  });
7935
8922
  return result.rows.map((row) => rowToMessage(row));
7936
8923
  }
7937
- async function getReadMessages(targetAgent) {
8924
+ async function getReadMessages(targetAgent, sessionScope) {
7938
8925
  const client = getClient();
8926
+ const scope = strictSessionScopeFilter(sessionScope);
7939
8927
  const result = await client.execute({
7940
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
7941
- args: [targetAgent]
8928
+ sql: `SELECT * FROM messages
8929
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
8930
+ ORDER BY id`,
8931
+ args: [targetAgent, ...scope.args]
7942
8932
  });
7943
8933
  return result.rows.map((row) => rowToMessage(row));
7944
8934
  }
7945
- async function markFailed(messageId, reason) {
8935
+ async function markFailed(messageId, reason, sessionScope) {
7946
8936
  const client = getClient();
8937
+ const scope = strictSessionScopeFilter(sessionScope);
7947
8938
  await client.execute({
7948
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
7949
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
8939
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
8940
+ WHERE id = ?${scope.sql}`,
8941
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
7950
8942
  });
7951
8943
  }
7952
- async function getFailedMessages() {
8944
+ async function getFailedMessages(sessionScope) {
7953
8945
  const client = getClient();
8946
+ const scope = strictSessionScopeFilter(sessionScope);
7954
8947
  const result = await client.execute({
7955
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
7956
- args: []
8948
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
8949
+ args: [...scope.args]
7957
8950
  });
7958
8951
  return result.rows.map((row) => rowToMessage(row));
7959
8952
  }
7960
- async function retryPendingMessages() {
8953
+ async function retryPendingMessages(sessionScope) {
7961
8954
  const client = getClient();
8955
+ const scope = strictSessionScopeFilter(sessionScope);
7962
8956
  const result = await client.execute({
7963
8957
  sql: `SELECT * FROM messages
7964
- WHERE status = 'pending' AND retry_count < ?
8958
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
7965
8959
  ORDER BY id`,
7966
- args: [MAX_RETRIES3]
8960
+ args: [MAX_RETRIES3, ...scope.args]
7967
8961
  });
7968
8962
  let delivered = 0;
7969
8963
  for (const row of result.rows) {
@@ -7982,6 +8976,7 @@ var init_messaging = __esm({
7982
8976
  "use strict";
7983
8977
  init_database();
7984
8978
  init_tmux_routing();
8979
+ init_task_scope();
7985
8980
  MAX_RETRIES3 = 10;
7986
8981
  _wsClientSend = null;
7987
8982
  }
@@ -8179,11 +9174,11 @@ init_crm_bridge();
8179
9174
 
8180
9175
  // src/lib/pipeline-router.ts
8181
9176
  init_database();
8182
- import crypto2 from "crypto";
9177
+ import crypto3 from "crypto";
8183
9178
  async function sinkConversationStore(msg, agentResponse, agentName) {
8184
9179
  try {
8185
9180
  const client = getClient();
8186
- const id = crypto2.randomUUID();
9181
+ const id = crypto3.randomUUID();
8187
9182
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
8188
9183
  await client.execute({
8189
9184
  sql: `INSERT INTO conversations
@@ -8233,7 +9228,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
8233
9228
  ].filter(Boolean).join("\n");
8234
9229
  const vector = await embed2(rawText);
8235
9230
  await writeMemory2({
8236
- id: crypto2.randomUUID(),
9231
+ id: crypto3.randomUUID(),
8237
9232
  agent_id: agentName ?? "gateway",
8238
9233
  agent_role: "gateway",
8239
9234
  session_id: `gateway-${msg.platform}`,
@@ -9586,7 +10581,7 @@ var OllamaProvider = class {
9586
10581
  import { randomUUID as randomUUID5 } from "crypto";
9587
10582
  import { homedir } from "os";
9588
10583
  import { join } from "path";
9589
- import { mkdirSync as mkdirSync2 } from "fs";
10584
+ import { mkdirSync as mkdirSync3 } from "fs";
9590
10585
  var INITIAL_BACKOFF_MS = 1e3;
9591
10586
  var MAX_BACKOFF_MS = 3e5;
9592
10587
  var BACKOFF_MULTIPLIER = 2;
@@ -9612,7 +10607,7 @@ var WhatsAppAdapter = class {
9612
10607
  disconnectedAt = 0;
9613
10608
  async connect(config2) {
9614
10609
  this.authDir = config2.credentials.authDir ?? AUTH_DIR;
9615
- mkdirSync2(this.authDir, { recursive: true });
10610
+ mkdirSync3(this.authDir, { recursive: true });
9616
10611
  const baileys = await import("@whiskeysockets/baileys");
9617
10612
  const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = baileys;
9618
10613
  const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
@@ -10914,12 +11909,12 @@ var SlackAdapter = class {
10914
11909
  // src/gateway/adapters/imessage.ts
10915
11910
  import { execFile } from "child_process";
10916
11911
  import { promisify } from "util";
10917
- import os5 from "os";
10918
- import path6 from "path";
11912
+ import os6 from "os";
11913
+ import path9 from "path";
10919
11914
  var execFileAsync = promisify(execFile);
10920
11915
  var POLL_INTERVAL_MS = 5e3;
10921
- var MESSAGES_DB_PATH = path6.join(
10922
- process.env.HOME ?? os5.homedir(),
11916
+ var MESSAGES_DB_PATH = path9.join(
11917
+ process.env.HOME ?? os6.homedir(),
10923
11918
  "Library/Messages/chat.db"
10924
11919
  );
10925
11920
  var IMessageAdapter = class {
@@ -11762,11 +12757,11 @@ async function ensureCRMContact(info) {
11762
12757
  }
11763
12758
 
11764
12759
  // src/automation/trigger-engine.ts
11765
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
12760
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
11766
12761
  import { randomUUID as randomUUID12 } from "crypto";
11767
- import path19 from "path";
11768
- import os11 from "os";
11769
- var TRIGGERS_PATH = path19.join(os11.homedir(), ".exe-os", "triggers.json");
12762
+ import path21 from "path";
12763
+ import os13 from "os";
12764
+ var TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
11770
12765
  var GRAPH_API_VERSION = "v21.0";
11771
12766
  function substituteTemplate(template, record) {
11772
12767
  return template.replace(
@@ -11820,9 +12815,9 @@ function evaluateConditions(conditions, record) {
11820
12815
  return conditions.every((c) => evaluateCondition(c, record));
11821
12816
  }
11822
12817
  function loadTriggers(project) {
11823
- if (!existsSync15(TRIGGERS_PATH)) return [];
12818
+ if (!existsSync17(TRIGGERS_PATH)) return [];
11824
12819
  try {
11825
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
12820
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
11826
12821
  const all = JSON.parse(raw);
11827
12822
  if (!Array.isArray(all)) return [];
11828
12823
  if (project) {