fix: error out on tool approvals instead of hanging

This commit ensures that the interactive agent loop does not hang when
a tool requires manual approval (since confirmation dialogs are not yet
implemented for the new protocol).

Instead of waiting indefinitely, 'LegacyAgentSession' now detects the
'awaiting_approval' status, emits a descriptive error event, and
aborts the session.

Key changes:
- 'LegacyAgentSession' now emits status updates in 'tool_update' events.
- 'LegacyAgentSession' aborts and errors on 'awaiting_approval' status.
- 'useAgentStream' now correctly tracks and displays tool status transitions
  and intermediate progress via the 'AgentEvent' stream.
This commit is contained in:
Michael Bleigh
2026-03-24 12:24:13 -07:00
parent 87e9491e78
commit 0f37dd1d78
3 changed files with 86 additions and 8 deletions
@@ -292,6 +292,53 @@ describe('useAgentStream', () => {
});
});
it('should display error message when a tool call requires approval', async () => {
let eventHandler: (event: any) => void = () => {};
vi.spyOn(mockLegacyAgentSession, 'subscribe').mockImplementation((handler) => {
eventHandler = handler;
return () => {};
});
await renderHookWithProviders(() =>
useAgentStream(
{} as any,
[],
mockAddItem,
mockConfig,
mockSettings,
mockOnDebugMessage,
mockHandleSlashCommand,
false,
() => undefined,
mockOnAuthError,
mockPerformMemoryRefresh,
false,
mockSetModelSwitchedFromQuotaError,
mockOnCancelSubmit,
mockSetShellInputFocused,
80,
24,
),
);
act(() => {
eventHandler({
type: 'error',
status: 'UNIMPLEMENTED',
message: 'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
fatal: true,
});
});
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.ERROR,
text: 'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
}),
expect.any(Number),
);
});
it('should call session.abort when cancelOngoingRequest is called', async () => {
const { result } = await renderHookWithProviders(() =>
useAgentStream(
+26 -8
View File
@@ -218,7 +218,7 @@ export const useAgentStream = (
isClientInitiated: false,
originalRequestName: event.name,
},
status: 'executing',
status: 'scheduled',
tool: {
displayName: (event._meta?.['displayName'] as string) ?? event.name,
isOutputMarkdown: (event._meta?.['isOutputMarkdown'] as boolean) ?? false,
@@ -236,13 +236,27 @@ export const useAgentStream = (
tc.request.callId === event.requestId
? ({
...tc,
liveOutput: event.displayContent?.[0]?.type === 'text' ? event.displayContent[0].text : undefined,
progressMessage: event.data?.['progressMessage'] as string | undefined,
progress: event.data?.['progress'] as number | undefined,
progressTotal: event.data?.['progressTotal'] as number | undefined,
pid: event.data?.['pid'] as number | undefined,
status: (event.data?.['status'] as any) ?? tc.status,
liveOutput:
event.displayContent?.[0]?.type === 'text'
? event.displayContent[0].text
: (tc as any).liveOutput,
progressMessage:
(event.data?.['progressMessage'] as string | undefined) ??
(tc as any).progressMessage,
progress:
(event.data?.['progress'] as number | undefined) ??
(tc as any).progress,
progressTotal:
(event.data?.['progressTotal'] as number | undefined) ??
(tc as any).progressTotal,
pid:
(event.data?.['pid'] as number | undefined) ??
(tc as any).pid,
invocation: {
getDescription: () => (event._meta?.['description'] as string) ?? (tc as any).invocation?.getDescription(),
getDescription: () =>
(event._meta?.['description'] as string) ??
(tc as any).invocation?.getDescription(),
},
} as unknown as TrackedToolCall)
: tc,
@@ -257,7 +271,11 @@ export const useAgentStream = (
...tc,
status: event.isError ? 'error' : 'success',
response: {
resultDisplay: event._meta?.['resultDisplay'] ?? (event.displayContent?.[0]?.type === 'text' ? event.displayContent[0].text : undefined),
resultDisplay:
event._meta?.['resultDisplay'] ??
(event.displayContent?.[0]?.type === 'text'
? event.displayContent[0].text
: undefined),
outputFile: event._meta?.['outputFile'] as string | undefined,
},
responseSubmittedToGemini: true,
@@ -168,6 +168,19 @@ class LegacyAgentProtocol implements AgentProtocol {
const handleToolCallsUpdate = (event: ToolCallsUpdateMessage) => {
const toolUpdates: AgentEvent[] = [];
for (const tc of event.toolCalls) {
if (tc.status === 'awaiting_approval') {
this._emit([
this._makeErrorEvent({
status: 'UNIMPLEMENTED',
message:
'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
fatal: true,
}),
]);
void this.abort();
return;
}
if (tc.status === 'executing') {
toolUpdates.push(
this._makeToolUpdateEvent({