@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
@@ -505,18 +505,69 @@ var init_provider_table = __esm({
505
505
  }
506
506
  });
507
507
 
508
- // src/lib/intercom-queue.ts
509
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
508
+ // src/lib/runtime-table.ts
509
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
510
+ var init_runtime_table = __esm({
511
+ "src/lib/runtime-table.ts"() {
512
+ "use strict";
513
+ RUNTIME_TABLE = {
514
+ codex: {
515
+ binary: "codex",
516
+ launchMode: "exec",
517
+ autoApproveFlag: "--full-auto",
518
+ inlineFlag: "--no-alt-screen",
519
+ apiKeyEnv: "OPENAI_API_KEY",
520
+ defaultModel: "gpt-5.4"
521
+ }
522
+ };
523
+ DEFAULT_RUNTIME = "claude";
524
+ }
525
+ });
526
+
527
+ // src/lib/agent-config.ts
528
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
510
529
  import path5 from "path";
530
+ function loadAgentConfig() {
531
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
532
+ try {
533
+ return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
534
+ } catch {
535
+ return {};
536
+ }
537
+ }
538
+ function getAgentRuntime(agentId) {
539
+ const config = loadAgentConfig();
540
+ const entry = config[agentId];
541
+ if (entry) return entry;
542
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
543
+ }
544
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
545
+ var init_agent_config = __esm({
546
+ "src/lib/agent-config.ts"() {
547
+ "use strict";
548
+ init_config();
549
+ init_runtime_table();
550
+ AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
551
+ DEFAULT_MODELS = {
552
+ claude: "claude-opus-4",
553
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
554
+ opencode: "minimax-m2.7"
555
+ };
556
+ }
557
+ });
558
+
559
+ // src/lib/intercom-queue.ts
560
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
561
+ import path6 from "path";
511
562
  import os4 from "os";
512
563
  function ensureDir() {
513
- const dir = path5.dirname(QUEUE_PATH);
514
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
564
+ const dir = path6.dirname(QUEUE_PATH);
565
+ if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
515
566
  }
