@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,34 @@ 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
+ async function enforcePrivateFile(filePath) {
322
+ try {
323
+ await chmod(filePath, PRIVATE_FILE_MODE);
324
+ } catch {
325
+ }
326
+ }
327
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
328
+ var init_secure_files = __esm({
329
+ "src/lib/secure-files.ts"() {
330
+ "use strict";
331
+ PRIVATE_DIR_MODE = 448;
332
+ PRIVATE_FILE_MODE = 384;
333
+ }
334
+ });
335
+
311
336
  // src/lib/config.ts
312
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
313
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
337
+ import { readFile, writeFile } from "fs/promises";
338
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
314
339
  import path2 from "path";
315
340
  import os2 from "os";
316
341
  function resolveDataDir() {
@@ -318,7 +343,7 @@ function resolveDataDir() {
318
343
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
319
344
  const newDir = path2.join(os2.homedir(), ".exe-os");
320
345
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
321
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
346
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
322
347
  try {
323
348
  renameSync(legacyDir, newDir);
324
349
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -381,9 +406,9 @@ function normalizeAutoUpdate(raw) {
381
406
  }
382
407
  async function loadConfig() {
383
408
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
384
- await mkdir(dir, { recursive: true });
409
+ await ensurePrivateDir(dir);
385
410
  const configPath = path2.join(dir, "config.json");
386
- if (!existsSync2(configPath)) {
411
+ if (!existsSync3(configPath)) {
387
412
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
388
413
  }
389
414
  const raw = await readFile(configPath, "utf-8");
@@ -396,6 +421,7 @@ async function loadConfig() {
396
421
  `);
397
422
  try {
398
423
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
424
+ await enforcePrivateFile(configPath);
399
425
  } catch {
400
426
  }
401
427
  }
@@ -415,6 +441,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
415
441
  var init_config = __esm({
416
442
  "src/lib/config.ts"() {
417
443
  "use strict";
444
+ init_secure_files();
418
445
  EXE_AI_DIR = resolveDataDir();
419
446
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
420
447
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -519,10 +546,10 @@ var init_runtime_table = __esm({
519
546
  });
520
547
 
521
548
  // src/lib/agent-config.ts
522
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
549
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
523
550
  import path3 from "path";
524
551
  function loadAgentConfig() {
525
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
552
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
526
553
  try {
527
554
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
528
555
  } catch {
@@ -543,6 +570,7 @@ var init_agent_config = __esm({
543
570
  "use strict";
544
571
  init_config();
545
572
  init_runtime_table();
573
+ init_secure_files();
546
574
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
547
575
  DEFAULT_MODELS = {
548
576
  claude: "claude-opus-4",
@@ -561,16 +589,16 @@ __export(intercom_queue_exports, {
561
589
  queueIntercom: () => queueIntercom,
562
590
  readQueue: () => readQueue
563
591
  });
564
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
592
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
565
593
  import path4 from "path";
566
594
  import os3 from "os";
567
595
  function ensureDir() {
568
596
  const dir = path4.dirname(QUEUE_PATH);
569
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
597
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
570
598
  }
571
599
  function readQueue() {
572
600
  try {
573
- if (!existsSync4(QUEUE_PATH)) return [];
601
+ if (!existsSync5(QUEUE_PATH)) return [];
574
602
  return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
575
603
  } catch {
576
604
  return [];
@@ -735,7 +763,7 @@ var init_db_retry = __esm({
735
763
 
736
764
  // src/lib/employees.ts
737
765
  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";
766
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
739
767
  import { execSync as execSync3 } from "child_process";
740
768
  import path5 from "path";
741
769
  import os4 from "os";
@@ -756,7 +784,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
756
784
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
757
785
  }
758
786
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
759
- if (!existsSync5(employeesPath)) return [];
787
+ if (!existsSync6(employeesPath)) return [];
760
788
  try {
761
789
  return JSON.parse(readFileSync5(employeesPath, "utf-8"));
762
790
  } catch {
@@ -1719,6 +1747,7 @@ async function ensureSchema() {
1719
1747
  project TEXT NOT NULL,
1720
1748
  summary TEXT NOT NULL,
1721
1749
  task_file TEXT,
1750
+ session_scope TEXT,
1722
1751
  read INTEGER NOT NULL DEFAULT 0,
1723
1752
  created_at TEXT NOT NULL
1724
1753
  );
@@ -1727,7 +1756,7 @@ async function ensureSchema() {
1727
1756
  ON notifications(read);
1728
1757
 
1729
1758
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1730
- ON notifications(agent_id);
1759
+ ON notifications(agent_id, session_scope);
1731
1760
 
1732
1761
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1733
1762
  ON notifications(task_file);
@@ -1765,6 +1794,7 @@ async function ensureSchema() {
1765
1794
  target_agent TEXT NOT NULL,
1766
1795
  target_project TEXT,
1767
1796
  target_device TEXT NOT NULL DEFAULT 'local',
1797
+ session_scope TEXT,
1768
1798
  content TEXT NOT NULL,
1769
1799
  priority TEXT DEFAULT 'normal',
1770
1800
  status TEXT DEFAULT 'pending',
@@ -1778,10 +1808,31 @@ async function ensureSchema() {
1778
1808
  );
1779
1809
 
1780
1810
  CREATE INDEX IF NOT EXISTS idx_messages_target
1781
- ON messages(target_agent, status);
1811
+ ON messages(target_agent, session_scope, status);
1782
1812
 
1783
1813
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1784
- ON messages(target_agent, from_agent, server_seq);
1814
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1815
+ `);
1816
+ try {
1817
+ await client.execute({
1818
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1819
+ args: []
1820
+ });
1821
+ } catch {
1822
+ }
1823
+ try {
1824
+ await client.execute({
1825
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1826
+ args: []
1827
+ });
1828
+ } catch {
1829
+ }
1830
+ await client.executeMultiple(`
1831
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1832
+ ON notifications(agent_id, session_scope, read, created_at);
1833
+
1834
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1835
+ ON messages(target_agent, session_scope, status, created_at);
1785
1836
  `);
1786
1837
  try {
1787
1838
  await client.execute({
@@ -2365,6 +2416,13 @@ async function ensureSchema() {
2365
2416
  } catch {
2366
2417
  }
2367
2418
  }
2419
+ try {
2420
+ await client.execute({
2421
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2422
+ args: []
2423
+ });
2424
+ } catch {
2425
+ }
2368
2426
  }
2369
2427
  var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
2370
2428
  var init_database = __esm({
@@ -2383,8 +2441,11 @@ var init_database = __esm({
2383
2441
  });
2384
2442
 
2385
2443
  // src/lib/license.ts
2386
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2444
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2387
2445
  import { randomUUID } from "crypto";
2446
+ import { createRequire as createRequire2 } from "module";
2447
+ import { pathToFileURL as pathToFileURL2 } from "url";
2448
+ import os6 from "os";
2388
2449
  import path7 from "path";
2389
2450
  import { jwtVerify, importSPKI } from "jose";
2390
2451
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
@@ -2406,11 +2467,11 @@ var init_license = __esm({
2406
2467
  });
2407
2468
 
2408
2469
  // src/lib/plan-limits.ts
2409
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2470
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
2410
2471
  import path8 from "path";
2411
2472
  function getLicenseSync() {
2412
2473
  try {
2413
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2474
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2414
2475
  const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2415
2476
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2416
2477
  const parts = raw.token.split(".");
@@ -2449,7 +2510,7 @@ function assertEmployeeLimitSync(rosterPath) {
2449
2510
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2450
2511
  let count = 0;
2451
2512
  try {
2452
- if (existsSync7(filePath)) {
2513
+ if (existsSync8(filePath)) {
2453
2514
  const raw = readFileSync7(filePath, "utf8");
2454
2515
  const employees = JSON.parse(raw);
2455
2516
  count = Array.isArray(employees) ? employees.length : 0;
@@ -2483,15 +2544,48 @@ var init_plan_limits = __esm({
2483
2544
  }
2484
2545
  });
2485
2546
 
2547
+ // src/lib/task-scope.ts
2548
+ function getCurrentSessionScope() {
2549
+ try {
2550
+ return resolveExeSession();
2551
+ } catch {
2552
+ return null;
2553
+ }
2554
+ }
2555
+ function sessionScopeFilter(sessionScope, tableAlias) {
2556
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2557
+ if (!scope) return { sql: "", args: [] };
2558
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2559
+ return {
2560
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2561
+ args: [scope]
2562
+ };
2563
+ }
2564
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
2565
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2566
+ if (!scope) return { sql: "", args: [] };
2567
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2568
+ return {
2569
+ sql: ` AND ${col} = ?`,
2570
+ args: [scope]
2571
+ };
2572
+ }
2573
+ var init_task_scope = __esm({
2574
+ "src/lib/task-scope.ts"() {
2575
+ "use strict";
2576
+ init_tmux_routing();
2577
+ }
2578
+ });
2579
+
2486
2580
  // src/lib/notifications.ts
2487
2581
  import crypto from "crypto";
2488
2582
  import path9 from "path";
2489
- import os6 from "os";
2583
+ import os7 from "os";
2490
2584
  import {
2491
2585
  readFileSync as readFileSync8,
2492
2586
  readdirSync,
2493
2587
  unlinkSync as unlinkSync2,
2494
- existsSync as existsSync8,
2588
+ existsSync as existsSync9,
2495
2589
  rmdirSync
2496
2590
  } from "fs";
2497
2591
  async function writeNotification(notification) {
@@ -2499,9 +2593,10 @@ async function writeNotification(notification) {
2499
2593
  const client = getClient();
2500
2594
  const id = crypto.randomUUID();
2501
2595
  const now = (/* @__PURE__ */ new Date()).toISOString();
2596
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2502
2597
  await client.execute({
2503
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2504
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2598
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
2599
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2505
2600
  args: [
2506
2601
  id,
2507
2602
  notification.agentId,
@@ -2510,6 +2605,7 @@ async function writeNotification(notification) {
2510
2605
  notification.project,
2511
2606
  notification.summary,
2512
2607
  notification.taskFile ?? null,
2608
+ sessionScope,
2513
2609
  now
2514
2610
  ]
2515
2611
  });
@@ -2518,12 +2614,14 @@ async function writeNotification(notification) {
2518
2614
  `);
2519
2615
  }
2520
2616
  }
2521
- async function markAsReadByTaskFile(taskFile) {
2617
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2522
2618
  try {
2523
2619
  const client = getClient();
2620
+ const scope = strictSessionScopeFilter(sessionScope);
2524
2621
  await client.execute({
2525
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2526
- args: [taskFile]
2622
+ sql: `UPDATE notifications SET read = 1
2623
+ WHERE task_file = ? AND read = 0${scope.sql}`,
2624
+ args: [taskFile, ...scope.args]
2527
2625
  });
2528
2626
  } catch {
2529
2627
  }
@@ -2532,6 +2630,7 @@ var init_notifications = __esm({
2532
2630
  "src/lib/notifications.ts"() {
2533
2631
  "use strict";
2534
2632
  init_database();
2633
+ init_task_scope();
2535
2634
  }
2536
2635
  });
2537
2636
 
@@ -2569,30 +2668,6 @@ var init_session_kill_telemetry = __esm({
2569
2668
  }
2570
2669
  });
2571
2670
 
2572
- // src/lib/task-scope.ts
2573
- function getCurrentSessionScope() {
2574
- try {
2575
- return resolveExeSession();
2576
- } catch {
2577
- return null;
2578
- }
2579
- }
2580
- function sessionScopeFilter(sessionScope, tableAlias) {
2581
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2582
- if (!scope) return { sql: "", args: [] };
2583
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2584
- return {
2585
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2586
- args: [scope]
2587
- };
2588
- }
2589
- var init_task_scope = __esm({
2590
- "src/lib/task-scope.ts"() {
2591
- "use strict";
2592
- init_tmux_routing();
2593
- }
2594
- });
2595
-
2596
2671
  // src/lib/state-bus.ts
2597
2672
  var StateBus, orgBus;
2598
2673
  var init_state_bus = __esm({
@@ -2651,10 +2726,10 @@ var init_state_bus = __esm({
2651
2726
  // src/lib/tasks-crud.ts
2652
2727
  import crypto3 from "crypto";
2653
2728
  import path10 from "path";
2654
- import os7 from "os";
2729
+ import os8 from "os";
2655
2730
  import { execSync as execSync4 } from "child_process";
2656
2731
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2657
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2732
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
2658
2733
  async function writeCheckpoint(input) {
2659
2734
  const client = getClient();
2660
2735
  const row = await resolveTask(client, input.taskId);
@@ -2866,13 +2941,19 @@ ${laneWarning}` : laneWarning;
2866
2941
  });
2867
2942
  if (input.baseDir) {
2868
2943
  try {
2869
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2944
+ const EXE_OS_DIR = path10.join(os8.homedir(), ".exe-os");
2870
2945
  const mdPath = path10.join(EXE_OS_DIR, taskFile);
2871
2946
  const mdDir = path10.dirname(mdPath);
2872
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2947
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2873
2948
  const reviewer = input.reviewer ?? input.assignedBy;
2874
2949
  const mdContent = `# ${input.title}
2875
2950
 
2951
+ ## MANDATORY: When done
2952
+
2953
+ You MUST call update_task with status "done" and a result summary when finished.
2954
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2955
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
2956
+
2876
2957
  **ID:** ${id}
2877
2958
  **Status:** ${initialStatus}
2878
2959
  **Priority:** ${input.priority}
@@ -2886,12 +2967,6 @@ ${laneWarning}` : laneWarning;
2886
2967
  ## Context
2887
2968
 
2888
2969
  ${input.context}
2889
-
2890
- ## MANDATORY: When done
2891
-
2892
- You MUST call update_task with status "done" and a result summary when finished.
2893
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2894
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
2895
2970
  `;
2896
2971
  await writeFile3(mdPath, mdContent, "utf-8");
2897
2972
  } catch (err) {
@@ -3140,7 +3215,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3140
3215
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3141
3216
  } catch {
3142
3217
  }
3143
- if (input.status === "done" || input.status === "cancelled") {
3218
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3144
3219
  try {
3145
3220
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3146
3221
  clearQueueForAgent2(String(row.assigned_to));
@@ -3171,7 +3246,7 @@ async function deleteTaskCore(taskId, _baseDir) {
3171
3246
  async function ensureArchitectureDoc(baseDir, projectName) {
3172
3247
  const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3173
3248
  try {
3174
- if (existsSync9(archPath)) return;
3249
+ if (existsSync10(archPath)) return;
3175
3250
  const template = [
3176
3251
  `# ${projectName} \u2014 System Architecture`,
3177
3252
  "",
@@ -3206,7 +3281,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3206
3281
  async function ensureGitignoreExe(baseDir) {
3207
3282
  const gitignorePath = path10.join(baseDir, ".gitignore");
3208
3283
  try {
3209
- if (existsSync9(gitignorePath)) {
3284
+ if (existsSync10(gitignorePath)) {
3210
3285
  const content = readFileSync9(gitignorePath, "utf-8");
3211
3286
  if (/^\/?exe\/?$/m.test(content)) return;
3212
3287
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
@@ -3239,57 +3314,41 @@ var init_tasks_crud = __esm({
3239
3314
 
3240
3315
  // src/lib/tasks-review.ts
3241
3316
  import path11 from "path";
3242
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3317
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3243
3318
  async function countPendingReviews(sessionScope) {
3244
3319
  const client = getClient();
3245
- if (sessionScope) {
3246
- const result3 = await client.execute({
3247
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3248
- args: [sessionScope]
3249
- });
3250
- return Number(result3.rows[0]?.cnt) || 0;
3251
- }
3320
+ const scope = strictSessionScopeFilter(
3321
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3322
+ );
3252
3323
  const result2 = await client.execute({
3253
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3254
- args: []
3324
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3325
+ WHERE status = 'needs_review'${scope.sql}`,
3326
+ args: [...scope.args]
3255
3327
  });
3256
3328
  return Number(result2.rows[0]?.cnt) || 0;
3257
3329
  }
3258
3330
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3259
3331
  const client = getClient();
3260
- if (sessionScope) {
3261
- const result3 = await client.execute({
3262
- sql: `SELECT COUNT(*) as cnt FROM tasks
3263
- WHERE status = 'needs_review' AND updated_at > ?
3264
- AND session_scope = ?`,
3265
- args: [sinceIso, sessionScope]
3266
- });
3267
- return Number(result3.rows[0]?.cnt) || 0;
3268
- }
3332
+ const scope = strictSessionScopeFilter(
3333
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3334
+ );
3269
3335
  const result2 = await client.execute({
3270
3336
  sql: `SELECT COUNT(*) as cnt FROM tasks
3271
- WHERE status = 'needs_review' AND updated_at > ?`,
3272
- args: [sinceIso]
3337
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3338
+ args: [sinceIso, ...scope.args]
3273
3339
  });
3274
3340
  return Number(result2.rows[0]?.cnt) || 0;
3275
3341
  }
3276
3342
  async function listPendingReviews(limit, sessionScope) {
3277
3343
  const client = getClient();
3278
- if (sessionScope) {
3279
- const result3 = await client.execute({
3280
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3281
- WHERE status = 'needs_review'
3282
- AND session_scope = ?
3283
- ORDER BY updated_at ASC LIMIT ?`,
3284
- args: [sessionScope, limit]
3285
- });
3286
- return result3.rows;
3287
- }
3344
+ const scope = strictSessionScopeFilter(
3345
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3346
+ );
3288
3347
  const result2 = await client.execute({
3289
3348
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3290
- WHERE status = 'needs_review'
3349
+ WHERE status = 'needs_review'${scope.sql}
3291
3350
  ORDER BY updated_at ASC LIMIT ?`,
3292
- args: [limit]
3351
+ args: [...scope.args, limit]
3293
3352
  });
3294
3353
  return result2.rows;
3295
3354
  }
@@ -3301,7 +3360,7 @@ async function cleanupOrphanedReviews() {
3301
3360
  WHERE status IN ('open', 'needs_review', 'in_progress')
3302
3361
  AND assigned_by = 'system'
3303
3362
  AND title LIKE 'Review:%'
3304
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3363
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3305
3364
  args: [now]
3306
3365
  });
3307
3366
  const r1b = await client.execute({
@@ -3421,7 +3480,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3421
3480
  }
3422
3481
  try {
3423
3482
  const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3424
- if (existsSync10(cacheDir)) {
3483
+ if (existsSync11(cacheDir)) {
3425
3484
  for (const f of readdirSync2(cacheDir)) {
3426
3485
  if (f.startsWith("review-notified-")) {
3427
3486
  unlinkSync3(path11.join(cacheDir, f));
@@ -3441,6 +3500,7 @@ var init_tasks_review = __esm({
3441
3500
  init_tmux_routing();
3442
3501
  init_session_key();
3443
3502
  init_state_bus();
3503
+ init_task_scope();
3444
3504
  }
3445
3505
  });
3446
3506
 
@@ -3497,7 +3557,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3497
3557
  const scScope = sessionScopeFilter();
3498
3558
  const remaining = await client.execute({
3499
3559
  sql: `SELECT COUNT(*) as cnt FROM tasks
3500
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
3560
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3501
3561
  args: [parentTaskId, ...scScope.args]
3502
3562
  });
3503
3563
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4055,7 +4115,7 @@ async function updateTask(input) {
4055
4115
  if (input.status === "in_progress") {
4056
4116
  mkdirSync5(cacheDir, { recursive: true });
4057
4117
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4058
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4118
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4059
4119
  try {
4060
4120
  unlinkSync4(cachePath);
4061
4121
  } catch {
@@ -4063,10 +4123,10 @@ async function updateTask(input) {
4063
4123
  }
4064
4124
  } catch {
4065
4125
  }
4066
- if (input.status === "done") {
4126
+ if (input.status === "done" || input.status === "closed") {
4067
4127
  await cleanupReviewFile(row, taskFile, input.baseDir);
4068
4128
  }
4069
- if (input.status === "done" || input.status === "cancelled") {
4129
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4070
4130
  try {
4071
4131
  const client = getClient();
4072
4132
  const taskTitle = String(row.title);
@@ -4082,7 +4142,7 @@ async function updateTask(input) {
4082
4142
  if (!isCoordinatorName(assignedAgent)) {
4083
4143
  try {
4084
4144
  const draftClient = getClient();
4085
- if (input.status === "done") {
4145
+ if (input.status === "done" || input.status === "closed") {
4086
4146
  await draftClient.execute({
4087
4147
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4088
4148
  args: [assignedAgent]
@@ -4099,7 +4159,7 @@ async function updateTask(input) {
4099
4159
  try {
4100
4160
  const client = getClient();
4101
4161
  const cascaded = await client.execute({
4102
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4162
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4103
4163
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4104
4164
  args: [now, taskId]
4105
4165
  });
@@ -4112,14 +4172,14 @@ async function updateTask(input) {
4112
4172
  } catch {
4113
4173
  }
4114
4174
  }
4115
- const isTerminal = input.status === "done" || input.status === "needs_review";
4175
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4116
4176
  if (isTerminal) {
4117
4177
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4118
4178
  if (!isCoordinator) {
4119
4179
  notifyTaskDone();
4120
4180
  }
4121
4181
  await markTaskNotificationsRead(taskFile);
4122
- if (input.status === "done") {
4182
+ if (input.status === "done" || input.status === "closed") {
4123
4183
  try {
4124
4184
  await cascadeUnblock(taskId, input.baseDir, now);
4125
4185
  } catch {
@@ -4139,7 +4199,7 @@ async function updateTask(input) {
4139
4199
  }
4140
4200
  }
4141
4201
  }
4142
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4202
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4143
4203
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4144
4204
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4145
4205
  taskId,
@@ -4511,6 +4571,7 @@ __export(tmux_routing_exports, {
4511
4571
  isEmployeeAlive: () => isEmployeeAlive,
4512
4572
  isExeSession: () => isExeSession,
4513
4573
  isSessionBusy: () => isSessionBusy,
4574
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4514
4575
  notifyParentExe: () => notifyParentExe,
4515
4576
  parseParentExe: () => parseParentExe,
4516
4577
  registerParentExe: () => registerParentExe,
@@ -4521,9 +4582,9 @@ __export(tmux_routing_exports, {
4521
4582
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4522
4583
  });
4523
4584
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4524
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync3 } from "fs";
4585
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
4525
4586
  import path15 from "path";
4526
- import os8 from "os";
4587
+ import os9 from "os";
4527
4588
  import { fileURLToPath } from "url";
4528
4589
  import { unlinkSync as unlinkSync5 } from "fs";
4529
4590
  function spawnLockPath(sessionName) {
@@ -4538,11 +4599,11 @@ function isProcessAlive(pid) {
4538
4599
  }
4539
4600
  }
4540
4601
  function acquireSpawnLock(sessionName) {
4541
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4602
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4542
4603
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4543
4604
  }
4544
4605
  const lockFile = spawnLockPath(sessionName);
4545
- if (existsSync11(lockFile)) {
4606
+ if (existsSync12(lockFile)) {
4546
4607
  try {
4547
4608
  const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4548
4609
  const age = Date.now() - lock.timestamp;
@@ -4570,7 +4631,7 @@ function resolveBehaviorsExporterScript() {
4570
4631
  "bin",
4571
4632
  "exe-export-behaviors.js"
4572
4633
  );
4573
- return existsSync11(scriptPath) ? scriptPath : null;
4634
+ return existsSync12(scriptPath) ? scriptPath : null;
4574
4635
  } catch {
4575
4636
  return null;
4576
4637
  }
@@ -4636,7 +4697,7 @@ function extractRootExe(name) {
4636
4697
  return parts.length > 0 ? parts[parts.length - 1] : null;
4637
4698
  }
4638
4699
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4639
- if (!existsSync11(SESSION_CACHE)) {
4700
+ if (!existsSync12(SESSION_CACHE)) {
4640
4701
  mkdirSync6(SESSION_CACHE, { recursive: true });
4641
4702
  }
4642
4703
  const rootExe = extractRootExe(parentExe) ?? parentExe;
@@ -4728,7 +4789,7 @@ async function verifyPaneAtCapacity(sessionName) {
4728
4789
  }
4729
4790
  function readDebounceState() {
4730
4791
  try {
4731
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4792
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4732
4793
  const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4733
4794
  const state = {};
4734
4795
  for (const [key, val] of Object.entries(raw)) {
@@ -4745,7 +4806,7 @@ function readDebounceState() {
4745
4806
  }
4746
4807
  function writeDebounceState(state) {
4747
4808
  try {
4748
- if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4809
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4749
4810
  writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4750
4811
  } catch {
4751
4812
  }
@@ -4845,7 +4906,7 @@ function sendIntercom(targetSession) {
4845
4906
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4846
4907
  const agent = baseAgentName(rawAgent);
4847
4908
  const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
4848
- if (existsSync11(markerPath)) {
4909
+ if (existsSync12(markerPath)) {
4849
4910
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4850
4911
  return "debounced";
4851
4912
  }
@@ -4855,7 +4916,7 @@ function sendIntercom(targetSession) {
4855
4916
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4856
4917
  const agent = baseAgentName(rawAgent);
4857
4918
  const taskDir = path15.join(process.cwd(), "exe", agent);
4858
- if (existsSync11(taskDir)) {
4919
+ if (existsSync12(taskDir)) {
4859
4920
  const files = readdirSync3(taskDir).filter(
4860
4921
  (f) => f.endsWith(".md") && f !== "DONE.txt"
4861
4922
  );
@@ -4915,6 +4976,21 @@ function notifyParentExe(sessionKey) {
4915
4976
  }
4916
4977
  return true;
4917
4978
  }
4979
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
4980
+ const transport = getTransport();
4981
+ try {
4982
+ const sessions = transport.listSessions();
4983
+ if (!sessions.includes(coordinatorSession)) return false;
4984
+ execSync6(
4985
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
4986
+ { timeout: 3e3 }
4987
+ );
4988
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
4989
+ return true;
4990
+ } catch {
4991
+ return false;
4992
+ }
4993
+ }
4918
4994
  function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
4919
4995
  if (isCoordinatorName(employeeName2)) {
4920
4996
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -4988,9 +5064,9 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4988
5064
  const transport = getTransport();
4989
5065
  const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
4990
5066
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
4991
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5067
+ const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
4992
5068
  const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4993
- if (!existsSync11(logDir)) {
5069
+ if (!existsSync12(logDir)) {
4994
5070
  mkdirSync6(logDir, { recursive: true });
4995
5071
  }
4996
5072
  transport.kill(sessionName);
@@ -4998,13 +5074,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4998
5074
  try {
4999
5075
  const thisFile = fileURLToPath(import.meta.url);
5000
5076
  const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5001
- if (existsSync11(cleanupScript)) {
5077
+ if (existsSync12(cleanupScript)) {
5002
5078
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
5003
5079
  }
5004
5080
  } catch {
5005
5081
  }
5006
5082
  try {
5007
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
5083
+ const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
5008
5084
  let claudeJson = {};
5009
5085
  try {
5010
5086
  claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
@@ -5019,7 +5095,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5019
5095
  } catch {
5020
5096
  }
5021
5097
  try {
5022
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
5098
+ const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
5023
5099
  const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
5024
5100
  const projSettingsDir = path15.join(settingsDir, normalizedKey);
5025
5101
  const settingsPath = path15.join(projSettingsDir, "settings.json");
@@ -5070,7 +5146,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5070
5146
  let legacyFallbackWarned = false;
5071
5147
  if (!useExeAgent && !useBinSymlink) {
5072
5148
  const identityPath = path15.join(
5073
- os8.homedir(),
5149
+ os9.homedir(),
5074
5150
  ".exe-os",
5075
5151
  "identity",
5076
5152
  `${employeeName2}.md`
@@ -5079,7 +5155,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5079
5155
  const hasAgentFlag = claudeSupportsAgentFlag();
5080
5156
  if (hasAgentFlag) {
5081
5157
  identityFlag = ` --agent ${employeeName2}`;
5082
- } else if (existsSync11(identityPath)) {
5158
+ } else if (existsSync12(identityPath)) {
5083
5159
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5084
5160
  legacyFallbackWarned = true;
5085
5161
  }
@@ -5100,7 +5176,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5100
5176
  }
5101
5177
  let sessionContextFlag = "";
5102
5178
  try {
5103
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
5179
+ const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
5104
5180
  mkdirSync6(ctxDir, { recursive: true });
5105
5181
  const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
5106
5182
  const ctxContent = [
@@ -5261,14 +5337,14 @@ var init_tmux_routing = __esm({
5261
5337
  init_intercom_queue();
5262
5338
  init_plan_limits();
5263
5339
  init_employees();
5264
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5265
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
5340
+ SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
5341
+ SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
5266
5342
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5267
5343
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5268
5344
  VERIFY_PANE_LINES = 200;
5269
5345
  INTERCOM_DEBOUNCE_MS = 3e4;
5270
5346
  CODEX_DEBOUNCE_MS = 12e4;
5271
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5347
+ INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
5272
5348
  DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5273
5349
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5274
5350
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
@@ -5280,6 +5356,7 @@ var shard_manager_exports = {};
5280
5356
  __export(shard_manager_exports, {
5281
5357
  disposeShards: () => disposeShards,
5282
5358
  ensureShardSchema: () => ensureShardSchema,
5359
+ getOpenShardCount: () => getOpenShardCount,
5283
5360
  getReadyShardClient: () => getReadyShardClient,
5284
5361
  getShardClient: () => getShardClient,
5285
5362
  getShardsDir: () => getShardsDir,
@@ -5289,14 +5366,17 @@ __export(shard_manager_exports, {
5289
5366
  shardExists: () => shardExists
5290
5367
  });
5291
5368
  import path17 from "path";
5292
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5369
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5293
5370
  import { createClient as createClient2 } from "@libsql/client";
5294
5371
  function initShardManager(encryptionKey) {
5295
5372
  _encryptionKey = encryptionKey;
5296
- if (!existsSync13(SHARDS_DIR)) {
5373
+ if (!existsSync14(SHARDS_DIR)) {
5297
5374
  mkdirSync7(SHARDS_DIR, { recursive: true });
5298
5375
  }
5299
5376
  _shardingEnabled = true;
5377
+ if (_evictionTimer) clearInterval(_evictionTimer);
5378
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
5379
+ _evictionTimer.unref();
5300
5380
  }
5301
5381
  function isShardingEnabled() {
5302
5382
  return _shardingEnabled;
@@ -5313,21 +5393,28 @@ function getShardClient(projectName) {
5313
5393
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5314
5394
  }
5315
5395
  const cached = _shards.get(safeName);
5316
- if (cached) return cached;
5396
+ if (cached) {
5397
+ _shardLastAccess.set(safeName, Date.now());
5398
+ return cached;
5399
+ }
5400
+ while (_shards.size >= MAX_OPEN_SHARDS) {
5401
+ evictLRU();
5402
+ }
5317
5403
  const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5318
5404
  const client = createClient2({
5319
5405
  url: `file:${dbPath}`,
5320
5406
  encryptionKey: _encryptionKey
5321
5407
  });
5322
5408
  _shards.set(safeName, client);
5409
+ _shardLastAccess.set(safeName, Date.now());
5323
5410
  return client;
5324
5411
  }
5325
5412
  function shardExists(projectName) {
5326
5413
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5327
- return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
5414
+ return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
5328
5415
  }
5329
5416
  function listShards() {
5330
- if (!existsSync13(SHARDS_DIR)) return [];
5417
+ if (!existsSync14(SHARDS_DIR)) return [];
5331
5418
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5332
5419
  }
5333
5420
  async function ensureShardSchema(client) {
@@ -5379,6 +5466,8 @@ async function ensureShardSchema(client) {
5379
5466
  for (const col of [
5380
5467
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5381
5468
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
5469
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
5470
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5382
5471
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5383
5472
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5384
5473
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5516,21 +5605,69 @@ async function getReadyShardClient(projectName) {
5516
5605
  await ensureShardSchema(client);
5517
5606
  return client;
5518
5607
  }
5608
+ function evictLRU() {
5609
+ let oldest = null;
5610
+ let oldestTime = Infinity;
5611
+ for (const [name, time] of _shardLastAccess) {
5612
+ if (time < oldestTime) {
5613
+ oldestTime = time;
5614
+ oldest = name;
5615
+ }
5616
+ }
5617
+ if (oldest) {
5618
+ const client = _shards.get(oldest);
5619
+ if (client) {
5620
+ client.close();
5621
+ }
5622
+ _shards.delete(oldest);
5623
+ _shardLastAccess.delete(oldest);
5624
+ }
5625
+ }
5626
+ function evictIdleShards() {
5627
+ const now = Date.now();
5628
+ const toEvict = [];
5629
+ for (const [name, lastAccess] of _shardLastAccess) {
5630
+ if (now - lastAccess > SHARD_IDLE_MS) {
5631
+ toEvict.push(name);
5632
+ }
5633
+ }
5634
+ for (const name of toEvict) {
5635
+ const client = _shards.get(name);
5636
+ if (client) {
5637
+ client.close();
5638
+ }
5639
+ _shards.delete(name);
5640
+ _shardLastAccess.delete(name);
5641
+ }
5642
+ }
5643
+ function getOpenShardCount() {
5644
+ return _shards.size;
5645
+ }
5519
5646
  function disposeShards() {
5647
+ if (_evictionTimer) {
5648
+ clearInterval(_evictionTimer);
5649
+ _evictionTimer = null;
5650
+ }
5520
5651
  for (const [, client] of _shards) {
5521
5652
  client.close();
5522
5653
  }
5523
5654
  _shards.clear();
5655
+ _shardLastAccess.clear();
5524
5656
  _shardingEnabled = false;
5525
5657
  _encryptionKey = null;
5526
5658
  }
5527
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
5659
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5528
5660
  var init_shard_manager = __esm({
5529
5661
  "src/lib/shard-manager.ts"() {
5530
5662
  "use strict";
5531
5663
  init_config();
5532
5664
  SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5665
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
5666
+ MAX_OPEN_SHARDS = 10;
5667
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5533
5668
  _shards = /* @__PURE__ */ new Map();
5669
+ _shardLastAccess = /* @__PURE__ */ new Map();
5670
+ _evictionTimer = null;
5534
5671
  _encryptionKey = null;
5535
5672
  _shardingEnabled = false;
5536
5673
  }
@@ -5733,13 +5870,13 @@ init_database();
5733
5870
 
5734
5871
  // src/lib/keychain.ts
5735
5872
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5736
- import { existsSync as existsSync12 } from "fs";
5873
+ import { existsSync as existsSync13 } from "fs";
5737
5874
  import path16 from "path";
5738
- import os9 from "os";
5875
+ import os10 from "os";
5739
5876
  var SERVICE = "exe-mem";
5740
5877
  var ACCOUNT = "master-key";
5741
5878
  function getKeyDir() {
5742
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5879
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os10.homedir(), ".exe-os");
5743
5880
  }
5744
5881
  function getKeyPath() {
5745
5882
  return path16.join(getKeyDir(), "master.key");
@@ -5763,9 +5900,9 @@ async function getMasterKey() {
5763
5900
  }
5764
5901
  }
5765
5902
  const keyPath = getKeyPath();
5766
- if (!existsSync12(keyPath)) {
5903
+ if (!existsSync13(keyPath)) {
5767
5904
  process.stderr.write(
5768
- `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5905
+ `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5769
5906
  `
5770
5907
  );
5771
5908
  return null;