@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -308,9 +308,47 @@ var init_provider_table = __esm({
308
308
  }
309
309
  });
310
310
 
311
+ // src/lib/secure-files.ts
312
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
313
+ import { chmod, mkdir } from "fs/promises";
314
+ async function ensurePrivateDir(dirPath) {
315
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
316
+ try {
317
+ await chmod(dirPath, PRIVATE_DIR_MODE);
318
+ } catch {
319
+ }
320
+ }
321
+ function ensurePrivateDirSync(dirPath) {
322
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
323
+ try {
324
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
325
+ } catch {
326
+ }
327
+ }
328
+ async function enforcePrivateFile(filePath) {
329
+ try {
330
+ await chmod(filePath, PRIVATE_FILE_MODE);
331
+ } catch {
332
+ }
333
+ }
334
+ function enforcePrivateFileSync(filePath) {
335
+ try {
336
+ if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
337
+ } catch {
338
+ }
339
+ }
340
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
341
+ var init_secure_files = __esm({
342
+ "src/lib/secure-files.ts"() {
343
+ "use strict";
344
+ PRIVATE_DIR_MODE = 448;
345
+ PRIVATE_FILE_MODE = 384;
346
+ }
347
+ });
348
+
311
349
  // src/lib/config.ts
312
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
313
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
350
+ import { readFile, writeFile } from "fs/promises";
351
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
314
352
  import path2 from "path";
315
353
  import os2 from "os";
