diff --git a/desktop/src/components/chat/ToolCallBlock.tsx b/desktop/src/components/chat/ToolCallBlock.tsx index 794fae62..9f17da6d 100644 --- a/desktop/src/components/chat/ToolCallBlock.tsx +++ b/desktop/src/components/chat/ToolCallBlock.tsx @@ -131,57 +131,94 @@ function renderPreview( t?: (key: TranslationKey, params?: Record) => string, ) { const filePath = typeof obj.file_path === 'string' ? obj.file_path : 'file' + const resultText = getVisibleResultText(toolName, result) + const resultOutput = result && resultText ? renderResultOutput(result, resultText, t) : null if (toolName === 'Edit' && typeof obj.old_string === 'string' && typeof obj.new_string === 'string') { - return + return ( + <> + + {resultOutput} + + ) } if (toolName === 'Write' && typeof obj.content === 'string') { - return + return ( + <> + + {resultOutput} + + ) } if (toolName === 'Bash' && typeof obj.command === 'string') { return ( - -
- $ {obj.command} -
-
+ <> + +
+ $ {obj.command} +
+
+ {resultOutput} + ) } if (toolName === 'Read') { - return null + return resultOutput } - if (result) { - const text = extractTextContent(result.content) - if (text) { - return ( - <> - -
-
- {result.isError ? t?.('tool.errorOutput') ?? 'Error Output' : t?.('tool.toolOutput') ?? 'Tool Output'} - -
- -
- - ) - } - } + if (resultOutput) return resultOutput return null } +function getVisibleResultText( + toolName: string, + result?: { content: unknown; isError: boolean } | null, +): string | null { + if (!result) return null + const text = extractTextContent(result.content) + if (!text) return null + + if (result.isError) return text + if (toolName === 'Bash' || toolName === 'Read' || toolName === 'Edit' || toolName === 'Write') return null + return text +} + +function renderResultOutput( + result: { content: unknown; isError: boolean }, + text: string, + t?: (key: TranslationKey, params?: Record) => string, +) { + return ( + <> + +
+
+ {result.isError ? t?.('tool.errorOutput') ?? 'Error Output' : t?.('tool.toolOutput') ?? 'Tool Output'} + +
+ {result.isError ? ( +
+            {text}
+          
+ ) : ( + + )} +
+ + ) +} + function renderDetails( toolName: string, obj: Record, diff --git a/desktop/src/components/chat/chatBlocks.test.tsx b/desktop/src/components/chat/chatBlocks.test.tsx index 2b2e0c0d..ad7d05be 100644 --- a/desktop/src/components/chat/chatBlocks.test.tsx +++ b/desktop/src/components/chat/chatBlocks.test.tsx @@ -159,6 +159,84 @@ describe('chat blocks', () => { expect(container.textContent).toContain('fatal: unrecognized argument: --no-stat') }) + it('shows full bash error output when the tool block is expanded', () => { + const lines = Array.from({ length: 8 }, (_, index) => `detail line ${index + 1}`) + const fullError = [ + 'InputValidationError: Bash failed due to the following issues: The required parameter `description` is missing.', + ...lines, + 'final remediation hint: provide a concise command description.', + ].join('\n') + const { container } = render( + , + ) + + expect(container.textContent).toContain('InputValidationError') + expect(container.textContent).not.toContain('final remediation hint') + + fireEvent.click(screen.getByRole('button')) + + expect(container.textContent).toContain('Error Output') + expect(container.textContent).toContain('detail line 8') + expect(container.textContent).toContain('final remediation hint') + }) + + it('shows read tool validation errors when the tool block is expanded', () => { + const fullError = [ + 'InputValidationError: Read failed due to the following issues:', + 'The required parameter `file_path` is missing.', + 'The provided limit must be greater than 0.', + ].join('\n') + const { container } = render( + , + ) + + expect(container.textContent).toContain('Read') + expect(container.textContent).not.toContain('file_path') + + fireEvent.click(screen.getByRole('button')) + + expect(container.textContent).toContain('Error Output') + expect(container.textContent).toContain('file_path') + expect(container.textContent).toContain('limit must be greater than 0') + }) + + it('keeps edit previews while showing edit tool error output when expanded', () => { + const { container } = render( + , + ) + + expect(container.textContent).toContain('Edit') + expect(container.textContent).not.toContain('old_string was not found') + + fireEvent.click(screen.getByRole('button')) + + expect(container.textContent).toContain('example.ts') + expect(container.textContent).toContain('Error Output') + expect(container.textContent).toContain('old_string was not found') + }) + it('expands tool errors so full Computer Use gate messages are readable', () => { const { container } = render(