@kbediako/codex-orchestrator 0.1.36 → 0.1.38

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.
@@ -21,7 +21,7 @@ import { PipelineResolver } from './services/pipelineResolver.js';
21
21
  import { ControlPlaneService } from './services/controlPlaneService.js';
22
22
  import { ControlWatcher } from './control/controlWatcher.js';
23
23
  import { SchedulerService } from './services/schedulerService.js';
24
- import { applyHandlesToRunSummary, applyPrivacyToRunSummary, applyCloudExecutionToRunSummary, applyCloudFallbackToRunSummary, applyUsageKpiToRunSummary, persistRunSummary } from './services/runSummaryWriter.js';
24
+ import { applyRuntimeToRunSummary, applyHandlesToRunSummary, applyPrivacyToRunSummary, applyCloudExecutionToRunSummary, applyCloudFallbackToRunSummary, applyUsageKpiToRunSummary, persistRunSummary } from './services/runSummaryWriter.js';
25
25
  import { prepareRun, resolvePipelineForResume, overrideTaskEnvironment } from './services/runPreparation.js';
26
26
  import { loadPackageConfig, loadUserConfig } from './config/userConfig.js';
27
27
  import { formatRepoConfigRequiredError, isRepoConfigRequired } from './config/repoConfigPolicy.js';
@@ -35,6 +35,7 @@ import { CodexCloudTaskExecutor } from '../cloud/CodexCloudTaskExecutor.js';
35
35
  import { persistPipelineExperience } from './services/pipelineExperience.js';
36
36
  import { runCloudPreflight } from './utils/cloudPreflight.js';
37
37
  import { writeJsonAtomic } from './utils/fs.js';
38
+ import { resolveRuntimeMode, resolveRuntimeSelection } from './runtime/index.js';
38
39
  import { buildAutoScoutEvidence, resolveAdvancedAutopilotDecision } from './utils/advancedAutopilot.js';
39
40
  const resolveBaseEnvironment = () => normalizeEnvironmentPaths(resolveEnvironmentPaths());
40
41
  const CONFIG_OVERRIDE_ENV_KEYS = ['CODEX_CONFIG_OVERRIDES', 'CODEX_MCP_CONFIG_OVERRIDES'];
@@ -233,6 +234,11 @@ export class CodexOrchestrator {
233
234
  planTargetFallback: null
234
235
  });
235
236
  const runId = generateRunId();
237
+ const runtimeModeResolution = resolveRuntimeMode({
238
+ flag: options.runtimeMode,
239
+ env: { ...process.env, ...(preparation.envOverrides ?? {}) },
240
+ configDefault: preparation.runtimeModeDefault
241
+ });
236
242
  const { manifest, paths } = await bootstrapManifest(runId, {
237
243
  env: preparation.env,
238
244
  pipeline: preparation.pipeline,
@@ -241,6 +247,7 @@ export class CodexOrchestrator {
241
247
  approvalPolicy: options.approvalPolicy ?? null,
242
248
  planTargetId: preparation.planPreview?.targetId ?? preparation.plannerTargetId ?? null
243
249
  });
250
+ this.applyRequestedRuntimeMode(manifest, runtimeModeResolution.mode);
244
251
  if (preparation.configNotice) {
245
252
  appendSummary(manifest, preparation.configNotice);
246
253
  }
@@ -305,6 +312,8 @@ export class CodexOrchestrator {
305
312
  onEventEntry,
306
313
  persister,
307
314
  envOverrides: preparation.envOverrides,
315
+ runtimeModeRequested: runtimeModeResolution.mode,
316
+ runtimeModeSource: runtimeModeResolution.source,
308
317
  executionModeOverride: options.executionMode
309
318
  });
310
319
  }
