mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
feat(hooks): simplify hook firing with HookSystem wrapper methods (#15982)
Co-authored-by: Ishaan Gupta <ishaankone@gmail.com>
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
@@ -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: () => ({}),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user