feat: add codex steer cli
This commit is contained in:
+90
-23
@@ -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
@@ -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>",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user