@askexenow/exe-os 0.9.8 → 0.9.10

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 +1411 -953
  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 +913 -543
  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 +418 -262
  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 +793 -485
  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 +566 -357
  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 +530 -319
  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 +547 -336
  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 +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  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 +3442 -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 +534 -323
  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 +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  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 +664 -431
  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 +1049 -680
  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 +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  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 +448 -371
  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 +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
@@ -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({
@@ -3741,13 +3870,117 @@ var init_state_bus = __esm({
3741
3870
  }
3742
3871
  });
3743
3872
 
3744
- // src/lib/tasks-crud.ts
3745
- import crypto3 from "crypto";
3746
- import path11 from "path";
3747
- import os8 from "os";
3873
+ // src/lib/project-name.ts
3748
3874
  import { execSync as execSync5 } from "child_process";
3875
+ import path12 from "path";
3876
+ function getProjectName(cwd) {
3877
+ const dir = cwd ?? process.cwd();
3878
+ if (_cached2 && _cachedCwd === dir) return _cached2;
3879
+ try {
3880
+ let repoRoot;
3881
+ try {
3882
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
3883
+ cwd: dir,
3884
+ encoding: "utf8",
3885
+ timeout: 2e3,
3886
+ stdio: ["pipe", "pipe", "pipe"]
3887
+ }).trim();
3888
+ repoRoot = path12.dirname(gitCommonDir);
3889
+ } catch {
3890
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
3891
+ cwd: dir,
3892
+ encoding: "utf8",
3893
+ timeout: 2e3,
3894
+ stdio: ["pipe", "pipe", "pipe"]
3895
+ }).trim();
3896
+ }
3897
+ _cached2 = path12.basename(repoRoot);
3898
+ _cachedCwd = dir;
3899
+ return _cached2;
3900
+ } catch {
3901
+ _cached2 = path12.basename(dir);
3902
+ _cachedCwd = dir;
3903
+ return _cached2;
3904
+ }
3905
+ }
3906
+ var _cached2, _cachedCwd;
3907
+ var init_project_name = __esm({
3908
+ "src/lib/project-name.ts"() {
3909
+ "use strict";
3910
+ _cached2 = null;
3911
+ _cachedCwd = null;
3912
+ }
3913
+ });
3914
+
3915
+ // src/lib/session-scope.ts
3916
+ var session_scope_exports = {};
3917
+ __export(session_scope_exports, {
3918
+ assertSessionScope: () => assertSessionScope,
3919
+ findSessionForProject: () => findSessionForProject,
3920
+ getSessionProject: () => getSessionProject
3921
+ });
3922
+ function getSessionProject(sessionName) {
3923
+ const sessions = listSessions();
3924
+ const entry = sessions.find((s) => s.windowName === sessionName);
3925
+ if (!entry) return null;
3926
+ const parts = entry.projectDir.split("/").filter(Boolean);
3927
+ return parts[parts.length - 1] ?? null;
3928
+ }
3929
+ function findSessionForProject(projectName) {
3930
+ const sessions = listSessions();
3931
+ for (const s of sessions) {
3932
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
3933
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
3934
+ }
3935
+ return null;
3936
+ }
3937
+ function assertSessionScope(actionType, targetProject) {
3938
+ try {
3939
+ const currentProject = getProjectName();
3940
+ const exeSession = resolveExeSession();
3941
+ if (!exeSession) {
3942
+ return { allowed: true, reason: "no_session" };
3943
+ }
3944
+ if (currentProject === targetProject) {
3945
+ return {
3946
+ allowed: true,
3947
+ reason: "same_session",
3948
+ currentProject,
3949
+ targetProject
3950
+ };
3951
+ }
3952
+ process.stderr.write(
3953
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3954
+ `
3955
+ );
3956
+ return {
3957
+ allowed: false,
3958
+ reason: "cross_session_denied",
3959
+ currentProject,
3960
+ targetProject,
3961
+ targetSession: findSessionForProject(targetProject)?.windowName
3962
+ };
3963
+ } catch {
3964
+ return { allowed: true, reason: "no_session" };
3965
+ }
3966
+ }
3967
+ var init_session_scope = __esm({
3968
+ "src/lib/session-scope.ts"() {
3969
+ "use strict";
3970
+ init_session_registry();
3971
+ init_project_name();
3972
+ init_tmux_routing();
3973
+ init_employees();
3974
+ }
3975
+ });
3976
+
3977
+ // src/lib/tasks-crud.ts
3978
+ import crypto4 from "crypto";
3979
+ import path13 from "path";
3980
+ import os9 from "os";
3981
+ import { execSync as execSync6 } from "child_process";
3749
3982
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3750
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3983
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3751
3984
  async function writeCheckpoint(input) {
3752
3985
  const client = getClient();
3753
3986
  const row = await resolveTask(client, input.taskId);
@@ -3863,13 +4096,28 @@ async function resolveTask(client, identifier, scopeSession) {
3863
4096
  }
3864
4097
  async function createTaskCore(input) {
3865
4098
  const client = getClient();
3866
- const id = crypto3.randomUUID();
4099
+ const id = crypto4.randomUUID();
3867
4100
  const now = (/* @__PURE__ */ new Date()).toISOString();
3868
4101
  const slug = slugify(input.title);
3869
4102
  let earlySessionScope = null;
4103
+ let scopeMismatchWarning;
3870
4104
  try {
3871
4105
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3872
- earlySessionScope = resolveExeSession2();
4106
+ const resolved = resolveExeSession2();
4107
+ if (resolved && input.projectName) {
4108
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
4109
+ const sessionProject = getSessionProject2(resolved);
4110
+ if (sessionProject && sessionProject !== input.projectName) {
4111
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
4112
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
4113
+ `);
4114
+ earlySessionScope = null;
4115
+ } else {
4116
+ earlySessionScope = resolved;
4117
+ }
4118
+ } else {
4119
+ earlySessionScope = resolved;
4120
+ }
3873
4121
  } catch {
3874
4122
  }
3875
4123
  const scope = earlySessionScope ?? "default";
