fix(code-queue): return terminal read summaries

Merge PR #92 after rebasing onto current master and validating focused Code Queue CLI contracts. Adds bounded terminal review data to codex read without prompt/tool log disclosure.
This commit is contained in:
Lyon
2026-05-23 15:45:23 +08:00
committed by GitHub
parent 7c039a6f67
commit 7d3b06bd22
5 changed files with 429 additions and 6 deletions
@@ -0,0 +1,242 @@
import { codexReadTaskForTest } from "./src/code-queue";
type JsonRecord = Record<string, unknown>;
type FetchCall = { path: string; init?: { method?: string; body?: unknown } };
const promptSecret = "PROMPT_BODY_SHOULD_NOT_LEAK_FROM_CODEX_READ";
const toolSecret = "TOOL_LOG_SHOULD_NOT_LEAK_FROM_CODEX_READ";
const feedbackSecret = "FEEDBACK_PROMPT_SHOULD_NOT_LEAK_FROM_CODEX_READ";
const referenceSecret = "REFERENCE_INJECTION_BASE_PROMPT_SHOULD_NOT_LEAK_FROM_CODEX_READ";
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function asRecord(value: unknown, label: string): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
return value as JsonRecord;
}
function asArray(value: unknown, label: string): unknown[] {
assertCondition(Array.isArray(value), `${label} must be an array`, value);
return value as unknown[];
}
function taskIdFromPath(path: string): string {
const match = path.match(/\/api\/tasks\/([^/?]+)/u);
return decodeURIComponent(match?.[1] ?? "unknown");
}
function summaryFor(taskId: string, status: "succeeded" | "failed"): JsonRecord {
const failed = status === "failed";
const finalResponse = failed
? "Failure summary: tests did not pass, but the runner reported the exact failing command."
: "Final response: implemented the terminal read fix and validated the focused contract.";
return {
id: taskId,
queueId: "hwlab",
status,
providerId: "D601",
executionMode: "container",
model: "gpt-5.5",
agentPort: "codex",
cwd: "/workspace/unidesk",
reasoningEffort: "medium",
maxAttempts: 2,
currentAttempt: failed ? 2 : 1,
currentMode: failed ? "retry" : "initial",
judgeFailCount: failed ? 2 : 0,
judgeFailRetryLimit: 3,
codexThreadId: `thread_${taskId}`,
activeTurnId: null,
createdAt: "2026-05-22T00:00:00.000Z",
startedAt: "2026-05-22T00:01:00.000Z",
updatedAt: "2026-05-22T00:03:00.000Z",
finishedAt: "2026-05-22T00:03:00.000Z",
timing: { totalMs: 120000 },
initialPrompt: `${promptSecret}\nPlease fix the task.`,
prompt: `${promptSecret}\nPlease fix the task.`,
referenceTaskIds: ["codex_reference_task"],
referenceInjection: {
version: 2,
injectedAt: "2026-05-22T00:00:30.000Z",
basePrompt: referenceSecret,
directReferenceTaskIds: ["codex_reference_task"],
maxRounds: 3,
truncated: false,
itemCount: 1,
items: [{
round: 1,
roundIndex: 0,
taskId: "codex_reference_task",
viaTaskId: null,
status: "succeeded",
providerId: "D601",
executionMode: "container",
model: "gpt-5.5",
cwd: "/workspace/unidesk",
createdAt: "2026-05-21T00:00:00.000Z",
updatedAt: "2026-05-21T00:03:00.000Z",
promptChars: 9999,
finalResponseChars: 1234,
finalResponseAt: "2026-05-21T00:03:00.000Z",
finalResponseSource: "finalResponse",
referenceTaskIds: [],
cliHint: "bun scripts/cli.ts codex task codex_reference_task",
}],
},
lastAssistantMessage: {
at: "2026-05-22T00:03:00.000Z",
seq: 41,
source: "finalResponse",
text: finalResponse,
},
toolSummary: {
count: 3,
returned: 1,
limit: 1,
truncated: true,
items: [{ seq: 39, kind: "ran", outputPreview: toolSecret }],
},
attempts: [
{
index: failed ? 2 : 1,
mode: failed ? "retry" : "initial",
terminalStatus: failed ? "failed" : "completed",
appServerExitCode: failed ? 1 : 0,
appServerSignal: null,
error: failed ? "focused contract failed" : null,
stderrTail: failed ? "bun scripts/code-queue-cli-read-terminal-contract-test.ts failed" : "",
startedAt: "2026-05-22T00:01:00.000Z",
finishedAt: "2026-05-22T00:03:00.000Z",
outputStartSeq: 1,
outputEndSeq: 42,
finalResponse,
finalResponsePreview: finalResponse,
finalResponseChars: finalResponse.length,
feedbackPromptPreview: feedbackSecret,
judge: failed ? { decision: "fail", confidence: 0.88, reason: "contract failed" } : { decision: "complete", confidence: 0.97, reason: "verified" },
runnerErrorClassification: failed ? { class: "test-failure", retryable: false } : null,
},
],
lastJudge: failed ? { decision: "fail", confidence: 0.88, reason: "contract failed", source: "minimax" } : { decision: "complete", confidence: 0.97, reason: "verified", source: "minimax" },
lastError: failed ? "focused contract failed" : null,
cancelRequested: false,
transcriptCount: 12,
transcriptMaxSeq: 42,
outputCount: 42,
retainedOutputCount: 20,
outputMaxSeq: 42,
eventCount: 5,
};
}
function readTerminalFixture(calls: FetchCall[]): (path: string, init?: { method?: string; body?: unknown }) => unknown {
return (path, init) => {
calls.push({ path, init });
const taskId = taskIdFromPath(path);
const status = taskId.includes("failed") ? "failed" : "succeeded";
if (path.includes("/summary")) {
return { ok: true, status: 200, body: { ok: true, summary: summaryFor(taskId, status) } };
}
if (path.includes("/read")) {
return {
ok: true,
status: 200,
body: {
ok: true,
task: {
id: taskId,
queueId: "hwlab",
status,
readAt: "2026-05-22T00:04:00.000Z",
terminalUnread: false,
},
queue: { counts: { [status]: 1 }, unreadTerminal: 0 },
},
};
}
throw new Error(`unexpected path ${path}`);
};
}
function missingTaskFixture(calls: FetchCall[]): (path: string, init?: { method?: string; body?: unknown }) => unknown {
return (path, init) => {
calls.push({ path, init });
return { ok: true, status: 404, body: { ok: false, error: "task not found" } };
};
}
function assertTerminalReadShape(result: unknown, taskId: string, status: "succeeded" | "failed"): void {
const data = asRecord(result, "result");
const task = asRecord(data.task, "task");
const finalResponse = asRecord(task.finalResponse, "finalResponse");
const attempts = asRecord(task.attempts, "attempts");
const lastAttempt = asRecord(attempts.lastAttempt, "lastAttempt");
const read = asRecord(data.read, "read");
const disclosure = asRecord(task.disclosure, "disclosure");
const body = JSON.stringify(result);
assertCondition(task.id === taskId, "read result must preserve task id", task);
assertCondition(task.queueId === "hwlab", "read result must preserve queue id", task);
assertCondition(task.status === status, "read result must preserve terminal status", task);
assertCondition(task.model === "gpt-5.5" && task.providerId === "D601" && task.cwd === "/workspace/unidesk", "read result must preserve stable execution metadata", task);
assertCondition(task.createdAt === "2026-05-22T00:00:00.000Z", "read result must include createdAt", task);
assertCondition(task.startedAt === "2026-05-22T00:01:00.000Z", "read result must include startedAt", task);
assertCondition(task.updatedAt === "2026-05-22T00:03:00.000Z", "read result must include updatedAt", task);
assertCondition(task.finishedAt === "2026-05-22T00:03:00.000Z", "read result must include finishedAt", task);
assertCondition(task.readAt === "2026-05-22T00:04:00.000Z" && task.terminalUnread === false, "read result must preserve read acknowledgement", task);
assertCondition(read.marked === true && read.terminalUnread === false, "top-level read acknowledgement must be stable", read);
assertCondition(String(finalResponse.text ?? "").includes(status === "failed" ? "Failure summary" : "Final response"), "read result must include final response text", finalResponse);
assertCondition(finalResponse.chars === String(finalResponse.text ?? "").length && finalResponse.truncated === false, "read result must include bounded final response preview metadata", finalResponse);
assertCondition(lastAttempt.terminalStatus === (status === "failed" ? "failed" : "completed"), "read result must include terminal attempt summary", lastAttempt);
assertCondition(disclosure.promptIncluded === false && disclosure.toolLogsIncluded === false && disclosure.finalResponseIncluded === true, "read disclosure policy must be explicit", disclosure);
const commands = asRecord(task.commands, "task.commands");
assertCondition(String(commands.detail ?? "") === `bun scripts/cli.ts codex task ${taskId} --detail`, "read result must include detail drill-down command", commands);
assertCondition(String(commands.trace ?? "").includes(`codex task ${taskId} --trace`), "read result must include trace drill-down command", commands);
assertCondition(String(commands.output ?? "").includes(`codex output ${taskId}`), "read result must include output drill-down command", commands);
assertCondition(!body.includes(promptSecret), "read result must not leak prompt body", body);
assertCondition(!body.includes(toolSecret), "read result must not leak tool logs", body);
assertCondition(!body.includes(feedbackSecret), "read result must not leak feedback prompt body", body);
assertCondition(!body.includes(referenceSecret), "read result must not leak reference injection base prompt", body);
if (status === "failed") {
assertCondition(task.lastError === "focused contract failed", "failed read must include lastError", task);
assertCondition(String(asRecord(lastAttempt.stderrTail, "stderrTail").text ?? "").includes("contract-test"), "failed read must include stderr tail", lastAttempt);
assertCondition(asRecord(lastAttempt.runnerErrorClassification, "runnerErrorClassification").class === "test-failure", "failed read must include runner error classification", lastAttempt);
}
}
function run(): JsonRecord {
const succeededCalls: FetchCall[] = [];
const succeeded = codexReadTaskForTest("codex_succeeded_terminal", readTerminalFixture(succeededCalls));
assertTerminalReadShape(succeeded, "codex_succeeded_terminal", "succeeded");
assertCondition(succeededCalls.length === 2, "succeeded read must fetch summary then mark read", succeededCalls);
assertCondition(succeededCalls[0]?.path.includes("/summary?toolLimit=3") && succeededCalls[1]?.path.includes("/read"), "succeeded read call order must preserve body before mutation", succeededCalls);
assertCondition(succeededCalls[1]?.init?.method === "POST", "read mutation must use POST", succeededCalls);
const failedCalls: FetchCall[] = [];
const failed = codexReadTaskForTest("codex_failed_terminal", readTerminalFixture(failedCalls));
assertTerminalReadShape(failed, "codex_failed_terminal", "failed");
const missingCalls: FetchCall[] = [];
let missingError: Error | null = null;
try {
codexReadTaskForTest("codex_missing_terminal", missingTaskFixture(missingCalls));
} catch (error) {
missingError = error instanceof Error ? error : new Error(String(error));
}
assertCondition(missingError !== null && missingError.message.includes("task not found"), "missing task must fail with task not found", missingError?.message);
assertCondition(missingCalls.length === 1 && missingCalls[0]?.path.includes("/summary"), "missing task must not issue read mutation after failed lookup", missingCalls);
return {
ok: true,
checks: [
"succeeded terminal read returns status, queue, timestamps, final response preview, and drill-down commands from summary before marking read",
"failed terminal read returns final response, lastError, stderr tail, and runner classification",
"missing task fails before issuing a read mutation",
"prompt, tool logs, and feedback prompts stay behind progressive disclosure commands",
],
};
}
process.stdout.write(`${JSON.stringify(run(), null, 2)}\n`);
+4
View File
@@ -33,6 +33,7 @@ const syntaxFiles = [
"scripts/code-queue-cli-disclosure-contract-test.ts",
"scripts/code-queue-cli-steer-test.ts",
"scripts/code-queue-cli-submit-prompt-contract-test.ts",
"scripts/code-queue-cli-read-terminal-contract-test.ts",
"scripts/code-queue-gh-auth-redaction-contract-test.ts",
"scripts/microservice-health-output-contract-test.ts",
"scripts/code-queue-supervisor-disclosure-contract-test.ts",
@@ -311,6 +312,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("scripts/code-queue-runner-skills-contract-test.ts"),
fileItem("scripts/code-queue-cli-disclosure-contract-test.ts"),
fileItem("scripts/code-queue-cli-steer-test.ts"),
fileItem("scripts/code-queue-cli-read-terminal-contract-test.ts"),
fileItem("scripts/code-queue-submit-routing-contract-test.ts"),
fileItem("scripts/code-queue-gh-auth-redaction-contract-test.ts"),
fileItem("scripts/code-queue-supervisor-disclosure-contract-test.ts"),
@@ -352,6 +354,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("code-queue:runner-skills-contract", ["bun", "scripts/code-queue-runner-skills-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:cli-disclosure-contract", ["bun", "scripts/code-queue-cli-disclosure-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:cli-steer-contract", ["bun", "scripts/code-queue-cli-steer-test.ts"], 30_000));
items.push(commandItem("code-queue:read-terminal-contract", ["bun", "scripts/code-queue-cli-read-terminal-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-prompt-contract", ["bun", "scripts/code-queue-cli-submit-prompt-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-routing-contract", ["bun", "scripts/code-queue-submit-routing-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:gh-auth-redaction-contract", ["bun", "scripts/code-queue-gh-auth-redaction-contract-test.ts"], 30_000));
@@ -383,6 +386,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("code-queue:runner-skills-contract", "Code Queue runner skill availability contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:cli-disclosure-contract", "Code Queue CLI disclosure/noise contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:cli-steer-contract", "Code Queue steer CLI contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:read-terminal-contract", "Code Queue terminal read contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-prompt-contract", "Code Queue submit prompt contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-routing-contract", "Code Queue submit routing contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:gh-auth-redaction-contract", "Code Queue GitHub auth output redaction contract is opt-in with script checks", "--scripts-typecheck or --full"));
+177 -4
View File
@@ -3415,19 +3415,192 @@ export function compactSubmitSuccessResponseForTest(body: Record<string, unknown
return compactSubmitSuccessResponse(body, upstream, lock);
}
function codexReadTask(taskId: string): unknown {
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
function compactTerminalReadAttempt(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
return {
upstream: response.upstream,
task: compactTaskMutationResponse(response.body.task),
index: record.index ?? null,
synthetic: record.synthetic ?? false,
label: record.label ?? null,
mode: record.mode ?? null,
terminalStatus: record.terminalStatus ?? null,
appServerExitCode: record.appServerExitCode ?? null,
appServerSignal: record.appServerSignal ?? null,
transportClosedBeforeTerminal: record.transportClosedBeforeTerminal ?? null,
error: record.error ?? null,
stderrTail: textView(asString(record.stderrTail), false, 1200),
startedAt: record.startedAt ?? null,
finishedAt: record.finishedAt ?? null,
startSeq: record.startSeq ?? record.outputStartSeq ?? null,
endSeq: record.endSeq ?? record.outputEndSeq ?? null,
execution: record.execution ?? null,
finalResponse: textView(asString(record.finalResponsePreview ?? record.finalResponse), false, 3000),
judge: record.judge ?? null,
runnerErrorClassification: record.runnerErrorClassification ?? null,
};
}
function compactTerminalReadFinalResponse(summary: Record<string, unknown>, readTask: Record<string, unknown>): Record<string, unknown> {
const lastAssistantMessage = asRecord(summary.lastAssistantMessage);
const text = asString(lastAssistantMessage?.text ?? summary.finalResponse ?? readTask.finalResponse);
return {
at: lastAssistantMessage?.at ?? summary.finishedAt ?? readTask.finishedAt ?? null,
seq: lastAssistantMessage?.seq ?? null,
source: lastAssistantMessage?.source ?? (text.trim().length > 0 ? "finalResponse" : "none"),
...textView(text, false, 6000),
};
}
function compactReadReferenceInjection(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
const items = asArray(record.items);
const returnedItems = items.slice(0, 12);
return {
version: record.version ?? null,
injectedAt: record.injectedAt ?? null,
itemCount: record.itemCount ?? null,
directReferenceTaskIds: record.directReferenceTaskIds ?? [],
maxRounds: record.maxRounds ?? null,
truncated: record.truncated ?? null,
itemsReturned: returnedItems.length,
itemsTruncated: items.length > returnedItems.length,
items: returnedItems.map((item) => {
const itemRecord = asRecord(item) ?? {};
return {
round: itemRecord.round ?? null,
roundIndex: itemRecord.roundIndex ?? null,
taskId: itemRecord.taskId ?? null,
viaTaskId: itemRecord.viaTaskId ?? null,
status: itemRecord.status ?? null,
providerId: itemRecord.providerId ?? null,
executionMode: itemRecord.executionMode ?? null,
model: itemRecord.model ?? null,
cwd: itemRecord.cwd ?? null,
createdAt: itemRecord.createdAt ?? null,
updatedAt: itemRecord.updatedAt ?? null,
promptChars: itemRecord.promptChars ?? null,
finalResponseChars: itemRecord.finalResponseChars ?? null,
finalResponseAt: itemRecord.finalResponseAt ?? null,
finalResponseSource: itemRecord.finalResponseSource ?? null,
referenceTaskIds: itemRecord.referenceTaskIds ?? [],
cliHint: itemRecord.cliHint ?? null,
};
}),
};
}
function compactTerminalReadTask(summary: unknown, readTask: unknown, taskId: string): Record<string, unknown> {
const summaryRecord = asRecord(summary) ?? {};
const readRecord = asRecord(readTask) ?? {};
const id = asString(summaryRecord.id ?? readRecord.id) || taskId;
const attempts = asArray(summaryRecord.attempts);
const lastAttempt = compactTerminalReadAttempt(attempts.at(-1));
const toolSummary = asRecord(summaryRecord.toolSummary) ?? {};
const readAt = readRecord.readAt ?? summaryRecord.readAt ?? null;
const referenceInjection = compactReadReferenceInjection(summaryRecord.referenceInjection ?? readRecord.referenceInjectionSummary);
return {
id,
queueId: summaryRecord.queueId ?? readRecord.queueId ?? null,
status: summaryRecord.status ?? readRecord.status ?? null,
terminalUnread: false,
readAt,
providerId: summaryRecord.providerId ?? readRecord.providerId ?? null,
executionMode: summaryRecord.executionMode ?? readRecord.executionMode ?? null,
executionModeInfo: summaryRecord.executionModeInfo ?? readRecord.executionModeInfo ?? null,
model: summaryRecord.model ?? readRecord.model ?? null,
agentPort: summaryRecord.agentPort ?? readRecord.agentPort ?? null,
agentPortInfo: summaryRecord.agentPortInfo ?? readRecord.agentPortInfo ?? null,
cwd: summaryRecord.cwd ?? readRecord.cwd ?? null,
reasoningEffort: summaryRecord.reasoningEffort ?? readRecord.reasoningEffort ?? null,
maxAttempts: summaryRecord.maxAttempts ?? readRecord.maxAttempts ?? null,
attempts: {
currentAttempt: summaryRecord.currentAttempt ?? readRecord.currentAttempt ?? null,
maxAttempts: summaryRecord.maxAttempts ?? readRecord.maxAttempts ?? null,
currentMode: summaryRecord.currentMode ?? readRecord.currentMode ?? null,
judgeFailCount: summaryRecord.judgeFailCount ?? readRecord.judgeFailCount ?? null,
judgeFailRetryLimit: summaryRecord.judgeFailRetryLimit ?? readRecord.judgeFailRetryLimit ?? null,
count: attempts.length,
lastAttempt,
},
thread: {
codexThreadId: summaryRecord.codexThreadId ?? readRecord.codexThreadId ?? null,
activeTurnId: summaryRecord.activeTurnId ?? readRecord.activeTurnId ?? null,
cancelRequested: summaryRecord.cancelRequested ?? readRecord.cancelRequested ?? null,
},
timing: summaryRecord.timing ?? readRecord.timing ?? null,
createdAt: summaryRecord.createdAt ?? readRecord.createdAt ?? null,
startedAt: summaryRecord.startedAt ?? readRecord.startedAt ?? null,
updatedAt: summaryRecord.updatedAt ?? readRecord.updatedAt ?? null,
finishedAt: summaryRecord.finishedAt ?? readRecord.finishedAt ?? null,
referenceTaskIds: summaryRecord.referenceTaskIds ?? readRecord.referenceTaskIds ?? [],
referenceInjection,
finalResponse: compactTerminalReadFinalResponse(summaryRecord, readRecord),
lastError: summaryRecord.lastError ?? readRecord.lastError ?? lastAttempt?.error ?? null,
lastJudge: summaryRecord.lastJudge ?? readRecord.lastJudge ?? null,
counts: {
transcript: summaryRecord.transcriptCount ?? readRecord.transcriptCount ?? null,
transcriptMaxSeq: summaryRecord.transcriptMaxSeq ?? readRecord.transcriptMaxSeq ?? null,
output: summaryRecord.outputCount ?? readRecord.outputCount ?? null,
retainedOutput: summaryRecord.retainedOutputCount ?? readRecord.retainedOutputCount ?? null,
outputMaxSeq: summaryRecord.outputMaxSeq ?? readRecord.outputMaxSeq ?? null,
events: summaryRecord.eventCount ?? readRecord.eventCount ?? null,
tools: toolSummary.count ?? null,
},
disclosure: {
mode: "terminal-read",
promptIncluded: false,
toolLogsIncluded: false,
finalResponseIncluded: true,
promptChars: asString(summaryRecord.initialPrompt ?? summaryRecord.prompt).length || (summaryRecord.promptChars ?? null),
toolCount: toolSummary.count ?? null,
policy: "read returns terminal metadata and final response; prompt and tool logs remain behind explicit progressive drill-down commands",
},
commands: {
show: `bun scripts/cli.ts codex task ${id}`,
detail: `bun scripts/cli.ts codex task ${id} --detail`,
full: `bun scripts/cli.ts codex task ${id} --full`,
trace: `bun scripts/cli.ts codex task ${id} --trace --tail --limit ${defaultTraceLimit}`,
output: `bun scripts/cli.ts codex output ${id} --tail --limit ${defaultOutputLimit}`,
},
};
}
function codexReadTaskWithFetcher(taskId: string, fetcher: CodexResponseFetcher): unknown {
const summaryResponse = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: defaultToolLimit })}`)));
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
const readTask = asRecord(response.body.task) ?? {};
const terminalUnread = readTask.terminalUnread ?? readTask.unreadTerminal ?? false;
return {
upstream: {
summary: summaryResponse.upstream,
read: response.upstream,
},
read: {
marked: true,
readAt: readTask.readAt ?? null,
terminalUnread,
},
task: compactTerminalReadTask(summaryResponse.body.summary, response.body.task, taskId),
queue: compactQueueMutationSummary(response.body.queue),
commands: {
show: `bun scripts/cli.ts codex task ${taskId}`,
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
unread: `bun scripts/cli.ts codex tasks --unread --limit ${defaultTasksLimit}`,
},
};
}
function codexReadTask(taskId: string): unknown {
return codexReadTaskWithFetcher(taskId, coreInternalFetch);
}
export function codexReadTaskForTest(taskId: string, fetcher: CodexResponseFetcher): unknown {
return codexReadTaskWithFetcher(taskId, fetcher);
}
function compactQueueMutationSummary(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
+5 -1
View File
@@ -58,7 +58,7 @@ export function rootHelp(): unknown {
{ command: "codex task <taskId> [--detail] [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", description: "Fetch the bounded review view by default; --detail is still capped, while --full/trace/output explicitly expand evidence." },
{ command: "codex tasks [--view supervisor|full] [--queue id] [--status status[,status]] [--unread|--unread-only] [--limit N] [--before-id id]", description: "Show the low-noise supervisor view by default: compact task rows, tiny local sections, activity counts, diagnostics, and drill-down commands; use --view full for detailed rows." },
{ command: "codex output <taskId> [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", description: "Fetch paged raw Code Queue output records; default caps large limits/text previews, --full-text explicitly expands one seq window." },
{ command: "codex read <taskId>", description: "Mark one reviewed terminal task read; never run automatically as part of listing." },
{ 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." },
@@ -271,6 +271,10 @@ function codexHelp(): unknown {
positional: "Keep positional prompt usage to short one-line smoke prompts only.",
sourceRule: "Exactly one prompt source is accepted: positional prompt, --prompt-file, or --prompt-stdin.",
},
readOutput: {
default: "codex read marks a terminal task read and returns terminal metadata, final response, last error/judge, counts, and drill-down commands.",
disclosure: "Full prompt, tool logs, and feedback prompts are not printed by codex read; use codex task/detail/trace/output for progressive disclosure.",
},
examples: {
stdin: [
"cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run",