@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -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,13 @@ var init_session_kill_telemetry = __esm({
5867
6081
  }
5868
6082
  });
5869
6083
 
5870
- // src/lib/task-scope.ts
5871
- function getCurrentSessionScope() {
5872
- try {
5873
- return resolveExeSession();
5874
- } catch {
5875
- return null;
5876
- }
5877
- }
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
- };
5886
- }
5887
- var init_task_scope = __esm({
5888
- "src/lib/task-scope.ts"() {
5889
- "use strict";
5890
- init_tmux_routing();
5891
- }
5892
- });
5893
-
5894
6084
  // src/lib/tasks-crud.ts
5895
- import crypto5 from "crypto";
5896
- import path14 from "path";
5897
- import os10 from "os";
6085
+ import crypto6 from "crypto";
6086
+ import path15 from "path";
6087
+ import os11 from "os";
5898
6088
  import { execSync as execSync4 } from "child_process";
5899
6089
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5900
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
6090
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
5901
6091
  async function writeCheckpoint(input) {
5902
6092
  const client = getClient();
5903
6093
  const row = await resolveTask(client, input.taskId);
@@ -6013,7 +6203,7 @@ async function resolveTask(client, identifier, scopeSession) {
6013
6203
  }
6014
6204
  async function createTaskCore(input) {
6015
6205
  const client = getClient();
6016
- const id = crypto5.randomUUID();
6206
+ const id = crypto6.randomUUID();
6017
6207
  const now = (/* @__PURE__ */ new Date()).toISOString();
6018
6208
  const slug = slugify(input.title);
6019
6209
  let earlySessionScope = null;
@@ -6072,8 +6262,8 @@ ${laneWarning}` : laneWarning;
6072
6262
  }
6073
6263
  if (input.baseDir) {
6074
6264
  try {
6075
- await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
6076
- await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
6265
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
6266
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
6077
6267
  await ensureArchitectureDoc(input.baseDir, input.projectName);
6078
6268
  await ensureGitignoreExe(input.baseDir);
6079
6269
  } catch {
@@ -6109,13 +6299,19 @@ ${laneWarning}` : laneWarning;
6109
6299
  });
6110
6300
  if (input.baseDir) {
6111
6301
  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 });
6302
+ const EXE_OS_DIR = path15.join(os11.homedir(), ".exe-os");
6303
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
6304
+ const mdDir = path15.dirname(mdPath);
6305
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
6116
6306
  const reviewer = input.reviewer ?? input.assignedBy;
6117
6307
  const mdContent = `# ${input.title}
6118
6308
 
6309
+ ## MANDATORY: When done
6310
+
6311
+ You MUST call update_task with status "done" and a result summary when finished.
6312
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
6313
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
6314
+
6119
6315
  **ID:** ${id}
6120
6316
  **Status:** ${initialStatus}
6121
6317
  **Priority:** ${input.priority}
@@ -6129,12 +6325,6 @@ ${laneWarning}` : laneWarning;
6129
6325
  ## Context
6130
6326
 
6131
6327
  ${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
6328
  `;
6139
6329
  await writeFile4(mdPath, mdContent, "utf-8");
6140
6330
  } catch (err) {
@@ -6383,7 +6573,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
6383
6573
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
6384
6574
  } catch {
6385
6575
  }
6386
- if (input.status === "done" || input.status === "cancelled") {
6576
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
6387
6577
  try {
6388
6578
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
6389
6579
  clearQueueForAgent2(String(row.assigned_to));
@@ -6412,9 +6602,9 @@ async function deleteTaskCore(taskId, _baseDir) {
6412
6602
  return { taskFile, assignedTo, assignedBy, taskSlug };
6413
6603
  }
6414
6604
  async function ensureArchitectureDoc(baseDir, projectName) {
6415
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
6605
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
6416
6606
  try {
6417
- if (existsSync12(archPath)) return;
6607
+ if (existsSync14(archPath)) return;
6418
6608
  const template = [
6419
6609
  `# ${projectName} \u2014 System Architecture`,
6420
6610
  "",
@@ -6447,10 +6637,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
6447
6637
  }
6448
6638
  }
