UNPKG

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
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; }; } }