diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index f98cd7c3c9..f8bfa55383 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -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', diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 4a0096150a..b3ce41f7fe 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -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.`, diff --git a/packages/cli/src/gemini_cleanup.test.tsx b/packages/cli/src/gemini_cleanup.test.tsx index 95471ef031..e198d3e887 100644 --- a/packages/cli/src/gemini_cleanup.test.tsx +++ b/packages/cli/src/gemini_cleanup.test.tsx @@ -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: () => ({}), diff --git a/packages/core/src/hooks/hookSystem.ts b/packages/core/src/hooks/hookSystem.ts index 3f170368a9..98f7baf817 100644 --- a/packages/core/src/hooks/hookSystem.ts +++ b/packages/core/src/hooks/hookSystem.ts @@ -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 { + if (!this.config.getEnableHooks()) { + return undefined; + } + return this.hookEventHandler.fireSessionStartEvent(source); + } + + async fireSessionEndEvent( + reason: SessionEndReason, + ): Promise { + if (!this.config.getEnableHooks()) { + return undefined; + } + return this.hookEventHandler.fireSessionEndEvent(reason); + } + + async firePreCompressEvent( + trigger: PreCompressTrigger, + ): Promise { + if (!this.config.getEnableHooks()) { + return undefined; + } + return this.hookEventHandler.firePreCompressEvent(trigger); + } }