mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -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(),
|
subscribe: vi.fn(),
|
||||||
}),
|
}),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
getToolRegistry: vi.fn(),
|
getToolRegistry: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getModel: () => 'gemini-pro',
|
getModel: () => 'gemini-pro',
|
||||||
@@ -505,6 +506,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
subscribe: vi.fn(),
|
subscribe: vi.fn(),
|
||||||
}),
|
}),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
getToolRegistry: vi.fn(),
|
getToolRegistry: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getModel: () => 'gemini-pro',
|
getModel: () => 'gemini-pro',
|
||||||
@@ -609,6 +611,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
@@ -692,6 +695,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
@@ -760,6 +764,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
@@ -843,6 +848,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
@@ -923,6 +929,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
@@ -997,6 +1004,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
@@ -1170,6 +1178,7 @@ describe('gemini.tsx main function exit codes', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
getToolRegistry: vi.fn(),
|
getToolRegistry: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getModel: () => 'gemini-pro',
|
getModel: () => 'gemini-pro',
|
||||||
@@ -1234,6 +1243,7 @@ describe('gemini.tsx main function exit codes', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: () => false,
|
getEnableHooks: () => false,
|
||||||
|
getHookSystem: () => undefined,
|
||||||
getToolRegistry: vi.fn(),
|
getToolRegistry: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getModel: () => 'gemini-pro',
|
getModel: () => 'gemini-pro',
|
||||||
|
|||||||
+10
-21
@@ -64,8 +64,6 @@ import {
|
|||||||
ExitCodes,
|
ExitCodes,
|
||||||
SessionStartSource,
|
SessionStartSource,
|
||||||
SessionEndReason,
|
SessionEndReason,
|
||||||
fireSessionStartHook,
|
|
||||||
fireSessionEndHook,
|
|
||||||
getVersion,
|
getVersion,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import {
|
import {
|
||||||
@@ -491,11 +489,9 @@ export async function main() {
|
|||||||
|
|
||||||
// Register SessionEnd hook to fire on graceful exit
|
// Register SessionEnd hook to fire on graceful exit
|
||||||
// This runs before telemetry shutdown in runExitCleanup()
|
// This runs before telemetry shutdown in runExitCleanup()
|
||||||
if (config.getEnableHooks() && messageBus) {
|
|
||||||
registerCleanup(async () => {
|
registerCleanup(async () => {
|
||||||
await fireSessionEndHook(messageBus, SessionEndReason.Exit);
|
await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup sessions after config initialization
|
// Cleanup sessions after config initialization
|
||||||
try {
|
try {
|
||||||
@@ -646,35 +642,28 @@ export async function main() {
|
|||||||
|
|
||||||
// Fire SessionStart hook through MessageBus (only if hooks are enabled)
|
// Fire SessionStart hook through MessageBus (only if hooks are enabled)
|
||||||
// Must be called AFTER config.initialize() to ensure HookRegistry is loaded
|
// 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
|
const sessionStartSource = resumedSessionData
|
||||||
? SessionStartSource.Resume
|
? SessionStartSource.Resume
|
||||||
: SessionStartSource.Startup;
|
: SessionStartSource.Startup;
|
||||||
const result = await fireSessionStartHook(
|
const result = await config
|
||||||
hookMessageBus,
|
.getHookSystem()
|
||||||
sessionStartSource,
|
?.fireSessionStartEvent(sessionStartSource);
|
||||||
);
|
|
||||||
|
|
||||||
if (result) {
|
if (result?.finalOutput) {
|
||||||
if (result.systemMessage) {
|
if (result.finalOutput.systemMessage) {
|
||||||
writeToStderr(result.systemMessage + '\n');
|
writeToStderr(result.finalOutput.systemMessage + '\n');
|
||||||
}
|
}
|
||||||
const additionalContext = result.getAdditionalContext();
|
const additionalContext = result.finalOutput.getAdditionalContext();
|
||||||
if (additionalContext) {
|
if (additionalContext) {
|
||||||
// Prepend context to input (System Context -> Stdin -> Question)
|
// Prepend context to input (System Context -> Stdin -> Question)
|
||||||
input = input
|
input = input ? `${additionalContext}\n\n${input}` : additionalContext;
|
||||||
? `${additionalContext}\n\n${input}`
|
|
||||||
: additionalContext;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register SessionEnd hook for graceful exit
|
// Register SessionEnd hook for graceful exit
|
||||||
registerCleanup(async () => {
|
registerCleanup(async () => {
|
||||||
await fireSessionEndHook(hookMessageBus, SessionEndReason.Exit);
|
await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (!input) {
|
if (!input) {
|
||||||
debugLogger.error(
|
debugLogger.error(
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ describe('gemini.tsx main function cleanup', () => {
|
|||||||
getPolicyEngine: vi.fn(),
|
getPolicyEngine: vi.fn(),
|
||||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||||
getEnableHooks: vi.fn(() => false),
|
getEnableHooks: vi.fn(() => false),
|
||||||
|
getHookSystem: () => undefined,
|
||||||
initialize: vi.fn(),
|
initialize: vi.fn(),
|
||||||
getContentGeneratorConfig: vi.fn(),
|
getContentGeneratorConfig: vi.fn(),
|
||||||
getMcpServers: () => ({}),
|
getMcpServers: () => ({}),
|
||||||
|
|||||||
@@ -14,11 +14,17 @@ import type { HookRegistryEntry } from './hookRegistry.js';
|
|||||||
import { logs, type Logger } from '@opentelemetry/api-logs';
|
import { logs, type Logger } from '@opentelemetry/api-logs';
|
||||||
import { SERVICE_NAME } from '../telemetry/constants.js';
|
import { SERVICE_NAME } from '../telemetry/constants.js';
|
||||||
import { debugLogger } from '../utils/debugLogger.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
|
* Main hook system that coordinates all hook-related functionality
|
||||||
*/
|
*/
|
||||||
export class HookSystem {
|
export class HookSystem {
|
||||||
|
private readonly config: Config;
|
||||||
private readonly hookRegistry: HookRegistry;
|
private readonly hookRegistry: HookRegistry;
|
||||||
private readonly hookRunner: HookRunner;
|
private readonly hookRunner: HookRunner;
|
||||||
private readonly hookAggregator: HookAggregator;
|
private readonly hookAggregator: HookAggregator;
|
||||||
@@ -26,6 +32,7 @@ export class HookSystem {
|
|||||||
private readonly hookEventHandler: HookEventHandler;
|
private readonly hookEventHandler: HookEventHandler;
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
|
this.config = config;
|
||||||
const logger: Logger = logs.getLogger(SERVICE_NAME);
|
const logger: Logger = logs.getLogger(SERVICE_NAME);
|
||||||
const messageBus = config.getMessageBus();
|
const messageBus = config.getMessageBus();
|
||||||
|
|
||||||
@@ -79,4 +86,35 @@ export class HookSystem {
|
|||||||
getAllHooks(): HookRegistryEntry[] {
|
getAllHooks(): HookRegistryEntry[] {
|
||||||
return this.hookRegistry.getAllHooks();
|
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