@askexenow/exe-os 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1411 -953
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +913 -543
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +418 -262
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +793 -485
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +566 -357
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +530 -319
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +547 -336
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3442 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +534 -323
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +664 -431
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +1049 -680
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +448 -371
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
@@ -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({
@@ -2648,13 +2723,117 @@ var init_state_bus = __esm({
2648
2723
  }
2649
2724
  });
2650
2725
 
2726
+ // src/lib/project-name.ts
2727
+ import { execSync as execSync4 } from "child_process";
2728
+ import path10 from "path";
2729
+ function getProjectName(cwd) {
2730
+ const dir = cwd ?? process.cwd();
2731
+ if (_cached2 && _cachedCwd === dir) return _cached2;
2732
+ try {
2733
+ let repoRoot;
2734
+ try {
2735
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
2736
+ cwd: dir,
2737
+ encoding: "utf8",
2738
+ timeout: 2e3,
2739
+ stdio: ["pipe", "pipe", "pipe"]
2740
+ }).trim();
2741
+ repoRoot = path10.dirname(gitCommonDir);
2742
+ } catch {
2743
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
2744
+ cwd: dir,
2745
+ encoding: "utf8",
2746
+ timeout: 2e3,
2747
+ stdio: ["pipe", "pipe", "pipe"]
2748
+ }).trim();
2749
+ }
2750
+ _cached2 = path10.basename(repoRoot);
2751
+ _cachedCwd = dir;
2752
+ return _cached2;
2753
+ } catch {
2754
+ _cached2 = path10.basename(dir);
2755
+ _cachedCwd = dir;
2756
+ return _cached2;
2757
+ }
2758
+ }
2759
+ var _cached2, _cachedCwd;
2760
+ var init_project_name = __esm({
2761
+ "src/lib/project-name.ts"() {
2762
+ "use strict";
2763
+ _cached2 = null;
2764
+ _cachedCwd = null;
2765
+ }
2766
+ });
2767
+
2768
+ // src/lib/session-scope.ts
2769
+ var session_scope_exports = {};
2770
+ __export(session_scope_exports, {
2771
+ assertSessionScope: () => assertSessionScope,
2772
+ findSessionForProject: () => findSessionForProject,
2773
+ getSessionProject: () => getSessionProject
2774
+ });
2775
+ function getSessionProject(sessionName) {
2776
+ const sessions = listSessions();
2777
+ const entry = sessions.find((s) => s.windowName === sessionName);
2778
+ if (!entry) return null;
2779
+ const parts = entry.projectDir.split("/").filter(Boolean);
2780
+ return parts[parts.length - 1] ?? null;
2781
+ }
2782
+ function findSessionForProject(projectName) {
2783
+ const sessions = listSessions();
2784
+ for (const s of sessions) {
2785
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
2786
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
2787
+ }
2788
+ return null;
2789
+ }
2790
+ function assertSessionScope(actionType, targetProject) {
2791
+ try {
2792
+ const currentProject = getProjectName();
2793
+ const exeSession2 = resolveExeSession();
2794
+ if (!exeSession2) {
2795
+ return { allowed: true, reason: "no_session" };
2796
+ }
2797
+ if (currentProject === targetProject) {
2798
+ return {
2799
+ allowed: true,
2800
+ reason: "same_session",
2801
+ currentProject,
2802
+ targetProject
2803
+ };
2804
+ }
2805
+ process.stderr.write(
2806
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
2807
+ `
2808
+ );
2809
+ return {
2810
+ allowed: false,
2811
+ reason: "cross_session_denied",
2812
+ currentProject,
2813
+ targetProject,
2814
+ targetSession: findSessionForProject(targetProject)?.windowName
2815
+ };
2816
+ } catch {
2817
+ return { allowed: true, reason: "no_session" };
2818
+ }
2819
+ }
2820
+ var init_session_scope = __esm({
2821
+ "src/lib/session-scope.ts"() {
2822
+ "use strict";
2823
+ init_session_registry();
2824
+ init_project_name();
2825
+ init_tmux_routing();
2826
+ init_employees();
2827
+ }
2828
+ });
2829
+
2651
2830
  // src/lib/tasks-crud.ts
2652
2831
  import crypto3 from "crypto";
2653
- import path10 from "path";
2654
- import os7 from "os";
2655
- import { execSync as execSync4 } from "child_process";
2832
+ import path11 from "path";
2833
+ import os8 from "os";
2834
+ import { execSync as execSync5 } from "child_process";
2656
2835
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2657
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2836
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
2658
2837
  async function writeCheckpoint(input) {
2659
2838
  const client = getClient();
2660
2839
  const row = await resolveTask(client, input.taskId);
@@ -2774,9 +2953,24 @@ async function createTaskCore(input) {
2774
2953
  const now = (/* @__PURE__ */ new Date()).toISOString();
2775
2954
  const slug = slugify(input.title);
2776
2955
  let earlySessionScope = null;
2956
+ let scopeMismatchWarning;
2777
2957
  try {
2778
2958
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2779
- earlySessionScope = resolveExeSession2();
2959
+ const resolved = resolveExeSession2();
2960
+ if (resolved && input.projectName) {
2961
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
2962
+ const sessionProject = getSessionProject2(resolved);
2963
+ if (sessionProject && sessionProject !== input.projectName) {
2964
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
2965
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
2966
+ `);
2967
+ earlySessionScope = null;
2968
+ } else {
2969
+ earlySessionScope = resolved;
2970
+ }
2971
+ } else {
2972
+ earlySessionScope = resolved;
2973
+ }
2780
2974
  } catch {
2781
2975
  }