516
567
  function readQueue() {
517
568
  try {
518
- if (!existsSync4(QUEUE_PATH)) return [];
519
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
569
+ if (!existsSync5(QUEUE_PATH)) return [];
570
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
520
571
  } catch {
521
572
  return [];
522
573
  }
@@ -524,7 +575,7 @@ function readQueue() {
524
575
  function writeQueue(queue) {
525
576
  ensureDir();
526
577
  const tmp = `${QUEUE_PATH}.tmp`;
527
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
578
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
528
579
  renameSync3(tmp, QUEUE_PATH);
529
580
  }
530
581
  function queueIntercom(targetSession, reason) {
@@ -548,9 +599,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
548
599
  var init_intercom_queue = __esm({
549
600
  "src/lib/intercom-queue.ts"() {
550
601
  "use strict";
551
- QUEUE_PATH = path5.join(os4.homedir(), ".exe-os", "intercom-queue.json");
602
+ QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
552
603
  TTL_MS = 60 * 60 * 1e3;
553
- INTERCOM_LOG = path5.join(os4.homedir(), ".exe-os", "intercom.log");
604
+ INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
554
605
  }
555
606
  });
556
607
 
@@ -613,8 +664,8 @@ var init_db_retry = __esm({
613
664
  import net from "net";
614
665
  import { spawn } from "child_process";
615
666
  import { randomUUID } from "crypto";
616
- import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
617
- import path6 from "path";
667
+ import { existsSync as existsSync6, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
668
+ import path7 from "path";
618
669
  import { fileURLToPath } from "url";
619
670
  function handleData(chunk) {
620
671
  _buffer += chunk.toString();
@@ -642,9 +693,9 @@ function handleData(chunk) {
642
693
  }
643
694
  }
644
695
  function cleanupStaleFiles() {
645
- if (existsSync5(PID_PATH)) {
696
+ if (existsSync6(PID_PATH)) {
646
697
  try {
647
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
698
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
648
699
  if (pid > 0) {
649
700
  try {
650
701
  process.kill(pid, 0);
@@ -665,11 +716,11 @@ function cleanupStaleFiles() {
665
716
  }
666
717
  }
667
718
  function findPackageRoot() {
668
- let dir = path6.dirname(fileURLToPath(import.meta.url));
669
- const { root } = path6.parse(dir);
719
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
720
+ const { root } = path7.parse(dir);
670
721
  while (dir !== root) {
671
- if (existsSync5(path6.join(dir, "package.json"))) return dir;
672
- dir = path6.dirname(dir);
722
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
723
+ dir = path7.dirname(dir);
673
724
  }
674
725
  return null;
675
726
  }
@@ -679,8 +730,8 @@ function spawnDaemon() {
679
730
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
680
731
  return;
681
732
  }
682
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
683
- if (!existsSync5(daemonPath)) {
733
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
734
+ if (!existsSync6(daemonPath)) {
684
735
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
685
736
  `);
686
737
  return;
@@ -688,7 +739,7 @@ function spawnDaemon() {
688
739
  const resolvedPath = daemonPath;
689
740
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
690
741
  `);
691
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
742
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
692
743
  let stderrFd = "ignore";
693
744
  try {
694
745
  stderrFd = openSync(logPath, "a");
@@ -833,9 +884,9 @@ var init_exe_daemon_client = __esm({
833
884
  "src/lib/exe-daemon-client.ts"() {
834
885
  "use strict";
835
886
  init_config();
836
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
837
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
838
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
887
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
888
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
889
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
839
890
  SPAWN_LOCK_STALE_MS = 3e4;
840
891
  CONNECT_TIMEOUT_MS = 15e3;
841
892
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2060,18 +2111,18 @@ var init_database = __esm({
2060
2111
  });
2061
2112
 
2062
2113
  // src/lib/license.ts
2063
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2114
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
2064
2115
  import { randomUUID as randomUUID2 } from "crypto";
2065
- import path7 from "path";
2116
+ import path8 from "path";
2066
2117
  import { jwtVerify, importSPKI } from "jose";
2067
2118
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2068
2119
  var init_license = __esm({
2069
2120
  "src/lib/license.ts"() {
2070
2121
  "use strict";
2071
2122
  init_config();
2072
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2073
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2074
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2123
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2124
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2125
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2075
2126
  PLAN_LIMITS = {
2076
2127
  free: { devices: 1, employees: 1, memories: 5e3 },
2077
2128
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2083,12 +2134,12 @@ var init_license = __esm({
2083
2134
  });
2084
2135
 
2085
2136
  // src/lib/plan-limits.ts
2086
- import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
2087
- import path8 from "path";
2137
+ import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
2138
+ import path9 from "path";
2088
2139
  function getLicenseSync() {
2089
2140
  try {
2090
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2091
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2141
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2142
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2092
2143
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2093
2144
  const parts = raw.token.split(".");
2094
2145
  if (parts.length !== 3) return freeLicense();
@@ -2126,8 +2177,8 @@ function assertEmployeeLimitSync(rosterPath) {
2126
2177
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2127
2178
  let count = 0;
2128
2179
  try {
2129
- if (existsSync7(filePath)) {
2130
- const raw = readFileSync8(filePath, "utf8");
2180
+ if (existsSync8(filePath)) {
2181
+ const raw = readFileSync9(filePath, "utf8");
2131
2182
  const employees = JSON.parse(raw);
2132
2183
  count = Array.isArray(employees) ? employees.length : 0;
2133
2184
  }
@@ -2156,7 +2207,7 @@ var init_plan_limits = __esm({
2156
2207
  this.name = "PlanLimitError";
2157
2208
  }
2158
2209
  };
2159
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2210
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2160
2211
  }
2161
2212
  });
2162
2213
 
@@ -2173,13 +2224,13 @@ __export(notifications_exports, {
2173
2224
  writeNotification: () => writeNotification
2174
2225
  });
2175
2226
  import crypto from "crypto";
2176
- import path9 from "path";
2227
+ import path10 from "path";
2177
2228
  import os5 from "os";
2178
2229
  import {
2179
- readFileSync as readFileSync9,
2230
+ readFileSync as readFileSync10,
2180
2231
  readdirSync as readdirSync2,
2181
2232
  unlinkSync as unlinkSync4,
2182
- existsSync as existsSync8,
2233
+ existsSync as existsSync9,
2183
2234
  rmdirSync
2184
2235
  } from "fs";
2185
2236
  async function writeNotification(notification) {
@@ -2315,9 +2366,9 @@ function formatNotifications(notifications) {
2315
2366
  return lines.join("\n");
2316
2367
  }
2317
2368
  async function migrateJsonNotifications() {
2318
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path9.join(os5.homedir(), ".exe-os");
2319
- const notifDir = path9.join(base, "notifications");
2320
- if (!existsSync8(notifDir)) return 0;
2369
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path10.join(os5.homedir(), ".exe-os");
2370
+ const notifDir = path10.join(base, "notifications");
2371
+ if (!existsSync9(notifDir)) return 0;
2321
2372
  let migrated = 0;
2322
2373
  try {
2323
2374
  const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
@@ -2325,8 +2376,8 @@ async function migrateJsonNotifications() {
2325
2376
  const client = getClient();
2326
2377
  for (const file of files) {
2327
2378
  try {
2328
- const filePath = path9.join(notifDir, file);
2329
- const data = JSON.parse(readFileSync9(filePath, "utf8"));
2379
+ const filePath = path10.join(notifDir, file);
2380
+ const data = JSON.parse(readFileSync10(filePath, "utf8"));
2330
2381
  await client.execute({
2331
2382
  sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2332
2383
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -2502,11 +2553,11 @@ __export(tasks_crud_exports, {
2502
2553
  writeCheckpoint: () => writeCheckpoint
2503
2554
  });
2504
2555
  import crypto3 from "crypto";
2505
- import path10 from "path";
2556
+ import path11 from "path";
2506
2557
  import os6 from "os";
2507
2558
  import { execSync as execSync5 } from "child_process";
2508
2559
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2509
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
2560
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
2510
2561
  async function writeCheckpoint(input2) {
2511
2562
  const client = getClient();
2512
2563
  const row = await resolveTask(client, input2.taskId);
@@ -2681,8 +2732,8 @@ ${laneWarning}` : laneWarning;
2681
2732
  }
2682
2733
  if (input2.baseDir) {
2683
2734
  try {
2684
- await mkdir3(path10.join(input2.baseDir, "exe", "output"), { recursive: true });
2685
- await mkdir3(path10.join(input2.baseDir, "exe", "research"), { recursive: true });
2735
+ await mkdir3(path11.join(input2.baseDir, "exe", "output"), { recursive: true });
2736
+ await mkdir3(path11.join(input2.baseDir, "exe", "research"), { recursive: true });
2686
2737
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
2687
2738
  await ensureGitignoreExe(input2.baseDir);
2688
2739
  } catch {
@@ -2718,10 +2769,10 @@ ${laneWarning}` : laneWarning;
2718
2769
  });
2719
2770
  if (input2.baseDir) {
2720
2771
  try {
2721
- const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
2722
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2723
- const mdDir = path10.dirname(mdPath);
2724
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2772
+ const EXE_OS_DIR = path11.join(os6.homedir(), ".exe-os");
2773
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
2774
+ const mdDir = path11.dirname(mdPath);
2775
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2725
2776
  const reviewer = input2.reviewer ?? input2.assignedBy;
2726
2777
  const mdContent = `# ${input2.title}
2727
2778
 
@@ -2746,7 +2797,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2746
2797
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2747
2798
  `;
2748
2799
  await writeFile3(mdPath, mdContent, "utf-8");
2749
- } catch {
2800
+ } catch (err) {
2801
+ process.stderr.write(
2802
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2803
+ `
2804
+ );
2750
2805
  }
2751
2806
  }
2752
2807
  return {
@@ -3006,9 +3061,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3006
3061
  return { taskFile, assignedTo, assignedBy, taskSlug };
3007
3062
  }
3008
3063
  async function ensureArchitectureDoc(baseDir, projectName) {
3009
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3064
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3010
3065
  try {
3011
- if (existsSync9(archPath)) return;
3066
+ if (existsSync10(archPath)) return;
3012
3067
  const template = [
3013
3068
  `# ${projectName} \u2014 System Architecture`,
3014
3069
  "",
@@ -3041,10 +3096,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3041
3096
  }
3042
3097
  }
3043
3098
  async function ensureGitignoreExe(baseDir) {
3044
- const gitignorePath = path10.join(baseDir, ".gitignore");
3099
+ const gitignorePath = path11.join(baseDir, ".gitignore");
3045
3100
  try {
3046
- if (existsSync9(gitignorePath)) {
3047
- const content = readFileSync10(gitignorePath, "utf-8");
3101
+ if (existsSync10(gitignorePath)) {
3102
+ const content = readFileSync11(gitignorePath, "utf-8");
3048
3103
  if (/^\/?exe\/?$/m.test(content)) return;
3049
3104
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3050
3105
  } else {
@@ -3075,8 +3130,8 @@ var init_tasks_crud = __esm({
3075
3130
  });
3076
3131
 
3077
3132
  // src/lib/tasks-review.ts
3078
- import path11 from "path";
3079
- import { existsSync as existsSync10, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
3133
+ import path12 from "path";
3134
+ import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
3080
3135
  async function countPendingReviews(sessionScope) {
3081
3136
  const client = getClient();
3082
3137
  if (sessionScope) {
@@ -3257,11 +3312,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3257
3312
  );
3258
3313
  }
3259
3314
  try {
3260
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3261
- if (existsSync10(cacheDir)) {
3315
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3316
+ if (existsSync11(cacheDir)) {
3262
3317
  for (const f of readdirSync3(cacheDir)) {
3263
3318
  if (f.startsWith("review-notified-")) {
3264
- unlinkSync5(path11.join(cacheDir, f));
3319
+ unlinkSync5(path12.join(cacheDir, f));
3265
3320
  }
3266
3321
  }
3267
3322
  }
@@ -3282,7 +3337,7 @@ var init_tasks_review = __esm({
3282
3337
  });
3283
3338
 
3284
3339
  // src/lib/tasks-chain.ts
3285
- import path12 from "path";
3340
+ import path13 from "path";
3286
3341
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3287
3342
  async function cascadeUnblock(taskId, baseDir, now) {
3288
3343
  const client = getClient();
@@ -3299,7 +3354,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3299
3354
  });
3300
3355
  for (const ur of unblockedRows.rows) {
3301
3356
  try {
3302
- const ubFile = path12.join(baseDir, String(ur.task_file));
3357
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3303
3358
  let ubContent = await readFile3(ubFile, "utf-8");
3304
3359
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3305
3360
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3368,7 +3423,7 @@ var init_tasks_chain = __esm({
3368
3423
 
3369
3424
  // src/lib/project-name.ts
3370
3425
  import { execSync as execSync6 } from "child_process";
3371
- import path13 from "path";
3426
+ import path14 from "path";
3372
3427
  function getProjectName(cwd) {
3373
3428
  const dir = cwd ?? process.cwd();
3374
3429
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3381,7 +3436,7 @@ function getProjectName(cwd) {
3381
3436
  timeout: 2e3,
3382
3437
  stdio: ["pipe", "pipe", "pipe"]
3383
3438
  }).trim();
3384
- repoRoot = path13.dirname(gitCommonDir);
3439
+ repoRoot = path14.dirname(gitCommonDir);
3385
3440
  } catch {
3386
3441
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3387
3442
  cwd: dir,
@@ -3390,11 +3445,11 @@ function getProjectName(cwd) {
3390
3445
  stdio: ["pipe", "pipe", "pipe"]
3391
3446
  }).trim();
3392
3447
  }
3393
- _cached2 = path13.basename(repoRoot);
3448
+ _cached2 = path14.basename(repoRoot);
3394
3449
  _cachedCwd = dir;
3395
3450
  return _cached2;
3396
3451
  } catch {
3397
- _cached2 = path13.basename(dir);
3452
+ _cached2 = path14.basename(dir);
3398
3453
  _cachedCwd = dir;
3399
3454
  return _cached2;
3400
3455
  }
@@ -3867,8 +3922,8 @@ __export(tasks_exports, {
3867
3922
  updateTaskStatus: () => updateTaskStatus,
3868
3923
  writeCheckpoint: () => writeCheckpoint
3869
3924
  });
3870
- import path14 from "path";
3871
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync6 } from "fs";
3925
+ import path15 from "path";
3926
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
3872
3927
  async function createTask(input2) {
3873
3928
  const result = await createTaskCore(input2);
3874
3929
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3887,11 +3942,11 @@ async function updateTask(input2) {
3887
3942
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
3888
3943
  try {
3889
3944
  const agent = String(row.assigned_to);
3890
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3891
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3945
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3946
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
3892
3947
  if (input2.status === "in_progress") {
3893
- mkdirSync5(cacheDir, { recursive: true });
3894
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3948
+ mkdirSync6(cacheDir, { recursive: true });
3949
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3895
3950
  } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
3896
3951
  try {
3897
3952
  unlinkSync6(cachePath);
@@ -4358,13 +4413,13 @@ __export(tmux_routing_exports, {
4358
4413
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4359
4414
  });
4360
4415
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4361
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
4362
- import path15 from "path";
4416
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync12, appendFileSync } from "fs";
4417
+ import path16 from "path";
4363
4418
  import os7 from "os";
4364
4419
  import { fileURLToPath as fileURLToPath2 } from "url";
4365
4420
  import { unlinkSync as unlinkSync7 } from "fs";
4366
4421
  function spawnLockPath(sessionName) {
4367
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4422
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4368
4423
  }
4369
4424
  function isProcessAlive(pid) {
4370
4425
  try {
@@ -4375,13 +4430,13 @@ function isProcessAlive(pid) {
4375
4430
  }
4376
4431
  }
4377
4432
  function acquireSpawnLock2(sessionName) {
4378
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4379
- mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4433
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4434
+ mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
4380
4435
  }
4381
4436
  const lockFile = spawnLockPath(sessionName);
4382
- if (existsSync11(lockFile)) {
4437
+ if (existsSync12(lockFile)) {
4383
4438
  try {
4384
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4439
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4385
4440
  const age = Date.now() - lock.timestamp;
4386
4441
  if (isProcessAlive(lock.pid) && age < 6e4) {
4387
4442
  return false;
@@ -4389,7 +4444,7 @@ function acquireSpawnLock2(sessionName) {
4389
4444
  } catch {
4390
4445
  }
4391
4446
  }
4392
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4447
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4393
4448
  return true;
4394
4449
  }
4395
4450
  function releaseSpawnLock2(sessionName) {
@@ -4401,13 +4456,13 @@ function releaseSpawnLock2(sessionName) {
4401
4456
  function resolveBehaviorsExporterScript() {
4402
4457
  try {
4403
4458
  const thisFile = fileURLToPath2(import.meta.url);
4404
- const scriptPath = path15.join(
4405
- path15.dirname(thisFile),
4459
+ const scriptPath = path16.join(
4460
+ path16.dirname(thisFile),
4406
4461
  "..",
4407
4462
  "bin",
4408
4463
  "exe-export-behaviors.js"
4409
4464
  );
4410
- return existsSync11(scriptPath) ? scriptPath : null;
4465
+ return existsSync12(scriptPath) ? scriptPath : null;
4411
4466
  } catch {
4412
4467
  return null;
4413
4468
  }
@@ -4473,12 +4528,12 @@ function extractRootExe(name) {
4473
4528
  return parts.length > 0 ? parts[parts.length - 1] : null;
4474
4529
  }
4475
4530
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4476
- if (!existsSync11(SESSION_CACHE)) {
4477
- mkdirSync6(SESSION_CACHE, { recursive: true });
4531
+ if (!existsSync12(SESSION_CACHE)) {
4532
+ mkdirSync7(SESSION_CACHE, { recursive: true });
4478
4533
  }
4479
4534
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4480
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4481
- writeFileSync7(filePath, JSON.stringify({
4535
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4536
+ writeFileSync8(filePath, JSON.stringify({
4482
4537
  parentExe: rootExe,
4483
4538
  dispatchedBy: dispatchedBy || rootExe,
4484
4539
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4486,7 +4541,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4486
4541
  }
4487
4542
  function getParentExe(sessionKey) {
4488
4543
  try {
4489
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4544
+ const data = JSON.parse(readFileSync12(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4490
4545
  return data.parentExe || null;
4491
4546
  } catch {
4492
4547
  return null;
@@ -4494,8 +4549,8 @@ function getParentExe(sessionKey) {
4494
4549
  }
4495
4550
  function getDispatchedBy(sessionKey) {
4496
4551
  try {
4497
- const data = JSON.parse(readFileSync11(
4498
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4552
+ const data = JSON.parse(readFileSync12(
4553
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4499
4554
  "utf8"
4500
4555
  ));
4501
4556
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4556,32 +4611,50 @@ async function verifyPaneAtCapacity(sessionName) {
4556
4611
  }
4557
4612
  function readDebounceState() {
4558
4613
  try {
4559
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4560
- return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4614
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4615
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4616
+ const state = {};
4617
+ for (const [key, val] of Object.entries(raw)) {
4618
+ if (typeof val === "number") {
4619
+ state[key] = { lastSent: val, pending: 0 };
4620
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4621
+ state[key] = val;
4622
+ }
4623
+ }
4624
+ return state;
4561
4625
  } catch {
4562
4626
  return {};
4563
4627
  }
4564
4628
  }
4565
4629
  function writeDebounceState(state) {
4566
4630
  try {
4567
- if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4568
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4631
+ if (!existsSync12(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
4632
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
4569
4633
  } catch {
4570
4634
  }
4571
4635
  }
4572
4636
  function isDebounced(targetSession) {
4573
4637
  const state = readDebounceState();
4574
- const lastSent = state[targetSession] ?? 0;
4575
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4638
+ const entry = state[targetSession];
4639
+ const lastSent = entry?.lastSent ?? 0;
4640
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4641
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4642
+ state[targetSession].pending++;
4643
+ writeDebounceState(state);
4644
+ return true;
4645
+ }
4646
+ return false;
4576
4647
  }
4577
4648
  function recordDebounce(targetSession) {
4578
4649
  const state = readDebounceState();
4579
- state[targetSession] = Date.now();
4650
+ const batched = state[targetSession]?.pending ?? 0;
4651
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4580
4652
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4581
4653
  for (const key of Object.keys(state)) {
4582
- if ((state[key] ?? 0) < cutoff) delete state[key];
4654
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4583
4655
  }
4584
4656
  writeDebounceState(state);
4657
+ return batched;
4585
4658
  }
4586
4659
  function logIntercom(msg) {
4587
4660
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4626,7 +4699,7 @@ function sendIntercom(targetSession) {
4626
4699
  return "skipped_exe";
4627
4700
  }
4628
4701
  if (isDebounced(targetSession)) {
4629
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4702
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4630
4703
  return "debounced";
4631
4704
  }
4632
4705
  try {
@@ -4638,14 +4711,14 @@ function sendIntercom(targetSession) {
4638
4711
  const sessionState = getSessionState(targetSession);
4639
4712
  if (sessionState === "no_claude") {
4640
4713
  queueIntercom(targetSession, "claude not running in session");
4641
- recordDebounce(targetSession);
4642
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4714
+ const batched2 = recordDebounce(targetSession);
4715
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4643
4716
  return "queued";
4644
4717
  }
4645
4718
  if (sessionState === "thinking" || sessionState === "tool") {
4646
4719
  queueIntercom(targetSession, "session busy at send time");
4647
- recordDebounce(targetSession);
4648
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4720
+ const batched2 = recordDebounce(targetSession);
4721
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4649
4722
  return "queued";
4650
4723
  }
4651
4724
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4653,8 +4726,8 @@ function sendIntercom(targetSession) {
4653
4726
  transport.sendKeys(targetSession, "q");
4654
4727
  }
4655
4728
  transport.sendKeys(targetSession, "/exe-intercom");
4656
- recordDebounce(targetSession);
4657
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4729
+ const batched = recordDebounce(targetSession);
4730
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4658
4731
  return "delivered";
4659
4732
  } catch {
4660
4733
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4756,26 +4829,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4756
4829
  const transport = getTransport();
4757
4830
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4758
4831
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4759
- const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
4760
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4761
- if (!existsSync11(logDir)) {
4762
- mkdirSync6(logDir, { recursive: true });
4832
+ const logDir = path16.join(os7.homedir(), ".exe-os", "session-logs");
4833
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4834
+ if (!existsSync12(logDir)) {
4835
+ mkdirSync7(logDir, { recursive: true });
4763
4836
  }
4764
4837
  transport.kill(sessionName);
4765
4838
  let cleanupSuffix = "";
4766
4839
  try {
4767
4840
  const thisFile = fileURLToPath2(import.meta.url);
4768
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4769
- if (existsSync11(cleanupScript)) {
4841
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4842
+ if (existsSync12(cleanupScript)) {
4770
4843
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4771
4844
  }
4772
4845
  } catch {
4773
4846
  }
4774
4847
  try {
4775
- const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
4848
+ const claudeJsonPath = path16.join(os7.homedir(), ".claude.json");
4776
4849
  let claudeJson = {};
4777
4850
  try {
4778
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4851
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4779
4852
  } catch {
4780
4853
  }
4781
4854
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4783,17 +4856,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4783
4856
  const trustDir = opts?.cwd ?? projectDir;
4784
4857
  if (!projects[trustDir]) projects[trustDir] = {};
4785
4858
  projects[trustDir].hasTrustDialogAccepted = true;
4786
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4859
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4787
4860
  } catch {
4788
4861
  }
4789
4862
  try {
4790
- const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
4863
+ const settingsDir = path16.join(os7.homedir(), ".claude", "projects");
4791
4864
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4792
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
4793
- const settingsPath = path15.join(projSettingsDir, "settings.json");
4865
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
4866
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
4794
4867
  let settings = {};
4795
4868
  try {
4796
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4869
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4797
4870
  } catch {
4798
4871
  }
4799
4872
  const perms = settings.permissions ?? {};
@@ -4821,20 +4894,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4821
4894
  if (changed) {
4822
4895
  perms.allow = allow;
4823
4896
  settings.permissions = perms;
4824
- mkdirSync6(projSettingsDir, { recursive: true });
4825
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4897
+ mkdirSync7(projSettingsDir, { recursive: true });
4898
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4826
4899
  }
4827
4900
  } catch {
4828
4901
  }
4829
4902
  const spawnCwd = opts?.cwd ?? projectDir;
4830
4903
  const useExeAgent = !!(opts?.model && opts?.provider);
4831
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4904
+ const agentRtConfig = getAgentRuntime(employeeName);
4905
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4906
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4907
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4832
4908
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4833
4909
  let identityFlag = "";
4834
4910
  let behaviorsFlag = "";
4835
4911
  let legacyFallbackWarned = false;
4836
4912
  if (!useExeAgent && !useBinSymlink) {
4837
- const identityPath = path15.join(
4913
+ const identityPath = path16.join(
4838
4914
  os7.homedir(),
4839
4915
  ".exe-os",
4840
4916
  "identity",
@@ -4844,13 +4920,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4844
4920
  const hasAgentFlag = claudeSupportsAgentFlag();
4845
4921
  if (hasAgentFlag) {
4846
4922
  identityFlag = ` --agent ${employeeName}`;
4847
- } else if (existsSync11(identityPath)) {
4923
+ } else if (existsSync12(identityPath)) {
4848
4924
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4849
4925
  legacyFallbackWarned = true;
4850
4926
  }
4851
4927
  const behaviorsFile = exportBehaviorsSync(
4852
4928
  employeeName,
4853
- path15.basename(spawnCwd),
4929
+ path16.basename(spawnCwd),
4854
4930
  sessionName
4855
4931
  );
4856
4932
  if (behaviorsFile) {
@@ -4865,16 +4941,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4865
4941
  }
4866
4942
  let sessionContextFlag = "";
4867
4943
  try {
4868
- const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
4869
- mkdirSync6(ctxDir, { recursive: true });
4870
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4944
+ const ctxDir = path16.join(os7.homedir(), ".exe-os", "session-cache");
4945
+ mkdirSync7(ctxDir, { recursive: true });
4946
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
4871
4947
  const ctxContent = [
4872
4948
  `## Session Context`,
4873
4949
  `You are running in tmux session: ${sessionName}.`,
4874
4950
  `Your parent coordinator session is ${exeSession}.`,
4875
4951
  `Your employees (if any) use the -${exeSession} suffix.`
4876
4952
  ].join("\n");
4877
- writeFileSync7(ctxFile, ctxContent);
4953
+ writeFileSync8(ctxFile, ctxContent);
4878
4954
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4879
4955
  } catch {
4880
4956
  }
@@ -4888,9 +4964,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4888
4964
  }
4889
4965
  }
4890
4966
  }
4967
+ if (useCodex) {
4968
+ const codexCfg = RUNTIME_TABLE.codex;
4969
+ if (codexCfg?.apiKeyEnv) {
4970
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4971
+ if (keyVal) {
4972
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4973
+ }
4974
+ }
4975
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4976
+ }
4977
+ if (useOpencode) {
4978
+ const ocCfg = PROVIDER_TABLE.opencode;
4979
+ if (ocCfg?.apiKeyEnv) {
4980
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4981
+ if (keyVal) {
4982
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4983
+ }
4984
+ }
4985
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4986
+ }
4987
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4988
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4989
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4990
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4991
+ }
4992
+ }
4891
4993
  let spawnCommand;
4892
4994
  if (useExeAgent) {
4893
4995
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4996
+ } else if (useCodex) {
4997
+ process.stderr.write(
4998
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4999
+ `
5000
+ );
5001
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
5002
+ } else if (useOpencode) {
5003
+ const binName = `${employeeName}-opencode`;
5004
+ process.stderr.write(
5005
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
5006
+ `
5007
+ );
5008
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4894
5009
  } else if (useBinSymlink) {
4895
5010
  const binName = `${employeeName}-${ccProvider}`;
4896
5011
  process.stderr.write(
@@ -4912,11 +5027,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4912
5027
  transport.pipeLog(sessionName, logFile);
4913
5028
  try {
4914
5029
  const mySession = getMySession();
4915
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4916
- writeFileSync7(dispatchInfo, JSON.stringify({
5030
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5031
+ writeFileSync8(dispatchInfo, JSON.stringify({
4917
5032
  dispatchedBy: mySession,
4918
5033
  rootExe: exeSession,
4919
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
5034
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
5035
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
5036
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4920
5037
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4921
5038
  }));
4922
5039
  } catch {
@@ -4934,6 +5051,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4934
5051
  booted = true;
4935
5052
  break;
4936
5053
  }
5054
+ } else if (useCodex) {
5055
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
5056
+ booted = true;
5057
+ break;
5058
+ }
4937
5059
  } else {
4938
5060
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4939
5061
  booted = true;
@@ -4945,9 +5067,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4945
5067
  }
4946
5068
  if (!booted) {
4947
5069
  releaseSpawnLock2(sessionName);
4948
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
5070
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
5071
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4949
5072
  }
4950
- if (!useExeAgent) {
5073
+ if (!useExeAgent && !useCodex) {
4951
5074
  try {
4952
5075
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4953
5076
  } catch {
@@ -4974,17 +5097,19 @@ var init_tmux_routing = __esm({
4974
5097
  init_cc_agent_support();
4975
5098
  init_mcp_prefix();
4976
5099
  init_provider_table();
5100
+ init_agent_config();
5101
+ init_runtime_table();
4977
5102
  init_intercom_queue();
4978
5103
  init_plan_limits();
4979
5104
  init_employees();
4980
- SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
4981
- SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
5105
+ SPAWN_LOCK_DIR = path16.join(os7.homedir(), ".exe-os", "spawn-locks");
5106
+ SESSION_CACHE = path16.join(os7.homedir(), ".exe-os", "session-cache");
4982
5107
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4983
5108
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4984
5109
  VERIFY_PANE_LINES = 200;
4985
5110
  INTERCOM_DEBOUNCE_MS = 3e4;
4986
- INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
4987
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5111
+ INTERCOM_LOG2 = path16.join(os7.homedir(), ".exe-os", "intercom.log");
5112
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
4988
5113
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4989
5114
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4990
5115
  }
@@ -5025,14 +5150,14 @@ var init_memory = __esm({
5025
5150
 
5026
5151
  // src/lib/keychain.ts
5027
5152
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5028
- import { existsSync as existsSync12 } from "fs";
5029
- import path16 from "path";
5153
+ import { existsSync as existsSync13 } from "fs";
5154
+ import path17 from "path";
5030
5155
  import os8 from "os";
5031
5156
  function getKeyDir() {
5032
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
5157
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os8.homedir(), ".exe-os");
5033
5158
  }
5034
5159
  function getKeyPath() {
5035
- return path16.join(getKeyDir(), "master.key");
5160
+ return path17.join(getKeyDir(), "master.key");
5036
5161
  }
5037
5162
  async function tryKeytar() {
5038
5163
  try {
@@ -5053,7 +5178,7 @@ async function getMasterKey() {
5053
5178
  }
5054
5179
  }
5055
5180
  const keyPath = getKeyPath();
5056
- if (!existsSync12(keyPath)) {
5181
+ if (!existsSync13(keyPath)) {
5057
5182
  process.stderr.write(
5058
5183
  `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5059
5184
  `
@@ -5093,13 +5218,13 @@ __export(shard_manager_exports, {
5093
5218
  listShards: () => listShards,
5094
5219
  shardExists: () => shardExists
5095
5220
  });
5096
- import path17 from "path";
5097
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5221
+ import path18 from "path";
5222
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync4 } from "fs";
5098
5223
  import { createClient as createClient2 } from "@libsql/client";
5099
5224
  function initShardManager(encryptionKey) {
5100
5225
  _encryptionKey = encryptionKey;
5101
- if (!existsSync13(SHARDS_DIR)) {
5102
- mkdirSync7(SHARDS_DIR, { recursive: true });
5226
+ if (!existsSync14(SHARDS_DIR)) {
5227
+ mkdirSync8(SHARDS_DIR, { recursive: true });
5103
5228
  }
5104
5229
  _shardingEnabled = true;
5105
5230
  }
@@ -5119,7 +5244,7 @@ function getShardClient(projectName) {
5119
5244
  }
5120
5245
  const cached = _shards.get(safeName);
5121
5246
  if (cached) return cached;
5122
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5247
+ const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
5123
5248
  const client = createClient2({
5124
5249
  url: `file:${dbPath}`,
5125
5250
  encryptionKey: _encryptionKey
@@ -5129,10 +5254,10 @@ function getShardClient(projectName) {
5129
5254
  }
5130
5255
  function shardExists(projectName) {
5131
5256
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5132
- return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
5257
+ return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
5133
5258
  }
5134
5259
  function listShards() {
5135
- if (!existsSync13(SHARDS_DIR)) return [];
5260
+ if (!existsSync14(SHARDS_DIR)) return [];
5136
5261
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5137
5262
  }
5138
5263
  async function ensureShardSchema(client) {
@@ -5318,7 +5443,7 @@ var init_shard_manager = __esm({
5318
5443
  "src/lib/shard-manager.ts"() {
5319
5444
  "use strict";
5320
5445
  init_config();
5321
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5446
+ SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
5322
5447
  _shards = /* @__PURE__ */ new Map();
5323
5448
  _encryptionKey = null;
5324
5449
  _shardingEnabled = false;