@kbediako/codex-orchestrator 0.1.12 → 0.1.14-alpha.1

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 (38) hide show
  1. package/LICENSE +19 -5
  2. package/README.md +47 -2
  3. package/dist/bin/codex-orchestrator.js +93 -0
  4. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +27 -3
  5. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +17 -1
  6. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +36 -1
  7. package/dist/orchestrator/src/cli/adapters/CommandTester.js +28 -0
  8. package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +45 -0
  9. package/dist/orchestrator/src/cli/codexCliSetup.js +294 -0
  10. package/dist/orchestrator/src/cli/init.js +3 -0
  11. package/dist/orchestrator/src/cli/mcp.js +4 -2
  12. package/dist/orchestrator/src/cli/orchestrator.js +298 -28
  13. package/dist/orchestrator/src/cli/rlm/context.js +31 -3
  14. package/dist/orchestrator/src/cli/rlm/symbolic.js +152 -15
  15. package/dist/orchestrator/src/cli/rlmRunner.js +59 -5
  16. package/dist/orchestrator/src/cli/run/manifest.js +3 -0
  17. package/dist/orchestrator/src/cli/services/commandRunner.js +87 -0
  18. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +24 -0
  19. package/dist/orchestrator/src/cli/skills.js +1 -1
  20. package/dist/orchestrator/src/cli/utils/codexCli.js +94 -0
  21. package/dist/orchestrator/src/cli/utils/codexPaths.js +13 -0
  22. package/dist/orchestrator/src/cli/utils/devtools.js +9 -12
  23. package/dist/orchestrator/src/cloud/CodexCloudTaskExecutor.js +255 -0
  24. package/dist/orchestrator/src/learning/crystalizer.js +2 -1
  25. package/dist/orchestrator/src/manager.js +1 -0
  26. package/dist/orchestrator/src/sync/CloudSyncWorker.js +37 -7
  27. package/dist/scripts/design/pipeline/context.js +3 -2
  28. package/dist/scripts/lib/run-manifests.js +14 -0
  29. package/docs/README.md +22 -2
  30. package/package.json +6 -2
  31. package/schemas/manifest.json +83 -0
  32. package/skills/collab-deliberation/SKILL.md +21 -0
  33. package/skills/collab-evals/SKILL.md +32 -0
  34. package/skills/delegate-early/SKILL.md +47 -0
  35. package/skills/delegation-usage/DELEGATION_GUIDE.md +5 -4
  36. package/skills/delegation-usage/SKILL.md +11 -5
  37. package/skills/docs-first/SKILL.md +2 -1
  38. package/templates/README.md +4 -0
@@ -274,8 +274,8 @@ async function runCodexAgent(input, env, repoRoot, nonInteractive, subagentsEnab
274
274
  const output = await runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagentsEnabled, true);
275
275
  return { output };
276
276
  }
277
- async function runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput) {
278
- const { command, args } = resolveCodexCommand(['exec', prompt], env);
277
+ async function runCodexExec(args, env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput) {
278
+ const { command, args: resolvedArgs } = resolveCodexCommand(args, env);
279
279
  const childEnv = { ...process.env, ...env };
280
280
  if (nonInteractive) {
281
281
  childEnv.CODEX_NON_INTERACTIVE = childEnv.CODEX_NON_INTERACTIVE ?? '1';
@@ -283,7 +283,7 @@ async function runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagen
283
283
  childEnv.CODEX_INTERACTIVE = childEnv.CODEX_INTERACTIVE ?? '0';
284
284
  }
285
285
  childEnv.CODEX_SUBAGENTS = subagentsEnabled ? '1' : '0';
286
- const child = spawn(command, args, { cwd: repoRoot, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] });
286
+ const child = spawn(command, resolvedArgs, { cwd: repoRoot, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] });
287
287
  let stdout = '';
288
288
  let stderr = '';
289
289
  child.stdout?.on('data', (chunk) => {
@@ -311,8 +311,52 @@ async function runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagen
311
311
  }
312
312
  });
313
313
  });
