@askexenow/exe-os 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1411 -953
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +913 -543
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +418 -262
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +793 -485
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +566 -357
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +530 -319
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +547 -336
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3442 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +534 -323
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +664 -431
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +1049 -680
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +448 -371
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
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({
@@ -3646,13 +3776,117 @@ var init_state_bus = __esm({
3646
3776
  }
3647
3777
  });
3648
3778
 
3649
- // src/lib/tasks-crud.ts
3650
- import crypto3 from "crypto";
3651
- import path12 from "path";
3652
- import os9 from "os";
3779
+ // src/lib/project-name.ts
3653
3780
  import { execSync as execSync5 } from "child_process";
3781
+ import path13 from "path";
3782
+ function getProjectName(cwd) {
3783
+ const dir = cwd ?? process.cwd();
3784
+ if (_cached2 && _cachedCwd === dir) return _cached2;
3785
+ try {
3786
+ let repoRoot;
3787
+ try {
3788
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
3789
+ cwd: dir,
3790
+ encoding: "utf8",
3791
+ timeout: 2e3,
3792
+ stdio: ["pipe", "pipe", "pipe"]
3793
+ }).trim();
3794
+ repoRoot = path13.dirname(gitCommonDir);
3795
+ } catch {
3796
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
3797
+ cwd: dir,
3798
+ encoding: "utf8",
3799
+ timeout: 2e3,
3800
+ stdio: ["pipe", "pipe", "pipe"]
3801
+ }).trim();
3802
+ }
3803
+ _cached2 = path13.basename(repoRoot);
3804
+ _cachedCwd = dir;
3805
+ return _cached2;
3806
+ } catch {
3807
+ _cached2 = path13.basename(dir);
3808
+ _cachedCwd = dir;
3809
+ return _cached2;
3810
+ }
3811
+ }
3812
+ var _cached2, _cachedCwd;
3813
+ var init_project_name = __esm({
3814
+ "src/lib/project-name.ts"() {
3815
+ "use strict";
3816
+ _cached2 = null;
3817
+ _cachedCwd = null;
3818
+ }
3819
+ });
3820
+
3821
+ // src/lib/session-scope.ts
3822
+ var session_scope_exports = {};
3823
+ __export(session_scope_exports, {
3824
+ assertSessionScope: () => assertSessionScope,
3825
+ findSessionForProject: () => findSessionForProject,
3826
+ getSessionProject: () => getSessionProject
3827
+ });
3828
+ function getSessionProject(sessionName) {
3829
+ const sessions = listSessions();
3830
+ const entry = sessions.find((s) => s.windowName === sessionName);
3831
+ if (!entry) return null;
3832
+ const parts = entry.projectDir.split("/").filter(Boolean);
3833
+ return parts[parts.length - 1] ?? null;
3834
+ }
3835
+ function findSessionForProject(projectName) {
3836
+ const sessions = listSessions();
3837
+ for (const s of sessions) {
3838
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
3839
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
3840
+ }
3841
+ return null;
3842
+ }
3843
+ function assertSessionScope(actionType, targetProject) {
3844
+ try {
3845
+ const currentProject = getProjectName();
3846
+ const exeSession = resolveExeSession();
3847
+ if (!exeSession) {
3848
+ return { allowed: true, reason: "no_session" };
3849
+ }
3850
+ if (currentProject === targetProject) {
3851
+ return {
3852
+ allowed: true,
3853
+ reason: "same_session",
3854
+ currentProject,
3855
+ targetProject
3856
+ };
3857
+ }
3858
+ process.stderr.write(
3859
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3860
+ `
3861
+ );
3862
+ return {
3863
+ allowed: false,
3864
+ reason: "cross_session_denied",
3865
+ currentProject,
3866
+ targetProject,
3867
+ targetSession: findSessionForProject(targetProject)?.windowName
3868
+ };
3869
+ } catch {
3870
+ return { allowed: true, reason: "no_session" };
3871
+ }
3872
+ }
3873
+ var init_session_scope = __esm({
3874
+ "src/lib/session-scope.ts"() {
3875
+ "use strict";
3876
+ init_session_registry();
3877
+ init_project_name();
3878
+ init_tmux_routing();
3879
+ init_employees();
3880
+ }
3881
+ });
3882
+
3883
+ // src/lib/tasks-crud.ts
3884
+ import crypto4 from "crypto";
3885
+ import path14 from "path";
3886
+ import os10 from "os";
3887
+ import { execSync as execSync6 } from "child_process";
3654
3888
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3655
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3889
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3656
3890
  async function writeCheckpoint(input) {
3657
3891
  const client = getClient();
3658
3892
  const row = await resolveTask(client, input.taskId);
@@ -3768,13 +4002,28 @@ async function resolveTask(client, identifier, scopeSession) {
3768
4002
  }
3769
4003
  async function createTaskCore(input) {
3770
4004
  const client = getClient();
3771
- const id = crypto3.randomUUID();
4005
+ const id = crypto4.randomUUID();
3772
4006
  const now = (/* @__PURE__ */ new Date()).toISOString();
3773
4007
  const slug = slugify(input.title);
3774
4008
  let earlySessionScope = null;
4009
+ let scopeMismatchWarning;
3775
4010
  try {
3776
4011
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3777
- earlySessionScope = resolveExeSession2();
4012
+ const resolved = resolveExeSession2();
4013
+ if (resolved && input.projectName) {
4014
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
4015
+ const sessionProject = getSessionProject2(resolved);
4016
+ if (sessionProject && sessionProject !== input.projectName) {
4017
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
4018
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
4019
+ `);
4020
+ earlySessionScope = null;
4021
+ } else {
4022
+ earlySessionScope = resolved;
4023
+ }
4024
+ } else {
4025
+ earlySessionScope = resolved;
4026
+ }
3778
4027
  } catch {
3779
4028
  }
3780
4029
  const scope = earlySessionScope ?? "default";
