feat: add codex steer cli

This commit is contained in:
Codex
2026-05-20 08:35:41 +00:00
parent ca74677703
commit 4fdf31a67e
6 changed files with 202 additions and 27 deletions
+90 -23
View File
@@ -53,6 +53,11 @@ interface CodexSubmitOptions {
dryRun: boolean;
}
interface CodexSteerOptions {
prompt: string;
dryRun: boolean;
}
interface CompactTaskMutationResponseOptions {
fullPrompt?: boolean;
}
@@ -83,6 +88,7 @@ interface CodexTasksEntry {
show: string;
trace: string;
output: string;
steer: string;
read: string;
full: string;
};
@@ -966,6 +972,7 @@ function taskWatchEntry(task: Record<string, unknown>, summary: Record<string, u
? summary.traceHint
: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`;
const outputCommand = `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`;
const steerCommand = `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path>`;
return {
taskId,
queueId: asString(task.queueId) || null,
@@ -983,6 +990,7 @@ function taskWatchEntry(task: Record<string, unknown>, summary: Record<string, u
show: typeof summaryCommands?.show === "string" && summaryCommands.show.length > 0 ? summaryCommands.show : showCommand,
trace: typeof summaryCommands?.trace === "string" && summaryCommands.trace.length > 0 ? summaryCommands.trace : traceCommand,
output: outputCommand,
steer: steerCommand,
read: `bun scripts/cli.ts codex read ${taskId}`,
full: `bun scripts/cli.ts codex task ${taskId} --full`,
},
@@ -1527,38 +1535,45 @@ function codexMoveTask(taskId: string, queueId: string): unknown {
return unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/move`), { method: "POST", body: { queueId } }));
}
function promptFromSubmitArgs(args: string[]): string {
function promptFromArgs(args: string[], command: string, valueOptions: Set<string>): string {
const promptFile = optionValue(args, ["--prompt-file", "--file"]);
const promptStdin = hasFlag(args, "--prompt-stdin") || hasFlag(args, "--stdin");
const promptArgs = positionalArgsWithValueOptions(args, new Set([
"--prompt-file",
"--file",
"--queue",
"--queue-id",
"--provider",
"--provider-id",
"--cwd",
"--workdir",
"--model",
"--reasoning-effort",
"--execution-mode",
"--mode",
"--max-attempts",
"--reference-task-id",
"--reference",
"--ref",
]));
const promptArgs = positionalArgsWithValueOptions(args, valueOptions);
const sources = [promptFile !== undefined, promptStdin, promptArgs.length > 0].filter(Boolean).length;
if (sources !== 1) throw new Error("codex submit requires exactly one prompt source: positional prompt, --prompt-file, or --prompt-stdin");
if (sources !== 1) throw new Error(`${command} requires exactly one prompt source: positional prompt, --prompt-file, or --prompt-stdin`);
const text = promptFile !== undefined
? (promptFile === "-" ? readFileSync(0, "utf8") : readFileSync(promptFile, "utf8"))
: promptStdin
? readFileSync(0, "utf8")
: promptArgs.join(" ");
if (text.trim().length === 0) throw new Error("codex submit prompt must not be empty");
if (text.trim().length === 0) throw new Error(`${command} prompt must not be empty`);
return text;
}
const submitPromptValueOptions = new Set([
"--prompt-file",
"--file",
"--queue",
"--queue-id",
"--provider",
"--provider-id",
"--cwd",
"--workdir",
"--model",
"--reasoning-effort",
"--execution-mode",
"--mode",
"--max-attempts",
"--reference-task-id",
"--reference",
"--ref",
]);
const steerPromptValueOptions = new Set([
"--prompt-file",
"--file",
]);
function referenceTaskIdsFromOptions(args: string[]): string[] {
const values = optionValues(args, ["--reference-task-id", "--reference", "--ref"]);
const ids: string[] = [];
@@ -1595,7 +1610,7 @@ function parseSubmitOptions(args: string[]): CodexSubmitOptions {
? positiveIntegerOption(args, ["--max-attempts"], 99, 99)
: undefined;
return {
prompt: promptFromSubmitArgs(args),
prompt: promptFromArgs(args, "codex submit", submitPromptValueOptions),
queueId: optionValue(args, ["--queue", "--queue-id"]),
providerId: optionValue(args, ["--provider-id", "--provider"]),
cwd: optionValue(args, ["--cwd", "--workdir"]),
@@ -1608,6 +1623,17 @@ function parseSubmitOptions(args: string[]): CodexSubmitOptions {
};
}
function parseSteerOptions(args: string[]): CodexSteerOptions {
assertKnownOptions(args, {
flags: ["--prompt-stdin", "--stdin", "--dry-run"],
valueOptions: ["--prompt-file", "--file"],
}, "codex steer");
return {
prompt: promptFromArgs(args, "codex steer", steerPromptValueOptions),
dryRun: hasFlag(args, "--dry-run"),
};
}
function submitPayload(options: CodexSubmitOptions): Record<string, unknown> {
return {
prompt: options.prompt,
@@ -1649,6 +1675,7 @@ function compactTaskMutationResponse(task: unknown, options: CompactTaskMutation
show: `bun scripts/cli.ts codex task ${taskId}`,
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
steer: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path>`,
interrupt: `bun scripts/cli.ts codex interrupt ${taskId}`,
move: `bun scripts/cli.ts codex move ${taskId} --queue <queueId>`,
},
@@ -1752,6 +1779,42 @@ function codexInterruptTask(taskId: string): unknown {
};
}
function codexSteerTask(taskId: string, args: string[]): unknown {
const options = parseSteerOptions(args);
const prompt = textView(options.prompt, true, 3000);
const request = {
path: `/api/tasks/${taskId}/steer`,
method: "POST",
body: { prompt },
};
if (options.dryRun) {
return {
ok: true,
dryRun: true,
request,
commands: {
run: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path>`,
rawProxy: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/${encodeURIComponent(taskId)}/steer --method POST --body-json '{"prompt":"..."}'`,
},
};
}
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/steer`), { method: "POST", body: { prompt: options.prompt } }));
return {
upstream: response.upstream,
steer: {
accepted: true,
prompt,
},
task: compactTaskMutationResponse(response.body.task),
queue: compactQueueMutationSummary(response.body.queue),
commands: {
show: `bun scripts/cli.ts codex task ${taskId}`,
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
},
};
}
export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
const [action = "task", taskIdArg] = args;
if (action === "submit" || action === "enqueue") {
@@ -1798,5 +1861,9 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
return codexInterruptTask(taskId);
}
throw new Error("codex command must be one of: submit, enqueue, task, summary, show, tasks, overview, output, judge, read, mark-read, dev-ready, health, queues, queue list, queue create, queue merge, move, interrupt, cancel");
if (action === "steer") {
const taskId = requireTaskId(taskIdArg, "codex steer");
return codexSteerTask(taskId, args.slice(2));
}
throw new Error("codex command must be one of: submit, enqueue, task, summary, show, tasks, overview, output, judge, read, mark-read, dev-ready, health, queues, queue list, queue create, queue merge, move, steer, interrupt, cancel");
}
+3 -1
View File
@@ -55,6 +55,7 @@ export function rootHelp(): unknown {
{ command: "codex read <taskId>", description: "Mark one reviewed terminal task read; never run automatically as part of listing." },
{ 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]", description: "Push a bounded corrective prompt into a running Code Queue task through the stable private proxy path." },
{ 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; 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." },
@@ -194,7 +195,7 @@ function scheduleHelp(): unknown {
function codexHelp(): unknown {
return {
command: "codex deploy|submit|task|tasks|output|read|dev-ready|judge|interrupt|cancel|queues|queue|move",
command: "codex deploy|submit|task|tasks|output|read|dev-ready|judge|steer|interrupt|cancel|queues|queue|move",
output: "json",
usage: [
"bun scripts/cli.ts codex deploy <commitId> # disabled legacy deployment entry",
@@ -205,6 +206,7 @@ function codexHelp(): unknown {
"bun scripts/cli.ts codex read <taskId>",
"bun scripts/cli.ts codex dev-ready",
"bun scripts/cli.ts codex judge <taskId> --attempt N [--dry-run] [--include-prompt]",
"bun scripts/cli.ts codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run]",
"bun scripts/cli.ts codex interrupt|cancel <taskId>",
"bun scripts/cli.ts codex queues [--full|--all] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>",
],