@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
package/dist/tui/App.js CHANGED
@@ -355,6 +355,44 @@ var init_provider_table = __esm({
355
355
  }
356
356
  });
357
357
 
358
+ // src/lib/secure-files.ts
359
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
360
+ import { chmod, mkdir } from "fs/promises";
361
+ async function ensurePrivateDir(dirPath) {
362
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
363
+ try {
364
+ await chmod(dirPath, PRIVATE_DIR_MODE);
365
+ } catch {
366
+ }
367
+ }
368
+ function ensurePrivateDirSync(dirPath) {
369
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
370
+ try {
371
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
372
+ } catch {
373
+ }
374
+ }
375
+ async function enforcePrivateFile(filePath) {
376
+ try {
377
+ await chmod(filePath, PRIVATE_FILE_MODE);
378
+ } catch {
379
+ }
380
+ }
381
+ function enforcePrivateFileSync(filePath) {
382
+ try {
383
+ if (existsSync3(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
384
+ } catch {
385
+ }
386
+ }
387
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
388
+ var init_secure_files = __esm({
389
+ "src/lib/secure-files.ts"() {
390
+ "use strict";
391
+ PRIVATE_DIR_MODE = 448;
392
+ PRIVATE_FILE_MODE = 384;
393
+ }
394
+ });
395
+
358
396
  // src/lib/config.ts
359
397
  var config_exports = {};
360
398
  __export(config_exports, {
@@ -371,8 +409,8 @@ __export(config_exports, {
371
409
  migrateConfig: () => migrateConfig,
372
410
  saveConfig: () => saveConfig
373
411
  });
374
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
375
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync } from "fs";
412
+ import { readFile, writeFile } from "fs/promises";
413
+ import { readFileSync as readFileSync3, existsSync as existsSync4, renameSync } from "fs";
376
414
  import path2 from "path";
377
415
  import os2 from "os";
378
416
  function resolveDataDir() {
@@ -380,7 +418,7 @@ function resolveDataDir() {
380
418
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
381
419
  const newDir = path2.join(os2.homedir(), ".exe-os");
382
420
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
383
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
421
+ if (!existsSync4(newDir) && existsSync4(legacyDir)) {
384
422
  try {
385
423
  renameSync(legacyDir, newDir);
386
424
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -443,9 +481,9 @@ function normalizeAutoUpdate(raw) {
443
481
  }
444
482
  async function loadConfig() {
445
483
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
446
- await mkdir(dir, { recursive: true });
484
+ await ensurePrivateDir(dir);
447
485
  const configPath = path2.join(dir, "config.json");
448
- if (!existsSync3(configPath)) {
486
+ if (!existsSync4(configPath)) {
449
487
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
450
488
  }
451
489
  const raw = await readFile(configPath, "utf-8");
@@ -458,6 +496,7 @@ async function loadConfig() {
458
496
  `);
459
497
  try {
460
498
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
499
+ await enforcePrivateFile(configPath);
461
500
  } catch {
462
501
  }
463
502
  }
@@ -476,7 +515,7 @@ async function loadConfig() {
476
515
  function loadConfigSync() {
477
516
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
478
517
  const configPath = path2.join(dir, "config.json");
479
- if (!existsSync3(configPath)) {
518
+ if (!existsSync4(configPath)) {
480
519
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
481
520
  }
482
521
  try {
@@ -494,12 +533,10 @@ function loadConfigSync() {
494
533
  }
495
534
  async function saveConfig(config) {
496
535
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
497
- await mkdir(dir, { recursive: true });
536
+ await ensurePrivateDir(dir);
498
537
  const configPath = path2.join(dir, "config.json");
499
538
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
500
- if (config.cloud?.apiKey) {
501
- await chmod(configPath, 384);
502
- }
539
+ await enforcePrivateFile(configPath);
503
540
  }
504
541
  async function loadConfigFrom(configPath) {
505
542
  const raw = await readFile(configPath, "utf-8");
@@ -519,6 +556,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
519
556
  var init_config = __esm({
520
557
  "src/lib/config.ts"() {
521
558
  "use strict";
559
+ init_secure_files();
522
560
  EXE_AI_DIR = resolveDataDir();
523
561
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
524
562
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -635,10 +673,10 @@ __export(agent_config_exports, {
635
673
  saveAgentConfig: () => saveAgentConfig,
636
674
  setAgentRuntime: () => setAgentRuntime
637
675
  });
638
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
676
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
639
677
  import path3 from "path";
640
678
  function loadAgentConfig() {
641
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
679
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
642
680
  try {
643
681
  return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
644
682
  } catch {
@@ -647,8 +685,9 @@ function loadAgentConfig() {
647
685
  }
648
686
  function saveAgentConfig(config) {
649
687
  const dir = path3.dirname(AGENT_CONFIG_PATH);
650
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
688
+ ensurePrivateDirSync(dir);
651
689
  writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
690
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
652
691
  }
653
692
  function getAgentRuntime(agentId) {
654
693
  const config = loadAgentConfig();
@@ -688,6 +727,7 @@ var init_agent_config = __esm({
688
727
  "use strict";
689
728
  init_config();
690
729
  init_runtime_table();
730
+ init_secure_files();
691
731
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
692
732
  KNOWN_RUNTIMES = {
693
733
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -716,16 +756,16 @@ __export(intercom_queue_exports, {
716
756
  queueIntercom: () => queueIntercom,
717
757
  readQueue: () => readQueue
718
758
  });
719
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
759
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
720
760
  import path4 from "path";
721
761
  import os3 from "os";
722
762
  function ensureDir() {
723
763
  const dir = path4.dirname(QUEUE_PATH);
724
- if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
764
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
725
765
  }
726
766
  function readQueue() {
727
767
  try {
728
- if (!existsSync5(QUEUE_PATH)) return [];
768
+ if (!existsSync6(QUEUE_PATH)) return [];
729
769
  return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
730
770
  } catch {
731
771
  return [];
@@ -916,7 +956,7 @@ __export(employees_exports, {
916
956
  validateEmployeeName: () => validateEmployeeName
917
957
  });
918
958
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
919
- import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
959
+ import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
920
960
  import { execSync as execSync4 } from "child_process";
921
961
  import path5 from "path";
922
962
  import os4 from "os";
@@ -955,7 +995,7 @@ function validateEmployeeName(name) {
955
995
  return { valid: true };
956
996
  }
957
997
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
958
- if (!existsSync6(employeesPath)) {
998
+ if (!existsSync7(employeesPath)) {
959
999
  return [];
960
1000
  }
961
1001
  const raw = await readFile2(employeesPath, "utf-8");
@@ -970,7 +1010,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
970
1010
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
971
1011
  }
972
1012
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
973
- if (!existsSync6(employeesPath)) return [];
1013
+ if (!existsSync7(employeesPath)) return [];
974
1014
  try {
975
1015
  return JSON.parse(readFileSync6(employeesPath, "utf-8"));
976
1016
  } catch {
@@ -1018,7 +1058,7 @@ function appendToCoordinatorTeam(employee) {
1018
1058
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1019
1059
  if (!coordinator) return;
1020
1060
  const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1021
- if (!existsSync6(idPath)) return;
1061
+ if (!existsSync7(idPath)) return;
1022
1062
  const content = readFileSync6(idPath, "utf-8");
1023
1063
  if (content.includes(`**${capitalize(employee.name)}`)) return;
1024
1064
  const teamMatch = content.match(TEAM_SECTION_RE);
@@ -1072,9 +1112,9 @@ async function normalizeRosterCase(rosterPath) {
1072
1112
  const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
1073
1113
  const oldPath = path5.join(identityDir, `${oldName}.md`);
1074
1114
  const newPath = path5.join(identityDir, `${emp.name}.md`);
1075
- if (existsSync6(oldPath) && !existsSync6(newPath)) {
1115
+ if (existsSync7(oldPath) && !existsSync7(newPath)) {
1076
1116
  renameSync3(oldPath, newPath);
1077
- } else if (existsSync6(oldPath) && oldPath !== newPath) {
1117
+ } else if (existsSync7(oldPath) && oldPath !== newPath) {
1078
1118
  const content = readFileSync6(oldPath, "utf-8");
1079
1119
  writeFileSync4(newPath, content, "utf-8");
1080
1120
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
@@ -1117,7 +1157,7 @@ function registerBinSymlinks(name) {
1117
1157
  for (const suffix of ["", "-opencode"]) {
1118
1158
  const linkName = `${name}${suffix}`;
1119
1159
  const linkPath = path5.join(binDir, linkName);
1120
- if (existsSync6(linkPath)) {
1160
+ if (existsSync7(linkPath)) {
1121
1161
  skipped.push(linkName);
1122
1162
  continue;
1123
1163
  }
@@ -1728,13 +1768,50 @@ var init_database_adapter = __esm({
1728
1768
  }
1729
1769
  });
1730
1770
 
1771
+ // src/lib/daemon-auth.ts
1772
+ import crypto from "crypto";
1773
+ import path7 from "path";
1774
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1775
+ function normalizeToken(token) {
1776
+ if (!token) return null;
1777
+ const trimmed = token.trim();
1778
+ return trimmed.length > 0 ? trimmed : null;
1779
+ }
1780
+ function readDaemonToken() {
1781
+ try {
1782
+ if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
1783
+ return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
1784
+ } catch {
1785
+ return null;
1786
+ }
1787
+ }
1788
+ function ensureDaemonToken(seed) {
1789
+ const existing = readDaemonToken();
1790
+ if (existing) return existing;
1791
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1792
+ ensurePrivateDirSync(EXE_AI_DIR);
1793
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1794
+ `, "utf8");
1795
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1796
+ return token;
1797
+ }
1798
+ var DAEMON_TOKEN_PATH;
1799
+ var init_daemon_auth = __esm({
1800
+ "src/lib/daemon-auth.ts"() {
1801
+ "use strict";
1802
+ init_config();
1803
+ init_secure_files();
1804
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
1805
+ }
1806
+ });
1807
+
1731
1808
  // src/lib/exe-daemon-client.ts
1732
1809
  import net from "net";
1733
1810
  import os6 from "os";
1734
1811
  import { spawn } from "child_process";
1735
1812
  import { randomUUID } from "crypto";
1736
- import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1737
- import path7 from "path";
1813
+ import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
1814
+ import path8 from "path";
1738
1815
  import { fileURLToPath } from "url";
1739
1816
  function handleData(chunk) {
1740
1817
  _buffer += chunk.toString();
@@ -1762,9 +1839,9 @@ function handleData(chunk) {
1762
1839
  }
1763
1840
  }
1764
1841
  function cleanupStaleFiles() {
1765
- if (existsSync7(PID_PATH)) {
1842
+ if (existsSync9(PID_PATH)) {
1766
1843
  try {
1767
- const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1844
+ const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
1768
1845
  if (pid > 0) {
1769
1846
  try {
1770
1847
  process.kill(pid, 0);
@@ -1785,11 +1862,11 @@ function cleanupStaleFiles() {
1785
1862
  }
1786
1863
  }
1787
1864
  function findPackageRoot() {
1788
- let dir = path7.dirname(fileURLToPath(import.meta.url));
1789
- const { root } = path7.parse(dir);
1865
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
1866
+ const { root } = path8.parse(dir);
1790
1867
  while (dir !== root) {
1791
- if (existsSync7(path7.join(dir, "package.json"))) return dir;
1792
- dir = path7.dirname(dir);
1868
+ if (existsSync9(path8.join(dir, "package.json"))) return dir;
1869
+ dir = path8.dirname(dir);
1793
1870
  }
1794
1871
  return null;
1795
1872
  }
@@ -1815,16 +1892,17 @@ function spawnDaemon() {
1815
1892
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1816
1893
  return;
1817
1894
  }
1818
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1819
- if (!existsSync7(daemonPath)) {
1895
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1896
+ if (!existsSync9(daemonPath)) {
1820
1897
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1821
1898
  `);
1822
1899
  return;
1823
1900
  }
1824
1901
  const resolvedPath = daemonPath;
1902
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1825
1903
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1826
1904
  `);
1827
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1905
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1828
1906
  let stderrFd = "ignore";
1829
1907
  try {
1830
1908
  stderrFd = openSync(logPath, "a");
@@ -1842,7 +1920,8 @@ function spawnDaemon() {
1842
1920
  TMUX_PANE: void 0,
1843
1921
  // Prevents resolveExeSession() from scoping to one session
1844
1922
  EXE_DAEMON_SOCK: SOCKET_PATH,
1845
- EXE_DAEMON_PID: PID_PATH
1923
+ EXE_DAEMON_PID: PID_PATH,
1924
+ [DAEMON_TOKEN_ENV]: daemonToken
1846
1925
  }
1847
1926
  });
1848
1927
  child.unref();
@@ -1949,13 +2028,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1949
2028
  return;
1950
2029
  }
1951
2030
  const id = randomUUID();
2031
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1952
2032
  const timer = setTimeout(() => {
1953
2033
  _pending.delete(id);
1954
2034
  resolve({ error: "Request timeout" });
1955
2035
  }, timeoutMs);
1956
2036
  _pending.set(id, { resolve, timer });
1957
2037
  try {
1958
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2038
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1959
2039
  } catch {
1960
2040
  clearTimeout(timer);
1961
2041
  _pending.delete(id);
@@ -1966,17 +2046,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1966
2046
  function isClientConnected() {
1967
2047
  return _connected;
1968
2048
  }
1969
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
2049
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1970
2050
  var init_exe_daemon_client = __esm({
1971
2051
  "src/lib/exe-daemon-client.ts"() {
1972
2052
  "use strict";
1973
2053
  init_config();
1974
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1975
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1976
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
2054
+ init_daemon_auth();
2055
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
2056
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
2057
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1977
2058
  SPAWN_LOCK_STALE_MS = 3e4;
1978
2059
  CONNECT_TIMEOUT_MS = 15e3;
1979
2060
  REQUEST_TIMEOUT_MS = 3e4;
2061
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1980
2062
  _socket = null;
1981
2063
  _connected = false;
1982
2064
  _buffer = "";
@@ -2555,6 +2637,7 @@ async function ensureSchema() {
2555
2637
  project TEXT NOT NULL,
2556
2638
  summary TEXT NOT NULL,
2557
2639
  task_file TEXT,
2640
+ session_scope TEXT,
2558
2641
  read INTEGER NOT NULL DEFAULT 0,
2559
2642
  created_at TEXT NOT NULL
2560
2643
  );
@@ -2563,7 +2646,7 @@ async function ensureSchema() {
2563
2646
  ON notifications(read);
2564
2647
 
2565
2648
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2566
- ON notifications(agent_id);
2649
+ ON notifications(agent_id, session_scope);
2567
2650
 
2568
2651
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2569
2652
  ON notifications(task_file);
@@ -2601,6 +2684,7 @@ async function ensureSchema() {
2601
2684
  target_agent TEXT NOT NULL,
2602
2685
  target_project TEXT,
2603
2686
  target_device TEXT NOT NULL DEFAULT 'local',
2687
+ session_scope TEXT,
2604
2688
  content TEXT NOT NULL,
2605
2689
  priority TEXT DEFAULT 'normal',
2606
2690
  status TEXT DEFAULT 'pending',
@@ -2614,10 +2698,31 @@ async function ensureSchema() {
2614
2698
  );
2615
2699
 
2616
2700
  CREATE INDEX IF NOT EXISTS idx_messages_target
2617
- ON messages(target_agent, status);
2701
+ ON messages(target_agent, session_scope, status);
2618
2702
 
2619
2703
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2620
- ON messages(target_agent, from_agent, server_seq);
2704
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2705
+ `);
2706
+ try {
2707
+ await client.execute({
2708
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2709
+ args: []
2710
+ });
2711
+ } catch {
2712
+ }
2713
+ try {
2714
+ await client.execute({
2715
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2716
+ args: []
2717
+ });
2718
+ } catch {
2719
+ }
2720
+ await client.executeMultiple(`
2721
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2722
+ ON notifications(agent_id, session_scope, read, created_at);
2723
+
2724
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2725
+ ON messages(target_agent, session_scope, status, created_at);
2621
2726
  `);
2622
2727
  try {
2623
2728
  await client.execute({
@@ -3201,6 +3306,13 @@ async function ensureSchema() {
3201
3306
  } catch {
3202
3307
  }
3203
3308
  }
3309
+ try {
3310
+ await client.execute({
3311
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3312
+ args: []
3313
+ });
3314
+ } catch {
3315
+ }
3204
3316
  }
3205
3317
  async function disposeDatabase() {
3206
3318
  if (_walCheckpointTimer) {
@@ -3255,9 +3367,12 @@ __export(license_exports, {
3255
3367
  stopLicenseRevalidation: () => stopLicenseRevalidation,
3256
3368
  validateLicense: () => validateLicense
3257
3369
  });
3258
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
3370
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
3259
3371
  import { randomUUID as randomUUID2 } from "crypto";
3260
- import path8 from "path";
3372
+ import { createRequire as createRequire2 } from "module";
3373
+ import { pathToFileURL as pathToFileURL2 } from "url";
3374
+ import os7 from "os";
3375
+ import path9 from "path";
3261
3376
  import { jwtVerify, importSPKI } from "jose";
3262
3377
  async function fetchRetry(url, init) {
3263
3378
  try {
@@ -3268,37 +3383,37 @@ async function fetchRetry(url, init) {
3268
3383
  }
3269
3384
  }
3270
3385
  function loadDeviceId() {
3271
- const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
3386
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
3272
3387
  try {
3273
- if (existsSync8(deviceJsonPath)) {
3274
- const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
3388
+ if (existsSync10(deviceJsonPath)) {
3389
+ const data = JSON.parse(readFileSync9(deviceJsonPath, "utf8"));
3275
3390
  if (data.deviceId) return data.deviceId;
3276
3391
  }
3277
3392
  } catch {
3278
3393
  }
3279
3394
  try {
3280
- if (existsSync8(DEVICE_ID_PATH)) {
3281
- const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
3395
+ if (existsSync10(DEVICE_ID_PATH)) {
3396
+ const id2 = readFileSync9(DEVICE_ID_PATH, "utf8").trim();
3282
3397
  if (id2) return id2;
3283
3398
  }
3284
3399
  } catch {
3285
3400
  }
3286
3401
  const id = randomUUID2();
3287
3402
  mkdirSync4(EXE_AI_DIR, { recursive: true });
3288
- writeFileSync5(DEVICE_ID_PATH, id, "utf8");
3403
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
3289
3404
  return id;
3290
3405
  }
3291
3406
  function loadLicense() {
3292
3407
  try {
3293
- if (!existsSync8(LICENSE_PATH)) return null;
3294
- return readFileSync8(LICENSE_PATH, "utf8").trim();
3408
+ if (!existsSync10(LICENSE_PATH)) return null;
3409
+ return readFileSync9(LICENSE_PATH, "utf8").trim();
3295
3410
  } catch {
3296
3411
  return null;
3297
3412
  }
3298
3413
  }
3299
3414
  function saveLicense(apiKey) {
3300
3415
  mkdirSync4(EXE_AI_DIR, { recursive: true });
3301
- writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3416
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3302
3417
  }
3303
3418
  async function verifyLicenseJwt(token) {
3304
3419
  try {
@@ -3324,8 +3439,8 @@ async function verifyLicenseJwt(token) {
3324
3439
  }
3325
3440
  async function getCachedLicense() {
3326
3441
  try {
3327
- if (!existsSync8(CACHE_PATH)) return null;
3328
- const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3442
+ if (!existsSync10(CACHE_PATH)) return null;
3443
+ const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
3329
3444
  if (!raw.token || typeof raw.token !== "string") return null;
3330
3445
  return await verifyLicenseJwt(raw.token);
3331
3446
  } catch {
@@ -3334,8 +3449,8 @@ async function getCachedLicense() {
3334
3449
  }
3335
3450
  function readCachedToken() {
3336
3451
  try {
3337
- if (!existsSync8(CACHE_PATH)) return null;
3338
- const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3452
+ if (!existsSync10(CACHE_PATH)) return null;
3453
+ const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
3339
3454
  return typeof raw.token === "string" ? raw.token : null;
3340
3455
  } catch {
3341
3456
  return null;
@@ -3369,56 +3484,130 @@ function getRawCachedPlan() {
3369
3484
  }
3370
3485
  function cacheResponse(token) {
3371
3486
  try {
3372
- writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
3487
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
3373
3488
  } catch {
3374
3489
  }
3375
3490
  }
3376
- async function validateLicense(apiKey, deviceId) {
3377
- const did = deviceId ?? loadDeviceId();
3491
+ function loadPrismaForLicense() {
3492
+ if (_prismaFailed) return null;
3493
+ const dbUrl = process.env.DATABASE_URL;
3494
+ if (!dbUrl) {
3495
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
3496
+ if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
3497
+ _prismaFailed = true;
3498
+ return null;
3499
+ }
3500
+ }
3501
+ if (!_prismaPromise) {
3502
+ _prismaPromise = (async () => {
3503
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3504
+ if (explicitPath) {
3505
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
3506
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3507
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3508
+ return new Ctor2();
3509
+ }
3510
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
3511
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
3512
+ const entry = req.resolve("@prisma/client");
3513
+ const mod = await import(pathToFileURL2(entry).href);
3514
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3515
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
3516
+ return new Ctor();
3517
+ })().catch((err) => {
3518
+ _prismaFailed = true;
3519
+ _prismaPromise = null;
3520
+ throw err;
3521
+ });
3522
+ }
3523
+ return _prismaPromise;
3524
+ }
3525
+ async function validateViaPostgres(apiKey) {
3526
+ const loader = loadPrismaForLicense();
3527
+ if (!loader) return null;
3528
+ try {
3529
+ const prisma = await loader;
3530
+ const rows = await prisma.$queryRawUnsafe(
3531
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
3532
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
3533
+ apiKey
3534
+ );
3535
+ if (!rows || rows.length === 0) return null;
3536
+ const row = rows[0];
3537
+ if (row.status !== "active") return null;
3538
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
3539
+ const plan = row.plan;
3540
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3541
+ return {
3542
+ valid: true,
3543
+ plan,
3544
+ email: row.email,
3545
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
3546
+ deviceLimit: row.device_limit ?? limits.devices,
3547
+ employeeLimit: row.employee_limit ?? limits.employees,
3548
+ memoryLimit: row.memory_limit ?? limits.memories
3549
+ };
3550
+ } catch {
3551
+ return null;
3552
+ }
3553
+ }
3554
+ async function validateViaCFWorker(apiKey, deviceId) {
3378
3555
  try {
3379
3556
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3380
3557
  method: "POST",
3381
3558
  headers: { "Content-Type": "application/json" },
3382
- body: JSON.stringify({ apiKey, deviceId: did }),
3559
+ body: JSON.stringify({ apiKey, deviceId }),
3383
3560
  signal: AbortSignal.timeout(1e4)
3384
3561
  });
3385
- if (res.ok) {
3386
- const data = await res.json();
3387
- if (data.error === "device_limit_exceeded") {
3388
- const cached2 = await getCachedLicense();
3389
- if (cached2) return cached2;
3390
- const raw2 = getRawCachedPlan();
3391
- if (raw2) return { ...raw2, valid: false };
3392
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3393
- }
3394
- if (data.token) {
3395
- cacheResponse(data.token);
3396
- const verified = await verifyLicenseJwt(data.token);
3397
- if (verified) return verified;
3398
- }
3399
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3400
- return {
3401
- valid: data.valid,
3402
- plan: data.plan,
3403
- email: data.email,
3404
- expiresAt: data.expiresAt,
3405
- deviceLimit: limits.devices,
3406
- employeeLimit: limits.employees,
3407
- memoryLimit: limits.memories
3408
- };
3562
+ if (!res.ok) return null;
3563
+ const data = await res.json();
3564
+ if (data.error === "device_limit_exceeded") return null;
3565
+ if (!data.valid) return null;
3566
+ if (data.token) {
3567
+ cacheResponse(data.token);
3568
+ const verified = await verifyLicenseJwt(data.token);
3569
+ if (verified) return verified;
3570
+ }
3571
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3572
+ return {
3573
+ valid: data.valid,
3574
+ plan: data.plan,
3575
+ email: data.email,
3576
+ expiresAt: data.expiresAt,
3577
+ deviceLimit: limits.devices,
3578
+ employeeLimit: limits.employees,
3579
+ memoryLimit: limits.memories
3580
+ };
3581
+ } catch {
3582
+ return null;
3583
+ }
3584
+ }
3585
+ async function validateLicense(apiKey, deviceId) {
3586
+ const did = deviceId ?? loadDeviceId();
3587
+ const pgResult = await validateViaPostgres(apiKey);
3588
+ if (pgResult) {
3589
+ try {
3590
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
3591
+ } catch {
3592
+ }
3593
+ return pgResult;
3594
+ }
3595
+ const cfResult = await validateViaCFWorker(apiKey, did);
3596
+ if (cfResult) return cfResult;
3597
+ const cached = await getCachedLicense();
3598
+ if (cached) return cached;
3599
+ try {
3600
+ if (existsSync10(CACHE_PATH)) {
3601
+ const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
3602
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
3603
+ return raw.pgLicense;
3604
+ }
3409
3605
  }
3410
- const cached = await getCachedLicense();
3411
- if (cached) return cached;
3412
- const raw = getRawCachedPlan();
3413
- if (raw) return raw;
3414
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3415
3606
  } catch {
3416
- const cached = await getCachedLicense();
3417
- if (cached) return cached;
3418
- const rawFallback = getRawCachedPlan();
3419
- if (rawFallback) return rawFallback;
3420
- return { ...FREE_LICENSE, valid: false, error: "offline" };
3421
3607
  }
3608
+ const rawFallback = getRawCachedPlan();
3609
+ if (rawFallback) return rawFallback;
3610
+ return { ...FREE_LICENSE, valid: false };
3422
3611
  }
3423
3612
  function getCacheAgeMs() {
3424
3613
  try {
@@ -3433,9 +3622,9 @@ async function checkLicense() {
3433
3622
  let key = loadLicense();
3434
3623
  if (!key) {
3435
3624
  try {
3436
- const configPath = path8.join(EXE_AI_DIR, "config.json");
3437
- if (existsSync8(configPath)) {
3438
- const raw = JSON.parse(readFileSync8(configPath, "utf8"));
3625
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
3626
+ if (existsSync10(configPath)) {
3627
+ const raw = JSON.parse(readFileSync9(configPath, "utf8"));
3439
3628
  const cloud = raw.cloud;
3440
3629
  if (cloud?.apiKey) {
3441
3630
  key = cloud.apiKey;
@@ -3589,14 +3778,14 @@ function stopLicenseRevalidation() {
3589
3778
  _revalTimer = null;
3590
3779
  }
3591
3780
  }
3592
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
3781
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
3593
3782
  var init_license = __esm({
3594
3783
  "src/lib/license.ts"() {
3595
3784
  "use strict";
3596
3785
  init_config();
3597
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
3598
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
3599
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3786
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3787
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3788
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3600
3789
  API_BASE = "https://askexe.com/cloud";
3601
3790
  RETRY_DELAY_MS = 500;
3602
3791
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -3620,18 +3809,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3620
3809
  employeeLimit: 1,
3621
3810
  memoryLimit: 5e3
3622
3811
  };
3812
+ _prismaPromise = null;
3813
+ _prismaFailed = false;
3623
3814
  CACHE_MAX_AGE_MS = 36e5;
3624
3815
  _revalTimer = null;
3625
3816
  }
3626
3817
  });
3627
3818
 
3628
3819
  // src/lib/plan-limits.ts
3629
- import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
3630
- import path9 from "path";
3820
+ import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
3821
+ import path10 from "path";
3631
3822
  function getLicenseSync() {
3632
3823
  try {
3633
- if (!existsSync9(CACHE_PATH2)) return freeLicense();
3634
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3824
+ if (!existsSync11(CACHE_PATH2)) return freeLicense();
3825
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
3635
3826
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3636
3827
  const parts = raw.token.split(".");
3637
3828
  if (parts.length !== 3) return freeLicense();
@@ -3669,8 +3860,8 @@ function assertEmployeeLimitSync(rosterPath) {
3669
3860
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3670
3861
  let count = 0;
3671
3862
  try {
3672
- if (existsSync9(filePath)) {
3673
- const raw = readFileSync9(filePath, "utf8");
3863
+ if (existsSync11(filePath)) {
3864
+ const raw = readFileSync10(filePath, "utf8");
3674
3865
  const employees = JSON.parse(raw);
3675
3866
  count = Array.isArray(employees) ? employees.length : 0;
3676
3867
  }
@@ -3699,29 +3890,30 @@ var init_plan_limits = __esm({
3699
3890
  this.name = "PlanLimitError";
3700
3891
  }
3701
3892
  };
3702
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3893
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3703
3894
  }
3704
3895
  });
3705
3896
 
3706
3897
  // src/lib/notifications.ts
3707
- import crypto from "crypto";
3708
- import path10 from "path";
3709
- import os7 from "os";
3898
+ import crypto2 from "crypto";
3899
+ import path11 from "path";
3900
+ import os8 from "os";
3710
3901
  import {
3711
- readFileSync as readFileSync10,
3902
+ readFileSync as readFileSync11,
3712
3903
  readdirSync,
3713
3904
  unlinkSync as unlinkSync3,
3714
- existsSync as existsSync10,
3905
+ existsSync as existsSync12,
3715
3906
  rmdirSync
3716
3907
  } from "fs";
3717
3908
  async function writeNotification(notification) {
3718
3909
  try {
3719
3910
  const client = getClient();
3720
- const id = crypto.randomUUID();
3911
+ const id = crypto2.randomUUID();
3721
3912
  const now = (/* @__PURE__ */ new Date()).toISOString();
3913
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3722
3914
  await client.execute({
3723
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3724
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3915
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3916
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3725
3917
  args: [
3726
3918
  id,
3727
3919
  notification.agentId,
@@ -3730,6 +3922,7 @@ async function writeNotification(notification) {
3730
3922
  notification.project,
3731
3923
  notification.summary,
3732
3924
  notification.taskFile ?? null,
3925
+ sessionScope,
3733
3926
  now
3734
3927
  ]
3735
3928
  });
@@ -3738,12 +3931,14 @@ async function writeNotification(notification) {
3738
3931
  `);
3739
3932
  }
3740
3933
  }
3741
- async function markAsReadByTaskFile(taskFile) {
3934
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3742
3935
  try {
3743
3936
  const client = getClient();
3937
+ const scope = strictSessionScopeFilter(sessionScope);
3744
3938
  await client.execute({
3745
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3746
- args: [taskFile]
3939
+ sql: `UPDATE notifications SET read = 1
3940
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3941
+ args: [taskFile, ...scope.args]
3747
3942
  });
3748
3943
  } catch {
3749
3944
  }
@@ -3752,11 +3947,12 @@ var init_notifications = __esm({
3752
3947
  "src/lib/notifications.ts"() {
3753
3948
  "use strict";
3754
3949
  init_database();
3950
+ init_task_scope();
3755
3951
  }
3756
3952
  });
3757
3953
 
3758
3954
  // src/lib/session-kill-telemetry.ts
3759
- import crypto2 from "crypto";
3955
+ import crypto3 from "crypto";
3760
3956
  async function recordSessionKill(input) {
3761
3957
  try {
3762
3958
  const client = getClient();
@@ -3766,7 +3962,7 @@ async function recordSessionKill(input) {
3766
3962
  ticks_idle, estimated_tokens_saved)
3767
3963
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3768
3964
  args: [
3769
- crypto2.randomUUID(),
3965
+ crypto3.randomUUID(),
3770
3966
  input.sessionName,
3771
3967
  input.agentId,
3772
3968
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3861,12 +4057,12 @@ __export(tasks_crud_exports, {
3861
4057
  updateTaskStatus: () => updateTaskStatus,
3862
4058
  writeCheckpoint: () => writeCheckpoint
3863
4059
  });
3864
- import crypto3 from "crypto";
3865
- import path11 from "path";
3866
- import os8 from "os";
4060
+ import crypto4 from "crypto";
4061
+ import path12 from "path";
4062
+ import os9 from "os";
3867
4063
  import { execSync as execSync5 } from "child_process";
3868
4064
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3869
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
4065
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
3870
4066
  async function writeCheckpoint(input) {
3871
4067
  const client = getClient();
3872
4068
  const row = await resolveTask(client, input.taskId);
@@ -3982,7 +4178,7 @@ async function resolveTask(client, identifier, scopeSession) {
3982
4178
  }
3983
4179
  async function createTaskCore(input) {
3984
4180
  const client = getClient();
3985
- const id = crypto3.randomUUID();
4181
+ const id = crypto4.randomUUID();
3986
4182
  const now = (/* @__PURE__ */ new Date()).toISOString();
3987
4183
  const slug = slugify(input.title);
3988
4184
  let earlySessionScope = null;
@@ -4041,8 +4237,8 @@ ${laneWarning}` : laneWarning;
4041
4237
  }
4042
4238
  if (input.baseDir) {
4043
4239
  try {
4044
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
4045
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
4240
+ await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
4241
+ await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
4046
4242
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4047
4243
  await ensureGitignoreExe(input.baseDir);
4048
4244
  } catch {
@@ -4078,13 +4274,19 @@ ${laneWarning}` : laneWarning;
4078
4274
  });
4079
4275
  if (input.baseDir) {
4080
4276
  try {
4081
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
4082
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
4083
- const mdDir = path11.dirname(mdPath);
4084
- if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
4277
+ const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
4278
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
4279
+ const mdDir = path12.dirname(mdPath);
4280
+ if (!existsSync13(mdDir)) await mkdir3(mdDir, { recursive: true });
4085
4281
  const reviewer = input.reviewer ?? input.assignedBy;
4086
4282
  const mdContent = `# ${input.title}
4087
4283
 
4284
+ ## MANDATORY: When done
4285
+
4286
+ You MUST call update_task with status "done" and a result summary when finished.
4287
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4288
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4289
+
4088
4290
  **ID:** ${id}
4089
4291
  **Status:** ${initialStatus}
4090
4292
  **Priority:** ${input.priority}
@@ -4098,12 +4300,6 @@ ${laneWarning}` : laneWarning;
4098
4300
  ## Context
4099
4301
 
4100
4302
  ${input.context}
4101
-
4102
- ## MANDATORY: When done
4103
-
4104
- You MUST call update_task with status "done" and a result summary when finished.
4105
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4106
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
4107
4303
  `;
4108
4304
  await writeFile3(mdPath, mdContent, "utf-8");
4109
4305
  } catch (err) {
@@ -4352,7 +4548,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4352
4548
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4353
4549
  } catch {
4354
4550
  }
4355
- if (input.status === "done" || input.status === "cancelled") {
4551
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4356
4552
  try {
4357
4553
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4358
4554
  clearQueueForAgent2(String(row.assigned_to));
@@ -4381,9 +4577,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4381
4577
  return { taskFile, assignedTo, assignedBy, taskSlug };
4382
4578
  }
4383
4579
  async function ensureArchitectureDoc(baseDir, projectName) {
4384
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4580
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
4385
4581
  try {
4386
- if (existsSync11(archPath)) return;
4582
+ if (existsSync13(archPath)) return;
4387
4583
  const template = [
4388
4584
  `# ${projectName} \u2014 System Architecture`,
4389
4585
  "",
@@ -4416,10 +4612,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4416
4612
  }
4417
4613
  }
4418
4614
  async function ensureGitignoreExe(baseDir) {
4419
- const gitignorePath = path11.join(baseDir, ".gitignore");
4615
+ const gitignorePath = path12.join(baseDir, ".gitignore");
4420
4616
  try {
4421
- if (existsSync11(gitignorePath)) {
4422
- const content = readFileSync11(gitignorePath, "utf-8");
4617
+ if (existsSync13(gitignorePath)) {
4618
+ const content = readFileSync12(gitignorePath, "utf-8");
4423
4619
  if (/^\/?exe\/?$/m.test(content)) return;
4424
4620
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4425
4621
  } else {
@@ -4450,58 +4646,42 @@ var init_tasks_crud = __esm({
4450
4646
  });
4451
4647
 
4452
4648
  // src/lib/tasks-review.ts
4453
- import path12 from "path";
4454
- import { existsSync as existsSync12, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4649
+ import path13 from "path";
4650
+ import { existsSync as existsSync14, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4455
4651
  async function countPendingReviews(sessionScope) {
4456
4652
  const client = getClient();
4457
- if (sessionScope) {
4458
- const result2 = await client.execute({
4459
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
4460
- args: [sessionScope]
4461
- });
4462
- return Number(result2.rows[0]?.cnt) || 0;
4463
- }
4653
+ const scope = strictSessionScopeFilter(
4654
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4655
+ );
4464
4656
  const result = await client.execute({
4465
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4466
- args: []
4657
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4658
+ WHERE status = 'needs_review'${scope.sql}`,
4659
+ args: [...scope.args]
4467
4660
  });
4468
4661
  return Number(result.rows[0]?.cnt) || 0;
4469
4662
  }
4470
4663
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4471
4664
  const client = getClient();
4472
- if (sessionScope) {
4473
- const result2 = await client.execute({
4474
- sql: `SELECT COUNT(*) as cnt FROM tasks
4475
- WHERE status = 'needs_review' AND updated_at > ?
4476
- AND session_scope = ?`,
4477
- args: [sinceIso, sessionScope]
4478
- });
4479
- return Number(result2.rows[0]?.cnt) || 0;
4480
- }
4665
+ const scope = strictSessionScopeFilter(
4666
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4667
+ );
4481
4668
  const result = await client.execute({
4482
4669
  sql: `SELECT COUNT(*) as cnt FROM tasks
4483
- WHERE status = 'needs_review' AND updated_at > ?`,
4484
- args: [sinceIso]
4670
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4671
+ args: [sinceIso, ...scope.args]
4485
4672
  });
4486
4673
  return Number(result.rows[0]?.cnt) || 0;
4487
4674
  }
4488
4675
  async function listPendingReviews(limit, sessionScope) {
4489
4676
  const client = getClient();
4490
- if (sessionScope) {
4491
- const result2 = await client.execute({
4492
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4493
- WHERE status = 'needs_review'
4494
- AND session_scope = ?
4495
- ORDER BY updated_at ASC LIMIT ?`,
4496
- args: [sessionScope, limit]
4497
- });
4498
- return result2.rows;
4499
- }
4677
+ const scope = strictSessionScopeFilter(
4678
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4679
+ );
4500
4680
  const result = await client.execute({
4501
4681
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4502
- WHERE status = 'needs_review'
4682
+ WHERE status = 'needs_review'${scope.sql}
4503
4683
  ORDER BY updated_at ASC LIMIT ?`,
4504
- args: [limit]
4684
+ args: [...scope.args, limit]
4505
4685
  });
4506
4686
  return result.rows;
4507
4687
  }
@@ -4513,7 +4693,7 @@ async function cleanupOrphanedReviews() {
4513
4693
  WHERE status IN ('open', 'needs_review', 'in_progress')
4514
4694
  AND assigned_by = 'system'
4515
4695
  AND title LIKE 'Review:%'
4516
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4696
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4517
4697
  args: [now]
4518
4698
  });
4519
4699
  const r1b = await client.execute({
@@ -4632,11 +4812,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4632
4812
  );
4633
4813
  }
4634
4814
  try {
4635
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
4636
- if (existsSync12(cacheDir)) {
4815
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4816
+ if (existsSync14(cacheDir)) {
4637
4817
  for (const f of readdirSync2(cacheDir)) {
4638
4818
  if (f.startsWith("review-notified-")) {
4639
- unlinkSync4(path12.join(cacheDir, f));
4819
+ unlinkSync4(path13.join(cacheDir, f));
4640
4820
  }
4641
4821
  }
4642
4822
  }
@@ -4653,11 +4833,12 @@ var init_tasks_review = __esm({
4653
4833
  init_tmux_routing();
4654
4834
  init_session_key();
4655
4835
  init_state_bus();
4836
+ init_task_scope();
4656
4837
  }
4657
4838
  });
4658
4839
 
4659
4840
  // src/lib/tasks-chain.ts
4660
- import path13 from "path";
4841
+ import path14 from "path";
4661
4842
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4662
4843
  async function cascadeUnblock(taskId, baseDir, now) {
4663
4844
  const client = getClient();
@@ -4674,7 +4855,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4674
4855
  });
4675
4856
  for (const ur of unblockedRows.rows) {
4676
4857
  try {
4677
- const ubFile = path13.join(baseDir, String(ur.task_file));
4858
+ const ubFile = path14.join(baseDir, String(ur.task_file));
4678
4859
  let ubContent = await readFile3(ubFile, "utf-8");
4679
4860
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4680
4861
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4709,7 +4890,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4709
4890
  const scScope = sessionScopeFilter();
4710
4891
  const remaining = await client.execute({
4711
4892
  sql: `SELECT COUNT(*) as cnt FROM tasks
4712
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4893
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4713
4894
  args: [parentTaskId, ...scScope.args]
4714
4895
  });
4715
4896
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4743,7 +4924,7 @@ var init_tasks_chain = __esm({
4743
4924
 
4744
4925
  // src/lib/project-name.ts
4745
4926
  import { execSync as execSync6 } from "child_process";
4746
- import path14 from "path";
4927
+ import path15 from "path";
4747
4928
  function getProjectName(cwd2) {
4748
4929
  const dir = cwd2 ?? process.cwd();
4749
4930
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4756,7 +4937,7 @@ function getProjectName(cwd2) {
4756
4937
  timeout: 2e3,
4757
4938
  stdio: ["pipe", "pipe", "pipe"]
4758
4939
  }).trim();
4759
- repoRoot = path14.dirname(gitCommonDir);
4940
+ repoRoot = path15.dirname(gitCommonDir);
4760
4941
  } catch {
4761
4942
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4762
4943
  cwd: dir,
@@ -4765,11 +4946,11 @@ function getProjectName(cwd2) {
4765
4946
  stdio: ["pipe", "pipe", "pipe"]
4766
4947
  }).trim();
4767
4948
  }
4768
- _cached2 = path14.basename(repoRoot);
4949
+ _cached2 = path15.basename(repoRoot);
4769
4950
  _cachedCwd = dir;
4770
4951
  return _cached2;
4771
4952
  } catch {
4772
- _cached2 = path14.basename(dir);
4953
+ _cached2 = path15.basename(dir);
4773
4954
  _cachedCwd = dir;
4774
4955
  return _cached2;
4775
4956
  }
@@ -4912,10 +5093,10 @@ var init_tasks_notify = __esm({
4912
5093
  });
4913
5094
 
4914
5095
  // src/lib/behaviors.ts
4915
- import crypto4 from "crypto";
5096
+ import crypto5 from "crypto";
4916
5097
  async function storeBehavior(opts) {
4917
5098
  const client = getClient();
4918
- const id = crypto4.randomUUID();
5099
+ const id = crypto5.randomUUID();
4919
5100
  const now = (/* @__PURE__ */ new Date()).toISOString();
4920
5101
  await client.execute({
4921
5102
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4944,7 +5125,7 @@ __export(skill_learning_exports, {
4944
5125
  storeTrajectory: () => storeTrajectory,
4945
5126
  sweepTrajectories: () => sweepTrajectories
4946
5127
  });
4947
- import crypto5 from "crypto";
5128
+ import crypto6 from "crypto";
4948
5129
  async function extractTrajectory(taskId, agentId) {
4949
5130
  const client = getClient();
4950
5131
  const result = await client.execute({
@@ -4973,11 +5154,11 @@ async function extractTrajectory(taskId, agentId) {
4973
5154
  return signature;
4974
5155
  }
4975
5156
  function hashSignature(signature) {
4976
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5157
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4977
5158
  }
4978
5159
  async function storeTrajectory(opts) {
4979
5160
  const client = getClient();
4980
- const id = crypto5.randomUUID();
5161
+ const id = crypto6.randomUUID();
4981
5162
  const now = (/* @__PURE__ */ new Date()).toISOString();
4982
5163
  const signatureHash = hashSignature(opts.signature);
4983
5164
  await client.execute({
@@ -5242,8 +5423,8 @@ __export(tasks_exports, {
5242
5423
  updateTaskStatus: () => updateTaskStatus,
5243
5424
  writeCheckpoint: () => writeCheckpoint
5244
5425
  });
5245
- import path15 from "path";
5246
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5426
+ import path16 from "path";
5427
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5247
5428
  async function createTask(input) {
5248
5429
  const result = await createTaskCore(input);
5249
5430
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5262,12 +5443,12 @@ async function updateTask(input) {
5262
5443
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5263
5444
  try {
5264
5445
  const agent = String(row.assigned_to);
5265
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5266
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
5446
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5447
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
5267
5448
  if (input.status === "in_progress") {
5268
5449
  mkdirSync5(cacheDir, { recursive: true });
5269
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5270
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5450
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5451
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5271
5452
  try {
5272
5453
  unlinkSync5(cachePath);
5273
5454
  } catch {
@@ -5275,10 +5456,10 @@ async function updateTask(input) {
5275
5456
  }
5276
5457
  } catch {
5277
5458
  }
5278
- if (input.status === "done") {
5459
+ if (input.status === "done" || input.status === "closed") {
5279
5460
  await cleanupReviewFile(row, taskFile, input.baseDir);
5280
5461
  }
5281
- if (input.status === "done" || input.status === "cancelled") {
5462
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5282
5463
  try {
5283
5464
  const client = getClient();
5284
5465
  const taskTitle = String(row.title);
@@ -5294,7 +5475,7 @@ async function updateTask(input) {
5294
5475
  if (!isCoordinatorName(assignedAgent)) {
5295
5476
  try {
5296
5477
  const draftClient = getClient();
5297
- if (input.status === "done") {
5478
+ if (input.status === "done" || input.status === "closed") {
5298
5479
  await draftClient.execute({
5299
5480
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5300
5481
  args: [assignedAgent]
@@ -5311,7 +5492,7 @@ async function updateTask(input) {
5311
5492
  try {
5312
5493
  const client = getClient();
5313
5494
  const cascaded = await client.execute({
5314
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5495
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5315
5496
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5316
5497
  args: [now, taskId]
5317
5498
  });
@@ -5324,14 +5505,14 @@ async function updateTask(input) {
5324
5505
  } catch {
5325
5506
  }
5326
5507
  }
5327
- const isTerminal = input.status === "done" || input.status === "needs_review";
5508
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5328
5509
  if (isTerminal) {
5329
5510
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5330
5511
  if (!isCoordinator) {
5331
5512
  notifyTaskDone();
5332
5513
  }
5333
5514
  await markTaskNotificationsRead(taskFile);
5334
- if (input.status === "done") {
5515
+ if (input.status === "done" || input.status === "closed") {
5335
5516
  try {
5336
5517
  await cascadeUnblock(taskId, input.baseDir, now);
5337
5518
  } catch {
@@ -5351,7 +5532,7 @@ async function updateTask(input) {
5351
5532
  }
5352
5533
  }
5353
5534
  }
5354
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5535
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5355
5536
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5356
5537
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5357
5538
  taskId,
@@ -5723,6 +5904,7 @@ __export(tmux_routing_exports, {
5723
5904
  isEmployeeAlive: () => isEmployeeAlive,
5724
5905
  isExeSession: () => isExeSession,
5725
5906
  isSessionBusy: () => isSessionBusy,
5907
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5726
5908
  notifyParentExe: () => notifyParentExe,
5727
5909
  parseParentExe: () => parseParentExe,
5728
5910
  registerParentExe: () => registerParentExe,
@@ -5733,13 +5915,13 @@ __export(tmux_routing_exports, {
5733
5915
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5734
5916
  });
5735
5917
  import { execFileSync as execFileSync3, execSync as execSync7 } from "child_process";
5736
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync13, appendFileSync, readdirSync as readdirSync3 } from "fs";
5737
- import path16 from "path";
5738
- import os9 from "os";
5918
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync15, appendFileSync, readdirSync as readdirSync3 } from "fs";
5919
+ import path17 from "path";
5920
+ import os10 from "os";
5739
5921
  import { fileURLToPath as fileURLToPath2 } from "url";
5740
5922
  import { unlinkSync as unlinkSync6 } from "fs";
5741
5923
  function spawnLockPath(sessionName) {
5742
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5924
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5743
5925
  }
5744
5926
  function isProcessAlive(pid) {
5745
5927
  try {
@@ -5750,13 +5932,13 @@ function isProcessAlive(pid) {
5750
5932
  }
5751
5933
  }
5752
5934
  function acquireSpawnLock2(sessionName) {
5753
- if (!existsSync13(SPAWN_LOCK_DIR)) {
5935
+ if (!existsSync15(SPAWN_LOCK_DIR)) {
5754
5936
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5755
5937
  }
5756
5938
  const lockFile = spawnLockPath(sessionName);
5757
- if (existsSync13(lockFile)) {
5939
+ if (existsSync15(lockFile)) {
5758
5940
  try {
5759
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5941
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
5760
5942
  const age = Date.now() - lock.timestamp;
5761
5943
  if (isProcessAlive(lock.pid) && age < 6e4) {
5762
5944
  return false;
@@ -5764,7 +5946,7 @@ function acquireSpawnLock2(sessionName) {
5764
5946
  } catch {
5765
5947
  }
5766
5948
  }
5767
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5949
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5768
5950
  return true;
5769
5951
  }
5770
5952
  function releaseSpawnLock2(sessionName) {
@@ -5776,13 +5958,13 @@ function releaseSpawnLock2(sessionName) {
5776
5958
  function resolveBehaviorsExporterScript() {
5777
5959
  try {
5778
5960
  const thisFile = fileURLToPath2(import.meta.url);
5779
- const scriptPath = path16.join(
5780
- path16.dirname(thisFile),
5961
+ const scriptPath = path17.join(
5962
+ path17.dirname(thisFile),
5781
5963
  "..",
5782
5964
  "bin",
5783
5965
  "exe-export-behaviors.js"
5784
5966
  );
5785
- return existsSync13(scriptPath) ? scriptPath : null;
5967
+ return existsSync15(scriptPath) ? scriptPath : null;
5786
5968
  } catch {
5787
5969
  return null;
5788
5970
  }
@@ -5848,12 +6030,12 @@ function extractRootExe(name) {
5848
6030
  return parts.length > 0 ? parts[parts.length - 1] : null;
5849
6031
  }
5850
6032
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5851
- if (!existsSync13(SESSION_CACHE)) {
6033
+ if (!existsSync15(SESSION_CACHE)) {
5852
6034
  mkdirSync6(SESSION_CACHE, { recursive: true });
5853
6035
  }
5854
6036
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5855
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5856
- writeFileSync7(filePath, JSON.stringify({
6037
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6038
+ writeFileSync8(filePath, JSON.stringify({
5857
6039
  parentExe: rootExe,
5858
6040
  dispatchedBy: dispatchedBy || rootExe,
5859
6041
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5861,7 +6043,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5861
6043
  }
5862
6044
  function getParentExe(sessionKey) {
5863
6045
  try {
5864
- const data = JSON.parse(readFileSync12(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6046
+ const data = JSON.parse(readFileSync13(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5865
6047
  return data.parentExe || null;
5866
6048
  } catch {
5867
6049
  return null;
@@ -5869,8 +6051,8 @@ function getParentExe(sessionKey) {
5869
6051
  }
5870
6052
  function getDispatchedBy(sessionKey) {
5871
6053
  try {
5872
- const data = JSON.parse(readFileSync12(
5873
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6054
+ const data = JSON.parse(readFileSync13(
6055
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5874
6056
  "utf8"
5875
6057
  ));
5876
6058
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5940,8 +6122,8 @@ async function verifyPaneAtCapacity(sessionName) {
5940
6122
  }
5941
6123
  function readDebounceState() {
5942
6124
  try {
5943
- if (!existsSync13(DEBOUNCE_FILE)) return {};
5944
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
6125
+ if (!existsSync15(DEBOUNCE_FILE)) return {};
6126
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
5945
6127
  const state = {};
5946
6128
  for (const [key, val] of Object.entries(raw)) {
5947
6129
  if (typeof val === "number") {
@@ -5957,8 +6139,8 @@ function readDebounceState() {
5957
6139
  }
5958
6140
  function writeDebounceState(state) {
5959
6141
  try {
5960
- if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5961
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6142
+ if (!existsSync15(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
6143
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5962
6144
  } catch {
5963
6145
  }
5964
6146
  }
@@ -6056,8 +6238,8 @@ function sendIntercom(targetSession) {
6056
6238
  try {
6057
6239
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6058
6240
  const agent = baseAgentName(rawAgent);
6059
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
6060
- if (existsSync13(markerPath)) {
6241
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6242
+ if (existsSync15(markerPath)) {
6061
6243
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6062
6244
  return "debounced";
6063
6245
  }
@@ -6066,8 +6248,8 @@ function sendIntercom(targetSession) {
6066
6248
  try {
6067
6249
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6068
6250
  const agent = baseAgentName(rawAgent);
6069
- const taskDir = path16.join(process.cwd(), "exe", agent);
6070
- if (existsSync13(taskDir)) {
6251
+ const taskDir = path17.join(process.cwd(), "exe", agent);
6252
+ if (existsSync15(taskDir)) {
6071
6253
  const files = readdirSync3(taskDir).filter(
6072
6254
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6073
6255
  );
@@ -6127,6 +6309,21 @@ function notifyParentExe(sessionKey) {
6127
6309
  }
6128
6310
  return true;
6129
6311
  }
6312
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6313
+ const transport = getTransport();
6314
+ try {
6315
+ const sessions = transport.listSessions();
6316
+ if (!sessions.includes(coordinatorSession)) return false;
6317
+ execSync7(
6318
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6319
+ { timeout: 3e3 }
6320
+ );
6321
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6322
+ return true;
6323
+ } catch {
6324
+ return false;
6325
+ }
6326
+ }
6130
6327
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6131
6328
  if (isCoordinatorName(employeeName)) {
6132
6329
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6200,26 +6397,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6200
6397
  const transport = getTransport();
6201
6398
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6202
6399
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6203
- const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
6204
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6205
- if (!existsSync13(logDir)) {
6400
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
6401
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6402
+ if (!existsSync15(logDir)) {
6206
6403
  mkdirSync6(logDir, { recursive: true });
6207
6404
  }
6208
6405
  transport.kill(sessionName);
6209
6406
  let cleanupSuffix = "";
6210
6407
  try {
6211
6408
  const thisFile = fileURLToPath2(import.meta.url);
6212
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6213
- if (existsSync13(cleanupScript)) {
6409
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6410
+ if (existsSync15(cleanupScript)) {
6214
6411
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6215
6412
  }
6216
6413
  } catch {
6217
6414
  }
6218
6415
  try {
6219
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
6416
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
6220
6417
  let claudeJson = {};
6221
6418
  try {
6222
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6419
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
6223
6420
  } catch {
6224
6421
  }
6225
6422
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6227,17 +6424,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6227
6424
  const trustDir = opts?.cwd ?? projectDir;
6228
6425
  if (!projects[trustDir]) projects[trustDir] = {};
6229
6426
  projects[trustDir].hasTrustDialogAccepted = true;
6230
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6427
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6231
6428
  } catch {
6232
6429
  }
6233
6430
  try {
6234
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
6431
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
6235
6432
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6236
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
6237
- const settingsPath = path16.join(projSettingsDir, "settings.json");
6433
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6434
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
6238
6435
  let settings = {};
6239
6436
  try {
6240
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6437
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
6241
6438
  } catch {
6242
6439
  }
6243
6440
  const perms = settings.permissions ?? {};
@@ -6266,7 +6463,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6266
6463
  perms.allow = allow;
6267
6464
  settings.permissions = perms;
6268
6465
  mkdirSync6(projSettingsDir, { recursive: true });
6269
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6466
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6270
6467
  }
6271
6468
  } catch {
6272
6469
  }
@@ -6281,8 +6478,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6281
6478
  let behaviorsFlag = "";
6282
6479
  let legacyFallbackWarned = false;
6283
6480
  if (!useExeAgent && !useBinSymlink) {
6284
- const identityPath = path16.join(
6285
- os9.homedir(),
6481
+ const identityPath = path17.join(
6482
+ os10.homedir(),
6286
6483
  ".exe-os",
6287
6484
  "identity",
6288
6485
  `${employeeName}.md`
@@ -6291,13 +6488,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6291
6488
  const hasAgentFlag = claudeSupportsAgentFlag();
6292
6489
  if (hasAgentFlag) {
6293
6490
  identityFlag = ` --agent ${employeeName}`;
6294
- } else if (existsSync13(identityPath)) {
6491
+ } else if (existsSync15(identityPath)) {
6295
6492
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6296
6493
  legacyFallbackWarned = true;
6297
6494
  }
6298
6495
  const behaviorsFile = exportBehaviorsSync(
6299
6496
  employeeName,
6300
- path16.basename(spawnCwd),
6497
+ path17.basename(spawnCwd),
6301
6498
  sessionName
6302
6499
  );
6303
6500
  if (behaviorsFile) {
@@ -6312,16 +6509,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6312
6509
  }
6313
6510
  let sessionContextFlag = "";
6314
6511
  try {
6315
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
6512
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
6316
6513
  mkdirSync6(ctxDir, { recursive: true });
6317
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
6514
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6318
6515
  const ctxContent = [
6319
6516
  `## Session Context`,
6320
6517
  `You are running in tmux session: ${sessionName}.`,
6321
6518
  `Your parent coordinator session is ${exeSession}.`,
6322
6519
  `Your employees (if any) use the -${exeSession} suffix.`
6323
6520
  ].join("\n");
6324
- writeFileSync7(ctxFile, ctxContent);
6521
+ writeFileSync8(ctxFile, ctxContent);
6325
6522
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6326
6523
  } catch {
6327
6524
  }
@@ -6398,8 +6595,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6398
6595
  transport.pipeLog(sessionName, logFile);
6399
6596
  try {
6400
6597
  const mySession = getMySession();
6401
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6402
- writeFileSync7(dispatchInfo, JSON.stringify({
6598
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6599
+ writeFileSync8(dispatchInfo, JSON.stringify({
6403
6600
  dispatchedBy: mySession,
6404
6601
  rootExe: exeSession,
6405
6602
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6473,15 +6670,15 @@ var init_tmux_routing = __esm({
6473
6670
  init_intercom_queue();
6474
6671
  init_plan_limits();
6475
6672
  init_employees();
6476
- SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
6477
- SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
6673
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
6674
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
6478
6675
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6479
6676
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6480
6677
  VERIFY_PANE_LINES = 200;
6481
6678
  INTERCOM_DEBOUNCE_MS = 3e4;
6482
6679
  CODEX_DEBOUNCE_MS = 12e4;
6483
- INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
6484
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
6680
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
6681
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
6485
6682
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6486
6683
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6487
6684
  }
@@ -6491,7 +6688,8 @@ var init_tmux_routing = __esm({
6491
6688
  var task_scope_exports = {};
6492
6689
  __export(task_scope_exports, {
6493
6690
  getCurrentSessionScope: () => getCurrentSessionScope,
6494
- sessionScopeFilter: () => sessionScopeFilter
6691
+ sessionScopeFilter: () => sessionScopeFilter,
6692
+ strictSessionScopeFilter: () => strictSessionScopeFilter
6495
6693
  });
6496
6694
  function getCurrentSessionScope() {
6497
6695
  try {
@@ -6509,6 +6707,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
6509
6707
  args: [scope]
6510
6708
  };
6511
6709
  }
6710
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
6711
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
6712
+ if (!scope) return { sql: "", args: [] };
6713
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
6714
+ return {
6715
+ sql: ` AND ${col} = ?`,
6716
+ args: [scope]
6717
+ };
6718
+ }
6512
6719
  var init_task_scope = __esm({
6513
6720
  "src/lib/task-scope.ts"() {
6514
6721
  "use strict";
@@ -8367,10 +8574,10 @@ var init_hooks = __esm({
8367
8574
  });
8368
8575
 
8369
8576
  // src/runtime/safety-checks.ts
8370
- import path17 from "path";
8371
- import os10 from "os";
8577
+ import path18 from "path";
8578
+ import os11 from "os";
8372
8579
  function checkPathSafety(filePath) {
8373
- const resolved = path17.resolve(filePath);
8580
+ const resolved = path18.resolve(filePath);
8374
8581
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
8375
8582
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
8376
8583
  if (matches) {
@@ -8380,7 +8587,7 @@ function checkPathSafety(filePath) {
8380
8587
  return { safe: true, bypassImmune: true };
8381
8588
  }
8382
8589
  function checkReadPathSafety(filePath) {
8383
- const resolved = path17.resolve(filePath);
8590
+ const resolved = path18.resolve(filePath);
8384
8591
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
8385
8592
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
8386
8593
  );
@@ -8395,7 +8602,7 @@ var HOME, BYPASS_IMMUNE_PATTERNS;
8395
8602
  var init_safety_checks = __esm({
8396
8603
  "src/runtime/safety-checks.ts"() {
8397
8604
  "use strict";
8398
- HOME = os10.homedir();
8605
+ HOME = os11.homedir();
8399
8606
  BYPASS_IMMUNE_PATTERNS = [
8400
8607
  {
8401
8608
  pattern: /\/\.git\/hooks\//,
@@ -8406,11 +8613,11 @@ var init_safety_checks = __esm({
8406
8613
  reason: "Git config can set hooks and command execution"
8407
8614
  },
8408
8615
  {
8409
- pattern: (p) => p.startsWith(path17.join(HOME, ".claude")),
8616
+ pattern: (p) => p.startsWith(path18.join(HOME, ".claude")),
8410
8617
  reason: "Claude configuration files are protected"
8411
8618
  },
8412
8619
  {
8413
- pattern: (p) => p.startsWith(path17.join(HOME, ".exe-os")),
8620
+ pattern: (p) => p.startsWith(path18.join(HOME, ".exe-os")),
8414
8621
  reason: "exe-os configuration files are protected"
8415
8622
  },
8416
8623
  {
@@ -8427,7 +8634,7 @@ var init_safety_checks = __esm({
8427
8634
  },
8428
8635
  {
8429
8636
  pattern: (p) => {
8430
- const name = path17.basename(p);
8637
+ const name = path18.basename(p);
8431
8638
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
8432
8639
  },
8433
8640
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -8454,7 +8661,7 @@ __export(file_read_exports, {
8454
8661
  FileReadTool: () => FileReadTool
8455
8662
  });
8456
8663
  import fs3 from "fs/promises";
8457
- import path18 from "path";
8664
+ import path19 from "path";
8458
8665
  import { z } from "zod";
8459
8666
  function isBinary(buf) {
8460
8667
  for (let i = 0; i < buf.length; i++) {
@@ -8490,7 +8697,7 @@ var init_file_read = __esm({
8490
8697
  return { behavior: "allow" };
8491
8698
  },
8492
8699
  async call(input, context) {
8493
- const filePath = path18.isAbsolute(input.file_path) ? input.file_path : path18.resolve(context.cwd, input.file_path);
8700
+ const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
8494
8701
  let stat;
8495
8702
  try {
8496
8703
  stat = await fs3.stat(filePath);
@@ -8530,7 +8737,7 @@ __export(glob_exports, {
8530
8737
  GlobTool: () => GlobTool
8531
8738
  });
8532
8739
  import fs4 from "fs/promises";
8533
- import path19 from "path";
8740
+ import path20 from "path";
8534
8741
  import { z as z2 } from "zod";
8535
8742
  async function walkDir(dir, maxDepth = 10) {
8536
8743
  const results = [];
@@ -8546,7 +8753,7 @@ async function walkDir(dir, maxDepth = 10) {
8546
8753
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
8547
8754
  continue;
8548
8755
  }
8549
- const fullPath = path19.join(current, entry.name);
8756
+ const fullPath = path20.join(current, entry.name);
8550
8757
  if (entry.isDirectory()) {
8551
8758
  await walk(fullPath, depth + 1);
8552
8759
  } else {
@@ -8580,11 +8787,11 @@ var init_glob = __esm({
8580
8787
  inputSchema: inputSchema2,
8581
8788
  isReadOnly: true,
8582
8789
  async call(input, context) {
8583
- const baseDir = input.path ? path19.isAbsolute(input.path) ? input.path : path19.resolve(context.cwd, input.path) : context.cwd;
8790
+ const baseDir = input.path ? path20.isAbsolute(input.path) ? input.path : path20.resolve(context.cwd, input.path) : context.cwd;
8584
8791
  try {
8585
8792
  const entries = await walkDir(baseDir);
8586
8793
  const matched = entries.filter(
8587
- (e) => simpleGlobMatch(path19.relative(baseDir, e.path), input.pattern)
8794
+ (e) => simpleGlobMatch(path20.relative(baseDir, e.path), input.pattern)
8588
8795
  );
8589
8796
  matched.sort((a, b) => b.mtime - a.mtime);
8590
8797
  if (matched.length === 0) {
@@ -8610,7 +8817,7 @@ __export(grep_exports, {
8610
8817
  });
8611
8818
  import { spawn as spawn2 } from "child_process";
8612
8819
  import fs5 from "fs/promises";
8613
- import path20 from "path";
8820
+ import path21 from "path";
8614
8821
  import { z as z3 } from "zod";
8615
8822
  function runRipgrep(input, searchPath, context) {
8616
8823
  return new Promise((resolve, reject) => {
@@ -8664,7 +8871,7 @@ async function nodeGrep(input, searchPath) {
8664
8871
  }
8665
8872
  for (const entry of entries) {
8666
8873
  if (entry.name === "node_modules" || entry.name === ".git") continue;
8667
- const fullPath = path20.join(dir, entry.name);
8874
+ const fullPath = path21.join(dir, entry.name);
8668
8875
  if (entry.isDirectory()) {
8669
8876
  await walk(fullPath);
8670
8877
  } else {
@@ -8710,7 +8917,7 @@ var init_grep = __esm({
8710
8917
  inputSchema: inputSchema3,
8711
8918
  isReadOnly: true,
8712
8919
  async call(input, context) {
8713
- const searchPath = input.path ? path20.isAbsolute(input.path) ? input.path : path20.resolve(context.cwd, input.path) : context.cwd;
8920
+ const searchPath = input.path ? path21.isAbsolute(input.path) ? input.path : path21.resolve(context.cwd, input.path) : context.cwd;
8714
8921
  try {
8715
8922
  const result = await runRipgrep(input, searchPath, context);
8716
8923
  return result;
@@ -8735,7 +8942,7 @@ __export(file_write_exports, {
8735
8942
  FileWriteTool: () => FileWriteTool
8736
8943
  });
8737
8944
  import fs6 from "fs/promises";
8738
- import path21 from "path";
8945
+ import path22 from "path";
8739
8946
  import { z as z4 } from "zod";
8740
8947
  var inputSchema4, FileWriteTool;
8741
8948
  var init_file_write = __esm({
@@ -8763,8 +8970,8 @@ var init_file_write = __esm({
8763
8970
  return { behavior: "allow" };
8764
8971
  },
8765
8972
  async call(input, context) {
8766
- const filePath = path21.isAbsolute(input.file_path) ? input.file_path : path21.resolve(context.cwd, input.file_path);
8767
- const dir = path21.dirname(filePath);
8973
+ const filePath = path22.isAbsolute(input.file_path) ? input.file_path : path22.resolve(context.cwd, input.file_path);
8974
+ const dir = path22.dirname(filePath);
8768
8975
  await fs6.mkdir(dir, { recursive: true });
8769
8976
  await fs6.writeFile(filePath, input.content, "utf-8");
8770
8977
  return {
@@ -8782,7 +8989,7 @@ __export(file_edit_exports, {
8782
8989
  FileEditTool: () => FileEditTool
8783
8990
  });
8784
8991
  import fs7 from "fs/promises";
8785
- import path22 from "path";
8992
+ import path23 from "path";
8786
8993
  import { z as z5 } from "zod";
8787
8994
  function countOccurrences(haystack, needle) {
8788
8995
  let count = 0;
@@ -8823,7 +9030,7 @@ var init_file_edit = __esm({
8823
9030
  return { behavior: "allow" };
8824
9031
  },
8825
9032
  async call(input, context) {
8826
- const filePath = path22.isAbsolute(input.file_path) ? input.file_path : path22.resolve(context.cwd, input.file_path);
9033
+ const filePath = path23.isAbsolute(input.file_path) ? input.file_path : path23.resolve(context.cwd, input.file_path);
8827
9034
  let content;
8828
9035
  try {
8829
9036
  content = await fs7.readFile(filePath, "utf-8");
@@ -9430,6 +9637,133 @@ ${task.context}`,
9430
9637
  }
9431
9638
  });
9432
9639
 
9640
+ // src/lib/tui-data.ts
9641
+ var tui_data_exports = {};
9642
+ __export(tui_data_exports, {
9643
+ loadMemoryDashboard: () => loadMemoryDashboard,
9644
+ loadTaskList: () => loadTaskList,
9645
+ loadTeamMetrics: () => loadTeamMetrics,
9646
+ searchWikiMemoryRows: () => searchWikiMemoryRows
9647
+ });
9648
+ async function loadMemoryDashboard(limit) {
9649
+ const client = getClient();
9650
+ const [countResult, recentResult, agentResult] = await Promise.all([
9651
+ client.execute("SELECT COUNT(*) as cnt FROM memories"),
9652
+ client.execute({
9653
+ sql: "SELECT agent_id, tool_name, project_name, raw_text, timestamp FROM memories ORDER BY timestamp DESC LIMIT ?",
9654
+ args: [limit]
9655
+ }),
9656
+ client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id ORDER BY cnt DESC")
9657
+ ]);
9658
+ return {
9659
+ total: Number(countResult.rows[0]?.cnt ?? 0),
9660
+ recent: recentResult.rows.map((row) => ({
9661
+ agentId: String(row.agent_id ?? "unknown"),
9662
+ toolName: String(row.tool_name ?? ""),
9663
+ projectName: String(row.project_name ?? ""),
9664
+ rawText: String(row.raw_text ?? ""),
9665
+ timestamp: String(row.timestamp ?? "")
9666
+ })),
9667
+ byAgent: agentResult.rows.map((row) => ({
9668
+ agentId: String(row.agent_id ?? "unknown"),
9669
+ count: Number(row.cnt ?? 0)
9670
+ }))
9671
+ };
9672
+ }
9673
+ async function loadTeamMetrics(employeeNames) {
9674
+ const client = getClient();
9675
+ const memoryCounts = /* @__PURE__ */ new Map();
9676
+ const projectsByEmployee = /* @__PURE__ */ new Map();
9677
+ const currentTaskByEmployee = /* @__PURE__ */ new Map();
9678
+ const scope = sessionScopeFilter();
9679
+ const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
9680
+ for (const row of memResult.rows) {
9681
+ memoryCounts.set(String(row.agent_id), Number(row.cnt));
9682
+ }
9683
+ for (const employeeName of employeeNames) {
9684
+ const [projectResult, taskResult] = await Promise.all([
9685
+ client.execute({
9686
+ sql: `SELECT DISTINCT project_name,
9687
+ MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
9688
+ FROM tasks
9689
+ WHERE assigned_to = ? AND status IN ('open','in_progress','done')${scope.sql}
9690
+ GROUP BY project_name
9691
+ ORDER BY urgency ASC
9692
+ LIMIT 5`,
9693
+ args: [employeeName, ...scope.args]
9694
+ }),
9695
+ client.execute({
9696
+ sql: `SELECT title FROM tasks
9697
+ WHERE assigned_to = ? AND status = 'in_progress'${scope.sql}
9698
+ ORDER BY updated_at DESC
9699
+ LIMIT 1`,
9700
+ args: [employeeName, ...scope.args]
9701
+ })
9702
+ ]);
9703
+ const projects = projectResult.rows.map((row) => {
9704
+ const urgency = Number(row.urgency);
9705
+ return {
9706
+ name: String(row.project_name),
9707
+ status: urgency === 1 ? "active" : urgency === 2 ? "has_tasks" : "idle"
9708
+ };
9709
+ });
9710
+ if (projects.length > 0) projectsByEmployee.set(employeeName, projects);
9711
+ if (taskResult.rows.length > 0) {
9712
+ currentTaskByEmployee.set(employeeName, String(taskResult.rows[0].title));
9713
+ }
9714
+ }
9715
+ return { memoryCounts, projectsByEmployee, currentTaskByEmployee };
9716
+ }
9717
+ async function loadTaskList() {
9718
+ const client = getClient();
9719
+ const scope = sessionScopeFilter();
9720
+ const result = await client.execute({
9721
+ sql: `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
9722
+ FROM tasks
9723
+ WHERE 1=1${scope.sql}
9724
+ ORDER BY
9725
+ CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 WHEN 'needs_review' THEN 3 WHEN 'done' THEN 4 ELSE 5 END,
9726
+ CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
9727
+ created_at DESC`,
9728
+ args: [...scope.args]
9729
+ });
9730
+ return result.rows.map((row) => ({
9731
+ id: String(row.id ?? ""),
9732
+ title: String(row.title ?? ""),
9733
+ priority: String(row.priority ?? "p2").toUpperCase(),
9734
+ assignedTo: String(row.assigned_to ?? ""),
9735
+ assignedBy: String(row.assigned_by ?? ""),
9736
+ status: String(row.status ?? "open"),
9737
+ projectName: String(row.project_name ?? ""),
9738
+ createdAt: String(row.created_at ?? ""),
9739
+ result: String(row.result ?? "")
9740
+ }));
9741
+ }
9742
+ async function searchWikiMemoryRows(query) {
9743
+ const client = getClient();
9744
+ const result = await client.execute({
9745
+ sql: `SELECT id, agent_id, raw_text, timestamp, project_name
9746
+ FROM memories
9747
+ WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
9748
+ ORDER BY timestamp DESC LIMIT 20`,
9749
+ args: [`%${query}%`]
9750
+ });
9751
+ return result.rows.map((row) => ({
9752
+ id: String(row.id),
9753
+ agentId: String(row.agent_id),
9754
+ rawText: String(row.raw_text),
9755
+ timestamp: String(row.timestamp),
9756
+ projectName: String(row.project_name ?? "")
9757
+ }));
9758
+ }
9759
+ var init_tui_data = __esm({
9760
+ "src/lib/tui-data.ts"() {
9761
+ "use strict";
9762
+ init_database();
9763
+ init_task_scope();
9764
+ }
9765
+ });
9766
+
9433
9767
  // src/lib/message-queue.ts
9434
9768
  var DEFAULT_MAX_SIZE, DEFAULT_TTL_MS, MessageQueue;
9435
9769
  var init_message_queue = __esm({
@@ -9488,14 +9822,14 @@ __export(keychain_exports, {
9488
9822
  setMasterKey: () => setMasterKey
9489
9823
  });
9490
9824
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
9491
- import { existsSync as existsSync14 } from "fs";
9492
- import path25 from "path";
9493
- import os11 from "os";
9825
+ import { existsSync as existsSync16 } from "fs";
9826
+ import path26 from "path";
9827
+ import os12 from "os";
9494
9828
  function getKeyDir() {
9495
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path25.join(os11.homedir(), ".exe-os");
9829
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path26.join(os12.homedir(), ".exe-os");
9496
9830
  }
9497
9831
  function getKeyPath() {
9498
- return path25.join(getKeyDir(), "master.key");
9832
+ return path26.join(getKeyDir(), "master.key");
9499
9833
  }
9500
9834
  async function tryKeytar() {
9501
9835
  try {
@@ -9516,9 +9850,9 @@ async function getMasterKey() {
9516
9850
  }
9517
9851
  }
9518
9852
  const keyPath = getKeyPath();
9519
- if (!existsSync14(keyPath)) {
9853
+ if (!existsSync16(keyPath)) {
9520
9854
  process.stderr.write(
9521
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
9855
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
9522
9856
  `
9523
9857
  );
9524
9858
  return null;
@@ -9559,7 +9893,7 @@ async function deleteMasterKey() {
9559
9893
  }
9560
9894
  }
9561
9895
  const keyPath = getKeyPath();
9562
- if (existsSync14(keyPath)) {
9896
+ if (existsSync16(keyPath)) {
9563
9897
  await unlink(keyPath);
9564
9898
  }
9565
9899
  }
@@ -9608,16 +9942,16 @@ __export(ws_auth_exports, {
9608
9942
  deriveWsAuthToken: () => deriveWsAuthToken,
9609
9943
  hashAuthToken: () => hashAuthToken
9610
9944
  });
9611
- import crypto6 from "crypto";
9945
+ import crypto7 from "crypto";
9612
9946
  function deriveWsAuthToken(masterKey) {
9613
- return Buffer.from(crypto6.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
9947
+ return Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
9614
9948
  }
9615
9949
  function deriveOrgId(masterKey) {
9616
- const raw = Buffer.from(crypto6.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
9617
- return crypto6.createHash("sha256").update(raw).digest("hex").slice(0, 32);
9950
+ const raw = Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
9951
+ return crypto7.createHash("sha256").update(raw).digest("hex").slice(0, 32);
9618
9952
  }
9619
9953
  function hashAuthToken(token) {
9620
- return crypto6.createHash("sha256").update(token).digest("hex");
9954
+ return crypto7.createHash("sha256").update(token).digest("hex");
9621
9955
  }
9622
9956
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
9623
9957
  var init_ws_auth = __esm({
@@ -9859,8 +10193,8 @@ __export(wiki_client_exports, {
9859
10193
  listDocuments: () => listDocuments,
9860
10194
  listWorkspaces: () => listWorkspaces
9861
10195
  });
9862
- async function wikiFetch(config, path27, method = "GET", body) {
9863
- const url = `${config.baseUrl}/api/v1${path27}`;
10196
+ async function wikiFetch(config, path28, method = "GET", body) {
10197
+ const url = `${config.baseUrl}/api/v1${path28}`;
9864
10198
  const headers = {
9865
10199
  Authorization: `Bearer ${config.apiKey}`,
9866
10200
  "Content-Type": "application/json"
@@ -9893,7 +10227,7 @@ async function wikiFetch(config, path27, method = "GET", body) {
9893
10227
  }
9894
10228
  }
9895
10229
  if (!response.ok) {
9896
- throw new Error(`Wiki API ${method} ${path27}: ${response.status} ${response.statusText}`);
10230
+ throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
9897
10231
  }
9898
10232
  return response.json();
9899
10233
  } finally {
@@ -10006,6 +10340,7 @@ var shard_manager_exports = {};
10006
10340
  __export(shard_manager_exports, {
10007
10341
  disposeShards: () => disposeShards,
10008
10342
  ensureShardSchema: () => ensureShardSchema,
10343
+ getOpenShardCount: () => getOpenShardCount,
10009
10344
  getReadyShardClient: () => getReadyShardClient,
10010
10345
  getShardClient: () => getShardClient,
10011
10346
  getShardsDir: () => getShardsDir,
@@ -10014,15 +10349,18 @@ __export(shard_manager_exports, {
10014
10349
  listShards: () => listShards,
10015
10350
  shardExists: () => shardExists
10016
10351
  });
10017
- import path26 from "path";
10018
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
10352
+ import path27 from "path";
10353
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
10019
10354
  import { createClient as createClient2 } from "@libsql/client";
10020
10355
  function initShardManager(encryptionKey) {
10021
10356
  _encryptionKey = encryptionKey;
10022
- if (!existsSync15(SHARDS_DIR)) {
10357
+ if (!existsSync17(SHARDS_DIR)) {
10023
10358
  mkdirSync7(SHARDS_DIR, { recursive: true });
10024
10359
  }
10025
10360
  _shardingEnabled = true;
10361
+ if (_evictionTimer) clearInterval(_evictionTimer);
10362
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
10363
+ _evictionTimer.unref();
10026
10364
  }
10027
10365
  function isShardingEnabled() {
10028
10366
  return _shardingEnabled;
@@ -10039,21 +10377,28 @@ function getShardClient(projectName) {
10039
10377
  throw new Error(`Invalid project name for shard: "${projectName}"`);
10040
10378
  }
10041
10379
  const cached = _shards.get(safeName);
10042
- if (cached) return cached;
10043
- const dbPath = path26.join(SHARDS_DIR, `${safeName}.db`);
10380
+ if (cached) {
10381
+ _shardLastAccess.set(safeName, Date.now());
10382
+ return cached;
10383
+ }
10384
+ while (_shards.size >= MAX_OPEN_SHARDS) {
10385
+ evictLRU();
10386
+ }
10387
+ const dbPath = path27.join(SHARDS_DIR, `${safeName}.db`);
10044
10388
  const client = createClient2({
10045
10389
  url: `file:${dbPath}`,
10046
10390
  encryptionKey: _encryptionKey
10047
10391
  });
10048
10392
  _shards.set(safeName, client);
10393
+ _shardLastAccess.set(safeName, Date.now());
10049
10394
  return client;
10050
10395
  }
10051
10396
  function shardExists(projectName) {
10052
10397
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
10053
- return existsSync15(path26.join(SHARDS_DIR, `${safeName}.db`));
10398
+ return existsSync17(path27.join(SHARDS_DIR, `${safeName}.db`));
10054
10399
  }
10055
10400
  function listShards() {
10056
- if (!existsSync15(SHARDS_DIR)) return [];
10401
+ if (!existsSync17(SHARDS_DIR)) return [];
10057
10402
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
10058
10403
  }
10059
10404
  async function ensureShardSchema(client) {
@@ -10105,6 +10450,8 @@ async function ensureShardSchema(client) {
10105
10450
  for (const col of [
10106
10451
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
10107
10452
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
10453
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
10454
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
10108
10455
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
10109
10456
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
10110
10457
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -10242,21 +10589,69 @@ async function getReadyShardClient(projectName) {
10242
10589
  await ensureShardSchema(client);
10243
10590
  return client;
10244
10591
  }
10592
+ function evictLRU() {
10593
+ let oldest = null;
10594
+ let oldestTime = Infinity;
10595
+ for (const [name, time] of _shardLastAccess) {
10596
+ if (time < oldestTime) {
10597
+ oldestTime = time;
10598
+ oldest = name;
10599
+ }
10600
+ }
10601
+ if (oldest) {
10602
+ const client = _shards.get(oldest);
10603
+ if (client) {
10604
+ client.close();
10605
+ }
10606
+ _shards.delete(oldest);
10607
+ _shardLastAccess.delete(oldest);
10608
+ }
10609
+ }
10610
+ function evictIdleShards() {
10611
+ const now = Date.now();
10612
+ const toEvict = [];
10613
+ for (const [name, lastAccess] of _shardLastAccess) {
10614
+ if (now - lastAccess > SHARD_IDLE_MS) {
10615
+ toEvict.push(name);
10616
+ }
10617
+ }
10618
+ for (const name of toEvict) {
10619
+ const client = _shards.get(name);
10620
+ if (client) {
10621
+ client.close();
10622
+ }
10623
+ _shards.delete(name);
10624
+ _shardLastAccess.delete(name);
10625
+ }
10626
+ }
10627
+ function getOpenShardCount() {
10628
+ return _shards.size;
10629
+ }
10245
10630
  function disposeShards() {
10631
+ if (_evictionTimer) {
10632
+ clearInterval(_evictionTimer);
10633
+ _evictionTimer = null;
10634
+ }
10246
10635
  for (const [, client] of _shards) {
10247
10636
  client.close();
10248
10637
  }
10249
10638
  _shards.clear();
10639
+ _shardLastAccess.clear();
10250
10640
  _shardingEnabled = false;
10251
10641
  _encryptionKey = null;
10252
10642
  }
10253
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
10643
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
10254
10644
  var init_shard_manager = __esm({
10255
10645
  "src/lib/shard-manager.ts"() {
10256
10646
  "use strict";
10257
10647
  init_config();
10258
- SHARDS_DIR = path26.join(EXE_AI_DIR, "shards");
10648
+ SHARDS_DIR = path27.join(EXE_AI_DIR, "shards");
10649
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
10650
+ MAX_OPEN_SHARDS = 10;
10651
+ EVICTION_INTERVAL_MS = 60 * 1e3;
10259
10652
  _shards = /* @__PURE__ */ new Map();
10653
+ _shardLastAccess = /* @__PURE__ */ new Map();
10654
+ _evictionTimer = null;
10260
10655
  _encryptionKey = null;
10261
10656
  _shardingEnabled = false;
10262
10657
  }
@@ -14827,8 +15222,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
14827
15222
  }
14828
15223
 
14829
15224
  // src/tui/ink/components/ErrorOverview.js
14830
- var cleanupPath = (path27) => {
14831
- return path27?.replace(`file://${cwd()}/`, "");
15225
+ var cleanupPath = (path28) => {
15226
+ return path28?.replace(`file://${cwd()}/`, "");
14832
15227
  };
14833
15228
  var stackUtils = new StackUtils({
14834
15229
  cwd: cwd(),
@@ -16760,11 +17155,11 @@ function Footer() {
16760
17155
  } catch {
16761
17156
  }
16762
17157
  try {
16763
- const { existsSync: existsSync16 } = await import("fs");
17158
+ const { existsSync: existsSync18 } = await import("fs");
16764
17159
  const { join } = await import("path");
16765
17160
  const home = process.env.HOME ?? "";
16766
17161
  const pidPath = join(home, ".exe-os", "exed.pid");
16767
- setDaemon(existsSync16(pidPath) ? "running" : "stopped");
17162
+ setDaemon(existsSync18(pidPath) ? "running" : "stopped");
16768
17163
  } catch {
16769
17164
  setDaemon("unknown");
16770
17165
  }
@@ -16846,7 +17241,7 @@ function Footer() {
16846
17241
  // src/tui/views/CommandCenter.tsx
16847
17242
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
16848
17243
  import TextInput from "ink-text-input";
16849
- import path23 from "path";
17244
+ import path24 from "path";
16850
17245
  import { homedir } from "os";
16851
17246
 
16852
17247
  // src/tui/components/StatusDot.tsx
@@ -17633,15 +18028,15 @@ function CommandCenterView({
17633
18028
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
17634
18029
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
17635
18030
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
17636
- const { readFileSync: readFileSync13, existsSync: existsSync16 } = await import("fs");
18031
+ const { readFileSync: readFileSync14, existsSync: existsSync18 } = await import("fs");
17637
18032
  const { join } = await import("path");
17638
18033
  const { homedir: homedir3 } = await import("os");
17639
18034
  const configPath = join(homedir3(), ".exe-os", "config.json");
17640
18035
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
17641
18036
  let providerConfigs = {};
17642
- if (existsSync16(configPath)) {
18037
+ if (existsSync18(configPath)) {
17643
18038
  try {
17644
- const raw = JSON.parse(readFileSync13(configPath, "utf8"));
18039
+ const raw = JSON.parse(readFileSync14(configPath, "utf8"));
17645
18040
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
17646
18041
  if (raw.providers && typeof raw.providers === "object") {
17647
18042
  providerConfigs = raw.providers;
@@ -17702,7 +18097,7 @@ function CommandCenterView({
17702
18097
  const markerDir = join(homedir3(), ".exe-os", "session-cache");
17703
18098
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
17704
18099
  for (const f of agentFiles) {
17705
- const data = JSON.parse(readFileSync13(join(markerDir, f), "utf8"));
18100
+ const data = JSON.parse(readFileSync14(join(markerDir, f), "utf8"));
17706
18101
  if (data.agentRole) {
17707
18102
  agentRole = data.agentRole;
17708
18103
  break;
@@ -17847,7 +18242,7 @@ function CommandCenterView({
17847
18242
  const demoEntries = DEMO_PROJECTS.map((p) => ({
17848
18243
  projectName: p.projectName,
17849
18244
  exeSession: p.exeSession,
17850
- projectDir: path23.join(homedir(), p.projectName),
18245
+ projectDir: path24.join(homedir(), p.projectName),
17851
18246
  employeeCount: p.employees.length,
17852
18247
  activeCount: p.employees.filter((e) => e.status === "active").length,
17853
18248
  memoryCount: p.employees.length * 4e3,
@@ -17885,7 +18280,7 @@ function CommandCenterView({
17885
18280
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
17886
18281
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
17887
18282
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17888
- const { existsSync: existsSync16 } = await import("fs");
18283
+ const { existsSync: existsSync18 } = await import("fs");
17889
18284
  const { join } = await import("path");
17890
18285
  const client = getClient2();
17891
18286
  if (!client) {
@@ -17956,7 +18351,7 @@ function CommandCenterView({
17956
18351
  }
17957
18352
  const memoryCount = memoryCounts.get(name) ?? 0;
17958
18353
  const openTaskCount = openTaskCounts.get(name) ?? 0;
17959
- const hasGit = projectDir ? existsSync16(join(projectDir, ".git")) : false;
18354
+ const hasGit = projectDir ? existsSync18(join(projectDir, ".git")) : false;
17960
18355
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
17961
18356
  projectList.push({
17962
18357
  projectName: name,
@@ -17981,7 +18376,7 @@ function CommandCenterView({
17981
18376
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
17982
18377
  try {
17983
18378
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
17984
- setHealth((h) => ({ ...h, daemon: existsSync16(pidPath) ? "running" : "stopped" }));
18379
+ setHealth((h) => ({ ...h, daemon: existsSync18(pidPath) ? "running" : "stopped" }));
17985
18380
  } catch {
17986
18381
  }
17987
18382
  const activityResult = await client.execute(
@@ -18196,7 +18591,7 @@ function ChatMessageRow({ msg }) {
18196
18591
 
18197
18592
  // src/tui/views/Sessions.tsx
18198
18593
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
18199
- import path24 from "path";
18594
+ import path25 from "path";
18200
18595
  import { homedir as homedir2 } from "os";
18201
18596
 
18202
18597
  // src/tui/components/TmuxPane.tsx
@@ -18500,7 +18895,7 @@ function SessionsView({
18500
18895
  if (demo) {
18501
18896
  setProjects(DEMO_PROJECTS.map((p) => ({
18502
18897
  ...p,
18503
- projectDir: path24.join(homedir2(), p.projectName),
18898
+ projectDir: path25.join(homedir2(), p.projectName),
18504
18899
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
18505
18900
  })));
18506
18901
  return;
@@ -18896,7 +19291,6 @@ function SessionsView({
18896
19291
 
18897
19292
  // src/tui/views/Tasks.tsx
18898
19293
  import React20, { useState as useState10, useEffect as useEffect12, useMemo as useMemo5 } from "react";
18899
- init_task_scope();
18900
19294
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
18901
19295
  var STATUS_FILTERS = ["all", "open", "in_progress", "done", "blocked", "needs_review"];
18902
19296
  var PRIORITY_COLORS = {
@@ -19029,37 +19423,22 @@ function TasksView({ onBack }) {
19029
19423
  const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
19030
19424
  return withTrace2("tui.tasks.loadTasks", async () => {
19031
19425
  try {
19032
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
19033
- const client = getClient2();
19034
- if (client) {
19035
- const tScope = sessionScopeFilter();
19036
- const result = await client.execute({
19037
- sql: `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
19038
- FROM tasks
19039
- WHERE 1=1${tScope.sql}
19040
- ORDER BY
19041
- CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 WHEN 'needs_review' THEN 3 WHEN 'done' THEN 4 ELSE 5 END,
19042
- CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
19043
- created_at DESC`,
19044
- args: [...tScope.args]
19045
- });
19046
- setAllTasks(
19047
- result.rows.map((r) => ({
19048
- id: String(r.id ?? ""),
19049
- priority: String(r.priority ?? "p2").toUpperCase(),
19050
- title: String(r.title ?? ""),
19051
- assignee: String(r.assigned_to ?? ""),
19052
- assignedBy: String(r.assigned_by ?? ""),
19053
- status: String(r.status ?? "open"),
19054
- project: String(r.project_name ?? ""),
19055
- createdAt: String(r.created_at ?? ""),
19056
- result: String(r.result ?? "")
19057
- }))
19058
- );
19059
- setDbError(null);
19060
- } else {
19061
- setDbError("Database client not initialized. Run exe-os setup.");
19062
- }
19426
+ const { loadTaskList: loadTaskList2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
19427
+ const tasks = await loadTaskList2();
19428
+ setAllTasks(
19429
+ tasks.map((task) => ({
19430
+ id: task.id,
19431
+ priority: task.priority,
19432
+ title: task.title,
19433
+ assignee: task.assignedTo,
19434
+ assignedBy: task.assignedBy,
19435
+ status: task.status,
19436
+ project: task.projectName,
19437
+ createdAt: task.createdAt,
19438
+ result: task.result
19439
+ }))
19440
+ );
19441
+ setDbError(null);
19063
19442
  } catch (err) {
19064
19443
  setDbError(err instanceof Error ? err.message : "Unknown error");
19065
19444
  } finally {
@@ -19397,12 +19776,12 @@ async function loadGatewayConfig() {
19397
19776
  state.running = false;
19398
19777
  }
19399
19778
  try {
19400
- const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
19779
+ const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
19401
19780
  const { join } = await import("path");
19402
19781
  const home = process.env.HOME ?? "";
19403
19782
  const configPath = join(home, ".exe-os", "gateway.json");
19404
- if (existsSync16(configPath)) {
19405
- const raw = JSON.parse(readFileSync13(configPath, "utf8"));
19783
+ if (existsSync18(configPath)) {
19784
+ const raw = JSON.parse(readFileSync14(configPath, "utf8"));
19406
19785
  state.port = raw.port ?? 3100;
19407
19786
  state.gatewayUrl = raw.gatewayUrl ?? "";
19408
19787
  if (raw.adapters) {
@@ -19802,7 +20181,6 @@ function getAgentStatus(agentId) {
19802
20181
  }
19803
20182
 
19804
20183
  // src/tui/views/Team.tsx
19805
- init_task_scope();
19806
20184
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
19807
20185
  var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
19808
20186
  function roleBadgeColor(role) {
@@ -19875,41 +20253,16 @@ function TeamView({ onBack, onViewSessions }) {
19875
20253
  let projectsByEmployee = /* @__PURE__ */ new Map();
19876
20254
  let currentTaskByEmployee = /* @__PURE__ */ new Map();
19877
20255
  try {
19878
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
19879
- const client = getClient2();
19880
- if (client) {
19881
- const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
19882
- for (const row of memResult.rows) {
19883
- memoryCounts.set(String(row.agent_id), Number(row.cnt));
19884
- }
19885
- const tmScope = sessionScopeFilter();
19886
- for (const emp of roster) {
19887
- const projResult = await client.execute({
19888
- sql: `SELECT DISTINCT project_name,
19889
- MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
19890
- FROM tasks WHERE assigned_to = ? AND status IN ('open','in_progress','done')${tmScope.sql}
19891
- GROUP BY project_name ORDER BY urgency ASC LIMIT 5`,
19892
- args: [emp.name, ...tmScope.args]
19893
- });
19894
- const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
19895
- const urgency = Number(r.urgency);
19896
- let pStatus = "idle";
19897
- if (urgency === 1) pStatus = "active";
19898
- else if (urgency === 2) pStatus = "has_tasks";
19899
- return { name: String(r.project_name), status: pStatus };
19900
- });
19901
- if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
19902
- }
19903
- for (const emp of roster) {
19904
- const taskResult = await client.execute({
19905
- sql: `SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress'${tmScope.sql} ORDER BY updated_at DESC LIMIT 1`,
19906
- args: [emp.name, ...tmScope.args]
19907
- });
19908
- if (taskResult.rows.length > 0) {
19909
- currentTaskByEmployee.set(emp.name, String(taskResult.rows[0].title));
19910
- }
19911
- }
19912
- }
20256
+ const { loadTeamMetrics: loadTeamMetrics2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
20257
+ const teamMetrics = await loadTeamMetrics2(roster.map((emp) => emp.name));
20258
+ memoryCounts = teamMetrics.memoryCounts;
20259
+ projectsByEmployee = new Map(
20260
+ Array.from(teamMetrics.projectsByEmployee.entries()).map(([name, projects]) => [
20261
+ name,
20262
+ projects.filter((project) => !DEPRECATED_PROJECTS.has(project.name))
20263
+ ])
20264
+ );
20265
+ currentTaskByEmployee = teamMetrics.currentTaskByEmployee;
19913
20266
  } catch {
19914
20267
  }
19915
20268
  const teamData = roster.map((emp) => {
@@ -19928,12 +20281,12 @@ function TeamView({ onBack, onViewSessions }) {
19928
20281
  setMembers(teamData);
19929
20282
  setDbError(null);
19930
20283
  try {
19931
- const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
20284
+ const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
19932
20285
  const { join } = await import("path");
19933
20286
  const home = process.env.HOME ?? "";
19934
20287
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
19935
- if (existsSync16(gatewayConfig)) {
19936
- const raw = JSON.parse(readFileSync13(gatewayConfig, "utf8"));
20288
+ if (existsSync18(gatewayConfig)) {
20289
+ const raw = JSON.parse(readFileSync14(gatewayConfig, "utf8"));
19937
20290
  if (raw.agents && raw.agents.length > 0) {
19938
20291
  setExternals(raw.agents.map((a) => ({
19939
20292
  name: a.name,
@@ -20247,24 +20600,8 @@ function WikiView({ onBack }) {
20247
20600
  }
20248
20601
  setSearchLoading(true);
20249
20602
  try {
20250
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
20251
- const client = getClient2();
20252
- const result = await client.execute({
20253
- sql: `SELECT id, agent_id, raw_text, timestamp, project_name
20254
- FROM memories
20255
- WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
20256
- ORDER BY timestamp DESC LIMIT 20`,
20257
- args: [`%${query}%`]
20258
- });
20259
- setSearchResults(
20260
- result.rows.map((r) => ({
20261
- id: String(r.id),
20262
- agentId: String(r.agent_id),
20263
- rawText: String(r.raw_text),
20264
- timestamp: String(r.timestamp),
20265
- projectName: String(r.project_name ?? "")
20266
- }))
20267
- );
20603
+ const { searchWikiMemoryRows: searchWikiMemoryRows2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
20604
+ setSearchResults(await searchWikiMemoryRows2(query));
20268
20605
  } catch {
20269
20606
  setSearchResults([]);
20270
20607
  } finally {
@@ -20597,12 +20934,12 @@ function SettingsView({ onBack }) {
20597
20934
  }
20598
20935
  setProviders(providerList);
20599
20936
  try {
20600
- const { existsSync: existsSync16 } = await import("fs");
20937
+ const { existsSync: existsSync18 } = await import("fs");
20601
20938
  const { join } = await import("path");
20602
20939
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
20603
20940
  const cfg = await loadConfig2();
20604
20941
  const home = process.env.HOME ?? "";
20605
- const hasKey = existsSync16(join(home, ".exe-os", "master.key"));
20942
+ const hasKey = existsSync18(join(home, ".exe-os", "master.key"));
20606
20943
  if (cfg.cloud) {
20607
20944
  setCloud({
20608
20945
  configured: true,
@@ -20615,22 +20952,22 @@ function SettingsView({ onBack }) {
20615
20952
  const pidPath = join(home, ".exe-os", "exed.pid");
20616
20953
  let daemon = "unknown";
20617
20954
  try {
20618
- daemon = existsSync16(pidPath) ? "running" : "stopped";
20955
+ daemon = existsSync18(pidPath) ? "running" : "stopped";
20619
20956
  } catch {
20620
20957
  }
20621
20958
  let version = "unknown";
20622
20959
  try {
20623
- const { readFileSync: readFileSync13 } = await import("fs");
20624
- const { createRequire: createRequire2 } = await import("module");
20625
- const require2 = createRequire2(import.meta.url);
20960
+ const { readFileSync: readFileSync14 } = await import("fs");
20961
+ const { createRequire: createRequire3 } = await import("module");
20962
+ const require2 = createRequire3(import.meta.url);
20626
20963
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
20627
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
20964
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
20628
20965
  version = pkg.version;
20629
20966
  } catch {
20630
20967
  try {
20631
- const { readFileSync: readFileSync13 } = await import("fs");
20968
+ const { readFileSync: readFileSync14 } = await import("fs");
20632
20969
  const { join: joinPath } = await import("path");
20633
- const pkg = JSON.parse(readFileSync13(joinPath(process.cwd(), "package.json"), "utf8"));
20970
+ const pkg = JSON.parse(readFileSync14(joinPath(process.cwd(), "package.json"), "utf8"));
20634
20971
  version = pkg.version;
20635
20972
  } catch {
20636
20973
  }