@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -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,56 +5526,130 @@ 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;
5612
+ }
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
+ };
5623
+ } catch {
5624
+ return null;
5625
+ }
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
+ }
5390
5647
  }
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" };
5396
5648
  } 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" };
5402
5649
  }
5650
+ const rawFallback = getRawCachedPlan();
5651
+ if (rawFallback) return rawFallback;
5652
+ return { ...FREE_LICENSE, valid: false };
5403
5653
  }
5404
5654
  function getCacheAgeMs() {
5405
5655
  try {
@@ -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,13 @@ var init_session_kill_telemetry = __esm({
7519
7809
  }
7520
7810
  });
7521
7811
 
7522
- // src/lib/task-scope.ts
7523
- function getCurrentSessionScope() {
7524
- try {
7525
- return resolveExeSession();
7526
- } catch {
7527
- return null;
7528
- }
7529
- }
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
- };
7538
- }
7539
- var init_task_scope = __esm({
7540
- "src/lib/task-scope.ts"() {
7541
- "use strict";
7542
- init_tmux_routing();
7543
- }
7544
- });
7545
-
7546
7812
  // src/lib/tasks-crud.ts
7547
- import crypto5 from "crypto";
7548
- import path14 from "path";
7549
- import os10 from "os";
7813
+ import crypto6 from "crypto";
7814
+ import path15 from "path";
7815
+ import os11 from "os";
7550
7816
  import { execSync as execSync4 } from "child_process";
7551
7817
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
7552
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
7818
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
7553
7819
  async function writeCheckpoint(input) {
7554
7820
  const client = getClient();
7555
7821
  const row = await resolveTask(client, input.taskId);
@@ -7665,7 +7931,7 @@ async function resolveTask(client, identifier, scopeSession) {
7665
7931
  }
7666
7932
  async function createTaskCore(input) {
7667
7933
  const client = getClient();
7668
- const id = crypto5.randomUUID();
7934
+ const id = crypto6.randomUUID();
7669
7935
  const now = (/* @__PURE__ */ new Date()).toISOString();
7670
7936
  const slug = slugify(input.title);
7671
7937
  let earlySessionScope = null;
@@ -7724,8 +7990,8 @@ ${laneWarning}` : laneWarning;
7724
7990
  }
7725
7991
  if (input.baseDir) {
7726
7992
  try {
7727
- await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
7728
- await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
7993
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
7994
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
7729
7995
  await ensureArchitectureDoc(input.baseDir, input.projectName);
7730
7996
  await ensureGitignoreExe(input.baseDir);
7731
7997
  } catch {
@@ -7761,13 +8027,19 @@ ${laneWarning}` : laneWarning;
7761
8027
  });
7762
8028
  if (input.baseDir) {
7763
8029
  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 });
8030
+ const EXE_OS_DIR = path15.join(os11.homedir(), ".exe-os");
8031
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
8032
+ const mdDir = path15.dirname(mdPath);
8033
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
7768
8034
  const reviewer = input.reviewer ?? input.assignedBy;
7769
8035
  const mdContent = `# ${input.title}
7770
8036
 
8037
+ ## MANDATORY: When done
8038
+
8039
+ You MUST call update_task with status "done" and a result summary when finished.
8040
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
8041
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
8042
+
7771
8043
  **ID:** ${id}
7772
8044
  **Status:** ${initialStatus}
7773
8045
  **Priority:** ${input.priority}
@@ -7781,12 +8053,6 @@ ${laneWarning}` : laneWarning;
7781
8053
  ## Context
7782
8054
 
7783
8055
  ${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
8056
  `;
7791
8057
  await writeFile4(mdPath, mdContent, "utf-8");
7792
8058
  } catch (err) {
@@ -8035,7 +8301,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
8035
8301
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
8036
8302
  } catch {
8037
8303
  }
8038
- if (input.status === "done" || input.status === "cancelled") {
8304
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
8039
8305
  try {
8040
8306
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
8041
8307
  clearQueueForAgent2(String(row.assigned_to));
@@ -8064,9 +8330,9 @@ async function deleteTaskCore(taskId, _baseDir) {
8064
8330
  return { taskFile, assignedTo, assignedBy, taskSlug };
8065
8331
  }
8066
8332
  async function ensureArchitectureDoc(baseDir, projectName) {
8067
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
8333
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
8068
8334
  try {
8069
- if (existsSync12(archPath)) return;
8335
+ if (existsSync14(archPath)) return;
8070
8336
  const template = [
8071
8337
  `# ${projectName} \u2014 System Architecture`,
8072
8338
  "",
@@ -8099,10 +8365,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
8099
8365
  }
8100
8366
  }
