@askexenow/exe-os 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1411 -953
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +913 -543
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +418 -262
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +793 -485
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +566 -357
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +530 -319
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +547 -336
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3442 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +534 -323
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +664 -431
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +1049 -680
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +448 -371
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
@@ -25,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 path2 from "path";
32
70
  import os2 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 = path2.join(os2.homedir(), ".exe-os");
37
75
  const legacyDir = path2.join(os2.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 = path2.join(dir, "config.json");
103
- if (!existsSync(configPath)) {
141
+ if (!existsSync2(configPath)) {
104
142
  return { ...DEFAULT_CONFIG, dbPath: path2.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 = path2.join(EXE_AI_DIR, "memories.db");
137
177
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -210,7 +250,7 @@ var init_config = __esm({
210
250
 
211
251
  // src/lib/employees.ts
212
252
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
213
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
253
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
214
254
  import { execSync } from "child_process";
215
255
  import path3 from "path";
216
256
  import os3 from "os";
@@ -231,7 +271,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
231
271
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
232
272
  }
233
273
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
234
- if (!existsSync2(employeesPath)) return [];
274
+ if (!existsSync3(employeesPath)) return [];
235
275
  try {
236
276
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
237
277
  } catch {
@@ -275,14 +315,14 @@ __export(session_registry_exports, {
275
315
  pruneStaleSessions: () => pruneStaleSessions,
276
316
  registerSession: () => registerSession
277
317
  });
278
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
318
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
279
319
  import { execSync as execSync2 } from "child_process";
280
320
  import path4 from "path";
281
321
  import os4 from "os";
282
322
  function registerSession(entry) {
283
323
  const dir = path4.dirname(REGISTRY_PATH);
284
- if (!existsSync3(dir)) {
285
- mkdirSync(dir, { recursive: true });
324
+ if (!existsSync4(dir)) {
325
+ mkdirSync2(dir, { recursive: true });
286
326
  }
287
327
  const sessions = listSessions();
288
328
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -614,10 +654,10 @@ var init_runtime_table = __esm({
614
654
  });
615
655
 
616
656
  // src/lib/agent-config.ts
617
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
657
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
618
658
  import path5 from "path";
619
659
  function loadAgentConfig() {
620
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
660
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
621
661
  try {
622
662
  return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
623
663
  } catch {
@@ -638,6 +678,7 @@ var init_agent_config = __esm({
638
678
  "use strict";
639
679
  init_config();
640
680
  init_runtime_table();
681
+ init_secure_files();
641
682
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
642
683
  DEFAULT_MODELS = {
643
684
  claude: "claude-opus-4",
@@ -656,16 +697,16 @@ __export(intercom_queue_exports, {
656
697
  queueIntercom: () => queueIntercom,
657
698
  readQueue: () => readQueue
658
699
  });
659
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
700
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
660
701
  import path6 from "path";
661
702
  import os5 from "os";
662
703
  function ensureDir() {
663
704
  const dir = path6.dirname(QUEUE_PATH);
664
- if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
705
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
665
706
  }
666
707
  function readQueue() {
667
708
  try {
668
- if (!existsSync5(QUEUE_PATH)) return [];
709
+ if (!existsSync6(QUEUE_PATH)) return [];
669
710
  return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
670
711
  } catch {
671
712
  return [];
@@ -1412,13 +1453,50 @@ var init_database_adapter = __esm({
1412
1453
  }
1413
1454
  });
1414
1455
 
1456
+ // src/lib/daemon-auth.ts
1457
+ import crypto from "crypto";
1458
+ import path8 from "path";
1459
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1460
+ function normalizeToken(token) {
1461
+ if (!token) return null;
1462
+ const trimmed = token.trim();
1463
+ return trimmed.length > 0 ? trimmed : null;
1464
+ }
1465
+ function readDaemonToken() {
1466
+ try {
1467
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1468
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1469
+ } catch {
1470
+ return null;
1471
+ }
1472
+ }
1473
+ function ensureDaemonToken(seed) {
1474
+ const existing = readDaemonToken();
1475
+ if (existing) return existing;
1476
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1477
+ ensurePrivateDirSync(EXE_AI_DIR);
1478
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1479
+ `, "utf8");
1480
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1481
+ return token;
1482
+ }
1483
+ var DAEMON_TOKEN_PATH;
1484
+ var init_daemon_auth = __esm({
1485
+ "src/lib/daemon-auth.ts"() {
1486
+ "use strict";
1487
+ init_config();
1488
+ init_secure_files();
1489
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1490
+ }
1491
+ });
1492
+
1415
1493
  // src/lib/exe-daemon-client.ts
1416
1494
  import net from "net";
1417
1495
  import os7 from "os";
1418
1496
  import { spawn } from "child_process";
1419
1497
  import { randomUUID as randomUUID2 } from "crypto";
1420
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1421
- import path8 from "path";
1498
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1499
+ import path9 from "path";
1422
1500
  import { fileURLToPath } from "url";
1423
1501
  function handleData(chunk) {
1424
1502
  _buffer += chunk.toString();
@@ -1446,9 +1524,9 @@ function handleData(chunk) {
1446
1524
  }
1447
1525
  }
1448
1526
  function cleanupStaleFiles() {
1449
- if (existsSync6(PID_PATH)) {
1527
+ if (existsSync8(PID_PATH)) {
1450
1528
  try {
1451
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1529
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1452
1530
  if (pid > 0) {
1453
1531
  try {
1454
1532
  process.kill(pid, 0);
@@ -1469,11 +1547,11 @@ function cleanupStaleFiles() {
1469
1547
  }
1470
1548
  }
1471
1549
  function findPackageRoot() {
1472
- let dir = path8.dirname(fileURLToPath(import.meta.url));
1473
- const { root } = path8.parse(dir);
1550
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1551
+ const { root } = path9.parse(dir);
1474
1552
  while (dir !== root) {
1475
- if (existsSync6(path8.join(dir, "package.json"))) return dir;
1476
- dir = path8.dirname(dir);
1553
+ if (existsSync8(path9.join(dir, "package.json"))) return dir;
1554
+ dir = path9.dirname(dir);
1477
1555
  }
1478
1556
  return null;
1479
1557
  }
@@ -1499,16 +1577,17 @@ function spawnDaemon() {
1499
1577
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1500
1578
  return;
1501
1579
  }
1502
- const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1503
- if (!existsSync6(daemonPath)) {
1580
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1581
+ if (!existsSync8(daemonPath)) {
1504
1582
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1505
1583
  `);
1506
1584
  return;
1507
1585
  }
1508
1586
  const resolvedPath = daemonPath;
1587
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1509
1588
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1510
1589
  `);
1511
- const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1590
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1512
1591
  let stderrFd = "ignore";
1513
1592
  try {
1514
1593
  stderrFd = openSync(logPath, "a");
@@ -1526,7 +1605,8 @@ function spawnDaemon() {
1526
1605
  TMUX_PANE: void 0,
1527
1606
  // Prevents resolveExeSession() from scoping to one session
1528
1607
  EXE_DAEMON_SOCK: SOCKET_PATH,
1529
- EXE_DAEMON_PID: PID_PATH
1608
+ EXE_DAEMON_PID: PID_PATH,
1609
+ [DAEMON_TOKEN_ENV]: daemonToken
1530
1610
  }
1531
1611
  });
1532
1612
  child.unref();
@@ -1633,13 +1713,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1633
1713
  return;
1634
1714
  }
1635
1715
  const id = randomUUID2();
1716
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1636
1717
  const timer = setTimeout(() => {
1637
1718
  _pending.delete(id);
1638
1719
  resolve({ error: "Request timeout" });
1639
1720
  }, timeoutMs);
1640
1721
  _pending.set(id, { resolve, timer });
1641
1722
  try {
1642
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1723
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1643
1724
  } catch {
1644
1725
  clearTimeout(timer);
1645
1726
  _pending.delete(id);
@@ -1650,17 +1731,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1650
1731
  function isClientConnected() {
1651
1732
  return _connected;
1652
1733
  }
1653
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1734
+ 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;
1654
1735
  var init_exe_daemon_client = __esm({
1655
1736
  "src/lib/exe-daemon-client.ts"() {
1656
1737
  "use strict";
1657
1738
  init_config();
1658
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1659
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1660
- SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1739
+ init_daemon_auth();
1740
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1741
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1742
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1661
1743
  SPAWN_LOCK_STALE_MS = 3e4;
1662
1744
  CONNECT_TIMEOUT_MS = 15e3;
1663
1745
  REQUEST_TIMEOUT_MS = 3e4;
1746
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1664
1747
  _socket = null;
1665
1748
  _connected = false;
1666
1749
  _buffer = "";
@@ -2239,6 +2322,7 @@ async function ensureSchema() {
2239
2322
  project TEXT NOT NULL,
2240
2323
  summary TEXT NOT NULL,
2241
2324
  task_file TEXT,
2325
+ session_scope TEXT,
2242
2326
  read INTEGER NOT NULL DEFAULT 0,
2243
2327
  created_at TEXT NOT NULL
2244
2328
  );
@@ -2247,7 +2331,7 @@ async function ensureSchema() {
2247
2331
  ON notifications(read);
2248
2332
 
2249
2333
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2250
- ON notifications(agent_id);
2334
+ ON notifications(agent_id, session_scope);
2251
2335
 
2252
2336
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2253
2337
  ON notifications(task_file);
@@ -2285,6 +2369,7 @@ async function ensureSchema() {
2285
2369
  target_agent TEXT NOT NULL,
2286
2370
  target_project TEXT,
2287
2371
  target_device TEXT NOT NULL DEFAULT 'local',
2372
+ session_scope TEXT,
2288
2373
  content TEXT NOT NULL,
2289
2374
  priority TEXT DEFAULT 'normal',
2290
2375
  status TEXT DEFAULT 'pending',
@@ -2298,10 +2383,31 @@ async function ensureSchema() {
2298
2383
  );
2299
2384
 
2300
2385
  CREATE INDEX IF NOT EXISTS idx_messages_target
2301
- ON messages(target_agent, status);
2386
+ ON messages(target_agent, session_scope, status);
2302
2387
 
2303
2388
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2304
- ON messages(target_agent, from_agent, server_seq);
2389
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2390
+ `);
2391
+ try {
2392
+ await client.execute({
2393
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2394
+ args: []
2395
+ });
2396
+ } catch {
2397
+ }
2398
+ try {
2399
+ await client.execute({
2400
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2401
+ args: []
2402
+ });
2403
+ } catch {
2404
+ }
2405
+ await client.executeMultiple(`
2406
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2407
+ ON notifications(agent_id, session_scope, read, created_at);
2408
+
2409
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2410
+ ON messages(target_agent, session_scope, status, created_at);
2305
2411
  `);
2306
2412
  try {
2307
2413
  await client.execute({
@@ -2885,6 +2991,13 @@ async function ensureSchema() {
2885
2991
  } catch {
2886
2992
  }
2887
2993
  }
2994
+ try {
2995
+ await client.execute({
2996
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2997
+ args: []
2998
+ });
2999
+ } catch {
3000
+ }
2888
3001
  }
2889
3002
  async function disposeDatabase() {
2890
3003
  if (_walCheckpointTimer) {
@@ -2923,18 +3036,21 @@ var init_database = __esm({
2923
3036
  });
2924
3037
 
2925
3038
  // src/lib/license.ts
2926
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3039
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2927
3040
  import { randomUUID as randomUUID3 } from "crypto";
2928
- import path9 from "path";
3041
+ import { createRequire as createRequire2 } from "module";
3042
+ import { pathToFileURL as pathToFileURL2 } from "url";
3043
+ import os8 from "os";
3044
+ import path10 from "path";
2929
3045
  import { jwtVerify, importSPKI } from "jose";
2930
3046
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2931
3047
  var init_license = __esm({
2932
3048
  "src/lib/license.ts"() {
2933
3049
  "use strict";
2934
3050
  init_config();
2935
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2936
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2937
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3051
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3052
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3053
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2938
3054
  PLAN_LIMITS = {
2939
3055
  free: { devices: 1, employees: 1, memories: 5e3 },
2940
3056
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2946,12 +3062,12 @@ var init_license = __esm({
2946
3062
  });
2947
3063
 
2948
3064
  // src/lib/plan-limits.ts
2949
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2950
- import path10 from "path";
3065
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3066
+ import path11 from "path";
2951
3067
  function getLicenseSync() {
2952
3068
  try {
2953
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2954
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3069
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3070
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2955
3071
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2956
3072
  const parts = raw.token.split(".");
2957
3073
  if (parts.length !== 3) return freeLicense();
@@ -2989,8 +3105,8 @@ function assertEmployeeLimitSync(rosterPath) {
2989
3105
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2990
3106
  let count = 0;
2991
3107
  try {
2992
- if (existsSync8(filePath)) {
2993
- const raw = readFileSync8(filePath, "utf8");
3108
+ if (existsSync10(filePath)) {
3109
+ const raw = readFileSync9(filePath, "utf8");
2994
3110
  const employees = JSON.parse(raw);
2995
3111
  count = Array.isArray(employees) ? employees.length : 0;
2996
3112
  }
@@ -3019,29 +3135,69 @@ var init_plan_limits = __esm({
3019
3135
  this.name = "PlanLimitError";
3020
3136
  }
3021
3137
  };
3022
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3138
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
3139
+ }
3140
+ });
3141
+
3142
+ // src/lib/task-scope.ts
3143
+ var task_scope_exports = {};
3144
+ __export(task_scope_exports, {
3145
+ getCurrentSessionScope: () => getCurrentSessionScope,
3146
+ sessionScopeFilter: () => sessionScopeFilter,
3147
+ strictSessionScopeFilter: () => strictSessionScopeFilter
3148
+ });
3149
+ function getCurrentSessionScope() {
3150
+ try {
3151
+ return resolveExeSession();
3152
+ } catch {
3153
+ return null;
3154
+ }
3155
+ }
3156
+ function sessionScopeFilter(sessionScope, tableAlias) {
3157
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3158
+ if (!scope) return { sql: "", args: [] };
3159
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3160
+ return {
3161
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3162
+ args: [scope]
3163
+ };
3164
+ }
3165
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
3166
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3167
+ if (!scope) return { sql: "", args: [] };
3168
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3169
+ return {
3170
+ sql: ` AND ${col} = ?`,
3171
+ args: [scope]
3172
+ };
3173
+ }
3174
+ var init_task_scope = __esm({
3175
+ "src/lib/task-scope.ts"() {
3176
+ "use strict";
3177
+ init_tmux_routing();
3023
3178
  }
3024
3179
  });
3025
3180
 
3026
3181
  // src/lib/notifications.ts
3027
- import crypto from "crypto";
3028
- import path11 from "path";
3029
- import os8 from "os";
3182
+ import crypto2 from "crypto";
3183
+ import path12 from "path";
3184
+ import os9 from "os";
3030
3185
  import {
3031
- readFileSync as readFileSync9,
3186
+ readFileSync as readFileSync10,
3032
3187
  readdirSync,
3033
3188
  unlinkSync as unlinkSync3,
3034
- existsSync as existsSync9,
3189
+ existsSync as existsSync11,
3035
3190
  rmdirSync
3036
3191
  } from "fs";
3037
3192
  async function writeNotification(notification) {
3038
3193
  try {
3039
3194
  const client = getClient();
3040
- const id = crypto.randomUUID();
3195
+ const id = crypto2.randomUUID();
3041
3196
  const now = (/* @__PURE__ */ new Date()).toISOString();
3197
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3042
3198
  await client.execute({
3043
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3044
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3199
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3200
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3045
3201
  args: [
3046
3202
  id,
3047
3203
  notification.agentId,
@@ -3050,6 +3206,7 @@ async function writeNotification(notification) {
3050
3206
  notification.project,
3051
3207
  notification.summary,
3052
3208
  notification.taskFile ?? null,
3209
+ sessionScope,
3053
3210
  now
3054
3211
  ]
3055
3212
  });
@@ -3058,12 +3215,14 @@ async function writeNotification(notification) {
3058
3215
  `);
3059
3216
  }
3060
3217
  }
3061
- async function markAsReadByTaskFile(taskFile) {
3218
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3062
3219
  try {
3063
3220
  const client = getClient();
3221
+ const scope = strictSessionScopeFilter(sessionScope);
3064
3222
  await client.execute({
3065
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3066
- args: [taskFile]
3223
+ sql: `UPDATE notifications SET read = 1
3224
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3225
+ args: [taskFile, ...scope.args]
3067
3226
  });
3068
3227
  } catch {
3069
3228
  }
@@ -3072,11 +3231,12 @@ var init_notifications = __esm({
3072
3231
  "src/lib/notifications.ts"() {
3073
3232
  "use strict";
3074
3233
  init_database();
3234
+ init_task_scope();
3075
3235
  }
3076
3236
  });
3077
3237
 
3078
3238
  // src/lib/session-kill-telemetry.ts
3079
- import crypto2 from "crypto";
3239
+ import crypto3 from "crypto";
3080
3240
  async function recordSessionKill(input) {
3081
3241
  try {
3082
3242
  const client = getClient();
@@ -3086,7 +3246,7 @@ async function recordSessionKill(input) {
3086
3246
  ticks_idle, estimated_tokens_saved)
3087
3247
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3088
3248
  args: [
3089
- crypto2.randomUUID(),
3249
+ crypto3.randomUUID(),
3090
3250
  input.sessionName,
3091
3251
  input.agentId,
3092
3252
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3109,35 +3269,6 @@ var init_session_kill_telemetry = __esm({
3109
3269
  }
3110
3270
  });
3111
3271
 
3112
- // src/lib/task-scope.ts
3113
- var task_scope_exports = {};
3114
- __export(task_scope_exports, {
3115
- getCurrentSessionScope: () => getCurrentSessionScope,
3116
- sessionScopeFilter: () => sessionScopeFilter
3117
- });
3118
- function getCurrentSessionScope() {
3119
- try {
3120
- return resolveExeSession();
3121
- } catch {
3122
- return null;
3123
- }
3124
- }
3125
- function sessionScopeFilter(sessionScope, tableAlias) {
3126
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3127
- if (!scope) return { sql: "", args: [] };
3128
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3129
- return {
3130
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3131
- args: [scope]
3132
- };
3133
- }
3134
- var init_task_scope = __esm({
3135
- "src/lib/task-scope.ts"() {
3136
- "use strict";
3137
- init_tmux_routing();
3138
- }
3139
- });
3140
-
3141
3272
  // src/lib/state-bus.ts
3142
3273
  var StateBus, orgBus;
3143
3274
  var init_state_bus = __esm({
@@ -3193,13 +3324,117 @@ var init_state_bus = __esm({
3193
3324
  }
3194
3325
  });
3195
3326
 
3196
- // src/lib/tasks-crud.ts
3197
- import crypto3 from "crypto";
3198
- import path12 from "path";
3199
- import os9 from "os";
3327
+ // src/lib/project-name.ts
3200
3328
  import { execSync as execSync5 } from "child_process";
3329
+ import path13 from "path";
3330
+ function getProjectName(cwd) {
3331
+ const dir = cwd ?? process.cwd();
3332
+ if (_cached2 && _cachedCwd === dir) return _cached2;
3333
+ try {
3334
+ let repoRoot;
3335
+ try {
3336
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
3337
+ cwd: dir,
3338
+ encoding: "utf8",
3339
+ timeout: 2e3,
3340
+ stdio: ["pipe", "pipe", "pipe"]
3341
+ }).trim();
3342
+ repoRoot = path13.dirname(gitCommonDir);
3343
+ } catch {
3344
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
3345
+ cwd: dir,
3346
+ encoding: "utf8",
3347
+ timeout: 2e3,
3348
+ stdio: ["pipe", "pipe", "pipe"]
3349
+ }).trim();
3350
+ }
3351
+ _cached2 = path13.basename(repoRoot);
3352
+ _cachedCwd = dir;
3353
+ return _cached2;
3354
+ } catch {
3355
+ _cached2 = path13.basename(dir);
3356
+ _cachedCwd = dir;
3357
+ return _cached2;
3358
+ }
3359
+ }
3360
+ var _cached2, _cachedCwd;
3361
+ var init_project_name = __esm({
3362
+ "src/lib/project-name.ts"() {
3363
+ "use strict";
3364
+ _cached2 = null;
3365
+ _cachedCwd = null;
3366
+ }
3367
+ });
3368
+
3369
+ // src/lib/session-scope.ts
3370
+ var session_scope_exports = {};
3371
+ __export(session_scope_exports, {
3372
+ assertSessionScope: () => assertSessionScope,
3373
+ findSessionForProject: () => findSessionForProject,
3374
+ getSessionProject: () => getSessionProject
3375
+ });
3376
+ function getSessionProject(sessionName) {
3377
+ const sessions = listSessions();
3378
+ const entry = sessions.find((s) => s.windowName === sessionName);
3379
+ if (!entry) return null;
3380
+ const parts = entry.projectDir.split("/").filter(Boolean);
3381
+ return parts[parts.length - 1] ?? null;
3382
+ }
3383
+ function findSessionForProject(projectName) {
3384
+ const sessions = listSessions();
3385
+ for (const s of sessions) {
3386
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
3387
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
3388
+ }
3389
+ return null;
3390
+ }
3391
+ function assertSessionScope(actionType, targetProject) {
3392
+ try {
3393
+ const currentProject = getProjectName();
3394
+ const exeSession = resolveExeSession();
3395
+ if (!exeSession) {
3396
+ return { allowed: true, reason: "no_session" };
3397
+ }
3398
+ if (currentProject === targetProject) {
3399
+ return {
3400
+ allowed: true,
3401
+ reason: "same_session",
3402
+ currentProject,
3403
+ targetProject
3404
+ };
3405
+ }
3406
+ process.stderr.write(
3407
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3408
+ `
3409
+ );
3410
+ return {
3411
+ allowed: false,
3412
+ reason: "cross_session_denied",
3413
+ currentProject,
3414
+ targetProject,
3415
+ targetSession: findSessionForProject(targetProject)?.windowName
3416
+ };
3417
+ } catch {
3418
+ return { allowed: true, reason: "no_session" };
3419
+ }
3420
+ }
3421
+ var init_session_scope = __esm({
3422
+ "src/lib/session-scope.ts"() {
3423
+ "use strict";
3424
+ init_session_registry();
3425
+ init_project_name();
3426
+ init_tmux_routing();
3427
+ init_employees();
3428
+ }
3429
+ });
3430
+
3431
+ // src/lib/tasks-crud.ts
3432
+ import crypto4 from "crypto";
3433
+ import path14 from "path";
3434
+ import os10 from "os";
3435
+ import { execSync as execSync6 } from "child_process";
3201
3436
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3202
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3437
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3203
3438
  async function writeCheckpoint(input) {
3204
3439
  const client = getClient();
3205
3440
  const row = await resolveTask(client, input.taskId);
@@ -3315,13 +3550,28 @@ async function resolveTask(client, identifier, scopeSession) {
3315
3550
  }
3316
3551
  async function createTaskCore(input) {
3317
3552
  const client = getClient();
3318
- const id = crypto3.randomUUID();
3553
+ const id = crypto4.randomUUID();
3319
3554
  const now = (/* @__PURE__ */ new Date()).toISOString();
3320
3555
  const slug = slugify(input.title);
3321
3556
  let earlySessionScope = null;
3557
+ let scopeMismatchWarning;
3322
3558
  try {
3323
3559
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3324
- earlySessionScope = resolveExeSession2();
3560
+ const resolved = resolveExeSession2();
3561
+ if (resolved && input.projectName) {
3562
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
3563
+ const sessionProject = getSessionProject2(resolved);
3564
+ if (sessionProject && sessionProject !== input.projectName) {
3565
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
3566
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
3567
+ `);
3568
+ earlySessionScope = null;
3569
+ } else {
3570
+ earlySessionScope = resolved;
3571
+ }
3572
+ } else {
3573
+ earlySessionScope = resolved;
3574
+ }
3325
3575
  } catch {
3326
3576
  }
3327
3577
  const scope = earlySessionScope ?? "default";
@@ -3372,10 +3622,14 @@ async function createTaskCore(input) {
3372
3622
  ${laneWarning}` : laneWarning;
3373
3623
  }
3374
3624
  }
3625
+ if (scopeMismatchWarning) {
3626
+ warning = warning ? `${warning}
3627
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
3628
+ }
3375
3629
  if (input.baseDir) {
3376
3630
  try {
3377
- await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3378
- await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3631
+ await mkdir3(path14.join(input.baseDir, "exe", "output"), { recursive: true });
3632
+ await mkdir3(path14.join(input.baseDir, "exe", "research"), { recursive: true });
3379
3633
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3380
3634
  await ensureGitignoreExe(input.baseDir);
3381
3635
  } catch {
@@ -3411,13 +3665,19 @@ ${laneWarning}` : laneWarning;
3411
3665
  });
3412
3666
  if (input.baseDir) {
3413
3667
  try {
3414
- const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
3415
- const mdPath = path12.join(EXE_OS_DIR, taskFile);
3416
- const mdDir = path12.dirname(mdPath);
3417
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3668
+ const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
3669
+ const mdPath = path14.join(EXE_OS_DIR, taskFile);
3670
+ const mdDir = path14.dirname(mdPath);
3671
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3418
3672
  const reviewer = input.reviewer ?? input.assignedBy;
3419
3673
  const mdContent = `# ${input.title}
3420
3674
 
3675
+ ## MANDATORY: When done
3676
+
3677
+ You MUST call update_task with status "done" and a result summary when finished.
3678
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3679
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3680
+
3421
3681
  **ID:** ${id}
3422
3682
  **Status:** ${initialStatus}
3423
3683
  **Priority:** ${input.priority}
@@ -3431,12 +3691,6 @@ ${laneWarning}` : laneWarning;
3431
3691
  ## Context
3432
3692
 
3433
3693
  ${input.context}
3434
-
3435
- ## MANDATORY: When done
3436
-
3437
- You MUST call update_task with status "done" and a result summary when finished.
3438
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3439
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3440
3694
  `;
3441
3695
  await writeFile3(mdPath, mdContent, "utf-8");
3442
3696
  } catch (err) {
@@ -3518,14 +3772,14 @@ function isTmuxSessionAlive(identifier) {
3518
3772
  if (!identifier || identifier === "unknown") return true;
3519
3773
  try {
3520
3774
  if (identifier.startsWith("%")) {
3521
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
3775
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
3522
3776
  timeout: 2e3,
3523
3777
  encoding: "utf8",
3524
3778
  stdio: ["pipe", "pipe", "pipe"]
3525
3779
  });
3526
3780
  return output.split("\n").some((l) => l.trim() === identifier);
3527
3781
  } else {
3528
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3782
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3529
3783
  timeout: 2e3,
3530
3784
  stdio: ["pipe", "pipe", "pipe"]
3531
3785
  });
@@ -3534,7 +3788,7 @@ function isTmuxSessionAlive(identifier) {
3534
3788
  } catch {
3535
3789
  if (identifier.startsWith("%")) return true;
3536
3790
  try {
3537
- execSync5("tmux list-sessions", {
3791
+ execSync6("tmux list-sessions", {
3538
3792
  timeout: 2e3,
3539
3793
  stdio: ["pipe", "pipe", "pipe"]
3540
3794
  });
@@ -3549,12 +3803,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
3549
3803
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
3550
3804
  try {
3551
3805
  const since = new Date(taskCreatedAt).toISOString();
3552
- const branch = execSync5(
3806
+ const branch = execSync6(
3553
3807
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
3554
3808
  { encoding: "utf8", timeout: 3e3 }
3555
3809
  ).trim();
3556
3810
  const branchArg = branch && branch !== "HEAD" ? branch : "";
3557
- const commitCount = execSync5(
3811
+ const commitCount = execSync6(
3558
3812
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
3559
3813
  { encoding: "utf8", timeout: 5e3 }
3560
3814
  ).trim();
@@ -3685,7 +3939,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3685
3939
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3686
3940
  } catch {
3687
3941
  }
3688
- if (input.status === "done" || input.status === "cancelled") {
3942
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3689
3943
  try {
3690
3944
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3691
3945
  clearQueueForAgent2(String(row.assigned_to));
@@ -3714,9 +3968,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3714
3968
  return { taskFile, assignedTo, assignedBy, taskSlug };
3715
3969
  }
3716
3970
  async function ensureArchitectureDoc(baseDir, projectName) {
3717
- const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3971
+ const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
3718
3972
  try {
3719
- if (existsSync10(archPath)) return;
3973
+ if (existsSync12(archPath)) return;
3720
3974
  const template = [
3721
3975
  `# ${projectName} \u2014 System Architecture`,
3722
3976
  "",
@@ -3749,10 +4003,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3749
4003
  }
3750
4004
  }
3751
4005
  async function ensureGitignoreExe(baseDir) {
3752
- const gitignorePath = path12.join(baseDir, ".gitignore");
4006
+ const gitignorePath = path14.join(baseDir, ".gitignore");
3753
4007
  try {
3754
- if (existsSync10(gitignorePath)) {
3755
- const content = readFileSync10(gitignorePath, "utf-8");
4008
+ if (existsSync12(gitignorePath)) {
4009
+ const content = readFileSync11(gitignorePath, "utf-8");
3756
4010
  if (/^\/?exe\/?$/m.test(content)) return;
3757
4011
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3758
4012
  } else {
@@ -3783,58 +4037,42 @@ var init_tasks_crud = __esm({
3783
4037
  });
3784
4038
 
3785
4039
  // src/lib/tasks-review.ts
3786
- import path13 from "path";
3787
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4040
+ import path15 from "path";
4041
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3788
4042
  async function countPendingReviews(sessionScope) {
3789
4043
  const client = getClient();
3790
- if (sessionScope) {
3791
- const result2 = await client.execute({
3792
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3793
- args: [sessionScope]
3794
- });
3795
- return Number(result2.rows[0]?.cnt) || 0;
3796
- }
4044
+ const scope = strictSessionScopeFilter(
4045
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4046
+ );
3797
4047
  const result = await client.execute({
3798
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3799
- args: []
4048
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4049
+ WHERE status = 'needs_review'${scope.sql}`,
4050
+ args: [...scope.args]
3800
4051
  });
3801
4052
  return Number(result.rows[0]?.cnt) || 0;
3802
4053
  }
3803
4054
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3804
4055
  const client = getClient();
3805
- if (sessionScope) {
3806
- const result2 = await client.execute({
3807
- sql: `SELECT COUNT(*) as cnt FROM tasks
3808
- WHERE status = 'needs_review' AND updated_at > ?
3809
- AND session_scope = ?`,
3810
- args: [sinceIso, sessionScope]
3811
- });
3812
- return Number(result2.rows[0]?.cnt) || 0;
3813
- }
4056
+ const scope = strictSessionScopeFilter(
4057
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4058
+ );
3814
4059
  const result = await client.execute({
3815
4060
  sql: `SELECT COUNT(*) as cnt FROM tasks
3816
- WHERE status = 'needs_review' AND updated_at > ?`,
3817
- args: [sinceIso]
4061
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4062
+ args: [sinceIso, ...scope.args]
3818
4063
  });
3819
4064
  return Number(result.rows[0]?.cnt) || 0;
3820
4065
  }
3821
4066
  async function listPendingReviews(limit, sessionScope) {
3822
4067
  const client = getClient();
3823
- if (sessionScope) {
3824
- const result2 = await client.execute({
3825
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3826
- WHERE status = 'needs_review'
3827
- AND session_scope = ?
3828
- ORDER BY updated_at ASC LIMIT ?`,
3829
- args: [sessionScope, limit]
3830
- });
3831
- return result2.rows;
3832
- }
4068
+ const scope = strictSessionScopeFilter(
4069
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4070
+ );
3833
4071
  const result = await client.execute({
3834
4072
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3835
- WHERE status = 'needs_review'
4073
+ WHERE status = 'needs_review'${scope.sql}
3836
4074
  ORDER BY updated_at ASC LIMIT ?`,
3837
- args: [limit]
4075
+ args: [...scope.args, limit]
3838
4076
  });
3839
4077
  return result.rows;
3840
4078
  }
@@ -3846,7 +4084,7 @@ async function cleanupOrphanedReviews() {
3846
4084
  WHERE status IN ('open', 'needs_review', 'in_progress')
3847
4085
  AND assigned_by = 'system'
3848
4086
  AND title LIKE 'Review:%'
3849
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4087
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3850
4088
  args: [now]
3851
4089
  });
3852
4090
  const r1b = await client.execute({
@@ -3965,11 +4203,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3965
4203
  );
3966
4204
  }
3967
4205
  try {
3968
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3969
- if (existsSync11(cacheDir)) {
4206
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4207
+ if (existsSync13(cacheDir)) {
3970
4208
  for (const f of readdirSync2(cacheDir)) {
3971
4209
  if (f.startsWith("review-notified-")) {
3972
- unlinkSync4(path13.join(cacheDir, f));
4210
+ unlinkSync4(path15.join(cacheDir, f));
3973
4211
  }
3974
4212
  }
3975
4213
  }
@@ -3986,11 +4224,12 @@ var init_tasks_review = __esm({
3986
4224
  init_tmux_routing();
3987
4225
  init_session_key();
3988
4226
  init_state_bus();
4227
+ init_task_scope();
3989
4228
  }
3990
4229
  });
3991
4230
 
3992
4231
  // src/lib/tasks-chain.ts
3993
- import path14 from "path";
4232
+ import path16 from "path";
3994
4233
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3995
4234
  async function cascadeUnblock(taskId, baseDir, now) {
3996
4235
  const client = getClient();
@@ -4007,7 +4246,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4007
4246
  });
4008
4247
  for (const ur of unblockedRows.rows) {
4009
4248
  try {
4010
- const ubFile = path14.join(baseDir, String(ur.task_file));
4249
+ const ubFile = path16.join(baseDir, String(ur.task_file));
4011
4250
  let ubContent = await readFile3(ubFile, "utf-8");
4012
4251
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4013
4252
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4042,7 +4281,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4042
4281
  const scScope = sessionScopeFilter();
4043
4282
  const remaining = await client.execute({
4044
4283
  sql: `SELECT COUNT(*) as cnt FROM tasks
4045
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4284
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4046
4285
  args: [parentTaskId, ...scScope.args]
4047
4286
  });
4048
4287
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4074,110 +4313,6 @@ var init_tasks_chain = __esm({
4074
4313
  }
4075
4314
  });
4076
4315
 
4077
- // src/lib/project-name.ts
4078
- import { execSync as execSync6 } from "child_process";
4079
- import path15 from "path";
4080
- function getProjectName(cwd) {
4081
- const dir = cwd ?? process.cwd();
4082
- if (_cached2 && _cachedCwd === dir) return _cached2;
4083
- try {
4084
- let repoRoot;
4085
- try {
4086
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
4087
- cwd: dir,
4088
- encoding: "utf8",
4089
- timeout: 2e3,
4090
- stdio: ["pipe", "pipe", "pipe"]
4091
- }).trim();
4092
- repoRoot = path15.dirname(gitCommonDir);
4093
- } catch {
4094
- repoRoot = execSync6("git rev-parse --show-toplevel", {
4095
- cwd: dir,
4096
- encoding: "utf8",
4097
- timeout: 2e3,
4098
- stdio: ["pipe", "pipe", "pipe"]
4099
- }).trim();
4100
- }
4101
- _cached2 = path15.basename(repoRoot);
4102
- _cachedCwd = dir;
4103
- return _cached2;
4104
- } catch {
4105
- _cached2 = path15.basename(dir);
4106
- _cachedCwd = dir;
4107
- return _cached2;
4108
- }
4109
- }
4110
- var _cached2, _cachedCwd;
4111
- var init_project_name = __esm({
4112
- "src/lib/project-name.ts"() {
4113
- "use strict";
4114
- _cached2 = null;
4115
- _cachedCwd = null;
4116
- }
4117
- });
4118
-
4119
- // src/lib/session-scope.ts
4120
- var session_scope_exports = {};
4121
- __export(session_scope_exports, {
4122
- assertSessionScope: () => assertSessionScope,
4123
- findSessionForProject: () => findSessionForProject,
4124
- getSessionProject: () => getSessionProject
4125
- });
4126
- function getSessionProject(sessionName) {
4127
- const sessions = listSessions();
4128
- const entry = sessions.find((s) => s.windowName === sessionName);
4129
- if (!entry) return null;
4130
- const parts = entry.projectDir.split("/").filter(Boolean);
4131
- return parts[parts.length - 1] ?? null;
4132
- }
4133
- function findSessionForProject(projectName) {
4134
- const sessions = listSessions();
4135
- for (const s of sessions) {
4136
- const proj = s.projectDir.split("/").filter(Boolean).pop();
4137
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4138
- }
4139
- return null;
4140
- }
4141
- function assertSessionScope(actionType, targetProject) {
4142
- try {
4143
- const currentProject = getProjectName();
4144
- const exeSession = resolveExeSession();
4145
- if (!exeSession) {
4146
- return { allowed: true, reason: "no_session" };
4147
- }
4148
- if (currentProject === targetProject) {
4149
- return {
4150
- allowed: true,
4151
- reason: "same_session",
4152
- currentProject,
4153
- targetProject
4154
- };
4155
- }
4156
- process.stderr.write(
4157
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4158
- `
4159
- );
4160
- return {
4161
- allowed: false,
4162
- reason: "cross_session_denied",
4163
- currentProject,
4164
- targetProject,
4165
- targetSession: findSessionForProject(targetProject)?.windowName
4166
- };
4167
- } catch {
4168
- return { allowed: true, reason: "no_session" };
4169
- }
4170
- }
4171
- var init_session_scope = __esm({
4172
- "src/lib/session-scope.ts"() {
4173
- "use strict";
4174
- init_session_registry();
4175
- init_project_name();
4176
- init_tmux_routing();
4177
- init_employees();
4178
- }
4179
- });
4180
-
4181
4316
  // src/lib/tasks-notify.ts
4182
4317
  async function dispatchTaskToEmployee(input) {
4183
4318
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -4252,10 +4387,10 @@ __export(behaviors_exports, {
4252
4387
  listBehaviorsByDomain: () => listBehaviorsByDomain,
4253
4388
  storeBehavior: () => storeBehavior
4254
4389
  });
4255
- import crypto4 from "crypto";
4390
+ import crypto5 from "crypto";
4256
4391
  async function storeBehavior(opts) {
4257
4392
  const client = getClient();
4258
- const id = crypto4.randomUUID();
4393
+ const id = crypto5.randomUUID();
4259
4394
  const now = (/* @__PURE__ */ new Date()).toISOString();
4260
4395
  await client.execute({
4261
4396
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4339,7 +4474,7 @@ __export(skill_learning_exports, {
4339
4474
  storeTrajectory: () => storeTrajectory,
4340
4475
  sweepTrajectories: () => sweepTrajectories
4341
4476
  });
4342
- import crypto5 from "crypto";
4477
+ import crypto6 from "crypto";
4343
4478
  async function extractTrajectory(taskId, agentId) {
4344
4479
  const client = getClient();
4345
4480
  const result = await client.execute({
@@ -4368,11 +4503,11 @@ async function extractTrajectory(taskId, agentId) {
4368
4503
  return signature;
4369
4504
  }
4370
4505
  function hashSignature(signature) {
4371
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4506
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4372
4507
  }
4373
4508
  async function storeTrajectory(opts) {
4374
4509
  const client = getClient();
4375
- const id = crypto5.randomUUID();
4510
+ const id = crypto6.randomUUID();
4376
4511
  const now = (/* @__PURE__ */ new Date()).toISOString();
4377
4512
  const signatureHash = hashSignature(opts.signature);
4378
4513
  await client.execute({
@@ -4637,8 +4772,8 @@ __export(tasks_exports, {
4637
4772
  updateTaskStatus: () => updateTaskStatus,
4638
4773
  writeCheckpoint: () => writeCheckpoint
4639
4774
  });
4640
- import path16 from "path";
4641
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4775
+ import path17 from "path";
4776
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4642
4777
  async function createTask(input) {
4643
4778
  const result = await createTaskCore(input);
4644
4779
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4657,12 +4792,12 @@ async function updateTask(input) {
4657
4792
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4658
4793
  try {
4659
4794
  const agent = String(row.assigned_to);
4660
- const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4661
- const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4795
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
4796
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
4662
4797
  if (input.status === "in_progress") {
4663
4798
  mkdirSync5(cacheDir, { recursive: true });
4664
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4665
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4799
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4800
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4666
4801
  try {
4667
4802
  unlinkSync5(cachePath);
4668
4803
  } catch {
@@ -4670,10 +4805,10 @@ async function updateTask(input) {
4670
4805
  }
4671
4806
  } catch {
4672
4807
  }
4673
- if (input.status === "done") {
4808
+ if (input.status === "done" || input.status === "closed") {
4674
4809
  await cleanupReviewFile(row, taskFile, input.baseDir);
4675
4810
  }
4676
- if (input.status === "done" || input.status === "cancelled") {
4811
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4677
4812
  try {
4678
4813
  const client = getClient();
4679
4814
  const taskTitle = String(row.title);
@@ -4689,7 +4824,7 @@ async function updateTask(input) {
4689
4824
  if (!isCoordinatorName(assignedAgent)) {
4690
4825
  try {
4691
4826
  const draftClient = getClient();
4692
- if (input.status === "done") {
4827
+ if (input.status === "done" || input.status === "closed") {
4693
4828
  await draftClient.execute({
4694
4829
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4695
4830
  args: [assignedAgent]
@@ -4706,7 +4841,7 @@ async function updateTask(input) {
4706
4841
  try {
4707
4842
  const client = getClient();
4708
4843
  const cascaded = await client.execute({
4709
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4844
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4710
4845
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4711
4846
  args: [now, taskId]
4712
4847
  });
@@ -4719,14 +4854,14 @@ async function updateTask(input) {
4719
4854
  } catch {
4720
4855
  }
4721
4856
  }
4722
- const isTerminal = input.status === "done" || input.status === "needs_review";
4857
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4723
4858
  if (isTerminal) {
4724
4859
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4725
4860
  if (!isCoordinator) {
4726
4861
  notifyTaskDone();
4727
4862
  }
4728
4863
  await markTaskNotificationsRead(taskFile);
4729
- if (input.status === "done") {
4864
+ if (input.status === "done" || input.status === "closed") {
4730
4865
  try {
4731
4866
  await cascadeUnblock(taskId, input.baseDir, now);
4732
4867
  } catch {
@@ -4746,7 +4881,7 @@ async function updateTask(input) {
4746
4881
  }
4747
4882
  }
4748
4883
  }
4749
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4884
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4750
4885
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4751
4886
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4752
4887
  taskId,
@@ -5118,6 +5253,7 @@ __export(tmux_routing_exports, {
5118
5253
  isEmployeeAlive: () => isEmployeeAlive,
5119
5254
  isExeSession: () => isExeSession,
5120
5255
  isSessionBusy: () => isSessionBusy,
5256
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5121
5257
  notifyParentExe: () => notifyParentExe,
5122
5258
  parseParentExe: () => parseParentExe,
5123
5259
  registerParentExe: () => registerParentExe,
@@ -5128,13 +5264,13 @@ __export(tmux_routing_exports, {
5128
5264
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5129
5265
  });
5130
5266
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5131
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5132
- import path17 from "path";
5133
- import os10 from "os";
5267
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5268
+ import path18 from "path";
5269
+ import os11 from "os";
5134
5270
  import { fileURLToPath as fileURLToPath2 } from "url";
5135
5271
  import { unlinkSync as unlinkSync6 } from "fs";
5136
5272
  function spawnLockPath(sessionName) {
5137
- return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5273
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5138
5274
  }
5139
5275
  function isProcessAlive(pid) {
5140
5276
  try {
@@ -5145,13 +5281,13 @@ function isProcessAlive(pid) {
5145
5281
  }
5146
5282
  }
5147
5283
  function acquireSpawnLock2(sessionName) {
5148
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5284
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5149
5285
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5150
5286
  }
5151
5287
  const lockFile = spawnLockPath(sessionName);
5152
- if (existsSync12(lockFile)) {
5288
+ if (existsSync14(lockFile)) {
5153
5289
  try {
5154
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5290
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5155
5291
  const age = Date.now() - lock.timestamp;
5156
5292
  if (isProcessAlive(lock.pid) && age < 6e4) {
5157
5293
  return false;
@@ -5159,7 +5295,7 @@ function acquireSpawnLock2(sessionName) {
5159
5295
  } catch {
5160
5296
  }
5161
5297
  }
5162
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5298
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5163
5299
  return true;
5164
5300
  }
5165
5301
  function releaseSpawnLock2(sessionName) {
@@ -5171,13 +5307,13 @@ function releaseSpawnLock2(sessionName) {
5171
5307
  function resolveBehaviorsExporterScript() {
5172
5308
  try {
5173
5309
  const thisFile = fileURLToPath2(import.meta.url);
5174
- const scriptPath = path17.join(
5175
- path17.dirname(thisFile),
5310
+ const scriptPath = path18.join(
5311
+ path18.dirname(thisFile),
5176
5312
  "..",
5177
5313
  "bin",
5178
5314
  "exe-export-behaviors.js"
5179
5315
  );
5180
- return existsSync12(scriptPath) ? scriptPath : null;
5316
+ return existsSync14(scriptPath) ? scriptPath : null;
5181
5317
  } catch {
5182
5318
  return null;
5183
5319
  }
@@ -5243,12 +5379,12 @@ function extractRootExe(name) {
5243
5379
  return parts.length > 0 ? parts[parts.length - 1] : null;
5244
5380
  }
5245
5381
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5246
- if (!existsSync12(SESSION_CACHE)) {
5382
+ if (!existsSync14(SESSION_CACHE)) {
5247
5383
  mkdirSync6(SESSION_CACHE, { recursive: true });
5248
5384
  }
5249
5385
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5250
- const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5251
- writeFileSync7(filePath, JSON.stringify({
5386
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5387
+ writeFileSync8(filePath, JSON.stringify({
5252
5388
  parentExe: rootExe,
5253
5389
  dispatchedBy: dispatchedBy || rootExe,
5254
5390
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5256,7 +5392,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5256
5392
  }
5257
5393
  function getParentExe(sessionKey) {
5258
5394
  try {
5259
- const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5395
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5260
5396
  return data.parentExe || null;
5261
5397
  } catch {
5262
5398
  return null;
@@ -5264,8 +5400,8 @@ function getParentExe(sessionKey) {
5264
5400
  }
5265
5401
  function getDispatchedBy(sessionKey) {
5266
5402
  try {
5267
- const data = JSON.parse(readFileSync11(
5268
- path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5403
+ const data = JSON.parse(readFileSync12(
5404
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5269
5405
  "utf8"
5270
5406
  ));
5271
5407
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5335,8 +5471,8 @@ async function verifyPaneAtCapacity(sessionName) {
5335
5471
  }
5336
5472
  function readDebounceState() {
5337
5473
  try {
5338
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5339
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5474
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5475
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5340
5476
  const state = {};
5341
5477
  for (const [key, val] of Object.entries(raw)) {
5342
5478
  if (typeof val === "number") {
@@ -5352,8 +5488,8 @@ function readDebounceState() {
5352
5488
  }
5353
5489
  function writeDebounceState(state) {
5354
5490
  try {
5355
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5356
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5491
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5492
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5357
5493
  } catch {
5358
5494
  }
5359
5495
  }
@@ -5451,8 +5587,8 @@ function sendIntercom(targetSession) {
5451
5587
  try {
5452
5588
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5453
5589
  const agent = baseAgentName(rawAgent);
5454
- const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5455
- if (existsSync12(markerPath)) {
5590
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
5591
+ if (existsSync14(markerPath)) {
5456
5592
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5457
5593
  return "debounced";
5458
5594
  }
@@ -5461,8 +5597,8 @@ function sendIntercom(targetSession) {
5461
5597
  try {
5462
5598
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5463
5599
  const agent = baseAgentName(rawAgent);
5464
- const taskDir = path17.join(process.cwd(), "exe", agent);
5465
- if (existsSync12(taskDir)) {
5600
+ const taskDir = path18.join(process.cwd(), "exe", agent);
5601
+ if (existsSync14(taskDir)) {
5466
5602
  const files = readdirSync3(taskDir).filter(
5467
5603
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5468
5604
  );
@@ -5522,6 +5658,21 @@ function notifyParentExe(sessionKey) {
5522
5658
  }
5523
5659
  return true;
5524
5660
  }
5661
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5662
+ const transport = getTransport();
5663
+ try {
5664
+ const sessions = transport.listSessions();
5665
+ if (!sessions.includes(coordinatorSession)) return false;
5666
+ execSync7(
5667
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5668
+ { timeout: 3e3 }
5669
+ );
5670
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5671
+ return true;
5672
+ } catch {
5673
+ return false;
5674
+ }
5675
+ }
5525
5676
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5526
5677
  if (isCoordinatorName(employeeName)) {
5527
5678
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5595,26 +5746,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5595
5746
  const transport = getTransport();
5596
5747
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5597
5748
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5598
- const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
5599
- const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5600
- if (!existsSync12(logDir)) {
5749
+ const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
5750
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5751
+ if (!existsSync14(logDir)) {
5601
5752
  mkdirSync6(logDir, { recursive: true });
5602
5753
  }
5603
5754
  transport.kill(sessionName);
5604
5755
  let cleanupSuffix = "";
5605
5756
  try {
5606
5757
  const thisFile = fileURLToPath2(import.meta.url);
5607
- const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5608
- if (existsSync12(cleanupScript)) {
5758
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5759
+ if (existsSync14(cleanupScript)) {
5609
5760
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5610
5761
  }
5611
5762
  } catch {
5612
5763
  }
5613
5764
  try {
5614
- const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
5765
+ const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
5615
5766
  let claudeJson = {};
5616
5767
  try {
5617
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5768
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5618
5769
  } catch {
5619
5770
  }
5620
5771
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5622,17 +5773,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5622
5773
  const trustDir = opts?.cwd ?? projectDir;
5623
5774
  if (!projects[trustDir]) projects[trustDir] = {};
5624
5775
  projects[trustDir].hasTrustDialogAccepted = true;
5625
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5776
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5626
5777
  } catch {
5627
5778
  }
5628
5779
  try {
5629
- const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
5780
+ const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
5630
5781
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5631
- const projSettingsDir = path17.join(settingsDir, normalizedKey);
5632
- const settingsPath = path17.join(projSettingsDir, "settings.json");
5782
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
5783
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
5633
5784
  let settings = {};
5634
5785
  try {
5635
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5786
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5636
5787
  } catch {
5637
5788
  }
5638
5789
  const perms = settings.permissions ?? {};
@@ -5661,7 +5812,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5661
5812
  perms.allow = allow;
5662
5813
  settings.permissions = perms;
5663
5814
  mkdirSync6(projSettingsDir, { recursive: true });
5664
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5815
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5665
5816
  }
5666
5817
  } catch {
5667
5818
  }
@@ -5676,8 +5827,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5676
5827
  let behaviorsFlag = "";
5677
5828
  let legacyFallbackWarned = false;
5678
5829
  if (!useExeAgent && !useBinSymlink) {
5679
- const identityPath = path17.join(
5680
- os10.homedir(),
5830
+ const identityPath = path18.join(
5831
+ os11.homedir(),
5681
5832
  ".exe-os",
5682
5833
  "identity",
5683
5834
  `${employeeName}.md`
@@ -5686,13 +5837,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5686
5837
  const hasAgentFlag = claudeSupportsAgentFlag();
5687
5838
  if (hasAgentFlag) {
5688
5839
  identityFlag = ` --agent ${employeeName}`;
5689
- } else if (existsSync12(identityPath)) {
5840
+ } else if (existsSync14(identityPath)) {
5690
5841
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5691
5842
  legacyFallbackWarned = true;
5692
5843
  }
5693
5844
  const behaviorsFile = exportBehaviorsSync(
5694
5845
  employeeName,
5695
- path17.basename(spawnCwd),
5846
+ path18.basename(spawnCwd),
5696
5847
  sessionName
5697
5848
  );
5698
5849
  if (behaviorsFile) {
@@ -5707,16 +5858,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5707
5858
  }
5708
5859
  let sessionContextFlag = "";
5709
5860
  try {
5710
- const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
5861
+ const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
5711
5862
  mkdirSync6(ctxDir, { recursive: true });
5712
- const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
5863
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
5713
5864
  const ctxContent = [
5714
5865
  `## Session Context`,
5715
5866
  `You are running in tmux session: ${sessionName}.`,
5716
5867
  `Your parent coordinator session is ${exeSession}.`,
5717
5868
  `Your employees (if any) use the -${exeSession} suffix.`
5718
5869
  ].join("\n");
5719
- writeFileSync7(ctxFile, ctxContent);
5870
+ writeFileSync8(ctxFile, ctxContent);
5720
5871
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5721
5872
  } catch {
5722
5873
  }
@@ -5793,8 +5944,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5793
5944
  transport.pipeLog(sessionName, logFile);
5794
5945
  try {
5795
5946
  const mySession = getMySession();
5796
- const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5797
- writeFileSync7(dispatchInfo, JSON.stringify({
5947
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5948
+ writeFileSync8(dispatchInfo, JSON.stringify({
5798
5949
  dispatchedBy: mySession,
5799
5950
  rootExe: exeSession,
5800
5951
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5868,15 +6019,15 @@ var init_tmux_routing = __esm({
5868
6019
  init_intercom_queue();
5869
6020
  init_plan_limits();
5870
6021
  init_employees();
5871
- SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
5872
- SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
6022
+ SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
6023
+ SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
5873
6024
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5874
6025
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5875
6026
  VERIFY_PANE_LINES = 200;
5876
6027
  INTERCOM_DEBOUNCE_MS = 3e4;
5877
6028
  CODEX_DEBOUNCE_MS = 12e4;
5878
- INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
5879
- DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
6029
+ INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
6030
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
5880
6031
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5881
6032
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5882
6033
  }
@@ -5893,14 +6044,14 @@ var init_memory = __esm({
5893
6044
 
5894
6045
  // src/lib/keychain.ts
5895
6046
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5896
- import { existsSync as existsSync13 } from "fs";
5897
- import path18 from "path";
5898
- import os11 from "os";
6047
+ import { existsSync as existsSync15 } from "fs";
6048
+ import path19 from "path";
6049
+ import os12 from "os";
5899
6050
  function getKeyDir() {
5900
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
6051
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
5901
6052
  }
5902
6053
  function getKeyPath() {
5903
- return path18.join(getKeyDir(), "master.key");
6054
+ return path19.join(getKeyDir(), "master.key");
5904
6055
  }
5905
6056
  async function tryKeytar() {
5906
6057
  try {
@@ -5921,9 +6072,9 @@ async function getMasterKey() {
5921
6072
  }
5922
6073
  }
5923
6074
  const keyPath = getKeyPath();
5924
- if (!existsSync13(keyPath)) {
6075
+ if (!existsSync15(keyPath)) {
5925
6076
  process.stderr.write(
5926
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6077
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5927
6078
  `
5928
6079
  );
5929
6080
  return null;
@@ -5953,6 +6104,7 @@ var shard_manager_exports = {};
5953
6104
  __export(shard_manager_exports, {
5954
6105
  disposeShards: () => disposeShards,
5955
6106
  ensureShardSchema: () => ensureShardSchema,
6107
+ getOpenShardCount: () => getOpenShardCount,
5956
6108
  getReadyShardClient: () => getReadyShardClient,
5957
6109
  getShardClient: () => getShardClient,
5958
6110
  getShardsDir: () => getShardsDir,
@@ -5961,15 +6113,18 @@ __export(shard_manager_exports, {
5961
6113
  listShards: () => listShards,
5962
6114
  shardExists: () => shardExists
5963
6115
  });
5964
- import path19 from "path";
5965
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6116
+ import path20 from "path";
6117
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5966
6118
  import { createClient as createClient2 } from "@libsql/client";
5967
6119
  function initShardManager(encryptionKey) {
5968
6120
  _encryptionKey = encryptionKey;
5969
- if (!existsSync14(SHARDS_DIR)) {
6121
+ if (!existsSync16(SHARDS_DIR)) {
5970
6122
  mkdirSync7(SHARDS_DIR, { recursive: true });
5971
6123
  }
5972
6124
  _shardingEnabled = true;
6125
+ if (_evictionTimer) clearInterval(_evictionTimer);
6126
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6127
+ _evictionTimer.unref();
5973
6128
  }
5974
6129
  function isShardingEnabled() {
5975
6130
  return _shardingEnabled;
@@ -5986,21 +6141,28 @@ function getShardClient(projectName) {
5986
6141
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5987
6142
  }
5988
6143
  const cached = _shards.get(safeName);
5989
- if (cached) return cached;
5990
- const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
6144
+ if (cached) {
6145
+ _shardLastAccess.set(safeName, Date.now());
6146
+ return cached;
6147
+ }
6148
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6149
+ evictLRU();
6150
+ }
6151
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
5991
6152
  const client = createClient2({
5992
6153
  url: `file:${dbPath}`,
5993
6154
  encryptionKey: _encryptionKey
5994
6155
  });
5995
6156
  _shards.set(safeName, client);
6157
+ _shardLastAccess.set(safeName, Date.now());
5996
6158
  return client;
5997
6159
  }
5998
6160
  function shardExists(projectName) {
5999
6161
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
6000
- return existsSync14(path19.join(SHARDS_DIR, `${safeName}.db`));
6162
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
6001
6163
  }
6002
6164
  function listShards() {
6003
- if (!existsSync14(SHARDS_DIR)) return [];
6165
+ if (!existsSync16(SHARDS_DIR)) return [];
6004
6166
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6005
6167
  }
6006
6168
  async function ensureShardSchema(client) {
@@ -6052,6 +6214,8 @@ async function ensureShardSchema(client) {
6052
6214
  for (const col of [
6053
6215
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
6054
6216
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6217
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6218
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
6055
6219
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
6056
6220
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
6057
6221
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6189,21 +6353,69 @@ async function getReadyShardClient(projectName) {
6189
6353
  await ensureShardSchema(client);
6190
6354
  return client;
6191
6355
  }
6356
+ function evictLRU() {
6357
+ let oldest = null;
6358
+ let oldestTime = Infinity;
6359
+ for (const [name, time] of _shardLastAccess) {
6360
+ if (time < oldestTime) {
6361
+ oldestTime = time;
6362
+ oldest = name;
6363
+ }
6364
+ }
6365
+ if (oldest) {
6366
+ const client = _shards.get(oldest);
6367
+ if (client) {
6368
+ client.close();
6369
+ }
6370
+ _shards.delete(oldest);
6371
+ _shardLastAccess.delete(oldest);
6372
+ }
6373
+ }
6374
+ function evictIdleShards() {
6375
+ const now = Date.now();
6376
+ const toEvict = [];
6377
+ for (const [name, lastAccess] of _shardLastAccess) {
6378
+ if (now - lastAccess > SHARD_IDLE_MS) {
6379
+ toEvict.push(name);
6380
+ }
6381
+ }
6382
+ for (const name of toEvict) {
6383
+ const client = _shards.get(name);
6384
+ if (client) {
6385
+ client.close();
6386
+ }
6387
+ _shards.delete(name);
6388
+ _shardLastAccess.delete(name);
6389
+ }
6390
+ }
6391
+ function getOpenShardCount() {
6392
+ return _shards.size;
6393
+ }
6192
6394
  function disposeShards() {
6395
+ if (_evictionTimer) {
6396
+ clearInterval(_evictionTimer);
6397
+ _evictionTimer = null;
6398
+ }
6193
6399
  for (const [, client] of _shards) {
6194
6400
  client.close();
6195
6401
  }
6196
6402
  _shards.clear();
6403
+ _shardLastAccess.clear();
6197
6404
  _shardingEnabled = false;
6198
6405
  _encryptionKey = null;
6199
6406
  }
6200
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6407
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
6201
6408
  var init_shard_manager = __esm({
6202
6409
  "src/lib/shard-manager.ts"() {
6203
6410
  "use strict";
6204
6411
  init_config();
6205
- SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6412
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6413
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6414
+ MAX_OPEN_SHARDS = 10;
6415
+ EVICTION_INTERVAL_MS = 60 * 1e3;
6206
6416
  _shards = /* @__PURE__ */ new Map();
6417
+ _shardLastAccess = /* @__PURE__ */ new Map();
6418
+ _evictionTimer = null;
6207
6419
  _encryptionKey = null;
6208
6420
  _shardingEnabled = false;
6209
6421
  }
@@ -6999,8 +7211,8 @@ function findContainingChunk(filePath, snippet) {
6999
7211
  try {
7000
7212
  const ext = filePath.split(".").pop()?.toLowerCase();
7001
7213
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
7002
- const { readFileSync: readFileSync12 } = __require("fs");
7003
- const source = readFileSync12(filePath, "utf8");
7214
+ const { readFileSync: readFileSync13 } = __require("fs");
7215
+ const source = readFileSync13(filePath, "utf8");
7004
7216
  const lines = source.split("\n");
7005
7217
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
7006
7218
  let matchLine = -1;
@@ -7066,9 +7278,9 @@ function extractBash(input, response) {
7066
7278
  }
7067
7279
  function extractGrep(input, response) {
7068
7280
  const pattern = String(input.pattern ?? "");
7069
- const path20 = input.path ? String(input.path) : "";
7281
+ const path21 = input.path ? String(input.path) : "";
7070
7282
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
7071
- return `Searched for "${pattern}"${path20 ? ` in ${path20}` : ""}
7283
+ return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
7072
7284
  ${output.slice(0, MAX_OUTPUT)}`;
7073
7285
  }
7074
7286
  function extractGlob(input, response) {
@@ -7171,7 +7383,7 @@ __export(error_detector_exports, {
7171
7383
  errorFingerprint: () => errorFingerprint,
7172
7384
  isExeOsError: () => isExeOsError
7173
7385
  });
7174
- import crypto6 from "crypto";
7386
+ import crypto7 from "crypto";
7175
7387
  function isRealStderr(stderr) {
7176
7388
  const lines = stderr.trim().split("\n");
7177
7389
  const meaningful = lines.filter(
@@ -7242,7 +7454,7 @@ function classifyError(errorText) {
7242
7454
  }
7243
7455
  function errorFingerprint(toolName, errorText) {
7244
7456
  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);
7245
- return crypto6.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7457
+ return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7246
7458
  }
7247
7459
  var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
7248
7460
  var init_error_detector = __esm({