@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -25,9 +25,47 @@ 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
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
30
- import { readFileSync, existsSync, renameSync } from "fs";
67
+ import { readFile, writeFile } from "fs/promises";
68
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
31
69
  import path from "path";
32
70
  import os from "os";
33
71
  function resolveDataDir() {
@@ -35,7 +73,7 @@ function resolveDataDir() {
35
73
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
36
74
  const newDir = path.join(os.homedir(), ".exe-os");
37
75
  const legacyDir = path.join(os.homedir(), ".exe-mem");
38
- if (!existsSync(newDir) && existsSync(legacyDir)) {
76
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
39
77
  try {
40
78
  renameSync(legacyDir, newDir);
41
79
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
98
136
  }
99
137
  async function loadConfig() {
100
138
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
101
- await mkdir(dir, { recursive: true });
139
+ await ensurePrivateDir(dir);
102
140
  const configPath = path.join(dir, "config.json");
103
- if (!existsSync(configPath)) {
141
+ if (!existsSync2(configPath)) {
104
142
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
105
143
  }
106
144
  const raw = await readFile(configPath, "utf-8");
@@ -113,6 +151,7 @@ async function loadConfig() {
113
151
  `);
114
152
  try {
115
153
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
154
+ await enforcePrivateFile(configPath);
116
155
  } catch {
117
156
  }
118
157
  }
@@ -132,6 +171,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
132
171
  var init_config = __esm({
133
172
  "src/lib/config.ts"() {
134
173
  "use strict";
174
+ init_secure_files();
135
175
  EXE_AI_DIR = resolveDataDir();
136
176
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
137
177
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -278,7 +318,7 @@ var init_session_key = __esm({
278
318
 
279
319
  // src/lib/employees.ts
280
320
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
281
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
321
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
282
322
  import { execSync as execSync2 } from "child_process";
283
323
  import path2 from "path";
284
324
  import os2 from "os";
@@ -299,7 +339,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
299
339
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
300
340
  }
301
341
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
302
- if (!existsSync2(employeesPath)) return [];
342
+ if (!existsSync3(employeesPath)) return [];
303
343
  try {
304
344
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
305
345
  } catch {
@@ -337,13 +377,13 @@ var init_employees = __esm({
337
377
  });
338
378
 
339
379
  // src/lib/session-registry.ts
340
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
380
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
341
381
  import path4 from "path";
342
382
  import os3 from "os";
343
383
  function registerSession(entry) {
344
384
  const dir = path4.dirname(REGISTRY_PATH);
345
- if (!existsSync3(dir)) {
346
- mkdirSync2(dir, { recursive: true });
385
+ if (!existsSync4(dir)) {
386
+ mkdirSync3(dir, { recursive: true });
347
387
  }
348
388
  const sessions = listSessions();
349
389
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -578,10 +618,10 @@ var init_runtime_table = __esm({
578
618
  });
579
619
 
580
620
  // src/lib/agent-config.ts
581
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
621
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
582
622
  import path5 from "path";
583
623
  function loadAgentConfig() {
584
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
624
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
585
625
  try {
586
626
  return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
587
627
  } catch {
@@ -602,6 +642,7 @@ var init_agent_config = __esm({
602
642
  "use strict";
603
643
  init_config();
604
644
  init_runtime_table();
645
+ init_secure_files();
605
646
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
606
647
  DEFAULT_MODELS = {
607
648
  claude: "claude-opus-4",
@@ -620,16 +661,16 @@ __export(intercom_queue_exports, {
620
661
  queueIntercom: () => queueIntercom,
621
662
  readQueue: () => readQueue
622
663
  });
623
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
664
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
624
665
  import path6 from "path";
625
666
  import os4 from "os";
626
667
  function ensureDir() {
627
668
  const dir = path6.dirname(QUEUE_PATH);
628
- if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
669
+ if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
629
670
  }
630
671
  function readQueue() {
631
672
  try {
632
- if (!existsSync5(QUEUE_PATH)) return [];
673
+ if (!existsSync6(QUEUE_PATH)) return [];
633
674
  return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
634
675
  } catch {
635
676
  return [];
@@ -1376,13 +1417,50 @@ var init_database_adapter = __esm({
1376
1417
  }
1377
1418
  });
1378
1419
 
1420
+ // src/lib/daemon-auth.ts
1421
+ import crypto from "crypto";
1422
+ import path8 from "path";
1423
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1424
+ function normalizeToken(token) {
1425
+ if (!token) return null;
1426
+ const trimmed = token.trim();
1427
+ return trimmed.length > 0 ? trimmed : null;
1428
+ }
1429
+ function readDaemonToken() {
1430
+ try {
1431
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1432
+ return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
1433
+ } catch {
1434
+ return null;
1435
+ }
1436
+ }
1437
+ function ensureDaemonToken(seed) {
1438
+ const existing = readDaemonToken();
1439
+ if (existing) return existing;
1440
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1441
+ ensurePrivateDirSync(EXE_AI_DIR);
1442
+ writeFileSync6(DAEMON_TOKEN_PATH, `${token}
1443
+ `, "utf8");
1444
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1445
+ return token;
1446
+ }
1447
+ var DAEMON_TOKEN_PATH;
1448
+ var init_daemon_auth = __esm({
1449
+ "src/lib/daemon-auth.ts"() {
1450
+ "use strict";
1451
+ init_config();
1452
+ init_secure_files();
1453
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1454
+ }
1455
+ });
1456
+
1379
1457
  // src/lib/exe-daemon-client.ts
1380
1458
  import net from "net";
1381
1459
  import os6 from "os";
1382
1460
  import { spawn } from "child_process";
1383
1461
  import { randomUUID } from "crypto";
1384
- import { existsSync as existsSync6, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1385
- import path8 from "path";
1462
+ import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
1463
+ import path9 from "path";
1386
1464
  import { fileURLToPath } from "url";
1387
1465
  function handleData(chunk) {
1388
1466
  _buffer += chunk.toString();
@@ -1410,9 +1488,9 @@ function handleData(chunk) {
1410
1488
  }
1411
1489
  }
1412
1490
  function cleanupStaleFiles() {
1413
- if (existsSync6(PID_PATH)) {
1491
+ if (existsSync8(PID_PATH)) {
1414
1492
  try {
1415
- const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1493
+ const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
1416
1494
  if (pid > 0) {
1417
1495
  try {
1418
1496
  process.kill(pid, 0);
@@ -1433,11 +1511,11 @@ function cleanupStaleFiles() {
1433
1511
  }
1434
1512
  }
1435
1513
  function findPackageRoot() {
1436
- let dir = path8.dirname(fileURLToPath(import.meta.url));
1437
- const { root } = path8.parse(dir);
1514
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1515
+ const { root } = path9.parse(dir);
1438
1516
  while (dir !== root) {
1439
- if (existsSync6(path8.join(dir, "package.json"))) return dir;
1440
- dir = path8.dirname(dir);
1517
+ if (existsSync8(path9.join(dir, "package.json"))) return dir;
1518
+ dir = path9.dirname(dir);
1441
1519
  }
1442
1520
  return null;
1443
1521
  }
@@ -1463,16 +1541,17 @@ function spawnDaemon() {
1463
1541
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1464
1542
  return;
1465
1543
  }
1466
- const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1467
- if (!existsSync6(daemonPath)) {
1544
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1545
+ if (!existsSync8(daemonPath)) {
1468
1546
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1469
1547
  `);
1470
1548
  return;
1471
1549
  }
1472
1550
  const resolvedPath = daemonPath;
1551
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1473
1552
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1474
1553
  `);
1475
- const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1554
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1476
1555
  let stderrFd = "ignore";
1477
1556
  try {
1478
1557
  stderrFd = openSync(logPath, "a");
@@ -1490,7 +1569,8 @@ function spawnDaemon() {
1490
1569
  TMUX_PANE: void 0,
1491
1570
  // Prevents resolveExeSession() from scoping to one session
1492
1571
  EXE_DAEMON_SOCK: SOCKET_PATH,
1493
- EXE_DAEMON_PID: PID_PATH
1572
+ EXE_DAEMON_PID: PID_PATH,
1573
+ [DAEMON_TOKEN_ENV]: daemonToken
1494
1574
  }
1495
1575
  });
1496
1576
  child.unref();
@@ -1597,13 +1677,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1597
1677
  return;
1598
1678
  }
1599
1679
  const id = randomUUID();
1680
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1600
1681
  const timer = setTimeout(() => {
1601
1682
  _pending.delete(id);
1602
1683
  resolve({ error: "Request timeout" });
1603
1684
  }, timeoutMs);
1604
1685
  _pending.set(id, { resolve, timer });
1605
1686
  try {
1606
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1687
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1607
1688
  } catch {
1608
1689
  clearTimeout(timer);
1609
1690
  _pending.delete(id);
@@ -1614,17 +1695,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1614
1695
  function isClientConnected() {
1615
1696
  return _connected;
1616
1697
  }
1617
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1698
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1618
1699
  var init_exe_daemon_client = __esm({
1619
1700
  "src/lib/exe-daemon-client.ts"() {
1620
1701
  "use strict";
1621
1702
  init_config();
1622
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1623
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1624
- SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1703
+ init_daemon_auth();
1704
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1705
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1706
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1625
1707
  SPAWN_LOCK_STALE_MS = 3e4;
1626
1708
  CONNECT_TIMEOUT_MS = 15e3;
1627
1709
  REQUEST_TIMEOUT_MS = 3e4;
1710
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1628
1711
  _socket = null;
1629
1712
  _connected = false;
1630
1713
  _buffer = "";
@@ -2203,6 +2286,7 @@ async function ensureSchema() {
2203
2286
  project TEXT NOT NULL,
2204
2287
  summary TEXT NOT NULL,
2205
2288
  task_file TEXT,
2289
+ session_scope TEXT,
2206
2290
  read INTEGER NOT NULL DEFAULT 0,
2207
2291
  created_at TEXT NOT NULL
2208
2292
  );
@@ -2211,7 +2295,7 @@ async function ensureSchema() {
2211
2295
  ON notifications(read);
2212
2296
 
2213
2297
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2214
- ON notifications(agent_id);
2298
+ ON notifications(agent_id, session_scope);
2215
2299
 
2216
2300
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2217
2301
  ON notifications(task_file);
@@ -2249,6 +2333,7 @@ async function ensureSchema() {
2249
2333
  target_agent TEXT NOT NULL,
2250
2334
  target_project TEXT,
2251
2335
  target_device TEXT NOT NULL DEFAULT 'local',
2336
+ session_scope TEXT,
2252
2337
  content TEXT NOT NULL,
2253
2338
  priority TEXT DEFAULT 'normal',
2254
2339
  status TEXT DEFAULT 'pending',
@@ -2262,10 +2347,31 @@ async function ensureSchema() {
2262
2347
  );
2263
2348
 
2264
2349
  CREATE INDEX IF NOT EXISTS idx_messages_target
2265
- ON messages(target_agent, status);
2350
+ ON messages(target_agent, session_scope, status);
2266
2351
 
2267
2352
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2268
- ON messages(target_agent, from_agent, server_seq);
2353
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2354
+ `);
2355
+ try {
2356
+ await client.execute({
2357
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2358
+ args: []
2359
+ });
2360
+ } catch {
2361
+ }
2362
+ try {
2363
+ await client.execute({
2364
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2365
+ args: []
2366
+ });
2367
+ } catch {
2368
+ }
2369
+ await client.executeMultiple(`
2370
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2371
+ ON notifications(agent_id, session_scope, read, created_at);
2372
+
2373
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2374
+ ON messages(target_agent, session_scope, status, created_at);
2269
2375
  `);
2270
2376
  try {
2271
2377
  await client.execute({
@@ -2849,6 +2955,13 @@ async function ensureSchema() {
2849
2955
  } catch {
2850
2956
  }
2851
2957
  }
2958
+ try {
2959
+ await client.execute({
2960
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2961
+ args: []
2962
+ });
2963
+ } catch {
2964
+ }
2852
2965
  }
2853
2966
  async function disposeDatabase() {
2854
2967
  if (_walCheckpointTimer) {
@@ -2887,18 +3000,21 @@ var init_database = __esm({
2887
3000
  });
2888
3001
 
2889
3002
  // src/lib/license.ts
2890
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
3003
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
2891
3004
  import { randomUUID as randomUUID2 } from "crypto";
2892
- import path9 from "path";
3005
+ import { createRequire as createRequire2 } from "module";
3006
+ import { pathToFileURL as pathToFileURL2 } from "url";
3007
+ import os7 from "os";
3008
+ import path10 from "path";
2893
3009
  import { jwtVerify, importSPKI } from "jose";
2894
3010
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2895
3011
  var init_license = __esm({
2896
3012
  "src/lib/license.ts"() {
2897
3013
  "use strict";
2898
3014
  init_config();
2899
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2900
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2901
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3015
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3016
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3017
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2902
3018
  PLAN_LIMITS = {
2903
3019
  free: { devices: 1, employees: 1, memories: 5e3 },
2904
3020
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2910,12 +3026,12 @@ var init_license = __esm({
2910
3026
  });
2911
3027
 
2912
3028
  // src/lib/plan-limits.ts
2913
- import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
2914
- import path10 from "path";
3029
+ import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
3030
+ import path11 from "path";
2915
3031
  function getLicenseSync() {
2916
3032
  try {
2917
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2918
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3033
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3034
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
2919
3035
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2920
3036
  const parts = raw.token.split(".");
2921
3037
  if (parts.length !== 3) return freeLicense();
@@ -2953,8 +3069,8 @@ function assertEmployeeLimitSync(rosterPath) {
2953
3069
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2954
3070
  let count = 0;
2955
3071
  try {
2956
- if (existsSync8(filePath)) {
2957
- const raw = readFileSync9(filePath, "utf8");
3072
+ if (existsSync10(filePath)) {
3073
+ const raw = readFileSync10(filePath, "utf8");
2958
3074
  const employees = JSON.parse(raw);
2959
3075
  count = Array.isArray(employees) ? employees.length : 0;
2960
3076
  }
@@ -2983,29 +3099,30 @@ var init_plan_limits = __esm({
2983
3099
  this.name = "PlanLimitError";
2984
3100
  }
2985
3101
  };
2986
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3102
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2987
3103
  }
2988
3104
  });
2989
3105
 
2990
3106
  // src/lib/notifications.ts
2991
- import crypto from "crypto";
2992
- import path11 from "path";
2993
- import os7 from "os";
3107
+ import crypto2 from "crypto";
3108
+ import path12 from "path";
3109
+ import os8 from "os";
2994
3110
  import {
2995
- readFileSync as readFileSync10,
3111
+ readFileSync as readFileSync11,
2996
3112
  readdirSync as readdirSync2,
2997
3113
  unlinkSync as unlinkSync4,
2998
- existsSync as existsSync9,
3114
+ existsSync as existsSync11,
2999
3115
  rmdirSync
3000
3116
  } from "fs";
3001
3117
  async function writeNotification(notification) {
3002
3118
  try {
3003
3119
  const client = getClient();
3004
- const id = crypto.randomUUID();
3120
+ const id = crypto2.randomUUID();
3005
3121
  const now = (/* @__PURE__ */ new Date()).toISOString();
3122
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3006
3123
  await client.execute({
3007
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3008
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3124
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3125
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3009
3126
  args: [
3010
3127
  id,
3011
3128
  notification.agentId,
@@ -3014,6 +3131,7 @@ async function writeNotification(notification) {
3014
3131
  notification.project,
3015
3132
  notification.summary,
3016
3133
  notification.taskFile ?? null,
3134
+ sessionScope,
3017
3135
  now
3018
3136
  ]
3019
3137
  });
@@ -3022,12 +3140,14 @@ async function writeNotification(notification) {
3022
3140
  `);
3023
3141
  }
3024
3142
  }
3025
- async function markAsReadByTaskFile(taskFile) {
3143
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3026
3144
  try {
3027
3145
  const client = getClient();
3146
+ const scope = strictSessionScopeFilter(sessionScope);
3028
3147
  await client.execute({
3029
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3030
- args: [taskFile]
3148
+ sql: `UPDATE notifications SET read = 1
3149
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3150
+ args: [taskFile, ...scope.args]
3031
3151
  });
3032
3152
  } catch {
3033
3153
  }
@@ -3036,11 +3156,12 @@ var init_notifications = __esm({
3036
3156
  "src/lib/notifications.ts"() {
3037
3157
  "use strict";
3038
3158
  init_database();
3159
+ init_task_scope();
3039
3160
  }
3040
3161
  });
3041
3162
 
3042
3163
  // src/lib/session-kill-telemetry.ts
3043
- import crypto2 from "crypto";
3164
+ import crypto3 from "crypto";
3044
3165
  async function recordSessionKill(input2) {
3045
3166
  try {
3046
3167
  const client = getClient();
@@ -3050,7 +3171,7 @@ async function recordSessionKill(input2) {
3050
3171
  ticks_idle, estimated_tokens_saved)
3051
3172
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3052
3173
  args: [
3053
- crypto2.randomUUID(),
3174
+ crypto3.randomUUID(),
3054
3175
  input2.sessionName,
3055
3176
  input2.agentId,
3056
3177
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3129,12 +3250,12 @@ var init_state_bus = __esm({
3129
3250
  });
3130
3251
 
3131
3252
  // src/lib/tasks-crud.ts
3132
- import crypto3 from "crypto";
3133
- import path12 from "path";
3134
- import os8 from "os";
3253
+ import crypto4 from "crypto";
3254
+ import path13 from "path";
3255
+ import os9 from "os";
3135
3256
  import { execSync as execSync5 } from "child_process";
3136
3257
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3137
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
3258
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
3138
3259
  async function writeCheckpoint(input2) {
3139
3260
  const client = getClient();
3140
3261
  const row = await resolveTask(client, input2.taskId);
@@ -3250,7 +3371,7 @@ async function resolveTask(client, identifier, scopeSession) {
3250
3371
  }
3251
3372
  async function createTaskCore(input2) {
3252
3373
  const client = getClient();
3253
- const id = crypto3.randomUUID();
3374
+ const id = crypto4.randomUUID();
3254
3375
  const now = (/* @__PURE__ */ new Date()).toISOString();
3255
3376
  const slug = slugify(input2.title);
3256
3377
  let earlySessionScope = null;
@@ -3309,8 +3430,8 @@ ${laneWarning}` : laneWarning;
3309
3430
  }
3310
3431
  if (input2.baseDir) {
3311
3432
  try {
3312
- await mkdir3(path12.join(input2.baseDir, "exe", "output"), { recursive: true });
3313
- await mkdir3(path12.join(input2.baseDir, "exe", "research"), { recursive: true });
3433
+ await mkdir3(path13.join(input2.baseDir, "exe", "output"), { recursive: true });
3434
+ await mkdir3(path13.join(input2.baseDir, "exe", "research"), { recursive: true });
3314
3435
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
3315
3436
  await ensureGitignoreExe(input2.baseDir);
3316
3437
  } catch {
@@ -3346,13 +3467,19 @@ ${laneWarning}` : laneWarning;
3346
3467
  });
3347
3468
  if (input2.baseDir) {
3348
3469
  try {
3349
- const EXE_OS_DIR = path12.join(os8.homedir(), ".exe-os");
3350
- const mdPath = path12.join(EXE_OS_DIR, taskFile);
3351
- const mdDir = path12.dirname(mdPath);
3352
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3470
+ const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
3471
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
3472
+ const mdDir = path13.dirname(mdPath);
3473
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3353
3474
  const reviewer = input2.reviewer ?? input2.assignedBy;
3354
3475
  const mdContent = `# ${input2.title}
3355
3476
 
3477
+ ## MANDATORY: When done
3478
+
3479
+ You MUST call update_task with status "done" and a result summary when finished.
3480
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3481
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3482
+
3356
3483
  **ID:** ${id}
3357
3484
  **Status:** ${initialStatus}
3358
3485
  **Priority:** ${input2.priority}
@@ -3366,12 +3493,6 @@ ${laneWarning}` : laneWarning;
3366
3493
  ## Context
3367
3494
 
3368
3495
  ${input2.context}
3369
-
3370
- ## MANDATORY: When done
3371
-
3372
- You MUST call update_task with status "done" and a result summary when finished.
3373
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3374
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3375
3496
  `;
3376
3497
  await writeFile3(mdPath, mdContent, "utf-8");
3377
3498
  } catch (err) {
@@ -3620,7 +3741,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
3620
3741
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3621
3742
  } catch {
3622
3743
  }
3623
- if (input2.status === "done" || input2.status === "cancelled") {
3744
+ if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
3624
3745
  try {
3625
3746
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3626
3747
  clearQueueForAgent2(String(row.assigned_to));
@@ -3649,9 +3770,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3649
3770
  return { taskFile, assignedTo, assignedBy, taskSlug };
3650
3771
  }
3651
3772
  async function ensureArchitectureDoc(baseDir, projectName) {
3652
- const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3773
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
3653
3774
  try {
3654
- if (existsSync10(archPath)) return;
3775
+ if (existsSync12(archPath)) return;
3655
3776
  const template = [
3656
3777
  `# ${projectName} \u2014 System Architecture`,
3657
3778
  "",
@@ -3684,10 +3805,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3684
3805
  }
3685
3806
  }
3686
3807
  async function ensureGitignoreExe(baseDir) {
3687
- const gitignorePath = path12.join(baseDir, ".gitignore");
3808
+ const gitignorePath = path13.join(baseDir, ".gitignore");
3688
3809
  try {
3689
- if (existsSync10(gitignorePath)) {
3690
- const content = readFileSync11(gitignorePath, "utf-8");
3810
+ if (existsSync12(gitignorePath)) {
3811
+ const content = readFileSync12(gitignorePath, "utf-8");
3691
3812
  if (/^\/?exe\/?$/m.test(content)) return;
3692
3813
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3693
3814
  } else {
@@ -3718,58 +3839,42 @@ var init_tasks_crud = __esm({
3718
3839
  });
3719
3840
 
3720
3841
  // src/lib/tasks-review.ts
3721
- import path13 from "path";
3722
- import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
3842
+ import path14 from "path";
3843
+ import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
3723
3844
  async function countPendingReviews(sessionScope) {
3724
3845
  const client = getClient();
3725
- if (sessionScope) {
3726
- const result2 = await client.execute({
3727
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3728
- args: [sessionScope]
3729
- });
3730
- return Number(result2.rows[0]?.cnt) || 0;
3731
- }
3846
+ const scope = strictSessionScopeFilter(
3847
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3848
+ );
3732
3849
  const result = await client.execute({
3733
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3734
- args: []
3850
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3851
+ WHERE status = 'needs_review'${scope.sql}`,
3852
+ args: [...scope.args]
3735
3853
  });
3736
3854
  return Number(result.rows[0]?.cnt) || 0;
3737
3855
  }
3738
3856
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3739
3857
  const client = getClient();
3740
- if (sessionScope) {
3741
- const result2 = await client.execute({
3742
- sql: `SELECT COUNT(*) as cnt FROM tasks
3743
- WHERE status = 'needs_review' AND updated_at > ?
3744
- AND session_scope = ?`,
3745
- args: [sinceIso, sessionScope]
3746
- });
3747
- return Number(result2.rows[0]?.cnt) || 0;
3748
- }
3858
+ const scope = strictSessionScopeFilter(
3859
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3860
+ );
3749
3861
  const result = await client.execute({
3750
3862
  sql: `SELECT COUNT(*) as cnt FROM tasks
3751
- WHERE status = 'needs_review' AND updated_at > ?`,
3752
- args: [sinceIso]
3863
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3864
+ args: [sinceIso, ...scope.args]
3753
3865
  });
3754
3866
  return Number(result.rows[0]?.cnt) || 0;
3755
3867
  }
3756
3868
  async function listPendingReviews(limit, sessionScope) {
3757
3869
  const client = getClient();
3758
- if (sessionScope) {
3759
- const result2 = await client.execute({
3760
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3761
- WHERE status = 'needs_review'
3762
- AND session_scope = ?
3763
- ORDER BY updated_at ASC LIMIT ?`,
3764
- args: [sessionScope, limit]
3765
- });
3766
- return result2.rows;
3767
- }
3870
+ const scope = strictSessionScopeFilter(
3871
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3872
+ );
3768
3873
  const result = await client.execute({
3769
3874
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3770
- WHERE status = 'needs_review'
3875
+ WHERE status = 'needs_review'${scope.sql}
3771
3876
  ORDER BY updated_at ASC LIMIT ?`,
3772
- args: [limit]
3877
+ args: [...scope.args, limit]
3773
3878
  });
3774
3879
  return result.rows;
3775
3880
  }
@@ -3781,7 +3886,7 @@ async function cleanupOrphanedReviews() {
3781
3886
  WHERE status IN ('open', 'needs_review', 'in_progress')
3782
3887
  AND assigned_by = 'system'
3783
3888
  AND title LIKE 'Review:%'
3784
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3889
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3785
3890
  args: [now]
3786
3891
  });
3787
3892
  const r1b = await client.execute({
@@ -3900,11 +4005,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3900
4005
  );
3901
4006
  }
3902
4007
  try {
3903
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3904
- if (existsSync11(cacheDir)) {
4008
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4009
+ if (existsSync13(cacheDir)) {
3905
4010
  for (const f of readdirSync3(cacheDir)) {
3906
4011
  if (f.startsWith("review-notified-")) {
3907
- unlinkSync5(path13.join(cacheDir, f));
4012
+ unlinkSync5(path14.join(cacheDir, f));
3908
4013
  }
3909
4014
  }
3910
4015
  }
@@ -3921,11 +4026,12 @@ var init_tasks_review = __esm({
3921
4026
  init_tmux_routing();
3922
4027
  init_session_key();
3923
4028
  init_state_bus();
4029
+ init_task_scope();
3924
4030
  }
3925
4031
  });
3926
4032
 
3927
4033
  // src/lib/tasks-chain.ts
3928
- import path14 from "path";
4034
+ import path15 from "path";
3929
4035
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3930
4036
  async function cascadeUnblock(taskId, baseDir, now) {
3931
4037
  const client = getClient();
@@ -3942,7 +4048,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3942
4048
  });
3943
4049
  for (const ur of unblockedRows.rows) {
3944
4050
  try {
3945
- const ubFile = path14.join(baseDir, String(ur.task_file));
4051
+ const ubFile = path15.join(baseDir, String(ur.task_file));
3946
4052
  let ubContent = await readFile3(ubFile, "utf-8");
3947
4053
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3948
4054
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3977,7 +4083,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3977
4083
  const scScope = sessionScopeFilter();
3978
4084
  const remaining = await client.execute({
3979
4085
  sql: `SELECT COUNT(*) as cnt FROM tasks
3980
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4086
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3981
4087
  args: [parentTaskId, ...scScope.args]
3982
4088
  });
3983
4089
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4011,7 +4117,7 @@ var init_tasks_chain = __esm({
4011
4117
 
4012
4118
  // src/lib/project-name.ts
4013
4119
  import { execSync as execSync6 } from "child_process";
4014
- import path15 from "path";
4120
+ import path16 from "path";
4015
4121
  function getProjectName(cwd) {
4016
4122
  const dir = cwd ?? process.cwd();
4017
4123
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4024,7 +4130,7 @@ function getProjectName(cwd) {
4024
4130
  timeout: 2e3,
4025
4131
  stdio: ["pipe", "pipe", "pipe"]
4026
4132
  }).trim();
4027
- repoRoot = path15.dirname(gitCommonDir);
4133
+ repoRoot = path16.dirname(gitCommonDir);
4028
4134
  } catch {
4029
4135
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4030
4136
  cwd: dir,
@@ -4033,11 +4139,11 @@ function getProjectName(cwd) {
4033
4139
  stdio: ["pipe", "pipe", "pipe"]
4034
4140
  }).trim();
4035
4141
  }
4036
- _cached2 = path15.basename(repoRoot);
4142
+ _cached2 = path16.basename(repoRoot);
4037
4143
  _cachedCwd = dir;
4038
4144
  return _cached2;
4039
4145
  } catch {
4040
- _cached2 = path15.basename(dir);
4146
+ _cached2 = path16.basename(dir);
4041
4147
  _cachedCwd = dir;
4042
4148
  return _cached2;
4043
4149
  }
@@ -4180,10 +4286,10 @@ var init_tasks_notify = __esm({
4180
4286
  });
4181
4287
 
4182
4288
  // src/lib/behaviors.ts
4183
- import crypto4 from "crypto";
4289
+ import crypto5 from "crypto";
4184
4290
  async function storeBehavior(opts) {
4185
4291
  const client = getClient();
4186
- const id = crypto4.randomUUID();
4292
+ const id = crypto5.randomUUID();
4187
4293
  const now = (/* @__PURE__ */ new Date()).toISOString();
4188
4294
  await client.execute({
4189
4295
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4212,7 +4318,7 @@ __export(skill_learning_exports, {
4212
4318
  storeTrajectory: () => storeTrajectory,
4213
4319
  sweepTrajectories: () => sweepTrajectories
4214
4320
  });
4215
- import crypto5 from "crypto";
4321
+ import crypto6 from "crypto";
4216
4322
  async function extractTrajectory(taskId, agentId) {
4217
4323
  const client = getClient();
4218
4324
  const result = await client.execute({
@@ -4241,11 +4347,11 @@ async function extractTrajectory(taskId, agentId) {
4241
4347
  return signature;
4242
4348
  }
4243
4349
  function hashSignature(signature) {
4244
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4350
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4245
4351
  }
4246
4352
  async function storeTrajectory(opts) {
4247
4353
  const client = getClient();
4248
- const id = crypto5.randomUUID();
4354
+ const id = crypto6.randomUUID();
4249
4355
  const now = (/* @__PURE__ */ new Date()).toISOString();
4250
4356
  const signatureHash = hashSignature(opts.signature);
4251
4357
  await client.execute({
@@ -4510,8 +4616,8 @@ __export(tasks_exports, {
4510
4616
  updateTaskStatus: () => updateTaskStatus,
4511
4617
  writeCheckpoint: () => writeCheckpoint
4512
4618
  });
4513
- import path16 from "path";
4514
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
4619
+ import path17 from "path";
4620
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
4515
4621
  async function createTask(input2) {
4516
4622
  const result = await createTaskCore(input2);
4517
4623
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4530,12 +4636,12 @@ async function updateTask(input2) {
4530
4636
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
4531
4637
  try {
4532
4638
  const agent = String(row.assigned_to);
4533
- const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4534
- const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4639
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
4640
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
4535
4641
  if (input2.status === "in_progress") {
4536
4642
  mkdirSync6(cacheDir, { recursive: true });
4537
- writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4538
- } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
4643
+ writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4644
+ } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
4539
4645
  try {
4540
4646
  unlinkSync6(cachePath);
4541
4647
  } catch {
@@ -4543,10 +4649,10 @@ async function updateTask(input2) {
4543
4649
  }
4544
4650
  } catch {
4545
4651
  }
4546
- if (input2.status === "done") {
4652
+ if (input2.status === "done" || input2.status === "closed") {
4547
4653
  await cleanupReviewFile(row, taskFile, input2.baseDir);
4548
4654
  }
4549
- if (input2.status === "done" || input2.status === "cancelled") {
4655
+ if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
4550
4656
  try {
4551
4657
  const client = getClient();
4552
4658
  const taskTitle = String(row.title);
@@ -4562,7 +4668,7 @@ async function updateTask(input2) {
4562
4668
  if (!isCoordinatorName(assignedAgent)) {
4563
4669
  try {
4564
4670
  const draftClient = getClient();
4565
- if (input2.status === "done") {
4671
+ if (input2.status === "done" || input2.status === "closed") {
4566
4672
  await draftClient.execute({
4567
4673
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4568
4674
  args: [assignedAgent]
@@ -4579,7 +4685,7 @@ async function updateTask(input2) {
4579
4685
  try {
4580
4686
  const client = getClient();
4581
4687
  const cascaded = await client.execute({
4582
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4688
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4583
4689
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4584
4690
  args: [now, taskId]
4585
4691
  });
@@ -4592,14 +4698,14 @@ async function updateTask(input2) {
4592
4698
  } catch {
4593
4699
  }
4594
4700
  }
4595
- const isTerminal = input2.status === "done" || input2.status === "needs_review";
4701
+ const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
4596
4702
  if (isTerminal) {
4597
4703
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4598
4704
  if (!isCoordinator) {
4599
4705
  notifyTaskDone();
4600
4706
  }
4601
4707
  await markTaskNotificationsRead(taskFile);
4602
- if (input2.status === "done") {
4708
+ if (input2.status === "done" || input2.status === "closed") {
4603
4709
  try {
4604
4710
  await cascadeUnblock(taskId, input2.baseDir, now);
4605
4711
  } catch {
@@ -4619,7 +4725,7 @@ async function updateTask(input2) {
4619
4725
  }
4620
4726
  }
4621
4727
  }
4622
- if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4728
+ if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4623
4729
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4624
4730
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4625
4731
  taskId,
@@ -4991,6 +5097,7 @@ __export(tmux_routing_exports, {
4991
5097
  isEmployeeAlive: () => isEmployeeAlive,
4992
5098
  isExeSession: () => isExeSession,
4993
5099
  isSessionBusy: () => isSessionBusy,
5100
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4994
5101
  notifyParentExe: () => notifyParentExe,
4995
5102
  parseParentExe: () => parseParentExe,
4996
5103
  registerParentExe: () => registerParentExe,
@@ -5001,13 +5108,13 @@ __export(tmux_routing_exports, {
5001
5108
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5002
5109
  });
5003
5110
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5004
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync4 } from "fs";
5005
- import path17 from "path";
5006
- import os9 from "os";
5111
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
5112
+ import path18 from "path";
5113
+ import os10 from "os";
5007
5114
  import { fileURLToPath as fileURLToPath2 } from "url";
5008
5115
  import { unlinkSync as unlinkSync7 } from "fs";
5009
5116
  function spawnLockPath(sessionName) {
5010
- return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5117
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5011
5118
  }
5012
5119
  function isProcessAlive(pid) {
5013
5120
  try {
@@ -5018,13 +5125,13 @@ function isProcessAlive(pid) {
5018
5125
  }
5019
5126
  }
5020
5127
  function acquireSpawnLock2(sessionName) {
5021
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5128
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5022
5129
  mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
5023
5130
  }
5024
5131
  const lockFile = spawnLockPath(sessionName);
5025
- if (existsSync12(lockFile)) {
5132
+ if (existsSync14(lockFile)) {
5026
5133
  try {
5027
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5134
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
5028
5135
  const age = Date.now() - lock.timestamp;
5029
5136
  if (isProcessAlive(lock.pid) && age < 6e4) {
5030
5137
  return false;
@@ -5032,7 +5139,7 @@ function acquireSpawnLock2(sessionName) {
5032
5139
  } catch {
5033
5140
  }
5034
5141
  }
5035
- writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5142
+ writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5036
5143
  return true;
5037
5144
  }
5038
5145
  function releaseSpawnLock2(sessionName) {
@@ -5044,13 +5151,13 @@ function releaseSpawnLock2(sessionName) {
5044
5151
  function resolveBehaviorsExporterScript() {
5045
5152
  try {
5046
5153
  const thisFile = fileURLToPath2(import.meta.url);
5047
- const scriptPath = path17.join(
5048
- path17.dirname(thisFile),
5154
+ const scriptPath = path18.join(
5155
+ path18.dirname(thisFile),
5049
5156
  "..",
5050
5157
  "bin",
5051
5158
  "exe-export-behaviors.js"
5052
5159
  );
5053
- return existsSync12(scriptPath) ? scriptPath : null;
5160
+ return existsSync14(scriptPath) ? scriptPath : null;
5054
5161
  } catch {
5055
5162
  return null;
5056
5163
  }
@@ -5116,12 +5223,12 @@ function extractRootExe(name) {
5116
5223
  return parts.length > 0 ? parts[parts.length - 1] : null;
5117
5224
  }
5118
5225
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5119
- if (!existsSync12(SESSION_CACHE)) {
5226
+ if (!existsSync14(SESSION_CACHE)) {
5120
5227
  mkdirSync7(SESSION_CACHE, { recursive: true });
5121
5228
  }
5122
5229
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5123
- const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5124
- writeFileSync8(filePath, JSON.stringify({
5230
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5231
+ writeFileSync9(filePath, JSON.stringify({
5125
5232
  parentExe: rootExe,
5126
5233
  dispatchedBy: dispatchedBy || rootExe,
5127
5234
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5129,7 +5236,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5129
5236
  }
5130
5237
  function getParentExe(sessionKey) {
5131
5238
  try {
5132
- const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5239
+ const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5133
5240
  return data.parentExe || null;
5134
5241
  } catch {
5135
5242
  return null;
@@ -5137,8 +5244,8 @@ function getParentExe(sessionKey) {
5137
5244
  }
5138
5245
  function getDispatchedBy(sessionKey) {
5139
5246
  try {
5140
- const data = JSON.parse(readFileSync12(
5141
- path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5247
+ const data = JSON.parse(readFileSync13(
5248
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5142
5249
  "utf8"
5143
5250
  ));
5144
5251
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5208,8 +5315,8 @@ async function verifyPaneAtCapacity(sessionName) {
5208
5315
  }
5209
5316
  function readDebounceState() {
5210
5317
  try {
5211
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5212
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5318
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5319
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
5213
5320
  const state = {};
5214
5321
  for (const [key, val] of Object.entries(raw)) {
5215
5322
  if (typeof val === "number") {
@@ -5225,8 +5332,8 @@ function readDebounceState() {
5225
5332
  }
5226
5333
  function writeDebounceState(state) {
5227
5334
  try {
5228
- if (!existsSync12(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5229
- writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5335
+ if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5336
+ writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
5230
5337
  } catch {
5231
5338
  }
5232
5339
  }
@@ -5324,8 +5431,8 @@ function sendIntercom(targetSession) {
5324
5431
  try {
5325
5432
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5326
5433
  const agent = baseAgentName(rawAgent);
5327
- const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5328
- if (existsSync12(markerPath)) {
5434
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
5435
+ if (existsSync14(markerPath)) {
5329
5436
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5330
5437
  return "debounced";
5331
5438
  }
@@ -5334,8 +5441,8 @@ function sendIntercom(targetSession) {
5334
5441
  try {
5335
5442
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5336
5443
  const agent = baseAgentName(rawAgent);
5337
- const taskDir = path17.join(process.cwd(), "exe", agent);
5338
- if (existsSync12(taskDir)) {
5444
+ const taskDir = path18.join(process.cwd(), "exe", agent);
5445
+ if (existsSync14(taskDir)) {
5339
5446
  const files = readdirSync4(taskDir).filter(
5340
5447
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5341
5448
  );
@@ -5395,6 +5502,21 @@ function notifyParentExe(sessionKey) {
5395
5502
  }
5396
5503
  return true;
5397
5504
  }
5505
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5506
+ const transport = getTransport();
5507
+ try {
5508
+ const sessions = transport.listSessions();
5509
+ if (!sessions.includes(coordinatorSession)) return false;
5510
+ execSync7(
5511
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5512
+ { timeout: 3e3 }
5513
+ );
5514
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5515
+ return true;
5516
+ } catch {
5517
+ return false;
5518
+ }
5519
+ }
5398
5520
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5399
5521
  if (isCoordinatorName(employeeName)) {
5400
5522
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5468,26 +5590,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5468
5590
  const transport = getTransport();
5469
5591
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5470
5592
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5471
- const logDir = path17.join(os9.homedir(), ".exe-os", "session-logs");
5472
- const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5473
- if (!existsSync12(logDir)) {
5593
+ const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
5594
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5595
+ if (!existsSync14(logDir)) {
5474
5596
  mkdirSync7(logDir, { recursive: true });
5475
5597
  }
5476
5598
  transport.kill(sessionName);
5477
5599
  let cleanupSuffix = "";
5478
5600
  try {
5479
5601
  const thisFile = fileURLToPath2(import.meta.url);
5480
- const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5481
- if (existsSync12(cleanupScript)) {
5602
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5603
+ if (existsSync14(cleanupScript)) {
5482
5604
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5483
5605
  }
5484
5606
  } catch {
5485
5607
  }
5486
5608
  try {
5487
- const claudeJsonPath = path17.join(os9.homedir(), ".claude.json");
5609
+ const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
5488
5610
  let claudeJson = {};
5489
5611
  try {
5490
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5612
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
5491
5613
  } catch {
5492
5614
  }
5493
5615
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5495,17 +5617,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5495
5617
  const trustDir = opts?.cwd ?? projectDir;
5496
5618
  if (!projects[trustDir]) projects[trustDir] = {};
5497
5619
  projects[trustDir].hasTrustDialogAccepted = true;
5498
- writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5620
+ writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5499
5621
  } catch {
5500
5622
  }
5501
5623
  try {
5502
- const settingsDir = path17.join(os9.homedir(), ".claude", "projects");
5624
+ const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
5503
5625
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5504
- const projSettingsDir = path17.join(settingsDir, normalizedKey);
5505
- const settingsPath = path17.join(projSettingsDir, "settings.json");
5626
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
5627
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
5506
5628
  let settings = {};
5507
5629
  try {
5508
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5630
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
5509
5631
  } catch {
5510
5632
  }
5511
5633
  const perms = settings.permissions ?? {};
@@ -5534,7 +5656,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5534
5656
  perms.allow = allow;
5535
5657
  settings.permissions = perms;
5536
5658
  mkdirSync7(projSettingsDir, { recursive: true });
5537
- writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5659
+ writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5538
5660
  }
5539
5661
  } catch {
5540
5662
  }
@@ -5549,8 +5671,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5549
5671
  let behaviorsFlag = "";
5550
5672
  let legacyFallbackWarned = false;
5551
5673
  if (!useExeAgent && !useBinSymlink) {
5552
- const identityPath = path17.join(
5553
- os9.homedir(),
5674
+ const identityPath = path18.join(
5675
+ os10.homedir(),
5554
5676
  ".exe-os",
5555
5677
  "identity",
5556
5678
  `${employeeName}.md`
@@ -5559,13 +5681,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5559
5681
  const hasAgentFlag = claudeSupportsAgentFlag();
5560
5682
  if (hasAgentFlag) {
5561
5683
  identityFlag = ` --agent ${employeeName}`;
5562
- } else if (existsSync12(identityPath)) {
5684
+ } else if (existsSync14(identityPath)) {
5563
5685
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5564
5686
  legacyFallbackWarned = true;
5565
5687
  }
5566
5688
  const behaviorsFile = exportBehaviorsSync(
5567
5689
  employeeName,
5568
- path17.basename(spawnCwd),
5690
+ path18.basename(spawnCwd),
5569
5691
  sessionName
5570
5692
  );
5571
5693
  if (behaviorsFile) {
@@ -5580,16 +5702,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5580
5702
  }
5581
5703
  let sessionContextFlag = "";
5582
5704
  try {
5583
- const ctxDir = path17.join(os9.homedir(), ".exe-os", "session-cache");
5705
+ const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
5584
5706
  mkdirSync7(ctxDir, { recursive: true });
5585
- const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
5707
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
5586
5708
  const ctxContent = [
5587
5709
  `## Session Context`,
5588
5710
  `You are running in tmux session: ${sessionName}.`,
5589
5711
  `Your parent coordinator session is ${exeSession}.`,
5590
5712
  `Your employees (if any) use the -${exeSession} suffix.`
5591
5713
  ].join("\n");
5592
- writeFileSync8(ctxFile, ctxContent);
5714
+ writeFileSync9(ctxFile, ctxContent);
5593
5715
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5594
5716
  } catch {
5595
5717
  }
@@ -5666,8 +5788,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5666
5788
  transport.pipeLog(sessionName, logFile);
5667
5789
  try {
5668
5790
  const mySession = getMySession();
5669
- const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5670
- writeFileSync8(dispatchInfo, JSON.stringify({
5791
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5792
+ writeFileSync9(dispatchInfo, JSON.stringify({
5671
5793
  dispatchedBy: mySession,
5672
5794
  rootExe: exeSession,
5673
5795
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5741,15 +5863,15 @@ var init_tmux_routing = __esm({
5741
5863
  init_intercom_queue();
5742
5864
  init_plan_limits();
5743
5865
  init_employees();
5744
- SPAWN_LOCK_DIR = path17.join(os9.homedir(), ".exe-os", "spawn-locks");
5745
- SESSION_CACHE = path17.join(os9.homedir(), ".exe-os", "session-cache");
5866
+ SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
5867
+ SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
5746
5868
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5747
5869
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5748
5870
  VERIFY_PANE_LINES = 200;
5749
5871
  INTERCOM_DEBOUNCE_MS = 3e4;
5750
5872
  CODEX_DEBOUNCE_MS = 12e4;
5751
- INTERCOM_LOG2 = path17.join(os9.homedir(), ".exe-os", "intercom.log");
5752
- DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5873
+ INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
5874
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
5753
5875
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5754
5876
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5755
5877
  }
@@ -5772,6 +5894,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5772
5894
  args: [scope]
5773
5895
  };
5774
5896
  }
5897
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5898
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5899
+ if (!scope) return { sql: "", args: [] };
5900
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5901
+ return {
5902
+ sql: ` AND ${col} = ?`,
5903
+ args: [scope]
5904
+ };
5905
+ }
5775
5906
  var init_task_scope = __esm({
5776
5907
  "src/lib/task-scope.ts"() {
5777
5908
  "use strict";
@@ -5790,14 +5921,14 @@ var init_memory = __esm({
5790
5921
 
5791
5922
  // src/lib/keychain.ts
5792
5923
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5793
- import { existsSync as existsSync13 } from "fs";
5794
- import path18 from "path";
5795
- import os10 from "os";
5924
+ import { existsSync as existsSync15 } from "fs";
5925
+ import path19 from "path";
5926
+ import os11 from "os";
5796
5927
  function getKeyDir() {
5797
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os10.homedir(), ".exe-os");
5928
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
5798
5929
  }
5799
5930
  function getKeyPath() {
5800
- return path18.join(getKeyDir(), "master.key");
5931
+ return path19.join(getKeyDir(), "master.key");
5801
5932
  }
5802
5933
  async function tryKeytar() {
5803
5934
  try {
@@ -5818,9 +5949,9 @@ async function getMasterKey() {
5818
5949
  }
5819
5950
  }
5820
5951
  const keyPath = getKeyPath();
5821
- if (!existsSync13(keyPath)) {
5952
+ if (!existsSync15(keyPath)) {
5822
5953
  process.stderr.write(
5823
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5954
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5824
5955
  `
5825
5956
  );
5826
5957
  return null;
@@ -5850,6 +5981,7 @@ var shard_manager_exports = {};
5850
5981
  __export(shard_manager_exports, {
5851
5982
  disposeShards: () => disposeShards,
5852
5983
  ensureShardSchema: () => ensureShardSchema,
5984
+ getOpenShardCount: () => getOpenShardCount,
5853
5985
  getReadyShardClient: () => getReadyShardClient,
5854
5986
  getShardClient: () => getShardClient,
5855
5987
  getShardsDir: () => getShardsDir,
@@ -5858,15 +5990,18 @@ __export(shard_manager_exports, {
5858
5990
  listShards: () => listShards,
5859
5991
  shardExists: () => shardExists
5860
5992
  });
5861
- import path19 from "path";
5862
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
5993
+ import path20 from "path";
5994
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
5863
5995
  import { createClient as createClient2 } from "@libsql/client";
5864
5996
  function initShardManager(encryptionKey) {
5865
5997
  _encryptionKey = encryptionKey;
5866
- if (!existsSync14(SHARDS_DIR)) {
5998
+ if (!existsSync16(SHARDS_DIR)) {
5867
5999
  mkdirSync8(SHARDS_DIR, { recursive: true });
5868
6000
  }
5869
6001
  _shardingEnabled = true;
6002
+ if (_evictionTimer) clearInterval(_evictionTimer);
6003
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6004
+ _evictionTimer.unref();
5870
6005
  }
5871
6006
  function isShardingEnabled() {
5872
6007
  return _shardingEnabled;
@@ -5883,21 +6018,28 @@ function getShardClient(projectName) {
5883
6018
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5884
6019
  }
5885
6020
  const cached = _shards.get(safeName);
5886
- if (cached) return cached;
5887
- const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
6021
+ if (cached) {
6022
+ _shardLastAccess.set(safeName, Date.now());
6023
+ return cached;
6024
+ }
6025
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6026
+ evictLRU();
6027
+ }
6028
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
5888
6029
  const client = createClient2({
5889
6030
  url: `file:${dbPath}`,
5890
6031
  encryptionKey: _encryptionKey
5891
6032
  });
5892
6033
  _shards.set(safeName, client);
6034
+ _shardLastAccess.set(safeName, Date.now());
5893
6035
  return client;
5894
6036
  }
5895
6037
  function shardExists(projectName) {
5896
6038
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5897
- return existsSync14(path19.join(SHARDS_DIR, `${safeName}.db`));
6039
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
5898
6040
  }
5899
6041
  function listShards() {
5900
- if (!existsSync14(SHARDS_DIR)) return [];
6042
+ if (!existsSync16(SHARDS_DIR)) return [];
5901
6043
  return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5902
6044
  }
5903
6045
  async function ensureShardSchema(client) {
@@ -5949,6 +6091,8 @@ async function ensureShardSchema(client) {
5949
6091
  for (const col of [
5950
6092
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5951
6093
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6094
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6095
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5952
6096
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5953
6097
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5954
6098
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6086,21 +6230,69 @@ async function getReadyShardClient(projectName) {
6086
6230
  await ensureShardSchema(client);
6087
6231
  return client;
6088
6232
  }
6233
+ function evictLRU() {
6234
+ let oldest = null;
6235
+ let oldestTime = Infinity;
6236
+ for (const [name, time] of _shardLastAccess) {
6237
+ if (time < oldestTime) {
6238
+ oldestTime = time;
6239
+ oldest = name;
6240
+ }
6241
+ }
6242
+ if (oldest) {
6243
+ const client = _shards.get(oldest);
6244
+ if (client) {
6245
+ client.close();
6246
+ }
6247
+ _shards.delete(oldest);
6248
+ _shardLastAccess.delete(oldest);
6249
+ }
6250
+ }
6251
+ function evictIdleShards() {
6252
+ const now = Date.now();
6253
+ const toEvict = [];
6254
+ for (const [name, lastAccess] of _shardLastAccess) {
6255
+ if (now - lastAccess > SHARD_IDLE_MS) {
6256
+ toEvict.push(name);
6257
+ }
6258
+ }
6259
+ for (const name of toEvict) {
6260
+ const client = _shards.get(name);
6261
+ if (client) {
6262
+ client.close();
6263
+ }
6264
+ _shards.delete(name);
6265
+ _shardLastAccess.delete(name);
6266
+ }
6267
+ }
6268
+ function getOpenShardCount() {
6269
+ return _shards.size;
6270
+ }
6089
6271
  function disposeShards() {
6272
+ if (_evictionTimer) {
6273
+ clearInterval(_evictionTimer);
6274
+ _evictionTimer = null;
6275
+ }
6090
6276
  for (const [, client] of _shards) {
6091
6277
  client.close();
6092
6278
  }
6093
6279
  _shards.clear();
6280
+ _shardLastAccess.clear();
6094
6281
  _shardingEnabled = false;
6095
6282
  _encryptionKey = null;
6096
6283
  }
6097
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6284
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
6098
6285
  var init_shard_manager = __esm({
6099
6286
  "src/lib/shard-manager.ts"() {
6100
6287
  "use strict";
6101
6288
  init_config();
6102
- SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6289
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6290
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6291
+ MAX_OPEN_SHARDS = 10;
6292
+ EVICTION_INTERVAL_MS = 60 * 1e3;
6103
6293
  _shards = /* @__PURE__ */ new Map();
6294
+ _shardLastAccess = /* @__PURE__ */ new Map();
6295
+ _evictionTimer = null;
6104
6296
  _encryptionKey = null;
6105
6297
  _shardingEnabled = false;
6106
6298
  }
@@ -6864,13 +7056,13 @@ var init_store = __esm({
6864
7056
  });
6865
7057
 
6866
7058
  // src/adapters/claude/hooks/pre-compact.ts
6867
- import crypto6 from "crypto";
7059
+ import crypto7 from "crypto";
6868
7060
 
6869
7061
  // src/lib/active-agent.ts
6870
7062
  init_config();
6871
7063
  init_session_key();
6872
7064
  init_employees();
6873
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
7065
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6874
7066
  import { execSync as execSync3 } from "child_process";
6875
7067
  import path3 from "path";
6876
7068
  var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
@@ -7037,7 +7229,7 @@ ${taskLines}`);
7037
7229
  recoveryLines.push(`Files: ${lastCheckpoint.files_touched.join(", ")}`);
7038
7230
  }
7039
7231
  await writeMemory2({
7040
- id: crypto6.randomUUID(),
7232
+ id: crypto7.randomUUID(),
7041
7233
  agent_id: agent.agentId,
7042
7234
  agent_role: agent.agentRole,
7043
7235
  session_id: payload.session_id,