@askexenow/exe-os 0.9.7 → 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 +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -307,9 +307,34 @@ var init_provider_table = __esm({
307
307
  }
308
308
  });
309
309
 
310
+ // src/lib/secure-files.ts
311
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
312
+ import { chmod, mkdir } from "fs/promises";
313
+ async function ensurePrivateDir(dirPath) {
314
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
315
+ try {
316
+ await chmod(dirPath, PRIVATE_DIR_MODE);
317
+ } catch {
318
+ }
319
+ }
320
+ async function enforcePrivateFile(filePath) {
321
+ try {
322
+ await chmod(filePath, PRIVATE_FILE_MODE);
323
+ } catch {
324
+ }
325
+ }
326
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
327
+ var init_secure_files = __esm({
328
+ "src/lib/secure-files.ts"() {
329
+ "use strict";
330
+ PRIVATE_DIR_MODE = 448;
331
+ PRIVATE_FILE_MODE = 384;
332
+ }
333
+ });
334
+
310
335
  // src/lib/config.ts
311
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
312
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
336
+ import { readFile, writeFile } from "fs/promises";
337
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
313
338
  import path2 from "path";
314
339
  import os2 from "os";
315
340
  function resolveDataDir() {
@@ -317,7 +342,7 @@ function resolveDataDir() {
317
342
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
318
343
  const newDir = path2.join(os2.homedir(), ".exe-os");
319
344
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
320
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
345
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
321
346
  try {
322
347
  renameSync(legacyDir, newDir);
323
348
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -380,9 +405,9 @@ function normalizeAutoUpdate(raw) {
380
405
  }
381
406
  async function loadConfig() {
382
407
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
383
- await mkdir(dir, { recursive: true });
408
+ await ensurePrivateDir(dir);
384
409
  const configPath = path2.join(dir, "config.json");
385
- if (!existsSync2(configPath)) {
410
+ if (!existsSync3(configPath)) {
386
411
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
387
412
  }
388
413
  const raw = await readFile(configPath, "utf-8");
@@ -395,6 +420,7 @@ async function loadConfig() {
395
420
  `);
396
421
  try {
397
422
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
423
+ await enforcePrivateFile(configPath);
398
424
  } catch {
399
425
  }
400
426
  }
@@ -414,6 +440,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
414
440
  var init_config = __esm({
415
441
  "src/lib/config.ts"() {
416
442
  "use strict";
443
+ init_secure_files();
417
444
  EXE_AI_DIR = resolveDataDir();
418
445
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
419
446
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -518,10 +545,10 @@ var init_runtime_table = __esm({
518
545
  });
519
546
 
520
547
  // src/lib/agent-config.ts
521
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
548
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
522
549
  import path3 from "path";
523
550
  function loadAgentConfig() {
524
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
551
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
525
552
  try {
526
553
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
527
554
  } catch {
@@ -542,6 +569,7 @@ var init_agent_config = __esm({
542
569
  "use strict";
543
570
  init_config();
544
571
  init_runtime_table();
572
+ init_secure_files();
545
573
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
546
574
  DEFAULT_MODELS = {
547
575
  claude: "claude-opus-4",
@@ -560,16 +588,16 @@ __export(intercom_queue_exports, {
560
588
  queueIntercom: () => queueIntercom,
561
589
  readQueue: () => readQueue
562
590
  });
563
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
591
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
564
592
  import path4 from "path";
565
593
  import os3 from "os";
566
594
  function ensureDir() {
567
595
  const dir = path4.dirname(QUEUE_PATH);
568
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
596
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
569
597
  }
570
598
  function readQueue() {
571
599
  try {
572
- if (!existsSync4(QUEUE_PATH)) return [];
600
+ if (!existsSync5(QUEUE_PATH)) return [];
573
601
  return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
574
602
  } catch {
575
603
  return [];
@@ -686,7 +714,7 @@ var init_db_retry = __esm({
686
714
 
687
715
  // src/lib/employees.ts
688
716
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
689
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
717
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
690
718
  import { execSync as execSync3 } from "child_process";
691
719
  import path5 from "path";
692
720
  import os4 from "os";
@@ -707,7 +735,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
707
735
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
708
736
  }
709
737
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
710
- if (!existsSync5(employeesPath)) return [];
738
+ if (!existsSync6(employeesPath)) return [];
711
739
  try {
712
740
  return JSON.parse(readFileSync5(employeesPath, "utf-8"));
713
741
  } catch {
@@ -731,7 +759,7 @@ function isMultiInstance(agentName, employees) {
731
759
  if (!emp) return false;
732
760
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
733
761
  }
734
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
762
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
735
763
  var init_employees = __esm({
736
764
  "src/lib/employees.ts"() {
737
765
  "use strict";
@@ -740,15 +768,40 @@ var init_employees = __esm({
740
768
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
741
769
  COORDINATOR_ROLE = "COO";
742
770
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
771
+ IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
772
+ }
773
+ });
774
+
775
+ // src/lib/database-adapter.ts
776
+ import os5 from "os";
777
+ import path6 from "path";
778
+ import { createRequire } from "module";
779
+ import { pathToFileURL } from "url";
780
+ var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
781
+ var init_database_adapter = __esm({
782
+ "src/lib/database-adapter.ts"() {
783
+ "use strict";
784
+ BOOLEAN_COLUMNS_BY_TABLE = {
785
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
786
+ behaviors: /* @__PURE__ */ new Set(["active"]),
787
+ notifications: /* @__PURE__ */ new Set(["read"]),
788
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
789
+ };
790
+ BOOLEAN_COLUMN_NAMES = new Set(
791
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
792
+ );
743
793
  }
744
794
  });
745
795
 
746
796
  // src/lib/database.ts
747
797
  import { createClient } from "@libsql/client";
748
798
  function getClient() {
749
- if (!_resilientClient) {
799
+ if (!_adapterClient) {
750
800
  throw new Error("Database client not initialized. Call initDatabase() first.");
751
801
  }
802
+ if (process.env.DATABASE_URL) {
803
+ return _adapterClient;
804
+ }
752
805
  if (process.env.EXE_IS_DAEMON === "1") {
753
806
  return _resilientClient;
754
807
  }
@@ -757,30 +810,35 @@ function getClient() {
757
810
  }
758
811
  return _resilientClient;
759
812
  }
760
- var _resilientClient, _daemonClient;
813
+ var _resilientClient, _daemonClient, _adapterClient;
761
814
  var init_database = __esm({
762
815
  "src/lib/database.ts"() {
763
816
  "use strict";
764
817
  init_db_retry();
765
818
  init_employees();
819
+ init_database_adapter();
766
820
  _resilientClient = null;
767
821
  _daemonClient = null;
822
+ _adapterClient = null;
768
823
  }
769
824
  });
770
825
 
771
826
  // src/lib/license.ts
772
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
827
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
773
828
  import { randomUUID } from "crypto";
774
- import path6 from "path";
829
+ import { createRequire as createRequire2 } from "module";
830
+ import { pathToFileURL as pathToFileURL2 } from "url";
831
+ import os6 from "os";
832
+ import path7 from "path";
775
833
  import { jwtVerify, importSPKI } from "jose";
776
834
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
777
835
  var init_license = __esm({
778
836
  "src/lib/license.ts"() {
779
837
  "use strict";
780
838
  init_config();
781
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
782
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
783
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
839
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
840
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
841
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
784
842
  PLAN_LIMITS = {
785
843
  free: { devices: 1, employees: 1, memories: 5e3 },
786
844
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -792,11 +850,11 @@ var init_license = __esm({
792
850
  });
793
851
 
794
852
  // src/lib/plan-limits.ts
795
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
796
- import path7 from "path";
853
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
854
+ import path8 from "path";
797
855
  function getLicenseSync() {
798
856
  try {
799
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
857
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
800
858
  const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
801
859
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
802
860
  const parts = raw.token.split(".");
@@ -835,7 +893,7 @@ function assertEmployeeLimitSync(rosterPath) {
835
893
  const filePath = rosterPath ?? EMPLOYEES_PATH;
836
894
  let count = 0;
837
895
  try {
838
- if (existsSync7(filePath)) {
896
+ if (existsSync8(filePath)) {
839
897
  const raw = readFileSync7(filePath, "utf8");
840
898
  const employees = JSON.parse(raw);
841
899
  count = Array.isArray(employees) ? employees.length : 0;
@@ -865,19 +923,52 @@ var init_plan_limits = __esm({
865
923
  this.name = "PlanLimitError";
866
924
  }
867
925
  };
868
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
926
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
927
+ }
928
+ });
929
+
930
+ // src/lib/task-scope.ts
931
+ function getCurrentSessionScope() {
932
+ try {
933
+ return resolveExeSession();
934
+ } catch {
935
+ return null;
936
+ }
937
+ }
938
+ function sessionScopeFilter(sessionScope, tableAlias) {
939
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
940
+ if (!scope) return { sql: "", args: [] };
941
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
942
+ return {
943
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
944
+ args: [scope]
945
+ };
946
+ }
947
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
948
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
949
+ if (!scope) return { sql: "", args: [] };
950
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
951
+ return {
952
+ sql: ` AND ${col} = ?`,
953
+ args: [scope]
954
+ };
955
+ }
956
+ var init_task_scope = __esm({
957
+ "src/lib/task-scope.ts"() {
958
+ "use strict";
959
+ init_tmux_routing();
869
960
  }
870
961
  });
871
962
 
872
963
  // src/lib/notifications.ts
873
964
  import crypto from "crypto";
874
- import path8 from "path";
875
- import os5 from "os";
965
+ import path9 from "path";
966
+ import os7 from "os";
876
967
  import {
877
968
  readFileSync as readFileSync8,
878
969
  readdirSync,
879
970
  unlinkSync as unlinkSync2,
880
- existsSync as existsSync8,
971
+ existsSync as existsSync9,
881
972
  rmdirSync
882
973
  } from "fs";
883
974
  async function writeNotification(notification) {
@@ -885,9 +976,10 @@ async function writeNotification(notification) {
885
976
  const client = getClient();
886
977
  const id = crypto.randomUUID();
887
978
  const now = (/* @__PURE__ */ new Date()).toISOString();
979
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
888
980
  await client.execute({
889
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
890
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
981
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
982
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
891
983
  args: [
892
984
  id,
893
985
  notification.agentId,
@@ -896,6 +988,7 @@ async function writeNotification(notification) {
896
988
  notification.project,
897
989
  notification.summary,
898
990
  notification.taskFile ?? null,
991
+ sessionScope,
899
992
  now
900
993
  ]
901
994
  });
@@ -904,12 +997,14 @@ async function writeNotification(notification) {
904
997
  `);
905
998
  }
906
999
  }
907
- async function markAsReadByTaskFile(taskFile) {
1000
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
908
1001
  try {
909
1002
  const client = getClient();
1003
+ const scope = strictSessionScopeFilter(sessionScope);
910
1004
  await client.execute({
911
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
912
- args: [taskFile]
1005
+ sql: `UPDATE notifications SET read = 1
1006
+ WHERE task_file = ? AND read = 0${scope.sql}`,
1007
+ args: [taskFile, ...scope.args]
913
1008
  });
914
1009
  } catch {
915
1010
  }
@@ -918,6 +1013,7 @@ var init_notifications = __esm({
918
1013
  "src/lib/notifications.ts"() {
919
1014
  "use strict";
920
1015
  init_database();
1016
+ init_task_scope();
921
1017
  }
922
1018
  });
923
1019
 
@@ -955,30 +1051,6 @@ var init_session_kill_telemetry = __esm({
955
1051
  }
956
1052
  });
957
1053
 
958
- // src/lib/task-scope.ts
959
- function getCurrentSessionScope() {
960
- try {
961
- return resolveExeSession();
962
- } catch {
963
- return null;
964
- }
965
- }
966
- function sessionScopeFilter(sessionScope, tableAlias) {
967
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
968
- if (!scope) return { sql: "", args: [] };
969
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
970
- return {
971
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
972
- args: [scope]
973
- };
974
- }
975
- var init_task_scope = __esm({
976
- "src/lib/task-scope.ts"() {
977
- "use strict";
978
- init_tmux_routing();
979
- }
980
- });
981
-
982
1054
  // src/lib/state-bus.ts
983
1055
  var StateBus, orgBus;
984
1056
  var init_state_bus = __esm({
@@ -1036,11 +1108,11 @@ var init_state_bus = __esm({
1036
1108
 
1037
1109
  // src/lib/tasks-crud.ts
1038
1110
  import crypto3 from "crypto";
1039
- import path9 from "path";
1040
- import os6 from "os";
1111
+ import path10 from "path";
1112
+ import os8 from "os";
1041
1113
  import { execSync as execSync4 } from "child_process";
1042
1114
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1043
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1115
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
1044
1116
  async function writeCheckpoint(input) {
1045
1117
  const client = getClient();
1046
1118
  const row = await resolveTask(client, input.taskId);
@@ -1215,8 +1287,8 @@ ${laneWarning}` : laneWarning;
1215
1287
  }
1216
1288
  if (input.baseDir) {
1217
1289
  try {
1218
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
1219
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
1290
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
1291
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
1220
1292
  await ensureArchitectureDoc(input.baseDir, input.projectName);
1221
1293
  await ensureGitignoreExe(input.baseDir);
1222
1294
  } catch {
@@ -1252,13 +1324,19 @@ ${laneWarning}` : laneWarning;
1252
1324
  });
1253
1325
  if (input.baseDir) {
1254
1326
  try {
1255
- const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
1256
- const mdPath = path9.join(EXE_OS_DIR, taskFile);
1257
- const mdDir = path9.dirname(mdPath);
1258
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
1327
+ const EXE_OS_DIR = path10.join(os8.homedir(), ".exe-os");
1328
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
1329
+ const mdDir = path10.dirname(mdPath);
1330
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
1259
1331
  const reviewer = input.reviewer ?? input.assignedBy;
1260
1332
  const mdContent = `# ${input.title}
1261
1333
 
1334
+ ## MANDATORY: When done
1335
+
1336
+ You MUST call update_task with status "done" and a result summary when finished.
1337
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
1338
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
1339
+
1262
1340
  **ID:** ${id}
1263
1341
  **Status:** ${initialStatus}
1264
1342
  **Priority:** ${input.priority}
@@ -1272,12 +1350,6 @@ ${laneWarning}` : laneWarning;
1272
1350
  ## Context
1273
1351
 
1274
1352
  ${input.context}
1275
-
1276
- ## MANDATORY: When done
1277
-
1278
- You MUST call update_task with status "done" and a result summary when finished.
1279
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
1280
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
1281
1353
  `;
1282
1354
  await writeFile3(mdPath, mdContent, "utf-8");
1283
1355
  } catch (err) {
@@ -1526,7 +1598,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1526
1598
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
1527
1599
  } catch {
1528
1600
  }
1529
- if (input.status === "done" || input.status === "cancelled") {
1601
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
1530
1602
  try {
1531
1603
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
1532
1604
  clearQueueForAgent2(String(row.assigned_to));
@@ -1555,9 +1627,9 @@ async function deleteTaskCore(taskId, _baseDir) {
1555
1627
  return { taskFile, assignedTo, assignedBy, taskSlug };
1556
1628
  }
1557
1629
  async function ensureArchitectureDoc(baseDir, projectName) {
1558
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
1630
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
1559
1631
  try {
1560
- if (existsSync9(archPath)) return;
1632
+ if (existsSync10(archPath)) return;
1561
1633
  const template = [
1562
1634
  `# ${projectName} \u2014 System Architecture`,
1563
1635
  "",
@@ -1590,9 +1662,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
1590
1662
  }
1591
1663
  }
1592
1664
  async function ensureGitignoreExe(baseDir) {
1593
- const gitignorePath = path9.join(baseDir, ".gitignore");
1665
+ const gitignorePath = path10.join(baseDir, ".gitignore");
1594
1666
  try {
1595
- if (existsSync9(gitignorePath)) {
1667
+ if (existsSync10(gitignorePath)) {
1596
1668
  const content = readFileSync9(gitignorePath, "utf-8");
1597
1669
  if (/^\/?exe\/?$/m.test(content)) return;
1598
1670
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
@@ -1624,58 +1696,42 @@ var init_tasks_crud = __esm({
1624
1696
  });
1625
1697
 
1626
1698
  // src/lib/tasks-review.ts
1627
- import path10 from "path";
1628
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
1699
+ import path11 from "path";
1700
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
1629
1701
  async function countPendingReviews(sessionScope) {
1630
1702
  const client = getClient();
1631
- if (sessionScope) {
1632
- const result2 = await client.execute({
1633
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
1634
- args: [sessionScope]
1635
- });
1636
- return Number(result2.rows[0]?.cnt) || 0;
1637
- }
1703
+ const scope = strictSessionScopeFilter(
1704
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
1705
+ );
1638
1706
  const result = await client.execute({
1639
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
1640
- args: []
1707
+ sql: `SELECT COUNT(*) as cnt FROM tasks
1708
+ WHERE status = 'needs_review'${scope.sql}`,
1709
+ args: [...scope.args]
1641
1710
  });
1642
1711
  return Number(result.rows[0]?.cnt) || 0;
1643
1712
  }
1644
1713
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
1645
1714
  const client = getClient();
1646
- if (sessionScope) {
1647
- const result2 = await client.execute({
1648
- sql: `SELECT COUNT(*) as cnt FROM tasks
1649
- WHERE status = 'needs_review' AND updated_at > ?
1650
- AND session_scope = ?`,
1651
- args: [sinceIso, sessionScope]
1652
- });
1653
- return Number(result2.rows[0]?.cnt) || 0;
1654
- }
1715
+ const scope = strictSessionScopeFilter(
1716
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
1717
+ );
1655
1718
  const result = await client.execute({
1656
1719
  sql: `SELECT COUNT(*) as cnt FROM tasks
1657
- WHERE status = 'needs_review' AND updated_at > ?`,
1658
- args: [sinceIso]
1720
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
1721
+ args: [sinceIso, ...scope.args]
1659
1722
  });
1660
1723
  return Number(result.rows[0]?.cnt) || 0;
1661
1724
  }
1662
1725
  async function listPendingReviews(limit, sessionScope) {
1663
1726
  const client = getClient();
1664
- if (sessionScope) {
1665
- const result2 = await client.execute({
1666
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
1667
- WHERE status = 'needs_review'
1668
- AND session_scope = ?
1669
- ORDER BY updated_at ASC LIMIT ?`,
1670
- args: [sessionScope, limit]
1671
- });
1672
- return result2.rows;
1673
- }
1727
+ const scope = strictSessionScopeFilter(
1728
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
1729
+ );
1674
1730
  const result = await client.execute({
1675
1731
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
1676
- WHERE status = 'needs_review'
1732
+ WHERE status = 'needs_review'${scope.sql}
1677
1733
  ORDER BY updated_at ASC LIMIT ?`,
1678
- args: [limit]
1734
+ args: [...scope.args, limit]
1679
1735
  });
1680
1736
  return result.rows;
1681
1737
  }
@@ -1687,7 +1743,7 @@ async function cleanupOrphanedReviews() {
1687
1743
  WHERE status IN ('open', 'needs_review', 'in_progress')
1688
1744
  AND assigned_by = 'system'
1689
1745
  AND title LIKE 'Review:%'
1690
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
1746
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
1691
1747
  args: [now]
1692
1748
  });
1693
1749
  const r1b = await client.execute({
@@ -1806,11 +1862,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
1806
1862
  );
1807
1863
  }
1808
1864
  try {
1809
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
1810
- if (existsSync10(cacheDir)) {
1865
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
1866
+ if (existsSync11(cacheDir)) {
1811
1867
  for (const f of readdirSync2(cacheDir)) {
1812
1868
  if (f.startsWith("review-notified-")) {
1813
- unlinkSync3(path10.join(cacheDir, f));
1869
+ unlinkSync3(path11.join(cacheDir, f));
1814
1870
  }
1815
1871
  }
1816
1872
  }
@@ -1827,11 +1883,12 @@ var init_tasks_review = __esm({
1827
1883
  init_tmux_routing();
1828
1884
  init_session_key();
1829
1885
  init_state_bus();
1886
+ init_task_scope();
1830
1887
  }
1831
1888
  });
1832
1889
 
1833
1890
  // src/lib/tasks-chain.ts
1834
- import path11 from "path";
1891
+ import path12 from "path";
1835
1892
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
1836
1893
  async function cascadeUnblock(taskId, baseDir, now) {
1837
1894
  const client = getClient();
@@ -1848,7 +1905,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
1848
1905
  });
1849
1906
  for (const ur of unblockedRows.rows) {
1850
1907
  try {
1851
- const ubFile = path11.join(baseDir, String(ur.task_file));
1908
+ const ubFile = path12.join(baseDir, String(ur.task_file));
1852
1909
  let ubContent = await readFile3(ubFile, "utf-8");
1853
1910
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
1854
1911
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -1883,7 +1940,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
1883
1940
  const scScope = sessionScopeFilter();
1884
1941
  const remaining = await client.execute({
1885
1942
  sql: `SELECT COUNT(*) as cnt FROM tasks
1886
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
1943
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
1887
1944
  args: [parentTaskId, ...scScope.args]
1888
1945
  });
1889
1946
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -1917,7 +1974,7 @@ var init_tasks_chain = __esm({
1917
1974
 
1918
1975
  // src/lib/project-name.ts
1919
1976
  import { execSync as execSync5 } from "child_process";
1920
- import path12 from "path";
1977
+ import path13 from "path";
1921
1978
  function getProjectName(cwd) {
1922
1979
  const dir = cwd ?? process.cwd();
1923
1980
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -1930,7 +1987,7 @@ function getProjectName(cwd) {
1930
1987
  timeout: 2e3,
1931
1988
  stdio: ["pipe", "pipe", "pipe"]
1932
1989
  }).trim();
1933
- repoRoot = path12.dirname(gitCommonDir);
1990
+ repoRoot = path13.dirname(gitCommonDir);
1934
1991
  } catch {
1935
1992
  repoRoot = execSync5("git rev-parse --show-toplevel", {
1936
1993
  cwd: dir,
@@ -1939,11 +1996,11 @@ function getProjectName(cwd) {
1939
1996
  stdio: ["pipe", "pipe", "pipe"]
1940
1997
  }).trim();
1941
1998
  }
1942
- _cached2 = path12.basename(repoRoot);
1999
+ _cached2 = path13.basename(repoRoot);
1943
2000
  _cachedCwd = dir;
1944
2001
  return _cached2;
1945
2002
  } catch {
1946
- _cached2 = path12.basename(dir);
2003
+ _cached2 = path13.basename(dir);
1947
2004
  _cachedCwd = dir;
1948
2005
  return _cached2;
1949
2006
  }
@@ -2416,7 +2473,7 @@ __export(tasks_exports, {
2416
2473
  updateTaskStatus: () => updateTaskStatus,
2417
2474
  writeCheckpoint: () => writeCheckpoint
2418
2475
  });
2419
- import path13 from "path";
2476
+ import path14 from "path";
2420
2477
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
2421
2478
  async function createTask(input) {
2422
2479
  const result = await createTaskCore(input);
@@ -2436,12 +2493,12 @@ async function updateTask(input) {
2436
2493
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
2437
2494
  try {
2438
2495
  const agent = String(row.assigned_to);
2439
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
2440
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
2496
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
2497
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
2441
2498
  if (input.status === "in_progress") {
2442
2499
  mkdirSync5(cacheDir, { recursive: true });
2443
2500
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
2444
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
2501
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
2445
2502
  try {
2446
2503
  unlinkSync4(cachePath);
2447
2504
  } catch {
@@ -2449,10 +2506,10 @@ async function updateTask(input) {
2449
2506
  }
2450
2507
  } catch {
2451
2508
  }
2452
- if (input.status === "done") {
2509
+ if (input.status === "done" || input.status === "closed") {
2453
2510
  await cleanupReviewFile(row, taskFile, input.baseDir);
2454
2511
  }
2455
- if (input.status === "done" || input.status === "cancelled") {
2512
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
2456
2513
  try {
2457
2514
  const client = getClient();
2458
2515
  const taskTitle = String(row.title);
@@ -2468,7 +2525,7 @@ async function updateTask(input) {
2468
2525
  if (!isCoordinatorName(assignedAgent)) {
2469
2526
  try {
2470
2527
  const draftClient = getClient();
2471
- if (input.status === "done") {
2528
+ if (input.status === "done" || input.status === "closed") {
2472
2529
  await draftClient.execute({
2473
2530
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
2474
2531
  args: [assignedAgent]
@@ -2485,7 +2542,7 @@ async function updateTask(input) {
2485
2542
  try {
2486
2543
  const client = getClient();
2487
2544
  const cascaded = await client.execute({
2488
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
2545
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
2489
2546
  WHERE parent_task_id = ? AND status = 'needs_review'`,
2490
2547
  args: [now, taskId]
2491
2548
  });
@@ -2498,14 +2555,14 @@ async function updateTask(input) {
2498
2555
  } catch {
2499
2556
  }
2500
2557
  }
2501
- const isTerminal = input.status === "done" || input.status === "needs_review";
2558
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
2502
2559
  if (isTerminal) {
2503
2560
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
2504
2561
  if (!isCoordinator) {
2505
2562
  notifyTaskDone();
2506
2563
  }
2507
2564
  await markTaskNotificationsRead(taskFile);
2508
- if (input.status === "done") {
2565
+ if (input.status === "done" || input.status === "closed") {
2509
2566
  try {
2510
2567
  await cascadeUnblock(taskId, input.baseDir, now);
2511
2568
  } catch {
@@ -2525,7 +2582,7 @@ async function updateTask(input) {
2525
2582
  }
2526
2583
  }
2527
2584
  }
2528
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
2585
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
2529
2586
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
2530
2587
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
2531
2588
  taskId,
@@ -2897,6 +2954,7 @@ __export(tmux_routing_exports, {
2897
2954
  isEmployeeAlive: () => isEmployeeAlive,
2898
2955
  isExeSession: () => isExeSession,
2899
2956
  isSessionBusy: () => isSessionBusy,
2957
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
2900
2958
  notifyParentExe: () => notifyParentExe,
2901
2959
  parseParentExe: () => parseParentExe,
2902
2960
  registerParentExe: () => registerParentExe,
@@ -2907,13 +2965,13 @@ __export(tmux_routing_exports, {
2907
2965
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
2908
2966
  });
2909
2967
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2910
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync3 } from "fs";
2911
- import path14 from "path";
2912
- import os7 from "os";
2968
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
2969
+ import path15 from "path";
2970
+ import os9 from "os";
2913
2971
  import { fileURLToPath } from "url";
2914
2972
  import { unlinkSync as unlinkSync5 } from "fs";
2915
2973
  function spawnLockPath(sessionName) {
2916
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2974
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2917
2975
  }
2918
2976
  function isProcessAlive(pid) {
2919
2977
  try {
@@ -2924,11 +2982,11 @@ function isProcessAlive(pid) {
2924
2982
  }
2925
2983
  }
2926
2984
  function acquireSpawnLock(sessionName) {
2927
- if (!existsSync11(SPAWN_LOCK_DIR)) {
2985
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
2928
2986
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
2929
2987
  }
2930
2988
  const lockFile = spawnLockPath(sessionName);
2931
- if (existsSync11(lockFile)) {
2989
+ if (existsSync12(lockFile)) {
2932
2990
  try {
2933
2991
  const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
2934
2992
  const age = Date.now() - lock.timestamp;
@@ -2950,13 +3008,13 @@ function releaseSpawnLock(sessionName) {
2950
3008
  function resolveBehaviorsExporterScript() {
2951
3009
  try {
2952
3010
  const thisFile = fileURLToPath(import.meta.url);
2953
- const scriptPath = path14.join(
2954
- path14.dirname(thisFile),
3011
+ const scriptPath = path15.join(
3012
+ path15.dirname(thisFile),
2955
3013
  "..",
2956
3014
  "bin",
2957
3015
  "exe-export-behaviors.js"
2958
3016
  );
2959
- return existsSync11(scriptPath) ? scriptPath : null;
3017
+ return existsSync12(scriptPath) ? scriptPath : null;
2960
3018
  } catch {
2961
3019
  return null;
2962
3020
  }
@@ -3022,11 +3080,11 @@ function extractRootExe(name) {
3022
3080
  return parts.length > 0 ? parts[parts.length - 1] : null;
3023
3081
  }
3024
3082
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3025
- if (!existsSync11(SESSION_CACHE)) {
3083
+ if (!existsSync12(SESSION_CACHE)) {
3026
3084
  mkdirSync6(SESSION_CACHE, { recursive: true });
3027
3085
  }
3028
3086
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3029
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3087
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3030
3088
  writeFileSync7(filePath, JSON.stringify({
3031
3089
  parentExe: rootExe,
3032
3090
  dispatchedBy: dispatchedBy || rootExe,
@@ -3035,7 +3093,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3035
3093
  }
3036
3094
  function getParentExe(sessionKey) {
3037
3095
  try {
3038
- const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3096
+ const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3039
3097
  return data.parentExe || null;
3040
3098
  } catch {
3041
3099
  return null;
@@ -3044,7 +3102,7 @@ function getParentExe(sessionKey) {
3044
3102
  function getDispatchedBy(sessionKey) {
3045
3103
  try {
3046
3104
  const data = JSON.parse(readFileSync10(
3047
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3105
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3048
3106
  "utf8"
3049
3107
  ));
3050
3108
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -3114,7 +3172,7 @@ async function verifyPaneAtCapacity(sessionName) {
3114
3172
  }
3115
3173
  function readDebounceState() {
3116
3174
  try {
3117
- if (!existsSync11(DEBOUNCE_FILE)) return {};
3175
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
3118
3176
  const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
3119
3177
  const state = {};
3120
3178
  for (const [key, val] of Object.entries(raw)) {
@@ -3131,7 +3189,7 @@ function readDebounceState() {
3131
3189
  }
3132
3190
  function writeDebounceState(state) {
3133
3191
  try {
3134
- if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
3192
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
3135
3193
  writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
3136
3194
  } catch {
3137
3195
  }
@@ -3230,8 +3288,8 @@ function sendIntercom(targetSession) {
3230
3288
  try {
3231
3289
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
3232
3290
  const agent = baseAgentName(rawAgent);
3233
- const markerPath = path14.join(SESSION_CACHE, `current-task-${agent}.json`);
3234
- if (existsSync11(markerPath)) {
3291
+ const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
3292
+ if (existsSync12(markerPath)) {
3235
3293
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
3236
3294
  return "debounced";
3237
3295
  }
@@ -3240,8 +3298,8 @@ function sendIntercom(targetSession) {
3240
3298
  try {
3241
3299
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
3242
3300
  const agent = baseAgentName(rawAgent);
3243
- const taskDir = path14.join(process.cwd(), "exe", agent);
3244
- if (existsSync11(taskDir)) {
3301
+ const taskDir = path15.join(process.cwd(), "exe", agent);
3302
+ if (existsSync12(taskDir)) {
3245
3303
  const files = readdirSync3(taskDir).filter(
3246
3304
  (f) => f.endsWith(".md") && f !== "DONE.txt"
3247
3305
  );
@@ -3301,6 +3359,21 @@ function notifyParentExe(sessionKey) {
3301
3359
  }
3302
3360
  return true;
3303
3361
  }
3362
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
3363
+ const transport = getTransport();
3364
+ try {
3365
+ const sessions = transport.listSessions();
3366
+ if (!sessions.includes(coordinatorSession)) return false;
3367
+ execSync6(
3368
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
3369
+ { timeout: 3e3 }
3370
+ );
3371
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
3372
+ return true;
3373
+ } catch {
3374
+ return false;
3375
+ }
3376
+ }
3304
3377
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3305
3378
  if (isCoordinatorName(employeeName)) {
3306
3379
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -3374,23 +3447,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3374
3447
  const transport = getTransport();
3375
3448
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3376
3449
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3377
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
3378
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3379
- if (!existsSync11(logDir)) {
3450
+ const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
3451
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3452
+ if (!existsSync12(logDir)) {
3380
3453
  mkdirSync6(logDir, { recursive: true });
3381
3454
  }
3382
3455
  transport.kill(sessionName);
3383
3456
  let cleanupSuffix = "";
3384
3457
  try {
3385
3458
  const thisFile = fileURLToPath(import.meta.url);
3386
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3387
- if (existsSync11(cleanupScript)) {
3459
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3460
+ if (existsSync12(cleanupScript)) {
3388
3461
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
3389
3462
  }
3390
3463
  } catch {
3391
3464
  }
3392
3465
  try {
3393
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
3466
+ const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
3394
3467
  let claudeJson = {};
3395
3468
  try {
3396
3469
  claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
@@ -3405,10 +3478,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3405
3478
  } catch {
3406
3479
  }
3407
3480
  try {
3408
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
3481
+ const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
3409
3482
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3410
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
3411
- const settingsPath = path14.join(projSettingsDir, "settings.json");
3483
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
3484
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
3412
3485
  let settings = {};
3413
3486
  try {
3414
3487
  settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
@@ -3455,8 +3528,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3455
3528
  let behaviorsFlag = "";
3456
3529
  let legacyFallbackWarned = false;
3457
3530
  if (!useExeAgent && !useBinSymlink) {
3458
- const identityPath = path14.join(
3459
- os7.homedir(),
3531
+ const identityPath = path15.join(
3532
+ os9.homedir(),
3460
3533
  ".exe-os",
3461
3534
  "identity",
3462
3535
  `${employeeName}.md`
@@ -3465,13 +3538,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3465
3538
  const hasAgentFlag = claudeSupportsAgentFlag();
3466
3539
  if (hasAgentFlag) {
3467
3540
  identityFlag = ` --agent ${employeeName}`;
3468
- } else if (existsSync11(identityPath)) {
3541
+ } else if (existsSync12(identityPath)) {
3469
3542
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
3470
3543
  legacyFallbackWarned = true;
3471
3544
  }
3472
3545
  const behaviorsFile = exportBehaviorsSync(
3473
3546
  employeeName,
3474
- path14.basename(spawnCwd),
3547
+ path15.basename(spawnCwd),
3475
3548
  sessionName
3476
3549
  );
3477
3550
  if (behaviorsFile) {
@@ -3486,9 +3559,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3486
3559
  }
3487
3560
  let sessionContextFlag = "";
3488
3561
  try {
3489
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
3562
+ const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
3490
3563
  mkdirSync6(ctxDir, { recursive: true });
3491
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
3564
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
3492
3565
  const ctxContent = [
3493
3566
  `## Session Context`,
3494
3567
  `You are running in tmux session: ${sessionName}.`,
@@ -3572,7 +3645,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3572
3645
  transport.pipeLog(sessionName, logFile);
3573
3646
  try {
3574
3647
  const mySession = getMySession();
3575
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3648
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3576
3649
  writeFileSync7(dispatchInfo, JSON.stringify({
3577
3650
  dispatchedBy: mySession,
3578
3651
  rootExe: exeSession,
@@ -3646,15 +3719,15 @@ var init_tmux_routing = __esm({
3646
3719
  init_intercom_queue();
3647
3720
  init_plan_limits();
3648
3721
  init_employees();
3649
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
3650
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
3722
+ SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
3723
+ SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
3651
3724
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3652
3725
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
3653
3726
  VERIFY_PANE_LINES = 200;
3654
3727
  INTERCOM_DEBOUNCE_MS = 3e4;
3655
3728
  CODEX_DEBOUNCE_MS = 12e4;
3656
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
3657
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
3729
+ INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
3730
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
3658
3731
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3659
3732
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
3660
3733
  }
@@ -3673,6 +3746,7 @@ export {
3673
3746
  isEmployeeAlive,
3674
3747
  isExeSession,
3675
3748
  isSessionBusy,
3749
+ notifyCoordinatorTaskCompletion,
3676
3750
  notifyParentExe,
3677
3751
  parseParentExe,
3678
3752
  registerParentExe,