@kbediako/codex-orchestrator 0.1.33 → 0.1.34
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 +19 -3
- package/codex.orchestrator.json +448 -0
- package/dist/bin/codex-orchestrator.js +365 -78
- package/dist/orchestrator/src/cli/config/repoConfigPolicy.js +22 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +20 -9
- package/dist/orchestrator/src/cli/delegationSetup.js +111 -14
- package/dist/orchestrator/src/cli/doctor.js +82 -5
- package/dist/orchestrator/src/cli/doctorIssueLog.js +350 -0
- package/dist/orchestrator/src/cli/init.js +23 -0
- package/dist/orchestrator/src/cli/orchestrator.js +19 -3
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +70 -18
- package/dist/orchestrator/src/cli/services/runPreparation.js +2 -0
- package/dist/orchestrator/src/cli/utils/commandPreview.js +10 -0
- package/dist/orchestrator/src/cli/utils/devtools.js +2 -1
- package/dist/orchestrator/src/cloud/CodexCloudTaskExecutor.js +21 -0
- package/docs/README.md +12 -7
- package/package.json +2 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
export const REPO_CONFIG_REQUIRED_ENV_KEY = 'CODEX_ORCHESTRATOR_REPO_CONFIG_REQUIRED';
|
|
4
|
+
const REPO_CONFIG_REQUIRED_TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);
|
|
5
|
+
export function isRepoConfigRequired(env = process.env) {
|
|
6
|
+
const raw = env[REPO_CONFIG_REQUIRED_ENV_KEY];
|
|
7
|
+
if (typeof raw !== 'string') {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const normalized = raw.trim().toLowerCase();
|
|
11
|
+
if (!normalized) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return REPO_CONFIG_REQUIRED_TRUE_VALUES.has(normalized);
|
|
15
|
+
}
|
|
16
|
+
export function formatRepoConfigRequiredError(repoRoot) {
|
|
17
|
+
return [
|
|
18
|
+
`Repo-local codex.orchestrator.json is required when ${REPO_CONFIG_REQUIRED_ENV_KEY}=1.`,
|
|
19
|
+
`Expected: ${join(repoRoot, 'codex.orchestrator.json')}.`,
|
|
20
|
+
'Run `codex-orchestrator init codex` to scaffold repo-local config.'
|
|
21
|
+
].join(' ');
|
|
22
|
+
}
|
|
@@ -2,17 +2,21 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { logger } from '../../logger.js';
|
|
4
4
|
import { findPackageRoot } from '../utils/packageInfo.js';
|
|
5
|
-
export async function loadRepoConfig(env) {
|
|
5
|
+
export async function loadRepoConfig(env, options = {}) {
|
|
6
6
|
const repoConfigPath = join(env.repoRoot, 'codex.orchestrator.json');
|
|
7
7
|
const repoConfig = await readConfig(repoConfigPath);
|
|
8
8
|
if (repoConfig) {
|
|
9
|
-
|
|
9
|
+
if (!options.quiet) {
|
|
10
|
+
logger.info(`[codex-config] Loaded user config from ${repoConfigPath}`);
|
|
11
|
+
}
|
|
10
12
|
return normalizeUserConfig(repoConfig, 'repo');
|
|
11
13
|
}
|
|
12
|
-
|
|
14
|
+
if (!options.quiet) {
|
|
15
|
+
logger.warn(`[codex-config] Missing codex.orchestrator.json at ${repoConfigPath}`);
|
|
16
|
+
}
|
|
13
17
|
return null;
|
|
14
18
|
}
|
|
15
|
-
export async function loadPackageConfig(env) {
|
|
19
|
+
export async function loadPackageConfig(env, options = {}) {
|
|
16
20
|
const repoConfigPath = join(env.repoRoot, 'codex.orchestrator.json');
|
|
17
21
|
const packageRoot = findPackageRoot();
|
|
18
22
|
const packageConfigPath = join(packageRoot, 'codex.orchestrator.json');
|
|
@@ -21,18 +25,25 @@ export async function loadPackageConfig(env) {
|
|
|
21
25
|
}
|
|
22
26
|
const packageConfig = await readConfig(packageConfigPath);
|
|
23
27
|
if (packageConfig) {
|
|
24
|
-
|
|
28
|
+
if (!options.quiet) {
|
|
29
|
+
logger.info(`[codex-config] Loaded user config from ${packageConfigPath}`);
|
|
30
|
+
}
|
|
25
31
|
return normalizeUserConfig(packageConfig, 'package');
|
|
26
32
|
}
|
|
27
|
-
|
|
33
|
+
if (!options.quiet) {
|
|
34
|
+
logger.warn(`[codex-config] Missing package config at ${packageConfigPath}`);
|
|
35
|
+
}
|
|
28
36
|
return null;
|
|
29
37
|
}
|
|
30
|
-
export async function loadUserConfig(env) {
|
|
31
|
-
const repoConfig = await loadRepoConfig(env);
|
|
38
|
+
export async function loadUserConfig(env, options = {}) {
|
|
39
|
+
const repoConfig = await loadRepoConfig(env, options);
|
|
32
40
|
if (repoConfig) {
|
|
33
41
|
return repoConfig;
|
|
34
42
|
}
|
|
35
|
-
|
|
43
|
+
if (options.allowPackageFallback === false) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return await loadPackageConfig(env, options);
|
|
36
47
|
}
|
|
37
48
|
export function findPipeline(config, id) {
|
|
38
49
|
if (!config?.pipelines) {
|
|
@@ -4,9 +4,10 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { resolveCodexCliBin } from './utils/codexCli.js';
|
|
6
6
|
import { resolveCodexHome } from './utils/codexPaths.js';
|
|
7
|
+
import { buildCommandPreview } from './utils/commandPreview.js';
|
|
7
8
|
export async function runDelegationSetup(options = {}) {
|
|
8
9
|
const env = options.env ?? process.env;
|
|
9
|
-
const repoRoot = options.repoRoot ?? process.cwd();
|
|
10
|
+
const repoRoot = resolve(options.repoRoot ?? process.cwd());
|
|
10
11
|
const codexBin = resolveCodexCliBin(env);
|
|
11
12
|
const codexHome = resolveCodexHome(env);
|
|
12
13
|
const configPath = join(codexHome, 'config.toml');
|
|
@@ -14,7 +15,16 @@ export async function runDelegationSetup(options = {}) {
|
|
|
14
15
|
codexBin,
|
|
15
16
|
codexHome,
|
|
16
17
|
repoRoot,
|
|
17
|
-
commandLine:
|
|
18
|
+
commandLine: buildCommandPreview(codexBin, [
|
|
19
|
+
'mcp',
|
|
20
|
+
'add',
|
|
21
|
+
'delegation',
|
|
22
|
+
'--',
|
|
23
|
+
'codex-orchestrator',
|
|
24
|
+
'delegate-server',
|
|
25
|
+
'--repo',
|
|
26
|
+
repoRoot
|
|
27
|
+
])
|
|
18
28
|
};
|
|
19
29
|
const probe = inspectDelegationReadiness({ codexBin, configPath, repoRoot, env });
|
|
20
30
|
const readiness = { configured: probe.configured, configPath };
|
|
@@ -24,7 +34,7 @@ export async function runDelegationSetup(options = {}) {
|
|
|
24
34
|
if (probe.configured) {
|
|
25
35
|
return { status: 'skipped', reason: probe.reason ?? 'Delegation MCP is already configured.', plan, readiness };
|
|
26
36
|
}
|
|
27
|
-
await applyDelegationSetup({ codexBin, removeExisting: probe.removeExisting, envVars: probe.envVars }, env);
|
|
37
|
+
await applyDelegationSetup({ codexBin, repoRoot, removeExisting: probe.removeExisting, envVars: probe.envVars }, env);
|
|
28
38
|
const configuredAfter = inspectDelegationReadiness({ codexBin, configPath, repoRoot, env }).configured;
|
|
29
39
|
return {
|
|
30
40
|
status: 'applied',
|
|
@@ -79,15 +89,14 @@ function inspectDelegationReadiness(options) {
|
|
|
79
89
|
};
|
|
80
90
|
}
|
|
81
91
|
return {
|
|
82
|
-
configured:
|
|
83
|
-
removeExisting:
|
|
92
|
+
configured: false,
|
|
93
|
+
removeExisting: true,
|
|
84
94
|
envVars,
|
|
85
|
-
reason:
|
|
95
|
+
reason: `Existing delegation MCP entry is not pinned; reconfiguring to ${requestedRepo}.`
|
|
86
96
|
};
|
|
87
97
|
}
|
|
88
98
|
// Fall back to directly scanning config.toml when the Codex CLI probe is unavailable.
|
|
89
|
-
|
|
90
|
-
return { configured, removeExisting: false, envVars: {} };
|
|
99
|
+
return inspectDelegationReadinessFallback(options.configPath, requestedRepo);
|
|
91
100
|
}
|
|
92
101
|
function applyDelegationSetup(plan, env) {
|
|
93
102
|
const envFlags = [];
|
|
@@ -96,7 +105,17 @@ function applyDelegationSetup(plan, env) {
|
|
|
96
105
|
}
|
|
97
106
|
return new Promise((resolve, reject) => {
|
|
98
107
|
const runAdd = () => {
|
|
99
|
-
const child = spawn(plan.codexBin, [
|
|
108
|
+
const child = spawn(plan.codexBin, [
|
|
109
|
+
'mcp',
|
|
110
|
+
'add',
|
|
111
|
+
'delegation',
|
|
112
|
+
...envFlags,
|
|
113
|
+
'--',
|
|
114
|
+
'codex-orchestrator',
|
|
115
|
+
'delegate-server',
|
|
116
|
+
'--repo',
|
|
117
|
+
plan.repoRoot
|
|
118
|
+
], { stdio: 'inherit', env });
|
|
100
119
|
child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
|
|
101
120
|
child.once('exit', (code) => {
|
|
102
121
|
if (code === 0) {
|
|
@@ -167,18 +186,96 @@ function readPinnedRepo(args) {
|
|
|
167
186
|
const candidate = args[index + 1];
|
|
168
187
|
return typeof candidate === 'string' && candidate.trim().length > 0 ? candidate.trim() : null;
|
|
169
188
|
}
|
|
170
|
-
function
|
|
189
|
+
function inspectDelegationReadinessFallback(configPath, requestedRepo) {
|
|
190
|
+
const config = readDelegationFallbackConfig(configPath);
|
|
191
|
+
if (!config) {
|
|
192
|
+
return { configured: false, removeExisting: false, envVars: {} };
|
|
193
|
+
}
|
|
194
|
+
const pinnedRepo = readPinnedRepo(config.args);
|
|
195
|
+
if (!pinnedRepo) {
|
|
196
|
+
return {
|
|
197
|
+
configured: false,
|
|
198
|
+
removeExisting: true,
|
|
199
|
+
envVars: config.envVars,
|
|
200
|
+
reason: `Existing delegation MCP entry is not pinned; reconfiguring to ${requestedRepo}.`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const normalizedPinned = resolve(pinnedRepo);
|
|
204
|
+
if (normalizedPinned !== requestedRepo) {
|
|
205
|
+
return {
|
|
206
|
+
configured: false,
|
|
207
|
+
removeExisting: true,
|
|
208
|
+
envVars: config.envVars,
|
|
209
|
+
reason: `Existing delegation MCP entry is pinned to ${pinnedRepo}; reconfiguring.`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
configured: true,
|
|
214
|
+
removeExisting: false,
|
|
215
|
+
envVars: config.envVars,
|
|
216
|
+
reason: `Delegation MCP is already configured (pinned to ${pinnedRepo}).`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function readDelegationFallbackConfig(configPath) {
|
|
171
220
|
if (!existsSync(configPath)) {
|
|
172
|
-
return
|
|
221
|
+
return null;
|
|
173
222
|
}
|
|
174
223
|
try {
|
|
175
|
-
// Keep parsing loose; we only need to know whether a delegation entry exists.
|
|
176
224
|
const raw = readFileSync(configPath, 'utf8');
|
|
177
|
-
|
|
225
|
+
if (!hasMcpServerEntry(raw, 'delegation')) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
args: readDelegationArgsFromConfig(raw),
|
|
230
|
+
envVars: readDelegationEnvVarsFromConfig(raw)
|
|
231
|
+
};
|
|
178
232
|
}
|
|
179
233
|
catch {
|
|
180
|
-
return
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function readDelegationArgsFromConfig(raw) {
|
|
238
|
+
const sectionMatch = raw.match(/\[mcp_servers(?:\.delegation|\."delegation"|.'delegation')\]([\s\S]*?)(?=\n\[|$)/u);
|
|
239
|
+
if (!sectionMatch) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const section = sectionMatch[1] ?? '';
|
|
243
|
+
const argsMatch = section.match(/^\s*args\s*=\s*\[([\s\S]*?)\]/mu);
|
|
244
|
+
if (!argsMatch) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const argsRaw = argsMatch[1] ?? '';
|
|
248
|
+
const args = [];
|
|
249
|
+
const tokenPattern = /"((?:\\"|[^"])*)"|'((?:\\'|[^'])*)'/gu;
|
|
250
|
+
let token = tokenPattern.exec(argsRaw);
|
|
251
|
+
while (token) {
|
|
252
|
+
const quoted = token[1] ?? token[2] ?? '';
|
|
253
|
+
const decoded = quoted.replace(/\\"/gu, '"').replace(/\\'/gu, '\'');
|
|
254
|
+
args.push(decoded);
|
|
255
|
+
token = tokenPattern.exec(argsRaw);
|
|
256
|
+
}
|
|
257
|
+
return args;
|
|
258
|
+
}
|
|
259
|
+
function readDelegationEnvVarsFromConfig(raw) {
|
|
260
|
+
const envVars = {};
|
|
261
|
+
const sectionMatch = raw.match(/\[mcp_servers(?:\.delegation|\."delegation"|.'delegation')\.env\]([\s\S]*?)(?=\n\[|$)/u);
|
|
262
|
+
if (!sectionMatch) {
|
|
263
|
+
return envVars;
|
|
264
|
+
}
|
|
265
|
+
const section = sectionMatch[1] ?? '';
|
|
266
|
+
const linePattern = /^\s*([A-Za-z0-9_.-]+)\s*=\s*("(?:\\"|[^"])*"|'(?:\\'|[^'])*')\s*$/gmu;
|
|
267
|
+
let match = linePattern.exec(section);
|
|
268
|
+
while (match) {
|
|
269
|
+
const key = match[1];
|
|
270
|
+
const rawValue = match[2] ?? '';
|
|
271
|
+
if (key) {
|
|
272
|
+
const unquoted = rawValue.slice(1, -1);
|
|
273
|
+
const decoded = unquoted.replace(/\\"/gu, '"').replace(/\\'/gu, '\'');
|
|
274
|
+
envVars[key] = decoded;
|
|
275
|
+
}
|
|
276
|
+
match = linePattern.exec(section);
|
|
181
277
|
}
|
|
278
|
+
return envVars;
|
|
182
279
|
}
|
|
183
280
|
function hasMcpServerEntry(raw, serverName) {
|
|
184
281
|
const lines = raw.split('\n');
|
|
@@ -7,6 +7,9 @@ import { isManagedCodexCliEnabled, resolveCodexCliBin, resolveCodexCliReadiness
|
|
|
7
7
|
import { resolveCodexHome } from './utils/codexPaths.js';
|
|
8
8
|
import { resolveOptionalDependency } from './utils/optionalDeps.js';
|
|
9
9
|
import { runCloudPreflight } from './utils/cloudPreflight.js';
|
|
10
|
+
import { CommandPlanner } from './adapters/CommandPlanner.js';
|
|
11
|
+
import { PipelineResolver } from './services/pipelineResolver.js';
|
|
12
|
+
import { isRepoConfigRequired } from './config/repoConfigPolicy.js';
|
|
10
13
|
const OPTIONAL_DEPENDENCIES = [
|
|
11
14
|
{
|
|
12
15
|
name: 'playwright',
|
|
@@ -95,6 +98,7 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
95
98
|
? process.env.CODEX_CLOUD_BRANCH.trim().replace(/^refs\/heads\//u, '')
|
|
96
99
|
: null;
|
|
97
100
|
const cloudStatus = !cloudCmdAvailable ? 'unavailable' : cloudEnvIdConfigured ? 'ok' : 'not_configured';
|
|
101
|
+
const cloudFallbackPolicy = resolveCloudFallbackPolicy();
|
|
98
102
|
const delegationConfig = inspectDelegationConfig();
|
|
99
103
|
const delegationStatus = delegationConfig.status === 'ok' ? 'ok' : 'missing-config';
|
|
100
104
|
return {
|
|
@@ -120,11 +124,13 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
120
124
|
status: cloudStatus,
|
|
121
125
|
env_id_configured: cloudEnvIdConfigured,
|
|
122
126
|
branch: cloudBranch,
|
|
127
|
+
fallback_policy: cloudFallbackPolicy,
|
|
123
128
|
enablement: [
|
|
124
129
|
'Set CODEX_CLOUD_ENV_ID to a valid Codex Cloud environment id.',
|
|
125
130
|
'Optional: set CODEX_CLOUD_BRANCH (must exist on origin).',
|
|
126
131
|
'Then run a pipeline stage in cloud mode with: codex-orchestrator start <pipeline> --cloud --target <stage-id>',
|
|
127
|
-
'
|
|
132
|
+
'Cloud fallback is a compatibility safety net; prefer fail-fast lanes with CODEX_ORCHESTRATOR_CLOUD_FALLBACK=deny.',
|
|
133
|
+
'If cloud preflight fails and fallback is allowed, CO falls back to mcp and records the reason in manifest.summary (surfaced in start output).'
|
|
128
134
|
]
|
|
129
135
|
},
|
|
130
136
|
delegation: {
|
|
@@ -151,7 +157,26 @@ export async function runDoctorCloudPreflight(options = {}) {
|
|
|
151
157
|
?? normalizeOptionalString(env.MCP_RUNNER_TASK_ID)
|
|
152
158
|
?? normalizeOptionalString(env.TASK)
|
|
153
159
|
?? normalizeOptionalString(env.CODEX_ORCHESTRATOR_TASK_ID);
|
|
154
|
-
const
|
|
160
|
+
const explicitEnvironmentId = normalizeOptionalString(options.environmentId);
|
|
161
|
+
const strictRepoConfigRequired = isRepoConfigRequired(env);
|
|
162
|
+
let planMetadataEnvironmentId = null;
|
|
163
|
+
let planMetadataIssue = null;
|
|
164
|
+
if (!explicitEnvironmentId || strictRepoConfigRequired) {
|
|
165
|
+
try {
|
|
166
|
+
planMetadataEnvironmentId = await resolvePlanMetadataCloudEnvironmentId(repoRoot, taskId, env);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (strictRepoConfigRequired) {
|
|
170
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
171
|
+
planMetadataIssue = {
|
|
172
|
+
code: 'pipeline_resolution_failed',
|
|
173
|
+
message: `Pipeline resolution failed during doctor cloud preflight: ${detail}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const environmentId = explicitEnvironmentId
|
|
179
|
+
?? planMetadataEnvironmentId
|
|
155
180
|
?? normalizeOptionalString(env.CODEX_CLOUD_ENV_ID)
|
|
156
181
|
?? resolveTaskMetadataCloudEnvironmentId(repoRoot, taskId);
|
|
157
182
|
const branch = normalizeOptionalBranch(options.branch) ?? normalizeOptionalBranch(env.CODEX_CLOUD_BRANCH);
|
|
@@ -162,15 +187,16 @@ export async function runDoctorCloudPreflight(options = {}) {
|
|
|
162
187
|
branch,
|
|
163
188
|
env
|
|
164
189
|
});
|
|
165
|
-
const
|
|
190
|
+
const issues = planMetadataIssue ? [planMetadataIssue, ...preflight.issues] : preflight.issues;
|
|
191
|
+
const guidance = buildCloudPreflightGuidance(issues);
|
|
166
192
|
return {
|
|
167
|
-
ok: preflight.ok,
|
|
193
|
+
ok: preflight.ok && planMetadataIssue === null,
|
|
168
194
|
details: {
|
|
169
195
|
codex_bin: preflight.details.codexBin,
|
|
170
196
|
environment_id: preflight.details.environmentId,
|
|
171
197
|
branch: preflight.details.branch
|
|
172
198
|
},
|
|
173
|
-
issues
|
|
199
|
+
issues,
|
|
174
200
|
guidance
|
|
175
201
|
};
|
|
176
202
|
}
|
|
@@ -283,6 +309,7 @@ export function formatDoctorSummary(result) {
|
|
|
283
309
|
lines.push(`Cloud: ${result.cloud.status}`);
|
|
284
310
|
lines.push(` - CODEX_CLOUD_ENV_ID: ${result.cloud.env_id_configured ? 'set' : 'missing'}`);
|
|
285
311
|
lines.push(` - CODEX_CLOUD_BRANCH: ${result.cloud.branch ?? '<unset>'}`);
|
|
312
|
+
lines.push(` - fallback policy: ${result.cloud.fallback_policy}`);
|
|
286
313
|
for (const line of result.cloud.enablement) {
|
|
287
314
|
lines.push(` - ${line}`);
|
|
288
315
|
}
|
|
@@ -310,6 +337,53 @@ function normalizeOptionalBranch(value) {
|
|
|
310
337
|
const normalized = normalizeOptionalString(value);
|
|
311
338
|
return normalized ? normalized.replace(/^refs\/heads\//u, '') : null;
|
|
312
339
|
}
|
|
340
|
+
function resolveCloudFallbackPolicy(env = process.env) {
|
|
341
|
+
const raw = normalizeOptionalString(env.CODEX_ORCHESTRATOR_CLOUD_FALLBACK);
|
|
342
|
+
if (!raw) {
|
|
343
|
+
return 'allow';
|
|
344
|
+
}
|
|
345
|
+
const normalized = raw.toLowerCase();
|
|
346
|
+
if (['0', 'false', 'off', 'deny', 'disabled', 'never', 'strict'].includes(normalized)) {
|
|
347
|
+
return 'deny';
|
|
348
|
+
}
|
|
349
|
+
return 'allow';
|
|
350
|
+
}
|
|
351
|
+
async function resolvePlanMetadataCloudEnvironmentId(repoRoot, taskId, processEnv = process.env) {
|
|
352
|
+
const env = {
|
|
353
|
+
repoRoot,
|
|
354
|
+
runsRoot: join(repoRoot, '.runs'),
|
|
355
|
+
outRoot: join(repoRoot, 'out'),
|
|
356
|
+
taskId: taskId ?? '0101'
|
|
357
|
+
};
|
|
358
|
+
const resolver = new PipelineResolver();
|
|
359
|
+
const resolution = await resolver.resolve(env, { quiet: true, processEnv });
|
|
360
|
+
const planner = new CommandPlanner(resolution.pipeline);
|
|
361
|
+
const context = {
|
|
362
|
+
id: env.taskId,
|
|
363
|
+
title: env.taskId,
|
|
364
|
+
metadata: {}
|
|
365
|
+
};
|
|
366
|
+
const plan = await planner.plan(context);
|
|
367
|
+
const selected = plan.items.find((item) => item.id === plan.targetId)
|
|
368
|
+
?? plan.items[0]
|
|
369
|
+
?? null;
|
|
370
|
+
if (!selected || !selected.metadata || typeof selected.metadata !== 'object') {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
return resolveCloudEnvironmentIdFromMetadata(selected.metadata);
|
|
374
|
+
}
|
|
375
|
+
function resolveCloudEnvironmentIdFromMetadata(metadata) {
|
|
376
|
+
const stagePlan = metadata.plan && typeof metadata.plan === 'object'
|
|
377
|
+
? metadata.plan
|
|
378
|
+
: null;
|
|
379
|
+
const candidates = [
|
|
380
|
+
normalizeOptionalString(typeof stagePlan?.cloudEnvId === 'string' ? stagePlan.cloudEnvId : null),
|
|
381
|
+
normalizeOptionalString(typeof stagePlan?.cloud_env_id === 'string' ? stagePlan.cloud_env_id : null),
|
|
382
|
+
normalizeOptionalString(typeof metadata.cloudEnvId === 'string' ? metadata.cloudEnvId : null),
|
|
383
|
+
normalizeOptionalString(typeof metadata.cloud_env_id === 'string' ? metadata.cloud_env_id : null)
|
|
384
|
+
];
|
|
385
|
+
return candidates.find((value) => Boolean(value)) ?? null;
|
|
386
|
+
}
|
|
313
387
|
function resolveTaskMetadataCloudEnvironmentId(repoRoot, taskId) {
|
|
314
388
|
if (!taskId) {
|
|
315
389
|
return null;
|
|
@@ -382,6 +456,9 @@ function buildCloudPreflightGuidance(issues) {
|
|
|
382
456
|
case 'git_unavailable':
|
|
383
457
|
guidance.push('Install git or run with CODEX_CLOUD_BRANCH unset to skip remote branch verification.');
|
|
384
458
|
break;
|
|
459
|
+
case 'pipeline_resolution_failed':
|
|
460
|
+
guidance.push('Fix pipeline/config resolution errors before cloud runs (run `codex-orchestrator init codex`).');
|
|
461
|
+
break;
|
|
385
462
|
default:
|
|
386
463
|
break;
|
|
387
464
|
}
|