@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
@@ -392,6 +392,44 @@ var init_db_retry = __esm({
392
392
  }
393
393
  });
394
394
 
395
+ // src/lib/secure-files.ts
396
+ import { chmodSync, existsSync, mkdirSync } from "fs";
397
+ import { chmod, mkdir } from "fs/promises";
398
+ async function ensurePrivateDir(dirPath) {
399
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
400
+ try {
401
+ await chmod(dirPath, PRIVATE_DIR_MODE);
402
+ } catch {
403
+ }
404
+ }
405
+ function ensurePrivateDirSync(dirPath) {
406
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
407
+ try {
408
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
409
+ } catch {
410
+ }
411
+ }
412
+ async function enforcePrivateFile(filePath) {
413
+ try {
414
+ await chmod(filePath, PRIVATE_FILE_MODE);
415
+ } catch {
416
+ }
417
+ }
418
+ function enforcePrivateFileSync(filePath) {
419
+ try {
420
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
421
+ } catch {
422
+ }
423
+ }
424
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
425
+ var init_secure_files = __esm({
426
+ "src/lib/secure-files.ts"() {
427
+ "use strict";
428
+ PRIVATE_DIR_MODE = 448;
429
+ PRIVATE_FILE_MODE = 384;
430
+ }
431
+ });
432
+
395
433
  // src/lib/config.ts
396
434
  var config_exports = {};
