@askexenow/exe-os 0.8.85 → 0.8.87

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 +510 -340
  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 +50 -5
  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
@@ -544,18 +544,69 @@ var init_provider_table = __esm({
544
544
  }
545
545
  });
546
546
 
547
- // src/lib/intercom-queue.ts
548
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
547
+ // src/lib/runtime-table.ts
548
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
549
+ var init_runtime_table = __esm({
550
+ "src/lib/runtime-table.ts"() {
551
+ "use strict";
552
+ RUNTIME_TABLE = {
553
+ codex: {
554
+ binary: "codex",
555
+ launchMode: "exec",
556
+ autoApproveFlag: "--full-auto",
557
+ inlineFlag: "--no-alt-screen",
558
+ apiKeyEnv: "OPENAI_API_KEY",
559
+ defaultModel: "gpt-5.4"
560
+ }
561
+ };
562
+ DEFAULT_RUNTIME = "claude";
563
+ }
564
+ });
565
+
566
+ // src/lib/agent-config.ts
567
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
549
568
  import path5 from "path";
569
+ function loadAgentConfig() {
570
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
571
+ try {
572
+ return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
573
+ } catch {
574
+ return {};
575
+ }
576
+ }
577
+ function getAgentRuntime(agentId) {
578
+ const config = loadAgentConfig();
579
+ const entry = config[agentId];
580
+ if (entry) return entry;
581
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
582
+ }
583
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
584
+ var init_agent_config = __esm({
585
+ "src/lib/agent-config.ts"() {
586
+ "use strict";
587
+ init_config();
588
+ init_runtime_table();
589
+ AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
590
+ DEFAULT_MODELS = {
591
+ claude: "claude-opus-4",
592
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
593
+ opencode: "minimax-m2.7"
594
+ };
595
+ }
596
+ });
597
+
598
+ // src/lib/intercom-queue.ts
599
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
600
+ import path6 from "path";
550
601
  import os5 from "os";
551
602
  function ensureDir() {
552
- const dir = path5.dirname(QUEUE_PATH);
553
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
603
+ const dir = path6.dirname(QUEUE_PATH);
604
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
554
605
  }
