@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
@@ -320,9 +320,47 @@ var init_provider_table = __esm({
320
320
  }
321
321
  });
322
322
 
323
+ // src/lib/secure-files.ts
324
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
325
+ import { chmod, mkdir } from "fs/promises";
326
+ async function ensurePrivateDir(dirPath) {
327
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
328
+ try {
329
+ await chmod(dirPath, PRIVATE_DIR_MODE);
330
+ } catch {
331
+ }
332
+ }
333
+ function ensurePrivateDirSync(dirPath) {
334
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
335
+ try {
336
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
337
+ } catch {
338
+ }
339
+ }
340
+ async function enforcePrivateFile(filePath) {
341
+ try {
342
+ await chmod(filePath, PRIVATE_FILE_MODE);
343
+ } catch {
344
+ }
345
+ }
346
+ function enforcePrivateFileSync(filePath) {
347
+ try {
348
+ if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
349
+ } catch {
350
+ }
351
+ }
352
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
353
+ var init_secure_files = __esm({
354
+ "src/lib/secure-files.ts"() {
355
+ "use strict";
356
+ PRIVATE_DIR_MODE = 448;
357
+ PRIVATE_FILE_MODE = 384;
358
+ }
359
+ });
360
+
323
361
  // src/lib/config.ts
324
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
325
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
362
+ import { readFile, writeFile } from "fs/promises";
363
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
326
364
  import path2 from "path";
327
365
  import os2 from "os";
