@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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 path2 from "path";
47
85
  import os2 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 = path2.join(os2.homedir(), ".exe-os");
52
90
  const legacyDir = path2.join(os2.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 = path2.join(dir, "config.json");
118
- if (!existsSync(configPath)) {
156
+ if (!existsSync2(configPath)) {
119
157
  return { ...DEFAULT_CONFIG, dbPath: path2.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 = path2.join(dir, "config.json");
149
- if (!existsSync(configPath)) {
188
+ if (!existsSync2(configPath)) {
150
189
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
151
190
  }
152
191
  try {
@@ -164,12 +203,10 @@ function loadConfigSync() {
164
203
  }
165
204
  async function saveConfig(config2) {
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 = path2.join(dir, "config.json");
169
208
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
170
- if (config2.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 = path2.join(EXE_AI_DIR, "memories.db");
194
232
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -305,10 +343,10 @@ __export(agent_config_exports, {
305
343
  saveAgentConfig: () => saveAgentConfig,
306
344
  setAgentRuntime: () => setAgentRuntime
307
345
  });
308
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
346
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
309
347
  import path3 from "path";
310
348
  function loadAgentConfig() {
311
- if (!existsSync2(AGENT_CONFIG_PATH)) return {};
349
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
312
350
  try {
313
351
  return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
314
352
  } catch {
@@ -317,8 +355,9 @@ function loadAgentConfig() {
317
355
  }
318
356
  function saveAgentConfig(config2) {
319
357
  const dir = path3.dirname(AGENT_CONFIG_PATH);
320
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
358
+ ensurePrivateDirSync(dir);
321
359
  writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
360
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
322
361
  }
323
362
  function getAgentRuntime(agentId) {
324
363
  const config2 = loadAgentConfig();
@@ -358,6 +397,7 @@ var init_agent_config = __esm({
358
397
  "use strict";
359
398
  init_config();
360
399
  init_runtime_table();
400
+ init_secure_files();
361
401
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
362
402
  KNOWN_RUNTIMES = {
363
403
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -405,7 +445,7 @@ __export(employees_exports, {
405
445
  validateEmployeeName: () => validateEmployeeName
406
446
  });
407
447
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
408
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
448
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
409
449
  import { execSync } from "child_process";
410
450
  import path4 from "path";
411
451
  import os3 from "os";
@@ -444,7 +484,7 @@ function validateEmployeeName(name) {
444
484
  return { valid: true };
445
485
  }
446
486
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
447
- if (!existsSync3(employeesPath)) {
487
+ if (!existsSync4(employeesPath)) {
448
488
  return [];
449
489
  }
450
490
  const raw = await readFile2(employeesPath, "utf-8");
@@ -459,7 +499,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
459
499
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
460
500
  }
461
501
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
462
- if (!existsSync3(employeesPath)) return [];
502
+ if (!existsSync4(employeesPath)) return [];
463
503
  try {
464
504
  return JSON.parse(readFileSync3(employeesPath, "utf-8"));
465
505
  } catch {
@@ -507,7 +547,7 @@ function appendToCoordinatorTeam(employee) {
507
547
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
508
548
  if (!coordinator) return;
509
549
  const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
510
- if (!existsSync3(idPath)) return;
550
+ if (!existsSync4(idPath)) return;
511
551
  const content = readFileSync3(idPath, "utf-8");
512
552
  if (content.includes(`**${capitalize(employee.name)}`)) return;
513
553
  const teamMatch = content.match(TEAM_SECTION_RE);
@@ -561,9 +601,9 @@ async function normalizeRosterCase(rosterPath) {
561
601
  const identityDir = path4.join(os3.homedir(), ".exe-os", "identity");
562
602
  const oldPath = path4.join(identityDir, `${oldName}.md`);
563
603
  const newPath = path4.join(identityDir, `${emp.name}.md`);
564
- if (existsSync3(oldPath) && !existsSync3(newPath)) {
604
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
565
605
  renameSync2(oldPath, newPath);
566
- } else if (existsSync3(oldPath) && oldPath !== newPath) {
606
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
567
607
  const content = readFileSync3(oldPath, "utf-8");
568
608
  writeFileSync2(newPath, content, "utf-8");
569
609
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
@@ -606,7 +646,7 @@ function registerBinSymlinks(name) {
606
646
  for (const suffix of ["", "-opencode"]) {
607
647
  const linkName = `${name}${suffix}`;
608
648
  const linkPath = path4.join(binDir, linkName);
609
- if (existsSync3(linkPath)) {
649
+ if (existsSync4(linkPath)) {
610
650
  skipped.push(linkName);
611
651
  continue;
612
652
  }
@@ -640,13 +680,13 @@ __export(session_registry_exports, {
640
680
  pruneStaleSessions: () => pruneStaleSessions,
641
681
  registerSession: () => registerSession
642
682
  });
643
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
683
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
644
684
  import { execSync as execSync2 } from "child_process";
645
685
  import path5 from "path";
646
686
  import os4 from "os";
647
687
  function registerSession(entry) {
648
688
  const dir = path5.dirname(REGISTRY_PATH);
649
- if (!existsSync4(dir)) {
689
+ if (!existsSync5(dir)) {
650
690
  mkdirSync2(dir, { recursive: true });
651
691
  }
652
692
  const sessions = listSessions();
@@ -960,16 +1000,16 @@ __export(intercom_queue_exports, {
960
1000
  queueIntercom: () => queueIntercom,
961
1001
  readQueue: () => readQueue
962
1002
  });
963
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1003
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
964
1004
  import path6 from "path";
965
1005
  import os5 from "os";
966
1006
  function ensureDir() {
967
1007
  const dir = path6.dirname(QUEUE_PATH);
968
- if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
1008
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
969
1009
  }
970
1010
  function readQueue() {
971
1011
  try {
972
- if (!existsSync5(QUEUE_PATH)) return [];
1012
+ if (!existsSync6(QUEUE_PATH)) return [];
973
1013
  return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
974
1014
  } catch {
975
1015
  return [];
@@ -1716,13 +1756,50 @@ var init_database_adapter = __esm({
1716
1756
  }
1717
1757
  });
1718
1758
 
1759
+ // src/lib/daemon-auth.ts
1760
+ import crypto from "crypto";
1761
+ import path8 from "path";
1762
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1763
+ function normalizeToken(token) {
1764
+ if (!token) return null;
1765
+ const trimmed = token.trim();
1766
+ return trimmed.length > 0 ? trimmed : null;
1767
+ }
1768
+ function readDaemonToken() {
1769
+ try {
1770
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1771
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1772
+ } catch {
1773
+ return null;
1774
+ }
1775
+ }
1776
+ function ensureDaemonToken(seed) {
1777
+ const existing = readDaemonToken();
1778
+ if (existing) return existing;
1779
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1780
+ ensurePrivateDirSync(EXE_AI_DIR);
1781
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1782
+ `, "utf8");
1783
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1784
+ return token;
1785
+ }
1786
+ var DAEMON_TOKEN_PATH;
1787
+ var init_daemon_auth = __esm({
1788
+ "src/lib/daemon-auth.ts"() {
1789
+ "use strict";
1790
+ init_config();
1791
+ init_secure_files();
1792
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1793
+ }
1794
+ });
1795
+
1719
1796
  // src/lib/exe-daemon-client.ts
1720
1797
  import net from "net";
1721
1798
  import os7 from "os";
1722
1799
  import { spawn } from "child_process";
1723
1800
  import { randomUUID as randomUUID2 } from "crypto";
1724
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1725
- import path8 from "path";
1801
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1802
+ import path9 from "path";
1726
1803
  import { fileURLToPath } from "url";
1727
1804
  function handleData(chunk) {
1728
1805
  _buffer += chunk.toString();
@@ -1750,9 +1827,9 @@ function handleData(chunk) {
1750
1827
  }
1751
1828
  }
1752
1829
  function cleanupStaleFiles() {
1753
- if (existsSync6(PID_PATH)) {
1830
+ if (existsSync8(PID_PATH)) {
1754
1831
  try {
1755
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1832
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1756
1833
  if (pid > 0) {
1757
1834
  try {
1758
1835
  process.kill(pid, 0);
@@ -1773,11 +1850,11 @@ function cleanupStaleFiles() {
1773
1850
  }
1774
1851
  }
1775
1852
  function findPackageRoot() {
1776
- let dir = path8.dirname(fileURLToPath(import.meta.url));
1777
- const { root } = path8.parse(dir);
1853
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1854
+ const { root } = path9.parse(dir);
1778
1855
  while (dir !== root) {
1779
- if (existsSync6(path8.join(dir, "package.json"))) return dir;
1780
- dir = path8.dirname(dir);
1856
+ if (existsSync8(path9.join(dir, "package.json"))) return dir;
1857
+ dir = path9.dirname(dir);
1781
1858
  }
1782
1859
  return null;
1783
1860
  }
@@ -1803,16 +1880,17 @@ function spawnDaemon() {
1803
1880
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1804
1881
  return;
1805
1882
  }
1806
- const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1807
- if (!existsSync6(daemonPath)) {
1883
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1884
+ if (!existsSync8(daemonPath)) {
1808
1885
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1809
1886
  `);
1810
1887
  return;
1811
1888
  }
1812
1889
  const resolvedPath = daemonPath;
1890
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1813
1891
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1814
1892
  `);
1815
- const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1893
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1816
1894
  let stderrFd = "ignore";
1817
1895
  try {
1818
1896
  stderrFd = openSync(logPath, "a");
@@ -1830,7 +1908,8 @@ function spawnDaemon() {
1830
1908
  TMUX_PANE: void 0,
1831
1909
  // Prevents resolveExeSession() from scoping to one session
1832
1910
  EXE_DAEMON_SOCK: SOCKET_PATH,
1833
- EXE_DAEMON_PID: PID_PATH
1911
+ EXE_DAEMON_PID: PID_PATH,
1912
+ [DAEMON_TOKEN_ENV]: daemonToken
1834
1913
  }
1835
1914
  });
1836
1915
  child.unref();
@@ -1940,13 +2019,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1940
2019
  return;
1941
2020
  }
1942
2021
  const id = randomUUID2();
2022
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1943
2023
  const timer = setTimeout(() => {
1944
2024
  _pending.delete(id);
1945
2025
  resolve({ error: "Request timeout" });
1946
2026
  }, timeoutMs);
1947
2027
  _pending.set(id, { resolve, timer });
1948
2028
  try {
1949
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2029
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1950
2030
  } catch {
1951
2031
  clearTimeout(timer);
1952
2032
  _pending.delete(id);
@@ -1975,9 +2055,9 @@ function killAndRespawnDaemon() {
1975
2055
  }
1976
2056
  try {
1977
2057
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1978
- if (existsSync6(PID_PATH)) {
2058
+ if (existsSync8(PID_PATH)) {
1979
2059
  try {
1980
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
2060
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1981
2061
  if (pid > 0) {
1982
2062
  try {
1983
2063
  process.kill(pid, "SIGKILL");
@@ -2097,17 +2177,19 @@ function disconnectClient() {
2097
2177
  function isClientConnected() {
2098
2178
  return _connected;
2099
2179
  }
2100
- 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;
2180
+ 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;
2101
2181
  var init_exe_daemon_client = __esm({
2102
2182
  "src/lib/exe-daemon-client.ts"() {
2103
2183
  "use strict";
2104
2184
  init_config();
2105
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
2106
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
2107
- SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
2185
+ init_daemon_auth();
2186
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
2187
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
2188
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
2108
2189
  SPAWN_LOCK_STALE_MS = 3e4;
2109
2190
  CONNECT_TIMEOUT_MS = 15e3;
2110
2191
  REQUEST_TIMEOUT_MS = 3e4;
2192
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
2111
2193
  _socket = null;
2112
2194
  _connected = false;
2113
2195
  _buffer = "";
@@ -2692,6 +2774,7 @@ async function ensureSchema() {
2692
2774
  project TEXT NOT NULL,
2693
2775
  summary TEXT NOT NULL,
2694
2776
  task_file TEXT,
2777
+ session_scope TEXT,
2695
2778
  read INTEGER NOT NULL DEFAULT 0,
2696
2779
  created_at TEXT NOT NULL
2697
2780
  );
@@ -2700,7 +2783,7 @@ async function ensureSchema() {
2700
2783
  ON notifications(read);
2701
2784
 
2702
2785
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2703
- ON notifications(agent_id);
2786
+ ON notifications(agent_id, session_scope);
2704
2787
 
2705
2788
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2706
2789
  ON notifications(task_file);
@@ -2738,6 +2821,7 @@ async function ensureSchema() {
2738
2821
  target_agent TEXT NOT NULL,
2739
2822
  target_project TEXT,
2740
2823
  target_device TEXT NOT NULL DEFAULT 'local',
2824
+ session_scope TEXT,
2741
2825
  content TEXT NOT NULL,
2742
2826
  priority TEXT DEFAULT 'normal',
2743
2827
  status TEXT DEFAULT 'pending',
@@ -2751,10 +2835,31 @@ async function ensureSchema() {
2751
2835
  );
2752
2836
 
2753
2837
  CREATE INDEX IF NOT EXISTS idx_messages_target
2754
- ON messages(target_agent, status);
2838
+ ON messages(target_agent, session_scope, status);
2755
2839
 
2756
2840
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2757
- ON messages(target_agent, from_agent, server_seq);
2841
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2842
+ `);
2843
+ try {
2844
+ await client.execute({
2845
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2846
+ args: []
2847
+ });
2848
+ } catch {
2849
+ }
2850
+ try {
2851
+ await client.execute({
2852
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2853
+ args: []
2854
+ });
2855
+ } catch {
2856
+ }
2857
+ await client.executeMultiple(`
2858
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2859
+ ON notifications(agent_id, session_scope, read, created_at);
2860
+
2861
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2862
+ ON messages(target_agent, session_scope, status, created_at);
2758
2863
  `);
2759
2864
  try {
2760
2865
  await client.execute({
@@ -3338,6 +3443,13 @@ async function ensureSchema() {
3338
3443
  } catch {
3339
3444
  }
3340
3445
  }
3446
+ try {
3447
+ await client.execute({
3448
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3449
+ args: []
3450
+ });
3451
+ } catch {
3452
+ }
3341
3453
  }
3342
3454
  async function disposeDatabase() {
3343
3455
  if (_walCheckpointTimer) {
@@ -3376,18 +3488,21 @@ var init_database = __esm({
3376
3488
  });
3377
3489
 
3378
3490
  // src/lib/license.ts
3379
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3491
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
3380
3492
  import { randomUUID as randomUUID3 } from "crypto";
3381
- import path9 from "path";
3493
+ import { createRequire as createRequire2 } from "module";
3494
+ import { pathToFileURL as pathToFileURL2 } from "url";
3495
+ import os8 from "os";
3496
+ import path10 from "path";
3382
3497
  import { jwtVerify, importSPKI } from "jose";
3383
3498
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3384
3499
  var init_license = __esm({
3385
3500
  "src/lib/license.ts"() {
3386
3501
  "use strict";
3387
3502
  init_config();
3388
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3389
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3390
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3503
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3504
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3505
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3391
3506
  PLAN_LIMITS = {
3392
3507
  free: { devices: 1, employees: 1, memories: 5e3 },
3393
3508
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3399,12 +3514,12 @@ var init_license = __esm({
3399
3514
  });
3400
3515
 
3401
3516
  // src/lib/plan-limits.ts
3402
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
3403
- import path10 from "path";
3517
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3518
+ import path11 from "path";
3404
3519
  function getLicenseSync() {
3405
3520
  try {
3406
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
3407
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3521
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3522
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3408
3523
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3409
3524
  const parts = raw.token.split(".");
3410
3525
  if (parts.length !== 3) return freeLicense();
@@ -3442,8 +3557,8 @@ function assertEmployeeLimitSync(rosterPath) {
3442
3557
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3443
3558
  let count = 0;
3444
3559
  try {
3445
- if (existsSync8(filePath)) {
3446
- const raw = readFileSync8(filePath, "utf8");
3560
+ if (existsSync10(filePath)) {
3561
+ const raw = readFileSync9(filePath, "utf8");
3447
3562
  const employees = JSON.parse(raw);
3448
3563
  count = Array.isArray(employees) ? employees.length : 0;
3449
3564
  }
@@ -3472,29 +3587,69 @@ var init_plan_limits = __esm({
3472
3587
  this.name = "PlanLimitError";
3473
3588
  }
3474
3589
  };
3475
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3590
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
3591
+ }
3592
+ });
3593
+
3594
+ // src/lib/task-scope.ts
3595
+ var task_scope_exports = {};
3596
+ __export(task_scope_exports, {
3597
+ getCurrentSessionScope: () => getCurrentSessionScope,
3598
+ sessionScopeFilter: () => sessionScopeFilter,
3599
+ strictSessionScopeFilter: () => strictSessionScopeFilter
3600
+ });
3601
+ function getCurrentSessionScope() {
3602
+ try {
3603
+ return resolveExeSession();
3604
+ } catch {
3605
+ return null;
3606
+ }
3607
+ }
3608
+ function sessionScopeFilter(sessionScope, tableAlias) {
3609
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3610
+ if (!scope) return { sql: "", args: [] };
3611
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3612
+ return {
3613
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3614
+ args: [scope]
3615
+ };
3616
+ }
3617
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
3618
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3619
+ if (!scope) return { sql: "", args: [] };
3620
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3621
+ return {
3622
+ sql: ` AND ${col} = ?`,
3623
+ args: [scope]
3624
+ };
3625
+ }
3626
+ var init_task_scope = __esm({
3627
+ "src/lib/task-scope.ts"() {
3628
+ "use strict";
3629
+ init_tmux_routing();
3476
3630
  }
3477
3631
  });
3478
3632
 
3479
3633
  // src/lib/notifications.ts
3480
- import crypto from "crypto";
3481
- import path11 from "path";
3482
- import os8 from "os";
3634
+ import crypto2 from "crypto";
3635
+ import path12 from "path";
3636
+ import os9 from "os";
3483
3637
  import {
3484
- readFileSync as readFileSync9,
3638
+ readFileSync as readFileSync10,
3485
3639
  readdirSync,
3486
3640
  unlinkSync as unlinkSync3,
3487
- existsSync as existsSync9,
3641
+ existsSync as existsSync11,
3488
3642
  rmdirSync
3489
3643
  } from "fs";
3490
3644
  async function writeNotification(notification) {
3491
3645
  try {
3492
3646
  const client = getClient();
3493
- const id = crypto.randomUUID();
3647
+ const id = crypto2.randomUUID();
3494
3648
  const now = (/* @__PURE__ */ new Date()).toISOString();
3649
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3495
3650
  await client.execute({
3496
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3497
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3651
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3652
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3498
3653
  args: [
3499
3654
  id,
3500
3655
  notification.agentId,
@@ -3503,6 +3658,7 @@ async function writeNotification(notification) {
3503
3658
  notification.project,
3504
3659
  notification.summary,
3505
3660
  notification.taskFile ?? null,
3661
+ sessionScope,
3506
3662
  now
3507
3663
  ]
3508
3664
  });
@@ -3511,12 +3667,14 @@ async function writeNotification(notification) {
3511
3667
  `);
3512
3668
  }
3513
3669
  }
3514
- async function markAsReadByTaskFile(taskFile) {
3670
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3515
3671
  try {
3516
3672
  const client = getClient();
3673
+ const scope = strictSessionScopeFilter(sessionScope);
3517
3674
  await client.execute({
3518
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3519
- args: [taskFile]
3675
+ sql: `UPDATE notifications SET read = 1
3676
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3677
+ args: [taskFile, ...scope.args]
3520
3678
  });
3521
3679
  } catch {
3522
3680
  }
@@ -3525,11 +3683,12 @@ var init_notifications = __esm({
3525
3683
  "src/lib/notifications.ts"() {
3526
3684
  "use strict";
3527
3685
  init_database();
3686
+ init_task_scope();
3528
3687
  }
3529
3688
  });
3530
3689
 
3531
3690
  // src/lib/session-kill-telemetry.ts
3532
- import crypto2 from "crypto";
3691
+ import crypto3 from "crypto";
3533
3692
  async function recordSessionKill(input) {
3534
3693
  try {
3535
3694
  const client = getClient();
@@ -3539,7 +3698,7 @@ async function recordSessionKill(input) {
3539
3698
  ticks_idle, estimated_tokens_saved)
3540
3699
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3541
3700
  args: [
3542
- crypto2.randomUUID(),
3701
+ crypto3.randomUUID(),
3543
3702
  input.sessionName,
3544
3703
  input.agentId,
3545
3704
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3562,35 +3721,6 @@ var init_session_kill_telemetry = __esm({
3562
3721
  }
3563
3722
  });
3564
3723
 
3565
- // src/lib/task-scope.ts
3566
- var task_scope_exports = {};
3567
- __export(task_scope_exports, {
3568
- getCurrentSessionScope: () => getCurrentSessionScope,
3569
- sessionScopeFilter: () => sessionScopeFilter
3570
- });
3571
- function getCurrentSessionScope() {
3572
- try {
3573
- return resolveExeSession();
3574
- } catch {
3575
- return null;
3576
- }
3577
- }
3578
- function sessionScopeFilter(sessionScope, tableAlias) {
3579
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3580
- if (!scope) return { sql: "", args: [] };
3581
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3582
- return {
3583
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3584
- args: [scope]
3585
- };
3586
- }
3587
- var init_task_scope = __esm({
3588
- "src/lib/task-scope.ts"() {
3589
- "use strict";
3590
- init_tmux_routing();
3591
- }
3592
- });
3593
-
3594
3724
  // src/lib/state-bus.ts
3595
3725
  var StateBus, orgBus;
3596
3726
  var init_state_bus = __esm({
@@ -3647,12 +3777,12 @@ var init_state_bus = __esm({
3647
3777
  });
3648
3778
 
3649
3779
  // src/lib/tasks-crud.ts
3650
- import crypto3 from "crypto";
3651
- import path12 from "path";
3652
- import os9 from "os";
3780
+ import crypto4 from "crypto";
3781
+ import path13 from "path";
3782
+ import os10 from "os";
3653
3783
  import { execSync as execSync5 } from "child_process";
3654
3784
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3655
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3785
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3656
3786
  async function writeCheckpoint(input) {
3657
3787
  const client = getClient();
3658
3788
  const row = await resolveTask(client, input.taskId);
@@ -3768,7 +3898,7 @@ async function resolveTask(client, identifier, scopeSession) {
3768
3898
  }
3769
3899
  async function createTaskCore(input) {
3770
3900
  const client = getClient();
3771
- const id = crypto3.randomUUID();
3901
+ const id = crypto4.randomUUID();
3772
3902
  const now = (/* @__PURE__ */ new Date()).toISOString();
3773
3903
  const slug = slugify(input.title);
3774
3904
  let earlySessionScope = null;
@@ -3827,8 +3957,8 @@ ${laneWarning}` : laneWarning;
3827
3957
  }
3828
3958
  if (input.baseDir) {
3829
3959
  try {
3830
- await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3831
- await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3960
+ await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
3961
+ await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
3832
3962
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3833
3963
  await ensureGitignoreExe(input.baseDir);
3834
3964
  } catch {
@@ -3864,13 +3994,19 @@ ${laneWarning}` : laneWarning;
3864
3994
  });
3865
3995
  if (input.baseDir) {
3866
3996
  try {
3867
- const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
3868
- const mdPath = path12.join(EXE_OS_DIR, taskFile);
3869
- const mdDir = path12.dirname(mdPath);
3870
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3997
+ const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
3998
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
3999
+ const mdDir = path13.dirname(mdPath);
4000
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3871
4001
  const reviewer = input.reviewer ?? input.assignedBy;
3872
4002
  const mdContent = `# ${input.title}
3873
4003
 
4004
+ ## MANDATORY: When done
4005
+
4006
+ You MUST call update_task with status "done" and a result summary when finished.
4007
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4008
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4009
+
3874
4010
  **ID:** ${id}
3875
4011
  **Status:** ${initialStatus}
3876
4012
  **Priority:** ${input.priority}
@@ -3884,12 +4020,6 @@ ${laneWarning}` : laneWarning;
3884
4020
  ## Context
3885
4021
 
3886
4022
  ${input.context}
3887
-
3888
- ## MANDATORY: When done
3889
-
3890
- You MUST call update_task with status "done" and a result summary when finished.
3891
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3892
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3893
4023
  `;
3894
4024
  await writeFile3(mdPath, mdContent, "utf-8");
3895
4025
  } catch (err) {
@@ -4138,7 +4268,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4138
4268
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4139
4269
  } catch {
4140
4270
  }
4141
- if (input.status === "done" || input.status === "cancelled") {
4271
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4142
4272
  try {
4143
4273
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4144
4274
  clearQueueForAgent2(String(row.assigned_to));
@@ -4167,9 +4297,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4167
4297
  return { taskFile, assignedTo, assignedBy, taskSlug };
4168
4298
  }
4169
4299
  async function ensureArchitectureDoc(baseDir, projectName) {
4170
- const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
4300
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
4171
4301
  try {
4172
- if (existsSync10(archPath)) return;
4302
+ if (existsSync12(archPath)) return;
4173
4303
  const template = [
4174
4304
  `# ${projectName} \u2014 System Architecture`,
4175
4305
  "",
@@ -4202,10 +4332,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4202
4332
  }
4203
4333
  }
4204
4334
  async function ensureGitignoreExe(baseDir) {
4205
- const gitignorePath = path12.join(baseDir, ".gitignore");
4335
+ const gitignorePath = path13.join(baseDir, ".gitignore");
4206
4336
  try {
4207
- if (existsSync10(gitignorePath)) {
4208
- const content = readFileSync10(gitignorePath, "utf-8");
4337
+ if (existsSync12(gitignorePath)) {
4338
+ const content = readFileSync11(gitignorePath, "utf-8");
4209
4339
  if (/^\/?exe\/?$/m.test(content)) return;
4210
4340
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4211
4341
  } else {
@@ -4236,58 +4366,42 @@ var init_tasks_crud = __esm({
4236
4366
  });
4237
4367
 
4238
4368
  // src/lib/tasks-review.ts
4239
- import path13 from "path";
4240
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4369
+ import path14 from "path";
4370
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4241
4371
  async function countPendingReviews(sessionScope) {
4242
4372
  const client = getClient();
4243
- if (sessionScope) {
4244
- const result2 = await client.execute({
4245
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
4246
- args: [sessionScope]
4247
- });
4248
- return Number(result2.rows[0]?.cnt) || 0;
4249
- }
4373
+ const scope = strictSessionScopeFilter(
4374
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4375
+ );
4250
4376
  const result = await client.execute({
4251
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4252
- args: []
4377
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4378
+ WHERE status = 'needs_review'${scope.sql}`,
4379
+ args: [...scope.args]
4253
4380
  });
4254
4381
  return Number(result.rows[0]?.cnt) || 0;
4255
4382
  }
4256
4383
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4257
4384
  const client = getClient();
4258
- if (sessionScope) {
4259
- const result2 = await client.execute({
4260
- sql: `SELECT COUNT(*) as cnt FROM tasks
4261
- WHERE status = 'needs_review' AND updated_at > ?
4262
- AND session_scope = ?`,
4263
- args: [sinceIso, sessionScope]
4264
- });
4265
- return Number(result2.rows[0]?.cnt) || 0;
4266
- }
4385
+ const scope = strictSessionScopeFilter(
4386
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4387
+ );
4267
4388
  const result = await client.execute({
4268
4389
  sql: `SELECT COUNT(*) as cnt FROM tasks
4269
- WHERE status = 'needs_review' AND updated_at > ?`,
4270
- args: [sinceIso]
4390
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4391
+ args: [sinceIso, ...scope.args]
4271
4392
  });
4272
4393
  return Number(result.rows[0]?.cnt) || 0;
4273
4394
  }
4274
4395
  async function listPendingReviews(limit, sessionScope) {
4275
4396
  const client = getClient();
4276
- if (sessionScope) {
4277
- const result2 = await client.execute({
4278
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4279
- WHERE status = 'needs_review'
4280
- AND session_scope = ?
4281
- ORDER BY updated_at ASC LIMIT ?`,
4282
- args: [sessionScope, limit]
4283
- });
4284
- return result2.rows;
4285
- }
4397
+ const scope = strictSessionScopeFilter(
4398
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4399
+ );
4286
4400
  const result = await client.execute({
4287
4401
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4288
- WHERE status = 'needs_review'
4402
+ WHERE status = 'needs_review'${scope.sql}
4289
4403
  ORDER BY updated_at ASC LIMIT ?`,
4290
- args: [limit]
4404
+ args: [...scope.args, limit]
4291
4405
  });
4292
4406
  return result.rows;
4293
4407
  }
@@ -4299,7 +4413,7 @@ async function cleanupOrphanedReviews() {
4299
4413
  WHERE status IN ('open', 'needs_review', 'in_progress')
4300
4414
  AND assigned_by = 'system'
4301
4415
  AND title LIKE 'Review:%'
4302
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4416
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4303
4417
  args: [now]
4304
4418
  });
4305
4419
  const r1b = await client.execute({
@@ -4418,11 +4532,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4418
4532
  );
4419
4533
  }
4420
4534
  try {
4421
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4422
- if (existsSync11(cacheDir)) {
4535
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4536
+ if (existsSync13(cacheDir)) {
4423
4537
  for (const f of readdirSync2(cacheDir)) {
4424
4538
  if (f.startsWith("review-notified-")) {
4425
- unlinkSync4(path13.join(cacheDir, f));
4539
+ unlinkSync4(path14.join(cacheDir, f));
4426
4540
  }
4427
4541
  }
4428
4542
  }
@@ -4439,11 +4553,12 @@ var init_tasks_review = __esm({
4439
4553
  init_tmux_routing();
4440
4554
  init_session_key();
4441
4555
  init_state_bus();
4556
+ init_task_scope();
4442
4557
  }
4443
4558
  });
4444
4559
 
4445
4560
  // src/lib/tasks-chain.ts
4446
- import path14 from "path";
4561
+ import path15 from "path";
4447
4562
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4448
4563
  async function cascadeUnblock(taskId, baseDir, now) {
4449
4564
  const client = getClient();
@@ -4460,7 +4575,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4460
4575
  });
4461
4576
  for (const ur of unblockedRows.rows) {
4462
4577
  try {
4463
- const ubFile = path14.join(baseDir, String(ur.task_file));
4578
+ const ubFile = path15.join(baseDir, String(ur.task_file));
4464
4579
  let ubContent = await readFile3(ubFile, "utf-8");
4465
4580
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4466
4581
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4495,7 +4610,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4495
4610
  const scScope = sessionScopeFilter();
4496
4611
  const remaining = await client.execute({
4497
4612
  sql: `SELECT COUNT(*) as cnt FROM tasks
4498
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4613
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4499
4614
  args: [parentTaskId, ...scScope.args]
4500
4615
  });
4501
4616
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4529,7 +4644,7 @@ var init_tasks_chain = __esm({
4529
4644
 
4530
4645
  // src/lib/project-name.ts
4531
4646
  import { execSync as execSync6 } from "child_process";
4532
- import path15 from "path";
4647
+ import path16 from "path";
4533
4648
  function getProjectName(cwd) {
4534
4649
  const dir = cwd ?? process.cwd();
4535
4650
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4542,7 +4657,7 @@ function getProjectName(cwd) {
4542
4657
  timeout: 2e3,
4543
4658
  stdio: ["pipe", "pipe", "pipe"]
4544
4659
  }).trim();
4545
- repoRoot = path15.dirname(gitCommonDir);
4660
+ repoRoot = path16.dirname(gitCommonDir);
4546
4661
  } catch {
4547
4662
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4548
4663
  cwd: dir,
@@ -4551,11 +4666,11 @@ function getProjectName(cwd) {
4551
4666
  stdio: ["pipe", "pipe", "pipe"]
4552
4667
  }).trim();
4553
4668
  }
4554
- _cached2 = path15.basename(repoRoot);
4669
+ _cached2 = path16.basename(repoRoot);
4555
4670
  _cachedCwd = dir;
4556
4671
  return _cached2;
4557
4672
  } catch {
4558
- _cached2 = path15.basename(dir);
4673
+ _cached2 = path16.basename(dir);
4559
4674
  _cachedCwd = dir;
4560
4675
  return _cached2;
4561
4676
  }
@@ -4705,10 +4820,10 @@ __export(behaviors_exports, {
4705
4820
  listBehaviorsByDomain: () => listBehaviorsByDomain,
4706
4821
  storeBehavior: () => storeBehavior
4707
4822
  });
4708
- import crypto4 from "crypto";
4823
+ import crypto5 from "crypto";
4709
4824
  async function storeBehavior(opts) {
4710
4825
  const client = getClient();
4711
- const id = crypto4.randomUUID();
4826
+ const id = crypto5.randomUUID();
4712
4827
  const now = (/* @__PURE__ */ new Date()).toISOString();
4713
4828
  await client.execute({
4714
4829
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4792,7 +4907,7 @@ __export(skill_learning_exports, {
4792
4907
  storeTrajectory: () => storeTrajectory,
4793
4908
  sweepTrajectories: () => sweepTrajectories
4794
4909
  });
4795
- import crypto5 from "crypto";
4910
+ import crypto6 from "crypto";
4796
4911
  async function extractTrajectory(taskId, agentId) {
4797
4912
  const client = getClient();
4798
4913
  const result = await client.execute({
@@ -4821,11 +4936,11 @@ async function extractTrajectory(taskId, agentId) {
4821
4936
  return signature;
4822
4937
  }
4823
4938
  function hashSignature(signature) {
4824
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4939
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4825
4940
  }
4826
4941
  async function storeTrajectory(opts) {
4827
4942
  const client = getClient();
4828
- const id = crypto5.randomUUID();
4943
+ const id = crypto6.randomUUID();
4829
4944
  const now = (/* @__PURE__ */ new Date()).toISOString();
4830
4945
  const signatureHash = hashSignature(opts.signature);
4831
4946
  await client.execute({
@@ -5090,8 +5205,8 @@ __export(tasks_exports, {
5090
5205
  updateTaskStatus: () => updateTaskStatus,
5091
5206
  writeCheckpoint: () => writeCheckpoint
5092
5207
  });
5093
- import path16 from "path";
5094
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5208
+ import path17 from "path";
5209
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5095
5210
  async function createTask(input) {
5096
5211
  const result = await createTaskCore(input);
5097
5212
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5110,12 +5225,12 @@ async function updateTask(input) {
5110
5225
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5111
5226
  try {
5112
5227
  const agent = String(row.assigned_to);
5113
- const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5114
- const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
5228
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
5229
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
5115
5230
  if (input.status === "in_progress") {
5116
5231
  mkdirSync5(cacheDir, { recursive: true });
5117
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5118
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5232
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5233
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5119
5234
  try {
5120
5235
  unlinkSync5(cachePath);
5121
5236
  } catch {
@@ -5123,10 +5238,10 @@ async function updateTask(input) {
5123
5238
  }
5124
5239
  } catch {
5125
5240
  }
5126
- if (input.status === "done") {
5241
+ if (input.status === "done" || input.status === "closed") {
5127
5242
  await cleanupReviewFile(row, taskFile, input.baseDir);
5128
5243
  }
5129
- if (input.status === "done" || input.status === "cancelled") {
5244
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5130
5245
  try {
5131
5246
  const client = getClient();
5132
5247
  const taskTitle = String(row.title);
@@ -5142,7 +5257,7 @@ async function updateTask(input) {
5142
5257
  if (!isCoordinatorName(assignedAgent)) {
5143
5258
  try {
5144
5259
  const draftClient = getClient();
5145
- if (input.status === "done") {
5260
+ if (input.status === "done" || input.status === "closed") {
5146
5261
  await draftClient.execute({
5147
5262
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5148
5263
  args: [assignedAgent]
@@ -5159,7 +5274,7 @@ async function updateTask(input) {
5159
5274
  try {
5160
5275
  const client = getClient();
5161
5276
  const cascaded = await client.execute({
5162
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5277
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5163
5278
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5164
5279
  args: [now, taskId]
5165
5280
  });
@@ -5172,14 +5287,14 @@ async function updateTask(input) {
5172
5287
  } catch {
5173
5288
  }
5174
5289
  }
5175
- const isTerminal = input.status === "done" || input.status === "needs_review";
5290
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5176
5291
  if (isTerminal) {
5177
5292
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5178
5293
  if (!isCoordinator) {
5179
5294
  notifyTaskDone();
5180
5295
  }
5181
5296
  await markTaskNotificationsRead(taskFile);
5182
- if (input.status === "done") {
5297
+ if (input.status === "done" || input.status === "closed") {
5183
5298
  try {
5184
5299
  await cascadeUnblock(taskId, input.baseDir, now);
5185
5300
  } catch {
@@ -5199,7 +5314,7 @@ async function updateTask(input) {
5199
5314
  }
5200
5315
  }
5201
5316
  }
5202
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5317
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5203
5318
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5204
5319
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5205
5320
  taskId,
@@ -5571,6 +5686,7 @@ __export(tmux_routing_exports, {
5571
5686
  isEmployeeAlive: () => isEmployeeAlive,
5572
5687
  isExeSession: () => isExeSession,
5573
5688
  isSessionBusy: () => isSessionBusy,
5689
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5574
5690
  notifyParentExe: () => notifyParentExe,
5575
5691
  parseParentExe: () => parseParentExe,
5576
5692
  registerParentExe: () => registerParentExe,
@@ -5581,13 +5697,13 @@ __export(tmux_routing_exports, {
5581
5697
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5582
5698
  });
5583
5699
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5584
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5585
- import path17 from "path";
5586
- import os10 from "os";
5700
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5701
+ import path18 from "path";
5702
+ import os11 from "os";
5587
5703
  import { fileURLToPath as fileURLToPath2 } from "url";
5588
5704
  import { unlinkSync as unlinkSync6 } from "fs";
5589
5705
  function spawnLockPath(sessionName) {
5590
- return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5706
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5591
5707
  }
5592
5708
  function isProcessAlive(pid) {
5593
5709
  try {
@@ -5598,13 +5714,13 @@ function isProcessAlive(pid) {
5598
5714
  }
5599
5715
  }
5600
5716
  function acquireSpawnLock2(sessionName) {
5601
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5717
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5602
5718
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5603
5719
  }
5604
5720
  const lockFile = spawnLockPath(sessionName);
5605
- if (existsSync12(lockFile)) {
5721
+ if (existsSync14(lockFile)) {
5606
5722
  try {
5607
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5723
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5608
5724
  const age = Date.now() - lock.timestamp;
5609
5725
  if (isProcessAlive(lock.pid) && age < 6e4) {
5610
5726
  return false;
@@ -5612,7 +5728,7 @@ function acquireSpawnLock2(sessionName) {
5612
5728
  } catch {
5613
5729
  }
5614
5730
  }
5615
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5731
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5616
5732
  return true;
5617
5733
  }
5618
5734
  function releaseSpawnLock2(sessionName) {
@@ -5624,13 +5740,13 @@ function releaseSpawnLock2(sessionName) {
5624
5740
  function resolveBehaviorsExporterScript() {
5625
5741
  try {
5626
5742
  const thisFile = fileURLToPath2(import.meta.url);
5627
- const scriptPath = path17.join(
5628
- path17.dirname(thisFile),
5743
+ const scriptPath = path18.join(
5744
+ path18.dirname(thisFile),
5629
5745
  "..",
5630
5746
  "bin",
5631
5747
  "exe-export-behaviors.js"
5632
5748
  );
5633
- return existsSync12(scriptPath) ? scriptPath : null;
5749
+ return existsSync14(scriptPath) ? scriptPath : null;
5634
5750
  } catch {
5635
5751
  return null;
5636
5752
  }
@@ -5696,12 +5812,12 @@ function extractRootExe(name) {
5696
5812
  return parts.length > 0 ? parts[parts.length - 1] : null;
5697
5813
  }
5698
5814
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5699
- if (!existsSync12(SESSION_CACHE)) {
5815
+ if (!existsSync14(SESSION_CACHE)) {
5700
5816
  mkdirSync6(SESSION_CACHE, { recursive: true });
5701
5817
  }
5702
5818
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5703
- const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5704
- writeFileSync7(filePath, JSON.stringify({
5819
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5820
+ writeFileSync8(filePath, JSON.stringify({
5705
5821
  parentExe: rootExe,
5706
5822
  dispatchedBy: dispatchedBy || rootExe,
5707
5823
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5709,7 +5825,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5709
5825
  }
5710
5826
  function getParentExe(sessionKey) {
5711
5827
  try {
5712
- const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5828
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5713
5829
  return data.parentExe || null;
5714
5830
  } catch {
5715
5831
  return null;
@@ -5717,8 +5833,8 @@ function getParentExe(sessionKey) {
5717
5833
  }
5718
5834
  function getDispatchedBy(sessionKey) {
5719
5835
  try {
5720
- const data = JSON.parse(readFileSync11(
5721
- path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5836
+ const data = JSON.parse(readFileSync12(
5837
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5722
5838
  "utf8"
5723
5839
  ));
5724
5840
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5788,8 +5904,8 @@ async function verifyPaneAtCapacity(sessionName) {
5788
5904
  }
5789
5905
  function readDebounceState() {
5790
5906
  try {
5791
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5792
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5907
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5908
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5793
5909
  const state = {};
5794
5910
  for (const [key, val] of Object.entries(raw)) {
5795
5911
  if (typeof val === "number") {
@@ -5805,8 +5921,8 @@ function readDebounceState() {
5805
5921
  }
5806
5922
  function writeDebounceState(state) {
5807
5923
  try {
5808
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5809
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5924
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5925
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5810
5926
  } catch {
5811
5927
  }
5812
5928
  }
@@ -5904,8 +6020,8 @@ function sendIntercom(targetSession) {
5904
6020
  try {
5905
6021
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5906
6022
  const agent = baseAgentName(rawAgent);
5907
- const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5908
- if (existsSync12(markerPath)) {
6023
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
6024
+ if (existsSync14(markerPath)) {
5909
6025
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5910
6026
  return "debounced";
5911
6027
  }
@@ -5914,8 +6030,8 @@ function sendIntercom(targetSession) {
5914
6030
  try {
5915
6031
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5916
6032
  const agent = baseAgentName(rawAgent);
5917
- const taskDir = path17.join(process.cwd(), "exe", agent);
5918
- if (existsSync12(taskDir)) {
6033
+ const taskDir = path18.join(process.cwd(), "exe", agent);
6034
+ if (existsSync14(taskDir)) {
5919
6035
  const files = readdirSync3(taskDir).filter(
5920
6036
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5921
6037
  );
@@ -5975,6 +6091,21 @@ function notifyParentExe(sessionKey) {
5975
6091
  }
5976
6092
  return true;
5977
6093
  }
6094
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6095
+ const transport = getTransport();
6096
+ try {
6097
+ const sessions = transport.listSessions();
6098
+ if (!sessions.includes(coordinatorSession)) return false;
6099
+ execSync7(
6100
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6101
+ { timeout: 3e3 }
6102
+ );
6103
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6104
+ return true;
6105
+ } catch {
6106
+ return false;
6107
+ }
6108
+ }
5978
6109
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5979
6110
  if (isCoordinatorName(employeeName)) {
5980
6111
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6048,26 +6179,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6048
6179
  const transport = getTransport();
6049
6180
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6050
6181
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6051
- const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
6052
- const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6053
- if (!existsSync12(logDir)) {
6182
+ const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
6183
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6184
+ if (!existsSync14(logDir)) {
6054
6185
  mkdirSync6(logDir, { recursive: true });
6055
6186
  }
6056
6187
  transport.kill(sessionName);
6057
6188
  let cleanupSuffix = "";
6058
6189
  try {
6059
6190
  const thisFile = fileURLToPath2(import.meta.url);
6060
- const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6061
- if (existsSync12(cleanupScript)) {
6191
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6192
+ if (existsSync14(cleanupScript)) {
6062
6193
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6063
6194
  }
6064
6195
  } catch {
6065
6196
  }
6066
6197
  try {
6067
- const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
6198
+ const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
6068
6199
  let claudeJson = {};
6069
6200
  try {
6070
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
6201
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6071
6202
  } catch {
6072
6203
  }
6073
6204
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6075,17 +6206,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6075
6206
  const trustDir = opts?.cwd ?? projectDir;
6076
6207
  if (!projects[trustDir]) projects[trustDir] = {};
6077
6208
  projects[trustDir].hasTrustDialogAccepted = true;
6078
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6209
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6079
6210
  } catch {
6080
6211
  }
6081
6212
  try {
6082
- const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
6213
+ const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
6083
6214
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6084
- const projSettingsDir = path17.join(settingsDir, normalizedKey);
6085
- const settingsPath = path17.join(projSettingsDir, "settings.json");
6215
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
6216
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
6086
6217
  let settings = {};
6087
6218
  try {
6088
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6219
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6089
6220
  } catch {
6090
6221
  }
6091
6222
  const perms = settings.permissions ?? {};
@@ -6114,7 +6245,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6114
6245
  perms.allow = allow;
6115
6246
  settings.permissions = perms;
6116
6247
  mkdirSync6(projSettingsDir, { recursive: true });
6117
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6248
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6118
6249
  }
6119
6250
  } catch {
6120
6251
  }
@@ -6129,8 +6260,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6129
6260
  let behaviorsFlag = "";
6130
6261
  let legacyFallbackWarned = false;
6131
6262
  if (!useExeAgent && !useBinSymlink) {
6132
- const identityPath = path17.join(
6133
- os10.homedir(),
6263
+ const identityPath = path18.join(
6264
+ os11.homedir(),
6134
6265
  ".exe-os",
6135
6266
  "identity",
6136
6267
  `${employeeName}.md`
@@ -6139,13 +6270,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6139
6270
  const hasAgentFlag = claudeSupportsAgentFlag();
6140
6271
  if (hasAgentFlag) {
6141
6272
  identityFlag = ` --agent ${employeeName}`;
6142
- } else if (existsSync12(identityPath)) {
6273
+ } else if (existsSync14(identityPath)) {
6143
6274
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6144
6275
  legacyFallbackWarned = true;
6145
6276
  }
6146
6277
  const behaviorsFile = exportBehaviorsSync(
6147
6278
  employeeName,
6148
- path17.basename(spawnCwd),
6279
+ path18.basename(spawnCwd),
6149
6280
  sessionName
6150
6281
  );
6151
6282
  if (behaviorsFile) {
@@ -6160,16 +6291,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6160
6291
  }
6161
6292
  let sessionContextFlag = "";
6162
6293
  try {
6163
- const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
6294
+ const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
6164
6295
  mkdirSync6(ctxDir, { recursive: true });
6165
- const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6296
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
6166
6297
  const ctxContent = [
6167
6298
  `## Session Context`,
6168
6299
  `You are running in tmux session: ${sessionName}.`,
6169
6300
  `Your parent coordinator session is ${exeSession}.`,
6170
6301
  `Your employees (if any) use the -${exeSession} suffix.`
6171
6302
  ].join("\n");
6172
- writeFileSync7(ctxFile, ctxContent);
6303
+ writeFileSync8(ctxFile, ctxContent);
6173
6304
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6174
6305
  } catch {
6175
6306
  }
@@ -6246,8 +6377,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6246
6377
  transport.pipeLog(sessionName, logFile);
6247
6378
  try {
6248
6379
  const mySession = getMySession();
6249
- const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6250
- writeFileSync7(dispatchInfo, JSON.stringify({
6380
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6381
+ writeFileSync8(dispatchInfo, JSON.stringify({
6251
6382
  dispatchedBy: mySession,
6252
6383
  rootExe: exeSession,
6253
6384
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6321,15 +6452,15 @@ var init_tmux_routing = __esm({
6321
6452
  init_intercom_queue();
6322
6453
  init_plan_limits();
6323
6454
  init_employees();
6324
- SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
6325
- SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
6455
+ SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
6456
+ SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
6326
6457
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6327
6458
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6328
6459
  VERIFY_PANE_LINES = 200;
6329
6460
  INTERCOM_DEBOUNCE_MS = 3e4;
6330
6461
  CODEX_DEBOUNCE_MS = 12e4;
6331
- INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
6332
- DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
6462
+ INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
6463
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
6333
6464
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6334
6465
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6335
6466
  }
@@ -6346,14 +6477,14 @@ var init_memory = __esm({
6346
6477
 
6347
6478
  // src/lib/keychain.ts
6348
6479
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
6349
- import { existsSync as existsSync13 } from "fs";
6350
- import path18 from "path";
6351
- import os11 from "os";
6480
+ import { existsSync as existsSync15 } from "fs";
6481
+ import path19 from "path";
6482
+ import os12 from "os";
6352
6483
  function getKeyDir() {
6353
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
6484
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
6354
6485
  }
6355
6486
  function getKeyPath() {
6356
- return path18.join(getKeyDir(), "master.key");
6487
+ return path19.join(getKeyDir(), "master.key");
6357
6488
  }
6358
6489
  async function tryKeytar() {
6359
6490
  try {
@@ -6374,9 +6505,9 @@ async function getMasterKey() {
6374
6505
  }
6375
6506
  }
6376
6507
  const keyPath = getKeyPath();
6377
- if (!existsSync13(keyPath)) {
6508
+ if (!existsSync15(keyPath)) {
6378
6509
  process.stderr.write(
6379
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6510
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6380
6511
  `
6381
6512
  );
6382
6513
  return null;
@@ -6406,6 +6537,7 @@ var shard_manager_exports = {};
6406
6537
  __export(shard_manager_exports, {
6407
6538
  disposeShards: () => disposeShards,
6408
6539
  ensureShardSchema: () => ensureShardSchema,
6540
+ getOpenShardCount: () => getOpenShardCount,
6409
6541
  getReadyShardClient: () => getReadyShardClient,
6410
6542
  getShardClient: () => getShardClient,
6411
6543
  getShardsDir: () => getShardsDir,
@@ -6414,15 +6546,18 @@ __export(shard_manager_exports, {
6414
6546
  listShards: () => listShards,
6415
6547
  shardExists: () => shardExists
6416
6548
  });
6417
- import path19 from "path";
6418
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6549
+ import path20 from "path";
6550
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6419
6551
  import { createClient as createClient2 } from "@libsql/client";
6420
6552
  function initShardManager(encryptionKey) {
6421
6553
  _encryptionKey = encryptionKey;
6422
- if (!existsSync14(SHARDS_DIR)) {
6554
+ if (!existsSync16(SHARDS_DIR)) {
6423
6555
  mkdirSync7(SHARDS_DIR, { recursive: true });
6424
6556
  }
6425
6557
  _shardingEnabled = true;
6558
+ if (_evictionTimer) clearInterval(_evictionTimer);
6559
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6560
+ _evictionTimer.unref();
6426
6561
  }
6427
6562
  function isShardingEnabled() {
6428
6563
  return _shardingEnabled;
@@ -6439,21 +6574,28 @@ function getShardClient(projectName) {
6439
6574
  throw new Error(`Invalid project name for shard: "${projectName}"`);
6440
6575
  }
6441
6576
  const cached = _shards.get(safeName);
6442
- if (cached) return cached;
6443
- const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
6577
+ if (cached) {
6578
+ _shardLastAccess.set(safeName, Date.now());
6579
+ return cached;
6580
+ }
6581
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6582
+ evictLRU();
6583
+ }
6584
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
6444
6585
  const client = createClient2({
6445
6586
  url: `file:${dbPath}`,
6446
6587
  encryptionKey: _encryptionKey
6447
6588
  });
6448
6589
  _shards.set(safeName, client);
6590
+ _shardLastAccess.set(safeName, Date.now());
6449
6591
  return client;
6450
6592
  }
6451
6593
  function shardExists(projectName) {
6452
6594
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
6453
- return existsSync14(path19.join(SHARDS_DIR, `${safeName}.db`));
6595
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
6454
6596
  }
6455
6597
  function listShards() {
6456
- if (!existsSync14(SHARDS_DIR)) return [];
6598
+ if (!existsSync16(SHARDS_DIR)) return [];
6457
6599
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6458
6600
  }
6459
6601
  async function ensureShardSchema(client) {
@@ -6505,6 +6647,8 @@ async function ensureShardSchema(client) {
6505
6647
  for (const col of [
6506
6648
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
6507
6649
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6650
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6651
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
6508
6652
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
6509
6653
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
6510
6654
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6642,21 +6786,69 @@ async function getReadyShardClient(projectName) {
6642
6786
  await ensureShardSchema(client);
6643
6787
  return client;
6644
6788
  }
6789
+ function evictLRU() {
6790
+ let oldest = null;
6791
+ let oldestTime = Infinity;
6792
+ for (const [name, time] of _shardLastAccess) {
6793
+ if (time < oldestTime) {
6794
+ oldestTime = time;
6795
+ oldest = name;
6796
+ }
6797
+ }
6798
+ if (oldest) {
6799
+ const client = _shards.get(oldest);
6800
+ if (client) {
6801
+ client.close();
6802
+ }
6803
+ _shards.delete(oldest);
6804
+ _shardLastAccess.delete(oldest);
6805
+ }
6806
+ }
6807
+ function evictIdleShards() {
6808
+ const now = Date.now();
6809
+ const toEvict = [];
6810
+ for (const [name, lastAccess] of _shardLastAccess) {
6811
+ if (now - lastAccess > SHARD_IDLE_MS) {
6812
+ toEvict.push(name);
6813
+ }
6814
+ }
6815
+ for (const name of toEvict) {
6816
+ const client = _shards.get(name);
6817
+ if (client) {
6818
+ client.close();
6819
+ }
6820
+ _shards.delete(name);
6821
+ _shardLastAccess.delete(name);
6822
+ }
6823
+ }
6824
+ function getOpenShardCount() {
6825
+ return _shards.size;
6826
+ }
6645
6827
  function disposeShards() {
6828
+ if (_evictionTimer) {
6829
+ clearInterval(_evictionTimer);
6830
+ _evictionTimer = null;
6831
+ }
6646
6832
  for (const [, client] of _shards) {
6647
6833
  client.close();
6648
6834
  }
6649
6835
  _shards.clear();
6836
+ _shardLastAccess.clear();
6650
6837
  _shardingEnabled = false;
6651
6838
  _encryptionKey = null;
6652
6839
  }
6653
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6840
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
6654
6841
  var init_shard_manager = __esm({
6655
6842
  "src/lib/shard-manager.ts"() {
6656
6843
  "use strict";
6657
6844
  init_config();
6658
- SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6845
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6846
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6847
+ MAX_OPEN_SHARDS = 10;
6848
+ EVICTION_INTERVAL_MS = 60 * 1e3;
6659
6849
  _shards = /* @__PURE__ */ new Map();
6850
+ _shardLastAccess = /* @__PURE__ */ new Map();
6851
+ _evictionTimer = null;
6660
6852
  _encryptionKey = null;
6661
6853
  _shardingEnabled = false;
6662
6854
  }
@@ -7452,8 +7644,8 @@ function findContainingChunk(filePath, snippet) {
7452
7644
  try {
7453
7645
  const ext = filePath.split(".").pop()?.toLowerCase();
7454
7646
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
7455
- const { readFileSync: readFileSync14 } = __require("fs");
7456
- const source = readFileSync14(filePath, "utf8");
7647
+ const { readFileSync: readFileSync15 } = __require("fs");
7648
+ const source = readFileSync15(filePath, "utf8");
7457
7649
  const lines = source.split("\n");
7458
7650
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
7459
7651
  let matchLine = -1;
@@ -7519,9 +7711,9 @@ function extractBash(input, response) {
7519
7711
  }
7520
7712
  function extractGrep(input, response) {
7521
7713
  const pattern = String(input.pattern ?? "");
7522
- const path22 = input.path ? String(input.path) : "";
7714
+ const path23 = input.path ? String(input.path) : "";
7523
7715
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
7524
- return `Searched for "${pattern}"${path22 ? ` in ${path22}` : ""}
7716
+ return `Searched for "${pattern}"${path23 ? ` in ${path23}` : ""}
7525
7717
  ${output.slice(0, MAX_OUTPUT)}`;
7526
7718
  }
7527
7719
  function extractGlob(input, response) {
@@ -7624,7 +7816,7 @@ __export(error_detector_exports, {
7624
7816
  errorFingerprint: () => errorFingerprint,
7625
7817
  isExeOsError: () => isExeOsError
7626
7818
  });
7627
- import crypto6 from "crypto";
7819
+ import crypto7 from "crypto";
7628
7820
  function isRealStderr(stderr) {
7629
7821
  const lines = stderr.trim().split("\n");
7630
7822
  const meaningful = lines.filter(
@@ -7695,7 +7887,7 @@ function classifyError(errorText) {
7695
7887
  }
7696
7888
  function errorFingerprint(toolName, errorText) {
7697
7889
  const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
7698
- return crypto6.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7890
+ return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7699
7891
  }
7700
7892
  var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
7701
7893
  var init_error_detector = __esm({
@@ -8059,10 +8251,10 @@ async function disposeEmbedder() {
8059
8251
  async function embedDirect(text) {
8060
8252
  const llamaCpp = await import("node-llama-cpp");
8061
8253
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
8062
- const { existsSync: existsSync16 } = await import("fs");
8063
- const path22 = await import("path");
8064
- const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8065
- if (!existsSync16(modelPath)) {
8254
+ const { existsSync: existsSync18 } = await import("fs");
8255
+ const path23 = await import("path");
8256
+ const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8257
+ if (!existsSync18(modelPath)) {
8066
8258
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
8067
8259
  }
8068
8260
  const llama = await llamaCpp.getLlama();
@@ -8099,8 +8291,8 @@ __export(wiki_client_exports, {
8099
8291
  listDocuments: () => listDocuments,
8100
8292
  listWorkspaces: () => listWorkspaces
8101
8293
  });
8102
- async function wikiFetch(config2, path22, method = "GET", body) {
8103
- const url = `${config2.baseUrl}/api/v1${path22}`;
8294
+ async function wikiFetch(config2, path23, method = "GET", body) {
8295
+ const url = `${config2.baseUrl}/api/v1${path23}`;
8104
8296
  const headers = {
8105
8297
  Authorization: `Bearer ${config2.apiKey}`,
8106
8298
  "Content-Type": "application/json"
@@ -8133,7 +8325,7 @@ async function wikiFetch(config2, path22, method = "GET", body) {
8133
8325
  }
8134
8326
  }
8135
8327
  if (!response.ok) {
8136
- throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
8328
+ throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
8137
8329
  }
8138
8330
  return response.json();
8139
8331
  } finally {
@@ -8426,13 +8618,13 @@ __export(graph_rag_exports, {
8426
8618
  resolveAlias: () => resolveAlias,
8427
8619
  storeExtraction: () => storeExtraction
8428
8620
  });
8429
- import crypto7 from "crypto";
8621
+ import crypto8 from "crypto";
8430
8622
  function normalizeEntityName(name) {
8431
8623
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
8432
8624
  }
8433
8625
  function entityId(name, type) {
8434
8626
  const normalized = normalizeEntityName(name);
8435
- return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
8627
+ return crypto8.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
8436
8628
  }
8437
8629
  async function resolveAlias(client, name) {
8438
8630
  const normalized = normalizeEntityName(name);
@@ -8682,7 +8874,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
8682
8874
  const targetAlias = await resolveAlias(client, r.target);
8683
8875
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
8684
8876
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
8685
- const relId = crypto7.randomUUID().slice(0, 16);
8877
+ const relId = crypto8.randomUUID().slice(0, 16);
8686
8878
  try {
8687
8879
  await client.execute({
8688
8880
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -8745,7 +8937,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
8745
8937
  }
8746
8938
  }
8747
8939
  for (const h of extraction.hyperedges) {
8748
- const hId = crypto7.randomUUID().slice(0, 16);
8940
+ const hId = crypto8.randomUUID().slice(0, 16);
8749
8941
  try {
8750
8942
  await client.execute({
8751
8943
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -8809,7 +9001,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
8809
9001
  totalEntities += stored.entitiesStored;
8810
9002
  totalRelationships += stored.relationshipsStored;
8811
9003
  }
8812
- const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
9004
+ const contentHash = crypto8.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
8813
9005
  await client.execute({
8814
9006
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
8815
9007
  args: [contentHash, contentHash, memoryId]
@@ -9154,13 +9346,13 @@ __export(whatsapp_accounts_exports, {
9154
9346
  getDefaultAccount: () => getDefaultAccount,
9155
9347
  loadAccounts: () => loadAccounts
9156
9348
  });
9157
- import { readFileSync as readFileSync12 } from "fs";
9349
+ import { readFileSync as readFileSync13 } from "fs";
9158
9350
  import { join as join2 } from "path";
9159
9351
  import { homedir as homedir2 } from "os";
9160
9352
  function loadAccounts() {
9161
9353
  if (cachedAccounts !== null) return cachedAccounts;
9162
9354
  try {
9163
- const raw = readFileSync12(CONFIG_PATH2, "utf8");
9355
+ const raw = readFileSync13(CONFIG_PATH2, "utf8");
9164
9356
  const parsed = JSON.parse(raw);
9165
9357
  if (!Array.isArray(parsed)) {
9166
9358
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -9216,10 +9408,10 @@ __export(messaging_exports, {
9216
9408
  sendMessage: () => sendMessage,
9217
9409
  setWsClientSend: () => setWsClientSend
9218
9410
  });
9219
- import crypto9 from "crypto";
9411
+ import crypto10 from "crypto";
9220
9412
  function generateUlid() {
9221
9413
  const timestamp = Date.now().toString(36).padStart(10, "0");
9222
- const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
9414
+ const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
9223
9415
  return (timestamp + random).toUpperCase();
9224
9416
  }
9225
9417
  function rowToMessage(row) {
@@ -9230,6 +9422,7 @@ function rowToMessage(row) {
9230
9422
  targetAgent: row.target_agent,
9231
9423
  targetProject: row.target_project ?? null,
9232
9424
  targetDevice: row.target_device,
9425
+ sessionScope: row.session_scope ?? null,
9233
9426
  content: row.content,
9234
9427
  priority: row.priority ?? "normal",
9235
9428
  status: row.status ?? "pending",
@@ -9247,15 +9440,17 @@ async function sendMessage(input) {
9247
9440
  const id = generateUlid();
9248
9441
  const now = (/* @__PURE__ */ new Date()).toISOString();
9249
9442
  const targetDevice = input.targetDevice ?? "local";
9443
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
9250
9444
  await client.execute({
9251
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
9252
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
9445
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
9446
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
9253
9447
  args: [
9254
9448
  id,
9255
9449
  input.fromAgent,
9256
9450
  input.targetAgent,
9257
9451
  input.targetProject ?? null,
9258
9452
  targetDevice,
9453
+ sessionScope,
9259
9454
  input.content,
9260
9455
  input.priority ?? "normal",
9261
9456
  now
@@ -9269,9 +9464,10 @@ async function sendMessage(input) {
9269
9464
  }
9270
9465
  } catch {
9271
9466
  }
9467
+ const sentScope = strictSessionScopeFilter(sessionScope);
9272
9468
  const result = await client.execute({
9273
- sql: "SELECT * FROM messages WHERE id = ?",
9274
- args: [id]
9469
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
9470
+ args: [id, ...sentScope.args]
9275
9471
  });
9276
9472
  return rowToMessage(result.rows[0]);
9277
9473
  }
@@ -9295,6 +9491,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
9295
9491
  fromAgent: msg.fromAgent,
9296
9492
  targetAgent: msg.targetAgent,
9297
9493
  targetProject: msg.targetProject,
9494
+ sessionScope: msg.sessionScope,
9298
9495
  content: msg.content,
9299
9496
  priority: msg.priority,
9300
9497
  createdAt: msg.createdAt
@@ -9338,7 +9535,7 @@ async function deliverLocalMessage(messageId) {
9338
9535
  } catch {
9339
9536
  const newRetryCount = msg.retryCount + 1;
9340
9537
  if (newRetryCount >= MAX_RETRIES3) {
9341
- await markFailed(messageId, "session unavailable after 10 retries");
9538
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
9342
9539
  } else {
9343
9540
  await client.execute({
9344
9541
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -9348,85 +9545,101 @@ async function deliverLocalMessage(messageId) {
9348
9545
  return false;
9349
9546
  }
9350
9547
  }
9351
- async function getPendingMessages(targetAgent) {
9548
+ async function getPendingMessages(targetAgent, sessionScope) {
9352
9549
  const client = getClient();
9550
+ const scope = strictSessionScopeFilter(sessionScope);
9353
9551
  const result = await client.execute({
9354
9552
  sql: `SELECT * FROM messages
9355
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
9553
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
9356
9554
  ORDER BY id`,
9357
- args: [targetAgent]
9555
+ args: [targetAgent, ...scope.args]
9358
9556
  });
9359
9557
  return result.rows.map((row) => rowToMessage(row));
9360
9558
  }
9361
- async function markRead(messageId) {
9559
+ async function markRead(messageId, sessionScope) {
9362
9560
  const client = getClient();
9561
+ const scope = strictSessionScopeFilter(sessionScope);
9363
9562
  await client.execute({
9364
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
9365
- args: [messageId]
9563
+ sql: `UPDATE messages SET status = 'read'
9564
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
9565
+ args: [messageId, ...scope.args]
9366
9566
  });
9367
9567
  }
9368
- async function markAcknowledged(messageId) {
9568
+ async function markAcknowledged(messageId, sessionScope) {
9369
9569
  const client = getClient();
9570
+ const scope = strictSessionScopeFilter(sessionScope);
9370
9571
  await client.execute({
9371
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
9372
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
9572
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
9573
+ WHERE id = ? AND status = 'read'${scope.sql}`,
9574
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9373
9575
  });
9374
9576
  }
9375
- async function markProcessed(messageId) {
9577
+ async function markProcessed(messageId, sessionScope) {
9376
9578
  const client = getClient();
9579
+ const scope = strictSessionScopeFilter(sessionScope);
9377
9580
  await client.execute({
9378
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
9379
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
9581
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
9582
+ WHERE id = ?${scope.sql}`,
9583
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9380
9584
  });
9381
9585
  }
9382
- async function getMessageStatus(messageId) {
9586
+ async function getMessageStatus(messageId, sessionScope) {
9383
9587
  const client = getClient();
9588
+ const scope = strictSessionScopeFilter(sessionScope);
9384
9589
  const result = await client.execute({
9385
- sql: "SELECT status FROM messages WHERE id = ?",
9386
- args: [messageId]
9590
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
9591
+ args: [messageId, ...scope.args]
9387
9592
  });
9388
9593
  return result.rows[0]?.status ?? null;
9389
9594
  }
9390
- async function getUnacknowledgedMessages(targetAgent) {
9595
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
9391
9596
  const client = getClient();
9597
+ const scope = strictSessionScopeFilter(sessionScope);
9392
9598
  const result = await client.execute({
9393
9599
  sql: `SELECT * FROM messages
9394
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
9600
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
9395
9601
  ORDER BY id`,
9396
- args: [targetAgent]
9602
+ args: [targetAgent, ...scope.args]
9397
9603
  });
9398
9604
  return result.rows.map((row) => rowToMessage(row));
9399
9605
  }
9400
- async function getReadMessages(targetAgent) {
9606
+ async function getReadMessages(targetAgent, sessionScope) {
9401
9607
  const client = getClient();
9608
+ const scope = strictSessionScopeFilter(sessionScope);
9402
9609
  const result = await client.execute({
9403
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
9404
- args: [targetAgent]
9610
+ sql: `SELECT * FROM messages
9611
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
9612
+ ORDER BY id`,
9613
+ args: [targetAgent, ...scope.args]
9405
9614
  });
9406
9615
  return result.rows.map((row) => rowToMessage(row));
9407
9616
  }
9408
- async function markFailed(messageId, reason) {
9617
+ async function markFailed(messageId, reason, sessionScope) {
9409
9618
  const client = getClient();
9619
+ const scope = strictSessionScopeFilter(sessionScope);
9410
9620
  await client.execute({
9411
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
9412
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
9621
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
9622
+ WHERE id = ?${scope.sql}`,
9623
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
9413
9624
  });
9414
9625
  }
9415
- async function getFailedMessages() {
9626
+ async function getFailedMessages(sessionScope) {
9416
9627
  const client = getClient();
9628
+ const scope = strictSessionScopeFilter(sessionScope);
9417
9629
  const result = await client.execute({
9418
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
9419
- args: []
9630
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
9631
+ args: [...scope.args]
9420
9632
  });
9421
9633
  return result.rows.map((row) => rowToMessage(row));
9422
9634
  }
9423
- async function retryPendingMessages() {
9635
+ async function retryPendingMessages(sessionScope) {
9424
9636
  const client = getClient();
9637
+ const scope = strictSessionScopeFilter(sessionScope);
9425
9638
  const result = await client.execute({
9426
9639
  sql: `SELECT * FROM messages
9427
- WHERE status = 'pending' AND retry_count < ?
9640
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
9428
9641
  ORDER BY id`,
9429
- args: [MAX_RETRIES3]
9642
+ args: [MAX_RETRIES3, ...scope.args]
9430
9643
  });
9431
9644
  let delivered = 0;
9432
9645
  for (const row of result.rows) {
@@ -9445,6 +9658,7 @@ var init_messaging = __esm({
9445
9658
  "use strict";
9446
9659
  init_database();
9447
9660
  init_tmux_routing();
9661
+ init_task_scope();
9448
9662
  MAX_RETRIES3 = 10;
9449
9663
  _wsClientSend = null;
9450
9664
  }
@@ -11925,11 +12139,11 @@ init_crm_bridge();
11925
12139
 
11926
12140
  // src/lib/pipeline-router.ts
11927
12141
  init_database();
11928
- import crypto8 from "crypto";
12142
+ import crypto9 from "crypto";
11929
12143
  async function sinkConversationStore(msg, agentResponse, agentName) {
11930
12144
  try {
11931
12145
  const client = getClient();
11932
- const id = crypto8.randomUUID();
12146
+ const id = crypto9.randomUUID();
11933
12147
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
11934
12148
  await client.execute({
11935
12149
  sql: `INSERT INTO conversations
@@ -11979,7 +12193,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
11979
12193
  ].filter(Boolean).join("\n");
11980
12194
  const vector = await embed2(rawText);
11981
12195
  await writeMemory2({
11982
- id: crypto8.randomUUID(),
12196
+ id: crypto9.randomUUID(),
11983
12197
  agent_id: agentName ?? "gateway",
11984
12198
  agent_role: "gateway",
11985
12199
  session_id: `gateway-${msg.platform}`,
@@ -14660,12 +14874,12 @@ var SlackAdapter = class {
14660
14874
  // src/gateway/adapters/imessage.ts
14661
14875
  import { execFile } from "child_process";
14662
14876
  import { promisify } from "util";
14663
- import os12 from "os";
14664
- import path20 from "path";
14877
+ import os13 from "os";
14878
+ import path21 from "path";
14665
14879
  var execFileAsync = promisify(execFile);
14666
14880
  var POLL_INTERVAL_MS = 5e3;
14667
- var MESSAGES_DB_PATH = path20.join(
14668
- process.env.HOME ?? os12.homedir(),
14881
+ var MESSAGES_DB_PATH = path21.join(
14882
+ process.env.HOME ?? os13.homedir(),
14669
14883
  "Library/Messages/chat.db"
14670
14884
  );
14671
14885
  var IMessageAdapter = class {
@@ -15508,11 +15722,11 @@ async function ensureCRMContact(info) {
15508
15722
  }
15509
15723
 
15510
15724
  // src/automation/trigger-engine.ts
15511
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
15725
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
15512
15726
  import { randomUUID as randomUUID15 } from "crypto";
15513
- import path21 from "path";
15514
- import os13 from "os";
15515
- var TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
15727
+ import path22 from "path";
15728
+ import os14 from "os";
15729
+ var TRIGGERS_PATH = path22.join(os14.homedir(), ".exe-os", "triggers.json");
15516
15730
  var GRAPH_API_VERSION = "v21.0";
15517
15731
  function substituteTemplate(template, record) {
15518
15732
  return template.replace(
@@ -15566,9 +15780,9 @@ function evaluateConditions(conditions, record) {
15566
15780
  return conditions.every((c) => evaluateCondition(c, record));
15567
15781
  }
15568
15782
  function loadTriggers(project) {
15569
- if (!existsSync15(TRIGGERS_PATH)) return [];
15783
+ if (!existsSync17(TRIGGERS_PATH)) return [];
15570
15784
  try {
15571
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
15785
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
15572
15786
  const all = JSON.parse(raw);
15573
15787
  if (!Array.isArray(all)) return [];
15574
15788
  if (project) {