@kbediako/codex-orchestrator 0.1.36 → 0.1.37
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.
- package/README.md +1 -1
- package/dist/bin/codex-orchestrator.js +56 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +17 -1
- package/dist/orchestrator/src/cli/frontendTestingRunner.js +24 -6
- package/dist/orchestrator/src/cli/orchestrator.js +119 -16
- package/dist/orchestrator/src/cli/rlmRunner.js +27 -3
- package/dist/orchestrator/src/cli/run/manifest.js +19 -0
- package/dist/orchestrator/src/cli/runtime/codexCommand.js +39 -0
- package/dist/orchestrator/src/cli/runtime/index.js +3 -0
- package/dist/orchestrator/src/cli/runtime/mode.js +53 -0
- package/dist/orchestrator/src/cli/runtime/provider.js +205 -0
- package/dist/orchestrator/src/cli/runtime/types.js +1 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +19 -5
- package/dist/orchestrator/src/cli/services/runPreparation.js +2 -0
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +12 -0
- package/dist/scripts/run-review.js +55 -9
- package/docs/README.md +1 -1
- package/package.json +2 -1
- package/schemas/manifest.json +20 -0
package/README.md
CHANGED
|
@@ -307,7 +307,7 @@ codex-orchestrator devtools setup
|
|
|
307
307
|
- `codex-orchestrator mcp enable --servers <csv> --yes` — enable specific disabled MCP servers from existing Codex config entries.
|
|
308
308
|
- `codex-orchestrator self-check --format json` — JSON health payload.
|
|
309
309
|
- `codex-orchestrator mcp serve` — Codex MCP stdio server.
|
|
310
|
-
- `npm run pack:smoke` — maintainer smoke gate for packaged downstream behavior (tarball install + review/skill checks).
|
|
310
|
+
- `npm run pack:smoke` — maintainer smoke gate for packaged downstream behavior (tarball install + review/skill checks). Core lane runs it on downstream-facing diffs; `.github/workflows/pack-smoke-backstop.yml` runs a weekly `main` backstop.
|
|
311
311
|
|
|
312
312
|
## What ships in the npm release
|
|
313
313
|
|
|
@@ -393,6 +393,23 @@ function resolveExecutionModeFlag(flags) {
|
|
|
393
393
|
}
|
|
394
394
|
return normalized;
|
|
395
395
|
}
|
|
396
|
+
function resolveRuntimeModeFlag(flags) {
|
|
397
|
+
if (flags['runtime-mode'] === true) {
|
|
398
|
+
throw new Error('--runtime-mode requires a value. Expected one of: cli, appserver.');
|
|
399
|
+
}
|
|
400
|
+
const rawMode = readStringFlag(flags, 'runtime-mode');
|
|
401
|
+
if (flags['runtime-mode'] !== undefined && !rawMode) {
|
|
402
|
+
throw new Error('--runtime-mode requires a non-empty value. Expected one of: cli, appserver.');
|
|
403
|
+
}
|
|
404
|
+
if (!rawMode) {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
const normalized = rawMode.toLowerCase();
|
|
408
|
+
if (normalized !== 'cli' && normalized !== 'appserver') {
|
|
409
|
+
throw new Error('Invalid --runtime-mode value. Expected one of: cli, appserver.');
|
|
410
|
+
}
|
|
411
|
+
return normalized;
|
|
412
|
+
}
|
|
396
413
|
function normalizeRlmMultiAgentValue(raw) {
|
|
397
414
|
if (raw === true) {
|
|
398
415
|
return 'enabled';
|
|
@@ -580,6 +597,7 @@ async function handleStart(orchestrator, rawArgs) {
|
|
|
580
597
|
const pipelineId = positionals[0];
|
|
581
598
|
const format = flags['format'] === 'json' ? 'json' : 'text';
|
|
582
599
|
const executionMode = resolveExecutionModeFlag(flags);
|
|
600
|
+
const runtimeMode = resolveRuntimeModeFlag(flags);
|
|
583
601
|
applyRepoConfigRequiredPolicy(flags);
|
|
584
602
|
const autoIssueLogEnabled = resolveAutoIssueLogEnabled(flags);
|
|
585
603
|
if (pipelineId === 'rlm') {
|
|
@@ -607,6 +625,7 @@ async function handleStart(orchestrator, rawArgs) {
|
|
|
607
625
|
approvalPolicy: typeof flags['approval-policy'] === 'string' ? flags['approval-policy'] : undefined,
|
|
608
626
|
targetStageId: resolveTargetStageId(flags),
|
|
609
627
|
executionMode,
|
|
628
|
+
runtimeMode,
|
|
610
629
|
runEvents
|
|
611
630
|
});
|
|
612
631
|
const issueLogCapture = result.manifest.status !== 'succeeded'
|
|
@@ -618,6 +637,9 @@ async function handleStart(orchestrator, rawArgs) {
|
|
|
618
637
|
})
|
|
619
638
|
: { issueLog: null, issueLogError: null };
|
|
620
639
|
emitRunOutput(result, format, 'Run started', issueLogCapture);
|
|
640
|
+
if (result.manifest.status === 'failed' || result.manifest.status === 'cancelled') {
|
|
641
|
+
process.exitCode = 1;
|
|
642
|
+
}
|
|
621
643
|
if (result.manifest.status === 'succeeded' && result.manifest.pipeline_id !== 'rlm') {
|
|
622
644
|
await maybeEmitRunAdoptionHint({
|
|
623
645
|
format,
|
|
@@ -640,6 +662,7 @@ async function handleFrontendTest(orchestrator, rawArgs) {
|
|
|
640
662
|
const { positionals, flags } = parseArgs(rawArgs);
|
|
641
663
|
const format = flags['format'] === 'json' ? 'json' : 'text';
|
|
642
664
|
const devtools = Boolean(flags['devtools']);
|
|
665
|
+
const runtimeMode = resolveRuntimeModeFlag(flags);
|
|
643
666
|
applyRepoConfigRequiredPolicy(flags);
|
|
644
667
|
if (positionals.length > 0) {
|
|
645
668
|
console.error(`[frontend-test] ignoring extra arguments: ${positionals.join(' ')}`);
|
|
@@ -656,9 +679,13 @@ async function handleFrontendTest(orchestrator, rawArgs) {
|
|
|
656
679
|
parentRunId: typeof flags['parent-run'] === 'string' ? flags['parent-run'] : undefined,
|
|
657
680
|
approvalPolicy: typeof flags['approval-policy'] === 'string' ? flags['approval-policy'] : undefined,
|
|
658
681
|
targetStageId: resolveTargetStageId(flags),
|
|
682
|
+
runtimeMode,
|
|
659
683
|
runEvents
|
|
660
684
|
});
|
|
661
685
|
emitRunOutput(result, format, 'Run started');
|
|
686
|
+
if (result.manifest.status === 'failed' || result.manifest.status === 'cancelled') {
|
|
687
|
+
process.exitCode = 1;
|
|
688
|
+
}
|
|
662
689
|
});
|
|
663
690
|
}
|
|
664
691
|
finally {
|
|
@@ -683,6 +710,7 @@ async function handleFlow(orchestrator, rawArgs) {
|
|
|
683
710
|
}
|
|
684
711
|
const format = flags['format'] === 'json' ? 'json' : 'text';
|
|
685
712
|
const executionMode = resolveExecutionModeFlag(flags);
|
|
713
|
+
const runtimeMode = resolveRuntimeModeFlag(flags);
|
|
686
714
|
applyRepoConfigRequiredPolicy(flags);
|
|
687
715
|
const autoIssueLogEnabled = resolveAutoIssueLogEnabled(flags);
|
|
688
716
|
const taskId = typeof flags['task'] === 'string' ? flags['task'] : undefined;
|
|
@@ -699,6 +727,7 @@ async function handleFlow(orchestrator, rawArgs) {
|
|
|
699
727
|
approvalPolicy,
|
|
700
728
|
targetStageId: docsReviewTargetStageId,
|
|
701
729
|
executionMode,
|
|
730
|
+
runtimeMode,
|
|
702
731
|
runEvents
|
|
703
732
|
});
|
|
704
733
|
const docsPayload = toRunOutputPayload(docsReviewResult);
|
|
@@ -744,6 +773,7 @@ async function handleFlow(orchestrator, rawArgs) {
|
|
|
744
773
|
approvalPolicy,
|
|
745
774
|
targetStageId: implementationGateTargetStageId,
|
|
746
775
|
executionMode,
|
|
776
|
+
runtimeMode,
|
|
747
777
|
runEvents
|
|
748
778
|
});
|
|
749
779
|
const implementationPayload = toRunOutputPayload(implementationGateResult);
|
|
@@ -886,6 +916,7 @@ async function handleRlm(orchestrator, rawArgs) {
|
|
|
886
916
|
printRlmHelp();
|
|
887
917
|
return;
|
|
888
918
|
}
|
|
919
|
+
const runtimeMode = resolveRuntimeModeFlag(flags);
|
|
889
920
|
applyRepoConfigRequiredPolicy(flags);
|
|
890
921
|
const goalFromArgs = positionals.length > 0 ? positionals.join(' ') : undefined;
|
|
891
922
|
const goal = goalFromArgs ?? readStringFlag(flags, 'goal') ?? process.env.RLM_GOAL?.trim();
|
|
@@ -921,6 +952,7 @@ async function handleRlm(orchestrator, rawArgs) {
|
|
|
921
952
|
taskId,
|
|
922
953
|
parentRunId: typeof flags['parent-run'] === 'string' ? flags['parent-run'] : undefined,
|
|
923
954
|
approvalPolicy: typeof flags['approval-policy'] === 'string' ? flags['approval-policy'] : undefined,
|
|
955
|
+
runtimeMode,
|
|
924
956
|
runEvents
|
|
925
957
|
});
|
|
926
958
|
emitRunOutput(startResult, 'text', 'Run started');
|
|
@@ -949,6 +981,7 @@ async function handleResume(orchestrator, rawArgs) {
|
|
|
949
981
|
printResumeHelp();
|
|
950
982
|
return;
|
|
951
983
|
}
|
|
984
|
+
const runtimeMode = resolveRuntimeModeFlag(flags);
|
|
952
985
|
applyRepoConfigRequiredPolicy(flags);
|
|
953
986
|
const runId = (flags['run'] ?? positionals[0]);
|
|
954
987
|
if (!runId) {
|
|
@@ -962,6 +995,7 @@ async function handleResume(orchestrator, rawArgs) {
|
|
|
962
995
|
actor: typeof flags['actor'] === 'string' ? flags['actor'] : undefined,
|
|
963
996
|
reason: typeof flags['reason'] === 'string' ? flags['reason'] : undefined,
|
|
964
997
|
targetStageId: resolveTargetStageId(flags),
|
|
998
|
+
runtimeMode,
|
|
965
999
|
runEvents
|
|
966
1000
|
});
|
|
967
1001
|
emitRunOutput(result, format, 'Run resumed');
|
|
@@ -1034,6 +1068,13 @@ function emitRunOutput(result, format, label, issueLogCapture = { issueLog: null
|
|
|
1034
1068
|
console.log(`Status: ${payload.status}`);
|
|
1035
1069
|
console.log(`Manifest: ${payload.manifest}`);
|
|
1036
1070
|
console.log(`Log: ${payload.log_path}`);
|
|
1071
|
+
if (payload.runtime_mode) {
|
|
1072
|
+
console.log(`Runtime: ${payload.runtime_mode}${payload.runtime_mode_requested ? ` (requested ${payload.runtime_mode_requested})` : ''}` +
|
|
1073
|
+
(payload.runtime_provider ? ` via ${payload.runtime_provider}` : ''));
|
|
1074
|
+
if (payload.runtime_fallback?.occurred) {
|
|
1075
|
+
console.log(`Runtime fallback: ${payload.runtime_fallback.code ?? 'runtime-fallback'} (${payload.runtime_fallback.reason ?? 'n/a'})`);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1037
1078
|
if (payload.cloud_fallback_reason) {
|
|
1038
1079
|
console.log(`Cloud fallback: ${payload.cloud_fallback_reason}`);
|
|
1039
1080
|
}
|
|
@@ -1060,6 +1101,10 @@ function toRunOutputPayload(result, issueLogCapture = { issueLog: null, issueLog
|
|
|
1060
1101
|
manifest: `${result.manifest.artifact_root}/manifest.json`,
|
|
1061
1102
|
log_path: result.manifest.log_path,
|
|
1062
1103
|
summary: result.manifest.summary ?? null,
|
|
1104
|
+
runtime_mode_requested: result.manifest.runtime_mode_requested ?? null,
|
|
1105
|
+
runtime_mode: result.manifest.runtime_mode ?? null,
|
|
1106
|
+
runtime_provider: result.manifest.runtime_provider ?? null,
|
|
1107
|
+
runtime_fallback: result.manifest.runtime_fallback ?? null,
|
|
1063
1108
|
cloud_fallback_reason: result.manifest.cloud_fallback?.reason ?? null,
|
|
1064
1109
|
issue_log: issueLogCapture.issueLog,
|
|
1065
1110
|
issue_log_error: issueLogCapture.issueLogError
|
|
@@ -1998,6 +2043,7 @@ Commands:
|
|
|
1998
2043
|
--format json Emit machine-readable output.
|
|
1999
2044
|
--execution-mode <mcp|cloud> Force execution mode for this run and child subpipelines.
|
|
2000
2045
|
--cloud Shortcut for --execution-mode cloud.
|
|
2046
|
+
--runtime-mode <cli|appserver> Force runtime mode for this run and child subpipelines.
|
|
2001
2047
|
--target <stage-id> Focus plan/build metadata on a specific stage (alias: --target-stage).
|
|
2002
2048
|
--auto-issue-log [true|false] On failure, auto-write doctor issue bundle/log entry.
|
|
2003
2049
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
@@ -2013,6 +2059,7 @@ Commands:
|
|
|
2013
2059
|
|
|
2014
2060
|
rlm "<goal>" Run RLM loop until validator passes.
|
|
2015
2061
|
--task <id> Override task identifier.
|
|
2062
|
+
--runtime-mode <cli|appserver> Force runtime mode for this run.
|
|
2016
2063
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
2017
2064
|
--multi-agent [auto|true|false] Preferred alias for multi-agent collab subagents (implies symbolic mode).
|
|
2018
2065
|
--collab [auto|true|false] Legacy alias for --multi-agent.
|
|
@@ -2028,6 +2075,7 @@ Commands:
|
|
|
2028
2075
|
frontend-test Run frontend testing pipeline.
|
|
2029
2076
|
--devtools Enable Chrome DevTools MCP for this run.
|
|
2030
2077
|
--task <id> Override task identifier (defaults to MCP_RUNNER_TASK_ID).
|
|
2078
|
+
--runtime-mode <cli|appserver> Force runtime mode for this run.
|
|
2031
2079
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
2032
2080
|
--parent-run <id> Link run to parent run id.
|
|
2033
2081
|
--approval-policy <p> Record approval policy metadata.
|
|
@@ -2043,6 +2091,7 @@ Commands:
|
|
|
2043
2091
|
--format json Emit machine-readable output summary for both runs.
|
|
2044
2092
|
--execution-mode <mcp|cloud> Force execution mode for both runs.
|
|
2045
2093
|
--cloud Shortcut for --execution-mode cloud.
|
|
2094
|
+
--runtime-mode <cli|appserver> Force runtime mode for both runs.
|
|
2046
2095
|
--target <stage-id> Focus plan/build metadata on a specific stage (alias: --target-stage).
|
|
2047
2096
|
--auto-issue-log [true|false] On failure, auto-write doctor issue bundle/log entry.
|
|
2048
2097
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
@@ -2058,6 +2107,7 @@ Commands:
|
|
|
2058
2107
|
--base <branch> Review against base branch.
|
|
2059
2108
|
--commit <sha> Review specific commit.
|
|
2060
2109
|
--non-interactive Force non-interactive review behavior.
|
|
2110
|
+
--runtime-mode <cli|appserver> Force runtime mode for the underlying codex review call.
|
|
2061
2111
|
--auto-issue-log [true|false] Auto-capture issue bundle on review failure.
|
|
2062
2112
|
--disable-delegation-mcp [true|false] Disable delegation MCP for this review.
|
|
2063
2113
|
|
|
@@ -2080,6 +2130,7 @@ Commands:
|
|
|
2080
2130
|
--actor <name> Record who resumed the run.
|
|
2081
2131
|
--reason <text> Record why the run was resumed.
|
|
2082
2132
|
--target <stage-id> Override stage selection before resuming (alias: --target-stage).
|
|
2133
|
+
--runtime-mode <cli|appserver> Force runtime mode before resuming.
|
|
2083
2134
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
2084
2135
|
--format json Emit machine-readable output.
|
|
2085
2136
|
--interactive | --ui Enable read-only HUD when running in a TTY.
|
|
@@ -2226,6 +2277,7 @@ Options:
|
|
|
2226
2277
|
--actor <name> Record who resumed the run.
|
|
2227
2278
|
--reason <text> Record why the run was resumed.
|
|
2228
2279
|
--target <stage-id> Override stage selection before resuming.
|
|
2280
|
+
--runtime-mode <cli|appserver> Force runtime mode before resuming.
|
|
2229
2281
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
2230
2282
|
--format json Emit machine-readable output.
|
|
2231
2283
|
--interactive | --ui Enable read-only HUD when running in a TTY.
|
|
@@ -2281,6 +2333,7 @@ function printRlmHelp() {
|
|
|
2281
2333
|
Options:
|
|
2282
2334
|
--goal "<goal>" Alternate way to set the goal (positional is preferred).
|
|
2283
2335
|
--task <id> Override task identifier (defaults to MCP_RUNNER_TASK_ID).
|
|
2336
|
+
--runtime-mode <cli|appserver> Force runtime mode for this run.
|
|
2284
2337
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
2285
2338
|
--multi-agent [auto|true|false] Preferred alias for multi-agent collab subagents (implies symbolic mode).
|
|
2286
2339
|
--collab [auto|true|false] Legacy alias for --multi-agent.
|
|
@@ -2315,6 +2368,7 @@ Options:
|
|
|
2315
2368
|
--format json Emit machine-readable output for both runs.
|
|
2316
2369
|
--execution-mode <mcp|cloud> Force execution mode for both runs.
|
|
2317
2370
|
--cloud Shortcut for --execution-mode cloud.
|
|
2371
|
+
--runtime-mode <cli|appserver> Force runtime mode for both runs.
|
|
2318
2372
|
--target <stage-id> Focus plan/build metadata (applies where the stage exists).
|
|
2319
2373
|
--auto-issue-log [true|false] On failure, auto-write doctor issue bundle/log entry.
|
|
2320
2374
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
@@ -2344,6 +2398,7 @@ Common options:
|
|
|
2344
2398
|
--commit <sha> Review a specific commit.
|
|
2345
2399
|
--title "<text>" Optional review title in the prompt.
|
|
2346
2400
|
--non-interactive Force non-interactive behavior.
|
|
2401
|
+
--runtime-mode <cli|appserver> Force runtime mode for the underlying codex review call.
|
|
2347
2402
|
--auto-issue-log [true|false] Auto-capture issue bundle on review failure.
|
|
2348
2403
|
--disable-delegation-mcp [true|false] Disable delegation MCP for this review.
|
|
2349
2404
|
--enable-delegation-mcp [true|false] Legacy delegation MCP toggle (disable via false).
|
|
@@ -2373,6 +2428,7 @@ Options:
|
|
|
2373
2428
|
--format json Emit machine-readable output.
|
|
2374
2429
|
--execution-mode <mcp|cloud> Force execution mode for this run.
|
|
2375
2430
|
--cloud Shortcut for --execution-mode cloud.
|
|
2431
|
+
--runtime-mode <cli|appserver> Force runtime mode for this run.
|
|
2376
2432
|
--target <stage-id> Focus plan/build metadata on a specific stage.
|
|
2377
2433
|
--auto-issue-log [true|false] On failure, auto-write doctor issue bundle/log entry.
|
|
2378
2434
|
--repo-config-required [true|false] Require repo-local codex.orchestrator.json (no package fallback).
|
|
@@ -55,11 +55,17 @@ function normalizeUserConfig(config, source) {
|
|
|
55
55
|
if (!config) {
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
+
const runtimeMode = normalizeRuntimeMode(config.runtimeMode);
|
|
58
59
|
const stageSets = normalizeStageSets(config.stageSets);
|
|
59
60
|
const pipelines = Array.isArray(config.pipelines)
|
|
60
61
|
? config.pipelines.map((pipeline) => expandPipelineStages(pipeline, stageSets))
|
|
61
62
|
: config.pipelines;
|
|
62
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
pipelines,
|
|
65
|
+
defaultPipeline: config.defaultPipeline,
|
|
66
|
+
runtimeMode,
|
|
67
|
+
source
|
|
68
|
+
};
|
|
63
69
|
}
|
|
64
70
|
async function readConfig(configPath) {
|
|
65
71
|
try {
|
|
@@ -111,3 +117,13 @@ function expandPipelineStages(pipeline, stageSets) {
|
|
|
111
117
|
function isStageSetRef(stage) {
|
|
112
118
|
return stage.kind === 'stage-set';
|
|
113
119
|
}
|
|
120
|
+
function normalizeRuntimeMode(value) {
|
|
121
|
+
if (typeof value !== 'string') {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const normalized = value.trim().toLowerCase();
|
|
125
|
+
if (normalized === 'cli' || normalized === 'appserver') {
|
|
126
|
+
return normalized;
|
|
127
|
+
}
|
|
128
|
+
throw new Error(`Invalid codex.orchestrator.json runtimeMode "${value}". Expected one of: cli, appserver.`);
|
|
129
|
+
}
|
|
@@ -4,7 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { logger } from '../logger.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createRuntimeCodexCommandContext, formatRuntimeSelectionSummary, parseRuntimeMode, resolveRuntimeCodexCommand } from './runtime/index.js';
|
|
8
8
|
const DEFAULT_PROMPT = [
|
|
9
9
|
'You are running frontend testing for the current project.',
|
|
10
10
|
'',
|
|
@@ -37,9 +37,9 @@ export async function loadFrontendTestingPrompt(env = process.env) {
|
|
|
37
37
|
}
|
|
38
38
|
return DEFAULT_PROMPT;
|
|
39
39
|
}
|
|
40
|
-
export function resolveFrontendTestingCommand(prompt,
|
|
40
|
+
export function resolveFrontendTestingCommand(prompt, context) {
|
|
41
41
|
const args = ['exec', prompt];
|
|
42
|
-
return
|
|
42
|
+
return resolveRuntimeCodexCommand(args, context);
|
|
43
43
|
}
|
|
44
44
|
function envFlagEnabled(value) {
|
|
45
45
|
if (!value) {
|
|
@@ -59,16 +59,21 @@ function shouldForceNonInteractive(env) {
|
|
|
59
59
|
}
|
|
60
60
|
export async function runFrontendTesting(env = process.env) {
|
|
61
61
|
const prompt = await loadFrontendTestingPrompt(env);
|
|
62
|
-
const
|
|
62
|
+
const repoRoot = typeof env.CODEX_ORCHESTRATOR_ROOT === 'string' && env.CODEX_ORCHESTRATOR_ROOT.trim().length > 0
|
|
63
|
+
? env.CODEX_ORCHESTRATOR_ROOT.trim()
|
|
64
|
+
: process.cwd();
|
|
65
|
+
const runtimeContext = await resolveFrontendTestingRuntimeContext(env, repoRoot);
|
|
66
|
+
logger.info(`[frontend-testing-runtime] ${formatRuntimeSelectionSummary(runtimeContext.runtime)}`);
|
|
67
|
+
const { command, args } = resolveFrontendTestingCommand(prompt, runtimeContext);
|
|
63
68
|
const nonInteractive = shouldForceNonInteractive(env);
|
|
64
|
-
const childEnv = { ...process.env, ...env };
|
|
69
|
+
const childEnv = { ...process.env, ...env, ...runtimeContext.env };
|
|
65
70
|
if (nonInteractive) {
|
|
66
71
|
childEnv.CODEX_NON_INTERACTIVE = childEnv.CODEX_NON_INTERACTIVE ?? '1';
|
|
67
72
|
childEnv.CODEX_NO_INTERACTIVE = childEnv.CODEX_NO_INTERACTIVE ?? '1';
|
|
68
73
|
childEnv.CODEX_INTERACTIVE = childEnv.CODEX_INTERACTIVE ?? '0';
|
|
69
74
|
}
|
|
70
75
|
const stdio = nonInteractive ? ['ignore', 'inherit', 'inherit'] : 'inherit';
|
|
71
|
-
const child = spawn(command, args, { stdio, env: childEnv });
|
|
76
|
+
const child = spawn(command, args, { stdio, env: childEnv, cwd: repoRoot });
|
|
72
77
|
await new Promise((resolvePromise, reject) => {
|
|
73
78
|
child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
|
|
74
79
|
child.once('exit', (code) => {
|
|
@@ -81,6 +86,19 @@ export async function runFrontendTesting(env = process.env) {
|
|
|
81
86
|
});
|
|
82
87
|
});
|
|
83
88
|
}
|
|
89
|
+
async function resolveFrontendTestingRuntimeContext(env, repoRoot) {
|
|
90
|
+
const requestedMode = parseRuntimeMode(env.CODEX_ORCHESTRATOR_RUNTIME_MODE_ACTIVE ?? env.CODEX_ORCHESTRATOR_RUNTIME_MODE ?? null);
|
|
91
|
+
const runId = typeof env.CODEX_ORCHESTRATOR_RUN_ID === 'string' && env.CODEX_ORCHESTRATOR_RUN_ID.trim().length > 0
|
|
92
|
+
? env.CODEX_ORCHESTRATOR_RUN_ID.trim()
|
|
93
|
+
: `frontend-testing-${Date.now()}`;
|
|
94
|
+
return await createRuntimeCodexCommandContext({
|
|
95
|
+
requestedMode,
|
|
96
|
+
executionMode: 'mcp',
|
|
97
|
+
repoRoot,
|
|
98
|
+
env: { ...process.env, ...env },
|
|
99
|
+
runId
|
|
100
|
+
});
|
|
101
|
+
}
|
|
84
102
|
async function main() {
|
|
85
103
|
await runFrontendTesting();
|
|
86
104
|
}
|
|
@@ -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,
|
|
587
|
-
const branch = readCloudString(
|
|
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
|
|
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:
|
|
647
|
+
env: effectiveMergedEnv
|
|
597
648
|
});
|
|
598
649
|
if (!preflight.ok) {
|
|
599
|
-
if (!allowCloudFallback(
|
|
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({
|
|
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
|
|
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:
|
|
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}` : '';
|