314
+ return { stdout, stderr };
315
+ }
316
+ async function runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput) {
317
+ const { stdout, stderr } = await runCodexExec(['exec', prompt], env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput);
318
+ return [stdout.trim(), stderr.trim()].filter(Boolean).join('\n');
319
+ }
320
+ async function runCodexJsonlCompletion(prompt, env, repoRoot, nonInteractive, mirrorOutput, extraArgs = []) {
321
+ const { stdout, stderr } = await runCodexExec(['exec', '--json', ...extraArgs, prompt], env, repoRoot, nonInteractive, false, mirrorOutput);
322
+ const message = extractAgentMessageFromJsonl(stdout);
323
+ if (message) {
324
+ return message;
325
+ }
314
326
  return [stdout.trim(), stderr.trim()].filter(Boolean).join('\n');
315
327
  }
328
+ function extractAgentMessageFromJsonl(raw) {
329
+ let lastMessage = null;
330
+ const lines = raw.split(/\r?\n/);
331
+ for (const line of lines) {
332
+ const trimmed = line.trim();
333
+ if (!trimmed.startsWith('{')) {
334
+ continue;
335
+ }
336
+ try {
337
+ const parsed = JSON.parse(trimmed);
338
+ if ((parsed.type === 'item.completed' || parsed.type === 'item.updated') &&
339
+ parsed.item?.type === 'agent_message' &&
340
+ typeof parsed.item.text === 'string') {
341
+ lastMessage = parsed.item.text;
342
+ }
343
+ }
344
+ catch {
345
+ // ignore parse errors for non-json lines
346
+ }
347
+ }
348
+ return lastMessage;
349
+ }
350
+ function buildCollabSubcallPrompt(prompt) {
351
+ return [
352
+ 'Use collab tools to spawn a sub-agent with the prompt below.',
353
+ 'Wait for the sub-agent, then close it.',
354
+ 'Return only the sub-agent response text and nothing else.',
355
+ '',
356
+ 'Sub-agent prompt:',
357
+ prompt
358
+ ].join('\n');
359
+ }
316
360
  function normalizeExitCode(code) {
317
361
  if (typeof code === 'number' && Number.isInteger(code)) {
318
362
  return code;
@@ -571,6 +615,7 @@ async function main() {
571
615
  console.log(`Validator: ${validatorCommand}`);
572
616
  }
573
617
  const subagentsEnabled = envFlagEnabled(env.CODEX_SUBAGENTS) || envFlagEnabled(env.RLM_SUBAGENTS);
618
+ const symbolicCollabEnabled = envFlagEnabled(env.RLM_SYMBOLIC_COLLAB);
574
619
  const nonInteractive = shouldForceNonInteractive(env);
575
620
  if (mode === 'symbolic') {
576
621
  const budgets = {
@@ -622,11 +667,20 @@ async function main() {
622
667
  budgets,
623
668
  runPlanner: (prompt, _attempt) => {
624
669
  void _attempt;
625
- return runCodexCompletion(prompt, env, repoRoot, nonInteractive, false, false);
670
+ return runCodexJsonlCompletion(prompt, env, repoRoot, nonInteractive, false);
626
671
  },
627
672
  runSubcall: (prompt, _meta) => {
628
673
  void _meta;
629
- return runCodexCompletion(prompt, env, repoRoot, nonInteractive, false, false);
674
+ if (!symbolicCollabEnabled) {
675
+ return runCodexCompletion(prompt, env, repoRoot, nonInteractive, false, false);
676
+ }
677
+ const collabPrompt = buildCollabSubcallPrompt(prompt);
678
+ return runCodexJsonlCompletion(collabPrompt, env, repoRoot, nonInteractive, true, [
679
+ '--enable',
680
+ 'collab',
681
+ '--sandbox',
682
+ 'read-only'
683
+ ]);
630
684
  },
631
685
  logger: (line) => logger.info(line)
632
686
  });
@@ -47,6 +47,7 @@ export async function bootstrapManifest(runId, options) {
47
47
  resume_events: [],
48
48
  approvals: [],
49
49
  commands,
50
+ collab_tool_calls: [],
50
51
  child_runs: [],
51
52
  run_summary_path: null,
52
53
  plan_target_id: options.planTargetId ?? null,
@@ -54,6 +55,7 @@ export async function bootstrapManifest(runId, options) {
54
55
  instructions_sources: [],
55
56
  prompt_packs: [],
56
57
  guardrails_required: pipeline.guardrailsRequired !== false,
58
+ cloud_execution: null,
57
59
  learning: {
58
60
  validation: {
59
61
  mode: 'per-task',
@@ -169,6 +171,7 @@ export function resetForResume(manifest) {
169
171
  manifest.status = 'in_progress';
170
172
  manifest.status_detail = 'resuming';
171
173
  manifest.guardrail_status = undefined;
174
+ manifest.cloud_execution = null;
172
175
  }
173
176
  export function recordResumeEvent(manifest, event) {
174
177
  manifest.resume_events.push({ ...event, timestamp: isoTimestamp() });
@@ -13,6 +13,7 @@ import { findPackageRoot } from '../utils/packageInfo.js';
13
13
  const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
14
14
  const EMIT_COMMAND_STREAM_MIRRORS = EnvUtils.getBoolean('CODEX_ORCHESTRATOR_EMIT_COMMAND_STREAMS', false);
15
15
  export const MAX_CAPTURED_CHUNK_EVENTS = EnvUtils.getInt('CODEX_ORCHESTRATOR_EXEC_EVENT_MAX_CHUNKS', 500);
16
+ const MAX_COLLAB_TOOL_CALLS = EnvUtils.getInt('CODEX_ORCHESTRATOR_COLLAB_MAX_EVENTS', 200);
16
17
  const PACKAGE_ROOT = findPackageRoot();
17
18
  export async function runCommandStage(context, hooks = {}) {
18
19
  const { env, paths, manifest, stage, index, events, persister, envOverrides } = context;
@@ -50,6 +51,33 @@ export async function runCommandStage(context, hooks = {}) {
50
51
  let stderrBytes = 0;
51
52
  let stdoutTruncated = false;
52
53
  let stderrTruncated = false;
54
+ let collabBuffer = '';
55
+ let collabCount = manifest.collab_tool_calls?.length ?? 0;
56
+ const recordCollabToolCall = (record) => {
57
+ if (MAX_COLLAB_TOOL_CALLS <= 0) {
58
+ return;
59
+ }
60
+ if (collabCount >= MAX_COLLAB_TOOL_CALLS) {
61
+ return;
62
+ }
63
+ if (!manifest.collab_tool_calls) {
64
+ manifest.collab_tool_calls = [];
65
+ }
66
+ manifest.collab_tool_calls.push(record);
67
+ collabCount += 1;
68
+ void persister?.schedule({ manifest: true });
69
+ };
70
+ const ingestCollabStdout = (data) => {
71
+ collabBuffer += data;
72
+ const lines = collabBuffer.split('\n');
73
+ collabBuffer = lines.pop() ?? '';
74
+ for (const line of lines) {
75
+ const record = parseCollabToolCallLine(line, stage.id, entry.index);
76
+ if (record) {
77
+ recordCollabToolCall(record);
78
+ }
79
+ }
80
+ };
53
81
  const handleEvent = (event) => {
54
82
  if (!activeCorrelationId) {
55
83
  activeCorrelationId = event.correlationId;
@@ -87,6 +115,9 @@ export async function runCommandStage(context, hooks = {}) {
87
115
  message: event.payload.data,
88
116
  source: event.payload.stream
89
117
  });
118
+ if (event.payload.stream === 'stdout') {
119
+ ingestCollabStdout(event.payload.data);
120
+ }
90
121
  break;
91
122
  case 'exec:retry':
92
123
  events?.toolCall({
@@ -192,6 +223,13 @@ export async function runCommandStage(context, hooks = {}) {
192
223
  entry.exit_code = normalizedExitCode;
193
224
  entry.summary = summary;
194
225
  entry.status = result.status === 'succeeded' ? 'succeeded' : stage.allowFailure ? 'skipped' : 'failed';
226
+ if (collabBuffer.trim()) {
227
+ const record = parseCollabToolCallLine(collabBuffer, stage.id, entry.index);
228
+ if (record) {
229
+ recordCollabToolCall(record);
230
+ }
231
+ collabBuffer = '';
232
+ }
195
233
  if (entry.status === 'failed') {
196
234
  const errorDetails = {
197
235
  exit_code: normalizedExitCode,
@@ -418,3 +456,52 @@ function truncate(value, length = 240) {
418
456
  }
419
457
  return `${value.slice(0, length)}…`;
420
458
  }
459
+ function parseCollabToolCallLine(line, stageId, commandIndex) {
460
+ const trimmed = line.trim();
461
+ if (!trimmed || !trimmed.includes('"collab_tool_call"')) {
462
+ return null;
463
+ }
464
+ let parsed;
465
+ try {
466
+ parsed = JSON.parse(trimmed);
467
+ }
468
+ catch {
469
+ return null;
470
+ }
471
+ if (!parsed || typeof parsed !== 'object') {
472
+ return null;
473
+ }
474
+ const record = parsed;
475
+ const eventType = record.type;
476
+ if (eventType !== 'item.started' && eventType !== 'item.completed' && eventType !== 'item.updated') {
477
+ return null;
478
+ }
479
+ const item = record.item;
480
+ if (!item || item.type !== 'collab_tool_call') {
481
+ return null;
482
+ }
483
+ const receiverThreadIds = Array.isArray(item.receiver_thread_ids)
484
+ ? item.receiver_thread_ids.filter((entry) => typeof entry === 'string')
485
+ : [];
486
+ return {
487
+ observed_at: isoTimestamp(),
488
+ stage_id: stageId,
489
+ command_index: commandIndex,
490
+ event_type: eventType,
491
+ item_id: typeof item.id === 'string' ? item.id : 'unknown',
492
+ tool: typeof item.tool === 'string' ? item.tool : 'unknown',
493
+ status: normalizeCollabStatus(item.status),
494
+ sender_thread_id: typeof item.sender_thread_id === 'string' ? item.sender_thread_id : 'unknown',
495
+ receiver_thread_ids: receiverThreadIds,
496
+ prompt: typeof item.prompt === 'string' ? item.prompt : null,
497
+ agents_states: item.agents_states && typeof item.agents_states === 'object'
498
+ ? item.agents_states
499
+ : null
500
+ };
501
+ }
502
+ function normalizeCollabStatus(value) {
503
+ if (value === 'completed' || value === 'failed' || value === 'in_progress') {
504
+ return value;
505
+ }
506
+ return 'in_progress';
507
+ }
@@ -27,6 +27,30 @@ export function applyPrivacyToRunSummary(runSummary, manifest) {
27
27
  allowedFrames: manifest.privacy.totals.allowed_frames
28
28
  };
29
29
  }
30
+ export function applyCloudExecutionToRunSummary(runSummary, manifest) {
31
+ if (!manifest.cloud_execution) {
32
+ return;
33
+ }
34
+ runSummary.cloudExecution = {
35
+ taskId: manifest.cloud_execution.task_id,
36
+ environmentId: manifest.cloud_execution.environment_id,
37
+ status: manifest.cloud_execution.status,
38
+ statusUrl: manifest.cloud_execution.status_url,
39
+ submittedAt: manifest.cloud_execution.submitted_at,
40
+ completedAt: manifest.cloud_execution.completed_at,
41
+ lastPolledAt: manifest.cloud_execution.last_polled_at,
42
+ pollCount: manifest.cloud_execution.poll_count,
43
+ pollIntervalSeconds: manifest.cloud_execution.poll_interval_seconds,
44
+ timeoutSeconds: manifest.cloud_execution.timeout_seconds,
45
+ attempts: manifest.cloud_execution.attempts,
46
+ diffPath: manifest.cloud_execution.diff_path,
47
+ diffUrl: manifest.cloud_execution.diff_url,
48
+ diffStatus: manifest.cloud_execution.diff_status,
49
+ applyStatus: manifest.cloud_execution.apply_status,
50
+ logPath: manifest.cloud_execution.log_path,
51
+ error: manifest.cloud_execution.error
52
+ };
53
+ }
30
54
  export async function persistRunSummary(env, paths, manifest, runSummary, persister) {
31
55
  const summaryPath = join(paths.runDir, 'run-summary.json');
32
56
  await writeJsonAtomic(summaryPath, runSummary);
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';
3
3
  import { dirname, join, relative, resolve } from 'node:path';
4
4
  import process from 'node:process';
5
- import { resolveCodexHome } from './utils/devtools.js';
5
+ import { resolveCodexHome } from './utils/codexPaths.js';
6
6
  import { findPackageRoot } from './utils/packageInfo.js';
7
7
  export async function installSkills(options = {}) {
8
8
  const pkgRoot = findPackageRoot();
@@ -0,0 +1,94 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import process from 'node:process';
5
+ import { resolveCodexOrchestratorHome } from './codexPaths.js';
6
+ const CONFIG_FILENAME = 'codex-cli.json';
7
+ export function resolveCodexCliRoot(env = process.env) {
8
+ return join(resolveCodexOrchestratorHome(env), 'codex-cli');
9
+ }
10
+ export function resolveCodexCliConfigPath(env = process.env) {
11
+ return join(resolveCodexCliRoot(env), CONFIG_FILENAME);
12
+ }
13
+ export function resolveCodexCliBinPath(env = process.env) {
14
+ return join(resolveCodexCliRoot(env), 'bin', codexBinaryName());
15
+ }
16
+ export function resolveCodexCliBin(env = process.env) {
17
+ const override = env.CODEX_CLI_BIN?.trim();
18
+ if (override) {
19
+ return override;
20
+ }
21
+ const { config } = readCodexCliConfig(env);
22
+ if (config?.binary_path) {
23
+ return config.binary_path;
24
+ }
25
+ return 'codex';
26
+ }
27
+ export function resolveCodexCliReadiness(env = process.env) {
28
+ const configPath = resolveCodexCliConfigPath(env);
29
+ if (!existsSync(configPath)) {
30
+ return {
31
+ status: 'missing',
32
+ config: { status: 'missing', path: configPath },
33
+ binary: { status: 'missing', path: resolveCodexCliBinPath(env) }
34
+ };
35
+ }
36
+ const { config, error } = readCodexCliConfig(env);
37
+ if (error || !config) {
38
+ return {
39
+ status: 'invalid',
40
+ config: { status: 'invalid', path: configPath, error: error ?? 'Invalid config JSON.' },
41
+ binary: { status: 'missing', path: resolveCodexCliBinPath(env) }
42
+ };
43
+ }
44
+ const binaryPath = config.binary_path;
45
+ const binaryExists = binaryPath ? existsSync(binaryPath) : false;
46
+ const binaryStatus = binaryExists ? 'ok' : 'missing';
47
+ if (binaryExists && config.sha256) {
48
+ const actualSha256 = sha256FileSync(binaryPath);
49
+ if (actualSha256 !== config.sha256) {
50
+ return {
51
+ status: 'invalid',
52
+ config: {
53
+ status: 'invalid',
54
+ path: configPath,
55
+ error: `codex-cli sha256 mismatch (expected ${config.sha256}, got ${actualSha256})`
56
+ },
57
+ binary: { status: binaryStatus, path: binaryPath },
58
+ install: config
59
+ };
60
+ }
61
+ }
62
+ const status = binaryStatus === 'ok' ? 'ok' : 'missing';
63
+ return {
64
+ status,
65
+ config: { status: 'ok', path: configPath },
66
+ binary: { status: binaryStatus, path: binaryPath },
67
+ install: config
68
+ };
69
+ }
70
+ export function readCodexCliConfig(env = process.env) {
71
+ const configPath = resolveCodexCliConfigPath(env);
72
+ if (!existsSync(configPath)) {
73
+ return { config: null };
74
+ }
75
+ try {
76
+ const raw = readFileSync(configPath, 'utf8');
77
+ const parsed = JSON.parse(raw);
78
+ if (!parsed || typeof parsed.binary_path !== 'string') {
79
+ return { config: null, error: 'codex-cli.json missing binary_path.' };
80
+ }
81
+ return { config: parsed };
82
+ }
83
+ catch (error) {
84
+ return { config: null, error: error instanceof Error ? error.message : String(error) };
85
+ }
86
+ }
87
+ function codexBinaryName() {
88
+ return process.platform === 'win32' ? 'codex.exe' : 'codex';
89
+ }
90
+ function sha256FileSync(path) {
91
+ const hash = createHash('sha256');
92
+ hash.update(readFileSync(path));
93
+ return hash.digest('hex');
94
+ }
@@ -0,0 +1,13 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import process from 'node:process';
4
+ export function resolveCodexHome(env = process.env) {
5
+ const override = env.CODEX_HOME?.trim();
6
+ if (override) {
7
+ return override;
8
+ }
9
+ return join(homedir(), '.codex');
10
+ }
11
+ export function resolveCodexOrchestratorHome(env = process.env) {
12
+ return join(resolveCodexHome(env), 'orchestrator');
13
+ }
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
2
  import { join } from 'node:path';
4
3
  import process from 'node:process';
5
4
  import { EnvUtils } from '../../../../packages/shared/config/env.js';
5
+ import { resolveCodexCliBin } from './codexCli.js';
6
+ import { resolveCodexHome } from './codexPaths.js';
6
7
  export const DEVTOOLS_SKILL_NAME = 'chrome-devtools';
7
8
  export const DEVTOOLS_CONFIG_OVERRIDE = 'mcp_servers.chrome-devtools.enabled=true';
8
9
  const CONFIG_OVERRIDE_ENV_KEYS = ['CODEX_MCP_CONFIG_OVERRIDES', 'CODEX_CONFIG_OVERRIDES'];
@@ -32,13 +33,6 @@ export function isDevtoolsEnabled(env = process.env) {
32
33
  }
33
34
  return EnvUtils.isTrue(raw.trim().toLowerCase());
34
35
  }
35
- export function resolveCodexHome(env = process.env) {
36
- const override = env.CODEX_HOME?.trim();
37
- if (override) {
38
- return override;
39
- }
40
- return join(homedir(), '.codex');
41
- }
42
36
  export function resolveCodexConfigPath(env = process.env) {
43
37
  return join(resolveCodexHome(env), DEVTOOLS_CONFIG_FILENAME);
44
38
  }
@@ -76,28 +70,31 @@ export function resolveDevtoolsReadiness(env = process.env) {
76
70
  export function buildDevtoolsSetupPlan(env = process.env) {
77
71
  const codexHome = resolveCodexHome(env);
78
72
  const configPath = resolveCodexConfigPath(env);
73
+ const command = resolveCodexCliBin(env);
79
74
  const args = [...DEVTOOLS_MCP_COMMAND];
80
75
  return {
81
76
  codexHome,
82
77
  configPath,
83
- command: 'codex',
78
+ command,
84
79
  args,
85
- commandLine: ['codex', ...args].join(' '),
80
+ commandLine: [command, ...args].join(' '),
86
81
  configSnippet: DEVTOOLS_CONFIG_SNIPPET
87
82
  };
88
83
  }
89
84
  export function resolveCodexCommand(args, env = process.env) {
90
85
  const overrides = parseConfigOverrides(env);
91
86
  if (!isDevtoolsEnabled(env)) {
92
- return { command: 'codex', args: applyConfigOverrides(overrides, args) };
87
+ const command = resolveCodexCliBin(env);
88
+ return { command, args: applyConfigOverrides(overrides, args) };
93
89
  }
94
90
  const readiness = resolveDevtoolsReadiness(env);
95
91
  if (readiness.status !== 'ok') {
96
92
  throw new Error(formatDevtoolsPreflightError(readiness));
97
93
  }
98
94
  const mergedOverrides = dedupeOverrides([DEVTOOLS_CONFIG_OVERRIDE, ...overrides]);
95
+ const command = resolveCodexCliBin(env);
99
96
  return {
100
- command: 'codex',
97
+ command,
101
98
  args: applyConfigOverrides(mergedOverrides, args)
102
99
  };
103
100
  }