mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 22:48:03 +00:00
feat(hooks): reduce log verbosity and improve error reporting in UI (#15297)
This commit is contained in:
@@ -53,7 +53,7 @@ export async function fireBeforeAgentHook(
|
||||
? createHookOutput('BeforeAgent', response.output)
|
||||
: undefined;
|
||||
} catch (error) {
|
||||
debugLogger.warn(`BeforeAgent hook failed: ${error}`);
|
||||
debugLogger.debug(`BeforeAgent hook failed: ${error}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export async function fireAfterAgentHook(
|
||||
? createHookOutput('AfterAgent', response.output)
|
||||
: undefined;
|
||||
} catch (error) {
|
||||
debugLogger.warn(`AfterAgent hook failed: ${error}`);
|
||||
debugLogger.debug(`AfterAgent hook failed: ${error}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export async function fireToolNotificationHook(
|
||||
MessageBusType.HOOK_EXECUTION_RESPONSE,
|
||||
);
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
debugLogger.debug(
|
||||
`Notification hook failed for ${confirmationDetails.title}:`,
|
||||
error,
|
||||
);
|
||||
@@ -185,7 +185,7 @@ export async function fireBeforeToolHook(
|
||||
? createHookOutput('BeforeTool', response.output)
|
||||
: undefined;
|
||||
} catch (error) {
|
||||
debugLogger.warn(`BeforeTool hook failed for ${toolName}:`, error);
|
||||
debugLogger.debug(`BeforeTool hook failed for ${toolName}:`, error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,7 @@ export async function fireAfterToolHook(
|
||||
? createHookOutput('AfterTool', response.output)
|
||||
: undefined;
|
||||
} catch (error) {
|
||||
debugLogger.warn(`AfterTool hook failed for ${toolName}:`, error);
|
||||
debugLogger.debug(`AfterTool hook failed for ${toolName}:`, error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export async function fireBeforeModelHook(
|
||||
|
||||
return { blocked: false };
|
||||
} catch (error) {
|
||||
debugLogger.warn(`BeforeModel hook failed:`, error);
|
||||
debugLogger.debug(`BeforeModel hook failed:`, error);
|
||||
return { blocked: false };
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ export async function fireBeforeToolSelectionHook(
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
debugLogger.warn(`BeforeToolSelection hook failed:`, error);
|
||||
debugLogger.debug(`BeforeToolSelection hook failed:`, error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export async function fireAfterModelHook(
|
||||
|
||||
return { response: chunk };
|
||||
} catch (error) {
|
||||
debugLogger.warn(`AfterModel hook failed:`, error);
|
||||
debugLogger.debug(`AfterModel hook failed:`, error);
|
||||
// On error, return original chunk to avoid interrupting the stream.
|
||||
return { response: chunk };
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function fireSessionStartHook(
|
||||
MessageBusType.HOOK_EXECUTION_RESPONSE,
|
||||
);
|
||||
} catch (error) {
|
||||
debugLogger.warn(`SessionStart hook failed:`, error);
|
||||
debugLogger.debug(`SessionStart hook failed:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export async function fireSessionEndHook(
|
||||
MessageBusType.HOOK_EXECUTION_RESPONSE,
|
||||
);
|
||||
} catch (error) {
|
||||
debugLogger.warn(`SessionEnd hook failed:`, error);
|
||||
debugLogger.debug(`SessionEnd hook failed:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,6 @@ export async function firePreCompressHook(
|
||||
MessageBusType.HOOK_EXECUTION_RESPONSE,
|
||||
);
|
||||
} catch (error) {
|
||||
debugLogger.warn(`PreCompress hook failed:`, error);
|
||||
debugLogger.debug(`PreCompress hook failed:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,19 @@ const mockDebugLogger = vi.hoisted(() => ({
|
||||
debug: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock coreEvents
|
||||
const mockCoreEvents = vi.hoisted(() => ({
|
||||
emitFeedback: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../utils/debugLogger.js', () => ({
|
||||
debugLogger: mockDebugLogger,
|
||||
}));
|
||||
|
||||
vi.mock('../utils/events.js', () => ({
|
||||
coreEvents: mockCoreEvents,
|
||||
}));
|
||||
|
||||
describe('HookEventHandler', () => {
|
||||
let hookEventHandler: HookEventHandler;
|
||||
let mockConfig: Config;
|
||||
@@ -167,6 +176,53 @@ describe('HookEventHandler', () => {
|
||||
expect(result.errors[0].message).toBe('Planning failed');
|
||||
expect(mockDebugLogger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit feedback when some hooks fail', async () => {
|
||||
const mockPlan = [
|
||||
{
|
||||
type: HookType.Command,
|
||||
command: './fail.sh',
|
||||
} as HookConfig,
|
||||
];
|
||||
const mockResults: HookExecutionResult[] = [
|
||||
{
|
||||
success: false,
|
||||
duration: 50,
|
||||
hookConfig: mockPlan[0],
|
||||
eventName: HookEventName.BeforeTool,
|
||||
error: new Error('Failed to execute'),
|
||||
},
|
||||
];
|
||||
const mockAggregated = {
|
||||
success: false,
|
||||
allOutputs: [],
|
||||
errors: [new Error('Failed to execute')],
|
||||
totalDuration: 50,
|
||||
};
|
||||
|
||||
vi.mocked(mockHookPlanner.createExecutionPlan).mockReturnValue({
|
||||
eventName: HookEventName.BeforeTool,
|
||||
hookConfigs: mockPlan,
|
||||
sequential: false,
|
||||
});
|
||||
vi.mocked(mockHookRunner.executeHooksParallel).mockResolvedValue(
|
||||
mockResults,
|
||||
);
|
||||
vi.mocked(mockHookAggregator.aggregateResults).mockReturnValue(
|
||||
mockAggregated,
|
||||
);
|
||||
|
||||
await hookEventHandler.fireBeforeToolEvent('EditTool', {});
|
||||
|
||||
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||
'warning',
|
||||
expect.stringContaining('./fail.sh'),
|
||||
);
|
||||
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||
'warning',
|
||||
expect.stringContaining('F12'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fireAfterToolEvent', () => {
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
type HookExecutionRequest,
|
||||
} from '../confirmation-bus/types.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { coreEvents } from '../utils/events.js';
|
||||
|
||||
/**
|
||||
* Validates that a value is a non-null object
|
||||
@@ -573,14 +574,24 @@ export class HookEventHandler {
|
||||
results: HookExecutionResult[],
|
||||
aggregated: AggregatedHookResult,
|
||||
): void {
|
||||
const successCount = results.filter((r) => r.success).length;
|
||||
const errorCount = results.length - successCount;
|
||||
const failedHooks = results.filter((r) => !r.success);
|
||||
const successCount = results.length - failedHooks.length;
|
||||
const errorCount = failedHooks.length;
|
||||
|
||||
if (errorCount > 0) {
|
||||
const failedNames = failedHooks
|
||||
.map((r) => this.getHookNameFromResult(r))
|
||||
.join(', ');
|
||||
|
||||
debugLogger.warn(
|
||||
`Hook execution for ${eventName}: ${successCount} succeeded, ${errorCount} failed, ` +
|
||||
`Hook execution for ${eventName}: ${successCount} succeeded, ${errorCount} failed (${failedNames}), ` +
|
||||
`total duration: ${aggregated.totalDuration}ms`,
|
||||
);
|
||||
|
||||
coreEvents.emitFeedback(
|
||||
'warning',
|
||||
`Hook(s) [${failedNames}] failed for event ${eventName}. Press F12 to see the debug drawer for more details.\n`,
|
||||
);
|
||||
} else {
|
||||
debugLogger.debug(
|
||||
`Hook execution for ${eventName}: ${successCount} hooks executed successfully, ` +
|
||||
@@ -613,7 +624,7 @@ export class HookEventHandler {
|
||||
|
||||
// Log individual errors
|
||||
for (const error of aggregated.errors) {
|
||||
debugLogger.error(`Hook execution error: ${error.message}`);
|
||||
debugLogger.warn(`Hook execution error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ export class HookRunner {
|
||||
child.stdin.on('error', (err: NodeJS.ErrnoException) => {
|
||||
// Ignore EPIPE errors which happen when the child process closes stdin early
|
||||
if (err.code !== 'EPIPE') {
|
||||
debugLogger.warn(`Hook stdin error: ${err}`);
|
||||
debugLogger.debug(`Hook stdin error: ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -247,7 +247,7 @@ export class HookRunner {
|
||||
} catch (err) {
|
||||
// Ignore EPIPE errors which happen when the child process closes stdin early
|
||||
if (err instanceof Error && 'code' in err && err.code !== 'EPIPE') {
|
||||
debugLogger.warn(`Hook stdin write error: ${err}`);
|
||||
debugLogger.debug(`Hook stdin write error: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user