@kbediako/codex-orchestrator 0.2.0 → 0.2.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.
- package/README.md +43 -83
- package/dist/bin/codex-orchestrator.js +2 -0
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +50 -0
- package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +117 -5
- package/dist/orchestrator/src/cli/coStatusAttachCliShell.js +2 -2
- package/dist/orchestrator/src/cli/coStatusCliShell.js +28 -6
- package/dist/orchestrator/src/cli/codexCliShell.js +48 -1
- package/dist/orchestrator/src/cli/codexDefaultsSetup.js +217 -26
- package/dist/orchestrator/src/cli/control/controlHostSupervision.js +28 -6
- package/dist/orchestrator/src/cli/control/controlRuntime.js +17 -6
- package/dist/orchestrator/src/cli/control/controlStatusDashboard.js +6 -1
- package/dist/orchestrator/src/cli/control/selectedRunProjection.js +49 -2
- package/dist/orchestrator/src/cli/doctor.js +142 -48
- package/dist/orchestrator/src/cli/init.js +94 -1
- package/dist/orchestrator/src/cli/providerLinearChildLaneRunner.js +64 -1
- package/dist/orchestrator/src/cli/providerLinearWorkerRunner.js +1165 -69
- package/dist/orchestrator/src/cli/rlm/alignment.js +3 -3
- package/dist/orchestrator/src/cli/services/commandRunner.js +31 -0
- package/dist/orchestrator/src/cli/utils/cloudPreflight.js +202 -6
- package/dist/orchestrator/src/cli/utils/codexFeatures.js +60 -0
- package/dist/orchestrator/src/manager.js +74 -4
- package/dist/scripts/lib/docs-catalog.js +35 -1
- package/docs/README.md +333 -0
- package/docs/book/README.md +19 -0
- package/docs/book/codex-cli-0124-adoption.md +68 -0
- package/docs/book/local-hook-impact.md +73 -0
- package/docs/book/operations.md +60 -0
- package/docs/book/public-posture.md +34 -0
- package/docs/book/setup.md +91 -0
- package/docs/book/skills.md +11 -0
- package/docs/guides/codex-version-policy.md +104 -0
- package/docs/public/downstream-setup.md +25 -18
- package/package.json +4 -1
- package/plugins/codex-orchestrator/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-orchestrator/launcher.mjs +6 -4
- package/schemas/manifest.json +17 -0
- package/skills/README.md +26 -0
- package/skills/collab-subagents-first/SKILL.md +1 -1
- package/skills/delegation-usage/DELEGATION_GUIDE.md +12 -7
- package/skills/delegation-usage/SKILL.md +13 -8
- package/templates/codex/AGENTS.md +12 -10
|
@@ -46,9 +46,9 @@ export const DEFAULT_ALIGNMENT_POLICY = {
|
|
|
46
46
|
mandatory_turn_window: 20
|
|
47
47
|
},
|
|
48
48
|
route: {
|
|
49
|
-
sentinel_model: 'gpt-5.
|
|
50
|
-
high_reasoning_model: 'gpt-5.
|
|
51
|
-
arbitration_model: 'gpt-5.
|
|
49
|
+
sentinel_model: 'gpt-5.5',
|
|
50
|
+
high_reasoning_model: 'gpt-5.5',
|
|
51
|
+
arbitration_model: 'gpt-5.5',
|
|
52
52
|
high_reasoning_available: true
|
|
53
53
|
}
|
|
54
54
|
};
|
|
@@ -354,6 +354,8 @@ export async function runCommandStage(context, hooks = {}) {
|
|
|
354
354
|
providerLinearWorkerProof = null;
|
|
355
355
|
providerLinearWorkerProofRecord = null;
|
|
356
356
|
}
|
|
357
|
+
manifest.provider_linear_worker_tokens =
|
|
358
|
+
buildProviderLinearWorkerManifestTokenUsage(providerLinearWorkerProof?.tokens) ?? null;
|
|
357
359
|
if (result.status === 'succeeded' && providerLinearWorkerProofRecord === null) {
|
|
358
360
|
providerLinearWorkerFailureReason = 'provider_linear_worker_proof_missing_or_unreadable';
|
|
359
361
|
effectiveSummary = buildProviderLinearWorkerTerminalSummary({
|
|
@@ -773,6 +775,35 @@ async function loadProviderLinearWorkerProof(proofPath) {
|
|
|
773
775
|
return null;
|
|
774
776
|
}
|
|
775
777
|
}
|
|
778
|
+
function buildProviderLinearWorkerManifestTokenUsage(tokens) {
|
|
779
|
+
if (!tokens || typeof tokens !== 'object') {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
const inputTokens = normalizeManifestTokenCount(tokens.input_tokens);
|
|
783
|
+
const outputTokens = normalizeManifestTokenCount(tokens.output_tokens);
|
|
784
|
+
const totalTokens = normalizeManifestTokenCount(tokens.total_tokens);
|
|
785
|
+
const reasoningOutputTokens = normalizeManifestTokenCount(tokens.reasoning_output_tokens);
|
|
786
|
+
if (inputTokens === null &&
|
|
787
|
+
outputTokens === null &&
|
|
788
|
+
totalTokens === null &&
|
|
789
|
+
reasoningOutputTokens === null) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
const usage = {
|
|
793
|
+
input_tokens: inputTokens,
|
|
794
|
+
output_tokens: outputTokens,
|
|
795
|
+
total_tokens: totalTokens
|
|
796
|
+
};
|
|
797
|
+
if (reasoningOutputTokens !== null) {
|
|
798
|
+
usage.reasoning_output_tokens = reasoningOutputTokens;
|
|
799
|
+
}
|
|
800
|
+
return usage;
|
|
801
|
+
}
|
|
802
|
+
function normalizeManifestTokenCount(value) {
|
|
803
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
804
|
+
? Math.max(0, Math.trunc(value))
|
|
805
|
+
: null;
|
|
806
|
+
}
|
|
776
807
|
function coerceTelemetryString(value) {
|
|
777
808
|
if (typeof value !== 'string') {
|
|
778
809
|
return null;
|
|
@@ -2,14 +2,32 @@ import process from 'node:process';
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { fingerprintAuthProvenanceValue } from './authProvenanceFingerprint.js';
|
|
4
4
|
import { resolveCodexCliBin } from './codexCli.js';
|
|
5
|
+
function formatCommandSpawnError(error) {
|
|
6
|
+
if (error instanceof Error) {
|
|
7
|
+
const errno = error;
|
|
8
|
+
const code = typeof errno.code === 'string' ? errno.code : null;
|
|
9
|
+
if (code && !error.message.includes(code)) {
|
|
10
|
+
return `${code}: ${error.message}`;
|
|
11
|
+
}
|
|
12
|
+
return error.message;
|
|
13
|
+
}
|
|
14
|
+
return String(error);
|
|
15
|
+
}
|
|
5
16
|
function runCommand(command, args, options) {
|
|
6
17
|
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
7
18
|
return new Promise((resolve) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
let child;
|
|
20
|
+
try {
|
|
21
|
+
child = spawn(command, args, {
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
env: options.env,
|
|
24
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
resolve({ exitCode: 1, stdout: '', stderr: formatCommandSpawnError(error) });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
13
31
|
let stdout = '';
|
|
14
32
|
let stderr = '';
|
|
15
33
|
let settled = false;
|
|
@@ -39,7 +57,7 @@ function runCommand(command, args, options) {
|
|
|
39
57
|
}
|
|
40
58
|
settled = true;
|
|
41
59
|
clearTimeout(timer);
|
|
42
|
-
resolve({ exitCode: 1, stdout, stderr: `${stderr}\n${error
|
|
60
|
+
resolve({ exitCode: 1, stdout, stderr: `${stderr}\n${formatCommandSpawnError(error)}`.trim() });
|
|
43
61
|
});
|
|
44
62
|
child.once('close', (code) => {
|
|
45
63
|
if (settled) {
|
|
@@ -62,6 +80,169 @@ function normalizeCloudPreflightRequestValue(raw) {
|
|
|
62
80
|
const trimmed = String(raw ?? '').trim();
|
|
63
81
|
return trimmed.length > 0 ? trimmed : null;
|
|
64
82
|
}
|
|
83
|
+
function readCloudPreflightCommandOutput(result) {
|
|
84
|
+
const output = [result.stderr, result.stdout]
|
|
85
|
+
.map((value) => value.trim())
|
|
86
|
+
.filter((value) => value.length > 0)
|
|
87
|
+
.join(' ')
|
|
88
|
+
.replace(/\s+/gu, ' ')
|
|
89
|
+
.trim();
|
|
90
|
+
return output || 'no output';
|
|
91
|
+
}
|
|
92
|
+
function readCloudPreflightErrorOutput(result) {
|
|
93
|
+
const output = result.stderr.trim().replace(/\s+/gu, ' ');
|
|
94
|
+
return output || 'no output';
|
|
95
|
+
}
|
|
96
|
+
function compactCloudPreflightOutput(output) {
|
|
97
|
+
return output.length > 500 ? `${output.slice(0, 497)}...` : output;
|
|
98
|
+
}
|
|
99
|
+
function redactCloudPreflightOutput(output) {
|
|
100
|
+
return output
|
|
101
|
+
.replace(/\b(?:authorization|bearer)\s*[:=]\s*bearer\s+[^\s,;]+/giu, 'authorization: Bearer <redacted>')
|
|
102
|
+
.replace(/\bbearer\s+[A-Za-z0-9._~+/-]{10,}/giu, 'Bearer <redacted>')
|
|
103
|
+
.replace(/\b(?:sk|sess|eyJ)[A-Za-z0-9._~+/-]{12,}/gu, '<redacted-token>')
|
|
104
|
+
.replace(/\b(?:CODEX|OPENAI|CHATGPT|GITHUB|GH)_[A-Z0-9_]*(?:API_KEY|AUTH_TOKEN|ACCESS_TOKEN|REFRESH_TOKEN|TOKEN|SECRET|PASSWORD)\s*=\s*[^\s,;]+/gu, (match) => `${match.split('=')[0] ?? 'secret'}=<redacted>`)
|
|
105
|
+
.replace(/\b(?:api[_ -]?key|auth[_ -]?token|access[_ -]?token|refresh[_ -]?token|bearer[_ -]?token|password|secret|credential)\s*[:=]\s*[^\s,;]+/giu, (match) => `${match.split(/[:=]/u)[0]?.trim() ?? 'secret'}=<redacted>`)
|
|
106
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu, '<redacted-email>');
|
|
107
|
+
}
|
|
108
|
+
function compactCloudPreflightCommandOutput(result) {
|
|
109
|
+
return compactCloudPreflightOutput(redactCloudPreflightOutput(readCloudPreflightCommandOutput(result)));
|
|
110
|
+
}
|
|
111
|
+
function escapeRegExpLiteral(value) {
|
|
112
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
113
|
+
}
|
|
114
|
+
function isEnvironmentNotFoundSignal(signal, environmentId) {
|
|
115
|
+
const normalized = signal.toLowerCase();
|
|
116
|
+
const normalizedEnvironmentId = environmentId.toLowerCase();
|
|
117
|
+
if (normalized.includes('environment_not_found')) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
const escapedEnvironmentId = escapeRegExpLiteral(normalizedEnvironmentId);
|
|
121
|
+
return new RegExp(`\\benvironment\\s+(?:(["'])${escapedEnvironmentId}\\1|${escapedEnvironmentId})\\s+not\\s+found\\b`, 'u').test(normalized);
|
|
122
|
+
}
|
|
123
|
+
function maskCloudPreflightEnvironmentIdentifierValues(signal) {
|
|
124
|
+
return signal
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.replace(/\benvironment\s+(?:['"][^'"]+['"]|[^\s'"]+)\s+not\s+found\b/gu, 'environment <env-id> not found')
|
|
127
|
+
.replace(/\bcodex_cloud_env_id\s+(?:['"][^'"]+['"]|[^\s'"]+)/gu, 'codex_cloud_env_id <env-id>')
|
|
128
|
+
.replace(/\bcodex cloud env id\s+(?:['"][^'"]+['"]|[^\s'"]+)/gu, 'codex cloud env id <env-id>');
|
|
129
|
+
}
|
|
130
|
+
function hasWrappedEnvironmentProbeUnavailableSignal(signal) {
|
|
131
|
+
const normalized = maskCloudPreflightEnvironmentIdentifierValues(signal);
|
|
132
|
+
return (/\b(?:missing[_ -]github[_ -]connector[_ -]link|github connection not found|github connector not found)\b/u.test(normalized) ||
|
|
133
|
+
/\b(?:cloud denial|cloud-denial|cloud_denial|cloud denied|not allowed in cloud|cloud access denied|cloud execution denied)\b/u.test(normalized) ||
|
|
134
|
+
/\b(?:forbidden|unauthorized|not logged in|login required|active account|active profile|account mismatch|profile mismatch|invalid token|expired token|token expired|missing token|token missing|api key|auth token|access token|refresh token|bearer token)\b/u.test(normalized) ||
|
|
135
|
+
/\b(?:rate limit|rate-limit|rate_limited|rate_limit_exceeded|quota|too many requests|usage limit|usage_limit_reached)\b/u.test(normalized) ||
|
|
136
|
+
/\b(?:enotfound|econn|network|timed out|timeout|502|503|504|bad gateway|service unavailable|gateway timeout)\b/u.test(normalized));
|
|
137
|
+
}
|
|
138
|
+
function buildEnvironmentProbeIssue(environmentId, result) {
|
|
139
|
+
const fullDetail = readCloudPreflightCommandOutput(result);
|
|
140
|
+
const detail = compactCloudPreflightCommandOutput(result);
|
|
141
|
+
if (isEnvironmentNotFoundSignal(fullDetail, environmentId) &&
|
|
142
|
+
!hasWrappedEnvironmentProbeUnavailableSignal(fullDetail)) {
|
|
143
|
+
return {
|
|
144
|
+
code: 'environment_not_found',
|
|
145
|
+
message: `Configured CODEX_CLOUD_ENV_ID '${environmentId}' is not visible to codex cloud before codex cloud exec: ${detail}`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
code: 'environment_unavailable',
|
|
150
|
+
message: `Configured CODEX_CLOUD_ENV_ID '${environmentId}' could not be verified by codex cloud before codex cloud exec: ${detail}`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function tryParseCloudListJson(stdout) {
|
|
154
|
+
const trimmed = stdout.trim();
|
|
155
|
+
if (!trimmed) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(trimmed);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function readCloudListTasks(payload) {
|
|
166
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const record = payload;
|
|
170
|
+
return Array.isArray(record.tasks) ? record.tasks : null;
|
|
171
|
+
}
|
|
172
|
+
function readCloudListTaskEnvironmentIdentities(task) {
|
|
173
|
+
if (!task || typeof task !== 'object') {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
const record = task;
|
|
177
|
+
const identities = [];
|
|
178
|
+
for (const key of ['environment_id', 'environmentId', 'cloud_environment_id', 'cloudEnvId']) {
|
|
179
|
+
const value = normalizeCloudPreflightRequestValue(record[key]);
|
|
180
|
+
if (value) {
|
|
181
|
+
identities.push(value);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const key of ['environment_label', 'environmentLabel']) {
|
|
185
|
+
const value = normalizeCloudPreflightRequestValue(record[key]);
|
|
186
|
+
if (value) {
|
|
187
|
+
identities.push(value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const environment = record.environment;
|
|
191
|
+
if (environment && typeof environment === 'object') {
|
|
192
|
+
const environmentRecord = environment;
|
|
193
|
+
for (const key of ['id', 'environment_id', 'environmentId', 'cloud_environment_id', 'cloudEnvId']) {
|
|
194
|
+
const value = normalizeCloudPreflightRequestValue(environmentRecord[key]);
|
|
195
|
+
if (value) {
|
|
196
|
+
identities.push(value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const key of ['label', 'environment_label', 'environmentLabel']) {
|
|
200
|
+
const value = normalizeCloudPreflightRequestValue(environmentRecord[key]);
|
|
201
|
+
if (value) {
|
|
202
|
+
identities.push(value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return [...new Set(identities)];
|
|
207
|
+
}
|
|
208
|
+
function normalizeCloudEnvironmentIdentity(value) {
|
|
209
|
+
return value.trim().toLowerCase();
|
|
210
|
+
}
|
|
211
|
+
function buildEnvironmentProbePayloadIssue(environmentId, detail) {
|
|
212
|
+
const safeDetail = compactCloudPreflightOutput(redactCloudPreflightOutput(detail));
|
|
213
|
+
return {
|
|
214
|
+
code: 'environment_unavailable',
|
|
215
|
+
message: `Configured CODEX_CLOUD_ENV_ID '${environmentId}' could not be verified by codex cloud before codex cloud exec: ${safeDetail}`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function inspectSuccessfulEnvironmentProbe(environmentId, result) {
|
|
219
|
+
const stderrDetail = readCloudPreflightErrorOutput(result);
|
|
220
|
+
if (isEnvironmentNotFoundSignal(stderrDetail, environmentId)) {
|
|
221
|
+
return buildEnvironmentProbeIssue(environmentId, {
|
|
222
|
+
...result,
|
|
223
|
+
stdout: ''
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const payload = tryParseCloudListJson(result.stdout);
|
|
227
|
+
const tasks = readCloudListTasks(payload);
|
|
228
|
+
if (!tasks) {
|
|
229
|
+
return buildEnvironmentProbePayloadIssue(environmentId, `unexpected codex cloud list JSON payload: ${compactCloudPreflightOutput(result.stdout.trim() || 'no output')}`);
|
|
230
|
+
}
|
|
231
|
+
const taskEnvironmentIdentities = tasks.map((task) => readCloudListTaskEnvironmentIdentities(task));
|
|
232
|
+
const normalizedEnvironmentId = normalizeCloudEnvironmentIdentity(environmentId);
|
|
233
|
+
if (taskEnvironmentIdentities.some((identities) => identities.length === 0)) {
|
|
234
|
+
return buildEnvironmentProbePayloadIssue(environmentId, 'codex cloud list returned task rows without an environment identity');
|
|
235
|
+
}
|
|
236
|
+
const mismatchedEnvironmentIds = taskEnvironmentIdentities
|
|
237
|
+
.filter((identities) => !identities.some((identity) => normalizeCloudEnvironmentIdentity(identity) === normalizedEnvironmentId))
|
|
238
|
+
.flat();
|
|
239
|
+
if (mismatchedEnvironmentIds.length > 0) {
|
|
240
|
+
return buildEnvironmentProbePayloadIssue(environmentId, `codex cloud list returned task rows for a different environment identity: ${[
|
|
241
|
+
...new Set(mismatchedEnvironmentIds)
|
|
242
|
+
].join(', ')}`);
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
65
246
|
function readFirstCloudPreflightEnvValue(env, keys) {
|
|
66
247
|
if (!env) {
|
|
67
248
|
return null;
|
|
@@ -151,6 +332,21 @@ export async function runCloudPreflight(params) {
|
|
|
151
332
|
message: `Codex CLI is unavailable (${params.codexBin} --version failed).`
|
|
152
333
|
});
|
|
153
334
|
}
|
|
335
|
+
if (params.environmentId && codexCheck.exitCode === 0) {
|
|
336
|
+
const environmentCheck = await runCommand(params.codexBin, ['cloud', 'list', '--env', params.environmentId, '--limit', '1', '--json'], {
|
|
337
|
+
cwd: params.repoRoot,
|
|
338
|
+
env: params.env
|
|
339
|
+
});
|
|
340
|
+
if (environmentCheck.exitCode !== 0) {
|
|
341
|
+
issues.push(buildEnvironmentProbeIssue(params.environmentId, environmentCheck));
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
const environmentProbeIssue = inspectSuccessfulEnvironmentProbe(params.environmentId, environmentCheck);
|
|
345
|
+
if (environmentProbeIssue) {
|
|
346
|
+
issues.push(environmentProbeIssue);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
154
350
|
if (branch) {
|
|
155
351
|
const gitCheck = await runCommand('git', ['--version'], { cwd: params.repoRoot, env: params.env });
|
|
156
352
|
if (gitCheck.exitCode !== 0) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
export function readCodexFeatureProbe(codexBin, env = process.env) {
|
|
3
|
+
const result = spawnSync(codexBin, ['features', 'list'], {
|
|
4
|
+
encoding: 'utf8',
|
|
5
|
+
env,
|
|
6
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
7
|
+
timeout: 5000
|
|
8
|
+
});
|
|
9
|
+
const stderr = String(result.stderr ?? '');
|
|
10
|
+
if (result.error || result.status !== 0) {
|
|
11
|
+
return {
|
|
12
|
+
flags: null,
|
|
13
|
+
stderr,
|
|
14
|
+
error: result.error
|
|
15
|
+
? result.error.message
|
|
16
|
+
: `codex features list exited with status ${result.status ?? '<unknown>'}`,
|
|
17
|
+
status: result.status
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const stdout = String(result.stdout ?? '');
|
|
21
|
+
return {
|
|
22
|
+
flags: parseFeatureFlagsFromText(stdout),
|
|
23
|
+
stderr,
|
|
24
|
+
error: null,
|
|
25
|
+
status: result.status
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function codexFeatureProbeRejectsAgentMaxThreads(probe) {
|
|
29
|
+
const text = `${probe.error ?? ''}\n${probe.stderr}`.toLowerCase();
|
|
30
|
+
const mentionsMaxThreads = /\bagents\.max_threads\b/u.test(text) || /\bmax[_\s-]?threads\b/u.test(text);
|
|
31
|
+
return mentionsMaxThreads && /\bmulti_agent_v2\b/u.test(text);
|
|
32
|
+
}
|
|
33
|
+
export function codexFeatureProbeDisablesMultiAgentV2(probe) {
|
|
34
|
+
return probe.flags?.multi_agent_v2 === false;
|
|
35
|
+
}
|
|
36
|
+
function parseFeatureFlagsFromText(stdout) {
|
|
37
|
+
const flags = {};
|
|
38
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
39
|
+
const trimmed = line.trim();
|
|
40
|
+
if (!trimmed) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const tokens = trimmed.split(/\s+/u);
|
|
44
|
+
if (tokens.length < 2) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const name = tokens[0] ?? '';
|
|
48
|
+
const enabledToken = tokens[tokens.length - 1] ?? '';
|
|
49
|
+
if (!name) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (enabledToken === 'true') {
|
|
53
|
+
flags[name] = true;
|
|
54
|
+
}
|
|
55
|
+
else if (enabledToken === 'false') {
|
|
56
|
+
flags[name] = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return flags;
|
|
60
|
+
}
|
|
@@ -160,7 +160,7 @@ export class TaskManager {
|
|
|
160
160
|
const build = await this.executeBuilder(task, plan, target, mode, runId);
|
|
161
161
|
if (!build.success) {
|
|
162
162
|
const skippedTest = this.createSkippedTestResult(build, runId);
|
|
163
|
-
const skippedReview = this.createSkippedReviewResult('build-failed');
|
|
163
|
+
const skippedReview = this.createSkippedReviewResult('build-failed', build);
|
|
164
164
|
const summary = this.createRunSummary(task, mode, plan, build, skippedTest, skippedReview, runId);
|
|
165
165
|
return { target, mode, build, test: skippedTest, review: skippedReview, summary };
|
|
166
166
|
}
|
|
@@ -239,13 +239,27 @@ export class TaskManager {
|
|
|
239
239
|
runId
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
|
-
createSkippedReviewResult(reason) {
|
|
242
|
+
createSkippedReviewResult(reason, build) {
|
|
243
243
|
if (reason === 'build-failed') {
|
|
244
|
+
const prerequisiteStage = this.extractFailedPrerequisiteStage(build);
|
|
245
|
+
if (prerequisiteStage) {
|
|
246
|
+
const artifactPath = this.findFailedStageArtifactPath(build);
|
|
247
|
+
const artifactFeedback = artifactPath ? ` Error artifact: ${artifactPath}.` : '';
|
|
248
|
+
return {
|
|
249
|
+
summary: `Review skipped: prerequisite stage \`${prerequisiteStage}\` failed.`,
|
|
250
|
+
decision: {
|
|
251
|
+
approved: false,
|
|
252
|
+
feedback: `Prerequisite stage \`${prerequisiteStage}\` failed; review skipped.${artifactFeedback}`
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const artifactPath = this.findFailedStageArtifactPath(build);
|
|
257
|
+
const artifactFeedback = artifactPath ? ` Error artifact: ${artifactPath}.` : '';
|
|
244
258
|
return {
|
|
245
259
|
summary: 'Review skipped: build stage failed.',
|
|
246
260
|
decision: {
|
|
247
261
|
approved: false,
|
|
248
|
-
feedback:
|
|
262
|
+
feedback: `Build stage failed; review skipped.${artifactFeedback}`
|
|
249
263
|
}
|
|
250
264
|
};
|
|
251
265
|
}
|
|
@@ -257,6 +271,62 @@ export class TaskManager {
|
|
|
257
271
|
}
|
|
258
272
|
};
|
|
259
273
|
}
|
|
274
|
+
extractFailedPrerequisiteStage(build) {
|
|
275
|
+
const stage = build?.failureStage?.trim();
|
|
276
|
+
const canonicalStage = stage?.toLowerCase();
|
|
277
|
+
if (!stage || (canonicalStage != null && ['build', 'test', 'review'].includes(canonicalStage))) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return stage;
|
|
281
|
+
}
|
|
282
|
+
findFailedStageArtifactPath(build) {
|
|
283
|
+
if (build?.failureArtifactPath) {
|
|
284
|
+
return build.failureArtifactPath;
|
|
285
|
+
}
|
|
286
|
+
const failureStage = build?.failureStage?.trim().toLowerCase();
|
|
287
|
+
if (!failureStage) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
if (['build', 'test', 'review'].includes(failureStage)) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
const stagePathToken = failureStage.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
294
|
+
const stageArtifact = build?.artifacts.find((candidate) => {
|
|
295
|
+
const normalizedPath = candidate.path.replace(/\\/g, '/').toLowerCase();
|
|
296
|
+
const isErrorArtifact = normalizedPath.startsWith('errors/') ||
|
|
297
|
+
normalizedPath.includes('/errors/') ||
|
|
298
|
+
/\berror\b/i.test(candidate.description);
|
|
299
|
+
if (!isErrorArtifact) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return this.artifactMatchesFailedStage(candidate, failureStage, stagePathToken);
|
|
303
|
+
});
|
|
304
|
+
return stageArtifact?.path ?? null;
|
|
305
|
+
}
|
|
306
|
+
artifactMatchesFailedStage(candidate, failureStage, stagePathToken) {
|
|
307
|
+
const normalizedDescription = candidate.description.toLowerCase();
|
|
308
|
+
const parentheticalStages = [...normalizedDescription.matchAll(/\(([^)]+)\)/g)]
|
|
309
|
+
.map((match) => match[1]?.trim() ?? '')
|
|
310
|
+
.filter(Boolean);
|
|
311
|
+
if (parentheticalStages.some((stage) => this.stageTokenMatches(stage, failureStage, stagePathToken))) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
const normalizedPath = candidate.path.replace(/\\/g, '/').toLowerCase();
|
|
315
|
+
return normalizedPath
|
|
316
|
+
.split('/')
|
|
317
|
+
.map((segment) => segment.replace(/\.[^.]+$/, '').replace(/^\d+-/, ''))
|
|
318
|
+
.some((segment) => this.stageTokenMatches(segment, failureStage, stagePathToken));
|
|
319
|
+
}
|
|
320
|
+
stageTokenMatches(candidate, failureStage, stagePathToken) {
|
|
321
|
+
if (candidate === failureStage) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
if (!stagePathToken) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
const candidatePathToken = candidate.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
328
|
+
return candidatePathToken === stagePathToken;
|
|
329
|
+
}
|
|
260
330
|
async executePlanner(task, runId) {
|
|
261
331
|
try {
|
|
262
332
|
const plan = await this.options.planner.plan(task);
|
|
@@ -335,7 +405,7 @@ export class TaskManager {
|
|
|
335
405
|
const cause = error ?? new Error('Planner stage failed without an error payload');
|
|
336
406
|
const build = this.createBuilderErrorResult('planner-unavailable', mode, runId, cause);
|
|
337
407
|
const skippedTest = this.createSkippedTestResult(build, runId);
|
|
338
|
-
const skippedReview = this.createSkippedReviewResult('build-failed');
|
|
408
|
+
const skippedReview = this.createSkippedReviewResult('build-failed', build);
|
|
339
409
|
const summary = this.createRunSummary(task, mode, plan, build, skippedTest, skippedReview, runId);
|
|
340
410
|
this.eventBus.emit({ type: 'run:completed', payload: summary });
|
|
341
411
|
return summary;
|
|
@@ -266,6 +266,7 @@ export async function readCurrentCodexPosture(repoRoot, policy = {}) {
|
|
|
266
266
|
return {
|
|
267
267
|
source_path: '',
|
|
268
268
|
cli_version: null,
|
|
269
|
+
cli_compatibility_versions: [],
|
|
269
270
|
model: null,
|
|
270
271
|
default_runtime: null,
|
|
271
272
|
explorer_fast_model: null,
|
|
@@ -278,15 +279,48 @@ export async function readCurrentCodexPosture(repoRoot, policy = {}) {
|
|
|
278
279
|
/delegated subagent and review surfaces on [^;\n]*; `([^`]+)` is currently unsupported there/i.exec(content)?.[1] ??
|
|
279
280
|
/delegated(?: subagent)?(?: and|\/) review surfaces on [^\n]* validates `([^`]+)`/i.exec(content)?.[1] ??
|
|
280
281
|
null;
|
|
282
|
+
const cliVersion = /Codex CLI\s+\(?`?([0-9]+\.[0-9]+\.[0-9]+)`?\)?/.exec(content)?.[1] ?? null;
|
|
281
283
|
return {
|
|
282
284
|
source_path: sourcePath,
|
|
283
|
-
cli_version:
|
|
285
|
+
cli_version: cliVersion,
|
|
286
|
+
cli_compatibility_versions: extractAllowedCliCompatibilityVersions(content, cliVersion),
|
|
284
287
|
model: /Current model posture(?: is|:)\s*`([^`]+)`/i.exec(content)?.[1] ?? null,
|
|
285
288
|
default_runtime: /Local ([A-Za-z0-9_-]+) remains the expected default runtime path/.exec(content)?.[1] ?? null,
|
|
286
289
|
explorer_fast_model: /explorer_fast[^\n]*`(gpt-[^`]+)`/i.exec(content)?.[1] ?? null,
|
|
287
290
|
unsupported_review_model: unsupportedReviewModel
|
|
288
291
|
};
|
|
289
292
|
}
|
|
293
|
+
function extractAllowedCliCompatibilityVersions(content, currentCliVersion) {
|
|
294
|
+
const versions = new Set();
|
|
295
|
+
const scanContent = extractCurrentPostureContent(content);
|
|
296
|
+
for (const line of scanContent.split(/\r?\n/)) {
|
|
297
|
+
if (!/compatibility/i.test(line) ||
|
|
298
|
+
!/(separately\s+rebaselined|downstream-smoke|release-facing)/i.test(line)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
for (const version of extractCodexCliVersionMentions(line)) {
|
|
302
|
+
if (version !== currentCliVersion) {
|
|
303
|
+
versions.add(version);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return [...versions].sort();
|
|
308
|
+
}
|
|
309
|
+
function extractCurrentPostureContent(content) {
|
|
310
|
+
const lines = content.split(/\r?\n/);
|
|
311
|
+
const startIndex = lines.findIndex((line) => /^##\s+Current Posture\b/i.test(line.trim()));
|
|
312
|
+
if (startIndex === -1) {
|
|
313
|
+
return '';
|
|
314
|
+
}
|
|
315
|
+
const sectionLines = [];
|
|
316
|
+
for (const line of lines.slice(startIndex + 1)) {
|
|
317
|
+
if (/^##\s+/.test(line.trim())) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
sectionLines.push(line);
|
|
321
|
+
}
|
|
322
|
+
return sectionLines.join('\n');
|
|
323
|
+
}
|
|
290
324
|
export function extractCodexCliVersionMentions(content) {
|
|
291
325
|
const results = new Set();
|
|
292
326
|
const patterns = [
|