feat(hooks): implement granular stop and block behavior for agent hooks (#15824)

This commit is contained in:
Sandy Tao
2026-01-04 18:58:34 -08:00
committed by GitHub
parent bdb349e7f6
commit dd84c2fb83
7 changed files with 388 additions and 13 deletions
@@ -1797,4 +1797,63 @@ describe('runNonInteractive', () => {
// The key assertion: sendMessageStream should have been called ONLY ONCE (initial user input).
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
});
describe('Agent Execution Events', () => {
it('should handle AgentExecutionStopped event', async () => {
const events: ServerGeminiStreamEvent[] = [
{
type: GeminiEventType.AgentExecutionStopped,
value: { reason: 'Stopped by hook' },
},
];
mockGeminiClient.sendMessageStream.mockReturnValue(
createStreamFromEvents(events),
);
await runNonInteractive({
config: mockConfig,
settings: mockSettings,
input: 'test stop',
prompt_id: 'prompt-id-stop',
});
expect(processStderrSpy).toHaveBeenCalledWith(
'Agent execution stopped: Stopped by hook\n',
);
// Should exit without calling sendMessageStream again
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
});
it('should handle AgentExecutionBlocked event', async () => {
const allEvents: ServerGeminiStreamEvent[] = [
{
type: GeminiEventType.AgentExecutionBlocked,
value: { reason: 'Blocked by hook' },
},
{ type: GeminiEventType.Content, value: 'Final answer' },
{
type: GeminiEventType.Finished,
value: { reason: undefined, usageMetadata: { totalTokenCount: 10 } },
},
];
mockGeminiClient.sendMessageStream.mockReturnValue(
createStreamFromEvents(allEvents),
);
await runNonInteractive({
config: mockConfig,
settings: mockSettings,
input: 'test block',
prompt_id: 'prompt-id-block',
});
expect(processStderrSpy).toHaveBeenCalledWith(
'[WARNING] Agent execution blocked: Blocked by hook\n',
);
// sendMessageStream is called once, recursion is internal to it and transparent to the caller
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
expect(getWrittenOutput()).toBe('Final answer\n');
});
});
});