@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
@@ -25,6 +25,44 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/secure-files.ts
29
+ import { chmodSync, existsSync, mkdirSync } from "fs";
30
+ import { chmod, mkdir } from "fs/promises";
31
+ async function ensurePrivateDir(dirPath) {
32
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
33
+ try {
34
+ await chmod(dirPath, PRIVATE_DIR_MODE);
35
+ } catch {
36
+ }
37
+ }
38
+ function ensurePrivateDirSync(dirPath) {
39
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
40
+ try {
41
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ async function enforcePrivateFile(filePath) {
46
+ try {
47
+ await chmod(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ function enforcePrivateFileSync(filePath) {
52
+ try {
53
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
54
+ } catch {
55
+ }
56
+ }
57
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
58
+ var init_secure_files = __esm({
59
+ "src/lib/secure-files.ts"() {
60
+ "use strict";
61
+ PRIVATE_DIR_MODE = 448;
62
+ PRIVATE_FILE_MODE = 384;
63
+ }
64
+ });
65
+
28
66
  // src/lib/config.ts
29
67
  var config_exports = {};
30
68
  __export(config_exports, {
@@ -41,8 +79,8 @@ __export(config_exports, {
41
79
  migrateConfig: () => migrateConfig,
42
80
  saveConfig: () => saveConfig
43
81
  });
44
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
45
- import { readFileSync, existsSync, renameSync } from "fs";
82
+ import { readFile, writeFile } from "fs/promises";
83
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
46
84
  import path from "path";
47
85
  import os from "os";
48
86
  function resolveDataDir() {
@@ -50,7 +88,7 @@ function resolveDataDir() {
50
88
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
51
89
  const newDir = path.join(os.homedir(), ".exe-os");
52
90
  const legacyDir = path.join(os.homedir(), ".exe-mem");
53
- if (!existsSync(newDir) && existsSync(legacyDir)) {
91
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
54
92
  try {
55
93
  renameSync(legacyDir, newDir);
56
94
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -113,9 +151,9 @@ function normalizeAutoUpdate(raw) {
113
151
  }
114
152
  async function loadConfig() {
115
153
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
116
- await mkdir(dir, { recursive: true });
154
+ await ensurePrivateDir(dir);
117
155
  const configPath = path.join(dir, "config.json");
118
- if (!existsSync(configPath)) {
156
+ if (!existsSync2(configPath)) {
119
157
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
120
158
  }
121
159
  const raw = await readFile(configPath, "utf-8");
@@ -128,6 +166,7 @@ async function loadConfig() {
128
166
  `);
129
167
  try {
130
168
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
169
+ await enforcePrivateFile(configPath);
131
170
  } catch {
132
171
  }
133
172
  }
@@ -146,7 +185,7 @@ async function loadConfig() {
146
185
  function loadConfigSync() {
147
186
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
148
187
  const configPath = path.join(dir, "config.json");
149
- if (!existsSync(configPath)) {
188
+ if (!existsSync2(configPath)) {
150
189
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
151
190
  }
152
191
  try {
@@ -164,12 +203,10 @@ function loadConfigSync() {
164
203
  }
165
204
  async function saveConfig(config) {
166
205
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
167
- await mkdir(dir, { recursive: true });
206
+ await ensurePrivateDir(dir);
168
207
  const configPath = path.join(dir, "config.json");
169
208
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
170
- if (config.cloud?.apiKey) {
171
- await chmod(configPath, 384);
172
- }
209
+ await enforcePrivateFile(configPath);
173
210
  }
174
211
  async function loadConfigFrom(configPath) {
175
212
  const raw = await readFile(configPath, "utf-8");
@@ -189,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
189
226
  var init_config = __esm({
190
227
  "src/lib/config.ts"() {
191
228
  "use strict";
229
+ init_secure_files();
192
230
  EXE_AI_DIR = resolveDataDir();
193
231
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
194
232
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -361,6 +399,43 @@ var init_daemon_protocol = __esm({
361
399
  }
362
400
  });
363
401
 
402
+ // src/lib/daemon-auth.ts
403
+ import crypto from "crypto";
404
+ import path2 from "path";
405
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
406
+ function normalizeToken(token) {
407
+ if (!token) return null;
408
+ const trimmed = token.trim();
409
+ return trimmed.length > 0 ? trimmed : null;
410
+ }
411
+ function readDaemonToken() {
412
+ try {
413
+ if (!existsSync3(DAEMON_TOKEN_PATH)) return null;
414
+ return normalizeToken(readFileSync2(DAEMON_TOKEN_PATH, "utf8"));
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ function ensureDaemonToken(seed) {
420
+ const existing = readDaemonToken();
421
+ if (existing) return existing;
422
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
423
+ ensurePrivateDirSync(EXE_AI_DIR);
424
+ writeFileSync(DAEMON_TOKEN_PATH, `${token}
425
+ `, "utf8");
426
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
427
+ return token;
428
+ }
429
+ var DAEMON_TOKEN_PATH;
430
+ var init_daemon_auth = __esm({
431
+ "src/lib/daemon-auth.ts"() {
432
+ "use strict";
433
+ init_config();
434
+ init_secure_files();
435
+ DAEMON_TOKEN_PATH = path2.join(EXE_AI_DIR, "exed.token");
436
+ }
437
+ });
438
+
364
439
  // src/lib/session-registry.ts
365
440
  var session_registry_exports = {};
366
441
  __export(session_registry_exports, {
@@ -368,14 +443,14 @@ __export(session_registry_exports, {
368
443
  pruneStaleSessions: () => pruneStaleSessions,
369
444
  registerSession: () => registerSession
370
445
  });
371
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
446
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
372
447
  import { execSync } from "child_process";
373
- import path2 from "path";
448
+ import path3 from "path";
374
449
  import os2 from "os";
375
450
  function registerSession(entry) {
376
- const dir = path2.dirname(REGISTRY_PATH);
377
- if (!existsSync2(dir)) {
378
- mkdirSync(dir, { recursive: true });
451
+ const dir = path3.dirname(REGISTRY_PATH);
452
+ if (!existsSync4(dir)) {
453
+ mkdirSync2(dir, { recursive: true });
379
454
  }
380
455
  const sessions = listSessions();
381
456
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -384,11 +459,11 @@ function registerSession(entry) {
384
459
  } else {
385
460
  sessions.push(entry);
386
461
  }
387
- writeFileSync(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
462
+ writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
388
463
  }
389
464
  function listSessions() {
390
465
  try {
391
- const raw = readFileSync2(REGISTRY_PATH, "utf8");
466
+ const raw = readFileSync3(REGISTRY_PATH, "utf8");
392
467
  return JSON.parse(raw);
393
468
  } catch {
394
469
  return [];
@@ -409,7 +484,7 @@ function pruneStaleSessions() {
409
484
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
410
485
  const pruned = sessions.length - alive.length;
411
486
  if (pruned > 0) {
412
- writeFileSync(REGISTRY_PATH, JSON.stringify(alive, null, 2));
487
+ writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
413
488
  }
414
489
  return pruned;
415
490
  }
@@ -417,7 +492,7 @@ var REGISTRY_PATH;
417
492
  var init_session_registry = __esm({
418
493
  "src/lib/session-registry.ts"() {
419
494
  "use strict";
420
- REGISTRY_PATH = path2.join(os2.homedir(), ".exe-os", "session-registry.json");
495
+ REGISTRY_PATH = path3.join(os2.homedir(), ".exe-os", "session-registry.json");
421
496
  }
422
497
  });
423
498
 
@@ -717,20 +792,21 @@ __export(agent_config_exports, {
717
792
  saveAgentConfig: () => saveAgentConfig,
718
793
  setAgentRuntime: () => setAgentRuntime
719
794
  });
720
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
721
- import path3 from "path";
795
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
796
+ import path4 from "path";
722
797
  function loadAgentConfig() {
723
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
798
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
724
799
  try {
725
- return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
800
+ return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
726
801
  } catch {
727
802
  return {};
728
803
  }
729
804
  }
730
805
  function saveAgentConfig(config) {
731
- const dir = path3.dirname(AGENT_CONFIG_PATH);
732
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
733
- writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
806
+ const dir = path4.dirname(AGENT_CONFIG_PATH);
807
+ ensurePrivateDirSync(dir);
808
+ writeFileSync3(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
809
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
734
810
  }
735
811
  function getAgentRuntime(agentId) {
736
812
  const config = loadAgentConfig();
@@ -770,7 +846,8 @@ var init_agent_config = __esm({
770
846
  "use strict";
771
847
  init_config();
772
848
  init_runtime_table();
773
- AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
849
+ init_secure_files();
850
+ AGENT_CONFIG_PATH = path4.join(EXE_AI_DIR, "agent-config.json");
774
851
  KNOWN_RUNTIMES = {
775
852
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
776
853
  codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
@@ -798,17 +875,17 @@ __export(intercom_queue_exports, {
798
875
  queueIntercom: () => queueIntercom,
799
876
  readQueue: () => readQueue
800
877
  });
801
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
802
- import path4 from "path";
878
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
879
+ import path5 from "path";
803
880
  import os3 from "os";
804
881
  function ensureDir() {
805
- const dir = path4.dirname(QUEUE_PATH);
806
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
882
+ const dir = path5.dirname(QUEUE_PATH);
883
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
807
884
  }
808
885
  function readQueue() {
809
886
  try {
810
- if (!existsSync4(QUEUE_PATH)) return [];
811
- return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
887
+ if (!existsSync6(QUEUE_PATH)) return [];
888
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
812
889
  } catch {
813
890
  return [];
814
891
  }
@@ -816,7 +893,7 @@ function readQueue() {
816
893
  function writeQueue(queue) {
817
894
  ensureDir();
818
895
  const tmp = `${QUEUE_PATH}.tmp`;
819
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
896
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
820
897
  renameSync2(tmp, QUEUE_PATH);
821
898
  }
822
899
  function queueIntercom(targetSession, reason) {
@@ -908,10 +985,10 @@ var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
908
985
  var init_intercom_queue = __esm({
909
986
  "src/lib/intercom-queue.ts"() {
910
987
  "use strict";
911
- QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
988
+ QUEUE_PATH = path5.join(os3.homedir(), ".exe-os", "intercom-queue.json");
912
989
  MAX_RETRIES = 5;
913
990
  TTL_MS = 60 * 60 * 1e3;
914
- INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
991
+ INTERCOM_LOG = path5.join(os3.homedir(), ".exe-os", "intercom.log");
915
992
  }
916
993
  });
917
994
 
@@ -985,6 +1062,7 @@ __export(employees_exports, {
985
1062
  getEmployeeByRole: () => getEmployeeByRole,
986
1063
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
987
1064
  hasRole: () => hasRole,
1065
+ hireEmployee: () => hireEmployee,
988
1066
  isCoordinatorName: () => isCoordinatorName,
989
1067
  isCoordinatorRole: () => isCoordinatorRole,
990
1068
  isMultiInstance: () => isMultiInstance,
@@ -997,9 +1075,9 @@ __export(employees_exports, {
997
1075
  validateEmployeeName: () => validateEmployeeName
998
1076
  });
999
1077
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1000
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
1078
+ import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
1001
1079
  import { execSync as execSync4 } from "child_process";
1002
- import path5 from "path";
1080
+ import path6 from "path";
1003
1081
  import os4 from "os";
1004
1082
  function normalizeRole(role) {
1005
1083
  return (role ?? "").trim().toLowerCase();
@@ -1036,7 +1114,7 @@ function validateEmployeeName(name) {
1036
1114
  return { valid: true };
1037
1115
  }
1038
1116
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1039
- if (!existsSync5(employeesPath)) {
1117
+ if (!existsSync7(employeesPath)) {
1040
1118
  return [];
1041
1119
  }
1042
1120
  const raw = await readFile2(employeesPath, "utf-8");
@@ -1047,13 +1125,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1047
1125
  }
1048
1126
  }
1049
1127
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1050
- await mkdir2(path5.dirname(employeesPath), { recursive: true });
1128
+ await mkdir2(path6.dirname(employeesPath), { recursive: true });
1051
1129
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1052
1130
  }
1053
1131
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1054
- if (!existsSync5(employeesPath)) return [];
1132
+ if (!existsSync7(employeesPath)) return [];
1055
1133
  try {
1056
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
1134
+ return JSON.parse(readFileSync6(employeesPath, "utf-8"));
1057
1135
  } catch {
1058
1136
  return [];
1059
1137
  }
@@ -1095,6 +1173,52 @@ function addEmployee(employees, employee) {
1095
1173
  }
1096
1174
  return [...employees, normalized];
1097
1175
  }
1176
+ function appendToCoordinatorTeam(employee) {
1177
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1178
+ if (!coordinator) return;
1179
+ const idPath = path6.join(IDENTITY_DIR, `${coordinator.name}.md`);
1180
+ if (!existsSync7(idPath)) return;
1181
+ const content = readFileSync6(idPath, "utf-8");
1182
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
1183
+ const teamMatch = content.match(TEAM_SECTION_RE);
1184
+ if (!teamMatch || teamMatch.index === void 0) return;
1185
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
1186
+ const nextHeading = afterTeam.match(/\n## /);
1187
+ const entry = `
1188
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
1189
+ `;
1190
+ let updated;
1191
+ if (nextHeading && nextHeading.index !== void 0) {
1192
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
1193
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
1194
+ } else {
1195
+ updated = content.trimEnd() + "\n" + entry;
1196
+ }
1197
+ writeFileSync5(idPath, updated, "utf-8");
1198
+ }
1199
+ function capitalize(s) {
1200
+ return s.charAt(0).toUpperCase() + s.slice(1);
1201
+ }
1202
+ async function hireEmployee(employee) {
1203
+ const employees = await loadEmployees();
1204
+ const updated = addEmployee(employees, employee);
1205
+ await saveEmployees(updated);
1206
+ try {
1207
+ appendToCoordinatorTeam(employee);
1208
+ } catch {
1209
+ }
1210
+ try {
1211
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1212
+ const config = loadAgentConfig2();
1213
+ const name = employee.name.toLowerCase();
1214
+ if (!config[name] && config["default"]) {
1215
+ config[name] = { ...config["default"] };
1216
+ saveAgentConfig2(config);
1217
+ }
1218
+ } catch {
1219
+ }
1220
+ return updated;
1221
+ }
1098
1222
  async function normalizeRosterCase(rosterPath) {
1099
1223
  const employees = await loadEmployees(rosterPath);
1100
1224
  let changed = false;
@@ -1104,14 +1228,14 @@ async function normalizeRosterCase(rosterPath) {
1104
1228
  emp.name = emp.name.toLowerCase();
1105
1229
  changed = true;
1106
1230
  try {
1107
- const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
1108
- const oldPath = path5.join(identityDir, `${oldName}.md`);
1109
- const newPath = path5.join(identityDir, `${emp.name}.md`);
1110
- if (existsSync5(oldPath) && !existsSync5(newPath)) {
1231
+ const identityDir = path6.join(os4.homedir(), ".exe-os", "identity");
1232
+ const oldPath = path6.join(identityDir, `${oldName}.md`);
1233
+ const newPath = path6.join(identityDir, `${emp.name}.md`);
1234
+ if (existsSync7(oldPath) && !existsSync7(newPath)) {
1111
1235
  renameSync3(oldPath, newPath);
1112
- } else if (existsSync5(oldPath) && oldPath !== newPath) {
1113
- const content = readFileSync5(oldPath, "utf-8");
1114
- writeFileSync4(newPath, content, "utf-8");
1236
+ } else if (existsSync7(oldPath) && oldPath !== newPath) {
1237
+ const content = readFileSync6(oldPath, "utf-8");
1238
+ writeFileSync5(newPath, content, "utf-8");
1115
1239
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
1116
1240
  unlinkSync(oldPath);
1117
1241
  }
@@ -1141,7 +1265,7 @@ function registerBinSymlinks(name) {
1141
1265
  errors.push("Could not find 'exe-os' in PATH");
1142
1266
  return { created, skipped, errors };
1143
1267
  }
1144
- const binDir = path5.dirname(exeBinPath);
1268
+ const binDir = path6.dirname(exeBinPath);
1145
1269
  let target;
1146
1270
  try {
1147
1271
  target = readlinkSync(exeBinPath);
@@ -1151,8 +1275,8 @@ function registerBinSymlinks(name) {
1151
1275
  }
1152
1276
  for (const suffix of ["", "-opencode"]) {
1153
1277
  const linkName = `${name}${suffix}`;
1154
- const linkPath = path5.join(binDir, linkName);
1155
- if (existsSync5(linkPath)) {
1278
+ const linkPath = path6.join(binDir, linkName);
1279
+ if (existsSync7(linkPath)) {
1156
1280
  skipped.push(linkName);
1157
1281
  continue;
1158
1282
  }
@@ -1165,25 +1289,611 @@ function registerBinSymlinks(name) {
1165
1289
  }
1166
1290
  return { created, skipped, errors };
1167
1291
  }
1168
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
1292
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
1169
1293
  var init_employees = __esm({
1170
1294
  "src/lib/employees.ts"() {
1171
1295
  "use strict";
1172
1296
  init_config();
1173
- EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
1297
+ EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
1174
1298
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
1175
1299
  COORDINATOR_ROLE = "COO";
1176
1300
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1301
+ IDENTITY_DIR = path6.join(EXE_AI_DIR, "identity");
1302
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
1303
+ }
1304
+ });
1305
+
1306
+ // src/lib/database-adapter.ts
1307
+ import os5 from "os";
1308
+ import path7 from "path";
1309
+ import { createRequire } from "module";
1310
+ import { pathToFileURL } from "url";
1311
+ function quotedIdentifier(identifier) {
1312
+ return `"${identifier.replace(/"/g, '""')}"`;
1313
+ }
1314
+ function unqualifiedTableName(name) {
1315
+ const raw = name.trim().replace(/^"|"$/g, "");
1316
+ const parts = raw.split(".");
1317
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1318
+ }
1319
+ function stripTrailingSemicolon(sql) {
1320
+ return sql.trim().replace(/;+\s*$/u, "");
1321
+ }
1322
+ function appendClause(sql, clause) {
1323
+ const trimmed = stripTrailingSemicolon(sql);
1324
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1325
+ if (!returningMatch) {
1326
+ return `${trimmed}${clause}`;
1327
+ }
1328
+ const idx = returningMatch.index;
1329
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1330
+ }
1331
+ function normalizeStatement(stmt) {
1332
+ if (typeof stmt === "string") {
1333
+ return { kind: "positional", sql: stmt, args: [] };
1334
+ }
1335
+ const sql = stmt.sql;
1336
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1337
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1338
+ }
1339
+ return { kind: "named", sql, args: stmt.args };
1340
+ }
1341
+ function rewriteBooleanLiterals(sql) {
1342
+ let out = sql;
1343
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1344
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1345
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1346
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1347
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1348
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1349
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1350
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1351
+ }
1352
+ return out;
1353
+ }
1354
+ function rewriteInsertOrIgnore(sql) {
1355
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1356
+ return sql;
1357
+ }
1358
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1359
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1360
+ }
1361
+ function rewriteInsertOrReplace(sql) {
1362
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1363
+ if (!match) {
1364
+ return sql;
1365
+ }
1366
+ const rawTable = match[1];
1367
+ const rawColumns = match[2];
1368
+ const remainder = match[3];
1369
+ const tableName = unqualifiedTableName(rawTable);
1370
+ const conflictKeys = UPSERT_KEYS[tableName];
1371
+ if (!conflictKeys?.length) {
1372
+ return sql;
1373
+ }
1374
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1375
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1376
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1377
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1378
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1379
+ }
1380
+ function rewriteSql(sql) {
1381
+ let out = sql;
1382
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1383
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1384
+ out = rewriteBooleanLiterals(out);
1385
+ out = rewriteInsertOrReplace(out);
1386
+ out = rewriteInsertOrIgnore(out);
1387
+ return stripTrailingSemicolon(out);
1388
+ }
1389
+ function toBoolean(value) {
1390
+ if (value === null || value === void 0) return value;
1391
+ if (typeof value === "boolean") return value;
1392
+ if (typeof value === "number") return value !== 0;
1393
+ if (typeof value === "bigint") return value !== 0n;
1394
+ if (typeof value === "string") {
1395
+ const normalized = value.trim().toLowerCase();
1396
+ if (normalized === "0" || normalized === "false") return false;
1397
+ if (normalized === "1" || normalized === "true") return true;
1398
+ }
1399
+ return Boolean(value);
1400
+ }
1401
+ function countQuestionMarks(sql, end) {
1402
+ let count = 0;
1403
+ let inSingle = false;
1404
+ let inDouble = false;
1405
+ let inLineComment = false;
1406
+ let inBlockComment = false;
1407
+ for (let i = 0; i < end; i++) {
1408
+ const ch = sql[i];
1409
+ const next = sql[i + 1];
1410
+ if (inLineComment) {
1411
+ if (ch === "\n") inLineComment = false;
1412
+ continue;
1413
+ }
1414
+ if (inBlockComment) {
1415
+ if (ch === "*" && next === "/") {
1416
+ inBlockComment = false;
1417
+ i += 1;
1418
+ }
1419
+ continue;
1420
+ }
1421
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1422
+ inLineComment = true;
1423
+ i += 1;
1424
+ continue;
1425
+ }
1426
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1427
+ inBlockComment = true;
1428
+ i += 1;
1429
+ continue;
1430
+ }
1431
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1432
+ inSingle = !inSingle;
1433
+ continue;
1434
+ }
1435
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1436
+ inDouble = !inDouble;
1437
+ continue;
1438
+ }
1439
+ if (!inSingle && !inDouble && ch === "?") {
1440
+ count += 1;
1441
+ }
1442
+ }
1443
+ return count;
1444
+ }
1445
+ function findBooleanPlaceholderIndexes(sql) {
1446
+ const indexes = /* @__PURE__ */ new Set();
1447
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1448
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1449
+ for (const match of sql.matchAll(pattern)) {
1450
+ const matchText = match[0];
1451
+ const qIndex = match.index + matchText.lastIndexOf("?");
1452
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1453
+ }
1454
+ }
1455
+ return indexes;
1456
+ }
1457
+ function coerceInsertBooleanArgs(sql, args) {
1458
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1459
+ if (!match) return;
1460
+ const rawTable = match[1];
1461
+ const rawColumns = match[2];
1462
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1463
+ if (!boolColumns?.size) return;
1464
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1465
+ for (const [index, column] of columns.entries()) {
1466
+ if (boolColumns.has(column) && index < args.length) {
1467
+ args[index] = toBoolean(args[index]);
1468
+ }
1469
+ }
1470
+ }
1471
+ function coerceUpdateBooleanArgs(sql, args) {
1472
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1473
+ if (!match) return;
1474
+ const rawTable = match[1];
1475
+ const setClause = match[2];
1476
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1477
+ if (!boolColumns?.size) return;
1478
+ const assignments = setClause.split(",");
1479
+ let placeholderIndex = 0;
1480
+ for (const assignment of assignments) {
1481
+ if (!assignment.includes("?")) continue;
1482
+ placeholderIndex += 1;
1483
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1484
+ if (colMatch && boolColumns.has(colMatch[1])) {
1485
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1486
+ }
1487
+ }
1488
+ }
1489
+ function coerceBooleanArgs(sql, args) {
1490
+ const nextArgs = [...args];
1491
+ coerceInsertBooleanArgs(sql, nextArgs);
1492
+ coerceUpdateBooleanArgs(sql, nextArgs);
1493
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1494
+ for (const index of placeholderIndexes) {
1495
+ if (index > 0 && index <= nextArgs.length) {
1496
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1497
+ }
1498
+ }
1499
+ return nextArgs;
1500
+ }
1501
+ function convertQuestionMarksToDollarParams(sql) {
1502
+ let out = "";
1503
+ let placeholder = 0;
1504
+ let inSingle = false;
1505
+ let inDouble = false;
1506
+ let inLineComment = false;
1507
+ let inBlockComment = false;
1508
+ for (let i = 0; i < sql.length; i++) {
1509
+ const ch = sql[i];
1510
+ const next = sql[i + 1];
1511
+ if (inLineComment) {
1512
+ out += ch;
1513
+ if (ch === "\n") inLineComment = false;
1514
+ continue;
1515
+ }
1516
+ if (inBlockComment) {
1517
+ out += ch;
1518
+ if (ch === "*" && next === "/") {
1519
+ out += next;
1520
+ inBlockComment = false;
1521
+ i += 1;
1522
+ }
1523
+ continue;
1524
+ }
1525
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1526
+ out += ch + next;
1527
+ inLineComment = true;
1528
+ i += 1;
1529
+ continue;
1530
+ }
1531
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1532
+ out += ch + next;
1533
+ inBlockComment = true;
1534
+ i += 1;
1535
+ continue;
1536
+ }
1537
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1538
+ inSingle = !inSingle;
1539
+ out += ch;
1540
+ continue;
1541
+ }
1542
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1543
+ inDouble = !inDouble;
1544
+ out += ch;
1545
+ continue;
1546
+ }
1547
+ if (!inSingle && !inDouble && ch === "?") {
1548
+ placeholder += 1;
1549
+ out += `$${placeholder}`;
1550
+ continue;
1551
+ }
1552
+ out += ch;
1553
+ }
1554
+ return out;
1555
+ }
1556
+ function translateStatementForPostgres(stmt) {
1557
+ const normalized = normalizeStatement(stmt);
1558
+ if (normalized.kind === "named") {
1559
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1560
+ }
1561
+ const rewrittenSql = rewriteSql(normalized.sql);
1562
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1563
+ return {
1564
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1565
+ args: coercedArgs
1566
+ };
1567
+ }
1568
+ function shouldBypassPostgres(stmt) {
1569
+ const normalized = normalizeStatement(stmt);
1570
+ if (normalized.kind === "named") {
1571
+ return true;
1572
+ }
1573
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1574
+ }
1575
+ function shouldFallbackOnError(error) {
1576
+ const message = error instanceof Error ? error.message : String(error);
1577
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1578
+ }
1579
+ function isReadQuery(sql) {
1580
+ const trimmed = sql.trimStart();
1581
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1582
+ }
1583
+ function buildRow(row, columns) {
1584
+ const values = columns.map((column) => row[column]);
1585
+ return Object.assign(values, row);
1586
+ }
1587
+ function buildResultSet(rows, rowsAffected = 0) {
1588
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1589
+ const resultRows = rows.map((row) => buildRow(row, columns));
1590
+ return {
1591
+ columns,
1592
+ columnTypes: columns.map(() => ""),
1593
+ rows: resultRows,
1594
+ rowsAffected,
1595
+ lastInsertRowid: void 0,
1596
+ toJSON() {
1597
+ return {
1598
+ columns,
1599
+ columnTypes: columns.map(() => ""),
1600
+ rows,
1601
+ rowsAffected,
1602
+ lastInsertRowid: void 0
1603
+ };
1604
+ }
1605
+ };
1606
+ }
1607
+ async function loadPrismaClient() {
1608
+ if (!prismaClientPromise) {
1609
+ prismaClientPromise = (async () => {
1610
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1611
+ if (explicitPath) {
1612
+ const module2 = await import(pathToFileURL(explicitPath).href);
1613
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1614
+ if (!PrismaClient2) {
1615
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1616
+ }
1617
+ return new PrismaClient2();
1618
+ }
1619
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1620
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1621
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1622
+ const module = await import(pathToFileURL(prismaEntry).href);
1623
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1624
+ if (!PrismaClient) {
1625
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1626
+ }
1627
+ return new PrismaClient();
1628
+ })();
1629
+ }
1630
+ return prismaClientPromise;
1631
+ }
1632
+ async function ensureCompatibilityViews(prisma) {
1633
+ if (!compatibilityBootstrapPromise) {
1634
+ compatibilityBootstrapPromise = (async () => {
1635
+ for (const mapping of VIEW_MAPPINGS) {
1636
+ const relation = mapping.source.replace(/"/g, "");
1637
+ const rows = await prisma.$queryRawUnsafe(
1638
+ "SELECT to_regclass($1) AS regclass",
1639
+ relation
1640
+ );
1641
+ if (!rows[0]?.regclass) {
1642
+ continue;
1643
+ }
1644
+ await prisma.$executeRawUnsafe(
1645
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1646
+ );
1647
+ }
1648
+ })();
1649
+ }
1650
+ return compatibilityBootstrapPromise;
1651
+ }
1652
+ async function executeOnPrisma(executor, stmt) {
1653
+ const translated = translateStatementForPostgres(stmt);
1654
+ if (isReadQuery(translated.sql)) {
1655
+ const rows = await executor.$queryRawUnsafe(
1656
+ translated.sql,
1657
+ ...translated.args
1658
+ );
1659
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1660
+ }
1661
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1662
+ return buildResultSet([], rowsAffected);
1663
+ }
1664
+ function splitSqlStatements(sql) {
1665
+ const parts = [];
1666
+ let current = "";
1667
+ let inSingle = false;
1668
+ let inDouble = false;
1669
+ let inLineComment = false;
1670
+ let inBlockComment = false;
1671
+ for (let i = 0; i < sql.length; i++) {
1672
+ const ch = sql[i];
1673
+ const next = sql[i + 1];
1674
+ if (inLineComment) {
1675
+ current += ch;
1676
+ if (ch === "\n") inLineComment = false;
1677
+ continue;
1678
+ }
1679
+ if (inBlockComment) {
1680
+ current += ch;
1681
+ if (ch === "*" && next === "/") {
1682
+ current += next;
1683
+ inBlockComment = false;
1684
+ i += 1;
1685
+ }
1686
+ continue;
1687
+ }
1688
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1689
+ current += ch + next;
1690
+ inLineComment = true;
1691
+ i += 1;
1692
+ continue;
1693
+ }
1694
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1695
+ current += ch + next;
1696
+ inBlockComment = true;
1697
+ i += 1;
1698
+ continue;
1699
+ }
1700
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1701
+ inSingle = !inSingle;
1702
+ current += ch;
1703
+ continue;
1704
+ }
1705
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1706
+ inDouble = !inDouble;
1707
+ current += ch;
1708
+ continue;
1709
+ }
1710
+ if (!inSingle && !inDouble && ch === ";") {
1711
+ if (current.trim()) {
1712
+ parts.push(current.trim());
1713
+ }
1714
+ current = "";
1715
+ continue;
1716
+ }
1717
+ current += ch;
1718
+ }
1719
+ if (current.trim()) {
1720
+ parts.push(current.trim());
1721
+ }
1722
+ return parts;
1723
+ }
1724
+ async function createPrismaDbAdapter(fallbackClient) {
1725
+ const prisma = await loadPrismaClient();
1726
+ await ensureCompatibilityViews(prisma);
1727
+ let closed = false;
1728
+ let adapter;
1729
+ const fallbackExecute = async (stmt, error) => {
1730
+ if (!fallbackClient) {
1731
+ if (error) throw error;
1732
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1733
+ }
1734
+ if (error) {
1735
+ process.stderr.write(
1736
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1737
+ `
1738
+ );
1739
+ }
1740
+ return fallbackClient.execute(stmt);
1741
+ };
1742
+ adapter = {
1743
+ async execute(stmt) {
1744
+ if (shouldBypassPostgres(stmt)) {
1745
+ return fallbackExecute(stmt);
1746
+ }
1747
+ try {
1748
+ return await executeOnPrisma(prisma, stmt);
1749
+ } catch (error) {
1750
+ if (shouldFallbackOnError(error)) {
1751
+ return fallbackExecute(stmt, error);
1752
+ }
1753
+ throw error;
1754
+ }
1755
+ },
1756
+ async batch(stmts, mode) {
1757
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1758
+ if (!fallbackClient) {
1759
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1760
+ }
1761
+ return fallbackClient.batch(stmts, mode);
1762
+ }
1763
+ try {
1764
+ if (prisma.$transaction) {
1765
+ return await prisma.$transaction(async (tx) => {
1766
+ const results2 = [];
1767
+ for (const stmt of stmts) {
1768
+ results2.push(await executeOnPrisma(tx, stmt));
1769
+ }
1770
+ return results2;
1771
+ });
1772
+ }
1773
+ const results = [];
1774
+ for (const stmt of stmts) {
1775
+ results.push(await executeOnPrisma(prisma, stmt));
1776
+ }
1777
+ return results;
1778
+ } catch (error) {
1779
+ if (fallbackClient && shouldFallbackOnError(error)) {
1780
+ process.stderr.write(
1781
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1782
+ `
1783
+ );
1784
+ return fallbackClient.batch(stmts, mode);
1785
+ }
1786
+ throw error;
1787
+ }
1788
+ },
1789
+ async migrate(stmts) {
1790
+ if (fallbackClient) {
1791
+ return fallbackClient.migrate(stmts);
1792
+ }
1793
+ return adapter.batch(stmts, "deferred");
1794
+ },
1795
+ async transaction(mode) {
1796
+ if (!fallbackClient) {
1797
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1798
+ }
1799
+ return fallbackClient.transaction(mode);
1800
+ },
1801
+ async executeMultiple(sql) {
1802
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1803
+ return fallbackClient.executeMultiple(sql);
1804
+ }
1805
+ for (const statement of splitSqlStatements(sql)) {
1806
+ await adapter.execute(statement);
1807
+ }
1808
+ },
1809
+ async sync() {
1810
+ if (fallbackClient) {
1811
+ return fallbackClient.sync();
1812
+ }
1813
+ return { frame_no: 0, frames_synced: 0 };
1814
+ },
1815
+ close() {
1816
+ closed = true;
1817
+ prismaClientPromise = null;
1818
+ compatibilityBootstrapPromise = null;
1819
+ void prisma.$disconnect?.();
1820
+ },
1821
+ get closed() {
1822
+ return closed;
1823
+ },
1824
+ get protocol() {
1825
+ return "prisma-postgres";
1826
+ }
1827
+ };
1828
+ return adapter;
1829
+ }
1830
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1831
+ var init_database_adapter = __esm({
1832
+ "src/lib/database-adapter.ts"() {
1833
+ "use strict";
1834
+ VIEW_MAPPINGS = [
1835
+ { view: "memories", source: "memory.memory_records" },
1836
+ { view: "tasks", source: "memory.tasks" },
1837
+ { view: "behaviors", source: "memory.behaviors" },
1838
+ { view: "entities", source: "memory.entities" },
1839
+ { view: "relationships", source: "memory.relationships" },
1840
+ { view: "entity_memories", source: "memory.entity_memories" },
1841
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1842
+ { view: "notifications", source: "memory.notifications" },
1843
+ { view: "messages", source: "memory.messages" },
1844
+ { view: "users", source: "wiki.users" },
1845
+ { view: "workspaces", source: "wiki.workspaces" },
1846
+ { view: "workspace_users", source: "wiki.workspace_users" },
1847
+ { view: "documents", source: "wiki.workspace_documents" },
1848
+ { view: "chats", source: "wiki.workspace_chats" }
1849
+ ];
1850
+ UPSERT_KEYS = {
1851
+ memories: ["id"],
1852
+ tasks: ["id"],
1853
+ behaviors: ["id"],
1854
+ entities: ["id"],
1855
+ relationships: ["id"],
1856
+ entity_aliases: ["alias"],
1857
+ notifications: ["id"],
1858
+ messages: ["id"],
1859
+ users: ["id"],
1860
+ workspaces: ["id"],
1861
+ workspace_users: ["id"],
1862
+ documents: ["id"],
1863
+ chats: ["id"]
1864
+ };
1865
+ BOOLEAN_COLUMNS_BY_TABLE = {
1866
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1867
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1868
+ notifications: /* @__PURE__ */ new Set(["read"]),
1869
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1870
+ };
1871
+ BOOLEAN_COLUMN_NAMES = new Set(
1872
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1873
+ );
1874
+ IMMEDIATE_FALLBACK_PATTERNS = [
1875
+ /\bPRAGMA\b/i,
1876
+ /\bsqlite_master\b/i,
1877
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1878
+ /\bMATCH\b/i,
1879
+ /\bvector_distance_cos\s*\(/i,
1880
+ /\bjson_extract\s*\(/i,
1881
+ /\bjulianday\s*\(/i,
1882
+ /\bstrftime\s*\(/i,
1883
+ /\blast_insert_rowid\s*\(/i
1884
+ ];
1885
+ prismaClientPromise = null;
1886
+ compatibilityBootstrapPromise = null;
1177
1887
  }
1178
1888
  });
1179
1889
 
1180
1890
  // src/lib/exe-daemon-client.ts
1181
1891
  import net from "net";
1182
- import os5 from "os";
1892
+ import os6 from "os";
1183
1893
  import { spawn } from "child_process";
1184
1894
  import { randomUUID } from "crypto";
1185
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1186
- import path6 from "path";
1895
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1896
+ import path8 from "path";
1187
1897
  import { fileURLToPath } from "url";
1188
1898
  function handleData(chunk) {
1189
1899
  _buffer += chunk.toString();
@@ -1211,9 +1921,9 @@ function handleData(chunk) {
1211
1921
  }
1212
1922
  }
1213
1923
  function cleanupStaleFiles() {
1214
- if (existsSync6(PID_PATH)) {
1924
+ if (existsSync8(PID_PATH)) {
1215
1925
  try {
1216
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1926
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1217
1927
  if (pid > 0) {
1218
1928
  try {
1219
1929
  process.kill(pid, 0);
@@ -1234,17 +1944,17 @@ function cleanupStaleFiles() {
1234
1944
  }
1235
1945
  }
1236
1946
  function findPackageRoot() {
1237
- let dir = path6.dirname(fileURLToPath(import.meta.url));
1238
- const { root } = path6.parse(dir);
1947
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
1948
+ const { root } = path8.parse(dir);
1239
1949
  while (dir !== root) {
1240
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
1241
- dir = path6.dirname(dir);
1950
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
1951
+ dir = path8.dirname(dir);
1242
1952
  }
1243
1953
  return null;
1244
1954
  }
1245
1955
  function spawnDaemon() {
1246
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
1247
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1956
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1957
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1248
1958
  if (totalGB <= 8) {
1249
1959
  process.stderr.write(
1250
1960
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1264,16 +1974,17 @@ function spawnDaemon() {
1264
1974
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1265
1975
  return;
1266
1976
  }
1267
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1268
- if (!existsSync6(daemonPath)) {
1977
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1978
+ if (!existsSync8(daemonPath)) {
1269
1979
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1270
1980
  `);
1271
1981
  return;
1272
1982
  }
1273
1983
  const resolvedPath = daemonPath;
1984
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1274
1985
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1275
1986
  `);
1276
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1987
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1277
1988
  let stderrFd = "ignore";
1278
1989
  try {
1279
1990
  stderrFd = openSync(logPath, "a");
@@ -1291,7 +2002,8 @@ function spawnDaemon() {
1291
2002
  TMUX_PANE: void 0,
1292
2003
  // Prevents resolveExeSession() from scoping to one session
1293
2004
  EXE_DAEMON_SOCK: SOCKET_PATH,
1294
- EXE_DAEMON_PID: PID_PATH
2005
+ EXE_DAEMON_PID: PID_PATH,
2006
+ [DAEMON_TOKEN_ENV]: daemonToken
1295
2007
  }
1296
2008
  });
1297
2009
  child.unref();
@@ -1401,13 +2113,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1401
2113
  return;
1402
2114
  }
1403
2115
  const id = randomUUID();
2116
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1404
2117
  const timer = setTimeout(() => {
1405
2118
  _pending.delete(id);
1406
2119
  resolve({ error: "Request timeout" });
1407
2120
  }, timeoutMs);
1408
2121
  _pending.set(id, { resolve, timer });
1409
2122
  try {
1410
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2123
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1411
2124
  } catch {
1412
2125
  clearTimeout(timer);
1413
2126
  _pending.delete(id);
@@ -1424,74 +2137,123 @@ async function pingDaemon() {
1424
2137
  return null;
1425
2138
  }
1426
2139
  function killAndRespawnDaemon() {
1427
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
1428
- if (existsSync6(PID_PATH)) {
1429
- try {
1430
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1431
- if (pid > 0) {
1432
- try {
1433
- process.kill(pid, "SIGKILL");
1434
- } catch {
2140
+ if (!acquireSpawnLock()) {
2141
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2142
+ if (_socket) {
2143
+ _socket.destroy();
2144
+ _socket = null;
2145
+ }
2146
+ _connected = false;
2147
+ _buffer = "";
2148
+ return;
2149
+ }
2150
+ try {
2151
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2152
+ if (existsSync8(PID_PATH)) {
2153
+ try {
2154
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
2155
+ if (pid > 0) {
2156
+ try {
2157
+ process.kill(pid, "SIGKILL");
2158
+ } catch {
2159
+ }
1435
2160
  }
2161
+ } catch {
1436
2162
  }
2163
+ }
2164
+ if (_socket) {
2165
+ _socket.destroy();
2166
+ _socket = null;
2167
+ }
2168
+ _connected = false;
2169
+ _buffer = "";
2170
+ try {
2171
+ unlinkSync2(PID_PATH);
1437
2172
  } catch {
1438
2173
  }
2174
+ try {
2175
+ unlinkSync2(SOCKET_PATH);
2176
+ } catch {
2177
+ }
2178
+ spawnDaemon();
2179
+ } finally {
2180
+ releaseSpawnLock();
1439
2181
  }
1440
- if (_socket) {
1441
- _socket.destroy();
1442
- _socket = null;
1443
- }
1444
- _connected = false;
1445
- _buffer = "";
2182
+ }
2183
+ function isDaemonTooYoung() {
1446
2184
  try {
1447
- unlinkSync2(PID_PATH);
2185
+ const stat = statSync(PID_PATH);
2186
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
1448
2187
  } catch {
2188
+ return false;
1449
2189
  }
1450
- try {
1451
- unlinkSync2(SOCKET_PATH);
1452
- } catch {
2190
+ }
2191
+ async function retryThenRestart(doRequest, label) {
2192
+ const result = await doRequest();
2193
+ if (!result.error) {
2194
+ _consecutiveFailures = 0;
2195
+ return result;
1453
2196
  }
1454
- spawnDaemon();
2197
+ _consecutiveFailures++;
2198
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
2199
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
2200
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
2201
+ `);
2202
+ await new Promise((r) => setTimeout(r, delayMs));
2203
+ if (!_connected) {
2204
+ if (!await connectToSocket()) continue;
2205
+ }
2206
+ const retry = await doRequest();
2207
+ if (!retry.error) {
2208
+ _consecutiveFailures = 0;
2209
+ return retry;
2210
+ }
2211
+ _consecutiveFailures++;
2212
+ }
2213
+ if (isDaemonTooYoung()) {
2214
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
2215
+ `);
2216
+ return { error: result.error };
2217
+ }
2218
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
2219
+ `);
2220
+ killAndRespawnDaemon();
2221
+ const start = Date.now();
2222
+ let delay2 = 200;
2223
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2224
+ await new Promise((r) => setTimeout(r, delay2));
2225
+ if (await connectToSocket()) break;
2226
+ delay2 = Math.min(delay2 * 2, 3e3);
2227
+ }
2228
+ if (!_connected) return { error: "Daemon restart failed" };
2229
+ const final = await doRequest();
2230
+ if (!final.error) _consecutiveFailures = 0;
2231
+ return final;
1455
2232
  }
1456
2233
  async function embedViaClient(text, priority = "high") {
1457
2234
  if (!_connected && !await connectEmbedDaemon()) return null;
1458
2235
  _requestCount++;
1459
2236
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1460
2237
  const health = await pingDaemon();
1461
- if (!health) {
2238
+ if (!health && !isDaemonTooYoung()) {
1462
2239
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1463
2240
  `);
1464
2241
  killAndRespawnDaemon();
1465
2242
  const start = Date.now();
1466
- let delay2 = 200;
2243
+ let d = 200;
1467
2244
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1468
- await new Promise((r) => setTimeout(r, delay2));
2245
+ await new Promise((r) => setTimeout(r, d));
1469
2246
  if (await connectToSocket()) break;
1470
- delay2 = Math.min(delay2 * 2, 3e3);
2247
+ d = Math.min(d * 2, 3e3);
1471
2248
  }
1472
2249
  if (!_connected) return null;
1473
2250
  }
1474
2251
  }
1475
- const result = await sendRequest([text], priority);
1476
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
1477
- if (result.error) {
1478
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
1479
- `);
1480
- killAndRespawnDaemon();
1481
- const start = Date.now();
1482
- let delay2 = 200;
1483
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1484
- await new Promise((r) => setTimeout(r, delay2));
1485
- if (await connectToSocket()) break;
1486
- delay2 = Math.min(delay2 * 2, 3e3);
1487
- }
1488
- if (!_connected) return null;
1489
- const retry = await sendRequest([text], priority);
1490
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
1491
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
1492
- `);
1493
- }
1494
- return null;
2252
+ const result = await retryThenRestart(
2253
+ () => sendRequest([text], priority),
2254
+ "Embed"
2255
+ );
2256
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
1495
2257
  }
1496
2258
  function disconnectClient() {
1497
2259
  if (_socket) {
@@ -1509,22 +2271,28 @@ function disconnectClient() {
1509
2271
  function isClientConnected() {
1510
2272
  return _connected;
1511
2273
  }
1512
- 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;
2274
+ 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;
1513
2275
  var init_exe_daemon_client = __esm({
1514
2276
  "src/lib/exe-daemon-client.ts"() {
1515
2277
  "use strict";
1516
2278
  init_config();
1517
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1518
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1519
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2279
+ init_daemon_auth();
2280
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
2281
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
2282
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1520
2283
  SPAWN_LOCK_STALE_MS = 3e4;
1521
2284
  CONNECT_TIMEOUT_MS = 15e3;
1522
2285
  REQUEST_TIMEOUT_MS = 3e4;
2286
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1523
2287
  _socket = null;
1524
2288
  _connected = false;
1525
2289
  _buffer = "";
1526
2290
  _requestCount = 0;
2291
+ _consecutiveFailures = 0;
1527
2292
  HEALTH_CHECK_INTERVAL = 100;
2293
+ MAX_RETRIES_BEFORE_RESTART = 3;
2294
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
2295
+ MIN_DAEMON_AGE_MS = 3e4;
1528
2296
  _pending = /* @__PURE__ */ new Map();
1529
2297
  MAX_BUFFER = 1e7;
1530
2298
  }
@@ -1536,7 +2304,7 @@ __export(db_daemon_client_exports, {
1536
2304
  createDaemonDbClient: () => createDaemonDbClient,
1537
2305
  initDaemonDbClient: () => initDaemonDbClient
1538
2306
  });
1539
- function normalizeStatement(stmt) {
2307
+ function normalizeStatement2(stmt) {
1540
2308
  if (typeof stmt === "string") {
1541
2309
  return { sql: stmt, args: [] };
1542
2310
  }
@@ -1560,7 +2328,7 @@ function createDaemonDbClient(fallbackClient) {
1560
2328
  if (!_useDaemon || !isClientConnected()) {
1561
2329
  return fallbackClient.execute(stmt);
1562
2330
  }
1563
- const { sql, args } = normalizeStatement(stmt);
2331
+ const { sql, args } = normalizeStatement2(stmt);
1564
2332
  const response = await sendDaemonRequest({
1565
2333
  type: "db-execute",
1566
2334
  sql,
@@ -1585,7 +2353,7 @@ function createDaemonDbClient(fallbackClient) {
1585
2353
  if (!_useDaemon || !isClientConnected()) {
1586
2354
  return fallbackClient.batch(stmts, mode);
1587
2355
  }
1588
- const statements = stmts.map(normalizeStatement);
2356
+ const statements = stmts.map(normalizeStatement2);
1589
2357
  const response = await sendDaemonRequest({
1590
2358
  type: "db-batch",
1591
2359
  statements,
@@ -1680,6 +2448,18 @@ __export(database_exports, {
1680
2448
  });
1681
2449
  import { createClient } from "@libsql/client";
1682
2450
  async function initDatabase(config) {
2451
+ if (_walCheckpointTimer) {
2452
+ clearInterval(_walCheckpointTimer);
2453
+ _walCheckpointTimer = null;
2454
+ }
2455
+ if (_daemonClient) {
2456
+ _daemonClient.close();
2457
+ _daemonClient = null;
2458
+ }
2459
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2460
+ _adapterClient.close();
2461
+ }
2462
+ _adapterClient = null;
1683
2463
  if (_client) {
1684
2464
  _client.close();
1685
2465
  _client = null;
@@ -1693,6 +2473,7 @@ async function initDatabase(config) {
1693
2473
  }
1694
2474
  _client = createClient(opts);
1695
2475
  _resilientClient = wrapWithRetry(_client);
2476
+ _adapterClient = _resilientClient;
1696
2477
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1697
2478
  });
1698
2479
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1703,14 +2484,20 @@ async function initDatabase(config) {
1703
2484
  });
1704
2485
  }, 3e4);
1705
2486
  _walCheckpointTimer.unref();
2487
+ if (process.env.DATABASE_URL) {
2488
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2489
+ }
1706
2490
  }
1707
2491
  function isInitialized() {
1708
- return _client !== null;
2492
+ return _adapterClient !== null || _client !== null;
1709
2493
  }
1710
2494
  function getClient() {
1711
- if (!_resilientClient) {
2495
+ if (!_adapterClient) {
1712
2496
  throw new Error("Database client not initialized. Call initDatabase() first.");
1713
2497
  }
2498
+ if (process.env.DATABASE_URL) {
2499
+ return _adapterClient;
2500
+ }
1714
2501
  if (process.env.EXE_IS_DAEMON === "1") {
1715
2502
  return _resilientClient;
1716
2503
  }
@@ -1720,6 +2507,7 @@ function getClient() {
1720
2507
  return _resilientClient;
1721
2508
  }
1722
2509
  async function initDaemonClient() {
2510
+ if (process.env.DATABASE_URL) return;
1723
2511
  if (process.env.EXE_IS_DAEMON === "1") return;
1724
2512
  if (!_resilientClient) return;
1725
2513
  try {
@@ -2016,6 +2804,7 @@ async function ensureSchema() {
2016
2804
  project TEXT NOT NULL,
2017
2805
  summary TEXT NOT NULL,
2018
2806
  task_file TEXT,
2807
+ session_scope TEXT,
2019
2808
  read INTEGER NOT NULL DEFAULT 0,
2020
2809
  created_at TEXT NOT NULL
2021
2810
  );
@@ -2024,7 +2813,7 @@ async function ensureSchema() {
2024
2813
  ON notifications(read);
2025
2814
 
2026
2815
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2027
- ON notifications(agent_id);
2816
+ ON notifications(agent_id, session_scope);
2028
2817
 
2029
2818
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2030
2819
  ON notifications(task_file);
@@ -2062,6 +2851,7 @@ async function ensureSchema() {
2062
2851
  target_agent TEXT NOT NULL,
2063
2852
  target_project TEXT,
2064
2853
  target_device TEXT NOT NULL DEFAULT 'local',
2854
+ session_scope TEXT,
2065
2855
  content TEXT NOT NULL,
2066
2856
  priority TEXT DEFAULT 'normal',
2067
2857
  status TEXT DEFAULT 'pending',
@@ -2075,10 +2865,31 @@ async function ensureSchema() {
2075
2865
  );
2076
2866
 
2077
2867
  CREATE INDEX IF NOT EXISTS idx_messages_target
2078
- ON messages(target_agent, status);
2868
+ ON messages(target_agent, session_scope, status);
2079
2869
 
2080
2870
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2081
- ON messages(target_agent, from_agent, server_seq);
2871
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2872
+ `);
2873
+ try {
2874
+ await client.execute({
2875
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2876
+ args: []
2877
+ });
2878
+ } catch {
2879
+ }
2880
+ try {
2881
+ await client.execute({
2882
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2883
+ args: []
2884
+ });
2885
+ } catch {
2886
+ }
2887
+ await client.executeMultiple(`
2888
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2889
+ ON notifications(agent_id, session_scope, read, created_at);
2890
+
2891
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2892
+ ON messages(target_agent, session_scope, status, created_at);
2082
2893
  `);
2083
2894
  try {
2084
2895
  await client.execute({
@@ -2662,46 +3473,66 @@ async function ensureSchema() {
2662
3473
  } catch {
2663
3474
  }
2664
3475
  }
3476
+ try {
3477
+ await client.execute({
3478
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3479
+ args: []
3480
+ });
3481
+ } catch {
3482
+ }
2665
3483
  }
2666
3484
  async function disposeDatabase() {
3485
+ if (_walCheckpointTimer) {
3486
+ clearInterval(_walCheckpointTimer);
3487
+ _walCheckpointTimer = null;
3488
+ }
2667
3489
  if (_daemonClient) {
2668
3490
  _daemonClient.close();
2669
3491
  _daemonClient = null;
2670
3492
  }
3493
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3494
+ _adapterClient.close();
3495
+ }
3496
+ _adapterClient = null;
2671
3497
  if (_client) {
2672
3498
  _client.close();
2673
3499
  _client = null;
2674
3500
  _resilientClient = null;
2675
3501
  }
2676
3502
  }
2677
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
3503
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2678
3504
  var init_database = __esm({
2679
3505
  "src/lib/database.ts"() {
2680
3506
  "use strict";
2681
3507
  init_db_retry();
2682
3508
  init_employees();
3509
+ init_database_adapter();
2683
3510
  _client = null;
2684
3511
  _resilientClient = null;
2685
3512
  _walCheckpointTimer = null;
2686
3513
  _daemonClient = null;
3514
+ _adapterClient = null;
2687
3515
  initTurso = initDatabase;
2688
3516
  disposeTurso = disposeDatabase;
2689
3517
  }
2690
3518
  });
2691
3519
 
2692
3520
  // src/lib/license.ts
2693
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3521
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2694
3522
  import { randomUUID as randomUUID2 } from "crypto";
2695
- import path7 from "path";
3523
+ import { createRequire as createRequire2 } from "module";
3524
+ import { pathToFileURL as pathToFileURL2 } from "url";
3525
+ import os7 from "os";
3526
+ import path9 from "path";
2696
3527
  import { jwtVerify, importSPKI } from "jose";
2697
3528
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2698
3529
  var init_license = __esm({
2699
3530
  "src/lib/license.ts"() {
2700
3531
  "use strict";
2701
3532
  init_config();
2702
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2703
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2704
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
3533
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3534
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3535
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2705
3536
  PLAN_LIMITS = {
2706
3537
  free: { devices: 1, employees: 1, memories: 5e3 },
2707
3538
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2713,12 +3544,12 @@ var init_license = __esm({
2713
3544
  });
2714
3545
 
2715
3546
  // src/lib/plan-limits.ts
2716
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2717
- import path8 from "path";
3547
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3548
+ import path10 from "path";
2718
3549
  function getLicenseSync() {
2719
3550
  try {
2720
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2721
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3551
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3552
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2722
3553
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2723
3554
  const parts = raw.token.split(".");
2724
3555
  if (parts.length !== 3) return freeLicense();
@@ -2756,8 +3587,8 @@ function assertEmployeeLimitSync(rosterPath) {
2756
3587
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2757
3588
  let count = 0;
2758
3589
  try {
2759
- if (existsSync8(filePath)) {
2760
- const raw = readFileSync8(filePath, "utf8");
3590
+ if (existsSync10(filePath)) {
3591
+ const raw = readFileSync9(filePath, "utf8");
2761
3592
  const employees = JSON.parse(raw);
2762
3593
  count = Array.isArray(employees) ? employees.length : 0;
2763
3594
  }
@@ -2786,29 +3617,63 @@ var init_plan_limits = __esm({
2786
3617
  this.name = "PlanLimitError";
2787
3618
  }
2788
3619
  };
2789
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
3620
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3621
+ }
3622
+ });
3623
+
3624
+ // src/lib/task-scope.ts
3625
+ function getCurrentSessionScope() {
3626
+ try {
3627
+ return resolveExeSession();
3628
+ } catch {
3629
+ return null;
3630
+ }
3631
+ }
3632
+ function sessionScopeFilter(sessionScope, tableAlias) {
3633
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3634
+ if (!scope) return { sql: "", args: [] };
3635
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3636
+ return {
3637
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3638
+ args: [scope]
3639
+ };
3640
+ }
3641
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
3642
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3643
+ if (!scope) return { sql: "", args: [] };
3644
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3645
+ return {
3646
+ sql: ` AND ${col} = ?`,
3647
+ args: [scope]
3648
+ };
3649
+ }
3650
+ var init_task_scope = __esm({
3651
+ "src/lib/task-scope.ts"() {
3652
+ "use strict";
3653
+ init_tmux_routing();
2790
3654
  }
2791
3655
  });
2792
3656
 
2793
3657
  // src/lib/notifications.ts
2794
- import crypto from "crypto";
2795
- import path9 from "path";
2796
- import os6 from "os";
3658
+ import crypto2 from "crypto";
3659
+ import path11 from "path";
3660
+ import os8 from "os";
2797
3661
  import {
2798
- readFileSync as readFileSync9,
3662
+ readFileSync as readFileSync10,
2799
3663
  readdirSync,
2800
3664
  unlinkSync as unlinkSync3,
2801
- existsSync as existsSync9,
3665
+ existsSync as existsSync11,
2802
3666
  rmdirSync
2803
3667
  } from "fs";
2804
3668
  async function writeNotification(notification) {
2805
3669
  try {
2806
3670
  const client = getClient();
2807
- const id = crypto.randomUUID();
3671
+ const id = crypto2.randomUUID();
2808
3672
  const now = (/* @__PURE__ */ new Date()).toISOString();
3673
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2809
3674
  await client.execute({
2810
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2811
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3675
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3676
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2812
3677
  args: [
2813
3678
  id,
2814
3679
  notification.agentId,
@@ -2817,6 +3682,7 @@ async function writeNotification(notification) {
2817
3682
  notification.project,
2818
3683
  notification.summary,
2819
3684
  notification.taskFile ?? null,
3685
+ sessionScope,
2820
3686
  now
2821
3687
  ]
2822
3688
  });
@@ -2825,12 +3691,14 @@ async function writeNotification(notification) {
2825
3691
  `);
2826
3692
  }
2827
3693
  }
2828
- async function markAsReadByTaskFile(taskFile) {
3694
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2829
3695
  try {
2830
3696
  const client = getClient();
3697
+ const scope = strictSessionScopeFilter(sessionScope);
2831
3698
  await client.execute({
2832
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2833
- args: [taskFile]
3699
+ sql: `UPDATE notifications SET read = 1
3700
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3701
+ args: [taskFile, ...scope.args]
2834
3702
  });
2835
3703
  } catch {
2836
3704
  }
@@ -2839,6 +3707,7 @@ var init_notifications = __esm({
2839
3707
  "src/lib/notifications.ts"() {
2840
3708
  "use strict";
2841
3709
  init_database();
3710
+ init_task_scope();
2842
3711
  }
2843
3712
  });
2844
3713
 
@@ -2855,7 +3724,7 @@ __export(session_kill_telemetry_exports, {
2855
3724
  recordSessionKill: () => recordSessionKill,
2856
3725
  sumTokensSavedSince: () => sumTokensSavedSince
2857
3726
  });
2858
- import crypto2 from "crypto";
3727
+ import crypto3 from "crypto";
2859
3728
  async function recordSessionKill(input) {
2860
3729
  try {
2861
3730
  const client = getClient();
@@ -2865,7 +3734,7 @@ async function recordSessionKill(input) {
2865
3734
  ticks_idle, estimated_tokens_saved)
2866
3735
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
2867
3736
  args: [
2868
- crypto2.randomUUID(),
3737
+ crypto3.randomUUID(),
2869
3738
  input.sessionName,
2870
3739
  input.agentId,
2871
3740
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -2946,30 +3815,6 @@ var init_session_kill_telemetry = __esm({
2946
3815
  }
2947
3816
  });
2948
3817
 
2949
- // src/lib/task-scope.ts
2950
- function getCurrentSessionScope() {
2951
- try {
2952
- return resolveExeSession();
2953
- } catch {
2954
- return null;
2955
- }
2956
- }
2957
- function sessionScopeFilter(sessionScope, tableAlias) {
2958
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2959
- if (!scope) return { sql: "", args: [] };
2960
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2961
- return {
2962
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2963
- args: [scope]
2964
- };
2965
- }
2966
- var init_task_scope = __esm({
2967
- "src/lib/task-scope.ts"() {
2968
- "use strict";
2969
- init_tmux_routing();
2970
- }
2971
- });
2972
-
2973
3818
  // src/lib/state-bus.ts
2974
3819
  var StateBus, orgBus;
2975
3820
  var init_state_bus = __esm({
@@ -3026,12 +3871,12 @@ var init_state_bus = __esm({
3026
3871
  });
3027
3872
 
3028
3873
  // src/lib/tasks-crud.ts
3029
- import crypto3 from "crypto";
3030
- import path10 from "path";
3031
- import os7 from "os";
3874
+ import crypto4 from "crypto";
3875
+ import path12 from "path";
3876
+ import os9 from "os";
3032
3877
  import { execSync as execSync5 } from "child_process";
3033
3878
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3034
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3879
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3035
3880
  async function writeCheckpoint(input) {
3036
3881
  const client = getClient();
3037
3882
  const row = await resolveTask(client, input.taskId);
@@ -3147,7 +3992,7 @@ async function resolveTask(client, identifier, scopeSession) {
3147
3992
  }
3148
3993
  async function createTaskCore(input) {
3149
3994
  const client = getClient();
3150
- const id = crypto3.randomUUID();
3995
+ const id = crypto4.randomUUID();
3151
3996
  const now = (/* @__PURE__ */ new Date()).toISOString();
3152
3997
  const slug = slugify(input.title);
3153
3998
  let earlySessionScope = null;
@@ -3206,8 +4051,8 @@ ${laneWarning}` : laneWarning;
3206
4051
  }
3207
4052
  if (input.baseDir) {
3208
4053
  try {
3209
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
3210
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
4054
+ await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
4055
+ await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3211
4056
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3212
4057
  await ensureGitignoreExe(input.baseDir);
3213
4058
  } catch {
@@ -3243,13 +4088,19 @@ ${laneWarning}` : laneWarning;
3243
4088
  });
3244
4089
  if (input.baseDir) {
3245
4090
  try {
3246
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
3247
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
3248
- const mdDir = path10.dirname(mdPath);
3249
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
4091
+ const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
4092
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
4093
+ const mdDir = path12.dirname(mdPath);
4094
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3250
4095
  const reviewer = input.reviewer ?? input.assignedBy;
3251
4096
  const mdContent = `# ${input.title}
3252
4097
 
4098
+ ## MANDATORY: When done
4099
+
4100
+ You MUST call update_task with status "done" and a result summary when finished.
4101
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4102
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4103
+
3253
4104
  **ID:** ${id}
3254
4105
  **Status:** ${initialStatus}
3255
4106
  **Priority:** ${input.priority}
@@ -3263,12 +4114,6 @@ ${laneWarning}` : laneWarning;
3263
4114
  ## Context
3264
4115
 
3265
4116
  ${input.context}
3266
-
3267
- ## MANDATORY: When done
3268
-
3269
- You MUST call update_task with status "done" and a result summary when finished.
3270
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3271
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3272
4117
  `;
3273
4118
  await writeFile3(mdPath, mdContent, "utf-8");
3274
4119
  } catch (err) {
@@ -3517,7 +4362,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3517
4362
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3518
4363
  } catch {
3519
4364
  }
3520
- if (input.status === "done" || input.status === "cancelled") {
4365
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3521
4366
  try {
3522
4367
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3523
4368
  clearQueueForAgent2(String(row.assigned_to));
@@ -3546,9 +4391,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3546
4391
  return { taskFile, assignedTo, assignedBy, taskSlug };
3547
4392
  }
3548
4393
  async function ensureArchitectureDoc(baseDir, projectName) {
3549
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
4394
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3550
4395
  try {
3551
- if (existsSync10(archPath)) return;
4396
+ if (existsSync12(archPath)) return;
3552
4397
  const template = [
3553
4398
  `# ${projectName} \u2014 System Architecture`,
3554
4399
  "",
@@ -3581,10 +4426,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3581
4426
  }
3582
4427
  }
3583
4428
  async function ensureGitignoreExe(baseDir) {
3584
- const gitignorePath = path10.join(baseDir, ".gitignore");
4429
+ const gitignorePath = path12.join(baseDir, ".gitignore");
3585
4430
  try {
3586
- if (existsSync10(gitignorePath)) {
3587
- const content = readFileSync10(gitignorePath, "utf-8");
4431
+ if (existsSync12(gitignorePath)) {
4432
+ const content = readFileSync11(gitignorePath, "utf-8");
3588
4433
  if (/^\/?exe\/?$/m.test(content)) return;
3589
4434
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3590
4435
  } else {
@@ -3627,8 +4472,8 @@ __export(tasks_review_exports, {
3627
4472
  isStale: () => isStale,
3628
4473
  listPendingReviews: () => listPendingReviews
3629
4474
  });
3630
- import path11 from "path";
3631
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4475
+ import path13 from "path";
4476
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3632
4477
  function formatAge(isoTimestamp) {
3633
4478
  if (!isoTimestamp) return "";
3634
4479
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -3646,54 +4491,38 @@ function isStale(isoTimestamp) {
3646
4491
  }
3647
4492
  async function countPendingReviews(sessionScope) {
3648
4493
  const client = getClient();
3649
- if (sessionScope) {
3650
- const result2 = await client.execute({
3651
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3652
- args: [sessionScope]
3653
- });
3654
- return Number(result2.rows[0]?.cnt) || 0;
3655
- }
4494
+ const scope = strictSessionScopeFilter(
4495
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4496
+ );
3656
4497
  const result = await client.execute({
3657
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3658
- args: []
4498
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4499
+ WHERE status = 'needs_review'${scope.sql}`,
4500
+ args: [...scope.args]
3659
4501
  });
3660
4502
  return Number(result.rows[0]?.cnt) || 0;
3661
4503
  }
3662
4504
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3663
4505
  const client = getClient();
3664
- if (sessionScope) {
3665
- const result2 = await client.execute({
3666
- sql: `SELECT COUNT(*) as cnt FROM tasks
3667
- WHERE status = 'needs_review' AND updated_at > ?
3668
- AND session_scope = ?`,
3669
- args: [sinceIso, sessionScope]
3670
- });
3671
- return Number(result2.rows[0]?.cnt) || 0;
3672
- }
4506
+ const scope = strictSessionScopeFilter(
4507
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4508
+ );
3673
4509
  const result = await client.execute({
3674
4510
  sql: `SELECT COUNT(*) as cnt FROM tasks
3675
- WHERE status = 'needs_review' AND updated_at > ?`,
3676
- args: [sinceIso]
4511
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4512
+ args: [sinceIso, ...scope.args]
3677
4513
  });
3678
4514
  return Number(result.rows[0]?.cnt) || 0;
3679
4515
  }
3680
4516
  async function listPendingReviews(limit, sessionScope) {
3681
4517
  const client = getClient();
3682
- if (sessionScope) {
3683
- const result2 = await client.execute({
3684
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3685
- WHERE status = 'needs_review'
3686
- AND session_scope = ?
3687
- ORDER BY updated_at ASC LIMIT ?`,
3688
- args: [sessionScope, limit]
3689
- });
3690
- return result2.rows;
3691
- }
4518
+ const scope = strictSessionScopeFilter(
4519
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4520
+ );
3692
4521
  const result = await client.execute({
3693
4522
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3694
- WHERE status = 'needs_review'
4523
+ WHERE status = 'needs_review'${scope.sql}
3695
4524
  ORDER BY updated_at ASC LIMIT ?`,
3696
- args: [limit]
4525
+ args: [...scope.args, limit]
3697
4526
  });
3698
4527
  return result.rows;
3699
4528
  }
@@ -3705,7 +4534,7 @@ async function cleanupOrphanedReviews() {
3705
4534
  WHERE status IN ('open', 'needs_review', 'in_progress')
3706
4535
  AND assigned_by = 'system'
3707
4536
  AND title LIKE 'Review:%'
3708
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4537
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3709
4538
  args: [now]
3710
4539
  });
3711
4540
  const r1b = await client.execute({
@@ -3913,11 +4742,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3913
4742
  );
3914
4743
  }
3915
4744
  try {
3916
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3917
- if (existsSync11(cacheDir)) {
4745
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4746
+ if (existsSync13(cacheDir)) {
3918
4747
  for (const f of readdirSync2(cacheDir)) {
3919
4748
  if (f.startsWith("review-notified-")) {
3920
- unlinkSync4(path11.join(cacheDir, f));
4749
+ unlinkSync4(path13.join(cacheDir, f));
3921
4750
  }
3922
4751
  }
3923
4752
  }
@@ -3934,11 +4763,12 @@ var init_tasks_review = __esm({
3934
4763
  init_tmux_routing();
3935
4764
  init_session_key();
3936
4765
  init_state_bus();
4766
+ init_task_scope();
3937
4767
  }
3938
4768
  });
3939
4769
 
3940
4770
  // src/lib/tasks-chain.ts
3941
- import path12 from "path";
4771
+ import path14 from "path";
3942
4772
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3943
4773
  async function cascadeUnblock(taskId, baseDir, now) {
3944
4774
  const client = getClient();
@@ -3955,7 +4785,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3955
4785
  });
3956
4786
  for (const ur of unblockedRows.rows) {
3957
4787
  try {
3958
- const ubFile = path12.join(baseDir, String(ur.task_file));
4788
+ const ubFile = path14.join(baseDir, String(ur.task_file));
3959
4789
  let ubContent = await readFile3(ubFile, "utf-8");
3960
4790
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3961
4791
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3990,7 +4820,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3990
4820
  const scScope = sessionScopeFilter();
3991
4821
  const remaining = await client.execute({
3992
4822
  sql: `SELECT COUNT(*) as cnt FROM tasks
3993
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4823
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3994
4824
  args: [parentTaskId, ...scScope.args]
3995
4825
  });
3996
4826
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4024,7 +4854,7 @@ var init_tasks_chain = __esm({
4024
4854
 
4025
4855
  // src/lib/project-name.ts
4026
4856
  import { execSync as execSync6 } from "child_process";
4027
- import path13 from "path";
4857
+ import path15 from "path";
4028
4858
  function getProjectName(cwd) {
4029
4859
  const dir = cwd ?? process.cwd();
4030
4860
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4037,7 +4867,7 @@ function getProjectName(cwd) {
4037
4867
  timeout: 2e3,
4038
4868
  stdio: ["pipe", "pipe", "pipe"]
4039
4869
  }).trim();
4040
- repoRoot = path13.dirname(gitCommonDir);
4870
+ repoRoot = path15.dirname(gitCommonDir);
4041
4871
  } catch {
4042
4872
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4043
4873
  cwd: dir,
@@ -4046,11 +4876,11 @@ function getProjectName(cwd) {
4046
4876
  stdio: ["pipe", "pipe", "pipe"]
4047
4877
  }).trim();
4048
4878
  }
4049
- _cached2 = path13.basename(repoRoot);
4879
+ _cached2 = path15.basename(repoRoot);
4050
4880
  _cachedCwd = dir;
4051
4881
  return _cached2;
4052
4882
  } catch {
4053
- _cached2 = path13.basename(dir);
4883
+ _cached2 = path15.basename(dir);
4054
4884
  _cachedCwd = dir;
4055
4885
  return _cached2;
4056
4886
  }
@@ -4193,10 +5023,10 @@ var init_tasks_notify = __esm({
4193
5023
  });
4194
5024
 
4195
5025
  // src/lib/behaviors.ts
4196
- import crypto4 from "crypto";
5026
+ import crypto5 from "crypto";
4197
5027
  async function storeBehavior(opts) {
4198
5028
  const client = getClient();
4199
- const id = crypto4.randomUUID();
5029
+ const id = crypto5.randomUUID();
4200
5030
  const now = (/* @__PURE__ */ new Date()).toISOString();
4201
5031
  await client.execute({
4202
5032
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4225,7 +5055,7 @@ __export(skill_learning_exports, {
4225
5055
  storeTrajectory: () => storeTrajectory,
4226
5056
  sweepTrajectories: () => sweepTrajectories
4227
5057
  });
4228
- import crypto5 from "crypto";
5058
+ import crypto6 from "crypto";
4229
5059
  async function extractTrajectory(taskId, agentId) {
4230
5060
  const client = getClient();
4231
5061
  const result = await client.execute({
@@ -4254,11 +5084,11 @@ async function extractTrajectory(taskId, agentId) {
4254
5084
  return signature;
4255
5085
  }
4256
5086
  function hashSignature(signature) {
4257
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5087
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4258
5088
  }
4259
5089
  async function storeTrajectory(opts) {
4260
5090
  const client = getClient();
4261
- const id = crypto5.randomUUID();
5091
+ const id = crypto6.randomUUID();
4262
5092
  const now = (/* @__PURE__ */ new Date()).toISOString();
4263
5093
  const signatureHash = hashSignature(opts.signature);
4264
5094
  await client.execute({
@@ -4523,8 +5353,8 @@ __export(tasks_exports, {
4523
5353
  updateTaskStatus: () => updateTaskStatus,
4524
5354
  writeCheckpoint: () => writeCheckpoint
4525
5355
  });
4526
- import path14 from "path";
4527
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5356
+ import path16 from "path";
5357
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4528
5358
  async function createTask(input) {
4529
5359
  const result = await createTaskCore(input);
4530
5360
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4543,12 +5373,12 @@ async function updateTask(input) {
4543
5373
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4544
5374
  try {
4545
5375
  const agent = String(row.assigned_to);
4546
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4547
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
5376
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5377
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4548
5378
  if (input.status === "in_progress") {
4549
5379
  mkdirSync5(cacheDir, { recursive: true });
4550
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4551
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5380
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5381
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4552
5382
  try {
4553
5383
  unlinkSync5(cachePath);
4554
5384
  } catch {
@@ -4556,10 +5386,10 @@ async function updateTask(input) {
4556
5386
  }
4557
5387
  } catch {
4558
5388
  }
4559
- if (input.status === "done") {
5389
+ if (input.status === "done" || input.status === "closed") {
4560
5390
  await cleanupReviewFile(row, taskFile, input.baseDir);
4561
5391
  }
4562
- if (input.status === "done" || input.status === "cancelled") {
5392
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4563
5393
  try {
4564
5394
  const client = getClient();
4565
5395
  const taskTitle = String(row.title);
@@ -4575,7 +5405,7 @@ async function updateTask(input) {
4575
5405
  if (!isCoordinatorName(assignedAgent)) {
4576
5406
  try {
4577
5407
  const draftClient = getClient();
4578
- if (input.status === "done") {
5408
+ if (input.status === "done" || input.status === "closed") {
4579
5409
  await draftClient.execute({
4580
5410
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4581
5411
  args: [assignedAgent]
@@ -4592,7 +5422,7 @@ async function updateTask(input) {
4592
5422
  try {
4593
5423
  const client = getClient();
4594
5424
  const cascaded = await client.execute({
4595
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5425
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4596
5426
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4597
5427
  args: [now, taskId]
4598
5428
  });
@@ -4605,14 +5435,14 @@ async function updateTask(input) {
4605
5435
  } catch {
4606
5436
  }
4607
5437
  }
4608
- const isTerminal = input.status === "done" || input.status === "needs_review";
5438
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4609
5439
  if (isTerminal) {
4610
5440
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4611
5441
  if (!isCoordinator) {
4612
5442
  notifyTaskDone();
4613
5443
  }
4614
5444
  await markTaskNotificationsRead(taskFile);
4615
- if (input.status === "done") {
5445
+ if (input.status === "done" || input.status === "closed") {
4616
5446
  try {
4617
5447
  await cascadeUnblock(taskId, input.baseDir, now);
4618
5448
  } catch {
@@ -4632,7 +5462,7 @@ async function updateTask(input) {
4632
5462
  }
4633
5463
  }
4634
5464
  }
4635
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5465
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4636
5466
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4637
5467
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4638
5468
  taskId,
@@ -5004,6 +5834,7 @@ __export(tmux_routing_exports, {
5004
5834
  isEmployeeAlive: () => isEmployeeAlive,
5005
5835
  isExeSession: () => isExeSession,
5006
5836
  isSessionBusy: () => isSessionBusy,
5837
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5007
5838
  notifyParentExe: () => notifyParentExe,
5008
5839
  parseParentExe: () => parseParentExe,
5009
5840
  registerParentExe: () => registerParentExe,
@@ -5014,13 +5845,13 @@ __export(tmux_routing_exports, {
5014
5845
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5015
5846
  });
5016
5847
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5017
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5018
- import path15 from "path";
5019
- import os8 from "os";
5848
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5849
+ import path17 from "path";
5850
+ import os10 from "os";
5020
5851
  import { fileURLToPath as fileURLToPath2 } from "url";
5021
5852
  import { unlinkSync as unlinkSync6 } from "fs";
5022
5853
  function spawnLockPath(sessionName) {
5023
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5854
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5024
5855
  }
5025
5856
  function isProcessAlive(pid) {
5026
5857
  try {
@@ -5031,13 +5862,13 @@ function isProcessAlive(pid) {
5031
5862
  }
5032
5863
  }
5033
5864
  function acquireSpawnLock2(sessionName) {
5034
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5865
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5035
5866
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5036
5867
  }
5037
5868
  const lockFile = spawnLockPath(sessionName);
5038
- if (existsSync12(lockFile)) {
5869
+ if (existsSync14(lockFile)) {
5039
5870
  try {
5040
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5871
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5041
5872
  const age = Date.now() - lock.timestamp;
5042
5873
  if (isProcessAlive(lock.pid) && age < 6e4) {
5043
5874
  return false;
@@ -5045,7 +5876,7 @@ function acquireSpawnLock2(sessionName) {
5045
5876
  } catch {
5046
5877
  }
5047
5878
  }
5048
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5879
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5049
5880
  return true;
5050
5881
  }
5051
5882
  function releaseSpawnLock2(sessionName) {
@@ -5057,13 +5888,13 @@ function releaseSpawnLock2(sessionName) {
5057
5888
  function resolveBehaviorsExporterScript() {
5058
5889
  try {
5059
5890
  const thisFile = fileURLToPath2(import.meta.url);
5060
- const scriptPath = path15.join(
5061
- path15.dirname(thisFile),
5891
+ const scriptPath = path17.join(
5892
+ path17.dirname(thisFile),
5062
5893
  "..",
5063
5894
  "bin",
5064
5895
  "exe-export-behaviors.js"
5065
5896
  );
5066
- return existsSync12(scriptPath) ? scriptPath : null;
5897
+ return existsSync14(scriptPath) ? scriptPath : null;
5067
5898
  } catch {
5068
5899
  return null;
5069
5900
  }
@@ -5129,12 +5960,12 @@ function extractRootExe(name) {
5129
5960
  return parts.length > 0 ? parts[parts.length - 1] : null;
5130
5961
  }
5131
5962
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5132
- if (!existsSync12(SESSION_CACHE)) {
5963
+ if (!existsSync14(SESSION_CACHE)) {
5133
5964
  mkdirSync6(SESSION_CACHE, { recursive: true });
5134
5965
  }
5135
5966
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5136
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5137
- writeFileSync7(filePath, JSON.stringify({
5967
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5968
+ writeFileSync8(filePath, JSON.stringify({
5138
5969
  parentExe: rootExe,
5139
5970
  dispatchedBy: dispatchedBy || rootExe,
5140
5971
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5142,7 +5973,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5142
5973
  }
5143
5974
  function getParentExe(sessionKey) {
5144
5975
  try {
5145
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5976
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5146
5977
  return data.parentExe || null;
5147
5978
  } catch {
5148
5979
  return null;
@@ -5150,8 +5981,8 @@ function getParentExe(sessionKey) {
5150
5981
  }
5151
5982
  function getDispatchedBy(sessionKey) {
5152
5983
  try {
5153
- const data = JSON.parse(readFileSync11(
5154
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5984
+ const data = JSON.parse(readFileSync12(
5985
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5155
5986
  "utf8"
5156
5987
  ));
5157
5988
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5221,8 +6052,8 @@ async function verifyPaneAtCapacity(sessionName) {
5221
6052
  }
5222
6053
  function readDebounceState() {
5223
6054
  try {
5224
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5225
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6055
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
6056
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5226
6057
  const state = {};
5227
6058
  for (const [key, val] of Object.entries(raw)) {
5228
6059
  if (typeof val === "number") {
@@ -5238,8 +6069,8 @@ function readDebounceState() {
5238
6069
  }
5239
6070
  function writeDebounceState(state) {
5240
6071
  try {
5241
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5242
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6072
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
6073
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5243
6074
  } catch {
5244
6075
  }
5245
6076
  }
@@ -5337,8 +6168,8 @@ function sendIntercom(targetSession) {
5337
6168
  try {
5338
6169
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5339
6170
  const agent = baseAgentName(rawAgent);
5340
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
5341
- if (existsSync12(markerPath)) {
6171
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6172
+ if (existsSync14(markerPath)) {
5342
6173
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5343
6174
  return "debounced";
5344
6175
  }
@@ -5347,8 +6178,8 @@ function sendIntercom(targetSession) {
5347
6178
  try {
5348
6179
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5349
6180
  const agent = baseAgentName(rawAgent);
5350
- const taskDir = path15.join(process.cwd(), "exe", agent);
5351
- if (existsSync12(taskDir)) {
6181
+ const taskDir = path17.join(process.cwd(), "exe", agent);
6182
+ if (existsSync14(taskDir)) {
5352
6183
  const files = readdirSync3(taskDir).filter(
5353
6184
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5354
6185
  );
@@ -5408,6 +6239,21 @@ function notifyParentExe(sessionKey) {
5408
6239
  }
5409
6240
  return true;
5410
6241
  }
6242
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6243
+ const transport = getTransport();
6244
+ try {
6245
+ const sessions = transport.listSessions();
6246
+ if (!sessions.includes(coordinatorSession)) return false;
6247
+ execSync7(
6248
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6249
+ { timeout: 3e3 }
6250
+ );
6251
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6252
+ return true;
6253
+ } catch {
6254
+ return false;
6255
+ }
6256
+ }
5411
6257
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5412
6258
  if (isCoordinatorName(employeeName)) {
5413
6259
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5481,26 +6327,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5481
6327
  const transport = getTransport();
5482
6328
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5483
6329
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5484
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5485
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5486
- if (!existsSync12(logDir)) {
6330
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
6331
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6332
+ if (!existsSync14(logDir)) {
5487
6333
  mkdirSync6(logDir, { recursive: true });
5488
6334
  }
5489
6335
  transport.kill(sessionName);
5490
6336
  let cleanupSuffix = "";
5491
6337
  try {
5492
6338
  const thisFile = fileURLToPath2(import.meta.url);
5493
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5494
- if (existsSync12(cleanupScript)) {
6339
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6340
+ if (existsSync14(cleanupScript)) {
5495
6341
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5496
6342
  }
5497
6343
  } catch {
5498
6344
  }
5499
6345
  try {
5500
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
6346
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
5501
6347
  let claudeJson = {};
5502
6348
  try {
5503
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
6349
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5504
6350
  } catch {
5505
6351
  }
5506
6352
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5508,17 +6354,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5508
6354
  const trustDir = opts?.cwd ?? projectDir;
5509
6355
  if (!projects[trustDir]) projects[trustDir] = {};
5510
6356
  projects[trustDir].hasTrustDialogAccepted = true;
5511
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6357
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5512
6358
  } catch {
5513
6359
  }
5514
6360
  try {
5515
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
6361
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
5516
6362
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5517
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
5518
- const settingsPath = path15.join(projSettingsDir, "settings.json");
6363
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6364
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
5519
6365
  let settings = {};
5520
6366
  try {
5521
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6367
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5522
6368
  } catch {
5523
6369
  }
5524
6370
  const perms = settings.permissions ?? {};
@@ -5547,7 +6393,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5547
6393
  perms.allow = allow;
5548
6394
  settings.permissions = perms;
5549
6395
  mkdirSync6(projSettingsDir, { recursive: true });
5550
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6396
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5551
6397
  }
5552
6398
  } catch {
5553
6399
  }
@@ -5562,8 +6408,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5562
6408
  let behaviorsFlag = "";
5563
6409
  let legacyFallbackWarned = false;
5564
6410
  if (!useExeAgent && !useBinSymlink) {
5565
- const identityPath = path15.join(
5566
- os8.homedir(),
6411
+ const identityPath = path17.join(
6412
+ os10.homedir(),
5567
6413
  ".exe-os",
5568
6414
  "identity",
5569
6415
  `${employeeName}.md`
@@ -5572,13 +6418,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5572
6418
  const hasAgentFlag = claudeSupportsAgentFlag();
5573
6419
  if (hasAgentFlag) {
5574
6420
  identityFlag = ` --agent ${employeeName}`;
5575
- } else if (existsSync12(identityPath)) {
6421
+ } else if (existsSync14(identityPath)) {
5576
6422
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5577
6423
  legacyFallbackWarned = true;
5578
6424
  }
5579
6425
  const behaviorsFile = exportBehaviorsSync(
5580
6426
  employeeName,
5581
- path15.basename(spawnCwd),
6427
+ path17.basename(spawnCwd),
5582
6428
  sessionName
5583
6429
  );
5584
6430
  if (behaviorsFile) {
@@ -5593,16 +6439,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5593
6439
  }
5594
6440
  let sessionContextFlag = "";
5595
6441
  try {
5596
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
6442
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
5597
6443
  mkdirSync6(ctxDir, { recursive: true });
5598
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
6444
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
5599
6445
  const ctxContent = [
5600
6446
  `## Session Context`,
5601
6447
  `You are running in tmux session: ${sessionName}.`,
5602
6448
  `Your parent coordinator session is ${exeSession}.`,
5603
6449
  `Your employees (if any) use the -${exeSession} suffix.`
5604
6450
  ].join("\n");
5605
- writeFileSync7(ctxFile, ctxContent);
6451
+ writeFileSync8(ctxFile, ctxContent);
5606
6452
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5607
6453
  } catch {
5608
6454
  }
@@ -5679,8 +6525,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5679
6525
  transport.pipeLog(sessionName, logFile);
5680
6526
  try {
5681
6527
  const mySession = getMySession();
5682
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5683
- writeFileSync7(dispatchInfo, JSON.stringify({
6528
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6529
+ writeFileSync8(dispatchInfo, JSON.stringify({
5684
6530
  dispatchedBy: mySession,
5685
6531
  rootExe: exeSession,
5686
6532
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5754,15 +6600,15 @@ var init_tmux_routing = __esm({
5754
6600
  init_intercom_queue();
5755
6601
  init_plan_limits();
5756
6602
  init_employees();
5757
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5758
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
6603
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
6604
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
5759
6605
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5760
6606
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5761
6607
  VERIFY_PANE_LINES = 200;
5762
6608
  INTERCOM_DEBOUNCE_MS = 3e4;
5763
6609
  CODEX_DEBOUNCE_MS = 12e4;
5764
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5765
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
6610
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
6611
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5766
6612
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5767
6613
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5768
6614
  }
@@ -6046,9 +6892,9 @@ __export(agent_signals_exports, {
6046
6892
  hasOpenTasks: () => hasOpenTasks,
6047
6893
  hasUnreadInbox: () => hasUnreadInbox
6048
6894
  });
6049
- import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
6050
- import os9 from "os";
6051
- import path16 from "path";
6895
+ import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
6896
+ import os11 from "os";
6897
+ import path18 from "path";
6052
6898
  async function hasOpenTasks(client, agentId) {
6053
6899
  try {
6054
6900
  const scope = sessionScopeFilter(null);
@@ -6090,10 +6936,10 @@ async function hasUnreadInbox(client, agentId) {
6090
6936
  return CONSERVATIVE_ON_ERROR;
6091
6937
  }
6092
6938
  }
6093
- function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path16.join(os9.homedir(), ".exe-os", "intercom.log")) {
6094
- if (!existsSync13(intercomLog)) return false;
6939
+ function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path18.join(os11.homedir(), ".exe-os", "intercom.log")) {
6940
+ if (!existsSync15(intercomLog)) return false;
6095
6941
  try {
6096
- const raw = readFileSync12(intercomLog, "utf8");
6942
+ const raw = readFileSync13(intercomLog, "utf8");
6097
6943
  const lines = raw.split("\n");
6098
6944
  for (let i = lines.length - 1; i >= 0; i--) {
6099
6945
  const line = lines[i];
@@ -6166,7 +7012,7 @@ __export(daemon_orchestration_exports, {
6166
7012
  shouldNudgeEmployee: () => shouldNudgeEmployee
6167
7013
  });
6168
7014
  import { execSync as execSync9 } from "child_process";
6169
- import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
7015
+ import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
6170
7016
  import { homedir } from "os";
6171
7017
  import { join } from "path";
6172
7018
  function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
@@ -6357,8 +7203,8 @@ async function pollReviewNudge(deps, state) {
6357
7203
  function loadNudgeState() {
6358
7204
  const state = { lastNudge: /* @__PURE__ */ new Map() };
6359
7205
  try {
6360
- if (!existsSync14(NUDGE_STATE_PATH)) return state;
6361
- const raw = JSON.parse(readFileSync13(NUDGE_STATE_PATH, "utf8"));
7206
+ if (!existsSync16(NUDGE_STATE_PATH)) return state;
7207
+ const raw = JSON.parse(readFileSync14(NUDGE_STATE_PATH, "utf8"));
6362
7208
  if (Array.isArray(raw)) {
6363
7209
  for (const [key, val] of raw) {
6364
7210
  if (key && typeof val?.at === "number" && typeof val?.count === "number") {
@@ -6372,7 +7218,7 @@ function loadNudgeState() {
6372
7218
  }
6373
7219
  function saveNudgeState(state) {
6374
7220
  const entries = Array.from(state.lastNudge.entries());
6375
- writeFileSync8(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
7221
+ writeFileSync9(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
6376
7222
  }
6377
7223
  function createReviewNudgeRealDeps(getClient2) {
6378
7224
  return {
@@ -6420,7 +7266,7 @@ function createIdleNudgeRealDeps(getClient2) {
6420
7266
  const doScope = sessionScopeFilter(null);
6421
7267
  const result = await client.execute({
6422
7268
  sql: `SELECT id, title, priority FROM tasks
6423
- WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'needs_review')${doScope.sql}
7269
+ WHERE assigned_to = ? AND status IN ('open', 'in_progress')${doScope.sql}
6424
7270
  ORDER BY CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 ELSE 2 END
6425
7271
  LIMIT 1`,
6426
7272
  args: [agentId, ...doScope.args]
@@ -6710,134 +7556,12 @@ var init_daemon_orchestration = __esm({
6710
7556
  }
6711
7557
  });
6712
7558
 
6713
- // src/lib/keychain.ts
6714
- var keychain_exports = {};
6715
- __export(keychain_exports, {
6716
- deleteMasterKey: () => deleteMasterKey,
6717
- exportMnemonic: () => exportMnemonic,
6718
- getMasterKey: () => getMasterKey,
6719
- importMnemonic: () => importMnemonic,
6720
- setMasterKey: () => setMasterKey
6721
- });
6722
- import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
6723
- import { existsSync as existsSync15 } from "fs";
6724
- import path17 from "path";
6725
- import os10 from "os";
6726
- function getKeyDir() {
6727
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
6728
- }
6729
- function getKeyPath() {
6730
- return path17.join(getKeyDir(), "master.key");
6731
- }
6732
- async function tryKeytar() {
6733
- try {
6734
- return await import("keytar");
6735
- } catch {
6736
- return null;
6737
- }
6738
- }
6739
- async function getMasterKey() {
6740
- const keytar = await tryKeytar();
6741
- if (keytar) {
6742
- try {
6743
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
6744
- if (stored) {
6745
- return Buffer.from(stored, "base64");
6746
- }
6747
- } catch {
6748
- }
6749
- }
6750
- const keyPath = getKeyPath();
6751
- if (!existsSync15(keyPath)) {
6752
- process.stderr.write(
6753
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6754
- `
6755
- );
6756
- return null;
6757
- }
6758
- try {
6759
- const content = await readFile4(keyPath, "utf-8");
6760
- return Buffer.from(content.trim(), "base64");
6761
- } catch (err) {
6762
- process.stderr.write(
6763
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
6764
- `
6765
- );
6766
- return null;
6767
- }
6768
- }
6769
- async function setMasterKey(key) {
6770
- const b64 = key.toString("base64");
6771
- const keytar = await tryKeytar();
6772
- if (keytar) {
6773
- try {
6774
- await keytar.setPassword(SERVICE, ACCOUNT, b64);
6775
- return;
6776
- } catch {
6777
- }
6778
- }
6779
- const dir = getKeyDir();
6780
- await mkdir4(dir, { recursive: true });
6781
- const keyPath = getKeyPath();
6782
- await writeFile5(keyPath, b64 + "\n", "utf-8");
6783
- await chmod2(keyPath, 384);
6784
- }
6785
- async function deleteMasterKey() {
6786
- const keytar = await tryKeytar();
6787
- if (keytar) {
6788
- try {
6789
- await keytar.deletePassword(SERVICE, ACCOUNT);
6790
- } catch {
6791
- }
6792
- }
6793
- const keyPath = getKeyPath();
6794
- if (existsSync15(keyPath)) {
6795
- await unlink(keyPath);
6796
- }
6797
- }
6798
- async function loadBip39() {
6799
- try {
6800
- return await import("bip39");
6801
- } catch {
6802
- throw new Error(
6803
- "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
6804
- );
6805
- }
6806
- }
6807
- async function exportMnemonic(key) {
6808
- if (key.length !== 32) {
6809
- throw new Error(`Key must be 32 bytes, got ${key.length}`);
6810
- }
6811
- const { entropyToMnemonic } = await loadBip39();
6812
- return entropyToMnemonic(key.toString("hex"));
6813
- }
6814
- async function importMnemonic(mnemonic) {
6815
- const trimmed = mnemonic.trim();
6816
- const words = trimmed.split(/\s+/);
6817
- if (words.length !== 24) {
6818
- throw new Error(`Expected 24 words, got ${words.length}`);
6819
- }
6820
- const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
6821
- if (!validateMnemonic(trimmed)) {
6822
- throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
6823
- }
6824
- const entropy = mnemonicToEntropy(trimmed);
6825
- return Buffer.from(entropy, "hex");
6826
- }
6827
- var SERVICE, ACCOUNT;
6828
- var init_keychain = __esm({
6829
- "src/lib/keychain.ts"() {
6830
- "use strict";
6831
- SERVICE = "exe-mem";
6832
- ACCOUNT = "master-key";
6833
- }
6834
- });
6835
-
6836
7559
  // src/lib/shard-manager.ts
6837
7560
  var shard_manager_exports = {};
6838
7561
  __export(shard_manager_exports, {
6839
7562
  disposeShards: () => disposeShards,
6840
7563
  ensureShardSchema: () => ensureShardSchema,
7564
+ getOpenShardCount: () => getOpenShardCount,
6841
7565
  getReadyShardClient: () => getReadyShardClient,
6842
7566
  getShardClient: () => getShardClient,
6843
7567
  getShardsDir: () => getShardsDir,
@@ -6846,15 +7570,18 @@ __export(shard_manager_exports, {
6846
7570
  listShards: () => listShards,
6847
7571
  shardExists: () => shardExists
6848
7572
  });
6849
- import path18 from "path";
6850
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
7573
+ import path19 from "path";
7574
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6851
7575
  import { createClient as createClient2 } from "@libsql/client";
6852
7576
  function initShardManager(encryptionKey) {
6853
7577
  _encryptionKey = encryptionKey;
6854
- if (!existsSync16(SHARDS_DIR)) {
7578
+ if (!existsSync17(SHARDS_DIR)) {
6855
7579
  mkdirSync7(SHARDS_DIR, { recursive: true });
6856
7580
  }
6857
7581
  _shardingEnabled = true;
7582
+ if (_evictionTimer) clearInterval(_evictionTimer);
7583
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
7584
+ _evictionTimer.unref();
6858
7585
  }
6859
7586
  function isShardingEnabled() {
6860
7587
  return _shardingEnabled;
@@ -6871,21 +7598,28 @@ function getShardClient(projectName) {
6871
7598
  throw new Error(`Invalid project name for shard: "${projectName}"`);
6872
7599
  }
6873
7600
  const cached = _shards.get(safeName);
6874
- if (cached) return cached;
6875
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
7601
+ if (cached) {
7602
+ _shardLastAccess.set(safeName, Date.now());
7603
+ return cached;
7604
+ }
7605
+ while (_shards.size >= MAX_OPEN_SHARDS) {
7606
+ evictLRU();
7607
+ }
7608
+ const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
6876
7609
  const client = createClient2({
6877
7610
  url: `file:${dbPath}`,
6878
7611
  encryptionKey: _encryptionKey
6879
7612
  });
6880
7613
  _shards.set(safeName, client);
7614
+ _shardLastAccess.set(safeName, Date.now());
6881
7615
  return client;
6882
7616
  }
6883
7617
  function shardExists(projectName) {
6884
7618
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
6885
- return existsSync16(path18.join(SHARDS_DIR, `${safeName}.db`));
7619
+ return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
6886
7620
  }
6887
7621
  function listShards() {
6888
- if (!existsSync16(SHARDS_DIR)) return [];
7622
+ if (!existsSync17(SHARDS_DIR)) return [];
6889
7623
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6890
7624
  }
6891
7625
  async function ensureShardSchema(client) {
@@ -6937,6 +7671,8 @@ async function ensureShardSchema(client) {
6937
7671
  for (const col of [
6938
7672
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
6939
7673
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
7674
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
7675
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
6940
7676
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
6941
7677
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
6942
7678
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6959,7 +7695,23 @@ async function ensureShardSchema(client) {
6959
7695
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
6960
7696
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
6961
7697
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
6962
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
7698
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
7699
+ // Metadata enrichment columns (must match database.ts)
7700
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
7701
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
7702
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
7703
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
7704
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
7705
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
7706
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
7707
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
7708
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
7709
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
7710
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
7711
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
7712
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
7713
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
7714
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
6963
7715
  ]) {
6964
7716
  try {
6965
7717
  await client.execute(col);
@@ -7053,31 +7805,202 @@ async function ensureShardSchema(client) {
7053
7805
  }
7054
7806
  }
7055
7807
  }
7056
- async function getReadyShardClient(projectName) {
7057
- const client = getShardClient(projectName);
7058
- await ensureShardSchema(client);
7059
- return client;
7808
+ async function getReadyShardClient(projectName) {
7809
+ const client = getShardClient(projectName);
7810
+ await ensureShardSchema(client);
7811
+ return client;
7812
+ }
7813
+ function evictLRU() {
7814
+ let oldest = null;
7815
+ let oldestTime = Infinity;
7816
+ for (const [name, time] of _shardLastAccess) {
7817
+ if (time < oldestTime) {
7818
+ oldestTime = time;
7819
+ oldest = name;
7820
+ }
7821
+ }
7822
+ if (oldest) {
7823
+ const client = _shards.get(oldest);
7824
+ if (client) {
7825
+ client.close();
7826
+ }
7827
+ _shards.delete(oldest);
7828
+ _shardLastAccess.delete(oldest);
7829
+ }
7830
+ }
7831
+ function evictIdleShards() {
7832
+ const now = Date.now();
7833
+ const toEvict = [];
7834
+ for (const [name, lastAccess] of _shardLastAccess) {
7835
+ if (now - lastAccess > SHARD_IDLE_MS) {
7836
+ toEvict.push(name);
7837
+ }
7838
+ }
7839
+ for (const name of toEvict) {
7840
+ const client = _shards.get(name);
7841
+ if (client) {
7842
+ client.close();
7843
+ }
7844
+ _shards.delete(name);
7845
+ _shardLastAccess.delete(name);
7846
+ }
7847
+ }
7848
+ function getOpenShardCount() {
7849
+ return _shards.size;
7060
7850
  }
7061
7851
  function disposeShards() {
7852
+ if (_evictionTimer) {
7853
+ clearInterval(_evictionTimer);
7854
+ _evictionTimer = null;
7855
+ }
7062
7856
  for (const [, client] of _shards) {
7063
7857
  client.close();
7064
7858
  }
7065
7859
  _shards.clear();
7860
+ _shardLastAccess.clear();
7066
7861
  _shardingEnabled = false;
7067
7862
  _encryptionKey = null;
7068
7863
  }
7069
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
7864
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
7070
7865
  var init_shard_manager = __esm({
7071
7866
  "src/lib/shard-manager.ts"() {
7072
7867
  "use strict";
7073
7868
  init_config();
7074
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
7869
+ SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
7870
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
7871
+ MAX_OPEN_SHARDS = 10;
7872
+ EVICTION_INTERVAL_MS = 60 * 1e3;
7075
7873
  _shards = /* @__PURE__ */ new Map();
7874
+ _shardLastAccess = /* @__PURE__ */ new Map();
7875
+ _evictionTimer = null;
7076
7876
  _encryptionKey = null;
7077
7877
  _shardingEnabled = false;
7078
7878
  }
7079
7879
  });
7080
7880
 
7881
+ // src/lib/keychain.ts
7882
+ var keychain_exports = {};
7883
+ __export(keychain_exports, {
7884
+ deleteMasterKey: () => deleteMasterKey,
7885
+ exportMnemonic: () => exportMnemonic,
7886
+ getMasterKey: () => getMasterKey,
7887
+ importMnemonic: () => importMnemonic,
7888
+ setMasterKey: () => setMasterKey
7889
+ });
7890
+ import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
7891
+ import { existsSync as existsSync18 } from "fs";
7892
+ import path20 from "path";
7893
+ import os12 from "os";
7894
+ function getKeyDir() {
7895
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
7896
+ }
7897
+ function getKeyPath() {
7898
+ return path20.join(getKeyDir(), "master.key");
7899
+ }
7900
+ async function tryKeytar() {
7901
+ try {
7902
+ return await import("keytar");
7903
+ } catch {
7904
+ return null;
7905
+ }
7906
+ }
7907
+ async function getMasterKey() {
7908
+ const keytar = await tryKeytar();
7909
+ if (keytar) {
7910
+ try {
7911
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
7912
+ if (stored) {
7913
+ return Buffer.from(stored, "base64");
7914
+ }
7915
+ } catch {
7916
+ }
7917
+ }
7918
+ const keyPath = getKeyPath();
7919
+ if (!existsSync18(keyPath)) {
7920
+ process.stderr.write(
7921
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
7922
+ `
7923
+ );
7924
+ return null;
7925
+ }
7926
+ try {
7927
+ const content = await readFile4(keyPath, "utf-8");
7928
+ return Buffer.from(content.trim(), "base64");
7929
+ } catch (err) {
7930
+ process.stderr.write(
7931
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
7932
+ `
7933
+ );
7934
+ return null;
7935
+ }
7936
+ }
7937
+ async function setMasterKey(key) {
7938
+ const b64 = key.toString("base64");
7939
+ const keytar = await tryKeytar();
7940
+ if (keytar) {
7941
+ try {
7942
+ await keytar.setPassword(SERVICE, ACCOUNT, b64);
7943
+ return;
7944
+ } catch {
7945
+ }
7946
+ }
7947
+ const dir = getKeyDir();
7948
+ await mkdir4(dir, { recursive: true });
7949
+ const keyPath = getKeyPath();
7950
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
7951
+ await chmod2(keyPath, 384);
7952
+ }
7953
+ async function deleteMasterKey() {
7954
+ const keytar = await tryKeytar();
7955
+ if (keytar) {
7956
+ try {
7957
+ await keytar.deletePassword(SERVICE, ACCOUNT);
7958
+ } catch {
7959
+ }
7960
+ }
7961
+ const keyPath = getKeyPath();
7962
+ if (existsSync18(keyPath)) {
7963
+ await unlink(keyPath);
7964
+ }
7965
+ }
7966
+ async function loadBip39() {
7967
+ try {
7968
+ return await import("bip39");
7969
+ } catch {
7970
+ throw new Error(
7971
+ "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
7972
+ );
7973
+ }
7974
+ }
7975
+ async function exportMnemonic(key) {
7976
+ if (key.length !== 32) {
7977
+ throw new Error(`Key must be 32 bytes, got ${key.length}`);
7978
+ }
7979
+ const { entropyToMnemonic } = await loadBip39();
7980
+ return entropyToMnemonic(key.toString("hex"));
7981
+ }
7982
+ async function importMnemonic(mnemonic) {
7983
+ const trimmed = mnemonic.trim();
7984
+ const words = trimmed.split(/\s+/);
7985
+ if (words.length !== 24) {
7986
+ throw new Error(`Expected 24 words, got ${words.length}`);
7987
+ }
7988
+ const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
7989
+ if (!validateMnemonic(trimmed)) {
7990
+ throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
7991
+ }
7992
+ const entropy = mnemonicToEntropy(trimmed);
7993
+ return Buffer.from(entropy, "hex");
7994
+ }
7995
+ var SERVICE, ACCOUNT;
7996
+ var init_keychain = __esm({
7997
+ "src/lib/keychain.ts"() {
7998
+ "use strict";
7999
+ SERVICE = "exe-mem";
8000
+ ACCOUNT = "master-key";
8001
+ }
8002
+ });
8003
+
7081
8004
  // src/lib/platform-procedures.ts
7082
8005
  var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
7083
8006
  var init_platform_procedures = __esm({
@@ -7850,15 +8773,15 @@ async function pollPendingReviews(deps, state) {
7850
8773
  return [];
7851
8774
  }
7852
8775
  if (sessions.length === 0) return [];
7853
- let reviewCount;
7854
- try {
7855
- reviewCount = await deps.countPendingReviews();
7856
- } catch {
7857
- return [];
7858
- }
7859
- if (reviewCount === 0) return [];
7860
8776
  const sent = [];
7861
8777
  for (const exeSession of sessions) {
8778
+ let reviewCount = 0;
8779
+ try {
8780
+ reviewCount = await deps.countPendingReviews(exeSession);
8781
+ } catch {
8782
+ continue;
8783
+ }
8784
+ if (reviewCount === 0) continue;
7862
8785
  const lastSent = state.lastIntercomSent.get(exeSession) ?? 0;
7863
8786
  if (Date.now() - lastSent < state.intervalMs) continue;
7864
8787
  try {
@@ -7868,15 +8791,17 @@ async function pollPendingReviews(deps, state) {
7868
8791
  } catch {
7869
8792
  }
7870
8793
  }
7871
- try {
7872
- const orphans = await deps.findOrphanedDoneTasks();
7873
- for (const orphan of orphans) {
7874
- try {
7875
- await deps.createReviewForOrphan(orphan);
7876
- } catch {
8794
+ for (const exeSession of sessions) {
8795
+ try {
8796
+ const orphans = await deps.findOrphanedDoneTasks(exeSession);
8797
+ for (const orphan of orphans) {
8798
+ try {
8799
+ await deps.createReviewForOrphan(orphan);
8800
+ } catch {
8801
+ }
7877
8802
  }
8803
+ } catch {
7878
8804
  }
7879
- } catch {
7880
8805
  }
7881
8806
  if (deps.findStaleTasks && deps.sendNudge) {
7882
8807
  try {
@@ -7894,50 +8819,56 @@ async function pollPendingReviews(deps, state) {
7894
8819
  }
7895
8820
  }
7896
8821
  if (deps.findUrgentUnread) {
7897
- try {
7898
- const urgent = await deps.findUrgentUnread();
7899
- for (const msg of urgent) {
7900
- try {
7901
- const employeeSessions = sessions.length > 0 ? deps.listTmuxSessions().filter((s) => s.startsWith(`${msg.target_agent}-`)) : [];
7902
- for (const sess of employeeSessions) {
7903
- deps.sendIntercom(sess);
7904
- }
7905
- const ageMs = Date.now() - new Date(msg.created_at).getTime();
7906
- if (ageMs > 5 * 60 * 1e3) {
7907
- process.stderr.write(
7908
- `[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
7909
- `
8822
+ for (const exeSession of sessions) {
8823
+ try {
8824
+ const urgent = await deps.findUrgentUnread(exeSession);
8825
+ for (const msg of urgent) {
8826
+ try {
8827
+ const employeeSessions = deps.listTmuxSessions().filter(
8828
+ (s) => s.startsWith(`${msg.target_agent}-`) && s.endsWith(`-${exeSession}`)
7910
8829
  );
8830
+ for (const sess of employeeSessions) {
8831
+ deps.sendIntercom(sess);
8832
+ }
8833
+ const ageMs = Date.now() - new Date(msg.created_at).getTime();
8834
+ if (ageMs > 5 * 60 * 1e3) {
8835
+ process.stderr.write(
8836
+ `[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
8837
+ `
8838
+ );
8839
+ }
8840
+ } catch {
7911
8841
  }
7912
- } catch {
7913
8842
  }
8843
+ } catch {
7914
8844
  }
7915
- } catch {
7916
8845
  }
7917
8846
  }
7918
8847
  if (deps.findUnstartedTasks) {
7919
- try {
7920
- const unstarted = await deps.findUnstartedTasks();
7921
- for (const task of unstarted) {
7922
- try {
7923
- const employeeSessions = deps.listTmuxSessions().filter(
7924
- (s) => s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)
7925
- );
7926
- for (const sess of employeeSessions) {
7927
- deps.sendIntercom(sess);
7928
- }
7929
- const ageMs = Date.now() - new Date(task.created_at).getTime();
7930
- const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
7931
- if (ageMs > UNSTARTED_WARNING_MS) {
7932
- process.stderr.write(
7933
- `[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
7934
- `
8848
+ for (const exeSession of sessions) {
8849
+ try {
8850
+ const unstarted = await deps.findUnstartedTasks(exeSession);
8851
+ for (const task of unstarted) {
8852
+ try {
8853
+ const employeeSessions = deps.listTmuxSessions().filter(
8854
+ (s) => (s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)) && s.endsWith(`-${exeSession}`)
7935
8855
  );
8856
+ for (const sess of employeeSessions) {
8857
+ deps.sendIntercom(sess);
8858
+ }
8859
+ const ageMs = Date.now() - new Date(task.created_at).getTime();
8860
+ const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
8861
+ if (ageMs > UNSTARTED_WARNING_MS) {
8862
+ process.stderr.write(
8863
+ `[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
8864
+ `
8865
+ );
8866
+ }
8867
+ } catch {
7936
8868
  }
7937
- } catch {
7938
8869
  }
8870
+ } catch {
7939
8871
  }
7940
- } catch {
7941
8872
  }
7942
8873
  }
7943
8874
  return sent;
@@ -7950,9 +8881,9 @@ function createRealDeps(getClient2) {
7950
8881
  timeout: 3e3
7951
8882
  }).trim().split("\n").filter(Boolean);
7952
8883
  },
7953
- countPendingReviews: async () => {
8884
+ countPendingReviews: async (sessionScope) => {
7954
8885
  const client = getClient2();
7955
- const rpScope = sessionScopeFilter();
8886
+ const rpScope = strictSessionScopeFilter(sessionScope);
7956
8887
  const result = await client.execute({
7957
8888
  sql: `SELECT COUNT(*) as count FROM tasks
7958
8889
  WHERE status = 'needs_review'${rpScope.sql}`,
@@ -7964,10 +8895,10 @@ function createRealDeps(getClient2) {
7964
8895
  const { sendIntercom: centralSend } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
7965
8896
  centralSend(session);
7966
8897
  },
7967
- findOrphanedDoneTasks: async () => {
8898
+ findOrphanedDoneTasks: async (sessionScope) => {
7968
8899
  const client = getClient2();
7969
8900
  const coordinatorName = getCoordinatorName();
7970
- const odScope = sessionScopeFilter(void 0, "t");
8901
+ const odScope = strictSessionScopeFilter(sessionScope, "t");
7971
8902
  const result = await client.execute({
7972
8903
  sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
7973
8904
  t.project_name, t.task_file, t.result, t.status
@@ -7992,24 +8923,25 @@ function createRealDeps(getClient2) {
7992
8923
  process.stderr.write(`[exed] Created missing review for: ${task.title} (${task.assigned_to})
7993
8924
  `);
7994
8925
  },
7995
- findUrgentUnread: async () => {
8926
+ findUrgentUnread: async (sessionScope) => {
7996
8927
  const client = getClient2();
8928
+ const msgScope = strictSessionScopeFilter(sessionScope);
7997
8929
  const result = await client.execute({
7998
8930
  sql: `SELECT id, target_agent, content, created_at
7999
8931
  FROM messages
8000
8932
  WHERE priority = 'urgent'
8001
8933
  AND status IN ('pending', 'delivered')
8002
- AND created_at <= datetime('now', '-2 minutes')
8934
+ AND created_at <= datetime('now', '-2 minutes')${msgScope.sql}
8003
8935
  ORDER BY created_at ASC
8004
8936
  LIMIT 10`,
8005
- args: []
8937
+ args: [...msgScope.args]
8006
8938
  });
8007
8939
  return result.rows;
8008
8940
  },
8009
- findUnstartedTasks: async () => {
8941
+ findUnstartedTasks: async (sessionScope) => {
8010
8942
  const client = getClient2();
8011
8943
  const coordinatorName = getCoordinatorName();
8012
- const usScope = sessionScopeFilter();
8944
+ const usScope = strictSessionScopeFilter(sessionScope);
8013
8945
  const result = await client.execute({
8014
8946
  sql: `SELECT id, title, assigned_to, created_at
8015
8947
  FROM tasks
@@ -8444,10 +9376,10 @@ async function disposeEmbedder() {
8444
9376
  async function embedDirect(text) {
8445
9377
  const llamaCpp = await import("node-llama-cpp");
8446
9378
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
8447
- const { existsSync: existsSync19 } = await import("fs");
8448
- const path23 = await import("path");
8449
- const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8450
- if (!existsSync19(modelPath)) {
9379
+ const { existsSync: existsSync21 } = await import("fs");
9380
+ const path25 = await import("path");
9381
+ const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
9382
+ if (!existsSync21(modelPath)) {
8451
9383
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
8452
9384
  }
8453
9385
  const llama = await llamaCpp.getLlama();
@@ -8669,13 +9601,13 @@ __export(graph_rag_exports, {
8669
9601
  resolveAlias: () => resolveAlias,
8670
9602
  storeExtraction: () => storeExtraction
8671
9603
  });
8672
- import crypto6 from "crypto";
9604
+ import crypto7 from "crypto";
8673
9605
  function normalizeEntityName(name) {
8674
9606
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
8675
9607
  }
8676
9608
  function entityId(name, type) {
8677
9609
  const normalized = normalizeEntityName(name);
8678
- return crypto6.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
9610
+ return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
8679
9611
  }
8680
9612
  async function resolveAlias(client, name) {
8681
9613
  const normalized = normalizeEntityName(name);
@@ -8925,7 +9857,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
8925
9857
  const targetAlias = await resolveAlias(client, r.target);
8926
9858
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
8927
9859
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
8928
- const relId = crypto6.randomUUID().slice(0, 16);
9860
+ const relId = crypto7.randomUUID().slice(0, 16);
8929
9861
  try {
8930
9862
  await client.execute({
8931
9863
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -8988,7 +9920,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
8988
9920
  }
8989
9921
  }
8990
9922
  for (const h of extraction.hyperedges) {
8991
- const hId = crypto6.randomUUID().slice(0, 16);
9923
+ const hId = crypto7.randomUUID().slice(0, 16);
8992
9924
  try {
8993
9925
  await client.execute({
8994
9926
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -9052,7 +9984,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
9052
9984
  totalEntities += stored.entitiesStored;
9053
9985
  totalRelationships += stored.relationshipsStored;
9054
9986
  }
9055
- const contentHash = crypto6.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
9987
+ const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
9056
9988
  await client.execute({
9057
9989
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
9058
9990
  args: [contentHash, contentHash, memoryId]
@@ -9169,8 +10101,8 @@ __export(wiki_sync_exports, {
9169
10101
  listWorkspaces: () => listWorkspaces,
9170
10102
  syncMemories: () => syncMemories
9171
10103
  });
9172
- async function wikiRequest(config, path23, method = "GET", body) {
9173
- const url = `${config.wikiUrl}/api/v1${path23}`;
10104
+ async function wikiRequest(config, path25, method = "GET", body) {
10105
+ const url = `${config.wikiUrl}/api/v1${path25}`;
9174
10106
  const headers = {
9175
10107
  "Authorization": `Bearer ${config.wikiApiKey}`,
9176
10108
  "Content-Type": "application/json"
@@ -9182,7 +10114,7 @@ async function wikiRequest(config, path23, method = "GET", body) {
9182
10114
  signal: AbortSignal.timeout(3e4)
9183
10115
  });
9184
10116
  if (!response.ok) {
9185
- throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
10117
+ throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
9186
10118
  }
9187
10119
  return response.json();
9188
10120
  }
@@ -9294,8 +10226,8 @@ __export(token_spend_exports, {
9294
10226
  import { readdir } from "fs/promises";
9295
10227
  import { createReadStream } from "fs";
9296
10228
  import { createInterface } from "readline";
9297
- import path19 from "path";
9298
- import os11 from "os";
10229
+ import path21 from "path";
10230
+ import os13 from "os";
9299
10231
  function getPricing(model) {
9300
10232
  if (MODEL_PRICING[model]) return MODEL_PRICING[model];
9301
10233
  const stripped = model.replace(/-\d{8}$/, "");
@@ -9307,29 +10239,33 @@ function getPricing(model) {
9307
10239
  return DEFAULT_PRICING;
9308
10240
  }
9309
10241
  async function getAgentSpend(period = "7d") {
10242
+ const cached = _spendCache.get(period);
10243
+ if (cached && Date.now() < cached.expires) {
10244
+ return cached.result;
10245
+ }
9310
10246
  const cutoff = periodToCutoff(period);
9311
10247
  const client = getClient();
9312
- const result = await client.execute({
10248
+ const dbResult = await client.execute({
9313
10249
  sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
9314
10250
  args: [cutoff]
9315
10251
  });
9316
- if (result.rows.length === 0) return [];
10252
+ if (dbResult.rows.length === 0) return [];
9317
10253
  const sessionAgent = /* @__PURE__ */ new Map();
9318
- for (const row of result.rows) {
10254
+ for (const row of dbResult.rows) {
9319
10255
  sessionAgent.set(row.session_uuid, row.agent_id);
9320
10256
  }
9321
- const claudeDir = path19.join(os11.homedir(), ".claude", "projects");
10257
+ const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
9322
10258
  let projectDirs = [];
9323
10259
  try {
9324
10260
  const entries = await readdir(claudeDir);
9325
- projectDirs = entries.map((e) => path19.join(claudeDir, e));
10261
+ projectDirs = entries.map((e) => path21.join(claudeDir, e));
9326
10262
  } catch {
9327
10263
  return [];
9328
10264
  }
9329
10265
  const agentTotals = /* @__PURE__ */ new Map();
9330
10266
  for (const [sessionUuid, agentId] of sessionAgent) {
9331
10267
  for (const dir of projectDirs) {
9332
- const jsonlPath = path19.join(dir, `${sessionUuid}.jsonl`);
10268
+ const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
9333
10269
  try {
9334
10270
  const usage = await extractSessionUsage(jsonlPath);
9335
10271
  if (usage.input === 0 && usage.output === 0) continue;
@@ -9353,7 +10289,7 @@ async function getAgentSpend(period = "7d") {
9353
10289
  }
9354
10290
  }
9355
10291
  }
9356
- return Array.from(agentTotals.entries()).map(([agentId, t]) => ({
10292
+ const result = Array.from(agentTotals.entries()).map(([agentId, t]) => ({
9357
10293
  agentId,
9358
10294
  inputTokens: t.input,
9359
10295
  outputTokens: t.output,
@@ -9363,6 +10299,8 @@ async function getAgentSpend(period = "7d") {
9363
10299
  sessions: t.sessions.size,
9364
10300
  period
9365
10301
  })).sort((a, b) => b.costUSD - a.costUSD);
10302
+ _spendCache.set(period, { result, expires: Date.now() + CACHE_TTL_MS });
10303
+ return result;
9366
10304
  }
9367
10305
  async function extractSessionUsage(jsonlPath) {
9368
10306
  let input = 0;
@@ -9409,7 +10347,7 @@ function periodToCutoff(period) {
9409
10347
  const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
9410
10348
  return new Date(Date.now() - ms).toISOString();
9411
10349
  }
9412
- var MODEL_PRICING, DEFAULT_PRICING;
10350
+ var MODEL_PRICING, DEFAULT_PRICING, CACHE_TTL_MS, _spendCache;
9413
10351
  var init_token_spend = __esm({
9414
10352
  "src/lib/token-spend.ts"() {
9415
10353
  "use strict";
@@ -9439,6 +10377,8 @@ var init_token_spend = __esm({
9439
10377
  "claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
9440
10378
  };
9441
10379
  DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
10380
+ CACHE_TTL_MS = 5 * 60 * 1e3;
10381
+ _spendCache = /* @__PURE__ */ new Map();
9442
10382
  }
9443
10383
  });
9444
10384
 
@@ -9450,11 +10390,11 @@ __export(update_check_exports, {
9450
10390
  getRemoteVersion: () => getRemoteVersion
9451
10391
  });
9452
10392
  import { execSync as execSync11 } from "child_process";
9453
- import { readFileSync as readFileSync14 } from "fs";
9454
- import path20 from "path";
10393
+ import { readFileSync as readFileSync15 } from "fs";
10394
+ import path22 from "path";
9455
10395
  function getLocalVersion(packageRoot) {
9456
- const pkgPath = path20.join(packageRoot, "package.json");
9457
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
10396
+ const pkgPath = path22.join(packageRoot, "package.json");
10397
+ const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
9458
10398
  return pkg.version;
9459
10399
  }
9460
10400
  function getRemoteVersion() {
@@ -9497,16 +10437,16 @@ __export(ws_auth_exports, {
9497
10437
  deriveWsAuthToken: () => deriveWsAuthToken,
9498
10438
  hashAuthToken: () => hashAuthToken
9499
10439
  });
9500
- import crypto7 from "crypto";
10440
+ import crypto8 from "crypto";
9501
10441
  function deriveWsAuthToken(masterKey) {
9502
- return Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
10442
+ return Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
9503
10443
  }
9504
10444
  function deriveOrgId(masterKey) {
9505
- const raw = Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
9506
- return crypto7.createHash("sha256").update(raw).digest("hex").slice(0, 32);
10445
+ const raw = Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
10446
+ return crypto8.createHash("sha256").update(raw).digest("hex").slice(0, 32);
9507
10447
  }
9508
10448
  function hashAuthToken(token) {
9509
- return crypto7.createHash("sha256").update(token).digest("hex");
10449
+ return crypto8.createHash("sha256").update(token).digest("hex");
9510
10450
  }
9511
10451
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
9512
10452
  var init_ws_auth = __esm({
@@ -9524,14 +10464,14 @@ __export(device_registry_exports, {
9524
10464
  resolveTargetDevice: () => resolveTargetDevice,
9525
10465
  setFriendlyName: () => setFriendlyName
9526
10466
  });
9527
- import crypto8 from "crypto";
9528
- import os12 from "os";
9529
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync17 } from "fs";
9530
- import path21 from "path";
10467
+ import crypto9 from "crypto";
10468
+ import os14 from "os";
10469
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync19 } from "fs";
10470
+ import path23 from "path";
9531
10471
  function getDeviceInfo() {
9532
- if (existsSync17(DEVICE_JSON_PATH)) {
10472
+ if (existsSync19(DEVICE_JSON_PATH)) {
9533
10473
  try {
9534
- const raw = readFileSync15(DEVICE_JSON_PATH, "utf8");
10474
+ const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
9535
10475
  const data = JSON.parse(raw);
9536
10476
  if (data.deviceId && data.friendlyName && data.hostname) {
9537
10477
  return data;
@@ -9539,20 +10479,20 @@ function getDeviceInfo() {
9539
10479
  } catch {
9540
10480
  }
9541
10481
  }
9542
- const hostname = os12.hostname();
10482
+ const hostname = os14.hostname();
9543
10483
  const info = {
9544
- deviceId: crypto8.randomUUID(),
10484
+ deviceId: crypto9.randomUUID(),
9545
10485
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
9546
10486
  hostname
9547
10487
  };
9548
- mkdirSync8(path21.dirname(DEVICE_JSON_PATH), { recursive: true });
9549
- writeFileSync9(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10488
+ mkdirSync8(path23.dirname(DEVICE_JSON_PATH), { recursive: true });
10489
+ writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
9550
10490
  return info;
9551
10491
  }
9552
10492
  function setFriendlyName(name) {
9553
10493
  const info = getDeviceInfo();
9554
10494
  info.friendlyName = name;
9555
- writeFileSync9(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10495
+ writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
9556
10496
  }
9557
10497
  async function resolveTargetDevice(targetAgent, targetProject) {
9558
10498
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -9586,7 +10526,7 @@ var init_device_registry = __esm({
9586
10526
  "src/lib/device-registry.ts"() {
9587
10527
  "use strict";
9588
10528
  init_config();
9589
- DEVICE_JSON_PATH = path21.join(EXE_AI_DIR, "device.json");
10529
+ DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
9590
10530
  }
9591
10531
  });
9592
10532
 
@@ -9810,10 +10750,10 @@ __export(messaging_exports, {
9810
10750
  sendMessage: () => sendMessage,
9811
10751
  setWsClientSend: () => setWsClientSend
9812
10752
  });
9813
- import crypto9 from "crypto";
10753
+ import crypto10 from "crypto";
9814
10754
  function generateUlid() {
9815
10755
  const timestamp = Date.now().toString(36).padStart(10, "0");
9816
- const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
10756
+ const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
9817
10757
  return (timestamp + random).toUpperCase();
9818
10758
  }
9819
10759
  function rowToMessage(row) {
@@ -9824,6 +10764,7 @@ function rowToMessage(row) {
9824
10764
  targetAgent: row.target_agent,
9825
10765
  targetProject: row.target_project ?? null,
9826
10766
  targetDevice: row.target_device,
10767
+ sessionScope: row.session_scope ?? null,
9827
10768
  content: row.content,
9828
10769
  priority: row.priority ?? "normal",
9829
10770
  status: row.status ?? "pending",
@@ -9841,15 +10782,17 @@ async function sendMessage(input) {
9841
10782
  const id = generateUlid();
9842
10783
  const now = (/* @__PURE__ */ new Date()).toISOString();
9843
10784
  const targetDevice = input.targetDevice ?? "local";
10785
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
9844
10786
  await client.execute({
9845
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
9846
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
10787
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
10788
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
9847
10789
  args: [
9848
10790
  id,
9849
10791
  input.fromAgent,
9850
10792
  input.targetAgent,
9851
10793
  input.targetProject ?? null,
9852
10794
  targetDevice,
10795
+ sessionScope,
9853
10796
  input.content,
9854
10797
  input.priority ?? "normal",
9855
10798
  now
@@ -9863,9 +10806,10 @@ async function sendMessage(input) {
9863
10806
  }
9864
10807
  } catch {
9865
10808
  }
10809
+ const sentScope = strictSessionScopeFilter(sessionScope);
9866
10810
  const result = await client.execute({
9867
- sql: "SELECT * FROM messages WHERE id = ?",
9868
- args: [id]
10811
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
10812
+ args: [id, ...sentScope.args]
9869
10813
  });
9870
10814
  return rowToMessage(result.rows[0]);
9871
10815
  }
@@ -9889,6 +10833,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
9889
10833
  fromAgent: msg.fromAgent,
9890
10834
  targetAgent: msg.targetAgent,
9891
10835
  targetProject: msg.targetProject,
10836
+ sessionScope: msg.sessionScope,
9892
10837
  content: msg.content,
9893
10838
  priority: msg.priority,
9894
10839
  createdAt: msg.createdAt
@@ -9932,7 +10877,7 @@ async function deliverLocalMessage(messageId) {
9932
10877
  } catch {
9933
10878
  const newRetryCount = msg.retryCount + 1;
9934
10879
  if (newRetryCount >= MAX_RETRIES3) {
9935
- await markFailed(messageId, "session unavailable after 10 retries");
10880
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
9936
10881
  } else {
9937
10882
  await client.execute({
9938
10883
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -9942,85 +10887,101 @@ async function deliverLocalMessage(messageId) {
9942
10887
  return false;
9943
10888
  }
9944
10889
  }
9945
- async function getPendingMessages(targetAgent) {
10890
+ async function getPendingMessages(targetAgent, sessionScope) {
9946
10891
  const client = getClient();
10892
+ const scope = strictSessionScopeFilter(sessionScope);
9947
10893
  const result = await client.execute({
9948
10894
  sql: `SELECT * FROM messages
9949
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
10895
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
9950
10896
  ORDER BY id`,
9951
- args: [targetAgent]
10897
+ args: [targetAgent, ...scope.args]
9952
10898
  });
9953
10899
  return result.rows.map((row) => rowToMessage(row));
9954
10900
  }
9955
- async function markRead(messageId) {
10901
+ async function markRead(messageId, sessionScope) {
9956
10902
  const client = getClient();
10903
+ const scope = strictSessionScopeFilter(sessionScope);
9957
10904
  await client.execute({
9958
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
9959
- args: [messageId]
10905
+ sql: `UPDATE messages SET status = 'read'
10906
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
10907
+ args: [messageId, ...scope.args]
9960
10908
  });
9961
10909
  }
9962
- async function markAcknowledged(messageId) {
10910
+ async function markAcknowledged(messageId, sessionScope) {
9963
10911
  const client = getClient();
10912
+ const scope = strictSessionScopeFilter(sessionScope);
9964
10913
  await client.execute({
9965
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
9966
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10914
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
10915
+ WHERE id = ? AND status = 'read'${scope.sql}`,
10916
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9967
10917
  });
9968
10918
  }
9969
- async function markProcessed(messageId) {
10919
+ async function markProcessed(messageId, sessionScope) {
9970
10920
  const client = getClient();
10921
+ const scope = strictSessionScopeFilter(sessionScope);
9971
10922
  await client.execute({
9972
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
9973
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10923
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
10924
+ WHERE id = ?${scope.sql}`,
10925
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9974
10926
  });
9975
10927
  }
9976
- async function getMessageStatus(messageId) {
10928
+ async function getMessageStatus(messageId, sessionScope) {
9977
10929
  const client = getClient();
10930
+ const scope = strictSessionScopeFilter(sessionScope);
9978
10931
  const result = await client.execute({
9979
- sql: "SELECT status FROM messages WHERE id = ?",
9980
- args: [messageId]
10932
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
10933
+ args: [messageId, ...scope.args]
9981
10934
  });
9982
10935
  return result.rows[0]?.status ?? null;
9983
10936
  }
9984
- async function getUnacknowledgedMessages(targetAgent) {
10937
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
9985
10938
  const client = getClient();
10939
+ const scope = strictSessionScopeFilter(sessionScope);
9986
10940
  const result = await client.execute({
9987
10941
  sql: `SELECT * FROM messages
9988
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
10942
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
9989
10943
  ORDER BY id`,
9990
- args: [targetAgent]
10944
+ args: [targetAgent, ...scope.args]
9991
10945
  });
9992
10946
  return result.rows.map((row) => rowToMessage(row));
9993
10947
  }
9994
- async function getReadMessages(targetAgent) {
10948
+ async function getReadMessages(targetAgent, sessionScope) {
9995
10949
  const client = getClient();
10950
+ const scope = strictSessionScopeFilter(sessionScope);
9996
10951
  const result = await client.execute({
9997
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
9998
- args: [targetAgent]
10952
+ sql: `SELECT * FROM messages
10953
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
10954
+ ORDER BY id`,
10955
+ args: [targetAgent, ...scope.args]
9999
10956
  });
10000
10957
  return result.rows.map((row) => rowToMessage(row));
10001
10958
  }
10002
- async function markFailed(messageId, reason) {
10959
+ async function markFailed(messageId, reason, sessionScope) {
10003
10960
  const client = getClient();
10961
+ const scope = strictSessionScopeFilter(sessionScope);
10004
10962
  await client.execute({
10005
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
10006
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
10963
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
10964
+ WHERE id = ?${scope.sql}`,
10965
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
10007
10966
  });
10008
10967
  }
10009
- async function getFailedMessages() {
10968
+ async function getFailedMessages(sessionScope) {
10010
10969
  const client = getClient();
10970
+ const scope = strictSessionScopeFilter(sessionScope);
10011
10971
  const result = await client.execute({
10012
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
10013
- args: []
10972
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
10973
+ args: [...scope.args]
10014
10974
  });
10015
10975
  return result.rows.map((row) => rowToMessage(row));
10016
10976
  }
10017
- async function retryPendingMessages() {
10977
+ async function retryPendingMessages(sessionScope) {
10018
10978
  const client = getClient();
10979
+ const scope = strictSessionScopeFilter(sessionScope);
10019
10980
  const result = await client.execute({
10020
10981
  sql: `SELECT * FROM messages
10021
- WHERE status = 'pending' AND retry_count < ?
10982
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
10022
10983
  ORDER BY id`,
10023
- args: [MAX_RETRIES3]
10984
+ args: [MAX_RETRIES3, ...scope.args]
10024
10985
  });
10025
10986
  let delivered = 0;
10026
10987
  for (const row of result.rows) {
@@ -10039,6 +11000,7 @@ var init_messaging = __esm({
10039
11000
  "use strict";
10040
11001
  init_database();
10041
11002
  init_tmux_routing();
11003
+ init_task_scope();
10042
11004
  MAX_RETRIES3 = 10;
10043
11005
  _wsClientSend = null;
10044
11006
  }
@@ -10048,19 +11010,22 @@ var init_messaging = __esm({
10048
11010
  init_config();
10049
11011
  init_memory();
10050
11012
  init_daemon_protocol();
11013
+ init_daemon_auth();
10051
11014
  init_daemon_orchestration();
10052
11015
  import net2 from "net";
10053
- import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
10054
- import path22 from "path";
11016
+ import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync20, readFileSync as readFileSync17, chmodSync as chmodSync2 } from "fs";
11017
+ import path24 from "path";
10055
11018
  import { getLlama } from "node-llama-cpp";
10056
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path22.join(EXE_AI_DIR, "exed.sock");
10057
- var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path22.join(EXE_AI_DIR, "exed.pid");
11019
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path24.join(EXE_AI_DIR, "exed.sock");
11020
+ var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path24.join(EXE_AI_DIR, "exed.pid");
10058
11021
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
10059
11022
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
10060
11023
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
11024
+ var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
10061
11025
  var _context = null;
10062
11026
  var _model = null;
10063
11027
  var _llama = null;
11028
+ var _daemonToken = "";
10064
11029
  var MAX_QUEUE_SIZE = 1e3;
10065
11030
  var highQueue = [];
10066
11031
  var lowQueue = [];
@@ -10080,8 +11045,8 @@ function enqueue(queue, entry) {
10080
11045
  queue.push(entry);
10081
11046
  }
10082
11047
  async function loadModel() {
10083
- const modelPath = path22.join(MODELS_DIR, MODEL_FILE);
10084
- if (!existsSync18(modelPath)) {
11048
+ const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
11049
+ if (!existsSync20(modelPath)) {
10085
11050
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
10086
11051
  `);
10087
11052
  process.exit(1);
@@ -10105,6 +11070,7 @@ async function processQueue() {
10105
11070
  for (const text of entry.request.texts) {
10106
11071
  const embedding = await _context.getEmbeddingFor(text);
10107
11072
  const vector = Array.from(embedding.vector);
11073
+ embedding.vector = null;
10108
11074
  if (vector.length !== EMBEDDING_DIM) {
10109
11075
  throw new Error(`Dimension mismatch: got ${vector.length}, expected ${EMBEDDING_DIM}`);
10110
11076
  }
@@ -10150,6 +11116,11 @@ function checkIdle() {
10150
11116
  }
10151
11117
  async function shutdown() {
10152
11118
  resetIdleTimer();
11119
+ try {
11120
+ const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
11121
+ disposeShards2();
11122
+ } catch {
11123
+ }
10153
11124
  if (_context) {
10154
11125
  try {
10155
11126
  await _context.dispose();
@@ -10188,6 +11159,7 @@ async function handleHealthCheck(socket, requestId) {
10188
11159
  }
10189
11160
  }
10190
11161
  const dbConnected = _storeInitialized;
11162
+ const mem = process.memoryUsage();
10191
11163
  sendResponse(socket, {
10192
11164
  id: requestId,
10193
11165
  ...healthy && testOk ? {
@@ -10195,7 +11167,13 @@ async function handleHealthCheck(socket, requestId) {
10195
11167
  status: "ok",
10196
11168
  uptime: Math.floor((Date.now() - _startedAt) / 1e3),
10197
11169
  requests_served: _requestsServed,
10198
- db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
11170
+ db: { connected: dbConnected, totalDbRequests: _dbRequestsServed },
11171
+ memory: {
11172
+ rss_mb: Math.round(mem.rss / 1024 / 1024),
11173
+ heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
11174
+ external_mb: Math.round(mem.external / 1024 / 1024),
11175
+ array_buffers_mb: Math.round(mem.arrayBuffers / 1024 / 1024)
11176
+ }
10199
11177
  }
10200
11178
  } : { error: "unhealthy: model not loaded or test embed failed" }
10201
11179
  });
@@ -10249,13 +11227,67 @@ async function handleDbBatch(socket, requestId, statements, mode) {
10249
11227
  });
10250
11228
  }
10251
11229
  }
11230
+ var _ingestCount = 0;
11231
+ async function handleIngest(req) {
11232
+ try {
11233
+ if (!await ensureStoreForPolling()) return;
11234
+ if (!req.rawText || req.rawText.length < 50) return;
11235
+ let vectorBlob = null;
11236
+ if (_context) {
11237
+ try {
11238
+ const embedding = await _context.getEmbeddingFor(req.rawText);
11239
+ const vector = Array.from(embedding.vector);
11240
+ embedding.vector = null;
11241
+ if (vector.length === EMBEDDING_DIM) {
11242
+ const { vectorToBlob: vectorToBlob2 } = await Promise.resolve().then(() => (init_store(), store_exports));
11243
+ vectorBlob = vectorToBlob2(vector);
11244
+ }
11245
+ } catch {
11246
+ }
11247
+ }
11248
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
11249
+ const client = getClient2();
11250
+ const { randomUUID: randomUUID5 } = await import("crypto");
11251
+ const id = randomUUID5();
11252
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11253
+ await client.execute({
11254
+ sql: `INSERT INTO memories (id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, vector, task_id, confidence, draft, memory_type, trajectory)
11255
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'raw', ?)`,
11256
+ args: [
11257
+ id,
11258
+ req.agentId,
11259
+ req.agentRole,
11260
+ req.sessionId,
11261
+ now,
11262
+ req.toolName,
11263
+ req.projectName,
11264
+ req.hasError ? 1 : 0,
11265
+ req.rawText,
11266
+ vectorBlob,
11267
+ req.taskId ?? null,
11268
+ req.confidence ?? 0.7,
11269
+ req.draft ? 1 : 0,
11270
+ req.trajectory ? JSON.stringify(req.trajectory) : null
11271
+ ]
11272
+ });
11273
+ _ingestCount++;
11274
+ } catch (err) {
11275
+ process.stderr.write(`[exed] Ingest error: ${err instanceof Error ? err.message : String(err)}
11276
+ `);
11277
+ }
11278
+ }
10252
11279
  function startServer() {
10253
- mkdirSync9(path22.dirname(SOCKET_PATH2), { recursive: true });
11280
+ mkdirSync9(path24.dirname(SOCKET_PATH2), { recursive: true });
11281
+ try {
11282
+ chmodSync2(path24.dirname(SOCKET_PATH2), 448);
11283
+ } catch {
11284
+ }
11285
+ _daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
10254
11286
  for (const oldFile of ["embed.sock", "embed.pid"]) {
10255
- const oldPath = path22.join(path22.dirname(SOCKET_PATH2), oldFile);
11287
+ const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
10256
11288
  try {
10257
11289
  if (oldFile.endsWith(".pid")) {
10258
- const pid = parseInt(readFileSync16(oldPath, "utf8").trim(), 10);
11290
+ const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
10259
11291
  if (pid > 0) try {
10260
11292
  process.kill(pid, "SIGKILL");
10261
11293
  } catch {
@@ -10287,6 +11319,10 @@ function startServer() {
10287
11319
  if (!line) continue;
10288
11320
  try {
10289
11321
  const request = JSON.parse(line);
11322
+ if (!request.token || request.token !== _daemonToken) {
11323
+ sendResponse(socket, { id: request.id ?? "unauthorized", error: "Unauthorized daemon request" });
11324
+ continue;
11325
+ }
10290
11326
  if (request.type === "health") {
10291
11327
  void handleHealthCheck(socket, request.id ?? "health");
10292
11328
  continue;
@@ -10307,6 +11343,10 @@ function startServer() {
10307
11343
  void handleDbBatch(socket, request.id, request.statements, request.mode);
10308
11344
  continue;
10309
11345
  }
11346
+ if (request.type === "ingest") {
11347
+ void handleIngest(request);
11348
+ continue;
11349
+ }
10310
11350
  if (!request.id || !Array.isArray(request.texts)) {
10311
11351
  sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
10312
11352
  continue;
@@ -10344,7 +11384,15 @@ function startServer() {
10344
11384
  server.listen(SOCKET_PATH2, () => {
10345
11385
  process.stderr.write(`[exed] Listening on ${SOCKET_PATH2}
10346
11386
  `);
10347
- writeFileSync10(PID_PATH2, String(process.pid));
11387
+ try {
11388
+ chmodSync2(SOCKET_PATH2, 384);
11389
+ } catch {
11390
+ }
11391
+ writeFileSync11(PID_PATH2, String(process.pid));
11392
+ try {
11393
+ chmodSync2(PID_PATH2, 384);
11394
+ } catch {
11395
+ }
10348
11396
  checkIdle();
10349
11397
  });
10350
11398
  }
@@ -10637,7 +11685,7 @@ function startWikiSync() {
10637
11685
  });
10638
11686
  }
10639
11687
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
10640
- var AGENT_STATS_PATH = path22.join(EXE_AI_DIR, "agent-stats.json");
11688
+ var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
10641
11689
  async function writeAgentStats() {
10642
11690
  if (!await ensureStoreForPolling()) return;
10643
11691
  try {
@@ -10695,7 +11743,7 @@ async function writeAgentStats() {
10695
11743
  pid: process.pid
10696
11744
  }
10697
11745
  };
10698
- writeFileSync10(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
11746
+ writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
10699
11747
  } catch (err) {
10700
11748
  process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
10701
11749
  `);
@@ -10767,12 +11815,12 @@ function startIntercomQueueDrain() {
10767
11815
  const hasInProgressTask = (session) => {
10768
11816
  try {
10769
11817
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
10770
- const path23 = __require("path");
10771
- const { existsSync: existsSync19 } = __require("fs");
10772
- const os13 = __require("os");
11818
+ const path25 = __require("path");
11819
+ const { existsSync: existsSync21 } = __require("fs");
11820
+ const os15 = __require("os");
10773
11821
  const agent = ban(session.split("-")[0] ?? session);
10774
- const markerPath = path23.join(os13.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
10775
- return existsSync19(markerPath);
11822
+ const markerPath = path25.join(os15.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
11823
+ return existsSync21(markerPath);
10776
11824
  } catch {
10777
11825
  return false;
10778
11826
  }
@@ -10861,12 +11909,43 @@ function startAutoWake() {
10861
11909
  process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
10862
11910
  `);
10863
11911
  }
11912
+ var RSS_WARN_BYTES = 1024 * 1024 * 1024;
11913
+ var RSS_RESTART_BYTES = 2048 * 1024 * 1024;
11914
+ var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
11915
+ var _rssWarned = false;
11916
+ function startRssWatchdog() {
11917
+ const tick = () => {
11918
+ const rss = process.memoryUsage.rss();
11919
+ if (rss > RSS_RESTART_BYTES) {
11920
+ process.stderr.write(
11921
+ `[exed] RSS CRITICAL: ${(rss / 1024 / 1024).toFixed(0)} MB exceeds 2 GB limit \u2014 restarting.
11922
+ `
11923
+ );
11924
+ void shutdown();
11925
+ return;
11926
+ }
11927
+ if (rss > RSS_WARN_BYTES && !_rssWarned) {
11928
+ _rssWarned = true;
11929
+ const heap = process.memoryUsage();
11930
+ process.stderr.write(
11931
+ `[exed] RSS WARNING: ${(rss / 1024 / 1024).toFixed(0)} MB (heap used: ${(heap.heapUsed / 1024 / 1024).toFixed(0)} MB, external: ${(heap.external / 1024 / 1024).toFixed(0)} MB, arrayBuffers: ${(heap.arrayBuffers / 1024 / 1024).toFixed(0)} MB)
11932
+ `
11933
+ );
11934
+ } else if (rss < RSS_WARN_BYTES && _rssWarned) {
11935
+ _rssWarned = false;
11936
+ }
11937
+ };
11938
+ const timer = setInterval(tick, RSS_CHECK_INTERVAL_MS);
11939
+ timer.unref();
11940
+ process.stderr.write(`[exed] RSS watchdog started (warn: 1 GB, restart: 2 GB)
11941
+ `);
11942
+ }
10864
11943
  process.on("SIGINT", () => void shutdown());
10865
11944
  process.on("SIGTERM", () => void shutdown());
10866
11945
  function checkExistingDaemon() {
10867
11946
  try {
10868
- if (!existsSync18(PID_PATH2)) return false;
10869
- const pid = parseInt(readFileSync16(PID_PATH2, "utf8").trim(), 10);
11947
+ if (!existsSync20(PID_PATH2)) return false;
11948
+ const pid = parseInt(readFileSync17(PID_PATH2, "utf8").trim(), 10);
10870
11949
  if (!pid || isNaN(pid)) return false;
10871
11950
  process.kill(pid, 0);
10872
11951
  process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
@@ -10954,6 +12033,7 @@ try {
10954
12033
  startIntercomQueueDrain();
10955
12034
  startConfidenceDecay();
10956
12035
  startAutoUpdateCheck();
12036
+ startRssWatchdog();
10957
12037
  try {
10958
12038
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
10959
12039
  const config = await loadConfig2();