@askexenow/exe-os 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1411 -953
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +913 -543
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +418 -262
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +793 -485
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +566 -357
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +530 -319
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +547 -336
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3442 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +534 -323
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +664 -431
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +1049 -680
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +448 -371
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
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(),
@@ -3844,6 +4040,110 @@ var init_state_bus = __esm({
3844
4040
  }
3845
4041
  });
3846
4042
 
4043
+ // src/lib/project-name.ts
4044
+ import { execSync as execSync5 } from "child_process";
4045
+ import path12 from "path";
4046
+ function getProjectName(cwd2) {
4047
+ const dir = cwd2 ?? process.cwd();
4048
+ if (_cached2 && _cachedCwd === dir) return _cached2;
4049
+ try {
4050
+ let repoRoot;
4051
+ try {
4052
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4053
+ cwd: dir,
4054
+ encoding: "utf8",
4055
+ timeout: 2e3,
4056
+ stdio: ["pipe", "pipe", "pipe"]
4057
+ }).trim();
4058
+ repoRoot = path12.dirname(gitCommonDir);
4059
+ } catch {
4060
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
4061
+ cwd: dir,
4062
+ encoding: "utf8",
4063
+ timeout: 2e3,
4064
+ stdio: ["pipe", "pipe", "pipe"]
4065
+ }).trim();
4066
+ }
4067
+ _cached2 = path12.basename(repoRoot);
4068
+ _cachedCwd = dir;
4069
+ return _cached2;
4070
+ } catch {
4071
+ _cached2 = path12.basename(dir);
4072
+ _cachedCwd = dir;
4073
+ return _cached2;
4074
+ }
4075
+ }
4076
+ var _cached2, _cachedCwd;
4077
+ var init_project_name = __esm({
4078
+ "src/lib/project-name.ts"() {
4079
+ "use strict";
4080
+ _cached2 = null;
4081
+ _cachedCwd = null;
4082
+ }
4083
+ });
4084
+
4085
+ // src/lib/session-scope.ts
4086
+ var session_scope_exports = {};
4087
+ __export(session_scope_exports, {
4088
+ assertSessionScope: () => assertSessionScope,
4089
+ findSessionForProject: () => findSessionForProject,
4090
+ getSessionProject: () => getSessionProject
4091
+ });
4092
+ function getSessionProject(sessionName) {
4093
+ const sessions = listSessions();
4094
+ const entry = sessions.find((s) => s.windowName === sessionName);
4095
+ if (!entry) return null;
4096
+ const parts = entry.projectDir.split("/").filter(Boolean);
4097
+ return parts[parts.length - 1] ?? null;
4098
+ }
4099
+ function findSessionForProject(projectName) {
4100
+ const sessions = listSessions();
4101
+ for (const s of sessions) {
4102
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
4103
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4104
+ }
4105
+ return null;
4106
+ }
4107
+ function assertSessionScope(actionType, targetProject) {
4108
+ try {
4109
+ const currentProject = getProjectName();
4110
+ const exeSession = resolveExeSession();
4111
+ if (!exeSession) {
4112
+ return { allowed: true, reason: "no_session" };
4113
+ }
4114
+ if (currentProject === targetProject) {
4115
+ return {
4116
+ allowed: true,
4117
+ reason: "same_session",
4118
+ currentProject,
4119
+ targetProject
4120
+ };
4121
+ }
4122
+ process.stderr.write(
4123
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4124
+ `
4125
+ );
4126
+ return {
4127
+ allowed: false,
4128
+ reason: "cross_session_denied",
4129
+ currentProject,
4130
+ targetProject,
4131
+ targetSession: findSessionForProject(targetProject)?.windowName
4132
+ };
4133
+ } catch {
4134
+ return { allowed: true, reason: "no_session" };
4135
+ }
4136
+ }
4137
+ var init_session_scope = __esm({
4138
+ "src/lib/session-scope.ts"() {
4139
+ "use strict";
4140
+ init_session_registry();
4141
+ init_project_name();
4142
+ init_tmux_routing();
4143
+ init_employees();
4144
+ }
4145
+ });
4146
+
3847
4147
  // src/lib/tasks-crud.ts
3848
4148
  var tasks_crud_exports = {};
3849
4149
  __export(tasks_crud_exports, {
@@ -3861,12 +4161,12 @@ __export(tasks_crud_exports, {
3861
4161
  updateTaskStatus: () => updateTaskStatus,
3862
4162
  writeCheckpoint: () => writeCheckpoint
3863
4163
  });
3864
- import crypto3 from "crypto";
3865
- import path11 from "path";
3866
- import os8 from "os";
3867
- import { execSync as execSync5 } from "child_process";
4164
+ import crypto4 from "crypto";
4165
+ import path13 from "path";
4166
+ import os9 from "os";
4167
+ import { execSync as execSync6 } from "child_process";
3868
4168
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3869
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
4169
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
3870
4170
  async function writeCheckpoint(input) {
3871
4171
  const client = getClient();
3872
4172
  const row = await resolveTask(client, input.taskId);
@@ -3982,13 +4282,28 @@ async function resolveTask(client, identifier, scopeSession) {
3982
4282
  }
3983
4283
  async function createTaskCore(input) {
3984
4284
  const client = getClient();
3985
- const id = crypto3.randomUUID();
4285
+ const id = crypto4.randomUUID();
3986
4286
  const now = (/* @__PURE__ */ new Date()).toISOString();
3987
4287
  const slug = slugify(input.title);
3988
4288
  let earlySessionScope = null;
4289
+ let scopeMismatchWarning;
3989
4290
  try {
3990
4291
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3991
- earlySessionScope = resolveExeSession2();
4292
+ const resolved = resolveExeSession2();
4293
+ if (resolved && input.projectName) {
4294
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
4295
+ const sessionProject = getSessionProject2(resolved);
4296
+ if (sessionProject && sessionProject !== input.projectName) {
4297
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
4298
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
4299
+ `);
4300
+ earlySessionScope = null;
4301
+ } else {
4302
+ earlySessionScope = resolved;
4303
+ }
4304
+ } else {
4305
+ earlySessionScope = resolved;
4306
+ }
3992
4307
  } catch {
3993
4308
  }
3994
4309
  const scope = earlySessionScope ?? "default";
