fix: compact terminal codex steer rejection

This commit is contained in:
Codex
2026-05-23 02:49:45 +00:00
parent 10bb228df5
commit f80e8d5d2b
5 changed files with 94 additions and 6 deletions
+40
View File
@@ -200,6 +200,45 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
assertCondition(nestedRecord(exhaustedDiagnostics, ["operatorGuidance"]).rawProxyEquivalentIsFallback === false, "raw proxy equivalent should be diagnostic, not fallback", exhaustedDiagnostics);
assertCondition(String(nestedRecord(exhausted, ["commands"]).rawProxy || "").includes("microservice proxy code-queue /api/tasks/direct_task/steer"), "failure should still expose raw proxy diagnostic command", exhausted);
const terminalPrompt = `${"do not leak ".repeat(40)}tail-secret-marker`;
const terminalRejection = codexSteerTaskForTest("completed_task", [terminalPrompt], () => ({
ok: false,
status: 409,
body: {
ok: false,
error: "task does not have an active steerable turn",
task: {
id: "completed_task",
queueId: "default",
status: "succeeded",
terminalStatus: "completed",
currentAttempt: 1,
updatedAt: "2026-05-22T00:00:00.000Z",
finishedAt: "2026-05-22T00:00:00.000Z",
prompt: `${"hidden task prompt ".repeat(60)}tail`,
output: [{ seq: 1, text: "noisy raw task output" }],
},
},
})) as JsonRecord;
const terminalSteer = nestedRecord(terminalRejection, ["steer"]);
assertCondition(terminalRejection.ok === false, "terminal steer rejection should fail", terminalRejection);
assertCondition(terminalSteer.reason === "task-already-terminal", "terminal steer rejection should use compact terminal reason", terminalSteer);
assertCondition(terminalSteer.status === "succeeded", "terminal steer rejection should expose task status", terminalSteer);
assertCondition(terminalSteer.terminalStatus === "completed", "terminal steer rejection should expose terminal status", terminalSteer);
assertCondition(terminalSteer.lastUpdate === "2026-05-22T00:00:00.000Z", "terminal steer rejection should expose last update", terminalSteer);
assertCondition(terminalSteer.updatedAt === "2026-05-22T00:00:00.000Z", "terminal steer rejection should expose last update time", terminalSteer);
assertCondition(terminalSteer.retryable === false, "terminal steer rejection should not be retryable", terminalSteer);
const terminalCommands = nestedRecord(terminalRejection, ["commands"]);
assertCondition(String(terminalCommands.show || "").includes("codex task completed_task"), "terminal rejection should suggest show command", terminalCommands);
assertCondition(String(terminalCommands.read || "").includes("codex read completed_task"), "terminal rejection should suggest read command", terminalCommands);
assertCondition(String(terminalCommands.followUpSubmit || "").includes("codex submit --prompt-file <path> --reference-task-id completed_task"), "terminal rejection should suggest follow-up submit pattern", terminalCommands);
const terminalJson = JSON.stringify(terminalRejection);
assertCondition(!terminalJson.includes("tail-secret-marker"), "terminal rejection must not echo steer prompt", terminalRejection);
assertCondition(!terminalJson.includes("hidden task prompt"), "terminal rejection must not echo task prompt", terminalRejection);
assertCondition(!terminalJson.includes("noisy raw task output"), "terminal rejection must not echo task output", terminalRejection);
assertCondition(!("request" in terminalRejection), "terminal rejection should omit request preview", terminalRejection);
assertCondition(!("diagnostics" in terminalRejection), "terminal rejection should omit bulky diagnostics", terminalRejection);
return {
ok: true,
checks: [
@@ -216,6 +255,7 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
"successful steer confirms write without echoing prompt",
"steer failure classification is JSON-consumable",
"retryable tunnel aborts are retried with bounded diagnostics",
"terminal steer rejection is compact and actionable",
],
};
}
+50 -2
View File
@@ -621,11 +621,57 @@ function classifySteerFailure(response: unknown, targetPath: string, stableProxy
};
}
function unwrapSteerResponse(response: unknown, targetPath: string, stableProxyPath: string, rawProxyEquivalent: string): { ok: true; upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } | { ok: false; diagnostics: ClassifiedCodexSteerError } {
function unwrapSteerResponse(response: unknown, targetPath: string, stableProxyPath: string, rawProxyEquivalent: string): { ok: true; upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } | { ok: false; diagnostics: ClassifiedCodexSteerError; rawResponse: unknown } {
const record = asRecord(response);
const body = responseBody(record);
if (record?.ok === true && body?.ok === true) return { ok: true, upstream: { ok: record.ok, status: record.status }, body };
return { ok: false, diagnostics: classifySteerFailure(response, targetPath, stableProxyPath, rawProxyEquivalent) };
return { ok: false, diagnostics: classifySteerFailure(response, targetPath, stableProxyPath, rawProxyEquivalent), rawResponse: response };
}
function terminalStatusFromTask(task: Record<string, unknown> | null): string {
const direct = asString(task?.terminalStatus);
if (direct.length > 0) return direct;
const attempts = asArray(task?.attempts).map((item) => asRecord(item)).filter((item): item is Record<string, unknown> => item !== null);
for (let index = attempts.length - 1; index >= 0; index -= 1) {
const status = asString(attempts[index]?.terminalStatus);
if (status.length > 0) return status;
}
return "";
}
function compactTerminalSteerRejection(taskId: string, response: unknown): Record<string, unknown> | null {
const record = asRecord(response);
const body = responseBody(record);
const task = asRecord(body?.task);
const status = asString(task?.status);
if (!isTerminalTaskStatus(status)) return null;
const terminalStatus = terminalStatusFromTask(task);
const lastUpdate = task?.updatedAt ?? task?.finishedAt ?? null;
return {
ok: false,
steer: {
accepted: false,
reason: "task-already-terminal",
taskId,
status,
terminalStatus: terminalStatus || null,
lastUpdate,
updatedAt: task?.updatedAt ?? null,
finishedAt: task?.finishedAt ?? null,
retryable: false,
},
message: `task ${taskId} is already terminal (${status}); codex steer only applies to an active running turn`,
commands: {
show: `bun scripts/cli.ts codex task ${taskId}`,
read: `bun scripts/cli.ts codex read ${taskId}`,
followUpSubmit: `bun scripts/cli.ts codex submit --prompt-file <path> --reference-task-id ${taskId}`,
supervisor: `bun scripts/cli.ts codex tasks --view supervisor --limit ${defaultTasksLimit}`,
},
upstream: {
status: responseStatus(record),
error: asString(body?.error) || null,
},
};
}
function steerSuccessAttempt(attempt: number, durationMs: number, upstream: { ok: unknown; status: unknown }): CodexSteerAttemptSummary {
@@ -4756,6 +4802,8 @@ function codexSteerTask(taskId: string, args: string[], fetcher: CodexResponseFe
}
attempts.push(steerFailureAttempt(attempt, durationMs, response.diagnostics));
failedResponse = response;
const terminalRejection = compactTerminalSteerRejection(taskId, response.rawResponse);
if (terminalRejection !== null) return terminalRejection;
if (!shouldRetrySteerFailure(response.diagnostics, attempt, options.retryAttempts)) break;
sleepSync(options.retryDelayMs);
}
+2 -2
View File
@@ -61,7 +61,7 @@ export function rootHelp(): unknown {
{ command: "codex read <taskId>", description: "Mark one reviewed terminal task read and return terminal metadata plus final response; prompt/tool logs stay behind drill-down commands." },
{ command: "codex dev-ready", description: "Fetch execution-container readiness, including sanitized skill injection status from /api/dev-ready." },
{ command: "codex judge <taskId> --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." },
{ command: "codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N]", description: "Push a corrective prompt into a running Code Queue task; retryable tunnel aborts get bounded retry diagnostics, and real success does not echo prompt text." },
{ command: "codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N]", description: "Push a corrective prompt into a running Code Queue task; retryable tunnel aborts get bounded diagnostics, terminal-task rejection suggests codex task/read plus codex submit --reference-task-id <taskId>, and real success does not echo prompt text." },
{ command: "codex interrupt|cancel <taskId>", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." },
{ command: "codex (queues [--full|--all] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>)", description: "List low-noise queue summaries by default, including effective activity counts that distinguish scheduler-local queues, DB running tasks, and heartbeat-fresh runners; full queue rows require --full/--all." },
{ command: "job list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page." },
@@ -300,7 +300,7 @@ function codexHelp(): unknown {
redline: "data.supervisor.activeRunning.redline names the count field, routine target, burst redline, hard redline, and decisionReady flag.",
limitSemantics: "filters.requestedLimit preserves the user input; filters.limit/effectiveLimit shows the capped query budget; section outputBudget/rowPage show returned-row caps.",
},
description: "Operate Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text.",
description: "Operate Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text; terminal steer rejection returns compact status plus codex task/read/submit follow-up commands.",
};
}