UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

290 lines (289 loc) 9.43 kB
/** * CFN Loop Circuit Breaker * Migrated from legacy/v1/src/cfn-loop/circuit-breaker.ts */ import { EventEmitter } from 'events'; import { Logger } from '../core/logger.js'; export var CircuitState = /*#__PURE__*/ function(CircuitState) { CircuitState["CLOSED"] = "CLOSED"; CircuitState["OPEN"] = "OPEN"; CircuitState["HALF_OPEN"] = "HALF_OPEN"; return CircuitState; }({}); /** * Circuit Breaker with timeout and failure tracking */ export class CFNCircuitBreaker extends EventEmitter { name; failureCount = 0; successCount = 0; state = "CLOSED"; lastFailureTime; lastSuccessTime; nextAttemptTime; totalRequests = 0; rejectedRequests = 0; timeoutCount = 0; halfOpenRequests = 0; failureThreshold; successThreshold; delays; maxAttempts; halfOpenLimit; defaultTimeoutMs; currentAttempt = 0; logger; constructor(name, options = {}){ super(), this.name = name; this.failureThreshold = options.failureThreshold || 3; this.successThreshold = options.successThreshold || 2; this.delays = options.delays || [ 1000, 2000, 4000, 8000 ]; this.maxAttempts = options.maxAttempts || this.delays.length; this.halfOpenLimit = options.halfOpenLimit || 3; this.defaultTimeoutMs = options.timeoutMs || 30 * 60 * 1000; const loggerConfig = process.env.CLAUDE_FLOW_ENV === 'test' ? { level: 'error', format: 'json', destination: 'console' } : { level: 'info', format: 'json', destination: 'console' }; this.logger = new Logger(loggerConfig, { component: `CFNCircuitBreaker:${name}` }); } /** * Execute a function with circuit breaker protection and timeout */ async execute(fn, options) { this.totalRequests++; if (!this.canExecute()) { this.rejectedRequests++; const error = this.createCircuitOpenError(); this.logger.warn('Request rejected - circuit is OPEN', { name: this.name, state: this.state, nextAttempt: this.nextAttemptTime }); this.emit('request:rejected', { name: this.name, state: this.getState() }); throw error; } const timeoutMs = options?.timeoutMs || this.defaultTimeoutMs; try { const result = await this.executeWithTimeout(fn, timeoutMs); this.recordSuccess(); return result; } catch (error) { if (error instanceof Error && error.name === 'TimeoutError') { this.timeoutCount++; this.logger.error('Operation timed out', { name: this.name, timeoutMs, timeoutCount: this.timeoutCount }); } this.recordFailure(); throw error; } } /** * Execute function with timeout wrapper */ async executeWithTimeout(fn, timeoutMs) { return Promise.race([ fn(), new Promise((_, reject)=>{ setTimeout(()=>{ const error = new Error(`CFN loop operation timed out after ${timeoutMs}ms`); error.name = 'TimeoutError'; error.timeoutMs = timeoutMs; error.operation = this.name; reject(error); }, timeoutMs); }) ]); } /** * Record successful execution */ recordSuccess() { this.lastSuccessTime = new Date(); this.currentAttempt = 0; switch(this.state){ case "CLOSED": this.failureCount = 0; break; case "HALF_OPEN": this.successCount++; this.halfOpenRequests++; if (this.successCount >= this.successThreshold) { this.transitionTo("CLOSED"); } break; case "OPEN": this.transitionTo("HALF_OPEN"); break; } this.emit('success', { name: this.name, state: this.state, successCount: this.successCount }); } /** * Record failed execution */ recordFailure() { this.lastFailureTime = new Date(); this.currentAttempt++; switch(this.state){ case "CLOSED": this.failureCount++; if (this.failureCount >= this.failureThreshold) { this.transitionTo("OPEN"); } break; case "HALF_OPEN": this.transitionTo("OPEN"); break; case "OPEN": const delayIndex = Math.min(this.currentAttempt - 1, this.delays.length - 1); const delayMs = this.delays[delayIndex]; this.nextAttemptTime = new Date(Date.now() + delayMs); break; } this.emit('failure', { name: this.name, state: this.state, failureCount: this.failureCount, currentAttempt: this.currentAttempt, maxAttempts: this.maxAttempts }); } /** * Reset circuit breaker to closed state */ reset() { this.logger.info('Resetting circuit breaker', { name: this.name }); this.state = "CLOSED"; this.failureCount = 0; this.successCount = 0; this.halfOpenRequests = 0; this.timeoutCount = 0; this.currentAttempt = 0; delete this.lastFailureTime; delete this.lastSuccessTime; delete this.nextAttemptTime; this.emit('reset', { name: this.name }); } /** * Get current circuit breaker state */ getState() { const state = { state: this.state, failureCount: this.failureCount, successCount: this.successCount, totalRequests: this.totalRequests, rejectedRequests: this.rejectedRequests, timeoutCount: this.timeoutCount }; if (this.lastFailureTime !== undefined) { state.lastFailureTime = this.lastFailureTime; } if (this.lastSuccessTime !== undefined) { state.lastSuccessTime = this.lastSuccessTime; } if (this.nextAttemptTime !== undefined) { state.nextAttemptTime = this.nextAttemptTime; } return state; } /** * Check if execution is allowed */ canExecute() { switch(this.state){ case "CLOSED": return true; case "OPEN": if (this.nextAttemptTime && new Date() >= this.nextAttemptTime) { this.transitionTo("HALF_OPEN"); return true; } return false; case "HALF_OPEN": return this.halfOpenRequests < this.halfOpenLimit; default: return false; } } /** * Transition to new state */ transitionTo(newState) { const oldState = this.state; if (oldState === newState) { return; } this.state = newState; this.logger.info('Circuit state transition', { name: this.name, from: oldState, to: newState, failureCount: this.failureCount, successCount: this.successCount }); switch(newState){ case "CLOSED": this.failureCount = 0; this.successCount = 0; this.halfOpenRequests = 0; this.currentAttempt = 0; delete this.nextAttemptTime; break; case "OPEN": this.successCount = 0; this.halfOpenRequests = 0; const delayIndex = Math.min(this.currentAttempt - 1, this.delays.length - 1); const delayMs = this.delays[Math.max(0, delayIndex)]; this.nextAttemptTime = new Date(Date.now() + delayMs); break; case "HALF_OPEN": this.successCount = 0; this.failureCount = 0; this.halfOpenRequests = 0; break; } this.emit('state:transition', { name: this.name, from: oldState, to: newState, state: this.getState() }); } /** * Create circuit open error */ createCircuitOpenError() { const error = new Error(`Circuit breaker '${this.name}' is OPEN. Next attempt at ${this.nextAttemptTime?.toISOString() || 'unknown'}`); error.name = 'CircuitOpenError'; error.circuitName = this.name; error.state = this.getState(); return error; } /** * Force state transition (for testing) */ forceState(state) { this.logger.warn('Forcing circuit state', { name: this.name, from: this.state, to: state }); this.transitionTo(state); } } //# sourceMappingURL=circuit-breaker.js.map