From eb7e7cfa34dc30cbcfa110fee57a57d351df9635 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 7 Feb 2026 08:14:06 +0100 Subject: [PATCH] fix(coding-agent/tools): improved Python cell error handling and output display formatting - Improved error handling in Python cell execution to preserve and display combined output from previous cells when an error occurs. - Fixed tab character rendering in Python tool output display to properly format whitespace in cell output and status events. - Enhanced error messages for failed Python cells to include full combined output context instead of just the error message. - Updated error cell output styling to use error color theme instead of standard tool output color for better visual distinction. --- packages/coding-agent/CHANGELOG.md | 9 +++ packages/coding-agent/src/tools/python.ts | 92 +++++++++++++++++++++-- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index bd4b5af2c..9fa2cf8cc 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog ## [Unreleased] +### Changed + +- Enhanced error messages for failed Python cells to include full combined output context instead of just the error message +- Updated error cell output styling to use error color theme instead of standard tool output color for better visual distinction + +### Fixed + +- Improved error handling in Python cell execution to preserve and display combined output from previous cells when an error occurs +- Fixed tab character rendering in Python tool output display to properly format whitespace in cell output and status events ## [11.6.1] - 2026-02-07 ### Fixed diff --git a/packages/coding-agent/src/tools/python.ts b/packages/coding-agent/src/tools/python.ts index d5e3b07ee..98807ed3d 100644 --- a/packages/coding-agent/src/tools/python.ts +++ b/packages/coding-agent/src/tools/python.ts @@ -17,7 +17,7 @@ import type { ToolSession } from "."; import type { OutputMeta } from "./output-meta"; import { allocateOutputArtifact, createTailBuffer } from "./output-utils"; import { resolveToCwd } from "./path-utils"; -import { shortenPath, ToolUIKit, truncateToWidth } from "./render-utils"; +import { replaceTabs, shortenPath, ToolUIKit, truncateToWidth } from "./render-utils"; import { ToolAbortError, ToolError } from "./tool-errors"; import { toolResult } from "./tool-result"; import { DEFAULT_MAX_BYTES } from "./truncate"; @@ -341,18 +341,93 @@ export class PythonTool implements AgentTool { cellResult.status = "error"; pushUpdate(); const errorMsg = result.output || "Command aborted"; - throw new ToolError(cells.length > 1 ? `Cell ${i + 1} aborted: ${errorMsg}` : errorMsg); + const combinedOutput = cellOutputs.join("\n\n"); + const outputText = + cells.length > 1 + ? `${combinedOutput}\n\nCell ${i + 1} aborted: ${errorMsg}` + : combinedOutput || errorMsg; + + const rawSummary = (await finalizeOutput()) ?? { + output: "", + truncated: false, + totalLines: 0, + totalBytes: 0, + outputLines: 0, + outputBytes: 0, + }; + const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0; + const outputBytes = Buffer.byteLength(combinedOutput, "utf-8"); + const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines); + const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes); + const summaryForMeta: OutputSummary = { + output: combinedOutput, + truncated: rawSummary.truncated, + totalLines: outputLines + missingLines, + totalBytes: outputBytes + missingBytes, + outputLines, + outputBytes, + artifactId: rawSummary.artifactId, + }; + + const details: PythonToolDetails = { + cells: cellResults, + jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined, + images: images.length > 0 ? images : undefined, + statusEvents: statusEvents.length > 0 ? statusEvents : undefined, + isError: true, + }; + + return toolResult(details) + .text(outputText) + .truncationFromSummary(summaryForMeta, { direction: "tail" }) + .done(); } if (result.exitCode !== 0 && result.exitCode !== undefined) { cellResult.status = "error"; pushUpdate(); const combinedOutput = cellOutputs.join("\n\n"); - throw new ToolError( + const outputText = cells.length > 1 ? `${combinedOutput}\n\nCell ${i + 1} failed (exit code ${result.exitCode}). Earlier cells succeeded—their state persists. Fix only cell ${i + 1}.` - : `${combinedOutput}\n\nCommand exited with code ${result.exitCode}`, - ); + : combinedOutput + ? `${combinedOutput}\n\nCommand exited with code ${result.exitCode}` + : `Command exited with code ${result.exitCode}`; + + const rawSummary = (await finalizeOutput()) ?? { + output: "", + truncated: false, + totalLines: 0, + totalBytes: 0, + outputLines: 0, + outputBytes: 0, + }; + const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0; + const outputBytes = Buffer.byteLength(combinedOutput, "utf-8"); + const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines); + const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes); + const summaryForMeta: OutputSummary = { + output: combinedOutput, + truncated: rawSummary.truncated, + totalLines: outputLines + missingLines, + totalBytes: outputBytes + missingBytes, + outputLines, + outputBytes, + artifactId: rawSummary.artifactId, + }; + + const details: PythonToolDetails = { + cells: cellResults, + jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined, + images: images.length > 0 ? images : undefined, + statusEvents: statusEvents.length > 0 ? statusEvents : undefined, + isError: true, + }; + + return toolResult(details) + .text(outputText) + .truncationFromSummary(summaryForMeta, { direction: "tail" }) + .done(); } cellResult.status = "complete"; @@ -627,7 +702,7 @@ function formatStatusEventExpanded(event: PythonStatusEvent, theme: Theme): stri const addPreview = (preview: string, maxLines = 3) => { const previewLines = String(preview).split("\n").slice(0, maxLines); for (const line of previewLines) { - lines.push(` ${theme.fg("toolOutput", truncateToWidth(line, 80))}`); + lines.push(` ${theme.fg("toolOutput", truncateToWidth(replaceTabs(line), 80))}`); } const totalLines = String(preview).split("\n").length; if (totalLines > maxLines) { @@ -744,7 +819,10 @@ function formatCellOutputLines( const rawLines = cell.output ? cell.output.split("\n") : []; const displayLines = expanded ? rawLines : rawLines.slice(-previewLines); const hiddenCount = rawLines.length - displayLines.length; - const outputLines = displayLines.map(line => theme.fg("toolOutput", line)); + const outputLines = displayLines.map(line => { + const cleaned = replaceTabs(line); + return cell.status === "error" ? theme.fg("error", cleaned) : theme.fg("toolOutput", cleaned); + }); if (outputLines.length === 0) { return { lines: [], hiddenCount: 0 };