From 732ae4e46c8c00f46ac49c95a52ed9bf7691720e Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 5 Mar 2026 10:59:49 -0600 Subject: [PATCH] feat(cursor): compact shell tool calls and format results in run log Show only the command for shellToolCall/shell inputs instead of the full payload. Format shell results with exit code + truncated stdout/stderr sections for readability. Co-Authored-By: Claude Opus 4.6 --- .../cursor-local/src/ui/parse-stdout.ts | 70 ++++++++++++++++--- .../__tests__/cursor-local-adapter.test.ts | 65 +++++++++++++++++ 2 files changed, 127 insertions(+), 8 deletions(-) diff --git a/packages/adapters/cursor-local/src/ui/parse-stdout.ts b/packages/adapters/cursor-local/src/ui/parse-stdout.ts index 047a9041..33fd970b 100644 --- a/packages/adapters/cursor-local/src/ui/parse-stdout.ts +++ b/packages/adapters/cursor-local/src/ui/parse-stdout.ts @@ -32,6 +32,46 @@ function stringifyUnknown(value: unknown): string { } } +/** Max chars of stdout/stderr to show in run log for shell tool results. */ +const SHELL_OUTPUT_TRUNCATE = 2000; + +/** + * Format shell tool result for run log: exit code + stdout/stderr (truncated). + * If the result is not a shell-shaped object, returns full stringify. + */ +function formatShellToolResultForLog(result: unknown): string { + const obj = asRecord(result); + if (!obj) return stringifyUnknown(result); + const success = asRecord(obj.success); + if (!success) return stringifyUnknown(result); + const exitCode = asNumber(success.exitCode, NaN); + const stdout = asString(success.stdout).trim(); + const stderr = asString(success.stderr).trim(); + const hasShellShape = Number.isFinite(exitCode) || stdout.length > 0 || stderr.length > 0; + if (!hasShellShape) return stringifyUnknown(result); + + const lines: string[] = []; + if (Number.isFinite(exitCode)) lines.push(`exit ${exitCode}`); + if (stdout) { + const out = stdout.length > SHELL_OUTPUT_TRUNCATE ? stdout.slice(0, SHELL_OUTPUT_TRUNCATE) + "\n... (truncated)" : stdout; + lines.push(""); + lines.push(out); + } + if (stderr) { + const err = stderr.length > SHELL_OUTPUT_TRUNCATE ? stderr.slice(0, SHELL_OUTPUT_TRUNCATE) + "\n... (truncated)" : stderr; + lines.push(""); + lines.push(err); + } + return lines.join("\n"); +} + +/** Return compact input for run log when tool is shell/shellToolCall (command only). */ +function compactShellToolInput(rawInput: unknown, payload?: Record): unknown { + const cmd = asString(payload?.command ?? asRecord(rawInput)?.command); + if (cmd) return { command: cmd }; + return rawInput; +} + function parseUserMessage(messageRaw: unknown, ts: string): TranscriptEntry[] { if (typeof messageRaw === "string") { const text = messageRaw.trim(); @@ -92,11 +132,17 @@ function parseAssistantMessage(messageRaw: unknown, ts: string): TranscriptEntry } if (type === "tool_call") { + const name = asString(part.name, asString(part.tool, "tool")); + const rawInput = part.input ?? part.arguments ?? part.args ?? {}; + const input = + name === "shellToolCall" || name === "shell" + ? compactShellToolInput(rawInput, asRecord(rawInput) ?? undefined) + : rawInput; entries.push({ kind: "tool_call", ts, - name: asString(part.name, asString(part.tool, "tool")), - input: part.input ?? part.arguments ?? part.args ?? {}, + name, + input, }); continue; } @@ -108,11 +154,11 @@ function parseAssistantMessage(messageRaw: unknown, ts: string): TranscriptEntry asString(part.call_id) || asString(part.id) || "tool_result"; + const rawOutput = part.output ?? part.result ?? part.text; const contentText = - asString(part.output) || - asString(part.text) || - asString(part.result) || - stringifyUnknown(part.output ?? part.result ?? part.text ?? part); + typeof rawOutput === "object" && rawOutput !== null + ? formatShellToolResultForLog(rawOutput) + : asString(rawOutput) || stringifyUnknown(rawOutput); const isError = part.is_error === true || asString(part.status).toLowerCase() === "error"; entries.push({ kind: "tool_result", @@ -144,7 +190,9 @@ function parseCursorToolCallEvent(event: Record, ts: string): T return [{ kind: "system", ts, text: `tool_call${subtype ? ` (${subtype})` : ""}` }]; } const payload = asRecord(toolCall[toolName]) ?? {}; - const input = payload.args ?? asRecord(payload.function)?.arguments ?? {}; + const rawInput = payload.args ?? asRecord(payload.function)?.arguments ?? payload; + const isShellTool = toolName === "shellToolCall" || toolName === "shell"; + const input = isShellTool ? compactShellToolInput(rawInput, payload) : rawInput; if (subtype === "started" || subtype === "start") { return [{ @@ -169,11 +217,17 @@ function parseCursorToolCallEvent(event: Record, ts: string): T asString(payload.status).toLowerCase() === "failed" || asString(payload.status).toLowerCase() === "cancelled" || payload.error !== undefined; + const content = + result !== undefined + ? isShellTool + ? formatShellToolResultForLog(result) + : stringifyUnknown(result) + : `${toolName} completed`; return [{ kind: "tool_result", ts, toolUseId: callId, - content: result !== undefined ? stringifyUnknown(result) : `${toolName} completed`, + content, isError, }]; } diff --git a/server/src/__tests__/cursor-local-adapter.test.ts b/server/src/__tests__/cursor-local-adapter.test.ts index 22bb4ba6..258109cb 100644 --- a/server/src/__tests__/cursor-local-adapter.test.ts +++ b/server/src/__tests__/cursor-local-adapter.test.ts @@ -137,6 +137,71 @@ describe("cursor ui stdout parser", () => { ).toEqual([{ kind: "thinking", ts, text: "streamed" }]); }); + it("compacts shellToolCall and shell tool result for run log", () => { + const ts = "2026-03-05T00:00:00.000Z"; + const longCommand = "curl -s -X POST \"$PAPERCLIP_API_URL/api/issues/abc/checkout\" -H \"Authorization: Bearer $PAPERCLIP_API_KEY\""; + + expect( + parseCursorStdoutLine( + JSON.stringify({ + type: "tool_call", + subtype: "started", + call_id: "call_shell_1", + tool_call: { + shellToolCall: { + command: longCommand, + workingDirectory: "/tmp", + timeout: 30000, + toolCallId: "tool_xyz", + simpleCommands: ["curl"], + parsingResult: { parsingFailed: false, executableCommands: [] }, + }, + }, + }), + ts, + ), + ).toEqual([ + { + kind: "tool_call", + ts, + name: "shellToolCall", + input: { command: longCommand }, + }, + ]); + + expect( + parseCursorStdoutLine( + JSON.stringify({ + type: "tool_call", + subtype: "completed", + call_id: "call_shell_1", + tool_call: { + shellToolCall: { + result: { + success: { + command: longCommand, + exitCode: 0, + stdout: '{"id":"abc","status":"in_progress"}', + stderr: "", + executionTime: 100, + }, + }, + }, + }, + }), + ts, + ), + ).toEqual([ + { + kind: "tool_result", + ts, + toolUseId: "call_shell_1", + content: "exit 0\n\n{\"id\":\"abc\",\"status\":\"in_progress\"}", + isError: false, + }, + ]); + }); + it("parses user, top-level thinking, and top-level tool_call events", () => { const ts = "2026-03-05T00:00:00.000Z";