mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(core): accurately reflect subagent tool failure in UI (#23187)
This commit is contained in:
@@ -182,4 +182,25 @@ describe('<SubagentProgressDisplay />', () => {
|
|||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
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(
|
||||||
|
<SubagentProgressDisplay progress={progress} terminalWidth={80} />,
|
||||||
|
);
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+7
@@ -40,6 +40,13 @@ exports[`<SubagentProgressDisplay /> > renders correctly with file_path 1`] = `
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`<SubagentProgressDisplay /> > renders error tool status correctly 1`] = `
|
||||||
|
"Running subagent TestAgent...
|
||||||
|
|
||||||
|
x run_shell_command echo hello
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`<SubagentProgressDisplay /> > renders thought bubbles correctly 1`] = `
|
exports[`<SubagentProgressDisplay /> > renders thought bubbles correctly 1`] = `
|
||||||
"Running subagent TestAgent...
|
"Running subagent TestAgent...
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
type SubagentActivityEvent,
|
type SubagentActivityEvent,
|
||||||
type SubagentProgress,
|
type SubagentProgress,
|
||||||
type SubagentActivityItem,
|
type SubagentActivityItem,
|
||||||
|
isToolActivityError,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
|
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
|
||||||
import {
|
import {
|
||||||
@@ -210,8 +211,9 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
|||||||
const callId = activity.data['id']
|
const callId = activity.data['id']
|
||||||
? String(activity.data['id'])
|
? String(activity.data['id'])
|
||||||
: undefined;
|
: undefined;
|
||||||
// Find the tool call by ID
|
const data = activity.data['data'];
|
||||||
// Find the tool call by ID
|
const isError = isToolActivityError(data);
|
||||||
|
|
||||||
for (let i = recentActivity.length - 1; i >= 0; i--) {
|
for (let i = recentActivity.length - 1; i >= 0; i--) {
|
||||||
if (
|
if (
|
||||||
recentActivity[i].type === 'tool_call' &&
|
recentActivity[i].type === 'tool_call' &&
|
||||||
@@ -219,7 +221,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
|||||||
recentActivity[i].id === callId &&
|
recentActivity[i].id === callId &&
|
||||||
recentActivity[i].status === 'running'
|
recentActivity[i].status === 'running'
|
||||||
) {
|
) {
|
||||||
recentActivity[i].status = 'completed';
|
recentActivity[i].status = isError ? 'error' : 'completed';
|
||||||
updated = true;
|
updated = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1240,6 +1240,7 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
|||||||
name: toolName,
|
name: toolName,
|
||||||
id: call.request.callId,
|
id: call.request.callId,
|
||||||
output: call.response.resultDisplay,
|
output: call.response.resultDisplay,
|
||||||
|
data: call.response.data,
|
||||||
});
|
});
|
||||||
} else if (call.status === 'error') {
|
} else if (call.status === 'error') {
|
||||||
this.emitActivity('ERROR', {
|
this.emitActivity('ERROR', {
|
||||||
|
|||||||
@@ -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 () => {
|
it('should reflect tool rejections in the activity stream as cancelled but not abort the agent', async () => {
|
||||||
mockExecutorInstance.run.mockImplementation(async () => {
|
mockExecutorInstance.run.mockImplementation(async () => {
|
||||||
const onActivity = MockLocalAgentExecutor.create.mock.calls[0][2];
|
const onActivity = MockLocalAgentExecutor.create.mock.calls[0][2];
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
SubagentActivityErrorType,
|
SubagentActivityErrorType,
|
||||||
SUBAGENT_REJECTED_ERROR_PREFIX,
|
SUBAGENT_REJECTED_ERROR_PREFIX,
|
||||||
SUBAGENT_CANCELLED_ERROR_MESSAGE,
|
SUBAGENT_CANCELLED_ERROR_MESSAGE,
|
||||||
|
isToolActivityError,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||||
@@ -166,14 +167,16 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
|||||||
}
|
}
|
||||||
case 'TOOL_CALL_END': {
|
case 'TOOL_CALL_END': {
|
||||||
const name = String(activity.data['name']);
|
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--) {
|
for (let i = recentActivity.length - 1; i >= 0; i--) {
|
||||||
if (
|
if (
|
||||||
recentActivity[i].type === 'tool_call' &&
|
recentActivity[i].type === 'tool_call' &&
|
||||||
recentActivity[i].content === name &&
|
recentActivity[i].content === name &&
|
||||||
recentActivity[i].status === 'running'
|
recentActivity[i].status === 'running'
|
||||||
) {
|
) {
|
||||||
recentActivity[i].status = 'completed';
|
recentActivity[i].status = isError ? 'error' : 'completed';
|
||||||
updated = true;
|
updated = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
* The base definition for an agent.
|
||||||
* @template TOutput The specific Zod schema for the agent's final output object.
|
* @template TOutput The specific Zod schema for the agent's final output object.
|
||||||
|
|||||||
@@ -381,6 +381,10 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
|||||||
|
|
||||||
if (result.exitCode !== null && result.exitCode !== 0) {
|
if (result.exitCode !== null && result.exitCode !== 0) {
|
||||||
llmContentParts.push(`Exit Code: ${result.exitCode}`);
|
llmContentParts.push(`Exit Code: ${result.exitCode}`);
|
||||||
|
data = {
|
||||||
|
exitCode: result.exitCode,
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.signal) {
|
if (result.signal) {
|
||||||
|
|||||||
Reference in New Issue
Block a user