diff --git a/.changeset/summary-verbatim.md b/.changeset/summary-verbatim.md new file mode 100644 index 0000000..511f987 --- /dev/null +++ b/.changeset/summary-verbatim.md @@ -0,0 +1,5 @@ +--- +"@pungrumpy/cursor-action": patch +--- + +Publish the agent's answer verbatim in `summary`. An answer that happened to be a JSON object was previously reduced to its `response`, `summary`, `result`, `output`, or `text` field, a leftover from the CLI's `--output-format json` era; workflows that ask for structured output now receive the whole object. diff --git a/__tests__/output.test.ts b/__tests__/output.test.ts index 1abe630..641add2 100644 --- a/__tests__/output.test.ts +++ b/__tests__/output.test.ts @@ -50,54 +50,15 @@ describe("setOutputs", () => { expect(mockSummaryChain.write).toHaveBeenCalled(); }); - it("extracts summary from JSON response field", async () => { - const result = { - exitCode: 0, - stderr: "", - stdout: JSON.stringify({ response: "JSON response text" }), - }; - const outputs = await setOutputs(result); - expect(outputs.summary).toBe("JSON response text"); - }); - - it("extracts summary from JSON summary field", async () => { - const result = { - exitCode: 0, - stderr: "", - stdout: JSON.stringify({ summary: "Summary field text" }), - }; - const outputs = await setOutputs(result); - expect(outputs.summary).toBe("Summary field text"); - }); - - it("extracts summary from JSON result field", async () => { - const result = { - exitCode: 0, - stderr: "", - stdout: JSON.stringify({ result: "Result field text" }), - }; - const outputs = await setOutputs(result); - expect(outputs.summary).toBe("Result field text"); - }); - - it("extracts summary from JSON output field", async () => { - const result = { - exitCode: 0, - stderr: "", - stdout: JSON.stringify({ output: "Output field text" }), - }; - const outputs = await setOutputs(result); - expect(outputs.summary).toBe("Output field text"); - }); - - it("extracts summary from JSON text field", async () => { - const result = { - exitCode: 0, - stderr: "", - stdout: JSON.stringify({ text: "Text field value" }), - }; - const outputs = await setOutputs(result); - expect(outputs.summary).toBe("Text field value"); + it("passes a JSON-shaped answer through unchanged", async () => { + const stdout = JSON.stringify({ + findings: ["hardcoded token in config.ts"], + summary: "only this field used to survive", + }); + const outputs = await setOutputs({ exitCode: 0, stderr: "", stdout }); + + expect(outputs.summary).toBe(stdout); + expect(mockSetOutput).toHaveBeenCalledWith("summary", stdout); }); it("handles empty stdout gracefully", async () => { diff --git a/dist/index.mjs b/dist/index.mjs index 3ca3737..77cefd3 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -34,21 +34,12 @@ const getInputs = () => { }; //#endregion //#region src/output.ts -const parseSummary = (stdout) => { - const trimmed = stdout.trim(); - if (!trimmed) return ""; - try { - const parsed = JSON.parse(trimmed); - if (typeof parsed === "object" && parsed !== null) { - if (typeof parsed.response === "string") return parsed.response.trim(); - if (typeof parsed.summary === "string") return parsed.summary.trim(); - if (typeof parsed.result === "string") return parsed.result.trim(); - if (typeof parsed.output === "string") return parsed.output.trim(); - if (typeof parsed.text === "string") return parsed.text.trim(); - } - } catch {} - return trimmed.replaceAll(/\u001B\[[0-9;]*[mGKHF]/gu, ""); -}; +/** +* The SDK returns the final assistant message as plain text. Only whitespace +* and stray terminal colour codes are removed; a JSON-shaped answer is +* published as-is so callers can parse the whole object. +*/ +const parseSummary = (stdout) => stdout.trim().replaceAll(/\u001B\[[0-9;]*[mGKHF]/gu, ""); const buildSummaryTableRows = (result) => { const rows = [["Status", result.exitCode === 0 ? "✅ Success" : `❌ Failed (exit ${result.exitCode})`], ["Exit Code", String(result.exitCode)]]; if (result.status) rows.push(["Agent Status", result.status]); diff --git a/docs/content/behaviour.mdx b/docs/content/behaviour.mdx index baea40a..e28590f 100644 --- a/docs/content/behaviour.mdx +++ b/docs/content/behaviour.mdx @@ -8,7 +8,7 @@ description: What the action does at runtime and how it reports results. 1. Inputs are validated. An empty `prompt`, an unknown `permissions` value, or a non-positive `timeout` fails the step before anything is sent. 2. The API key is registered with `::add-mask::`. 3. A local SDK agent is created against the resolved `working-directory` (`Agent.create({ local: { cwd } })`) and the prompt is sent. -4. Streamed text is collected, then replaced by the final run result when the SDK provides one. +4. Streamed text is collected, then replaced by the final run result when the SDK provides one. `summary` is that text verbatim, with surrounding whitespace trimmed; a JSON-shaped answer is not unwrapped. 5. A job summary is written with the status, exit code, agent response, duration, token usage, and any stderr or diagnostics. ## Timeouts and failures diff --git a/src/output.ts b/src/output.ts index f2160c6..54f2a91 100644 --- a/src/output.ts +++ b/src/output.ts @@ -2,44 +2,18 @@ import { setOutput, setSecret, summary } from "@actions/core"; import type { ActionOutputs, AgentResult } from "./types"; -const parseSummary = (stdout: string): string => { - const trimmed = stdout.trim(); - if (!trimmed) { - return ""; - } - - try { - const parsed = JSON.parse(trimmed); - if (typeof parsed === "object" && parsed !== null) { - if (typeof parsed.response === "string") { - return parsed.response.trim(); - } - if (typeof parsed.summary === "string") { - return parsed.summary.trim(); - } - if (typeof parsed.result === "string") { - return parsed.result.trim(); - } - if (typeof parsed.output === "string") { - return parsed.output.trim(); - } - if (typeof parsed.text === "string") { - return parsed.text.trim(); - } - } - } catch { - // Not JSON - } - - const clean = trimmed.replaceAll( +/** + * The SDK returns the final assistant message as plain text. Only whitespace + * and stray terminal colour codes are removed; a JSON-shaped answer is + * published as-is so callers can parse the whole object. + */ +const parseSummary = (stdout: string): string => + stdout.trim().replaceAll( // eslint-disable-next-line no-control-regex /\u001B\[[0-9;]*[mGKHF]/gu, "" ); - return clean; -}; - const buildSummaryTableRows = (result: AgentResult): string[][] => { const status = result.exitCode === 0