@@ -3825,10 +4074,14 @@ async function createTaskCore(input) {
3825
4074
  ${laneWarning}` : laneWarning;
3826
4075
  }
3827
4076
  }
4077
+ if (scopeMismatchWarning) {
4078
+ warning = warning ? `${warning}
4079
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
4080
+ }
3828
4081
  if (input.baseDir) {
3829
4082
  try {
3830
- await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3831
- await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
4083
+ await mkdir3(path14.join(input.baseDir, "exe", "output"), { recursive: true });
4084
+ await mkdir3(path14.join(input.baseDir, "exe", "research"), { recursive: true });
3832
4085
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3833
4086
  await ensureGitignoreExe(input.baseDir);
3834
4087
  } catch {
@@ -3864,13 +4117,19 @@ ${laneWarning}` : laneWarning;
3864
4117
  });
3865
4118
  if (input.baseDir) {
3866
4119
  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 });
4120
+ const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
4121
+ const mdPath = path14.join(EXE_OS_DIR, taskFile);
4122
+ const mdDir = path14.dirname(mdPath);
4123
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3871
4124
  const reviewer = input.reviewer ?? input.assignedBy;
3872
4125
  const mdContent = `# ${input.title}
3873
4126
 
4127
+ ## MANDATORY: When done
4128
+
4129
+ You MUST call update_task with status "done" and a result summary when finished.
4130
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4131
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4132
+
3874
4133
  **ID:** ${id}
3875
4134
  **Status:** ${initialStatus}
3876
4135
  **Priority:** ${input.priority}
@@ -3884,12 +4143,6 @@ ${laneWarning}` : laneWarning;
3884
4143
  ## Context
3885
4144
 
3886
4145
  ${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
4146
  `;
3894
4147
  await writeFile3(mdPath, mdContent, "utf-8");
3895
4148
  } catch (err) {
@@ -3971,14 +4224,14 @@ function isTmuxSessionAlive(identifier) {
3971
4224
  if (!identifier || identifier === "unknown") return true;
3972
4225
  try {
3973
4226
  if (identifier.startsWith("%")) {
3974
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
4227
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
3975
4228
  timeout: 2e3,
3976
4229
  encoding: "utf8",
3977
4230
  stdio: ["pipe", "pipe", "pipe"]
3978
4231
  });
3979
4232
  return output.split("\n").some((l) => l.trim() === identifier);
3980
4233
  } else {
3981
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4234
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3982
4235
  timeout: 2e3,
3983
4236
  stdio: ["pipe", "pipe", "pipe"]
3984
4237
  });
@@ -3987,7 +4240,7 @@ function isTmuxSessionAlive(identifier) {
3987
4240
  } catch {
3988
4241
  if (identifier.startsWith("%")) return true;
3989
4242
  try {
3990
- execSync5("tmux list-sessions", {
4243
+ execSync6("tmux list-sessions", {
3991
4244
  timeout: 2e3,
3992
4245
  stdio: ["pipe", "pipe", "pipe"]
3993
4246
  });
@@ -4002,12 +4255,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
4002
4255
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
4003
4256
  try {
4004
4257
  const since = new Date(taskCreatedAt).toISOString();
4005
- const branch = execSync5(
4258
+ const branch = execSync6(
4006
4259
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
4007
4260
  { encoding: "utf8", timeout: 3e3 }
4008
4261
  ).trim();
4009
4262
  const branchArg = branch && branch !== "HEAD" ? branch : "";
4010
- const commitCount = execSync5(
4263
+ const commitCount = execSync6(
4011
4264
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
4012
4265
  { encoding: "utf8", timeout: 5e3 }
4013
4266
  ).trim();
@@ -4138,7 +4391,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4138
4391
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4139
4392
  } catch {
4140
4393
  }
4141
- if (input.status === "done" || input.status === "cancelled") {
4394
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4142
4395
  try {
4143
4396
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4144
4397
  clearQueueForAgent2(String(row.assigned_to));
@@ -4167,9 +4420,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4167
4420
  return { taskFile, assignedTo, assignedBy, taskSlug };
4168
4421
  }
4169
4422
  async function ensureArchitectureDoc(baseDir, projectName) {
4170
- const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
4423
+ const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
4171
4424
  try {
4172
- if (existsSync10(archPath)) return;
4425
+ if (existsSync12(archPath)) return;
4173
4426
  const template = [
4174
4427
  `# ${projectName} \u2014 System Architecture`,
4175
4428
  "",
@@ -4202,10 +4455,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4202
4455
  }
4203
4456
  }
4204
4457
  async function ensureGitignoreExe(baseDir) {
4205
- const gitignorePath = path12.join(baseDir, ".gitignore");
4458
+ const gitignorePath = path14.join(baseDir, ".gitignore");
4206
4459
  try {
4207
- if (existsSync10(gitignorePath)) {
4208
- const content = readFileSync10(gitignorePath, "utf-8");
4460
+ if (existsSync12(gitignorePath)) {
4461
+ const content = readFileSync11(gitignorePath, "utf-8");
4209
4462
  if (/^\/?exe\/?$/m.test(content)) return;
4210
4463
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4211
4464
  } else {
@@ -4236,58 +4489,42 @@ var init_tasks_crud = __esm({
4236
4489
  });
4237
4490
 
4238
4491
  // src/lib/tasks-review.ts
4239
- import path13 from "path";
4240
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4492
+ import path15 from "path";
4493
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4241
4494
  async function countPendingReviews(sessionScope) {
4242
4495
  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
- }
4496
+ const scope = strictSessionScopeFilter(
4497
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4498
+ );
4250
4499
  const result = await client.execute({
4251
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4252
- args: []
4500
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4501
+ WHERE status = 'needs_review'${scope.sql}`,
4502
+ args: [...scope.args]
4253
4503
  });
4254
4504
  return Number(result.rows[0]?.cnt) || 0;
4255
4505
  }
4256
4506
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4257
4507
  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
- }
4508
+ const scope = strictSessionScopeFilter(
4509
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4510
+ );
4267
4511
  const result = await client.execute({
4268
4512
  sql: `SELECT COUNT(*) as cnt FROM tasks
4269
- WHERE status = 'needs_review' AND updated_at > ?`,
4270
- args: [sinceIso]
4513
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4514
+ args: [sinceIso, ...scope.args]
4271
4515
  });
4272
4516
  return Number(result.rows[0]?.cnt) || 0;
4273
4517
  }
4274
4518
  async function listPendingReviews(limit, sessionScope) {
4275
4519
  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
- }
4520
+ const scope = strictSessionScopeFilter(
4521
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4522
+ );
4286
4523
  const result = await client.execute({
4287
4524
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4288
- WHERE status = 'needs_review'
4525
+ WHERE status = 'needs_review'${scope.sql}
4289
4526
  ORDER BY updated_at ASC LIMIT ?`,
4290
- args: [limit]
4527
+ args: [...scope.args, limit]
4291
4528
  });
4292
4529
  return result.rows;
4293
4530
  }
@@ -4299,7 +4536,7 @@ async function cleanupOrphanedReviews() {
4299
4536
  WHERE status IN ('open', 'needs_review', 'in_progress')
4300
4537
  AND assigned_by = 'system'
4301
4538
  AND title LIKE 'Review:%'
4302
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4539
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4303
4540
  args: [now]
4304
4541
  });
4305
4542
  const r1b = await client.execute({
@@ -4418,11 +4655,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4418
4655
  );
4419
4656
  }
4420
4657
  try {
4421
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4422
- if (existsSync11(cacheDir)) {
4658
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4659
+ if (existsSync13(cacheDir)) {
4423
4660
  for (const f of readdirSync2(cacheDir)) {
4424
4661
  if (f.startsWith("review-notified-")) {
4425
- unlinkSync4(path13.join(cacheDir, f));
4662
+ unlinkSync4(path15.join(cacheDir, f));
4426
4663
  }
4427
4664
  }
4428
4665
  }
@@ -4439,11 +4676,12 @@ var init_tasks_review = __esm({
4439
4676
  init_tmux_routing();
4440
4677
  init_session_key();
4441
4678
  init_state_bus();
4679
+ init_task_scope();
4442
4680
  }
4443
4681
  });
4444
4682
 
4445
4683
  // src/lib/tasks-chain.ts
4446
- import path14 from "path";
4684
+ import path16 from "path";
4447
4685
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4448
4686
  async function cascadeUnblock(taskId, baseDir, now) {
4449
4687
  const client = getClient();
@@ -4460,7 +4698,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4460
4698
  });
4461
4699
  for (const ur of unblockedRows.rows) {
4462
4700
  try {
4463
- const ubFile = path14.join(baseDir, String(ur.task_file));
4701
+ const ubFile = path16.join(baseDir, String(ur.task_file));
4464
4702
  let ubContent = await readFile3(ubFile, "utf-8");
4465
4703
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4466
4704
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4495,7 +4733,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4495
4733
  const scScope = sessionScopeFilter();
4496
4734
  const remaining = await client.execute({
4497
4735
  sql: `SELECT COUNT(*) as cnt FROM tasks
4498
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4736
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4499
4737
  args: [parentTaskId, ...scScope.args]
4500
4738
  });
4501
4739
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4527,110 +4765,6 @@ var init_tasks_chain = __esm({
4527
4765
  }
4528
4766
  });
4529
4767
 
4530
- // src/lib/project-name.ts
4531
- import { execSync as execSync6 } from "child_process";
4532
- import path15 from "path";
4533
- function getProjectName(cwd) {
4534
- const dir = cwd ?? process.cwd();
4535
- if (_cached2 && _cachedCwd === dir) return _cached2;
4536
- try {
4537
- let repoRoot;
4538
- try {
4539
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
4540
- cwd: dir,
4541
- encoding: "utf8",
4542
- timeout: 2e3,
4543
- stdio: ["pipe", "pipe", "pipe"]
4544
- }).trim();
4545
- repoRoot = path15.dirname(gitCommonDir);
4546
- } catch {
4547
- repoRoot = execSync6("git rev-parse --show-toplevel", {
4548
- cwd: dir,
4549
- encoding: "utf8",
4550
- timeout: 2e3,
4551
- stdio: ["pipe", "pipe", "pipe"]
4552
- }).trim();
4553
- }
4554
- _cached2 = path15.basename(repoRoot);
4555
- _cachedCwd = dir;
4556
- return _cached2;
4557
- } catch {
4558
- _cached2 = path15.basename(dir);
4559
- _cachedCwd = dir;
4560
- return _cached2;
4561
- }
4562
- }
4563
- var _cached2, _cachedCwd;
4564
- var init_project_name = __esm({
4565
- "src/lib/project-name.ts"() {
4566
- "use strict";
4567
- _cached2 = null;
4568
- _cachedCwd = null;
4569
- }
4570
- });
4571
-
4572
- // src/lib/session-scope.ts
4573
- var session_scope_exports = {};
4574
- __export(session_scope_exports, {
4575
- assertSessionScope: () => assertSessionScope,
4576
- findSessionForProject: () => findSessionForProject,
4577
- getSessionProject: () => getSessionProject
4578
- });
4579
- function getSessionProject(sessionName) {
4580
- const sessions = listSessions();
4581
- const entry = sessions.find((s) => s.windowName === sessionName);
4582
- if (!entry) return null;
4583
- const parts = entry.projectDir.split("/").filter(Boolean);
4584
- return parts[parts.length - 1] ?? null;
4585
- }
4586
- function findSessionForProject(projectName) {
4587
- const sessions = listSessions();
4588
- for (const s of sessions) {
4589
- const proj = s.projectDir.split("/").filter(Boolean).pop();
4590
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4591
- }
4592
- return null;
4593
- }
4594
- function assertSessionScope(actionType, targetProject) {
4595
- try {
4596
- const currentProject = getProjectName();
4597
- const exeSession = resolveExeSession();
4598
- if (!exeSession) {
4599
- return { allowed: true, reason: "no_session" };
4600
- }
4601
- if (currentProject === targetProject) {
4602
- return {
4603
- allowed: true,
4604
- reason: "same_session",
4605
- currentProject,
4606
- targetProject
4607
- };
4608
- }
4609
- process.stderr.write(
4610
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4611
- `
4612
- );
4613
- return {
4614
- allowed: false,
4615
- reason: "cross_session_denied",
4616
- currentProject,
4617
- targetProject,
4618
- targetSession: findSessionForProject(targetProject)?.windowName
4619
- };
4620
- } catch {
4621
- return { allowed: true, reason: "no_session" };
4622
- }
4623
- }
4624
- var init_session_scope = __esm({
4625
- "src/lib/session-scope.ts"() {
4626
- "use strict";
4627
- init_session_registry();
4628
- init_project_name();
4629
- init_tmux_routing();
4630
- init_employees();
4631
- }
4632
- });
4633
-
4634
4768
  // src/lib/tasks-notify.ts
4635
4769
  async function dispatchTaskToEmployee(input) {
4636
4770
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -4705,10 +4839,10 @@ __export(behaviors_exports, {
4705
4839
  listBehaviorsByDomain: () => listBehaviorsByDomain,
4706
4840
  storeBehavior: () => storeBehavior
4707
4841
  });
4708
- import crypto4 from "crypto";
4842
+ import crypto5 from "crypto";
4709
4843
  async function storeBehavior(opts) {
4710
4844
  const client = getClient();
4711
- const id = crypto4.randomUUID();
4845
+ const id = crypto5.randomUUID();
4712
4846
  const now = (/* @__PURE__ */ new Date()).toISOString();
4713
4847
  await client.execute({
4714
4848
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4792,7 +4926,7 @@ __export(skill_learning_exports, {
4792
4926
  storeTrajectory: () => storeTrajectory,
4793
4927
  sweepTrajectories: () => sweepTrajectories
4794
4928
  });
4795
- import crypto5 from "crypto";
4929
+ import crypto6 from "crypto";
4796
4930
  async function extractTrajectory(taskId, agentId) {
4797
4931
  const client = getClient();
4798
4932
  const result = await client.execute({
@@ -4821,11 +4955,11 @@ async function extractTrajectory(taskId, agentId) {
4821
4955
  return signature;
4822
4956
  }
4823
4957
  function hashSignature(signature) {
4824
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4958
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4825
4959
  }
4826
4960
  async function storeTrajectory(opts) {
4827
4961
  const client = getClient();
4828
- const id = crypto5.randomUUID();
4962
+ const id = crypto6.randomUUID();
4829
4963
  const now = (/* @__PURE__ */ new Date()).toISOString();
4830
4964
  const signatureHash = hashSignature(opts.signature);
4831
4965
  await client.execute({
@@ -5090,8 +5224,8 @@ __export(tasks_exports, {
5090
5224
  updateTaskStatus: () => updateTaskStatus,
5091
5225
  writeCheckpoint: () => writeCheckpoint
5092
5226
  });
5093
- import path16 from "path";
5094
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5227
+ import path17 from "path";
5228
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5095
5229
  async function createTask(input) {
5096
5230
  const result = await createTaskCore(input);
5097
5231
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5110,12 +5244,12 @@ async function updateTask(input) {
5110
5244
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5111
5245
  try {
5112
5246
  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`);
5247
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
5248
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
5115
5249
  if (input.status === "in_progress") {
5116
5250
  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") {
5251
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5252
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5119
5253
  try {
5120
5254
  unlinkSync5(cachePath);
5121
5255
  } catch {
@@ -5123,10 +5257,10 @@ async function updateTask(input) {
5123
5257
  }
5124
5258
  } catch {
5125
5259
  }
5126
- if (input.status === "done") {
5260
+ if (input.status === "done" || input.status === "closed") {
5127
5261
  await cleanupReviewFile(row, taskFile, input.baseDir);
5128
5262
  }
5129
- if (input.status === "done" || input.status === "cancelled") {
5263
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5130
5264
  try {
5131
5265
  const client = getClient();
5132
5266
  const taskTitle = String(row.title);
@@ -5142,7 +5276,7 @@ async function updateTask(input) {
5142
5276
  if (!isCoordinatorName(assignedAgent)) {
5143
5277
  try {
5144
5278
  const draftClient = getClient();
5145
- if (input.status === "done") {
5279
+ if (input.status === "done" || input.status === "closed") {
5146
5280
  await draftClient.execute({
5147
5281
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5148
5282
  args: [assignedAgent]
@@ -5159,7 +5293,7 @@ async function updateTask(input) {
5159
5293
  try {
5160
5294
  const client = getClient();
5161
5295
  const cascaded = await client.execute({
5162
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5296
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5163
5297
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5164
5298
  args: [now, taskId]
5165
5299
  });
@@ -5172,14 +5306,14 @@ async function updateTask(input) {
5172
5306
  } catch {
5173
5307
  }
5174
5308
  }
5175
- const isTerminal = input.status === "done" || input.status === "needs_review";
5309
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5176
5310
  if (isTerminal) {
5177
5311
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5178
5312
  if (!isCoordinator) {
5179
5313
  notifyTaskDone();
5180
5314
  }
5181
5315
  await markTaskNotificationsRead(taskFile);
5182
- if (input.status === "done") {
5316
+ if (input.status === "done" || input.status === "closed") {
5183
5317
  try {
5184
5318
  await cascadeUnblock(taskId, input.baseDir, now);
5185
5319
  } catch {
@@ -5199,7 +5333,7 @@ async function updateTask(input) {
5199
5333
  }
5200
5334
  }
5201
5335
  }
5202
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5336
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5203
5337
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5204
5338
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5205
5339
  taskId,
@@ -5571,6 +5705,7 @@ __export(tmux_routing_exports, {
5571
5705
  isEmployeeAlive: () => isEmployeeAlive,
5572
5706
  isExeSession: () => isExeSession,
5573
5707
  isSessionBusy: () => isSessionBusy,
5708
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5574
5709
  notifyParentExe: () => notifyParentExe,
5575
5710
  parseParentExe: () => parseParentExe,
5576
5711
  registerParentExe: () => registerParentExe,
@@ -5581,13 +5716,13 @@ __export(tmux_routing_exports, {
5581
5716
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5582
5717
  });
5583
5718
  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";
5719
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5720
+ import path18 from "path";
5721
+ import os11 from "os";
5587
5722
  import { fileURLToPath as fileURLToPath2 } from "url";
5588
5723
  import { unlinkSync as unlinkSync6 } from "fs";
5589
5724
  function spawnLockPath(sessionName) {
5590
- return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5725
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5591
5726
  }
5592
5727
  function isProcessAlive(pid) {
5593
5728
  try {
@@ -5598,13 +5733,13 @@ function isProcessAlive(pid) {
5598
5733
  }
5599
5734
  }
5600
5735
  function acquireSpawnLock2(sessionName) {
5601
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5736
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5602
5737
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5603
5738
  }
5604
5739
  const lockFile = spawnLockPath(sessionName);
5605
- if (existsSync12(lockFile)) {
5740
+ if (existsSync14(lockFile)) {
5606
5741
  try {
5607
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5742
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5608
5743
  const age = Date.now() - lock.timestamp;
5609
5744
  if (isProcessAlive(lock.pid) && age < 6e4) {
5610
5745
  return false;
@@ -5612,7 +5747,7 @@ function acquireSpawnLock2(sessionName) {
5612
5747
  } catch {
5613
5748
  }
5614
5749
  }
5615
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5750
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5616
5751
  return true;
5617
5752
  }
5618
5753
  function releaseSpawnLock2(sessionName) {
@@ -5624,13 +5759,13 @@ function releaseSpawnLock2(sessionName) {
5624
5759
  function resolveBehaviorsExporterScript() {
5625
5760
  try {
5626
5761
  const thisFile = fileURLToPath2(import.meta.url);
5627
- const scriptPath = path17.join(
5628
- path17.dirname(thisFile),
5762
+ const scriptPath = path18.join(
5763
+ path18.dirname(thisFile),
5629
5764
  "..",
5630
5765
  "bin",
5631
5766
  "exe-export-behaviors.js"
5632
5767
  );
5633
- return existsSync12(scriptPath) ? scriptPath : null;
5768
+ return existsSync14(scriptPath) ? scriptPath : null;
5634
5769
  } catch {
5635
5770
  return null;
5636
5771
  }
@@ -5696,12 +5831,12 @@ function extractRootExe(name) {
5696
5831
  return parts.length > 0 ? parts[parts.length - 1] : null;
5697
5832
  }
5698
5833
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5699
- if (!existsSync12(SESSION_CACHE)) {
5834
+ if (!existsSync14(SESSION_CACHE)) {
5700
5835
  mkdirSync6(SESSION_CACHE, { recursive: true });
5701
5836
  }
5702
5837
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5703
- const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5704
- writeFileSync7(filePath, JSON.stringify({
5838
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5839
+ writeFileSync8(filePath, JSON.stringify({
5705
5840
  parentExe: rootExe,
5706
5841
  dispatchedBy: dispatchedBy || rootExe,
5707
5842
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5709,7 +5844,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5709
5844
  }
5710
5845
  function getParentExe(sessionKey) {
5711
5846
  try {
5712
- const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5847
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5713
5848
  return data.parentExe || null;
5714
5849
  } catch {
5715
5850
  return null;
@@ -5717,8 +5852,8 @@ function getParentExe(sessionKey) {
5717
5852
  }
5718
5853
  function getDispatchedBy(sessionKey) {
5719
5854
  try {
5720
- const data = JSON.parse(readFileSync11(
5721
- path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5855
+ const data = JSON.parse(readFileSync12(
5856
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5722
5857
  "utf8"
5723
5858
  ));
5724
5859
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5788,8 +5923,8 @@ async function verifyPaneAtCapacity(sessionName) {
5788
5923
  }
5789
5924
  function readDebounceState() {
5790
5925
  try {
5791
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5792
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5926
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5927
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5793
5928
  const state = {};
5794
5929
  for (const [key, val] of Object.entries(raw)) {
5795
5930
  if (typeof val === "number") {
@@ -5805,8 +5940,8 @@ function readDebounceState() {
5805
5940
  }
5806
5941
  function writeDebounceState(state) {
5807
5942
  try {
5808
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5809
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5943
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5944
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5810
5945
  } catch {
5811
5946
  }
5812
5947
  }
@@ -5904,8 +6039,8 @@ function sendIntercom(targetSession) {
5904
6039
  try {
5905
6040
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5906
6041
  const agent = baseAgentName(rawAgent);
5907
- const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5908
- if (existsSync12(markerPath)) {
6042
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
6043
+ if (existsSync14(markerPath)) {
5909
6044
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5910
6045
  return "debounced";
5911
6046
  }
@@ -5914,8 +6049,8 @@ function sendIntercom(targetSession) {
5914
6049
  try {
5915
6050
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5916
6051
  const agent = baseAgentName(rawAgent);
5917
- const taskDir = path17.join(process.cwd(), "exe", agent);
5918
- if (existsSync12(taskDir)) {
6052
+ const taskDir = path18.join(process.cwd(), "exe", agent);
6053
+ if (existsSync14(taskDir)) {
5919
6054
  const files = readdirSync3(taskDir).filter(
5920
6055
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5921
6056
  );
@@ -5975,6 +6110,21 @@ function notifyParentExe(sessionKey) {
5975
6110
  }
5976
6111
  return true;
5977
6112
  }
6113
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6114
+ const transport = getTransport();
6115
+ try {
6116
+ const sessions = transport.listSessions();
6117
+ if (!sessions.includes(coordinatorSession)) return false;
6118
+ execSync7(
6119
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6120
+ { timeout: 3e3 }
6121
+ );
6122
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6123
+ return true;
6124
+ } catch {
6125
+ return false;
6126
+ }
6127
+ }
5978
6128
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5979
6129
  if (isCoordinatorName(employeeName)) {
5980
6130
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6048,26 +6198,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6048
6198
  const transport = getTransport();
6049
6199
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6050
6200
  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)) {
6201
+ const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
6202
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6203
+ if (!existsSync14(logDir)) {
6054
6204
  mkdirSync6(logDir, { recursive: true });
6055
6205
  }
6056
6206
  transport.kill(sessionName);
6057
6207
  let cleanupSuffix = "";
6058
6208
  try {
6059
6209
  const thisFile = fileURLToPath2(import.meta.url);
6060
- const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6061
- if (existsSync12(cleanupScript)) {
6210
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6211
+ if (existsSync14(cleanupScript)) {
6062
6212
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6063
6213
  }
6064
6214
  } catch {
6065
6215
  }
6066
6216
  try {
6067
- const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
6217
+ const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
6068
6218
  let claudeJson = {};
6069
6219
  try {
6070
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
6220
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6071
6221
  } catch {
6072
6222
  }
6073
6223
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6075,17 +6225,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6075
6225
  const trustDir = opts?.cwd ?? projectDir;
6076
6226
  if (!projects[trustDir]) projects[trustDir] = {};
6077
6227
  projects[trustDir].hasTrustDialogAccepted = true;
6078
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6228
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6079
6229
  } catch {
6080
6230
  }
6081
6231
  try {
6082
- const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
6232
+ const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
6083
6233
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6084
- const projSettingsDir = path17.join(settingsDir, normalizedKey);
6085
- const settingsPath = path17.join(projSettingsDir, "settings.json");
6234
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
6235
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
6086
6236
  let settings = {};
6087
6237
  try {
6088
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6238
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6089
6239
  } catch {
6090
6240
  }
6091
6241
  const perms = settings.permissions ?? {};
@@ -6114,7 +6264,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6114
6264
  perms.allow = allow;
6115
6265
  settings.permissions = perms;
6116
6266
  mkdirSync6(projSettingsDir, { recursive: true });
6117
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6267
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6118
6268
  }
6119
6269
  } catch {
6120
6270
  }
@@ -6129,8 +6279,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6129
6279
  let behaviorsFlag = "";
6130
6280
  let legacyFallbackWarned = false;
6131
6281
  if (!useExeAgent && !useBinSymlink) {
6132
- const identityPath = path17.join(
6133
- os10.homedir(),
6282
+ const identityPath = path18.join(
6283
+ os11.homedir(),
6134
6284
  ".exe-os",
6135
6285
  "identity",
6136
6286
  `${employeeName}.md`
@@ -6139,13 +6289,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6139
6289
  const hasAgentFlag = claudeSupportsAgentFlag();
6140
6290
  if (hasAgentFlag) {
6141
6291
  identityFlag = ` --agent ${employeeName}`;
6142
- } else if (existsSync12(identityPath)) {
6292
+ } else if (existsSync14(identityPath)) {
6143
6293
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6144
6294
  legacyFallbackWarned = true;
6145
6295
  }
6146
6296
  const behaviorsFile = exportBehaviorsSync(
6147
6297
  employeeName,
6148
- path17.basename(spawnCwd),
6298
+ path18.basename(spawnCwd),
6149
6299
  sessionName
6150
6300
  );
6151
6301
  if (behaviorsFile) {
@@ -6160,16 +6310,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6160
6310
  }
6161
6311
  let sessionContextFlag = "";
6162
6312
  try {
6163
- const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
6313
+ const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
6164
6314
  mkdirSync6(ctxDir, { recursive: true });
6165
- const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6315
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
6166
6316
  const ctxContent = [
6167
6317
  `## Session Context`,
6168
6318
  `You are running in tmux session: ${sessionName}.`,
6169
6319
  `Your parent coordinator session is ${exeSession}.`,
6170
6320
  `Your employees (if any) use the -${exeSession} suffix.`
6171
6321
  ].join("\n");
6172
- writeFileSync7(ctxFile, ctxContent);
6322
+ writeFileSync8(ctxFile, ctxContent);
6173
6323
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6174
6324
  } catch {
6175
6325
  }
@@ -6246,8 +6396,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6246
6396
  transport.pipeLog(sessionName, logFile);
6247
6397
  try {
6248
6398
  const mySession = getMySession();
6249
- const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6250
- writeFileSync7(dispatchInfo, JSON.stringify({
6399
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6400
+ writeFileSync8(dispatchInfo, JSON.stringify({
6251
6401
  dispatchedBy: mySession,
6252
6402
  rootExe: exeSession,
6253
6403
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6321,15 +6471,15 @@ var init_tmux_routing = __esm({
6321
6471
  init_intercom_queue();
6322
6472
  init_plan_limits();
6323
6473
  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");
6474
+ SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
6475
+ SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
6326
6476
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6327
6477
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6328
6478
  VERIFY_PANE_LINES = 200;
6329
6479
  INTERCOM_DEBOUNCE_MS = 3e4;
6330
6480
  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");
6481
+ INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
6482
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
6333
6483
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6334
6484
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6335
6485
  }
@@ -6346,14 +6496,14 @@ var init_memory = __esm({
6346
6496
 
6347
6497
  // src/lib/keychain.ts
6348
6498
  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";
6499
+ import { existsSync as existsSync15 } from "fs";
6500
+ import path19 from "path";
6501
+ import os12 from "os";
6352
6502
  function getKeyDir() {
6353
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
6503
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
6354
6504
  }
6355
6505
  function getKeyPath() {
6356
- return path18.join(getKeyDir(), "master.key");
6506
+ return path19.join(getKeyDir(), "master.key");
6357
6507
  }
6358
6508
  async function tryKeytar() {
6359
6509
  try {
@@ -6374,9 +6524,9 @@ async function getMasterKey() {
6374
6524
  }
6375
6525
  }
6376
6526
  const keyPath = getKeyPath();
6377
- if (!existsSync13(keyPath)) {
6527
+ if (!existsSync15(keyPath)) {
6378
6528
  process.stderr.write(
6379
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6529
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6380
6530
  `
6381
6531
  );
6382
6532
  return null;
@@ -6406,6 +6556,7 @@ var shard_manager_exports = {};
6406
6556
  __export(shard_manager_exports, {
6407
6557
  disposeShards: () => disposeShards,
6408
6558
  ensureShardSchema: () => ensureShardSchema,
6559
+ getOpenShardCount: () => getOpenShardCount,
6409
6560
  getReadyShardClient: () => getReadyShardClient,
6410
6561
  getShardClient: () => getShardClient,
6411
6562
  getShardsDir: () => getShardsDir,
@@ -6414,15 +6565,18 @@ __export(shard_manager_exports, {
6414
6565
  listShards: () => listShards,
6415
6566
  shardExists: () => shardExists
6416
6567
  });
6417
- import path19 from "path";
6418
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6568
+ import path20 from "path";
6569
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6419
6570
  import { createClient as createClient2 } from "@libsql/client";
6420
6571
  function initShardManager(encryptionKey) {
6421
6572
  _encryptionKey = encryptionKey;
6422
- if (!existsSync14(SHARDS_DIR)) {
6573
+ if (!existsSync16(SHARDS_DIR)) {
6423
6574
  mkdirSync7(SHARDS_DIR, { recursive: true });
6424
6575
  }
6425
6576
  _shardingEnabled = true;
6577
+ if (_evictionTimer) clearInterval(_evictionTimer);
6578
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6579
+ _evictionTimer.unref();
6426
6580
  }
6427
6581
  function isShardingEnabled() {
6428
6582
  return _shardingEnabled;
@@ -6439,21 +6593,28 @@ function getShardClient(projectName) {
6439
6593
  throw new Error(`Invalid project name for shard: "${projectName}"`);
6440
6594
  }
6441
6595
  const cached = _shards.get(safeName);
6442
- if (cached) return cached;
6443
- const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
6596
+ if (cached) {
6597
+ _shardLastAccess.set(safeName, Date.now());
6598
+ return cached;
6599
+ }
6600
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6601
+ evictLRU();
6602
+ }
6603
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
6444
6604
  const client = createClient2({
6445
6605
  url: `file:${dbPath}`,
6446
6606
  encryptionKey: _encryptionKey
6447
6607
  });
6448
6608
  _shards.set(safeName, client);
6609
+ _shardLastAccess.set(safeName, Date.now());
6449
6610
  return client;
6450
6611
  }
6451
6612
  function shardExists(projectName) {
6452
6613
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
6453
- return existsSync14(path19.join(SHARDS_DIR, `${safeName}.db`));
6614
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
6454
6615
  }
6455
6616
  function listShards() {
6456
- if (!existsSync14(SHARDS_DIR)) return [];
6617
+ if (!existsSync16(SHARDS_DIR)) return [];
6457
6618
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6458
6619
  }
6459
6620
  async function ensureShardSchema(client) {
@@ -6505,6 +6666,8 @@ async function ensureShardSchema(client) {
6505
6666
  for (const col of [
6506
6667
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
6507
6668
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6669
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6670
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
6508
6671
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
6509
6672
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
6510
6673
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6642,21 +6805,69 @@ async function getReadyShardClient(projectName) {
6642
6805
  await ensureShardSchema(client);
6643
6806
  return client;
6644
6807
  }
6808
+ function evictLRU() {
6809
+ let oldest = null;
6810
+ let oldestTime = Infinity;
6811
+ for (const [name, time] of _shardLastAccess) {
6812
+ if (time < oldestTime) {
6813
+ oldestTime = time;
6814
+ oldest = name;
6815
+ }
6816
+ }
6817
+ if (oldest) {
6818
+ const client = _shards.get(oldest);
6819
+ if (client) {
6820
+ client.close();
6821
+ }
6822
+ _shards.delete(oldest);
6823
+ _shardLastAccess.delete(oldest);
6824
+ }
6825
+ }
6826
+ function evictIdleShards() {
6827
+ const now = Date.now();
6828
+ const toEvict = [];
6829
+ for (const [name, lastAccess] of _shardLastAccess) {
6830
+ if (now - lastAccess > SHARD_IDLE_MS) {
6831
+ toEvict.push(name);
6832
+ }
6833
+ }
6834
+ for (const name of toEvict) {
6835
+ const client = _shards.get(name);
6836
+ if (client) {
6837
+ client.close();
6838
+ }
6839
+ _shards.delete(name);
6840
+ _shardLastAccess.delete(name);
6841
+ }
6842
+ }
6843
+ function getOpenShardCount() {
6844
+ return _shards.size;
6845
+ }
6645
6846
  function disposeShards() {
6847
+ if (_evictionTimer) {
6848
+ clearInterval(_evictionTimer);
6849
+ _evictionTimer = null;
6850
+ }
6646
6851
  for (const [, client] of _shards) {
6647
6852
  client.close();
6648
6853
  }
6649
6854
  _shards.clear();
6855
+ _shardLastAccess.clear();
6650
6856
  _shardingEnabled = false;
6651
6857
  _encryptionKey = null;
6652
6858
  }
6653
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6859
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
6654
6860
  var init_shard_manager = __esm({
6655
6861
  "src/lib/shard-manager.ts"() {
6656
6862
  "use strict";
6657
6863
  init_config();
6658
- SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6864
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6865
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6866
+ MAX_OPEN_SHARDS = 10;
6867
+ EVICTION_INTERVAL_MS = 60 * 1e3;
6659
6868
  _shards = /* @__PURE__ */ new Map();
6869
+ _shardLastAccess = /* @__PURE__ */ new Map();
6870
+ _evictionTimer = null;
6660
6871
  _encryptionKey = null;
6661
6872
  _shardingEnabled = false;
6662
6873
  }
@@ -7452,8 +7663,8 @@ function findContainingChunk(filePath, snippet) {
7452
7663
  try {
7453
7664
  const ext = filePath.split(".").pop()?.toLowerCase();
7454
7665
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
7455
- const { readFileSync: readFileSync14 } = __require("fs");
7456
- const source = readFileSync14(filePath, "utf8");
7666
+ const { readFileSync: readFileSync15 } = __require("fs");
7667
+ const source = readFileSync15(filePath, "utf8");
7457
7668
  const lines = source.split("\n");
7458
7669
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
7459
7670
  let matchLine = -1;
@@ -7519,9 +7730,9 @@ function extractBash(input, response) {
7519
7730
  }
7520
7731
  function extractGrep(input, response) {
7521
7732
  const pattern = String(input.pattern ?? "");
7522
- const path22 = input.path ? String(input.path) : "";
7733
+ const path23 = input.path ? String(input.path) : "";
7523
7734
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
7524
- return `Searched for "${pattern}"${path22 ? ` in ${path22}` : ""}
7735
+ return `Searched for "${pattern}"${path23 ? ` in ${path23}` : ""}
7525
7736
  ${output.slice(0, MAX_OUTPUT)}`;
7526
7737
  }
7527
7738
  function extractGlob(input, response) {
@@ -7624,7 +7835,7 @@ __export(error_detector_exports, {
7624
7835
  errorFingerprint: () => errorFingerprint,
7625
7836
  isExeOsError: () => isExeOsError
7626
7837
  });
7627
- import crypto6 from "crypto";
7838
+ import crypto7 from "crypto";
7628
7839
  function isRealStderr(stderr) {
7629
7840
  const lines = stderr.trim().split("\n");
7630
7841
  const meaningful = lines.filter(
@@ -7695,7 +7906,7 @@ function classifyError(errorText) {
7695
7906
  }
7696
7907
  function errorFingerprint(toolName, errorText) {
7697
7908
  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);
7909
+ return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7699
7910
  }
7700
7911
  var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
7701
7912
  var init_error_detector = __esm({
@@ -8059,10 +8270,10 @@ async function disposeEmbedder() {
8059
8270
  async function embedDirect(text) {
8060
8271
  const llamaCpp = await import("node-llama-cpp");
8061
8272
  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)) {
8273
+ const { existsSync: existsSync18 } = await import("fs");
8274
+ const path23 = await import("path");
8275
+ const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8276
+ if (!existsSync18(modelPath)) {
8066
8277
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
8067
8278
  }
8068
8279
  const llama = await llamaCpp.getLlama();
@@ -8099,8 +8310,8 @@ __export(wiki_client_exports, {
8099
8310
  listDocuments: () => listDocuments,
8100
8311
  listWorkspaces: () => listWorkspaces
8101
8312
  });
8102
- async function wikiFetch(config2, path22, method = "GET", body) {
8103
- const url = `${config2.baseUrl}/api/v1${path22}`;
8313
+ async function wikiFetch(config2, path23, method = "GET", body) {
8314
+ const url = `${config2.baseUrl}/api/v1${path23}`;
8104
8315
  const headers = {
8105
8316
  Authorization: `Bearer ${config2.apiKey}`,
8106
8317
  "Content-Type": "application/json"
@@ -8133,7 +8344,7 @@ async function wikiFetch(config2, path22, method = "GET", body) {
8133
8344
  }
8134
8345
  }
8135
8346
  if (!response.ok) {
8136
- throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
8347
+ throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
8137
8348
  }
8138
8349
  return response.json();
8139
8350
  } finally {
@@ -8426,13 +8637,13 @@ __export(graph_rag_exports, {
8426
8637
  resolveAlias: () => resolveAlias,
8427
8638
  storeExtraction: () => storeExtraction
8428
8639
  });
8429
- import crypto7 from "crypto";
8640
+ import crypto8 from "crypto";
8430
8641
  function normalizeEntityName(name) {
8431
8642
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
8432
8643
  }
8433
8644
  function entityId(name, type) {
8434
8645
  const normalized = normalizeEntityName(name);
8435
- return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
8646
+ return crypto8.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
8436
8647
  }
8437
8648
  async function resolveAlias(client, name) {
8438
8649
  const normalized = normalizeEntityName(name);
@@ -8682,7 +8893,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
8682
8893
  const targetAlias = await resolveAlias(client, r.target);
8683
8894
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
8684
8895
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
8685
- const relId = crypto7.randomUUID().slice(0, 16);
8896
+ const relId = crypto8.randomUUID().slice(0, 16);
8686
8897
  try {
8687
8898
  await client.execute({
8688
8899
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -8745,7 +8956,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
8745
8956
  }
8746
8957
  }
8747
8958
  for (const h of extraction.hyperedges) {
8748
- const hId = crypto7.randomUUID().slice(0, 16);
8959
+ const hId = crypto8.randomUUID().slice(0, 16);
8749
8960
  try {
8750
8961
  await client.execute({
8751
8962
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -8809,7 +9020,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
8809
9020
  totalEntities += stored.entitiesStored;
8810
9021
  totalRelationships += stored.relationshipsStored;
8811
9022
  }
8812
- const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
9023
+ const contentHash = crypto8.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
8813
9024
  await client.execute({
8814
9025
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
8815
9026
  args: [contentHash, contentHash, memoryId]
@@ -9154,13 +9365,13 @@ __export(whatsapp_accounts_exports, {
9154
9365
  getDefaultAccount: () => getDefaultAccount,
9155
9366
  loadAccounts: () => loadAccounts
9156
9367
  });
9157
- import { readFileSync as readFileSync12 } from "fs";
9368
+ import { readFileSync as readFileSync13 } from "fs";
9158
9369
  import { join as join2 } from "path";
9159
9370
  import { homedir as homedir2 } from "os";
9160
9371
  function loadAccounts() {
9161
9372
  if (cachedAccounts !== null) return cachedAccounts;
9162
9373
  try {
9163
- const raw = readFileSync12(CONFIG_PATH2, "utf8");
9374
+ const raw = readFileSync13(CONFIG_PATH2, "utf8");
9164
9375
  const parsed = JSON.parse(raw);
9165
9376
  if (!Array.isArray(parsed)) {
9166
9377
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -9216,10 +9427,10 @@ __export(messaging_exports, {
9216
9427
  sendMessage: () => sendMessage,
9217
9428
  setWsClientSend: () => setWsClientSend
9218
9429
  });
9219
- import crypto9 from "crypto";
9430
+ import crypto10 from "crypto";
9220
9431
  function generateUlid() {
9221
9432
  const timestamp = Date.now().toString(36).padStart(10, "0");
9222
- const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
9433
+ const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
9223
9434
  return (timestamp + random).toUpperCase();
9224
9435
  }
9225
9436
  function rowToMessage(row) {
@@ -9230,6 +9441,7 @@ function rowToMessage(row) {
9230
9441
  targetAgent: row.target_agent,
9231
9442
  targetProject: row.target_project ?? null,
9232
9443
  targetDevice: row.target_device,
9444
+ sessionScope: row.session_scope ?? null,
9233
9445
  content: row.content,
9234
9446
  priority: row.priority ?? "normal",
9235
9447
  status: row.status ?? "pending",
@@ -9247,15 +9459,17 @@ async function sendMessage(input) {
9247
9459
  const id = generateUlid();
9248
9460
  const now = (/* @__PURE__ */ new Date()).toISOString();
9249
9461
  const targetDevice = input.targetDevice ?? "local";
9462
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
9250
9463
  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', ?)`,
9464
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
9465
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
9253
9466
  args: [
9254
9467
  id,
9255
9468
  input.fromAgent,
9256
9469
  input.targetAgent,
9257
9470
  input.targetProject ?? null,
9258
9471
  targetDevice,
9472
+ sessionScope,
9259
9473
  input.content,
9260
9474
  input.priority ?? "normal",
9261
9475
  now
@@ -9269,9 +9483,10 @@ async function sendMessage(input) {
9269
9483
  }
9270
9484
  } catch {
9271
9485
  }
9486
+ const sentScope = strictSessionScopeFilter(sessionScope);
9272
9487
  const result = await client.execute({
9273
- sql: "SELECT * FROM messages WHERE id = ?",
9274
- args: [id]
9488
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
9489
+ args: [id, ...sentScope.args]
9275
9490
  });
9276
9491
  return rowToMessage(result.rows[0]);
9277
9492
  }
@@ -9295,6 +9510,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
9295
9510
  fromAgent: msg.fromAgent,
9296
9511
  targetAgent: msg.targetAgent,
9297
9512
  targetProject: msg.targetProject,
9513
+ sessionScope: msg.sessionScope,
9298
9514
  content: msg.content,
9299
9515
  priority: msg.priority,
9300
9516
  createdAt: msg.createdAt
@@ -9338,7 +9554,7 @@ async function deliverLocalMessage(messageId) {
9338
9554
  } catch {
9339
9555
  const newRetryCount = msg.retryCount + 1;
9340
9556
  if (newRetryCount >= MAX_RETRIES3) {
9341
- await markFailed(messageId, "session unavailable after 10 retries");
9557
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
9342
9558
  } else {
9343
9559
  await client.execute({
9344
9560
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -9348,85 +9564,101 @@ async function deliverLocalMessage(messageId) {
9348
9564
  return false;
9349
9565
  }
9350
9566
  }
9351
- async function getPendingMessages(targetAgent) {
9567
+ async function getPendingMessages(targetAgent, sessionScope) {
9352
9568
  const client = getClient();
9569
+ const scope = strictSessionScopeFilter(sessionScope);
9353
9570
  const result = await client.execute({
9354
9571
  sql: `SELECT * FROM messages
9355
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
9572
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
9356
9573
  ORDER BY id`,
9357
- args: [targetAgent]
9574
+ args: [targetAgent, ...scope.args]
9358
9575
  });
9359
9576
  return result.rows.map((row) => rowToMessage(row));
9360
9577
  }
9361
- async function markRead(messageId) {
9578
+ async function markRead(messageId, sessionScope) {
9362
9579
  const client = getClient();
9580
+ const scope = strictSessionScopeFilter(sessionScope);
9363
9581
  await client.execute({
9364
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
9365
- args: [messageId]
9582
+ sql: `UPDATE messages SET status = 'read'
9583
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
9584
+ args: [messageId, ...scope.args]
9366
9585
  });
9367
9586
  }
9368
- async function markAcknowledged(messageId) {
9587
+ async function markAcknowledged(messageId, sessionScope) {
9369
9588
  const client = getClient();
9589
+ const scope = strictSessionScopeFilter(sessionScope);
9370
9590
  await client.execute({
9371
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
9372
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
9591
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
9592
+ WHERE id = ? AND status = 'read'${scope.sql}`,
9593
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9373
9594
  });
9374
9595
  }
9375
- async function markProcessed(messageId) {
9596
+ async function markProcessed(messageId, sessionScope) {
9376
9597
  const client = getClient();
9598
+ const scope = strictSessionScopeFilter(sessionScope);
9377
9599
  await client.execute({
9378
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
9379
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
9600
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
9601
+ WHERE id = ?${scope.sql}`,
9602
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
9380
9603
  });
9381
9604
  }
9382
- async function getMessageStatus(messageId) {
9605
+ async function getMessageStatus(messageId, sessionScope) {
9383
9606
  const client = getClient();
9607
+ const scope = strictSessionScopeFilter(sessionScope);
9384
9608
  const result = await client.execute({
9385
- sql: "SELECT status FROM messages WHERE id = ?",
9386
- args: [messageId]
9609
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
9610
+ args: [messageId, ...scope.args]
9387
9611
  });
9388
9612
  return result.rows[0]?.status ?? null;
9389
9613
  }
9390
- async function getUnacknowledgedMessages(targetAgent) {
9614
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
9391
9615
  const client = getClient();
9616
+ const scope = strictSessionScopeFilter(sessionScope);
9392
9617
  const result = await client.execute({
9393
9618
  sql: `SELECT * FROM messages
9394
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
9619
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
9395
9620
  ORDER BY id`,
9396
- args: [targetAgent]
9621
+ args: [targetAgent, ...scope.args]
9397
9622
  });
9398
9623
  return result.rows.map((row) => rowToMessage(row));
9399
9624
  }
9400
- async function getReadMessages(targetAgent) {
9625
+ async function getReadMessages(targetAgent, sessionScope) {
9401
9626
  const client = getClient();
9627
+ const scope = strictSessionScopeFilter(sessionScope);
9402
9628
  const result = await client.execute({
9403
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
9404
- args: [targetAgent]
9629
+ sql: `SELECT * FROM messages
9630
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
9631
+ ORDER BY id`,
9632
+ args: [targetAgent, ...scope.args]
9405
9633
  });
9406
9634
  return result.rows.map((row) => rowToMessage(row));
9407
9635
  }
9408
- async function markFailed(messageId, reason) {
9636
+ async function markFailed(messageId, reason, sessionScope) {
9409
9637
  const client = getClient();
9638
+ const scope = strictSessionScopeFilter(sessionScope);
9410
9639
  await client.execute({
9411
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
9412
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
9640
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
9641
+ WHERE id = ?${scope.sql}`,
9642
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
9413
9643
  });
9414
9644
  }
9415
- async function getFailedMessages() {
9645
+ async function getFailedMessages(sessionScope) {
9416
9646
  const client = getClient();
9647
+ const scope = strictSessionScopeFilter(sessionScope);
9417
9648
  const result = await client.execute({
9418
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
9419
- args: []
9649
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
9650
+ args: [...scope.args]
9420
9651
  });
9421
9652
  return result.rows.map((row) => rowToMessage(row));
9422
9653
  }
9423
- async function retryPendingMessages() {
9654
+ async function retryPendingMessages(sessionScope) {
9424
9655
  const client = getClient();
9656
+ const scope = strictSessionScopeFilter(sessionScope);
9425
9657
  const result = await client.execute({
9426
9658
  sql: `SELECT * FROM messages
9427
- WHERE status = 'pending' AND retry_count < ?
9659
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
9428
9660
  ORDER BY id`,
9429
- args: [MAX_RETRIES3]
9661
+ args: [MAX_RETRIES3, ...scope.args]
9430
9662
  });
9431
9663
  let delivered = 0;
9432
9664
  for (const row of result.rows) {
@@ -9445,6 +9677,7 @@ var init_messaging = __esm({
9445
9677
  "use strict";
9446
9678
  init_database();
9447
9679
  init_tmux_routing();
9680
+ init_task_scope();
9448
9681
  MAX_RETRIES3 = 10;
9449
9682
  _wsClientSend = null;
9450
9683
  }
@@ -11925,11 +12158,11 @@ init_crm_bridge();
11925
12158
 
11926
12159
  // src/lib/pipeline-router.ts
11927
12160
  init_database();
11928
- import crypto8 from "crypto";
12161
+ import crypto9 from "crypto";
11929
12162
  async function sinkConversationStore(msg, agentResponse, agentName) {
11930
12163
  try {
11931
12164
  const client = getClient();
11932
- const id = crypto8.randomUUID();
12165
+ const id = crypto9.randomUUID();
11933
12166
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
11934
12167
  await client.execute({
11935
12168
  sql: `INSERT INTO conversations
@@ -11979,7 +12212,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
11979
12212
  ].filter(Boolean).join("\n");
11980
12213
  const vector = await embed2(rawText);
11981
12214
  await writeMemory2({
11982
- id: crypto8.randomUUID(),
12215
+ id: crypto9.randomUUID(),
11983
12216
  agent_id: agentName ?? "gateway",
11984
12217
  agent_role: "gateway",
11985
12218
  session_id: `gateway-${msg.platform}`,
@@ -14660,12 +14893,12 @@ var SlackAdapter = class {
14660
14893
  // src/gateway/adapters/imessage.ts
14661
14894
  import { execFile } from "child_process";
14662
14895
  import { promisify } from "util";
14663
- import os12 from "os";
14664
- import path20 from "path";
14896
+ import os13 from "os";
14897
+ import path21 from "path";
14665
14898
  var execFileAsync = promisify(execFile);
14666
14899
  var POLL_INTERVAL_MS = 5e3;
14667
- var MESSAGES_DB_PATH = path20.join(
14668
- process.env.HOME ?? os12.homedir(),
14900
+ var MESSAGES_DB_PATH = path21.join(
14901
+ process.env.HOME ?? os13.homedir(),
14669
14902
  "Library/Messages/chat.db"
14670
14903
  );
14671
14904
  var IMessageAdapter = class {
@@ -15508,11 +15741,11 @@ async function ensureCRMContact(info) {
15508
15741
  }
15509
15742
 
15510
15743
  // src/automation/trigger-engine.ts
15511
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
15744
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
15512
15745
  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");
15746
+ import path22 from "path";
15747
+ import os14 from "os";
15748
+ var TRIGGERS_PATH = path22.join(os14.homedir(), ".exe-os", "triggers.json");
15516
15749
  var GRAPH_API_VERSION = "v21.0";
15517
15750
  function substituteTemplate(template, record) {
15518
15751
  return template.replace(
@@ -15566,9 +15799,9 @@ function evaluateConditions(conditions, record) {
15566
15799
  return conditions.every((c) => evaluateCondition(c, record));
15567
15800
  }
15568
15801
  function loadTriggers(project) {
15569
- if (!existsSync15(TRIGGERS_PATH)) return [];
15802
+ if (!existsSync17(TRIGGERS_PATH)) return [];
15570
15803
  try {
15571
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
15804
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
15572
15805
  const all = JSON.parse(raw);
15573
15806
  if (!Array.isArray(all)) return [];
15574
15807
  if (project) {