diff --git a/README.md b/README.md index 458c39fb..acbc868b 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,9 @@ Use it when you want Codex to: > [!NOTE] > Depending on the task and the model you choose these tasks might take a long time and it's generally recommended to force the task to be in the background or move the agent to the background. -It supports `--background`, `--wait`, `--resume`, and `--fresh`. If you omit `--resume` and `--fresh`, the plugin can offer to continue the latest rescue thread for this repo. +It supports `--background`, `--wait`, `--resume`, `--resume-id `, and `--fresh`. If you omit `--resume` and `--fresh`, the plugin can offer to continue the latest rescue thread for this repo. + +Use `--resume-id ` to resume a specific thread by ID. This is useful when managing multiple concurrent threads (e.g., parallel PR reviews) where `--resume` would pick the wrong thread. Examples: @@ -145,6 +147,7 @@ Examples: /codex:rescue investigate why the tests started failing /codex:rescue fix the failing test with the smallest safe patch /codex:rescue --resume apply the top fix from the last run +/codex:rescue --resume-id thread_abc123 continue from the specific thread /codex:rescue --model gpt-5.4-mini --effort medium investigate the flaky integration test /codex:rescue --model spark fix the issue quickly /codex:rescue --background investigate the regression diff --git a/plugins/codex/scripts/codex-companion.mjs b/plugins/codex/scripts/codex-companion.mjs index 35222fd5..67289a54 100644 --- a/plugins/codex/scripts/codex-companion.mjs +++ b/plugins/codex/scripts/codex-companion.mjs @@ -77,7 +77,7 @@ function printUsage() { " node scripts/codex-companion.mjs setup [--enable-review-gate|--disable-review-gate] [--json]", " node scripts/codex-companion.mjs review [--wait|--background] [--base ] [--scope ]", " node scripts/codex-companion.mjs adversarial-review [--wait|--background] [--base ] [--scope ] [focus text]", - " node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--fresh] [--model ] [--effort ] [prompt]", + " node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--resume-id |--fresh] [--model ] [--effort ] [prompt]", " node scripts/codex-companion.mjs status [job-id] [--all] [--json]", " node scripts/codex-companion.mjs result [job-id] [--json]", " node scripts/codex-companion.mjs cancel [job-id] [--json]" @@ -461,11 +461,13 @@ async function executeTaskRun(request) { const taskMetadata = buildTaskRunMetadata({ prompt: request.prompt, - resumeLast: request.resumeLast + resumeLast: request.resumeLast || Boolean(request.resumeId) }); let resumeThreadId = null; - if (request.resumeLast) { + if (request.resumeId) { + resumeThreadId = request.resumeId; + } else if (request.resumeLast) { const latestThread = await resolveLatestTrackedTaskThread(workspaceRoot, { excludeJobId: request.jobId }); @@ -598,7 +600,7 @@ function buildTaskJob(workspaceRoot, taskMetadata, write) { }); } -function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, jobId }) { +function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, resumeId, jobId }) { return { cwd, model, @@ -606,6 +608,7 @@ function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, jobId prompt, write, resumeLast, + resumeId, jobId }; } @@ -731,7 +734,7 @@ async function handleReview(argv) { async function handleTask(argv) { const { options, positionals } = parseCommandInput(argv, { - valueOptions: ["model", "effort", "cwd", "prompt-file"], + valueOptions: ["model", "effort", "cwd", "prompt-file", "resume-id"], booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background"], aliasMap: { m: "model" @@ -744,20 +747,25 @@ async function handleTask(argv) { const effort = normalizeReasoningEffort(options.effort); const prompt = readTaskPrompt(cwd, options, positionals); + const rawResumeId = typeof options["resume-id"] === "string" ? options["resume-id"] : null; + if (rawResumeId && rawResumeId.startsWith("-")) { + throw new Error(`Invalid --resume-id value: "${rawResumeId}". Provide a thread ID, not a flag.`); + } + const resumeId = rawResumeId; const resumeLast = Boolean(options["resume-last"] || options.resume); const fresh = Boolean(options.fresh); - if (resumeLast && fresh) { - throw new Error("Choose either --resume/--resume-last or --fresh."); + if ((resumeLast || resumeId) && fresh) { + throw new Error("Choose either --resume/--resume-last/--resume-id or --fresh."); } const write = Boolean(options.write); const taskMetadata = buildTaskRunMetadata({ prompt, - resumeLast + resumeLast: resumeLast || Boolean(resumeId) }); if (options.background) { ensureCodexAvailable(cwd); - requireTaskRequest(prompt, resumeLast); + requireTaskRequest(prompt, resumeLast || Boolean(resumeId)); const job = buildTaskJob(workspaceRoot, taskMetadata, write); const request = buildTaskRequest({ @@ -767,6 +775,7 @@ async function handleTask(argv) { prompt, write, resumeLast, + resumeId, jobId: job.id }); const { payload } = enqueueBackgroundTask(cwd, job, request); @@ -785,6 +794,7 @@ async function handleTask(argv) { prompt, write, resumeLast, + resumeId, jobId: job.id, onProgress: progress }),