diff --git a/apps/desktop/src/components/assistant-ui/clarify-tool.tsx b/apps/desktop/src/components/assistant-ui/clarify-tool.tsx index cad01efe6ee..e0784b06c5b 100644 --- a/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +++ b/apps/desktop/src/components/assistant-ui/clarify-tool.tsx @@ -164,10 +164,10 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) { data-slot="clarify-inline" > -
+
diff --git a/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts b/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts index 78d2c923a4c..55b7755973e 100644 --- a/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +++ b/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts @@ -33,3 +33,34 @@ describe('buildToolView image handling', () => { expect(buildToolView(part({ result: { url } }), '').imageUrl).toBe(url) }) }) + +describe('buildToolView terminal exit-code status', () => { + const terminal = (result: Record) => + buildToolView(part({ result, toolName: 'terminal' }), '') + + // A non-zero exit code with real output is not a failure (grep no-match, + // diff differences, piped commands surfacing the last stage's code, etc.) — + // it should render as success so the card isn't painted red. + it('treats non-zero exit with output as success', () => { + expect(terminal({ exit_code: 7, output: 'node ... 5174 (LISTEN)' }).status).toBe('success') + expect(terminal({ exit_code: 1, stdout: 'partial results' }).status).toBe('success') + }) + + // No output + non-zero exit is a genuine failure worth flagging. + it('treats non-zero exit with no output as error', () => { + expect(terminal({ exit_code: 127, output: '' }).status).toBe('error') + expect(terminal({ exit_code: 1 }).status).toBe('error') + }) + + it('treats zero exit as success', () => { + expect(terminal({ exit_code: 0, output: 'done' }).status).toBe('success') + }) + + // Explicit error signals still win regardless of output presence. + it('keeps explicit error signals red even with output', () => { + expect(terminal({ error: 'boom', exit_code: 0, output: 'partial' }).status).toBe('error') + expect(buildToolView(part({ isError: true, result: { output: 'x' }, toolName: 'terminal' }), '').status).toBe( + 'error' + ) + }) +}) diff --git a/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts b/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts index c5b4d7a789f..25fa75190a1 100644 --- a/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +++ b/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts @@ -742,9 +742,20 @@ function toolErrorText(part: ToolPart, result: Record): string return firstStringField(result, ['message', 'reason', 'detail']) || `Tool returned status "${result.status}".` } + // A non-zero exit code alone is a weak failure signal: grep returns 1 on + // no-match, diff returns 1 on differences, piped commands surface the last + // stage's code, etc. — all routinely produce useful output and aren't + // failures. Only treat it as an error when the command produced no real + // output to show; otherwise render the output normally (not red). const exit = numberValue(result.exit_code) - return exit !== null && exit !== 0 ? `Command failed with exit code ${exit}.` : '' + if (exit !== null && exit !== 0) { + const hasOutput = Boolean(firstStringField(result, ['output', 'stdout', 'stderr'])?.trim()) + + return hasOutput ? '' : `Command failed with exit code ${exit}.` + } + + return '' } function toolStatus(part: ToolPart, resultRecord: Record): ToolStatus {