@askexenow/exe-os 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1411 -953
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +913 -543
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +418 -262
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +793 -485
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +566 -357
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +530 -319
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +547 -336
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3442 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +534 -323
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +664 -431
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +1049 -680
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +448 -371
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
@@ -391,6 +391,44 @@ var init_db_retry = __esm({
391
391
  }
392
392
  });
393
393
 
394
+ // src/lib/secure-files.ts
395
+ import { chmodSync, existsSync, mkdirSync } from "fs";
396
+ import { chmod, mkdir } from "fs/promises";
397
+ async function ensurePrivateDir(dirPath) {
398
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
399
+ try {
400
+ await chmod(dirPath, PRIVATE_DIR_MODE);
401
+ } catch {
402
+ }
403
+ }
404
+ function ensurePrivateDirSync(dirPath) {
405
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
406
+ try {
407
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
408
+ } catch {
409
+ }
410
+ }
411
+ async function enforcePrivateFile(filePath) {
412
+ try {
413
+ await chmod(filePath, PRIVATE_FILE_MODE);
414
+ } catch {
415
+ }
416
+ }
417
+ function enforcePrivateFileSync(filePath) {
418
+ try {
419
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
420
+ } catch {
421
+ }
422
+ }
423
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
424
+ var init_secure_files = __esm({
425
+ "src/lib/secure-files.ts"() {
426
+ "use strict";
427
+ PRIVATE_DIR_MODE = 448;
428
+ PRIVATE_FILE_MODE = 384;
429
+ }
430
+ });
431
+
394
432
  // src/lib/config.ts
395
433
  var config_exports = {};