555
606
  function readQueue() {
556
607
  try {
557
- if (!existsSync4(QUEUE_PATH)) return [];
558
- return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
608
+ if (!existsSync5(QUEUE_PATH)) return [];
609
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
559
610
  } catch {
560
611
  return [];
561
612
  }
@@ -563,7 +614,7 @@ function readQueue() {
563
614
  function writeQueue(queue) {
564
615
  ensureDir();
565
616
  const tmp = `${QUEUE_PATH}.tmp`;
566
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
617
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
567
618
  renameSync3(tmp, QUEUE_PATH);
568
619
  }
569
620
  function queueIntercom(targetSession, reason) {
@@ -587,9 +638,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
587
638
  var init_intercom_queue = __esm({
588
639
  "src/lib/intercom-queue.ts"() {
589
640
  "use strict";
590
- QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
641
+ QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
591
642
  TTL_MS = 60 * 60 * 1e3;
592
- INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
643
+ INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
593
644
  }
594
645
  });
595
646
 
@@ -652,8 +703,8 @@ var init_db_retry = __esm({
652
703
  import net from "net";
653
704
  import { spawn } from "child_process";
654
705
  import { randomUUID as randomUUID2 } from "crypto";
655
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
656
- import path6 from "path";
706
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
707
+ import path7 from "path";
657
708
  import { fileURLToPath } from "url";
658
709
  function handleData(chunk) {
659
710
  _buffer += chunk.toString();
@@ -681,9 +732,9 @@ function handleData(chunk) {
681
732
  }
682
733
  }
683
734
  function cleanupStaleFiles() {
684
- if (existsSync5(PID_PATH)) {
735
+ if (existsSync6(PID_PATH)) {
685
736
  try {
686
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
737
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
687
738
  if (pid > 0) {
688
739
  try {
689
740
  process.kill(pid, 0);
@@ -704,11 +755,11 @@ function cleanupStaleFiles() {
704
755
  }
705
756
  }
706
757
  function findPackageRoot() {
707
- let dir = path6.dirname(fileURLToPath(import.meta.url));
708
- const { root } = path6.parse(dir);
758
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
759
+ const { root } = path7.parse(dir);
709
760
  while (dir !== root) {
710
- if (existsSync5(path6.join(dir, "package.json"))) return dir;
711
- dir = path6.dirname(dir);
761
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
762
+ dir = path7.dirname(dir);
712
763
  }
713
764
  return null;
714
765
  }
@@ -718,8 +769,8 @@ function spawnDaemon() {
718
769
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
719
770
  return;
720
771
  }
721
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
722
- if (!existsSync5(daemonPath)) {
772
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
773
+ if (!existsSync6(daemonPath)) {
723
774
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
724
775
  `);
725
776
  return;
@@ -727,7 +778,7 @@ function spawnDaemon() {
727
778
  const resolvedPath = daemonPath;
728
779
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
729
780
  `);
730
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
781
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
731
782
  let stderrFd = "ignore";
732
783
  try {
733
784
  stderrFd = openSync(logPath, "a");
@@ -872,9 +923,9 @@ var init_exe_daemon_client = __esm({
872
923
  "src/lib/exe-daemon-client.ts"() {
873
924
  "use strict";
874
925
  init_config();
875
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
876
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
877
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
926
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
927
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
928
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
878
929
  SPAWN_LOCK_STALE_MS = 3e4;
879
930
  CONNECT_TIMEOUT_MS = 15e3;
880
931
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2099,18 +2150,18 @@ var init_database = __esm({
2099
2150
  });
2100
2151
 
2101
2152
  // src/lib/license.ts
2102
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2153
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2103
2154
  import { randomUUID as randomUUID3 } from "crypto";
2104
- import path7 from "path";
2155
+ import path8 from "path";
2105
2156
  import { jwtVerify, importSPKI } from "jose";
2106
2157
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2107
2158
  var init_license = __esm({
2108
2159
  "src/lib/license.ts"() {
2109
2160
  "use strict";
2110
2161
  init_config();
2111
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2112
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2113
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2162
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2163
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2164
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2114
2165
  PLAN_LIMITS = {
2115
2166
  free: { devices: 1, employees: 1, memories: 5e3 },
2116
2167
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2122,12 +2173,12 @@ var init_license = __esm({
2122
2173
  });
2123
2174
 
2124
2175
  // src/lib/plan-limits.ts
2125
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2126
- import path8 from "path";
2176
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2177
+ import path9 from "path";
2127
2178
  function getLicenseSync() {
2128
2179
  try {
2129
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2130
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2180
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2181
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2131
2182
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2132
2183
  const parts = raw.token.split(".");
2133
2184
  if (parts.length !== 3) return freeLicense();
@@ -2165,8 +2216,8 @@ function assertEmployeeLimitSync(rosterPath) {
2165
2216
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2166
2217
  let count = 0;
2167
2218
  try {
2168
- if (existsSync7(filePath)) {
2169
- const raw = readFileSync7(filePath, "utf8");
2219
+ if (existsSync8(filePath)) {
2220
+ const raw = readFileSync8(filePath, "utf8");
2170
2221
  const employees = JSON.parse(raw);
2171
2222
  count = Array.isArray(employees) ? employees.length : 0;
2172
2223
  }
@@ -2195,19 +2246,19 @@ var init_plan_limits = __esm({
2195
2246
  this.name = "PlanLimitError";
2196
2247
  }
2197
2248
  };
2198
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2249
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2199
2250
  }
2200
2251
  });
2201
2252
 
2202
2253
  // src/lib/notifications.ts
2203
2254
  import crypto from "crypto";
2204
- import path9 from "path";
2255
+ import path10 from "path";
2205
2256
  import os6 from "os";
2206
2257
  import {
2207
- readFileSync as readFileSync8,
2258
+ readFileSync as readFileSync9,
2208
2259
  readdirSync,
2209
2260
  unlinkSync as unlinkSync3,
2210
- existsSync as existsSync8,
2261
+ existsSync as existsSync9,
2211
2262
  rmdirSync
2212
2263
  } from "fs";
2213
2264
  async function writeNotification(notification) {
@@ -2371,11 +2422,11 @@ var init_state_bus = __esm({
2371
2422
 
2372
2423
  // src/lib/tasks-crud.ts
2373
2424
  import crypto3 from "crypto";
2374
- import path10 from "path";
2425
+ import path11 from "path";
2375
2426
  import os7 from "os";
2376
2427
  import { execSync as execSync5 } from "child_process";
2377
2428
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2378
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2429
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2379
2430
  async function writeCheckpoint(input) {
2380
2431
  const client = getClient();
2381
2432
  const row = await resolveTask(client, input.taskId);
@@ -2550,8 +2601,8 @@ ${laneWarning}` : laneWarning;
2550
2601
  }
2551
2602
  if (input.baseDir) {
2552
2603
  try {
2553
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2554
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2604
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
2605
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
2555
2606
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2556
2607
  await ensureGitignoreExe(input.baseDir);
2557
2608
  } catch {
@@ -2587,10 +2638,10 @@ ${laneWarning}` : laneWarning;
2587
2638
  });
2588
2639
  if (input.baseDir) {
2589
2640
  try {
2590
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2591
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2592
- const mdDir = path10.dirname(mdPath);
2593
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2641
+ const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
2642
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
2643
+ const mdDir = path11.dirname(mdPath);
2644
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2594
2645
  const reviewer = input.reviewer ?? input.assignedBy;
2595
2646
  const mdContent = `# ${input.title}
2596
2647
 
@@ -2615,7 +2666,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2615
2666
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2616
2667
  `;
2617
2668
  await writeFile3(mdPath, mdContent, "utf-8");
2618
- } catch {
2669
+ } catch (err) {
2670
+ process.stderr.write(
2671
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2672
+ `
2673
+ );
2619
2674
  }
2620
2675
  }
2621
2676
  return {
@@ -2875,9 +2930,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2875
2930
  return { taskFile, assignedTo, assignedBy, taskSlug };
2876
2931
  }
2877
2932
  async function ensureArchitectureDoc(baseDir, projectName) {
2878
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2933
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
2879
2934
  try {
2880
- if (existsSync9(archPath)) return;
2935
+ if (existsSync10(archPath)) return;
2881
2936
  const template = [
2882
2937
  `# ${projectName} \u2014 System Architecture`,
2883
2938
  "",
@@ -2910,10 +2965,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2910
2965
  }
2911
2966
  }
2912
2967
  async function ensureGitignoreExe(baseDir) {
2913
- const gitignorePath = path10.join(baseDir, ".gitignore");
2968
+ const gitignorePath = path11.join(baseDir, ".gitignore");
2914
2969
  try {
2915
- if (existsSync9(gitignorePath)) {
2916
- const content = readFileSync9(gitignorePath, "utf-8");
2970
+ if (existsSync10(gitignorePath)) {
2971
+ const content = readFileSync10(gitignorePath, "utf-8");
2917
2972
  if (/^\/?exe\/?$/m.test(content)) return;
2918
2973
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2919
2974
  } else {
@@ -2944,8 +2999,8 @@ var init_tasks_crud = __esm({
2944
2999
  });
2945
3000
 
2946
3001
  // src/lib/tasks-review.ts
2947
- import path11 from "path";
2948
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3002
+ import path12 from "path";
3003
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2949
3004
  async function countPendingReviews(sessionScope) {
2950
3005
  const client = getClient();
2951
3006
  if (sessionScope) {
@@ -3126,11 +3181,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3126
3181
  );
3127
3182
  }
3128
3183
  try {
3129
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3130
- if (existsSync10(cacheDir)) {
3184
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3185
+ if (existsSync11(cacheDir)) {
3131
3186
  for (const f of readdirSync2(cacheDir)) {
3132
3187
  if (f.startsWith("review-notified-")) {
3133
- unlinkSync4(path11.join(cacheDir, f));
3188
+ unlinkSync4(path12.join(cacheDir, f));
3134
3189
  }
3135
3190
  }
3136
3191
  }
@@ -3151,7 +3206,7 @@ var init_tasks_review = __esm({
3151
3206
  });
3152
3207
 
3153
3208
  // src/lib/tasks-chain.ts
3154
- import path12 from "path";
3209
+ import path13 from "path";
3155
3210
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3156
3211
  async function cascadeUnblock(taskId, baseDir, now) {
3157
3212
  const client = getClient();
@@ -3168,7 +3223,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3168
3223
  });
3169
3224
  for (const ur of unblockedRows.rows) {
3170
3225
  try {
3171
- const ubFile = path12.join(baseDir, String(ur.task_file));
3226
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3172
3227
  let ubContent = await readFile3(ubFile, "utf-8");
3173
3228
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3174
3229
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3237,7 +3292,7 @@ var init_tasks_chain = __esm({
3237
3292
 
3238
3293
  // src/lib/project-name.ts
3239
3294
  import { execSync as execSync6 } from "child_process";
3240
- import path13 from "path";
3295
+ import path14 from "path";
3241
3296
  function getProjectName(cwd) {
3242
3297
  const dir = cwd ?? process.cwd();
3243
3298
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3250,7 +3305,7 @@ function getProjectName(cwd) {
3250
3305
  timeout: 2e3,
3251
3306
  stdio: ["pipe", "pipe", "pipe"]
3252
3307
  }).trim();
3253
- repoRoot = path13.dirname(gitCommonDir);
3308
+ repoRoot = path14.dirname(gitCommonDir);
3254
3309
  } catch {
3255
3310
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3256
3311
  cwd: dir,
@@ -3259,11 +3314,11 @@ function getProjectName(cwd) {
3259
3314
  stdio: ["pipe", "pipe", "pipe"]
3260
3315
  }).trim();
3261
3316
  }
3262
- _cached2 = path13.basename(repoRoot);
3317
+ _cached2 = path14.basename(repoRoot);
3263
3318
  _cachedCwd = dir;
3264
3319
  return _cached2;
3265
3320
  } catch {
3266
- _cached2 = path13.basename(dir);
3321
+ _cached2 = path14.basename(dir);
3267
3322
  _cachedCwd = dir;
3268
3323
  return _cached2;
3269
3324
  }
@@ -3798,8 +3853,8 @@ __export(tasks_exports, {
3798
3853
  updateTaskStatus: () => updateTaskStatus,
3799
3854
  writeCheckpoint: () => writeCheckpoint
3800
3855
  });
3801
- import path14 from "path";
3802
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3856
+ import path15 from "path";
3857
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3803
3858
  async function createTask(input) {
3804
3859
  const result = await createTaskCore(input);
3805
3860
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3818,11 +3873,11 @@ async function updateTask(input) {
3818
3873
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3819
3874
  try {
3820
3875
  const agent = String(row.assigned_to);
3821
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3822
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3876
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3877
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
3823
3878
  if (input.status === "in_progress") {
3824
- mkdirSync4(cacheDir, { recursive: true });
3825
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3879
+ mkdirSync5(cacheDir, { recursive: true });
3880
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3826
3881
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3827
3882
  try {
3828
3883
  unlinkSync5(cachePath);
@@ -4289,13 +4344,13 @@ __export(tmux_routing_exports, {
4289
4344
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4290
4345
  });
4291
4346
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4292
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4293
- import path15 from "path";
4347
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4348
+ import path16 from "path";
4294
4349
  import os8 from "os";
4295
4350
  import { fileURLToPath as fileURLToPath2 } from "url";
4296
4351
  import { unlinkSync as unlinkSync6 } from "fs";
4297
4352
  function spawnLockPath(sessionName) {
4298
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4353
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4299
4354
  }
4300
4355
  function isProcessAlive(pid) {
4301
4356
  try {
@@ -4306,13 +4361,13 @@ function isProcessAlive(pid) {
4306
4361
  }
4307
4362
  }
4308
4363
  function acquireSpawnLock2(sessionName) {
4309
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4310
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4364
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4365
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4311
4366
  }
4312
4367
  const lockFile = spawnLockPath(sessionName);
4313
- if (existsSync11(lockFile)) {
4368
+ if (existsSync12(lockFile)) {
4314
4369
  try {
4315
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4370
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4316
4371
  const age = Date.now() - lock.timestamp;
4317
4372
  if (isProcessAlive(lock.pid) && age < 6e4) {
4318
4373
  return false;
@@ -4320,7 +4375,7 @@ function acquireSpawnLock2(sessionName) {
4320
4375
  } catch {
4321
4376
  }
4322
4377
  }
4323
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4378
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4324
4379
  return true;
4325
4380
  }
4326
4381
  function releaseSpawnLock2(sessionName) {
@@ -4332,13 +4387,13 @@ function releaseSpawnLock2(sessionName) {
4332
4387
  function resolveBehaviorsExporterScript() {
4333
4388
  try {
4334
4389
  const thisFile = fileURLToPath2(import.meta.url);
4335
- const scriptPath = path15.join(
4336
- path15.dirname(thisFile),
4390
+ const scriptPath = path16.join(
4391
+ path16.dirname(thisFile),
4337
4392
  "..",
4338
4393
  "bin",
4339
4394
  "exe-export-behaviors.js"
4340
4395
  );
4341
- return existsSync11(scriptPath) ? scriptPath : null;
4396
+ return existsSync12(scriptPath) ? scriptPath : null;
4342
4397
  } catch {
4343
4398
  return null;
4344
4399
  }
@@ -4404,12 +4459,12 @@ function extractRootExe(name) {
4404
4459
  return parts.length > 0 ? parts[parts.length - 1] : null;
4405
4460
  }
4406
4461
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4407
- if (!existsSync11(SESSION_CACHE)) {
4408
- mkdirSync5(SESSION_CACHE, { recursive: true });
4462
+ if (!existsSync12(SESSION_CACHE)) {
4463
+ mkdirSync6(SESSION_CACHE, { recursive: true });
4409
4464
  }
4410
4465
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4411
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4412
- writeFileSync6(filePath, JSON.stringify({
4466
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4467
+ writeFileSync7(filePath, JSON.stringify({
4413
4468
  parentExe: rootExe,
4414
4469
  dispatchedBy: dispatchedBy || rootExe,
4415
4470
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4417,7 +4472,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4417
4472
  }
4418
4473
  function getParentExe(sessionKey) {
4419
4474
  try {
4420
- const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4475
+ const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4421
4476
  return data.parentExe || null;
4422
4477
  } catch {
4423
4478
  return null;
@@ -4425,8 +4480,8 @@ function getParentExe(sessionKey) {
4425
4480
  }
4426
4481
  function getDispatchedBy(sessionKey) {
4427
4482
  try {
4428
- const data = JSON.parse(readFileSync10(
4429
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4483
+ const data = JSON.parse(readFileSync11(
4484
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4430
4485
  "utf8"
4431
4486
  ));
4432
4487
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4487,32 +4542,50 @@ async function verifyPaneAtCapacity(sessionName) {
4487
4542
  }
4488
4543
  function readDebounceState() {
4489
4544
  try {
4490
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4491
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4545
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4546
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4547
+ const state = {};
4548
+ for (const [key, val] of Object.entries(raw)) {
4549
+ if (typeof val === "number") {
4550
+ state[key] = { lastSent: val, pending: 0 };
4551
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4552
+ state[key] = val;
4553
+ }
4554
+ }
4555
+ return state;
4492
4556
  } catch {
4493
4557
  return {};
4494
4558
  }
4495
4559
  }
4496
4560
  function writeDebounceState(state) {
4497
4561
  try {
4498
- if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4499
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4562
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4563
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4500
4564
  } catch {
4501
4565
  }
4502
4566
  }
4503
4567
  function isDebounced(targetSession) {
4504
4568
  const state = readDebounceState();
4505
- const lastSent = state[targetSession] ?? 0;
4506
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4569
+ const entry = state[targetSession];
4570
+ const lastSent = entry?.lastSent ?? 0;
4571
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4572
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4573
+ state[targetSession].pending++;
4574
+ writeDebounceState(state);
4575
+ return true;
4576
+ }
4577
+ return false;
4507
4578
  }
4508
4579
  function recordDebounce(targetSession) {
4509
4580
  const state = readDebounceState();
4510
- state[targetSession] = Date.now();
4581
+ const batched = state[targetSession]?.pending ?? 0;
4582
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4511
4583
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4512
4584
  for (const key of Object.keys(state)) {
4513
- if ((state[key] ?? 0) < cutoff) delete state[key];
4585
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4514
4586
  }
4515
4587
  writeDebounceState(state);
4588
+ return batched;
4516
4589
  }
4517
4590
  function logIntercom(msg) {
4518
4591
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4557,7 +4630,7 @@ function sendIntercom(targetSession) {
4557
4630
  return "skipped_exe";
4558
4631
  }
4559
4632
  if (isDebounced(targetSession)) {
4560
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4633
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4561
4634
  return "debounced";
4562
4635
  }
4563
4636
  try {
@@ -4569,14 +4642,14 @@ function sendIntercom(targetSession) {
4569
4642
  const sessionState = getSessionState(targetSession);
4570
4643
  if (sessionState === "no_claude") {
4571
4644
  queueIntercom(targetSession, "claude not running in session");
4572
- recordDebounce(targetSession);
4573
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4645
+ const batched2 = recordDebounce(targetSession);
4646
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4574
4647
  return "queued";
4575
4648
  }
4576
4649
  if (sessionState === "thinking" || sessionState === "tool") {
4577
4650
  queueIntercom(targetSession, "session busy at send time");
4578
- recordDebounce(targetSession);
4579
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4651
+ const batched2 = recordDebounce(targetSession);
4652
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4580
4653
  return "queued";
4581
4654
  }
4582
4655
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4584,8 +4657,8 @@ function sendIntercom(targetSession) {
4584
4657
  transport.sendKeys(targetSession, "q");
4585
4658
  }
4586
4659
  transport.sendKeys(targetSession, "/exe-intercom");
4587
- recordDebounce(targetSession);
4588
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4660
+ const batched = recordDebounce(targetSession);
4661
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4589
4662
  return "delivered";
4590
4663
  } catch {
4591
4664
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4687,26 +4760,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4687
4760
  const transport = getTransport();
4688
4761
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4689
4762
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4690
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
4691
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4692
- if (!existsSync11(logDir)) {
4693
- mkdirSync5(logDir, { recursive: true });
4763
+ const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
4764
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4765
+ if (!existsSync12(logDir)) {
4766
+ mkdirSync6(logDir, { recursive: true });
4694
4767
  }
4695
4768
  transport.kill(sessionName);
4696
4769
  let cleanupSuffix = "";
4697
4770
  try {
4698
4771
  const thisFile = fileURLToPath2(import.meta.url);
4699
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4700
- if (existsSync11(cleanupScript)) {
4772
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4773
+ if (existsSync12(cleanupScript)) {
4701
4774
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4702
4775
  }
4703
4776
  } catch {
4704
4777
  }
4705
4778
  try {
4706
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
4779
+ const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
4707
4780
  let claudeJson = {};
4708
4781
  try {
4709
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4782
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4710
4783
  } catch {
4711
4784
  }
4712
4785
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4714,17 +4787,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4714
4787
  const trustDir = opts?.cwd ?? projectDir;
4715
4788
  if (!projects[trustDir]) projects[trustDir] = {};
4716
4789
  projects[trustDir].hasTrustDialogAccepted = true;
4717
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4790
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4718
4791
  } catch {
4719
4792
  }
4720
4793
  try {
4721
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
4794
+ const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
4722
4795
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4723
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
4724
- const settingsPath = path15.join(projSettingsDir, "settings.json");
4796
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
4797
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
4725
4798
  let settings = {};
4726
4799
  try {
4727
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4800
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4728
4801
  } catch {
4729
4802
  }
4730
4803
  const perms = settings.permissions ?? {};
@@ -4752,20 +4825,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4752
4825
  if (changed) {
4753
4826
  perms.allow = allow;
4754
4827
  settings.permissions = perms;
4755
- mkdirSync5(projSettingsDir, { recursive: true });
4756
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4828
+ mkdirSync6(projSettingsDir, { recursive: true });
4829
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4757
4830
  }
4758
4831
  } catch {
4759
4832
  }
4760
4833
  const spawnCwd = opts?.cwd ?? projectDir;
4761
4834
  const useExeAgent = !!(opts?.model && opts?.provider);
4762
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4835
+ const agentRtConfig = getAgentRuntime(employeeName);
4836
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4837
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4838
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4763
4839
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4764
4840
  let identityFlag = "";
4765
4841
  let behaviorsFlag = "";
4766
4842
  let legacyFallbackWarned = false;
4767
4843
  if (!useExeAgent && !useBinSymlink) {
4768
- const identityPath = path15.join(
4844
+ const identityPath = path16.join(
4769
4845
  os8.homedir(),
4770
4846
  ".exe-os",
4771
4847
  "identity",
@@ -4775,13 +4851,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4775
4851
  const hasAgentFlag = claudeSupportsAgentFlag();
4776
4852
  if (hasAgentFlag) {
4777
4853
  identityFlag = ` --agent ${employeeName}`;
4778
- } else if (existsSync11(identityPath)) {
4854
+ } else if (existsSync12(identityPath)) {
4779
4855
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4780
4856
  legacyFallbackWarned = true;
4781
4857
  }
4782
4858
  const behaviorsFile = exportBehaviorsSync(
4783
4859
  employeeName,
4784
- path15.basename(spawnCwd),
4860
+ path16.basename(spawnCwd),
4785
4861
  sessionName
4786
4862
  );
4787
4863
  if (behaviorsFile) {
@@ -4796,16 +4872,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4796
4872
  }
4797
4873
  let sessionContextFlag = "";
4798
4874
  try {
4799
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
4800
- mkdirSync5(ctxDir, { recursive: true });
4801
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4875
+ const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
4876
+ mkdirSync6(ctxDir, { recursive: true });
4877
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
4802
4878
  const ctxContent = [
4803
4879
  `## Session Context`,
4804
4880
  `You are running in tmux session: ${sessionName}.`,
4805
4881
  `Your parent coordinator session is ${exeSession}.`,
4806
4882
  `Your employees (if any) use the -${exeSession} suffix.`
4807
4883
  ].join("\n");
4808
- writeFileSync6(ctxFile, ctxContent);
4884
+ writeFileSync7(ctxFile, ctxContent);
4809
4885
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4810
4886
  } catch {
4811
4887
  }
@@ -4819,9 +4895,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4819
4895
  }
4820
4896
  }
4821
4897
  }
4898
+ if (useCodex) {
4899
+ const codexCfg = RUNTIME_TABLE.codex;
4900
+ if (codexCfg?.apiKeyEnv) {
4901
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4902
+ if (keyVal) {
4903
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4904
+ }
4905
+ }
4906
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4907
+ }
4908
+ if (useOpencode) {
4909
+ const ocCfg = PROVIDER_TABLE.opencode;
4910
+ if (ocCfg?.apiKeyEnv) {
4911
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4912
+ if (keyVal) {
4913
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4914
+ }
4915
+ }
4916
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4917
+ }
4918
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4919
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4920
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4921
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4922
+ }
4923
+ }
4822
4924
  let spawnCommand;
4823
4925
  if (useExeAgent) {
4824
4926
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4927
+ } else if (useCodex) {
4928
+ process.stderr.write(
4929
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4930
+ `
4931
+ );
4932
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4933
+ } else if (useOpencode) {
4934
+ const binName = `${employeeName}-opencode`;
4935
+ process.stderr.write(
4936
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4937
+ `
4938
+ );
4939
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4825
4940
  } else if (useBinSymlink) {
4826
4941
  const binName = `${employeeName}-${ccProvider}`;
4827
4942
  process.stderr.write(
@@ -4843,11 +4958,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4843
4958
  transport.pipeLog(sessionName, logFile);
4844
4959
  try {
4845
4960
  const mySession = getMySession();
4846
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4847
- writeFileSync6(dispatchInfo, JSON.stringify({
4961
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4962
+ writeFileSync7(dispatchInfo, JSON.stringify({
4848
4963
  dispatchedBy: mySession,
4849
4964
  rootExe: exeSession,
4850
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4965
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4966
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4967
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4851
4968
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4852
4969
  }));
4853
4970
  } catch {
@@ -4865,6 +4982,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4865
4982
  booted = true;
4866
4983
  break;
4867
4984
  }
4985
+ } else if (useCodex) {
4986
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4987
+ booted = true;
4988
+ break;
4989
+ }
4868
4990
  } else {
4869
4991
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4870
4992
  booted = true;
@@ -4876,9 +4998,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4876
4998
  }
4877
4999
  if (!booted) {
4878
5000
  releaseSpawnLock2(sessionName);
4879
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
5001
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
5002
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4880
5003
  }
4881
- if (!useExeAgent) {
5004
+ if (!useExeAgent && !useCodex) {
4882
5005
  try {
4883
5006
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4884
5007
  } catch {
@@ -4905,17 +5028,19 @@ var init_tmux_routing = __esm({
4905
5028
  init_cc_agent_support();
4906
5029
  init_mcp_prefix();
4907
5030
  init_provider_table();
5031
+ init_agent_config();
5032
+ init_runtime_table();
4908
5033
  init_intercom_queue();
4909
5034
  init_plan_limits();
4910
5035
  init_employees();
4911
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
4912
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
5036
+ SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
5037
+ SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
4913
5038
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4914
5039
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4915
5040
  VERIFY_PANE_LINES = 200;
4916
5041
  INTERCOM_DEBOUNCE_MS = 3e4;
4917
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
4918
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5042
+ INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
5043
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
4919
5044
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4920
5045
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4921
5046
  }
@@ -4932,14 +5057,14 @@ var init_memory = __esm({
4932
5057
 
4933
5058
  // src/lib/keychain.ts
4934
5059
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4935
- import { existsSync as existsSync12 } from "fs";
4936
- import path16 from "path";
5060
+ import { existsSync as existsSync13 } from "fs";
5061
+ import path17 from "path";
4937
5062
  import os9 from "os";
4938
5063
  function getKeyDir() {
4939
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5064
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os9.homedir(), ".exe-os");
4940
5065
  }
4941
5066
  function getKeyPath() {
4942
- return path16.join(getKeyDir(), "master.key");
5067
+ return path17.join(getKeyDir(), "master.key");
4943
5068
  }
4944
5069
  async function tryKeytar() {
4945
5070
  try {
@@ -4960,7 +5085,7 @@ async function getMasterKey() {
4960
5085
  }
4961
5086
  }
4962
5087
  const keyPath = getKeyPath();
4963
- if (!existsSync12(keyPath)) {
5088
+ if (!existsSync13(keyPath)) {
4964
5089
  process.stderr.write(
4965
5090
  `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4966
5091
  `
@@ -5000,13 +5125,13 @@ __export(shard_manager_exports, {
5000
5125
  listShards: () => listShards,
5001
5126
  shardExists: () => shardExists
5002
5127
  });
5003
- import path17 from "path";
5004
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5128
+ import path18 from "path";
5129
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
5005
5130
  import { createClient as createClient2 } from "@libsql/client";
5006
5131
  function initShardManager(encryptionKey) {
5007
5132
  _encryptionKey = encryptionKey;
5008
- if (!existsSync13(SHARDS_DIR)) {
5009
- mkdirSync6(SHARDS_DIR, { recursive: true });
5133
+ if (!existsSync14(SHARDS_DIR)) {
5134
+ mkdirSync7(SHARDS_DIR, { recursive: true });
5010
5135
  }
5011
5136
  _shardingEnabled = true;
5012
5137
  }
@@ -5026,7 +5151,7 @@ function getShardClient(projectName) {
5026
5151
  }
5027
5152
  const cached = _shards.get(safeName);
5028
5153
  if (cached) return cached;
5029
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5154
+ const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
5030
5155
  const client = createClient2({
5031
5156
  url: `file:${dbPath}`,
5032
5157
  encryptionKey: _encryptionKey
@@ -5036,10 +5161,10 @@ function getShardClient(projectName) {
5036
5161
  }
5037
5162
  function shardExists(projectName) {
5038
5163
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5039
- return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
5164
+ return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
5040
5165
  }
5041
5166
  function listShards() {
5042
- if (!existsSync13(SHARDS_DIR)) return [];
5167
+ if (!existsSync14(SHARDS_DIR)) return [];
5043
5168
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5044
5169
  }
5045
5170
  async function ensureShardSchema(client) {
@@ -5225,7 +5350,7 @@ var init_shard_manager = __esm({
5225
5350
  "src/lib/shard-manager.ts"() {
5226
5351
  "use strict";
5227
5352
  init_config();
5228
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5353
+ SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
5229
5354
  _shards = /* @__PURE__ */ new Map();
5230
5355
  _encryptionKey = null;
5231
5356
  _shardingEnabled = false;
@@ -6014,8 +6139,8 @@ function findContainingChunk(filePath, snippet) {
6014
6139
  try {
6015
6140
  const ext = filePath.split(".").pop()?.toLowerCase();
6016
6141
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
6017
- const { readFileSync: readFileSync11 } = __require("fs");
6018
- const source = readFileSync11(filePath, "utf8");
6142
+ const { readFileSync: readFileSync12 } = __require("fs");
6143
+ const source = readFileSync12(filePath, "utf8");
6019
6144
  const lines = source.split("\n");
6020
6145
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
6021
6146
  let matchLine = -1;
@@ -6081,9 +6206,9 @@ function extractBash(input, response) {
6081
6206
  }
6082
6207
  function extractGrep(input, response) {
6083
6208
  const pattern = String(input.pattern ?? "");
6084
- const path18 = input.path ? String(input.path) : "";
6209
+ const path19 = input.path ? String(input.path) : "";
6085
6210
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
6086
- return `Searched for "${pattern}"${path18 ? ` in ${path18}` : ""}
6211
+ return `Searched for "${pattern}"${path19 ? ` in ${path19}` : ""}
6087
6212
  ${output.slice(0, MAX_OUTPUT)}`;
6088
6213
  }
6089
6214
  function extractGlob(input, response) {