@askexenow/exe-os 0.8.85 → 0.8.86

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 (57) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +57 -19
  2. package/dist/bin/cli.js +507 -337
  3. package/dist/bin/exe-agent-config.js +242 -0
  4. package/dist/bin/exe-agent.js +3 -3
  5. package/dist/bin/exe-boot.js +344 -346
  6. package/dist/bin/exe-dispatch.js +375 -250
  7. package/dist/bin/exe-forget.js +5 -1
  8. package/dist/bin/exe-gateway.js +260 -135
  9. package/dist/bin/exe-healthcheck.js +133 -1
  10. package/dist/bin/exe-heartbeat.js +72 -31
  11. package/dist/bin/exe-link.js +25 -2
  12. package/dist/bin/exe-new-employee.js +22 -0
  13. package/dist/bin/exe-pending-messages.js +55 -17
  14. package/dist/bin/exe-pending-reviews.js +57 -19
  15. package/dist/bin/exe-search.js +6 -2
  16. package/dist/bin/exe-session-cleanup.js +260 -135
  17. package/dist/bin/exe-start-codex.js +2598 -0
  18. package/dist/bin/exe-start.sh +15 -3
  19. package/dist/bin/exe-status.js +57 -19
  20. package/dist/bin/git-sweep.js +391 -266
  21. package/dist/bin/install.js +22 -0
  22. package/dist/bin/scan-tasks.js +394 -269
  23. package/dist/bin/setup.js +47 -2
  24. package/dist/gateway/index.js +257 -132
  25. package/dist/hooks/bug-report-worker.js +242 -117
  26. package/dist/hooks/commit-complete.js +389 -264
  27. package/dist/hooks/error-recall.js +6 -2
  28. package/dist/hooks/ingest-worker.js +314 -193
  29. package/dist/hooks/post-compact.js +84 -46
  30. package/dist/hooks/pre-compact.js +272 -147
  31. package/dist/hooks/pre-tool-use.js +104 -66
  32. package/dist/hooks/prompt-submit.js +126 -66
  33. package/dist/hooks/session-end.js +277 -152
  34. package/dist/hooks/session-start.js +70 -28
  35. package/dist/hooks/stop.js +90 -52
  36. package/dist/hooks/subagent-stop.js +84 -46
  37. package/dist/hooks/summary-worker.js +175 -114
  38. package/dist/index.js +296 -171
  39. package/dist/lib/agent-config.js +167 -0
  40. package/dist/lib/cloud-sync.js +25 -2
  41. package/dist/lib/exe-daemon.js +338 -213
  42. package/dist/lib/hybrid-search.js +7 -2
  43. package/dist/lib/messaging.js +95 -39
  44. package/dist/lib/runtime-table.js +16 -0
  45. package/dist/lib/session-wrappers.js +22 -0
  46. package/dist/lib/tasks.js +242 -117
  47. package/dist/lib/tmux-routing.js +314 -189
  48. package/dist/mcp/server.js +573 -274
  49. package/dist/mcp/tools/create-task.js +260 -135
  50. package/dist/mcp/tools/list-tasks.js +68 -30
  51. package/dist/mcp/tools/send-message.js +100 -44
  52. package/dist/mcp/tools/update-task.js +123 -67
  53. package/dist/runtime/index.js +276 -151
  54. package/dist/tui/App.js +479 -354
  55. package/package.json +1 -1
  56. package/src/commands/exe/agent-config.md +27 -0
  57. package/src/commands/exe/cc-doctor.md +10 -0
package/dist/index.js CHANGED
@@ -757,18 +757,69 @@ var init_provider_table = __esm({
757
757
  }
758
758
  });
759
759
 
760
- // src/lib/intercom-queue.ts
761
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
760
+ // src/lib/runtime-table.ts
761
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
762
+ var init_runtime_table = __esm({
763
+ "src/lib/runtime-table.ts"() {
764
+ "use strict";
765
+ RUNTIME_TABLE = {
766
+ codex: {
767
+ binary: "codex",
768
+ launchMode: "exec",
769
+ autoApproveFlag: "--full-auto",
770
+ inlineFlag: "--no-alt-screen",
771
+ apiKeyEnv: "OPENAI_API_KEY",
772
+ defaultModel: "gpt-5.4"
773
+ }
774
+ };
775
+ DEFAULT_RUNTIME = "claude";
776
+ }
777
+ });
778
+
779
+ // src/lib/agent-config.ts
780
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
762
781
  import path5 from "path";
782
+ function loadAgentConfig() {
783
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
784
+ try {
785
+ return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
786
+ } catch {
787
+ return {};
788
+ }
789
+ }
790
+ function getAgentRuntime(agentId) {
791
+ const config2 = loadAgentConfig();
792
+ const entry = config2[agentId];
793
+ if (entry) return entry;
794
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
795
+ }
796
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
797
+ var init_agent_config = __esm({
798
+ "src/lib/agent-config.ts"() {
799
+ "use strict";
800
+ init_config();
801
+ init_runtime_table();
802
+ AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
803
+ DEFAULT_MODELS = {
804
+ claude: "claude-opus-4",
805
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
806
+ opencode: "minimax-m2.7"
807
+ };
808
+ }
809
+ });
810
+
811
+ // src/lib/intercom-queue.ts
812
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
813
+ import path6 from "path";
763
814
  import os5 from "os";
764
815
  function ensureDir() {
765
- const dir = path5.dirname(QUEUE_PATH);
766
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
816
+ const dir = path6.dirname(QUEUE_PATH);
817
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
767
818
  }
