diff --git a/plugins/codex/scripts/codex-companion.mjs b/plugins/codex/scripts/codex-companion.mjs index 83df468ad..1f54e2ce0 100644 --- a/plugins/codex/scripts/codex-companion.mjs +++ b/plugins/codex/scripts/codex-companion.mjs @@ -79,7 +79,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|--fresh] [--model ] [--effort ] [--prompt-file [--prompt-file-consume]] [prompt]", " node scripts/codex-companion.mjs transfer [--source ] [--json]", " node scripts/codex-companion.mjs status [job-id] [--all] [--json]", " node scripts/codex-companion.mjs result [job-id] [--json]", @@ -641,8 +641,18 @@ async function executeTransfer(cwd, options = {}) { } function readTaskPrompt(cwd, options, positionals) { + const consumePromptFile = Boolean(options["prompt-file-consume"]); + if (consumePromptFile && !options["prompt-file"]) { + throw new Error("--prompt-file-consume requires --prompt-file."); + } + if (options["prompt-file"]) { - return fs.readFileSync(path.resolve(cwd, options["prompt-file"]), "utf8"); + const promptPath = path.resolve(cwd, options["prompt-file"]); + const prompt = fs.readFileSync(promptPath, "utf8"); + if (consumePromptFile) { + fs.unlinkSync(promptPath); + } + return prompt; } const positionalPrompt = positionals.join(" "); @@ -762,7 +772,15 @@ async function handleReview(argv) { async function handleTask(argv) { const { options, positionals } = parseCommandInput(argv, { valueOptions: ["model", "effort", "cwd", "prompt-file"], - booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background"], + booleanOptions: [ + "json", + "write", + "resume-last", + "resume", + "fresh", + "background", + "prompt-file-consume" + ], aliasMap: { m: "model" } diff --git a/tests/runtime.test.mjs b/tests/runtime.test.mjs index 8f276835b..b9997bd2d 100644 --- a/tests/runtime.test.mjs +++ b/tests/runtime.test.mjs @@ -716,6 +716,59 @@ test("write task output focuses on the Codex result without generic follow-up hi assert.equal(result.stdout, "Handled the requested task.\nTask prompt accepted.\n"); }); +test("task --prompt-file-consume removes the prompt file after a successful read", () => { + const repo = makeTempDir(); + const binDir = makeTempDir(); + const statePath = path.join(binDir, "fake-codex-state.json"); + installFakeCodex(binDir); + initGitRepo(repo); + const promptPath = path.join(repo, "prompt.txt"); + fs.writeFileSync(promptPath, "inspect the failing test", "utf8"); + + const result = run( + "node", + [SCRIPT, "task", "--prompt-file", promptPath, "--prompt-file-consume"], + { cwd: repo, env: buildEnv(binDir) } + ); + + assert.equal(result.status, 0, result.stderr); + assert.equal(fs.existsSync(promptPath), false); + const fakeState = JSON.parse(fs.readFileSync(statePath, "utf8")); + assert.equal(fakeState.lastTurnStart.prompt, "inspect the failing test"); +}); + +test("task --prompt-file preserves the prompt file without consume", () => { + const repo = makeTempDir(); + const binDir = makeTempDir(); + installFakeCodex(binDir); + initGitRepo(repo); + const promptPath = path.join(repo, "prompt.txt"); + fs.writeFileSync(promptPath, "inspect the failing test", "utf8"); + + const result = run("node", [SCRIPT, "task", "--prompt-file", promptPath], { + cwd: repo, + env: buildEnv(binDir) + }); + + assert.equal(result.status, 0, result.stderr); + assert.equal(fs.existsSync(promptPath), true); +}); + +test("task --prompt-file-consume requires --prompt-file", () => { + const repo = makeTempDir(); + const binDir = makeTempDir(); + installFakeCodex(binDir); + initGitRepo(repo); + + const result = run("node", [SCRIPT, "task", "--prompt-file-consume", "hello"], { + cwd: repo, + env: buildEnv(binDir) + }); + + assert.equal(result.status, 1); + assert.match(result.stderr, /--prompt-file-consume requires --prompt-file\./); +}); + test("task --resume acts like --resume-last without leaking the flag into the prompt", () => { const repo = makeTempDir(); const binDir = makeTempDir();