/** * @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 { 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 { 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 { 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 { 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 { 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, toolUseId: string, permissionMode: PermissionMode, signal?: AbortSignal, ): Promise { 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, toolResponse: Record, toolUseId: string, permissionMode: PermissionMode, signal?: AbortSignal, ): Promise { 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, errorMessage: string, isInterrupt?: boolean, permissionMode?: PermissionMode, signal?: AbortSignal, ): Promise { 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 { 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 { 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 { 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 { 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, permissionMode: PermissionMode, permissionSuggestions?: PermissionSuggestion[], signal?: AbortSignal, ): Promise { const result = await this.hookEventHandler.firePermissionRequestEvent( toolName, toolInput, permissionMode, permissionSuggestions, signal, ); return result.finalOutput ? createHookOutput('PermissionRequest', result.finalOutput) : undefined; } }