qwen-code/packages/core/src/hooks/hookSystem.ts
2026-03-23 16:02:54 +08:00

327 lines
8.5 KiB
TypeScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type { Config } from '../config/config.js';
import { HookRegistry } from './hookRegistry.js';
import { HookRunner } from './hookRunner.js';
import { HookAggregator } from './hookAggregator.js';
import { HookPlanner } from './hookPlanner.js';
import { HookEventHandler } from './hookEventHandler.js';
import type { HookRegistryEntry } from './hookRegistry.js';
import { createDebugLogger } from '../utils/debugLogger.js';
import type { DefaultHookOutput } from './types.js';
import { createHookOutput } from './types.js';
import type {
SessionStartSource,
SessionEndReason,
AgentType,
PermissionMode,
PreCompactTrigger,
NotificationType,
PermissionSuggestion,
} from './types.js';
const debugLogger = createDebugLogger('TRUSTED_HOOKS');
/**
* Main hook system that coordinates all hook-related functionality
*/
export class HookSystem {
private readonly hookRegistry: HookRegistry;
private readonly hookRunner: HookRunner;
private readonly hookAggregator: HookAggregator;
private readonly hookPlanner: HookPlanner;
private readonly hookEventHandler: HookEventHandler;
constructor(config: Config) {
// Initialize components
this.hookRegistry = new HookRegistry(config);
this.hookRunner = new HookRunner();
this.hookAggregator = new HookAggregator();
this.hookPlanner = new HookPlanner(this.hookRegistry);
this.hookEventHandler = new HookEventHandler(
config,
this.hookPlanner,
this.hookRunner,
this.hookAggregator,
);
}
/**
* Initialize the hook system
*/
async initialize(): Promise<void> {
await this.hookRegistry.initialize();
debugLogger.debug('Hook system initialized successfully');
}
/**
* Get the hook event bus for firing events
*/
getEventHandler(): HookEventHandler {
return this.hookEventHandler;
}
/**
* Get hook registry for management operations
*/
getRegistry(): HookRegistry {
return this.hookRegistry;
}
/**
* Enable or disable a hook
*/
setHookEnabled(hookName: string, enabled: boolean): void {
this.hookRegistry.setHookEnabled(hookName, enabled);
}
/**
* Get all registered hooks for display/management
*/
getAllHooks(): HookRegistryEntry[] {
return this.hookRegistry.getAllHooks();
}
async fireUserPromptSubmitEvent(
prompt: string,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireUserPromptSubmitEvent(
prompt,
signal,
);
return result.finalOutput
? createHookOutput('UserPromptSubmit', result.finalOutput)
: undefined;
}
async fireStopEvent(
stopHookActive: boolean = false,
lastAssistantMessage: string = '',
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireStopEvent(
stopHookActive,
lastAssistantMessage,
signal,
);
return result.finalOutput
? createHookOutput('Stop', result.finalOutput)
: undefined;
}
async fireSessionStartEvent(
source: SessionStartSource,
model: string,
permissionMode?: PermissionMode,
agentType?: AgentType,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireSessionStartEvent(
source,
model,
permissionMode,
agentType,
signal,
);
return result.finalOutput
? createHookOutput('SessionStart', result.finalOutput)
: undefined;
}
async fireSessionEndEvent(
reason: SessionEndReason,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireSessionEndEvent(
reason,
signal,
);
return result.finalOutput
? createHookOutput('SessionEnd', result.finalOutput)
: undefined;
}
/**
* Fire a PreToolUse event - called before tool execution
*/
async firePreToolUseEvent(
toolName: string,
toolInput: Record<string, unknown>,
toolUseId: string,
permissionMode: PermissionMode,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.firePreToolUseEvent(
toolName,
toolInput,
toolUseId,
permissionMode,
signal,
);
return result.finalOutput
? createHookOutput('PreToolUse', result.finalOutput)
: undefined;
}
/**
* Fire a PostToolUse event - called after successful tool execution
*/
async firePostToolUseEvent(
toolName: string,
toolInput: Record<string, unknown>,
toolResponse: Record<string, unknown>,
toolUseId: string,
permissionMode: PermissionMode,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.firePostToolUseEvent(
toolName,
toolInput,
toolResponse,
toolUseId,
permissionMode,
signal,
);
return result.finalOutput
? createHookOutput('PostToolUse', result.finalOutput)
: undefined;
}
/**
* Fire a PostToolUseFailure event - called when tool execution fails
*/
async firePostToolUseFailureEvent(
toolUseId: string,
toolName: string,
toolInput: Record<string, unknown>,
errorMessage: string,
isInterrupt?: boolean,
permissionMode?: PermissionMode,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.firePostToolUseFailureEvent(
toolUseId,
toolName,
toolInput,
errorMessage,
isInterrupt,
permissionMode,
signal,
);
return result.finalOutput
? createHookOutput('PostToolUseFailure', result.finalOutput)
: undefined;
}
/**
* Fire a PreCompact event - called before conversation compaction
*/
async firePreCompactEvent(
trigger: PreCompactTrigger,
customInstructions: string = '',
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.firePreCompactEvent(
trigger,
customInstructions,
signal,
);
return result.finalOutput
? createHookOutput('PreCompact', result.finalOutput)
: undefined;
}
/**
* Fire a Notification event
*/
async fireNotificationEvent(
message: string,
notificationType: NotificationType,
title?: string,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireNotificationEvent(
message,
notificationType,
title,
signal,
);
return result.finalOutput
? createHookOutput('Notification', result.finalOutput)
: undefined;
}
/**
* Fire a SubagentStart event - called when a subagent is spawned
*/
async fireSubagentStartEvent(
agentId: string,
agentType: AgentType | string,
permissionMode: PermissionMode,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireSubagentStartEvent(
agentId,
agentType,
permissionMode,
signal,
);
return result.finalOutput
? createHookOutput('SubagentStart', result.finalOutput)
: undefined;
}
/**
* Fire a SubagentStop event - called when a subagent finishes
*/
async fireSubagentStopEvent(
agentId: string,
agentType: AgentType | string,
agentTranscriptPath: string,
lastAssistantMessage: string,
stopHookActive: boolean,
permissionMode: PermissionMode,
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.fireSubagentStopEvent(
agentId,
agentType,
agentTranscriptPath,
lastAssistantMessage,
stopHookActive,
permissionMode,
signal,
);
return result.finalOutput
? createHookOutput('SubagentStop', result.finalOutput)
: undefined;
}
/**
* Fire a PermissionRequest event
*/
async firePermissionRequestEvent(
toolName: string,
toolInput: Record<string, unknown>,
permissionMode: PermissionMode,
permissionSuggestions?: PermissionSuggestion[],
signal?: AbortSignal,
): Promise<DefaultHookOutput | undefined> {
const result = await this.hookEventHandler.firePermissionRequestEvent(
toolName,
toolInput,
permissionMode,
permissionSuggestions,
signal,
);
return result.finalOutput
? createHookOutput('PermissionRequest', result.finalOutput)
: undefined;
}
}