396
434
  __export(config_exports, {
@@ -407,8 +445,8 @@ __export(config_exports, {
407
445
  migrateConfig: () => migrateConfig,
408
446
  saveConfig: () => saveConfig
409
447
  });
410
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
411
- import { readFileSync, existsSync, renameSync } from "fs";
448
+ import { readFile, writeFile } from "fs/promises";
449
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
412
450
  import path from "path";
413
451
  import os from "os";
414
452
  function resolveDataDir() {
@@ -416,7 +454,7 @@ function resolveDataDir() {
416
454
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
417
455
  const newDir = path.join(os.homedir(), ".exe-os");
418
456
  const legacyDir = path.join(os.homedir(), ".exe-mem");
419
- if (!existsSync(newDir) && existsSync(legacyDir)) {
457
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
420
458
  try {
421
459
  renameSync(legacyDir, newDir);
422
460
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -479,9 +517,9 @@ function normalizeAutoUpdate(raw) {
479
517
  }
480
518
  async function loadConfig() {
481
519
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
482
- await mkdir(dir, { recursive: true });
520
+ await ensurePrivateDir(dir);
483
521
  const configPath = path.join(dir, "config.json");
484
- if (!existsSync(configPath)) {
522
+ if (!existsSync2(configPath)) {
485
523
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
486
524
  }
487
525
  const raw = await readFile(configPath, "utf-8");
@@ -494,6 +532,7 @@ async function loadConfig() {
494
532
  `);
495
533
  try {
496
534
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
535
+ await enforcePrivateFile(configPath);
497
536
  } catch {
498
537
  }
499
538
  }
@@ -512,7 +551,7 @@ async function loadConfig() {
512
551
  function loadConfigSync() {
513
552
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
514
553
  const configPath = path.join(dir, "config.json");
515
- if (!existsSync(configPath)) {
554
+ if (!existsSync2(configPath)) {
516
555
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
517
556
  }
518
557
  try {
@@ -530,12 +569,10 @@ function loadConfigSync() {
530
569
  }
531
570
  async function saveConfig(config2) {
532
571
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
533
- await mkdir(dir, { recursive: true });
572
+ await ensurePrivateDir(dir);
534
573
  const configPath = path.join(dir, "config.json");
535
574
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
536
- if (config2.cloud?.apiKey) {
537
- await chmod(configPath, 384);
538
- }
575
+ await enforcePrivateFile(configPath);
539
576
  }
540
577
  async function loadConfigFrom(configPath) {
541
578
  const raw = await readFile(configPath, "utf-8");
@@ -555,6 +592,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
555
592
  var init_config = __esm({
556
593
  "src/lib/config.ts"() {
557
594
  "use strict";
595
+ init_secure_files();
558
596
  EXE_AI_DIR = resolveDataDir();
559
597
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
560
598
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -671,10 +709,10 @@ __export(agent_config_exports, {
671
709
  saveAgentConfig: () => saveAgentConfig,
672
710
  setAgentRuntime: () => setAgentRuntime
673
711
  });
674
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
712
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
675
713
  import path2 from "path";
676
714
  function loadAgentConfig() {
677
- if (!existsSync2(AGENT_CONFIG_PATH)) return {};
715
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
678
716
  try {
679
717
  return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
680
718
  } catch {
@@ -683,8 +721,9 @@ function loadAgentConfig() {
683
721
  }
684
722
  function saveAgentConfig(config2) {
685
723
  const dir = path2.dirname(AGENT_CONFIG_PATH);
686
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
724
+ ensurePrivateDirSync(dir);
687
725
  writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
726
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
688
727
  }
689
728
  function getAgentRuntime(agentId) {
690
729
  const config2 = loadAgentConfig();
@@ -724,6 +763,7 @@ var init_agent_config = __esm({
724
763
  "use strict";
725
764
  init_config();
726
765
  init_runtime_table();
766
+ init_secure_files();
727
767
  AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
728
768
  KNOWN_RUNTIMES = {
729
769
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -771,7 +811,7 @@ __export(employees_exports, {
771
811
  validateEmployeeName: () => validateEmployeeName
772
812
  });
773
813
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
774
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
814
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
775
815
  import { execSync } from "child_process";
776
816
  import path3 from "path";
777
817
  import os2 from "os";
@@ -810,7 +850,7 @@ function validateEmployeeName(name) {
810
850
  return { valid: true };
811
851
  }
812
852
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
813
- if (!existsSync3(employeesPath)) {
853
+ if (!existsSync4(employeesPath)) {
814
854
  return [];
815
855
  }
816
856
  const raw = await readFile2(employeesPath, "utf-8");
@@ -825,7 +865,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
825
865
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
826
866
  }
827
867
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
828
- if (!existsSync3(employeesPath)) return [];
868
+ if (!existsSync4(employeesPath)) return [];
829
869
  try {
830
870
  return JSON.parse(readFileSync3(employeesPath, "utf-8"));
831
871
  } catch {
@@ -873,7 +913,7 @@ function appendToCoordinatorTeam(employee) {
873
913
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
874
914
  if (!coordinator) return;
875
915
  const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
876
- if (!existsSync3(idPath)) return;
916
+ if (!existsSync4(idPath)) return;
877
917
  const content = readFileSync3(idPath, "utf-8");
878
918
  if (content.includes(`**${capitalize(employee.name)}`)) return;
879
919
  const teamMatch = content.match(TEAM_SECTION_RE);
@@ -927,9 +967,9 @@ async function normalizeRosterCase(rosterPath) {
927
967
  const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
928
968
  const oldPath = path3.join(identityDir, `${oldName}.md`);
929
969
  const newPath = path3.join(identityDir, `${emp.name}.md`);
930
- if (existsSync3(oldPath) && !existsSync3(newPath)) {
970
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
931
971
  renameSync2(oldPath, newPath);
932
- } else if (existsSync3(oldPath) && oldPath !== newPath) {
972
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
933
973
  const content = readFileSync3(oldPath, "utf-8");
934
974
  writeFileSync2(newPath, content, "utf-8");
935
975
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
@@ -972,7 +1012,7 @@ function registerBinSymlinks(name) {
972
1012
  for (const suffix of ["", "-opencode"]) {
973
1013
  const linkName = `${name}${suffix}`;
974
1014
  const linkPath = path3.join(binDir, linkName);
975
- if (existsSync3(linkPath)) {
1015
+ if (existsSync4(linkPath)) {
976
1016
  skipped.push(linkName);
977
1017
  continue;
978
1018
  }
@@ -1925,6 +1965,7 @@ async function ensureSchema() {
1925
1965
  project TEXT NOT NULL,
1926
1966
  summary TEXT NOT NULL,
1927
1967
  task_file TEXT,
1968
+ session_scope TEXT,
1928
1969
  read INTEGER NOT NULL DEFAULT 0,
1929
1970
  created_at TEXT NOT NULL
1930
1971
  );
@@ -1933,7 +1974,7 @@ async function ensureSchema() {
1933
1974
  ON notifications(read);
1934
1975
 
1935
1976
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1936
- ON notifications(agent_id);
1977
+ ON notifications(agent_id, session_scope);
1937
1978
 
1938
1979
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1939
1980
  ON notifications(task_file);
@@ -1971,6 +2012,7 @@ async function ensureSchema() {
1971
2012
  target_agent TEXT NOT NULL,
1972
2013
  target_project TEXT,
1973
2014
  target_device TEXT NOT NULL DEFAULT 'local',
2015
+ session_scope TEXT,
1974
2016
  content TEXT NOT NULL,
1975
2017
  priority TEXT DEFAULT 'normal',
1976
2018
  status TEXT DEFAULT 'pending',
@@ -1984,10 +2026,31 @@ async function ensureSchema() {
1984
2026
  );
1985
2027
 
1986
2028
  CREATE INDEX IF NOT EXISTS idx_messages_target
1987
- ON messages(target_agent, status);
2029
+ ON messages(target_agent, session_scope, status);
1988
2030
 
1989
2031
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1990
- ON messages(target_agent, from_agent, server_seq);
2032
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2033
+ `);
2034
+ try {
2035
+ await client.execute({
2036
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2037
+ args: []
2038
+ });
2039
+ } catch {
2040
+ }
2041
+ try {
2042
+ await client.execute({
2043
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2044
+ args: []
2045
+ });
2046
+ } catch {
2047
+ }
2048
+ await client.executeMultiple(`
2049
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2050
+ ON notifications(agent_id, session_scope, read, created_at);
2051
+
2052
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2053
+ ON messages(target_agent, session_scope, status, created_at);
1991
2054
  `);
1992
2055
  try {
1993
2056
  await client.execute({
@@ -2571,6 +2634,13 @@ async function ensureSchema() {
2571
2634
  } catch {
2572
2635
  }
2573
2636
  }
2637
+ try {
2638
+ await client.execute({
2639
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2640
+ args: []
2641
+ });
2642
+ } catch {
2643
+ }
2574
2644
  }
2575
2645
  async function disposeDatabase() {
2576
2646
  if (_walCheckpointTimer) {
@@ -2617,13 +2687,50 @@ var init_memory = __esm({
2617
2687
  }
2618
2688
  });
2619
2689
 
2690
+ // src/lib/daemon-auth.ts
2691
+ import crypto from "crypto";
2692
+ import path5 from "path";
2693
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2694
+ function normalizeToken(token) {
2695
+ if (!token) return null;
2696
+ const trimmed = token.trim();
2697
+ return trimmed.length > 0 ? trimmed : null;
2698
+ }
2699
+ function readDaemonToken() {
2700
+ try {
2701
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
2702
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
2703
+ } catch {
2704
+ return null;
2705
+ }
2706
+ }
2707
+ function ensureDaemonToken(seed) {
2708
+ const existing = readDaemonToken();
2709
+ if (existing) return existing;
2710
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
2711
+ ensurePrivateDirSync(EXE_AI_DIR);
2712
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
2713
+ `, "utf8");
2714
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
2715
+ return token;
2716
+ }
2717
+ var DAEMON_TOKEN_PATH;
2718
+ var init_daemon_auth = __esm({
2719
+ "src/lib/daemon-auth.ts"() {
2720
+ "use strict";
2721
+ init_config();
2722
+ init_secure_files();
2723
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
2724
+ }
2725
+ });
2726
+
2620
2727
  // src/lib/exe-daemon-client.ts
2621
2728
  import net from "net";
2622
2729
  import os4 from "os";
2623
2730
  import { spawn } from "child_process";
2624
2731
  import { randomUUID } from "crypto";
2625
- import { existsSync as existsSync4, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
2626
- import path5 from "path";
2732
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
2733
+ import path6 from "path";
2627
2734
  import { fileURLToPath } from "url";
2628
2735
  function handleData(chunk) {
2629
2736
  _buffer += chunk.toString();
@@ -2651,9 +2758,9 @@ function handleData(chunk) {
2651
2758
  }
2652
2759
  }
2653
2760
  function cleanupStaleFiles() {
2654
- if (existsSync4(PID_PATH)) {
2761
+ if (existsSync6(PID_PATH)) {
2655
2762
  try {
2656
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2763
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2657
2764
  if (pid > 0) {
2658
2765
  try {
2659
2766
  process.kill(pid, 0);
@@ -2674,11 +2781,11 @@ function cleanupStaleFiles() {
2674
2781
  }
2675
2782
  }
2676
2783
  function findPackageRoot() {
2677
- let dir = path5.dirname(fileURLToPath(import.meta.url));
2678
- const { root } = path5.parse(dir);
2784
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2785
+ const { root } = path6.parse(dir);
2679
2786
  while (dir !== root) {
2680
- if (existsSync4(path5.join(dir, "package.json"))) return dir;
2681
- dir = path5.dirname(dir);
2787
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2788
+ dir = path6.dirname(dir);
2682
2789
  }
2683
2790
  return null;
2684
2791
  }
@@ -2704,16 +2811,17 @@ function spawnDaemon() {
2704
2811
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2705
2812
  return;
2706
2813
  }
2707
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2708
- if (!existsSync4(daemonPath)) {
2814
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2815
+ if (!existsSync6(daemonPath)) {
2709
2816
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2710
2817
  `);
2711
2818
  return;
2712
2819
  }
2713
2820
  const resolvedPath = daemonPath;
2821
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
2714
2822
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2715
2823
  `);
2716
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2824
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
2717
2825
  let stderrFd = "ignore";
2718
2826
  try {
2719
2827
  stderrFd = openSync(logPath, "a");
@@ -2731,7 +2839,8 @@ function spawnDaemon() {
2731
2839
  TMUX_PANE: void 0,
2732
2840
  // Prevents resolveExeSession() from scoping to one session
2733
2841
  EXE_DAEMON_SOCK: SOCKET_PATH,
2734
- EXE_DAEMON_PID: PID_PATH
2842
+ EXE_DAEMON_PID: PID_PATH,
2843
+ [DAEMON_TOKEN_ENV]: daemonToken
2735
2844
  }
2736
2845
  });
2737
2846
  child.unref();
@@ -2841,13 +2950,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2841
2950
  return;
2842
2951
  }
2843
2952
  const id = randomUUID();
2953
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2844
2954
  const timer = setTimeout(() => {
2845
2955
  _pending.delete(id);
2846
2956
  resolve({ error: "Request timeout" });
2847
2957
  }, timeoutMs);
2848
2958
  _pending.set(id, { resolve, timer });
2849
2959
  try {
2850
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2960
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
2851
2961
  } catch {
2852
2962
  clearTimeout(timer);
2853
2963
  _pending.delete(id);
@@ -2876,9 +2986,9 @@ function killAndRespawnDaemon() {
2876
2986
  }
2877
2987
  try {
2878
2988
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2879
- if (existsSync4(PID_PATH)) {
2989
+ if (existsSync6(PID_PATH)) {
2880
2990
  try {
2881
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2991
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2882
2992
  if (pid > 0) {
2883
2993
  try {
2884
2994
  process.kill(pid, "SIGKILL");
@@ -2995,17 +3105,19 @@ function disconnectClient() {
2995
3105
  entry.resolve({ error: "Client disconnected" });
2996
3106
  }
2997
3107
  }
2998
- 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;
3108
+ 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;
2999
3109
  var init_exe_daemon_client = __esm({
3000
3110
  "src/lib/exe-daemon-client.ts"() {
3001
3111
  "use strict";
3002
3112
  init_config();
3003
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
3004
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
3005
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
3113
+ init_daemon_auth();
3114
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3115
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3116
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
3006
3117
  SPAWN_LOCK_STALE_MS = 3e4;
3007
3118
  CONNECT_TIMEOUT_MS = 15e3;
3008
3119
  REQUEST_TIMEOUT_MS = 3e4;
3120
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
3009
3121
  _socket = null;
3010
3122
  _connected = false;
3011
3123
  _buffer = "";
@@ -3057,10 +3169,10 @@ async function disposeEmbedder() {
3057
3169
  async function embedDirect(text) {
3058
3170
  const llamaCpp = await import("node-llama-cpp");
3059
3171
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3060
- const { existsSync: existsSync16 } = await import("fs");
3061
- const path21 = await import("path");
3062
- const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3063
- if (!existsSync16(modelPath)) {
3172
+ const { existsSync: existsSync18 } = await import("fs");
3173
+ const path22 = await import("path");
3174
+ const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3175
+ if (!existsSync18(modelPath)) {
3064
3176
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3065
3177
  }
3066
3178
  const llama = await llamaCpp.getLlama();
@@ -3090,14 +3202,14 @@ var init_embedder = __esm({
3090
3202
 
3091
3203
  // src/lib/keychain.ts
3092
3204
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3093
- import { existsSync as existsSync5 } from "fs";
3094
- import path6 from "path";
3205
+ import { existsSync as existsSync7 } from "fs";
3206
+ import path7 from "path";
3095
3207
  import os5 from "os";
3096
3208
  function getKeyDir() {
3097
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3209
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3098
3210
  }
3099
3211
  function getKeyPath() {
3100
- return path6.join(getKeyDir(), "master.key");
3212
+ return path7.join(getKeyDir(), "master.key");
3101
3213
  }
3102
3214
  async function tryKeytar() {
3103
3215
  try {
@@ -3118,7 +3230,7 @@ async function getMasterKey() {
3118
3230
  }
3119
3231
  }
3120
3232
  const keyPath = getKeyPath();
3121
- if (!existsSync5(keyPath)) {
3233
+ if (!existsSync7(keyPath)) {
3122
3234
  process.stderr.write(
3123
3235
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3124
3236
  `
@@ -3150,6 +3262,7 @@ var shard_manager_exports = {};
3150
3262
  __export(shard_manager_exports, {
3151
3263
  disposeShards: () => disposeShards,
3152
3264
  ensureShardSchema: () => ensureShardSchema,
3265
+ getOpenShardCount: () => getOpenShardCount,
3153
3266
  getReadyShardClient: () => getReadyShardClient,
3154
3267
  getShardClient: () => getShardClient,
3155
3268
  getShardsDir: () => getShardsDir,
@@ -3158,15 +3271,18 @@ __export(shard_manager_exports, {
3158
3271
  listShards: () => listShards,
3159
3272
  shardExists: () => shardExists
3160
3273
  });
3161
- import path7 from "path";
3162
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
3274
+ import path8 from "path";
3275
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
3163
3276
  import { createClient as createClient2 } from "@libsql/client";
3164
3277
  function initShardManager(encryptionKey) {
3165
3278
  _encryptionKey = encryptionKey;
3166
- if (!existsSync6(SHARDS_DIR)) {
3279
+ if (!existsSync8(SHARDS_DIR)) {
3167
3280
  mkdirSync2(SHARDS_DIR, { recursive: true });
3168
3281
  }
3169
3282
  _shardingEnabled = true;
3283
+ if (_evictionTimer) clearInterval(_evictionTimer);
3284
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3285
+ _evictionTimer.unref();
3170
3286
  }
3171
3287
  function isShardingEnabled() {
3172
3288
  return _shardingEnabled;
@@ -3183,21 +3299,28 @@ function getShardClient(projectName) {
3183
3299
  throw new Error(`Invalid project name for shard: "${projectName}"`);
3184
3300
  }
3185
3301
  const cached = _shards.get(safeName);
3186
- if (cached) return cached;
3187
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3302
+ if (cached) {
3303
+ _shardLastAccess.set(safeName, Date.now());
3304
+ return cached;
3305
+ }
3306
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3307
+ evictLRU();
3308
+ }
3309
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
3188
3310
  const client = createClient2({
3189
3311
  url: `file:${dbPath}`,
3190
3312
  encryptionKey: _encryptionKey
3191
3313
  });
3192
3314
  _shards.set(safeName, client);
3315
+ _shardLastAccess.set(safeName, Date.now());
3193
3316
  return client;
3194
3317
  }
3195
3318
  function shardExists(projectName) {
3196
3319
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
3197
- return existsSync6(path7.join(SHARDS_DIR, `${safeName}.db`));
3320
+ return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
3198
3321
  }
3199
3322
  function listShards() {
3200
- if (!existsSync6(SHARDS_DIR)) return [];
3323
+ if (!existsSync8(SHARDS_DIR)) return [];
3201
3324
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3202
3325
  }
3203
3326
  async function ensureShardSchema(client) {
@@ -3249,6 +3372,8 @@ async function ensureShardSchema(client) {
3249
3372
  for (const col of [
3250
3373
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
3251
3374
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3375
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3376
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3252
3377
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3253
3378
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3254
3379
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -3386,21 +3511,69 @@ async function getReadyShardClient(projectName) {
3386
3511
  await ensureShardSchema(client);
3387
3512
  return client;
3388
3513
  }
3514
+ function evictLRU() {
3515
+ let oldest = null;
3516
+ let oldestTime = Infinity;
3517
+ for (const [name, time] of _shardLastAccess) {
3518
+ if (time < oldestTime) {
3519
+ oldestTime = time;
3520
+ oldest = name;
3521
+ }
3522
+ }
3523
+ if (oldest) {
3524
+ const client = _shards.get(oldest);
3525
+ if (client) {
3526
+ client.close();
3527
+ }
3528
+ _shards.delete(oldest);
3529
+ _shardLastAccess.delete(oldest);
3530
+ }
3531
+ }
3532
+ function evictIdleShards() {
3533
+ const now = Date.now();
3534
+ const toEvict = [];
3535
+ for (const [name, lastAccess] of _shardLastAccess) {
3536
+ if (now - lastAccess > SHARD_IDLE_MS) {
3537
+ toEvict.push(name);
3538
+ }
3539
+ }
3540
+ for (const name of toEvict) {
3541
+ const client = _shards.get(name);
3542
+ if (client) {
3543
+ client.close();
3544
+ }
3545
+ _shards.delete(name);
3546
+ _shardLastAccess.delete(name);
3547
+ }
3548
+ }
3549
+ function getOpenShardCount() {
3550
+ return _shards.size;
3551
+ }
3389
3552
  function disposeShards() {
3553
+ if (_evictionTimer) {
3554
+ clearInterval(_evictionTimer);
3555
+ _evictionTimer = null;
3556
+ }
3390
3557
  for (const [, client] of _shards) {
3391
3558
  client.close();
3392
3559
  }
3393
3560
  _shards.clear();
3561
+ _shardLastAccess.clear();
3394
3562
  _shardingEnabled = false;
3395
3563
  _encryptionKey = null;
3396
3564
  }
3397
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3565
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3398
3566
  var init_shard_manager = __esm({
3399
3567
  "src/lib/shard-manager.ts"() {
3400
3568
  "use strict";
3401
3569
  init_config();
3402
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3570
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
3571
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3572
+ MAX_OPEN_SHARDS = 10;
3573
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3403
3574
  _shards = /* @__PURE__ */ new Map();
3575
+ _shardLastAccess = /* @__PURE__ */ new Map();
3576
+ _evictionTimer = null;
3404
3577
  _encryptionKey = null;
3405
3578
  _shardingEnabled = false;
3406
3579
  }
@@ -4172,8 +4345,8 @@ __export(wiki_client_exports, {
4172
4345
  listDocuments: () => listDocuments,
4173
4346
  listWorkspaces: () => listWorkspaces
4174
4347
  });
4175
- async function wikiFetch(config2, path21, method = "GET", body) {
4176
- const url = `${config2.baseUrl}/api/v1${path21}`;
4348
+ async function wikiFetch(config2, path22, method = "GET", body) {
4349
+ const url = `${config2.baseUrl}/api/v1${path22}`;
4177
4350
  const headers = {
4178
4351
  Authorization: `Bearer ${config2.apiKey}`,
4179
4352
  "Content-Type": "application/json"
@@ -4206,7 +4379,7 @@ async function wikiFetch(config2, path21, method = "GET", body) {
4206
4379
  }
4207
4380
  }
4208
4381
  if (!response.ok) {
4209
- throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
4382
+ throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
4210
4383
  }
4211
4384
  return response.json();
4212
4385
  } finally {
@@ -4499,13 +4672,13 @@ __export(graph_rag_exports, {
4499
4672
  resolveAlias: () => resolveAlias,
4500
4673
  storeExtraction: () => storeExtraction
4501
4674
  });
4502
- import crypto from "crypto";
4675
+ import crypto2 from "crypto";
4503
4676
  function normalizeEntityName(name) {
4504
4677
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
4505
4678
  }
4506
4679
  function entityId(name, type) {
4507
4680
  const normalized = normalizeEntityName(name);
4508
- return crypto.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
4681
+ return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
4509
4682
  }
4510
4683
  async function resolveAlias(client, name) {
4511
4684
  const normalized = normalizeEntityName(name);
@@ -4755,7 +4928,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
4755
4928
  const targetAlias = await resolveAlias(client, r.target);
4756
4929
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
4757
4930
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
4758
- const relId = crypto.randomUUID().slice(0, 16);
4931
+ const relId = crypto2.randomUUID().slice(0, 16);
4759
4932
  try {
4760
4933
  await client.execute({
4761
4934
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -4818,7 +4991,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
4818
4991
  }
4819
4992
  }
4820
4993
  for (const h of extraction.hyperedges) {
4821
- const hId = crypto.randomUUID().slice(0, 16);
4994
+ const hId = crypto2.randomUUID().slice(0, 16);
4822
4995
  try {
4823
4996
  await client.execute({
4824
4997
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -4882,7 +5055,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
4882
5055
  totalEntities += stored.entitiesStored;
4883
5056
  totalRelationships += stored.relationshipsStored;
4884
5057
  }
4885
- const contentHash = crypto.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
5058
+ const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
4886
5059
  await client.execute({
4887
5060
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
4888
5061
  args: [contentHash, contentHash, memoryId]
@@ -5227,13 +5400,13 @@ __export(whatsapp_accounts_exports, {
5227
5400
  getDefaultAccount: () => getDefaultAccount,
5228
5401
  loadAccounts: () => loadAccounts
5229
5402
  });
5230
- import { readFileSync as readFileSync5 } from "fs";
5403
+ import { readFileSync as readFileSync6 } from "fs";
5231
5404
  import { join as join2 } from "path";
5232
5405
  import { homedir as homedir2 } from "os";
5233
5406
  function loadAccounts() {
5234
5407
  if (cachedAccounts !== null) return cachedAccounts;
5235
5408
  try {
5236
- const raw = readFileSync5(CONFIG_PATH2, "utf8");
5409
+ const raw = readFileSync6(CONFIG_PATH2, "utf8");
5237
5410
  const parsed = JSON.parse(raw);
5238
5411
  if (!Array.isArray(parsed)) {
5239
5412
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -5273,12 +5446,12 @@ var init_whatsapp_accounts = __esm({
5273
5446
  });
5274
5447
 
5275
5448
  // src/lib/session-registry.ts
5276
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync7 } from "fs";
5277
- import path9 from "path";
5449
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5450
+ import path10 from "path";
5278
5451
  import os7 from "os";
5279
5452
  function registerSession(entry) {
5280
- const dir = path9.dirname(REGISTRY_PATH);
5281
- if (!existsSync7(dir)) {
5453
+ const dir = path10.dirname(REGISTRY_PATH);
5454
+ if (!existsSync9(dir)) {
5282
5455
  mkdirSync4(dir, { recursive: true });
5283
5456
  }
5284
5457
  const sessions = listSessions();
@@ -5288,11 +5461,11 @@ function registerSession(entry) {
5288
5461
  } else {
5289
5462
  sessions.push(entry);
5290
5463
  }
5291
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5464
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5292
5465
  }
5293
5466
  function listSessions() {
5294
5467
  try {
5295
- const raw = readFileSync6(REGISTRY_PATH, "utf8");
5468
+ const raw = readFileSync7(REGISTRY_PATH, "utf8");
5296
5469
  return JSON.parse(raw);
5297
5470
  } catch {
5298
5471
  return [];
@@ -5302,7 +5475,7 @@ var REGISTRY_PATH;
5302
5475
  var init_session_registry = __esm({
5303
5476
  "src/lib/session-registry.ts"() {
5304
5477
  "use strict";
5305
- REGISTRY_PATH = path9.join(os7.homedir(), ".exe-os", "session-registry.json");
5478
+ REGISTRY_PATH = path10.join(os7.homedir(), ".exe-os", "session-registry.json");
5306
5479
  }
5307
5480
  });
5308
5481
 
@@ -5563,17 +5736,17 @@ __export(intercom_queue_exports, {
5563
5736
  queueIntercom: () => queueIntercom,
5564
5737
  readQueue: () => readQueue
5565
5738
  });
5566
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
5567
- import path10 from "path";
5739
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
5740
+ import path11 from "path";
5568
5741
  import os8 from "os";
5569
5742
  function ensureDir() {
5570
- const dir = path10.dirname(QUEUE_PATH);
5571
- if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
5743
+ const dir = path11.dirname(QUEUE_PATH);
5744
+ if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
5572
5745
  }
5573
5746
  function readQueue() {
5574
5747
  try {
5575
- if (!existsSync8(QUEUE_PATH)) return [];
5576
- return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
5748
+ if (!existsSync10(QUEUE_PATH)) return [];
5749
+ return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
5577
5750
  } catch {
5578
5751
  return [];
5579
5752
  }
@@ -5581,7 +5754,7 @@ function readQueue() {
5581
5754
  function writeQueue(queue) {
5582
5755
  ensureDir();
5583
5756
  const tmp = `${QUEUE_PATH}.tmp`;
5584
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
5757
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
5585
5758
  renameSync3(tmp, QUEUE_PATH);
5586
5759
  }
5587
5760
  function queueIntercom(targetSession, reason) {
@@ -5673,26 +5846,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
5673
5846
  var init_intercom_queue = __esm({
5674
5847
  "src/lib/intercom-queue.ts"() {
5675
5848
  "use strict";
5676
- QUEUE_PATH = path10.join(os8.homedir(), ".exe-os", "intercom-queue.json");
5849
+ QUEUE_PATH = path11.join(os8.homedir(), ".exe-os", "intercom-queue.json");
5677
5850
  MAX_RETRIES2 = 5;
5678
5851
  TTL_MS = 60 * 60 * 1e3;
5679
- INTERCOM_LOG = path10.join(os8.homedir(), ".exe-os", "intercom.log");
5852
+ INTERCOM_LOG = path11.join(os8.homedir(), ".exe-os", "intercom.log");
5680
5853
  }
5681
5854
  });
5682
5855
 
5683
5856
  // src/lib/license.ts
5684
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
5857
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
5685
5858
  import { randomUUID as randomUUID11 } from "crypto";
5686
- import path11 from "path";
5859
+ import { createRequire as createRequire2 } from "module";
5860
+ import { pathToFileURL as pathToFileURL2 } from "url";
5861
+ import os9 from "os";
5862
+ import path12 from "path";
5687
5863
  import { jwtVerify, importSPKI } from "jose";
5688
5864
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
5689
5865
  var init_license = __esm({
5690
5866
  "src/lib/license.ts"() {
5691
5867
  "use strict";
5692
5868
  init_config();
5693
- LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
5694
- CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
5695
- DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
5869
+ LICENSE_PATH = path12.join(EXE_AI_DIR, "license.key");
5870
+ CACHE_PATH = path12.join(EXE_AI_DIR, "license-cache.json");
5871
+ DEVICE_ID_PATH = path12.join(EXE_AI_DIR, "device-id");
5696
5872
  PLAN_LIMITS = {
5697
5873
  free: { devices: 1, employees: 1, memories: 5e3 },
5698
5874
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -5704,12 +5880,12 @@ var init_license = __esm({
5704
5880
  });
5705
5881
 
5706
5882
  // src/lib/plan-limits.ts
5707
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
5708
- import path12 from "path";
5883
+ import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
5884
+ import path13 from "path";
5709
5885
  function getLicenseSync() {
5710
5886
  try {
5711
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
5712
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
5887
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
5888
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
5713
5889
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
5714
5890
  const parts = raw.token.split(".");
5715
5891
  if (parts.length !== 3) return freeLicense();
@@ -5747,8 +5923,8 @@ function assertEmployeeLimitSync(rosterPath) {
5747
5923
  const filePath = rosterPath ?? EMPLOYEES_PATH;
5748
5924
  let count = 0;
5749
5925
  try {
5750
- if (existsSync10(filePath)) {
5751
- const raw = readFileSync9(filePath, "utf8");
5926
+ if (existsSync12(filePath)) {
5927
+ const raw = readFileSync10(filePath, "utf8");
5752
5928
  const employees = JSON.parse(raw);
5753
5929
  count = Array.isArray(employees) ? employees.length : 0;
5754
5930
  }
@@ -5777,29 +5953,63 @@ var init_plan_limits = __esm({
5777
5953
  this.name = "PlanLimitError";
5778
5954
  }
5779
5955
  };
5780
- CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
5956
+ CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
5957
+ }
5958
+ });
5959
+
5960
+ // src/lib/task-scope.ts
5961
+ function getCurrentSessionScope() {
5962
+ try {
5963
+ return resolveExeSession();
5964
+ } catch {
5965
+ return null;
5966
+ }
5967
+ }
5968
+ function sessionScopeFilter(sessionScope, tableAlias) {
5969
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5970
+ if (!scope) return { sql: "", args: [] };
5971
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5972
+ return {
5973
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
5974
+ args: [scope]
5975
+ };
5976
+ }
5977
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5978
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5979
+ if (!scope) return { sql: "", args: [] };
5980
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5981
+ return {
5982
+ sql: ` AND ${col} = ?`,
5983
+ args: [scope]
5984
+ };
5985
+ }
5986
+ var init_task_scope = __esm({
5987
+ "src/lib/task-scope.ts"() {
5988
+ "use strict";
5989
+ init_tmux_routing();
5781
5990
  }
5782
5991
  });
5783
5992
 
5784
5993
  // src/lib/notifications.ts
5785
- import crypto3 from "crypto";
5786
- import path13 from "path";
5787
- import os9 from "os";
5994
+ import crypto4 from "crypto";
5995
+ import path14 from "path";
5996
+ import os10 from "os";
5788
5997
  import {
5789
- readFileSync as readFileSync10,
5998
+ readFileSync as readFileSync11,
5790
5999
  readdirSync as readdirSync2,
5791
6000
  unlinkSync as unlinkSync3,
5792
- existsSync as existsSync11,
6001
+ existsSync as existsSync13,
5793
6002
  rmdirSync
5794
6003
  } from "fs";
5795
6004
  async function writeNotification(notification) {
5796
6005
  try {
5797
6006
  const client = getClient();
5798
- const id = crypto3.randomUUID();
6007
+ const id = crypto4.randomUUID();
5799
6008
  const now = (/* @__PURE__ */ new Date()).toISOString();
6009
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
5800
6010
  await client.execute({
5801
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
5802
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
6011
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
6012
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
5803
6013
  args: [
5804
6014
  id,
5805
6015
  notification.agentId,
@@ -5808,6 +6018,7 @@ async function writeNotification(notification) {
5808
6018
  notification.project,
5809
6019
  notification.summary,
5810
6020
  notification.taskFile ?? null,
6021
+ sessionScope,
5811
6022
  now
5812
6023
  ]
5813
6024
  });
@@ -5816,12 +6027,14 @@ async function writeNotification(notification) {
5816
6027
  `);
5817
6028
  }
5818
6029
  }
5819
- async function markAsReadByTaskFile(taskFile) {
6030
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
5820
6031
  try {
5821
6032
  const client = getClient();
6033
+ const scope = strictSessionScopeFilter(sessionScope);
5822
6034
  await client.execute({
5823
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
5824
- args: [taskFile]
6035
+ sql: `UPDATE notifications SET read = 1
6036
+ WHERE task_file = ? AND read = 0${scope.sql}`,
6037
+ args: [taskFile, ...scope.args]
5825
6038
  });
5826
6039
  } catch {
5827
6040
  }
@@ -5830,11 +6043,12 @@ var init_notifications = __esm({
5830
6043
  "src/lib/notifications.ts"() {
5831
6044
  "use strict";
5832
6045
  init_database();
6046
+ init_task_scope();
5833
6047
  }
5834
6048
  });
5835
6049
 
5836
6050
  // src/lib/session-kill-telemetry.ts
5837
- import crypto4 from "crypto";
6051
+ import crypto5 from "crypto";
5838
6052
  async function recordSessionKill(input) {
5839
6053
  try {
5840
6054
  const client = getClient();
@@ -5844,7 +6058,7 @@ async function recordSessionKill(input) {
5844
6058
  ticks_idle, estimated_tokens_saved)
5845
6059
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
5846
6060
  args: [
5847
- crypto4.randomUUID(),
6061
+ crypto5.randomUUID(),
5848
6062
  input.sessionName,
5849
6063
  input.agentId,
5850
6064
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -5867,37 +6081,117 @@ var init_session_kill_telemetry = __esm({
5867
6081
  }
5868
6082
  });
5869
6083
 
5870
- // src/lib/task-scope.ts
5871
- function getCurrentSessionScope() {
6084
+ // src/lib/project-name.ts
6085
+ import { execSync as execSync4 } from "child_process";
6086
+ import path15 from "path";
6087
+ function getProjectName(cwd) {
6088
+ const dir = cwd ?? process.cwd();
6089
+ if (_cached2 && _cachedCwd === dir) return _cached2;
5872
6090
  try {
5873
- return resolveExeSession();
6091
+ let repoRoot;
6092
+ try {
6093
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
6094
+ cwd: dir,
6095
+ encoding: "utf8",
6096
+ timeout: 2e3,
6097
+ stdio: ["pipe", "pipe", "pipe"]
6098
+ }).trim();
6099
+ repoRoot = path15.dirname(gitCommonDir);
6100
+ } catch {
6101
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
6102
+ cwd: dir,
6103
+ encoding: "utf8",
6104
+ timeout: 2e3,
6105
+ stdio: ["pipe", "pipe", "pipe"]
6106
+ }).trim();
6107
+ }
6108
+ _cached2 = path15.basename(repoRoot);
6109
+ _cachedCwd = dir;
6110
+ return _cached2;
5874
6111
  } catch {
5875
- return null;
6112
+ _cached2 = path15.basename(dir);
6113
+ _cachedCwd = dir;
6114
+ return _cached2;
5876
6115
  }
5877
6116
  }
5878
- function sessionScopeFilter(sessionScope, tableAlias) {
5879
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5880
- if (!scope) return { sql: "", args: [] };
5881
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5882
- return {
5883
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
5884
- args: [scope]
5885
- };
6117
+ var _cached2, _cachedCwd;
6118
+ var init_project_name = __esm({
6119
+ "src/lib/project-name.ts"() {
6120
+ "use strict";
6121
+ _cached2 = null;
6122
+ _cachedCwd = null;
6123
+ }
6124
+ });
6125
+
6126
+ // src/lib/session-scope.ts
6127
+ var session_scope_exports = {};
6128
+ __export(session_scope_exports, {
6129
+ assertSessionScope: () => assertSessionScope,
6130
+ findSessionForProject: () => findSessionForProject,
6131
+ getSessionProject: () => getSessionProject
6132
+ });
6133
+ function getSessionProject(sessionName) {
6134
+ const sessions = listSessions();
6135
+ const entry = sessions.find((s) => s.windowName === sessionName);
6136
+ if (!entry) return null;
6137
+ const parts = entry.projectDir.split("/").filter(Boolean);
6138
+ return parts[parts.length - 1] ?? null;
5886
6139
  }
5887
- var init_task_scope = __esm({
5888
- "src/lib/task-scope.ts"() {
6140
+ function findSessionForProject(projectName) {
6141
+ const sessions = listSessions();
6142
+ for (const s of sessions) {
6143
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
6144
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
6145
+ }
6146
+ return null;
6147
+ }
6148
+ function assertSessionScope(actionType, targetProject) {
6149
+ try {
6150
+ const currentProject = getProjectName();
6151
+ const exeSession = resolveExeSession();
6152
+ if (!exeSession) {
6153
+ return { allowed: true, reason: "no_session" };
6154
+ }
6155
+ if (currentProject === targetProject) {
6156
+ return {
6157
+ allowed: true,
6158
+ reason: "same_session",
6159
+ currentProject,
6160
+ targetProject
6161
+ };
6162
+ }
6163
+ process.stderr.write(
6164
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
6165
+ `
6166
+ );
6167
+ return {
6168
+ allowed: false,
6169
+ reason: "cross_session_denied",
6170
+ currentProject,
6171
+ targetProject,
6172
+ targetSession: findSessionForProject(targetProject)?.windowName
6173
+ };
6174
+ } catch {
6175
+ return { allowed: true, reason: "no_session" };
6176
+ }
6177
+ }
6178
+ var init_session_scope = __esm({
6179
+ "src/lib/session-scope.ts"() {
5889
6180
  "use strict";
6181
+ init_session_registry();
6182
+ init_project_name();
5890
6183
  init_tmux_routing();
6184
+ init_employees();
5891
6185
  }
5892
6186
  });
5893
6187
 
5894
6188
  // src/lib/tasks-crud.ts
5895
- import crypto5 from "crypto";
5896
- import path14 from "path";
5897
- import os10 from "os";
5898
- import { execSync as execSync4 } from "child_process";
6189
+ import crypto6 from "crypto";
6190
+ import path16 from "path";
6191
+ import os11 from "os";
6192
+ import { execSync as execSync5 } from "child_process";
5899
6193
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5900
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
6194
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
5901
6195
  async function writeCheckpoint(input) {
5902
6196
  const client = getClient();
5903
6197
  const row = await resolveTask(client, input.taskId);
@@ -6013,13 +6307,28 @@ async function resolveTask(client, identifier, scopeSession) {
6013
6307
  }
6014
6308
  async function createTaskCore(input) {
6015
6309
  const client = getClient();
6016
- const id = crypto5.randomUUID();
6310
+ const id = crypto6.randomUUID();
6017
6311
  const now = (/* @__PURE__ */ new Date()).toISOString();
6018
6312
  const slug = slugify(input.title);
6019
6313
  let earlySessionScope = null;
6314
+ let scopeMismatchWarning;
6020
6315
  try {
6021
6316
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
6022
- earlySessionScope = resolveExeSession2();
6317
+ const resolved = resolveExeSession2();
6318
+ if (resolved && input.projectName) {
6319
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
6320
+ const sessionProject = getSessionProject2(resolved);
6321
+ if (sessionProject && sessionProject !== input.projectName) {
6322
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
6323
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
6324
+ `);
6325
+ earlySessionScope = null;
6326
+ } else {
6327
+ earlySessionScope = resolved;
6328
+ }
6329
+ } else {
6330
+ earlySessionScope = resolved;
6331
+ }
6023
6332
  } catch {
6024
6333
  }
6025
6334
  const scope = earlySessionScope ?? "default";
@@ -6070,10 +6379,14 @@ async function createTaskCore(input) {
6070
6379
  ${laneWarning}` : laneWarning;
6071
6380
  }
6072
6381
  }
6382
+ if (scopeMismatchWarning) {
6383
+ warning = warning ? `${warning}
6384
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
6385
+ }
6073
6386
  if (input.baseDir) {
6074
6387
  try {
6075
- await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
6076
- await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
6388
+ await mkdir4(path16.join(input.baseDir, "exe", "output"), { recursive: true });
6389
+ await mkdir4(path16.join(input.baseDir, "exe", "research"), { recursive: true });
6077
6390
  await ensureArchitectureDoc(input.baseDir, input.projectName);
6078
6391
  await ensureGitignoreExe(input.baseDir);
6079
6392
  } catch {
@@ -6109,13 +6422,19 @@ ${laneWarning}` : laneWarning;
6109
6422
  });
6110
6423
  if (input.baseDir) {
6111
6424
  try {
6112
- const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
6113
- const mdPath = path14.join(EXE_OS_DIR, taskFile);
6114
- const mdDir = path14.dirname(mdPath);
6115
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
6425
+ const EXE_OS_DIR = path16.join(os11.homedir(), ".exe-os");
6426
+ const mdPath = path16.join(EXE_OS_DIR, taskFile);
6427
+ const mdDir = path16.dirname(mdPath);
6428
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
6116
6429
  const reviewer = input.reviewer ?? input.assignedBy;
6117
6430
  const mdContent = `# ${input.title}
6118
6431
 
6432
+ ## MANDATORY: When done
6433
+
6434
+ You MUST call update_task with status "done" and a result summary when finished.
6435
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
6436
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
6437
+
6119
6438
  **ID:** ${id}
6120
6439
  **Status:** ${initialStatus}
6121
6440
  **Priority:** ${input.priority}
@@ -6129,12 +6448,6 @@ ${laneWarning}` : laneWarning;
6129
6448
  ## Context
6130
6449
 
6131
6450
  ${input.context}
6132
-
6133
- ## MANDATORY: When done
6134
-
6135
- You MUST call update_task with status "done" and a result summary when finished.
6136
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
6137
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
6138
6451
  `;
6139
6452
  await writeFile4(mdPath, mdContent, "utf-8");
6140
6453
  } catch (err) {
@@ -6216,14 +6529,14 @@ function isTmuxSessionAlive(identifier) {
6216
6529
  if (!identifier || identifier === "unknown") return true;
6217
6530
  try {
6218
6531
  if (identifier.startsWith("%")) {
6219
- const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
6532
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
6220
6533
  timeout: 2e3,
6221
6534
  encoding: "utf8",
6222
6535
  stdio: ["pipe", "pipe", "pipe"]
6223
6536
  });
6224
6537
  return output.split("\n").some((l) => l.trim() === identifier);
6225
6538
  } else {
6226
- execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
6539
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
6227
6540
  timeout: 2e3,
6228
6541
  stdio: ["pipe", "pipe", "pipe"]
6229
6542
  });
@@ -6232,7 +6545,7 @@ function isTmuxSessionAlive(identifier) {
6232
6545
  } catch {
6233
6546
  if (identifier.startsWith("%")) return true;
6234
6547
  try {
6235
- execSync4("tmux list-sessions", {
6548
+ execSync5("tmux list-sessions", {
6236
6549
  timeout: 2e3,
6237
6550
  stdio: ["pipe", "pipe", "pipe"]
6238
6551
  });
@@ -6247,12 +6560,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
6247
6560
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
6248
6561
  try {
6249
6562
  const since = new Date(taskCreatedAt).toISOString();
6250
- const branch = execSync4(
6563
+ const branch = execSync5(
6251
6564
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
6252
6565
  { encoding: "utf8", timeout: 3e3 }
6253
6566
  ).trim();
6254
6567
  const branchArg = branch && branch !== "HEAD" ? branch : "";
6255
- const commitCount = execSync4(
6568
+ const commitCount = execSync5(
6256
6569
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
6257
6570
  { encoding: "utf8", timeout: 5e3 }
6258
6571
  ).trim();
@@ -6383,7 +6696,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
6383
6696
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
6384
6697
  } catch {
6385
6698
  }
6386
- if (input.status === "done" || input.status === "cancelled") {
6699
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
6387
6700
  try {
6388
6701
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
6389
6702
  clearQueueForAgent2(String(row.assigned_to));
@@ -6412,9 +6725,9 @@ async function deleteTaskCore(taskId, _baseDir) {
6412
6725
  return { taskFile, assignedTo, assignedBy, taskSlug };
6413
6726
  }
6414
6727
  async function ensureArchitectureDoc(baseDir, projectName) {
6415
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
6728
+ const archPath = path16.join(baseDir, "exe", "ARCHITECTURE.md");
6416
6729
  try {
6417
- if (existsSync12(archPath)) return;
6730
+ if (existsSync14(archPath)) return;
6418
6731
  const template = [
6419
6732
  `# ${projectName} \u2014 System Architecture`,
6420
6733
  "",
@@ -6447,10 +6760,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
6447
6760
  }
6448
6761
  }
6449
6762
  async function ensureGitignoreExe(baseDir) {
6450
- const gitignorePath = path14.join(baseDir, ".gitignore");
6763
+ const gitignorePath = path16.join(baseDir, ".gitignore");
6451
6764
  try {
6452
- if (existsSync12(gitignorePath)) {
6453
- const content = readFileSync11(gitignorePath, "utf-8");
6765
+ if (existsSync14(gitignorePath)) {
6766
+ const content = readFileSync12(gitignorePath, "utf-8");
6454
6767
  if (/^\/?exe\/?$/m.test(content)) return;
6455
6768
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
6456
6769
  } else {
@@ -6481,58 +6794,42 @@ var init_tasks_crud = __esm({
6481
6794
  });
6482
6795
 
6483
6796
  // src/lib/tasks-review.ts
6484
- import path15 from "path";
6485
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
6797
+ import path17 from "path";
6798
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
6486
6799
  async function countPendingReviews(sessionScope) {
6487
6800
  const client = getClient();
6488
- if (sessionScope) {
6489
- const result2 = await client.execute({
6490
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
6491
- args: [sessionScope]
6492
- });
6493
- return Number(result2.rows[0]?.cnt) || 0;
6494
- }
6801
+ const scope = strictSessionScopeFilter(
6802
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6803
+ );
6495
6804
  const result = await client.execute({
6496
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
6497
- args: []
6805
+ sql: `SELECT COUNT(*) as cnt FROM tasks
6806
+ WHERE status = 'needs_review'${scope.sql}`,
6807
+ args: [...scope.args]
6498
6808
  });
6499
6809
  return Number(result.rows[0]?.cnt) || 0;
6500
6810
  }
6501
6811
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
6502
6812
  const client = getClient();
6503
- if (sessionScope) {
6504
- const result2 = await client.execute({
6505
- sql: `SELECT COUNT(*) as cnt FROM tasks
6506
- WHERE status = 'needs_review' AND updated_at > ?
6507
- AND session_scope = ?`,
6508
- args: [sinceIso, sessionScope]
6509
- });
6510
- return Number(result2.rows[0]?.cnt) || 0;
6511
- }
6813
+ const scope = strictSessionScopeFilter(
6814
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6815
+ );
6512
6816
  const result = await client.execute({
6513
6817
  sql: `SELECT COUNT(*) as cnt FROM tasks
6514
- WHERE status = 'needs_review' AND updated_at > ?`,
6515
- args: [sinceIso]
6818
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
6819
+ args: [sinceIso, ...scope.args]
6516
6820
  });
6517
6821
  return Number(result.rows[0]?.cnt) || 0;
6518
6822
  }
6519
6823
  async function listPendingReviews(limit, sessionScope) {
6520
6824
  const client = getClient();
6521
- if (sessionScope) {
6522
- const result2 = await client.execute({
6523
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
6524
- WHERE status = 'needs_review'
6525
- AND session_scope = ?
6526
- ORDER BY updated_at ASC LIMIT ?`,
6527
- args: [sessionScope, limit]
6528
- });
6529
- return result2.rows;
6530
- }
6825
+ const scope = strictSessionScopeFilter(
6826
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6827
+ );
6531
6828
  const result = await client.execute({
6532
6829
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
6533
- WHERE status = 'needs_review'
6830
+ WHERE status = 'needs_review'${scope.sql}
6534
6831
  ORDER BY updated_at ASC LIMIT ?`,
6535
- args: [limit]
6832
+ args: [...scope.args, limit]
6536
6833
  });
6537
6834
  return result.rows;
6538
6835
  }
@@ -6544,7 +6841,7 @@ async function cleanupOrphanedReviews() {
6544
6841
  WHERE status IN ('open', 'needs_review', 'in_progress')
6545
6842
  AND assigned_by = 'system'
6546
6843
  AND title LIKE 'Review:%'
6547
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
6844
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
6548
6845
  args: [now]
6549
6846
  });
6550
6847
  const r1b = await client.execute({
@@ -6663,11 +6960,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
6663
6960
  );
6664
6961
  }
6665
6962
  try {
6666
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
6667
- if (existsSync13(cacheDir)) {
6963
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
6964
+ if (existsSync15(cacheDir)) {
6668
6965
  for (const f of readdirSync3(cacheDir)) {
6669
6966
  if (f.startsWith("review-notified-")) {
6670
- unlinkSync4(path15.join(cacheDir, f));
6967
+ unlinkSync4(path17.join(cacheDir, f));
6671
6968
  }
6672
6969
  }
6673
6970
  }
@@ -6684,11 +6981,12 @@ var init_tasks_review = __esm({
6684
6981
  init_tmux_routing();
6685
6982
  init_session_key();
6686
6983
  init_state_bus();
6984
+ init_task_scope();
6687
6985
  }
6688
6986
  });
6689
6987
 
6690
6988
  // src/lib/tasks-chain.ts
6691
- import path16 from "path";
6989
+ import path18 from "path";
6692
6990
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
6693
6991
  async function cascadeUnblock(taskId, baseDir, now) {
6694
6992
  const client = getClient();
@@ -6705,7 +7003,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
6705
7003
  });
6706
7004
  for (const ur of unblockedRows.rows) {
6707
7005
  try {
6708
- const ubFile = path16.join(baseDir, String(ur.task_file));
7006
+ const ubFile = path18.join(baseDir, String(ur.task_file));
6709
7007
  let ubContent = await readFile4(ubFile, "utf-8");
6710
7008
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
6711
7009
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -6740,7 +7038,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
6740
7038
  const scScope = sessionScopeFilter();
6741
7039
  const remaining = await client.execute({
6742
7040
  sql: `SELECT COUNT(*) as cnt FROM tasks
6743
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
7041
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
6744
7042
  args: [parentTaskId, ...scScope.args]
6745
7043
  });
6746
7044
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -6772,110 +7070,6 @@ var init_tasks_chain = __esm({
6772
7070
  }
6773
7071
  });
6774
7072
 
6775
- // src/lib/project-name.ts
6776
- import { execSync as execSync5 } from "child_process";
6777
- import path17 from "path";
6778
- function getProjectName(cwd) {
6779
- const dir = cwd ?? process.cwd();
6780
- if (_cached2 && _cachedCwd === dir) return _cached2;
6781
- try {
6782
- let repoRoot;
6783
- try {
6784
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
6785
- cwd: dir,
6786
- encoding: "utf8",
6787
- timeout: 2e3,
6788
- stdio: ["pipe", "pipe", "pipe"]
6789
- }).trim();
6790
- repoRoot = path17.dirname(gitCommonDir);
6791
- } catch {
6792
- repoRoot = execSync5("git rev-parse --show-toplevel", {
6793
- cwd: dir,
6794
- encoding: "utf8",
6795
- timeout: 2e3,
6796
- stdio: ["pipe", "pipe", "pipe"]
6797
- }).trim();
6798
- }
6799
- _cached2 = path17.basename(repoRoot);
6800
- _cachedCwd = dir;
6801
- return _cached2;
6802
- } catch {
6803
- _cached2 = path17.basename(dir);
6804
- _cachedCwd = dir;
6805
- return _cached2;
6806
- }
6807
- }
6808
- var _cached2, _cachedCwd;
6809
- var init_project_name = __esm({
6810
- "src/lib/project-name.ts"() {
6811
- "use strict";
6812
- _cached2 = null;
6813
- _cachedCwd = null;
6814
- }
6815
- });
6816
-
6817
- // src/lib/session-scope.ts
6818
- var session_scope_exports = {};
6819
- __export(session_scope_exports, {
6820
- assertSessionScope: () => assertSessionScope,
6821
- findSessionForProject: () => findSessionForProject,
6822
- getSessionProject: () => getSessionProject
6823
- });
6824
- function getSessionProject(sessionName) {
6825
- const sessions = listSessions();
6826
- const entry = sessions.find((s) => s.windowName === sessionName);
6827
- if (!entry) return null;
6828
- const parts = entry.projectDir.split("/").filter(Boolean);
6829
- return parts[parts.length - 1] ?? null;
6830
- }
6831
- function findSessionForProject(projectName) {
6832
- const sessions = listSessions();
6833
- for (const s of sessions) {
6834
- const proj = s.projectDir.split("/").filter(Boolean).pop();
6835
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
6836
- }
6837
- return null;
6838
- }
6839
- function assertSessionScope(actionType, targetProject) {
6840
- try {
6841
- const currentProject = getProjectName();
6842
- const exeSession = resolveExeSession();
6843
- if (!exeSession) {
6844
- return { allowed: true, reason: "no_session" };
6845
- }
6846
- if (currentProject === targetProject) {
6847
- return {
6848
- allowed: true,
6849
- reason: "same_session",
6850
- currentProject,
6851
- targetProject
6852
- };
6853
- }
6854
- process.stderr.write(
6855
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
6856
- `
6857
- );
6858
- return {
6859
- allowed: false,
6860
- reason: "cross_session_denied",
6861
- currentProject,
6862
- targetProject,
6863
- targetSession: findSessionForProject(targetProject)?.windowName
6864
- };
6865
- } catch {
6866
- return { allowed: true, reason: "no_session" };
6867
- }
6868
- }
6869
- var init_session_scope = __esm({
6870
- "src/lib/session-scope.ts"() {
6871
- "use strict";
6872
- init_session_registry();
6873
- init_project_name();
6874
- init_tmux_routing();
6875
- init_employees();
6876
- }
6877
- });
6878
-
6879
7073
  // src/lib/tasks-notify.ts
6880
7074
  async function dispatchTaskToEmployee(input) {
6881
7075
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -6943,10 +7137,10 @@ var init_tasks_notify = __esm({
6943
7137
  });
6944
7138
 
6945
7139
  // src/lib/behaviors.ts
6946
- import crypto6 from "crypto";
7140
+ import crypto7 from "crypto";
6947
7141
  async function storeBehavior(opts) {
6948
7142
  const client = getClient();
6949
- const id = crypto6.randomUUID();
7143
+ const id = crypto7.randomUUID();
6950
7144
  const now = (/* @__PURE__ */ new Date()).toISOString();
6951
7145
  await client.execute({
6952
7146
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -6975,7 +7169,7 @@ __export(skill_learning_exports, {
6975
7169
  storeTrajectory: () => storeTrajectory,
6976
7170
  sweepTrajectories: () => sweepTrajectories
6977
7171
  });
6978
- import crypto7 from "crypto";
7172
+ import crypto8 from "crypto";
6979
7173
  async function extractTrajectory(taskId, agentId) {
6980
7174
  const client = getClient();
6981
7175
  const result = await client.execute({
@@ -7004,11 +7198,11 @@ async function extractTrajectory(taskId, agentId) {
7004
7198
  return signature;
7005
7199
  }
7006
7200
  function hashSignature(signature) {
7007
- return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
7201
+ return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
7008
7202
  }
7009
7203
  async function storeTrajectory(opts) {
7010
7204
  const client = getClient();
7011
- const id = crypto7.randomUUID();
7205
+ const id = crypto8.randomUUID();
7012
7206
  const now = (/* @__PURE__ */ new Date()).toISOString();
7013
7207
  const signatureHash = hashSignature(opts.signature);
7014
7208
  await client.execute({
@@ -7273,8 +7467,8 @@ __export(tasks_exports, {
7273
7467
  updateTaskStatus: () => updateTaskStatus,
7274
7468
  writeCheckpoint: () => writeCheckpoint
7275
7469
  });
7276
- import path18 from "path";
7277
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
7470
+ import path19 from "path";
7471
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
7278
7472
  async function createTask(input) {
7279
7473
  const result = await createTaskCore(input);
7280
7474
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -7293,12 +7487,12 @@ async function updateTask(input) {
7293
7487
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
7294
7488
  try {
7295
7489
  const agent = String(row.assigned_to);
7296
- const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
7297
- const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
7490
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
7491
+ const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
7298
7492
  if (input.status === "in_progress") {
7299
7493
  mkdirSync7(cacheDir, { recursive: true });
7300
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7301
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
7494
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7495
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
7302
7496
  try {
7303
7497
  unlinkSync5(cachePath);
7304
7498
  } catch {
@@ -7306,10 +7500,10 @@ async function updateTask(input) {
7306
7500
  }
7307
7501
  } catch {
7308
7502
  }
7309
- if (input.status === "done") {
7503
+ if (input.status === "done" || input.status === "closed") {
7310
7504
  await cleanupReviewFile(row, taskFile, input.baseDir);
7311
7505
  }
7312
- if (input.status === "done" || input.status === "cancelled") {
7506
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
7313
7507
  try {
7314
7508
  const client = getClient();
7315
7509
  const taskTitle = String(row.title);
@@ -7325,7 +7519,7 @@ async function updateTask(input) {
7325
7519
  if (!isCoordinatorName(assignedAgent)) {
7326
7520
  try {
7327
7521
  const draftClient = getClient();
7328
- if (input.status === "done") {
7522
+ if (input.status === "done" || input.status === "closed") {
7329
7523
  await draftClient.execute({
7330
7524
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
7331
7525
  args: [assignedAgent]
@@ -7342,7 +7536,7 @@ async function updateTask(input) {
7342
7536
  try {
7343
7537
  const client = getClient();
7344
7538
  const cascaded = await client.execute({
7345
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
7539
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
7346
7540
  WHERE parent_task_id = ? AND status = 'needs_review'`,
7347
7541
  args: [now, taskId]
7348
7542
  });
@@ -7355,14 +7549,14 @@ async function updateTask(input) {
7355
7549
  } catch {
7356
7550
  }
7357
7551
  }
7358
- const isTerminal = input.status === "done" || input.status === "needs_review";
7552
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
7359
7553
  if (isTerminal) {
7360
7554
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
7361
7555
  if (!isCoordinator) {
7362
7556
  notifyTaskDone();
7363
7557
  }
7364
7558
  await markTaskNotificationsRead(taskFile);
7365
- if (input.status === "done") {
7559
+ if (input.status === "done" || input.status === "closed") {
7366
7560
  try {
7367
7561
  await cascadeUnblock(taskId, input.baseDir, now);
7368
7562
  } catch {
@@ -7382,7 +7576,7 @@ async function updateTask(input) {
7382
7576
  }
7383
7577
  }
7384
7578
  }
7385
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
7579
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
7386
7580
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
7387
7581
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
7388
7582
  taskId,
@@ -7754,6 +7948,7 @@ __export(tmux_routing_exports, {
7754
7948
  isEmployeeAlive: () => isEmployeeAlive,
7755
7949
  isExeSession: () => isExeSession,
7756
7950
  isSessionBusy: () => isSessionBusy,
7951
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
7757
7952
  notifyParentExe: () => notifyParentExe,
7758
7953
  parseParentExe: () => parseParentExe,
7759
7954
  registerParentExe: () => registerParentExe,
@@ -7764,13 +7959,13 @@ __export(tmux_routing_exports, {
7764
7959
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
7765
7960
  });
7766
7961
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
7767
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
7768
- import path19 from "path";
7769
- import os11 from "os";
7962
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
7963
+ import path20 from "path";
7964
+ import os12 from "os";
7770
7965
  import { fileURLToPath as fileURLToPath2 } from "url";
7771
7966
  import { unlinkSync as unlinkSync6 } from "fs";
7772
7967
  function spawnLockPath(sessionName) {
7773
- return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7968
+ return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7774
7969
  }
7775
7970
  function isProcessAlive(pid) {
7776
7971
  try {
@@ -7781,13 +7976,13 @@ function isProcessAlive(pid) {
7781
7976
  }
7782
7977
  }
7783
7978
  function acquireSpawnLock2(sessionName) {
7784
- if (!existsSync14(SPAWN_LOCK_DIR)) {
7979
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
7785
7980
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
7786
7981
  }
7787
7982
  const lockFile = spawnLockPath(sessionName);
7788
- if (existsSync14(lockFile)) {
7983
+ if (existsSync16(lockFile)) {
7789
7984
  try {
7790
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
7985
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
7791
7986
  const age = Date.now() - lock.timestamp;
7792
7987
  if (isProcessAlive(lock.pid) && age < 6e4) {
7793
7988
  return false;
@@ -7795,7 +7990,7 @@ function acquireSpawnLock2(sessionName) {
7795
7990
  } catch {
7796
7991
  }
7797
7992
  }
7798
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7993
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7799
7994
  return true;
7800
7995
  }
7801
7996
  function releaseSpawnLock2(sessionName) {
@@ -7807,13 +8002,13 @@ function releaseSpawnLock2(sessionName) {
7807
8002
  function resolveBehaviorsExporterScript() {
7808
8003
  try {
7809
8004
  const thisFile = fileURLToPath2(import.meta.url);
7810
- const scriptPath = path19.join(
7811
- path19.dirname(thisFile),
8005
+ const scriptPath = path20.join(
8006
+ path20.dirname(thisFile),
7812
8007
  "..",
7813
8008
  "bin",
7814
8009
  "exe-export-behaviors.js"
7815
8010
  );
7816
- return existsSync14(scriptPath) ? scriptPath : null;
8011
+ return existsSync16(scriptPath) ? scriptPath : null;
7817
8012
  } catch {
7818
8013
  return null;
7819
8014
  }
@@ -7879,12 +8074,12 @@ function extractRootExe(name) {
7879
8074
  return parts.length > 0 ? parts[parts.length - 1] : null;
7880
8075
  }
7881
8076
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7882
- if (!existsSync14(SESSION_CACHE)) {
8077
+ if (!existsSync16(SESSION_CACHE)) {
7883
8078
  mkdirSync8(SESSION_CACHE, { recursive: true });
7884
8079
  }
7885
8080
  const rootExe = extractRootExe(parentExe) ?? parentExe;
7886
- const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7887
- writeFileSync7(filePath, JSON.stringify({
8081
+ const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
8082
+ writeFileSync8(filePath, JSON.stringify({
7888
8083
  parentExe: rootExe,
7889
8084
  dispatchedBy: dispatchedBy || rootExe,
7890
8085
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7892,7 +8087,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7892
8087
  }
7893
8088
  function getParentExe(sessionKey) {
7894
8089
  try {
7895
- const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
8090
+ const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
7896
8091
  return data.parentExe || null;
7897
8092
  } catch {
7898
8093
  return null;
@@ -7900,8 +8095,8 @@ function getParentExe(sessionKey) {
7900
8095
  }
7901
8096
  function getDispatchedBy(sessionKey) {
7902
8097
  try {
7903
- const data = JSON.parse(readFileSync12(
7904
- path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
8098
+ const data = JSON.parse(readFileSync13(
8099
+ path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
7905
8100
  "utf8"
7906
8101
  ));
7907
8102
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -7971,8 +8166,8 @@ async function verifyPaneAtCapacity(sessionName) {
7971
8166
  }
7972
8167
  function readDebounceState() {
7973
8168
  try {
7974
- if (!existsSync14(DEBOUNCE_FILE)) return {};
7975
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
8169
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
8170
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
7976
8171
  const state = {};
7977
8172
  for (const [key, val] of Object.entries(raw)) {
7978
8173
  if (typeof val === "number") {
@@ -7988,8 +8183,8 @@ function readDebounceState() {
7988
8183
  }
7989
8184
  function writeDebounceState(state) {
7990
8185
  try {
7991
- if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
7992
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
8186
+ if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
8187
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
7993
8188
  } catch {
7994
8189
  }
7995
8190
  }
@@ -8087,8 +8282,8 @@ function sendIntercom(targetSession) {
8087
8282
  try {
8088
8283
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8089
8284
  const agent = baseAgentName(rawAgent);
8090
- const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
8091
- if (existsSync14(markerPath)) {
8285
+ const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
8286
+ if (existsSync16(markerPath)) {
8092
8287
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
8093
8288
  return "debounced";
8094
8289
  }
@@ -8097,8 +8292,8 @@ function sendIntercom(targetSession) {
8097
8292
  try {
8098
8293
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8099
8294
  const agent = baseAgentName(rawAgent);
8100
- const taskDir = path19.join(process.cwd(), "exe", agent);
8101
- if (existsSync14(taskDir)) {
8295
+ const taskDir = path20.join(process.cwd(), "exe", agent);
8296
+ if (existsSync16(taskDir)) {
8102
8297
  const files = readdirSync4(taskDir).filter(
8103
8298
  (f) => f.endsWith(".md") && f !== "DONE.txt"
8104
8299
  );
@@ -8158,6 +8353,21 @@ function notifyParentExe(sessionKey) {
8158
8353
  }
8159
8354
  return true;
8160
8355
  }
8356
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
8357
+ const transport = getTransport();
8358
+ try {
8359
+ const sessions = transport.listSessions();
8360
+ if (!sessions.includes(coordinatorSession)) return false;
8361
+ execSync6(
8362
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8363
+ { timeout: 3e3 }
8364
+ );
8365
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
8366
+ return true;
8367
+ } catch {
8368
+ return false;
8369
+ }
8370
+ }
8161
8371
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
8162
8372
  if (isCoordinatorName(employeeName)) {
8163
8373
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -8231,26 +8441,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8231
8441
  const transport = getTransport();
8232
8442
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
8233
8443
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
8234
- const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
8235
- const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8236
- if (!existsSync14(logDir)) {
8444
+ const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
8445
+ const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8446
+ if (!existsSync16(logDir)) {
8237
8447
  mkdirSync8(logDir, { recursive: true });
8238
8448
  }
8239
8449
  transport.kill(sessionName);
8240
8450
  let cleanupSuffix = "";
8241
8451
  try {
8242
8452
  const thisFile = fileURLToPath2(import.meta.url);
8243
- const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8244
- if (existsSync14(cleanupScript)) {
8453
+ const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8454
+ if (existsSync16(cleanupScript)) {
8245
8455
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
8246
8456
  }
8247
8457
  } catch {
8248
8458
  }
8249
8459
  try {
8250
- const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
8460
+ const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
8251
8461
  let claudeJson = {};
8252
8462
  try {
8253
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
8463
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
8254
8464
  } catch {
8255
8465
  }
8256
8466
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -8258,17 +8468,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8258
8468
  const trustDir = opts?.cwd ?? projectDir;
8259
8469
  if (!projects[trustDir]) projects[trustDir] = {};
8260
8470
  projects[trustDir].hasTrustDialogAccepted = true;
8261
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8471
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8262
8472
  } catch {
8263
8473
  }
8264
8474
  try {
8265
- const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
8475
+ const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
8266
8476
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
8267
- const projSettingsDir = path19.join(settingsDir, normalizedKey);
8268
- const settingsPath = path19.join(projSettingsDir, "settings.json");
8477
+ const projSettingsDir = path20.join(settingsDir, normalizedKey);
8478
+ const settingsPath = path20.join(projSettingsDir, "settings.json");
8269
8479
  let settings = {};
8270
8480
  try {
8271
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
8481
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
8272
8482
  } catch {
8273
8483
  }
8274
8484
  const perms = settings.permissions ?? {};
@@ -8297,7 +8507,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8297
8507
  perms.allow = allow;
8298
8508
  settings.permissions = perms;
8299
8509
  mkdirSync8(projSettingsDir, { recursive: true });
8300
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8510
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8301
8511
  }
8302
8512
  } catch {
8303
8513
  }
@@ -8312,8 +8522,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8312
8522
  let behaviorsFlag = "";
8313
8523
  let legacyFallbackWarned = false;
8314
8524
  if (!useExeAgent && !useBinSymlink) {
8315
- const identityPath = path19.join(
8316
- os11.homedir(),
8525
+ const identityPath = path20.join(
8526
+ os12.homedir(),
8317
8527
  ".exe-os",
8318
8528
  "identity",
8319
8529
  `${employeeName}.md`
@@ -8322,13 +8532,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8322
8532
  const hasAgentFlag = claudeSupportsAgentFlag();
8323
8533
  if (hasAgentFlag) {
8324
8534
  identityFlag = ` --agent ${employeeName}`;
8325
- } else if (existsSync14(identityPath)) {
8535
+ } else if (existsSync16(identityPath)) {
8326
8536
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
8327
8537
  legacyFallbackWarned = true;
8328
8538
  }
8329
8539
  const behaviorsFile = exportBehaviorsSync(
8330
8540
  employeeName,
8331
- path19.basename(spawnCwd),
8541
+ path20.basename(spawnCwd),
8332
8542
  sessionName
8333
8543
  );
8334
8544
  if (behaviorsFile) {
@@ -8343,16 +8553,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8343
8553
  }
8344
8554
  let sessionContextFlag = "";
8345
8555
  try {
8346
- const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
8556
+ const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
8347
8557
  mkdirSync8(ctxDir, { recursive: true });
8348
- const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
8558
+ const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
8349
8559
  const ctxContent = [
8350
8560
  `## Session Context`,
8351
8561
  `You are running in tmux session: ${sessionName}.`,
8352
8562
  `Your parent coordinator session is ${exeSession}.`,
8353
8563
  `Your employees (if any) use the -${exeSession} suffix.`
8354
8564
  ].join("\n");
8355
- writeFileSync7(ctxFile, ctxContent);
8565
+ writeFileSync8(ctxFile, ctxContent);
8356
8566
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
8357
8567
  } catch {
8358
8568
  }
@@ -8429,8 +8639,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8429
8639
  transport.pipeLog(sessionName, logFile);
8430
8640
  try {
8431
8641
  const mySession = getMySession();
8432
- const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8433
- writeFileSync7(dispatchInfo, JSON.stringify({
8642
+ const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8643
+ writeFileSync8(dispatchInfo, JSON.stringify({
8434
8644
  dispatchedBy: mySession,
8435
8645
  rootExe: exeSession,
8436
8646
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -8504,15 +8714,15 @@ var init_tmux_routing = __esm({
8504
8714
  init_intercom_queue();
8505
8715
  init_plan_limits();
8506
8716
  init_employees();
8507
- SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
8508
- SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
8717
+ SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
8718
+ SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
8509
8719
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
8510
8720
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
8511
8721
  VERIFY_PANE_LINES = 200;
8512
8722
  INTERCOM_DEBOUNCE_MS = 3e4;
8513
8723
  CODEX_DEBOUNCE_MS = 12e4;
8514
- INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
8515
- DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
8724
+ INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
8725
+ DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
8516
8726
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
8517
8727
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
8518
8728
  }
@@ -8535,10 +8745,10 @@ __export(messaging_exports, {
8535
8745
  sendMessage: () => sendMessage,
8536
8746
  setWsClientSend: () => setWsClientSend
8537
8747
  });
8538
- import crypto8 from "crypto";
8748
+ import crypto9 from "crypto";
8539
8749
  function generateUlid() {
8540
8750
  const timestamp = Date.now().toString(36).padStart(10, "0");
8541
- const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
8751
+ const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
8542
8752
  return (timestamp + random).toUpperCase();
8543
8753
  }
8544
8754
  function rowToMessage(row) {
@@ -8549,6 +8759,7 @@ function rowToMessage(row) {
8549
8759
  targetAgent: row.target_agent,
8550
8760
  targetProject: row.target_project ?? null,
8551
8761
  targetDevice: row.target_device,
8762
+ sessionScope: row.session_scope ?? null,
8552
8763
  content: row.content,
8553
8764
  priority: row.priority ?? "normal",
8554
8765
  status: row.status ?? "pending",
@@ -8566,15 +8777,17 @@ async function sendMessage(input) {
8566
8777
  const id = generateUlid();
8567
8778
  const now = (/* @__PURE__ */ new Date()).toISOString();
8568
8779
  const targetDevice = input.targetDevice ?? "local";
8780
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
8569
8781
  await client.execute({
8570
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
8571
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
8782
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
8783
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
8572
8784
  args: [
8573
8785
  id,
8574
8786
  input.fromAgent,
8575
8787
  input.targetAgent,
8576
8788
  input.targetProject ?? null,
8577
8789
  targetDevice,
8790
+ sessionScope,
8578
8791
  input.content,
8579
8792
  input.priority ?? "normal",
8580
8793
  now
@@ -8588,9 +8801,10 @@ async function sendMessage(input) {
8588
8801
  }
8589
8802
  } catch {
8590
8803
  }
8804
+ const sentScope = strictSessionScopeFilter(sessionScope);
8591
8805
  const result = await client.execute({
8592
- sql: "SELECT * FROM messages WHERE id = ?",
8593
- args: [id]
8806
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
8807
+ args: [id, ...sentScope.args]
8594
8808
  });
8595
8809
  return rowToMessage(result.rows[0]);
8596
8810
  }
@@ -8614,6 +8828,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
8614
8828
  fromAgent: msg.fromAgent,
8615
8829
  targetAgent: msg.targetAgent,
8616
8830
  targetProject: msg.targetProject,
8831
+ sessionScope: msg.sessionScope,
8617
8832
  content: msg.content,
8618
8833
  priority: msg.priority,
8619
8834
  createdAt: msg.createdAt
@@ -8657,7 +8872,7 @@ async function deliverLocalMessage(messageId) {
8657
8872
  } catch {
8658
8873
  const newRetryCount = msg.retryCount + 1;
8659
8874
  if (newRetryCount >= MAX_RETRIES3) {
8660
- await markFailed(messageId, "session unavailable after 10 retries");
8875
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
8661
8876
  } else {
8662
8877
  await client.execute({
8663
8878
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -8667,85 +8882,101 @@ async function deliverLocalMessage(messageId) {
8667
8882
  return false;
8668
8883
  }
8669
8884
  }
8670
- async function getPendingMessages(targetAgent) {
8885
+ async function getPendingMessages(targetAgent, sessionScope) {
8671
8886
  const client = getClient();
8887
+ const scope = strictSessionScopeFilter(sessionScope);
8672
8888
  const result = await client.execute({
8673
8889
  sql: `SELECT * FROM messages
8674
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
8890
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
8675
8891
  ORDER BY id`,
8676
- args: [targetAgent]
8892
+ args: [targetAgent, ...scope.args]
8677
8893
  });
8678
8894
  return result.rows.map((row) => rowToMessage(row));
8679
8895
  }
8680
- async function markRead(messageId) {
8896
+ async function markRead(messageId, sessionScope) {
8681
8897
  const client = getClient();
8898
+ const scope = strictSessionScopeFilter(sessionScope);
8682
8899
  await client.execute({
8683
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
8684
- args: [messageId]
8900
+ sql: `UPDATE messages SET status = 'read'
8901
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
8902
+ args: [messageId, ...scope.args]
8685
8903
  });
8686
8904
  }
8687
- async function markAcknowledged(messageId) {
8905
+ async function markAcknowledged(messageId, sessionScope) {
8688
8906
  const client = getClient();
8907
+ const scope = strictSessionScopeFilter(sessionScope);
8689
8908
  await client.execute({
8690
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
8691
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8909
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
8910
+ WHERE id = ? AND status = 'read'${scope.sql}`,
8911
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
8692
8912
  });
8693
8913
  }
8694
- async function markProcessed(messageId) {
8914
+ async function markProcessed(messageId, sessionScope) {
8695
8915
  const client = getClient();
8916
+ const scope = strictSessionScopeFilter(sessionScope);
8696
8917
  await client.execute({
8697
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
8698
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8918
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
8919
+ WHERE id = ?${scope.sql}`,
8920
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
8699
8921
  });
8700
8922
  }
8701
- async function getMessageStatus(messageId) {
8923
+ async function getMessageStatus(messageId, sessionScope) {
8702
8924
  const client = getClient();
8925
+ const scope = strictSessionScopeFilter(sessionScope);
8703
8926
  const result = await client.execute({
8704
- sql: "SELECT status FROM messages WHERE id = ?",
8705
- args: [messageId]
8927
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
8928
+ args: [messageId, ...scope.args]
8706
8929
  });
8707
8930
  return result.rows[0]?.status ?? null;
8708
8931
  }
8709
- async function getUnacknowledgedMessages(targetAgent) {
8932
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
8710
8933
  const client = getClient();
8934
+ const scope = strictSessionScopeFilter(sessionScope);
8711
8935
  const result = await client.execute({
8712
8936
  sql: `SELECT * FROM messages
8713
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
8937
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
8714
8938
  ORDER BY id`,
8715
- args: [targetAgent]
8939
+ args: [targetAgent, ...scope.args]
8716
8940
  });
8717
8941
  return result.rows.map((row) => rowToMessage(row));
8718
8942
  }
8719
- async function getReadMessages(targetAgent) {
8943
+ async function getReadMessages(targetAgent, sessionScope) {
8720
8944
  const client = getClient();
8945
+ const scope = strictSessionScopeFilter(sessionScope);
8721
8946
  const result = await client.execute({
8722
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
8723
- args: [targetAgent]
8947
+ sql: `SELECT * FROM messages
8948
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
8949
+ ORDER BY id`,
8950
+ args: [targetAgent, ...scope.args]
8724
8951
  });
8725
8952
  return result.rows.map((row) => rowToMessage(row));
8726
8953
  }
8727
- async function markFailed(messageId, reason) {
8954
+ async function markFailed(messageId, reason, sessionScope) {
8728
8955
  const client = getClient();
8956
+ const scope = strictSessionScopeFilter(sessionScope);
8729
8957
  await client.execute({
8730
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
8731
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
8958
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
8959
+ WHERE id = ?${scope.sql}`,
8960
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
8732
8961
  });
8733
8962
  }
8734
- async function getFailedMessages() {
8963
+ async function getFailedMessages(sessionScope) {
8735
8964
  const client = getClient();
8965
+ const scope = strictSessionScopeFilter(sessionScope);
8736
8966
  const result = await client.execute({
8737
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
8738
- args: []
8967
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
8968
+ args: [...scope.args]
8739
8969
  });
8740
8970
  return result.rows.map((row) => rowToMessage(row));
8741
8971
  }
8742
- async function retryPendingMessages() {
8972
+ async function retryPendingMessages(sessionScope) {
8743
8973
  const client = getClient();
8974
+ const scope = strictSessionScopeFilter(sessionScope);
8744
8975
  const result = await client.execute({
8745
8976
  sql: `SELECT * FROM messages
8746
- WHERE status = 'pending' AND retry_count < ?
8977
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
8747
8978
  ORDER BY id`,
8748
- args: [MAX_RETRIES3]
8979
+ args: [MAX_RETRIES3, ...scope.args]
8749
8980
  });
8750
8981
  let delivered = 0;
8751
8982
  for (const row of result.rows) {
@@ -8764,6 +8995,7 @@ var init_messaging = __esm({
8764
8995
  "use strict";
8765
8996
  init_database();
8766
8997
  init_tmux_routing();
8998
+ init_task_scope();
8767
8999
  MAX_RETRIES3 = 10;
8768
9000
  _wsClientSend = null;
8769
9001
  }
@@ -8961,11 +9193,11 @@ init_crm_bridge();
8961
9193
 
8962
9194
  // src/lib/pipeline-router.ts
8963
9195
  init_database();
8964
- import crypto2 from "crypto";
9196
+ import crypto3 from "crypto";
8965
9197
  async function sinkConversationStore(msg, agentResponse, agentName) {
8966
9198
  try {
8967
9199
  const client = getClient();
8968
- const id = crypto2.randomUUID();
9200
+ const id = crypto3.randomUUID();
8969
9201
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
8970
9202
  await client.execute({
8971
9203
  sql: `INSERT INTO conversations
@@ -9015,7 +9247,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
9015
9247
  ].filter(Boolean).join("\n");
9016
9248
  const vector = await embed2(rawText);
9017
9249
  await writeMemory2({
9018
- id: crypto2.randomUUID(),
9250
+ id: crypto3.randomUUID(),
9019
9251
  agent_id: agentName ?? "gateway",
9020
9252
  agent_role: "gateway",
9021
9253
  session_id: `gateway-${msg.platform}`,
@@ -11697,10 +11929,10 @@ var SlackAdapter = class {
11697
11929
  import { execFile } from "child_process";
11698
11930
  import { promisify } from "util";
11699
11931
  import os6 from "os";
11700
- import path8 from "path";
11932
+ import path9 from "path";
11701
11933
  var execFileAsync = promisify(execFile);
11702
11934
  var POLL_INTERVAL_MS = 5e3;
11703
- var MESSAGES_DB_PATH = path8.join(
11935
+ var MESSAGES_DB_PATH = path9.join(
11704
11936
  process.env.HOME ?? os6.homedir(),
11705
11937
  "Library/Messages/chat.db"
11706
11938
  );
@@ -12544,11 +12776,11 @@ async function ensureCRMContact(info) {
12544
12776
  }
12545
12777
 
12546
12778
  // src/automation/trigger-engine.ts
12547
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
12779
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
12548
12780
  import { randomUUID as randomUUID12 } from "crypto";
12549
- import path20 from "path";
12550
- import os12 from "os";
12551
- var TRIGGERS_PATH = path20.join(os12.homedir(), ".exe-os", "triggers.json");
12781
+ import path21 from "path";
12782
+ import os13 from "os";
12783
+ var TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
12552
12784
  var GRAPH_API_VERSION = "v21.0";
12553
12785
  function substituteTemplate(template, record) {
12554
12786
  return template.replace(
@@ -12602,9 +12834,9 @@ function evaluateConditions(conditions, record) {
12602
12834
  return conditions.every((c) => evaluateCondition(c, record));
12603
12835
  }
12604
12836
  function loadTriggers(project) {
12605
- if (!existsSync15(TRIGGERS_PATH)) return [];
12837
+ if (!existsSync17(TRIGGERS_PATH)) return [];
12606
12838
  try {
12607
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
12839
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
12608
12840
  const all = JSON.parse(raw);
12609
12841
  if (!Array.isArray(all)) return [];
12610
12842
  if (project) {