@@ -4039,10 +4354,14 @@ async function createTaskCore(input) {
4039
4354
  ${laneWarning}` : laneWarning;
4040
4355
  }
4041
4356
  }
4357
+ if (scopeMismatchWarning) {
4358
+ warning = warning ? `${warning}
4359
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
4360
+ }
4042
4361
  if (input.baseDir) {
4043
4362
  try {
4044
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
4045
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
4363
+ await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
4364
+ await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
4046
4365
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4047
4366
  await ensureGitignoreExe(input.baseDir);
4048
4367
  } catch {
@@ -4078,13 +4397,19 @@ ${laneWarning}` : laneWarning;
4078
4397
  });
4079
4398
  if (input.baseDir) {
4080
4399
  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 });
4400
+ const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
4401
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
4402
+ const mdDir = path13.dirname(mdPath);
4403
+ if (!existsSync13(mdDir)) await mkdir3(mdDir, { recursive: true });
4085
4404
  const reviewer = input.reviewer ?? input.assignedBy;
4086
4405
  const mdContent = `# ${input.title}
4087
4406
 
4407
+ ## MANDATORY: When done
4408
+
4409
+ You MUST call update_task with status "done" and a result summary when finished.
4410
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4411
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4412
+
4088
4413
  **ID:** ${id}
4089
4414
  **Status:** ${initialStatus}
4090
4415
  **Priority:** ${input.priority}
@@ -4098,12 +4423,6 @@ ${laneWarning}` : laneWarning;
4098
4423
  ## Context
4099
4424
 
4100
4425
  ${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
4426
  `;
4108
4427
  await writeFile3(mdPath, mdContent, "utf-8");
4109
4428
  } catch (err) {
@@ -4185,14 +4504,14 @@ function isTmuxSessionAlive(identifier) {
4185
4504
  if (!identifier || identifier === "unknown") return true;
4186
4505
  try {
4187
4506
  if (identifier.startsWith("%")) {
4188
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
4507
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
4189
4508
  timeout: 2e3,
4190
4509
  encoding: "utf8",
4191
4510
  stdio: ["pipe", "pipe", "pipe"]
4192
4511
  });
4193
4512
  return output.split("\n").some((l) => l.trim() === identifier);
4194
4513
  } else {
4195
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4514
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4196
4515
  timeout: 2e3,
4197
4516
  stdio: ["pipe", "pipe", "pipe"]
4198
4517
  });
@@ -4201,7 +4520,7 @@ function isTmuxSessionAlive(identifier) {
4201
4520
  } catch {
4202
4521
  if (identifier.startsWith("%")) return true;
4203
4522
  try {
4204
- execSync5("tmux list-sessions", {
4523
+ execSync6("tmux list-sessions", {
4205
4524
  timeout: 2e3,
4206
4525
  stdio: ["pipe", "pipe", "pipe"]
4207
4526
  });
@@ -4216,12 +4535,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
4216
4535
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
4217
4536
  try {
4218
4537
  const since = new Date(taskCreatedAt).toISOString();
4219
- const branch = execSync5(
4538
+ const branch = execSync6(
4220
4539
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
4221
4540
  { encoding: "utf8", timeout: 3e3 }
4222
4541
  ).trim();
4223
4542
  const branchArg = branch && branch !== "HEAD" ? branch : "";
4224
- const commitCount = execSync5(
4543
+ const commitCount = execSync6(
4225
4544
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
4226
4545
  { encoding: "utf8", timeout: 5e3 }
4227
4546
  ).trim();
@@ -4352,7 +4671,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4352
4671
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4353
4672
  } catch {
4354
4673
  }
4355
- if (input.status === "done" || input.status === "cancelled") {
4674
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4356
4675
  try {
4357
4676
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4358
4677
  clearQueueForAgent2(String(row.assigned_to));
@@ -4381,9 +4700,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4381
4700
  return { taskFile, assignedTo, assignedBy, taskSlug };
4382
4701
  }
4383
4702
  async function ensureArchitectureDoc(baseDir, projectName) {
4384
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4703
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
4385
4704
  try {
4386
- if (existsSync11(archPath)) return;
4705
+ if (existsSync13(archPath)) return;
4387
4706
  const template = [
4388
4707
  `# ${projectName} \u2014 System Architecture`,
4389
4708
  "",
@@ -4416,10 +4735,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4416
4735
  }
4417
4736
  }
4418
4737
  async function ensureGitignoreExe(baseDir) {
4419
- const gitignorePath = path11.join(baseDir, ".gitignore");
4738
+ const gitignorePath = path13.join(baseDir, ".gitignore");
4420
4739
  try {
4421
- if (existsSync11(gitignorePath)) {
4422
- const content = readFileSync11(gitignorePath, "utf-8");
4740
+ if (existsSync13(gitignorePath)) {
4741
+ const content = readFileSync12(gitignorePath, "utf-8");
4423
4742
  if (/^\/?exe\/?$/m.test(content)) return;
4424
4743
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4425
4744
  } else {
@@ -4450,58 +4769,42 @@ var init_tasks_crud = __esm({
4450
4769
  });
4451
4770
 
4452
4771
  // src/lib/tasks-review.ts
4453
- import path12 from "path";
4454
- import { existsSync as existsSync12, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4772
+ import path14 from "path";
4773
+ import { existsSync as existsSync14, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4455
4774
  async function countPendingReviews(sessionScope) {
4456
4775
  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
- }
4776
+ const scope = strictSessionScopeFilter(
4777
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4778
+ );
4464
4779
  const result = await client.execute({
4465
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4466
- args: []
4780
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4781
+ WHERE status = 'needs_review'${scope.sql}`,
4782
+ args: [...scope.args]
4467
4783
  });
4468
4784
  return Number(result.rows[0]?.cnt) || 0;
4469
4785
  }
4470
4786
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4471
4787
  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
- }
4788
+ const scope = strictSessionScopeFilter(
4789
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4790
+ );
4481
4791
  const result = await client.execute({
4482
4792
  sql: `SELECT COUNT(*) as cnt FROM tasks
4483
- WHERE status = 'needs_review' AND updated_at > ?`,
4484
- args: [sinceIso]
4793
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4794
+ args: [sinceIso, ...scope.args]
4485
4795
  });
4486
4796
  return Number(result.rows[0]?.cnt) || 0;
4487
4797
  }
4488
4798
  async function listPendingReviews(limit, sessionScope) {
4489
4799
  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
- }
4800
+ const scope = strictSessionScopeFilter(
4801
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4802
+ );
4500
4803
  const result = await client.execute({
4501
4804
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4502
- WHERE status = 'needs_review'
4805
+ WHERE status = 'needs_review'${scope.sql}
4503
4806
  ORDER BY updated_at ASC LIMIT ?`,
4504
- args: [limit]
4807
+ args: [...scope.args, limit]
4505
4808
  });
4506
4809
  return result.rows;
4507
4810
  }
@@ -4513,7 +4816,7 @@ async function cleanupOrphanedReviews() {
4513
4816
  WHERE status IN ('open', 'needs_review', 'in_progress')
4514
4817
  AND assigned_by = 'system'
4515
4818
  AND title LIKE 'Review:%'
4516
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4819
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4517
4820
  args: [now]
4518
4821
  });
4519
4822
  const r1b = await client.execute({
@@ -4632,11 +4935,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4632
4935
  );
4633
4936
  }
4634
4937
  try {
4635
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
4636
- if (existsSync12(cacheDir)) {
4938
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4939
+ if (existsSync14(cacheDir)) {
4637
4940
  for (const f of readdirSync2(cacheDir)) {
4638
4941
  if (f.startsWith("review-notified-")) {
4639
- unlinkSync4(path12.join(cacheDir, f));
4942
+ unlinkSync4(path14.join(cacheDir, f));
4640
4943
  }
4641
4944
  }
4642
4945
  }
@@ -4653,11 +4956,12 @@ var init_tasks_review = __esm({
4653
4956
  init_tmux_routing();
4654
4957
  init_session_key();
4655
4958
  init_state_bus();
4959
+ init_task_scope();
4656
4960
  }
4657
4961
  });
4658
4962
 
4659
4963
  // src/lib/tasks-chain.ts
4660
- import path13 from "path";
4964
+ import path15 from "path";
4661
4965
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4662
4966
  async function cascadeUnblock(taskId, baseDir, now) {
4663
4967
  const client = getClient();
@@ -4674,7 +4978,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4674
4978
  });
4675
4979
  for (const ur of unblockedRows.rows) {
4676
4980
  try {
4677
- const ubFile = path13.join(baseDir, String(ur.task_file));
4981
+ const ubFile = path15.join(baseDir, String(ur.task_file));
4678
4982
  let ubContent = await readFile3(ubFile, "utf-8");
4679
4983
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4680
4984
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4709,7 +5013,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4709
5013
  const scScope = sessionScopeFilter();
4710
5014
  const remaining = await client.execute({
4711
5015
  sql: `SELECT COUNT(*) as cnt FROM tasks
4712
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
5016
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4713
5017
  args: [parentTaskId, ...scScope.args]
4714
5018
  });
4715
5019
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4741,110 +5045,6 @@ var init_tasks_chain = __esm({
4741
5045
  }
4742
5046
  });
4743
5047
 
4744
- // src/lib/project-name.ts
4745
- import { execSync as execSync6 } from "child_process";
4746
- import path14 from "path";
4747
- function getProjectName(cwd2) {
4748
- const dir = cwd2 ?? process.cwd();
4749
- if (_cached2 && _cachedCwd === dir) return _cached2;
4750
- try {
4751
- let repoRoot;
4752
- try {
4753
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
4754
- cwd: dir,
4755
- encoding: "utf8",
4756
- timeout: 2e3,
4757
- stdio: ["pipe", "pipe", "pipe"]
4758
- }).trim();
4759
- repoRoot = path14.dirname(gitCommonDir);
4760
- } catch {
4761
- repoRoot = execSync6("git rev-parse --show-toplevel", {
4762
- cwd: dir,
4763
- encoding: "utf8",
4764
- timeout: 2e3,
4765
- stdio: ["pipe", "pipe", "pipe"]
4766
- }).trim();
4767
- }
4768
- _cached2 = path14.basename(repoRoot);
4769
- _cachedCwd = dir;
4770
- return _cached2;
4771
- } catch {
4772
- _cached2 = path14.basename(dir);
4773
- _cachedCwd = dir;
4774
- return _cached2;
4775
- }
4776
- }
4777
- var _cached2, _cachedCwd;
4778
- var init_project_name = __esm({
4779
- "src/lib/project-name.ts"() {
4780
- "use strict";
4781
- _cached2 = null;
4782
- _cachedCwd = null;
4783
- }
4784
- });
4785
-
4786
- // src/lib/session-scope.ts
4787
- var session_scope_exports = {};
4788
- __export(session_scope_exports, {
4789
- assertSessionScope: () => assertSessionScope,
4790
- findSessionForProject: () => findSessionForProject,
4791
- getSessionProject: () => getSessionProject
4792
- });
4793
- function getSessionProject(sessionName) {
4794
- const sessions = listSessions();
4795
- const entry = sessions.find((s) => s.windowName === sessionName);
4796
- if (!entry) return null;
4797
- const parts = entry.projectDir.split("/").filter(Boolean);
4798
- return parts[parts.length - 1] ?? null;
4799
- }
4800
- function findSessionForProject(projectName) {
4801
- const sessions = listSessions();
4802
- for (const s of sessions) {
4803
- const proj = s.projectDir.split("/").filter(Boolean).pop();
4804
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4805
- }
4806
- return null;
4807
- }
4808
- function assertSessionScope(actionType, targetProject) {
4809
- try {
4810
- const currentProject = getProjectName();
4811
- const exeSession = resolveExeSession();
4812
- if (!exeSession) {
4813
- return { allowed: true, reason: "no_session" };
4814
- }
4815
- if (currentProject === targetProject) {
4816
- return {
4817
- allowed: true,
4818
- reason: "same_session",
4819
- currentProject,
4820
- targetProject
4821
- };
4822
- }
4823
- process.stderr.write(
4824
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4825
- `
4826
- );
4827
- return {
4828
- allowed: false,
4829
- reason: "cross_session_denied",
4830
- currentProject,
4831
- targetProject,
4832
- targetSession: findSessionForProject(targetProject)?.windowName
4833
- };
4834
- } catch {
4835
- return { allowed: true, reason: "no_session" };
4836
- }
4837
- }
4838
- var init_session_scope = __esm({
4839
- "src/lib/session-scope.ts"() {
4840
- "use strict";
4841
- init_session_registry();
4842
- init_project_name();
4843
- init_tmux_routing();
4844
- init_employees();
4845
- }
4846
- });
4847
-
4848
5048
  // src/lib/tasks-notify.ts
4849
5049
  async function dispatchTaskToEmployee(input) {
4850
5050
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -4912,10 +5112,10 @@ var init_tasks_notify = __esm({
4912
5112
  });
4913
5113
 
4914
5114
  // src/lib/behaviors.ts
4915
- import crypto4 from "crypto";
5115
+ import crypto5 from "crypto";
4916
5116
  async function storeBehavior(opts) {
4917
5117
  const client = getClient();
4918
- const id = crypto4.randomUUID();
5118
+ const id = crypto5.randomUUID();
4919
5119
  const now = (/* @__PURE__ */ new Date()).toISOString();
4920
5120
  await client.execute({
4921
5121
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4944,7 +5144,7 @@ __export(skill_learning_exports, {
4944
5144
  storeTrajectory: () => storeTrajectory,
4945
5145
  sweepTrajectories: () => sweepTrajectories
4946
5146
  });
4947
- import crypto5 from "crypto";
5147
+ import crypto6 from "crypto";
4948
5148
  async function extractTrajectory(taskId, agentId) {
4949
5149
  const client = getClient();
4950
5150
  const result = await client.execute({
@@ -4973,11 +5173,11 @@ async function extractTrajectory(taskId, agentId) {
4973
5173
  return signature;
4974
5174
  }
4975
5175
  function hashSignature(signature) {
4976
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5176
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4977
5177
  }
4978
5178
  async function storeTrajectory(opts) {
4979
5179
  const client = getClient();
4980
- const id = crypto5.randomUUID();
5180
+ const id = crypto6.randomUUID();
4981
5181
  const now = (/* @__PURE__ */ new Date()).toISOString();
4982
5182
  const signatureHash = hashSignature(opts.signature);
4983
5183
  await client.execute({
@@ -5242,8 +5442,8 @@ __export(tasks_exports, {
5242
5442
  updateTaskStatus: () => updateTaskStatus,
5243
5443
  writeCheckpoint: () => writeCheckpoint
5244
5444
  });
5245
- import path15 from "path";
5246
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5445
+ import path16 from "path";
5446
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5247
5447
  async function createTask(input) {
5248
5448
  const result = await createTaskCore(input);
5249
5449
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5262,12 +5462,12 @@ async function updateTask(input) {
5262
5462
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5263
5463
  try {
5264
5464
  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`);
5465
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5466
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
5267
5467
  if (input.status === "in_progress") {
5268
5468
  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") {
5469
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5470
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5271
5471
  try {
5272
5472
  unlinkSync5(cachePath);
5273
5473
  } catch {
@@ -5275,10 +5475,10 @@ async function updateTask(input) {
5275
5475
  }
5276
5476
  } catch {
5277
5477
  }
5278
- if (input.status === "done") {
5478
+ if (input.status === "done" || input.status === "closed") {
5279
5479
  await cleanupReviewFile(row, taskFile, input.baseDir);
5280
5480
  }
5281
- if (input.status === "done" || input.status === "cancelled") {
5481
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5282
5482
  try {
5283
5483
  const client = getClient();
5284
5484
  const taskTitle = String(row.title);
@@ -5294,7 +5494,7 @@ async function updateTask(input) {
5294
5494
  if (!isCoordinatorName(assignedAgent)) {
5295
5495
  try {
5296
5496
  const draftClient = getClient();
5297
- if (input.status === "done") {
5497
+ if (input.status === "done" || input.status === "closed") {
5298
5498
  await draftClient.execute({
5299
5499
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5300
5500
  args: [assignedAgent]
@@ -5311,7 +5511,7 @@ async function updateTask(input) {
5311
5511
  try {
5312
5512
  const client = getClient();
5313
5513
  const cascaded = await client.execute({
5314
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5514
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5315
5515
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5316
5516
  args: [now, taskId]
5317
5517
  });
@@ -5324,14 +5524,14 @@ async function updateTask(input) {
5324
5524
  } catch {
5325
5525
  }
5326
5526
  }
5327
- const isTerminal = input.status === "done" || input.status === "needs_review";
5527
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5328
5528
  if (isTerminal) {
5329
5529
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5330
5530
  if (!isCoordinator) {
5331
5531
  notifyTaskDone();
5332
5532
  }
5333
5533
  await markTaskNotificationsRead(taskFile);
5334
- if (input.status === "done") {
5534
+ if (input.status === "done" || input.status === "closed") {
5335
5535
  try {
5336
5536
  await cascadeUnblock(taskId, input.baseDir, now);
5337
5537
  } catch {
@@ -5351,7 +5551,7 @@ async function updateTask(input) {
5351
5551
  }
5352
5552
  }
5353
5553
  }
5354
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5554
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5355
5555
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5356
5556
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5357
5557
  taskId,
@@ -5723,6 +5923,7 @@ __export(tmux_routing_exports, {
5723
5923
  isEmployeeAlive: () => isEmployeeAlive,
5724
5924
  isExeSession: () => isExeSession,
5725
5925
  isSessionBusy: () => isSessionBusy,
5926
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5726
5927
  notifyParentExe: () => notifyParentExe,
5727
5928
  parseParentExe: () => parseParentExe,
5728
5929
  registerParentExe: () => registerParentExe,
@@ -5733,13 +5934,13 @@ __export(tmux_routing_exports, {
5733
5934
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5734
5935
  });
5735
5936
  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";
5937
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync15, appendFileSync, readdirSync as readdirSync3 } from "fs";
5938
+ import path17 from "path";
5939
+ import os10 from "os";
5739
5940
  import { fileURLToPath as fileURLToPath2 } from "url";
5740
5941
  import { unlinkSync as unlinkSync6 } from "fs";
5741
5942
  function spawnLockPath(sessionName) {
5742
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5943
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5743
5944
  }
5744
5945
  function isProcessAlive(pid) {
5745
5946
  try {
@@ -5750,13 +5951,13 @@ function isProcessAlive(pid) {
5750
5951
  }
5751
5952
  }
5752
5953
  function acquireSpawnLock2(sessionName) {
5753
- if (!existsSync13(SPAWN_LOCK_DIR)) {
5954
+ if (!existsSync15(SPAWN_LOCK_DIR)) {
5754
5955
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5755
5956
  }
5756
5957
  const lockFile = spawnLockPath(sessionName);
5757
- if (existsSync13(lockFile)) {
5958
+ if (existsSync15(lockFile)) {
5758
5959
  try {
5759
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5960
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
5760
5961
  const age = Date.now() - lock.timestamp;
5761
5962
  if (isProcessAlive(lock.pid) && age < 6e4) {
5762
5963
  return false;
@@ -5764,7 +5965,7 @@ function acquireSpawnLock2(sessionName) {
5764
5965
  } catch {
5765
5966
  }
5766
5967
  }
5767
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5968
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5768
5969
  return true;
5769
5970
  }
5770
5971
  function releaseSpawnLock2(sessionName) {
@@ -5776,13 +5977,13 @@ function releaseSpawnLock2(sessionName) {
5776
5977
  function resolveBehaviorsExporterScript() {
5777
5978
  try {
5778
5979
  const thisFile = fileURLToPath2(import.meta.url);
5779
- const scriptPath = path16.join(
5780
- path16.dirname(thisFile),
5980
+ const scriptPath = path17.join(
5981
+ path17.dirname(thisFile),
5781
5982
  "..",
5782
5983
  "bin",
5783
5984
  "exe-export-behaviors.js"
5784
5985
  );
5785
- return existsSync13(scriptPath) ? scriptPath : null;
5986
+ return existsSync15(scriptPath) ? scriptPath : null;
5786
5987
  } catch {
5787
5988
  return null;
5788
5989
  }
@@ -5848,12 +6049,12 @@ function extractRootExe(name) {
5848
6049
  return parts.length > 0 ? parts[parts.length - 1] : null;
5849
6050
  }
5850
6051
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5851
- if (!existsSync13(SESSION_CACHE)) {
6052
+ if (!existsSync15(SESSION_CACHE)) {
5852
6053
  mkdirSync6(SESSION_CACHE, { recursive: true });
5853
6054
  }
5854
6055
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5855
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5856
- writeFileSync7(filePath, JSON.stringify({
6056
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6057
+ writeFileSync8(filePath, JSON.stringify({
5857
6058
  parentExe: rootExe,
5858
6059
  dispatchedBy: dispatchedBy || rootExe,
5859
6060
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5861,7 +6062,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5861
6062
  }
5862
6063
  function getParentExe(sessionKey) {
5863
6064
  try {
5864
- const data = JSON.parse(readFileSync12(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6065
+ const data = JSON.parse(readFileSync13(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5865
6066
  return data.parentExe || null;
5866
6067
  } catch {
5867
6068
  return null;
@@ -5869,8 +6070,8 @@ function getParentExe(sessionKey) {
5869
6070
  }
5870
6071
  function getDispatchedBy(sessionKey) {
5871
6072
  try {
5872
- const data = JSON.parse(readFileSync12(
5873
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6073
+ const data = JSON.parse(readFileSync13(
6074
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5874
6075
  "utf8"
5875
6076
  ));
5876
6077
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5940,8 +6141,8 @@ async function verifyPaneAtCapacity(sessionName) {
5940
6141
  }
5941
6142
  function readDebounceState() {
5942
6143
  try {
5943
- if (!existsSync13(DEBOUNCE_FILE)) return {};
5944
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
6144
+ if (!existsSync15(DEBOUNCE_FILE)) return {};
6145
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
5945
6146
  const state = {};
5946
6147
  for (const [key, val] of Object.entries(raw)) {
5947
6148
  if (typeof val === "number") {
@@ -5957,8 +6158,8 @@ function readDebounceState() {
5957
6158
  }
5958
6159
  function writeDebounceState(state) {
5959
6160
  try {
5960
- if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5961
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6161
+ if (!existsSync15(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
6162
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5962
6163
  } catch {
5963
6164
  }
5964
6165
  }
@@ -6056,8 +6257,8 @@ function sendIntercom(targetSession) {
6056
6257
  try {
6057
6258
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6058
6259
  const agent = baseAgentName(rawAgent);
6059
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
6060
- if (existsSync13(markerPath)) {
6260
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6261
+ if (existsSync15(markerPath)) {
6061
6262
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6062
6263
  return "debounced";
6063
6264
  }
@@ -6066,8 +6267,8 @@ function sendIntercom(targetSession) {
6066
6267
  try {
6067
6268
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6068
6269
  const agent = baseAgentName(rawAgent);
6069
- const taskDir = path16.join(process.cwd(), "exe", agent);
6070
- if (existsSync13(taskDir)) {
6270
+ const taskDir = path17.join(process.cwd(), "exe", agent);
6271
+ if (existsSync15(taskDir)) {
6071
6272
  const files = readdirSync3(taskDir).filter(
6072
6273
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6073
6274
  );
@@ -6127,6 +6328,21 @@ function notifyParentExe(sessionKey) {
6127
6328
  }
6128
6329
  return true;
6129
6330
  }
6331
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6332
+ const transport = getTransport();
6333
+ try {
6334
+ const sessions = transport.listSessions();
6335
+ if (!sessions.includes(coordinatorSession)) return false;
6336
+ execSync7(
6337
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6338
+ { timeout: 3e3 }
6339
+ );
6340
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6341
+ return true;
6342
+ } catch {
6343
+ return false;
6344
+ }
6345
+ }
6130
6346
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6131
6347
  if (isCoordinatorName(employeeName)) {
6132
6348
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6200,26 +6416,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6200
6416
  const transport = getTransport();
6201
6417
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6202
6418
  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)) {
6419
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
6420
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6421
+ if (!existsSync15(logDir)) {
6206
6422
  mkdirSync6(logDir, { recursive: true });
6207
6423
  }
6208
6424
  transport.kill(sessionName);
6209
6425
  let cleanupSuffix = "";
6210
6426
  try {
6211
6427
  const thisFile = fileURLToPath2(import.meta.url);
6212
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6213
- if (existsSync13(cleanupScript)) {
6428
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6429
+ if (existsSync15(cleanupScript)) {
6214
6430
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6215
6431
  }
6216
6432
  } catch {
6217
6433
  }
6218
6434
  try {
6219
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
6435
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
6220
6436
  let claudeJson = {};
6221
6437
  try {
6222
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6438
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
6223
6439
  } catch {
6224
6440
  }
6225
6441
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6227,17 +6443,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6227
6443
  const trustDir = opts?.cwd ?? projectDir;
6228
6444
  if (!projects[trustDir]) projects[trustDir] = {};
6229
6445
  projects[trustDir].hasTrustDialogAccepted = true;
6230
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6446
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6231
6447
  } catch {
6232
6448
  }
6233
6449
  try {
6234
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
6450
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
6235
6451
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6236
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
6237
- const settingsPath = path16.join(projSettingsDir, "settings.json");
6452
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6453
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
6238
6454
  let settings = {};
6239
6455
  try {
6240
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6456
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
6241
6457
  } catch {
6242
6458
  }
6243
6459
  const perms = settings.permissions ?? {};
@@ -6266,7 +6482,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6266
6482
  perms.allow = allow;
6267
6483
  settings.permissions = perms;
6268
6484
  mkdirSync6(projSettingsDir, { recursive: true });
6269
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6485
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6270
6486
  }
6271
6487
  } catch {
6272
6488
  }
@@ -6281,8 +6497,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6281
6497
  let behaviorsFlag = "";
6282
6498
  let legacyFallbackWarned = false;
6283
6499
  if (!useExeAgent && !useBinSymlink) {
6284
- const identityPath = path16.join(
6285
- os9.homedir(),
6500
+ const identityPath = path17.join(
6501
+ os10.homedir(),
6286
6502
  ".exe-os",
6287
6503
  "identity",
6288
6504
  `${employeeName}.md`
@@ -6291,13 +6507,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6291
6507
  const hasAgentFlag = claudeSupportsAgentFlag();
6292
6508
  if (hasAgentFlag) {
6293
6509
  identityFlag = ` --agent ${employeeName}`;
6294
- } else if (existsSync13(identityPath)) {
6510
+ } else if (existsSync15(identityPath)) {
6295
6511
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6296
6512
  legacyFallbackWarned = true;
6297
6513
  }
6298
6514
  const behaviorsFile = exportBehaviorsSync(
6299
6515
  employeeName,
6300
- path16.basename(spawnCwd),
6516
+ path17.basename(spawnCwd),
6301
6517
  sessionName
6302
6518
  );
6303
6519
  if (behaviorsFile) {
@@ -6312,16 +6528,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6312
6528
  }
6313
6529
  let sessionContextFlag = "";
6314
6530
  try {
6315
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
6531
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
6316
6532
  mkdirSync6(ctxDir, { recursive: true });
6317
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
6533
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6318
6534
  const ctxContent = [
6319
6535
  `## Session Context`,
6320
6536
  `You are running in tmux session: ${sessionName}.`,
6321
6537
  `Your parent coordinator session is ${exeSession}.`,
6322
6538
  `Your employees (if any) use the -${exeSession} suffix.`
6323
6539
  ].join("\n");
6324
- writeFileSync7(ctxFile, ctxContent);
6540
+ writeFileSync8(ctxFile, ctxContent);
6325
6541
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6326
6542
  } catch {
6327
6543
  }
@@ -6398,8 +6614,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6398
6614
  transport.pipeLog(sessionName, logFile);
6399
6615
  try {
6400
6616
  const mySession = getMySession();
6401
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6402
- writeFileSync7(dispatchInfo, JSON.stringify({
6617
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6618
+ writeFileSync8(dispatchInfo, JSON.stringify({
6403
6619
  dispatchedBy: mySession,
6404
6620
  rootExe: exeSession,
6405
6621
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6473,15 +6689,15 @@ var init_tmux_routing = __esm({
6473
6689
  init_intercom_queue();
6474
6690
  init_plan_limits();
6475
6691
  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");
6692
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
6693
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
6478
6694
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6479
6695
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6480
6696
  VERIFY_PANE_LINES = 200;
6481
6697
  INTERCOM_DEBOUNCE_MS = 3e4;
6482
6698
  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");
6699
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
6700
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
6485
6701
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6486
6702
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6487
6703
  }
@@ -6491,7 +6707,8 @@ var init_tmux_routing = __esm({
6491
6707
  var task_scope_exports = {};
6492
6708
  __export(task_scope_exports, {
6493
6709
  getCurrentSessionScope: () => getCurrentSessionScope,
6494
- sessionScopeFilter: () => sessionScopeFilter
6710
+ sessionScopeFilter: () => sessionScopeFilter,
6711
+ strictSessionScopeFilter: () => strictSessionScopeFilter
6495
6712
  });
6496
6713
  function getCurrentSessionScope() {
6497
6714
  try {
@@ -6509,6 +6726,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
6509
6726
  args: [scope]
6510
6727
  };
6511
6728
  }
6729
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
6730
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
6731
+ if (!scope) return { sql: "", args: [] };
6732
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
6733
+ return {
6734
+ sql: ` AND ${col} = ?`,
6735
+ args: [scope]
6736
+ };
6737
+ }
6512
6738
  var init_task_scope = __esm({
6513
6739
  "src/lib/task-scope.ts"() {
6514
6740
  "use strict";
@@ -8367,10 +8593,10 @@ var init_hooks = __esm({
8367
8593
  });
8368
8594
 
8369
8595
  // src/runtime/safety-checks.ts
8370
- import path17 from "path";
8371
- import os10 from "os";
8596
+ import path18 from "path";
8597
+ import os11 from "os";
8372
8598
  function checkPathSafety(filePath) {
8373
- const resolved = path17.resolve(filePath);
8599
+ const resolved = path18.resolve(filePath);
8374
8600
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
8375
8601
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
8376
8602
  if (matches) {
@@ -8380,7 +8606,7 @@ function checkPathSafety(filePath) {
8380
8606
  return { safe: true, bypassImmune: true };
8381
8607
  }
8382
8608
  function checkReadPathSafety(filePath) {
8383
- const resolved = path17.resolve(filePath);
8609
+ const resolved = path18.resolve(filePath);
8384
8610
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
8385
8611
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
8386
8612
  );
@@ -8395,7 +8621,7 @@ var HOME, BYPASS_IMMUNE_PATTERNS;
8395
8621
  var init_safety_checks = __esm({
8396
8622
  "src/runtime/safety-checks.ts"() {
8397
8623
  "use strict";
8398
- HOME = os10.homedir();
8624
+ HOME = os11.homedir();
8399
8625
  BYPASS_IMMUNE_PATTERNS = [
8400
8626
  {
8401
8627
  pattern: /\/\.git\/hooks\//,
@@ -8406,11 +8632,11 @@ var init_safety_checks = __esm({
8406
8632
  reason: "Git config can set hooks and command execution"
8407
8633
  },
8408
8634
  {
8409
- pattern: (p) => p.startsWith(path17.join(HOME, ".claude")),
8635
+ pattern: (p) => p.startsWith(path18.join(HOME, ".claude")),
8410
8636
  reason: "Claude configuration files are protected"
8411
8637
  },
8412
8638
  {
8413
- pattern: (p) => p.startsWith(path17.join(HOME, ".exe-os")),
8639
+ pattern: (p) => p.startsWith(path18.join(HOME, ".exe-os")),
8414
8640
  reason: "exe-os configuration files are protected"
8415
8641
  },
8416
8642
  {
@@ -8427,7 +8653,7 @@ var init_safety_checks = __esm({
8427
8653
  },
8428
8654
  {
8429
8655
  pattern: (p) => {
8430
- const name = path17.basename(p);
8656
+ const name = path18.basename(p);
8431
8657
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
8432
8658
  },
8433
8659
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -8454,7 +8680,7 @@ __export(file_read_exports, {
8454
8680
  FileReadTool: () => FileReadTool
8455
8681
  });
8456
8682
  import fs3 from "fs/promises";
8457
- import path18 from "path";
8683
+ import path19 from "path";
8458
8684
  import { z } from "zod";
8459
8685
  function isBinary(buf) {
8460
8686
  for (let i = 0; i < buf.length; i++) {
@@ -8490,7 +8716,7 @@ var init_file_read = __esm({
8490
8716
  return { behavior: "allow" };
8491
8717
  },
8492
8718
  async call(input, context) {
8493
- const filePath = path18.isAbsolute(input.file_path) ? input.file_path : path18.resolve(context.cwd, input.file_path);
8719
+ const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
8494
8720
  let stat;
8495
8721
  try {
8496
8722
  stat = await fs3.stat(filePath);
@@ -8530,7 +8756,7 @@ __export(glob_exports, {
8530
8756
  GlobTool: () => GlobTool
8531
8757
  });
8532
8758
  import fs4 from "fs/promises";
8533
- import path19 from "path";
8759
+ import path20 from "path";
8534
8760
  import { z as z2 } from "zod";
8535
8761
  async function walkDir(dir, maxDepth = 10) {
8536
8762
  const results = [];
@@ -8546,7 +8772,7 @@ async function walkDir(dir, maxDepth = 10) {
8546
8772
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
8547
8773
  continue;
8548
8774
  }
8549
- const fullPath = path19.join(current, entry.name);
8775
+ const fullPath = path20.join(current, entry.name);
8550
8776
  if (entry.isDirectory()) {
8551
8777
  await walk(fullPath, depth + 1);
8552
8778
  } else {
@@ -8580,11 +8806,11 @@ var init_glob = __esm({
8580
8806
  inputSchema: inputSchema2,
8581
8807
  isReadOnly: true,
8582
8808
  async call(input, context) {
8583
- const baseDir = input.path ? path19.isAbsolute(input.path) ? input.path : path19.resolve(context.cwd, input.path) : context.cwd;
8809
+ const baseDir = input.path ? path20.isAbsolute(input.path) ? input.path : path20.resolve(context.cwd, input.path) : context.cwd;
8584
8810
  try {
8585
8811
  const entries = await walkDir(baseDir);
8586
8812
  const matched = entries.filter(
8587
- (e) => simpleGlobMatch(path19.relative(baseDir, e.path), input.pattern)
8813
+ (e) => simpleGlobMatch(path20.relative(baseDir, e.path), input.pattern)
8588
8814
  );
8589
8815
  matched.sort((a, b) => b.mtime - a.mtime);
8590
8816
  if (matched.length === 0) {
@@ -8610,7 +8836,7 @@ __export(grep_exports, {
8610
8836
  });
8611
8837
  import { spawn as spawn2 } from "child_process";
8612
8838
  import fs5 from "fs/promises";
8613
- import path20 from "path";
8839
+ import path21 from "path";
8614
8840
  import { z as z3 } from "zod";
8615
8841
  function runRipgrep(input, searchPath, context) {
8616
8842
  return new Promise((resolve, reject) => {
@@ -8664,7 +8890,7 @@ async function nodeGrep(input, searchPath) {
8664
8890
  }
8665
8891
  for (const entry of entries) {
8666
8892
  if (entry.name === "node_modules" || entry.name === ".git") continue;
8667
- const fullPath = path20.join(dir, entry.name);
8893
+ const fullPath = path21.join(dir, entry.name);
8668
8894
  if (entry.isDirectory()) {
8669
8895
  await walk(fullPath);
8670
8896
  } else {
@@ -8710,7 +8936,7 @@ var init_grep = __esm({
8710
8936
  inputSchema: inputSchema3,
8711
8937
  isReadOnly: true,
8712
8938
  async call(input, context) {
8713
- const searchPath = input.path ? path20.isAbsolute(input.path) ? input.path : path20.resolve(context.cwd, input.path) : context.cwd;
8939
+ const searchPath = input.path ? path21.isAbsolute(input.path) ? input.path : path21.resolve(context.cwd, input.path) : context.cwd;
8714
8940
  try {
8715
8941
  const result = await runRipgrep(input, searchPath, context);
8716
8942
  return result;
@@ -8735,7 +8961,7 @@ __export(file_write_exports, {
8735
8961
  FileWriteTool: () => FileWriteTool
8736
8962
  });
8737
8963
  import fs6 from "fs/promises";
8738
- import path21 from "path";
8964
+ import path22 from "path";
8739
8965
  import { z as z4 } from "zod";
8740
8966
  var inputSchema4, FileWriteTool;
8741
8967
  var init_file_write = __esm({
@@ -8763,8 +8989,8 @@ var init_file_write = __esm({
8763
8989
  return { behavior: "allow" };
8764
8990
  },
8765
8991
  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);
8992
+ const filePath = path22.isAbsolute(input.file_path) ? input.file_path : path22.resolve(context.cwd, input.file_path);
8993
+ const dir = path22.dirname(filePath);
8768
8994
  await fs6.mkdir(dir, { recursive: true });
8769
8995
  await fs6.writeFile(filePath, input.content, "utf-8");
8770
8996
  return {
@@ -8782,7 +9008,7 @@ __export(file_edit_exports, {
8782
9008
  FileEditTool: () => FileEditTool
8783
9009
  });
8784
9010
  import fs7 from "fs/promises";
8785
- import path22 from "path";
9011
+ import path23 from "path";
8786
9012
  import { z as z5 } from "zod";
8787
9013
  function countOccurrences(haystack, needle) {
8788
9014
  let count = 0;
@@ -8823,7 +9049,7 @@ var init_file_edit = __esm({
8823
9049
  return { behavior: "allow" };
8824
9050
  },
8825
9051
  async call(input, context) {
8826
- const filePath = path22.isAbsolute(input.file_path) ? input.file_path : path22.resolve(context.cwd, input.file_path);
9052
+ const filePath = path23.isAbsolute(input.file_path) ? input.file_path : path23.resolve(context.cwd, input.file_path);
8827
9053
  let content;
8828
9054
  try {
8829
9055
  content = await fs7.readFile(filePath, "utf-8");
@@ -9430,6 +9656,133 @@ ${task.context}`,
9430
9656
  }
9431
9657
  });
9432
9658
 
9659
+ // src/lib/tui-data.ts
9660
+ var tui_data_exports = {};
9661
+ __export(tui_data_exports, {
9662
+ loadMemoryDashboard: () => loadMemoryDashboard,
9663
+ loadTaskList: () => loadTaskList,
9664
+ loadTeamMetrics: () => loadTeamMetrics,
9665
+ searchWikiMemoryRows: () => searchWikiMemoryRows
9666
+ });
9667
+ async function loadMemoryDashboard(limit) {
9668
+ const client = getClient();
9669
+ const [countResult, recentResult, agentResult] = await Promise.all([
9670
+ client.execute("SELECT COUNT(*) as cnt FROM memories"),
9671
+ client.execute({
9672
+ sql: "SELECT agent_id, tool_name, project_name, raw_text, timestamp FROM memories ORDER BY timestamp DESC LIMIT ?",
9673
+ args: [limit]
9674
+ }),
9675
+ client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id ORDER BY cnt DESC")
9676
+ ]);
9677
+ return {
9678
+ total: Number(countResult.rows[0]?.cnt ?? 0),
9679
+ recent: recentResult.rows.map((row) => ({
9680
+ agentId: String(row.agent_id ?? "unknown"),
9681
+ toolName: String(row.tool_name ?? ""),
9682
+ projectName: String(row.project_name ?? ""),
9683
+ rawText: String(row.raw_text ?? ""),
9684
+ timestamp: String(row.timestamp ?? "")
9685
+ })),
9686
+ byAgent: agentResult.rows.map((row) => ({
9687
+ agentId: String(row.agent_id ?? "unknown"),
9688
+ count: Number(row.cnt ?? 0)
9689
+ }))
9690
+ };
9691
+ }
9692
+ async function loadTeamMetrics(employeeNames) {
9693
+ const client = getClient();
9694
+ const memoryCounts = /* @__PURE__ */ new Map();
9695
+ const projectsByEmployee = /* @__PURE__ */ new Map();
9696
+ const currentTaskByEmployee = /* @__PURE__ */ new Map();
9697
+ const scope = sessionScopeFilter();
9698
+ const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
9699
+ for (const row of memResult.rows) {
9700
+ memoryCounts.set(String(row.agent_id), Number(row.cnt));
9701
+ }
9702
+ for (const employeeName of employeeNames) {
9703
+ const [projectResult, taskResult] = await Promise.all([
9704
+ client.execute({
9705
+ sql: `SELECT DISTINCT project_name,
9706
+ MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
9707
+ FROM tasks
9708
+ WHERE assigned_to = ? AND status IN ('open','in_progress','done')${scope.sql}
9709
+ GROUP BY project_name
9710
+ ORDER BY urgency ASC
9711
+ LIMIT 5`,
9712
+ args: [employeeName, ...scope.args]
9713
+ }),
9714
+ client.execute({
9715
+ sql: `SELECT title FROM tasks
9716
+ WHERE assigned_to = ? AND status = 'in_progress'${scope.sql}
9717
+ ORDER BY updated_at DESC
9718
+ LIMIT 1`,
9719
+ args: [employeeName, ...scope.args]
9720
+ })
9721
+ ]);
9722
+ const projects = projectResult.rows.map((row) => {
9723
+ const urgency = Number(row.urgency);
9724
+ return {
9725
+ name: String(row.project_name),
9726
+ status: urgency === 1 ? "active" : urgency === 2 ? "has_tasks" : "idle"
9727
+ };
9728
+ });
9729
+ if (projects.length > 0) projectsByEmployee.set(employeeName, projects);
9730
+ if (taskResult.rows.length > 0) {
9731
+ currentTaskByEmployee.set(employeeName, String(taskResult.rows[0].title));
9732
+ }
9733
+ }
9734
+ return { memoryCounts, projectsByEmployee, currentTaskByEmployee };
9735
+ }
9736
+ async function loadTaskList() {
9737
+ const client = getClient();
9738
+ const scope = sessionScopeFilter();
9739
+ const result = await client.execute({
9740
+ sql: `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
9741
+ FROM tasks
9742
+ WHERE 1=1${scope.sql}
9743
+ ORDER BY
9744
+ 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,
9745
+ CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
9746
+ created_at DESC`,
9747
+ args: [...scope.args]
9748
+ });
9749
+ return result.rows.map((row) => ({
9750
+ id: String(row.id ?? ""),
9751
+ title: String(row.title ?? ""),
9752
+ priority: String(row.priority ?? "p2").toUpperCase(),
9753
+ assignedTo: String(row.assigned_to ?? ""),
9754
+ assignedBy: String(row.assigned_by ?? ""),
9755
+ status: String(row.status ?? "open"),
9756
+ projectName: String(row.project_name ?? ""),
9757
+ createdAt: String(row.created_at ?? ""),
9758
+ result: String(row.result ?? "")
9759
+ }));
9760
+ }
9761
+ async function searchWikiMemoryRows(query) {
9762
+ const client = getClient();
9763
+ const result = await client.execute({
9764
+ sql: `SELECT id, agent_id, raw_text, timestamp, project_name
9765
+ FROM memories
9766
+ WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
9767
+ ORDER BY timestamp DESC LIMIT 20`,
9768
+ args: [`%${query}%`]
9769
+ });
9770
+ return result.rows.map((row) => ({
9771
+ id: String(row.id),
9772
+ agentId: String(row.agent_id),
9773
+ rawText: String(row.raw_text),
9774
+ timestamp: String(row.timestamp),
9775
+ projectName: String(row.project_name ?? "")
9776
+ }));
9777
+ }
9778
+ var init_tui_data = __esm({
9779
+ "src/lib/tui-data.ts"() {
9780
+ "use strict";
9781
+ init_database();
9782
+ init_task_scope();
9783
+ }
9784
+ });
9785
+
9433
9786
  // src/lib/message-queue.ts
9434
9787
  var DEFAULT_MAX_SIZE, DEFAULT_TTL_MS, MessageQueue;
9435
9788
  var init_message_queue = __esm({
@@ -9488,14 +9841,14 @@ __export(keychain_exports, {
9488
9841
  setMasterKey: () => setMasterKey
9489
9842
  });
9490
9843
  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";
9844
+ import { existsSync as existsSync16 } from "fs";
9845
+ import path26 from "path";
9846
+ import os12 from "os";
9494
9847
  function getKeyDir() {
9495
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path25.join(os11.homedir(), ".exe-os");
9848
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path26.join(os12.homedir(), ".exe-os");
9496
9849
  }
9497
9850
  function getKeyPath() {
9498
- return path25.join(getKeyDir(), "master.key");
9851
+ return path26.join(getKeyDir(), "master.key");
9499
9852
  }
9500
9853
  async function tryKeytar() {
9501
9854
  try {
@@ -9516,9 +9869,9 @@ async function getMasterKey() {
9516
9869
  }
9517
9870
  }
9518
9871
  const keyPath = getKeyPath();
9519
- if (!existsSync14(keyPath)) {
9872
+ if (!existsSync16(keyPath)) {
9520
9873
  process.stderr.write(
9521
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
9874
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
9522
9875
  `
9523
9876
  );
9524
9877
  return null;
@@ -9559,7 +9912,7 @@ async function deleteMasterKey() {
9559
9912
  }
9560
9913
  }
9561
9914
  const keyPath = getKeyPath();
9562
- if (existsSync14(keyPath)) {
9915
+ if (existsSync16(keyPath)) {
9563
9916
  await unlink(keyPath);
9564
9917
  }
9565
9918
  }
@@ -9608,16 +9961,16 @@ __export(ws_auth_exports, {
9608
9961
  deriveWsAuthToken: () => deriveWsAuthToken,
9609
9962
  hashAuthToken: () => hashAuthToken
9610
9963
  });
9611
- import crypto6 from "crypto";
9964
+ import crypto7 from "crypto";
9612
9965
  function deriveWsAuthToken(masterKey) {
9613
- return Buffer.from(crypto6.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
9966
+ return Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
9614
9967
  }
9615
9968
  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);
9969
+ const raw = Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
9970
+ return crypto7.createHash("sha256").update(raw).digest("hex").slice(0, 32);
9618
9971
  }
9619
9972
  function hashAuthToken(token) {
9620
- return crypto6.createHash("sha256").update(token).digest("hex");
9973
+ return crypto7.createHash("sha256").update(token).digest("hex");
9621
9974
  }
9622
9975
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
9623
9976
  var init_ws_auth = __esm({
@@ -9859,8 +10212,8 @@ __export(wiki_client_exports, {
9859
10212
  listDocuments: () => listDocuments,
9860
10213
  listWorkspaces: () => listWorkspaces
9861
10214
  });
9862
- async function wikiFetch(config, path27, method = "GET", body) {
9863
- const url = `${config.baseUrl}/api/v1${path27}`;
10215
+ async function wikiFetch(config, path28, method = "GET", body) {
10216
+ const url = `${config.baseUrl}/api/v1${path28}`;
9864
10217
  const headers = {
9865
10218
  Authorization: `Bearer ${config.apiKey}`,
9866
10219
  "Content-Type": "application/json"
@@ -9893,7 +10246,7 @@ async function wikiFetch(config, path27, method = "GET", body) {
9893
10246
  }
9894
10247
  }
9895
10248
  if (!response.ok) {
9896
- throw new Error(`Wiki API ${method} ${path27}: ${response.status} ${response.statusText}`);
10249
+ throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
9897
10250
  }
9898
10251
  return response.json();
9899
10252
  } finally {
@@ -10006,6 +10359,7 @@ var shard_manager_exports = {};
10006
10359
  __export(shard_manager_exports, {
10007
10360
  disposeShards: () => disposeShards,
10008
10361
  ensureShardSchema: () => ensureShardSchema,
10362
+ getOpenShardCount: () => getOpenShardCount,
10009
10363
  getReadyShardClient: () => getReadyShardClient,
10010
10364
  getShardClient: () => getShardClient,
10011
10365
  getShardsDir: () => getShardsDir,
@@ -10014,15 +10368,18 @@ __export(shard_manager_exports, {
10014
10368
  listShards: () => listShards,
10015
10369
  shardExists: () => shardExists
10016
10370
  });
10017
- import path26 from "path";
10018
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
10371
+ import path27 from "path";
10372
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
10019
10373
  import { createClient as createClient2 } from "@libsql/client";
10020
10374
  function initShardManager(encryptionKey) {
10021
10375
  _encryptionKey = encryptionKey;
10022
- if (!existsSync15(SHARDS_DIR)) {
10376
+ if (!existsSync17(SHARDS_DIR)) {
10023
10377
  mkdirSync7(SHARDS_DIR, { recursive: true });
10024
10378
  }
10025
10379
  _shardingEnabled = true;
10380
+ if (_evictionTimer) clearInterval(_evictionTimer);
10381
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
10382
+ _evictionTimer.unref();
10026
10383
  }
10027
10384
  function isShardingEnabled() {
10028
10385
  return _shardingEnabled;
@@ -10039,21 +10396,28 @@ function getShardClient(projectName) {
10039
10396
  throw new Error(`Invalid project name for shard: "${projectName}"`);
10040
10397
  }
10041
10398
  const cached = _shards.get(safeName);
10042
- if (cached) return cached;
10043
- const dbPath = path26.join(SHARDS_DIR, `${safeName}.db`);
10399
+ if (cached) {
10400
+ _shardLastAccess.set(safeName, Date.now());
10401
+ return cached;
10402
+ }
10403
+ while (_shards.size >= MAX_OPEN_SHARDS) {
10404
+ evictLRU();
10405
+ }
10406
+ const dbPath = path27.join(SHARDS_DIR, `${safeName}.db`);
10044
10407
  const client = createClient2({
10045
10408
  url: `file:${dbPath}`,
10046
10409
  encryptionKey: _encryptionKey
10047
10410
  });
10048
10411
  _shards.set(safeName, client);
10412
+ _shardLastAccess.set(safeName, Date.now());
10049
10413
  return client;
10050
10414
  }
10051
10415
  function shardExists(projectName) {
10052
10416
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
10053
- return existsSync15(path26.join(SHARDS_DIR, `${safeName}.db`));
10417
+ return existsSync17(path27.join(SHARDS_DIR, `${safeName}.db`));
10054
10418
  }
10055
10419
  function listShards() {
10056
- if (!existsSync15(SHARDS_DIR)) return [];
10420
+ if (!existsSync17(SHARDS_DIR)) return [];
10057
10421
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
10058
10422
  }
10059
10423
  async function ensureShardSchema(client) {
@@ -10105,6 +10469,8 @@ async function ensureShardSchema(client) {
10105
10469
  for (const col of [
10106
10470
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
10107
10471
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
10472
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
10473
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
10108
10474
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
10109
10475
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
10110
10476
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -10242,21 +10608,69 @@ async function getReadyShardClient(projectName) {
10242
10608
  await ensureShardSchema(client);
10243
10609
  return client;
10244
10610
  }
10611
+ function evictLRU() {
10612
+ let oldest = null;
10613
+ let oldestTime = Infinity;
10614
+ for (const [name, time] of _shardLastAccess) {
10615
+ if (time < oldestTime) {
10616
+ oldestTime = time;
10617
+ oldest = name;
10618
+ }
10619
+ }
10620
+ if (oldest) {
10621
+ const client = _shards.get(oldest);
10622
+ if (client) {
10623
+ client.close();
10624
+ }
10625
+ _shards.delete(oldest);
10626
+ _shardLastAccess.delete(oldest);
10627
+ }
10628
+ }
10629
+ function evictIdleShards() {
10630
+ const now = Date.now();
10631
+ const toEvict = [];
10632
+ for (const [name, lastAccess] of _shardLastAccess) {
10633
+ if (now - lastAccess > SHARD_IDLE_MS) {
10634
+ toEvict.push(name);
10635
+ }
10636
+ }
10637
+ for (const name of toEvict) {
10638
+ const client = _shards.get(name);
10639
+ if (client) {
10640
+ client.close();
10641
+ }
10642
+ _shards.delete(name);
10643
+ _shardLastAccess.delete(name);
10644
+ }
10645
+ }
10646
+ function getOpenShardCount() {
10647
+ return _shards.size;
10648
+ }
10245
10649
  function disposeShards() {
10650
+ if (_evictionTimer) {
10651
+ clearInterval(_evictionTimer);
10652
+ _evictionTimer = null;
10653
+ }
10246
10654
  for (const [, client] of _shards) {
10247
10655
  client.close();
10248
10656
  }
10249
10657
  _shards.clear();
10658
+ _shardLastAccess.clear();
10250
10659
  _shardingEnabled = false;
10251
10660
  _encryptionKey = null;
10252
10661
  }
10253
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
10662
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
10254
10663
  var init_shard_manager = __esm({
10255
10664
  "src/lib/shard-manager.ts"() {
10256
10665
  "use strict";
10257
10666
  init_config();
10258
- SHARDS_DIR = path26.join(EXE_AI_DIR, "shards");
10667
+ SHARDS_DIR = path27.join(EXE_AI_DIR, "shards");
10668
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
10669
+ MAX_OPEN_SHARDS = 10;
10670
+ EVICTION_INTERVAL_MS = 60 * 1e3;
10259
10671
  _shards = /* @__PURE__ */ new Map();
10672
+ _shardLastAccess = /* @__PURE__ */ new Map();
10673
+ _evictionTimer = null;
10260
10674
  _encryptionKey = null;
10261
10675
  _shardingEnabled = false;
10262
10676
  }
@@ -14827,8 +15241,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
14827
15241
  }
14828
15242
 
14829
15243
  // src/tui/ink/components/ErrorOverview.js
14830
- var cleanupPath = (path27) => {
14831
- return path27?.replace(`file://${cwd()}/`, "");
15244
+ var cleanupPath = (path28) => {
15245
+ return path28?.replace(`file://${cwd()}/`, "");
14832
15246
  };
14833
15247
  var stackUtils = new StackUtils({
14834
15248
  cwd: cwd(),
@@ -16760,11 +17174,11 @@ function Footer() {
16760
17174
  } catch {
16761
17175
  }
16762
17176
  try {
16763
- const { existsSync: existsSync16 } = await import("fs");
17177
+ const { existsSync: existsSync18 } = await import("fs");
16764
17178
  const { join } = await import("path");
16765
17179
  const home = process.env.HOME ?? "";
16766
17180
  const pidPath = join(home, ".exe-os", "exed.pid");
16767
- setDaemon(existsSync16(pidPath) ? "running" : "stopped");
17181
+ setDaemon(existsSync18(pidPath) ? "running" : "stopped");
16768
17182
  } catch {
16769
17183
  setDaemon("unknown");
16770
17184
  }
@@ -16846,7 +17260,7 @@ function Footer() {
16846
17260
  // src/tui/views/CommandCenter.tsx
16847
17261
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
16848
17262
  import TextInput from "ink-text-input";
16849
- import path23 from "path";
17263
+ import path24 from "path";
16850
17264
  import { homedir } from "os";
16851
17265
 
16852
17266
  // src/tui/components/StatusDot.tsx
@@ -17633,15 +18047,15 @@ function CommandCenterView({
17633
18047
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
17634
18048
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
17635
18049
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
17636
- const { readFileSync: readFileSync13, existsSync: existsSync16 } = await import("fs");
18050
+ const { readFileSync: readFileSync14, existsSync: existsSync18 } = await import("fs");
17637
18051
  const { join } = await import("path");
17638
18052
  const { homedir: homedir3 } = await import("os");
17639
18053
  const configPath = join(homedir3(), ".exe-os", "config.json");
17640
18054
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
17641
18055
  let providerConfigs = {};
17642
- if (existsSync16(configPath)) {
18056
+ if (existsSync18(configPath)) {
17643
18057
  try {
17644
- const raw = JSON.parse(readFileSync13(configPath, "utf8"));
18058
+ const raw = JSON.parse(readFileSync14(configPath, "utf8"));
17645
18059
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
17646
18060
  if (raw.providers && typeof raw.providers === "object") {
17647
18061
  providerConfigs = raw.providers;
@@ -17702,7 +18116,7 @@ function CommandCenterView({
17702
18116
  const markerDir = join(homedir3(), ".exe-os", "session-cache");
17703
18117
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
17704
18118
  for (const f of agentFiles) {
17705
- const data = JSON.parse(readFileSync13(join(markerDir, f), "utf8"));
18119
+ const data = JSON.parse(readFileSync14(join(markerDir, f), "utf8"));
17706
18120
  if (data.agentRole) {
17707
18121
  agentRole = data.agentRole;
17708
18122
  break;
@@ -17847,7 +18261,7 @@ function CommandCenterView({
17847
18261
  const demoEntries = DEMO_PROJECTS.map((p) => ({
17848
18262
  projectName: p.projectName,
17849
18263
  exeSession: p.exeSession,
17850
- projectDir: path23.join(homedir(), p.projectName),
18264
+ projectDir: path24.join(homedir(), p.projectName),
17851
18265
  employeeCount: p.employees.length,
17852
18266
  activeCount: p.employees.filter((e) => e.status === "active").length,
17853
18267
  memoryCount: p.employees.length * 4e3,
@@ -17885,7 +18299,7 @@ function CommandCenterView({
17885
18299
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
17886
18300
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
17887
18301
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17888
- const { existsSync: existsSync16 } = await import("fs");
18302
+ const { existsSync: existsSync18 } = await import("fs");
17889
18303
  const { join } = await import("path");
17890
18304
  const client = getClient2();
17891
18305
  if (!client) {
@@ -17956,7 +18370,7 @@ function CommandCenterView({
17956
18370
  }
17957
18371
  const memoryCount = memoryCounts.get(name) ?? 0;
17958
18372
  const openTaskCount = openTaskCounts.get(name) ?? 0;
17959
- const hasGit = projectDir ? existsSync16(join(projectDir, ".git")) : false;
18373
+ const hasGit = projectDir ? existsSync18(join(projectDir, ".git")) : false;
17960
18374
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
17961
18375
  projectList.push({
17962
18376
  projectName: name,
@@ -17981,7 +18395,7 @@ function CommandCenterView({
17981
18395
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
17982
18396
  try {
17983
18397
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
17984
- setHealth((h) => ({ ...h, daemon: existsSync16(pidPath) ? "running" : "stopped" }));
18398
+ setHealth((h) => ({ ...h, daemon: existsSync18(pidPath) ? "running" : "stopped" }));
17985
18399
  } catch {
17986
18400
  }
17987
18401
  const activityResult = await client.execute(
@@ -18196,7 +18610,7 @@ function ChatMessageRow({ msg }) {
18196
18610
 
18197
18611
  // src/tui/views/Sessions.tsx
18198
18612
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
18199
- import path24 from "path";
18613
+ import path25 from "path";
18200
18614
  import { homedir as homedir2 } from "os";
18201
18615
 
18202
18616
  // src/tui/components/TmuxPane.tsx
@@ -18500,7 +18914,7 @@ function SessionsView({
18500
18914
  if (demo) {
18501
18915
  setProjects(DEMO_PROJECTS.map((p) => ({
18502
18916
  ...p,
18503
- projectDir: path24.join(homedir2(), p.projectName),
18917
+ projectDir: path25.join(homedir2(), p.projectName),
18504
18918
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
18505
18919
  })));
18506
18920
  return;
@@ -18896,7 +19310,6 @@ function SessionsView({
18896
19310
 
18897
19311
  // src/tui/views/Tasks.tsx
18898
19312
  import React20, { useState as useState10, useEffect as useEffect12, useMemo as useMemo5 } from "react";
18899
- init_task_scope();
18900
19313
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
18901
19314
  var STATUS_FILTERS = ["all", "open", "in_progress", "done", "blocked", "needs_review"];
18902
19315
  var PRIORITY_COLORS = {
@@ -19029,37 +19442,22 @@ function TasksView({ onBack }) {
19029
19442
  const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
19030
19443
  return withTrace2("tui.tasks.loadTasks", async () => {
19031
19444
  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
- }
19445
+ const { loadTaskList: loadTaskList2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
19446
+ const tasks = await loadTaskList2();
19447
+ setAllTasks(
19448
+ tasks.map((task) => ({
19449
+ id: task.id,
19450
+ priority: task.priority,
19451
+ title: task.title,
19452
+ assignee: task.assignedTo,
19453
+ assignedBy: task.assignedBy,
19454
+ status: task.status,
19455
+ project: task.projectName,
19456
+ createdAt: task.createdAt,
19457
+ result: task.result
19458
+ }))
19459
+ );
19460
+ setDbError(null);
19063
19461
  } catch (err) {
19064
19462
  setDbError(err instanceof Error ? err.message : "Unknown error");
19065
19463
  } finally {
@@ -19397,12 +19795,12 @@ async function loadGatewayConfig() {
19397
19795
  state.running = false;
19398
19796
  }
19399
19797
  try {
19400
- const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
19798
+ const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
19401
19799
  const { join } = await import("path");
19402
19800
  const home = process.env.HOME ?? "";
19403
19801
  const configPath = join(home, ".exe-os", "gateway.json");
19404
- if (existsSync16(configPath)) {
19405
- const raw = JSON.parse(readFileSync13(configPath, "utf8"));
19802
+ if (existsSync18(configPath)) {
19803
+ const raw = JSON.parse(readFileSync14(configPath, "utf8"));
19406
19804
  state.port = raw.port ?? 3100;
19407
19805
  state.gatewayUrl = raw.gatewayUrl ?? "";
19408
19806
  if (raw.adapters) {
@@ -19802,7 +20200,6 @@ function getAgentStatus(agentId) {
19802
20200
  }
19803
20201
 
19804
20202
  // src/tui/views/Team.tsx
19805
- init_task_scope();
19806
20203
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
19807
20204
  var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
19808
20205
  function roleBadgeColor(role) {
@@ -19875,41 +20272,16 @@ function TeamView({ onBack, onViewSessions }) {
19875
20272
  let projectsByEmployee = /* @__PURE__ */ new Map();
19876
20273
  let currentTaskByEmployee = /* @__PURE__ */ new Map();
19877
20274
  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
- }
20275
+ const { loadTeamMetrics: loadTeamMetrics2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
20276
+ const teamMetrics = await loadTeamMetrics2(roster.map((emp) => emp.name));
20277
+ memoryCounts = teamMetrics.memoryCounts;
20278
+ projectsByEmployee = new Map(
20279
+ Array.from(teamMetrics.projectsByEmployee.entries()).map(([name, projects]) => [
20280
+ name,
20281
+ projects.filter((project) => !DEPRECATED_PROJECTS.has(project.name))
20282
+ ])
20283
+ );
20284
+ currentTaskByEmployee = teamMetrics.currentTaskByEmployee;
19913
20285
  } catch {
19914
20286
  }
19915
20287
  const teamData = roster.map((emp) => {
@@ -19928,12 +20300,12 @@ function TeamView({ onBack, onViewSessions }) {
19928
20300
  setMembers(teamData);
19929
20301
  setDbError(null);
19930
20302
  try {
19931
- const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
20303
+ const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
19932
20304
  const { join } = await import("path");
19933
20305
  const home = process.env.HOME ?? "";
19934
20306
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
19935
- if (existsSync16(gatewayConfig)) {
19936
- const raw = JSON.parse(readFileSync13(gatewayConfig, "utf8"));
20307
+ if (existsSync18(gatewayConfig)) {
20308
+ const raw = JSON.parse(readFileSync14(gatewayConfig, "utf8"));
19937
20309
  if (raw.agents && raw.agents.length > 0) {
19938
20310
  setExternals(raw.agents.map((a) => ({
19939
20311
  name: a.name,
@@ -20247,24 +20619,8 @@ function WikiView({ onBack }) {
20247
20619
  }
20248
20620
  setSearchLoading(true);
20249
20621
  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
- );
20622
+ const { searchWikiMemoryRows: searchWikiMemoryRows2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
20623
+ setSearchResults(await searchWikiMemoryRows2(query));
20268
20624
  } catch {
20269
20625
  setSearchResults([]);
20270
20626
  } finally {
@@ -20597,12 +20953,12 @@ function SettingsView({ onBack }) {
20597
20953
  }
20598
20954
  setProviders(providerList);
20599
20955
  try {
20600
- const { existsSync: existsSync16 } = await import("fs");
20956
+ const { existsSync: existsSync18 } = await import("fs");
20601
20957
  const { join } = await import("path");
20602
20958
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
20603
20959
  const cfg = await loadConfig2();
20604
20960
  const home = process.env.HOME ?? "";
20605
- const hasKey = existsSync16(join(home, ".exe-os", "master.key"));
20961
+ const hasKey = existsSync18(join(home, ".exe-os", "master.key"));
20606
20962
  if (cfg.cloud) {
20607
20963
  setCloud({
20608
20964
  configured: true,
@@ -20615,22 +20971,22 @@ function SettingsView({ onBack }) {
20615
20971
  const pidPath = join(home, ".exe-os", "exed.pid");
20616
20972
  let daemon = "unknown";
20617
20973
  try {
20618
- daemon = existsSync16(pidPath) ? "running" : "stopped";
20974
+ daemon = existsSync18(pidPath) ? "running" : "stopped";
20619
20975
  } catch {
20620
20976
  }
20621
20977
  let version = "unknown";
20622
20978
  try {
20623
- const { readFileSync: readFileSync13 } = await import("fs");
20624
- const { createRequire: createRequire2 } = await import("module");
20625
- const require2 = createRequire2(import.meta.url);
20979
+ const { readFileSync: readFileSync14 } = await import("fs");
20980
+ const { createRequire: createRequire3 } = await import("module");
20981
+ const require2 = createRequire3(import.meta.url);
20626
20982
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
20627
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
20983
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
20628
20984
  version = pkg.version;
20629
20985
  } catch {
20630
20986
  try {
20631
- const { readFileSync: readFileSync13 } = await import("fs");
20987
+ const { readFileSync: readFileSync14 } = await import("fs");
20632
20988
  const { join: joinPath } = await import("path");
20633
- const pkg = JSON.parse(readFileSync13(joinPath(process.cwd(), "package.json"), "utf8"));
20989
+ const pkg = JSON.parse(readFileSync14(joinPath(process.cwd(), "package.json"), "utf8"));
20634
20990
  version = pkg.version;
20635
20991
  } catch {
20636
20992
  }