UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

250 lines 8.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.circuitRegistry = exports.CircuitBreakerRegistry = exports.CircuitBreaker = exports.CircuitState = void 0; var CircuitState; (function (CircuitState) { CircuitState["CLOSED"] = "CLOSED"; CircuitState["OPEN"] = "OPEN"; CircuitState["HALF_OPEN"] = "HALF_OPEN"; })(CircuitState || (exports.CircuitState = CircuitState = {})); class CircuitBreaker { constructor(name, config = {}) { this.name = name; this.state = CircuitState.CLOSED; this.failures = 0; this.successes = 0; this.totalCalls = 0; this.nextAttemptTime = 0; this.stateChanges = 0; this.createdAt = Date.now(); this.recentFailures = []; this.config = { failureThreshold: 5, recoveryTimeout: 60000, successThreshold: 3, monitoringWindow: 300000, maxRetryAttempts: 3, exponentialBackoff: true, onStateChange: () => { }, onFailure: () => { }, onSuccess: () => { }, ...config }; } async execute(operation, fallback) { this.totalCalls++; if (this.state === CircuitState.OPEN) { if (Date.now() < this.nextAttemptTime) { const error = new Error(`Circuit breaker ${this.name} is OPEN. Next attempt at ${new Date(this.nextAttemptTime)}`); this.config.onFailure(error); if (fallback) { return await Promise.resolve(fallback()); } throw error; } else { this.setState(CircuitState.HALF_OPEN, 'Recovery timeout reached'); } } try { const result = await this.executeWithRetry(operation); this.onSuccess(); return result; } catch (error) { this.onFailure(error); if (fallback) { try { return await Promise.resolve(fallback()); } catch (fallbackError) { throw error; } } throw error; } } async executeWithRetry(operation) { let lastError; let baseDelay = 1000; for (let attempt = 1; attempt <= this.config.maxRetryAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === this.config.maxRetryAttempts) { break; } const delay = this.config.exponentialBackoff ? baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000 : baseDelay; await new Promise(resolve => setTimeout(resolve, Math.min(delay, 10000))); } } throw lastError; } onSuccess() { this.successes++; this.lastSuccessTime = Date.now(); this.config.onSuccess(); if (this.state === CircuitState.HALF_OPEN) { if (this.successes >= this.config.successThreshold) { this.setState(CircuitState.CLOSED, 'Success threshold reached in half-open state'); this.resetCounters(); } } else if (this.state === CircuitState.CLOSED) { this.failures = 0; this.recentFailures = []; } } onFailure(error) { this.failures++; this.lastFailureTime = Date.now(); this.config.onFailure(error); this.recentFailures.push({ timestamp: Date.now(), error: error.message, attempts: this.config.maxRetryAttempts }); const cutoff = Date.now() - this.config.monitoringWindow; this.recentFailures = this.recentFailures.filter(f => f.timestamp > cutoff); if (this.state === CircuitState.CLOSED || this.state === CircuitState.HALF_OPEN) { if (this.recentFailures.length >= this.config.failureThreshold) { this.setState(CircuitState.OPEN, `Failure threshold reached: ${this.recentFailures.length} failures`); this.scheduleRecovery(); } } } setState(newState, reason) { const oldState = this.state; this.state = newState; this.stateChanges++; console.log(`Circuit breaker ${this.name}: ${oldState} -> ${newState} (${reason})`); this.config.onStateChange(newState, reason); } scheduleRecovery() { if (this.recoveryTimer) { clearTimeout(this.recoveryTimer); } this.nextAttemptTime = Date.now() + this.config.recoveryTimeout; this.recoveryTimer = setTimeout(() => { if (this.state === CircuitState.OPEN) { this.setState(CircuitState.HALF_OPEN, 'Recovery timer expired'); } }, this.config.recoveryTimeout); } resetCounters() { this.failures = 0; this.successes = 0; this.recentFailures = []; } open(reason = 'Manual intervention') { this.setState(CircuitState.OPEN, reason); this.scheduleRecovery(); } close(reason = 'Manual intervention') { this.setState(CircuitState.CLOSED, reason); this.resetCounters(); if (this.recoveryTimer) { clearTimeout(this.recoveryTimer); this.recoveryTimer = undefined; } } getStats() { const now = Date.now(); const windowStart = now - this.config.monitoringWindow; const recentFailureCount = this.recentFailures.filter(f => f.timestamp > windowStart).length; const recentCallCount = Math.max(recentFailureCount + this.successes, 1); return { state: this.state, failures: this.failures, successes: this.successes, totalCalls: this.totalCalls, failureRate: recentFailureCount / recentCallCount, lastFailureTime: this.lastFailureTime, lastSuccessTime: this.lastSuccessTime, uptime: now - this.createdAt, stateChanges: this.stateChanges }; } getFailureHistory() { return [...this.recentFailures]; } isHealthy() { const stats = this.getStats(); return stats.state === CircuitState.CLOSED && stats.failureRate < 0.1; } destroy() { if (this.recoveryTimer) { clearTimeout(this.recoveryTimer); this.recoveryTimer = undefined; } } } exports.CircuitBreaker = CircuitBreaker; class CircuitBreakerRegistry { constructor() { this.breakers = new Map(); } static getInstance() { if (!CircuitBreakerRegistry.instance) { CircuitBreakerRegistry.instance = new CircuitBreakerRegistry(); } return CircuitBreakerRegistry.instance; } getBreaker(name, config) { if (!this.breakers.has(name)) { this.breakers.set(name, new CircuitBreaker(name, config)); } return this.breakers.get(name); } removeBreaker(name) { const breaker = this.breakers.get(name); if (breaker) { breaker.destroy(); return this.breakers.delete(name); } return false; } getAllStats() { const stats = {}; for (const [name, breaker] of this.breakers.entries()) { stats[name] = breaker.getStats(); } return stats; } getHealthStatus() { const healthy = []; const unhealthy = []; for (const [name, breaker] of this.breakers.entries()) { if (breaker.isHealthy()) { healthy.push(name); } else { unhealthy.push(name); } } return { healthy, unhealthy }; } emergencyStop() { for (const [name, breaker] of this.breakers.entries()) { breaker.open(`Emergency stop triggered`); } } resetAll() { for (const [name, breaker] of this.breakers.entries()) { breaker.close(`Global reset triggered`); } } destroy() { for (const breaker of this.breakers.values()) { breaker.destroy(); } this.breakers.clear(); } } exports.CircuitBreakerRegistry = CircuitBreakerRegistry; exports.circuitRegistry = CircuitBreakerRegistry.getInstance(); //# sourceMappingURL=circuitBreaker.js.map