768
819
  function readQueue() {
769
820
  try {
770
- if (!existsSync4(QUEUE_PATH)) return [];
771
- return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
821
+ if (!existsSync5(QUEUE_PATH)) return [];
822
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
772
823
  } catch {
773
824
  return [];
774
825
  }
@@ -776,7 +827,7 @@ function readQueue() {
776
827
  function writeQueue(queue) {
777
828
  ensureDir();
778
829
  const tmp = `${QUEUE_PATH}.tmp`;
779
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
830
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
780
831
  renameSync3(tmp, QUEUE_PATH);
781
832
  }
782
833
  function queueIntercom(targetSession, reason) {
@@ -800,9 +851,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
800
851
  var init_intercom_queue = __esm({
801
852
  "src/lib/intercom-queue.ts"() {
802
853
  "use strict";
803
- QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
854
+ QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
804
855
  TTL_MS = 60 * 60 * 1e3;
805
- INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
856
+ INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
806
857
  }
807
858
  });
808
859
 
@@ -865,8 +916,8 @@ var init_db_retry = __esm({
865
916
  import net from "net";
866
917
  import { spawn } from "child_process";
867
918
  import { randomUUID as randomUUID2 } from "crypto";
868
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
869
- import path6 from "path";
919
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
920
+ import path7 from "path";
870
921
  import { fileURLToPath } from "url";
871
922
  function handleData(chunk) {
872
923
  _buffer += chunk.toString();
@@ -894,9 +945,9 @@ function handleData(chunk) {
894
945
  }
895
946
  }
896
947
  function cleanupStaleFiles() {
897
- if (existsSync5(PID_PATH)) {
948
+ if (existsSync6(PID_PATH)) {
898
949
  try {
899
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
950
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
900
951
  if (pid > 0) {
901
952
  try {
902
953
  process.kill(pid, 0);
@@ -917,11 +968,11 @@ function cleanupStaleFiles() {
917
968
  }
918
969
  }
919
970
  function findPackageRoot() {
920
- let dir = path6.dirname(fileURLToPath(import.meta.url));
921
- const { root } = path6.parse(dir);
971
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
972
+ const { root } = path7.parse(dir);
922
973
  while (dir !== root) {
923
- if (existsSync5(path6.join(dir, "package.json"))) return dir;
924
- dir = path6.dirname(dir);
974
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
975
+ dir = path7.dirname(dir);
925
976
  }
926
977
  return null;
927
978
  }
@@ -931,8 +982,8 @@ function spawnDaemon() {
931
982
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
932
983
  return;
933
984
  }
934
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
935
- if (!existsSync5(daemonPath)) {
985
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
986
+ if (!existsSync6(daemonPath)) {
936
987
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
937
988
  `);
938
989
  return;
@@ -940,7 +991,7 @@ function spawnDaemon() {
940
991
  const resolvedPath = daemonPath;
941
992
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
942
993
  `);
943
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
994
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
944
995
  let stderrFd = "ignore";
945
996
  try {
946
997
  stderrFd = openSync(logPath, "a");
@@ -1090,9 +1141,9 @@ async function pingDaemon() {
1090
1141
  }
1091
1142
  function killAndRespawnDaemon() {
1092
1143
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1093
- if (existsSync5(PID_PATH)) {
1144
+ if (existsSync6(PID_PATH)) {
1094
1145
  try {
1095
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1146
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1096
1147
  if (pid > 0) {
1097
1148
  try {
1098
1149
  process.kill(pid, "SIGKILL");
@@ -1179,9 +1230,9 @@ var init_exe_daemon_client = __esm({
1179
1230
  "src/lib/exe-daemon-client.ts"() {
1180
1231
  "use strict";
1181
1232
  init_config();
1182
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1183
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1184
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1233
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1234
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1235
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1185
1236
  SPAWN_LOCK_STALE_MS = 3e4;
1186
1237
  CONNECT_TIMEOUT_MS = 15e3;
1187
1238
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2408,18 +2459,18 @@ var init_database = __esm({
2408
2459
  });
2409
2460
 
2410
2461
  // src/lib/license.ts
2411
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2462
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2412
2463
  import { randomUUID as randomUUID3 } from "crypto";
2413
- import path7 from "path";
2464
+ import path8 from "path";
2414
2465
  import { jwtVerify, importSPKI } from "jose";
2415
2466
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2416
2467
  var init_license = __esm({
2417
2468
  "src/lib/license.ts"() {
2418
2469
  "use strict";
2419
2470
  init_config();
2420
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2421
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2422
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2471
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2472
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2473
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2423
2474
  PLAN_LIMITS = {
2424
2475
  free: { devices: 1, employees: 1, memories: 5e3 },
2425
2476
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2431,12 +2482,12 @@ var init_license = __esm({
2431
2482
  });
2432
2483
 
2433
2484
  // src/lib/plan-limits.ts
2434
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2435
- import path8 from "path";
2485
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2486
+ import path9 from "path";
2436
2487
  function getLicenseSync() {
2437
2488
  try {
2438
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2439
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2489
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2490
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2440
2491
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2441
2492
  const parts = raw.token.split(".");
2442
2493
  if (parts.length !== 3) return freeLicense();
@@ -2474,8 +2525,8 @@ function assertEmployeeLimitSync(rosterPath) {
2474
2525
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2475
2526
  let count = 0;
2476
2527
  try {
2477
- if (existsSync7(filePath)) {
2478
- const raw = readFileSync7(filePath, "utf8");
2528
+ if (existsSync8(filePath)) {
2529
+ const raw = readFileSync8(filePath, "utf8");
2479
2530
  const employees = JSON.parse(raw);
2480
2531
  count = Array.isArray(employees) ? employees.length : 0;
2481
2532
  }
@@ -2504,19 +2555,19 @@ var init_plan_limits = __esm({
2504
2555
  this.name = "PlanLimitError";
2505
2556
  }
2506
2557
  };
2507
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2558
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2508
2559
  }
2509
2560
  });
2510
2561
 
2511
2562
  // src/lib/notifications.ts
2512
2563
  import crypto from "crypto";
2513
- import path9 from "path";
2564
+ import path10 from "path";
2514
2565
  import os6 from "os";
2515
2566
  import {
2516
- readFileSync as readFileSync8,
2567
+ readFileSync as readFileSync9,
2517
2568
  readdirSync,
2518
2569
  unlinkSync as unlinkSync3,
2519
- existsSync as existsSync8,
2570
+ existsSync as existsSync9,
2520
2571
  rmdirSync
2521
2572
  } from "fs";
2522
2573
  async function writeNotification(notification) {
@@ -2680,11 +2731,11 @@ var init_state_bus = __esm({
2680
2731
 
2681
2732
  // src/lib/tasks-crud.ts
2682
2733
  import crypto3 from "crypto";
2683
- import path10 from "path";
2734
+ import path11 from "path";
2684
2735
  import os7 from "os";
2685
2736
  import { execSync as execSync5 } from "child_process";
2686
2737
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2687
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2738
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2688
2739
  async function writeCheckpoint(input) {
2689
2740
  const client = getClient();
2690
2741
  const row = await resolveTask(client, input.taskId);
@@ -2859,8 +2910,8 @@ ${laneWarning}` : laneWarning;
2859
2910
  }
2860
2911
  if (input.baseDir) {
2861
2912
  try {
2862
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2863
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2913
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
2914
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
2864
2915
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2865
2916
  await ensureGitignoreExe(input.baseDir);
2866
2917
  } catch {
@@ -2896,10 +2947,10 @@ ${laneWarning}` : laneWarning;
2896
2947
  });
2897
2948
  if (input.baseDir) {
2898
2949
  try {
2899
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2900
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2901
- const mdDir = path10.dirname(mdPath);
2902
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2950
+ const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
2951
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
2952
+ const mdDir = path11.dirname(mdPath);
2953
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2903
2954
  const reviewer = input.reviewer ?? input.assignedBy;
2904
2955
  const mdContent = `# ${input.title}
2905
2956
 
@@ -2924,7 +2975,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2924
2975
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2925
2976
  `;
2926
2977
  await writeFile3(mdPath, mdContent, "utf-8");
2927
- } catch {
2978
+ } catch (err) {
2979
+ process.stderr.write(
2980
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2981
+ `
2982
+ );
2928
2983
  }
2929
2984
  }
2930
2985
  return {
@@ -3184,9 +3239,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3184
3239
  return { taskFile, assignedTo, assignedBy, taskSlug };
3185
3240
  }
3186
3241
  async function ensureArchitectureDoc(baseDir, projectName) {
3187
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3242
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3188
3243
  try {
3189
- if (existsSync9(archPath)) return;
3244
+ if (existsSync10(archPath)) return;
3190
3245
  const template = [
3191
3246
  `# ${projectName} \u2014 System Architecture`,
3192
3247
  "",
@@ -3219,10 +3274,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3219
3274
  }
3220
3275
  }
3221
3276
  async function ensureGitignoreExe(baseDir) {
3222
- const gitignorePath = path10.join(baseDir, ".gitignore");
3277
+ const gitignorePath = path11.join(baseDir, ".gitignore");
3223
3278
  try {
3224
- if (existsSync9(gitignorePath)) {
3225
- const content = readFileSync9(gitignorePath, "utf-8");
3279
+ if (existsSync10(gitignorePath)) {
3280
+ const content = readFileSync10(gitignorePath, "utf-8");
3226
3281
  if (/^\/?exe\/?$/m.test(content)) return;
3227
3282
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3228
3283
  } else {
@@ -3253,8 +3308,8 @@ var init_tasks_crud = __esm({
3253
3308
  });
3254
3309
 
3255
3310
  // src/lib/tasks-review.ts
3256
- import path11 from "path";
3257
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3311
+ import path12 from "path";
3312
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3258
3313
  async function countPendingReviews(sessionScope) {
3259
3314
  const client = getClient();
3260
3315
  if (sessionScope) {
@@ -3435,11 +3490,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3435
3490
  );
3436
3491
  }
3437
3492
  try {
3438
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3439
- if (existsSync10(cacheDir)) {
3493
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3494
+ if (existsSync11(cacheDir)) {
3440
3495
  for (const f of readdirSync2(cacheDir)) {
3441
3496
  if (f.startsWith("review-notified-")) {
3442
- unlinkSync4(path11.join(cacheDir, f));
3497
+ unlinkSync4(path12.join(cacheDir, f));
3443
3498
  }
3444
3499
  }
3445
3500
  }
@@ -3460,7 +3515,7 @@ var init_tasks_review = __esm({
3460
3515
  });
3461
3516
 
3462
3517
  // src/lib/tasks-chain.ts
3463
- import path12 from "path";
3518
+ import path13 from "path";
3464
3519
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3465
3520
  async function cascadeUnblock(taskId, baseDir, now) {
3466
3521
  const client = getClient();
@@ -3477,7 +3532,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3477
3532
  });
3478
3533
  for (const ur of unblockedRows.rows) {
3479
3534
  try {
3480
- const ubFile = path12.join(baseDir, String(ur.task_file));
3535
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3481
3536
  let ubContent = await readFile3(ubFile, "utf-8");
3482
3537
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3483
3538
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3546,7 +3601,7 @@ var init_tasks_chain = __esm({
3546
3601
 
3547
3602
  // src/lib/project-name.ts
3548
3603
  import { execSync as execSync6 } from "child_process";
3549
- import path13 from "path";
3604
+ import path14 from "path";
3550
3605
  function getProjectName(cwd) {
3551
3606
  const dir = cwd ?? process.cwd();
3552
3607
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3559,7 +3614,7 @@ function getProjectName(cwd) {
3559
3614
  timeout: 2e3,
3560
3615
  stdio: ["pipe", "pipe", "pipe"]
3561
3616
  }).trim();
3562
- repoRoot = path13.dirname(gitCommonDir);
3617
+ repoRoot = path14.dirname(gitCommonDir);
3563
3618
  } catch {
3564
3619
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3565
3620
  cwd: dir,
@@ -3568,11 +3623,11 @@ function getProjectName(cwd) {
3568
3623
  stdio: ["pipe", "pipe", "pipe"]
3569
3624
  }).trim();
3570
3625
  }
3571
- _cached2 = path13.basename(repoRoot);
3626
+ _cached2 = path14.basename(repoRoot);
3572
3627
  _cachedCwd = dir;
3573
3628
  return _cached2;
3574
3629
  } catch {
3575
- _cached2 = path13.basename(dir);
3630
+ _cached2 = path14.basename(dir);
3576
3631
  _cachedCwd = dir;
3577
3632
  return _cached2;
3578
3633
  }
@@ -4107,8 +4162,8 @@ __export(tasks_exports, {
4107
4162
  updateTaskStatus: () => updateTaskStatus,
4108
4163
  writeCheckpoint: () => writeCheckpoint
4109
4164
  });
4110
- import path14 from "path";
4111
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
4165
+ import path15 from "path";
4166
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4112
4167
  async function createTask(input) {
4113
4168
  const result = await createTaskCore(input);
4114
4169
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4127,11 +4182,11 @@ async function updateTask(input) {
4127
4182
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4128
4183
  try {
4129
4184
  const agent = String(row.assigned_to);
4130
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4131
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
4185
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4186
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4132
4187
  if (input.status === "in_progress") {
4133
- mkdirSync4(cacheDir, { recursive: true });
4134
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4188
+ mkdirSync5(cacheDir, { recursive: true });
4189
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4135
4190
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4136
4191
  try {
4137
4192
  unlinkSync5(cachePath);
@@ -4598,13 +4653,13 @@ __export(tmux_routing_exports, {
4598
4653
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4599
4654
  });
4600
4655
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4601
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4602
- import path15 from "path";
4656
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4657
+ import path16 from "path";
4603
4658
  import os8 from "os";
4604
4659
  import { fileURLToPath as fileURLToPath2 } from "url";
4605
4660
  import { unlinkSync as unlinkSync6 } from "fs";
4606
4661
  function spawnLockPath(sessionName) {
4607
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4662
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4608
4663
  }
4609
4664
  function isProcessAlive(pid) {
4610
4665
  try {
@@ -4615,13 +4670,13 @@ function isProcessAlive(pid) {
4615
4670
  }
4616
4671
  }
4617
4672
  function acquireSpawnLock2(sessionName) {
4618
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4619
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4673
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4674
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4620
4675
  }
4621
4676
  const lockFile = spawnLockPath(sessionName);
4622
- if (existsSync11(lockFile)) {
4677
+ if (existsSync12(lockFile)) {
4623
4678
  try {
4624
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4679
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4625
4680
  const age = Date.now() - lock.timestamp;
4626
4681
  if (isProcessAlive(lock.pid) && age < 6e4) {
4627
4682
  return false;
@@ -4629,7 +4684,7 @@ function acquireSpawnLock2(sessionName) {
4629
4684
  } catch {
4630
4685
  }
4631
4686
  }
4632
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4687
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4633
4688
  return true;
4634
4689
  }
4635
4690
  function releaseSpawnLock2(sessionName) {
@@ -4641,13 +4696,13 @@ function releaseSpawnLock2(sessionName) {
4641
4696
  function resolveBehaviorsExporterScript() {
4642
4697
  try {
4643
4698
  const thisFile = fileURLToPath2(import.meta.url);
4644
- const scriptPath = path15.join(
4645
- path15.dirname(thisFile),
4699
+ const scriptPath = path16.join(
4700
+ path16.dirname(thisFile),
4646
4701
  "..",
4647
4702
  "bin",
4648
4703
  "exe-export-behaviors.js"
4649
4704
  );
4650
- return existsSync11(scriptPath) ? scriptPath : null;
4705
+ return existsSync12(scriptPath) ? scriptPath : null;
4651
4706
  } catch {
4652
4707
  return null;
4653
4708
  }
@@ -4713,12 +4768,12 @@ function extractRootExe(name) {
4713
4768
  return parts.length > 0 ? parts[parts.length - 1] : null;
4714
4769
  }
4715
4770
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4716
- if (!existsSync11(SESSION_CACHE)) {
4717
- mkdirSync5(SESSION_CACHE, { recursive: true });
4771
+ if (!existsSync12(SESSION_CACHE)) {
4772
+ mkdirSync6(SESSION_CACHE, { recursive: true });
4718
4773
  }
4719
4774
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4720
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4721
- writeFileSync6(filePath, JSON.stringify({
4775
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4776
+ writeFileSync7(filePath, JSON.stringify({
4722
4777
  parentExe: rootExe,
4723
4778
  dispatchedBy: dispatchedBy || rootExe,
4724
4779
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4726,7 +4781,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4726
4781
  }
4727
4782
  function getParentExe(sessionKey) {
4728
4783
  try {
4729
- const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4784
+ const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4730
4785
  return data.parentExe || null;
4731
4786
  } catch {
4732
4787
  return null;
@@ -4734,8 +4789,8 @@ function getParentExe(sessionKey) {
4734
4789
  }
4735
4790
  function getDispatchedBy(sessionKey) {
4736
4791
  try {
4737
- const data = JSON.parse(readFileSync10(
4738
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4792
+ const data = JSON.parse(readFileSync11(
4793
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4739
4794
  "utf8"
4740
4795
  ));
4741
4796
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4796,32 +4851,50 @@ async function verifyPaneAtCapacity(sessionName) {
4796
4851
  }
4797
4852
  function readDebounceState() {
4798
4853
  try {
4799
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4800
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4854
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4855
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4856
+ const state = {};
4857
+ for (const [key, val] of Object.entries(raw)) {
4858
+ if (typeof val === "number") {
4859
+ state[key] = { lastSent: val, pending: 0 };
4860
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4861
+ state[key] = val;
4862
+ }
4863
+ }
4864
+ return state;
4801
4865
  } catch {
4802
4866
  return {};
4803
4867
  }
4804
4868
  }
4805
4869
  function writeDebounceState(state) {
4806
4870
  try {
4807
- if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4808
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4871
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4872
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4809
4873
  } catch {
4810
4874
  }
4811
4875
  }
4812
4876
  function isDebounced(targetSession) {
4813
4877
  const state = readDebounceState();
4814
- const lastSent = state[targetSession] ?? 0;
4815
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4878
+ const entry = state[targetSession];
4879
+ const lastSent = entry?.lastSent ?? 0;
4880
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4881
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4882
+ state[targetSession].pending++;
4883
+ writeDebounceState(state);
4884
+ return true;
4885
+ }
4886
+ return false;
4816
4887
  }
4817
4888
  function recordDebounce(targetSession) {
4818
4889
  const state = readDebounceState();
4819
- state[targetSession] = Date.now();
4890
+ const batched = state[targetSession]?.pending ?? 0;
4891
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4820
4892
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4821
4893
  for (const key of Object.keys(state)) {
4822
- if ((state[key] ?? 0) < cutoff) delete state[key];
4894
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4823
4895
  }
4824
4896
  writeDebounceState(state);
4897
+ return batched;
4825
4898
  }
4826
4899
  function logIntercom(msg) {
4827
4900
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4866,7 +4939,7 @@ function sendIntercom(targetSession) {
4866
4939
  return "skipped_exe";
4867
4940
  }
4868
4941
  if (isDebounced(targetSession)) {
4869
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4942
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4870
4943
  return "debounced";
4871
4944
  }
4872
4945
  try {
@@ -4878,14 +4951,14 @@ function sendIntercom(targetSession) {
4878
4951
  const sessionState = getSessionState(targetSession);
4879
4952
  if (sessionState === "no_claude") {
4880
4953
  queueIntercom(targetSession, "claude not running in session");
4881
- recordDebounce(targetSession);
4882
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4954
+ const batched2 = recordDebounce(targetSession);
4955
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4883
4956
  return "queued";
4884
4957
  }
4885
4958
  if (sessionState === "thinking" || sessionState === "tool") {
4886
4959
  queueIntercom(targetSession, "session busy at send time");
4887
- recordDebounce(targetSession);
4888
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4960
+ const batched2 = recordDebounce(targetSession);
4961
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4889
4962
  return "queued";
4890
4963
  }
4891
4964
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4893,8 +4966,8 @@ function sendIntercom(targetSession) {
4893
4966
  transport.sendKeys(targetSession, "q");
4894
4967
  }
4895
4968
  transport.sendKeys(targetSession, "/exe-intercom");
4896
- recordDebounce(targetSession);
4897
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4969
+ const batched = recordDebounce(targetSession);
4970
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4898
4971
  return "delivered";
4899
4972
  } catch {
4900
4973
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4996,26 +5069,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4996
5069
  const transport = getTransport();
4997
5070
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4998
5071
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4999
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5000
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5001
- if (!existsSync11(logDir)) {
5002
- mkdirSync5(logDir, { recursive: true });
5072
+ const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
5073
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5074
+ if (!existsSync12(logDir)) {
5075
+ mkdirSync6(logDir, { recursive: true });
5003
5076
  }
5004
5077
  transport.kill(sessionName);
5005
5078
  let cleanupSuffix = "";
5006
5079
  try {
5007
5080
  const thisFile = fileURLToPath2(import.meta.url);
5008
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5009
- if (existsSync11(cleanupScript)) {
5081
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5082
+ if (existsSync12(cleanupScript)) {
5010
5083
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5011
5084
  }
5012
5085
  } catch {
5013
5086
  }
5014
5087
  try {
5015
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
5088
+ const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
5016
5089
  let claudeJson = {};
5017
5090
  try {
5018
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
5091
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5019
5092
  } catch {
5020
5093
  }
5021
5094
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5023,17 +5096,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5023
5096
  const trustDir = opts?.cwd ?? projectDir;
5024
5097
  if (!projects[trustDir]) projects[trustDir] = {};
5025
5098
  projects[trustDir].hasTrustDialogAccepted = true;
5026
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5099
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5027
5100
  } catch {
5028
5101
  }
5029
5102
  try {
5030
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
5103
+ const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
5031
5104
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5032
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
5033
- const settingsPath = path15.join(projSettingsDir, "settings.json");
5105
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
5106
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
5034
5107
  let settings = {};
5035
5108
  try {
5036
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
5109
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5037
5110
  } catch {
5038
5111
  }
5039
5112
  const perms = settings.permissions ?? {};
@@ -5061,20 +5134,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5061
5134
  if (changed) {
5062
5135
  perms.allow = allow;
5063
5136
  settings.permissions = perms;
5064
- mkdirSync5(projSettingsDir, { recursive: true });
5065
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5137
+ mkdirSync6(projSettingsDir, { recursive: true });
5138
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5066
5139
  }
5067
5140
  } catch {
5068
5141
  }
5069
5142
  const spawnCwd = opts?.cwd ?? projectDir;
5070
5143
  const useExeAgent = !!(opts?.model && opts?.provider);
5071
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
5144
+ const agentRtConfig = getAgentRuntime(employeeName);
5145
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
5146
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
5147
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
5072
5148
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
5073
5149
  let identityFlag = "";
5074
5150
  let behaviorsFlag = "";
5075
5151
  let legacyFallbackWarned = false;
5076
5152
  if (!useExeAgent && !useBinSymlink) {
5077
- const identityPath = path15.join(
5153
+ const identityPath = path16.join(
5078
5154
  os8.homedir(),
5079
5155
  ".exe-os",
5080
5156
  "identity",
@@ -5084,13 +5160,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5084
5160
  const hasAgentFlag = claudeSupportsAgentFlag();
5085
5161
  if (hasAgentFlag) {
5086
5162
  identityFlag = ` --agent ${employeeName}`;
5087
- } else if (existsSync11(identityPath)) {
5163
+ } else if (existsSync12(identityPath)) {
5088
5164
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5089
5165
  legacyFallbackWarned = true;
5090
5166
  }
5091
5167
  const behaviorsFile = exportBehaviorsSync(
5092
5168
  employeeName,
5093
- path15.basename(spawnCwd),
5169
+ path16.basename(spawnCwd),
5094
5170
  sessionName
5095
5171
  );
5096
5172
  if (behaviorsFile) {
@@ -5105,16 +5181,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5105
5181
  }
5106
5182
  let sessionContextFlag = "";
5107
5183
  try {
5108
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
5109
- mkdirSync5(ctxDir, { recursive: true });
5110
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
5184
+ const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
5185
+ mkdirSync6(ctxDir, { recursive: true });
5186
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
5111
5187
  const ctxContent = [
5112
5188
  `## Session Context`,
5113
5189
  `You are running in tmux session: ${sessionName}.`,
5114
5190
  `Your parent coordinator session is ${exeSession}.`,
5115
5191
  `Your employees (if any) use the -${exeSession} suffix.`
5116
5192
  ].join("\n");
5117
- writeFileSync6(ctxFile, ctxContent);
5193
+ writeFileSync7(ctxFile, ctxContent);
5118
5194
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5119
5195
  } catch {
5120
5196
  }
@@ -5128,9 +5204,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5128
5204
  }
5129
5205
  }
5130
5206
  }
5207
+ if (useCodex) {
5208
+ const codexCfg = RUNTIME_TABLE.codex;
5209
+ if (codexCfg?.apiKeyEnv) {
5210
+ const keyVal = process.env[codexCfg.apiKeyEnv];
5211
+ if (keyVal) {
5212
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
5213
+ }
5214
+ }
5215
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
5216
+ }
5217
+ if (useOpencode) {
5218
+ const ocCfg = PROVIDER_TABLE.opencode;
5219
+ if (ocCfg?.apiKeyEnv) {
5220
+ const keyVal = process.env[ocCfg.apiKeyEnv];
5221
+ if (keyVal) {
5222
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
5223
+ }
5224
+ }
5225
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
5226
+ }
5227
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
5228
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
5229
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
5230
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
5231
+ }
5232
+ }
5131
5233
  let spawnCommand;
5132
5234
  if (useExeAgent) {
5133
5235
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
5236
+ } else if (useCodex) {
5237
+ process.stderr.write(
5238
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
5239
+ `
5240
+ );
5241
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
5242
+ } else if (useOpencode) {
5243
+ const binName = `${employeeName}-opencode`;
5244
+ process.stderr.write(
5245
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
5246
+ `
5247
+ );
5248
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
5134
5249
  } else if (useBinSymlink) {
5135
5250
  const binName = `${employeeName}-${ccProvider}`;
5136
5251
  process.stderr.write(
@@ -5152,11 +5267,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5152
5267
  transport.pipeLog(sessionName, logFile);
5153
5268
  try {
5154
5269
  const mySession = getMySession();
5155
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5156
- writeFileSync6(dispatchInfo, JSON.stringify({
5270
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5271
+ writeFileSync7(dispatchInfo, JSON.stringify({
5157
5272
  dispatchedBy: mySession,
5158
5273
  rootExe: exeSession,
5159
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
5274
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
5275
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
5276
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
5160
5277
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5161
5278
  }));
5162
5279
  } catch {
@@ -5174,6 +5291,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5174
5291
  booted = true;
5175
5292
  break;
5176
5293
  }
5294
+ } else if (useCodex) {
5295
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
5296
+ booted = true;
5297
+ break;
5298
+ }
5177
5299
  } else {
5178
5300
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
5179
5301
  booted = true;
@@ -5185,9 +5307,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5185
5307
  }
5186
5308
  if (!booted) {
5187
5309
  releaseSpawnLock2(sessionName);
5188
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
5310
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
5311
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
5189
5312
  }
5190
- if (!useExeAgent) {
5313
+ if (!useExeAgent && !useCodex) {
5191
5314
  try {
5192
5315
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
5193
5316
  } catch {
@@ -5214,17 +5337,19 @@ var init_tmux_routing = __esm({
5214
5337
  init_cc_agent_support();
5215
5338
  init_mcp_prefix();
5216
5339
  init_provider_table();
5340
+ init_agent_config();
5341
+ init_runtime_table();
5217
5342
  init_intercom_queue();
5218
5343
  init_plan_limits();
5219
5344
  init_employees();
5220
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5221
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
5345
+ SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
5346
+ SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
5222
5347
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5223
5348
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5224
5349
  VERIFY_PANE_LINES = 200;
5225
5350
  INTERCOM_DEBOUNCE_MS = 3e4;
5226
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5227
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5351
+ INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
5352
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
5228
5353
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5229
5354
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
5230
5355
  }
@@ -5241,14 +5366,14 @@ var init_memory = __esm({
5241
5366
 
5242
5367
  // src/lib/keychain.ts
5243
5368
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5244
- import { existsSync as existsSync12 } from "fs";
5245
- import path16 from "path";
5369
+ import { existsSync as existsSync13 } from "fs";
5370
+ import path17 from "path";
5246
5371
  import os9 from "os";
5247
5372
  function getKeyDir() {
5248
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5373
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os9.homedir(), ".exe-os");
5249
5374
  }
5250
5375
  function getKeyPath() {
5251
- return path16.join(getKeyDir(), "master.key");
5376
+ return path17.join(getKeyDir(), "master.key");
5252
5377
  }
5253
5378
  async function tryKeytar() {
5254
5379
  try {
@@ -5269,7 +5394,7 @@ async function getMasterKey() {
5269
5394
  }
5270
5395
  }
5271
5396
  const keyPath = getKeyPath();
5272
- if (!existsSync12(keyPath)) {
5397
+ if (!existsSync13(keyPath)) {
5273
5398
  process.stderr.write(
5274
5399
  `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5275
5400
  `
@@ -5309,13 +5434,13 @@ __export(shard_manager_exports, {
5309
5434
  listShards: () => listShards,
5310
5435
  shardExists: () => shardExists
5311
5436
  });
5312
- import path17 from "path";
5313
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5437
+ import path18 from "path";
5438
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
5314
5439
  import { createClient as createClient2 } from "@libsql/client";
5315
5440
  function initShardManager(encryptionKey) {
5316
5441
  _encryptionKey = encryptionKey;
5317
- if (!existsSync13(SHARDS_DIR)) {
5318
- mkdirSync6(SHARDS_DIR, { recursive: true });
5442
+ if (!existsSync14(SHARDS_DIR)) {
5443
+ mkdirSync7(SHARDS_DIR, { recursive: true });
5319
5444
  }
5320
5445
  _shardingEnabled = true;
5321
5446
  }
@@ -5335,7 +5460,7 @@ function getShardClient(projectName) {
5335
5460
  }
5336
5461
  const cached = _shards.get(safeName);
5337
5462
  if (cached) return cached;
5338
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5463
+ const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
5339
5464
  const client = createClient2({
5340
5465
  url: `file:${dbPath}`,
5341
5466
  encryptionKey: _encryptionKey
@@ -5345,10 +5470,10 @@ function getShardClient(projectName) {
5345
5470
  }
5346
5471
  function shardExists(projectName) {
5347
5472
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5348
- return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
5473
+ return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
5349
5474
  }
5350
5475
  function listShards() {
5351
- if (!existsSync13(SHARDS_DIR)) return [];
5476
+ if (!existsSync14(SHARDS_DIR)) return [];
5352
5477
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5353
5478
  }
5354
5479
  async function ensureShardSchema(client) {
@@ -5534,7 +5659,7 @@ var init_shard_manager = __esm({
5534
5659
  "src/lib/shard-manager.ts"() {
5535
5660
  "use strict";
5536
5661
  init_config();
5537
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5662
+ SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
5538
5663
  _shards = /* @__PURE__ */ new Map();
5539
5664
  _encryptionKey = null;
5540
5665
  _shardingEnabled = false;
@@ -6323,8 +6448,8 @@ function findContainingChunk(filePath, snippet) {
6323
6448
  try {
6324
6449
  const ext = filePath.split(".").pop()?.toLowerCase();
6325
6450
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
6326
- const { readFileSync: readFileSync13 } = __require("fs");
6327
- const source = readFileSync13(filePath, "utf8");
6451
+ const { readFileSync: readFileSync14 } = __require("fs");
6452
+ const source = readFileSync14(filePath, "utf8");
6328
6453
  const lines = source.split("\n");
6329
6454
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
6330
6455
  let matchLine = -1;
@@ -6390,9 +6515,9 @@ function extractBash(input, response) {
6390
6515
  }
6391
6516
  function extractGrep(input, response) {
6392
6517
  const pattern = String(input.pattern ?? "");
6393
- const path20 = input.path ? String(input.path) : "";
6518
+ const path21 = input.path ? String(input.path) : "";
6394
6519
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
6395
- return `Searched for "${pattern}"${path20 ? ` in ${path20}` : ""}
6520
+ return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
6396
6521
  ${output.slice(0, MAX_OUTPUT)}`;
6397
6522
  }
6398
6523
  function extractGlob(input, response) {
@@ -6930,10 +7055,10 @@ async function disposeEmbedder() {
6930
7055
  async function embedDirect(text) {
6931
7056
  const llamaCpp = await import("node-llama-cpp");
6932
7057
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6933
- const { existsSync: existsSync15 } = await import("fs");
6934
- const path20 = await import("path");
6935
- const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
6936
- if (!existsSync15(modelPath)) {
7058
+ const { existsSync: existsSync16 } = await import("fs");
7059
+ const path21 = await import("path");
7060
+ const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7061
+ if (!existsSync16(modelPath)) {
6937
7062
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
6938
7063
  }
6939
7064
  const llama = await llamaCpp.getLlama();
@@ -6970,8 +7095,8 @@ __export(wiki_client_exports, {
6970
7095
  listDocuments: () => listDocuments,
6971
7096
  listWorkspaces: () => listWorkspaces
6972
7097
  });
6973
- async function wikiFetch(config2, path20, method = "GET", body) {
6974
- const url = `${config2.baseUrl}/api/v1${path20}`;
7098
+ async function wikiFetch(config2, path21, method = "GET", body) {
7099
+ const url = `${config2.baseUrl}/api/v1${path21}`;
6975
7100
  const headers = {
6976
7101
  Authorization: `Bearer ${config2.apiKey}`,
6977
7102
  "Content-Type": "application/json"
@@ -7004,7 +7129,7 @@ async function wikiFetch(config2, path20, method = "GET", body) {
7004
7129
  }
7005
7130
  }
7006
7131
  if (!response.ok) {
7007
- throw new Error(`Wiki API ${method} ${path20}: ${response.status} ${response.statusText}`);
7132
+ throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
7008
7133
  }
7009
7134
  return response.json();
7010
7135
  } finally {
@@ -8025,13 +8150,13 @@ __export(whatsapp_accounts_exports, {
8025
8150
  getDefaultAccount: () => getDefaultAccount,
8026
8151
  loadAccounts: () => loadAccounts
8027
8152
  });
8028
- import { readFileSync as readFileSync11 } from "fs";
8153
+ import { readFileSync as readFileSync12 } from "fs";
8029
8154
  import { join as join2 } from "path";
8030
8155
  import { homedir as homedir2 } from "os";
8031
8156
  function loadAccounts() {
8032
8157
  if (cachedAccounts !== null) return cachedAccounts;
8033
8158
  try {
8034
- const raw = readFileSync11(CONFIG_PATH2, "utf8");
8159
+ const raw = readFileSync12(CONFIG_PATH2, "utf8");
8035
8160
  const parsed = JSON.parse(raw);
8036
8161
  if (!Array.isArray(parsed)) {
8037
8162
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -12192,7 +12317,7 @@ var OllamaProvider = class {
12192
12317
  import { randomUUID as randomUUID9 } from "crypto";
12193
12318
  import { homedir } from "os";
12194
12319
  import { join } from "path";
12195
- import { mkdirSync as mkdirSync7 } from "fs";
12320
+ import { mkdirSync as mkdirSync8 } from "fs";
12196
12321
  var INITIAL_BACKOFF_MS = 1e3;
12197
12322
  var MAX_BACKOFF_MS = 3e5;
12198
12323
  var BACKOFF_MULTIPLIER = 2;
@@ -12218,7 +12343,7 @@ var WhatsAppAdapter = class {
12218
12343
  disconnectedAt = 0;
12219
12344
  async connect(config2) {
12220
12345
  this.authDir = config2.credentials.authDir ?? AUTH_DIR;
12221
- mkdirSync7(this.authDir, { recursive: true });
12346
+ mkdirSync8(this.authDir, { recursive: true });
12222
12347
  const baileys = await import("@whiskeysockets/baileys");
12223
12348
  const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = baileys;
12224
12349
  const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
@@ -13521,10 +13646,10 @@ var SlackAdapter = class {
13521
13646
  import { execFile } from "child_process";
13522
13647
  import { promisify } from "util";
13523
13648
  import os10 from "os";
13524
- import path18 from "path";
13649
+ import path19 from "path";
13525
13650
  var execFileAsync = promisify(execFile);
13526
13651
  var POLL_INTERVAL_MS = 5e3;
13527
- var MESSAGES_DB_PATH = path18.join(
13652
+ var MESSAGES_DB_PATH = path19.join(
13528
13653
  process.env.HOME ?? os10.homedir(),
13529
13654
  "Library/Messages/chat.db"
13530
13655
  );
@@ -14368,11 +14493,11 @@ async function ensureCRMContact(info) {
14368
14493
  }
14369
14494
 
14370
14495
  // src/automation/trigger-engine.ts
14371
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
14496
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
14372
14497
  import { randomUUID as randomUUID15 } from "crypto";
14373
- import path19 from "path";
14498
+ import path20 from "path";
14374
14499
  import os11 from "os";
14375
- var TRIGGERS_PATH = path19.join(os11.homedir(), ".exe-os", "triggers.json");
14500
+ var TRIGGERS_PATH = path20.join(os11.homedir(), ".exe-os", "triggers.json");
14376
14501
  var GRAPH_API_VERSION = "v21.0";
14377
14502
  function substituteTemplate(template, record) {
14378
14503
  return template.replace(
@@ -14426,9 +14551,9 @@ function evaluateConditions(conditions, record) {
14426
14551
  return conditions.every((c) => evaluateCondition(c, record));
14427
14552
  }
14428
14553
  function loadTriggers(project) {
14429
- if (!existsSync14(TRIGGERS_PATH)) return [];
14554
+ if (!existsSync15(TRIGGERS_PATH)) return [];
14430
14555
  try {
14431
- const raw = readFileSync12(TRIGGERS_PATH, "utf-8");
14556
+ const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
14432
14557
  const all = JSON.parse(raw);
14433
14558
  if (!Array.isArray(all)) return [];
14434
14559
  if (project) {