UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

264 lines 9.15 kB
/** * V3 Hooks System - Hook Executor * * Executes hooks in priority order with timeout handling and error recovery. * Integrates with event bus for coordination and monitoring. * * @module v3/shared/hooks/executor */ /** * Hook executor implementation */ export class HookExecutor { registry; eventBus; constructor(registry, eventBus) { this.registry = registry; this.eventBus = eventBus; } /** * Execute all hooks for an event * * @param event - Hook event type * @param context - Hook context * @param options - Execution options * @returns Aggregated results */ async execute(event, context, options = {}) { const startTime = Date.now(); const results = []; let aborted = false; let finalContext = {}; // Get enabled hooks for this event const hooks = this.registry.getHandlers(event, false); // Emit pre-execution event this.eventBus?.emit('hooks:pre-execute', { event, hookCount: hooks.length, context, }); // Execute hooks in priority order for (const hook of hooks) { if (aborted) { break; } try { const result = await this.executeSingleHook(hook.handler, context, hook.timeout || options.timeout); results.push(result); // Record execution statistics this.registry.recordExecution(result.success, result.executionTime || 0); // Merge context modifications if (result.data) { finalContext = { ...finalContext, ...result.data }; // Update context for next hooks Object.assign(context, result.data); } // Check if we should abort if (result.abort) { aborted = true; break; } // Check if we should stop the chain if (result.continueChain === false) { break; } // Check if we should stop on error if (!result.success && !options.continueOnError) { aborted = true; break; } } catch (error) { const errorResult = { success: false, error: error instanceof Error ? error : new Error(String(error)), continueChain: options.continueOnError, }; results.push(errorResult); this.registry.recordExecution(false, 0); // Emit error event this.eventBus?.emit('hooks:error', { event, hookId: hook.id, error, }); if (!options.continueOnError) { aborted = true; break; } } } const totalExecutionTime = Date.now() - startTime; const hooksFailed = results.filter(r => !r.success).length; // Build aggregated result const aggregatedResult = { success: hooksFailed === 0 && !aborted, results: options.collectResults ? results : [], totalExecutionTime, hooksExecuted: results.length, hooksFailed, aborted, finalContext, }; // Emit post-execution event this.eventBus?.emit('hooks:post-execute', { event, ...aggregatedResult, }); return aggregatedResult; } /** * Execute hooks with timeout * * @param event - Hook event type * @param context - Hook context * @param timeout - Timeout in ms * @returns Aggregated results */ async executeWithTimeout(event, context, timeout) { return this.withTimeout(this.execute(event, context, { timeout }), timeout); } /** * Execute a single hook with timeout and error handling * * @param handler - Hook handler function * @param context - Hook context * @param timeout - Optional timeout in ms * @returns Hook result */ async executeSingleHook(handler, context, timeout) { const startTime = Date.now(); try { let resultPromise = Promise.resolve(handler(context)); // Apply timeout if specified if (timeout && timeout > 0) { resultPromise = this.withTimeout(resultPromise, timeout); } const result = await resultPromise; const executionTime = Date.now() - startTime; return { ...result, executionTime, }; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, error: error instanceof Error ? error : new Error(String(error)), executionTime, }; } } /** * Execute multiple hooks in parallel * * @param events - Array of hook events * @param contexts - Array of contexts (matched by index) * @param options - Execution options * @returns Array of aggregated results */ async executeParallel(events, contexts, options = {}) { if (events.length !== contexts.length) { throw new Error('Events and contexts arrays must have same length'); } const maxParallel = options.maxParallel || events.length; const results = []; // Execute in batches for (let i = 0; i < events.length; i += maxParallel) { const batch = events.slice(i, i + maxParallel); const batchContexts = contexts.slice(i, i + maxParallel); const batchResults = await Promise.allSettled(batch.map((event, index) => this.execute(event, batchContexts[index], options))); for (const result of batchResults) { if (result.status === 'fulfilled') { results.push(result.value); } else { // Create error result for rejected promises results.push({ success: false, results: [{ success: false, error: result.reason instanceof Error ? result.reason : new Error(String(result.reason)), }], totalExecutionTime: 0, hooksExecuted: 0, hooksFailed: 1, aborted: true, }); } } } return results; } /** * Execute hooks sequentially with context chaining * * @param events - Array of hook events * @param initialContext - Initial context * @param options - Execution options * @returns Final aggregated result with chained context */ async executeSequential(events, initialContext, options = {}) { const results = []; let currentContext = { ...initialContext }; let totalExecutionTime = 0; let aborted = false; for (const event of events) { if (aborted) { break; } const result = await this.execute(event, currentContext, options); results.push(...result.results); totalExecutionTime += result.totalExecutionTime; // Merge context for next event if (result.finalContext) { currentContext = { ...currentContext, ...result.finalContext }; } if (result.aborted || !result.success) { aborted = true; break; } } const hooksFailed = results.filter(r => !r.success).length; return { success: hooksFailed === 0 && !aborted, results: options.collectResults ? results : [], totalExecutionTime, hooksExecuted: results.length, hooksFailed, aborted, finalContext: currentContext, }; } /** * Wrap a promise with timeout */ async withTimeout(promise, timeout) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Hook execution timeout after ${timeout}ms`)), timeout)), ]); } /** * Set event bus for coordination */ setEventBus(eventBus) { this.eventBus = eventBus; } /** * Get hook registry */ getRegistry() { return this.registry; } } /** * Create a new hook executor */ export function createHookExecutor(registry, eventBus) { return new HookExecutor(registry, eventBus); } //# sourceMappingURL=executor.js.map