@askexenow/exe-os 0.9.8 → 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 +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -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
 
@@ -998,9 +1075,9 @@ __export(employees_exports, {
998
1075
  validateEmployeeName: () => validateEmployeeName
999
1076
  });
1000
1077
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1001
- 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";
1002
1079
  import { execSync as execSync4 } from "child_process";
1003
- import path5 from "path";
1080
+ import path6 from "path";
1004
1081
  import os4 from "os";
1005
1082
  function normalizeRole(role) {
1006
1083
  return (role ?? "").trim().toLowerCase();
@@ -1037,7 +1114,7 @@ function validateEmployeeName(name) {
1037
1114
  return { valid: true };
1038
1115
  }
1039
1116
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1040
- if (!existsSync5(employeesPath)) {
1117
+ if (!existsSync7(employeesPath)) {
1041
1118
  return [];
1042
1119
  }
1043
1120
  const raw = await readFile2(employeesPath, "utf-8");
@@ -1048,13 +1125,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1048
1125
  }
1049
1126
  }
1050
1127
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1051
- await mkdir2(path5.dirname(employeesPath), { recursive: true });
1128
+ await mkdir2(path6.dirname(employeesPath), { recursive: true });
1052
1129
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1053
1130
  }
1054
1131
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1055
- if (!existsSync5(employeesPath)) return [];
1132
+ if (!existsSync7(employeesPath)) return [];
1056
1133
  try {
1057
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
1134
+ return JSON.parse(readFileSync6(employeesPath, "utf-8"));
1058
1135
  } catch {
1059
1136
  return [];
1060
1137
  }