397
435
  __export(config_exports, {
@@ -408,8 +446,8 @@ __export(config_exports, {
408
446
  migrateConfig: () => migrateConfig,
409
447
  saveConfig: () => saveConfig
410
448
  });
411
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
412
- import { readFileSync, existsSync, renameSync } from "fs";
449
+ import { readFile, writeFile } from "fs/promises";
450
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
413
451
  import path from "path";
414
452
  import os from "os";
415
453
  function resolveDataDir() {
@@ -417,7 +455,7 @@ function resolveDataDir() {
417
455
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
418
456
  const newDir = path.join(os.homedir(), ".exe-os");
419
457
  const legacyDir = path.join(os.homedir(), ".exe-mem");
420
- if (!existsSync(newDir) && existsSync(legacyDir)) {
458
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
421
459
  try {
422
460
  renameSync(legacyDir, newDir);
423
461
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -480,9 +518,9 @@ function normalizeAutoUpdate(raw) {
480
518
  }
481
519
  async function loadConfig() {
482
520
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
483
- await mkdir(dir, { recursive: true });
521
+ await ensurePrivateDir(dir);
484
522
  const configPath = path.join(dir, "config.json");
485
- if (!existsSync(configPath)) {
523
+ if (!existsSync2(configPath)) {
486
524
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
487
525
  }
488
526
  const raw = await readFile(configPath, "utf-8");
@@ -495,6 +533,7 @@ async function loadConfig() {
495
533
  `);
496
534
  try {
497
535
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
536
+ await enforcePrivateFile(configPath);
498
537
  } catch {
499
538
  }
500
539
  }
@@ -513,7 +552,7 @@ async function loadConfig() {
513
552
  function loadConfigSync() {
514
553
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
515
554
  const configPath = path.join(dir, "config.json");
516
- if (!existsSync(configPath)) {
555
+ if (!existsSync2(configPath)) {
517
556
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
518
557
  }
519
558
  try {
@@ -531,12 +570,10 @@ function loadConfigSync() {
531
570
  }
532
571
  async function saveConfig(config2) {
533
572
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
534
- await mkdir(dir, { recursive: true });
573
+ await ensurePrivateDir(dir);
535
574
  const configPath = path.join(dir, "config.json");
536
575
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
537
- if (config2.cloud?.apiKey) {
538
- await chmod(configPath, 384);
539
- }
576
+ await enforcePrivateFile(configPath);
540
577
  }
541
578
  async function loadConfigFrom(configPath) {
542
579
  const raw = await readFile(configPath, "utf-8");
@@ -556,6 +593,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
556
593
  var init_config = __esm({
557
594
  "src/lib/config.ts"() {
558
595
  "use strict";
596
+ init_secure_files();
559
597
  EXE_AI_DIR = resolveDataDir();
560
598
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
561
599
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -672,10 +710,10 @@ __export(agent_config_exports, {
672
710
  saveAgentConfig: () => saveAgentConfig,
673
711
  setAgentRuntime: () => setAgentRuntime
674
712
  });
675
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
713
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
676
714
  import path2 from "path";
677
715
  function loadAgentConfig() {
678
- if (!existsSync2(AGENT_CONFIG_PATH)) return {};
716
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
679
717
  try {
680
718
  return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
681
719
  } catch {
@@ -684,8 +722,9 @@ function loadAgentConfig() {
684
722
  }
685
723
  function saveAgentConfig(config2) {
686
724
  const dir = path2.dirname(AGENT_CONFIG_PATH);
687
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
725
+ ensurePrivateDirSync(dir);
688
726
  writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
727
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
689
728
  }
690
729
  function getAgentRuntime(agentId) {
691
730
  const config2 = loadAgentConfig();
@@ -725,6 +764,7 @@ var init_agent_config = __esm({
725
764
  "use strict";
726
765
  init_config();
727
766
  init_runtime_table();
767
+ init_secure_files();
728
768
  AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
729
769
  KNOWN_RUNTIMES = {
730
770
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -772,7 +812,7 @@ __export(employees_exports, {
772
812
  validateEmployeeName: () => validateEmployeeName
773
813
  });
774
814
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
775
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
815
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
776
816
  import { execSync } from "child_process";
777
817
  import path3 from "path";
778
818
  import os2 from "os";
@@ -811,7 +851,7 @@ function validateEmployeeName(name) {
811
851
  return { valid: true };
812
852
  }
813
853
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
814
- if (!existsSync3(employeesPath)) {
854
+ if (!existsSync4(employeesPath)) {
815
855
  return [];
816
856
  }
817
857
  const raw = await readFile2(employeesPath, "utf-8");
@@ -826,7 +866,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
826
866
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
827
867
  }
828
868
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
829
- if (!existsSync3(employeesPath)) return [];
869
+ if (!existsSync4(employeesPath)) return [];
830
870
  try {
831
871
  return JSON.parse(readFileSync3(employeesPath, "utf-8"));
832
872
  } catch {
@@ -874,7 +914,7 @@ function appendToCoordinatorTeam(employee) {
874
914
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
875
915
  if (!coordinator) return;
876
916
  const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
877
- if (!existsSync3(idPath)) return;
917
+ if (!existsSync4(idPath)) return;
878
918
  const content = readFileSync3(idPath, "utf-8");
879
919
  if (content.includes(`**${capitalize(employee.name)}`)) return;
880
920
  const teamMatch = content.match(TEAM_SECTION_RE);
@@ -928,9 +968,9 @@ async function normalizeRosterCase(rosterPath) {
928
968
  const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
929
969
  const oldPath = path3.join(identityDir, `${oldName}.md`);
930
970
  const newPath = path3.join(identityDir, `${emp.name}.md`);
931
- if (existsSync3(oldPath) && !existsSync3(newPath)) {
971
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
932
972
  renameSync2(oldPath, newPath);
933
- } else if (existsSync3(oldPath) && oldPath !== newPath) {
973
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
934
974
  const content = readFileSync3(oldPath, "utf-8");
935
975
  writeFileSync2(newPath, content, "utf-8");
936
976
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
@@ -973,7 +1013,7 @@ function registerBinSymlinks(name) {
973
1013
  for (const suffix of ["", "-opencode"]) {
974
1014
  const linkName = `${name}${suffix}`;
975
1015
  const linkPath = path3.join(binDir, linkName);
976
- if (existsSync3(linkPath)) {
1016
+ if (existsSync4(linkPath)) {
977
1017
  skipped.push(linkName);
978
1018
  continue;
979
1019
  }
@@ -1926,6 +1966,7 @@ async function ensureSchema() {
1926
1966
  project TEXT NOT NULL,
1927
1967
  summary TEXT NOT NULL,
1928
1968
  task_file TEXT,
1969
+ session_scope TEXT,
1929
1970
  read INTEGER NOT NULL DEFAULT 0,
1930
1971
  created_at TEXT NOT NULL
1931
1972
  );
@@ -1934,7 +1975,7 @@ async function ensureSchema() {
1934
1975
  ON notifications(read);
1935
1976
 
1936
1977
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1937
- ON notifications(agent_id);
1978
+ ON notifications(agent_id, session_scope);
1938
1979
 
1939
1980
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1940
1981
  ON notifications(task_file);
@@ -1972,6 +2013,7 @@ async function ensureSchema() {
1972
2013
  target_agent TEXT NOT NULL,
1973
2014
  target_project TEXT,
1974
2015
  target_device TEXT NOT NULL DEFAULT 'local',
2016
+ session_scope TEXT,
1975
2017
  content TEXT NOT NULL,
1976
2018
  priority TEXT DEFAULT 'normal',
1977
2019
  status TEXT DEFAULT 'pending',
@@ -1985,10 +2027,31 @@ async function ensureSchema() {
1985
2027
  );
1986
2028
 
1987
2029
  CREATE INDEX IF NOT EXISTS idx_messages_target
1988
- ON messages(target_agent, status);
2030
+ ON messages(target_agent, session_scope, status);
1989
2031
 
1990
2032
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1991
- ON messages(target_agent, from_agent, server_seq);
2033
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2034
+ `);
2035
+ try {
2036
+ await client.execute({
2037
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2038
+ args: []
2039
+ });
2040
+ } catch {
2041
+ }
2042
+ try {
2043
+ await client.execute({
2044
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2045
+ args: []
2046
+ });
2047
+ } catch {
2048
+ }
2049
+ await client.executeMultiple(`
2050
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2051
+ ON notifications(agent_id, session_scope, read, created_at);
2052
+
2053
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2054
+ ON messages(target_agent, session_scope, status, created_at);
1992
2055
  `);
1993
2056
  try {
1994
2057
  await client.execute({
@@ -2572,6 +2635,13 @@ async function ensureSchema() {
2572
2635
  } catch {
2573
2636
  }
2574
2637
  }
2638
+ try {
2639
+ await client.execute({
2640
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2641
+ args: []
2642
+ });
2643
+ } catch {
2644
+ }
2575
2645
  }
2576
2646
  async function disposeDatabase() {
2577
2647
  if (_walCheckpointTimer) {
@@ -2618,13 +2688,50 @@ var init_memory = __esm({
2618
2688
  }
2619
2689
  });
2620
2690
 
2691
+ // src/lib/daemon-auth.ts
2692
+ import crypto from "crypto";
2693
+ import path5 from "path";
2694
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2695
+ function normalizeToken(token) {
2696
+ if (!token) return null;
2697
+ const trimmed = token.trim();
2698
+ return trimmed.length > 0 ? trimmed : null;
2699
+ }
2700
+ function readDaemonToken() {
2701
+ try {
2702
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
2703
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
2704
+ } catch {
2705
+ return null;
2706
+ }
2707
+ }
2708
+ function ensureDaemonToken(seed) {
2709
+ const existing = readDaemonToken();
2710
+ if (existing) return existing;
2711
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
2712
+ ensurePrivateDirSync(EXE_AI_DIR);
2713
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
2714
+ `, "utf8");
2715
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
2716
+ return token;
2717
+ }
2718
+ var DAEMON_TOKEN_PATH;
2719
+ var init_daemon_auth = __esm({
2720
+ "src/lib/daemon-auth.ts"() {
2721
+ "use strict";
2722
+ init_config();
2723
+ init_secure_files();
2724
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
2725
+ }
2726
+ });
2727
+
2621
2728
  // src/lib/exe-daemon-client.ts
2622
2729
  import net from "net";
2623
2730
  import os4 from "os";
2624
2731
  import { spawn } from "child_process";
2625
2732
  import { randomUUID } from "crypto";
2626
- import { existsSync as existsSync4, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
2627
- import path5 from "path";
2733
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
2734
+ import path6 from "path";
2628
2735
  import { fileURLToPath } from "url";
2629
2736
  function handleData(chunk) {
2630
2737
  _buffer += chunk.toString();
@@ -2652,9 +2759,9 @@ function handleData(chunk) {
2652
2759
  }
2653
2760
  }
2654
2761
  function cleanupStaleFiles() {
2655
- if (existsSync4(PID_PATH)) {
2762
+ if (existsSync6(PID_PATH)) {
2656
2763
  try {
2657
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2764
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2658
2765
  if (pid > 0) {
2659
2766
  try {
2660
2767
  process.kill(pid, 0);
@@ -2675,11 +2782,11 @@ function cleanupStaleFiles() {
2675
2782
  }
2676
2783
  }
2677
2784
  function findPackageRoot() {
2678
- let dir = path5.dirname(fileURLToPath(import.meta.url));
2679
- const { root } = path5.parse(dir);
2785
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2786
+ const { root } = path6.parse(dir);
2680
2787
  while (dir !== root) {
2681
- if (existsSync4(path5.join(dir, "package.json"))) return dir;
2682
- dir = path5.dirname(dir);
2788
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2789
+ dir = path6.dirname(dir);
2683
2790
  }
2684
2791
  return null;
2685
2792
  }
@@ -2705,16 +2812,17 @@ function spawnDaemon() {
2705
2812
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2706
2813
  return;
2707
2814
  }
2708
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2709
- if (!existsSync4(daemonPath)) {
2815
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2816
+ if (!existsSync6(daemonPath)) {
2710
2817
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2711
2818
  `);
2712
2819
  return;
2713
2820
  }
2714
2821
  const resolvedPath = daemonPath;
2822
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
2715
2823
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2716
2824
  `);
2717
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2825
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
2718
2826
  let stderrFd = "ignore";
2719
2827
  try {
2720
2828
  stderrFd = openSync(logPath, "a");
@@ -2732,7 +2840,8 @@ function spawnDaemon() {
2732
2840
  TMUX_PANE: void 0,
2733
2841
  // Prevents resolveExeSession() from scoping to one session
2734
2842
  EXE_DAEMON_SOCK: SOCKET_PATH,
2735
- EXE_DAEMON_PID: PID_PATH
2843
+ EXE_DAEMON_PID: PID_PATH,
2844
+ [DAEMON_TOKEN_ENV]: daemonToken
2736
2845
  }
2737
2846
  });
2738
2847
  child.unref();
@@ -2842,13 +2951,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2842
2951
  return;
2843
2952
  }
2844
2953
  const id = randomUUID();
2954
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2845
2955
  const timer = setTimeout(() => {
2846
2956
  _pending.delete(id);
2847
2957
  resolve({ error: "Request timeout" });
2848
2958
  }, timeoutMs);
2849
2959
  _pending.set(id, { resolve, timer });
2850
2960
  try {
2851
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2961
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
2852
2962
  } catch {
2853
2963
  clearTimeout(timer);
2854
2964
  _pending.delete(id);
@@ -2877,9 +2987,9 @@ function killAndRespawnDaemon() {
2877
2987
  }
2878
2988
  try {
2879
2989
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2880
- if (existsSync4(PID_PATH)) {
2990
+ if (existsSync6(PID_PATH)) {
2881
2991
  try {
2882
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2992
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2883
2993
  if (pid > 0) {
2884
2994
  try {
2885
2995
  process.kill(pid, "SIGKILL");
@@ -2996,17 +3106,19 @@ function disconnectClient() {
2996
3106
  entry.resolve({ error: "Client disconnected" });
2997
3107
  }
2998
3108
  }
2999
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
3109
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
3000
3110
  var init_exe_daemon_client = __esm({
3001
3111
  "src/lib/exe-daemon-client.ts"() {
3002
3112
  "use strict";
3003
3113
  init_config();
3004
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
3005
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
3006
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
3114
+ init_daemon_auth();
3115
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3116
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3117
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
3007
3118
  SPAWN_LOCK_STALE_MS = 3e4;
3008
3119
  CONNECT_TIMEOUT_MS = 15e3;
3009
3120
  REQUEST_TIMEOUT_MS = 3e4;
3121
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
3010
3122
  _socket = null;
3011
3123
  _connected = false;
3012
3124
  _buffer = "";
@@ -3058,10 +3170,10 @@ async function disposeEmbedder() {
3058
3170
  async function embedDirect(text) {
3059
3171
  const llamaCpp = await import("node-llama-cpp");
3060
3172
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3061
- const { existsSync: existsSync17 } = await import("fs");
3062
- const path22 = await import("path");
3063
- const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3064
- if (!existsSync17(modelPath)) {
3173
+ const { existsSync: existsSync19 } = await import("fs");
3174
+ const path23 = await import("path");
3175
+ const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3176
+ if (!existsSync19(modelPath)) {
3065
3177
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3066
3178
  }
3067
3179
  const llama = await llamaCpp.getLlama();
@@ -3091,14 +3203,14 @@ var init_embedder = __esm({
3091
3203
 
3092
3204
  // src/lib/keychain.ts
3093
3205
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3094
- import { existsSync as existsSync5 } from "fs";
3095
- import path6 from "path";
3206
+ import { existsSync as existsSync7 } from "fs";
3207
+ import path7 from "path";
3096
3208
  import os5 from "os";
3097
3209
  function getKeyDir() {
3098
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3210
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3099
3211
  }
3100
3212
  function getKeyPath() {
3101
- return path6.join(getKeyDir(), "master.key");
3213
+ return path7.join(getKeyDir(), "master.key");
3102
3214
  }
3103
3215
  async function tryKeytar() {
3104
3216
  try {
@@ -3119,7 +3231,7 @@ async function getMasterKey() {
3119
3231
  }
3120
3232
  }
3121
3233
  const keyPath = getKeyPath();
3122
- if (!existsSync5(keyPath)) {
3234
+ if (!existsSync7(keyPath)) {
3123
3235
  process.stderr.write(
3124
3236
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3125
3237
  `
@@ -3151,6 +3263,7 @@ var shard_manager_exports = {};
3151
3263
  __export(shard_manager_exports, {
3152
3264
  disposeShards: () => disposeShards,
3153
3265
  ensureShardSchema: () => ensureShardSchema,
3266
+ getOpenShardCount: () => getOpenShardCount,
3154
3267
  getReadyShardClient: () => getReadyShardClient,
3155
3268
  getShardClient: () => getShardClient,
3156
3269
  getShardsDir: () => getShardsDir,
@@ -3159,15 +3272,18 @@ __export(shard_manager_exports, {
3159
3272
  listShards: () => listShards,
3160
3273
  shardExists: () => shardExists
3161
3274
  });
3162
- import path7 from "path";
3163
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
3275
+ import path8 from "path";
3276
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
3164
3277
  import { createClient as createClient2 } from "@libsql/client";
3165
3278
  function initShardManager(encryptionKey) {
3166
3279
  _encryptionKey = encryptionKey;
3167
- if (!existsSync6(SHARDS_DIR)) {
3280
+ if (!existsSync8(SHARDS_DIR)) {
3168
3281
  mkdirSync2(SHARDS_DIR, { recursive: true });
3169
3282
  }
3170
3283
  _shardingEnabled = true;
3284
+ if (_evictionTimer) clearInterval(_evictionTimer);
3285
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3286
+ _evictionTimer.unref();
3171
3287
  }
3172
3288
  function isShardingEnabled() {
3173
3289
  return _shardingEnabled;
@@ -3184,21 +3300,28 @@ function getShardClient(projectName) {
3184
3300
  throw new Error(`Invalid project name for shard: "${projectName}"`);
3185
3301
  }
3186
3302
  const cached = _shards.get(safeName);
3187
- if (cached) return cached;
3188
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3303
+ if (cached) {
3304
+ _shardLastAccess.set(safeName, Date.now());
3305
+ return cached;
3306
+ }
3307
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3308
+ evictLRU();
3309
+ }
3310
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
3189
3311
  const client = createClient2({
3190
3312
  url: `file:${dbPath}`,
3191
3313
  encryptionKey: _encryptionKey
3192
3314
  });
3193
3315
  _shards.set(safeName, client);
3316
+ _shardLastAccess.set(safeName, Date.now());
3194
3317
  return client;
3195
3318
  }
3196
3319
  function shardExists(projectName) {
3197
3320
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
3198
- return existsSync6(path7.join(SHARDS_DIR, `${safeName}.db`));
3321
+ return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
3199
3322
  }
3200
3323
  function listShards() {
3201
- if (!existsSync6(SHARDS_DIR)) return [];
3324
+ if (!existsSync8(SHARDS_DIR)) return [];
3202
3325
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3203
3326
  }
3204
3327
  async function ensureShardSchema(client) {
@@ -3250,6 +3373,8 @@ async function ensureShardSchema(client) {
3250
3373
  for (const col of [
3251
3374
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
3252
3375
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3376
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3377
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3253
3378
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3254
3379
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3255
3380
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -3387,21 +3512,69 @@ async function getReadyShardClient(projectName) {
3387
3512
  await ensureShardSchema(client);
3388
3513
  return client;
3389
3514
  }
3515
+ function evictLRU() {
3516
+ let oldest = null;
3517
+ let oldestTime = Infinity;
3518
+ for (const [name, time] of _shardLastAccess) {
3519
+ if (time < oldestTime) {
3520
+ oldestTime = time;
3521
+ oldest = name;
3522
+ }
3523
+ }
3524
+ if (oldest) {
3525
+ const client = _shards.get(oldest);
3526
+ if (client) {
3527
+ client.close();
3528
+ }
3529
+ _shards.delete(oldest);
3530
+ _shardLastAccess.delete(oldest);
3531
+ }
3532
+ }
3533
+ function evictIdleShards() {
3534
+ const now = Date.now();
3535
+ const toEvict = [];
3536
+ for (const [name, lastAccess] of _shardLastAccess) {
3537
+ if (now - lastAccess > SHARD_IDLE_MS) {
3538
+ toEvict.push(name);
3539
+ }
3540
+ }
3541
+ for (const name of toEvict) {
3542
+ const client = _shards.get(name);
3543
+ if (client) {
3544
+ client.close();
3545
+ }
3546
+ _shards.delete(name);
3547
+ _shardLastAccess.delete(name);
3548
+ }
3549
+ }
3550
+ function getOpenShardCount() {
3551
+ return _shards.size;
3552
+ }
3390
3553
  function disposeShards() {
3554
+ if (_evictionTimer) {
3555
+ clearInterval(_evictionTimer);
3556
+ _evictionTimer = null;
3557
+ }
3391
3558
  for (const [, client] of _shards) {
3392
3559
  client.close();
3393
3560
  }
3394
3561
  _shards.clear();
3562
+ _shardLastAccess.clear();
3395
3563
  _shardingEnabled = false;
3396
3564
  _encryptionKey = null;
3397
3565
  }
3398
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3566
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3399
3567
  var init_shard_manager = __esm({
3400
3568
  "src/lib/shard-manager.ts"() {
3401
3569
  "use strict";
3402
3570
  init_config();
3403
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3571
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
3572
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3573
+ MAX_OPEN_SHARDS = 10;
3574
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3404
3575
  _shards = /* @__PURE__ */ new Map();
3576
+ _shardLastAccess = /* @__PURE__ */ new Map();
3577
+ _evictionTimer = null;
3405
3578
  _encryptionKey = null;
3406
3579
  _shardingEnabled = false;
3407
3580
  }
@@ -4173,8 +4346,8 @@ __export(wiki_client_exports, {
4173
4346
  listDocuments: () => listDocuments,
4174
4347
  listWorkspaces: () => listWorkspaces
4175
4348
  });
4176
- async function wikiFetch(config2, path22, method = "GET", body) {
4177
- const url = `${config2.baseUrl}/api/v1${path22}`;
4349
+ async function wikiFetch(config2, path23, method = "GET", body) {
4350
+ const url = `${config2.baseUrl}/api/v1${path23}`;
4178
4351
  const headers = {
4179
4352
  Authorization: `Bearer ${config2.apiKey}`,
4180
4353
  "Content-Type": "application/json"
@@ -4207,7 +4380,7 @@ async function wikiFetch(config2, path22, method = "GET", body) {
4207
4380
  }
4208
4381
  }
4209
4382
  if (!response.ok) {
4210
- throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
4383
+ throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
4211
4384
  }
4212
4385
  return response.json();
4213
4386
  } finally {
@@ -4500,13 +4673,13 @@ __export(graph_rag_exports, {
4500
4673
  resolveAlias: () => resolveAlias,
4501
4674
  storeExtraction: () => storeExtraction
4502
4675
  });
4503
- import crypto from "crypto";
4676
+ import crypto2 from "crypto";
4504
4677
  function normalizeEntityName(name) {
4505
4678
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
4506
4679
  }
4507
4680
  function entityId(name, type) {
4508
4681
  const normalized = normalizeEntityName(name);
4509
- return crypto.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
4682
+ return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
4510
4683
  }
4511
4684
  async function resolveAlias(client, name) {
4512
4685
  const normalized = normalizeEntityName(name);
@@ -4756,7 +4929,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
4756
4929
  const targetAlias = await resolveAlias(client, r.target);
4757
4930
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
4758
4931
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
4759
- const relId = crypto.randomUUID().slice(0, 16);
4932
+ const relId = crypto2.randomUUID().slice(0, 16);
4760
4933
  try {
4761
4934
  await client.execute({
4762
4935
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -4819,7 +4992,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
4819
4992
  }
4820
4993
  }
4821
4994
  for (const h of extraction.hyperedges) {
4822
- const hId = crypto.randomUUID().slice(0, 16);
4995
+ const hId = crypto2.randomUUID().slice(0, 16);
4823
4996
  try {
4824
4997
  await client.execute({
4825
4998
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -4883,7 +5056,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
4883
5056
  totalEntities += stored.entitiesStored;
4884
5057
  totalRelationships += stored.relationshipsStored;
4885
5058
  }
4886
- const contentHash = crypto.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
5059
+ const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
4887
5060
  await client.execute({
4888
5061
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
4889
5062
  args: [contentHash, contentHash, memoryId]
@@ -5236,9 +5409,12 @@ __export(license_exports, {
5236
5409
  stopLicenseRevalidation: () => stopLicenseRevalidation,
5237
5410
  validateLicense: () => validateLicense
5238
5411
  });
5239
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
5412
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
5240
5413
  import { randomUUID as randomUUID3 } from "crypto";
5241
- import path8 from "path";
5414
+ import { createRequire as createRequire2 } from "module";
5415
+ import { pathToFileURL as pathToFileURL2 } from "url";
5416
+ import os6 from "os";
5417
+ import path9 from "path";
5242
5418
  import { jwtVerify, importSPKI } from "jose";
5243
5419
  async function fetchRetry(url, init) {
5244
5420
  try {
@@ -5249,37 +5425,37 @@ async function fetchRetry(url, init) {
5249
5425
  }
5250
5426
  }
5251
5427
  function loadDeviceId() {
5252
- const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
5428
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
5253
5429
  try {
5254
- if (existsSync7(deviceJsonPath)) {
5255
- const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
5430
+ if (existsSync9(deviceJsonPath)) {
5431
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
5256
5432
  if (data.deviceId) return data.deviceId;
5257
5433
  }
5258
5434
  } catch {
5259
5435
  }
5260
5436
  try {
5261
- if (existsSync7(DEVICE_ID_PATH)) {
5262
- const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
5437
+ if (existsSync9(DEVICE_ID_PATH)) {
5438
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
5263
5439
  if (id2) return id2;
5264
5440
  }
5265
5441
  } catch {
5266
5442
  }
5267
5443
  const id = randomUUID3();
5268
5444
  mkdirSync3(EXE_AI_DIR, { recursive: true });
5269
- writeFileSync3(DEVICE_ID_PATH, id, "utf8");
5445
+ writeFileSync4(DEVICE_ID_PATH, id, "utf8");
5270
5446
  return id;
5271
5447
  }
5272
5448
  function loadLicense() {
5273
5449
  try {
5274
- if (!existsSync7(LICENSE_PATH)) return null;
5275
- return readFileSync5(LICENSE_PATH, "utf8").trim();
5450
+ if (!existsSync9(LICENSE_PATH)) return null;
5451
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
5276
5452
  } catch {
5277
5453
  return null;
5278
5454
  }
5279
5455
  }
5280
5456
  function saveLicense(apiKey) {
5281
5457
  mkdirSync3(EXE_AI_DIR, { recursive: true });
5282
- writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
5458
+ writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
5283
5459
  }
5284
5460
  async function verifyLicenseJwt(token) {
5285
5461
  try {
@@ -5305,8 +5481,8 @@ async function verifyLicenseJwt(token) {
5305
5481
  }
5306
5482
  async function getCachedLicense() {
5307
5483
  try {
5308
- if (!existsSync7(CACHE_PATH)) return null;
5309
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
5484
+ if (!existsSync9(CACHE_PATH)) return null;
5485
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
5310
5486
  if (!raw.token || typeof raw.token !== "string") return null;
5311
5487
  return await verifyLicenseJwt(raw.token);
5312
5488
  } catch {
@@ -5315,8 +5491,8 @@ async function getCachedLicense() {
5315
5491
  }
5316
5492
  function readCachedToken() {
5317
5493
  try {
5318
- if (!existsSync7(CACHE_PATH)) return null;
5319
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
5494
+ if (!existsSync9(CACHE_PATH)) return null;
5495
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
5320
5496
  return typeof raw.token === "string" ? raw.token : null;
5321
5497
  } catch {
5322
5498
  return null;
@@ -5350,57 +5526,131 @@ function getRawCachedPlan() {
5350
5526
  }
5351
5527
  function cacheResponse(token) {
5352
5528
  try {
5353
- writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
5529
+ writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
5354
5530
  } catch {
5355
5531
  }
5356
5532
  }
5357
- async function validateLicense(apiKey, deviceId) {
5358
- const did = deviceId ?? loadDeviceId();
5533
+ function loadPrismaForLicense() {
5534
+ if (_prismaFailed) return null;
5535
+ const dbUrl = process.env.DATABASE_URL;
5536
+ if (!dbUrl) {
5537
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
5538
+ if (!existsSync9(path9.join(exeDbRoot, "package.json"))) {
5539
+ _prismaFailed = true;
5540
+ return null;
5541
+ }
5542
+ }
5543
+ if (!_prismaPromise) {
5544
+ _prismaPromise = (async () => {
5545
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
5546
+ if (explicitPath) {
5547
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
5548
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
5549
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
5550
+ return new Ctor2();
5551
+ }
5552
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
5553
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
5554
+ const entry = req.resolve("@prisma/client");
5555
+ const mod = await import(pathToFileURL2(entry).href);
5556
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
5557
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
5558
+ return new Ctor();
5559
+ })().catch((err) => {
5560
+ _prismaFailed = true;
5561
+ _prismaPromise = null;
5562
+ throw err;
5563
+ });
5564
+ }
5565
+ return _prismaPromise;
5566
+ }
5567
+ async function validateViaPostgres(apiKey) {
5568
+ const loader = loadPrismaForLicense();
5569
+ if (!loader) return null;
5570
+ try {
5571
+ const prisma = await loader;
5572
+ const rows = await prisma.$queryRawUnsafe(
5573
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
5574
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
5575
+ apiKey
5576
+ );
5577
+ if (!rows || rows.length === 0) return null;
5578
+ const row = rows[0];
5579
+ if (row.status !== "active") return null;
5580
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
5581
+ const plan = row.plan;
5582
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
5583
+ return {
5584
+ valid: true,
5585
+ plan,
5586
+ email: row.email,
5587
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
5588
+ deviceLimit: row.device_limit ?? limits.devices,
5589
+ employeeLimit: row.employee_limit ?? limits.employees,
5590
+ memoryLimit: row.memory_limit ?? limits.memories
5591
+ };
5592
+ } catch {
5593
+ return null;
5594
+ }
5595
+ }
5596
+ async function validateViaCFWorker(apiKey, deviceId) {
5359
5597
  try {
5360
5598
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
5361
5599
  method: "POST",
5362
5600
  headers: { "Content-Type": "application/json" },
5363
- body: JSON.stringify({ apiKey, deviceId: did }),
5601
+ body: JSON.stringify({ apiKey, deviceId }),
5364
5602
  signal: AbortSignal.timeout(1e4)
5365
5603
  });
5366
- if (res.ok) {
5367
- const data = await res.json();
5368
- if (data.error === "device_limit_exceeded") {
5369
- const cached2 = await getCachedLicense();
5370
- if (cached2) return cached2;
5371
- const raw2 = getRawCachedPlan();
5372
- if (raw2) return { ...raw2, valid: false };
5373
- return { ...FREE_LICENSE, valid: false, plan: "free" };
5374
- }
5375
- if (data.token) {
5376
- cacheResponse(data.token);
5377
- const verified = await verifyLicenseJwt(data.token);
5378
- if (verified) return verified;
5379
- }
5380
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
5381
- return {
5382
- valid: data.valid,
5383
- plan: data.plan,
5384
- email: data.email,
5385
- expiresAt: data.expiresAt,
5386
- deviceLimit: limits.devices,
5387
- employeeLimit: limits.employees,
5388
- memoryLimit: limits.memories
5389
- };
5604
+ if (!res.ok) return null;
5605
+ const data = await res.json();
5606
+ if (data.error === "device_limit_exceeded") return null;
5607
+ if (!data.valid) return null;
5608
+ if (data.token) {
5609
+ cacheResponse(data.token);
5610
+ const verified = await verifyLicenseJwt(data.token);
5611
+ if (verified) return verified;
5390
5612
  }
5391
- const cached = await getCachedLicense();
5392
- if (cached) return cached;
5393
- const raw = getRawCachedPlan();
5394
- if (raw) return raw;
5395
- return { ...FREE_LICENSE, valid: false, plan: "free" };
5613
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
5614
+ return {
5615
+ valid: data.valid,
5616
+ plan: data.plan,
5617
+ email: data.email,
5618
+ expiresAt: data.expiresAt,
5619
+ deviceLimit: limits.devices,
5620
+ employeeLimit: limits.employees,
5621
+ memoryLimit: limits.memories
5622
+ };
5396
5623
  } catch {
5397
- const cached = await getCachedLicense();
5398
- if (cached) return cached;
5399
- const rawFallback = getRawCachedPlan();
5400
- if (rawFallback) return rawFallback;
5401
- return { ...FREE_LICENSE, valid: false, error: "offline" };
5624
+ return null;
5402
5625
  }
5403
5626
  }
5627
+ async function validateLicense(apiKey, deviceId) {
5628
+ const did = deviceId ?? loadDeviceId();
5629
+ const pgResult = await validateViaPostgres(apiKey);
5630
+ if (pgResult) {
5631
+ try {
5632
+ writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
5633
+ } catch {
5634
+ }
5635
+ return pgResult;
5636
+ }
5637
+ const cfResult = await validateViaCFWorker(apiKey, did);
5638
+ if (cfResult) return cfResult;
5639
+ const cached = await getCachedLicense();
5640
+ if (cached) return cached;
5641
+ try {
5642
+ if (existsSync9(CACHE_PATH)) {
5643
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
5644
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
5645
+ return raw.pgLicense;
5646
+ }
5647
+ }
5648
+ } catch {
5649
+ }
5650
+ const rawFallback = getRawCachedPlan();
5651
+ if (rawFallback) return rawFallback;
5652
+ return { ...FREE_LICENSE, valid: false };
5653
+ }
5404
5654
  function getCacheAgeMs() {
5405
5655
  try {
5406
5656
  const { statSync: statSync2 } = __require("fs");
@@ -5414,9 +5664,9 @@ async function checkLicense() {
5414
5664
  let key = loadLicense();
5415
5665
  if (!key) {
5416
5666
  try {
5417
- const configPath = path8.join(EXE_AI_DIR, "config.json");
5418
- if (existsSync7(configPath)) {
5419
- const raw = JSON.parse(readFileSync5(configPath, "utf8"));
5667
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
5668
+ if (existsSync9(configPath)) {
5669
+ const raw = JSON.parse(readFileSync6(configPath, "utf8"));
5420
5670
  const cloud = raw.cloud;
5421
5671
  if (cloud?.apiKey) {
5422
5672
  key = cloud.apiKey;
@@ -5570,14 +5820,14 @@ function stopLicenseRevalidation() {
5570
5820
  _revalTimer = null;
5571
5821
  }
5572
5822
  }
5573
- 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;
5823
+ 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;
5574
5824
  var init_license = __esm({
5575
5825
  "src/lib/license.ts"() {
5576
5826
  "use strict";
5577
5827
  init_config();
5578
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
5579
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
5580
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
5828
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
5829
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
5830
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
5581
5831
  API_BASE = "https://askexe.com/cloud";
5582
5832
  RETRY_DELAY_MS = 500;
5583
5833
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -5601,6 +5851,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
5601
5851
  employeeLimit: 1,
5602
5852
  memoryLimit: 5e3
5603
5853
  };
5854
+ _prismaPromise = null;
5855
+ _prismaFailed = false;
5604
5856
  CACHE_MAX_AGE_MS = 36e5;
5605
5857
  _revalTimer = null;
5606
5858
  }
@@ -6475,16 +6727,16 @@ __export(imessage_exports, {
6475
6727
  });
6476
6728
  import { execFile } from "child_process";
6477
6729
  import { promisify } from "util";
6478
- import os6 from "os";
6479
- import path9 from "path";
6730
+ import os7 from "os";
6731
+ import path10 from "path";
6480
6732
  var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
6481
6733
  var init_imessage = __esm({
6482
6734
  "src/gateway/adapters/imessage.ts"() {
6483
6735
  "use strict";
6484
6736
  execFileAsync = promisify(execFile);
6485
6737
  POLL_INTERVAL_MS = 5e3;
6486
- MESSAGES_DB_PATH = path9.join(
6487
- process.env.HOME ?? os6.homedir(),
6738
+ MESSAGES_DB_PATH = path10.join(
6739
+ process.env.HOME ?? os7.homedir(),
6488
6740
  "Library/Messages/chat.db"
6489
6741
  );
6490
6742
  IMessageAdapter = class {
@@ -6797,9 +7049,9 @@ __export(webhook_exports, {
6797
7049
  WebhookAdapter: () => WebhookAdapter
6798
7050
  });
6799
7051
  import { randomUUID as randomUUID7 } from "crypto";
6800
- function resolvePath(obj, path22) {
7052
+ function resolvePath(obj, path23) {
6801
7053
  let current = obj;
6802
- for (const segment of path22.split(".")) {
7054
+ for (const segment of path23.split(".")) {
6803
7055
  if (current == null || typeof current !== "object") return void 0;
6804
7056
  current = current[segment];
6805
7057
  }
@@ -6902,13 +7154,13 @@ __export(whatsapp_accounts_exports, {
6902
7154
  getDefaultAccount: () => getDefaultAccount,
6903
7155
  loadAccounts: () => loadAccounts
6904
7156
  });
6905
- import { readFileSync as readFileSync6 } from "fs";
7157
+ import { readFileSync as readFileSync7 } from "fs";
6906
7158
  import { join as join2 } from "path";
6907
7159
  import { homedir as homedir2 } from "os";
6908
7160
  function loadAccounts() {
6909
7161
  if (cachedAccounts !== null) return cachedAccounts;
6910
7162
  try {
6911
- const raw = readFileSync6(CONFIG_PATH2, "utf8");
7163
+ const raw = readFileSync7(CONFIG_PATH2, "utf8");
6912
7164
  const parsed = JSON.parse(raw);
6913
7165
  if (!Array.isArray(parsed)) {
6914
7166
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -6948,12 +7200,12 @@ var init_whatsapp_accounts = __esm({
6948
7200
  });
6949
7201
 
6950
7202
  // src/lib/session-registry.ts
6951
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync8 } from "fs";
6952
- import path10 from "path";
6953
- import os7 from "os";
7203
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
7204
+ import path11 from "path";
7205
+ import os8 from "os";
6954
7206
  function registerSession(entry) {
6955
- const dir = path10.dirname(REGISTRY_PATH);
6956
- if (!existsSync8(dir)) {
7207
+ const dir = path11.dirname(REGISTRY_PATH);
7208
+ if (!existsSync10(dir)) {
6957
7209
  mkdirSync5(dir, { recursive: true });
6958
7210
  }
6959
7211
  const sessions = listSessions();
@@ -6963,11 +7215,11 @@ function registerSession(entry) {
6963
7215
  } else {
6964
7216
  sessions.push(entry);
6965
7217
  }
6966
- writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
7218
+ writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
6967
7219
  }
6968
7220
  function listSessions() {
6969
7221
  try {
6970
- const raw = readFileSync7(REGISTRY_PATH, "utf8");
7222
+ const raw = readFileSync8(REGISTRY_PATH, "utf8");
6971
7223
  return JSON.parse(raw);
6972
7224
  } catch {
6973
7225
  return [];
@@ -6977,7 +7229,7 @@ var REGISTRY_PATH;
6977
7229
  var init_session_registry = __esm({
6978
7230
  "src/lib/session-registry.ts"() {
6979
7231
  "use strict";
6980
- REGISTRY_PATH = path10.join(os7.homedir(), ".exe-os", "session-registry.json");
7232
+ REGISTRY_PATH = path11.join(os8.homedir(), ".exe-os", "session-registry.json");
6981
7233
  }
6982
7234
  });
6983
7235
 
@@ -7238,17 +7490,17 @@ __export(intercom_queue_exports, {
7238
7490
  queueIntercom: () => queueIntercom,
7239
7491
  readQueue: () => readQueue
7240
7492
  });
7241
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
7242
- import path11 from "path";
7243
- import os8 from "os";
7493
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
7494
+ import path12 from "path";
7495
+ import os9 from "os";
7244
7496
  function ensureDir() {
7245
- const dir = path11.dirname(QUEUE_PATH);
7246
- if (!existsSync9(dir)) mkdirSync6(dir, { recursive: true });
7497
+ const dir = path12.dirname(QUEUE_PATH);
7498
+ if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true });
7247
7499
  }
7248
7500
  function readQueue() {
7249
7501
  try {
7250
- if (!existsSync9(QUEUE_PATH)) return [];
7251
- return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
7502
+ if (!existsSync11(QUEUE_PATH)) return [];
7503
+ return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
7252
7504
  } catch {
7253
7505
  return [];
7254
7506
  }
@@ -7256,7 +7508,7 @@ function readQueue() {
7256
7508
  function writeQueue(queue) {
7257
7509
  ensureDir();
7258
7510
  const tmp = `${QUEUE_PATH}.tmp`;
7259
- writeFileSync5(tmp, JSON.stringify(queue, null, 2));
7511
+ writeFileSync6(tmp, JSON.stringify(queue, null, 2));
7260
7512
  renameSync3(tmp, QUEUE_PATH);
7261
7513
  }
7262
7514
  function queueIntercom(targetSession, reason) {
@@ -7348,20 +7600,20 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
7348
7600
  var init_intercom_queue = __esm({
7349
7601
  "src/lib/intercom-queue.ts"() {
7350
7602
  "use strict";
7351
- QUEUE_PATH = path11.join(os8.homedir(), ".exe-os", "intercom-queue.json");
7603
+ QUEUE_PATH = path12.join(os9.homedir(), ".exe-os", "intercom-queue.json");
7352
7604
  MAX_RETRIES2 = 5;
7353
7605
  TTL_MS = 60 * 60 * 1e3;
7354
- INTERCOM_LOG = path11.join(os8.homedir(), ".exe-os", "intercom.log");
7606
+ INTERCOM_LOG = path12.join(os9.homedir(), ".exe-os", "intercom.log");
7355
7607
  }
7356
7608
  });
7357
7609
 
7358
7610
  // src/lib/plan-limits.ts
7359
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
7360
- import path12 from "path";
7611
+ import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
7612
+ import path13 from "path";
7361
7613
  function getLicenseSync() {
7362
7614
  try {
7363
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
7364
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
7615
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
7616
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
7365
7617
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
7366
7618
  const parts = raw.token.split(".");
7367
7619
  if (parts.length !== 3) return freeLicense();
@@ -7399,8 +7651,8 @@ function assertEmployeeLimitSync(rosterPath) {
7399
7651
  const filePath = rosterPath ?? EMPLOYEES_PATH;
7400
7652
  let count = 0;
7401
7653
  try {
7402
- if (existsSync10(filePath)) {
7403
- const raw = readFileSync9(filePath, "utf8");
7654
+ if (existsSync12(filePath)) {
7655
+ const raw = readFileSync10(filePath, "utf8");
7404
7656
  const employees = JSON.parse(raw);
7405
7657
  count = Array.isArray(employees) ? employees.length : 0;
7406
7658
  }
@@ -7429,29 +7681,63 @@ var init_plan_limits = __esm({
7429
7681
  this.name = "PlanLimitError";
7430
7682
  }
7431
7683
  };
7432
- CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
7684
+ CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
7685
+ }
7686
+ });
7687
+
7688
+ // src/lib/task-scope.ts
7689
+ function getCurrentSessionScope() {
7690
+ try {
7691
+ return resolveExeSession();
7692
+ } catch {
7693
+ return null;
7694
+ }
7695
+ }
7696
+ function sessionScopeFilter(sessionScope, tableAlias) {
7697
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7698
+ if (!scope) return { sql: "", args: [] };
7699
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7700
+ return {
7701
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
7702
+ args: [scope]
7703
+ };
7704
+ }
7705
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
7706
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7707
+ if (!scope) return { sql: "", args: [] };
7708
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7709
+ return {
7710
+ sql: ` AND ${col} = ?`,
7711
+ args: [scope]
7712
+ };
7713
+ }
7714
+ var init_task_scope = __esm({
7715
+ "src/lib/task-scope.ts"() {
7716
+ "use strict";
7717
+ init_tmux_routing();
7433
7718
  }
7434
7719
  });
7435
7720
 
7436
7721
  // src/lib/notifications.ts
7437
- import crypto3 from "crypto";
7438
- import path13 from "path";
7439
- import os9 from "os";
7722
+ import crypto4 from "crypto";
7723
+ import path14 from "path";
7724
+ import os10 from "os";
7440
7725
  import {
7441
- readFileSync as readFileSync10,
7726
+ readFileSync as readFileSync11,
7442
7727
  readdirSync as readdirSync2,
7443
7728
  unlinkSync as unlinkSync3,
7444
- existsSync as existsSync11,
7729
+ existsSync as existsSync13,
7445
7730
  rmdirSync
7446
7731
  } from "fs";
7447
7732
  async function writeNotification(notification) {
7448
7733
  try {
7449
7734
  const client = getClient();
7450
- const id = crypto3.randomUUID();
7735
+ const id = crypto4.randomUUID();
7451
7736
  const now = (/* @__PURE__ */ new Date()).toISOString();
7737
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
7452
7738
  await client.execute({
7453
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
7454
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
7739
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
7740
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
7455
7741
  args: [
7456
7742
  id,
7457
7743
  notification.agentId,
@@ -7460,6 +7746,7 @@ async function writeNotification(notification) {
7460
7746
  notification.project,
7461
7747
  notification.summary,
7462
7748
  notification.taskFile ?? null,
7749
+ sessionScope,
7463
7750
  now
7464
7751
  ]
7465
7752
  });
@@ -7468,12 +7755,14 @@ async function writeNotification(notification) {
7468
7755
  `);
7469
7756
  }
7470
7757
  }
7471
- async function markAsReadByTaskFile(taskFile) {
7758
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
7472
7759
  try {
7473
7760
  const client = getClient();
7761
+ const scope = strictSessionScopeFilter(sessionScope);
7474
7762
  await client.execute({
7475
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
7476
- args: [taskFile]
7763
+ sql: `UPDATE notifications SET read = 1
7764
+ WHERE task_file = ? AND read = 0${scope.sql}`,
7765
+ args: [taskFile, ...scope.args]
7477
7766
  });
7478
7767
  } catch {
7479
7768
  }
@@ -7482,11 +7771,12 @@ var init_notifications = __esm({
7482
7771
  "src/lib/notifications.ts"() {
7483
7772
  "use strict";
7484
7773
  init_database();
7774
+ init_task_scope();
7485
7775
  }
7486
7776
  });
7487
7777
 
7488
7778
  // src/lib/session-kill-telemetry.ts
7489
- import crypto4 from "crypto";
7779
+ import crypto5 from "crypto";
7490
7780
  async function recordSessionKill(input) {
7491
7781
  try {
7492
7782
  const client = getClient();
@@ -7496,7 +7786,7 @@ async function recordSessionKill(input) {
7496
7786
  ticks_idle, estimated_tokens_saved)
7497
7787
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
7498
7788
  args: [
7499
- crypto4.randomUUID(),
7789
+ crypto5.randomUUID(),
7500
7790
  input.sessionName,
7501
7791
  input.agentId,
7502
7792
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -7519,37 +7809,117 @@ var init_session_kill_telemetry = __esm({
7519
7809
  }
7520
7810
  });
7521
7811
 
7522
- // src/lib/task-scope.ts
7523
- function getCurrentSessionScope() {
7812
+ // src/lib/project-name.ts
7813
+ import { execSync as execSync4 } from "child_process";
7814
+ import path15 from "path";
7815
+ function getProjectName(cwd) {
7816
+ const dir = cwd ?? process.cwd();
7817
+ if (_cached2 && _cachedCwd === dir) return _cached2;
7524
7818
  try {
7525
- return resolveExeSession();
7819
+ let repoRoot;
7820
+ try {
7821
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
7822
+ cwd: dir,
7823
+ encoding: "utf8",
7824
+ timeout: 2e3,
7825
+ stdio: ["pipe", "pipe", "pipe"]
7826
+ }).trim();
7827
+ repoRoot = path15.dirname(gitCommonDir);
7828
+ } catch {
7829
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
7830
+ cwd: dir,
7831
+ encoding: "utf8",
7832
+ timeout: 2e3,
7833
+ stdio: ["pipe", "pipe", "pipe"]
7834
+ }).trim();
7835
+ }
7836
+ _cached2 = path15.basename(repoRoot);
7837
+ _cachedCwd = dir;
7838
+ return _cached2;
7526
7839
  } catch {
7527
- return null;
7840
+ _cached2 = path15.basename(dir);
7841
+ _cachedCwd = dir;
7842
+ return _cached2;
7528
7843
  }
7529
7844
  }
7530
- function sessionScopeFilter(sessionScope, tableAlias) {
7531
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7532
- if (!scope) return { sql: "", args: [] };
7533
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7534
- return {
7535
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
7536
- args: [scope]
7537
- };
7845
+ var _cached2, _cachedCwd;
7846
+ var init_project_name = __esm({
7847
+ "src/lib/project-name.ts"() {
7848
+ "use strict";
7849
+ _cached2 = null;
7850
+ _cachedCwd = null;
7851
+ }
7852
+ });
7853
+
7854
+ // src/lib/session-scope.ts
7855
+ var session_scope_exports = {};
7856
+ __export(session_scope_exports, {
7857
+ assertSessionScope: () => assertSessionScope,
7858
+ findSessionForProject: () => findSessionForProject,
7859
+ getSessionProject: () => getSessionProject
7860
+ });
7861
+ function getSessionProject(sessionName) {
7862
+ const sessions = listSessions();
7863
+ const entry = sessions.find((s) => s.windowName === sessionName);
7864
+ if (!entry) return null;
7865
+ const parts = entry.projectDir.split("/").filter(Boolean);
7866
+ return parts[parts.length - 1] ?? null;
7538
7867
  }
7539
- var init_task_scope = __esm({
7540
- "src/lib/task-scope.ts"() {
7868
+ function findSessionForProject(projectName) {
7869
+ const sessions = listSessions();
7870
+ for (const s of sessions) {
7871
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
7872
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
7873
+ }
7874
+ return null;
7875
+ }
7876
+ function assertSessionScope(actionType, targetProject) {
7877
+ try {
7878
+ const currentProject = getProjectName();
7879
+ const exeSession = resolveExeSession();
7880
+ if (!exeSession) {
7881
+ return { allowed: true, reason: "no_session" };
7882
+ }
7883
+ if (currentProject === targetProject) {
7884
+ return {
7885
+ allowed: true,
7886
+ reason: "same_session",
7887
+ currentProject,
7888
+ targetProject
7889
+ };
7890
+ }
7891
+ process.stderr.write(
7892
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
7893
+ `
7894
+ );
7895
+ return {
7896
+ allowed: false,
7897
+ reason: "cross_session_denied",
7898
+ currentProject,
7899
+ targetProject,
7900
+ targetSession: findSessionForProject(targetProject)?.windowName
7901
+ };
7902
+ } catch {
7903
+ return { allowed: true, reason: "no_session" };
7904
+ }
7905
+ }
7906
+ var init_session_scope = __esm({
7907
+ "src/lib/session-scope.ts"() {
7541
7908
  "use strict";
7909
+ init_session_registry();
7910
+ init_project_name();
7542
7911
  init_tmux_routing();
7912
+ init_employees();
7543
7913
  }
7544
7914
  });
7545
7915
 
7546
7916
  // src/lib/tasks-crud.ts
7547
- import crypto5 from "crypto";
7548
- import path14 from "path";
7549
- import os10 from "os";
7550
- import { execSync as execSync4 } from "child_process";
7917
+ import crypto6 from "crypto";
7918
+ import path16 from "path";
7919
+ import os11 from "os";
7920
+ import { execSync as execSync5 } from "child_process";
7551
7921
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
7552
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
7922
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
7553
7923
  async function writeCheckpoint(input) {
7554
7924
  const client = getClient();
7555
7925
  const row = await resolveTask(client, input.taskId);
@@ -7665,13 +8035,28 @@ async function resolveTask(client, identifier, scopeSession) {
7665
8035
  }
7666
8036
  async function createTaskCore(input) {
7667
8037
  const client = getClient();
7668
- const id = crypto5.randomUUID();
8038
+ const id = crypto6.randomUUID();
7669
8039
  const now = (/* @__PURE__ */ new Date()).toISOString();
7670
8040
  const slug = slugify(input.title);
7671
8041
  let earlySessionScope = null;
8042
+ let scopeMismatchWarning;
7672
8043
  try {
7673
8044
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
7674
- earlySessionScope = resolveExeSession2();
8045
+ const resolved = resolveExeSession2();
8046
+ if (resolved && input.projectName) {
8047
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
8048
+ const sessionProject = getSessionProject2(resolved);
8049
+ if (sessionProject && sessionProject !== input.projectName) {
8050
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
8051
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
8052
+ `);
8053
+ earlySessionScope = null;
8054
+ } else {
8055
+ earlySessionScope = resolved;
8056
+ }
8057
+ } else {
8058
+ earlySessionScope = resolved;
8059
+ }
7675
8060
  } catch {
7676
8061
  }
7677
8062
  const scope = earlySessionScope ?? "default";
@@ -7722,10 +8107,14 @@ async function createTaskCore(input) {
7722
8107
  ${laneWarning}` : laneWarning;
7723
8108
  }
7724
8109
  }
8110
+ if (scopeMismatchWarning) {
8111
+ warning = warning ? `${warning}
8112
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
8113
+ }
7725
8114
  if (input.baseDir) {
7726
8115
  try {
7727
- await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
7728
- await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
8116
+ await mkdir4(path16.join(input.baseDir, "exe", "output"), { recursive: true });
8117
+ await mkdir4(path16.join(input.baseDir, "exe", "research"), { recursive: true });
7729
8118
  await ensureArchitectureDoc(input.baseDir, input.projectName);
7730
8119
  await ensureGitignoreExe(input.baseDir);
7731
8120
  } catch {
@@ -7761,13 +8150,19 @@ ${laneWarning}` : laneWarning;
7761
8150
  });
7762
8151
  if (input.baseDir) {
7763
8152
  try {
7764
- const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
7765
- const mdPath = path14.join(EXE_OS_DIR, taskFile);
7766
- const mdDir = path14.dirname(mdPath);
7767
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
8153
+ const EXE_OS_DIR = path16.join(os11.homedir(), ".exe-os");
8154
+ const mdPath = path16.join(EXE_OS_DIR, taskFile);
8155
+ const mdDir = path16.dirname(mdPath);
8156
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
7768
8157
  const reviewer = input.reviewer ?? input.assignedBy;
7769
8158
  const mdContent = `# ${input.title}
7770
8159
 
8160
+ ## MANDATORY: When done
8161
+
8162
+ You MUST call update_task with status "done" and a result summary when finished.
8163
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
8164
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
8165
+
7771
8166
  **ID:** ${id}
7772
8167
  **Status:** ${initialStatus}
7773
8168
  **Priority:** ${input.priority}
@@ -7781,12 +8176,6 @@ ${laneWarning}` : laneWarning;
7781
8176
  ## Context
7782
8177
 
7783
8178
  ${input.context}
7784
-
7785
- ## MANDATORY: When done
7786
-
7787
- You MUST call update_task with status "done" and a result summary when finished.
7788
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
7789
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
7790
8179
  `;
7791
8180
  await writeFile4(mdPath, mdContent, "utf-8");
7792
8181
  } catch (err) {
@@ -7868,14 +8257,14 @@ function isTmuxSessionAlive(identifier) {
7868
8257
  if (!identifier || identifier === "unknown") return true;
7869
8258
  try {
7870
8259
  if (identifier.startsWith("%")) {
7871
- const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
8260
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
7872
8261
  timeout: 2e3,
7873
8262
  encoding: "utf8",
7874
8263
  stdio: ["pipe", "pipe", "pipe"]
7875
8264
  });
7876
8265
  return output.split("\n").some((l) => l.trim() === identifier);
7877
8266
  } else {
7878
- execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
8267
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
7879
8268
  timeout: 2e3,
7880
8269
  stdio: ["pipe", "pipe", "pipe"]
7881
8270
  });
@@ -7884,7 +8273,7 @@ function isTmuxSessionAlive(identifier) {
7884
8273
  } catch {
7885
8274
  if (identifier.startsWith("%")) return true;
7886
8275
  try {
7887
- execSync4("tmux list-sessions", {
8276
+ execSync5("tmux list-sessions", {
7888
8277
  timeout: 2e3,
7889
8278
  stdio: ["pipe", "pipe", "pipe"]
7890
8279
  });
@@ -7899,12 +8288,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
7899
8288
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
7900
8289
  try {
7901
8290
  const since = new Date(taskCreatedAt).toISOString();
7902
- const branch = execSync4(
8291
+ const branch = execSync5(
7903
8292
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
7904
8293
  { encoding: "utf8", timeout: 3e3 }
7905
8294
  ).trim();
7906
8295
  const branchArg = branch && branch !== "HEAD" ? branch : "";
7907
- const commitCount = execSync4(
8296
+ const commitCount = execSync5(
7908
8297
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
7909
8298
  { encoding: "utf8", timeout: 5e3 }
7910
8299
  ).trim();
@@ -8035,7 +8424,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
8035
8424
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
8036
8425
  } catch {
8037
8426
  }
8038
- if (input.status === "done" || input.status === "cancelled") {
8427
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
8039
8428
  try {
8040
8429
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
8041
8430
  clearQueueForAgent2(String(row.assigned_to));
@@ -8064,9 +8453,9 @@ async function deleteTaskCore(taskId, _baseDir) {
8064
8453
  return { taskFile, assignedTo, assignedBy, taskSlug };
8065
8454
  }
8066
8455
  async function ensureArchitectureDoc(baseDir, projectName) {
8067
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
8456
+ const archPath = path16.join(baseDir, "exe", "ARCHITECTURE.md");
8068
8457
  try {
8069
- if (existsSync12(archPath)) return;
8458
+ if (existsSync14(archPath)) return;
8070
8459
  const template = [
8071
8460
  `# ${projectName} \u2014 System Architecture`,
8072
8461
  "",
@@ -8099,10 +8488,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
8099
8488
  }
8100
8489
  }
8101
8490
  async function ensureGitignoreExe(baseDir) {
8102
- const gitignorePath = path14.join(baseDir, ".gitignore");
8491
+ const gitignorePath = path16.join(baseDir, ".gitignore");
8103
8492
  try {
8104
- if (existsSync12(gitignorePath)) {
8105
- const content = readFileSync11(gitignorePath, "utf-8");
8493
+ if (existsSync14(gitignorePath)) {
8494
+ const content = readFileSync12(gitignorePath, "utf-8");
8106
8495
  if (/^\/?exe\/?$/m.test(content)) return;
8107
8496
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
8108
8497
  } else {
@@ -8133,58 +8522,42 @@ var init_tasks_crud = __esm({
8133
8522
  });
8134
8523
 
8135
8524
  // src/lib/tasks-review.ts
8136
- import path15 from "path";
8137
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
8525
+ import path17 from "path";
8526
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
8138
8527
  async function countPendingReviews(sessionScope) {
8139
8528
  const client = getClient();
8140
- if (sessionScope) {
8141
- const result2 = await client.execute({
8142
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
8143
- args: [sessionScope]
8144
- });
8145
- return Number(result2.rows[0]?.cnt) || 0;
8146
- }
8529
+ const scope = strictSessionScopeFilter(
8530
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8531
+ );
8147
8532
  const result = await client.execute({
8148
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
8149
- args: []
8533
+ sql: `SELECT COUNT(*) as cnt FROM tasks
8534
+ WHERE status = 'needs_review'${scope.sql}`,
8535
+ args: [...scope.args]
8150
8536
  });
8151
8537
  return Number(result.rows[0]?.cnt) || 0;
8152
8538
  }
8153
8539
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
8154
8540
  const client = getClient();
8155
- if (sessionScope) {
8156
- const result2 = await client.execute({
8157
- sql: `SELECT COUNT(*) as cnt FROM tasks
8158
- WHERE status = 'needs_review' AND updated_at > ?
8159
- AND session_scope = ?`,
8160
- args: [sinceIso, sessionScope]
8161
- });
8162
- return Number(result2.rows[0]?.cnt) || 0;
8163
- }
8541
+ const scope = strictSessionScopeFilter(
8542
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8543
+ );
8164
8544
  const result = await client.execute({
8165
8545
  sql: `SELECT COUNT(*) as cnt FROM tasks
8166
- WHERE status = 'needs_review' AND updated_at > ?`,
8167
- args: [sinceIso]
8546
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
8547
+ args: [sinceIso, ...scope.args]
8168
8548
  });
8169
8549
  return Number(result.rows[0]?.cnt) || 0;
8170
8550
  }
8171
8551
  async function listPendingReviews(limit, sessionScope) {
8172
8552
  const client = getClient();
8173
- if (sessionScope) {
8174
- const result2 = await client.execute({
8175
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
8176
- WHERE status = 'needs_review'
8177
- AND session_scope = ?
8178
- ORDER BY updated_at ASC LIMIT ?`,
8179
- args: [sessionScope, limit]
8180
- });
8181
- return result2.rows;
8182
- }
8553
+ const scope = strictSessionScopeFilter(
8554
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8555
+ );
8183
8556
  const result = await client.execute({
8184
8557
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
8185
- WHERE status = 'needs_review'
8558
+ WHERE status = 'needs_review'${scope.sql}
8186
8559
  ORDER BY updated_at ASC LIMIT ?`,
8187
- args: [limit]
8560
+ args: [...scope.args, limit]
8188
8561
  });
8189
8562
  return result.rows;
8190
8563
  }
@@ -8196,7 +8569,7 @@ async function cleanupOrphanedReviews() {
8196
8569
  WHERE status IN ('open', 'needs_review', 'in_progress')
8197
8570
  AND assigned_by = 'system'
8198
8571
  AND title LIKE 'Review:%'
8199
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
8572
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
8200
8573
  args: [now]
8201
8574
  });
8202
8575
  const r1b = await client.execute({
@@ -8315,11 +8688,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
8315
8688
  );
8316
8689
  }
8317
8690
  try {
8318
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
8319
- if (existsSync13(cacheDir)) {
8691
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
8692
+ if (existsSync15(cacheDir)) {
8320
8693
  for (const f of readdirSync3(cacheDir)) {
8321
8694
  if (f.startsWith("review-notified-")) {
8322
- unlinkSync4(path15.join(cacheDir, f));
8695
+ unlinkSync4(path17.join(cacheDir, f));
8323
8696
  }
8324
8697
  }
8325
8698
  }
@@ -8336,11 +8709,12 @@ var init_tasks_review = __esm({
8336
8709
  init_tmux_routing();
8337
8710
  init_session_key();
8338
8711
  init_state_bus();
8712
+ init_task_scope();
8339
8713
  }
8340
8714
  });
8341
8715
 
8342
8716
  // src/lib/tasks-chain.ts
8343
- import path16 from "path";
8717
+ import path18 from "path";
8344
8718
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
8345
8719
  async function cascadeUnblock(taskId, baseDir, now) {
8346
8720
  const client = getClient();
@@ -8357,7 +8731,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
8357
8731
  });
8358
8732
  for (const ur of unblockedRows.rows) {
8359
8733
  try {
8360
- const ubFile = path16.join(baseDir, String(ur.task_file));
8734
+ const ubFile = path18.join(baseDir, String(ur.task_file));
8361
8735
  let ubContent = await readFile4(ubFile, "utf-8");
8362
8736
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
8363
8737
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -8392,7 +8766,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
8392
8766
  const scScope = sessionScopeFilter();
8393
8767
  const remaining = await client.execute({
8394
8768
  sql: `SELECT COUNT(*) as cnt FROM tasks
8395
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
8769
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
8396
8770
  args: [parentTaskId, ...scScope.args]
8397
8771
  });
8398
8772
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -8424,110 +8798,6 @@ var init_tasks_chain = __esm({
8424
8798
  }
8425
8799
  });
8426
8800
 
8427
- // src/lib/project-name.ts
8428
- import { execSync as execSync5 } from "child_process";
8429
- import path17 from "path";
8430
- function getProjectName(cwd) {
8431
- const dir = cwd ?? process.cwd();
8432
- if (_cached2 && _cachedCwd === dir) return _cached2;
8433
- try {
8434
- let repoRoot;
8435
- try {
8436
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
8437
- cwd: dir,
8438
- encoding: "utf8",
8439
- timeout: 2e3,
8440
- stdio: ["pipe", "pipe", "pipe"]
8441
- }).trim();
8442
- repoRoot = path17.dirname(gitCommonDir);
8443
- } catch {
8444
- repoRoot = execSync5("git rev-parse --show-toplevel", {
8445
- cwd: dir,
8446
- encoding: "utf8",
8447
- timeout: 2e3,
8448
- stdio: ["pipe", "pipe", "pipe"]
8449
- }).trim();
8450
- }
8451
- _cached2 = path17.basename(repoRoot);
8452
- _cachedCwd = dir;
8453
- return _cached2;
8454
- } catch {
8455
- _cached2 = path17.basename(dir);
8456
- _cachedCwd = dir;
8457
- return _cached2;
8458
- }
8459
- }
8460
- var _cached2, _cachedCwd;
8461
- var init_project_name = __esm({
8462
- "src/lib/project-name.ts"() {
8463
- "use strict";
8464
- _cached2 = null;
8465
- _cachedCwd = null;
8466
- }
8467
- });
8468
-
8469
- // src/lib/session-scope.ts
8470
- var session_scope_exports = {};
8471
- __export(session_scope_exports, {
8472
- assertSessionScope: () => assertSessionScope,
8473
- findSessionForProject: () => findSessionForProject,
8474
- getSessionProject: () => getSessionProject
8475
- });
8476
- function getSessionProject(sessionName) {
8477
- const sessions = listSessions();
8478
- const entry = sessions.find((s) => s.windowName === sessionName);
8479
- if (!entry) return null;
8480
- const parts = entry.projectDir.split("/").filter(Boolean);
8481
- return parts[parts.length - 1] ?? null;
8482
- }
8483
- function findSessionForProject(projectName) {
8484
- const sessions = listSessions();
8485
- for (const s of sessions) {
8486
- const proj = s.projectDir.split("/").filter(Boolean).pop();
8487
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
8488
- }
8489
- return null;
8490
- }
8491
- function assertSessionScope(actionType, targetProject) {
8492
- try {
8493
- const currentProject = getProjectName();
8494
- const exeSession = resolveExeSession();
8495
- if (!exeSession) {
8496
- return { allowed: true, reason: "no_session" };
8497
- }
8498
- if (currentProject === targetProject) {
8499
- return {
8500
- allowed: true,
8501
- reason: "same_session",
8502
- currentProject,
8503
- targetProject
8504
- };
8505
- }
8506
- process.stderr.write(
8507
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
8508
- `
8509
- );
8510
- return {
8511
- allowed: false,
8512
- reason: "cross_session_denied",
8513
- currentProject,
8514
- targetProject,
8515
- targetSession: findSessionForProject(targetProject)?.windowName
8516
- };
8517
- } catch {
8518
- return { allowed: true, reason: "no_session" };
8519
- }
8520
- }
8521
- var init_session_scope = __esm({
8522
- "src/lib/session-scope.ts"() {
8523
- "use strict";
8524
- init_session_registry();
8525
- init_project_name();
8526
- init_tmux_routing();
8527
- init_employees();
8528
- }
8529
- });
8530
-
8531
8801
  // src/lib/tasks-notify.ts
8532
8802
  async function dispatchTaskToEmployee(input) {
8533
8803
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -8595,10 +8865,10 @@ var init_tasks_notify = __esm({
8595
8865
  });
8596
8866
 
8597
8867
  // src/lib/behaviors.ts
8598
- import crypto6 from "crypto";
8868
+ import crypto7 from "crypto";
8599
8869
  async function storeBehavior(opts) {
8600
8870
  const client = getClient();
8601
- const id = crypto6.randomUUID();
8871
+ const id = crypto7.randomUUID();
8602
8872
  const now = (/* @__PURE__ */ new Date()).toISOString();
8603
8873
  await client.execute({
8604
8874
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -8627,7 +8897,7 @@ __export(skill_learning_exports, {
8627
8897
  storeTrajectory: () => storeTrajectory,
8628
8898
  sweepTrajectories: () => sweepTrajectories
8629
8899
  });
8630
- import crypto7 from "crypto";
8900
+ import crypto8 from "crypto";
8631
8901
  async function extractTrajectory(taskId, agentId) {
8632
8902
  const client = getClient();
8633
8903
  const result = await client.execute({
@@ -8656,11 +8926,11 @@ async function extractTrajectory(taskId, agentId) {
8656
8926
  return signature;
8657
8927
  }
8658
8928
  function hashSignature(signature) {
8659
- return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
8929
+ return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
8660
8930
  }
8661
8931
  async function storeTrajectory(opts) {
8662
8932
  const client = getClient();
8663
- const id = crypto7.randomUUID();
8933
+ const id = crypto8.randomUUID();
8664
8934
  const now = (/* @__PURE__ */ new Date()).toISOString();
8665
8935
  const signatureHash = hashSignature(opts.signature);
8666
8936
  await client.execute({
@@ -8925,8 +9195,8 @@ __export(tasks_exports, {
8925
9195
  updateTaskStatus: () => updateTaskStatus,
8926
9196
  writeCheckpoint: () => writeCheckpoint
8927
9197
  });
8928
- import path18 from "path";
8929
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
9198
+ import path19 from "path";
9199
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
8930
9200
  async function createTask(input) {
8931
9201
  const result = await createTaskCore(input);
8932
9202
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -8945,12 +9215,12 @@ async function updateTask(input) {
8945
9215
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
8946
9216
  try {
8947
9217
  const agent = String(row.assigned_to);
8948
- const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
8949
- const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
9218
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
9219
+ const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
8950
9220
  if (input.status === "in_progress") {
8951
9221
  mkdirSync7(cacheDir, { recursive: true });
8952
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
8953
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
9222
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
9223
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
8954
9224
  try {
8955
9225
  unlinkSync5(cachePath);
8956
9226
  } catch {
@@ -8958,10 +9228,10 @@ async function updateTask(input) {
8958
9228
  }
8959
9229
  } catch {
8960
9230
  }
8961
- if (input.status === "done") {
9231
+ if (input.status === "done" || input.status === "closed") {
8962
9232
  await cleanupReviewFile(row, taskFile, input.baseDir);
8963
9233
  }
8964
- if (input.status === "done" || input.status === "cancelled") {
9234
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
8965
9235
  try {
8966
9236
  const client = getClient();
8967
9237
  const taskTitle = String(row.title);
@@ -8977,7 +9247,7 @@ async function updateTask(input) {
8977
9247
  if (!isCoordinatorName(assignedAgent)) {
8978
9248
  try {
8979
9249
  const draftClient = getClient();
8980
- if (input.status === "done") {
9250
+ if (input.status === "done" || input.status === "closed") {
8981
9251
  await draftClient.execute({
8982
9252
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
8983
9253
  args: [assignedAgent]
@@ -8994,7 +9264,7 @@ async function updateTask(input) {
8994
9264
  try {
8995
9265
  const client = getClient();
8996
9266
  const cascaded = await client.execute({
8997
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
9267
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
8998
9268
  WHERE parent_task_id = ? AND status = 'needs_review'`,
8999
9269
  args: [now, taskId]
9000
9270
  });
@@ -9007,14 +9277,14 @@ async function updateTask(input) {
9007
9277
  } catch {
9008
9278
  }
9009
9279
  }
9010
- const isTerminal = input.status === "done" || input.status === "needs_review";
9280
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
9011
9281
  if (isTerminal) {
9012
9282
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
9013
9283
  if (!isCoordinator) {
9014
9284
  notifyTaskDone();
9015
9285
  }
9016
9286
  await markTaskNotificationsRead(taskFile);
9017
- if (input.status === "done") {
9287
+ if (input.status === "done" || input.status === "closed") {
9018
9288
  try {
9019
9289
  await cascadeUnblock(taskId, input.baseDir, now);
9020
9290
  } catch {
@@ -9034,7 +9304,7 @@ async function updateTask(input) {
9034
9304
  }
9035
9305
  }
9036
9306
  }
9037
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
9307
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
9038
9308
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
9039
9309
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
9040
9310
  taskId,
@@ -9406,6 +9676,7 @@ __export(tmux_routing_exports, {
9406
9676
  isEmployeeAlive: () => isEmployeeAlive,
9407
9677
  isExeSession: () => isExeSession,
9408
9678
  isSessionBusy: () => isSessionBusy,
9679
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
9409
9680
  notifyParentExe: () => notifyParentExe,
9410
9681
  parseParentExe: () => parseParentExe,
9411
9682
  registerParentExe: () => registerParentExe,
@@ -9416,13 +9687,13 @@ __export(tmux_routing_exports, {
9416
9687
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
9417
9688
  });
9418
9689
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
9419
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
9420
- import path19 from "path";
9421
- import os11 from "os";
9690
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
9691
+ import path20 from "path";
9692
+ import os12 from "os";
9422
9693
  import { fileURLToPath as fileURLToPath2 } from "url";
9423
9694
  import { unlinkSync as unlinkSync6 } from "fs";
9424
9695
  function spawnLockPath(sessionName) {
9425
- return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
9696
+ return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
9426
9697
  }
9427
9698
  function isProcessAlive(pid) {
9428
9699
  try {
@@ -9433,13 +9704,13 @@ function isProcessAlive(pid) {
9433
9704
  }
9434
9705
  }
9435
9706
  function acquireSpawnLock2(sessionName) {
9436
- if (!existsSync14(SPAWN_LOCK_DIR)) {
9707
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
9437
9708
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
9438
9709
  }
9439
9710
  const lockFile = spawnLockPath(sessionName);
9440
- if (existsSync14(lockFile)) {
9711
+ if (existsSync16(lockFile)) {
9441
9712
  try {
9442
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
9713
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
9443
9714
  const age = Date.now() - lock.timestamp;
9444
9715
  if (isProcessAlive(lock.pid) && age < 6e4) {
9445
9716
  return false;
@@ -9447,7 +9718,7 @@ function acquireSpawnLock2(sessionName) {
9447
9718
  } catch {
9448
9719
  }
9449
9720
  }
9450
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
9721
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
9451
9722
  return true;
9452
9723
  }
9453
9724
  function releaseSpawnLock2(sessionName) {
@@ -9459,13 +9730,13 @@ function releaseSpawnLock2(sessionName) {
9459
9730
  function resolveBehaviorsExporterScript() {
9460
9731
  try {
9461
9732
  const thisFile = fileURLToPath2(import.meta.url);
9462
- const scriptPath = path19.join(
9463
- path19.dirname(thisFile),
9733
+ const scriptPath = path20.join(
9734
+ path20.dirname(thisFile),
9464
9735
  "..",
9465
9736
  "bin",
9466
9737
  "exe-export-behaviors.js"
9467
9738
  );
9468
- return existsSync14(scriptPath) ? scriptPath : null;
9739
+ return existsSync16(scriptPath) ? scriptPath : null;
9469
9740
  } catch {
9470
9741
  return null;
9471
9742
  }
@@ -9531,12 +9802,12 @@ function extractRootExe(name) {
9531
9802
  return parts.length > 0 ? parts[parts.length - 1] : null;
9532
9803
  }
9533
9804
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
9534
- if (!existsSync14(SESSION_CACHE)) {
9805
+ if (!existsSync16(SESSION_CACHE)) {
9535
9806
  mkdirSync8(SESSION_CACHE, { recursive: true });
9536
9807
  }
9537
9808
  const rootExe = extractRootExe(parentExe) ?? parentExe;
9538
- const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
9539
- writeFileSync7(filePath, JSON.stringify({
9809
+ const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
9810
+ writeFileSync8(filePath, JSON.stringify({
9540
9811
  parentExe: rootExe,
9541
9812
  dispatchedBy: dispatchedBy || rootExe,
9542
9813
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -9544,7 +9815,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
9544
9815
  }
9545
9816
  function getParentExe(sessionKey) {
9546
9817
  try {
9547
- const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
9818
+ const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
9548
9819
  return data.parentExe || null;
9549
9820
  } catch {
9550
9821
  return null;
@@ -9552,8 +9823,8 @@ function getParentExe(sessionKey) {
9552
9823
  }
9553
9824
  function getDispatchedBy(sessionKey) {
9554
9825
  try {
9555
- const data = JSON.parse(readFileSync12(
9556
- path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
9826
+ const data = JSON.parse(readFileSync13(
9827
+ path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
9557
9828
  "utf8"
9558
9829
  ));
9559
9830
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -9623,8 +9894,8 @@ async function verifyPaneAtCapacity(sessionName) {
9623
9894
  }
9624
9895
  function readDebounceState() {
9625
9896
  try {
9626
- if (!existsSync14(DEBOUNCE_FILE)) return {};
9627
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
9897
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
9898
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
9628
9899
  const state = {};
9629
9900
  for (const [key, val] of Object.entries(raw)) {
9630
9901
  if (typeof val === "number") {
@@ -9640,8 +9911,8 @@ function readDebounceState() {
9640
9911
  }
9641
9912
  function writeDebounceState(state) {
9642
9913
  try {
9643
- if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
9644
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
9914
+ if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
9915
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
9645
9916
  } catch {
9646
9917
  }
9647
9918
  }
@@ -9739,8 +10010,8 @@ function sendIntercom(targetSession) {
9739
10010
  try {
9740
10011
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
9741
10012
  const agent = baseAgentName(rawAgent);
9742
- const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
9743
- if (existsSync14(markerPath)) {
10013
+ const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
10014
+ if (existsSync16(markerPath)) {
9744
10015
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
9745
10016
  return "debounced";
9746
10017
  }
@@ -9749,8 +10020,8 @@ function sendIntercom(targetSession) {
9749
10020
  try {
9750
10021
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
9751
10022
  const agent = baseAgentName(rawAgent);
9752
- const taskDir = path19.join(process.cwd(), "exe", agent);
9753
- if (existsSync14(taskDir)) {
10023
+ const taskDir = path20.join(process.cwd(), "exe", agent);
10024
+ if (existsSync16(taskDir)) {
9754
10025
  const files = readdirSync4(taskDir).filter(
9755
10026
  (f) => f.endsWith(".md") && f !== "DONE.txt"
9756
10027
  );
@@ -9810,6 +10081,21 @@ function notifyParentExe(sessionKey) {
9810
10081
  }
9811
10082
  return true;
9812
10083
  }
10084
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
10085
+ const transport = getTransport();
10086
+ try {
10087
+ const sessions = transport.listSessions();
10088
+ if (!sessions.includes(coordinatorSession)) return false;
10089
+ execSync6(
10090
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
10091
+ { timeout: 3e3 }
10092
+ );
10093
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
10094
+ return true;
10095
+ } catch {
10096
+ return false;
10097
+ }
10098
+ }
9813
10099
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
9814
10100
  if (isCoordinatorName(employeeName)) {
9815
10101
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -9883,26 +10169,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9883
10169
  const transport = getTransport();
9884
10170
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
9885
10171
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
9886
- const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
9887
- const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
9888
- if (!existsSync14(logDir)) {
10172
+ const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
10173
+ const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
10174
+ if (!existsSync16(logDir)) {
9889
10175
  mkdirSync8(logDir, { recursive: true });
9890
10176
  }
9891
10177
  transport.kill(sessionName);
9892
10178
  let cleanupSuffix = "";
9893
10179
  try {
9894
10180
  const thisFile = fileURLToPath2(import.meta.url);
9895
- const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
9896
- if (existsSync14(cleanupScript)) {
10181
+ const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
10182
+ if (existsSync16(cleanupScript)) {
9897
10183
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
9898
10184
  }
9899
10185
  } catch {
9900
10186
  }
9901
10187
  try {
9902
- const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
10188
+ const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
9903
10189
  let claudeJson = {};
9904
10190
  try {
9905
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
10191
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
9906
10192
  } catch {
9907
10193
  }
9908
10194
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -9910,17 +10196,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9910
10196
  const trustDir = opts?.cwd ?? projectDir;
9911
10197
  if (!projects[trustDir]) projects[trustDir] = {};
9912
10198
  projects[trustDir].hasTrustDialogAccepted = true;
9913
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
10199
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
9914
10200
  } catch {
9915
10201
  }
9916
10202
  try {
9917
- const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
10203
+ const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
9918
10204
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
9919
- const projSettingsDir = path19.join(settingsDir, normalizedKey);
9920
- const settingsPath = path19.join(projSettingsDir, "settings.json");
10205
+ const projSettingsDir = path20.join(settingsDir, normalizedKey);
10206
+ const settingsPath = path20.join(projSettingsDir, "settings.json");
9921
10207
  let settings = {};
9922
10208
  try {
9923
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
10209
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
9924
10210
  } catch {
9925
10211
  }
9926
10212
  const perms = settings.permissions ?? {};
@@ -9949,7 +10235,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9949
10235
  perms.allow = allow;
9950
10236
  settings.permissions = perms;
9951
10237
  mkdirSync8(projSettingsDir, { recursive: true });
9952
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
10238
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
9953
10239
  }
9954
10240
  } catch {
9955
10241
  }
@@ -9964,8 +10250,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9964
10250
  let behaviorsFlag = "";
9965
10251
  let legacyFallbackWarned = false;
9966
10252
  if (!useExeAgent && !useBinSymlink) {
9967
- const identityPath = path19.join(
9968
- os11.homedir(),
10253
+ const identityPath = path20.join(
10254
+ os12.homedir(),
9969
10255
  ".exe-os",
9970
10256
  "identity",
9971
10257
  `${employeeName}.md`
@@ -9974,13 +10260,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9974
10260
  const hasAgentFlag = claudeSupportsAgentFlag();
9975
10261
  if (hasAgentFlag) {
9976
10262
  identityFlag = ` --agent ${employeeName}`;
9977
- } else if (existsSync14(identityPath)) {
10263
+ } else if (existsSync16(identityPath)) {
9978
10264
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
9979
10265
  legacyFallbackWarned = true;
9980
10266
  }
9981
10267
  const behaviorsFile = exportBehaviorsSync(
9982
10268
  employeeName,
9983
- path19.basename(spawnCwd),
10269
+ path20.basename(spawnCwd),
9984
10270
  sessionName
9985
10271
  );
9986
10272
  if (behaviorsFile) {
@@ -9995,16 +10281,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9995
10281
  }
9996
10282
  let sessionContextFlag = "";
9997
10283
  try {
9998
- const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
10284
+ const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
9999
10285
  mkdirSync8(ctxDir, { recursive: true });
10000
- const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
10286
+ const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
10001
10287
  const ctxContent = [
10002
10288
  `## Session Context`,
10003
10289
  `You are running in tmux session: ${sessionName}.`,
10004
10290
  `Your parent coordinator session is ${exeSession}.`,
10005
10291
  `Your employees (if any) use the -${exeSession} suffix.`
10006
10292
  ].join("\n");
10007
- writeFileSync7(ctxFile, ctxContent);
10293
+ writeFileSync8(ctxFile, ctxContent);
10008
10294
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
10009
10295
  } catch {
10010
10296
  }
@@ -10081,8 +10367,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
10081
10367
  transport.pipeLog(sessionName, logFile);
10082
10368
  try {
10083
10369
  const mySession = getMySession();
10084
- const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
10085
- writeFileSync7(dispatchInfo, JSON.stringify({
10370
+ const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
10371
+ writeFileSync8(dispatchInfo, JSON.stringify({
10086
10372
  dispatchedBy: mySession,
10087
10373
  rootExe: exeSession,
10088
10374
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -10156,15 +10442,15 @@ var init_tmux_routing = __esm({
10156
10442
  init_intercom_queue();
10157
10443
  init_plan_limits();
10158
10444
  init_employees();
10159
- SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
10160
- SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
10445
+ SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
10446
+ SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
10161
10447
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
10162
10448
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
10163
10449
  VERIFY_PANE_LINES = 200;
10164
10450
  INTERCOM_DEBOUNCE_MS = 3e4;
10165
10451
  CODEX_DEBOUNCE_MS = 12e4;
10166
- INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
10167
- DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
10452
+ INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
10453
+ DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
10168
10454
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
10169
10455
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
10170
10456
  }
@@ -10187,10 +10473,10 @@ __export(messaging_exports, {
10187
10473
  sendMessage: () => sendMessage,
10188
10474
  setWsClientSend: () => setWsClientSend
10189
10475
  });
10190
- import crypto8 from "crypto";
10476
+ import crypto9 from "crypto";
10191
10477
  function generateUlid() {
10192
10478
  const timestamp = Date.now().toString(36).padStart(10, "0");
10193
- const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
10479
+ const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
10194
10480
  return (timestamp + random).toUpperCase();
10195
10481
  }
10196
10482
  function rowToMessage(row) {
@@ -10201,6 +10487,7 @@ function rowToMessage(row) {
10201
10487
  targetAgent: row.target_agent,
10202
10488
  targetProject: row.target_project ?? null,
10203
10489
  targetDevice: row.target_device,
10490
+ sessionScope: row.session_scope ?? null,
10204
10491
  content: row.content,
10205
10492
  priority: row.priority ?? "normal",
10206
10493
  status: row.status ?? "pending",
@@ -10218,15 +10505,17 @@ async function sendMessage(input) {
10218
10505
  const id = generateUlid();
10219
10506
  const now = (/* @__PURE__ */ new Date()).toISOString();
10220
10507
  const targetDevice = input.targetDevice ?? "local";
10508
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
10221
10509
  await client.execute({
10222
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
10223
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
10510
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
10511
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
10224
10512
  args: [
10225
10513
  id,
10226
10514
  input.fromAgent,
10227
10515
  input.targetAgent,
10228
10516
  input.targetProject ?? null,
10229
10517
  targetDevice,
10518
+ sessionScope,
10230
10519
  input.content,
10231
10520
  input.priority ?? "normal",
10232
10521
  now
@@ -10240,9 +10529,10 @@ async function sendMessage(input) {
10240
10529
  }
10241
10530
  } catch {
10242
10531
  }
10532
+ const sentScope = strictSessionScopeFilter(sessionScope);
10243
10533
  const result = await client.execute({
10244
- sql: "SELECT * FROM messages WHERE id = ?",
10245
- args: [id]
10534
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
10535
+ args: [id, ...sentScope.args]
10246
10536
  });
10247
10537
  return rowToMessage(result.rows[0]);
10248
10538
  }
@@ -10266,6 +10556,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
10266
10556
  fromAgent: msg.fromAgent,
10267
10557
  targetAgent: msg.targetAgent,
10268
10558
  targetProject: msg.targetProject,
10559
+ sessionScope: msg.sessionScope,
10269
10560
  content: msg.content,
10270
10561
  priority: msg.priority,
10271
10562
  createdAt: msg.createdAt
@@ -10309,7 +10600,7 @@ async function deliverLocalMessage(messageId) {
10309
10600
  } catch {
10310
10601
  const newRetryCount = msg.retryCount + 1;
10311
10602
  if (newRetryCount >= MAX_RETRIES3) {
10312
- await markFailed(messageId, "session unavailable after 10 retries");
10603
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
10313
10604
  } else {
10314
10605
  await client.execute({
10315
10606
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -10319,85 +10610,101 @@ async function deliverLocalMessage(messageId) {
10319
10610
  return false;
10320
10611
  }
10321
10612
  }
10322
- async function getPendingMessages(targetAgent) {
10613
+ async function getPendingMessages(targetAgent, sessionScope) {
10323
10614
  const client = getClient();
10615
+ const scope = strictSessionScopeFilter(sessionScope);
10324
10616
  const result = await client.execute({
10325
10617
  sql: `SELECT * FROM messages
10326
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
10618
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
10327
10619
  ORDER BY id`,
10328
- args: [targetAgent]
10620
+ args: [targetAgent, ...scope.args]
10329
10621
  });
10330
10622
  return result.rows.map((row) => rowToMessage(row));
10331
10623
  }
10332
- async function markRead(messageId) {
10624
+ async function markRead(messageId, sessionScope) {
10333
10625
  const client = getClient();
10626
+ const scope = strictSessionScopeFilter(sessionScope);
10334
10627
  await client.execute({
10335
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
10336
- args: [messageId]
10628
+ sql: `UPDATE messages SET status = 'read'
10629
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
10630
+ args: [messageId, ...scope.args]
10337
10631
  });
10338
10632
  }
10339
- async function markAcknowledged(messageId) {
10633
+ async function markAcknowledged(messageId, sessionScope) {
10340
10634
  const client = getClient();
10635
+ const scope = strictSessionScopeFilter(sessionScope);
10341
10636
  await client.execute({
10342
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
10343
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10637
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
10638
+ WHERE id = ? AND status = 'read'${scope.sql}`,
10639
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
10344
10640
  });
10345
10641
  }
10346
- async function markProcessed(messageId) {
10642
+ async function markProcessed(messageId, sessionScope) {
10347
10643
  const client = getClient();
10644
+ const scope = strictSessionScopeFilter(sessionScope);
10348
10645
  await client.execute({
10349
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
10350
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10646
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
10647
+ WHERE id = ?${scope.sql}`,
10648
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
10351
10649
  });
10352
10650
  }
10353
- async function getMessageStatus(messageId) {
10651
+ async function getMessageStatus(messageId, sessionScope) {
10354
10652
  const client = getClient();
10653
+ const scope = strictSessionScopeFilter(sessionScope);
10355
10654
  const result = await client.execute({
10356
- sql: "SELECT status FROM messages WHERE id = ?",
10357
- args: [messageId]
10655
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
10656
+ args: [messageId, ...scope.args]
10358
10657
  });
10359
10658
  return result.rows[0]?.status ?? null;
10360
10659
  }
10361
- async function getUnacknowledgedMessages(targetAgent) {
10660
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
10362
10661
  const client = getClient();
10662
+ const scope = strictSessionScopeFilter(sessionScope);
10363
10663
  const result = await client.execute({
10364
10664
  sql: `SELECT * FROM messages
10365
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
10665
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
10366
10666
  ORDER BY id`,
10367
- args: [targetAgent]
10667
+ args: [targetAgent, ...scope.args]
10368
10668
  });
10369
10669
  return result.rows.map((row) => rowToMessage(row));
10370
10670
  }
10371
- async function getReadMessages(targetAgent) {
10671
+ async function getReadMessages(targetAgent, sessionScope) {
10372
10672
  const client = getClient();
10673
+ const scope = strictSessionScopeFilter(sessionScope);
10373
10674
  const result = await client.execute({
10374
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
10375
- args: [targetAgent]
10675
+ sql: `SELECT * FROM messages
10676
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
10677
+ ORDER BY id`,
10678
+ args: [targetAgent, ...scope.args]
10376
10679
  });
10377
10680
  return result.rows.map((row) => rowToMessage(row));
10378
10681
  }
10379
- async function markFailed(messageId, reason) {
10682
+ async function markFailed(messageId, reason, sessionScope) {
10380
10683
  const client = getClient();
10684
+ const scope = strictSessionScopeFilter(sessionScope);
10381
10685
  await client.execute({
10382
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
10383
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
10686
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
10687
+ WHERE id = ?${scope.sql}`,
10688
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
10384
10689
  });
10385
10690
  }
10386
- async function getFailedMessages() {
10691
+ async function getFailedMessages(sessionScope) {
10387
10692
  const client = getClient();
10693
+ const scope = strictSessionScopeFilter(sessionScope);
10388
10694
  const result = await client.execute({
10389
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
10390
- args: []
10695
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
10696
+ args: [...scope.args]
10391
10697
  });
10392
10698
  return result.rows.map((row) => rowToMessage(row));
10393
10699
  }
10394
- async function retryPendingMessages() {
10700
+ async function retryPendingMessages(sessionScope) {
10395
10701
  const client = getClient();
10702
+ const scope = strictSessionScopeFilter(sessionScope);
10396
10703
  const result = await client.execute({
10397
10704
  sql: `SELECT * FROM messages
10398
- WHERE status = 'pending' AND retry_count < ?
10705
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
10399
10706
  ORDER BY id`,
10400
- args: [MAX_RETRIES3]
10707
+ args: [MAX_RETRIES3, ...scope.args]
10401
10708
  });
10402
10709
  let delivered = 0;
10403
10710
  for (const row of result.rows) {
@@ -10416,16 +10723,17 @@ var init_messaging = __esm({
10416
10723
  "use strict";
10417
10724
  init_database();
10418
10725
  init_tmux_routing();
10726
+ init_task_scope();
10419
10727
  MAX_RETRIES3 = 10;
10420
10728
  _wsClientSend = null;
10421
10729
  }
10422
10730
  });
10423
10731
 
10424
10732
  // src/automation/trigger-engine.ts
10425
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
10733
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
10426
10734
  import { randomUUID as randomUUID8 } from "crypto";
10427
- import path20 from "path";
10428
- import os12 from "os";
10735
+ import path21 from "path";
10736
+ import os13 from "os";
10429
10737
  function substituteTemplate(template, record) {
10430
10738
  return template.replace(
10431
10739
  /\{\{(\w+(?:\.\w+)*)\}\}/g,
@@ -10478,9 +10786,9 @@ function evaluateConditions(conditions, record) {
10478
10786
  return conditions.every((c) => evaluateCondition(c, record));
10479
10787
  }
10480
10788
  function loadTriggers(project) {
10481
- if (!existsSync15(TRIGGERS_PATH)) return [];
10789
+ if (!existsSync17(TRIGGERS_PATH)) return [];
10482
10790
  try {
10483
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
10791
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
10484
10792
  const all = JSON.parse(raw);
10485
10793
  if (!Array.isArray(all)) return [];
10486
10794
  if (project) {
@@ -10720,7 +11028,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
10720
11028
  var init_trigger_engine = __esm({
10721
11029
  "src/automation/trigger-engine.ts"() {
10722
11030
  "use strict";
10723
- TRIGGERS_PATH = path20.join(os12.homedir(), ".exe-os", "triggers.json");
11031
+ TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
10724
11032
  GRAPH_API_VERSION = "v21.0";
10725
11033
  }
10726
11034
  });
@@ -10783,9 +11091,9 @@ var init_crm_webhook = __esm({
10783
11091
  });
10784
11092
 
10785
11093
  // src/bin/exe-gateway.ts
10786
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
10787
- import path21 from "path";
10788
- import os13 from "os";
11094
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
11095
+ import path22 from "path";
11096
+ import os14 from "os";
10789
11097
 
10790
11098
  // src/gateway/webhook-server.ts
10791
11099
  import {
@@ -11027,11 +11335,11 @@ init_crm_bridge();
11027
11335
 
11028
11336
  // src/lib/pipeline-router.ts
11029
11337
  init_database();
11030
- import crypto2 from "crypto";
11338
+ import crypto3 from "crypto";
11031
11339
  async function sinkConversationStore(msg, agentResponse, agentName) {
11032
11340
  try {
11033
11341
  const client = getClient();
11034
- const id = crypto2.randomUUID();
11342
+ const id = crypto3.randomUUID();
11035
11343
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
11036
11344
  await client.execute({
11037
11345
  sql: `INSERT INTO conversations
@@ -11081,7 +11389,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
11081
11389
  ].filter(Boolean).join("\n");
11082
11390
  const vector = await embed2(rawText);
11083
11391
  await writeMemory2({
11084
- id: crypto2.randomUUID(),
11392
+ id: crypto3.randomUUID(),
11085
11393
  agent_id: agentName ?? "gateway",
11086
11394
  agent_role: "gateway",
11087
11395
  session_id: `gateway-${msg.platform}`,
@@ -11751,18 +12059,18 @@ var BotRegistry = class {
11751
12059
 
11752
12060
  // src/bin/exe-gateway.ts
11753
12061
  init_employees();
11754
- var CONFIG_DIR = path21.join(os13.homedir(), ".exe-os");
11755
- var CONFIG_PATH3 = path21.join(CONFIG_DIR, "gateway.json");
12062
+ var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path22.join(os14.homedir(), ".exe-os");
12063
+ var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path22.join(CONFIG_DIR, "gateway.json");
11756
12064
  var DEFAULT_PORT = 3100;
11757
12065
  function loadConfig2() {
11758
- if (!existsSync16(CONFIG_PATH3)) {
12066
+ if (!existsSync18(CONFIG_PATH3)) {
11759
12067
  console.log(
11760
12068
  `[exe-gateway] No config at ${CONFIG_PATH3} \u2014 using defaults (port ${DEFAULT_PORT})`
11761
12069
  );
11762
12070
  return {};
11763
12071
  }
11764
12072
  try {
11765
- const raw = readFileSync14(CONFIG_PATH3, "utf-8");
12073
+ const raw = readFileSync15(CONFIG_PATH3, "utf-8");
11766
12074
  return JSON.parse(raw);
11767
12075
  } catch (err) {
11768
12076
  console.error(