8101
8367
  async function ensureGitignoreExe(baseDir) {
8102
- const gitignorePath = path14.join(baseDir, ".gitignore");
8368
+ const gitignorePath = path15.join(baseDir, ".gitignore");
8103
8369
  try {
8104
- if (existsSync12(gitignorePath)) {
8105
- const content = readFileSync11(gitignorePath, "utf-8");
8370
+ if (existsSync14(gitignorePath)) {
8371
+ const content = readFileSync12(gitignorePath, "utf-8");
8106
8372
  if (/^\/?exe\/?$/m.test(content)) return;
8107
8373
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
8108
8374
  } else {
@@ -8133,58 +8399,42 @@ var init_tasks_crud = __esm({
8133
8399
  });
8134
8400
 
8135
8401
  // src/lib/tasks-review.ts
8136
- import path15 from "path";
8137
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
8402
+ import path16 from "path";
8403
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
8138
8404
  async function countPendingReviews(sessionScope) {
8139
8405
  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
- }
8406
+ const scope = strictSessionScopeFilter(
8407
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8408
+ );
8147
8409
  const result = await client.execute({
8148
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
8149
- args: []
8410
+ sql: `SELECT COUNT(*) as cnt FROM tasks
8411
+ WHERE status = 'needs_review'${scope.sql}`,
8412
+ args: [...scope.args]
8150
8413
  });
8151
8414
  return Number(result.rows[0]?.cnt) || 0;
8152
8415
  }
8153
8416
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
8154
8417
  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
- }
8418
+ const scope = strictSessionScopeFilter(
8419
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8420
+ );
8164
8421
  const result = await client.execute({
8165
8422
  sql: `SELECT COUNT(*) as cnt FROM tasks
8166
- WHERE status = 'needs_review' AND updated_at > ?`,
8167
- args: [sinceIso]
8423
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
8424
+ args: [sinceIso, ...scope.args]
8168
8425
  });
8169
8426
  return Number(result.rows[0]?.cnt) || 0;
8170
8427
  }
8171
8428
  async function listPendingReviews(limit, sessionScope) {
8172
8429
  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
- }
8430
+ const scope = strictSessionScopeFilter(
8431
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
8432
+ );
8183
8433
  const result = await client.execute({
8184
8434
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
8185
- WHERE status = 'needs_review'
8435
+ WHERE status = 'needs_review'${scope.sql}
8186
8436
  ORDER BY updated_at ASC LIMIT ?`,
8187
- args: [limit]
8437
+ args: [...scope.args, limit]
8188
8438
  });
8189
8439
  return result.rows;
8190
8440
  }
@@ -8196,7 +8446,7 @@ async function cleanupOrphanedReviews() {
8196
8446
  WHERE status IN ('open', 'needs_review', 'in_progress')
8197
8447
  AND assigned_by = 'system'
8198
8448
  AND title LIKE 'Review:%'
8199
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
8449
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
8200
8450
  args: [now]
8201
8451
  });
8202
8452
  const r1b = await client.execute({
@@ -8315,11 +8565,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
8315
8565
  );
8316
8566
  }
8317
8567
  try {
8318
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
8319
- if (existsSync13(cacheDir)) {
8568
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
8569
+ if (existsSync15(cacheDir)) {
8320
8570
  for (const f of readdirSync3(cacheDir)) {
8321
8571
  if (f.startsWith("review-notified-")) {
8322
- unlinkSync4(path15.join(cacheDir, f));
8572
+ unlinkSync4(path16.join(cacheDir, f));
8323
8573
  }
8324
8574
  }
8325
8575
  }
@@ -8336,11 +8586,12 @@ var init_tasks_review = __esm({
8336
8586
  init_tmux_routing();
8337
8587
  init_session_key();
8338
8588
  init_state_bus();
8589
+ init_task_scope();
8339
8590
  }
8340
8591
  });
8341
8592
 
8342
8593
  // src/lib/tasks-chain.ts
8343
- import path16 from "path";
8594
+ import path17 from "path";
8344
8595
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
8345
8596
  async function cascadeUnblock(taskId, baseDir, now) {
8346
8597
  const client = getClient();
@@ -8357,7 +8608,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
8357
8608
  });
8358
8609
  for (const ur of unblockedRows.rows) {
8359
8610
  try {
8360
- const ubFile = path16.join(baseDir, String(ur.task_file));
8611
+ const ubFile = path17.join(baseDir, String(ur.task_file));
8361
8612
  let ubContent = await readFile4(ubFile, "utf-8");
8362
8613
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
8363
8614
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -8392,7 +8643,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
8392
8643
  const scScope = sessionScopeFilter();
8393
8644
  const remaining = await client.execute({
8394
8645
  sql: `SELECT COUNT(*) as cnt FROM tasks
8395
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
8646
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
8396
8647
  args: [parentTaskId, ...scScope.args]
8397
8648
  });
8398
8649
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -8426,7 +8677,7 @@ var init_tasks_chain = __esm({
8426
8677
 
8427
8678
  // src/lib/project-name.ts
8428
8679
  import { execSync as execSync5 } from "child_process";
8429
- import path17 from "path";
8680
+ import path18 from "path";
8430
8681
  function getProjectName(cwd) {
8431
8682
  const dir = cwd ?? process.cwd();
8432
8683
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -8439,7 +8690,7 @@ function getProjectName(cwd) {
8439
8690
  timeout: 2e3,
8440
8691
  stdio: ["pipe", "pipe", "pipe"]
8441
8692
  }).trim();
8442
- repoRoot = path17.dirname(gitCommonDir);
8693
+ repoRoot = path18.dirname(gitCommonDir);
8443
8694
  } catch {
8444
8695
  repoRoot = execSync5("git rev-parse --show-toplevel", {
8445
8696
  cwd: dir,
@@ -8448,11 +8699,11 @@ function getProjectName(cwd) {
8448
8699
  stdio: ["pipe", "pipe", "pipe"]
8449
8700
  }).trim();
8450
8701
  }
8451
- _cached2 = path17.basename(repoRoot);
8702
+ _cached2 = path18.basename(repoRoot);
8452
8703
  _cachedCwd = dir;
8453
8704
  return _cached2;
8454
8705
  } catch {
8455
- _cached2 = path17.basename(dir);
8706
+ _cached2 = path18.basename(dir);
8456
8707
  _cachedCwd = dir;
8457
8708
  return _cached2;
8458
8709
  }
@@ -8595,10 +8846,10 @@ var init_tasks_notify = __esm({
8595
8846
  });
8596
8847
 
8597
8848
  // src/lib/behaviors.ts
8598
- import crypto6 from "crypto";
8849
+ import crypto7 from "crypto";
8599
8850
  async function storeBehavior(opts) {
8600
8851
  const client = getClient();
8601
- const id = crypto6.randomUUID();
8852
+ const id = crypto7.randomUUID();
8602
8853
  const now = (/* @__PURE__ */ new Date()).toISOString();
8603
8854
  await client.execute({
8604
8855
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -8627,7 +8878,7 @@ __export(skill_learning_exports, {
8627
8878
  storeTrajectory: () => storeTrajectory,
8628
8879
  sweepTrajectories: () => sweepTrajectories
8629
8880
  });
8630
- import crypto7 from "crypto";
8881
+ import crypto8 from "crypto";
8631
8882
  async function extractTrajectory(taskId, agentId) {
8632
8883
  const client = getClient();
8633
8884
  const result = await client.execute({
@@ -8656,11 +8907,11 @@ async function extractTrajectory(taskId, agentId) {
8656
8907
  return signature;
8657
8908
  }
8658
8909
  function hashSignature(signature) {
8659
- return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
8910
+ return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
8660
8911
  }
8661
8912
  async function storeTrajectory(opts) {
8662
8913
  const client = getClient();
8663
- const id = crypto7.randomUUID();
8914
+ const id = crypto8.randomUUID();
8664
8915
  const now = (/* @__PURE__ */ new Date()).toISOString();
8665
8916
  const signatureHash = hashSignature(opts.signature);
8666
8917
  await client.execute({
@@ -8925,8 +9176,8 @@ __export(tasks_exports, {
8925
9176
  updateTaskStatus: () => updateTaskStatus,
8926
9177
  writeCheckpoint: () => writeCheckpoint
8927
9178
  });
8928
- import path18 from "path";
8929
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
9179
+ import path19 from "path";
9180
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
8930
9181
  async function createTask(input) {
8931
9182
  const result = await createTaskCore(input);
8932
9183
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -8945,12 +9196,12 @@ async function updateTask(input) {
8945
9196
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
8946
9197
  try {
8947
9198
  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`);
9199
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
9200
+ const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
8950
9201
  if (input.status === "in_progress") {
8951
9202
  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") {
9203
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
9204
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
8954
9205
  try {
8955
9206
  unlinkSync5(cachePath);
8956
9207
  } catch {
@@ -8958,10 +9209,10 @@ async function updateTask(input) {
8958
9209
  }
8959
9210
  } catch {
8960
9211
  }
8961
- if (input.status === "done") {
9212
+ if (input.status === "done" || input.status === "closed") {
8962
9213
  await cleanupReviewFile(row, taskFile, input.baseDir);
8963
9214
  }
8964
- if (input.status === "done" || input.status === "cancelled") {
9215
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
8965
9216
  try {
8966
9217
  const client = getClient();
8967
9218
  const taskTitle = String(row.title);
@@ -8977,7 +9228,7 @@ async function updateTask(input) {
8977
9228
  if (!isCoordinatorName(assignedAgent)) {
8978
9229
  try {
8979
9230
  const draftClient = getClient();
8980
- if (input.status === "done") {
9231
+ if (input.status === "done" || input.status === "closed") {
8981
9232
  await draftClient.execute({
8982
9233
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
8983
9234
  args: [assignedAgent]
@@ -8994,7 +9245,7 @@ async function updateTask(input) {
8994
9245
  try {
8995
9246
  const client = getClient();
8996
9247
  const cascaded = await client.execute({
8997
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
9248
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
8998
9249
  WHERE parent_task_id = ? AND status = 'needs_review'`,
8999
9250
  args: [now, taskId]
9000
9251
  });
@@ -9007,14 +9258,14 @@ async function updateTask(input) {
9007
9258
  } catch {
9008
9259
  }
9009
9260
  }
9010
- const isTerminal = input.status === "done" || input.status === "needs_review";
9261
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
9011
9262
  if (isTerminal) {
9012
9263
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
9013
9264
  if (!isCoordinator) {
9014
9265
  notifyTaskDone();
9015
9266
  }
9016
9267
  await markTaskNotificationsRead(taskFile);
9017
- if (input.status === "done") {
9268
+ if (input.status === "done" || input.status === "closed") {
9018
9269
  try {
9019
9270
  await cascadeUnblock(taskId, input.baseDir, now);
9020
9271
  } catch {
@@ -9034,7 +9285,7 @@ async function updateTask(input) {
9034
9285
  }
9035
9286
  }
9036
9287
  }
9037
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
9288
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
9038
9289
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
9039
9290
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
9040
9291
  taskId,
@@ -9406,6 +9657,7 @@ __export(tmux_routing_exports, {
9406
9657
  isEmployeeAlive: () => isEmployeeAlive,
9407
9658
  isExeSession: () => isExeSession,
9408
9659
  isSessionBusy: () => isSessionBusy,
9660
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
9409
9661
  notifyParentExe: () => notifyParentExe,
9410
9662
  parseParentExe: () => parseParentExe,
9411
9663
  registerParentExe: () => registerParentExe,
@@ -9416,13 +9668,13 @@ __export(tmux_routing_exports, {
9416
9668
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
9417
9669
  });
9418
9670
  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";
9671
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
9672
+ import path20 from "path";
9673
+ import os12 from "os";
9422
9674
  import { fileURLToPath as fileURLToPath2 } from "url";
9423
9675
  import { unlinkSync as unlinkSync6 } from "fs";
9424
9676
  function spawnLockPath(sessionName) {
9425
- return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
9677
+ return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
9426
9678
  }
9427
9679
  function isProcessAlive(pid) {
9428
9680
  try {
@@ -9433,13 +9685,13 @@ function isProcessAlive(pid) {
9433
9685
  }
9434
9686
  }
9435
9687
  function acquireSpawnLock2(sessionName) {
9436
- if (!existsSync14(SPAWN_LOCK_DIR)) {
9688
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
9437
9689
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
9438
9690
  }
9439
9691
  const lockFile = spawnLockPath(sessionName);
9440
- if (existsSync14(lockFile)) {
9692
+ if (existsSync16(lockFile)) {
9441
9693
  try {
9442
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
9694
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
9443
9695
  const age = Date.now() - lock.timestamp;
9444
9696
  if (isProcessAlive(lock.pid) && age < 6e4) {
9445
9697
  return false;
@@ -9447,7 +9699,7 @@ function acquireSpawnLock2(sessionName) {
9447
9699
  } catch {
9448
9700
  }
9449
9701
  }
9450
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
9702
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
9451
9703
  return true;
9452
9704
  }
9453
9705
  function releaseSpawnLock2(sessionName) {
@@ -9459,13 +9711,13 @@ function releaseSpawnLock2(sessionName) {
9459
9711
  function resolveBehaviorsExporterScript() {
9460
9712
  try {
9461
9713
  const thisFile = fileURLToPath2(import.meta.url);
9462
- const scriptPath = path19.join(
9463
- path19.dirname(thisFile),
9714
+ const scriptPath = path20.join(
9715
+ path20.dirname(thisFile),
9464
9716
  "..",
9465
9717
  "bin",
9466
9718
  "exe-export-behaviors.js"
9467
9719
  );
9468
- return existsSync14(scriptPath) ? scriptPath : null;
9720
+ return existsSync16(scriptPath) ? scriptPath : null;
9469
9721
  } catch {
9470
9722
  return null;
9471
9723
  }
@@ -9531,12 +9783,12 @@ function extractRootExe(name) {
9531
9783
  return parts.length > 0 ? parts[parts.length - 1] : null;
9532
9784
  }
9533
9785
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
9534
- if (!existsSync14(SESSION_CACHE)) {
9786
+ if (!existsSync16(SESSION_CACHE)) {
9535
9787
  mkdirSync8(SESSION_CACHE, { recursive: true });
9536
9788
  }
9537
9789
  const rootExe = extractRootExe(parentExe) ?? parentExe;
9538
- const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
9539
- writeFileSync7(filePath, JSON.stringify({
9790
+ const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
9791
+ writeFileSync8(filePath, JSON.stringify({
9540
9792
  parentExe: rootExe,
9541
9793
  dispatchedBy: dispatchedBy || rootExe,
9542
9794
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -9544,7 +9796,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
9544
9796
  }
9545
9797
  function getParentExe(sessionKey) {
9546
9798
  try {
9547
- const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
9799
+ const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
9548
9800
  return data.parentExe || null;
9549
9801
  } catch {
9550
9802
  return null;
@@ -9552,8 +9804,8 @@ function getParentExe(sessionKey) {
9552
9804
  }
9553
9805
  function getDispatchedBy(sessionKey) {
9554
9806
  try {
9555
- const data = JSON.parse(readFileSync12(
9556
- path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
9807
+ const data = JSON.parse(readFileSync13(
9808
+ path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
9557
9809
  "utf8"
9558
9810
  ));
9559
9811
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -9623,8 +9875,8 @@ async function verifyPaneAtCapacity(sessionName) {
9623
9875
  }
9624
9876
  function readDebounceState() {
9625
9877
  try {
9626
- if (!existsSync14(DEBOUNCE_FILE)) return {};
9627
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
9878
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
9879
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
9628
9880
  const state = {};
9629
9881
  for (const [key, val] of Object.entries(raw)) {
9630
9882
  if (typeof val === "number") {
@@ -9640,8 +9892,8 @@ function readDebounceState() {
9640
9892
  }
9641
9893
  function writeDebounceState(state) {
9642
9894
  try {
9643
- if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
9644
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
9895
+ if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
9896
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
9645
9897
  } catch {
9646
9898
  }
9647
9899
  }
@@ -9739,8 +9991,8 @@ function sendIntercom(targetSession) {
9739
9991
  try {
9740
9992
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
9741
9993
  const agent = baseAgentName(rawAgent);
9742
- const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
9743
- if (existsSync14(markerPath)) {
9994
+ const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
9995
+ if (existsSync16(markerPath)) {
9744
9996
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
9745
9997
  return "debounced";
9746
9998
  }
@@ -9749,8 +10001,8 @@ function sendIntercom(targetSession) {
9749
10001
  try {
9750
10002
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
9751
10003
  const agent = baseAgentName(rawAgent);
9752
- const taskDir = path19.join(process.cwd(), "exe", agent);
9753
- if (existsSync14(taskDir)) {
10004
+ const taskDir = path20.join(process.cwd(), "exe", agent);
10005
+ if (existsSync16(taskDir)) {
9754
10006
  const files = readdirSync4(taskDir).filter(
9755
10007
  (f) => f.endsWith(".md") && f !== "DONE.txt"
9756
10008
  );
@@ -9810,6 +10062,21 @@ function notifyParentExe(sessionKey) {
9810
10062
  }
9811
10063
  return true;
9812
10064
  }
10065
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
10066
+ const transport = getTransport();
10067
+ try {
10068
+ const sessions = transport.listSessions();
10069
+ if (!sessions.includes(coordinatorSession)) return false;
10070
+ execSync6(
10071
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
10072
+ { timeout: 3e3 }
10073
+ );
10074
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
10075
+ return true;
10076
+ } catch {
10077
+ return false;
10078
+ }
10079
+ }
9813
10080
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
9814
10081
  if (isCoordinatorName(employeeName)) {
9815
10082
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -9883,26 +10150,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9883
10150
  const transport = getTransport();
9884
10151
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
9885
10152
  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)) {
10153
+ const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
10154
+ const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
10155
+ if (!existsSync16(logDir)) {
9889
10156
  mkdirSync8(logDir, { recursive: true });
9890
10157
  }
9891
10158
  transport.kill(sessionName);
9892
10159
  let cleanupSuffix = "";
9893
10160
  try {
9894
10161
  const thisFile = fileURLToPath2(import.meta.url);
9895
- const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
9896
- if (existsSync14(cleanupScript)) {
10162
+ const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
10163
+ if (existsSync16(cleanupScript)) {
9897
10164
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
9898
10165
  }
9899
10166
  } catch {
9900
10167
  }
9901
10168
  try {
9902
- const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
10169
+ const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
9903
10170
  let claudeJson = {};
9904
10171
  try {
9905
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
10172
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
9906
10173
  } catch {
9907
10174
  }
9908
10175
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -9910,17 +10177,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9910
10177
  const trustDir = opts?.cwd ?? projectDir;
9911
10178
  if (!projects[trustDir]) projects[trustDir] = {};
9912
10179
  projects[trustDir].hasTrustDialogAccepted = true;
9913
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
10180
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
9914
10181
  } catch {
9915
10182
  }
9916
10183
  try {
9917
- const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
10184
+ const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
9918
10185
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
9919
- const projSettingsDir = path19.join(settingsDir, normalizedKey);
9920
- const settingsPath = path19.join(projSettingsDir, "settings.json");
10186
+ const projSettingsDir = path20.join(settingsDir, normalizedKey);
10187
+ const settingsPath = path20.join(projSettingsDir, "settings.json");
9921
10188
  let settings = {};
9922
10189
  try {
9923
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
10190
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
9924
10191
  } catch {
9925
10192
  }
9926
10193
  const perms = settings.permissions ?? {};
@@ -9949,7 +10216,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9949
10216
  perms.allow = allow;
9950
10217
  settings.permissions = perms;
9951
10218
  mkdirSync8(projSettingsDir, { recursive: true });
9952
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
10219
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
9953
10220
  }
9954
10221
  } catch {
9955
10222
  }
@@ -9964,8 +10231,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9964
10231
  let behaviorsFlag = "";
9965
10232
  let legacyFallbackWarned = false;
9966
10233
  if (!useExeAgent && !useBinSymlink) {
9967
- const identityPath = path19.join(
9968
- os11.homedir(),
10234
+ const identityPath = path20.join(
10235
+ os12.homedir(),
9969
10236
  ".exe-os",
9970
10237
  "identity",
9971
10238
  `${employeeName}.md`
@@ -9974,13 +10241,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9974
10241
  const hasAgentFlag = claudeSupportsAgentFlag();
9975
10242
  if (hasAgentFlag) {
9976
10243
  identityFlag = ` --agent ${employeeName}`;
9977
- } else if (existsSync14(identityPath)) {
10244
+ } else if (existsSync16(identityPath)) {
9978
10245
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
9979
10246
  legacyFallbackWarned = true;
9980
10247
  }
9981
10248
  const behaviorsFile = exportBehaviorsSync(
9982
10249
  employeeName,
9983
- path19.basename(spawnCwd),
10250
+ path20.basename(spawnCwd),
9984
10251
  sessionName
9985
10252
  );
9986
10253
  if (behaviorsFile) {
@@ -9995,16 +10262,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9995
10262
  }
9996
10263
  let sessionContextFlag = "";
9997
10264
  try {
9998
- const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
10265
+ const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
9999
10266
  mkdirSync8(ctxDir, { recursive: true });
10000
- const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
10267
+ const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
10001
10268
  const ctxContent = [
10002
10269
  `## Session Context`,
10003
10270
  `You are running in tmux session: ${sessionName}.`,
10004
10271
  `Your parent coordinator session is ${exeSession}.`,
10005
10272
  `Your employees (if any) use the -${exeSession} suffix.`
10006
10273
  ].join("\n");
10007
- writeFileSync7(ctxFile, ctxContent);
10274
+ writeFileSync8(ctxFile, ctxContent);
10008
10275
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
10009
10276
  } catch {
10010
10277
  }
@@ -10081,8 +10348,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
10081
10348
  transport.pipeLog(sessionName, logFile);
10082
10349
  try {
10083
10350
  const mySession = getMySession();
10084
- const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
10085
- writeFileSync7(dispatchInfo, JSON.stringify({
10351
+ const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
10352
+ writeFileSync8(dispatchInfo, JSON.stringify({
10086
10353
  dispatchedBy: mySession,
10087
10354
  rootExe: exeSession,
10088
10355
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -10156,15 +10423,15 @@ var init_tmux_routing = __esm({
10156
10423
  init_intercom_queue();
10157
10424
  init_plan_limits();
10158
10425
  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");
10426
+ SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
10427
+ SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
10161
10428
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
10162
10429
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
10163
10430
  VERIFY_PANE_LINES = 200;
10164
10431
  INTERCOM_DEBOUNCE_MS = 3e4;
10165
10432
  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");
10433
+ INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
10434
+ DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
10168
10435
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
10169
10436
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
10170
10437
  }
@@ -10187,10 +10454,10 @@ __export(messaging_exports, {
10187
10454
  sendMessage: () => sendMessage,
10188
10455
  setWsClientSend: () => setWsClientSend
10189
10456
  });
10190
- import crypto8 from "crypto";
10457
+ import crypto9 from "crypto";
10191
10458
  function generateUlid() {
10192
10459
  const timestamp = Date.now().toString(36).padStart(10, "0");
10193
- const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
10460
+ const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
10194
10461
  return (timestamp + random).toUpperCase();
10195
10462
  }
10196
10463
  function rowToMessage(row) {
@@ -10201,6 +10468,7 @@ function rowToMessage(row) {
10201
10468
  targetAgent: row.target_agent,
10202
10469
  targetProject: row.target_project ?? null,
10203
10470
  targetDevice: row.target_device,
10471
+ sessionScope: row.session_scope ?? null,
10204
10472
  content: row.content,
10205
10473
  priority: row.priority ?? "normal",
10206
10474
  status: row.status ?? "pending",
@@ -10218,15 +10486,17 @@ async function sendMessage(input) {
10218
10486
  const id = generateUlid();
10219
10487
  const now = (/* @__PURE__ */ new Date()).toISOString();
10220
10488
  const targetDevice = input.targetDevice ?? "local";
10489
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
10221
10490
  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', ?)`,
10491
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
10492
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
10224
10493
  args: [
10225
10494
  id,
10226
10495
  input.fromAgent,
10227
10496
  input.targetAgent,
10228
10497
  input.targetProject ?? null,
10229
10498
  targetDevice,
10499
+ sessionScope,
10230
10500
  input.content,
10231
10501
  input.priority ?? "normal",
10232
10502
  now
@@ -10240,9 +10510,10 @@ async function sendMessage(input) {
10240
10510
  }
10241
10511
  } catch {
10242
10512
  }
10513
+ const sentScope = strictSessionScopeFilter(sessionScope);
10243
10514
  const result = await client.execute({
10244
- sql: "SELECT * FROM messages WHERE id = ?",
10245
- args: [id]
10515
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
10516
+ args: [id, ...sentScope.args]
10246
10517
  });
10247
10518
  return rowToMessage(result.rows[0]);
10248
10519
  }
@@ -10266,6 +10537,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
10266
10537
  fromAgent: msg.fromAgent,
10267
10538
  targetAgent: msg.targetAgent,
10268
10539
  targetProject: msg.targetProject,
10540
+ sessionScope: msg.sessionScope,
10269
10541
  content: msg.content,
10270
10542
  priority: msg.priority,
10271
10543
  createdAt: msg.createdAt
@@ -10309,7 +10581,7 @@ async function deliverLocalMessage(messageId) {
10309
10581
  } catch {
10310
10582
  const newRetryCount = msg.retryCount + 1;
10311
10583
  if (newRetryCount >= MAX_RETRIES3) {
10312
- await markFailed(messageId, "session unavailable after 10 retries");
10584
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
10313
10585
  } else {
10314
10586
  await client.execute({
10315
10587
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -10319,85 +10591,101 @@ async function deliverLocalMessage(messageId) {
10319
10591
  return false;
10320
10592
  }
10321
10593
  }
10322
- async function getPendingMessages(targetAgent) {
10594
+ async function getPendingMessages(targetAgent, sessionScope) {
10323
10595
  const client = getClient();
10596
+ const scope = strictSessionScopeFilter(sessionScope);
10324
10597
  const result = await client.execute({
10325
10598
  sql: `SELECT * FROM messages
10326
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
10599
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
10327
10600
  ORDER BY id`,
10328
- args: [targetAgent]
10601
+ args: [targetAgent, ...scope.args]
10329
10602
  });
10330
10603
  return result.rows.map((row) => rowToMessage(row));
10331
10604
  }
10332
- async function markRead(messageId) {
10605
+ async function markRead(messageId, sessionScope) {
10333
10606
  const client = getClient();
10607
+ const scope = strictSessionScopeFilter(sessionScope);
10334
10608
  await client.execute({
10335
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
10336
- args: [messageId]
10609
+ sql: `UPDATE messages SET status = 'read'
10610
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
10611
+ args: [messageId, ...scope.args]
10337
10612
  });
10338
10613
  }
10339
- async function markAcknowledged(messageId) {
10614
+ async function markAcknowledged(messageId, sessionScope) {
10340
10615
  const client = getClient();
10616
+ const scope = strictSessionScopeFilter(sessionScope);
10341
10617
  await client.execute({
10342
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
10343
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10618
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
10619
+ WHERE id = ? AND status = 'read'${scope.sql}`,
10620
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
10344
10621
  });
10345
10622
  }
10346
- async function markProcessed(messageId) {
10623
+ async function markProcessed(messageId, sessionScope) {
10347
10624
  const client = getClient();
10625
+ const scope = strictSessionScopeFilter(sessionScope);
10348
10626
  await client.execute({
10349
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
10350
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
10627
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
10628
+ WHERE id = ?${scope.sql}`,
10629
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
10351
10630
  });
10352
10631
  }
10353
- async function getMessageStatus(messageId) {
10632
+ async function getMessageStatus(messageId, sessionScope) {
10354
10633
  const client = getClient();
10634
+ const scope = strictSessionScopeFilter(sessionScope);
10355
10635
  const result = await client.execute({
10356
- sql: "SELECT status FROM messages WHERE id = ?",
10357
- args: [messageId]
10636
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
10637
+ args: [messageId, ...scope.args]
10358
10638
  });
10359
10639
  return result.rows[0]?.status ?? null;
10360
10640
  }
10361
- async function getUnacknowledgedMessages(targetAgent) {
10641
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
10362
10642
  const client = getClient();
10643
+ const scope = strictSessionScopeFilter(sessionScope);
10363
10644
  const result = await client.execute({
10364
10645
  sql: `SELECT * FROM messages
10365
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
10646
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
10366
10647
  ORDER BY id`,
10367
- args: [targetAgent]
10648
+ args: [targetAgent, ...scope.args]
10368
10649
  });
10369
10650
  return result.rows.map((row) => rowToMessage(row));
10370
10651
  }
10371
- async function getReadMessages(targetAgent) {
10652
+ async function getReadMessages(targetAgent, sessionScope) {
10372
10653
  const client = getClient();
10654
+ const scope = strictSessionScopeFilter(sessionScope);
10373
10655
  const result = await client.execute({
10374
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
10375
- args: [targetAgent]
10656
+ sql: `SELECT * FROM messages
10657
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
10658
+ ORDER BY id`,
10659
+ args: [targetAgent, ...scope.args]
10376
10660
  });
10377
10661
  return result.rows.map((row) => rowToMessage(row));
10378
10662
  }
10379
- async function markFailed(messageId, reason) {
10663
+ async function markFailed(messageId, reason, sessionScope) {
10380
10664
  const client = getClient();
10665
+ const scope = strictSessionScopeFilter(sessionScope);
10381
10666
  await client.execute({
10382
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
10383
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
10667
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
10668
+ WHERE id = ?${scope.sql}`,
10669
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
10384
10670
  });
10385
10671
  }
10386
- async function getFailedMessages() {
10672
+ async function getFailedMessages(sessionScope) {
10387
10673
  const client = getClient();
10674
+ const scope = strictSessionScopeFilter(sessionScope);
10388
10675
  const result = await client.execute({
10389
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
10390
- args: []
10676
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
10677
+ args: [...scope.args]
10391
10678
  });
10392
10679
  return result.rows.map((row) => rowToMessage(row));
10393
10680
  }
10394
- async function retryPendingMessages() {
10681
+ async function retryPendingMessages(sessionScope) {
10395
10682
  const client = getClient();
10683
+ const scope = strictSessionScopeFilter(sessionScope);
10396
10684
  const result = await client.execute({
10397
10685
  sql: `SELECT * FROM messages
10398
- WHERE status = 'pending' AND retry_count < ?
10686
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
10399
10687
  ORDER BY id`,
10400
- args: [MAX_RETRIES3]
10688
+ args: [MAX_RETRIES3, ...scope.args]
10401
10689
  });
10402
10690
  let delivered = 0;
10403
10691
  for (const row of result.rows) {
@@ -10416,16 +10704,17 @@ var init_messaging = __esm({
10416
10704
  "use strict";
10417
10705
  init_database();
10418
10706
  init_tmux_routing();
10707
+ init_task_scope();
10419
10708
  MAX_RETRIES3 = 10;
10420
10709
  _wsClientSend = null;
10421
10710
  }
10422
10711
  });
10423
10712
 
10424
10713
  // src/automation/trigger-engine.ts
10425
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
10714
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
10426
10715
  import { randomUUID as randomUUID8 } from "crypto";
10427
- import path20 from "path";
10428
- import os12 from "os";
10716
+ import path21 from "path";
10717
+ import os13 from "os";
10429
10718
  function substituteTemplate(template, record) {
10430
10719
  return template.replace(
10431
10720
  /\{\{(\w+(?:\.\w+)*)\}\}/g,
@@ -10478,9 +10767,9 @@ function evaluateConditions(conditions, record) {
10478
10767
  return conditions.every((c) => evaluateCondition(c, record));
10479
10768
  }
10480
10769
  function loadTriggers(project) {
10481
- if (!existsSync15(TRIGGERS_PATH)) return [];
10770
+ if (!existsSync17(TRIGGERS_PATH)) return [];
10482
10771
  try {
10483
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
10772
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
10484
10773
  const all = JSON.parse(raw);
10485
10774
  if (!Array.isArray(all)) return [];
10486
10775
  if (project) {
@@ -10720,7 +11009,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
10720
11009
  var init_trigger_engine = __esm({
10721
11010
  "src/automation/trigger-engine.ts"() {
10722
11011
  "use strict";
10723
- TRIGGERS_PATH = path20.join(os12.homedir(), ".exe-os", "triggers.json");
11012
+ TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
10724
11013
  GRAPH_API_VERSION = "v21.0";
10725
11014
  }
10726
11015
  });
@@ -10783,9 +11072,9 @@ var init_crm_webhook = __esm({
10783
11072
  });
10784
11073
 
10785
11074
  // 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";
11075
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
11076
+ import path22 from "path";
11077
+ import os14 from "os";
10789
11078
 
10790
11079
  // src/gateway/webhook-server.ts
10791
11080
  import {
@@ -11027,11 +11316,11 @@ init_crm_bridge();
11027
11316
 
11028
11317
  // src/lib/pipeline-router.ts
11029
11318
  init_database();
11030
- import crypto2 from "crypto";
11319
+ import crypto3 from "crypto";
11031
11320
  async function sinkConversationStore(msg, agentResponse, agentName) {
11032
11321
  try {
11033
11322
  const client = getClient();
11034
- const id = crypto2.randomUUID();
11323
+ const id = crypto3.randomUUID();
11035
11324
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
11036
11325
  await client.execute({
11037
11326
  sql: `INSERT INTO conversations
@@ -11081,7 +11370,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
11081
11370
  ].filter(Boolean).join("\n");
11082
11371
  const vector = await embed2(rawText);
11083
11372
  await writeMemory2({
11084
- id: crypto2.randomUUID(),
11373
+ id: crypto3.randomUUID(),
11085
11374
  agent_id: agentName ?? "gateway",
11086
11375
  agent_role: "gateway",
11087
11376
  session_id: `gateway-${msg.platform}`,
@@ -11751,18 +12040,18 @@ var BotRegistry = class {
11751
12040
 
11752
12041
  // src/bin/exe-gateway.ts
11753
12042
  init_employees();
11754
- var CONFIG_DIR = path21.join(os13.homedir(), ".exe-os");
11755
- var CONFIG_PATH3 = path21.join(CONFIG_DIR, "gateway.json");
12043
+ var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path22.join(os14.homedir(), ".exe-os");
12044
+ var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path22.join(CONFIG_DIR, "gateway.json");
11756
12045
  var DEFAULT_PORT = 3100;
11757
12046
  function loadConfig2() {
11758
- if (!existsSync16(CONFIG_PATH3)) {
12047
+ if (!existsSync18(CONFIG_PATH3)) {
11759
12048
  console.log(
11760
12049
  `[exe-gateway] No config at ${CONFIG_PATH3} \u2014 using defaults (port ${DEFAULT_PORT})`
11761
12050
  );
11762
12051
  return {};
11763
12052
  }
11764
12053
  try {
11765
- const raw = readFileSync14(CONFIG_PATH3, "utf-8");
12054
+ const raw = readFileSync15(CONFIG_PATH3, "utf-8");
11766
12055
  return JSON.parse(raw);
11767
12056
  } catch (err) {
11768
12057
  console.error(