@@ -3920,10 +4168,14 @@ async function createTaskCore(input) {
3920
4168
  ${laneWarning}` : laneWarning;
3921
4169
  }
3922
4170
  }
4171
+ if (scopeMismatchWarning) {
4172
+ warning = warning ? `${warning}
4173
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
4174
+ }
3923
4175
  if (input.baseDir) {
3924
4176
  try {
3925
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3926
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
4177
+ await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
4178
+ await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
3927
4179
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3928
4180
  await ensureGitignoreExe(input.baseDir);
3929
4181
  } catch {
@@ -3959,18 +4211,24 @@ ${laneWarning}` : laneWarning;
3959
4211
  });
3960
4212
  if (input.baseDir) {
3961
4213
  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 });
4214
+ const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
4215
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
4216
+ const mdDir = path13.dirname(mdPath);
4217
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3966
4218
  const reviewer = input.reviewer ?? input.assignedBy;
3967
4219
  const mdContent = `# ${input.title}
3968
4220
 
3969
- **ID:** ${id}
3970
- **Status:** ${initialStatus}
3971
- **Priority:** ${input.priority}
3972
- **Assigned by:** ${input.assignedBy}
3973
- **Assigned to:** ${input.assignedTo}
4221
+ ## MANDATORY: When done
4222
+
4223
+ You MUST call update_task with status "done" and a result summary when finished.
4224
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4225
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4226
+
4227
+ **ID:** ${id}
4228
+ **Status:** ${initialStatus}
4229
+ **Priority:** ${input.priority}
4230
+ **Assigned by:** ${input.assignedBy}
4231
+ **Assigned to:** ${input.assignedTo}
3974
4232
  **Project:** ${input.projectName}
3975
4233
  **Created:** ${now.split("T")[0]}${parentTaskId ? `
3976
4234
  **Parent task:** ${parentTaskId}` : ""}
@@ -3979,12 +4237,6 @@ ${laneWarning}` : laneWarning;
3979
4237
  ## Context
3980
4238
 
3981
4239
  ${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
4240
  `;
3989
4241
  await writeFile3(mdPath, mdContent, "utf-8");
3990
4242
  } catch (err) {
@@ -4066,14 +4318,14 @@ function isTmuxSessionAlive(identifier) {
4066
4318
  if (!identifier || identifier === "unknown") return true;
4067
4319
  try {
4068
4320
  if (identifier.startsWith("%")) {
4069
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
4321
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
4070
4322
  timeout: 2e3,
4071
4323
  encoding: "utf8",
4072
4324
  stdio: ["pipe", "pipe", "pipe"]
4073
4325
  });
4074
4326
  return output.split("\n").some((l) => l.trim() === identifier);
4075
4327
  } else {
4076
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4328
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4077
4329
  timeout: 2e3,
4078
4330
  stdio: ["pipe", "pipe", "pipe"]
4079
4331
  });
@@ -4082,7 +4334,7 @@ function isTmuxSessionAlive(identifier) {
4082
4334
  } catch {
4083
4335
  if (identifier.startsWith("%")) return true;
4084
4336
  try {
4085
- execSync5("tmux list-sessions", {
4337
+ execSync6("tmux list-sessions", {
4086
4338
  timeout: 2e3,
4087
4339
  stdio: ["pipe", "pipe", "pipe"]
4088
4340
  });
@@ -4097,12 +4349,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
4097
4349
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
4098
4350
  try {
4099
4351
  const since = new Date(taskCreatedAt).toISOString();
4100
- const branch = execSync5(
4352
+ const branch = execSync6(
4101
4353
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
4102
4354
  { encoding: "utf8", timeout: 3e3 }
4103
4355
  ).trim();
4104
4356
  const branchArg = branch && branch !== "HEAD" ? branch : "";
4105
- const commitCount = execSync5(
4357
+ const commitCount = execSync6(
4106
4358
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
4107
4359
  { encoding: "utf8", timeout: 5e3 }
4108
4360
  ).trim();
@@ -4233,7 +4485,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4233
4485
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4234
4486
  } catch {
4235
4487
  }
4236
- if (input.status === "done" || input.status === "cancelled") {
4488
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4237
4489
  try {
4238
4490
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4239
4491
  clearQueueForAgent2(String(row.assigned_to));
@@ -4262,9 +4514,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4262
4514
  return { taskFile, assignedTo, assignedBy, taskSlug };
4263
4515
  }
4264
4516
  async function ensureArchitectureDoc(baseDir, projectName) {
4265
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4517
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
4266
4518
  try {
4267
- if (existsSync10(archPath)) return;
4519
+ if (existsSync12(archPath)) return;
4268
4520
  const template = [
4269
4521
  `# ${projectName} \u2014 System Architecture`,
4270
4522
  "",
@@ -4297,10 +4549,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4297
4549
  }
4298
4550
  }
4299
4551
  async function ensureGitignoreExe(baseDir) {
4300
- const gitignorePath = path11.join(baseDir, ".gitignore");
4552
+ const gitignorePath = path13.join(baseDir, ".gitignore");
4301
4553
  try {
4302
- if (existsSync10(gitignorePath)) {
4303
- const content = readFileSync10(gitignorePath, "utf-8");
4554
+ if (existsSync12(gitignorePath)) {
4555
+ const content = readFileSync11(gitignorePath, "utf-8");
4304
4556
  if (/^\/?exe\/?$/m.test(content)) return;
4305
4557
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4306
4558
  } else {
@@ -4343,8 +4595,8 @@ __export(tasks_review_exports, {
4343
4595
  isStale: () => isStale,
4344
4596
  listPendingReviews: () => listPendingReviews
4345
4597
  });
4346
- import path12 from "path";
4347
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4598
+ import path14 from "path";
4599
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4348
4600
  function formatAge(isoTimestamp) {
4349
4601
  if (!isoTimestamp) return "";
4350
4602
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -4362,54 +4614,38 @@ function isStale(isoTimestamp) {
4362
4614
  }
4363
4615
  async function countPendingReviews(sessionScope) {
4364
4616
  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
- }
4617
+ const scope = strictSessionScopeFilter(
4618
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4619
+ );
4372
4620
  const result = await client.execute({
4373
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4374
- args: []
4621
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4622
+ WHERE status = 'needs_review'${scope.sql}`,
4623
+ args: [...scope.args]
4375
4624
  });
4376
4625
  return Number(result.rows[0]?.cnt) || 0;
4377
4626
  }
4378
4627
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4379
4628
  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
- }
4629
+ const scope = strictSessionScopeFilter(
4630
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4631
+ );
4389
4632
  const result = await client.execute({
4390
4633
  sql: `SELECT COUNT(*) as cnt FROM tasks
4391
- WHERE status = 'needs_review' AND updated_at > ?`,
4392
- args: [sinceIso]
4634
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4635
+ args: [sinceIso, ...scope.args]
4393
4636
  });
4394
4637
  return Number(result.rows[0]?.cnt) || 0;
4395
4638
  }
4396
4639
  async function listPendingReviews(limit, sessionScope) {
4397
4640
  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
- }
4641
+ const scope = strictSessionScopeFilter(
4642
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4643
+ );
4408
4644
  const result = await client.execute({
4409
4645
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4410
- WHERE status = 'needs_review'
4646
+ WHERE status = 'needs_review'${scope.sql}
4411
4647
  ORDER BY updated_at ASC LIMIT ?`,
4412
- args: [limit]
4648
+ args: [...scope.args, limit]
4413
4649
  });
4414
4650
  return result.rows;
4415
4651
  }
@@ -4421,7 +4657,7 @@ async function cleanupOrphanedReviews() {
4421
4657
  WHERE status IN ('open', 'needs_review', 'in_progress')
4422
4658
  AND assigned_by = 'system'
4423
4659
  AND title LIKE 'Review:%'
4424
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4660
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4425
4661
  args: [now]
4426
4662
  });
4427
4663
  const r1b = await client.execute({
@@ -4629,11 +4865,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4629
4865
  );
4630
4866
  }
4631
4867
  try {
4632
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
4633
- if (existsSync11(cacheDir)) {
4868
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4869
+ if (existsSync13(cacheDir)) {
4634
4870
  for (const f of readdirSync2(cacheDir)) {
4635
4871
  if (f.startsWith("review-notified-")) {
4636
- unlinkSync4(path12.join(cacheDir, f));
4872
+ unlinkSync4(path14.join(cacheDir, f));
4637
4873
  }
4638
4874
  }
4639
4875
  }
@@ -4650,11 +4886,12 @@ var init_tasks_review = __esm({
4650
4886
  init_tmux_routing();
4651
4887
  init_session_key();
4652
4888
  init_state_bus();
4889
+ init_task_scope();
4653
4890
  }
4654
4891
  });
4655
4892
 
4656
4893
  // src/lib/tasks-chain.ts
4657
- import path13 from "path";
4894
+ import path15 from "path";
4658
4895
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4659
4896
  async function cascadeUnblock(taskId, baseDir, now) {
4660
4897
  const client = getClient();
@@ -4671,7 +4908,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4671
4908
  });
4672
4909
  for (const ur of unblockedRows.rows) {
4673
4910
  try {
4674
- const ubFile = path13.join(baseDir, String(ur.task_file));
4911
+ const ubFile = path15.join(baseDir, String(ur.task_file));
4675
4912
  let ubContent = await readFile3(ubFile, "utf-8");
4676
4913
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4677
4914
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4706,7 +4943,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4706
4943
  const scScope = sessionScopeFilter();
4707
4944
  const remaining = await client.execute({
4708
4945
  sql: `SELECT COUNT(*) as cnt FROM tasks
4709
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4946
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4710
4947
  args: [parentTaskId, ...scScope.args]
4711
4948
  });
4712
4949
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4738,110 +4975,6 @@ var init_tasks_chain = __esm({
4738
4975
  }
4739
4976
  });
4740
4977
 
4741
- // src/lib/project-name.ts
4742
- import { execSync as execSync6 } from "child_process";
4743
- import path14 from "path";
4744
- function getProjectName(cwd) {
4745
- const dir = cwd ?? process.cwd();
4746
- if (_cached2 && _cachedCwd === dir) return _cached2;
4747
- try {
4748
- let repoRoot;
4749
- try {
4750
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
4751
- cwd: dir,
4752
- encoding: "utf8",
4753
- timeout: 2e3,
4754
- stdio: ["pipe", "pipe", "pipe"]
4755
- }).trim();
4756
- repoRoot = path14.dirname(gitCommonDir);
4757
- } catch {
4758
- repoRoot = execSync6("git rev-parse --show-toplevel", {
4759
- cwd: dir,
4760
- encoding: "utf8",
4761
- timeout: 2e3,
4762
- stdio: ["pipe", "pipe", "pipe"]
4763
- }).trim();
4764
- }
4765
- _cached2 = path14.basename(repoRoot);
4766
- _cachedCwd = dir;
4767
- return _cached2;
4768
- } catch {
4769
- _cached2 = path14.basename(dir);
4770
- _cachedCwd = dir;
4771
- return _cached2;
4772
- }
4773
- }
4774
- var _cached2, _cachedCwd;
4775
- var init_project_name = __esm({
4776
- "src/lib/project-name.ts"() {
4777
- "use strict";
4778
- _cached2 = null;
4779
- _cachedCwd = null;
4780
- }
4781
- });
4782
-
4783
- // src/lib/session-scope.ts
4784
- var session_scope_exports = {};
4785
- __export(session_scope_exports, {
4786
- assertSessionScope: () => assertSessionScope,
4787
- findSessionForProject: () => findSessionForProject,
4788
- getSessionProject: () => getSessionProject
4789
- });
4790
- function getSessionProject(sessionName) {
4791
- const sessions = listSessions();
4792
- const entry = sessions.find((s) => s.windowName === sessionName);
4793
- if (!entry) return null;
4794
- const parts = entry.projectDir.split("/").filter(Boolean);
4795
- return parts[parts.length - 1] ?? null;
4796
- }
4797
- function findSessionForProject(projectName) {
4798
- const sessions = listSessions();
4799
- for (const s of sessions) {
4800
- const proj = s.projectDir.split("/").filter(Boolean).pop();
4801
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4802
- }
4803
- return null;
4804
- }
4805
- function assertSessionScope(actionType, targetProject) {
4806
- try {
4807
- const currentProject = getProjectName();
4808
- const exeSession = resolveExeSession();
4809
- if (!exeSession) {
4810
- return { allowed: true, reason: "no_session" };
4811
- }
4812
- if (currentProject === targetProject) {
4813
- return {
4814
- allowed: true,
4815
- reason: "same_session",
4816
- currentProject,
4817
- targetProject
4818
- };
4819
- }
4820
- process.stderr.write(
4821
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4822
- `
4823
- );
4824
- return {
4825
- allowed: false,
4826
- reason: "cross_session_denied",
4827
- currentProject,
4828
- targetProject,
4829
- targetSession: findSessionForProject(targetProject)?.windowName
4830
- };
4831
- } catch {
4832
- return { allowed: true, reason: "no_session" };
4833
- }
4834
- }
4835
- var init_session_scope = __esm({
4836
- "src/lib/session-scope.ts"() {
4837
- "use strict";
4838
- init_session_registry();
4839
- init_project_name();
4840
- init_tmux_routing();
4841
- init_employees();
4842
- }
4843
- });
4844
-
4845
4978
  // src/lib/tasks-notify.ts
4846
4979
  async function dispatchTaskToEmployee(input) {
4847
4980
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -4909,10 +5042,10 @@ var init_tasks_notify = __esm({
4909
5042
  });
4910
5043
 
4911
5044
  // src/lib/behaviors.ts
4912
- import crypto4 from "crypto";
5045
+ import crypto5 from "crypto";
4913
5046
  async function storeBehavior(opts) {
4914
5047
  const client = getClient();
4915
- const id = crypto4.randomUUID();
5048
+ const id = crypto5.randomUUID();
4916
5049
  const now = (/* @__PURE__ */ new Date()).toISOString();
4917
5050
  await client.execute({
4918
5051
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4941,7 +5074,7 @@ __export(skill_learning_exports, {
4941
5074
  storeTrajectory: () => storeTrajectory,
4942
5075
  sweepTrajectories: () => sweepTrajectories
4943
5076
  });
4944
- import crypto5 from "crypto";
5077
+ import crypto6 from "crypto";
4945
5078
  async function extractTrajectory(taskId, agentId) {
4946
5079
  const client = getClient();
4947
5080
  const result = await client.execute({
@@ -4970,11 +5103,11 @@ async function extractTrajectory(taskId, agentId) {
4970
5103
  return signature;
4971
5104
  }
4972
5105
  function hashSignature(signature) {
4973
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5106
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4974
5107
  }
4975
5108
  async function storeTrajectory(opts) {
4976
5109
  const client = getClient();
4977
- const id = crypto5.randomUUID();
5110
+ const id = crypto6.randomUUID();
4978
5111
  const now = (/* @__PURE__ */ new Date()).toISOString();
4979
5112
  const signatureHash = hashSignature(opts.signature);
4980
5113
  await client.execute({
@@ -5239,8 +5372,8 @@ __export(tasks_exports, {
5239
5372
  updateTaskStatus: () => updateTaskStatus,
5240
5373
  writeCheckpoint: () => writeCheckpoint
5241
5374
  });
5242
- import path15 from "path";
5243
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5375
+ import path16 from "path";
5376
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5244
5377
  async function createTask(input) {
5245
5378
  const result = await createTaskCore(input);
5246
5379
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5259,12 +5392,12 @@ async function updateTask(input) {
5259
5392
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5260
5393
  try {
5261
5394
  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`);
5395
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5396
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
5264
5397
  if (input.status === "in_progress") {
5265
5398
  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") {
5399
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5400
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5268
5401
  try {
5269
5402
  unlinkSync5(cachePath);
5270
5403
  } catch {
@@ -5272,10 +5405,10 @@ async function updateTask(input) {
5272
5405
  }
5273
5406
  } catch {
5274
5407
  }
5275
- if (input.status === "done") {
5408
+ if (input.status === "done" || input.status === "closed") {
5276
5409
  await cleanupReviewFile(row, taskFile, input.baseDir);
5277
5410
  }
5278
- if (input.status === "done" || input.status === "cancelled") {
5411
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5279
5412
  try {
5280
5413
  const client = getClient();
5281
5414
  const taskTitle = String(row.title);
@@ -5291,7 +5424,7 @@ async function updateTask(input) {
5291
5424
  if (!isCoordinatorName(assignedAgent)) {
5292
5425
  try {
5293
5426
  const draftClient = getClient();
5294
- if (input.status === "done") {
5427
+ if (input.status === "done" || input.status === "closed") {
5295
5428
  await draftClient.execute({
5296
5429
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5297
5430
  args: [assignedAgent]
@@ -5308,7 +5441,7 @@ async function updateTask(input) {
5308
5441
  try {
5309
5442
  const client = getClient();
5310
5443
  const cascaded = await client.execute({
5311
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5444
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5312
5445
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5313
5446
  args: [now, taskId]
5314
5447
  });
@@ -5321,14 +5454,14 @@ async function updateTask(input) {
5321
5454
  } catch {
5322
5455
  }
5323
5456
  }
5324
- const isTerminal = input.status === "done" || input.status === "needs_review";
5457
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5325
5458
  if (isTerminal) {
5326
5459
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5327
5460
  if (!isCoordinator) {
5328
5461
  notifyTaskDone();
5329
5462
  }
5330
5463
  await markTaskNotificationsRead(taskFile);
5331
- if (input.status === "done") {
5464
+ if (input.status === "done" || input.status === "closed") {
5332
5465
  try {
5333
5466
  await cascadeUnblock(taskId, input.baseDir, now);
5334
5467
  } catch {
@@ -5348,7 +5481,7 @@ async function updateTask(input) {
5348
5481
  }
5349
5482
  }
5350
5483
  }
5351
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5484
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5352
5485
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5353
5486
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5354
5487
  taskId,
@@ -5720,6 +5853,7 @@ __export(tmux_routing_exports, {
5720
5853
  isEmployeeAlive: () => isEmployeeAlive,
5721
5854
  isExeSession: () => isExeSession,
5722
5855
  isSessionBusy: () => isSessionBusy,
5856
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5723
5857
  notifyParentExe: () => notifyParentExe,
5724
5858
  parseParentExe: () => parseParentExe,
5725
5859
  registerParentExe: () => registerParentExe,
@@ -5730,13 +5864,13 @@ __export(tmux_routing_exports, {
5730
5864
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5731
5865
  });
5732
5866
  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";
5867
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5868
+ import path17 from "path";
5869
+ import os10 from "os";
5736
5870
  import { fileURLToPath as fileURLToPath2 } from "url";
5737
5871
  import { unlinkSync as unlinkSync6 } from "fs";
5738
5872
  function spawnLockPath(sessionName) {
5739
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5873
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5740
5874
  }
5741
5875
  function isProcessAlive(pid) {
5742
5876
  try {
@@ -5747,13 +5881,13 @@ function isProcessAlive(pid) {
5747
5881
  }
5748
5882
  }
5749
5883
  function acquireSpawnLock2(sessionName) {
5750
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5884
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5751
5885
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5752
5886
  }
5753
5887
  const lockFile = spawnLockPath(sessionName);
5754
- if (existsSync12(lockFile)) {
5888
+ if (existsSync14(lockFile)) {
5755
5889
  try {
5756
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5890
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5757
5891
  const age = Date.now() - lock.timestamp;
5758
5892
  if (isProcessAlive(lock.pid) && age < 6e4) {
5759
5893
  return false;
@@ -5761,7 +5895,7 @@ function acquireSpawnLock2(sessionName) {
5761
5895
  } catch {
5762
5896
  }
5763
5897
  }
5764
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5898
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5765
5899
  return true;
5766
5900
  }
5767
5901
  function releaseSpawnLock2(sessionName) {
@@ -5773,13 +5907,13 @@ function releaseSpawnLock2(sessionName) {
5773
5907
  function resolveBehaviorsExporterScript() {
5774
5908
  try {
5775
5909
  const thisFile = fileURLToPath2(import.meta.url);
5776
- const scriptPath = path16.join(
5777
- path16.dirname(thisFile),
5910
+ const scriptPath = path17.join(
5911
+ path17.dirname(thisFile),
5778
5912
  "..",
5779
5913
  "bin",
5780
5914
  "exe-export-behaviors.js"
5781
5915
  );
5782
- return existsSync12(scriptPath) ? scriptPath : null;
5916
+ return existsSync14(scriptPath) ? scriptPath : null;
5783
5917
  } catch {
5784
5918
  return null;
5785
5919
  }
@@ -5845,12 +5979,12 @@ function extractRootExe(name) {
5845
5979
  return parts.length > 0 ? parts[parts.length - 1] : null;
5846
5980
  }
5847
5981
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5848
- if (!existsSync12(SESSION_CACHE)) {
5982
+ if (!existsSync14(SESSION_CACHE)) {
5849
5983
  mkdirSync6(SESSION_CACHE, { recursive: true });
5850
5984
  }
5851
5985
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5852
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5853
- writeFileSync7(filePath, JSON.stringify({
5986
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5987
+ writeFileSync8(filePath, JSON.stringify({
5854
5988
  parentExe: rootExe,
5855
5989
  dispatchedBy: dispatchedBy || rootExe,
5856
5990
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5858,7 +5992,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5858
5992
  }
5859
5993
  function getParentExe(sessionKey) {
5860
5994
  try {
5861
- const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5995
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5862
5996
  return data.parentExe || null;
5863
5997
  } catch {
5864
5998
  return null;
@@ -5866,8 +6000,8 @@ function getParentExe(sessionKey) {
5866
6000
  }
5867
6001
  function getDispatchedBy(sessionKey) {
5868
6002
  try {
5869
- const data = JSON.parse(readFileSync11(
5870
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6003
+ const data = JSON.parse(readFileSync12(
6004
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5871
6005
  "utf8"
5872
6006
  ));
5873
6007
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5937,8 +6071,8 @@ async function verifyPaneAtCapacity(sessionName) {
5937
6071
  }
5938
6072
  function readDebounceState() {
5939
6073
  try {
5940
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5941
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6074
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
6075
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5942
6076
  const state = {};
5943
6077
  for (const [key, val] of Object.entries(raw)) {
5944
6078
  if (typeof val === "number") {
@@ -5954,8 +6088,8 @@ function readDebounceState() {
5954
6088
  }
5955
6089
  function writeDebounceState(state) {
5956
6090
  try {
5957
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5958
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6091
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
6092
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5959
6093
  } catch {
5960
6094
  }
5961
6095
  }
@@ -6053,8 +6187,8 @@ function sendIntercom(targetSession) {
6053
6187
  try {
6054
6188
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6055
6189
  const agent = baseAgentName(rawAgent);
6056
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
6057
- if (existsSync12(markerPath)) {
6190
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6191
+ if (existsSync14(markerPath)) {
6058
6192
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6059
6193
  return "debounced";
6060
6194
  }
@@ -6063,8 +6197,8 @@ function sendIntercom(targetSession) {
6063
6197
  try {
6064
6198
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6065
6199
  const agent = baseAgentName(rawAgent);
6066
- const taskDir = path16.join(process.cwd(), "exe", agent);
6067
- if (existsSync12(taskDir)) {
6200
+ const taskDir = path17.join(process.cwd(), "exe", agent);
6201
+ if (existsSync14(taskDir)) {
6068
6202
  const files = readdirSync3(taskDir).filter(
6069
6203
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6070
6204
  );
@@ -6124,6 +6258,21 @@ function notifyParentExe(sessionKey) {
6124
6258
  }
6125
6259
  return true;
6126
6260
  }
6261
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6262
+ const transport = getTransport();
6263
+ try {
6264
+ const sessions = transport.listSessions();
6265
+ if (!sessions.includes(coordinatorSession)) return false;
6266
+ execSync7(
6267
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6268
+ { timeout: 3e3 }
6269
+ );
6270
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6271
+ return true;
6272
+ } catch {
6273
+ return false;
6274
+ }
6275
+ }
6127
6276
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6128
6277
  if (isCoordinatorName(employeeName)) {
6129
6278
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6197,26 +6346,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6197
6346
  const transport = getTransport();
6198
6347
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6199
6348
  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)) {
6349
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
6350
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6351
+ if (!existsSync14(logDir)) {
6203
6352
  mkdirSync6(logDir, { recursive: true });
6204
6353
  }
6205
6354
  transport.kill(sessionName);
6206
6355
  let cleanupSuffix = "";
6207
6356
  try {
6208
6357
  const thisFile = fileURLToPath2(import.meta.url);
6209
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6210
- if (existsSync12(cleanupScript)) {
6358
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6359
+ if (existsSync14(cleanupScript)) {
6211
6360
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6212
6361
  }
6213
6362
  } catch {
6214
6363
  }
6215
6364
  try {
6216
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
6365
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
6217
6366
  let claudeJson = {};
6218
6367
  try {
6219
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
6368
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6220
6369
  } catch {
6221
6370
  }
6222
6371
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6224,17 +6373,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6224
6373
  const trustDir = opts?.cwd ?? projectDir;
6225
6374
  if (!projects[trustDir]) projects[trustDir] = {};
6226
6375
  projects[trustDir].hasTrustDialogAccepted = true;
6227
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6376
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6228
6377
  } catch {
6229
6378
  }
6230
6379
  try {
6231
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
6380
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
6232
6381
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6233
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
6234
- const settingsPath = path16.join(projSettingsDir, "settings.json");
6382
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6383
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
6235
6384
  let settings = {};
6236
6385
  try {
6237
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6386
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6238
6387
  } catch {
6239
6388
  }
6240
6389
  const perms = settings.permissions ?? {};
@@ -6263,7 +6412,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6263
6412
  perms.allow = allow;
6264
6413
  settings.permissions = perms;
6265
6414
  mkdirSync6(projSettingsDir, { recursive: true });
6266
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6415
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6267
6416
  }
6268
6417
  } catch {
6269
6418
  }
@@ -6278,8 +6427,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6278
6427
  let behaviorsFlag = "";
6279
6428
  let legacyFallbackWarned = false;
6280
6429
  if (!useExeAgent && !useBinSymlink) {
6281
- const identityPath = path16.join(
6282
- os9.homedir(),
6430
+ const identityPath = path17.join(
6431
+ os10.homedir(),
6283
6432
  ".exe-os",
6284
6433
  "identity",
6285
6434
  `${employeeName}.md`
@@ -6288,13 +6437,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6288
6437
  const hasAgentFlag = claudeSupportsAgentFlag();
6289
6438
  if (hasAgentFlag) {
6290
6439
  identityFlag = ` --agent ${employeeName}`;
6291
- } else if (existsSync12(identityPath)) {
6440
+ } else if (existsSync14(identityPath)) {
6292
6441
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6293
6442
  legacyFallbackWarned = true;
6294
6443
  }
6295
6444
  const behaviorsFile = exportBehaviorsSync(
6296
6445
  employeeName,
6297
- path16.basename(spawnCwd),
6446
+ path17.basename(spawnCwd),
6298
6447
  sessionName
6299
6448
  );
6300
6449
  if (behaviorsFile) {
@@ -6309,16 +6458,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6309
6458
  }
6310
6459
  let sessionContextFlag = "";
6311
6460
  try {
6312
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
6461
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
6313
6462
  mkdirSync6(ctxDir, { recursive: true });
6314
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
6463
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6315
6464
  const ctxContent = [
6316
6465
  `## Session Context`,
6317
6466
  `You are running in tmux session: ${sessionName}.`,
6318
6467
  `Your parent coordinator session is ${exeSession}.`,
6319
6468
  `Your employees (if any) use the -${exeSession} suffix.`
6320
6469
  ].join("\n");
6321
- writeFileSync7(ctxFile, ctxContent);
6470
+ writeFileSync8(ctxFile, ctxContent);
6322
6471
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6323
6472
  } catch {
6324
6473
  }
@@ -6395,8 +6544,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6395
6544
  transport.pipeLog(sessionName, logFile);
6396
6545
  try {
6397
6546
  const mySession = getMySession();
6398
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6399
- writeFileSync7(dispatchInfo, JSON.stringify({
6547
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6548
+ writeFileSync8(dispatchInfo, JSON.stringify({
6400
6549
  dispatchedBy: mySession,
6401
6550
  rootExe: exeSession,
6402
6551
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6470,15 +6619,15 @@ var init_tmux_routing = __esm({
6470
6619
  init_intercom_queue();
6471
6620
  init_plan_limits();
6472
6621
  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");
6622
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
6623
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
6475
6624
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6476
6625
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6477
6626
  VERIFY_PANE_LINES = 200;
6478
6627
  INTERCOM_DEBOUNCE_MS = 3e4;
6479
6628
  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");
6629
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
6630
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
6482
6631
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6483
6632
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6484
6633
  }
@@ -6762,9 +6911,9 @@ __export(agent_signals_exports, {
6762
6911
  hasOpenTasks: () => hasOpenTasks,
6763
6912
  hasUnreadInbox: () => hasUnreadInbox
6764
6913
  });
6765
- import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
6766
- import os10 from "os";
6767
- import path17 from "path";
6914
+ import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
6915
+ import os11 from "os";
6916
+ import path18 from "path";
6768
6917
  async function hasOpenTasks(client, agentId) {
6769
6918
  try {
6770
6919
  const scope = sessionScopeFilter(null);
@@ -6806,10 +6955,10 @@ async function hasUnreadInbox(client, agentId) {
6806
6955
  return CONSERVATIVE_ON_ERROR;
6807
6956
  }
6808
6957
  }
6809
- function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path17.join(os10.homedir(), ".exe-os", "intercom.log")) {
6810
- if (!existsSync13(intercomLog)) return false;
6958
+ function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path18.join(os11.homedir(), ".exe-os", "intercom.log")) {
6959
+ if (!existsSync15(intercomLog)) return false;
6811
6960
  try {
6812
- const raw = readFileSync12(intercomLog, "utf8");
6961
+ const raw = readFileSync13(intercomLog, "utf8");
6813
6962
  const lines = raw.split("\n");
6814
6963
  for (let i = lines.length - 1; i >= 0; i--) {
6815
6964
  const line = lines[i];
@@ -6882,7 +7031,7 @@ __export(daemon_orchestration_exports, {
6882
7031
  shouldNudgeEmployee: () => shouldNudgeEmployee
6883
7032
  });
6884
7033
  import { execSync as execSync9 } from "child_process";
6885
- import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
7034
+ import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
6886
7035
  import { homedir } from "os";
6887
7036
  import { join } from "path";
6888
7037
  function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
@@ -7073,8 +7222,8 @@ async function pollReviewNudge(deps, state) {
7073
7222
  function loadNudgeState() {
7074
7223
  const state = { lastNudge: /* @__PURE__ */ new Map() };
7075
7224
  try {
7076
- if (!existsSync14(NUDGE_STATE_PATH)) return state;
7077
- const raw = JSON.parse(readFileSync13(NUDGE_STATE_PATH, "utf8"));
7225
+ if (!existsSync16(NUDGE_STATE_PATH)) return state;
7226
+ const raw = JSON.parse(readFileSync14(NUDGE_STATE_PATH, "utf8"));
7078
7227
  if (Array.isArray(raw)) {
7079
7228
  for (const [key, val] of raw) {
7080
7229
  if (key && typeof val?.at === "number" && typeof val?.count === "number") {
@@ -7088,7 +7237,7 @@ function loadNudgeState() {
7088
7237
  }
7089
7238
  function saveNudgeState(state) {
7090
7239
  const entries = Array.from(state.lastNudge.entries());
7091
- writeFileSync8(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
7240
+ writeFileSync9(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
7092
7241
  }
7093
7242
  function createReviewNudgeRealDeps(getClient2) {
7094
7243
  return {
@@ -7426,134 +7575,12 @@ var init_daemon_orchestration = __esm({
7426
7575
  }
7427
7576
  });
7428
7577
 
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
7437
- });
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");
7444
- }
7445
- function getKeyPath() {
7446
- return path18.join(getKeyDir(), "master.key");
7447
- }
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
7578
  // src/lib/shard-manager.ts
7553
7579
  var shard_manager_exports = {};
7554
7580
  __export(shard_manager_exports, {
7555
7581
  disposeShards: () => disposeShards,
7556
7582
  ensureShardSchema: () => ensureShardSchema,
7583
+ getOpenShardCount: () => getOpenShardCount,
7557
7584
  getReadyShardClient: () => getReadyShardClient,
7558
7585
  getShardClient: () => getShardClient,
7559
7586
  getShardsDir: () => getShardsDir,
@@ -7563,14 +7590,17 @@ __export(shard_manager_exports, {
7563
7590
  shardExists: () => shardExists
7564
7591
  });
7565
7592
  import path19 from "path";
7566
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
7593
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
7567
7594
  import { createClient as createClient2 } from "@libsql/client";
7568
7595
  function initShardManager(encryptionKey) {
7569
7596
  _encryptionKey = encryptionKey;
7570
- if (!existsSync16(SHARDS_DIR)) {
7597
+ if (!existsSync17(SHARDS_DIR)) {
7571
7598
  mkdirSync7(SHARDS_DIR, { recursive: true });
7572
7599
  }
7573
7600
  _shardingEnabled = true;
7601
+ if (_evictionTimer) clearInterval(_evictionTimer);
7602
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
7603
+ _evictionTimer.unref();
7574
7604
  }
7575
7605
  function isShardingEnabled() {
7576
7606
  return _shardingEnabled;
@@ -7587,21 +7617,28 @@ function getShardClient(projectName) {
7587
7617
  throw new Error(`Invalid project name for shard: "${projectName}"`);
7588
7618
  }
7589
7619
  const cached = _shards.get(safeName);
7590
- if (cached) return cached;
7620
+ if (cached) {
7621
+ _shardLastAccess.set(safeName, Date.now());
7622
+ return cached;
7623
+ }
7624
+ while (_shards.size >= MAX_OPEN_SHARDS) {
7625
+ evictLRU();
7626
+ }
7591
7627
  const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
7592
7628
  const client = createClient2({
7593
7629
  url: `file:${dbPath}`,
7594
7630
  encryptionKey: _encryptionKey
7595
7631
  });
7596
7632
  _shards.set(safeName, client);
7633
+ _shardLastAccess.set(safeName, Date.now());
7597
7634
  return client;
7598
7635
  }
7599
7636
  function shardExists(projectName) {
7600
7637
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
7601
- return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
7638
+ return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
7602
7639
  }
7603
7640
  function listShards() {
7604
- if (!existsSync16(SHARDS_DIR)) return [];
7641
+ if (!existsSync17(SHARDS_DIR)) return [];
7605
7642
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
7606
7643
  }
7607
7644
  async function ensureShardSchema(client) {
@@ -7653,6 +7690,8 @@ async function ensureShardSchema(client) {
7653
7690
  for (const col of [
7654
7691
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
7655
7692
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
7693
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
7694
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
7656
7695
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
7657
7696
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
7658
7697
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -7785,31 +7824,202 @@ async function ensureShardSchema(client) {
7785
7824
  }
7786
7825
  }
7787
7826
  }
7788
- async function getReadyShardClient(projectName) {
7789
- const client = getShardClient(projectName);
7790
- await ensureShardSchema(client);
7791
- return client;
7827
+ async function getReadyShardClient(projectName) {
7828
+ const client = getShardClient(projectName);
7829
+ await ensureShardSchema(client);
7830
+ return client;
7831
+ }
7832
+ function evictLRU() {
7833
+ let oldest = null;
7834
+ let oldestTime = Infinity;
7835
+ for (const [name, time] of _shardLastAccess) {
7836
+ if (time < oldestTime) {
7837
+ oldestTime = time;
7838
+ oldest = name;
7839
+ }
7840
+ }
7841
+ if (oldest) {
7842
+ const client = _shards.get(oldest);
7843
+ if (client) {
7844
+ client.close();
7845
+ }
7846
+ _shards.delete(oldest);
7847
+ _shardLastAccess.delete(oldest);
7848
+ }
7849
+ }
7850
+ function evictIdleShards() {
7851
+ const now = Date.now();
7852
+ const toEvict = [];
7853
+ for (const [name, lastAccess] of _shardLastAccess) {
7854
+ if (now - lastAccess > SHARD_IDLE_MS) {
7855
+ toEvict.push(name);
7856
+ }
7857
+ }
7858
+ for (const name of toEvict) {
7859
+ const client = _shards.get(name);
7860
+ if (client) {
7861
+ client.close();
7862
+ }
7863
+ _shards.delete(name);
7864
+ _shardLastAccess.delete(name);
7865
+ }
7866
+ }
7867
+ function getOpenShardCount() {
7868
+ return _shards.size;
7792
7869
  }
7793
7870
  function disposeShards() {
7871
+ if (_evictionTimer) {
7872
+ clearInterval(_evictionTimer);
7873
+ _evictionTimer = null;
7874
+ }
7794
7875
  for (const [, client] of _shards) {
7795
7876
  client.close();
7796
7877
  }
7797
7878
  _shards.clear();
7879
+ _shardLastAccess.clear();
7798
7880
  _shardingEnabled = false;
7799
7881
  _encryptionKey = null;
7800
7882
  }
7801
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
7883
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
7802
7884
  var init_shard_manager = __esm({
7803
7885
  "src/lib/shard-manager.ts"() {
7804
7886
  "use strict";
7805
7887
  init_config();
7806
7888
  SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
7889
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
7890
+ MAX_OPEN_SHARDS = 10;
7891
+ EVICTION_INTERVAL_MS = 60 * 1e3;
7807
7892
  _shards = /* @__PURE__ */ new Map();
7893
+ _shardLastAccess = /* @__PURE__ */ new Map();
7894
+ _evictionTimer = null;
7808
7895
  _encryptionKey = null;
7809
7896
  _shardingEnabled = false;
7810
7897
  }
7811
7898
  });
7812
7899
 
7900
+ // src/lib/keychain.ts
7901
+ var keychain_exports = {};
7902
+ __export(keychain_exports, {
7903
+ deleteMasterKey: () => deleteMasterKey,
7904
+ exportMnemonic: () => exportMnemonic,
7905
+ getMasterKey: () => getMasterKey,
7906
+ importMnemonic: () => importMnemonic,
7907
+ setMasterKey: () => setMasterKey
7908
+ });
7909
+ import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
7910
+ import { existsSync as existsSync18 } from "fs";
7911
+ import path20 from "path";
7912
+ import os12 from "os";
7913
+ function getKeyDir() {
7914
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
7915
+ }
7916
+ function getKeyPath() {
7917
+ return path20.join(getKeyDir(), "master.key");
7918
+ }
7919
+ async function tryKeytar() {
7920
+ try {
7921
+ return await import("keytar");
7922
+ } catch {
7923
+ return null;
7924
+ }
7925
+ }
7926
+ async function getMasterKey() {
7927
+ const keytar = await tryKeytar();
7928
+ if (keytar) {
7929
+ try {
7930
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
7931
+ if (stored) {
7932
+ return Buffer.from(stored, "base64");
7933
+ }
7934
+ } catch {
7935
+ }
7936
+ }
7937
+ const keyPath = getKeyPath();
7938
+ if (!existsSync18(keyPath)) {
7939
+ process.stderr.write(
7940
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
7941
+ `
7942
+ );
7943
+ return null;
7944
+ }
7945
+ try {
7946
+ const content = await readFile4(keyPath, "utf-8");
7947
+ return Buffer.from(content.trim(), "base64");
7948
+ } catch (err) {
7949
+ process.stderr.write(
7950
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
7951
+ `
7952
+ );
7953
+ return null;
7954
+ }
7955
+ }
7956
+ async function setMasterKey(key) {
7957
+ const b64 = key.toString("base64");
7958
+ const keytar = await tryKeytar();
7959
+ if (keytar) {
7960
+ try {
7961
+ await keytar.setPassword(SERVICE, ACCOUNT, b64);
7962
+ return;
7963
+ } catch {
7964
+ }
7965
+ }
7966
+ const dir = getKeyDir();
7967
+ await mkdir4(dir, { recursive: true });
7968
+ const keyPath = getKeyPath();
7969
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
7970
+ await chmod2(keyPath, 384);
7971
+ }
7972
+ async function deleteMasterKey() {
7973
+ const keytar = await tryKeytar();
7974
+ if (keytar) {
7975
+ try {
7976
+ await keytar.deletePassword(SERVICE, ACCOUNT);
7977
+ } catch {
7978
+ }
7979
+ }
7980
+ const keyPath = getKeyPath();
7981
+ if (existsSync18(keyPath)) {
7982
+ await unlink(keyPath);
7983
+ }
7984
+ }
7985
+ async function loadBip39() {
7986
+ try {
7987
+ return await import("bip39");
7988
+ } catch {
7989
+ throw new Error(
7990
+ "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
7991
+ );
7992
+ }
7993
+ }
7994
+ async function exportMnemonic(key) {
7995
+ if (key.length !== 32) {
7996
+ throw new Error(`Key must be 32 bytes, got ${key.length}`);
7997
+ }
7998
+ const { entropyToMnemonic } = await loadBip39();
7999
+ return entropyToMnemonic(key.toString("hex"));
8000
+ }
8001
+ async function importMnemonic(mnemonic) {
8002
+ const trimmed = mnemonic.trim();
8003
+ const words = trimmed.split(/\s+/);
8004
+ if (words.length !== 24) {
8005
+ throw new Error(`Expected 24 words, got ${words.length}`);
8006
+ }
8007
+ const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
8008
+ if (!validateMnemonic(trimmed)) {
8009
+ throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
8010
+ }
8011
+ const entropy = mnemonicToEntropy(trimmed);
8012
+ return Buffer.from(entropy, "hex");
8013
+ }
8014
+ var SERVICE, ACCOUNT;
8015
+ var init_keychain = __esm({
8016
+ "src/lib/keychain.ts"() {
8017
+ "use strict";
8018
+ SERVICE = "exe-mem";
8019
+ ACCOUNT = "master-key";
8020
+ }
8021
+ });
8022
+
7813
8023
  // src/lib/platform-procedures.ts
7814
8024
  var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
7815
8025
  var init_platform_procedures = __esm({
@@ -8582,15 +8792,15 @@ async function pollPendingReviews(deps, state) {
8582
8792
  return [];
8583
8793
  }
8584
8794
  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
8795
  const sent = [];
8593
8796
  for (const exeSession of sessions) {
8797
+ let reviewCount = 0;
8798
+ try {
8799
+ reviewCount = await deps.countPendingReviews(exeSession);
8800
+ } catch {
8801
+ continue;
8802
+ }
8803
+ if (reviewCount === 0) continue;
8594
8804
  const lastSent = state.lastIntercomSent.get(exeSession) ?? 0;
8595
8805
  if (Date.now() - lastSent < state.intervalMs) continue;
8596
8806
  try {
@@ -8600,15 +8810,17 @@ async function pollPendingReviews(deps, state) {
8600
8810
  } catch {
8601
8811
  }
8602
8812
  }
8603
- try {
8604
- const orphans = await deps.findOrphanedDoneTasks();
8605
- for (const orphan of orphans) {
8606
- try {
8607
- await deps.createReviewForOrphan(orphan);
8608
- } catch {
8813
+ for (const exeSession of sessions) {
8814
+ try {
8815
+ const orphans = await deps.findOrphanedDoneTasks(exeSession);
8816
+ for (const orphan of orphans) {
8817
+ try {
8818
+ await deps.createReviewForOrphan(orphan);
8819
+ } catch {
8820
+ }
8609
8821
  }
8822
+ } catch {
8610
8823
  }
8611
- } catch {
8612
8824
  }
8613
8825
  if (deps.findStaleTasks && deps.sendNudge) {
8614
8826
  try {
@@ -8626,50 +8838,56 @@ async function pollPendingReviews(deps, state) {
8626
8838
  }
8627
8839
  }
8628
8840
  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
- `
8841
+ for (const exeSession of sessions) {
8842
+ try {
8843
+ const urgent = await deps.findUrgentUnread(exeSession);
8844
+ for (const msg of urgent) {
8845
+ try {
8846
+ const employeeSessions = deps.listTmuxSessions().filter(
8847
+ (s) => s.startsWith(`${msg.target_agent}-`) && s.endsWith(`-${exeSession}`)
8642
8848
  );
8849
+ for (const sess of employeeSessions) {
8850
+ deps.sendIntercom(sess);
8851
+ }
8852
+ const ageMs = Date.now() - new Date(msg.created_at).getTime();
8853
+ if (ageMs > 5 * 60 * 1e3) {
8854
+ process.stderr.write(
8855
+ `[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
8856
+ `
8857
+ );
8858
+ }
8859
+ } catch {
8643
8860
  }
8644
- } catch {
8645
8861
  }
8862
+ } catch {
8646
8863
  }
8647
- } catch {
8648
8864
  }
8649
8865
  }
8650
8866
  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
- `
8867
+ for (const exeSession of sessions) {
8868
+ try {
8869
+ const unstarted = await deps.findUnstartedTasks(exeSession);
8870
+ for (const task of unstarted) {
8871
+ try {
8872
+ const employeeSessions = deps.listTmuxSessions().filter(
8873
+ (s) => (s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)) && s.endsWith(`-${exeSession}`)
8667
8874
  );
8875
+ for (const sess of employeeSessions) {
8876
+ deps.sendIntercom(sess);
8877
+ }
8878
+ const ageMs = Date.now() - new Date(task.created_at).getTime();
8879
+ const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
8880
+ if (ageMs > UNSTARTED_WARNING_MS) {
8881
+ process.stderr.write(
8882
+ `[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
8883
+ `
8884
+ );
8885
+ }
8886
+ } catch {
8668
8887
  }
8669
- } catch {
8670
8888
  }
8889
+ } catch {
8671
8890
  }
8672
- } catch {
8673
8891
  }
8674
8892
  }
8675
8893
  return sent;
@@ -8682,9 +8900,9 @@ function createRealDeps(getClient2) {
8682
8900
  timeout: 3e3
8683
8901
  }).trim().split("\n").filter(Boolean);
8684
8902
  },
8685
- countPendingReviews: async () => {
8903
+ countPendingReviews: async (sessionScope) => {
8686
8904
  const client = getClient2();
8687
- const rpScope = sessionScopeFilter();
8905
+ const rpScope = strictSessionScopeFilter(sessionScope);
8688
8906
  const result = await client.execute({
8689
8907
  sql: `SELECT COUNT(*) as count FROM tasks
8690
8908
  WHERE status = 'needs_review'${rpScope.sql}`,
@@ -8696,10 +8914,10 @@ function createRealDeps(getClient2) {
8696
8914
  const { sendIntercom: centralSend } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
8697
8915
  centralSend(session);
8698
8916
  },
8699
- findOrphanedDoneTasks: async () => {
8917
+ findOrphanedDoneTasks: async (sessionScope) => {
8700
8918
  const client = getClient2();
8701
8919
  const coordinatorName = getCoordinatorName();
8702
- const odScope = sessionScopeFilter(void 0, "t");
8920
+ const odScope = strictSessionScopeFilter(sessionScope, "t");
8703
8921
  const result = await client.execute({
8704
8922
  sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
8705
8923
  t.project_name, t.task_file, t.result, t.status
@@ -8724,24 +8942,25 @@ function createRealDeps(getClient2) {
8724
8942
  process.stderr.write(`[exed] Created missing review for: ${task.title} (${task.assigned_to})
8725
8943
  `);
8726
8944
  },
8727
- findUrgentUnread: async () => {
8945
+ findUrgentUnread: async (sessionScope) => {
8728
8946
  const client = getClient2();
8947
+ const msgScope = strictSessionScopeFilter(sessionScope);
8729
8948
  const result = await client.execute({
8730
8949
  sql: `SELECT id, target_agent, content, created_at
8731
8950
  FROM messages
8732
8951
  WHERE priority = 'urgent'
8733
8952
  AND status IN ('pending', 'delivered')
8734
- AND created_at <= datetime('now', '-2 minutes')
8953
+ AND created_at <= datetime('now', '-2 minutes')${msgScope.sql}
8735
8954
  ORDER BY created_at ASC
8736
8955
  LIMIT 10`,
8737
- args: []
8956
+ args: [...msgScope.args]
8738
8957
  });
8739
8958
  return result.rows;
8740
8959
  },
8741
- findUnstartedTasks: async () => {
8960
+ findUnstartedTasks: async (sessionScope) => {
8742
8961
  const client = getClient2();
8743
8962
  const coordinatorName = getCoordinatorName();
8744
- const usScope = sessionScopeFilter();
8963
+ const usScope = strictSessionScopeFilter(sessionScope);
8745
8964
  const result = await client.execute({
8746
8965
  sql: `SELECT id, title, assigned_to, created_at
8747
8966
  FROM tasks
@@ -9176,10 +9395,10 @@ async function disposeEmbedder() {
9176
9395
  async function embedDirect(text) {
9177
9396
  const llamaCpp = await import("node-llama-cpp");
9178
9397
  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)) {
9398
+ const { existsSync: existsSync21 } = await import("fs");
9399
+ const path25 = await import("path");
9400
+ const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
9401
+ if (!existsSync21(modelPath)) {
9183
9402
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
9184
9403
  }
9185
9404
  const llama = await llamaCpp.getLlama();
@@ -9401,13 +9620,13 @@ __export(graph_rag_exports, {
9401
9620
  resolveAlias: () => resolveAlias,
9402
9621
  storeExtraction: () => storeExtraction
9403
9622
  });
9404
- import crypto6 from "crypto";
9623
+ import crypto7 from "crypto";
9405
9624
  function normalizeEntityName(name) {
9406
9625
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
9407
9626
  }
9408
9627
  function entityId(name, type) {
9409
9628
  const normalized = normalizeEntityName(name);
9410
- return crypto6.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
9629
+ return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
9411
9630
  }
9412
9631
  async function resolveAlias(client, name) {
9413
9632
  const normalized = normalizeEntityName(name);
@@ -9657,7 +9876,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
9657
9876
  const targetAlias = await resolveAlias(client, r.target);
9658
9877
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
9659
9878
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
9660
- const relId = crypto6.randomUUID().slice(0, 16);
9879
+ const relId = crypto7.randomUUID().slice(0, 16);
9661
9880
  try {
9662
9881
  await client.execute({
9663
9882
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -9720,7 +9939,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
9720
9939
  }
9721
9940
  }
9722
9941
  for (const h of extraction.hyperedges) {
9723
- const hId = crypto6.randomUUID().slice(0, 16);
9942
+ const hId = crypto7.randomUUID().slice(0, 16);
9724
9943
  try {
9725
9944
  await client.execute({
9726
9945
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -9784,7 +10003,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
9784
10003
  totalEntities += stored.entitiesStored;
9785
10004
  totalRelationships += stored.relationshipsStored;
9786
10005
  }
9787
- const contentHash = crypto6.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
10006
+ const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
9788
10007
  await client.execute({
9789
10008
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
9790
10009
  args: [contentHash, contentHash, memoryId]
@@ -9901,8 +10120,8 @@ __export(wiki_sync_exports, {
9901
10120
  listWorkspaces: () => listWorkspaces,
9902
10121
  syncMemories: () => syncMemories
9903
10122
  });
9904
- async function wikiRequest(config, path24, method = "GET", body) {
9905
- const url = `${config.wikiUrl}/api/v1${path24}`;
10123
+ async function wikiRequest(config, path25, method = "GET", body) {
10124
+ const url = `${config.wikiUrl}/api/v1${path25}`;
9906
10125
  const headers = {
9907
10126
  "Authorization": `Bearer ${config.wikiApiKey}`,
9908
10127
  "Content-Type": "application/json"
@@ -9914,7 +10133,7 @@ async function wikiRequest(config, path24, method = "GET", body) {
9914
10133
  signal: AbortSignal.timeout(3e4)
9915
10134
  });
9916
10135
  if (!response.ok) {
9917
- throw new Error(`Wiki API ${method} ${path24}: ${response.status} ${response.statusText}`);
10136
+ throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
9918
10137
  }
9919
10138
  return response.json();
9920
10139
  }
@@ -10026,8 +10245,8 @@ __export(token_spend_exports, {
10026
10245
  import { readdir } from "fs/promises";
10027
10246
  import { createReadStream } from "fs";
10028
10247
  import { createInterface } from "readline";
10029
- import path20 from "path";
10030
- import os12 from "os";
10248
+ import path21 from "path";
10249
+ import os13 from "os";
10031
10250
  function getPricing(model) {
10032
10251
  if (MODEL_PRICING[model]) return MODEL_PRICING[model];
10033
10252
  const stripped = model.replace(/-\d{8}$/, "");
@@ -10039,29 +10258,33 @@ function getPricing(model) {
10039
10258
  return DEFAULT_PRICING;
10040
10259
  }
10041
10260
  async function getAgentSpend(period = "7d") {
10261
+ const cached = _spendCache.get(period);
10262
+ if (cached && Date.now() < cached.expires) {
10263
+ return cached.result;
10264
+ }
10042
10265
  const cutoff = periodToCutoff(period);
10043
10266
  const client = getClient();
10044
- const result = await client.execute({
10267
+ const dbResult = await client.execute({
10045
10268
  sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
10046
10269
  args: [cutoff]
10047
10270
  });
10048
- if (result.rows.length === 0) return [];
10271
+ if (dbResult.rows.length === 0) return [];
10049
10272
  const sessionAgent = /* @__PURE__ */ new Map();
10050
- for (const row of result.rows) {
10273
+ for (const row of dbResult.rows) {
10051
10274
  sessionAgent.set(row.session_uuid, row.agent_id);
10052
10275
  }
10053
- const claudeDir = path20.join(os12.homedir(), ".claude", "projects");
10276
+ const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
10054
10277
  let projectDirs = [];
10055
10278
  try {
10056
10279
  const entries = await readdir(claudeDir);
10057
- projectDirs = entries.map((e) => path20.join(claudeDir, e));
10280
+ projectDirs = entries.map((e) => path21.join(claudeDir, e));
10058
10281
  } catch {
10059
10282
  return [];
10060
10283
  }
10061
10284
  const agentTotals = /* @__PURE__ */ new Map();
10062
10285
  for (const [sessionUuid, agentId] of sessionAgent) {
10063
10286
  for (const dir of projectDirs) {
10064
- const jsonlPath = path20.join(dir, `${sessionUuid}.jsonl`);
10287
+ const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
10065
10288
  try {
10066
10289
  const usage = await extractSessionUsage(jsonlPath);
10067
10290
  if (usage.input === 0 && usage.output === 0) continue;
@@ -10085,7 +10308,7 @@ async function getAgentSpend(period = "7d") {
10085
10308
  }
10086
10309
  }
10087
10310
  }
10088
- return Array.from(agentTotals.entries()).map(([agentId, t]) => ({
10311
+ const result = Array.from(agentTotals.entries()).map(([agentId, t]) => ({
10089
10312
  agentId,
10090
10313
  inputTokens: t.input,
10091
10314
  outputTokens: t.output,
@@ -10095,6 +10318,8 @@ async function getAgentSpend(period = "7d") {
10095
10318
  sessions: t.sessions.size,
10096
10319
  period
10097
10320
  })).sort((a, b) => b.costUSD - a.costUSD);
10321
+ _spendCache.set(period, { result, expires: Date.now() + CACHE_TTL_MS });
10322
+ return result;
10098
10323
  }
10099
10324
  async function extractSessionUsage(jsonlPath) {
10100
10325
  let input = 0;
@@ -10141,7 +10366,7 @@ function periodToCutoff(period) {
10141
10366
  const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
10142
10367
  return new Date(Date.now() - ms).toISOString();
10143
10368
  }
10144
- var MODEL_PRICING, DEFAULT_PRICING;
10369
+ var MODEL_PRICING, DEFAULT_PRICING, CACHE_TTL_MS, _spendCache;
10145
10370
  var init_token_spend = __esm({
10146
10371
  "src/lib/token-spend.ts"() {
10147
10372
  "use strict";
@@ -10171,6 +10396,8 @@ var init_token_spend = __esm({
10171
10396
  "claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
10172
10397
  };
10173
10398
  DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
10399
+ CACHE_TTL_MS = 5 * 60 * 1e3;
10400
+ _spendCache = /* @__PURE__ */ new Map();
10174
10401
  }
10175
10402
  });
10176
10403
 
@@ -10182,11 +10409,11 @@ __export(update_check_exports, {
10182
10409
  getRemoteVersion: () => getRemoteVersion
10183
10410
  });
10184
10411
  import { execSync as execSync11 } from "child_process";
10185
- import { readFileSync as readFileSync14 } from "fs";
10186
- import path21 from "path";
10412
+ import { readFileSync as readFileSync15 } from "fs";
10413
+ import path22 from "path";
10187
10414
  function getLocalVersion(packageRoot) {
10188
- const pkgPath = path21.join(packageRoot, "package.json");
10189
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
10415
+ const pkgPath = path22.join(packageRoot, "package.json");
10416
+ const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
10190
10417
  return pkg.version;
10191
10418
  }
10192
10419
  function getRemoteVersion() {
@@ -10229,16 +10456,16 @@ __export(ws_auth_exports, {
10229
10456
  deriveWsAuthToken: () => deriveWsAuthToken,
10230
10457
  hashAuthToken: () => hashAuthToken
10231
10458
  });
10232
- import crypto7 from "crypto";
10459
+ import crypto8 from "crypto";
10233
10460
  function deriveWsAuthToken(masterKey) {
10234
- return Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
10461
+ return Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
10235
10462
  }
10236
10463
  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);
10464
+ const raw = Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
10465
+ return crypto8.createHash("sha256").update(raw).digest("hex").slice(0, 32);
10239
10466
  }
10240
10467
  function hashAuthToken(token) {
10241
- return crypto7.createHash("sha256").update(token).digest("hex");
10468
+ return crypto8.createHash("sha256").update(token).digest("hex");
10242
10469
  }
10243
10470
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
10244
10471
  var init_ws_auth = __esm({
@@ -10256,14 +10483,14 @@ __export(device_registry_exports, {
10256
10483
  resolveTargetDevice: () => resolveTargetDevice,
10257
10484
  setFriendlyName: () => setFriendlyName
10258
10485
  });
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";
10486
+ import crypto9 from "crypto";
10487
+ import os14 from "os";
10488
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync19 } from "fs";
10489
+ import path23 from "path";
10263
10490
  function getDeviceInfo() {
10264
- if (existsSync17(DEVICE_JSON_PATH)) {
10491
+ if (existsSync19(DEVICE_JSON_PATH)) {
10265
10492
  try {
10266
- const raw = readFileSync15(DEVICE_JSON_PATH, "utf8");
10493
+ const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
10267
10494
  const data = JSON.parse(raw);
10268
10495
  if (data.deviceId && data.friendlyName && data.hostname) {
10269
10496
  return data;
@@ -10271,20 +10498,20 @@ function getDeviceInfo() {
10271
10498
  } catch {
10272
10499
  }
10273
10500
  }
10274
- const hostname = os13.hostname();
10501
+ const hostname = os14.hostname();
10275
10502
  const info = {
10276
- deviceId: crypto8.randomUUID(),
10503
+ deviceId: crypto9.randomUUID(),
10277
10504
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
10278
10505
  hostname
10279
10506
  };
10280
- mkdirSync8(path22.dirname(DEVICE_JSON_PATH), { recursive: true });
10281
- writeFileSync9(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10507
+ mkdirSync8(path23.dirname(DEVICE_JSON_PATH), { recursive: true });
10508
+ writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10282
10509
  return info;
10283
10510
  }
10284
10511
  function setFriendlyName(name) {
10285
10512
  const info = getDeviceInfo();
10286
10513
  info.friendlyName = name;
10287
- writeFileSync9(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10514
+ writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10288
10515
  }
10289
10516
  async function resolveTargetDevice(targetAgent, targetProject) {
10290
10517
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -10318,7 +10545,7 @@ var init_device_registry = __esm({
10318
10545
  "src/lib/device-registry.ts"() {
10319
10546
  "use strict";
10320
10547
  init_config();
10321
- DEVICE_JSON_PATH = path22.join(EXE_AI_DIR, "device.json");
10548
+ DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
10322
10549
  }
10323
10550
  });
10324
10551
 
@@ -10542,10 +10769,10 @@ __export(messaging_exports, {
10542
10769
  sendMessage: () => sendMessage,
10543
10770
  setWsClientSend: () => setWsClientSend
10544
10771
  });
10545
- import crypto9 from "crypto";
10772
+ import crypto10 from "crypto";
10546
10773
  function generateUlid() {
10547
10774
  const timestamp = Date.now().toString(36).padStart(10, "0");
10548
- const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
10775
+ const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
10549
10776
  return (timestamp + random).toUpperCase();
10550
10777
  }
10551
10778
  function rowToMessage(row) {
@@ -10556,6 +10783,7 @@ function rowToMessage(row) {
10556
10783
  targetAgent: row.target_agent,
10557
10784
  targetProject: row.target_project ?? null,
10558
10785
  targetDevice: row.target_device,
10786
+ sessionScope: row.session_scope ?? null,
10559
10787
  content: row.content,
10560
10788
  priority: row.priority ?? "normal",
10561
10789
  status: row.status ?? "pending",
@@ -10573,15 +10801,17 @@ async function sendMessage(input) {
10573
10801
  const id = generateUlid();
10574
10802
  const now = (/* @__PURE__ */ new Date()).toISOString();
10575
10803
  const targetDevice = input.targetDevice ?? "local";
10804
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
10576
10805
  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', ?)`,
10806
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
10807
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
10579
10808
  args: [
10580
10809
  id,
10581
10810
  input.fromAgent,
10582
10811
  input.targetAgent,
10583
10812
  input.targetProject ?? null,
10584
10813
  targetDevice,
10814
+ sessionScope,
10585
10815
  input.content,
10586
10816
  input.priority ?? "normal",
10587
10817
  now
@@ -10595,9 +10825,10 @@ async function sendMessage(input) {
10595
10825
  }
10596
10826
  } catch {
10597
10827
  }
10828
+ const sentScope = strictSessionScopeFilter(sessionScope);
10598
10829
  const result = await client.execute({
10599
- sql: "SELECT * FROM messages WHERE id = ?",
10600
- args: [id]
10830
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
10831
+ args: [id, ...sentScope.args]
10601
10832
  });
10602
10833
  return rowToMessage(result.rows[0]);
10603
10834
  }
@@ -10621,6 +10852,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
10621
10852
  fromAgent: msg.fromAgent,
10622
10853
  targetAgent: msg.targetAgent,
10623
10854
  targetProject: msg.targetProject,
10855
+ sessionScope: msg.sessionScope,
10624
10856
  content: msg.content,
10625
10857
  priority: msg.priority,
10626
10858
  createdAt: msg.createdAt
@@ -10664,7 +10896,7 @@ async function deliverLocalMessage(messageId) {
10664
10896
  } catch {
10665
10897
  const newRetryCount = msg.retryCount + 1;
10666
10898
  if (newRetryCount >= MAX_RETRIES3) {
10667
- await markFailed(messageId, "session unavailable after 10 retries");
10899
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
10668
10900
  } else {
10669
10901
  await client.execute({
10670
10902
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -10674,85 +10906,101 @@ async function deliverLocalMessage(messageId) {
10674
10906
  return false;
10675
10907
  }
10676
10908
  }
10677
- async function getPendingMessages(targetAgent) {
10909
+ async function getPendingMessages(targetAgent, sessionScope) {
10678
10910
  const client = getClient();
10911
+ const scope = strictSessionScopeFilter(sessionScope);
10679
10912
  const result = await client.execute({
10680
10913
  sql: `SELECT * FROM messages
10681
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
10914
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
10682
10915
  ORDER BY id`,
10683
- args: [targetAgent]
10916
+ args: [targetAgent, ...scope.args]
10684
10917
  });
10685
10918
  return result.rows.map((row) => rowToMessage(row));
10686
10919
  }
10687
- async function markRead(messageId) {
10920
+ async function markRead(messageId, sessionScope) {
10688
10921
  const client = getClient();
10922
+ const scope = strictSessionScopeFilter(sessionScope);
10689
10923
  await client.execute({
10690
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
10691
- args: [messageId]
10924
+ sql: `UPDATE messages SET status = 'read'
10925
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
10926
+ args: [messageId, ...scope.args]
10692
10927
  });
10693
10928
  }
10694
- async function markAcknowledged(messageId) {
10929
+ async function markAcknowledged(messageId, sessionScope) {
10695
10930
  const client = getClient();
10931
+ const scope = strictSessionScopeFilter(sessionScope);
10696
10932
  await client.execute({
10697
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
10698
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10933
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
10934
+ WHERE id = ? AND status = 'read'${scope.sql}`,
10935
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
10699
10936
  });
10700
10937
  }
10701
- async function markProcessed(messageId) {
10938
+ async function markProcessed(messageId, sessionScope) {
10702
10939
  const client = getClient();
10940
+ const scope = strictSessionScopeFilter(sessionScope);
10703
10941
  await client.execute({
10704
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
10705
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10942
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
10943
+ WHERE id = ?${scope.sql}`,
10944
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
10706
10945
  });
10707
10946
  }
10708
- async function getMessageStatus(messageId) {
10947
+ async function getMessageStatus(messageId, sessionScope) {
10709
10948
  const client = getClient();
10949
+ const scope = strictSessionScopeFilter(sessionScope);
10710
10950
  const result = await client.execute({
10711
- sql: "SELECT status FROM messages WHERE id = ?",
10712
- args: [messageId]
10951
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
10952
+ args: [messageId, ...scope.args]
10713
10953
  });
10714
10954
  return result.rows[0]?.status ?? null;
10715
10955
  }
10716
- async function getUnacknowledgedMessages(targetAgent) {
10956
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
10717
10957
  const client = getClient();
10958
+ const scope = strictSessionScopeFilter(sessionScope);
10718
10959
  const result = await client.execute({
10719
10960
  sql: `SELECT * FROM messages
10720
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
10961
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
10721
10962
  ORDER BY id`,
10722
- args: [targetAgent]
10963
+ args: [targetAgent, ...scope.args]
10723
10964
  });
10724
10965
  return result.rows.map((row) => rowToMessage(row));
10725
10966
  }
10726
- async function getReadMessages(targetAgent) {
10967
+ async function getReadMessages(targetAgent, sessionScope) {
10727
10968
  const client = getClient();
10969
+ const scope = strictSessionScopeFilter(sessionScope);
10728
10970
  const result = await client.execute({
10729
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
10730
- args: [targetAgent]
10971
+ sql: `SELECT * FROM messages
10972
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
10973
+ ORDER BY id`,
10974
+ args: [targetAgent, ...scope.args]
10731
10975
  });
10732
10976
  return result.rows.map((row) => rowToMessage(row));
10733
10977
  }
10734
- async function markFailed(messageId, reason) {
10978
+ async function markFailed(messageId, reason, sessionScope) {
10735
10979
  const client = getClient();
10980
+ const scope = strictSessionScopeFilter(sessionScope);
10736
10981
  await client.execute({
10737
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
10738
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
10982
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
10983
+ WHERE id = ?${scope.sql}`,
10984
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
10739
10985
  });
10740
10986
  }
10741
- async function getFailedMessages() {
10987
+ async function getFailedMessages(sessionScope) {
10742
10988
  const client = getClient();
10989
+ const scope = strictSessionScopeFilter(sessionScope);
10743
10990
  const result = await client.execute({
10744
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
10745
- args: []
10991
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
10992
+ args: [...scope.args]
10746
10993
  });
10747
10994
  return result.rows.map((row) => rowToMessage(row));
10748
10995
  }
10749
- async function retryPendingMessages() {
10996
+ async function retryPendingMessages(sessionScope) {
10750
10997
  const client = getClient();
10998
+ const scope = strictSessionScopeFilter(sessionScope);
10751
10999
  const result = await client.execute({
10752
11000
  sql: `SELECT * FROM messages
10753
- WHERE status = 'pending' AND retry_count < ?
11001
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
10754
11002
  ORDER BY id`,
10755
- args: [MAX_RETRIES3]
11003
+ args: [MAX_RETRIES3, ...scope.args]
10756
11004
  });
10757
11005
  let delivered = 0;
10758
11006
  for (const row of result.rows) {
@@ -10771,6 +11019,7 @@ var init_messaging = __esm({
10771
11019
  "use strict";
10772
11020
  init_database();
10773
11021
  init_tmux_routing();
11022
+ init_task_scope();
10774
11023
  MAX_RETRIES3 = 10;
10775
11024
  _wsClientSend = null;
10776
11025
  }
@@ -10780,19 +11029,23 @@ var init_messaging = __esm({
10780
11029
  init_config();
10781
11030
  init_memory();
10782
11031
  init_daemon_protocol();
11032
+ init_daemon_auth();
10783
11033
  init_daemon_orchestration();
11034
+ import os15 from "os";
10784
11035
  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";
11036
+ import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync20, readFileSync as readFileSync17, chmodSync as chmodSync2 } from "fs";
11037
+ import path24 from "path";
10787
11038
  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");
11039
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path24.join(EXE_AI_DIR, "exed.sock");
11040
+ var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path24.join(EXE_AI_DIR, "exed.pid");
10790
11041
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
10791
11042
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
10792
11043
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
11044
+ var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
10793
11045
  var _context = null;
10794
11046
  var _model = null;
10795
11047
  var _llama = null;
11048
+ var _daemonToken = "";
10796
11049
  var MAX_QUEUE_SIZE = 1e3;
10797
11050
  var highQueue = [];
10798
11051
  var lowQueue = [];
@@ -10812,8 +11065,8 @@ function enqueue(queue, entry) {
10812
11065
  queue.push(entry);
10813
11066
  }
10814
11067
  async function loadModel() {
10815
- const modelPath = path23.join(MODELS_DIR, MODEL_FILE);
10816
- if (!existsSync18(modelPath)) {
11068
+ const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
11069
+ if (!existsSync20(modelPath)) {
10817
11070
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
10818
11071
  `);
10819
11072
  process.exit(1);
@@ -10837,6 +11090,7 @@ async function processQueue() {
10837
11090
  for (const text of entry.request.texts) {
10838
11091
  const embedding = await _context.getEmbeddingFor(text);
10839
11092
  const vector = Array.from(embedding.vector);
11093
+ embedding.vector = null;
10840
11094
  if (vector.length !== EMBEDDING_DIM) {
10841
11095
  throw new Error(`Dimension mismatch: got ${vector.length}, expected ${EMBEDDING_DIM}`);
10842
11096
  }
@@ -10882,6 +11136,11 @@ function checkIdle() {
10882
11136
  }
10883
11137
  async function shutdown() {
10884
11138
  resetIdleTimer();
11139
+ try {
11140
+ const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
11141
+ disposeShards2();
11142
+ } catch {
11143
+ }
10885
11144
  if (_context) {
10886
11145
  try {
10887
11146
  await _context.dispose();
@@ -10920,6 +11179,7 @@ async function handleHealthCheck(socket, requestId) {
10920
11179
  }
10921
11180
  }
10922
11181
  const dbConnected = _storeInitialized;
11182
+ const mem = process.memoryUsage();
10923
11183
  sendResponse(socket, {
10924
11184
  id: requestId,
10925
11185
  ...healthy && testOk ? {
@@ -10927,7 +11187,13 @@ async function handleHealthCheck(socket, requestId) {
10927
11187
  status: "ok",
10928
11188
  uptime: Math.floor((Date.now() - _startedAt) / 1e3),
10929
11189
  requests_served: _requestsServed,
10930
- db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
11190
+ db: { connected: dbConnected, totalDbRequests: _dbRequestsServed },
11191
+ memory: {
11192
+ rss_mb: Math.round(mem.rss / 1024 / 1024),
11193
+ heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
11194
+ external_mb: Math.round(mem.external / 1024 / 1024),
11195
+ array_buffers_mb: Math.round(mem.arrayBuffers / 1024 / 1024)
11196
+ }
10931
11197
  }
10932
11198
  } : { error: "unhealthy: model not loaded or test embed failed" }
10933
11199
  });
@@ -10981,13 +11247,67 @@ async function handleDbBatch(socket, requestId, statements, mode) {
10981
11247
  });
10982
11248
  }
10983
11249
  }
11250
+ var _ingestCount = 0;
11251
+ async function handleIngest(req) {
11252
+ try {
11253
+ if (!await ensureStoreForPolling()) return;
11254
+ if (!req.rawText || req.rawText.length < 50) return;
11255
+ let vectorBlob = null;
11256
+ if (_context) {
11257
+ try {
11258
+ const embedding = await _context.getEmbeddingFor(req.rawText);
11259
+ const vector = Array.from(embedding.vector);
11260
+ embedding.vector = null;
11261
+ if (vector.length === EMBEDDING_DIM) {
11262
+ const { vectorToBlob: vectorToBlob2 } = await Promise.resolve().then(() => (init_store(), store_exports));
11263
+ vectorBlob = vectorToBlob2(vector);
11264
+ }
11265
+ } catch {
11266
+ }
11267
+ }
11268
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
11269
+ const client = getClient2();
11270
+ const { randomUUID: randomUUID5 } = await import("crypto");
11271
+ const id = randomUUID5();
11272
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11273
+ await client.execute({
11274
+ 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)
11275
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'raw', ?)`,
11276
+ args: [
11277
+ id,
11278
+ req.agentId,
11279
+ req.agentRole,
11280
+ req.sessionId,
11281
+ now,
11282
+ req.toolName,
11283
+ req.projectName,
11284
+ req.hasError ? 1 : 0,
11285
+ req.rawText,
11286
+ vectorBlob,
11287
+ req.taskId ?? null,
11288
+ req.confidence ?? 0.7,
11289
+ req.draft ? 1 : 0,
11290
+ req.trajectory ? JSON.stringify(req.trajectory) : null
11291
+ ]
11292
+ });
11293
+ _ingestCount++;
11294
+ } catch (err) {
11295
+ process.stderr.write(`[exed] Ingest error: ${err instanceof Error ? err.message : String(err)}
11296
+ `);
11297
+ }
11298
+ }
10984
11299
  function startServer() {
10985
- mkdirSync9(path23.dirname(SOCKET_PATH2), { recursive: true });
11300
+ mkdirSync9(path24.dirname(SOCKET_PATH2), { recursive: true });
11301
+ try {
11302
+ chmodSync2(path24.dirname(SOCKET_PATH2), 448);
11303
+ } catch {
11304
+ }
11305
+ _daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
10986
11306
  for (const oldFile of ["embed.sock", "embed.pid"]) {
10987
- const oldPath = path23.join(path23.dirname(SOCKET_PATH2), oldFile);
11307
+ const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
10988
11308
  try {
10989
11309
  if (oldFile.endsWith(".pid")) {
10990
- const pid = parseInt(readFileSync16(oldPath, "utf8").trim(), 10);
11310
+ const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
10991
11311
  if (pid > 0) try {
10992
11312
  process.kill(pid, "SIGKILL");
10993
11313
  } catch {
@@ -11019,6 +11339,10 @@ function startServer() {
11019
11339
  if (!line) continue;
11020
11340
  try {
11021
11341
  const request = JSON.parse(line);
11342
+ if (!request.token || request.token !== _daemonToken) {
11343
+ sendResponse(socket, { id: request.id ?? "unauthorized", error: "Unauthorized daemon request" });
11344
+ continue;
11345
+ }
11022
11346
  if (request.type === "health") {
11023
11347
  void handleHealthCheck(socket, request.id ?? "health");
11024
11348
  continue;
@@ -11039,6 +11363,10 @@ function startServer() {
11039
11363
  void handleDbBatch(socket, request.id, request.statements, request.mode);
11040
11364
  continue;
11041
11365
  }
11366
+ if (request.type === "ingest") {
11367
+ void handleIngest(request);
11368
+ continue;
11369
+ }
11042
11370
  if (!request.id || !Array.isArray(request.texts)) {
11043
11371
  sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
11044
11372
  continue;
@@ -11076,7 +11404,15 @@ function startServer() {
11076
11404
  server.listen(SOCKET_PATH2, () => {
11077
11405
  process.stderr.write(`[exed] Listening on ${SOCKET_PATH2}
11078
11406
  `);
11079
- writeFileSync10(PID_PATH2, String(process.pid));
11407
+ try {
11408
+ chmodSync2(SOCKET_PATH2, 384);
11409
+ } catch {
11410
+ }
11411
+ writeFileSync11(PID_PATH2, String(process.pid));
11412
+ try {
11413
+ chmodSync2(PID_PATH2, 384);
11414
+ } catch {
11415
+ }
11080
11416
  checkIdle();
11081
11417
  });
11082
11418
  }
@@ -11369,7 +11705,7 @@ function startWikiSync() {
11369
11705
  });
11370
11706
  }
11371
11707
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
11372
- var AGENT_STATS_PATH = path23.join(EXE_AI_DIR, "agent-stats.json");
11708
+ var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
11373
11709
  async function writeAgentStats() {
11374
11710
  if (!await ensureStoreForPolling()) return;
11375
11711
  try {
@@ -11427,7 +11763,7 @@ async function writeAgentStats() {
11427
11763
  pid: process.pid
11428
11764
  }
11429
11765
  };
11430
- writeFileSync10(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
11766
+ writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
11431
11767
  } catch (err) {
11432
11768
  process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
11433
11769
  `);
@@ -11499,12 +11835,12 @@ function startIntercomQueueDrain() {
11499
11835
  const hasInProgressTask = (session) => {
11500
11836
  try {
11501
11837
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
11502
- const path24 = __require("path");
11503
- const { existsSync: existsSync19 } = __require("fs");
11504
- const os14 = __require("os");
11838
+ const path25 = __require("path");
11839
+ const { existsSync: existsSync21 } = __require("fs");
11840
+ const os16 = __require("os");
11505
11841
  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);
11842
+ const markerPath = path25.join(os16.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
11843
+ return existsSync21(markerPath);
11508
11844
  } catch {
11509
11845
  return false;
11510
11846
  }
@@ -11593,12 +11929,44 @@ function startAutoWake() {
11593
11929
  process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
11594
11930
  `);
11595
11931
  }
11932
+ var TOTAL_MEM_GB = os15.totalmem() / 1024 ** 3;
11933
+ var RSS_WARN_BYTES = Number(process.env.EXE_RSS_WARN_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 4 * 1024 ** 3 : 1024 ** 3);
11934
+ var RSS_RESTART_BYTES = Number(process.env.EXE_RSS_RESTART_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 8 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 6 * 1024 ** 3 : 2 * 1024 ** 3);
11935
+ var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
11936
+ var _rssWarned = false;
11937
+ function startRssWatchdog() {
11938
+ const tick = () => {
11939
+ const rss = process.memoryUsage.rss();
11940
+ if (rss > RSS_RESTART_BYTES) {
11941
+ process.stderr.write(
11942
+ `[exed] RSS CRITICAL: ${(rss / 1024 / 1024).toFixed(0)} MB exceeds ${(RSS_RESTART_BYTES / 1024 ** 3).toFixed(1)} GB limit \u2014 restarting.
11943
+ `
11944
+ );
11945
+ void shutdown();
11946
+ return;
11947
+ }
11948
+ if (rss > RSS_WARN_BYTES && !_rssWarned) {
11949
+ _rssWarned = true;
11950
+ const heap = process.memoryUsage();
11951
+ process.stderr.write(
11952
+ `[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)
11953
+ `
11954
+ );
11955
+ } else if (rss < RSS_WARN_BYTES && _rssWarned) {
11956
+ _rssWarned = false;
11957
+ }
11958
+ };
11959
+ const timer = setInterval(tick, RSS_CHECK_INTERVAL_MS);
11960
+ timer.unref();
11961
+ process.stderr.write(`[exed] RSS watchdog started (warn: ${(RSS_WARN_BYTES / 1024 ** 3).toFixed(1)} GB, restart: ${(RSS_RESTART_BYTES / 1024 ** 3).toFixed(1)} GB)
11962
+ `);
11963
+ }
11596
11964
  process.on("SIGINT", () => void shutdown());
11597
11965
  process.on("SIGTERM", () => void shutdown());
11598
11966
  function checkExistingDaemon() {
11599
11967
  try {
11600
- if (!existsSync18(PID_PATH2)) return false;
11601
- const pid = parseInt(readFileSync16(PID_PATH2, "utf8").trim(), 10);
11968
+ if (!existsSync20(PID_PATH2)) return false;
11969
+ const pid = parseInt(readFileSync17(PID_PATH2, "utf8").trim(), 10);
11602
11970
  if (!pid || isNaN(pid)) return false;
11603
11971
  process.kill(pid, 0);
11604
11972
  process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
@@ -11686,6 +12054,7 @@ try {
11686
12054
  startIntercomQueueDrain();
11687
12055
  startConfidenceDecay();
11688
12056
  startAutoUpdateCheck();
12057
+ startRssWatchdog();
11689
12058
  try {
11690
12059
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11691
12060
  const config = await loadConfig2();