feat(hooks): simplify hook firing with HookSystem wrapper methods (#15982)

Co-authored-by: Ishaan Gupta <ishaankone@gmail.com>
This commit is contained in:
Vedant Mahajan
2026-01-08 00:41:49 +05:30
committed by GitHub
parent 57012ae5b3
commit c64b5ec4a3
4 changed files with 72 additions and 34 deletions

View File

@@ -269,6 +269,7 @@ describe('gemini.tsx main function', () => {
subscribe: vi.fn(),
}),
getEnableHooks: () => false,
getHookSystem: () => undefined,
getToolRegistry: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getModel: () => 'gemini-pro',
@@ -505,6 +506,7 @@ describe('gemini.tsx main function kitty protocol', () => {
subscribe: vi.fn(),
}),
getEnableHooks: () => false,
getHookSystem: () => undefined,
getToolRegistry: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getModel: () => 'gemini-pro',
@@ -609,6 +611,7 @@ describe('gemini.tsx main function kitty protocol', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
@@ -692,6 +695,7 @@ describe('gemini.tsx main function kitty protocol', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
@@ -760,6 +764,7 @@ describe('gemini.tsx main function kitty protocol', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
@@ -843,6 +848,7 @@ describe('gemini.tsx main function kitty protocol', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
@@ -923,6 +929,7 @@ describe('gemini.tsx main function kitty protocol', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
@@ -997,6 +1004,7 @@ describe('gemini.tsx main function kitty protocol', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
@@ -1170,6 +1178,7 @@ describe('gemini.tsx main function exit codes', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
getToolRegistry: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getModel: () => 'gemini-pro',
@@ -1234,6 +1243,7 @@ describe('gemini.tsx main function exit codes', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: () => false,
getHookSystem: () => undefined,
getToolRegistry: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getModel: () => 'gemini-pro',

View File

@@ -64,8 +64,6 @@ import {
ExitCodes,
SessionStartSource,
SessionEndReason,
fireSessionStartHook,
fireSessionEndHook,
getVersion,
} from '@google/gemini-cli-core';
import {
@@ -491,11 +489,9 @@ export async function main() {
// Register SessionEnd hook to fire on graceful exit
// This runs before telemetry shutdown in runExitCleanup()
if (config.getEnableHooks() && messageBus) {
registerCleanup(async () => {
await fireSessionEndHook(messageBus, SessionEndReason.Exit);
});
}
registerCleanup(async () => {
await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
});
// Cleanup sessions after config initialization
try {
@@ -646,36 +642,29 @@ export async function main() {
// Fire SessionStart hook through MessageBus (only if hooks are enabled)
// Must be called AFTER config.initialize() to ensure HookRegistry is loaded
const hooksEnabled = config.getEnableHooks();
const hookMessageBus = config.getMessageBus();
if (hooksEnabled && hookMessageBus) {
const sessionStartSource = resumedSessionData
? SessionStartSource.Resume
: SessionStartSource.Startup;
const result = await fireSessionStartHook(
hookMessageBus,
sessionStartSource,
);
const sessionStartSource = resumedSessionData
? SessionStartSource.Resume
: SessionStartSource.Startup;
const result = await config
.getHookSystem()
?.fireSessionStartEvent(sessionStartSource);
if (result) {
if (result.systemMessage) {
writeToStderr(result.systemMessage + '\n');
}
const additionalContext = result.getAdditionalContext();
if (additionalContext) {
// Prepend context to input (System Context -> Stdin -> Question)
input = input
? `${additionalContext}\n\n${input}`
: additionalContext;
}
if (result?.finalOutput) {
if (result.finalOutput.systemMessage) {
writeToStderr(result.finalOutput.systemMessage + '\n');
}
const additionalContext = result.finalOutput.getAdditionalContext();
if (additionalContext) {
// Prepend context to input (System Context -> Stdin -> Question)
input = input ? `${additionalContext}\n\n${input}` : additionalContext;
}
// Register SessionEnd hook for graceful exit
registerCleanup(async () => {
await fireSessionEndHook(hookMessageBus, SessionEndReason.Exit);
});
}
// Register SessionEnd hook for graceful exit
registerCleanup(async () => {
await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
});
if (!input) {
debugLogger.error(
`No input provided via stdin. Input can be provided by piping data into gemini or using the --prompt option.`,

View File

@@ -189,6 +189,7 @@ describe('gemini.tsx main function cleanup', () => {
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
getEnableHooks: vi.fn(() => false),
getHookSystem: () => undefined,
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),

View File

@@ -14,11 +14,17 @@ import type { HookRegistryEntry } from './hookRegistry.js';
import { logs, type Logger } from '@opentelemetry/api-logs';
import { SERVICE_NAME } from '../telemetry/constants.js';
import { debugLogger } from '../utils/debugLogger.js';
import type {
SessionStartSource,
SessionEndReason,
PreCompressTrigger,
} from './types.js';
import type { AggregatedHookResult } from './hookAggregator.js';
/**
* Main hook system that coordinates all hook-related functionality
*/
export class HookSystem {
private readonly config: Config;
private readonly hookRegistry: HookRegistry;
private readonly hookRunner: HookRunner;
private readonly hookAggregator: HookAggregator;
@@ -26,6 +32,7 @@ export class HookSystem {
private readonly hookEventHandler: HookEventHandler;
constructor(config: Config) {
this.config = config;
const logger: Logger = logs.getLogger(SERVICE_NAME);
const messageBus = config.getMessageBus();
@@ -79,4 +86,35 @@ export class HookSystem {
getAllHooks(): HookRegistryEntry[] {
return this.hookRegistry.getAllHooks();
}
/**
* Fire hook events directly
* Returns undefined if hooks are disabled
*/
async fireSessionStartEvent(
source: SessionStartSource,
): Promise<AggregatedHookResult | undefined> {
if (!this.config.getEnableHooks()) {
return undefined;
}
return this.hookEventHandler.fireSessionStartEvent(source);
}
async fireSessionEndEvent(
reason: SessionEndReason,
): Promise<AggregatedHookResult | undefined> {
if (!this.config.getEnableHooks()) {
return undefined;
}
return this.hookEventHandler.fireSessionEndEvent(reason);
}
async firePreCompressEvent(
trigger: PreCompressTrigger,
): Promise<AggregatedHookResult | undefined> {
if (!this.config.getEnableHooks()) {
return undefined;
}
return this.hookEventHandler.firePreCompressEvent(trigger);
}
}