2782
2976
  const scope = earlySessionScope ?? "default";
@@ -2827,10 +3021,14 @@ async function createTaskCore(input) {
2827
3021
  ${laneWarning}` : laneWarning;
2828
3022
  }
2829
3023
  }
3024
+ if (scopeMismatchWarning) {
3025
+ warning = warning ? `${warning}
3026
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
3027
+ }
2830
3028
  if (input.baseDir) {
2831
3029
  try {
2832
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2833
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
3030
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3031
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
2834
3032
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2835
3033
  await ensureGitignoreExe(input.baseDir);
2836
3034
  } catch {
@@ -2866,13 +3064,19 @@ ${laneWarning}` : laneWarning;
2866
3064
  });
2867
3065
  if (input.baseDir) {
2868
3066
  try {
2869
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2870
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2871
- const mdDir = path10.dirname(mdPath);
2872
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
3067
+ const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3068
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
3069
+ const mdDir = path11.dirname(mdPath);
3070
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2873
3071
  const reviewer = input.reviewer ?? input.assignedBy;
2874
3072
  const mdContent = `# ${input.title}
2875
3073
 
3074
+ ## MANDATORY: When done
3075
+
3076
+ You MUST call update_task with status "done" and a result summary when finished.
3077
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3078
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3079
+
2876
3080
  **ID:** ${id}
2877
3081
  **Status:** ${initialStatus}
2878
3082
  **Priority:** ${input.priority}
@@ -2886,12 +3090,6 @@ ${laneWarning}` : laneWarning;
2886
3090
  ## Context
2887
3091
 
2888
3092
  ${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
3093
  `;
2896
3094
  await writeFile3(mdPath, mdContent, "utf-8");
2897
3095
  } catch (err) {
@@ -2973,14 +3171,14 @@ function isTmuxSessionAlive(identifier) {
2973
3171
  if (!identifier || identifier === "unknown") return true;
2974
3172
  try {
2975
3173
  if (identifier.startsWith("%")) {
2976
- const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
3174
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
2977
3175
  timeout: 2e3,
2978
3176
  encoding: "utf8",
2979
3177
  stdio: ["pipe", "pipe", "pipe"]
2980
3178
  });
2981
3179
  return output.split("\n").some((l) => l.trim() === identifier);
2982
3180
  } else {
2983
- execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3181
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
2984
3182
  timeout: 2e3,
2985
3183
  stdio: ["pipe", "pipe", "pipe"]
2986
3184
  });
@@ -2989,7 +3187,7 @@ function isTmuxSessionAlive(identifier) {
2989
3187
  } catch {
2990
3188
  if (identifier.startsWith("%")) return true;
2991
3189
  try {
2992
- execSync4("tmux list-sessions", {
3190
+ execSync5("tmux list-sessions", {
2993
3191
  timeout: 2e3,
2994
3192
  stdio: ["pipe", "pipe", "pipe"]
2995
3193
  });
@@ -3004,12 +3202,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
3004
3202
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
3005
3203
  try {
3006
3204
  const since = new Date(taskCreatedAt).toISOString();
3007
- const branch = execSync4(
3205
+ const branch = execSync5(
3008
3206
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
3009
3207
  { encoding: "utf8", timeout: 3e3 }
3010
3208
  ).trim();
3011
3209
  const branchArg = branch && branch !== "HEAD" ? branch : "";
3012
- const commitCount = execSync4(
3210
+ const commitCount = execSync5(
3013
3211
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
3014
3212
  { encoding: "utf8", timeout: 5e3 }
3015
3213
  ).trim();
@@ -3140,7 +3338,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3140
3338
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3141
3339
  } catch {
3142
3340
  }
3143
- if (input.status === "done" || input.status === "cancelled") {
3341
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3144
3342
  try {
3145
3343
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3146
3344
  clearQueueForAgent2(String(row.assigned_to));
@@ -3169,9 +3367,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3169
3367
  return { taskFile, assignedTo, assignedBy, taskSlug };
3170
3368
  }
3171
3369
  async function ensureArchitectureDoc(baseDir, projectName) {
3172
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3370
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3173
3371
  try {
3174
- if (existsSync9(archPath)) return;
3372
+ if (existsSync10(archPath)) return;
3175
3373
  const template = [
3176
3374
  `# ${projectName} \u2014 System Architecture`,
3177
3375
  "",
@@ -3204,9 +3402,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3204
3402
  }
3205
3403
  }
3206
3404
  async function ensureGitignoreExe(baseDir) {
3207
- const gitignorePath = path10.join(baseDir, ".gitignore");
3405
+ const gitignorePath = path11.join(baseDir, ".gitignore");
3208
3406
  try {
3209
- if (existsSync9(gitignorePath)) {
3407
+ if (existsSync10(gitignorePath)) {
3210
3408
  const content = readFileSync9(gitignorePath, "utf-8");
3211
3409
  if (/^\/?exe\/?$/m.test(content)) return;
3212
3410
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
@@ -3238,58 +3436,42 @@ var init_tasks_crud = __esm({
3238
3436
  });
3239
3437
 
3240
3438
  // src/lib/tasks-review.ts
3241
- import path11 from "path";
3242
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3439
+ import path12 from "path";
3440
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3243
3441
  async function countPendingReviews(sessionScope) {
3244
3442
  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
- }
3443
+ const scope = strictSessionScopeFilter(
3444
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3445
+ );
3252
3446
  const result2 = await client.execute({
3253
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3254
- args: []
3447
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3448
+ WHERE status = 'needs_review'${scope.sql}`,
3449
+ args: [...scope.args]
3255
3450
  });
3256
3451
  return Number(result2.rows[0]?.cnt) || 0;
3257
3452
  }
3258
3453
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3259
3454
  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
- }
3455
+ const scope = strictSessionScopeFilter(
3456
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3457
+ );
3269
3458
  const result2 = await client.execute({
3270
3459
  sql: `SELECT COUNT(*) as cnt FROM tasks
3271
- WHERE status = 'needs_review' AND updated_at > ?`,
3272
- args: [sinceIso]
3460
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3461
+ args: [sinceIso, ...scope.args]
3273
3462
  });
3274
3463
  return Number(result2.rows[0]?.cnt) || 0;
3275
3464
  }
3276
3465
  async function listPendingReviews(limit, sessionScope) {
3277
3466
  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
- }
3467
+ const scope = strictSessionScopeFilter(
3468
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3469
+ );
3288
3470
  const result2 = await client.execute({
3289
3471
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3290
- WHERE status = 'needs_review'
3472
+ WHERE status = 'needs_review'${scope.sql}
3291
3473
  ORDER BY updated_at ASC LIMIT ?`,
3292
- args: [limit]
3474
+ args: [...scope.args, limit]
3293
3475
  });
3294
3476
  return result2.rows;
3295
3477
  }
@@ -3301,7 +3483,7 @@ async function cleanupOrphanedReviews() {
3301
3483
  WHERE status IN ('open', 'needs_review', 'in_progress')
3302
3484
  AND assigned_by = 'system'
3303
3485
  AND title LIKE 'Review:%'
3304
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3486
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3305
3487
  args: [now]
3306
3488
  });
3307
3489
  const r1b = await client.execute({
@@ -3420,11 +3602,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3420
3602
  );
3421
3603
  }
3422
3604
  try {
3423
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3424
- if (existsSync10(cacheDir)) {
3605
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3606
+ if (existsSync11(cacheDir)) {
3425
3607
  for (const f of readdirSync2(cacheDir)) {
3426
3608
  if (f.startsWith("review-notified-")) {
3427
- unlinkSync3(path11.join(cacheDir, f));
3609
+ unlinkSync3(path12.join(cacheDir, f));
3428
3610
  }
3429
3611
  }
3430
3612
  }
@@ -3441,11 +3623,12 @@ var init_tasks_review = __esm({
3441
3623
  init_tmux_routing();
3442
3624
  init_session_key();
3443
3625
  init_state_bus();
3626
+ init_task_scope();
3444
3627
  }
3445
3628
  });
3446
3629
 
3447
3630
  // src/lib/tasks-chain.ts
3448
- import path12 from "path";
3631
+ import path13 from "path";
3449
3632
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3450
3633
  async function cascadeUnblock(taskId, baseDir, now) {
3451
3634
  const client = getClient();
@@ -3462,7 +3645,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3462
3645
  });
3463
3646
  for (const ur of unblockedRows.rows) {
3464
3647
  try {
3465
- const ubFile = path12.join(baseDir, String(ur.task_file));
3648
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3466
3649
  let ubContent = await readFile3(ubFile, "utf-8");
3467
3650
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3468
3651
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3497,7 +3680,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3497
3680
  const scScope = sessionScopeFilter();
3498
3681
  const remaining = await client.execute({
3499
3682
  sql: `SELECT COUNT(*) as cnt FROM tasks
3500
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
3683
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3501
3684
  args: [parentTaskId, ...scScope.args]
3502
3685
  });
3503
3686
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3529,110 +3712,6 @@ var init_tasks_chain = __esm({
3529
3712
  }
3530
3713
  });
3531
3714
 
3532
- // src/lib/project-name.ts
3533
- import { execSync as execSync5 } from "child_process";
3534
- import path13 from "path";
3535
- function getProjectName(cwd) {
3536
- const dir = cwd ?? process.cwd();
3537
- if (_cached2 && _cachedCwd === dir) return _cached2;
3538
- try {
3539
- let repoRoot;
3540
- try {
3541
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
3542
- cwd: dir,
3543
- encoding: "utf8",
3544
- timeout: 2e3,
3545
- stdio: ["pipe", "pipe", "pipe"]
3546
- }).trim();
3547
- repoRoot = path13.dirname(gitCommonDir);
3548
- } catch {
3549
- repoRoot = execSync5("git rev-parse --show-toplevel", {
3550
- cwd: dir,
3551
- encoding: "utf8",
3552
- timeout: 2e3,
3553
- stdio: ["pipe", "pipe", "pipe"]
3554
- }).trim();
3555
- }
3556
- _cached2 = path13.basename(repoRoot);
3557
- _cachedCwd = dir;
3558
- return _cached2;
3559
- } catch {
3560
- _cached2 = path13.basename(dir);
3561
- _cachedCwd = dir;
3562
- return _cached2;
3563
- }
3564
- }
3565
- var _cached2, _cachedCwd;
3566
- var init_project_name = __esm({
3567
- "src/lib/project-name.ts"() {
3568
- "use strict";
3569
- _cached2 = null;
3570
- _cachedCwd = null;
3571
- }
3572
- });
3573
-
3574
- // src/lib/session-scope.ts
3575
- var session_scope_exports = {};
3576
- __export(session_scope_exports, {
3577
- assertSessionScope: () => assertSessionScope,
3578
- findSessionForProject: () => findSessionForProject,
3579
- getSessionProject: () => getSessionProject
3580
- });
3581
- function getSessionProject(sessionName) {
3582
- const sessions = listSessions();
3583
- const entry = sessions.find((s) => s.windowName === sessionName);
3584
- if (!entry) return null;
3585
- const parts = entry.projectDir.split("/").filter(Boolean);
3586
- return parts[parts.length - 1] ?? null;
3587
- }
3588
- function findSessionForProject(projectName) {
3589
- const sessions = listSessions();
3590
- for (const s of sessions) {
3591
- const proj = s.projectDir.split("/").filter(Boolean).pop();
3592
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
3593
- }
3594
- return null;
3595
- }
3596
- function assertSessionScope(actionType, targetProject) {
3597
- try {
3598
- const currentProject = getProjectName();
3599
- const exeSession2 = resolveExeSession();
3600
- if (!exeSession2) {
3601
- return { allowed: true, reason: "no_session" };
3602
- }
3603
- if (currentProject === targetProject) {
3604
- return {
3605
- allowed: true,
3606
- reason: "same_session",
3607
- currentProject,
3608
- targetProject
3609
- };
3610
- }
3611
- process.stderr.write(
3612
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3613
- `
3614
- );
3615
- return {
3616
- allowed: false,
3617
- reason: "cross_session_denied",
3618
- currentProject,
3619
- targetProject,
3620
- targetSession: findSessionForProject(targetProject)?.windowName
3621
- };
3622
- } catch {
3623
- return { allowed: true, reason: "no_session" };
3624
- }
3625
- }
3626
- var init_session_scope = __esm({
3627
- "src/lib/session-scope.ts"() {
3628
- "use strict";
3629
- init_session_registry();
3630
- init_project_name();
3631
- init_tmux_routing();
3632
- init_employees();
3633
- }
3634
- });
3635
-
3636
3715
  // src/lib/tasks-notify.ts
3637
3716
  async function dispatchTaskToEmployee(input) {
3638
3717
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -4055,7 +4134,7 @@ async function updateTask(input) {
4055
4134
  if (input.status === "in_progress") {
4056
4135
  mkdirSync5(cacheDir, { recursive: true });
4057
4136
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4058
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4137
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4059
4138
  try {
4060
4139
  unlinkSync4(cachePath);
4061
4140
  } catch {
@@ -4063,10 +4142,10 @@ async function updateTask(input) {
4063
4142
  }
4064
4143
  } catch {
4065
4144
  }
4066
- if (input.status === "done") {
4145
+ if (input.status === "done" || input.status === "closed") {
4067
4146
  await cleanupReviewFile(row, taskFile, input.baseDir);
4068
4147
  }
4069
- if (input.status === "done" || input.status === "cancelled") {
4148
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4070
4149
  try {
4071
4150
  const client = getClient();
4072
4151
  const taskTitle = String(row.title);
@@ -4082,7 +4161,7 @@ async function updateTask(input) {
4082
4161
  if (!isCoordinatorName(assignedAgent)) {
4083
4162
  try {
4084
4163
  const draftClient = getClient();
4085
- if (input.status === "done") {
4164
+ if (input.status === "done" || input.status === "closed") {
4086
4165
  await draftClient.execute({
4087
4166
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4088
4167
  args: [assignedAgent]
@@ -4099,7 +4178,7 @@ async function updateTask(input) {
4099
4178
  try {
4100
4179
  const client = getClient();
4101
4180
  const cascaded = await client.execute({
4102
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4181
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4103
4182
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4104
4183
  args: [now, taskId]
4105
4184
  });
@@ -4112,14 +4191,14 @@ async function updateTask(input) {
4112
4191
  } catch {
4113
4192
  }
4114
4193
  }
4115
- const isTerminal = input.status === "done" || input.status === "needs_review";
4194
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4116
4195
  if (isTerminal) {
4117
4196
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4118
4197
  if (!isCoordinator) {
4119
4198
  notifyTaskDone();
4120
4199
  }
4121
4200
  await markTaskNotificationsRead(taskFile);
4122
- if (input.status === "done") {
4201
+ if (input.status === "done" || input.status === "closed") {
4123
4202
  try {
4124
4203
  await cascadeUnblock(taskId, input.baseDir, now);
4125
4204
  } catch {
@@ -4139,7 +4218,7 @@ async function updateTask(input) {
4139
4218
  }
4140
4219
  }
4141
4220
  }
4142
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4221
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4143
4222
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4144
4223
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4145
4224
  taskId,
@@ -4511,6 +4590,7 @@ __export(tmux_routing_exports, {
4511
4590
  isEmployeeAlive: () => isEmployeeAlive,
4512
4591
  isExeSession: () => isExeSession,
4513
4592
  isSessionBusy: () => isSessionBusy,
4593
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4514
4594
  notifyParentExe: () => notifyParentExe,
4515
4595
  parseParentExe: () => parseParentExe,
4516
4596
  registerParentExe: () => registerParentExe,
@@ -4521,9 +4601,9 @@ __export(tmux_routing_exports, {
4521
4601
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4522
4602
  });
4523
4603
  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";
4604
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
4525
4605
  import path15 from "path";
4526
- import os8 from "os";
4606
+ import os9 from "os";
4527
4607
  import { fileURLToPath } from "url";
4528
4608
  import { unlinkSync as unlinkSync5 } from "fs";
4529
4609
  function spawnLockPath(sessionName) {
@@ -4538,11 +4618,11 @@ function isProcessAlive(pid) {
4538
4618
  }
4539
4619
  }
4540
4620
  function acquireSpawnLock(sessionName) {
4541
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4621
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4542
4622
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4543
4623
  }
4544
4624
  const lockFile = spawnLockPath(sessionName);
4545
- if (existsSync11(lockFile)) {
4625
+ if (existsSync12(lockFile)) {
4546
4626
  try {
4547
4627
  const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4548
4628
  const age = Date.now() - lock.timestamp;
@@ -4570,7 +4650,7 @@ function resolveBehaviorsExporterScript() {
4570
4650
  "bin",
4571
4651
  "exe-export-behaviors.js"
4572
4652
  );
4573
- return existsSync11(scriptPath) ? scriptPath : null;
4653
+ return existsSync12(scriptPath) ? scriptPath : null;
4574
4654
  } catch {
4575
4655
  return null;
4576
4656
  }
@@ -4636,7 +4716,7 @@ function extractRootExe(name) {
4636
4716
  return parts.length > 0 ? parts[parts.length - 1] : null;
4637
4717
  }
4638
4718
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4639
- if (!existsSync11(SESSION_CACHE)) {
4719
+ if (!existsSync12(SESSION_CACHE)) {
4640
4720
  mkdirSync6(SESSION_CACHE, { recursive: true });
4641
4721
  }
4642
4722
  const rootExe = extractRootExe(parentExe) ?? parentExe;
@@ -4728,7 +4808,7 @@ async function verifyPaneAtCapacity(sessionName) {
4728
4808
  }
4729
4809
  function readDebounceState() {
4730
4810
  try {
4731
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4811
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4732
4812
  const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4733
4813
  const state = {};
4734
4814
  for (const [key, val] of Object.entries(raw)) {
@@ -4745,7 +4825,7 @@ function readDebounceState() {
4745
4825
  }
4746
4826
  function writeDebounceState(state) {
4747
4827
  try {
4748
- if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4828
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4749
4829
  writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4750
4830
  } catch {
4751
4831
  }
@@ -4845,7 +4925,7 @@ function sendIntercom(targetSession) {
4845
4925
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4846
4926
  const agent = baseAgentName(rawAgent);
4847
4927
  const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
4848
- if (existsSync11(markerPath)) {
4928
+ if (existsSync12(markerPath)) {
4849
4929
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4850
4930
  return "debounced";
4851
4931
  }
@@ -4855,7 +4935,7 @@ function sendIntercom(targetSession) {
4855
4935
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4856
4936
  const agent = baseAgentName(rawAgent);
4857
4937
  const taskDir = path15.join(process.cwd(), "exe", agent);
4858
- if (existsSync11(taskDir)) {
4938
+ if (existsSync12(taskDir)) {
4859
4939
  const files = readdirSync3(taskDir).filter(
4860
4940
  (f) => f.endsWith(".md") && f !== "DONE.txt"
4861
4941
  );
@@ -4915,6 +4995,21 @@ function notifyParentExe(sessionKey) {
4915
4995
  }
4916
4996
  return true;
4917
4997
  }
4998
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
4999
+ const transport = getTransport();
5000
+ try {
5001
+ const sessions = transport.listSessions();
5002
+ if (!sessions.includes(coordinatorSession)) return false;
5003
+ execSync6(
5004
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5005
+ { timeout: 3e3 }
5006
+ );
5007
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5008
+ return true;
5009
+ } catch {
5010
+ return false;
5011
+ }
5012
+ }
4918
5013
  function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
4919
5014
  if (isCoordinatorName(employeeName2)) {
4920
5015
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -4988,9 +5083,9 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4988
5083
  const transport = getTransport();
4989
5084
  const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
4990
5085
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
4991
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5086
+ const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
4992
5087
  const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4993
- if (!existsSync11(logDir)) {
5088
+ if (!existsSync12(logDir)) {
4994
5089
  mkdirSync6(logDir, { recursive: true });
4995
5090
  }
4996
5091
  transport.kill(sessionName);
@@ -4998,13 +5093,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4998
5093
  try {
4999
5094
  const thisFile = fileURLToPath(import.meta.url);
5000
5095
  const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5001
- if (existsSync11(cleanupScript)) {
5096
+ if (existsSync12(cleanupScript)) {
5002
5097
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
5003
5098
  }
5004
5099
  } catch {
5005
5100
  }
5006
5101
  try {
5007
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
5102
+ const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
5008
5103
  let claudeJson = {};
5009
5104
  try {
5010
5105
  claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
@@ -5019,7 +5114,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5019
5114
  } catch {
5020
5115
  }
5021
5116
  try {
5022
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
5117
+ const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
5023
5118
  const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
5024
5119
  const projSettingsDir = path15.join(settingsDir, normalizedKey);
5025
5120
  const settingsPath = path15.join(projSettingsDir, "settings.json");
@@ -5070,7 +5165,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5070
5165
  let legacyFallbackWarned = false;
5071
5166
  if (!useExeAgent && !useBinSymlink) {
5072
5167
  const identityPath = path15.join(
5073
- os8.homedir(),
5168
+ os9.homedir(),
5074
5169
  ".exe-os",
5075
5170
  "identity",
5076
5171
  `${employeeName2}.md`
@@ -5079,7 +5174,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5079
5174
  const hasAgentFlag = claudeSupportsAgentFlag();
5080
5175
  if (hasAgentFlag) {
5081
5176
  identityFlag = ` --agent ${employeeName2}`;
5082
- } else if (existsSync11(identityPath)) {
5177
+ } else if (existsSync12(identityPath)) {
5083
5178
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5084
5179
  legacyFallbackWarned = true;
5085
5180
  }
@@ -5100,7 +5195,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
5100
5195
  }
5101
5196
  let sessionContextFlag = "";
5102
5197
  try {
5103
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
5198
+ const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
5104
5199
  mkdirSync6(ctxDir, { recursive: true });
5105
5200
  const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
5106
5201
  const ctxContent = [
@@ -5261,14 +5356,14 @@ var init_tmux_routing = __esm({
5261
5356
  init_intercom_queue();
5262
5357
  init_plan_limits();
5263
5358
  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");
5359
+ SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
5360
+ SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
5266
5361
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5267
5362
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5268
5363
  VERIFY_PANE_LINES = 200;
5269
5364
  INTERCOM_DEBOUNCE_MS = 3e4;
5270
5365
  CODEX_DEBOUNCE_MS = 12e4;
5271
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5366
+ INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
5272
5367
  DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5273
5368
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5274
5369
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
@@ -5280,6 +5375,7 @@ var shard_manager_exports = {};
5280
5375
  __export(shard_manager_exports, {
5281
5376
  disposeShards: () => disposeShards,
5282
5377
  ensureShardSchema: () => ensureShardSchema,
5378
+ getOpenShardCount: () => getOpenShardCount,
5283
5379
  getReadyShardClient: () => getReadyShardClient,
5284
5380
  getShardClient: () => getShardClient,
5285
5381
  getShardsDir: () => getShardsDir,
@@ -5289,14 +5385,17 @@ __export(shard_manager_exports, {
5289
5385
  shardExists: () => shardExists
5290
5386
  });
5291
5387
  import path17 from "path";
5292
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5388
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5293
5389
  import { createClient as createClient2 } from "@libsql/client";
5294
5390
  function initShardManager(encryptionKey) {
5295
5391
  _encryptionKey = encryptionKey;
5296
- if (!existsSync13(SHARDS_DIR)) {
5392
+ if (!existsSync14(SHARDS_DIR)) {
5297
5393
  mkdirSync7(SHARDS_DIR, { recursive: true });
5298
5394
  }
5299
5395
  _shardingEnabled = true;
5396
+ if (_evictionTimer) clearInterval(_evictionTimer);
5397
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
5398
+ _evictionTimer.unref();
5300
5399
  }
5301
5400
  function isShardingEnabled() {
5302
5401
  return _shardingEnabled;
@@ -5313,21 +5412,28 @@ function getShardClient(projectName) {
5313
5412
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5314
5413
  }
5315
5414
  const cached = _shards.get(safeName);
5316
- if (cached) return cached;
5415
+ if (cached) {
5416
+ _shardLastAccess.set(safeName, Date.now());
5417
+ return cached;
5418
+ }
5419
+ while (_shards.size >= MAX_OPEN_SHARDS) {
5420
+ evictLRU();
5421
+ }
5317
5422
  const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5318
5423
  const client = createClient2({
5319
5424
  url: `file:${dbPath}`,
5320
5425
  encryptionKey: _encryptionKey
5321
5426
  });
5322
5427
  _shards.set(safeName, client);
5428
+ _shardLastAccess.set(safeName, Date.now());
5323
5429
  return client;
5324
5430
  }
5325
5431
  function shardExists(projectName) {
5326
5432
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5327
- return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
5433
+ return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
5328
5434
  }
5329
5435
  function listShards() {
5330
- if (!existsSync13(SHARDS_DIR)) return [];
5436
+ if (!existsSync14(SHARDS_DIR)) return [];
5331
5437
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5332
5438
  }
5333
5439
  async function ensureShardSchema(client) {
@@ -5379,6 +5485,8 @@ async function ensureShardSchema(client) {
5379
5485
  for (const col of [
5380
5486
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5381
5487
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
5488
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
5489
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5382
5490
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5383
5491
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5384
5492
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5516,21 +5624,69 @@ async function getReadyShardClient(projectName) {
5516
5624
  await ensureShardSchema(client);
5517
5625
  return client;
5518
5626
  }
5627
+ function evictLRU() {
5628
+ let oldest = null;
5629
+ let oldestTime = Infinity;
5630
+ for (const [name, time] of _shardLastAccess) {
5631
+ if (time < oldestTime) {
5632
+ oldestTime = time;
5633
+ oldest = name;
5634
+ }
5635
+ }
5636
+ if (oldest) {
5637
+ const client = _shards.get(oldest);
5638
+ if (client) {
5639
+ client.close();
5640
+ }
5641
+ _shards.delete(oldest);
5642
+ _shardLastAccess.delete(oldest);
5643
+ }
5644
+ }
5645
+ function evictIdleShards() {
5646
+ const now = Date.now();
5647
+ const toEvict = [];
5648
+ for (const [name, lastAccess] of _shardLastAccess) {
5649
+ if (now - lastAccess > SHARD_IDLE_MS) {
5650
+ toEvict.push(name);
5651
+ }
5652
+ }
5653
+ for (const name of toEvict) {
5654
+ const client = _shards.get(name);
5655
+ if (client) {
5656
+ client.close();
5657
+ }
5658
+ _shards.delete(name);
5659
+ _shardLastAccess.delete(name);
5660
+ }
5661
+ }
5662
+ function getOpenShardCount() {
5663
+ return _shards.size;
5664
+ }
5519
5665
  function disposeShards() {
5666
+ if (_evictionTimer) {
5667
+ clearInterval(_evictionTimer);
5668
+ _evictionTimer = null;
5669
+ }
5520
5670
  for (const [, client] of _shards) {
5521
5671
  client.close();
5522
5672
  }
5523
5673
  _shards.clear();
5674
+ _shardLastAccess.clear();
5524
5675
  _shardingEnabled = false;
5525
5676
  _encryptionKey = null;
5526
5677
  }
5527
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
5678
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5528
5679
  var init_shard_manager = __esm({
5529
5680
  "src/lib/shard-manager.ts"() {
5530
5681
  "use strict";
5531
5682
  init_config();
5532
5683
  SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5684
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
5685
+ MAX_OPEN_SHARDS = 10;
5686
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5533
5687
  _shards = /* @__PURE__ */ new Map();
5688
+ _shardLastAccess = /* @__PURE__ */ new Map();
5689
+ _evictionTimer = null;
5534
5690
  _encryptionKey = null;
5535
5691
  _shardingEnabled = false;
5536
5692
  }
@@ -5733,13 +5889,13 @@ init_database();
5733
5889
 
5734
5890
  // src/lib/keychain.ts
5735
5891
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5736
- import { existsSync as existsSync12 } from "fs";
5892
+ import { existsSync as existsSync13 } from "fs";
5737
5893
  import path16 from "path";
5738
- import os9 from "os";
5894
+ import os10 from "os";
5739
5895
  var SERVICE = "exe-mem";
5740
5896
  var ACCOUNT = "master-key";
5741
5897
  function getKeyDir() {
5742
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5898
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os10.homedir(), ".exe-os");
5743
5899
  }
5744
5900
  function getKeyPath() {
5745
5901
  return path16.join(getKeyDir(), "master.key");
@@ -5763,9 +5919,9 @@ async function getMasterKey() {
5763
5919
  }
5764
5920
  }
5765
5921
  const keyPath = getKeyPath();
5766
- if (!existsSync12(keyPath)) {
5922
+ if (!existsSync13(keyPath)) {
5767
5923
  process.stderr.write(
5768
- `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5924
+ `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5769
5925
  `
5770
5926
  );
5771
5927
  return null;