ai-debug-local-mcp
Version:
🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
214 lines • 7.06 kB
JavaScript
/**
* Tool Execution Safety System
* Provides isolation, timeouts, and retry mechanisms for tool execution
*/
export class ToolExecutionSafetyWrapper {
static instance;
circuitBreakers = new Map();
executionMetrics = new Map();
static getInstance() {
if (!ToolExecutionSafetyWrapper.instance) {
ToolExecutionSafetyWrapper.instance = new ToolExecutionSafetyWrapper();
}
return ToolExecutionSafetyWrapper.instance;
}
/**
* Execute a tool with safety wrapper including timeouts, retries, and circuit breaker
*/
async executeTool(toolName, handler, options = {}) {
const startTime = Date.now();
const { timeout = 30000, // 30 second default timeout
maxRetries = 3, retryDelay = 1000, circuitBreakerThreshold = 5 } = options;
// Check circuit breaker
const circuitBreaker = this.getCircuitBreaker(toolName, circuitBreakerThreshold);
if (circuitBreaker.isOpen()) {
return {
success: false,
error: new Error(`Circuit breaker open for tool: ${toolName}`),
executionTime: Date.now() - startTime,
retryCount: 0,
circuitBreakerTriggered: true
};
}
let lastError;
let retryCount = 0;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
// Execute with timeout
const result = await Promise.race([
handler(),
this.createTimeoutPromise(timeout, toolName)
]);
// Success - record metrics and return
this.recordSuccess(toolName);
circuitBreaker.recordSuccess();
return {
success: true,
result,
executionTime: Date.now() - startTime,
retryCount,
circuitBreakerTriggered: false
};
}
catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
retryCount = attempt;
this.recordFailure(toolName, lastError);
circuitBreaker.recordFailure();
console.warn(`⚠️ Tool ${toolName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message}`);
// Don't retry on timeout or critical errors
if (this.isCriticalError(lastError) || attempt === maxRetries) {
break;
}
// Exponential backoff
const backoffDelay = retryDelay * Math.pow(2, attempt);
await this.sleep(backoffDelay);
}
}
return {
success: false,
error: lastError,
executionTime: Date.now() - startTime,
retryCount,
circuitBreakerTriggered: false
};
}
/**
* Create timeout promise that rejects after specified time
*/
createTimeoutPromise(timeout, toolName) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Tool ${toolName} timed out after ${timeout}ms`));
}, timeout);
});
}
/**
* Check if error is critical (shouldn't retry)
*/
isCriticalError(error) {
const criticalPatterns = [
'timeout',
'ENOTFOUND',
'permission denied',
'file not found',
'invalid argument'
];
const errorMessage = error.message.toLowerCase();
return criticalPatterns.some(pattern => errorMessage.includes(pattern));
}
/**
* Get or create circuit breaker for tool
*/
getCircuitBreaker(toolName, threshold) {
if (!this.circuitBreakers.has(toolName)) {
this.circuitBreakers.set(toolName, new CircuitBreaker(threshold));
}
return this.circuitBreakers.get(toolName);
}
/**
* Record successful execution
*/
recordSuccess(toolName) {
const metrics = this.getOrCreateMetrics(toolName);
metrics.successCount++;
metrics.lastExecutionTime = Date.now();
}
/**
* Record failed execution
*/
recordFailure(toolName, error) {
const metrics = this.getOrCreateMetrics(toolName);
metrics.failureCount++;
metrics.lastError = error.message;
metrics.lastExecutionTime = Date.now();
}
/**
* Get or create execution metrics for tool
*/
getOrCreateMetrics(toolName) {
if (!this.executionMetrics.has(toolName)) {
this.executionMetrics.set(toolName, {
toolName,
successCount: 0,
failureCount: 0,
lastExecutionTime: 0,
lastError: null
});
}
return this.executionMetrics.get(toolName);
}
/**
* Get execution statistics for monitoring
*/
getExecutionStats() {
const tools = {};
for (const [toolName, metrics] of this.executionMetrics.entries()) {
tools[toolName] = { ...metrics };
}
return {
tools,
totalTools: this.executionMetrics.size,
circuitBreakersOpen: Array.from(this.circuitBreakers.values())
.filter(cb => cb.isOpen()).length
};
}
/**
* Reset circuit breaker for a tool
*/
resetCircuitBreaker(toolName) {
const circuitBreaker = this.circuitBreakers.get(toolName);
if (circuitBreaker) {
circuitBreaker.reset();
console.log(`🔄 Reset circuit breaker for tool: ${toolName}`);
}
}
/**
* Sleep utility for retry delays
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/**
* Simple Circuit Breaker implementation
*/
class CircuitBreaker {
threshold;
failures = 0;
lastFailureTime = 0;
state = 'closed';
cooldownPeriod = 60000; // 1 minute
constructor(threshold) {
this.threshold = threshold;
}
isOpen() {
if (this.state === 'open') {
// Check if cooldown period has passed
if (Date.now() - this.lastFailureTime > this.cooldownPeriod) {
this.state = 'half-open';
return false;
}
return true;
}
return false;
}
recordSuccess() {
this.failures = 0;
this.state = 'closed';
}
recordFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
console.warn(`🚨 Circuit breaker OPENED after ${this.failures} failures`);
}
}
reset() {
this.failures = 0;
this.state = 'closed';
this.lastFailureTime = 0;
}
}
//# sourceMappingURL=tool-execution-safety.js.map