316
354
  function resolveDataDir() {
@@ -318,7 +356,7 @@ function resolveDataDir() {
318
356
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
319
357
  const newDir = path2.join(os2.homedir(), ".exe-os");
320
358
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
321
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
359
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
322
360
  try {
323
361
  renameSync(legacyDir, newDir);
324
362
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -381,9 +419,9 @@ function normalizeAutoUpdate(raw) {
381
419
  }
382
420
  async function loadConfig() {
383
421
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
384
- await mkdir(dir, { recursive: true });
422
+ await ensurePrivateDir(dir);
385
423
  const configPath = path2.join(dir, "config.json");
386
- if (!existsSync2(configPath)) {
424
+ if (!existsSync3(configPath)) {
387
425
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
388
426
  }
389
427
  const raw = await readFile(configPath, "utf-8");
@@ -396,6 +434,7 @@ async function loadConfig() {
396
434
  `);
397
435
  try {
398
436
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
437
+ await enforcePrivateFile(configPath);
399
438
  } catch {
400
439
  }
401
440
  }
@@ -415,6 +454,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
415
454
  var init_config = __esm({
416
455
  "src/lib/config.ts"() {
417
456
  "use strict";
457
+ init_secure_files();
418
458
  EXE_AI_DIR = resolveDataDir();
419
459
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
420
460
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -519,10 +559,10 @@ var init_runtime_table = __esm({
519
559
  });
520
560
 
521
561
  // src/lib/agent-config.ts
522
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
562
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
523
563
  import path3 from "path";
524
564
  function loadAgentConfig() {
525
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
565
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
526
566
  try {
527
567
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
528
568
  } catch {
@@ -543,6 +583,7 @@ var init_agent_config = __esm({
543
583
  "use strict";
544
584
  init_config();
545
585
  init_runtime_table();
586
+ init_secure_files();
546
587
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
547
588
  DEFAULT_MODELS = {
548
589
  claude: "claude-opus-4",
@@ -561,16 +602,16 @@ __export(intercom_queue_exports, {
561
602
  queueIntercom: () => queueIntercom,
562
603
  readQueue: () => readQueue
563
604
  });
564
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
605
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
565
606
  import path4 from "path";
566
607
  import os3 from "os";
567
608
  function ensureDir() {
568
609
  const dir = path4.dirname(QUEUE_PATH);
569
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
610
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
570
611
  }
571
612
  function readQueue() {
572
613
  try {
573
- if (!existsSync4(QUEUE_PATH)) return [];
614
+ if (!existsSync5(QUEUE_PATH)) return [];
574
615
  return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
575
616
  } catch {
576
617
  return [];
@@ -735,7 +776,7 @@ var init_db_retry = __esm({
735
776
 
736
777
  // src/lib/employees.ts
737
778
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
738
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
779
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
739
780
  import { execSync as execSync3 } from "child_process";
740
781
  import path5 from "path";
741
782
  import os4 from "os";
@@ -756,7 +797,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
756
797
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
757
798
  }
758
799
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
759
- if (!existsSync5(employeesPath)) return [];
800
+ if (!existsSync6(employeesPath)) return [];
760
801
  try {
761
802
  return JSON.parse(readFileSync5(employeesPath, "utf-8"));
762
803
  } catch {
@@ -1377,13 +1418,50 @@ var init_database_adapter = __esm({
1377
1418
  }
1378
1419
  });
1379
1420
 
1421
+ // src/lib/daemon-auth.ts
1422
+ import crypto from "crypto";
1423
+ import path7 from "path";
1424
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1425
+ function normalizeToken(token) {
1426
+ if (!token) return null;
1427
+ const trimmed = token.trim();
1428
+ return trimmed.length > 0 ? trimmed : null;
1429
+ }
1430
+ function readDaemonToken() {
1431
+ try {
1432
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1433
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1434
+ } catch {
1435
+ return null;
1436
+ }
1437
+ }
1438
+ function ensureDaemonToken(seed) {
1439
+ const existing = readDaemonToken();
1440
+ if (existing) return existing;
1441
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1442
+ ensurePrivateDirSync(EXE_AI_DIR);
1443
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1444
+ `, "utf8");
1445
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1446
+ return token;
1447
+ }
1448
+ var DAEMON_TOKEN_PATH;
1449
+ var init_daemon_auth = __esm({
1450
+ "src/lib/daemon-auth.ts"() {
1451
+ "use strict";
1452
+ init_config();
1453
+ init_secure_files();
1454
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
1455
+ }
1456
+ });
1457
+
1380
1458
  // src/lib/exe-daemon-client.ts
1381
1459
  import net from "net";
1382
1460
  import os6 from "os";
1383
1461
  import { spawn } from "child_process";
1384
1462
  import { randomUUID } from "crypto";
1385
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1386
- import path7 from "path";
1463
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1464
+ import path8 from "path";
1387
1465
  import { fileURLToPath } from "url";
1388
1466
  function handleData(chunk) {
1389
1467
  _buffer += chunk.toString();
@@ -1411,9 +1489,9 @@ function handleData(chunk) {
1411
1489
  }
1412
1490
  }
1413
1491
  function cleanupStaleFiles() {
1414
- if (existsSync6(PID_PATH)) {
1492
+ if (existsSync8(PID_PATH)) {
1415
1493
  try {
1416
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1494
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1417
1495
  if (pid > 0) {
1418
1496
  try {
1419
1497
  process.kill(pid, 0);
@@ -1434,11 +1512,11 @@ function cleanupStaleFiles() {
1434
1512
  }
1435
1513
  }
1436
1514
  function findPackageRoot() {
1437
- let dir = path7.dirname(fileURLToPath(import.meta.url));
1438
- const { root } = path7.parse(dir);
1515
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
1516
+ const { root } = path8.parse(dir);
1439
1517
  while (dir !== root) {
1440
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
1441
- dir = path7.dirname(dir);
1518
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
1519
+ dir = path8.dirname(dir);
1442
1520
  }
1443
1521
  return null;
1444
1522
  }
@@ -1464,16 +1542,17 @@ function spawnDaemon() {
1464
1542
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1465
1543
  return;
1466
1544
  }
1467
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1468
- if (!existsSync6(daemonPath)) {
1545
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1546
+ if (!existsSync8(daemonPath)) {
1469
1547
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1470
1548
  `);
1471
1549
  return;
1472
1550
  }
1473
1551
  const resolvedPath = daemonPath;
1552
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1474
1553
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1475
1554
  `);
1476
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1555
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1477
1556
  let stderrFd = "ignore";
1478
1557
  try {
1479
1558
  stderrFd = openSync(logPath, "a");
@@ -1491,7 +1570,8 @@ function spawnDaemon() {
1491
1570
  TMUX_PANE: void 0,
1492
1571
  // Prevents resolveExeSession() from scoping to one session
1493
1572
  EXE_DAEMON_SOCK: SOCKET_PATH,
1494
- EXE_DAEMON_PID: PID_PATH
1573
+ EXE_DAEMON_PID: PID_PATH,
1574
+ [DAEMON_TOKEN_ENV]: daemonToken
1495
1575
  }
1496
1576
  });
1497
1577
  child.unref();
@@ -1598,13 +1678,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1598
1678
  return;
1599
1679
  }
1600
1680
  const id = randomUUID();
1681
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1601
1682
  const timer = setTimeout(() => {
1602
1683
  _pending.delete(id);
1603
1684
  resolve({ error: "Request timeout" });
1604
1685
  }, timeoutMs);
1605
1686
  _pending.set(id, { resolve, timer });
1606
1687
  try {
1607
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1688
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1608
1689
  } catch {
1609
1690
  clearTimeout(timer);
1610
1691
  _pending.delete(id);
@@ -1615,17 +1696,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1615
1696
  function isClientConnected() {
1616
1697
  return _connected;
1617
1698
  }
1618
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1699
+ 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;
1619
1700
  var init_exe_daemon_client = __esm({
1620
1701
  "src/lib/exe-daemon-client.ts"() {
1621
1702
  "use strict";
1622
1703
  init_config();
1623
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1624
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1625
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1704
+ init_daemon_auth();
1705
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1706
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1707
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1626
1708
  SPAWN_LOCK_STALE_MS = 3e4;
1627
1709
  CONNECT_TIMEOUT_MS = 15e3;
1628
1710
  REQUEST_TIMEOUT_MS = 3e4;
1711
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1629
1712
  _socket = null;
1630
1713
  _connected = false;
1631
1714
  _buffer = "";
@@ -2204,6 +2287,7 @@ async function ensureSchema() {
2204
2287
  project TEXT NOT NULL,
2205
2288
  summary TEXT NOT NULL,
2206
2289
  task_file TEXT,
2290
+ session_scope TEXT,
2207
2291
  read INTEGER NOT NULL DEFAULT 0,
2208
2292
  created_at TEXT NOT NULL
2209
2293
  );
@@ -2212,7 +2296,7 @@ async function ensureSchema() {
2212
2296
  ON notifications(read);
2213
2297
 
2214
2298
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2215
- ON notifications(agent_id);
2299
+ ON notifications(agent_id, session_scope);
2216
2300
 
2217
2301
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2218
2302
  ON notifications(task_file);
@@ -2250,6 +2334,7 @@ async function ensureSchema() {
2250
2334
  target_agent TEXT NOT NULL,
2251
2335
  target_project TEXT,
2252
2336
  target_device TEXT NOT NULL DEFAULT 'local',
2337
+ session_scope TEXT,
2253
2338
  content TEXT NOT NULL,
2254
2339
  priority TEXT DEFAULT 'normal',
2255
2340
  status TEXT DEFAULT 'pending',
@@ -2263,10 +2348,31 @@ async function ensureSchema() {
2263
2348
  );
2264
2349
 
2265
2350
  CREATE INDEX IF NOT EXISTS idx_messages_target
2266
- ON messages(target_agent, status);
2351
+ ON messages(target_agent, session_scope, status);
2267
2352
 
2268
2353
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2269
- ON messages(target_agent, from_agent, server_seq);
2354
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2355
+ `);
2356
+ try {
2357
+ await client.execute({
2358
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2359
+ args: []
2360
+ });
2361
+ } catch {
2362
+ }
2363
+ try {
2364
+ await client.execute({
2365
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2366
+ args: []
2367
+ });
2368
+ } catch {
2369
+ }
2370
+ await client.executeMultiple(`
2371
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2372
+ ON notifications(agent_id, session_scope, read, created_at);
2373
+
2374
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2375
+ ON messages(target_agent, session_scope, status, created_at);
2270
2376
  `);
2271
2377
  try {
2272
2378
  await client.execute({
@@ -2850,6 +2956,13 @@ async function ensureSchema() {
2850
2956
  } catch {
2851
2957
  }
2852
2958
  }
2959
+ try {
2960
+ await client.execute({
2961
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2962
+ args: []
2963
+ });
2964
+ } catch {
2965
+ }
2853
2966
  }
2854
2967
  async function disposeDatabase() {
2855
2968
  if (_walCheckpointTimer) {
@@ -2888,18 +3001,21 @@ var init_database = __esm({
2888
3001
  });
2889
3002
 
2890
3003
  // src/lib/license.ts
2891
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3004
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2892
3005
  import { randomUUID as randomUUID2 } from "crypto";
2893
- import path8 from "path";
3006
+ import { createRequire as createRequire2 } from "module";
3007
+ import { pathToFileURL as pathToFileURL2 } from "url";
3008
+ import os7 from "os";
3009
+ import path9 from "path";
2894
3010
  import { jwtVerify, importSPKI } from "jose";
2895
3011
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2896
3012
  var init_license = __esm({
2897
3013
  "src/lib/license.ts"() {
2898
3014
  "use strict";
2899
3015
  init_config();
2900
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2901
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2902
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3016
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3017
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3018
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2903
3019
  PLAN_LIMITS = {
2904
3020
  free: { devices: 1, employees: 1, memories: 5e3 },
2905
3021
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2911,12 +3027,12 @@ var init_license = __esm({
2911
3027
  });
2912
3028
 
2913
3029
  // src/lib/plan-limits.ts
2914
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2915
- import path9 from "path";
3030
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3031
+ import path10 from "path";
2916
3032
  function getLicenseSync() {
2917
3033
  try {
2918
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2919
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3034
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3035
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2920
3036
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2921
3037
  const parts = raw.token.split(".");
2922
3038
  if (parts.length !== 3) return freeLicense();
@@ -2954,8 +3070,8 @@ function assertEmployeeLimitSync(rosterPath) {
2954
3070
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2955
3071
  let count = 0;
2956
3072
  try {
2957
- if (existsSync8(filePath)) {
2958
- const raw = readFileSync8(filePath, "utf8");
3073
+ if (existsSync10(filePath)) {
3074
+ const raw = readFileSync9(filePath, "utf8");
2959
3075
  const employees = JSON.parse(raw);
2960
3076
  count = Array.isArray(employees) ? employees.length : 0;
2961
3077
  }
@@ -2984,29 +3100,30 @@ var init_plan_limits = __esm({
2984
3100
  this.name = "PlanLimitError";
2985
3101
  }
2986
3102
  };
2987
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3103
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2988
3104
  }
2989
3105
  });
2990
3106
 
2991
3107
  // src/lib/notifications.ts
2992
- import crypto from "crypto";
2993
- import path10 from "path";
2994
- import os7 from "os";
3108
+ import crypto2 from "crypto";
3109
+ import path11 from "path";
3110
+ import os8 from "os";
2995
3111
  import {
2996
- readFileSync as readFileSync9,
3112
+ readFileSync as readFileSync10,
2997
3113
  readdirSync,
2998
3114
  unlinkSync as unlinkSync3,
2999
- existsSync as existsSync9,
3115
+ existsSync as existsSync11,
3000
3116
  rmdirSync
3001
3117
  } from "fs";
3002
3118
  async function writeNotification(notification) {
3003
3119
  try {
3004
3120
  const client = getClient();
3005
- const id = crypto.randomUUID();
3121
+ const id = crypto2.randomUUID();
3006
3122
  const now = (/* @__PURE__ */ new Date()).toISOString();
3123
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3007
3124
  await client.execute({
3008
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3009
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3125
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3126
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3010
3127
  args: [
3011
3128
  id,
3012
3129
  notification.agentId,
@@ -3015,6 +3132,7 @@ async function writeNotification(notification) {
3015
3132
  notification.project,
3016
3133
  notification.summary,
3017
3134
  notification.taskFile ?? null,
3135
+ sessionScope,
3018
3136
  now
3019
3137
  ]
3020
3138
  });
@@ -3023,12 +3141,14 @@ async function writeNotification(notification) {
3023
3141
  `);
3024
3142
  }
3025
3143
  }
3026
- async function markAsReadByTaskFile(taskFile) {
3144
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3027
3145
  try {
3028
3146
  const client = getClient();
3147
+ const scope = strictSessionScopeFilter(sessionScope);
3029
3148
  await client.execute({
3030
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3031
- args: [taskFile]
3149
+ sql: `UPDATE notifications SET read = 1
3150
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3151
+ args: [taskFile, ...scope.args]
3032
3152
  });
3033
3153
  } catch {
3034
3154
  }
@@ -3037,11 +3157,12 @@ var init_notifications = __esm({
3037
3157
  "src/lib/notifications.ts"() {
3038
3158
  "use strict";
3039
3159
  init_database();
3160
+ init_task_scope();
3040
3161
  }
3041
3162
  });
3042
3163
 
3043
3164
  // src/lib/session-kill-telemetry.ts
3044
- import crypto2 from "crypto";
3165
+ import crypto3 from "crypto";
3045
3166
  async function recordSessionKill(input) {
3046
3167
  try {
3047
3168
  const client = getClient();
@@ -3051,7 +3172,7 @@ async function recordSessionKill(input) {
3051
3172
  ticks_idle, estimated_tokens_saved)
3052
3173
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3053
3174
  args: [
3054
- crypto2.randomUUID(),
3175
+ crypto3.randomUUID(),
3055
3176
  input.sessionName,
3056
3177
  input.agentId,
3057
3178
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3146,12 +3267,12 @@ __export(tasks_crud_exports, {
3146
3267
  updateTaskStatus: () => updateTaskStatus,
3147
3268
  writeCheckpoint: () => writeCheckpoint
3148
3269
  });
3149
- import crypto3 from "crypto";
3150
- import path11 from "path";
3151
- import os8 from "os";
3270
+ import crypto4 from "crypto";
3271
+ import path12 from "path";
3272
+ import os9 from "os";
3152
3273
  import { execSync as execSync4 } from "child_process";
3153
3274
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3154
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3275
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3155
3276
  async function writeCheckpoint(input) {
3156
3277
  const client = getClient();
3157
3278
  const row = await resolveTask(client, input.taskId);
@@ -3267,7 +3388,7 @@ async function resolveTask(client, identifier, scopeSession) {
3267
3388
  }
3268
3389
  async function createTaskCore(input) {
3269
3390
  const client = getClient();
3270
- const id = crypto3.randomUUID();
3391
+ const id = crypto4.randomUUID();
3271
3392
  const now = (/* @__PURE__ */ new Date()).toISOString();
3272
3393
  const slug = slugify(input.title);
3273
3394
  let earlySessionScope = null;
@@ -3326,8 +3447,8 @@ ${laneWarning}` : laneWarning;
3326
3447
  }
3327
3448
  if (input.baseDir) {
3328
3449
  try {
3329
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3330
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3450
+ await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3451
+ await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3331
3452
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3332
3453
  await ensureGitignoreExe(input.baseDir);
3333
3454
  } catch {
@@ -3363,13 +3484,19 @@ ${laneWarning}` : laneWarning;
3363
3484
  });
3364
3485
  if (input.baseDir) {
3365
3486
  try {
3366
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3367
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
3368
- const mdDir = path11.dirname(mdPath);
3369
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3487
+ const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
3488
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
3489
+ const mdDir = path12.dirname(mdPath);
3490
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3370
3491
  const reviewer = input.reviewer ?? input.assignedBy;
3371
3492
  const mdContent = `# ${input.title}
3372
3493
 
3494
+ ## MANDATORY: When done
3495
+
3496
+ You MUST call update_task with status "done" and a result summary when finished.
3497
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3498
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3499
+
3373
3500
  **ID:** ${id}
3374
3501
  **Status:** ${initialStatus}
3375
3502
  **Priority:** ${input.priority}
@@ -3383,12 +3510,6 @@ ${laneWarning}` : laneWarning;
3383
3510
  ## Context
3384
3511
 
3385
3512
  ${input.context}
3386
-
3387
- ## MANDATORY: When done
3388
-
3389
- You MUST call update_task with status "done" and a result summary when finished.
3390
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3391
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3392
3513
  `;
3393
3514
  await writeFile3(mdPath, mdContent, "utf-8");
3394
3515
  } catch (err) {
@@ -3637,7 +3758,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3637
3758
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3638
3759
  } catch {
3639
3760
  }
3640
- if (input.status === "done" || input.status === "cancelled") {
3761
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3641
3762
  try {
3642
3763
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3643
3764
  clearQueueForAgent2(String(row.assigned_to));
@@ -3666,9 +3787,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3666
3787
  return { taskFile, assignedTo, assignedBy, taskSlug };
3667
3788
  }
3668
3789
  async function ensureArchitectureDoc(baseDir, projectName2) {
3669
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3790
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3670
3791
  try {
3671
- if (existsSync10(archPath)) return;
3792
+ if (existsSync12(archPath)) return;
3672
3793
  const template = [
3673
3794
  `# ${projectName2} \u2014 System Architecture`,
3674
3795
  "",
@@ -3701,10 +3822,10 @@ async function ensureArchitectureDoc(baseDir, projectName2) {
3701
3822
  }
3702
3823
  }
3703
3824
  async function ensureGitignoreExe(baseDir) {
3704
- const gitignorePath = path11.join(baseDir, ".gitignore");
3825
+ const gitignorePath = path12.join(baseDir, ".gitignore");
3705
3826
  try {
3706
- if (existsSync10(gitignorePath)) {
3707
- const content = readFileSync10(gitignorePath, "utf-8");
3827
+ if (existsSync12(gitignorePath)) {
3828
+ const content = readFileSync11(gitignorePath, "utf-8");
3708
3829
  if (/^\/?exe\/?$/m.test(content)) return;
3709
3830
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3710
3831
  } else {
@@ -3735,58 +3856,42 @@ var init_tasks_crud = __esm({
3735
3856
  });
3736
3857
 
3737
3858
  // src/lib/tasks-review.ts
3738
- import path12 from "path";
3739
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3859
+ import path13 from "path";
3860
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3740
3861
  async function countPendingReviews(sessionScope) {
3741
3862
  const client = getClient();
3742
- if (sessionScope) {
3743
- const result2 = await client.execute({
3744
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3745
- args: [sessionScope]
3746
- });
3747
- return Number(result2.rows[0]?.cnt) || 0;
3748
- }
3863
+ const scope = strictSessionScopeFilter(
3864
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3865
+ );
3749
3866
  const result = await client.execute({
3750
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3751
- args: []
3867
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3868
+ WHERE status = 'needs_review'${scope.sql}`,
3869
+ args: [...scope.args]
3752
3870
  });
3753
3871
  return Number(result.rows[0]?.cnt) || 0;
3754
3872
  }
3755
3873
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3756
3874
  const client = getClient();
3757
- if (sessionScope) {
3758
- const result2 = await client.execute({
3759
- sql: `SELECT COUNT(*) as cnt FROM tasks
3760
- WHERE status = 'needs_review' AND updated_at > ?
3761
- AND session_scope = ?`,
3762
- args: [sinceIso, sessionScope]
3763
- });
3764
- return Number(result2.rows[0]?.cnt) || 0;
3765
- }
3875
+ const scope = strictSessionScopeFilter(
3876
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3877
+ );
3766
3878
  const result = await client.execute({
3767
3879
  sql: `SELECT COUNT(*) as cnt FROM tasks
3768
- WHERE status = 'needs_review' AND updated_at > ?`,
3769
- args: [sinceIso]
3880
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3881
+ args: [sinceIso, ...scope.args]
3770
3882
  });
3771
3883
  return Number(result.rows[0]?.cnt) || 0;
3772
3884
  }
3773
3885
  async function listPendingReviews(limit, sessionScope) {
3774
3886
  const client = getClient();
3775
- if (sessionScope) {
3776
- const result2 = await client.execute({
3777
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3778
- WHERE status = 'needs_review'
3779
- AND session_scope = ?
3780
- ORDER BY updated_at ASC LIMIT ?`,
3781
- args: [sessionScope, limit]
3782
- });
3783
- return result2.rows;
3784
- }
3887
+ const scope = strictSessionScopeFilter(
3888
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3889
+ );
3785
3890
  const result = await client.execute({
3786
3891
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3787
- WHERE status = 'needs_review'
3892
+ WHERE status = 'needs_review'${scope.sql}
3788
3893
  ORDER BY updated_at ASC LIMIT ?`,
3789
- args: [limit]
3894
+ args: [...scope.args, limit]
3790
3895
  });
3791
3896
  return result.rows;
3792
3897
  }
@@ -3798,7 +3903,7 @@ async function cleanupOrphanedReviews() {
3798
3903
  WHERE status IN ('open', 'needs_review', 'in_progress')
3799
3904
  AND assigned_by = 'system'
3800
3905
  AND title LIKE 'Review:%'
3801
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3906
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3802
3907
  args: [now]
3803
3908
  });
3804
3909
  const r1b = await client.execute({
@@ -3917,11 +4022,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3917
4022
  );
3918
4023
  }
3919
4024
  try {
3920
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3921
- if (existsSync11(cacheDir)) {
4025
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4026
+ if (existsSync13(cacheDir)) {
3922
4027
  for (const f of readdirSync2(cacheDir)) {
3923
4028
  if (f.startsWith("review-notified-")) {
3924
- unlinkSync4(path12.join(cacheDir, f));
4029
+ unlinkSync4(path13.join(cacheDir, f));
3925
4030
  }
3926
4031
  }
3927
4032
  }
@@ -3938,11 +4043,12 @@ var init_tasks_review = __esm({
3938
4043
  init_tmux_routing();
3939
4044
  init_session_key();
3940
4045
  init_state_bus();
4046
+ init_task_scope();
3941
4047
  }
3942
4048
  });
3943
4049
 
3944
4050
  // src/lib/tasks-chain.ts
3945
- import path13 from "path";
4051
+ import path14 from "path";
3946
4052
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3947
4053
  async function cascadeUnblock(taskId, baseDir, now) {
3948
4054
  const client = getClient();
@@ -3959,7 +4065,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3959
4065
  });
3960
4066
  for (const ur of unblockedRows.rows) {
3961
4067
  try {
3962
- const ubFile = path13.join(baseDir, String(ur.task_file));
4068
+ const ubFile = path14.join(baseDir, String(ur.task_file));
3963
4069
  let ubContent = await readFile3(ubFile, "utf-8");
3964
4070
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3965
4071
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3994,7 +4100,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName2) {
3994
4100
  const scScope = sessionScopeFilter();
3995
4101
  const remaining = await client.execute({
3996
4102
  sql: `SELECT COUNT(*) as cnt FROM tasks
3997
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4103
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3998
4104
  args: [parentTaskId, ...scScope.args]
3999
4105
  });
4000
4106
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4028,7 +4134,7 @@ var init_tasks_chain = __esm({
4028
4134
 
4029
4135
  // src/lib/project-name.ts
4030
4136
  import { execSync as execSync5 } from "child_process";
4031
- import path14 from "path";
4137
+ import path15 from "path";
4032
4138
  function getProjectName(cwd) {
4033
4139
  const dir = cwd ?? process.cwd();
4034
4140
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4041,7 +4147,7 @@ function getProjectName(cwd) {
4041
4147
  timeout: 2e3,
4042
4148
  stdio: ["pipe", "pipe", "pipe"]
4043
4149
  }).trim();
4044
- repoRoot = path14.dirname(gitCommonDir);
4150
+ repoRoot = path15.dirname(gitCommonDir);
4045
4151
  } catch {
4046
4152
  repoRoot = execSync5("git rev-parse --show-toplevel", {
4047
4153
  cwd: dir,
@@ -4050,11 +4156,11 @@ function getProjectName(cwd) {
4050
4156
  stdio: ["pipe", "pipe", "pipe"]
4051
4157
  }).trim();
4052
4158
  }
4053
- _cached2 = path14.basename(repoRoot);
4159
+ _cached2 = path15.basename(repoRoot);
4054
4160
  _cachedCwd = dir;
4055
4161
  return _cached2;
4056
4162
  } catch {
4057
- _cached2 = path14.basename(dir);
4163
+ _cached2 = path15.basename(dir);
4058
4164
  _cachedCwd = dir;
4059
4165
  return _cached2;
4060
4166
  }
@@ -4197,10 +4303,10 @@ var init_tasks_notify = __esm({
4197
4303
  });
4198
4304
 
4199
4305
  // src/lib/behaviors.ts
4200
- import crypto4 from "crypto";
4306
+ import crypto5 from "crypto";
4201
4307
  async function storeBehavior(opts) {
4202
4308
  const client = getClient();
4203
- const id = crypto4.randomUUID();
4309
+ const id = crypto5.randomUUID();
4204
4310
  const now = (/* @__PURE__ */ new Date()).toISOString();
4205
4311
  await client.execute({
4206
4312
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4229,7 +4335,7 @@ __export(skill_learning_exports, {
4229
4335
  storeTrajectory: () => storeTrajectory,
4230
4336
  sweepTrajectories: () => sweepTrajectories
4231
4337
  });
4232
- import crypto5 from "crypto";
4338
+ import crypto6 from "crypto";
4233
4339
  async function extractTrajectory(taskId, agentId) {
4234
4340
  const client = getClient();
4235
4341
  const result = await client.execute({
@@ -4258,11 +4364,11 @@ async function extractTrajectory(taskId, agentId) {
4258
4364
  return signature;
4259
4365
  }
4260
4366
  function hashSignature(signature) {
4261
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4367
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4262
4368
  }
4263
4369
  async function storeTrajectory(opts) {
4264
4370
  const client = getClient();
4265
- const id = crypto5.randomUUID();
4371
+ const id = crypto6.randomUUID();
4266
4372
  const now = (/* @__PURE__ */ new Date()).toISOString();
4267
4373
  const signatureHash = hashSignature(opts.signature);
4268
4374
  await client.execute({
@@ -4527,8 +4633,8 @@ __export(tasks_exports, {
4527
4633
  updateTaskStatus: () => updateTaskStatus,
4528
4634
  writeCheckpoint: () => writeCheckpoint
4529
4635
  });
4530
- import path15 from "path";
4531
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4636
+ import path16 from "path";
4637
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4532
4638
  async function createTask(input) {
4533
4639
  const result = await createTaskCore(input);
4534
4640
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4547,12 +4653,12 @@ async function updateTask(input) {
4547
4653
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4548
4654
  try {
4549
4655
  const agent = String(row.assigned_to);
4550
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4551
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4656
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4657
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4552
4658
  if (input.status === "in_progress") {
4553
4659
  mkdirSync5(cacheDir, { recursive: true });
4554
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4555
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4660
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4661
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4556
4662
  try {
4557
4663
  unlinkSync5(cachePath);
4558
4664
  } catch {
@@ -4560,10 +4666,10 @@ async function updateTask(input) {
4560
4666
  }
4561
4667
  } catch {
4562
4668
  }
4563
- if (input.status === "done") {
4669
+ if (input.status === "done" || input.status === "closed") {
4564
4670
  await cleanupReviewFile(row, taskFile, input.baseDir);
4565
4671
  }
4566
- if (input.status === "done" || input.status === "cancelled") {
4672
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4567
4673
  try {
4568
4674
  const client = getClient();
4569
4675
  const taskTitle = String(row.title);
@@ -4579,7 +4685,7 @@ async function updateTask(input) {
4579
4685
  if (!isCoordinatorName(assignedAgent)) {
4580
4686
  try {
4581
4687
  const draftClient = getClient();
4582
- if (input.status === "done") {
4688
+ if (input.status === "done" || input.status === "closed") {
4583
4689
  await draftClient.execute({
4584
4690
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4585
4691
  args: [assignedAgent]
@@ -4596,7 +4702,7 @@ async function updateTask(input) {
4596
4702
  try {
4597
4703
  const client = getClient();
4598
4704
  const cascaded = await client.execute({
4599
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4705
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4600
4706
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4601
4707
  args: [now, taskId]
4602
4708
  });
@@ -4609,14 +4715,14 @@ async function updateTask(input) {
4609
4715
  } catch {
4610
4716
  }
4611
4717
  }
4612
- const isTerminal = input.status === "done" || input.status === "needs_review";
4718
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4613
4719
  if (isTerminal) {
4614
4720
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4615
4721
  if (!isCoordinator) {
4616
4722
  notifyTaskDone();
4617
4723
  }
4618
4724
  await markTaskNotificationsRead(taskFile);
4619
- if (input.status === "done") {
4725
+ if (input.status === "done" || input.status === "closed") {
4620
4726
  try {
4621
4727
  await cascadeUnblock(taskId, input.baseDir, now);
4622
4728
  } catch {
@@ -4636,7 +4742,7 @@ async function updateTask(input) {
4636
4742
  }
4637
4743
  }
4638
4744
  }
4639
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4745
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4640
4746
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4641
4747
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4642
4748
  taskId,
@@ -5008,6 +5114,7 @@ __export(tmux_routing_exports, {
5008
5114
  isEmployeeAlive: () => isEmployeeAlive,
5009
5115
  isExeSession: () => isExeSession,
5010
5116
  isSessionBusy: () => isSessionBusy,
5117
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5011
5118
  notifyParentExe: () => notifyParentExe,
5012
5119
  parseParentExe: () => parseParentExe,
5013
5120
  registerParentExe: () => registerParentExe,
@@ -5018,13 +5125,13 @@ __export(tmux_routing_exports, {
5018
5125
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5019
5126
  });
5020
5127
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
5021
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5022
- import path16 from "path";
5023
- import os9 from "os";
5128
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5129
+ import path17 from "path";
5130
+ import os10 from "os";
5024
5131
  import { fileURLToPath as fileURLToPath2 } from "url";
5025
5132
  import { unlinkSync as unlinkSync6 } from "fs";
5026
5133
  function spawnLockPath(sessionName) {
5027
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5134
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5028
5135
  }
5029
5136
  function isProcessAlive(pid) {
5030
5137
  try {
@@ -5035,13 +5142,13 @@ function isProcessAlive(pid) {
5035
5142
  }
5036
5143
  }
5037
5144
  function acquireSpawnLock2(sessionName) {
5038
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5145
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5039
5146
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5040
5147
  }
5041
5148
  const lockFile = spawnLockPath(sessionName);
5042
- if (existsSync12(lockFile)) {
5149
+ if (existsSync14(lockFile)) {
5043
5150
  try {
5044
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5151
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5045
5152
  const age = Date.now() - lock.timestamp;
5046
5153
  if (isProcessAlive(lock.pid) && age < 6e4) {
5047
5154
  return false;
@@ -5049,7 +5156,7 @@ function acquireSpawnLock2(sessionName) {
5049
5156
  } catch {
5050
5157
  }
5051
5158
  }
5052
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5159
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5053
5160
  return true;
5054
5161
  }
5055
5162
  function releaseSpawnLock2(sessionName) {
@@ -5061,13 +5168,13 @@ function releaseSpawnLock2(sessionName) {
5061
5168
  function resolveBehaviorsExporterScript() {
5062
5169
  try {
5063
5170
  const thisFile = fileURLToPath2(import.meta.url);
5064
- const scriptPath = path16.join(
5065
- path16.dirname(thisFile),
5171
+ const scriptPath = path17.join(
5172
+ path17.dirname(thisFile),
5066
5173
  "..",
5067
5174
  "bin",
5068
5175
  "exe-export-behaviors.js"
5069
5176
  );
5070
- return existsSync12(scriptPath) ? scriptPath : null;
5177
+ return existsSync14(scriptPath) ? scriptPath : null;
5071
5178
  } catch {
5072
5179
  return null;
5073
5180
  }
@@ -5133,12 +5240,12 @@ function extractRootExe(name) {
5133
5240
  return parts.length > 0 ? parts[parts.length - 1] : null;
5134
5241
  }
5135
5242
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5136
- if (!existsSync12(SESSION_CACHE)) {
5243
+ if (!existsSync14(SESSION_CACHE)) {
5137
5244
  mkdirSync6(SESSION_CACHE, { recursive: true });
5138
5245
  }
5139
5246
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5140
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5141
- writeFileSync7(filePath, JSON.stringify({
5247
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5248
+ writeFileSync8(filePath, JSON.stringify({
5142
5249
  parentExe: rootExe,
5143
5250
  dispatchedBy: dispatchedBy || rootExe,
5144
5251
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5146,7 +5253,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5146
5253
  }
5147
5254
  function getParentExe(sessionKey) {
5148
5255
  try {
5149
- const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5256
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5150
5257
  return data.parentExe || null;
5151
5258
  } catch {
5152
5259
  return null;
@@ -5154,8 +5261,8 @@ function getParentExe(sessionKey) {
5154
5261
  }
5155
5262
  function getDispatchedBy(sessionKey) {
5156
5263
  try {
5157
- const data = JSON.parse(readFileSync11(
5158
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5264
+ const data = JSON.parse(readFileSync12(
5265
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5159
5266
  "utf8"
5160
5267
  ));
5161
5268
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5225,8 +5332,8 @@ async function verifyPaneAtCapacity(sessionName) {
5225
5332
  }
5226
5333
  function readDebounceState() {
5227
5334
  try {
5228
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5229
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5335
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5336
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5230
5337
  const state = {};
5231
5338
  for (const [key, val] of Object.entries(raw)) {
5232
5339
  if (typeof val === "number") {
@@ -5242,8 +5349,8 @@ function readDebounceState() {
5242
5349
  }
5243
5350
  function writeDebounceState(state) {
5244
5351
  try {
5245
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5246
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5352
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5353
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5247
5354
  } catch {
5248
5355
  }
5249
5356
  }
@@ -5341,8 +5448,8 @@ function sendIntercom(targetSession) {
5341
5448
  try {
5342
5449
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5343
5450
  const agent = baseAgentName(rawAgent);
5344
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
5345
- if (existsSync12(markerPath)) {
5451
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5452
+ if (existsSync14(markerPath)) {
5346
5453
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5347
5454
  return "debounced";
5348
5455
  }
@@ -5351,8 +5458,8 @@ function sendIntercom(targetSession) {
5351
5458
  try {
5352
5459
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5353
5460
  const agent = baseAgentName(rawAgent);
5354
- const taskDir = path16.join(process.cwd(), "exe", agent);
5355
- if (existsSync12(taskDir)) {
5461
+ const taskDir = path17.join(process.cwd(), "exe", agent);
5462
+ if (existsSync14(taskDir)) {
5356
5463
  const files = readdirSync3(taskDir).filter(
5357
5464
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5358
5465
  );
@@ -5412,6 +5519,21 @@ function notifyParentExe(sessionKey) {
5412
5519
  }
5413
5520
  return true;
5414
5521
  }
5522
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5523
+ const transport = getTransport();
5524
+ try {
5525
+ const sessions = transport.listSessions();
5526
+ if (!sessions.includes(coordinatorSession)) return false;
5527
+ execSync6(
5528
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5529
+ { timeout: 3e3 }
5530
+ );
5531
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5532
+ return true;
5533
+ } catch {
5534
+ return false;
5535
+ }
5536
+ }
5415
5537
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5416
5538
  if (isCoordinatorName(employeeName)) {
5417
5539
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5485,26 +5607,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5485
5607
  const transport = getTransport();
5486
5608
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5487
5609
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5488
- const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
5489
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5490
- if (!existsSync12(logDir)) {
5610
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
5611
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5612
+ if (!existsSync14(logDir)) {
5491
5613
  mkdirSync6(logDir, { recursive: true });
5492
5614
  }
5493
5615
  transport.kill(sessionName);
5494
5616
  let cleanupSuffix = "";
5495
5617
  try {
5496
5618
  const thisFile = fileURLToPath2(import.meta.url);
5497
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5498
- if (existsSync12(cleanupScript)) {
5619
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5620
+ if (existsSync14(cleanupScript)) {
5499
5621
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5500
5622
  }
5501
5623
  } catch {
5502
5624
  }
5503
5625
  try {
5504
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
5626
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
5505
5627
  let claudeJson = {};
5506
5628
  try {
5507
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5629
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5508
5630
  } catch {
5509
5631
  }
5510
5632
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5512,17 +5634,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5512
5634
  const trustDir = opts?.cwd ?? projectDir;
5513
5635
  if (!projects[trustDir]) projects[trustDir] = {};
5514
5636
  projects[trustDir].hasTrustDialogAccepted = true;
5515
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5637
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5516
5638
  } catch {
5517
5639
  }
5518
5640
  try {
5519
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
5641
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
5520
5642
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5521
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
5522
- const settingsPath = path16.join(projSettingsDir, "settings.json");
5643
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
5644
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
5523
5645
  let settings = {};
5524
5646
  try {
5525
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5647
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5526
5648
  } catch {
5527
5649
  }
5528
5650
  const perms = settings.permissions ?? {};
@@ -5551,7 +5673,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5551
5673
  perms.allow = allow;
5552
5674
  settings.permissions = perms;
5553
5675
  mkdirSync6(projSettingsDir, { recursive: true });
5554
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5676
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5555
5677
  }
5556
5678
  } catch {
5557
5679
  }
@@ -5566,8 +5688,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5566
5688
  let behaviorsFlag = "";
5567
5689
  let legacyFallbackWarned = false;
5568
5690
  if (!useExeAgent && !useBinSymlink) {
5569
- const identityPath = path16.join(
5570
- os9.homedir(),
5691
+ const identityPath = path17.join(
5692
+ os10.homedir(),
5571
5693
  ".exe-os",
5572
5694
  "identity",
5573
5695
  `${employeeName}.md`
@@ -5576,13 +5698,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5576
5698
  const hasAgentFlag = claudeSupportsAgentFlag();
5577
5699
  if (hasAgentFlag) {
5578
5700
  identityFlag = ` --agent ${employeeName}`;
5579
- } else if (existsSync12(identityPath)) {
5701
+ } else if (existsSync14(identityPath)) {
5580
5702
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5581
5703
  legacyFallbackWarned = true;
5582
5704
  }
5583
5705
  const behaviorsFile = exportBehaviorsSync(
5584
5706
  employeeName,
5585
- path16.basename(spawnCwd),
5707
+ path17.basename(spawnCwd),
5586
5708
  sessionName
5587
5709
  );
5588
5710
  if (behaviorsFile) {
@@ -5597,16 +5719,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5597
5719
  }
5598
5720
  let sessionContextFlag = "";
5599
5721
  try {
5600
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
5722
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
5601
5723
  mkdirSync6(ctxDir, { recursive: true });
5602
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
5724
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
5603
5725
  const ctxContent = [
5604
5726
  `## Session Context`,
5605
5727
  `You are running in tmux session: ${sessionName}.`,
5606
5728
  `Your parent coordinator session is ${exeSession}.`,
5607
5729
  `Your employees (if any) use the -${exeSession} suffix.`
5608
5730
  ].join("\n");
5609
- writeFileSync7(ctxFile, ctxContent);
5731
+ writeFileSync8(ctxFile, ctxContent);
5610
5732
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5611
5733
  } catch {
5612
5734
  }
@@ -5683,8 +5805,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5683
5805
  transport.pipeLog(sessionName, logFile);
5684
5806
  try {
5685
5807
  const mySession = getMySession();
5686
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5687
- writeFileSync7(dispatchInfo, JSON.stringify({
5808
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5809
+ writeFileSync8(dispatchInfo, JSON.stringify({
5688
5810
  dispatchedBy: mySession,
5689
5811
  rootExe: exeSession,
5690
5812
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5758,15 +5880,15 @@ var init_tmux_routing = __esm({
5758
5880
  init_intercom_queue();
5759
5881
  init_plan_limits();
5760
5882
  init_employees();
5761
- SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
5762
- SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
5883
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
5884
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
5763
5885
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5764
5886
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5765
5887
  VERIFY_PANE_LINES = 200;
5766
5888
  INTERCOM_DEBOUNCE_MS = 3e4;
5767
5889
  CODEX_DEBOUNCE_MS = 12e4;
5768
- INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
5769
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
5890
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
5891
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5770
5892
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5771
5893
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5772
5894
  }
@@ -5789,6 +5911,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5789
5911
  args: [scope]
5790
5912
  };
5791
5913
  }
5914
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5915
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5916
+ if (!scope) return { sql: "", args: [] };
5917
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5918
+ return {
5919
+ sql: ` AND ${col} = ?`,
5920
+ args: [scope]
5921
+ };
5922
+ }
5792
5923
  var init_task_scope = __esm({
5793
5924
  "src/lib/task-scope.ts"() {
5794
5925
  "use strict";
@@ -5807,14 +5938,14 @@ var init_memory = __esm({
5807
5938
 
5808
5939
  // src/lib/keychain.ts
5809
5940
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5810
- import { existsSync as existsSync13 } from "fs";
5811
- import path17 from "path";
5812
- import os10 from "os";
5941
+ import { existsSync as existsSync15 } from "fs";
5942
+ import path18 from "path";
5943
+ import os11 from "os";
5813
5944
  function getKeyDir() {
5814
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
5945
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
5815
5946
  }
5816
5947
  function getKeyPath() {
5817
- return path17.join(getKeyDir(), "master.key");
5948
+ return path18.join(getKeyDir(), "master.key");
5818
5949
  }
5819
5950
  async function tryKeytar() {
5820
5951
  try {
@@ -5835,9 +5966,9 @@ async function getMasterKey() {
5835
5966
  }
5836
5967
  }
5837
5968
  const keyPath = getKeyPath();
5838
- if (!existsSync13(keyPath)) {
5969
+ if (!existsSync15(keyPath)) {
5839
5970
  process.stderr.write(
5840
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5971
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5841
5972
  `
5842
5973
  );
5843
5974
  return null;
@@ -5867,6 +5998,7 @@ var shard_manager_exports = {};
5867
5998
  __export(shard_manager_exports, {
5868
5999
  disposeShards: () => disposeShards,
5869
6000
  ensureShardSchema: () => ensureShardSchema,
6001
+ getOpenShardCount: () => getOpenShardCount,
5870
6002
  getReadyShardClient: () => getReadyShardClient,
5871
6003
  getShardClient: () => getShardClient,
5872
6004
  getShardsDir: () => getShardsDir,
@@ -5875,15 +6007,18 @@ __export(shard_manager_exports, {
5875
6007
  listShards: () => listShards,
5876
6008
  shardExists: () => shardExists
5877
6009
  });
5878
- import path18 from "path";
5879
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6010
+ import path19 from "path";
6011
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5880
6012
  import { createClient as createClient2 } from "@libsql/client";
5881
6013
  function initShardManager(encryptionKey) {
5882
6014
  _encryptionKey = encryptionKey;
5883
- if (!existsSync14(SHARDS_DIR)) {
6015
+ if (!existsSync16(SHARDS_DIR)) {
5884
6016
  mkdirSync7(SHARDS_DIR, { recursive: true });
5885
6017
  }
5886
6018
  _shardingEnabled = true;
6019
+ if (_evictionTimer) clearInterval(_evictionTimer);
6020
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6021
+ _evictionTimer.unref();
5887
6022
  }
5888
6023
  function isShardingEnabled() {
5889
6024
  return _shardingEnabled;
@@ -5900,21 +6035,28 @@ function getShardClient(projectName2) {
5900
6035
  throw new Error(`Invalid project name for shard: "${projectName2}"`);
5901
6036
  }
5902
6037
  const cached = _shards.get(safeName);
5903
- if (cached) return cached;
5904
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
6038
+ if (cached) {
6039
+ _shardLastAccess.set(safeName, Date.now());
6040
+ return cached;
6041
+ }
6042
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6043
+ evictLRU();
6044
+ }
6045
+ const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
5905
6046
  const client = createClient2({
5906
6047
  url: `file:${dbPath}`,
5907
6048
  encryptionKey: _encryptionKey
5908
6049
  });
5909
6050
  _shards.set(safeName, client);
6051
+ _shardLastAccess.set(safeName, Date.now());
5910
6052
  return client;
5911
6053
  }
5912
6054
  function shardExists(projectName2) {
5913
6055
  const safeName = projectName2.replace(/[^a-zA-Z0-9_-]/g, "_");
5914
- return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
6056
+ return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
5915
6057
  }
5916
6058
  function listShards() {
5917
- if (!existsSync14(SHARDS_DIR)) return [];
6059
+ if (!existsSync16(SHARDS_DIR)) return [];
5918
6060
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5919
6061
  }
5920
6062
  async function ensureShardSchema(client) {
@@ -5966,6 +6108,8 @@ async function ensureShardSchema(client) {
5966
6108
  for (const col of [
5967
6109
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5968
6110
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6111
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6112
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5969
6113
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5970
6114
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5971
6115
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -6103,21 +6247,69 @@ async function getReadyShardClient(projectName2) {
6103
6247
  await ensureShardSchema(client);
6104
6248
  return client;
6105
6249
  }
6250
+ function evictLRU() {
6251
+ let oldest = null;
6252
+ let oldestTime = Infinity;
6253
+ for (const [name, time] of _shardLastAccess) {
6254
+ if (time < oldestTime) {
6255
+ oldestTime = time;
6256
+ oldest = name;
6257
+ }
6258
+ }
6259
+ if (oldest) {
6260
+ const client = _shards.get(oldest);
6261
+ if (client) {
6262
+ client.close();
6263
+ }
6264
+ _shards.delete(oldest);
6265
+ _shardLastAccess.delete(oldest);
6266
+ }
6267
+ }
6268
+ function evictIdleShards() {
6269
+ const now = Date.now();
6270
+ const toEvict = [];
6271
+ for (const [name, lastAccess] of _shardLastAccess) {
6272
+ if (now - lastAccess > SHARD_IDLE_MS) {
6273
+ toEvict.push(name);
6274
+ }
6275
+ }
6276
+ for (const name of toEvict) {
6277
+ const client = _shards.get(name);
6278
+ if (client) {
6279
+ client.close();
6280
+ }
6281
+ _shards.delete(name);
6282
+ _shardLastAccess.delete(name);
6283
+ }
6284
+ }
6285
+ function getOpenShardCount() {
6286
+ return _shards.size;
6287
+ }
6106
6288
  function disposeShards() {
6289
+ if (_evictionTimer) {
6290
+ clearInterval(_evictionTimer);
6291
+ _evictionTimer = null;
6292
+ }
6107
6293
  for (const [, client] of _shards) {
6108
6294
  client.close();
6109
6295
  }
6110
6296
  _shards.clear();
6297
+ _shardLastAccess.clear();
6111
6298
  _shardingEnabled = false;
6112
6299
  _encryptionKey = null;
6113
6300
  }
6114
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6301
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
6115
6302
  var init_shard_manager = __esm({
6116
6303
  "src/lib/shard-manager.ts"() {
6117
6304
  "use strict";
6118
6305
  init_config();
6119
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
6306
+ SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6307
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6308
+ MAX_OPEN_SHARDS = 10;
6309
+ EVICTION_INTERVAL_MS = 60 * 1e3;
6120
6310
  _shards = /* @__PURE__ */ new Map();
6311
+ _shardLastAccess = /* @__PURE__ */ new Map();
6312
+ _evictionTimer = null;
6121
6313
  _encryptionKey = null;
6122
6314
  _shardingEnabled = false;
6123
6315
  }
@@ -7085,13 +7277,13 @@ async function sweepTasks(projectName2, options = {}) {
7085
7277
  }
7086
7278
 
7087
7279
  // src/bin/git-sweep.ts
7088
- import path19 from "path";
7280
+ import path20 from "path";
7089
7281
  var args = process.argv.slice(2);
7090
7282
  var dryRun = args.includes("--dry-run");
7091
7283
  var projectIdx = args.indexOf("--project");
7092
7284
  var limitIdx = args.indexOf("--limit");
7093
7285
  var staleIdx = args.indexOf("--stale");
7094
- var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path19.sep).pop();
7286
+ var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path20.sep).pop();
7095
7287
  var commitLimit = limitIdx >= 0 ? Number(args[limitIdx + 1]) : void 0;
7096
7288
  var staleMinutes = staleIdx >= 0 ? Number(args[staleIdx + 1]) : void 0;
7097
7289
  async function main() {