@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
@@ -502,18 +502,69 @@ var init_provider_table = __esm({
502
502
  }
503
503
  });
504
504
 
505
- // src/lib/intercom-queue.ts
506
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
505
+ // src/lib/runtime-table.ts
506
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
507
+ var init_runtime_table = __esm({
508
+ "src/lib/runtime-table.ts"() {
509
+ "use strict";
510
+ RUNTIME_TABLE = {
511
+ codex: {
512
+ binary: "codex",
513
+ launchMode: "exec",
514
+ autoApproveFlag: "--full-auto",
515
+ inlineFlag: "--no-alt-screen",
516
+ apiKeyEnv: "OPENAI_API_KEY",
517
+ defaultModel: "gpt-5.4"
518
+ }
519
+ };
520
+ DEFAULT_RUNTIME = "claude";
521
+ }
522
+ });
523
+
524
+ // src/lib/agent-config.ts
525
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
507
526
  import path5 from "path";
527
+ function loadAgentConfig() {
528
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
529
+ try {
530
+ return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
531
+ } catch {
532
+ return {};
533
+ }
534
+ }
535
+ function getAgentRuntime(agentId) {
536
+ const config = loadAgentConfig();
537
+ const entry = config[agentId];
538
+ if (entry) return entry;
539
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
540
+ }
541
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
542
+ var init_agent_config = __esm({
543
+ "src/lib/agent-config.ts"() {
544
+ "use strict";
545
+ init_config();
546
+ init_runtime_table();
547
+ AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
548
+ DEFAULT_MODELS = {
549
+ claude: "claude-opus-4",
550
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
551
+ opencode: "minimax-m2.7"
552
+ };
553
+ }
554
+ });
555
+
556
+ // src/lib/intercom-queue.ts
557
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
558
+ import path6 from "path";
508
559
  import os4 from "os";
509
560
  function ensureDir() {
510
- const dir = path5.dirname(QUEUE_PATH);
511
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
561
+ const dir = path6.dirname(QUEUE_PATH);
562
+ if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
512
563
  }
