diff --git a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx index 955c4a5f8a..caed091b2b 100644 --- a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx +++ b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx @@ -182,4 +182,25 @@ describe('', () => { ); expect(lastFrame()).toMatchSnapshot(); }); + + it('renders error tool status correctly', async () => { + const progress: SubagentProgress = { + isSubagentProgress: true, + agentName: 'TestAgent', + recentActivity: [ + { + id: '7', + type: 'tool_call', + content: 'run_shell_command', + args: '{"command": "echo hello"}', + status: 'error', + }, + ], + }; + + const { lastFrame } = await render( + , + ); + expect(lastFrame()).toMatchSnapshot(); + }); }); diff --git a/packages/cli/src/ui/components/messages/__snapshots__/SubagentProgressDisplay.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/SubagentProgressDisplay.test.tsx.snap index 2d31c9c652..77a3ec001f 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/SubagentProgressDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/SubagentProgressDisplay.test.tsx.snap @@ -40,6 +40,13 @@ exports[` > renders correctly with file_path 1`] = ` " `; +exports[` > renders error tool status correctly 1`] = ` +"Running subagent TestAgent... + +x run_shell_command echo hello +" +`; + exports[` > renders thought bubbles correctly 1`] = ` "Running subagent TestAgent... diff --git a/packages/core/src/agents/browser/browserAgentInvocation.ts b/packages/core/src/agents/browser/browserAgentInvocation.ts index 60bd5201f0..0c96e1894c 100644 --- a/packages/core/src/agents/browser/browserAgentInvocation.ts +++ b/packages/core/src/agents/browser/browserAgentInvocation.ts @@ -30,6 +30,7 @@ import { type SubagentActivityEvent, type SubagentProgress, type SubagentActivityItem, + isToolActivityError, } from '../types.js'; import type { MessageBus } from '../../confirmation-bus/message-bus.js'; import { @@ -210,8 +211,9 @@ export class BrowserAgentInvocation extends BaseToolInvocation< const callId = activity.data['id'] ? String(activity.data['id']) : undefined; - // Find the tool call by ID - // Find the tool call by ID + const data = activity.data['data']; + const isError = isToolActivityError(data); + for (let i = recentActivity.length - 1; i >= 0; i--) { if ( recentActivity[i].type === 'tool_call' && @@ -219,7 +221,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< recentActivity[i].id === callId && recentActivity[i].status === 'running' ) { - recentActivity[i].status = 'completed'; + recentActivity[i].status = isError ? 'error' : 'completed'; updated = true; break; } diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index a860e1e597..ed26f634a0 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -1240,6 +1240,7 @@ export class LocalAgentExecutor { name: toolName, id: call.request.callId, output: call.response.resultDisplay, + data: call.response.data, }); } else if (call.status === 'error') { this.emitActivity('ERROR', { diff --git a/packages/core/src/agents/local-invocation.test.ts b/packages/core/src/agents/local-invocation.test.ts index 2153f538c9..478ceb9f34 100644 --- a/packages/core/src/agents/local-invocation.test.ts +++ b/packages/core/src/agents/local-invocation.test.ts @@ -338,6 +338,42 @@ describe('LocalSubagentInvocation', () => { ); }); + it('should mark tool call as error when TOOL_CALL_END contains isError: true', async () => { + mockExecutorInstance.run.mockImplementation(async () => { + const onActivity = MockLocalAgentExecutor.create.mock.calls[0][2]; + + if (onActivity) { + onActivity({ + isSubagentActivityEvent: true, + agentName: 'MockAgent', + type: 'TOOL_CALL_START', + data: { name: 'ls', args: {}, callId: 'call1' }, + } as SubagentActivityEvent); + onActivity({ + isSubagentActivityEvent: true, + agentName: 'MockAgent', + type: 'TOOL_CALL_END', + data: { name: 'ls', id: 'call1', data: { isError: true } }, + } as SubagentActivityEvent); + } + return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL }; + }); + + await invocation.execute(signal, updateOutput); + + expect(updateOutput).toHaveBeenCalled(); + const lastCall = updateOutput.mock.calls[ + updateOutput.mock.calls.length - 1 + ][0] as SubagentProgress; + expect(lastCall.recentActivity).toContainEqual( + expect.objectContaining({ + type: 'tool_call', + content: 'ls', + status: 'error', + }), + ); + }); + it('should reflect tool rejections in the activity stream as cancelled but not abort the agent', async () => { mockExecutorInstance.run.mockImplementation(async () => { const onActivity = MockLocalAgentExecutor.create.mock.calls[0][2]; diff --git a/packages/core/src/agents/local-invocation.ts b/packages/core/src/agents/local-invocation.ts index 08a4aa8264..0d28dcbe64 100644 --- a/packages/core/src/agents/local-invocation.ts +++ b/packages/core/src/agents/local-invocation.ts @@ -21,6 +21,7 @@ import { SubagentActivityErrorType, SUBAGENT_REJECTED_ERROR_PREFIX, SUBAGENT_CANCELLED_ERROR_MESSAGE, + isToolActivityError, } from './types.js'; import { randomUUID } from 'node:crypto'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; @@ -166,14 +167,16 @@ export class LocalSubagentInvocation extends BaseToolInvocation< } case 'TOOL_CALL_END': { const name = String(activity.data['name']); - // Find the last running tool call with this name + const data = activity.data['data']; + const isError = isToolActivityError(data); + for (let i = recentActivity.length - 1; i >= 0; i--) { if ( recentActivity[i].type === 'tool_call' && recentActivity[i].content === name && recentActivity[i].status === 'running' ) { - recentActivity[i].status = 'completed'; + recentActivity[i].status = isError ? 'error' : 'completed'; updated = true; break; } diff --git a/packages/core/src/agents/types.ts b/packages/core/src/agents/types.ts index 7f056c37ab..e36d8f0ccb 100644 --- a/packages/core/src/agents/types.ts +++ b/packages/core/src/agents/types.ts @@ -112,6 +112,18 @@ export function isSubagentProgress(obj: unknown): obj is SubagentProgress { ); } +/** + * Checks if the tool call data indicates an error. + */ +export function isToolActivityError(data: unknown): boolean { + return ( + data !== null && + typeof data === 'object' && + 'isError' in data && + data.isError === true + ); +} + /** * The base definition for an agent. * @template TOutput The specific Zod schema for the agent's final output object. diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index b05badecf9..86e3a68bc5 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -381,6 +381,10 @@ export class ShellToolInvocation extends BaseToolInvocation< if (result.exitCode !== null && result.exitCode !== 0) { llmContentParts.push(`Exit Code: ${result.exitCode}`); + data = { + exitCode: result.exitCode, + isError: true, + }; } if (result.signal) {