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
JavaScript
/**
* 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