513
564
  function readQueue() {
514
565
  try {
515
- if (!existsSync4(QUEUE_PATH)) return [];
516
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
566
+ if (!existsSync5(QUEUE_PATH)) return [];
567
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
517
568
  } catch {
518
569
  return [];
519
570
  }
@@ -521,7 +572,7 @@ function readQueue() {
521
572
  function writeQueue(queue) {
522
573
  ensureDir();
523
574
  const tmp = `${QUEUE_PATH}.tmp`;
524
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
575
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
525
576
  renameSync3(tmp, QUEUE_PATH);
526
577
  }
527
578
  function queueIntercom(targetSession, reason) {
@@ -545,9 +596,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
545
596
  var init_intercom_queue = __esm({
546
597
  "src/lib/intercom-queue.ts"() {
547
598
  "use strict";
548
- QUEUE_PATH = path5.join(os4.homedir(), ".exe-os", "intercom-queue.json");
599
+ QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
549
600
  TTL_MS = 60 * 60 * 1e3;
550
- INTERCOM_LOG = path5.join(os4.homedir(), ".exe-os", "intercom.log");
601
+ INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
551
602
  }
552
603
  });
553
604
 
@@ -610,8 +661,8 @@ var init_db_retry = __esm({
610
661
  import net from "net";
611
662
  import { spawn } from "child_process";
612
663
  import { randomUUID } from "crypto";
613
- import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
614
- import path6 from "path";
664
+ import { existsSync as existsSync6, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
665
+ import path7 from "path";
615
666
  import { fileURLToPath } from "url";
616
667
  function handleData(chunk) {
617
668
  _buffer += chunk.toString();
@@ -639,9 +690,9 @@ function handleData(chunk) {
639
690
  }
640
691
  }
641
692
  function cleanupStaleFiles() {
642
- if (existsSync5(PID_PATH)) {
693
+ if (existsSync6(PID_PATH)) {
643
694
  try {
644
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
695
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
645
696
  if (pid > 0) {
646
697
  try {
647
698
  process.kill(pid, 0);
@@ -662,11 +713,11 @@ function cleanupStaleFiles() {
662
713
  }
663
714
  }
664
715
  function findPackageRoot() {
665
- let dir = path6.dirname(fileURLToPath(import.meta.url));
666
- const { root } = path6.parse(dir);
716
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
717
+ const { root } = path7.parse(dir);
667
718
  while (dir !== root) {
668
- if (existsSync5(path6.join(dir, "package.json"))) return dir;
669
- dir = path6.dirname(dir);
719
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
720
+ dir = path7.dirname(dir);
670
721
  }
671
722
  return null;
672
723
  }
@@ -676,8 +727,8 @@ function spawnDaemon() {
676
727
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
677
728
  return;
678
729
  }
679
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
680
- if (!existsSync5(daemonPath)) {
730
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
731
+ if (!existsSync6(daemonPath)) {
681
732
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
682
733
  `);
683
734
  return;
@@ -685,7 +736,7 @@ function spawnDaemon() {
685
736
  const resolvedPath = daemonPath;
686
737
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
687
738
  `);
688
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
739
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
689
740
  let stderrFd = "ignore";
690
741
  try {
691
742
  stderrFd = openSync(logPath, "a");
@@ -830,9 +881,9 @@ var init_exe_daemon_client = __esm({
830
881
  "src/lib/exe-daemon-client.ts"() {
831
882
  "use strict";
832
883
  init_config();
833
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
834
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
835
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
884
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
885
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
886
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
836
887
  SPAWN_LOCK_STALE_MS = 3e4;
837
888
  CONNECT_TIMEOUT_MS = 15e3;
838
889
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2057,18 +2108,18 @@ var init_database = __esm({
2057
2108
  });
2058
2109
 
2059
2110
  // src/lib/license.ts
2060
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2111
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
2061
2112
  import { randomUUID as randomUUID2 } from "crypto";
2062
- import path7 from "path";
2113
+ import path8 from "path";
2063
2114
  import { jwtVerify, importSPKI } from "jose";
2064
2115
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2065
2116
  var init_license = __esm({
2066
2117
  "src/lib/license.ts"() {
2067
2118
  "use strict";
2068
2119
  init_config();
2069
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2070
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2071
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2120
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2121
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2122
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2072
2123
  PLAN_LIMITS = {
2073
2124
  free: { devices: 1, employees: 1, memories: 5e3 },
2074
2125
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2080,12 +2131,12 @@ var init_license = __esm({
2080
2131
  });
2081
2132
 
2082
2133
  // src/lib/plan-limits.ts
2083
- import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
2084
- import path8 from "path";
2134
+ import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
2135
+ import path9 from "path";
2085
2136
  function getLicenseSync() {
2086
2137
  try {
2087
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2088
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2138
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2139
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2089
2140
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2090
2141
  const parts = raw.token.split(".");
2091
2142
  if (parts.length !== 3) return freeLicense();
@@ -2123,8 +2174,8 @@ function assertEmployeeLimitSync(rosterPath) {
2123
2174
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2124
2175
  let count = 0;
2125
2176
  try {
2126
- if (existsSync7(filePath)) {
2127
- const raw = readFileSync8(filePath, "utf8");
2177
+ if (existsSync8(filePath)) {
2178
+ const raw = readFileSync9(filePath, "utf8");
2128
2179
  const employees = JSON.parse(raw);
2129
2180
  count = Array.isArray(employees) ? employees.length : 0;
2130
2181
  }
@@ -2153,19 +2204,19 @@ var init_plan_limits = __esm({
2153
2204
  this.name = "PlanLimitError";
2154
2205
  }
2155
2206
  };
2156
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2207
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2157
2208
  }
2158
2209
  });
2159
2210
 
2160
2211
  // src/lib/notifications.ts
2161
2212
  import crypto from "crypto";
2162
- import path9 from "path";
2213
+ import path10 from "path";
2163
2214
  import os5 from "os";
2164
2215
  import {
2165
- readFileSync as readFileSync9,
2216
+ readFileSync as readFileSync10,
2166
2217
  readdirSync as readdirSync2,
2167
2218
  unlinkSync as unlinkSync4,
2168
- existsSync as existsSync8,
2219
+ existsSync as existsSync9,
2169
2220
  rmdirSync
2170
2221
  } from "fs";
2171
2222
  async function writeNotification(notification) {
@@ -2300,11 +2351,11 @@ var init_state_bus = __esm({
2300
2351
 
2301
2352
  // src/lib/tasks-crud.ts
2302
2353
  import crypto3 from "crypto";
2303
- import path10 from "path";
2354
+ import path11 from "path";
2304
2355
  import os6 from "os";
2305
2356
  import { execSync as execSync5 } from "child_process";
2306
2357
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2307
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
2358
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
2308
2359
  async function writeCheckpoint(input2) {
2309
2360
  const client = getClient();
2310
2361
  const row = await resolveTask(client, input2.taskId);
@@ -2479,8 +2530,8 @@ ${laneWarning}` : laneWarning;
2479
2530
  }
2480
2531
  if (input2.baseDir) {
2481
2532
  try {
2482
- await mkdir3(path10.join(input2.baseDir, "exe", "output"), { recursive: true });
2483
- await mkdir3(path10.join(input2.baseDir, "exe", "research"), { recursive: true });
2533
+ await mkdir3(path11.join(input2.baseDir, "exe", "output"), { recursive: true });
2534
+ await mkdir3(path11.join(input2.baseDir, "exe", "research"), { recursive: true });
2484
2535
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
2485
2536
  await ensureGitignoreExe(input2.baseDir);
2486
2537
  } catch {
@@ -2516,10 +2567,10 @@ ${laneWarning}` : laneWarning;
2516
2567
  });
2517
2568
  if (input2.baseDir) {
2518
2569
  try {
2519
- const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
2520
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2521
- const mdDir = path10.dirname(mdPath);
2522
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2570
+ const EXE_OS_DIR = path11.join(os6.homedir(), ".exe-os");
2571
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
2572
+ const mdDir = path11.dirname(mdPath);
2573
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2523
2574
  const reviewer = input2.reviewer ?? input2.assignedBy;
2524
2575
  const mdContent = `# ${input2.title}
2525
2576
 
@@ -2544,7 +2595,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2544
2595
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2545
2596
  `;
2546
2597
  await writeFile3(mdPath, mdContent, "utf-8");
2547
- } catch {
2598
+ } catch (err) {
2599
+ process.stderr.write(
2600
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2601
+ `
2602
+ );
2548
2603
  }
2549
2604
  }
2550
2605
  return {
@@ -2804,9 +2859,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2804
2859
  return { taskFile, assignedTo, assignedBy, taskSlug };
2805
2860
  }
2806
2861
  async function ensureArchitectureDoc(baseDir, projectName) {
2807
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2862
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
2808
2863
  try {
2809
- if (existsSync9(archPath)) return;
2864
+ if (existsSync10(archPath)) return;
2810
2865
  const template = [
2811
2866
  `# ${projectName} \u2014 System Architecture`,
2812
2867
  "",
@@ -2839,10 +2894,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2839
2894
  }
2840
2895
  }
2841
2896
  async function ensureGitignoreExe(baseDir) {
2842
- const gitignorePath = path10.join(baseDir, ".gitignore");
2897
+ const gitignorePath = path11.join(baseDir, ".gitignore");
2843
2898
  try {
2844
- if (existsSync9(gitignorePath)) {
2845
- const content = readFileSync10(gitignorePath, "utf-8");
2899
+ if (existsSync10(gitignorePath)) {
2900
+ const content = readFileSync11(gitignorePath, "utf-8");
2846
2901
  if (/^\/?exe\/?$/m.test(content)) return;
2847
2902
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2848
2903
  } else {
@@ -2873,8 +2928,8 @@ var init_tasks_crud = __esm({
2873
2928
  });
2874
2929
 
2875
2930
  // src/lib/tasks-review.ts
2876
- import path11 from "path";
2877
- import { existsSync as existsSync10, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
2931
+ import path12 from "path";
2932
+ import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
2878
2933
  async function countPendingReviews(sessionScope) {
2879
2934
  const client = getClient();
2880
2935
  if (sessionScope) {
@@ -3055,11 +3110,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3055
3110
  );
3056
3111
  }
3057
3112
  try {
3058
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3059
- if (existsSync10(cacheDir)) {
3113
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3114
+ if (existsSync11(cacheDir)) {
3060
3115
  for (const f of readdirSync3(cacheDir)) {
3061
3116
  if (f.startsWith("review-notified-")) {
3062
- unlinkSync5(path11.join(cacheDir, f));
3117
+ unlinkSync5(path12.join(cacheDir, f));
3063
3118
  }
3064
3119
  }
3065
3120
  }
@@ -3080,7 +3135,7 @@ var init_tasks_review = __esm({
3080
3135
  });
3081
3136
 
3082
3137
  // src/lib/tasks-chain.ts
3083
- import path12 from "path";
3138
+ import path13 from "path";
3084
3139
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3085
3140
  async function cascadeUnblock(taskId, baseDir, now) {
3086
3141
  const client = getClient();
@@ -3097,7 +3152,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3097
3152
  });
3098
3153
  for (const ur of unblockedRows.rows) {
3099
3154
  try {
3100
- const ubFile = path12.join(baseDir, String(ur.task_file));
3155
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3101
3156
  let ubContent = await readFile3(ubFile, "utf-8");
3102
3157
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3103
3158
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3166,7 +3221,7 @@ var init_tasks_chain = __esm({
3166
3221
 
3167
3222
  // src/lib/project-name.ts
3168
3223
  import { execSync as execSync6 } from "child_process";
3169
- import path13 from "path";
3224
+ import path14 from "path";
3170
3225
  function getProjectName(cwd) {
3171
3226
  const dir = cwd ?? process.cwd();
3172
3227
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3179,7 +3234,7 @@ function getProjectName(cwd) {
3179
3234
  timeout: 2e3,
3180
3235
  stdio: ["pipe", "pipe", "pipe"]
3181
3236
  }).trim();
3182
- repoRoot = path13.dirname(gitCommonDir);
3237
+ repoRoot = path14.dirname(gitCommonDir);
3183
3238
  } catch {
3184
3239
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3185
3240
  cwd: dir,
@@ -3188,11 +3243,11 @@ function getProjectName(cwd) {
3188
3243
  stdio: ["pipe", "pipe", "pipe"]
3189
3244
  }).trim();
3190
3245
  }
3191
- _cached2 = path13.basename(repoRoot);
3246
+ _cached2 = path14.basename(repoRoot);
3192
3247
  _cachedCwd = dir;
3193
3248
  return _cached2;
3194
3249
  } catch {
3195
- _cached2 = path13.basename(dir);
3250
+ _cached2 = path14.basename(dir);
3196
3251
  _cachedCwd = dir;
3197
3252
  return _cached2;
3198
3253
  }
@@ -3665,8 +3720,8 @@ __export(tasks_exports, {
3665
3720
  updateTaskStatus: () => updateTaskStatus,
3666
3721
  writeCheckpoint: () => writeCheckpoint
3667
3722
  });
3668
- import path14 from "path";
3669
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync6 } from "fs";
3723
+ import path15 from "path";
3724
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
3670
3725
  async function createTask(input2) {
3671
3726
  const result = await createTaskCore(input2);
3672
3727
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3685,11 +3740,11 @@ async function updateTask(input2) {
3685
3740
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
3686
3741
  try {
3687
3742
  const agent = String(row.assigned_to);
3688
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3689
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3743
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3744
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
3690
3745
  if (input2.status === "in_progress") {
3691
- mkdirSync5(cacheDir, { recursive: true });
3692
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3746
+ mkdirSync6(cacheDir, { recursive: true });
3747
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3693
3748
  } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
3694
3749
  try {
3695
3750
  unlinkSync6(cachePath);
@@ -4156,13 +4211,13 @@ __export(tmux_routing_exports, {
4156
4211
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4157
4212
  });
4158
4213
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4159
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
4160
- import path15 from "path";
4214
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync12, appendFileSync } from "fs";
4215
+ import path16 from "path";
4161
4216
  import os7 from "os";
4162
4217
  import { fileURLToPath as fileURLToPath2 } from "url";
4163
4218
  import { unlinkSync as unlinkSync7 } from "fs";
4164
4219
  function spawnLockPath(sessionName) {
4165
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4220
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4166
4221
  }
4167
4222
  function isProcessAlive(pid) {
4168
4223
  try {
@@ -4173,13 +4228,13 @@ function isProcessAlive(pid) {
4173
4228
  }
4174
4229
  }
4175
4230
  function acquireSpawnLock2(sessionName) {
4176
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4177
- mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4231
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4232
+ mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
4178
4233
  }
4179
4234
  const lockFile = spawnLockPath(sessionName);
4180
- if (existsSync11(lockFile)) {
4235
+ if (existsSync12(lockFile)) {
4181
4236
  try {
4182
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4237
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4183
4238
  const age = Date.now() - lock.timestamp;
4184
4239
  if (isProcessAlive(lock.pid) && age < 6e4) {
4185
4240
  return false;
@@ -4187,7 +4242,7 @@ function acquireSpawnLock2(sessionName) {
4187
4242
  } catch {
4188
4243
  }
4189
4244
  }
4190
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4245
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4191
4246
  return true;
4192
4247
  }
4193
4248
  function releaseSpawnLock2(sessionName) {
@@ -4199,13 +4254,13 @@ function releaseSpawnLock2(sessionName) {
4199
4254
  function resolveBehaviorsExporterScript() {
4200
4255
  try {
4201
4256
  const thisFile = fileURLToPath2(import.meta.url);
4202
- const scriptPath = path15.join(
4203
- path15.dirname(thisFile),
4257
+ const scriptPath = path16.join(
4258
+ path16.dirname(thisFile),
4204
4259
  "..",
4205
4260
  "bin",
4206
4261
  "exe-export-behaviors.js"
4207
4262
  );
4208
- return existsSync11(scriptPath) ? scriptPath : null;
4263
+ return existsSync12(scriptPath) ? scriptPath : null;
4209
4264
  } catch {
4210
4265
  return null;
4211
4266
  }
@@ -4271,12 +4326,12 @@ function extractRootExe(name) {
4271
4326
  return parts.length > 0 ? parts[parts.length - 1] : null;
4272
4327
  }
4273
4328
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4274
- if (!existsSync11(SESSION_CACHE)) {
4275
- mkdirSync6(SESSION_CACHE, { recursive: true });
4329
+ if (!existsSync12(SESSION_CACHE)) {
4330
+ mkdirSync7(SESSION_CACHE, { recursive: true });
4276
4331
  }
4277
4332
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4278
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4279
- writeFileSync7(filePath, JSON.stringify({
4333
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4334
+ writeFileSync8(filePath, JSON.stringify({
4280
4335
  parentExe: rootExe,
4281
4336
  dispatchedBy: dispatchedBy || rootExe,
4282
4337
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4284,7 +4339,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4284
4339
  }
4285
4340
  function getParentExe(sessionKey) {
4286
4341
  try {
4287
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4342
+ const data = JSON.parse(readFileSync12(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4288
4343
  return data.parentExe || null;
4289
4344
  } catch {
4290
4345
  return null;
@@ -4292,8 +4347,8 @@ function getParentExe(sessionKey) {
4292
4347
  }
4293
4348
  function getDispatchedBy(sessionKey) {
4294
4349
  try {
4295
- const data = JSON.parse(readFileSync11(
4296
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4350
+ const data = JSON.parse(readFileSync12(
4351
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4297
4352
  "utf8"
4298
4353
  ));
4299
4354
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4354,32 +4409,50 @@ async function verifyPaneAtCapacity(sessionName) {
4354
4409
  }
4355
4410
  function readDebounceState() {
4356
4411
  try {
4357
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4358
- return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4412
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4413
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4414
+ const state = {};
4415
+ for (const [key, val] of Object.entries(raw)) {
4416
+ if (typeof val === "number") {
4417
+ state[key] = { lastSent: val, pending: 0 };
4418
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4419
+ state[key] = val;
4420
+ }
4421
+ }
4422
+ return state;
4359
4423
  } catch {
4360
4424
  return {};
4361
4425
  }
4362
4426
  }
4363
4427
  function writeDebounceState(state) {
4364
4428
  try {
4365
- if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4366
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4429
+ if (!existsSync12(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
4430
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
4367
4431
  } catch {
4368
4432
  }
4369
4433
  }
4370
4434
  function isDebounced(targetSession) {
4371
4435
  const state = readDebounceState();
4372
- const lastSent = state[targetSession] ?? 0;
4373
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4436
+ const entry = state[targetSession];
4437
+ const lastSent = entry?.lastSent ?? 0;
4438
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4439
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4440
+ state[targetSession].pending++;
4441
+ writeDebounceState(state);
4442
+ return true;
4443
+ }
4444
+ return false;
4374
4445
  }
4375
4446
  function recordDebounce(targetSession) {
4376
4447
  const state = readDebounceState();
4377
- state[targetSession] = Date.now();
4448
+ const batched = state[targetSession]?.pending ?? 0;
4449
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4378
4450
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4379
4451
  for (const key of Object.keys(state)) {
4380
- if ((state[key] ?? 0) < cutoff) delete state[key];
4452
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4381
4453
  }
4382
4454
  writeDebounceState(state);
4455
+ return batched;
4383
4456
  }
4384
4457
  function logIntercom(msg) {
4385
4458
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4424,7 +4497,7 @@ function sendIntercom(targetSession) {
4424
4497
  return "skipped_exe";
4425
4498
  }
4426
4499
  if (isDebounced(targetSession)) {
4427
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4500
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4428
4501
  return "debounced";
4429
4502
  }
4430
4503
  try {
@@ -4436,14 +4509,14 @@ function sendIntercom(targetSession) {
4436
4509
  const sessionState = getSessionState(targetSession);
4437
4510
  if (sessionState === "no_claude") {
4438
4511
  queueIntercom(targetSession, "claude not running in session");
4439
- recordDebounce(targetSession);
4440
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4512
+ const batched2 = recordDebounce(targetSession);
4513
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4441
4514
  return "queued";
4442
4515
  }
4443
4516
  if (sessionState === "thinking" || sessionState === "tool") {
4444
4517
  queueIntercom(targetSession, "session busy at send time");
4445
- recordDebounce(targetSession);
4446
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4518
+ const batched2 = recordDebounce(targetSession);
4519
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4447
4520
  return "queued";
4448
4521
  }
4449
4522
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4451,8 +4524,8 @@ function sendIntercom(targetSession) {
4451
4524
  transport.sendKeys(targetSession, "q");
4452
4525
  }
4453
4526
  transport.sendKeys(targetSession, "/exe-intercom");
4454
- recordDebounce(targetSession);
4455
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4527
+ const batched = recordDebounce(targetSession);
4528
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4456
4529
  return "delivered";
4457
4530
  } catch {
4458
4531
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4554,26 +4627,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4554
4627
  const transport = getTransport();
4555
4628
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4556
4629
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4557
- const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
4558
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4559
- if (!existsSync11(logDir)) {
4560
- mkdirSync6(logDir, { recursive: true });
4630
+ const logDir = path16.join(os7.homedir(), ".exe-os", "session-logs");
4631
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4632
+ if (!existsSync12(logDir)) {
4633
+ mkdirSync7(logDir, { recursive: true });
4561
4634
  }
4562
4635
  transport.kill(sessionName);
4563
4636
  let cleanupSuffix = "";
4564
4637
  try {
4565
4638
  const thisFile = fileURLToPath2(import.meta.url);
4566
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4567
- if (existsSync11(cleanupScript)) {
4639
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4640
+ if (existsSync12(cleanupScript)) {
4568
4641
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4569
4642
  }
4570
4643
  } catch {
4571
4644
  }
4572
4645
  try {
4573
- const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
4646
+ const claudeJsonPath = path16.join(os7.homedir(), ".claude.json");
4574
4647
  let claudeJson = {};
4575
4648
  try {
4576
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4649
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4577
4650
  } catch {
4578
4651
  }
4579
4652
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4581,17 +4654,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4581
4654
  const trustDir = opts?.cwd ?? projectDir;
4582
4655
  if (!projects[trustDir]) projects[trustDir] = {};
4583
4656
  projects[trustDir].hasTrustDialogAccepted = true;
4584
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4657
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4585
4658
  } catch {
4586
4659
  }
4587
4660
  try {
4588
- const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
4661
+ const settingsDir = path16.join(os7.homedir(), ".claude", "projects");
4589
4662
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4590
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
4591
- const settingsPath = path15.join(projSettingsDir, "settings.json");
4663
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
4664
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
4592
4665
  let settings = {};
4593
4666
  try {
4594
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4667
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4595
4668
  } catch {
4596
4669
  }
4597
4670
  const perms = settings.permissions ?? {};
@@ -4619,20 +4692,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4619
4692
  if (changed) {
4620
4693
  perms.allow = allow;
4621
4694
  settings.permissions = perms;
4622
- mkdirSync6(projSettingsDir, { recursive: true });
4623
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4695
+ mkdirSync7(projSettingsDir, { recursive: true });
4696
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4624
4697
  }
4625
4698
  } catch {
4626
4699
  }
4627
4700
  const spawnCwd = opts?.cwd ?? projectDir;
4628
4701
  const useExeAgent = !!(opts?.model && opts?.provider);
4629
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4702
+ const agentRtConfig = getAgentRuntime(employeeName);
4703
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4704
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4705
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4630
4706
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4631
4707
  let identityFlag = "";
4632
4708
  let behaviorsFlag = "";
4633
4709
  let legacyFallbackWarned = false;
4634
4710
  if (!useExeAgent && !useBinSymlink) {
4635
- const identityPath = path15.join(
4711
+ const identityPath = path16.join(
4636
4712
  os7.homedir(),
4637
4713
  ".exe-os",
4638
4714
  "identity",
@@ -4642,13 +4718,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4642
4718
  const hasAgentFlag = claudeSupportsAgentFlag();
4643
4719
  if (hasAgentFlag) {
4644
4720
  identityFlag = ` --agent ${employeeName}`;
4645
- } else if (existsSync11(identityPath)) {
4721
+ } else if (existsSync12(identityPath)) {
4646
4722
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4647
4723
  legacyFallbackWarned = true;
4648
4724
  }
4649
4725
  const behaviorsFile = exportBehaviorsSync(
4650
4726
  employeeName,
4651
- path15.basename(spawnCwd),
4727
+ path16.basename(spawnCwd),
4652
4728
  sessionName
4653
4729
  );
4654
4730
  if (behaviorsFile) {
@@ -4663,16 +4739,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4663
4739
  }
4664
4740
  let sessionContextFlag = "";
4665
4741
  try {
4666
- const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
4667
- mkdirSync6(ctxDir, { recursive: true });
4668
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4742
+ const ctxDir = path16.join(os7.homedir(), ".exe-os", "session-cache");
4743
+ mkdirSync7(ctxDir, { recursive: true });
4744
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
4669
4745
  const ctxContent = [
4670
4746
  `## Session Context`,
4671
4747
  `You are running in tmux session: ${sessionName}.`,
4672
4748
  `Your parent coordinator session is ${exeSession}.`,
4673
4749
  `Your employees (if any) use the -${exeSession} suffix.`
4674
4750
  ].join("\n");
4675
- writeFileSync7(ctxFile, ctxContent);
4751
+ writeFileSync8(ctxFile, ctxContent);
4676
4752
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4677
4753
  } catch {
4678
4754
  }
@@ -4686,9 +4762,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4686
4762
  }
4687
4763
  }
4688
4764
  }
4765
+ if (useCodex) {
4766
+ const codexCfg = RUNTIME_TABLE.codex;
4767
+ if (codexCfg?.apiKeyEnv) {
4768
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4769
+ if (keyVal) {
4770
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4771
+ }
4772
+ }
4773
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4774
+ }
4775
+ if (useOpencode) {
4776
+ const ocCfg = PROVIDER_TABLE.opencode;
4777
+ if (ocCfg?.apiKeyEnv) {
4778
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4779
+ if (keyVal) {
4780
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4781
+ }
4782
+ }
4783
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4784
+ }
4785
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4786
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4787
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4788
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4789
+ }
4790
+ }
4689
4791
  let spawnCommand;
4690
4792
  if (useExeAgent) {
4691
4793
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4794
+ } else if (useCodex) {
4795
+ process.stderr.write(
4796
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4797
+ `
4798
+ );
4799
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4800
+ } else if (useOpencode) {
4801
+ const binName = `${employeeName}-opencode`;
4802
+ process.stderr.write(
4803
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4804
+ `
4805
+ );
4806
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4692
4807
  } else if (useBinSymlink) {
4693
4808
  const binName = `${employeeName}-${ccProvider}`;
4694
4809
  process.stderr.write(
@@ -4710,11 +4825,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4710
4825
  transport.pipeLog(sessionName, logFile);
4711
4826
  try {
4712
4827
  const mySession = getMySession();
4713
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4714
- writeFileSync7(dispatchInfo, JSON.stringify({
4828
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4829
+ writeFileSync8(dispatchInfo, JSON.stringify({
4715
4830
  dispatchedBy: mySession,
4716
4831
  rootExe: exeSession,
4717
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4832
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4833
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4834
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4718
4835
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4719
4836
  }));
4720
4837
  } catch {
@@ -4732,6 +4849,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4732
4849
  booted = true;
4733
4850
  break;
4734
4851
  }
4852
+ } else if (useCodex) {
4853
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4854
+ booted = true;
4855
+ break;
4856
+ }
4735
4857
  } else {
4736
4858
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4737
4859
  booted = true;
@@ -4743,9 +4865,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4743
4865
  }
4744
4866
  if (!booted) {
4745
4867
  releaseSpawnLock2(sessionName);
4746
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4868
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
4869
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4747
4870
  }
4748
- if (!useExeAgent) {
4871
+ if (!useExeAgent && !useCodex) {
4749
4872
  try {
4750
4873
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4751
4874
  } catch {
@@ -4772,17 +4895,19 @@ var init_tmux_routing = __esm({
4772
4895
  init_cc_agent_support();
4773
4896
  init_mcp_prefix();
4774
4897
  init_provider_table();
4898
+ init_agent_config();
4899
+ init_runtime_table();
4775
4900
  init_intercom_queue();
4776
4901
  init_plan_limits();
4777
4902
  init_employees();
4778
- SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
4779
- SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
4903
+ SPAWN_LOCK_DIR = path16.join(os7.homedir(), ".exe-os", "spawn-locks");
4904
+ SESSION_CACHE = path16.join(os7.homedir(), ".exe-os", "session-cache");
4780
4905
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4781
4906
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4782
4907
  VERIFY_PANE_LINES = 200;
4783
4908
  INTERCOM_DEBOUNCE_MS = 3e4;
4784
- INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
4785
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4909
+ INTERCOM_LOG2 = path16.join(os7.homedir(), ".exe-os", "intercom.log");
4910
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
4786
4911
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4787
4912
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4788
4913
  }
@@ -4823,14 +4948,14 @@ var init_memory = __esm({
4823
4948
 
4824
4949
  // src/lib/keychain.ts
4825
4950
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4826
- import { existsSync as existsSync12 } from "fs";
4827
- import path16 from "path";
4951
+ import { existsSync as existsSync13 } from "fs";
4952
+ import path17 from "path";
4828
4953
  import os8 from "os";
4829
4954
  function getKeyDir() {
4830
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
4955
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os8.homedir(), ".exe-os");
4831
4956
  }
4832
4957
  function getKeyPath() {
4833
- return path16.join(getKeyDir(), "master.key");
4958
+ return path17.join(getKeyDir(), "master.key");
4834
4959
  }
4835
4960
  async function tryKeytar() {
4836
4961
  try {
@@ -4851,7 +4976,7 @@ async function getMasterKey() {
4851
4976
  }
4852
4977
  }
4853
4978
  const keyPath = getKeyPath();
4854
- if (!existsSync12(keyPath)) {
4979
+ if (!existsSync13(keyPath)) {
4855
4980
  process.stderr.write(
4856
4981
  `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4857
4982
  `
@@ -4891,13 +5016,13 @@ __export(shard_manager_exports, {
4891
5016
  listShards: () => listShards,
4892
5017
  shardExists: () => shardExists
4893
5018
  });
4894
- import path17 from "path";
4895
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5019
+ import path18 from "path";
5020
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync4 } from "fs";
4896
5021
  import { createClient as createClient2 } from "@libsql/client";
4897
5022
  function initShardManager(encryptionKey) {
4898
5023
  _encryptionKey = encryptionKey;
4899
- if (!existsSync13(SHARDS_DIR)) {
4900
- mkdirSync7(SHARDS_DIR, { recursive: true });
5024
+ if (!existsSync14(SHARDS_DIR)) {
5025
+ mkdirSync8(SHARDS_DIR, { recursive: true });
4901
5026
  }
4902
5027
  _shardingEnabled = true;
4903
5028
  }
@@ -4917,7 +5042,7 @@ function getShardClient(projectName) {
4917
5042
  }
4918
5043
  const cached = _shards.get(safeName);
4919
5044
  if (cached) return cached;
4920
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5045
+ const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
4921
5046
  const client = createClient2({
4922
5047
  url: `file:${dbPath}`,
4923
5048
  encryptionKey: _encryptionKey
@@ -4927,10 +5052,10 @@ function getShardClient(projectName) {
4927
5052
  }
4928
5053
  function shardExists(projectName) {
4929
5054
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4930
- return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
5055
+ return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
4931
5056
  }
4932
5057
  function listShards() {
4933
- if (!existsSync13(SHARDS_DIR)) return [];
5058
+ if (!existsSync14(SHARDS_DIR)) return [];
4934
5059
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4935
5060
  }
4936
5061
  async function ensureShardSchema(client) {
@@ -5116,7 +5241,7 @@ var init_shard_manager = __esm({
5116
5241
  "src/lib/shard-manager.ts"() {
5117
5242
  "use strict";
5118
5243
  init_config();
5119
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5244
+ SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
5120
5245
  _shards = /* @__PURE__ */ new Map();
5121
5246
  _encryptionKey = null;
5122
5247
  _shardingEnabled = false;