6449
6639
  async function ensureGitignoreExe(baseDir) {
6450
- const gitignorePath = path14.join(baseDir, ".gitignore");
6640
+ const gitignorePath = path15.join(baseDir, ".gitignore");
6451
6641
  try {
6452
- if (existsSync12(gitignorePath)) {
6453
- const content = readFileSync11(gitignorePath, "utf-8");
6642
+ if (existsSync14(gitignorePath)) {
6643
+ const content = readFileSync12(gitignorePath, "utf-8");
6454
6644
  if (/^\/?exe\/?$/m.test(content)) return;
6455
6645
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
6456
6646
  } else {
@@ -6481,58 +6671,42 @@ var init_tasks_crud = __esm({
6481
6671
  });
6482
6672
 
6483
6673
  // src/lib/tasks-review.ts
6484
- import path15 from "path";
6485
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
6674
+ import path16 from "path";
6675
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
6486
6676
  async function countPendingReviews(sessionScope) {
6487
6677
  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
- }
6678
+ const scope = strictSessionScopeFilter(
6679
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6680
+ );
6495
6681
  const result = await client.execute({
6496
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
6497
- args: []
6682
+ sql: `SELECT COUNT(*) as cnt FROM tasks
6683
+ WHERE status = 'needs_review'${scope.sql}`,
6684
+ args: [...scope.args]
6498
6685
  });
6499
6686
  return Number(result.rows[0]?.cnt) || 0;
6500
6687
  }
6501
6688
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
6502
6689
  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
- }
6690
+ const scope = strictSessionScopeFilter(
6691
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6692
+ );
6512
6693
  const result = await client.execute({
6513
6694
  sql: `SELECT COUNT(*) as cnt FROM tasks
6514
- WHERE status = 'needs_review' AND updated_at > ?`,
6515
- args: [sinceIso]
6695
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
6696
+ args: [sinceIso, ...scope.args]
6516
6697
  });
6517
6698
  return Number(result.rows[0]?.cnt) || 0;
6518
6699
  }
6519
6700
  async function listPendingReviews(limit, sessionScope) {
6520
6701
  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
- }
6702
+ const scope = strictSessionScopeFilter(
6703
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6704
+ );
6531
6705
  const result = await client.execute({
6532
6706
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
6533
- WHERE status = 'needs_review'
6707
+ WHERE status = 'needs_review'${scope.sql}
6534
6708
  ORDER BY updated_at ASC LIMIT ?`,
6535
- args: [limit]
6709
+ args: [...scope.args, limit]
6536
6710
  });
6537
6711
  return result.rows;
6538
6712
  }
@@ -6544,7 +6718,7 @@ async function cleanupOrphanedReviews() {
6544
6718
  WHERE status IN ('open', 'needs_review', 'in_progress')
6545
6719
  AND assigned_by = 'system'
6546
6720
  AND title LIKE 'Review:%'
6547
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
6721
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
6548
6722
  args: [now]
6549
6723
  });
6550
6724
  const r1b = await client.execute({
@@ -6663,11 +6837,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
6663
6837
  );
6664
6838
  }
6665
6839
  try {
6666
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
6667
- if (existsSync13(cacheDir)) {
6840
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
6841
+ if (existsSync15(cacheDir)) {
6668
6842
  for (const f of readdirSync3(cacheDir)) {
6669
6843
  if (f.startsWith("review-notified-")) {
6670
- unlinkSync4(path15.join(cacheDir, f));
6844
+ unlinkSync4(path16.join(cacheDir, f));
6671
6845
  }
6672
6846
  }
6673
6847
  }
@@ -6684,11 +6858,12 @@ var init_tasks_review = __esm({
6684
6858
  init_tmux_routing();
6685
6859
  init_session_key();
6686
6860
  init_state_bus();
6861
+ init_task_scope();
6687
6862
  }
6688
6863
  });
6689
6864
 
6690
6865
  // src/lib/tasks-chain.ts
6691
- import path16 from "path";
6866
+ import path17 from "path";
6692
6867
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
6693
6868
  async function cascadeUnblock(taskId, baseDir, now) {
6694
6869
  const client = getClient();
@@ -6705,7 +6880,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
6705
6880
  });
6706
6881
  for (const ur of unblockedRows.rows) {
6707
6882
  try {
6708
- const ubFile = path16.join(baseDir, String(ur.task_file));
6883
+ const ubFile = path17.join(baseDir, String(ur.task_file));
6709
6884
  let ubContent = await readFile4(ubFile, "utf-8");
6710
6885
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
6711
6886
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -6740,7 +6915,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
6740
6915
  const scScope = sessionScopeFilter();
6741
6916
  const remaining = await client.execute({
6742
6917
  sql: `SELECT COUNT(*) as cnt FROM tasks
6743
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
6918
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
6744
6919
  args: [parentTaskId, ...scScope.args]
6745
6920
  });
6746
6921
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -6774,7 +6949,7 @@ var init_tasks_chain = __esm({
6774
6949
 
6775
6950
  // src/lib/project-name.ts
6776
6951
  import { execSync as execSync5 } from "child_process";
6777
- import path17 from "path";
6952
+ import path18 from "path";
6778
6953
  function getProjectName(cwd) {
6779
6954
  const dir = cwd ?? process.cwd();
6780
6955
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -6787,7 +6962,7 @@ function getProjectName(cwd) {
6787
6962
  timeout: 2e3,
6788
6963
  stdio: ["pipe", "pipe", "pipe"]
6789
6964
  }).trim();
6790
- repoRoot = path17.dirname(gitCommonDir);
6965
+ repoRoot = path18.dirname(gitCommonDir);
6791
6966
  } catch {
6792
6967
  repoRoot = execSync5("git rev-parse --show-toplevel", {
6793
6968
  cwd: dir,
@@ -6796,11 +6971,11 @@ function getProjectName(cwd) {
6796
6971
  stdio: ["pipe", "pipe", "pipe"]
6797
6972
  }).trim();
6798
6973
  }
6799
- _cached2 = path17.basename(repoRoot);
6974
+ _cached2 = path18.basename(repoRoot);
6800
6975
  _cachedCwd = dir;
6801
6976
  return _cached2;
6802
6977
  } catch {
6803
- _cached2 = path17.basename(dir);
6978
+ _cached2 = path18.basename(dir);
6804
6979
  _cachedCwd = dir;
6805
6980
  return _cached2;
6806
6981
  }
@@ -6943,10 +7118,10 @@ var init_tasks_notify = __esm({
6943
7118
  });
6944
7119
 
6945
7120
  // src/lib/behaviors.ts
6946
- import crypto6 from "crypto";
7121
+ import crypto7 from "crypto";
6947
7122
  async function storeBehavior(opts) {
6948
7123
  const client = getClient();
6949
- const id = crypto6.randomUUID();
7124
+ const id = crypto7.randomUUID();
6950
7125
  const now = (/* @__PURE__ */ new Date()).toISOString();
6951
7126
  await client.execute({
6952
7127
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -6975,7 +7150,7 @@ __export(skill_learning_exports, {
6975
7150
  storeTrajectory: () => storeTrajectory,
6976
7151
  sweepTrajectories: () => sweepTrajectories
6977
7152
  });
6978
- import crypto7 from "crypto";
7153
+ import crypto8 from "crypto";
6979
7154
  async function extractTrajectory(taskId, agentId) {
6980
7155
  const client = getClient();
6981
7156
  const result = await client.execute({
@@ -7004,11 +7179,11 @@ async function extractTrajectory(taskId, agentId) {
7004
7179
  return signature;
7005
7180
  }
7006
7181
  function hashSignature(signature) {
7007
- return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
7182
+ return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
7008
7183
  }
7009
7184
  async function storeTrajectory(opts) {
7010
7185
  const client = getClient();
7011
- const id = crypto7.randomUUID();
7186
+ const id = crypto8.randomUUID();
7012
7187
  const now = (/* @__PURE__ */ new Date()).toISOString();
7013
7188
  const signatureHash = hashSignature(opts.signature);
7014
7189
  await client.execute({
@@ -7273,8 +7448,8 @@ __export(tasks_exports, {
7273
7448
  updateTaskStatus: () => updateTaskStatus,
7274
7449
  writeCheckpoint: () => writeCheckpoint
7275
7450
  });
7276
- import path18 from "path";
7277
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
7451
+ import path19 from "path";
7452
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
7278
7453
  async function createTask(input) {
7279
7454
  const result = await createTaskCore(input);
7280
7455
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -7293,12 +7468,12 @@ async function updateTask(input) {
7293
7468
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
7294
7469
  try {
7295
7470
  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`);
7471
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
7472
+ const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
7298
7473
  if (input.status === "in_progress") {
7299
7474
  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") {
7475
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7476
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
7302
7477
  try {
7303
7478
  unlinkSync5(cachePath);
7304
7479
  } catch {
@@ -7306,10 +7481,10 @@ async function updateTask(input) {
7306
7481
  }
7307
7482
  } catch {
7308
7483
  }
7309
- if (input.status === "done") {
7484
+ if (input.status === "done" || input.status === "closed") {
7310
7485
  await cleanupReviewFile(row, taskFile, input.baseDir);
7311
7486
  }
7312
- if (input.status === "done" || input.status === "cancelled") {
7487
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
7313
7488
  try {
7314
7489
  const client = getClient();
7315
7490
  const taskTitle = String(row.title);
@@ -7325,7 +7500,7 @@ async function updateTask(input) {
7325
7500
  if (!isCoordinatorName(assignedAgent)) {
7326
7501
  try {
7327
7502
  const draftClient = getClient();
7328
- if (input.status === "done") {
7503
+ if (input.status === "done" || input.status === "closed") {
7329
7504
  await draftClient.execute({
7330
7505
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
7331
7506
  args: [assignedAgent]
@@ -7342,7 +7517,7 @@ async function updateTask(input) {
7342
7517
  try {
7343
7518
  const client = getClient();
7344
7519
  const cascaded = await client.execute({
7345
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
7520
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
7346
7521
  WHERE parent_task_id = ? AND status = 'needs_review'`,
7347
7522
  args: [now, taskId]
7348
7523
  });
@@ -7355,14 +7530,14 @@ async function updateTask(input) {
7355
7530
  } catch {
7356
7531
  }
7357
7532
  }
7358
- const isTerminal = input.status === "done" || input.status === "needs_review";
7533
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
7359
7534
  if (isTerminal) {
7360
7535
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
7361
7536
  if (!isCoordinator) {
7362
7537
  notifyTaskDone();
7363
7538
  }
7364
7539
  await markTaskNotificationsRead(taskFile);
7365
- if (input.status === "done") {
7540
+ if (input.status === "done" || input.status === "closed") {
7366
7541
  try {
7367
7542
  await cascadeUnblock(taskId, input.baseDir, now);
7368
7543
  } catch {
@@ -7382,7 +7557,7 @@ async function updateTask(input) {
7382
7557
  }
7383
7558
  }
7384
7559
  }
7385
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
7560
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
7386
7561
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
7387
7562
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
7388
7563
  taskId,
@@ -7754,6 +7929,7 @@ __export(tmux_routing_exports, {
7754
7929
  isEmployeeAlive: () => isEmployeeAlive,
7755
7930
  isExeSession: () => isExeSession,
7756
7931
  isSessionBusy: () => isSessionBusy,
7932
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
7757
7933
  notifyParentExe: () => notifyParentExe,
7758
7934
  parseParentExe: () => parseParentExe,
7759
7935
  registerParentExe: () => registerParentExe,
@@ -7764,13 +7940,13 @@ __export(tmux_routing_exports, {
7764
7940
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
7765
7941
  });
7766
7942
  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";
7943
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
7944
+ import path20 from "path";
7945
+ import os12 from "os";
7770
7946
  import { fileURLToPath as fileURLToPath2 } from "url";
7771
7947
  import { unlinkSync as unlinkSync6 } from "fs";
7772
7948
  function spawnLockPath(sessionName) {
7773
- return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7949
+ return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7774
7950
  }
7775
7951
  function isProcessAlive(pid) {
7776
7952
  try {
@@ -7781,13 +7957,13 @@ function isProcessAlive(pid) {
7781
7957
  }
7782
7958
  }
7783
7959
  function acquireSpawnLock2(sessionName) {
7784
- if (!existsSync14(SPAWN_LOCK_DIR)) {
7960
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
7785
7961
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
7786
7962
  }
7787
7963
  const lockFile = spawnLockPath(sessionName);
7788
- if (existsSync14(lockFile)) {
7964
+ if (existsSync16(lockFile)) {
7789
7965
  try {
7790
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
7966
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
7791
7967
  const age = Date.now() - lock.timestamp;
7792
7968
  if (isProcessAlive(lock.pid) && age < 6e4) {
7793
7969
  return false;
@@ -7795,7 +7971,7 @@ function acquireSpawnLock2(sessionName) {
7795
7971
  } catch {
7796
7972
  }
7797
7973
  }
7798
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7974
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7799
7975
  return true;
7800
7976
  }
7801
7977
  function releaseSpawnLock2(sessionName) {
@@ -7807,13 +7983,13 @@ function releaseSpawnLock2(sessionName) {
7807
7983
  function resolveBehaviorsExporterScript() {
7808
7984
  try {
7809
7985
  const thisFile = fileURLToPath2(import.meta.url);
7810
- const scriptPath = path19.join(
7811
- path19.dirname(thisFile),
7986
+ const scriptPath = path20.join(
7987
+ path20.dirname(thisFile),
7812
7988
  "..",
7813
7989
  "bin",
7814
7990
  "exe-export-behaviors.js"
7815
7991
  );
7816
- return existsSync14(scriptPath) ? scriptPath : null;
7992
+ return existsSync16(scriptPath) ? scriptPath : null;
7817
7993
  } catch {
7818
7994
  return null;
7819
7995
  }
@@ -7879,12 +8055,12 @@ function extractRootExe(name) {
7879
8055
  return parts.length > 0 ? parts[parts.length - 1] : null;
7880
8056
  }
7881
8057
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7882
- if (!existsSync14(SESSION_CACHE)) {
8058
+ if (!existsSync16(SESSION_CACHE)) {
7883
8059
  mkdirSync8(SESSION_CACHE, { recursive: true });
7884
8060
  }
7885
8061
  const rootExe = extractRootExe(parentExe) ?? parentExe;
7886
- const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7887
- writeFileSync7(filePath, JSON.stringify({
8062
+ const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
8063
+ writeFileSync8(filePath, JSON.stringify({
7888
8064
  parentExe: rootExe,
7889
8065
  dispatchedBy: dispatchedBy || rootExe,
7890
8066
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7892,7 +8068,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7892
8068
  }
7893
8069
  function getParentExe(sessionKey) {
7894
8070
  try {
7895
- const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
8071
+ const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
7896
8072
  return data.parentExe || null;
7897
8073
  } catch {
7898
8074
  return null;
@@ -7900,8 +8076,8 @@ function getParentExe(sessionKey) {
7900
8076
  }
7901
8077
  function getDispatchedBy(sessionKey) {
7902
8078
  try {
7903
- const data = JSON.parse(readFileSync12(
7904
- path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
8079
+ const data = JSON.parse(readFileSync13(
8080
+ path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
7905
8081
  "utf8"
7906
8082
  ));
7907
8083
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -7971,8 +8147,8 @@ async function verifyPaneAtCapacity(sessionName) {
7971
8147
  }
7972
8148
  function readDebounceState() {
7973
8149
  try {
7974
- if (!existsSync14(DEBOUNCE_FILE)) return {};
7975
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
8150
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
8151
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
7976
8152
  const state = {};
7977
8153
  for (const [key, val] of Object.entries(raw)) {
7978
8154
  if (typeof val === "number") {
@@ -7988,8 +8164,8 @@ function readDebounceState() {
7988
8164
  }
7989
8165
  function writeDebounceState(state) {
7990
8166
  try {
7991
- if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
7992
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
8167
+ if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
8168
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
7993
8169
  } catch {
7994
8170
  }
7995
8171
  }
@@ -8087,8 +8263,8 @@ function sendIntercom(targetSession) {
8087
8263
  try {
8088
8264
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8089
8265
  const agent = baseAgentName(rawAgent);
8090
- const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
8091
- if (existsSync14(markerPath)) {
8266
+ const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
8267
+ if (existsSync16(markerPath)) {
8092
8268
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
8093
8269
  return "debounced";
8094
8270
  }
@@ -8097,8 +8273,8 @@ function sendIntercom(targetSession) {
8097
8273
  try {
8098
8274
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8099
8275
  const agent = baseAgentName(rawAgent);
8100
- const taskDir = path19.join(process.cwd(), "exe", agent);
8101
- if (existsSync14(taskDir)) {
8276
+ const taskDir = path20.join(process.cwd(), "exe", agent);
8277
+ if (existsSync16(taskDir)) {
8102
8278
  const files = readdirSync4(taskDir).filter(
8103
8279
  (f) => f.endsWith(".md") && f !== "DONE.txt"
8104
8280
  );
@@ -8158,6 +8334,21 @@ function notifyParentExe(sessionKey) {
8158
8334
  }
8159
8335
  return true;
8160
8336
  }
8337
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
8338
+ const transport = getTransport();
8339
+ try {
8340
+ const sessions = transport.listSessions();
8341
+ if (!sessions.includes(coordinatorSession)) return false;
8342
+ execSync6(
8343
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8344
+ { timeout: 3e3 }
8345
+ );
8346
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
8347
+ return true;
8348
+ } catch {
8349
+ return false;
8350
+ }
8351
+ }
8161
8352
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
8162
8353
  if (isCoordinatorName(employeeName)) {
8163
8354
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -8231,26 +8422,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8231
8422
  const transport = getTransport();
8232
8423
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
8233
8424
  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)) {
8425
+ const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
8426
+ const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8427
+ if (!existsSync16(logDir)) {
8237
8428
  mkdirSync8(logDir, { recursive: true });
8238
8429
  }
8239
8430
  transport.kill(sessionName);
8240
8431
  let cleanupSuffix = "";
8241
8432
  try {
8242
8433
  const thisFile = fileURLToPath2(import.meta.url);
8243
- const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8244
- if (existsSync14(cleanupScript)) {
8434
+ const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8435
+ if (existsSync16(cleanupScript)) {
8245
8436
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
8246
8437
  }
8247
8438
  } catch {
8248
8439
  }
8249
8440
  try {
8250
- const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
8441
+ const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
8251
8442
  let claudeJson = {};
8252
8443
  try {
8253
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
8444
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
8254
8445
  } catch {
8255
8446
  }
8256
8447
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -8258,17 +8449,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8258
8449
  const trustDir = opts?.cwd ?? projectDir;
8259
8450
  if (!projects[trustDir]) projects[trustDir] = {};
8260
8451
  projects[trustDir].hasTrustDialogAccepted = true;
8261
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8452
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8262
8453
  } catch {
8263
8454
  }
8264
8455
  try {
8265
- const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
8456
+ const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
8266
8457
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
8267
- const projSettingsDir = path19.join(settingsDir, normalizedKey);
8268
- const settingsPath = path19.join(projSettingsDir, "settings.json");
8458
+ const projSettingsDir = path20.join(settingsDir, normalizedKey);
8459
+ const settingsPath = path20.join(projSettingsDir, "settings.json");
8269
8460
  let settings = {};
8270
8461
  try {
8271
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
8462
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
8272
8463
  } catch {
8273
8464
  }
8274
8465
  const perms = settings.permissions ?? {};
@@ -8297,7 +8488,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8297
8488
  perms.allow = allow;
8298
8489
  settings.permissions = perms;
8299
8490
  mkdirSync8(projSettingsDir, { recursive: true });
8300
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8491
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8301
8492
  }
8302
8493
  } catch {
8303
8494
  }
@@ -8312,8 +8503,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8312
8503
  let behaviorsFlag = "";
8313
8504
  let legacyFallbackWarned = false;
8314
8505
  if (!useExeAgent && !useBinSymlink) {
8315
- const identityPath = path19.join(
8316
- os11.homedir(),
8506
+ const identityPath = path20.join(
8507
+ os12.homedir(),
8317
8508
  ".exe-os",
8318
8509
  "identity",
8319
8510
  `${employeeName}.md`
@@ -8322,13 +8513,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8322
8513
  const hasAgentFlag = claudeSupportsAgentFlag();
8323
8514
  if (hasAgentFlag) {
8324
8515
  identityFlag = ` --agent ${employeeName}`;
8325
- } else if (existsSync14(identityPath)) {
8516
+ } else if (existsSync16(identityPath)) {
8326
8517
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
8327
8518
  legacyFallbackWarned = true;
8328
8519
  }
8329
8520
  const behaviorsFile = exportBehaviorsSync(
8330
8521
  employeeName,
8331
- path19.basename(spawnCwd),
8522
+ path20.basename(spawnCwd),
8332
8523
  sessionName
8333
8524
  );
8334
8525
  if (behaviorsFile) {
@@ -8343,16 +8534,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8343
8534
  }
8344
8535
  let sessionContextFlag = "";
8345
8536
  try {
8346
- const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
8537
+ const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
8347
8538
  mkdirSync8(ctxDir, { recursive: true });
8348
- const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
8539
+ const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
8349
8540
  const ctxContent = [
8350
8541
  `## Session Context`,
8351
8542
  `You are running in tmux session: ${sessionName}.`,
8352
8543
  `Your parent coordinator session is ${exeSession}.`,
8353
8544
  `Your employees (if any) use the -${exeSession} suffix.`
8354
8545
  ].join("\n");
8355
- writeFileSync7(ctxFile, ctxContent);
8546
+ writeFileSync8(ctxFile, ctxContent);
8356
8547
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
8357
8548
  } catch {
8358
8549
  }
@@ -8429,8 +8620,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8429
8620
  transport.pipeLog(sessionName, logFile);
8430
8621
  try {
8431
8622
  const mySession = getMySession();
8432
- const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8433
- writeFileSync7(dispatchInfo, JSON.stringify({
8623
+ const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8624
+ writeFileSync8(dispatchInfo, JSON.stringify({
8434
8625
  dispatchedBy: mySession,
8435
8626
  rootExe: exeSession,
8436
8627
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -8504,15 +8695,15 @@ var init_tmux_routing = __esm({
8504
8695
  init_intercom_queue();
8505
8696
  init_plan_limits();
8506
8697
  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");
8698
+ SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
8699
+ SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
8509
8700
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
8510
8701
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
8511
8702
  VERIFY_PANE_LINES = 200;
8512
8703
  INTERCOM_DEBOUNCE_MS = 3e4;
8513
8704
  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");
8705
+ INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
8706
+ DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
8516
8707
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
8517
8708
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
8518
8709
  }
@@ -8535,10 +8726,10 @@ __export(messaging_exports, {
8535
8726
  sendMessage: () => sendMessage,
8536
8727
  setWsClientSend: () => setWsClientSend
8537
8728
  });
8538
- import crypto8 from "crypto";
8729
+ import crypto9 from "crypto";
8539
8730
  function generateUlid() {
8540
8731
  const timestamp = Date.now().toString(36).padStart(10, "0");
8541
- const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
8732
+ const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
8542
8733
  return (timestamp + random).toUpperCase();
8543
8734
  }
8544
8735
  function rowToMessage(row) {
@@ -8549,6 +8740,7 @@ function rowToMessage(row) {
8549
8740
  targetAgent: row.target_agent,
8550
8741
  targetProject: row.target_project ?? null,
8551
8742
  targetDevice: row.target_device,
8743
+ sessionScope: row.session_scope ?? null,
8552
8744
  content: row.content,
8553
8745
  priority: row.priority ?? "normal",
8554
8746
  status: row.status ?? "pending",
@@ -8566,15 +8758,17 @@ async function sendMessage(input) {
8566
8758
  const id = generateUlid();
8567
8759
  const now = (/* @__PURE__ */ new Date()).toISOString();
8568
8760
  const targetDevice = input.targetDevice ?? "local";
8761
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
8569
8762
  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', ?)`,
8763
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
8764
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
8572
8765
  args: [
8573
8766
  id,
8574
8767
  input.fromAgent,
8575
8768
  input.targetAgent,
8576
8769
  input.targetProject ?? null,
8577
8770
  targetDevice,
8771
+ sessionScope,
8578
8772
  input.content,
8579
8773
  input.priority ?? "normal",
8580
8774
  now
@@ -8588,9 +8782,10 @@ async function sendMessage(input) {
8588
8782
  }
8589
8783
  } catch {
8590
8784
  }
8785
+ const sentScope = strictSessionScopeFilter(sessionScope);
8591
8786
  const result = await client.execute({
8592
- sql: "SELECT * FROM messages WHERE id = ?",
8593
- args: [id]
8787
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
8788
+ args: [id, ...sentScope.args]
8594
8789
  });
8595
8790
  return rowToMessage(result.rows[0]);
8596
8791
  }
@@ -8614,6 +8809,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
8614
8809
  fromAgent: msg.fromAgent,
8615
8810
  targetAgent: msg.targetAgent,
8616
8811
  targetProject: msg.targetProject,
8812
+ sessionScope: msg.sessionScope,
8617
8813
  content: msg.content,
8618
8814
  priority: msg.priority,
8619
8815
  createdAt: msg.createdAt
@@ -8657,7 +8853,7 @@ async function deliverLocalMessage(messageId) {
8657
8853
  } catch {
8658
8854
  const newRetryCount = msg.retryCount + 1;
8659
8855
  if (newRetryCount >= MAX_RETRIES3) {
8660
- await markFailed(messageId, "session unavailable after 10 retries");
8856
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
8661
8857
  } else {
8662
8858
  await client.execute({
8663
8859
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -8667,85 +8863,101 @@ async function deliverLocalMessage(messageId) {
8667
8863
  return false;
8668
8864
  }
8669
8865
  }
8670
- async function getPendingMessages(targetAgent) {
8866
+ async function getPendingMessages(targetAgent, sessionScope) {
8671
8867
  const client = getClient();
8868
+ const scope = strictSessionScopeFilter(sessionScope);
8672
8869
  const result = await client.execute({
8673
8870
  sql: `SELECT * FROM messages
8674
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
8871
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
8675
8872
  ORDER BY id`,
8676
- args: [targetAgent]
8873
+ args: [targetAgent, ...scope.args]
8677
8874
  });
8678
8875
  return result.rows.map((row) => rowToMessage(row));
8679
8876
  }
8680
- async function markRead(messageId) {
8877
+ async function markRead(messageId, sessionScope) {
8681
8878
  const client = getClient();
8879
+ const scope = strictSessionScopeFilter(sessionScope);
8682
8880
  await client.execute({
8683
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
8684
- args: [messageId]
8881
+ sql: `UPDATE messages SET status = 'read'
8882
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
8883
+ args: [messageId, ...scope.args]
8685
8884
  });
8686
8885
  }
8687
- async function markAcknowledged(messageId) {
8886
+ async function markAcknowledged(messageId, sessionScope) {
8688
8887
  const client = getClient();
8888
+ const scope = strictSessionScopeFilter(sessionScope);
8689
8889
  await client.execute({
8690
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
8691
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8890
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
8891
+ WHERE id = ? AND status = 'read'${scope.sql}`,
8892
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
8692
8893
  });
8693
8894
  }
8694
- async function markProcessed(messageId) {
8895
+ async function markProcessed(messageId, sessionScope) {
8695
8896
  const client = getClient();
8897
+ const scope = strictSessionScopeFilter(sessionScope);
8696
8898
  await client.execute({
8697
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
8698
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8899
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
8900
+ WHERE id = ?${scope.sql}`,
8901
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
8699
8902
  });
8700
8903
  }
8701
- async function getMessageStatus(messageId) {
8904
+ async function getMessageStatus(messageId, sessionScope) {
8702
8905
  const client = getClient();
8906
+ const scope = strictSessionScopeFilter(sessionScope);
8703
8907
  const result = await client.execute({
8704
- sql: "SELECT status FROM messages WHERE id = ?",
8705
- args: [messageId]
8908
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
8909
+ args: [messageId, ...scope.args]
8706
8910
  });
8707
8911
  return result.rows[0]?.status ?? null;
8708
8912
  }
8709
- async function getUnacknowledgedMessages(targetAgent) {
8913
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
8710
8914
  const client = getClient();
8915
+ const scope = strictSessionScopeFilter(sessionScope);
8711
8916
  const result = await client.execute({
8712
8917
  sql: `SELECT * FROM messages
8713
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
8918
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
8714
8919
  ORDER BY id`,
8715
- args: [targetAgent]
8920
+ args: [targetAgent, ...scope.args]
8716
8921
  });
8717
8922
  return result.rows.map((row) => rowToMessage(row));
8718
8923
  }
8719
- async function getReadMessages(targetAgent) {
8924
+ async function getReadMessages(targetAgent, sessionScope) {
8720
8925
  const client = getClient();
8926
+ const scope = strictSessionScopeFilter(sessionScope);
8721
8927
  const result = await client.execute({
8722
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
8723
- args: [targetAgent]
8928
+ sql: `SELECT * FROM messages
8929
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
8930
+ ORDER BY id`,
8931
+ args: [targetAgent, ...scope.args]
8724
8932
  });
8725
8933
  return result.rows.map((row) => rowToMessage(row));
8726
8934
  }
8727
- async function markFailed(messageId, reason) {
8935
+ async function markFailed(messageId, reason, sessionScope) {
8728
8936
  const client = getClient();
8937
+ const scope = strictSessionScopeFilter(sessionScope);
8729
8938
  await client.execute({
8730
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
8731
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
8939
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
8940
+ WHERE id = ?${scope.sql}`,
8941
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
8732
8942
  });
8733
8943
  }
8734
- async function getFailedMessages() {
8944
+ async function getFailedMessages(sessionScope) {
8735
8945
  const client = getClient();
8946
+ const scope = strictSessionScopeFilter(sessionScope);
8736
8947
  const result = await client.execute({
8737
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
8738
- args: []
8948
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
8949
+ args: [...scope.args]
8739
8950
  });
8740
8951
  return result.rows.map((row) => rowToMessage(row));
8741
8952
  }
8742
- async function retryPendingMessages() {
8953
+ async function retryPendingMessages(sessionScope) {
8743
8954
  const client = getClient();
8955
+ const scope = strictSessionScopeFilter(sessionScope);
8744
8956
  const result = await client.execute({
8745
8957
  sql: `SELECT * FROM messages
8746
- WHERE status = 'pending' AND retry_count < ?
8958
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
8747
8959
  ORDER BY id`,
8748
- args: [MAX_RETRIES3]
8960
+ args: [MAX_RETRIES3, ...scope.args]
8749
8961
  });
8750
8962
  let delivered = 0;
8751
8963
  for (const row of result.rows) {
@@ -8764,6 +8976,7 @@ var init_messaging = __esm({
8764
8976
  "use strict";
8765
8977
  init_database();
8766
8978
  init_tmux_routing();
8979
+ init_task_scope();
8767
8980
  MAX_RETRIES3 = 10;
8768
8981
  _wsClientSend = null;
8769
8982
  }
@@ -8961,11 +9174,11 @@ init_crm_bridge();
8961
9174
 
8962
9175
  // src/lib/pipeline-router.ts
8963
9176
  init_database();
8964
- import crypto2 from "crypto";
9177
+ import crypto3 from "crypto";
8965
9178
  async function sinkConversationStore(msg, agentResponse, agentName) {
8966
9179
  try {
8967
9180
  const client = getClient();
8968
- const id = crypto2.randomUUID();
9181
+ const id = crypto3.randomUUID();
8969
9182
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
8970
9183
  await client.execute({
8971
9184
  sql: `INSERT INTO conversations
@@ -9015,7 +9228,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
9015
9228
  ].filter(Boolean).join("\n");
9016
9229
  const vector = await embed2(rawText);
9017
9230
  await writeMemory2({
9018
- id: crypto2.randomUUID(),
9231
+ id: crypto3.randomUUID(),
9019
9232
  agent_id: agentName ?? "gateway",
9020
9233
  agent_role: "gateway",
9021
9234
  session_id: `gateway-${msg.platform}`,
@@ -11697,10 +11910,10 @@ var SlackAdapter = class {
11697
11910
  import { execFile } from "child_process";
11698
11911
  import { promisify } from "util";
11699
11912
  import os6 from "os";
11700
- import path8 from "path";
11913
+ import path9 from "path";
11701
11914
  var execFileAsync = promisify(execFile);
11702
11915
  var POLL_INTERVAL_MS = 5e3;
11703
- var MESSAGES_DB_PATH = path8.join(
11916
+ var MESSAGES_DB_PATH = path9.join(
11704
11917
  process.env.HOME ?? os6.homedir(),
11705
11918
  "Library/Messages/chat.db"
11706
11919
  );
@@ -12544,11 +12757,11 @@ async function ensureCRMContact(info) {
12544
12757
  }
12545
12758
 
12546
12759
  // src/automation/trigger-engine.ts
12547
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
12760
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
12548
12761
  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");
12762
+ import path21 from "path";
12763
+ import os13 from "os";
12764
+ var TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
12552
12765
  var GRAPH_API_VERSION = "v21.0";
12553
12766
  function substituteTemplate(template, record) {
12554
12767
  return template.replace(
@@ -12602,9 +12815,9 @@ function evaluateConditions(conditions, record) {
12602
12815
  return conditions.every((c) => evaluateCondition(c, record));
12603
12816
  }
12604
12817
  function loadTriggers(project) {
12605
- if (!existsSync15(TRIGGERS_PATH)) return [];
12818
+ if (!existsSync17(TRIGGERS_PATH)) return [];
12606
12819
  try {
12607
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
12820
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
12608
12821
  const all = JSON.parse(raw);
12609
12822
  if (!Array.isArray(all)) return [];
12610
12823
  if (project) {