@@ -1099,9 +1176,9 @@ function addEmployee(employees, employee) {
1099
1176
  function appendToCoordinatorTeam(employee) {
1100
1177
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1101
1178
  if (!coordinator) return;
1102
- const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1103
- if (!existsSync5(idPath)) return;
1104
- const content = readFileSync5(idPath, "utf-8");
1179
+ const idPath = path6.join(IDENTITY_DIR, `${coordinator.name}.md`);
1180
+ if (!existsSync7(idPath)) return;
1181
+ const content = readFileSync6(idPath, "utf-8");
1105
1182
  if (content.includes(`**${capitalize(employee.name)}`)) return;
1106
1183
  const teamMatch = content.match(TEAM_SECTION_RE);
1107
1184
  if (!teamMatch || teamMatch.index === void 0) return;
@@ -1117,7 +1194,7 @@ function appendToCoordinatorTeam(employee) {
1117
1194
  } else {
1118
1195
  updated = content.trimEnd() + "\n" + entry;
1119
1196
  }
1120
- writeFileSync4(idPath, updated, "utf-8");
1197
+ writeFileSync5(idPath, updated, "utf-8");
1121
1198
  }
1122
1199
  function capitalize(s) {
1123
1200
  return s.charAt(0).toUpperCase() + s.slice(1);
@@ -1151,14 +1228,14 @@ async function normalizeRosterCase(rosterPath) {
1151
1228
  emp.name = emp.name.toLowerCase();
1152
1229
  changed = true;
1153
1230
  try {
1154
- const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
1155
- const oldPath = path5.join(identityDir, `${oldName}.md`);
1156
- const newPath = path5.join(identityDir, `${emp.name}.md`);
1157
- 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)) {
1158
1235
  renameSync3(oldPath, newPath);
1159
- } else if (existsSync5(oldPath) && oldPath !== newPath) {
1160
- const content = readFileSync5(oldPath, "utf-8");
1161
- 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");
1162
1239
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
1163
1240
  unlinkSync(oldPath);
1164
1241
  }
@@ -1188,7 +1265,7 @@ function registerBinSymlinks(name) {
1188
1265
  errors.push("Could not find 'exe-os' in PATH");
1189
1266
  return { created, skipped, errors };
1190
1267
  }
1191
- const binDir = path5.dirname(exeBinPath);
1268
+ const binDir = path6.dirname(exeBinPath);
1192
1269
  let target;
1193
1270
  try {
1194
1271
  target = readlinkSync(exeBinPath);
@@ -1198,8 +1275,8 @@ function registerBinSymlinks(name) {
1198
1275
  }
1199
1276
  for (const suffix of ["", "-opencode"]) {
1200
1277
  const linkName = `${name}${suffix}`;
1201
- const linkPath = path5.join(binDir, linkName);
1202
- if (existsSync5(linkPath)) {
1278
+ const linkPath = path6.join(binDir, linkName);
1279
+ if (existsSync7(linkPath)) {
1203
1280
  skipped.push(linkName);
1204
1281
  continue;
1205
1282
  }
@@ -1217,18 +1294,18 @@ var init_employees = __esm({
1217
1294
  "src/lib/employees.ts"() {
1218
1295
  "use strict";
1219
1296
  init_config();
1220
- EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
1297
+ EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
1221
1298
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
1222
1299
  COORDINATOR_ROLE = "COO";
1223
1300
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1224
- IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
1301
+ IDENTITY_DIR = path6.join(EXE_AI_DIR, "identity");
1225
1302
  TEAM_SECTION_RE = /^## Team\b.*$/m;
1226
1303
  }
1227
1304
  });
1228
1305
 
1229
1306
  // src/lib/database-adapter.ts
1230
1307
  import os5 from "os";
1231
- import path6 from "path";
1308
+ import path7 from "path";
1232
1309
  import { createRequire } from "module";
1233
1310
  import { pathToFileURL } from "url";
1234
1311
  function quotedIdentifier(identifier) {
@@ -1539,8 +1616,8 @@ async function loadPrismaClient() {
1539
1616
  }
1540
1617
  return new PrismaClient2();
1541
1618
  }
1542
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
1543
- const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
1619
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1620
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1544
1621
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1545
1622
  const module = await import(pathToFileURL(prismaEntry).href);
1546
1623
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1815,8 +1892,8 @@ import net from "net";
1815
1892
  import os6 from "os";
1816
1893
  import { spawn } from "child_process";
1817
1894
  import { randomUUID } from "crypto";
1818
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1819
- import path7 from "path";
1895
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1896
+ import path8 from "path";
1820
1897
  import { fileURLToPath } from "url";
1821
1898
  function handleData(chunk) {
1822
1899
  _buffer += chunk.toString();
@@ -1844,9 +1921,9 @@ function handleData(chunk) {
1844
1921
  }
1845
1922
  }
1846
1923
  function cleanupStaleFiles() {
1847
- if (existsSync6(PID_PATH)) {
1924
+ if (existsSync8(PID_PATH)) {
1848
1925
  try {
1849
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1926
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1850
1927
  if (pid > 0) {
1851
1928
  try {
1852
1929
  process.kill(pid, 0);
@@ -1867,11 +1944,11 @@ function cleanupStaleFiles() {
1867
1944
  }
1868
1945
  }
1869
1946
  function findPackageRoot() {
1870
- let dir = path7.dirname(fileURLToPath(import.meta.url));
1871
- const { root } = path7.parse(dir);
1947
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
1948
+ const { root } = path8.parse(dir);
1872
1949
  while (dir !== root) {
1873
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
1874
- dir = path7.dirname(dir);
1950
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
1951
+ dir = path8.dirname(dir);
1875
1952
  }
1876
1953
  return null;
1877
1954
  }
@@ -1897,16 +1974,17 @@ function spawnDaemon() {
1897
1974
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1898
1975
  return;
1899
1976
  }
1900
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1901
- if (!existsSync6(daemonPath)) {
1977
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1978
+ if (!existsSync8(daemonPath)) {
1902
1979
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1903
1980
  `);
1904
1981
  return;
1905
1982
  }
1906
1983
  const resolvedPath = daemonPath;
1984
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1907
1985
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1908
1986
  `);
1909
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1987
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1910
1988
  let stderrFd = "ignore";
1911
1989
  try {
1912
1990
  stderrFd = openSync(logPath, "a");
@@ -1924,7 +2002,8 @@ function spawnDaemon() {
1924
2002
  TMUX_PANE: void 0,
1925
2003
  // Prevents resolveExeSession() from scoping to one session
1926
2004
  EXE_DAEMON_SOCK: SOCKET_PATH,
1927
- EXE_DAEMON_PID: PID_PATH
2005
+ EXE_DAEMON_PID: PID_PATH,
2006
+ [DAEMON_TOKEN_ENV]: daemonToken
1928
2007
  }
1929
2008
  });
1930
2009
  child.unref();
@@ -2034,13 +2113,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2034
2113
  return;
2035
2114
  }
2036
2115
  const id = randomUUID();
2116
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2037
2117
  const timer = setTimeout(() => {
2038
2118
  _pending.delete(id);
2039
2119
  resolve({ error: "Request timeout" });
2040
2120
  }, timeoutMs);
2041
2121
  _pending.set(id, { resolve, timer });
2042
2122
  try {
2043
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2123
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
2044
2124
  } catch {
2045
2125
  clearTimeout(timer);
2046
2126
  _pending.delete(id);
@@ -2069,9 +2149,9 @@ function killAndRespawnDaemon() {
2069
2149
  }
2070
2150
  try {
2071
2151
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2072
- if (existsSync6(PID_PATH)) {
2152
+ if (existsSync8(PID_PATH)) {
2073
2153
  try {
2074
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
2154
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
2075
2155
  if (pid > 0) {
2076
2156
  try {
2077
2157
  process.kill(pid, "SIGKILL");
@@ -2191,17 +2271,19 @@ function disconnectClient() {
2191
2271
  function isClientConnected() {
2192
2272
  return _connected;
2193
2273
  }
2194
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _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;
2195
2275
  var init_exe_daemon_client = __esm({
2196
2276
  "src/lib/exe-daemon-client.ts"() {
2197
2277
  "use strict";
2198
2278
  init_config();
2199
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
2200
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
2201
- SPAWN_LOCK_PATH = path7.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");
2202
2283
  SPAWN_LOCK_STALE_MS = 3e4;
2203
2284
  CONNECT_TIMEOUT_MS = 15e3;
2204
2285
  REQUEST_TIMEOUT_MS = 3e4;
2286
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
2205
2287
  _socket = null;
2206
2288
  _connected = false;
2207
2289
  _buffer = "";
@@ -2722,6 +2804,7 @@ async function ensureSchema() {
2722
2804
  project TEXT NOT NULL,
2723
2805
  summary TEXT NOT NULL,
2724
2806
  task_file TEXT,
2807
+ session_scope TEXT,
2725
2808
  read INTEGER NOT NULL DEFAULT 0,
2726
2809
  created_at TEXT NOT NULL
2727
2810
  );
@@ -2730,7 +2813,7 @@ async function ensureSchema() {
2730
2813
  ON notifications(read);
2731
2814
 
2732
2815
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2733
- ON notifications(agent_id);
2816
+ ON notifications(agent_id, session_scope);
2734
2817
 
2735
2818
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2736
2819
  ON notifications(task_file);
@@ -2768,6 +2851,7 @@ async function ensureSchema() {
2768
2851
  target_agent TEXT NOT NULL,
2769
2852
  target_project TEXT,
2770
2853
  target_device TEXT NOT NULL DEFAULT 'local',
2854
+ session_scope TEXT,
2771
2855
  content TEXT NOT NULL,
2772
2856
  priority TEXT DEFAULT 'normal',
2773
2857
  status TEXT DEFAULT 'pending',
@@ -2781,10 +2865,31 @@ async function ensureSchema() {
2781
2865
  );
2782
2866
 
2783
2867
  CREATE INDEX IF NOT EXISTS idx_messages_target
2784
- ON messages(target_agent, status);
2868
+ ON messages(target_agent, session_scope, status);
2785
2869
 
2786
2870
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2787
- 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);
2788
2893
  `);
2789
2894
  try {
2790
2895
  await client.execute({
@@ -3368,6 +3473,13 @@ async function ensureSchema() {
3368
3473
  } catch {
3369
3474
  }
3370
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
+ }
3371
3483
  }
3372
3484
  async function disposeDatabase() {
3373
3485
  if (_walCheckpointTimer) {
@@ -3406,18 +3518,21 @@ var init_database = __esm({
3406
3518
  });
3407
3519
 
3408
3520
  // src/lib/license.ts
3409
- 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";
3410
3522
  import { randomUUID as randomUUID2 } from "crypto";
3411
- import path8 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";
3412
3527
  import { jwtVerify, importSPKI } from "jose";
3413
3528
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3414
3529
  var init_license = __esm({
3415
3530
  "src/lib/license.ts"() {
3416
3531
  "use strict";
3417
3532
  init_config();
3418
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
3419
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
3420
- DEVICE_ID_PATH = path8.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");
3421
3536
  PLAN_LIMITS = {
3422
3537
  free: { devices: 1, employees: 1, memories: 5e3 },
3423
3538
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3429,12 +3544,12 @@ var init_license = __esm({
3429
3544
  });
3430
3545
 
3431
3546
  // src/lib/plan-limits.ts
3432
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
3433
- import path9 from "path";
3547
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3548
+ import path10 from "path";
3434
3549
  function getLicenseSync() {
3435
3550
  try {
3436
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
3437
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3551
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3552
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3438
3553
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3439
3554
  const parts = raw.token.split(".");
3440
3555
  if (parts.length !== 3) return freeLicense();
@@ -3472,8 +3587,8 @@ function assertEmployeeLimitSync(rosterPath) {
3472
3587
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3473
3588
  let count = 0;
3474
3589
  try {
3475
- if (existsSync8(filePath)) {
3476
- const raw = readFileSync8(filePath, "utf8");
3590
+ if (existsSync10(filePath)) {
3591
+ const raw = readFileSync9(filePath, "utf8");
3477
3592
  const employees = JSON.parse(raw);
3478
3593
  count = Array.isArray(employees) ? employees.length : 0;
3479
3594
  }
@@ -3502,29 +3617,63 @@ var init_plan_limits = __esm({
3502
3617
  this.name = "PlanLimitError";
3503
3618
  }
3504
3619
  };
3505
- CACHE_PATH2 = path9.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();
3506
3654
  }
3507
3655
  });
3508
3656
 
3509
3657
  // src/lib/notifications.ts
3510
- import crypto from "crypto";
3511
- import path10 from "path";
3512
- import os7 from "os";
3658
+ import crypto2 from "crypto";
3659
+ import path11 from "path";
3660
+ import os8 from "os";
3513
3661
  import {
3514
- readFileSync as readFileSync9,
3662
+ readFileSync as readFileSync10,
3515
3663
  readdirSync,
3516
3664
  unlinkSync as unlinkSync3,
3517
- existsSync as existsSync9,
3665
+ existsSync as existsSync11,
3518
3666
  rmdirSync
3519
3667
  } from "fs";
3520
3668
  async function writeNotification(notification) {
3521
3669
  try {
3522
3670
  const client = getClient();
3523
- const id = crypto.randomUUID();
3671
+ const id = crypto2.randomUUID();
3524
3672
  const now = (/* @__PURE__ */ new Date()).toISOString();
3673
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3525
3674
  await client.execute({
3526
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3527
- 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, ?)`,
3528
3677
  args: [
3529
3678
  id,
3530
3679
  notification.agentId,
@@ -3533,6 +3682,7 @@ async function writeNotification(notification) {
3533
3682
  notification.project,
3534
3683
  notification.summary,
3535
3684
  notification.taskFile ?? null,
3685
+ sessionScope,
3536
3686
  now
3537
3687
  ]
3538
3688
  });
@@ -3541,12 +3691,14 @@ async function writeNotification(notification) {
3541
3691
  `);
3542
3692
  }
3543
3693
  }
3544
- async function markAsReadByTaskFile(taskFile) {
3694
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3545
3695
  try {
3546
3696
  const client = getClient();
3697
+ const scope = strictSessionScopeFilter(sessionScope);
3547
3698
  await client.execute({
3548
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3549
- args: [taskFile]
3699
+ sql: `UPDATE notifications SET read = 1
3700
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3701
+ args: [taskFile, ...scope.args]
3550
3702
  });
3551
3703
  } catch {
3552
3704
  }
@@ -3555,6 +3707,7 @@ var init_notifications = __esm({
3555
3707
  "src/lib/notifications.ts"() {
3556
3708
  "use strict";
3557
3709
  init_database();
3710
+ init_task_scope();
3558
3711
  }
3559
3712
  });
3560
3713
 
@@ -3571,7 +3724,7 @@ __export(session_kill_telemetry_exports, {
3571
3724
  recordSessionKill: () => recordSessionKill,
3572
3725
  sumTokensSavedSince: () => sumTokensSavedSince
3573
3726
  });
3574
- import crypto2 from "crypto";
3727
+ import crypto3 from "crypto";
3575
3728
  async function recordSessionKill(input) {
3576
3729
  try {
3577
3730
  const client = getClient();
@@ -3581,7 +3734,7 @@ async function recordSessionKill(input) {
3581
3734
  ticks_idle, estimated_tokens_saved)
3582
3735
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3583
3736
  args: [
3584
- crypto2.randomUUID(),
3737
+ crypto3.randomUUID(),
3585
3738
  input.sessionName,
3586
3739
  input.agentId,
3587
3740
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3662,30 +3815,6 @@ var init_session_kill_telemetry = __esm({
3662
3815
  }
3663
3816
  });
3664
3817
 
3665
- // src/lib/task-scope.ts
3666
- function getCurrentSessionScope() {
3667
- try {
3668
- return resolveExeSession();
3669
- } catch {
3670
- return null;
3671
- }
3672
- }
3673
- function sessionScopeFilter(sessionScope, tableAlias) {
3674
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3675
- if (!scope) return { sql: "", args: [] };
3676
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3677
- return {
3678
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3679
- args: [scope]
3680
- };
3681
- }
3682
- var init_task_scope = __esm({
3683
- "src/lib/task-scope.ts"() {
3684
- "use strict";
3685
- init_tmux_routing();
3686
- }
3687
- });
3688
-
3689
3818
  // src/lib/state-bus.ts
3690
3819
  var StateBus, orgBus;
3691
3820
  var init_state_bus = __esm({
@@ -3742,12 +3871,12 @@ var init_state_bus = __esm({
3742
3871
  });
3743
3872
 
3744
3873
  // src/lib/tasks-crud.ts
3745
- import crypto3 from "crypto";
3746
- import path11 from "path";
3747
- import os8 from "os";
3874
+ import crypto4 from "crypto";
3875
+ import path12 from "path";
3876
+ import os9 from "os";
3748
3877
  import { execSync as execSync5 } from "child_process";
3749
3878
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3750
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3879
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3751
3880
  async function writeCheckpoint(input) {
3752
3881
  const client = getClient();
3753
3882
  const row = await resolveTask(client, input.taskId);
@@ -3863,7 +3992,7 @@ async function resolveTask(client, identifier, scopeSession) {
3863
3992
  }
3864
3993
  async function createTaskCore(input) {
3865
3994
  const client = getClient();
3866
- const id = crypto3.randomUUID();
3995
+ const id = crypto4.randomUUID();
3867
3996
  const now = (/* @__PURE__ */ new Date()).toISOString();
3868
3997
  const slug = slugify(input.title);
3869
3998
  let earlySessionScope = null;
@@ -3922,8 +4051,8 @@ ${laneWarning}` : laneWarning;
3922
4051
  }
3923
4052
  if (input.baseDir) {
3924
4053
  try {
3925
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3926
- await mkdir3(path11.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 });
3927
4056
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3928
4057
  await ensureGitignoreExe(input.baseDir);
3929
4058
  } catch {
@@ -3959,13 +4088,19 @@ ${laneWarning}` : laneWarning;
3959
4088
  });
3960
4089
  if (input.baseDir) {
3961
4090
  try {
3962
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3963
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
3964
- const mdDir = path11.dirname(mdPath);
3965
- 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 });
3966
4095
  const reviewer = input.reviewer ?? input.assignedBy;
3967
4096
  const mdContent = `# ${input.title}
3968
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
+
3969
4104
  **ID:** ${id}
3970
4105
  **Status:** ${initialStatus}
3971
4106
  **Priority:** ${input.priority}
@@ -3979,12 +4114,6 @@ ${laneWarning}` : laneWarning;
3979
4114
  ## Context
3980
4115
 
3981
4116
  ${input.context}
3982
-
3983
- ## MANDATORY: When done
3984
-
3985
- You MUST call update_task with status "done" and a result summary when finished.
3986
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3987
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3988
4117
  `;
3989
4118
  await writeFile3(mdPath, mdContent, "utf-8");
3990
4119
  } catch (err) {
@@ -4233,7 +4362,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4233
4362
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4234
4363
  } catch {
4235
4364
  }
4236
- if (input.status === "done" || input.status === "cancelled") {
4365
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4237
4366
  try {
4238
4367
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4239
4368
  clearQueueForAgent2(String(row.assigned_to));
@@ -4262,9 +4391,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4262
4391
  return { taskFile, assignedTo, assignedBy, taskSlug };
4263
4392
  }
4264
4393
  async function ensureArchitectureDoc(baseDir, projectName) {
4265
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4394
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
4266
4395
  try {
4267
- if (existsSync10(archPath)) return;
4396
+ if (existsSync12(archPath)) return;
4268
4397
  const template = [
4269
4398
  `# ${projectName} \u2014 System Architecture`,
4270
4399
  "",
@@ -4297,10 +4426,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4297
4426
  }
4298
4427
  }
4299
4428
  async function ensureGitignoreExe(baseDir) {
4300
- const gitignorePath = path11.join(baseDir, ".gitignore");
4429
+ const gitignorePath = path12.join(baseDir, ".gitignore");
4301
4430
  try {
4302
- if (existsSync10(gitignorePath)) {
4303
- const content = readFileSync10(gitignorePath, "utf-8");
4431
+ if (existsSync12(gitignorePath)) {
4432
+ const content = readFileSync11(gitignorePath, "utf-8");
4304
4433
  if (/^\/?exe\/?$/m.test(content)) return;
4305
4434
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4306
4435
  } else {
@@ -4343,8 +4472,8 @@ __export(tasks_review_exports, {
4343
4472
  isStale: () => isStale,
4344
4473
  listPendingReviews: () => listPendingReviews
4345
4474
  });
4346
- import path12 from "path";
4347
- 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";
4348
4477
  function formatAge(isoTimestamp) {
4349
4478
  if (!isoTimestamp) return "";
4350
4479
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -4362,54 +4491,38 @@ function isStale(isoTimestamp) {
4362
4491
  }
4363
4492
  async function countPendingReviews(sessionScope) {
4364
4493
  const client = getClient();
4365
- if (sessionScope) {
4366
- const result2 = await client.execute({
4367
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
4368
- args: [sessionScope]
4369
- });
4370
- return Number(result2.rows[0]?.cnt) || 0;
4371
- }
4494
+ const scope = strictSessionScopeFilter(
4495
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4496
+ );
4372
4497
  const result = await client.execute({
4373
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4374
- args: []
4498
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4499
+ WHERE status = 'needs_review'${scope.sql}`,
4500
+ args: [...scope.args]
4375
4501
  });
4376
4502
  return Number(result.rows[0]?.cnt) || 0;
4377
4503
  }
4378
4504
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4379
4505
  const client = getClient();
4380
- if (sessionScope) {
4381
- const result2 = await client.execute({
4382
- sql: `SELECT COUNT(*) as cnt FROM tasks
4383
- WHERE status = 'needs_review' AND updated_at > ?
4384
- AND session_scope = ?`,
4385
- args: [sinceIso, sessionScope]
4386
- });
4387
- return Number(result2.rows[0]?.cnt) || 0;
4388
- }
4506
+ const scope = strictSessionScopeFilter(
4507
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4508
+ );
4389
4509
  const result = await client.execute({
4390
4510
  sql: `SELECT COUNT(*) as cnt FROM tasks
4391
- WHERE status = 'needs_review' AND updated_at > ?`,
4392
- args: [sinceIso]
4511
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4512
+ args: [sinceIso, ...scope.args]
4393
4513
  });
4394
4514
  return Number(result.rows[0]?.cnt) || 0;
4395
4515
  }
4396
4516
  async function listPendingReviews(limit, sessionScope) {
4397
4517
  const client = getClient();
4398
- if (sessionScope) {
4399
- const result2 = await client.execute({
4400
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4401
- WHERE status = 'needs_review'
4402
- AND session_scope = ?
4403
- ORDER BY updated_at ASC LIMIT ?`,
4404
- args: [sessionScope, limit]
4405
- });
4406
- return result2.rows;
4407
- }
4518
+ const scope = strictSessionScopeFilter(
4519
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4520
+ );
4408
4521
  const result = await client.execute({
4409
4522
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4410
- WHERE status = 'needs_review'
4523
+ WHERE status = 'needs_review'${scope.sql}
4411
4524
  ORDER BY updated_at ASC LIMIT ?`,
4412
- args: [limit]
4525
+ args: [...scope.args, limit]
4413
4526
  });
4414
4527
  return result.rows;
4415
4528
  }
@@ -4421,7 +4534,7 @@ async function cleanupOrphanedReviews() {
4421
4534
  WHERE status IN ('open', 'needs_review', 'in_progress')
4422
4535
  AND assigned_by = 'system'
4423
4536
  AND title LIKE 'Review:%'
4424
- 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'))`,
4425
4538
  args: [now]
4426
4539
  });
4427
4540
  const r1b = await client.execute({
@@ -4629,11 +4742,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4629
4742
  );
4630
4743
  }
4631
4744
  try {
4632
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
4633
- if (existsSync11(cacheDir)) {
4745
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4746
+ if (existsSync13(cacheDir)) {
4634
4747
  for (const f of readdirSync2(cacheDir)) {
4635
4748
  if (f.startsWith("review-notified-")) {
4636
- unlinkSync4(path12.join(cacheDir, f));
4749
+ unlinkSync4(path13.join(cacheDir, f));
4637
4750
  }
4638
4751
  }
4639
4752
  }
@@ -4650,11 +4763,12 @@ var init_tasks_review = __esm({
4650
4763
  init_tmux_routing();
4651
4764
  init_session_key();
4652
4765
  init_state_bus();
4766
+ init_task_scope();
4653
4767
  }
4654
4768
  });
4655
4769
 
4656
4770
  // src/lib/tasks-chain.ts
4657
- import path13 from "path";
4771
+ import path14 from "path";
4658
4772
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4659
4773
  async function cascadeUnblock(taskId, baseDir, now) {
4660
4774
  const client = getClient();
@@ -4671,7 +4785,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4671
4785
  });
4672
4786
  for (const ur of unblockedRows.rows) {
4673
4787
  try {
4674
- const ubFile = path13.join(baseDir, String(ur.task_file));
4788
+ const ubFile = path14.join(baseDir, String(ur.task_file));
4675
4789
  let ubContent = await readFile3(ubFile, "utf-8");
4676
4790
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4677
4791
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4706,7 +4820,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4706
4820
  const scScope = sessionScopeFilter();
4707
4821
  const remaining = await client.execute({
4708
4822
  sql: `SELECT COUNT(*) as cnt FROM tasks
4709
- 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}`,
4710
4824
  args: [parentTaskId, ...scScope.args]
4711
4825
  });
4712
4826
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4740,7 +4854,7 @@ var init_tasks_chain = __esm({
4740
4854
 
4741
4855
  // src/lib/project-name.ts
4742
4856
  import { execSync as execSync6 } from "child_process";
4743
- import path14 from "path";
4857
+ import path15 from "path";
4744
4858
  function getProjectName(cwd) {
4745
4859
  const dir = cwd ?? process.cwd();
4746
4860
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4753,7 +4867,7 @@ function getProjectName(cwd) {
4753
4867
  timeout: 2e3,
4754
4868
  stdio: ["pipe", "pipe", "pipe"]
4755
4869
  }).trim();
4756
- repoRoot = path14.dirname(gitCommonDir);
4870
+ repoRoot = path15.dirname(gitCommonDir);
4757
4871
  } catch {
4758
4872
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4759
4873
  cwd: dir,
@@ -4762,11 +4876,11 @@ function getProjectName(cwd) {
4762
4876
  stdio: ["pipe", "pipe", "pipe"]
4763
4877
  }).trim();
4764
4878
  }
4765
- _cached2 = path14.basename(repoRoot);
4879
+ _cached2 = path15.basename(repoRoot);
4766
4880
  _cachedCwd = dir;
4767
4881
  return _cached2;
4768
4882
  } catch {
4769
- _cached2 = path14.basename(dir);
4883
+ _cached2 = path15.basename(dir);
4770
4884
  _cachedCwd = dir;
4771
4885
  return _cached2;
4772
4886
  }
@@ -4909,10 +5023,10 @@ var init_tasks_notify = __esm({
4909
5023
  });
4910
5024
 
4911
5025
  // src/lib/behaviors.ts
4912
- import crypto4 from "crypto";
5026
+ import crypto5 from "crypto";
4913
5027
  async function storeBehavior(opts) {
4914
5028
  const client = getClient();
4915
- const id = crypto4.randomUUID();
5029
+ const id = crypto5.randomUUID();
4916
5030
  const now = (/* @__PURE__ */ new Date()).toISOString();
4917
5031
  await client.execute({
4918
5032
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4941,7 +5055,7 @@ __export(skill_learning_exports, {
4941
5055
  storeTrajectory: () => storeTrajectory,
4942
5056
  sweepTrajectories: () => sweepTrajectories
4943
5057
  });
4944
- import crypto5 from "crypto";
5058
+ import crypto6 from "crypto";
4945
5059
  async function extractTrajectory(taskId, agentId) {
4946
5060
  const client = getClient();
4947
5061
  const result = await client.execute({
@@ -4970,11 +5084,11 @@ async function extractTrajectory(taskId, agentId) {
4970
5084
  return signature;
4971
5085
  }
4972
5086
  function hashSignature(signature) {
4973
- 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);
4974
5088
  }
4975
5089
  async function storeTrajectory(opts) {
4976
5090
  const client = getClient();
4977
- const id = crypto5.randomUUID();
5091
+ const id = crypto6.randomUUID();
4978
5092
  const now = (/* @__PURE__ */ new Date()).toISOString();
4979
5093
  const signatureHash = hashSignature(opts.signature);
4980
5094
  await client.execute({
@@ -5239,8 +5353,8 @@ __export(tasks_exports, {
5239
5353
  updateTaskStatus: () => updateTaskStatus,
5240
5354
  writeCheckpoint: () => writeCheckpoint
5241
5355
  });
5242
- import path15 from "path";
5243
- 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";
5244
5358
  async function createTask(input) {
5245
5359
  const result = await createTaskCore(input);
5246
5360
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5259,12 +5373,12 @@ async function updateTask(input) {
5259
5373
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5260
5374
  try {
5261
5375
  const agent = String(row.assigned_to);
5262
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5263
- const cachePath = path15.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`);
5264
5378
  if (input.status === "in_progress") {
5265
5379
  mkdirSync5(cacheDir, { recursive: true });
5266
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5267
- } 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") {
5268
5382
  try {
5269
5383
  unlinkSync5(cachePath);
5270
5384
  } catch {
@@ -5272,10 +5386,10 @@ async function updateTask(input) {
5272
5386
  }
5273
5387
  } catch {
5274
5388
  }
5275
- if (input.status === "done") {
5389
+ if (input.status === "done" || input.status === "closed") {
5276
5390
  await cleanupReviewFile(row, taskFile, input.baseDir);
5277
5391
  }
5278
- if (input.status === "done" || input.status === "cancelled") {
5392
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5279
5393
  try {
5280
5394
  const client = getClient();
5281
5395
  const taskTitle = String(row.title);
@@ -5291,7 +5405,7 @@ async function updateTask(input) {
5291
5405
  if (!isCoordinatorName(assignedAgent)) {
5292
5406
  try {
5293
5407
  const draftClient = getClient();
5294
- if (input.status === "done") {
5408
+ if (input.status === "done" || input.status === "closed") {
5295
5409
  await draftClient.execute({
5296
5410
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5297
5411
  args: [assignedAgent]
@@ -5308,7 +5422,7 @@ async function updateTask(input) {
5308
5422
  try {
5309
5423
  const client = getClient();
5310
5424
  const cascaded = await client.execute({
5311
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5425
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5312
5426
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5313
5427
  args: [now, taskId]
5314
5428
  });
@@ -5321,14 +5435,14 @@ async function updateTask(input) {
5321
5435
  } catch {
5322
5436
  }
5323
5437
  }
5324
- const isTerminal = input.status === "done" || input.status === "needs_review";
5438
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5325
5439
  if (isTerminal) {
5326
5440
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5327
5441
  if (!isCoordinator) {
5328
5442
  notifyTaskDone();
5329
5443
  }
5330
5444
  await markTaskNotificationsRead(taskFile);
5331
- if (input.status === "done") {
5445
+ if (input.status === "done" || input.status === "closed") {
5332
5446
  try {
5333
5447
  await cascadeUnblock(taskId, input.baseDir, now);
5334
5448
  } catch {
@@ -5348,7 +5462,7 @@ async function updateTask(input) {
5348
5462
  }
5349
5463
  }
5350
5464
  }
5351
- 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) {
5352
5466
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5353
5467
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5354
5468
  taskId,
@@ -5720,6 +5834,7 @@ __export(tmux_routing_exports, {
5720
5834
  isEmployeeAlive: () => isEmployeeAlive,
5721
5835
  isExeSession: () => isExeSession,
5722
5836
  isSessionBusy: () => isSessionBusy,
5837
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5723
5838
  notifyParentExe: () => notifyParentExe,
5724
5839
  parseParentExe: () => parseParentExe,
5725
5840
  registerParentExe: () => registerParentExe,
@@ -5730,13 +5845,13 @@ __export(tmux_routing_exports, {
5730
5845
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5731
5846
  });
5732
5847
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5733
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5734
- import path16 from "path";
5735
- import os9 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";
5736
5851
  import { fileURLToPath as fileURLToPath2 } from "url";
5737
5852
  import { unlinkSync as unlinkSync6 } from "fs";
5738
5853
  function spawnLockPath(sessionName) {
5739
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5854
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5740
5855
  }
5741
5856
  function isProcessAlive(pid) {
5742
5857
  try {
@@ -5747,13 +5862,13 @@ function isProcessAlive(pid) {
5747
5862
  }
5748
5863
  }
5749
5864
  function acquireSpawnLock2(sessionName) {
5750
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5865
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5751
5866
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5752
5867
  }
5753
5868
  const lockFile = spawnLockPath(sessionName);
5754
- if (existsSync12(lockFile)) {
5869
+ if (existsSync14(lockFile)) {
5755
5870
  try {
5756
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5871
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5757
5872
  const age = Date.now() - lock.timestamp;
5758
5873
  if (isProcessAlive(lock.pid) && age < 6e4) {
5759
5874
  return false;
@@ -5761,7 +5876,7 @@ function acquireSpawnLock2(sessionName) {
5761
5876
  } catch {
5762
5877
  }
5763
5878
  }
5764
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5879
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5765
5880
  return true;
5766
5881
  }
5767
5882
  function releaseSpawnLock2(sessionName) {
@@ -5773,13 +5888,13 @@ function releaseSpawnLock2(sessionName) {
5773
5888
  function resolveBehaviorsExporterScript() {
5774
5889
  try {
5775
5890
  const thisFile = fileURLToPath2(import.meta.url);
5776
- const scriptPath = path16.join(
5777
- path16.dirname(thisFile),
5891
+ const scriptPath = path17.join(
5892
+ path17.dirname(thisFile),
5778
5893
  "..",
5779
5894
  "bin",
5780
5895
  "exe-export-behaviors.js"
5781
5896
  );
5782
- return existsSync12(scriptPath) ? scriptPath : null;
5897
+ return existsSync14(scriptPath) ? scriptPath : null;
5783
5898
  } catch {
5784
5899
  return null;
5785
5900
  }
@@ -5845,12 +5960,12 @@ function extractRootExe(name) {
5845
5960
  return parts.length > 0 ? parts[parts.length - 1] : null;
5846
5961
  }
5847
5962
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5848
- if (!existsSync12(SESSION_CACHE)) {
5963
+ if (!existsSync14(SESSION_CACHE)) {
5849
5964
  mkdirSync6(SESSION_CACHE, { recursive: true });
5850
5965
  }
5851
5966
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5852
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5853
- writeFileSync7(filePath, JSON.stringify({
5967
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5968
+ writeFileSync8(filePath, JSON.stringify({
5854
5969
  parentExe: rootExe,
5855
5970
  dispatchedBy: dispatchedBy || rootExe,
5856
5971
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5858,7 +5973,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5858
5973
  }
5859
5974
  function getParentExe(sessionKey) {
5860
5975
  try {
5861
- const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5976
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5862
5977
  return data.parentExe || null;
5863
5978
  } catch {
5864
5979
  return null;
@@ -5866,8 +5981,8 @@ function getParentExe(sessionKey) {
5866
5981
  }
5867
5982
  function getDispatchedBy(sessionKey) {
5868
5983
  try {
5869
- const data = JSON.parse(readFileSync11(
5870
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5984
+ const data = JSON.parse(readFileSync12(
5985
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5871
5986
  "utf8"
5872
5987
  ));
5873
5988
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5937,8 +6052,8 @@ async function verifyPaneAtCapacity(sessionName) {
5937
6052
  }
5938
6053
  function readDebounceState() {
5939
6054
  try {
5940
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5941
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6055
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
6056
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5942
6057
  const state = {};
5943
6058
  for (const [key, val] of Object.entries(raw)) {
5944
6059
  if (typeof val === "number") {
@@ -5954,8 +6069,8 @@ function readDebounceState() {
5954
6069
  }
5955
6070
  function writeDebounceState(state) {
5956
6071
  try {
5957
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5958
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6072
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
6073
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5959
6074
  } catch {
5960
6075
  }
5961
6076
  }
@@ -6053,8 +6168,8 @@ function sendIntercom(targetSession) {
6053
6168
  try {
6054
6169
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6055
6170
  const agent = baseAgentName(rawAgent);
6056
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
6057
- if (existsSync12(markerPath)) {
6171
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6172
+ if (existsSync14(markerPath)) {
6058
6173
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6059
6174
  return "debounced";
6060
6175
  }
@@ -6063,8 +6178,8 @@ function sendIntercom(targetSession) {
6063
6178
  try {
6064
6179
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6065
6180
  const agent = baseAgentName(rawAgent);
6066
- const taskDir = path16.join(process.cwd(), "exe", agent);
6067
- if (existsSync12(taskDir)) {
6181
+ const taskDir = path17.join(process.cwd(), "exe", agent);
6182
+ if (existsSync14(taskDir)) {
6068
6183
  const files = readdirSync3(taskDir).filter(
6069
6184
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6070
6185
  );
@@ -6124,6 +6239,21 @@ function notifyParentExe(sessionKey) {
6124
6239
  }
6125
6240
  return true;
6126
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
+ }
6127
6257
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6128
6258
  if (isCoordinatorName(employeeName)) {
6129
6259
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6197,26 +6327,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6197
6327
  const transport = getTransport();
6198
6328
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6199
6329
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6200
- const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
6201
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6202
- 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)) {
6203
6333
  mkdirSync6(logDir, { recursive: true });
6204
6334
  }
6205
6335
  transport.kill(sessionName);
6206
6336
  let cleanupSuffix = "";
6207
6337
  try {
6208
6338
  const thisFile = fileURLToPath2(import.meta.url);
6209
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6210
- if (existsSync12(cleanupScript)) {
6339
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6340
+ if (existsSync14(cleanupScript)) {
6211
6341
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6212
6342
  }
6213
6343
  } catch {
6214
6344
  }
6215
6345
  try {
6216
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
6346
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
6217
6347
  let claudeJson = {};
6218
6348
  try {
6219
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
6349
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6220
6350
  } catch {
6221
6351
  }
6222
6352
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6224,17 +6354,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6224
6354
  const trustDir = opts?.cwd ?? projectDir;
6225
6355
  if (!projects[trustDir]) projects[trustDir] = {};
6226
6356
  projects[trustDir].hasTrustDialogAccepted = true;
6227
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6357
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6228
6358
  } catch {
6229
6359
  }
6230
6360
  try {
6231
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
6361
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
6232
6362
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6233
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
6234
- const settingsPath = path16.join(projSettingsDir, "settings.json");
6363
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6364
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
6235
6365
  let settings = {};
6236
6366
  try {
6237
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6367
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6238
6368
  } catch {
6239
6369
  }
6240
6370
  const perms = settings.permissions ?? {};
@@ -6263,7 +6393,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6263
6393
  perms.allow = allow;
6264
6394
  settings.permissions = perms;
6265
6395
  mkdirSync6(projSettingsDir, { recursive: true });
6266
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6396
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6267
6397
  }
6268
6398
  } catch {
6269
6399
  }
@@ -6278,8 +6408,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6278
6408
  let behaviorsFlag = "";
6279
6409
  let legacyFallbackWarned = false;
6280
6410
  if (!useExeAgent && !useBinSymlink) {
6281
- const identityPath = path16.join(
6282
- os9.homedir(),
6411
+ const identityPath = path17.join(
6412
+ os10.homedir(),
6283
6413
  ".exe-os",
6284
6414
  "identity",
6285
6415
  `${employeeName}.md`
@@ -6288,13 +6418,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6288
6418
  const hasAgentFlag = claudeSupportsAgentFlag();
6289
6419
  if (hasAgentFlag) {
6290
6420
  identityFlag = ` --agent ${employeeName}`;
6291
- } else if (existsSync12(identityPath)) {
6421
+ } else if (existsSync14(identityPath)) {
6292
6422
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6293
6423
  legacyFallbackWarned = true;
6294
6424
  }
6295
6425
  const behaviorsFile = exportBehaviorsSync(
6296
6426
  employeeName,
6297
- path16.basename(spawnCwd),
6427
+ path17.basename(spawnCwd),
6298
6428
  sessionName
6299
6429
  );
6300
6430
  if (behaviorsFile) {
@@ -6309,16 +6439,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6309
6439
  }
6310
6440
  let sessionContextFlag = "";
6311
6441
  try {
6312
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
6442
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
6313
6443
  mkdirSync6(ctxDir, { recursive: true });
6314
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
6444
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6315
6445
  const ctxContent = [
6316
6446
  `## Session Context`,
6317
6447
  `You are running in tmux session: ${sessionName}.`,
6318
6448
  `Your parent coordinator session is ${exeSession}.`,
6319
6449
  `Your employees (if any) use the -${exeSession} suffix.`
6320
6450
  ].join("\n");
6321
- writeFileSync7(ctxFile, ctxContent);
6451
+ writeFileSync8(ctxFile, ctxContent);
6322
6452
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6323
6453
  } catch {
6324
6454
  }
@@ -6395,8 +6525,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6395
6525
  transport.pipeLog(sessionName, logFile);
6396
6526
  try {
6397
6527
  const mySession = getMySession();
6398
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6399
- writeFileSync7(dispatchInfo, JSON.stringify({
6528
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6529
+ writeFileSync8(dispatchInfo, JSON.stringify({
6400
6530
  dispatchedBy: mySession,
6401
6531
  rootExe: exeSession,
6402
6532
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6470,15 +6600,15 @@ var init_tmux_routing = __esm({
6470
6600
  init_intercom_queue();
6471
6601
  init_plan_limits();
6472
6602
  init_employees();
6473
- SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
6474
- SESSION_CACHE = path16.join(os9.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");
6475
6605
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6476
6606
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6477
6607
  VERIFY_PANE_LINES = 200;
6478
6608
  INTERCOM_DEBOUNCE_MS = 3e4;
6479
6609
  CODEX_DEBOUNCE_MS = 12e4;
6480
- INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
6481
- DEBOUNCE_FILE = path16.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");
6482
6612
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6483
6613
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6484
6614
  }
@@ -6762,9 +6892,9 @@ __export(agent_signals_exports, {
6762
6892
  hasOpenTasks: () => hasOpenTasks,
6763
6893
  hasUnreadInbox: () => hasUnreadInbox
6764
6894
  });
6765
- import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
6766
- import os10 from "os";
6767
- import path17 from "path";
6895
+ import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
6896
+ import os11 from "os";
6897
+ import path18 from "path";
6768
6898
  async function hasOpenTasks(client, agentId) {
6769
6899
  try {
6770
6900
  const scope = sessionScopeFilter(null);
@@ -6806,10 +6936,10 @@ async function hasUnreadInbox(client, agentId) {
6806
6936
  return CONSERVATIVE_ON_ERROR;
6807
6937
  }
6808
6938
  }
6809
- function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path17.join(os10.homedir(), ".exe-os", "intercom.log")) {
6810
- 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;
6811
6941
  try {
6812
- const raw = readFileSync12(intercomLog, "utf8");
6942
+ const raw = readFileSync13(intercomLog, "utf8");
6813
6943
  const lines = raw.split("\n");
6814
6944
  for (let i = lines.length - 1; i >= 0; i--) {
6815
6945
  const line = lines[i];
@@ -6882,7 +7012,7 @@ __export(daemon_orchestration_exports, {
6882
7012
  shouldNudgeEmployee: () => shouldNudgeEmployee
6883
7013
  });
6884
7014
  import { execSync as execSync9 } from "child_process";
6885
- 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";
6886
7016
  import { homedir } from "os";
6887
7017
  import { join } from "path";
6888
7018
  function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
@@ -7073,8 +7203,8 @@ async function pollReviewNudge(deps, state) {
7073
7203
  function loadNudgeState() {
7074
7204
  const state = { lastNudge: /* @__PURE__ */ new Map() };
7075
7205
  try {
7076
- if (!existsSync14(NUDGE_STATE_PATH)) return state;
7077
- 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"));
7078
7208
  if (Array.isArray(raw)) {
7079
7209
  for (const [key, val] of raw) {
7080
7210
  if (key && typeof val?.at === "number" && typeof val?.count === "number") {
@@ -7088,7 +7218,7 @@ function loadNudgeState() {
7088
7218
  }
7089
7219
  function saveNudgeState(state) {
7090
7220
  const entries = Array.from(state.lastNudge.entries());
7091
- writeFileSync8(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
7221
+ writeFileSync9(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
7092
7222
  }
7093
7223
  function createReviewNudgeRealDeps(getClient2) {
7094
7224
  return {
@@ -7426,157 +7556,38 @@ var init_daemon_orchestration = __esm({
7426
7556
  }
7427
7557
  });
7428
7558
 
7429
- // src/lib/keychain.ts
7430
- var keychain_exports = {};
7431
- __export(keychain_exports, {
7432
- deleteMasterKey: () => deleteMasterKey,
7433
- exportMnemonic: () => exportMnemonic,
7434
- getMasterKey: () => getMasterKey,
7435
- importMnemonic: () => importMnemonic,
7436
- setMasterKey: () => setMasterKey
7559
+ // src/lib/shard-manager.ts
7560
+ var shard_manager_exports = {};
7561
+ __export(shard_manager_exports, {
7562
+ disposeShards: () => disposeShards,
7563
+ ensureShardSchema: () => ensureShardSchema,
7564
+ getOpenShardCount: () => getOpenShardCount,
7565
+ getReadyShardClient: () => getReadyShardClient,
7566
+ getShardClient: () => getShardClient,
7567
+ getShardsDir: () => getShardsDir,
7568
+ initShardManager: () => initShardManager,
7569
+ isShardingEnabled: () => isShardingEnabled,
7570
+ listShards: () => listShards,
7571
+ shardExists: () => shardExists
7437
7572
  });
7438
- import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
7439
- import { existsSync as existsSync15 } from "fs";
7440
- import path18 from "path";
7441
- import os11 from "os";
7442
- function getKeyDir() {
7443
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
7573
+ import path19 from "path";
7574
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
7575
+ import { createClient as createClient2 } from "@libsql/client";
7576
+ function initShardManager(encryptionKey) {
7577
+ _encryptionKey = encryptionKey;
7578
+ if (!existsSync17(SHARDS_DIR)) {
7579
+ mkdirSync7(SHARDS_DIR, { recursive: true });
7580
+ }
7581
+ _shardingEnabled = true;
7582
+ if (_evictionTimer) clearInterval(_evictionTimer);
7583
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
7584
+ _evictionTimer.unref();
7444
7585
  }
7445
- function getKeyPath() {
7446
- return path18.join(getKeyDir(), "master.key");
7586
+ function isShardingEnabled() {
7587
+ return _shardingEnabled;
7447
7588
  }
7448
- async function tryKeytar() {
7449
- try {
7450
- return await import("keytar");
7451
- } catch {
7452
- return null;
7453
- }
7454
- }
7455
- async function getMasterKey() {
7456
- const keytar = await tryKeytar();
7457
- if (keytar) {
7458
- try {
7459
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
7460
- if (stored) {
7461
- return Buffer.from(stored, "base64");
7462
- }
7463
- } catch {
7464
- }
7465
- }
7466
- const keyPath = getKeyPath();
7467
- if (!existsSync15(keyPath)) {
7468
- process.stderr.write(
7469
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
7470
- `
7471
- );
7472
- return null;
7473
- }
7474
- try {
7475
- const content = await readFile4(keyPath, "utf-8");
7476
- return Buffer.from(content.trim(), "base64");
7477
- } catch (err) {
7478
- process.stderr.write(
7479
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
7480
- `
7481
- );
7482
- return null;
7483
- }
7484
- }
7485
- async function setMasterKey(key) {
7486
- const b64 = key.toString("base64");
7487
- const keytar = await tryKeytar();
7488
- if (keytar) {
7489
- try {
7490
- await keytar.setPassword(SERVICE, ACCOUNT, b64);
7491
- return;
7492
- } catch {
7493
- }
7494
- }
7495
- const dir = getKeyDir();
7496
- await mkdir4(dir, { recursive: true });
7497
- const keyPath = getKeyPath();
7498
- await writeFile5(keyPath, b64 + "\n", "utf-8");
7499
- await chmod2(keyPath, 384);
7500
- }
7501
- async function deleteMasterKey() {
7502
- const keytar = await tryKeytar();
7503
- if (keytar) {
7504
- try {
7505
- await keytar.deletePassword(SERVICE, ACCOUNT);
7506
- } catch {
7507
- }
7508
- }
7509
- const keyPath = getKeyPath();
7510
- if (existsSync15(keyPath)) {
7511
- await unlink(keyPath);
7512
- }
7513
- }
7514
- async function loadBip39() {
7515
- try {
7516
- return await import("bip39");
7517
- } catch {
7518
- throw new Error(
7519
- "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
7520
- );
7521
- }
7522
- }
7523
- async function exportMnemonic(key) {
7524
- if (key.length !== 32) {
7525
- throw new Error(`Key must be 32 bytes, got ${key.length}`);
7526
- }
7527
- const { entropyToMnemonic } = await loadBip39();
7528
- return entropyToMnemonic(key.toString("hex"));
7529
- }
7530
- async function importMnemonic(mnemonic) {
7531
- const trimmed = mnemonic.trim();
7532
- const words = trimmed.split(/\s+/);
7533
- if (words.length !== 24) {
7534
- throw new Error(`Expected 24 words, got ${words.length}`);
7535
- }
7536
- const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
7537
- if (!validateMnemonic(trimmed)) {
7538
- throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
7539
- }
7540
- const entropy = mnemonicToEntropy(trimmed);
7541
- return Buffer.from(entropy, "hex");
7542
- }
7543
- var SERVICE, ACCOUNT;
7544
- var init_keychain = __esm({
7545
- "src/lib/keychain.ts"() {
7546
- "use strict";
7547
- SERVICE = "exe-mem";
7548
- ACCOUNT = "master-key";
7549
- }
7550
- });
7551
-
7552
- // src/lib/shard-manager.ts
7553
- var shard_manager_exports = {};
7554
- __export(shard_manager_exports, {
7555
- disposeShards: () => disposeShards,
7556
- ensureShardSchema: () => ensureShardSchema,
7557
- getReadyShardClient: () => getReadyShardClient,
7558
- getShardClient: () => getShardClient,
7559
- getShardsDir: () => getShardsDir,
7560
- initShardManager: () => initShardManager,
7561
- isShardingEnabled: () => isShardingEnabled,
7562
- listShards: () => listShards,
7563
- shardExists: () => shardExists
7564
- });
7565
- import path19 from "path";
7566
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
7567
- import { createClient as createClient2 } from "@libsql/client";
7568
- function initShardManager(encryptionKey) {
7569
- _encryptionKey = encryptionKey;
7570
- if (!existsSync16(SHARDS_DIR)) {
7571
- mkdirSync7(SHARDS_DIR, { recursive: true });
7572
- }
7573
- _shardingEnabled = true;
7574
- }
7575
- function isShardingEnabled() {
7576
- return _shardingEnabled;
7577
- }
7578
- function getShardsDir() {
7579
- return SHARDS_DIR;
7589
+ function getShardsDir() {
7590
+ return SHARDS_DIR;
7580
7591
  }
7581
7592
  function getShardClient(projectName) {
7582
7593
  if (!_encryptionKey) {
@@ -7587,21 +7598,28 @@ function getShardClient(projectName) {
7587
7598
  throw new Error(`Invalid project name for shard: "${projectName}"`);
7588
7599
  }
7589
7600
  const cached = _shards.get(safeName);
7590
- if (cached) return cached;
7601
+ if (cached) {
7602
+ _shardLastAccess.set(safeName, Date.now());
7603
+ return cached;
7604
+ }
7605
+ while (_shards.size >= MAX_OPEN_SHARDS) {
7606
+ evictLRU();
7607
+ }
7591
7608
  const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
7592
7609
  const client = createClient2({
7593
7610
  url: `file:${dbPath}`,
7594
7611
  encryptionKey: _encryptionKey
7595
7612
  });
7596
7613
  _shards.set(safeName, client);
7614
+ _shardLastAccess.set(safeName, Date.now());
7597
7615
  return client;
7598
7616
  }
7599
7617
  function shardExists(projectName) {
7600
7618
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
7601
- return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
7619
+ return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
7602
7620
  }
7603
7621
  function listShards() {
7604
- if (!existsSync16(SHARDS_DIR)) return [];
7622
+ if (!existsSync17(SHARDS_DIR)) return [];
7605
7623
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
7606
7624
  }
7607
7625
  async function ensureShardSchema(client) {
@@ -7653,6 +7671,8 @@ async function ensureShardSchema(client) {
7653
7671
  for (const col of [
7654
7672
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
7655
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'",
7656
7676
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
7657
7677
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
7658
7678
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -7790,26 +7810,197 @@ async function getReadyShardClient(projectName) {
7790
7810
  await ensureShardSchema(client);
7791
7811
  return client;
7792
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;
7850
+ }
7793
7851
  function disposeShards() {
7852
+ if (_evictionTimer) {
7853
+ clearInterval(_evictionTimer);
7854
+ _evictionTimer = null;
7855
+ }
7794
7856
  for (const [, client] of _shards) {
7795
7857
  client.close();
7796
7858
  }
7797
7859
  _shards.clear();
7860
+ _shardLastAccess.clear();
7798
7861
  _shardingEnabled = false;
7799
7862
  _encryptionKey = null;
7800
7863
  }
7801
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
7864
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
7802
7865
  var init_shard_manager = __esm({
7803
7866
  "src/lib/shard-manager.ts"() {
7804
7867
  "use strict";
7805
7868
  init_config();
7806
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;
7807
7873
  _shards = /* @__PURE__ */ new Map();
7874
+ _shardLastAccess = /* @__PURE__ */ new Map();
7875
+ _evictionTimer = null;
7808
7876
  _encryptionKey = null;
7809
7877
  _shardingEnabled = false;
7810
7878
  }
7811
7879
  });
7812
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
+
7813
8004
  // src/lib/platform-procedures.ts
7814
8005
  var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
7815
8006
  var init_platform_procedures = __esm({
@@ -8582,15 +8773,15 @@ async function pollPendingReviews(deps, state) {
8582
8773
  return [];
8583
8774
  }
8584
8775
  if (sessions.length === 0) return [];
8585
- let reviewCount;
8586
- try {
8587
- reviewCount = await deps.countPendingReviews();
8588
- } catch {
8589
- return [];
8590
- }
8591
- if (reviewCount === 0) return [];
8592
8776
  const sent = [];
8593
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;
8594
8785
  const lastSent = state.lastIntercomSent.get(exeSession) ?? 0;
8595
8786
  if (Date.now() - lastSent < state.intervalMs) continue;
8596
8787
  try {
@@ -8600,15 +8791,17 @@ async function pollPendingReviews(deps, state) {
8600
8791
  } catch {
8601
8792
  }
8602
8793
  }
8603
- try {
8604
- const orphans = await deps.findOrphanedDoneTasks();
8605
- for (const orphan of orphans) {
8606
- try {
8607
- await deps.createReviewForOrphan(orphan);
8608
- } 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
+ }
8609
8802
  }
8803
+ } catch {
8610
8804
  }
8611
- } catch {
8612
8805
  }
8613
8806
  if (deps.findStaleTasks && deps.sendNudge) {
8614
8807
  try {
@@ -8626,50 +8819,56 @@ async function pollPendingReviews(deps, state) {
8626
8819
  }
8627
8820
  }
8628
8821
  if (deps.findUrgentUnread) {
8629
- try {
8630
- const urgent = await deps.findUrgentUnread();
8631
- for (const msg of urgent) {
8632
- try {
8633
- const employeeSessions = sessions.length > 0 ? deps.listTmuxSessions().filter((s) => s.startsWith(`${msg.target_agent}-`)) : [];
8634
- for (const sess of employeeSessions) {
8635
- deps.sendIntercom(sess);
8636
- }
8637
- const ageMs = Date.now() - new Date(msg.created_at).getTime();
8638
- if (ageMs > 5 * 60 * 1e3) {
8639
- process.stderr.write(
8640
- `[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
8641
- `
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}`)
8642
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 {
8643
8841
  }
8644
- } catch {
8645
8842
  }
8843
+ } catch {
8646
8844
  }
8647
- } catch {
8648
8845
  }
8649
8846
  }
8650
8847
  if (deps.findUnstartedTasks) {
8651
- try {
8652
- const unstarted = await deps.findUnstartedTasks();
8653
- for (const task of unstarted) {
8654
- try {
8655
- const employeeSessions = deps.listTmuxSessions().filter(
8656
- (s) => s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)
8657
- );
8658
- for (const sess of employeeSessions) {
8659
- deps.sendIntercom(sess);
8660
- }
8661
- const ageMs = Date.now() - new Date(task.created_at).getTime();
8662
- const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
8663
- if (ageMs > UNSTARTED_WARNING_MS) {
8664
- process.stderr.write(
8665
- `[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
8666
- `
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}`)
8667
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 {
8668
8868
  }
8669
- } catch {
8670
8869
  }
8870
+ } catch {
8671
8871
  }
8672
- } catch {
8673
8872
  }
8674
8873
  }
8675
8874
  return sent;
@@ -8682,9 +8881,9 @@ function createRealDeps(getClient2) {
8682
8881
  timeout: 3e3
8683
8882
  }).trim().split("\n").filter(Boolean);
8684
8883
  },
8685
- countPendingReviews: async () => {
8884
+ countPendingReviews: async (sessionScope) => {
8686
8885
  const client = getClient2();
8687
- const rpScope = sessionScopeFilter();
8886
+ const rpScope = strictSessionScopeFilter(sessionScope);
8688
8887
  const result = await client.execute({
8689
8888
  sql: `SELECT COUNT(*) as count FROM tasks
8690
8889
  WHERE status = 'needs_review'${rpScope.sql}`,
@@ -8696,10 +8895,10 @@ function createRealDeps(getClient2) {
8696
8895
  const { sendIntercom: centralSend } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
8697
8896
  centralSend(session);
8698
8897
  },
8699
- findOrphanedDoneTasks: async () => {
8898
+ findOrphanedDoneTasks: async (sessionScope) => {
8700
8899
  const client = getClient2();
8701
8900
  const coordinatorName = getCoordinatorName();
8702
- const odScope = sessionScopeFilter(void 0, "t");
8901
+ const odScope = strictSessionScopeFilter(sessionScope, "t");
8703
8902
  const result = await client.execute({
8704
8903
  sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
8705
8904
  t.project_name, t.task_file, t.result, t.status
@@ -8724,24 +8923,25 @@ function createRealDeps(getClient2) {
8724
8923
  process.stderr.write(`[exed] Created missing review for: ${task.title} (${task.assigned_to})
8725
8924
  `);
8726
8925
  },
8727
- findUrgentUnread: async () => {
8926
+ findUrgentUnread: async (sessionScope) => {
8728
8927
  const client = getClient2();
8928
+ const msgScope = strictSessionScopeFilter(sessionScope);
8729
8929
  const result = await client.execute({
8730
8930
  sql: `SELECT id, target_agent, content, created_at
8731
8931
  FROM messages
8732
8932
  WHERE priority = 'urgent'
8733
8933
  AND status IN ('pending', 'delivered')
8734
- AND created_at <= datetime('now', '-2 minutes')
8934
+ AND created_at <= datetime('now', '-2 minutes')${msgScope.sql}
8735
8935
  ORDER BY created_at ASC
8736
8936
  LIMIT 10`,
8737
- args: []
8937
+ args: [...msgScope.args]
8738
8938
  });
8739
8939
  return result.rows;
8740
8940
  },
8741
- findUnstartedTasks: async () => {
8941
+ findUnstartedTasks: async (sessionScope) => {
8742
8942
  const client = getClient2();
8743
8943
  const coordinatorName = getCoordinatorName();
8744
- const usScope = sessionScopeFilter();
8944
+ const usScope = strictSessionScopeFilter(sessionScope);
8745
8945
  const result = await client.execute({
8746
8946
  sql: `SELECT id, title, assigned_to, created_at
8747
8947
  FROM tasks
@@ -9176,10 +9376,10 @@ async function disposeEmbedder() {
9176
9376
  async function embedDirect(text) {
9177
9377
  const llamaCpp = await import("node-llama-cpp");
9178
9378
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
9179
- const { existsSync: existsSync19 } = await import("fs");
9180
- const path24 = await import("path");
9181
- const modelPath = path24.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
9182
- 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)) {
9183
9383
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
9184
9384
  }
9185
9385
  const llama = await llamaCpp.getLlama();
@@ -9401,13 +9601,13 @@ __export(graph_rag_exports, {
9401
9601
  resolveAlias: () => resolveAlias,
9402
9602
  storeExtraction: () => storeExtraction
9403
9603
  });
9404
- import crypto6 from "crypto";
9604
+ import crypto7 from "crypto";
9405
9605
  function normalizeEntityName(name) {
9406
9606
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
9407
9607
  }
9408
9608
  function entityId(name, type) {
9409
9609
  const normalized = normalizeEntityName(name);
9410
- 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);
9411
9611
  }
9412
9612
  async function resolveAlias(client, name) {
9413
9613
  const normalized = normalizeEntityName(name);
@@ -9657,7 +9857,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
9657
9857
  const targetAlias = await resolveAlias(client, r.target);
9658
9858
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
9659
9859
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
9660
- const relId = crypto6.randomUUID().slice(0, 16);
9860
+ const relId = crypto7.randomUUID().slice(0, 16);
9661
9861
  try {
9662
9862
  await client.execute({
9663
9863
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -9720,7 +9920,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
9720
9920
  }
9721
9921
  }
9722
9922
  for (const h of extraction.hyperedges) {
9723
- const hId = crypto6.randomUUID().slice(0, 16);
9923
+ const hId = crypto7.randomUUID().slice(0, 16);
9724
9924
  try {
9725
9925
  await client.execute({
9726
9926
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -9784,7 +9984,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
9784
9984
  totalEntities += stored.entitiesStored;
9785
9985
  totalRelationships += stored.relationshipsStored;
9786
9986
  }
9787
- 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);
9788
9988
  await client.execute({
9789
9989
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
9790
9990
  args: [contentHash, contentHash, memoryId]
@@ -9901,8 +10101,8 @@ __export(wiki_sync_exports, {
9901
10101
  listWorkspaces: () => listWorkspaces,
9902
10102
  syncMemories: () => syncMemories
9903
10103
  });
9904
- async function wikiRequest(config, path24, method = "GET", body) {
9905
- const url = `${config.wikiUrl}/api/v1${path24}`;
10104
+ async function wikiRequest(config, path25, method = "GET", body) {
10105
+ const url = `${config.wikiUrl}/api/v1${path25}`;
9906
10106
  const headers = {
9907
10107
  "Authorization": `Bearer ${config.wikiApiKey}`,
9908
10108
  "Content-Type": "application/json"
@@ -9914,7 +10114,7 @@ async function wikiRequest(config, path24, method = "GET", body) {
9914
10114
  signal: AbortSignal.timeout(3e4)
9915
10115
  });
9916
10116
  if (!response.ok) {
9917
- throw new Error(`Wiki API ${method} ${path24}: ${response.status} ${response.statusText}`);
10117
+ throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
9918
10118
  }
9919
10119
  return response.json();
9920
10120
  }
@@ -10026,8 +10226,8 @@ __export(token_spend_exports, {
10026
10226
  import { readdir } from "fs/promises";
10027
10227
  import { createReadStream } from "fs";
10028
10228
  import { createInterface } from "readline";
10029
- import path20 from "path";
10030
- import os12 from "os";
10229
+ import path21 from "path";
10230
+ import os13 from "os";
10031
10231
  function getPricing(model) {
10032
10232
  if (MODEL_PRICING[model]) return MODEL_PRICING[model];
10033
10233
  const stripped = model.replace(/-\d{8}$/, "");
@@ -10039,29 +10239,33 @@ function getPricing(model) {
10039
10239
  return DEFAULT_PRICING;
10040
10240
  }
10041
10241
  async function getAgentSpend(period = "7d") {
10242
+ const cached = _spendCache.get(period);
10243
+ if (cached && Date.now() < cached.expires) {
10244
+ return cached.result;
10245
+ }
10042
10246
  const cutoff = periodToCutoff(period);
10043
10247
  const client = getClient();
10044
- const result = await client.execute({
10248
+ const dbResult = await client.execute({
10045
10249
  sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
10046
10250
  args: [cutoff]
10047
10251
  });
10048
- if (result.rows.length === 0) return [];
10252
+ if (dbResult.rows.length === 0) return [];
10049
10253
  const sessionAgent = /* @__PURE__ */ new Map();
10050
- for (const row of result.rows) {
10254
+ for (const row of dbResult.rows) {
10051
10255
  sessionAgent.set(row.session_uuid, row.agent_id);
10052
10256
  }
10053
- const claudeDir = path20.join(os12.homedir(), ".claude", "projects");
10257
+ const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
10054
10258
  let projectDirs = [];
10055
10259
  try {
10056
10260
  const entries = await readdir(claudeDir);
10057
- projectDirs = entries.map((e) => path20.join(claudeDir, e));
10261
+ projectDirs = entries.map((e) => path21.join(claudeDir, e));
10058
10262
  } catch {
10059
10263
  return [];
10060
10264
  }
10061
10265
  const agentTotals = /* @__PURE__ */ new Map();
10062
10266
  for (const [sessionUuid, agentId] of sessionAgent) {
10063
10267
  for (const dir of projectDirs) {
10064
- const jsonlPath = path20.join(dir, `${sessionUuid}.jsonl`);
10268
+ const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
10065
10269
  try {
10066
10270
  const usage = await extractSessionUsage(jsonlPath);
10067
10271
  if (usage.input === 0 && usage.output === 0) continue;
@@ -10085,7 +10289,7 @@ async function getAgentSpend(period = "7d") {
10085
10289
  }
10086
10290
  }
10087
10291
  }
10088
- return Array.from(agentTotals.entries()).map(([agentId, t]) => ({
10292
+ const result = Array.from(agentTotals.entries()).map(([agentId, t]) => ({
10089
10293
  agentId,
10090
10294
  inputTokens: t.input,
10091
10295
  outputTokens: t.output,
@@ -10095,6 +10299,8 @@ async function getAgentSpend(period = "7d") {
10095
10299
  sessions: t.sessions.size,
10096
10300
  period
10097
10301
  })).sort((a, b) => b.costUSD - a.costUSD);
10302
+ _spendCache.set(period, { result, expires: Date.now() + CACHE_TTL_MS });
10303
+ return result;
10098
10304
  }
10099
10305
  async function extractSessionUsage(jsonlPath) {
10100
10306
  let input = 0;
@@ -10141,7 +10347,7 @@ function periodToCutoff(period) {
10141
10347
  const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
10142
10348
  return new Date(Date.now() - ms).toISOString();
10143
10349
  }
10144
- var MODEL_PRICING, DEFAULT_PRICING;
10350
+ var MODEL_PRICING, DEFAULT_PRICING, CACHE_TTL_MS, _spendCache;
10145
10351
  var init_token_spend = __esm({
10146
10352
  "src/lib/token-spend.ts"() {
10147
10353
  "use strict";
@@ -10171,6 +10377,8 @@ var init_token_spend = __esm({
10171
10377
  "claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
10172
10378
  };
10173
10379
  DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
10380
+ CACHE_TTL_MS = 5 * 60 * 1e3;
10381
+ _spendCache = /* @__PURE__ */ new Map();
10174
10382
  }
10175
10383
  });
10176
10384
 
@@ -10182,11 +10390,11 @@ __export(update_check_exports, {
10182
10390
  getRemoteVersion: () => getRemoteVersion
10183
10391
  });
10184
10392
  import { execSync as execSync11 } from "child_process";
10185
- import { readFileSync as readFileSync14 } from "fs";
10186
- import path21 from "path";
10393
+ import { readFileSync as readFileSync15 } from "fs";
10394
+ import path22 from "path";
10187
10395
  function getLocalVersion(packageRoot) {
10188
- const pkgPath = path21.join(packageRoot, "package.json");
10189
- 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"));
10190
10398
  return pkg.version;
10191
10399
  }
10192
10400
  function getRemoteVersion() {
@@ -10229,16 +10437,16 @@ __export(ws_auth_exports, {
10229
10437
  deriveWsAuthToken: () => deriveWsAuthToken,
10230
10438
  hashAuthToken: () => hashAuthToken
10231
10439
  });
10232
- import crypto7 from "crypto";
10440
+ import crypto8 from "crypto";
10233
10441
  function deriveWsAuthToken(masterKey) {
10234
- 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));
10235
10443
  }
10236
10444
  function deriveOrgId(masterKey) {
10237
- const raw = Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
10238
- 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);
10239
10447
  }
10240
10448
  function hashAuthToken(token) {
10241
- return crypto7.createHash("sha256").update(token).digest("hex");
10449
+ return crypto8.createHash("sha256").update(token).digest("hex");
10242
10450
  }
10243
10451
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
10244
10452
  var init_ws_auth = __esm({
@@ -10256,14 +10464,14 @@ __export(device_registry_exports, {
10256
10464
  resolveTargetDevice: () => resolveTargetDevice,
10257
10465
  setFriendlyName: () => setFriendlyName
10258
10466
  });
10259
- import crypto8 from "crypto";
10260
- import os13 from "os";
10261
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync17 } from "fs";
10262
- import path22 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";
10263
10471
  function getDeviceInfo() {
10264
- if (existsSync17(DEVICE_JSON_PATH)) {
10472
+ if (existsSync19(DEVICE_JSON_PATH)) {
10265
10473
  try {
10266
- const raw = readFileSync15(DEVICE_JSON_PATH, "utf8");
10474
+ const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
10267
10475
  const data = JSON.parse(raw);
10268
10476
  if (data.deviceId && data.friendlyName && data.hostname) {
10269
10477
  return data;
@@ -10271,20 +10479,20 @@ function getDeviceInfo() {
10271
10479
  } catch {
10272
10480
  }
10273
10481
  }
10274
- const hostname = os13.hostname();
10482
+ const hostname = os14.hostname();
10275
10483
  const info = {
10276
- deviceId: crypto8.randomUUID(),
10484
+ deviceId: crypto9.randomUUID(),
10277
10485
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
10278
10486
  hostname
10279
10487
  };
10280
- mkdirSync8(path22.dirname(DEVICE_JSON_PATH), { recursive: true });
10281
- 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));
10282
10490
  return info;
10283
10491
  }
10284
10492
  function setFriendlyName(name) {
10285
10493
  const info = getDeviceInfo();
10286
10494
  info.friendlyName = name;
10287
- writeFileSync9(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10495
+ writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10288
10496
  }
10289
10497
  async function resolveTargetDevice(targetAgent, targetProject) {
10290
10498
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -10318,7 +10526,7 @@ var init_device_registry = __esm({
10318
10526
  "src/lib/device-registry.ts"() {
10319
10527
  "use strict";
10320
10528
  init_config();
10321
- DEVICE_JSON_PATH = path22.join(EXE_AI_DIR, "device.json");
10529
+ DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
10322
10530
  }
10323
10531
  });
10324
10532
 
@@ -10542,10 +10750,10 @@ __export(messaging_exports, {
10542
10750
  sendMessage: () => sendMessage,
10543
10751
  setWsClientSend: () => setWsClientSend
10544
10752
  });
10545
- import crypto9 from "crypto";
10753
+ import crypto10 from "crypto";
10546
10754
  function generateUlid() {
10547
10755
  const timestamp = Date.now().toString(36).padStart(10, "0");
10548
- const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
10756
+ const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
10549
10757
  return (timestamp + random).toUpperCase();
10550
10758
  }
10551
10759
  function rowToMessage(row) {
@@ -10556,6 +10764,7 @@ function rowToMessage(row) {
10556
10764
  targetAgent: row.target_agent,
10557
10765
  targetProject: row.target_project ?? null,
10558
10766
  targetDevice: row.target_device,
10767
+ sessionScope: row.session_scope ?? null,
10559
10768
  content: row.content,
10560
10769
  priority: row.priority ?? "normal",
10561
10770
  status: row.status ?? "pending",
@@ -10573,15 +10782,17 @@ async function sendMessage(input) {
10573
10782
  const id = generateUlid();
10574
10783
  const now = (/* @__PURE__ */ new Date()).toISOString();
10575
10784
  const targetDevice = input.targetDevice ?? "local";
10785
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
10576
10786
  await client.execute({
10577
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
10578
- 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', ?)`,
10579
10789
  args: [
10580
10790
  id,
10581
10791
  input.fromAgent,
10582
10792
  input.targetAgent,
10583
10793
  input.targetProject ?? null,
10584
10794
  targetDevice,
10795
+ sessionScope,
10585
10796
  input.content,
10586
10797
  input.priority ?? "normal",
10587
10798
  now
@@ -10595,9 +10806,10 @@ async function sendMessage(input) {
10595
10806
  }
10596
10807
  } catch {
10597
10808
  }
10809
+ const sentScope = strictSessionScopeFilter(sessionScope);
10598
10810
  const result = await client.execute({
10599
- sql: "SELECT * FROM messages WHERE id = ?",
10600
- args: [id]
10811
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
10812
+ args: [id, ...sentScope.args]
10601
10813
  });
10602
10814
  return rowToMessage(result.rows[0]);
10603
10815
  }
@@ -10621,6 +10833,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
10621
10833
  fromAgent: msg.fromAgent,
10622
10834
  targetAgent: msg.targetAgent,
10623
10835
  targetProject: msg.targetProject,
10836
+ sessionScope: msg.sessionScope,
10624
10837
  content: msg.content,
10625
10838
  priority: msg.priority,
10626
10839
  createdAt: msg.createdAt
@@ -10664,7 +10877,7 @@ async function deliverLocalMessage(messageId) {
10664
10877
  } catch {
10665
10878
  const newRetryCount = msg.retryCount + 1;
10666
10879
  if (newRetryCount >= MAX_RETRIES3) {
10667
- await markFailed(messageId, "session unavailable after 10 retries");
10880
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
10668
10881
  } else {
10669
10882
  await client.execute({
10670
10883
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -10674,85 +10887,101 @@ async function deliverLocalMessage(messageId) {
10674
10887
  return false;
10675
10888
  }
10676
10889
  }
10677
- async function getPendingMessages(targetAgent) {
10890
+ async function getPendingMessages(targetAgent, sessionScope) {
10678
10891
  const client = getClient();
10892
+ const scope = strictSessionScopeFilter(sessionScope);
10679
10893
  const result = await client.execute({
10680
10894
  sql: `SELECT * FROM messages
10681
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
10895
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
10682
10896
  ORDER BY id`,
10683
- args: [targetAgent]
10897
+ args: [targetAgent, ...scope.args]
10684
10898
  });
10685
10899
  return result.rows.map((row) => rowToMessage(row));
10686
10900
  }
10687
- async function markRead(messageId) {
10901
+ async function markRead(messageId, sessionScope) {
10688
10902
  const client = getClient();
10903
+ const scope = strictSessionScopeFilter(sessionScope);
10689
10904
  await client.execute({
10690
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
10691
- args: [messageId]
10905
+ sql: `UPDATE messages SET status = 'read'
10906
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
10907
+ args: [messageId, ...scope.args]
10692
10908
  });
10693
10909
  }
10694
- async function markAcknowledged(messageId) {
10910
+ async function markAcknowledged(messageId, sessionScope) {
10695
10911
  const client = getClient();
10912
+ const scope = strictSessionScopeFilter(sessionScope);
10696
10913
  await client.execute({
10697
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
10698
- 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]
10699
10917
  });
10700
10918
  }
10701
- async function markProcessed(messageId) {
10919
+ async function markProcessed(messageId, sessionScope) {
10702
10920
  const client = getClient();
10921
+ const scope = strictSessionScopeFilter(sessionScope);
10703
10922
  await client.execute({
10704
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
10705
- 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]
10706
10926
  });
10707
10927
  }
10708
- async function getMessageStatus(messageId) {
10928
+ async function getMessageStatus(messageId, sessionScope) {
10709
10929
  const client = getClient();
10930
+ const scope = strictSessionScopeFilter(sessionScope);
10710
10931
  const result = await client.execute({
10711
- sql: "SELECT status FROM messages WHERE id = ?",
10712
- args: [messageId]
10932
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
10933
+ args: [messageId, ...scope.args]
10713
10934
  });
10714
10935
  return result.rows[0]?.status ?? null;
10715
10936
  }
10716
- async function getUnacknowledgedMessages(targetAgent) {
10937
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
10717
10938
  const client = getClient();
10939
+ const scope = strictSessionScopeFilter(sessionScope);
10718
10940
  const result = await client.execute({
10719
10941
  sql: `SELECT * FROM messages
10720
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
10942
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
10721
10943
  ORDER BY id`,
10722
- args: [targetAgent]
10944
+ args: [targetAgent, ...scope.args]
10723
10945
  });
10724
10946
  return result.rows.map((row) => rowToMessage(row));
10725
10947
  }
10726
- async function getReadMessages(targetAgent) {
10948
+ async function getReadMessages(targetAgent, sessionScope) {
10727
10949
  const client = getClient();
10950
+ const scope = strictSessionScopeFilter(sessionScope);
10728
10951
  const result = await client.execute({
10729
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
10730
- 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]
10731
10956
  });
10732
10957
  return result.rows.map((row) => rowToMessage(row));
10733
10958
  }
10734
- async function markFailed(messageId, reason) {
10959
+ async function markFailed(messageId, reason, sessionScope) {
10735
10960
  const client = getClient();
10961
+ const scope = strictSessionScopeFilter(sessionScope);
10736
10962
  await client.execute({
10737
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
10738
- 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]
10739
10966
  });
10740
10967
  }
10741
- async function getFailedMessages() {
10968
+ async function getFailedMessages(sessionScope) {
10742
10969
  const client = getClient();
10970
+ const scope = strictSessionScopeFilter(sessionScope);
10743
10971
  const result = await client.execute({
10744
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
10745
- args: []
10972
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
10973
+ args: [...scope.args]
10746
10974
  });
10747
10975
  return result.rows.map((row) => rowToMessage(row));
10748
10976
  }
10749
- async function retryPendingMessages() {
10977
+ async function retryPendingMessages(sessionScope) {
10750
10978
  const client = getClient();
10979
+ const scope = strictSessionScopeFilter(sessionScope);
10751
10980
  const result = await client.execute({
10752
10981
  sql: `SELECT * FROM messages
10753
- WHERE status = 'pending' AND retry_count < ?
10982
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
10754
10983
  ORDER BY id`,
10755
- args: [MAX_RETRIES3]
10984
+ args: [MAX_RETRIES3, ...scope.args]
10756
10985
  });
10757
10986
  let delivered = 0;
10758
10987
  for (const row of result.rows) {
@@ -10771,6 +11000,7 @@ var init_messaging = __esm({
10771
11000
  "use strict";
10772
11001
  init_database();
10773
11002
  init_tmux_routing();
11003
+ init_task_scope();
10774
11004
  MAX_RETRIES3 = 10;
10775
11005
  _wsClientSend = null;
10776
11006
  }
@@ -10780,19 +11010,22 @@ var init_messaging = __esm({
10780
11010
  init_config();
10781
11011
  init_memory();
10782
11012
  init_daemon_protocol();
11013
+ init_daemon_auth();
10783
11014
  init_daemon_orchestration();
10784
11015
  import net2 from "net";
10785
- import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
10786
- import path23 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";
10787
11018
  import { getLlama } from "node-llama-cpp";
10788
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path23.join(EXE_AI_DIR, "exed.sock");
10789
- var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path23.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");
10790
11021
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
10791
11022
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
10792
11023
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
11024
+ var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
10793
11025
  var _context = null;
10794
11026
  var _model = null;
10795
11027
  var _llama = null;
11028
+ var _daemonToken = "";
10796
11029
  var MAX_QUEUE_SIZE = 1e3;
10797
11030
  var highQueue = [];
10798
11031
  var lowQueue = [];
@@ -10812,8 +11045,8 @@ function enqueue(queue, entry) {
10812
11045
  queue.push(entry);
10813
11046
  }
10814
11047
  async function loadModel() {
10815
- const modelPath = path23.join(MODELS_DIR, MODEL_FILE);
10816
- if (!existsSync18(modelPath)) {
11048
+ const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
11049
+ if (!existsSync20(modelPath)) {
10817
11050
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
10818
11051
  `);
10819
11052
  process.exit(1);
@@ -10837,6 +11070,7 @@ async function processQueue() {
10837
11070
  for (const text of entry.request.texts) {
10838
11071
  const embedding = await _context.getEmbeddingFor(text);
10839
11072
  const vector = Array.from(embedding.vector);
11073
+ embedding.vector = null;
10840
11074
  if (vector.length !== EMBEDDING_DIM) {
10841
11075
  throw new Error(`Dimension mismatch: got ${vector.length}, expected ${EMBEDDING_DIM}`);
10842
11076
  }
@@ -10882,6 +11116,11 @@ function checkIdle() {
10882
11116
  }
10883
11117
  async function shutdown() {
10884
11118
  resetIdleTimer();
11119
+ try {
11120
+ const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
11121
+ disposeShards2();
11122
+ } catch {
11123
+ }
10885
11124
  if (_context) {
10886
11125
  try {
10887
11126
  await _context.dispose();
@@ -10920,6 +11159,7 @@ async function handleHealthCheck(socket, requestId) {
10920
11159
  }
10921
11160
  }
10922
11161
  const dbConnected = _storeInitialized;
11162
+ const mem = process.memoryUsage();
10923
11163
  sendResponse(socket, {
10924
11164
  id: requestId,
10925
11165
  ...healthy && testOk ? {
@@ -10927,7 +11167,13 @@ async function handleHealthCheck(socket, requestId) {
10927
11167
  status: "ok",
10928
11168
  uptime: Math.floor((Date.now() - _startedAt) / 1e3),
10929
11169
  requests_served: _requestsServed,
10930
- 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
+ }
10931
11177
  }
10932
11178
  } : { error: "unhealthy: model not loaded or test embed failed" }
10933
11179
  });
@@ -10981,13 +11227,67 @@ async function handleDbBatch(socket, requestId, statements, mode) {
10981
11227
  });
10982
11228
  }
10983
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
+ }
10984
11279
  function startServer() {
10985
- mkdirSync9(path23.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);
10986
11286
  for (const oldFile of ["embed.sock", "embed.pid"]) {
10987
- const oldPath = path23.join(path23.dirname(SOCKET_PATH2), oldFile);
11287
+ const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
10988
11288
  try {
10989
11289
  if (oldFile.endsWith(".pid")) {
10990
- const pid = parseInt(readFileSync16(oldPath, "utf8").trim(), 10);
11290
+ const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
10991
11291
  if (pid > 0) try {
10992
11292
  process.kill(pid, "SIGKILL");
10993
11293
  } catch {
@@ -11019,6 +11319,10 @@ function startServer() {
11019
11319
  if (!line) continue;
11020
11320
  try {
11021
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
+ }
11022
11326
  if (request.type === "health") {
11023
11327
  void handleHealthCheck(socket, request.id ?? "health");
11024
11328
  continue;
@@ -11039,6 +11343,10 @@ function startServer() {
11039
11343
  void handleDbBatch(socket, request.id, request.statements, request.mode);
11040
11344
  continue;
11041
11345
  }
11346
+ if (request.type === "ingest") {
11347
+ void handleIngest(request);
11348
+ continue;
11349
+ }
11042
11350
  if (!request.id || !Array.isArray(request.texts)) {
11043
11351
  sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
11044
11352
  continue;
@@ -11076,7 +11384,15 @@ function startServer() {
11076
11384
  server.listen(SOCKET_PATH2, () => {
11077
11385
  process.stderr.write(`[exed] Listening on ${SOCKET_PATH2}
11078
11386
  `);
11079
- 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
+ }
11080
11396
  checkIdle();
11081
11397
  });
11082
11398
  }
@@ -11369,7 +11685,7 @@ function startWikiSync() {
11369
11685
  });
11370
11686
  }
11371
11687
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
11372
- var AGENT_STATS_PATH = path23.join(EXE_AI_DIR, "agent-stats.json");
11688
+ var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
11373
11689
  async function writeAgentStats() {
11374
11690
  if (!await ensureStoreForPolling()) return;
11375
11691
  try {
@@ -11427,7 +11743,7 @@ async function writeAgentStats() {
11427
11743
  pid: process.pid
11428
11744
  }
11429
11745
  };
11430
- writeFileSync10(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
11746
+ writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
11431
11747
  } catch (err) {
11432
11748
  process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
11433
11749
  `);
@@ -11499,12 +11815,12 @@ function startIntercomQueueDrain() {
11499
11815
  const hasInProgressTask = (session) => {
11500
11816
  try {
11501
11817
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
11502
- const path24 = __require("path");
11503
- const { existsSync: existsSync19 } = __require("fs");
11504
- const os14 = __require("os");
11818
+ const path25 = __require("path");
11819
+ const { existsSync: existsSync21 } = __require("fs");
11820
+ const os15 = __require("os");
11505
11821
  const agent = ban(session.split("-")[0] ?? session);
11506
- const markerPath = path24.join(os14.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
11507
- return existsSync19(markerPath);
11822
+ const markerPath = path25.join(os15.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
11823
+ return existsSync21(markerPath);
11508
11824
  } catch {
11509
11825
  return false;
11510
11826
  }
@@ -11593,12 +11909,43 @@ function startAutoWake() {
11593
11909
  process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
11594
11910
  `);
11595
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
+ }
11596
11943
  process.on("SIGINT", () => void shutdown());
11597
11944
  process.on("SIGTERM", () => void shutdown());
11598
11945
  function checkExistingDaemon() {
11599
11946
  try {
11600
- if (!existsSync18(PID_PATH2)) return false;
11601
- 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);
11602
11949
  if (!pid || isNaN(pid)) return false;
11603
11950
  process.kill(pid, 0);
11604
11951
  process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
@@ -11686,6 +12033,7 @@ try {
11686
12033
  startIntercomQueueDrain();
11687
12034
  startConfidenceDecay();
11688
12035
  startAutoUpdateCheck();
12036
+ startRssWatchdog();
11689
12037
  try {
11690
12038
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11691
12039
  const config = await loadConfig2();