vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
259 lines (258 loc) • 11.2 kB
JavaScript
import logger from '../logger.js';
export class RecursionGuard {
static callStacks = new Map();
static callHistory = new Map();
static instanceCounters = new Map();
static DEFAULT_CONFIG = {
maxDepth: 10,
enableLogging: true,
executionTimeout: 30000,
trackHistory: true
};
static executeWithRecursionGuard(methodName, operation, config = {}, instanceId) {
const fullConfig = { ...this.DEFAULT_CONFIG, ...config };
const callKey = instanceId ? `${methodName}:${instanceId}` : methodName;
const startTime = Date.now();
return new Promise((resolve) => {
const currentStack = this.callStacks.get(callKey) || [];
const currentDepth = currentStack.length;
if (currentDepth >= fullConfig.maxDepth) {
if (fullConfig.enableLogging) {
logger.warn({
methodName,
instanceId,
currentDepth,
maxDepth: fullConfig.maxDepth,
callStack: currentStack.map(call => ({
method: call.methodName,
depth: call.depth,
timestamp: call.timestamp
}))
}, 'Recursion limit exceeded, preventing infinite recursion');
}
resolve({
success: false,
recursionDetected: true,
callDepth: currentDepth,
executionTime: Date.now() - startTime,
error: new Error(`Recursion limit exceeded for method: ${methodName} (depth: ${currentDepth})`)
});
return;
}
const currentCall = {
methodName,
timestamp: startTime,
depth: currentDepth,
instanceId
};
currentStack.push(currentCall);
this.callStacks.set(callKey, currentStack);
if (fullConfig.trackHistory) {
const history = this.callHistory.get(callKey) || [];
history.push({ ...currentCall });
if (history.length > 100) {
history.splice(0, history.length - 100);
}
this.callHistory.set(callKey, history);
}
const timeoutId = setTimeout(() => {
this.removeFromStack(callKey, currentCall);
if (fullConfig.enableLogging) {
logger.warn({
methodName,
instanceId,
executionTime: Date.now() - startTime,
timeout: fullConfig.executionTimeout
}, 'Method execution timeout, possible infinite loop');
}
resolve({
success: false,
recursionDetected: false,
callDepth: currentDepth,
executionTime: Date.now() - startTime,
error: new Error(`Method execution timeout: ${methodName} (${fullConfig.executionTimeout}ms)`)
});
}, fullConfig.executionTimeout);
try {
const operationResult = operation();
if (operationResult instanceof Promise) {
operationResult
.then(result => {
clearTimeout(timeoutId);
this.removeFromStack(callKey, currentCall);
if (fullConfig.enableLogging && currentDepth > 0) {
logger.debug({
methodName,
instanceId,
depth: currentDepth,
executionTime: Date.now() - startTime
}, 'Async method execution completed successfully');
}
resolve({
success: true,
result,
recursionDetected: false,
callDepth: currentDepth,
executionTime: Date.now() - startTime
});
})
.catch(error => {
clearTimeout(timeoutId);
this.removeFromStack(callKey, currentCall);
if (fullConfig.enableLogging) {
logger.warn({
err: error,
methodName,
instanceId,
depth: currentDepth,
executionTime: Date.now() - startTime
}, 'Async method execution failed');
}
resolve({
success: false,
recursionDetected: false,
callDepth: currentDepth,
executionTime: Date.now() - startTime,
error: error instanceof Error ? error : new Error(String(error))
});
});
}
else {
clearTimeout(timeoutId);
this.removeFromStack(callKey, currentCall);
if (fullConfig.enableLogging && currentDepth > 0) {
logger.debug({
methodName,
instanceId,
depth: currentDepth,
executionTime: Date.now() - startTime
}, 'Sync method execution completed successfully');
}
resolve({
success: true,
result: operationResult,
recursionDetected: false,
callDepth: currentDepth,
executionTime: Date.now() - startTime
});
}
}
catch (error) {
clearTimeout(timeoutId);
this.removeFromStack(callKey, currentCall);
if (fullConfig.enableLogging) {
logger.warn({
err: error,
methodName,
instanceId,
depth: currentDepth,
executionTime: Date.now() - startTime
}, 'Sync method execution failed');
}
resolve({
success: false,
recursionDetected: false,
callDepth: currentDepth,
executionTime: Date.now() - startTime,
error: error instanceof Error ? error : new Error(String(error))
});
}
});
}
static removeFromStack(callKey, callToRemove) {
const stack = this.callStacks.get(callKey);
if (stack) {
const index = stack.findIndex(call => call.timestamp === callToRemove.timestamp &&
call.depth === callToRemove.depth);
if (index !== -1) {
stack.splice(index, 1);
if (stack.length === 0) {
this.callStacks.delete(callKey);
}
else {
this.callStacks.set(callKey, stack);
}
}
}
}
static isMethodExecuting(methodName, instanceId) {
const callKey = instanceId ? `${methodName}:${instanceId}` : methodName;
const stack = this.callStacks.get(callKey);
return stack ? stack.length > 0 : false;
}
static getCurrentDepth(methodName, instanceId) {
const callKey = instanceId ? `${methodName}:${instanceId}` : methodName;
const stack = this.callStacks.get(callKey);
return stack ? stack.length : 0;
}
static getCallStack(methodName, instanceId) {
const callKey = instanceId ? `${methodName}:${instanceId}` : methodName;
return this.callStacks.get(callKey) || [];
}
static getCallHistory(methodName, instanceId) {
const callKey = instanceId ? `${methodName}:${instanceId}` : methodName;
return this.callHistory.get(callKey) || [];
}
static clearAll() {
this.callStacks.clear();
this.callHistory.clear();
this.instanceCounters.clear();
}
static clearMethod(methodName, instanceId) {
const callKey = instanceId ? `${methodName}:${instanceId}` : methodName;
this.callStacks.delete(callKey);
this.callHistory.delete(callKey);
}
static generateInstanceId(methodName) {
const counter = this.instanceCounters.get(methodName) || 0;
const newCounter = counter + 1;
this.instanceCounters.set(methodName, newCounter);
return `${methodName}_${newCounter}_${Date.now()}`;
}
static getStatistics() {
let deepestStack = 0;
const methodStats = {};
for (const [callKey, stack] of this.callStacks.entries()) {
if (stack.length > deepestStack) {
deepestStack = stack.length;
}
const history = this.callHistory.get(callKey) || [];
const lastCall = stack.length > 0 ? stack[stack.length - 1].timestamp :
history.length > 0 ? history[history.length - 1].timestamp : undefined;
methodStats[callKey] = {
currentDepth: stack.length,
historyCount: history.length,
lastCall
};
}
for (const [callKey, history] of this.callHistory.entries()) {
if (!methodStats[callKey]) {
methodStats[callKey] = {
currentDepth: 0,
historyCount: history.length,
lastCall: history.length > 0 ? history[history.length - 1].timestamp : undefined
};
}
}
return {
activeStacks: this.callStacks.size,
totalMethods: Object.keys(methodStats).length,
deepestStack,
methodStats
};
}
static createMethodGuard(methodName, config = {}) {
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
const instanceId = RecursionGuard.generateInstanceId(methodName);
const result = await RecursionGuard.executeWithRecursionGuard(methodName, () => originalMethod.apply(this, args), config, instanceId);
if (!result.success) {
throw result.error || new Error(`Method execution failed: ${methodName}`);
}
return result.result;
};
return descriptor;
};
}
}