328
366
  function resolveDataDir() {
@@ -330,7 +368,7 @@ function resolveDataDir() {
330
368
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
331
369
  const newDir = path2.join(os2.homedir(), ".exe-os");
332
370
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
333
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
371
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
334
372
  try {
335
373
  renameSync(legacyDir, newDir);
336
374
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -393,9 +431,9 @@ function normalizeAutoUpdate(raw) {
393
431
  }
394
432
  async function loadConfig() {
395
433
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
396
- await mkdir(dir, { recursive: true });
434
+ await ensurePrivateDir(dir);
397
435
  const configPath = path2.join(dir, "config.json");
398
- if (!existsSync2(configPath)) {
436
+ if (!existsSync3(configPath)) {
399
437
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
400
438
  }
401
439
  const raw = await readFile(configPath, "utf-8");
@@ -408,6 +446,7 @@ async function loadConfig() {
408
446
  `);
409
447
  try {
410
448
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
449
+ await enforcePrivateFile(configPath);
411
450
  } catch {
412
451
  }
413
452
  }
@@ -427,6 +466,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
427
466
  var init_config = __esm({
428
467
  "src/lib/config.ts"() {
429
468
  "use strict";
469
+ init_secure_files();
430
470
  EXE_AI_DIR = resolveDataDir();
431
471
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
432
472
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -531,10 +571,10 @@ var init_runtime_table = __esm({
531
571
  });
532
572
 
533
573
  // src/lib/agent-config.ts
534
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
574
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
535
575
  import path3 from "path";
536
576
  function loadAgentConfig() {
537
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
577
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
538
578
  try {
539
579
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
540
580
  } catch {
@@ -555,6 +595,7 @@ var init_agent_config = __esm({
555
595
  "use strict";
556
596
  init_config();
557
597
  init_runtime_table();
598
+ init_secure_files();
558
599
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
559
600
  DEFAULT_MODELS = {
560
601
  claude: "claude-opus-4",
@@ -573,16 +614,16 @@ __export(intercom_queue_exports, {
573
614
  queueIntercom: () => queueIntercom,
574
615
  readQueue: () => readQueue
575
616
  });
576
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
617
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
577
618
  import path4 from "path";
578
619
  import os3 from "os";
579
620
  function ensureDir() {
580
621
  const dir = path4.dirname(QUEUE_PATH);
581
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
622
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
582
623
  }
583
624
  function readQueue() {
584
625
  try {
585
- if (!existsSync4(QUEUE_PATH)) return [];
626
+ if (!existsSync5(QUEUE_PATH)) return [];
586
627
  return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
587
628
  } catch {
588
629
  return [];
@@ -747,7 +788,7 @@ var init_db_retry = __esm({
747
788
 
748
789
  // src/lib/employees.ts
749
790
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
750
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
791
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
751
792
  import { execSync as execSync3 } from "child_process";
752
793
  import path5 from "path";
753
794
  import os4 from "os";
@@ -768,7 +809,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
768
809
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
769
810
  }
770
811
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
771
- if (!existsSync5(employeesPath)) return [];
812
+ if (!existsSync6(employeesPath)) return [];
772
813
  try {
773
814
  return JSON.parse(readFileSync5(employeesPath, "utf-8"));
774
815
  } catch {
@@ -1389,13 +1430,50 @@ var init_database_adapter = __esm({
1389
1430
  }
1390
1431
  });
1391
1432
 
1433
+ // src/lib/daemon-auth.ts
1434
+ import crypto from "crypto";
1435
+ import path7 from "path";
1436
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1437
+ function normalizeToken(token) {
1438
+ if (!token) return null;
1439
+ const trimmed = token.trim();
1440
+ return trimmed.length > 0 ? trimmed : null;
1441
+ }
1442
+ function readDaemonToken() {
1443
+ try {
1444
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1445
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1446
+ } catch {
1447
+ return null;
1448
+ }
1449
+ }
1450
+ function ensureDaemonToken(seed) {
1451
+ const existing = readDaemonToken();
1452
+ if (existing) return existing;
1453
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1454
+ ensurePrivateDirSync(EXE_AI_DIR);
1455
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1456
+ `, "utf8");
1457
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1458
+ return token;
1459
+ }
1460
+ var DAEMON_TOKEN_PATH;
1461
+ var init_daemon_auth = __esm({
1462
+ "src/lib/daemon-auth.ts"() {
1463
+ "use strict";
1464
+ init_config();
1465
+ init_secure_files();
1466
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
1467
+ }
1468
+ });
1469
+
1392
1470
  // src/lib/exe-daemon-client.ts
1393
1471
  import net from "net";
1394
1472
  import os6 from "os";
1395
1473
  import { spawn } from "child_process";
1396
1474
  import { randomUUID } from "crypto";
1397
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1398
- import path7 from "path";
1475
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1476
+ import path8 from "path";
1399
1477
  import { fileURLToPath as fileURLToPath2 } from "url";
1400
1478
  function handleData(chunk) {
1401
1479
  _buffer += chunk.toString();
@@ -1423,9 +1501,9 @@ function handleData(chunk) {
1423
1501
  }
1424
1502
  }
1425
1503
  function cleanupStaleFiles() {
1426
- if (existsSync6(PID_PATH)) {
1504
+ if (existsSync8(PID_PATH)) {
1427
1505
  try {
1428
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1506
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1429
1507
  if (pid > 0) {
1430
1508
  try {
1431
1509
  process.kill(pid, 0);
@@ -1446,11 +1524,11 @@ function cleanupStaleFiles() {
1446
1524
  }
1447
1525
  }
1448
1526
  function findPackageRoot() {
1449
- let dir = path7.dirname(fileURLToPath2(import.meta.url));
1450
- const { root } = path7.parse(dir);
1527
+ let dir = path8.dirname(fileURLToPath2(import.meta.url));
1528
+ const { root } = path8.parse(dir);
1451
1529
  while (dir !== root) {
1452
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
1453
- dir = path7.dirname(dir);
1530
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
1531
+ dir = path8.dirname(dir);
1454
1532
  }
1455
1533
  return null;
1456
1534
  }
@@ -1476,16 +1554,17 @@ function spawnDaemon() {
1476
1554
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1477
1555
  return;
1478
1556
  }
1479
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1480
- if (!existsSync6(daemonPath)) {
1557
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1558
+ if (!existsSync8(daemonPath)) {
1481
1559
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1482
1560
  `);
1483
1561
  return;
1484
1562
  }
1485
1563
  const resolvedPath = daemonPath;
1564
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1486
1565
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1487
1566
  `);
1488
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1567
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1489
1568
  let stderrFd = "ignore";
1490
1569
  try {
1491
1570
  stderrFd = openSync(logPath, "a");
@@ -1503,7 +1582,8 @@ function spawnDaemon() {
1503
1582
  TMUX_PANE: void 0,
1504
1583
  // Prevents resolveExeSession() from scoping to one session
1505
1584
  EXE_DAEMON_SOCK: SOCKET_PATH,
1506
- EXE_DAEMON_PID: PID_PATH
1585
+ EXE_DAEMON_PID: PID_PATH,
1586
+ [DAEMON_TOKEN_ENV]: daemonToken
1507
1587
  }
1508
1588
  });
1509
1589
  child.unref();
@@ -1610,13 +1690,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1610
1690
  return;
1611
1691
  }
1612
1692
  const id = randomUUID();
1693
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1613
1694
  const timer = setTimeout(() => {
1614
1695
  _pending.delete(id);
1615
1696
  resolve({ error: "Request timeout" });
1616
1697
  }, timeoutMs);
1617
1698
  _pending.set(id, { resolve, timer });
1618
1699
  try {
1619
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1700
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1620
1701
  } catch {
1621
1702
  clearTimeout(timer);
1622
1703
  _pending.delete(id);
@@ -1627,17 +1708,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1627
1708
  function isClientConnected() {
1628
1709
  return _connected;
1629
1710
  }
1630
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1711
+ 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;
1631
1712
  var init_exe_daemon_client = __esm({
1632
1713
  "src/lib/exe-daemon-client.ts"() {
1633
1714
  "use strict";
1634
1715
  init_config();
1635
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1636
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1637
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1716
+ init_daemon_auth();
1717
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1718
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1719
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1638
1720
  SPAWN_LOCK_STALE_MS = 3e4;
1639
1721
  CONNECT_TIMEOUT_MS = 15e3;
1640
1722
  REQUEST_TIMEOUT_MS = 3e4;
1723
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1641
1724
  _socket = null;
1642
1725
  _connected = false;
1643
1726
  _buffer = "";
@@ -2216,6 +2299,7 @@ async function ensureSchema() {
2216
2299
  project TEXT NOT NULL,
2217
2300
  summary TEXT NOT NULL,
2218
2301
  task_file TEXT,
2302
+ session_scope TEXT,
2219
2303
  read INTEGER NOT NULL DEFAULT 0,
2220
2304
  created_at TEXT NOT NULL
2221
2305
  );
@@ -2224,7 +2308,7 @@ async function ensureSchema() {
2224
2308
  ON notifications(read);
2225
2309
 
2226
2310
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2227
- ON notifications(agent_id);
2311
+ ON notifications(agent_id, session_scope);
2228
2312
 
2229
2313
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2230
2314
  ON notifications(task_file);
@@ -2262,6 +2346,7 @@ async function ensureSchema() {
2262
2346
  target_agent TEXT NOT NULL,
2263
2347
  target_project TEXT,
2264
2348
  target_device TEXT NOT NULL DEFAULT 'local',
2349
+ session_scope TEXT,
2265
2350
  content TEXT NOT NULL,
2266
2351
  priority TEXT DEFAULT 'normal',
2267
2352
  status TEXT DEFAULT 'pending',
@@ -2275,10 +2360,31 @@ async function ensureSchema() {
2275
2360
  );
2276
2361
 
2277
2362
  CREATE INDEX IF NOT EXISTS idx_messages_target
2278
- ON messages(target_agent, status);
2363
+ ON messages(target_agent, session_scope, status);
2279
2364
 
2280
2365
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2281
- ON messages(target_agent, from_agent, server_seq);
2366
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2367
+ `);
2368
+ try {
2369
+ await client.execute({
2370
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2371
+ args: []
2372
+ });
2373
+ } catch {
2374
+ }
2375
+ try {
2376
+ await client.execute({
2377
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2378
+ args: []
2379
+ });
2380
+ } catch {
2381
+ }
2382
+ await client.executeMultiple(`
2383
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2384
+ ON notifications(agent_id, session_scope, read, created_at);
2385
+
2386
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2387
+ ON messages(target_agent, session_scope, status, created_at);
2282
2388
  `);
2283
2389
  try {
2284
2390
  await client.execute({
@@ -2862,6 +2968,13 @@ async function ensureSchema() {
2862
2968
  } catch {
2863
2969
  }
2864
2970
  }
2971
+ try {
2972
+ await client.execute({
2973
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2974
+ args: []
2975
+ });
2976
+ } catch {
2977
+ }
2865
2978
  }
2866
2979
  async function disposeDatabase() {
2867
2980
  if (_walCheckpointTimer) {
@@ -2900,18 +3013,21 @@ var init_database = __esm({
2900
3013
  });
2901
3014
 
2902
3015
  // src/lib/license.ts
2903
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3016
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2904
3017
  import { randomUUID as randomUUID2 } from "crypto";
2905
- import path8 from "path";
3018
+ import { createRequire as createRequire2 } from "module";
3019
+ import { pathToFileURL as pathToFileURL2 } from "url";
3020
+ import os7 from "os";
3021
+ import path9 from "path";
2906
3022
  import { jwtVerify, importSPKI } from "jose";
2907
3023
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2908
3024
  var init_license = __esm({
2909
3025
  "src/lib/license.ts"() {
2910
3026
  "use strict";
2911
3027
  init_config();
2912
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2913
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2914
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3028
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3029
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3030
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2915
3031
  PLAN_LIMITS = {
2916
3032
  free: { devices: 1, employees: 1, memories: 5e3 },
2917
3033
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2923,12 +3039,12 @@ var init_license = __esm({
2923
3039
  });
2924
3040
 
2925
3041
  // src/lib/plan-limits.ts
2926
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2927
- import path9 from "path";
3042
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3043
+ import path10 from "path";
2928
3044
  function getLicenseSync() {
2929
3045
  try {
2930
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2931
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3046
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3047
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2932
3048
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2933
3049
  const parts = raw.token.split(".");
2934
3050
  if (parts.length !== 3) return freeLicense();
@@ -2966,8 +3082,8 @@ function assertEmployeeLimitSync(rosterPath) {
2966
3082
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2967
3083
  let count = 0;
2968
3084
  try {
2969
- if (existsSync8(filePath)) {
2970
- const raw = readFileSync8(filePath, "utf8");
3085
+ if (existsSync10(filePath)) {
3086
+ const raw = readFileSync9(filePath, "utf8");
2971
3087
  const employees = JSON.parse(raw);
2972
3088
  count = Array.isArray(employees) ? employees.length : 0;
2973
3089
  }
@@ -2996,29 +3112,30 @@ var init_plan_limits = __esm({
2996
3112
  this.name = "PlanLimitError";
2997
3113
  }
2998
3114
  };
2999
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3115
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3000
3116
  }
3001
3117
  });
3002
3118
 
3003
3119
  // src/lib/notifications.ts
3004
- import crypto from "crypto";
3005
- import path10 from "path";
3006
- import os7 from "os";
3120
+ import crypto2 from "crypto";
3121
+ import path11 from "path";
3122
+ import os8 from "os";
3007
3123
  import {
3008
- readFileSync as readFileSync9,
3124
+ readFileSync as readFileSync10,
3009
3125
  readdirSync,
3010
3126
  unlinkSync as unlinkSync3,
3011
- existsSync as existsSync9,
3127
+ existsSync as existsSync11,
3012
3128
  rmdirSync
3013
3129
  } from "fs";
3014
3130
  async function writeNotification(notification) {
3015
3131
  try {
3016
3132
  const client = getClient();
3017
- const id = crypto.randomUUID();
3133
+ const id = crypto2.randomUUID();
3018
3134
  const now = (/* @__PURE__ */ new Date()).toISOString();
3135
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3019
3136
  await client.execute({
3020
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3021
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3137
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3138
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3022
3139
  args: [
3023
3140
  id,
3024
3141
  notification.agentId,
@@ -3027,6 +3144,7 @@ async function writeNotification(notification) {
3027
3144
  notification.project,
3028
3145
  notification.summary,
3029
3146
  notification.taskFile ?? null,
3147
+ sessionScope,
3030
3148
  now
3031
3149
  ]
3032
3150
  });
@@ -3035,12 +3153,14 @@ async function writeNotification(notification) {
3035
3153
  `);
3036
3154
  }
3037
3155
  }
3038
- async function markAsReadByTaskFile(taskFile) {
3156
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3039
3157
  try {
3040
3158
  const client = getClient();
3159
+ const scope = strictSessionScopeFilter(sessionScope);
3041
3160
  await client.execute({
3042
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3043
- args: [taskFile]
3161
+ sql: `UPDATE notifications SET read = 1
3162
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3163
+ args: [taskFile, ...scope.args]
3044
3164
  });
3045
3165
  } catch {
3046
3166
  }
@@ -3049,11 +3169,12 @@ var init_notifications = __esm({
3049
3169
  "src/lib/notifications.ts"() {
3050
3170
  "use strict";
3051
3171
  init_database();
3172
+ init_task_scope();
3052
3173
  }
3053
3174
  });
3054
3175
 
3055
3176
  // src/lib/session-kill-telemetry.ts
3056
- import crypto2 from "crypto";
3177
+ import crypto3 from "crypto";
3057
3178
  async function recordSessionKill(input) {
3058
3179
  try {
3059
3180
  const client = getClient();
@@ -3063,7 +3184,7 @@ async function recordSessionKill(input) {
3063
3184
  ticks_idle, estimated_tokens_saved)
3064
3185
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3065
3186
  args: [
3066
- crypto2.randomUUID(),
3187
+ crypto3.randomUUID(),
3067
3188
  input.sessionName,
3068
3189
  input.agentId,
3069
3190
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3141,13 +3262,126 @@ var init_state_bus = __esm({
3141
3262
  }
3142
3263
  });
3143
3264
 
3144
- // src/lib/tasks-crud.ts
3145
- import crypto3 from "crypto";
3146
- import path11 from "path";
3147
- import os8 from "os";
3265
+ // src/lib/project-name.ts
3266
+ var project_name_exports = {};
3267
+ __export(project_name_exports, {
3268
+ _resetCache: () => _resetCache,
3269
+ getProjectName: () => getProjectName
3270
+ });
3148
3271
  import { execSync as execSync4 } from "child_process";
3272
+ import path12 from "path";
3273
+ function getProjectName(cwd) {
3274
+ const dir = cwd ?? process.cwd();
3275
+ if (_cached2 && _cachedCwd === dir) return _cached2;
3276
+ try {
3277
+ let repoRoot;
3278
+ try {
3279
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
3280
+ cwd: dir,
3281
+ encoding: "utf8",
3282
+ timeout: 2e3,
3283
+ stdio: ["pipe", "pipe", "pipe"]
3284
+ }).trim();
3285
+ repoRoot = path12.dirname(gitCommonDir);
3286
+ } catch {
3287
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
3288
+ cwd: dir,
3289
+ encoding: "utf8",
3290
+ timeout: 2e3,
3291
+ stdio: ["pipe", "pipe", "pipe"]
3292
+ }).trim();
3293
+ }
3294
+ _cached2 = path12.basename(repoRoot);
3295
+ _cachedCwd = dir;
3296
+ return _cached2;
3297
+ } catch {
3298
+ _cached2 = path12.basename(dir);
3299
+ _cachedCwd = dir;
3300
+ return _cached2;
3301
+ }
3302
+ }
3303
+ function _resetCache() {
3304
+ _cached2 = null;
3305
+ _cachedCwd = null;
3306
+ }
3307
+ var _cached2, _cachedCwd;
3308
+ var init_project_name = __esm({
3309
+ "src/lib/project-name.ts"() {
3310
+ "use strict";
3311
+ _cached2 = null;
3312
+ _cachedCwd = null;
3313
+ }
3314
+ });
3315
+
3316
+ // src/lib/session-scope.ts
3317
+ var session_scope_exports = {};
3318
+ __export(session_scope_exports, {
3319
+ assertSessionScope: () => assertSessionScope,
3320
+ findSessionForProject: () => findSessionForProject,
3321
+ getSessionProject: () => getSessionProject
3322
+ });
3323
+ function getSessionProject(sessionName) {
3324
+ const sessions = listSessions();
3325
+ const entry = sessions.find((s) => s.windowName === sessionName);
3326
+ if (!entry) return null;
3327
+ const parts = entry.projectDir.split("/").filter(Boolean);
3328
+ return parts[parts.length - 1] ?? null;
3329
+ }
3330
+ function findSessionForProject(projectName) {
3331
+ const sessions = listSessions();
3332
+ for (const s of sessions) {
3333
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
3334
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
3335
+ }
3336
+ return null;
3337
+ }
3338
+ function assertSessionScope(actionType, targetProject) {
3339
+ try {
3340
+ const currentProject = getProjectName();
3341
+ const exeSession = resolveExeSession();
3342
+ if (!exeSession) {
3343
+ return { allowed: true, reason: "no_session" };
3344
+ }
3345
+ if (currentProject === targetProject) {
3346
+ return {
3347
+ allowed: true,
3348
+ reason: "same_session",
3349
+ currentProject,
3350
+ targetProject
3351
+ };
3352
+ }
3353
+ process.stderr.write(
3354
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3355
+ `
3356
+ );
3357
+ return {
3358
+ allowed: false,
3359
+ reason: "cross_session_denied",
3360
+ currentProject,
3361
+ targetProject,
3362
+ targetSession: findSessionForProject(targetProject)?.windowName
3363
+ };
3364
+ } catch {
3365
+ return { allowed: true, reason: "no_session" };
3366
+ }
3367
+ }
3368
+ var init_session_scope = __esm({
3369
+ "src/lib/session-scope.ts"() {
3370
+ "use strict";
3371
+ init_session_registry();
3372
+ init_project_name();
3373
+ init_tmux_routing();
3374
+ init_employees();
3375
+ }
3376
+ });
3377
+
3378
+ // src/lib/tasks-crud.ts
3379
+ import crypto4 from "crypto";
3380
+ import path13 from "path";
3381
+ import os9 from "os";
3382
+ import { execSync as execSync5 } from "child_process";
3149
3383
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3150
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3384
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3151
3385
  async function writeCheckpoint(input) {
3152
3386
  const client = getClient();
3153
3387
  const row = await resolveTask(client, input.taskId);
@@ -3263,13 +3497,28 @@ async function resolveTask(client, identifier, scopeSession) {
3263
3497
  }
3264
3498
  async function createTaskCore(input) {
3265
3499
  const client = getClient();
3266
- const id = crypto3.randomUUID();
3500
+ const id = crypto4.randomUUID();
3267
3501
  const now = (/* @__PURE__ */ new Date()).toISOString();
3268
3502
  const slug = slugify(input.title);
3269
3503
  let earlySessionScope = null;
3504
+ let scopeMismatchWarning;
3270
3505
  try {
3271
3506
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3272
- earlySessionScope = resolveExeSession2();
3507
+ const resolved = resolveExeSession2();
3508
+ if (resolved && input.projectName) {
3509
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
3510
+ const sessionProject = getSessionProject2(resolved);
3511
+ if (sessionProject && sessionProject !== input.projectName) {
3512
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
3513
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
3514
+ `);
3515
+ earlySessionScope = null;
3516
+ } else {
3517
+ earlySessionScope = resolved;
3518
+ }
3519
+ } else {
3520
+ earlySessionScope = resolved;
3521
+ }
3273
3522
  } catch {
3274
3523
  }
3275
3524
  const scope = earlySessionScope ?? "default";
@@ -3320,10 +3569,14 @@ async function createTaskCore(input) {
3320
3569
  ${laneWarning}` : laneWarning;
3321
3570
  }
3322
3571
  }
3572
+ if (scopeMismatchWarning) {
3573
+ warning = warning ? `${warning}
3574
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
3575
+ }
3323
3576
  if (input.baseDir) {
3324
3577
  try {
3325
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3326
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3578
+ await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
3579
+ await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
3327
3580
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3328
3581
  await ensureGitignoreExe(input.baseDir);
3329
3582
  } catch {
@@ -3359,13 +3612,19 @@ ${laneWarning}` : laneWarning;
3359
3612
  });
3360
3613
  if (input.baseDir) {
3361
3614
  try {
3362
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3363
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
3364
- const mdDir = path11.dirname(mdPath);
3365
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3615
+ const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
3616
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
3617
+ const mdDir = path13.dirname(mdPath);
3618
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3366
3619
  const reviewer = input.reviewer ?? input.assignedBy;
3367
3620
  const mdContent = `# ${input.title}
3368
3621
 
3622
+ ## MANDATORY: When done
3623
+
3624
+ You MUST call update_task with status "done" and a result summary when finished.
3625
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3626
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3627
+
3369
3628
  **ID:** ${id}
3370
3629
  **Status:** ${initialStatus}
3371
3630
  **Priority:** ${input.priority}
@@ -3379,12 +3638,6 @@ ${laneWarning}` : laneWarning;
3379
3638
  ## Context
3380
3639
 
3381
3640
  ${input.context}
3382
-
3383
- ## MANDATORY: When done
3384
-
3385
- You MUST call update_task with status "done" and a result summary when finished.
3386
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3387
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3388
3641
  `;
3389
3642
  await writeFile3(mdPath, mdContent, "utf-8");
3390
3643
  } catch (err) {
@@ -3466,14 +3719,14 @@ function isTmuxSessionAlive(identifier) {
3466
3719
  if (!identifier || identifier === "unknown") return true;
3467
3720
  try {
3468
3721
  if (identifier.startsWith("%")) {
3469
- const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
3722
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
3470
3723
  timeout: 2e3,
3471
3724
  encoding: "utf8",
3472
3725
  stdio: ["pipe", "pipe", "pipe"]
3473
3726
  });
3474
3727
  return output.split("\n").some((l) => l.trim() === identifier);
3475
3728
  } else {
3476
- execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3729
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3477
3730
  timeout: 2e3,
3478
3731
  stdio: ["pipe", "pipe", "pipe"]
3479
3732
  });
@@ -3482,7 +3735,7 @@ function isTmuxSessionAlive(identifier) {
3482
3735
  } catch {
3483
3736
  if (identifier.startsWith("%")) return true;
3484
3737
  try {
3485
- execSync4("tmux list-sessions", {
3738
+ execSync5("tmux list-sessions", {
3486
3739
  timeout: 2e3,
3487
3740
  stdio: ["pipe", "pipe", "pipe"]
3488
3741
  });
@@ -3497,12 +3750,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
3497
3750
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
3498
3751
  try {
3499
3752
  const since = new Date(taskCreatedAt).toISOString();
3500
- const branch = execSync4(
3753
+ const branch = execSync5(
3501
3754
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
3502
3755
  { encoding: "utf8", timeout: 3e3 }
3503
3756
  ).trim();
3504
3757
  const branchArg = branch && branch !== "HEAD" ? branch : "";
3505
- const commitCount = execSync4(
3758
+ const commitCount = execSync5(
3506
3759
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
3507
3760
  { encoding: "utf8", timeout: 5e3 }
3508
3761
  ).trim();
@@ -3633,7 +3886,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3633
3886
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3634
3887
  } catch {
3635
3888
  }
3636
- if (input.status === "done" || input.status === "cancelled") {
3889
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3637
3890
  try {
3638
3891
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3639
3892
  clearQueueForAgent2(String(row.assigned_to));
@@ -3662,9 +3915,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3662
3915
  return { taskFile, assignedTo, assignedBy, taskSlug };
3663
3916
  }
3664
3917
  async function ensureArchitectureDoc(baseDir, projectName) {
3665
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3918
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
3666
3919
  try {
3667
- if (existsSync10(archPath)) return;
3920
+ if (existsSync12(archPath)) return;
3668
3921
  const template = [
3669
3922
  `# ${projectName} \u2014 System Architecture`,
3670
3923
  "",
@@ -3697,10 +3950,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3697
3950
  }
3698
3951
  }
3699
3952
  async function ensureGitignoreExe(baseDir) {
3700
- const gitignorePath = path11.join(baseDir, ".gitignore");
3953
+ const gitignorePath = path13.join(baseDir, ".gitignore");
3701
3954
  try {
3702
- if (existsSync10(gitignorePath)) {
3703
- const content = readFileSync10(gitignorePath, "utf-8");
3955
+ if (existsSync12(gitignorePath)) {
3956
+ const content = readFileSync11(gitignorePath, "utf-8");
3704
3957
  if (/^\/?exe\/?$/m.test(content)) return;
3705
3958
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3706
3959
  } else {
@@ -3731,58 +3984,42 @@ var init_tasks_crud = __esm({
3731
3984
  });
3732
3985
 
3733
3986
  // src/lib/tasks-review.ts
3734
- import path12 from "path";
3735
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3987
+ import path14 from "path";
3988
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3736
3989
  async function countPendingReviews(sessionScope) {
3737
3990
  const client = getClient();
3738
- if (sessionScope) {
3739
- const result2 = await client.execute({
3740
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3741
- args: [sessionScope]
3742
- });
3743
- return Number(result2.rows[0]?.cnt) || 0;
3744
- }
3991
+ const scope = strictSessionScopeFilter(
3992
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3993
+ );
3745
3994
  const result = await client.execute({
3746
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3747
- args: []
3995
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3996
+ WHERE status = 'needs_review'${scope.sql}`,
3997
+ args: [...scope.args]
3748
3998
  });
3749
3999
  return Number(result.rows[0]?.cnt) || 0;
3750
4000
  }
3751
4001
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3752
4002
  const client = getClient();
3753
- if (sessionScope) {
3754
- const result2 = await client.execute({
3755
- sql: `SELECT COUNT(*) as cnt FROM tasks
3756
- WHERE status = 'needs_review' AND updated_at > ?
3757
- AND session_scope = ?`,
3758
- args: [sinceIso, sessionScope]
3759
- });
3760
- return Number(result2.rows[0]?.cnt) || 0;
3761
- }
4003
+ const scope = strictSessionScopeFilter(
4004
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4005
+ );
3762
4006
  const result = await client.execute({
3763
4007
  sql: `SELECT COUNT(*) as cnt FROM tasks
3764
- WHERE status = 'needs_review' AND updated_at > ?`,
3765
- args: [sinceIso]
4008
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4009
+ args: [sinceIso, ...scope.args]
3766
4010
  });
3767
4011
  return Number(result.rows[0]?.cnt) || 0;
3768
4012
  }
3769
4013
  async function listPendingReviews(limit, sessionScope) {
3770
4014
  const client = getClient();
3771
- if (sessionScope) {
3772
- const result2 = await client.execute({
3773
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3774
- WHERE status = 'needs_review'
3775
- AND session_scope = ?
3776
- ORDER BY updated_at ASC LIMIT ?`,
3777
- args: [sessionScope, limit]
3778
- });
3779
- return result2.rows;
3780
- }
4015
+ const scope = strictSessionScopeFilter(
4016
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4017
+ );
3781
4018
  const result = await client.execute({
3782
4019
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3783
- WHERE status = 'needs_review'
4020
+ WHERE status = 'needs_review'${scope.sql}
3784
4021
  ORDER BY updated_at ASC LIMIT ?`,
3785
- args: [limit]
4022
+ args: [...scope.args, limit]
3786
4023
  });
3787
4024
  return result.rows;
3788
4025
  }
@@ -3794,7 +4031,7 @@ async function cleanupOrphanedReviews() {
3794
4031
  WHERE status IN ('open', 'needs_review', 'in_progress')
3795
4032
  AND assigned_by = 'system'
3796
4033
  AND title LIKE 'Review:%'
3797
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4034
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3798
4035
  args: [now]
3799
4036
  });
3800
4037
  const r1b = await client.execute({
@@ -3913,11 +4150,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3913
4150
  );
3914
4151
  }
3915
4152
  try {
3916
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3917
- if (existsSync11(cacheDir)) {
4153
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4154
+ if (existsSync13(cacheDir)) {
3918
4155
  for (const f of readdirSync2(cacheDir)) {
3919
4156
  if (f.startsWith("review-notified-")) {
3920
- unlinkSync4(path12.join(cacheDir, f));
4157
+ unlinkSync4(path14.join(cacheDir, f));
3921
4158
  }
3922
4159
  }
3923
4160
  }
@@ -3934,11 +4171,12 @@ var init_tasks_review = __esm({
3934
4171
  init_tmux_routing();
3935
4172
  init_session_key();
3936
4173
  init_state_bus();
4174
+ init_task_scope();
3937
4175
  }
3938
4176
  });
3939
4177
 
3940
4178
  // src/lib/tasks-chain.ts
3941
- import path13 from "path";
4179
+ import path15 from "path";
3942
4180
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3943
4181
  async function cascadeUnblock(taskId, baseDir, now) {
3944
4182
  const client = getClient();
@@ -3955,7 +4193,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3955
4193
  });
3956
4194
  for (const ur of unblockedRows.rows) {
3957
4195
  try {
3958
- const ubFile = path13.join(baseDir, String(ur.task_file));
4196
+ const ubFile = path15.join(baseDir, String(ur.task_file));
3959
4197
  let ubContent = await readFile3(ubFile, "utf-8");
3960
4198
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3961
4199
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3990,7 +4228,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3990
4228
  const scScope = sessionScopeFilter();
3991
4229
  const remaining = await client.execute({
3992
4230
  sql: `SELECT COUNT(*) as cnt FROM tasks
3993
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4231
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3994
4232
  args: [parentTaskId, ...scScope.args]
3995
4233
  });
3996
4234
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4022,119 +4260,6 @@ var init_tasks_chain = __esm({
4022
4260
  }
4023
4261
  });
4024
4262
 
4025
- // src/lib/project-name.ts
4026
- var project_name_exports = {};
4027
- __export(project_name_exports, {
4028
- _resetCache: () => _resetCache,
4029
- getProjectName: () => getProjectName
4030
- });
4031
- import { execSync as execSync5 } from "child_process";
4032
- import path14 from "path";
4033
- function getProjectName(cwd) {
4034
- const dir = cwd ?? process.cwd();
4035
- if (_cached2 && _cachedCwd === dir) return _cached2;
4036
- try {
4037
- let repoRoot;
4038
- try {
4039
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4040
- cwd: dir,
4041
- encoding: "utf8",
4042
- timeout: 2e3,
4043
- stdio: ["pipe", "pipe", "pipe"]
4044
- }).trim();
4045
- repoRoot = path14.dirname(gitCommonDir);
4046
- } catch {
4047
- repoRoot = execSync5("git rev-parse --show-toplevel", {
4048
- cwd: dir,
4049
- encoding: "utf8",
4050
- timeout: 2e3,
4051
- stdio: ["pipe", "pipe", "pipe"]
4052
- }).trim();
4053
- }
4054
- _cached2 = path14.basename(repoRoot);
4055
- _cachedCwd = dir;
4056
- return _cached2;
4057
- } catch {
4058
- _cached2 = path14.basename(dir);
4059
- _cachedCwd = dir;
4060
- return _cached2;
4061
- }
4062
- }
4063
- function _resetCache() {
4064
- _cached2 = null;
4065
- _cachedCwd = null;
4066
- }
4067
- var _cached2, _cachedCwd;
4068
- var init_project_name = __esm({
4069
- "src/lib/project-name.ts"() {
4070
- "use strict";
4071
- _cached2 = null;
4072
- _cachedCwd = null;
4073
- }
4074
- });
4075
-
4076
- // src/lib/session-scope.ts
4077
- var session_scope_exports = {};
4078
- __export(session_scope_exports, {
4079
- assertSessionScope: () => assertSessionScope,
4080
- findSessionForProject: () => findSessionForProject,
4081
- getSessionProject: () => getSessionProject
4082
- });
4083
- function getSessionProject(sessionName) {
4084
- const sessions = listSessions();
4085
- const entry = sessions.find((s) => s.windowName === sessionName);
4086
- if (!entry) return null;
4087
- const parts = entry.projectDir.split("/").filter(Boolean);
4088
- return parts[parts.length - 1] ?? null;
4089
- }
4090
- function findSessionForProject(projectName) {
4091
- const sessions = listSessions();
4092
- for (const s of sessions) {
4093
- const proj = s.projectDir.split("/").filter(Boolean).pop();
4094
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4095
- }
4096
- return null;
4097
- }
4098
- function assertSessionScope(actionType, targetProject) {
4099
- try {
4100
- const currentProject = getProjectName();
4101
- const exeSession = resolveExeSession();
4102
- if (!exeSession) {
4103
- return { allowed: true, reason: "no_session" };
4104
- }
4105
- if (currentProject === targetProject) {
4106
- return {
4107
- allowed: true,
4108
- reason: "same_session",
4109
- currentProject,
4110
- targetProject
4111
- };
4112
- }
4113
- process.stderr.write(
4114
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4115
- `
4116
- );
4117
- return {
4118
- allowed: false,
4119
- reason: "cross_session_denied",
4120
- currentProject,
4121
- targetProject,
4122
- targetSession: findSessionForProject(targetProject)?.windowName
4123
- };
4124
- } catch {
4125
- return { allowed: true, reason: "no_session" };
4126
- }
4127
- }
4128
- var init_session_scope = __esm({
4129
- "src/lib/session-scope.ts"() {
4130
- "use strict";
4131
- init_session_registry();
4132
- init_project_name();
4133
- init_tmux_routing();
4134
- init_employees();
4135
- }
4136
- });
4137
-
4138
4263
  // src/lib/tasks-notify.ts
4139
4264
  async function dispatchTaskToEmployee(input) {
4140
4265
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -4202,10 +4327,10 @@ var init_tasks_notify = __esm({
4202
4327
  });
4203
4328
 
4204
4329
  // src/lib/behaviors.ts
4205
- import crypto4 from "crypto";
4330
+ import crypto5 from "crypto";
4206
4331
  async function storeBehavior(opts) {
4207
4332
  const client = getClient();
4208
- const id = crypto4.randomUUID();
4333
+ const id = crypto5.randomUUID();
4209
4334
  const now = (/* @__PURE__ */ new Date()).toISOString();
4210
4335
  await client.execute({
4211
4336
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4234,7 +4359,7 @@ __export(skill_learning_exports, {
4234
4359
  storeTrajectory: () => storeTrajectory,
4235
4360
  sweepTrajectories: () => sweepTrajectories
4236
4361
  });
4237
- import crypto5 from "crypto";
4362
+ import crypto6 from "crypto";
4238
4363
  async function extractTrajectory(taskId, agentId) {
4239
4364
  const client = getClient();
4240
4365
  const result = await client.execute({
@@ -4263,11 +4388,11 @@ async function extractTrajectory(taskId, agentId) {
4263
4388
  return signature;
4264
4389
  }
4265
4390
  function hashSignature(signature) {
4266
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4391
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4267
4392
  }
4268
4393
  async function storeTrajectory(opts) {
4269
4394
  const client = getClient();
4270
- const id = crypto5.randomUUID();
4395
+ const id = crypto6.randomUUID();
4271
4396
  const now = (/* @__PURE__ */ new Date()).toISOString();
4272
4397
  const signatureHash = hashSignature(opts.signature);
4273
4398
  await client.execute({
@@ -4532,8 +4657,8 @@ __export(tasks_exports, {
4532
4657
  updateTaskStatus: () => updateTaskStatus,
4533
4658
  writeCheckpoint: () => writeCheckpoint
4534
4659
  });
4535
- import path15 from "path";
4536
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4660
+ import path16 from "path";
4661
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4537
4662
  async function createTask(input) {
4538
4663
  const result = await createTaskCore(input);
4539
4664
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4552,12 +4677,12 @@ async function updateTask(input) {
4552
4677
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4553
4678
  try {
4554
4679
  const agent = String(row.assigned_to);
4555
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4556
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4680
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4681
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4557
4682
  if (input.status === "in_progress") {
4558
4683
  mkdirSync5(cacheDir, { recursive: true });
4559
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4560
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4684
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4685
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4561
4686
  try {
4562
4687
  unlinkSync5(cachePath);
4563
4688
  } catch {
@@ -4565,10 +4690,10 @@ async function updateTask(input) {
4565
4690
  }
4566
4691
  } catch {
4567
4692
  }
4568
- if (input.status === "done") {
4693
+ if (input.status === "done" || input.status === "closed") {
4569
4694
  await cleanupReviewFile(row, taskFile, input.baseDir);
4570
4695
  }
4571
- if (input.status === "done" || input.status === "cancelled") {
4696
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4572
4697
  try {
4573
4698
  const client = getClient();
4574
4699
  const taskTitle = String(row.title);
@@ -4584,7 +4709,7 @@ async function updateTask(input) {
4584
4709
  if (!isCoordinatorName(assignedAgent)) {
4585
4710
  try {
4586
4711
  const draftClient = getClient();
4587
- if (input.status === "done") {
4712
+ if (input.status === "done" || input.status === "closed") {
4588
4713
  await draftClient.execute({
4589
4714
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4590
4715
  args: [assignedAgent]
@@ -4601,7 +4726,7 @@ async function updateTask(input) {
4601
4726
  try {
4602
4727
  const client = getClient();
4603
4728
  const cascaded = await client.execute({
4604
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4729
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4605
4730
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4606
4731
  args: [now, taskId]
4607
4732
  });
@@ -4614,14 +4739,14 @@ async function updateTask(input) {
4614
4739
  } catch {
4615
4740
  }
4616
4741
  }
4617
- const isTerminal = input.status === "done" || input.status === "needs_review";
4742
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4618
4743
  if (isTerminal) {
4619
4744
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4620
4745
  if (!isCoordinator) {
4621
4746
  notifyTaskDone();
4622
4747
  }
4623
4748
  await markTaskNotificationsRead(taskFile);
4624
- if (input.status === "done") {
4749
+ if (input.status === "done" || input.status === "closed") {
4625
4750
  try {
4626
4751
  await cascadeUnblock(taskId, input.baseDir, now);
4627
4752
  } catch {
@@ -4641,7 +4766,7 @@ async function updateTask(input) {
4641
4766
  }
4642
4767
  }
4643
4768
  }
4644
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4769
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4645
4770
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4646
4771
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4647
4772
  taskId,
@@ -5013,6 +5138,7 @@ __export(tmux_routing_exports, {
5013
5138
  isEmployeeAlive: () => isEmployeeAlive,
5014
5139
  isExeSession: () => isExeSession,
5015
5140
  isSessionBusy: () => isSessionBusy,
5141
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5016
5142
  notifyParentExe: () => notifyParentExe,
5017
5143
  parseParentExe: () => parseParentExe,
5018
5144
  registerParentExe: () => registerParentExe,
@@ -5023,13 +5149,13 @@ __export(tmux_routing_exports, {
5023
5149
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5024
5150
  });
5025
5151
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
5026
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5027
- import path16 from "path";
5028
- import os9 from "os";
5152
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5153
+ import path17 from "path";
5154
+ import os10 from "os";
5029
5155
  import { fileURLToPath as fileURLToPath3 } from "url";
5030
5156
  import { unlinkSync as unlinkSync6 } from "fs";
5031
5157
  function spawnLockPath(sessionName) {
5032
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5158
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5033
5159
  }
5034
5160
  function isProcessAlive(pid) {
5035
5161
  try {
@@ -5040,13 +5166,13 @@ function isProcessAlive(pid) {
5040
5166
  }
5041
5167
  }
5042
5168
  function acquireSpawnLock2(sessionName) {
5043
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5169
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5044
5170
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5045
5171
  }
5046
5172
  const lockFile = spawnLockPath(sessionName);
5047
- if (existsSync12(lockFile)) {
5173
+ if (existsSync14(lockFile)) {
5048
5174
  try {
5049
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5175
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5050
5176
  const age = Date.now() - lock.timestamp;
5051
5177
  if (isProcessAlive(lock.pid) && age < 6e4) {
5052
5178
  return false;
@@ -5054,7 +5180,7 @@ function acquireSpawnLock2(sessionName) {
5054
5180
  } catch {
5055
5181
  }
5056
5182
  }
5057
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5183
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5058
5184
  return true;
5059
5185
  }
5060
5186
  function releaseSpawnLock2(sessionName) {
@@ -5066,13 +5192,13 @@ function releaseSpawnLock2(sessionName) {
5066
5192
  function resolveBehaviorsExporterScript() {
5067
5193
  try {
5068
5194
  const thisFile = fileURLToPath3(import.meta.url);
5069
- const scriptPath = path16.join(
5070
- path16.dirname(thisFile),
5195
+ const scriptPath = path17.join(
5196
+ path17.dirname(thisFile),
5071
5197
  "..",
5072
5198
  "bin",
5073
5199
  "exe-export-behaviors.js"
5074
5200
  );
5075
- return existsSync12(scriptPath) ? scriptPath : null;
5201
+ return existsSync14(scriptPath) ? scriptPath : null;
5076
5202
  } catch {
5077
5203
  return null;
5078
5204
  }
@@ -5138,12 +5264,12 @@ function extractRootExe(name) {
5138
5264
  return parts.length > 0 ? parts[parts.length - 1] : null;
5139
5265
  }
5140
5266
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5141
- if (!existsSync12(SESSION_CACHE)) {
5267
+ if (!existsSync14(SESSION_CACHE)) {
5142
5268
  mkdirSync6(SESSION_CACHE, { recursive: true });
5143
5269
  }
5144
5270
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5145
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5146
- writeFileSync7(filePath, JSON.stringify({
5271
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5272
+ writeFileSync8(filePath, JSON.stringify({
5147
5273
  parentExe: rootExe,
5148
5274
  dispatchedBy: dispatchedBy || rootExe,
5149
5275
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5151,7 +5277,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5151
5277
  }
5152
5278
  function getParentExe(sessionKey) {
5153
5279
  try {
5154
- const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5280
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5155
5281
  return data.parentExe || null;
5156
5282
  } catch {
5157
5283
  return null;
@@ -5159,8 +5285,8 @@ function getParentExe(sessionKey) {
5159
5285
  }
5160
5286
  function getDispatchedBy(sessionKey) {
5161
5287
  try {
5162
- const data = JSON.parse(readFileSync11(
5163
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5288
+ const data = JSON.parse(readFileSync12(
5289
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5164
5290
  "utf8"
5165
5291
  ));
5166
5292
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5230,8 +5356,8 @@ async function verifyPaneAtCapacity(sessionName) {
5230
5356
  }
5231
5357
  function readDebounceState() {
5232
5358
  try {
5233
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5234
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5359
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5360
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5235
5361
  const state = {};
5236
5362
  for (const [key, val] of Object.entries(raw)) {
5237
5363
  if (typeof val === "number") {
@@ -5247,8 +5373,8 @@ function readDebounceState() {
5247
5373
  }
5248
5374
  function writeDebounceState(state) {
5249
5375
  try {
5250
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5251
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5376
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5377
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5252
5378
  } catch {
5253
5379
  }
5254
5380
  }
@@ -5346,8 +5472,8 @@ function sendIntercom(targetSession) {
5346
5472
  try {
5347
5473
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5348
5474
  const agent = baseAgentName(rawAgent);
5349
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
5350
- if (existsSync12(markerPath)) {
5475
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5476
+ if (existsSync14(markerPath)) {
5351
5477
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5352
5478
  return "debounced";
5353
5479
  }
@@ -5356,8 +5482,8 @@ function sendIntercom(targetSession) {
5356
5482
  try {
5357
5483
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5358
5484
  const agent = baseAgentName(rawAgent);
5359
- const taskDir = path16.join(process.cwd(), "exe", agent);
5360
- if (existsSync12(taskDir)) {
5485
+ const taskDir = path17.join(process.cwd(), "exe", agent);
5486
+ if (existsSync14(taskDir)) {
5361
5487
  const files = readdirSync3(taskDir).filter(
5362
5488
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5363
5489
  );
@@ -5417,6 +5543,21 @@ function notifyParentExe(sessionKey) {
5417
5543
  }
5418
5544
  return true;
5419
5545
  }
5546
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5547
+ const transport = getTransport();
5548
+ try {
5549
+ const sessions = transport.listSessions();
5550
+ if (!sessions.includes(coordinatorSession)) return false;
5551
+ execSync6(
5552
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5553
+ { timeout: 3e3 }
5554
+ );
5555
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5556
+ return true;
5557
+ } catch {
5558
+ return false;
5559
+ }
5560
+ }
5420
5561
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5421
5562
  if (isCoordinatorName(employeeName)) {
5422
5563
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5490,26 +5631,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5490
5631
  const transport = getTransport();
5491
5632
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5492
5633
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5493
- const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
5494
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5495
- if (!existsSync12(logDir)) {
5634
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
5635
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5636
+ if (!existsSync14(logDir)) {
5496
5637
  mkdirSync6(logDir, { recursive: true });
5497
5638
  }
5498
5639
  transport.kill(sessionName);
5499
5640
  let cleanupSuffix = "";
5500
5641
  try {
5501
5642
  const thisFile = fileURLToPath3(import.meta.url);
5502
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5503
- if (existsSync12(cleanupScript)) {
5643
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5644
+ if (existsSync14(cleanupScript)) {
5504
5645
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5505
5646
  }
5506
5647
  } catch {
5507
5648
  }
5508
5649
  try {
5509
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
5650
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
5510
5651
  let claudeJson = {};
5511
5652
  try {
5512
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5653
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5513
5654
  } catch {
5514
5655
  }
5515
5656
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5517,17 +5658,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5517
5658
  const trustDir = opts?.cwd ?? projectDir;
5518
5659
  if (!projects[trustDir]) projects[trustDir] = {};
5519
5660
  projects[trustDir].hasTrustDialogAccepted = true;
5520
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5661
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5521
5662
  } catch {
5522
5663
  }
5523
5664
  try {
5524
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
5665
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
5525
5666
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5526
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
5527
- const settingsPath = path16.join(projSettingsDir, "settings.json");
5667
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
5668
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
5528
5669
  let settings = {};
5529
5670
  try {
5530
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5671
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5531
5672
  } catch {
5532
5673
  }
5533
5674
  const perms = settings.permissions ?? {};
@@ -5556,7 +5697,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5556
5697
  perms.allow = allow;
5557
5698
  settings.permissions = perms;
5558
5699
  mkdirSync6(projSettingsDir, { recursive: true });
5559
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5700
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5560
5701
  }
5561
5702
  } catch {
5562
5703
  }
@@ -5571,8 +5712,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5571
5712
  let behaviorsFlag = "";
5572
5713
  let legacyFallbackWarned = false;
5573
5714
  if (!useExeAgent && !useBinSymlink) {
5574
- const identityPath = path16.join(
5575
- os9.homedir(),
5715
+ const identityPath = path17.join(
5716
+ os10.homedir(),
5576
5717
  ".exe-os",
5577
5718
  "identity",
5578
5719
  `${employeeName}.md`
@@ -5581,13 +5722,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5581
5722
  const hasAgentFlag = claudeSupportsAgentFlag();
5582
5723
  if (hasAgentFlag) {
5583
5724
  identityFlag = ` --agent ${employeeName}`;
5584
- } else if (existsSync12(identityPath)) {
5725
+ } else if (existsSync14(identityPath)) {
5585
5726
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5586
5727
  legacyFallbackWarned = true;
5587
5728
  }
5588
5729
  const behaviorsFile = exportBehaviorsSync(
5589
5730
  employeeName,
5590
- path16.basename(spawnCwd),
5731
+ path17.basename(spawnCwd),
5591
5732
  sessionName
5592
5733
  );
5593
5734
  if (behaviorsFile) {
@@ -5602,16 +5743,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5602
5743
  }
5603
5744
  let sessionContextFlag = "";
5604
5745
  try {
5605
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
5746
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
5606
5747
  mkdirSync6(ctxDir, { recursive: true });
5607
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
5748
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
5608
5749
  const ctxContent = [
5609
5750
  `## Session Context`,
5610
5751
  `You are running in tmux session: ${sessionName}.`,
5611
5752
  `Your parent coordinator session is ${exeSession}.`,
5612
5753
  `Your employees (if any) use the -${exeSession} suffix.`
5613
5754
  ].join("\n");
5614
- writeFileSync7(ctxFile, ctxContent);
5755
+ writeFileSync8(ctxFile, ctxContent);
5615
5756
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5616
5757
  } catch {
5617
5758
  }
@@ -5688,8 +5829,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5688
5829
  transport.pipeLog(sessionName, logFile);
5689
5830
  try {
5690
5831
  const mySession = getMySession();
5691
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5692
- writeFileSync7(dispatchInfo, JSON.stringify({
5832
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5833
+ writeFileSync8(dispatchInfo, JSON.stringify({
5693
5834
  dispatchedBy: mySession,
5694
5835
  rootExe: exeSession,
5695
5836
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5763,15 +5904,15 @@ var init_tmux_routing = __esm({
5763
5904
  init_intercom_queue();
5764
5905
  init_plan_limits();
5765
5906
  init_employees();
5766
- SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
5767
- SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
5907
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
5908
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
5768
5909
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5769
5910
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5770
5911
  VERIFY_PANE_LINES = 200;
5771
5912
  INTERCOM_DEBOUNCE_MS = 3e4;
5772
5913
  CODEX_DEBOUNCE_MS = 12e4;
5773
- INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
5774
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
5914
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
5915
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5775
5916
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5776
5917
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5777
5918
  }
@@ -5794,6 +5935,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5794
5935
  args: [scope]
5795
5936
  };
5796
5937
  }
5938
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5939
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5940
+ if (!scope) return { sql: "", args: [] };
5941
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5942
+ return {
5943
+ sql: ` AND ${col} = ?`,
5944
+ args: [scope]
5945
+ };
5946
+ }
5797
5947
  var init_task_scope = __esm({
5798
5948
  "src/lib/task-scope.ts"() {
5799
5949
  "use strict";
@@ -5812,14 +5962,14 @@ var init_memory = __esm({
5812
5962
 
5813
5963
  // src/lib/keychain.ts
5814
5964
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5815
- import { existsSync as existsSync13 } from "fs";
5816
- import path17 from "path";
5817
- import os10 from "os";
5965
+ import { existsSync as existsSync15 } from "fs";
5966
+ import path18 from "path";
5967
+ import os11 from "os";
5818
5968
  function getKeyDir() {
5819
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
5969
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
5820
5970
  }
5821
5971
  function getKeyPath() {
5822
- return path17.join(getKeyDir(), "master.key");
5972
+ return path18.join(getKeyDir(), "master.key");
5823
5973
  }
5824
5974
  async function tryKeytar() {
5825
5975
  try {
@@ -5840,9 +5990,9 @@ async function getMasterKey() {
5840
5990
  }
5841
5991
  }
5842
5992
  const keyPath = getKeyPath();
5843
- if (!existsSync13(keyPath)) {
5993
+ if (!existsSync15(keyPath)) {
5844
5994
  process.stderr.write(
5845
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5995
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5846
5996
  `
5847
5997
  );
5848
5998
  return null;
@@ -5872,6 +6022,7 @@ var shard_manager_exports = {};
5872
6022
  __export(shard_manager_exports, {
5873
6023
  disposeShards: () => disposeShards,
5874
6024
  ensureShardSchema: () => ensureShardSchema,
6025
+ getOpenShardCount: () => getOpenShardCount,
5875
6026
  getReadyShardClient: () => getReadyShardClient,
5876
6027
  getShardClient: () => getShardClient,
5877
6028
  getShardsDir: () => getShardsDir,
@@ -5880,15 +6031,18 @@ __export(shard_manager_exports, {
5880
6031
  listShards: () => listShards,
5881
6032
  shardExists: () => shardExists
5882
6033
  });
5883
- import path18 from "path";
5884
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6034
+ import path19 from "path";
6035
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5885
6036
  import { createClient as createClient2 } from "@libsql/client";
5886
6037
  function initShardManager(encryptionKey) {
5887
6038
  _encryptionKey = encryptionKey;
5888
- if (!existsSync14(SHARDS_DIR)) {
6039
+ if (!existsSync16(SHARDS_DIR)) {
5889
6040
  mkdirSync7(SHARDS_DIR, { recursive: true });
5890
6041
  }
5891
6042
  _shardingEnabled = true;
6043
+ if (_evictionTimer) clearInterval(_evictionTimer);
6044
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6045
+ _evictionTimer.unref();
5892
6046
  }
5893
6047
  function isShardingEnabled() {
5894
6048
  return _shardingEnabled;
@@ -5905,21 +6059,28 @@ function getShardClient(projectName) {
5905
6059
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5906
6060
  }
5907
6061
  const cached = _shards.get(safeName);
5908
- if (cached) return cached;
5909
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
6062
+ if (cached) {
6063
+ _shardLastAccess.set(safeName, Date.now());
6064
+ return cached;
6065
+ }
6066
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6067
+ evictLRU();
6068
+ }
6069
+ const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
5910
6070
  const client = createClient2({
5911
6071
  url: `file:${dbPath}`,
5912
6072
  encryptionKey: _encryptionKey
5913
6073
  });
5914
6074
  _shards.set(safeName, client);
6075
+ _shardLastAccess.set(safeName, Date.now());
5915
6076
  return client;
5916
6077
  }
5917
6078
  function shardExists(projectName) {
5918
6079
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5919
- return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
6080
+ return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
5920
6081
  }
5921
6082
  function listShards() {
5922
- if (!existsSync14(SHARDS_DIR)) return [];
6083
+ if (!existsSync16(SHARDS_DIR)) return [];
5923
6084
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5924
6085
  }
5925
6086
  async function ensureShardSchema(client) {
@@ -5971,6 +6132,8 @@ async function ensureShardSchema(client) {
5971
6132
  for (const col of [
5972
6133
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5973
6134
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6135
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6136
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5974
6137
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5975
6138
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5976
6139
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6108,21 +6271,69 @@ async function getReadyShardClient(projectName) {
6108
6271
  await ensureShardSchema(client);
6109
6272
  return client;
6110
6273
  }
6274
+ function evictLRU() {
6275
+ let oldest = null;
6276
+ let oldestTime = Infinity;
6277
+ for (const [name, time] of _shardLastAccess) {
6278
+ if (time < oldestTime) {
6279
+ oldestTime = time;
6280
+ oldest = name;
6281
+ }
6282
+ }
6283
+ if (oldest) {
6284
+ const client = _shards.get(oldest);
6285
+ if (client) {
6286
+ client.close();
6287
+ }
6288
+ _shards.delete(oldest);
6289
+ _shardLastAccess.delete(oldest);
6290
+ }
6291
+ }
6292
+ function evictIdleShards() {
6293
+ const now = Date.now();
6294
+ const toEvict = [];
6295
+ for (const [name, lastAccess] of _shardLastAccess) {
6296
+ if (now - lastAccess > SHARD_IDLE_MS) {
6297
+ toEvict.push(name);
6298
+ }
6299
+ }
6300
+ for (const name of toEvict) {
6301
+ const client = _shards.get(name);
6302
+ if (client) {
6303
+ client.close();
6304
+ }
6305
+ _shards.delete(name);
6306
+ _shardLastAccess.delete(name);
6307
+ }
6308
+ }
6309
+ function getOpenShardCount() {
6310
+ return _shards.size;
6311
+ }
6111
6312
  function disposeShards() {
6313
+ if (_evictionTimer) {
6314
+ clearInterval(_evictionTimer);
6315
+ _evictionTimer = null;
6316
+ }
6112
6317
  for (const [, client] of _shards) {
6113
6318
  client.close();
6114
6319
  }
6115
6320
  _shards.clear();
6321
+ _shardLastAccess.clear();
6116
6322
  _shardingEnabled = false;
6117
6323
  _encryptionKey = null;
6118
6324
  }
6119
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6325
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
6120
6326
  var init_shard_manager = __esm({
6121
6327
  "src/lib/shard-manager.ts"() {
6122
6328
  "use strict";
6123
6329
  init_config();
6124
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
6330
+ SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6331
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6332
+ MAX_OPEN_SHARDS = 10;
6333
+ EVICTION_INTERVAL_MS = 60 * 1e3;
6125
6334
  _shards = /* @__PURE__ */ new Map();
6335
+ _shardLastAccess = /* @__PURE__ */ new Map();
6336
+ _evictionTimer = null;
6126
6337
  _encryptionKey = null;
6127
6338
  _shardingEnabled = false;
6128
6339
  }
@@ -6886,9 +7097,9 @@ var init_store = __esm({
6886
7097
  });
6887
7098
 
6888
7099
  // src/bin/scan-tasks.ts
6889
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
6890
- import path19 from "path";
6891
- import os11 from "os";
7100
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
7101
+ import path20 from "path";
7102
+ import os12 from "os";
6892
7103
 
6893
7104
  // src/lib/is-main.ts
6894
7105
  import { realpathSync } from "fs";
@@ -6908,27 +7119,27 @@ function isMainModule(importMetaUrl) {
6908
7119
  // src/bin/scan-tasks.ts
6909
7120
  init_session_key();
6910
7121
  init_task_scope();
6911
- function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os11.homedir()) {
7122
+ function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os12.homedir()) {
6912
7123
  if (runtime === "codex") {
6913
7124
  return null;
6914
7125
  }
6915
7126
  try {
6916
7127
  if (runtime === "opencode") {
6917
- const opencodeJson = path19.join(homeDir, ".config", "opencode", "opencode.json");
6918
- if (!existsSync15(opencodeJson)) {
7128
+ const opencodeJson = path20.join(homeDir, ".config", "opencode", "opencode.json");
7129
+ if (!existsSync17(opencodeJson)) {
6919
7130
  return "\u26A0\uFE0F MCP config missing (~/.config/opencode/opencode.json not found) \u2014 exe-os task tools may be unavailable. Run `exe-os opencode`.\n";
6920
7131
  }
6921
- const config2 = JSON.parse(readFileSync12(opencodeJson, "utf8"));
7132
+ const config2 = JSON.parse(readFileSync13(opencodeJson, "utf8"));
6922
7133
  if (!config2.mcp?.["exe-os"]?.enabled) {
6923
7134
  return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server is not enabled in ~/.config/opencode/opencode.json.\n";
6924
7135
  }
6925
7136
  return null;
6926
7137
  }
6927
- const claudeJson = path19.join(homeDir, ".claude.json");
6928
- if (!existsSync15(claudeJson)) {
7138
+ const claudeJson = path20.join(homeDir, ".claude.json");
7139
+ if (!existsSync17(claudeJson)) {
6929
7140
  return "\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n";
6930
7141
  }
6931
- const config = JSON.parse(readFileSync12(claudeJson, "utf8"));
7142
+ const config = JSON.parse(readFileSync13(claudeJson, "utf8"));
6932
7143
  const servers = config.mcpServers;
6933
7144
  if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
6934
7145
  return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server not configured in ~/.claude.json. close_task won't work.\n";