@@ -362,6 +371,7 @@ export class CodexOrchestrator {
362
371
  const preparation = await prepareRun({
363
372
  baseEnv: actualEnv,
364
373
  pipeline,
374
+ runtimeModeDefault: userConfig?.runtimeMode ?? null,
365
375
  resolver,
366
376
  taskIdOverride: manifest.task_id,
367
377
  targetStageId: options.targetStageId,
@@ -371,6 +381,14 @@ export class CodexOrchestrator {
371
381
  if (preparation.configNotice && !(manifest.summary ?? '').includes(preparation.configNotice)) {
372
382
  appendSummary(manifest, preparation.configNotice);
373
383
  }
384
+ const runtimeModeResolution = resolveRuntimeMode({
385
+ flag: options.runtimeMode,
386
+ env: { ...process.env, ...(preparation.envOverrides ?? {}) },
387
+ configDefault: preparation.runtimeModeDefault,
388
+ manifestMode: manifest.runtime_mode_requested ?? manifest.runtime_mode ?? null,
389
+ preferManifest: true
390
+ });
391
+ this.applyRequestedRuntimeMode(manifest, runtimeModeResolution.mode);
374
392
  manifest.plan_target_id = preparation.planPreview?.targetId ?? preparation.plannerTargetId ?? null;
375
393
  const persister = new ManifestPersister({
376
394
  manifest,
@@ -433,7 +451,9 @@ export class CodexOrchestrator {
433
451
  eventStream: stream,
434
452
  onEventEntry,
435
453
  persister,
436
- envOverrides: preparation.envOverrides
454
+ envOverrides: preparation.envOverrides,
455
+ runtimeModeRequested: runtimeModeResolution.mode,
456
+ runtimeModeSource: runtimeModeResolution.source
437
457
  });
438
458
  }
439
459
  finally {
@@ -582,21 +602,52 @@ export class CodexOrchestrator {
582
602
  return Boolean(task.metadata?.execution?.parallel);
583
603
  }
584
604
  async executePipeline(options) {
605
+ const baseEnvOverrides = { ...(options.envOverrides ?? {}) };
606
+ const mergedEnv = { ...process.env, ...baseEnvOverrides };
607
+ let runtimeSelection;
608
+ try {
609
+ runtimeSelection = await resolveRuntimeSelection({
610
+ requestedMode: options.runtimeModeRequested,
611
+ source: options.runtimeModeSource,
612
+ executionMode: options.mode,
613
+ repoRoot: options.env.repoRoot,
614
+ env: mergedEnv,
615
+ runId: options.manifest.run_id
616
+ });
617
+ }
618
+ catch (error) {
619
+ const detail = `Runtime selection failed: ${error?.message ?? String(error)}`;
620
+ finalizeStatus(options.manifest, 'failed', 'runtime-selection-failed');
621
+ appendSummary(options.manifest, detail);
622
+ logger.error(detail);
623
+ return {
624
+ success: false,
625
+ notes: [detail],
626
+ manifest: options.manifest,
627
+ manifestPath: options.paths.manifestPath,
628
+ logPath: options.paths.logPath
629
+ };
630
+ }
631
+ this.applyRuntimeSelection(options.manifest, runtimeSelection);
632
+ const effectiveEnvOverrides = {
633
+ ...baseEnvOverrides,
634
+ ...runtimeSelection.env_overrides
635
+ };
636
+ const effectiveMergedEnv = { ...process.env, ...effectiveEnvOverrides };
585
637
  if (options.mode === 'cloud') {
586
- const environmentId = resolveCloudEnvironmentId(options.task, options.target, options.envOverrides);
587
- const branch = readCloudString(options.envOverrides?.CODEX_CLOUD_BRANCH) ??
638
+ const environmentId = resolveCloudEnvironmentId(options.task, options.target, effectiveEnvOverrides);
639
+ const branch = readCloudString(effectiveEnvOverrides.CODEX_CLOUD_BRANCH) ??
588
640
  readCloudString(process.env.CODEX_CLOUD_BRANCH);
589
- const mergedEnv = { ...process.env, ...(options.envOverrides ?? {}) };
590
- const codexBin = resolveCodexCliBin(mergedEnv);
641
+ const codexBin = resolveCodexCliBin(effectiveMergedEnv);
591
642
  const preflight = await runCloudPreflight({
592
643
  repoRoot: options.env.repoRoot,
593
644
  codexBin,
594
645
  environmentId,
595
646
  branch,
596
- env: mergedEnv
647
+ env: effectiveMergedEnv
597
648
  });
598
649
  if (!preflight.ok) {
599
- if (!allowCloudFallback(options.envOverrides)) {
650
+ if (!allowCloudFallback(effectiveEnvOverrides)) {
600
651
  const detail = `Cloud preflight failed and cloud fallback is disabled. ` +
601
652
  preflight.issues.map((issue) => issue.message).join(' ');
602
653
  finalizeStatus(options.manifest, 'failed', 'cloud-preflight-failed');
@@ -621,21 +672,35 @@ export class CodexOrchestrator {
621
672
  };
622
673
  appendSummary(options.manifest, detail);
623
674
  logger.warn(detail);
624
- const fallback = await this.executePipeline({ ...options, mode: 'mcp', executionModeOverride: 'mcp' });
675
+ const fallback = await this.executePipeline({
676
+ ...options,
677
+ mode: 'mcp',
678
+ executionModeOverride: 'mcp',
679
+ runtimeModeRequested: runtimeSelection.selected_mode,
680
+ runtimeModeSource: runtimeSelection.source,
681
+ envOverrides: effectiveEnvOverrides
682
+ });
625
683
  fallback.notes.unshift(detail);
626
684
  return fallback;
627
685
  }
628
- return await this.executeCloudPipeline(options);
686
+ return await this.executeCloudPipeline({ ...options, envOverrides: effectiveEnvOverrides });
629
687
  }
630
- const { env, pipeline, manifest, paths, runEvents, envOverrides } = options;
688
+ const { env, pipeline, manifest, paths, runEvents } = options;
631
689
  const notes = [];
632
690
  let success = true;
633
691
  manifest.guardrail_status = undefined;
692
+ if (runtimeSelection.fallback.occurred) {
693
+ const fallbackCode = runtimeSelection.fallback.code ?? 'runtime-fallback';
694
+ const fallbackReason = runtimeSelection.fallback.reason ?? 'runtime fallback occurred';
695
+ const fallbackSummary = `Runtime fallback (${fallbackCode}): ${fallbackReason}`;
696
+ appendSummary(manifest, fallbackSummary);
697
+ notes.push(fallbackSummary);
698
+ }
634
699
  const advancedDecision = resolveAdvancedAutopilotDecision({
635
700
  pipelineId: pipeline.id,
636
701
  targetMetadata: (options.target.metadata ?? null),
637
702
  taskMetadata: (options.task.metadata ?? null),
638
- env: { ...process.env, ...(envOverrides ?? {}) }
703
+ env: effectiveMergedEnv
639
704
  });
640
705
  if (advancedDecision.enabled || advancedDecision.source !== 'default') {
641
706
  const advancedSummary = `Advanced mode (${advancedDecision.mode}) ${advancedDecision.enabled ? 'enabled' : 'disabled'}: ${advancedDecision.reason}.`;
@@ -673,7 +738,7 @@ export class CodexOrchestrator {
673
738
  pipeline,
674
739
  target: options.target,
675
740
  task: options.task,
676
- envOverrides,
741
+ envOverrides: effectiveEnvOverrides,
677
742
  advancedDecision
678
743
  });
679
744
  const scoutMessage = scoutOutcome.status === 'recorded'
@@ -719,7 +784,9 @@ export class CodexOrchestrator {
719
784
  index: entry.index,
720
785
  events: runEvents,
721
786
  persister,
722
- envOverrides
787
+ envOverrides: effectiveEnvOverrides,
788
+ runtimeMode: runtimeSelection.selected_mode,
789
+ runtimeSessionId: runtimeSelection.runtime_session_id
723
790
  });
724
791
  notes.push(`${stage.title}: ${result.summary}`);
725
792
  const updatedEntry = manifest.commands[i];
@@ -769,7 +836,8 @@ export class CodexOrchestrator {
769
836
  pipelineId: stage.pipeline,
770
837
  parentRunId: manifest.run_id,
771
838
  format: 'json',
772
- executionMode: options.executionModeOverride
839
+ executionMode: options.executionModeOverride,
840
+ runtimeMode: options.runtimeModeRequested
773
841
  });
774
842
  entry.completed_at = isoTimestamp();
775
843
  entry.sub_run_id = child.manifest.run_id;
@@ -1184,7 +1252,7 @@ export class CodexOrchestrator {
1184
1252
  }
1185
1253
  }
1186
1254
  async performRunLifecycle(context) {
1187
- const { env, pipeline, manifest, paths, planner, taskContext, runId, persister, envOverrides, executionModeOverride } = context;
1255
+ const { env, pipeline, manifest, paths, planner, taskContext, runId, persister, envOverrides, runtimeModeRequested, runtimeModeSource, executionModeOverride } = context;
1188
1256
  let latestPipelineResult = null;
1189
1257
  const executingByKey = new Map();
1190
1258
  const executePipeline = async (input) => {
@@ -1199,6 +1267,8 @@ export class CodexOrchestrator {
1199
1267
  manifest,
1200
1268
  paths,
1201
1269
  mode: input.mode,
1270
+ runtimeModeRequested,
1271
+ runtimeModeSource,
1202
1272
  executionModeOverride,
1203
1273
  target: input.target,
1204
1274
  task: taskContext,
@@ -1254,6 +1324,7 @@ export class CodexOrchestrator {
1254
1324
  persister
1255
1325
  });
1256
1326
  this.scheduler.applySchedulerToRunSummary(runSummary, schedulerPlan);
1327
+ applyRuntimeToRunSummary(runSummary, manifest);
1257
1328
  applyHandlesToRunSummary(runSummary, manifest);
1258
1329
  applyPrivacyToRunSummary(runSummary, manifest);
1259
1330
  applyCloudExecutionToRunSummary(runSummary, manifest);
@@ -1283,6 +1354,25 @@ export class CodexOrchestrator {
1283
1354
  });
1284
1355
  });
1285
1356
  }
1357
+ applyRequestedRuntimeMode(manifest, mode) {
1358
+ manifest.runtime_mode_requested = mode;
1359
+ manifest.runtime_mode = mode;
1360
+ manifest.runtime_provider = mode === 'appserver' ? 'AppServerRuntimeProvider' : 'CliRuntimeProvider';
1361
+ manifest.runtime_fallback = {
1362
+ occurred: false,
1363
+ code: null,
1364
+ reason: null,
1365
+ from_mode: null,
1366
+ to_mode: null,
1367
+ checked_at: isoTimestamp()
1368
+ };
1369
+ }
1370
+ applyRuntimeSelection(manifest, selection) {
1371
+ manifest.runtime_mode_requested = selection.requested_mode;
1372
+ manifest.runtime_mode = selection.selected_mode;
1373
+ manifest.runtime_provider = selection.provider;
1374
+ manifest.runtime_fallback = selection.fallback;
1375
+ }
1286
1376
  async validateResumeToken(paths, manifest, provided) {
1287
1377
  let stored = manifest.resume_token;
1288
1378
  if (!stored) {
@@ -1311,6 +1401,10 @@ export class CodexOrchestrator {
1311
1401
  activity,
1312
1402
  commands: manifest.commands,
1313
1403
  child_runs: manifest.child_runs,
1404
+ runtime_mode_requested: manifest.runtime_mode_requested,
1405
+ runtime_mode: manifest.runtime_mode,
1406
+ runtime_provider: manifest.runtime_provider,
1407
+ runtime_fallback: manifest.runtime_fallback ?? null,
1314
1408
  cloud_execution: manifest.cloud_execution ?? null,
1315
1409
  cloud_fallback: manifest.cloud_fallback ?? null
1316
1410
  };
@@ -1321,6 +1415,15 @@ export class CodexOrchestrator {
1321
1415
  logger.info(`Started: ${manifest.started_at}`);
1322
1416
  logger.info(`Completed: ${manifest.completed_at ?? 'in-progress'}`);
1323
1417
  logger.info(`Manifest: ${manifest.artifact_root}/manifest.json`);
1418
+ if (manifest.runtime_mode || manifest.runtime_mode_requested || manifest.runtime_provider) {
1419
+ const selectedMode = manifest.runtime_mode ?? 'unknown';
1420
+ logger.info(`Runtime: ${selectedMode}${manifest.runtime_mode_requested ? ` (requested ${manifest.runtime_mode_requested})` : ''}` +
1421
+ (manifest.runtime_provider ? ` via ${manifest.runtime_provider}` : ''));
1422
+ }
1423
+ if (manifest.runtime_fallback?.occurred) {
1424
+ const fallbackCode = manifest.runtime_fallback.code ?? 'runtime-fallback';
1425
+ logger.info(`Runtime fallback: ${fallbackCode} — ${manifest.runtime_fallback.reason ?? 'n/a'}`);
1426
+ }
1324
1427
  if (activity.observed_at) {
1325
1428
  const staleSuffix = activity.stale === null ? '' : activity.stale ? ' [stale]' : ' [active]';
1326
1429
  const sourceLabel = activity.observed_source ? ` via ${activity.observed_source}` : '';
@@ -7,7 +7,7 @@ import { promisify } from 'node:util';
7
7
  import { createInterface } from 'node:readline/promises';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { logger } from '../logger.js';
10
- import { resolveCodexCommand } from './utils/devtools.js';
10
+ import { createRuntimeCodexCommandContext, formatRuntimeSelectionSummary, parseRuntimeMode, resolveRuntimeCodexCommand } from './runtime/index.js';
11
11
  import { detectValidator } from './rlm/validator.js';
12
12
  import { buildRlmPrompt } from './rlm/prompt.js';
13
13
  import { runRlmLoop } from './rlm/runner.js';
@@ -57,6 +57,8 @@ const COLLAB_FEATURE_CANONICAL = 'multi_agent';
57
57
  const COLLAB_FEATURE_LEGACY = 'collab';
58
58
  const COLLAB_ROLE_TAG_PATTERN = /^\s*\[(?:agent_type|role)\s*:\s*([a-z0-9._-]+)\]/i;
59
59
  const COLLAB_ROLE_TOKEN_PATTERN = /^[a-z0-9._-]+$/;
60
+ let runtimeCodexContextPromise = null;
61
+ let runtimeCodexContextLogged = false;
60
62
  function parseArgs(argv) {
61
63
  const parsed = {};
62
64
  for (let i = 0; i < argv.length; i += 1) {
@@ -332,8 +334,9 @@ async function runCodexAgent(input, env, repoRoot, nonInteractive, subagentsEnab
332
334
  return { output };
333
335
  }
334
336
  async function runCodexExec(args, env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput) {
335
- const { command, args: resolvedArgs } = resolveCodexCommand(args, env);
336
- const childEnv = { ...process.env, ...env };
337
+ const runtimeContext = await resolveRlmRuntimeCodexContext(env, repoRoot);
338
+ const { command, args: resolvedArgs } = resolveRuntimeCodexCommand(args, runtimeContext);
339
+ const childEnv = { ...process.env, ...env, ...runtimeContext.env };
337
340
  if (nonInteractive) {
338
341
  childEnv.CODEX_NON_INTERACTIVE = childEnv.CODEX_NON_INTERACTIVE ?? '1';
339
342
  childEnv.CODEX_NO_INTERACTIVE = childEnv.CODEX_NO_INTERACTIVE ?? '1';
@@ -370,6 +373,27 @@ async function runCodexExec(args, env, repoRoot, nonInteractive, subagentsEnable
370
373
  });
371
374
  return { stdout, stderr };
372
375
  }
376
+ async function resolveRlmRuntimeCodexContext(env, repoRoot) {
377
+ if (!runtimeCodexContextPromise) {
378
+ const requestedMode = parseRuntimeMode(env.CODEX_ORCHESTRATOR_RUNTIME_MODE_ACTIVE ?? env.CODEX_ORCHESTRATOR_RUNTIME_MODE ?? null);
379
+ const runId = typeof env.CODEX_ORCHESTRATOR_RUN_ID === 'string' && env.CODEX_ORCHESTRATOR_RUN_ID.trim().length > 0
380
+ ? env.CODEX_ORCHESTRATOR_RUN_ID.trim()
381
+ : `rlm-${Date.now()}`;
382
+ runtimeCodexContextPromise = createRuntimeCodexCommandContext({
383
+ requestedMode,
384
+ executionMode: 'mcp',
385
+ repoRoot,
386
+ env: { ...process.env, ...env },
387
+ runId
388
+ });
389
+ }
390
+ const runtimeContext = await runtimeCodexContextPromise;
391
+ if (!runtimeCodexContextLogged) {
392
+ logger.info(`[rlm-runtime] ${formatRuntimeSelectionSummary(runtimeContext.runtime)}`);
393
+ runtimeCodexContextLogged = true;
394
+ }
395
+ return runtimeContext;
396
+ }
373
397
  async function runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput) {
374
398
  const { stdout, stderr } = await runCodexExec(['exec', prompt], env, repoRoot, nonInteractive, subagentsEnabled, mirrorOutput);
375
399
  return [stdout.trim(), stderr.trim()].filter(Boolean).join('\n');
@@ -13,6 +13,19 @@ const HEARTBEAT_INTERVAL_SECONDS = 5;
13
13
  const HEARTBEAT_STALE_AFTER_SECONDS = 30;
14
14
  const MAX_ERROR_DETAIL_CHARS = 8 * 1024;
15
15
  const DEFAULT_MIN_EXPERIENCE_REWARD = 0.1;
16
+ function createDefaultRuntimeFallback() {
17
+ return {
18
+ occurred: false,
19
+ code: null,
20
+ reason: null,
21
+ from_mode: null,
22
+ to_mode: null,
23
+ checked_at: isoTimestamp()
24
+ };
25
+ }
26
+ function runtimeProviderForMode(mode) {
27
+ return mode === 'appserver' ? 'AppServerRuntimeProvider' : 'CliRuntimeProvider';
28
+ }
16
29
  export async function bootstrapManifest(runId, options) {
17
30
  const { env, pipeline, parentRunId = null, taskSlug, approvalPolicy = null } = options;
18
31
  const paths = resolveRunPaths(env, runId);
@@ -57,6 +70,10 @@ export async function bootstrapManifest(runId, options) {
57
70
  instructions_sources: [],
58
71
  prompt_packs: [],
59
72
  guardrails_required: pipeline.guardrailsRequired !== false,
73
+ runtime_mode_requested: 'appserver',
74
+ runtime_mode: 'appserver',
75
+ runtime_provider: runtimeProviderForMode('appserver'),
76
+ runtime_fallback: createDefaultRuntimeFallback(),
60
77
  cloud_execution: null,
61
78
  cloud_fallback: null,
62
79
  learning: {
@@ -192,6 +209,8 @@ export function resetForResume(manifest) {
192
209
  manifest.status = 'in_progress';
193
210
  manifest.status_detail = 'resuming';
194
211
  manifest.guardrail_status = undefined;
212
+ manifest.runtime_provider = runtimeProviderForMode(manifest.runtime_mode);
213
+ manifest.runtime_fallback = createDefaultRuntimeFallback();
195
214
  manifest.cloud_execution = null;
196
215
  manifest.cloud_fallback = null;
197
216
  }
@@ -0,0 +1,39 @@
1
+ import { resolveCodexCommand } from '../utils/devtools.js';
2
+ import { resolveRuntimeMode } from './mode.js';
3
+ import { resolveRuntimeSelection } from './provider.js';
4
+ export async function createRuntimeCodexCommandContext(options) {
5
+ const runtimeEnv = options.env ?? process.env;
6
+ const modeResolution = resolveRuntimeMode({
7
+ flag: options.requestedMode,
8
+ env: runtimeEnv,
9
+ configDefault: options.configDefault
10
+ });
11
+ const runtime = await resolveRuntimeSelection({
12
+ requestedMode: modeResolution.mode,
13
+ source: modeResolution.source,
14
+ executionMode: options.executionMode ?? 'mcp',
15
+ repoRoot: options.repoRoot,
16
+ env: runtimeEnv,
17
+ runId: options.runId,
18
+ allowFallback: options.allowFallback
19
+ });
20
+ return {
21
+ runtime,
22
+ env: {
23
+ ...runtimeEnv,
24
+ ...runtime.env_overrides
25
+ }
26
+ };
27
+ }
28
+ export function resolveRuntimeCodexCommand(args, context) {
29
+ return resolveCodexCommand(args, context.env);
30
+ }
31
+ export function formatRuntimeSelectionSummary(selection) {
32
+ const base = `runtime requested=${selection.requested_mode} selected=${selection.selected_mode} provider=${selection.provider}`;
33
+ if (!selection.fallback.occurred) {
34
+ return base;
35
+ }
36
+ const code = selection.fallback.code ?? 'unknown';
37
+ const reason = selection.fallback.reason ?? 'fallback occurred';
38
+ return `${base} fallback=${code} (${reason})`;
39
+ }
@@ -0,0 +1,3 @@
1
+ export { parseRuntimeMode, resolveRuntimeMode, DEFAULT_RUNTIME_MODE, DEFAULT_RUNTIME_MODE_ENV_KEY } from './mode.js';
2
+ export { resolveRuntimeSelection } from './provider.js';
3
+ export { createRuntimeCodexCommandContext, resolveRuntimeCodexCommand, formatRuntimeSelectionSummary } from './codexCommand.js';
@@ -0,0 +1,53 @@
1
+ const RUNTIME_MODES = new Set(['cli', 'appserver']);
2
+ const DEFAULT_RUNTIME_MODE = 'appserver';
3
+ const DEFAULT_RUNTIME_MODE_ENV_KEY = 'CODEX_ORCHESTRATOR_RUNTIME_MODE';
4
+ function normalizeRuntimeMode(value) {
5
+ const normalized = value.trim().toLowerCase();
6
+ return RUNTIME_MODES.has(normalized) ? normalized : null;
7
+ }
8
+ function parseRuntimeModeFromSource(value, sourceLabel) {
9
+ const parsed = normalizeRuntimeMode(value);
10
+ if (!parsed) {
11
+ throw new Error(`Invalid runtime mode "${value}" from ${sourceLabel}. Expected one of: cli, appserver.`);
12
+ }
13
+ return parsed;
14
+ }
15
+ export function parseRuntimeMode(value) {
16
+ if (typeof value !== 'string' || value.trim().length === 0) {
17
+ return null;
18
+ }
19
+ return normalizeRuntimeMode(value);
20
+ }
21
+ export function resolveRuntimeMode(options = {}) {
22
+ if (typeof options.flag === 'string' && options.flag.trim().length > 0) {
23
+ return {
24
+ mode: parseRuntimeModeFromSource(options.flag, 'CLI flag'),
25
+ source: 'flag'
26
+ };
27
+ }
28
+ const envKey = options.envKey ?? DEFAULT_RUNTIME_MODE_ENV_KEY;
29
+ const envValue = options.env?.[envKey] ?? process.env[envKey];
30
+ if (typeof envValue === 'string' && envValue.trim().length > 0) {
31
+ return {
32
+ mode: parseRuntimeModeFromSource(envValue, `env ${envKey}`),
33
+ source: 'env'
34
+ };
35
+ }
36
+ if (typeof options.configDefault === 'string' && options.configDefault.trim().length > 0) {
37
+ return {
38
+ mode: parseRuntimeModeFromSource(options.configDefault, 'config default'),
39
+ source: 'config'
40
+ };
41
+ }
42
+ if (options.preferManifest && typeof options.manifestMode === 'string' && options.manifestMode.trim().length > 0) {
43
+ return {
44
+ mode: parseRuntimeModeFromSource(options.manifestMode, 'manifest'),
45
+ source: 'manifest'
46
+ };
47
+ }
48
+ return {
49
+ mode: DEFAULT_RUNTIME_MODE,
50
+ source: 'default'
51
+ };
52
+ }
53
+ export { DEFAULT_RUNTIME_MODE, DEFAULT_RUNTIME_MODE_ENV_KEY };