From 96de5ad40e5a09551745f044247352fe5de3ae08 Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:44:18 +0700 Subject: [PATCH 1/2] fix(output): publish the agent's answer verbatim The unwrapping of response, summary, result, output and text fields was a leftover from the CLI's --output-format json days. The SDK returns the final message as plain text, so a JSON answer lost every field but one. --- .changeset/summary-verbatim.md | 5 +++ __tests__/output.test.ts | 57 ++++++---------------------------- dist/index.mjs | 21 ++++--------- src/output.ts | 40 +++++------------------- 4 files changed, 27 insertions(+), 96 deletions(-) create mode 100644 .changeset/summary-verbatim.md 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/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 From a5e4bf082e61bfcf76acd98abae9aeedb3084e93 Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:44:25 +0700 Subject: [PATCH 2/2] docs(behaviour): note that summary is the answer verbatim --- docs/content/behaviour.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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