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.

328 lines (327 loc) 11.9 kB
import logger from '../logger.js'; export class TimeoutManager { static activeTimeouts = new Map(); static completedOperations = new Map(); static isTestEnvironment = false; static currentEnvironment = 'local'; static config; static environmentTimeouts = { local: { unitTest: 30000, integrationTest: 120000, e2eTest: 300000, setupTimeout: 30000, teardownTimeout: 20000, hookTimeout: 15000, llmRequestTimeout: 60000, fileOperationTimeout: 10000, networkOperationTimeout: 30000, databaseOperationTimeout: 15000, processStartupTimeout: 60000, processShutdownTimeout: 30000, resourceCleanupTimeout: 10000, warningThreshold: 0.8, maxRetries: 3, retryBackoffMs: 1000 }, ci: { unitTest: 60000, integrationTest: 180000, e2eTest: 600000, setupTimeout: 60000, teardownTimeout: 45000, hookTimeout: 30000, llmRequestTimeout: 120000, fileOperationTimeout: 20000, networkOperationTimeout: 60000, databaseOperationTimeout: 30000, processStartupTimeout: 120000, processShutdownTimeout: 60000, resourceCleanupTimeout: 30000, warningThreshold: 0.7, maxRetries: 2, retryBackoffMs: 2000 }, github: { unitTest: 90000, integrationTest: 300000, e2eTest: 900000, setupTimeout: 90000, teardownTimeout: 60000, hookTimeout: 45000, llmRequestTimeout: 180000, fileOperationTimeout: 30000, networkOperationTimeout: 90000, databaseOperationTimeout: 45000, processStartupTimeout: 180000, processShutdownTimeout: 90000, resourceCleanupTimeout: 45000, warningThreshold: 0.6, maxRetries: 1, retryBackoffMs: 3000 }, docker: { unitTest: 45000, integrationTest: 240000, e2eTest: 720000, setupTimeout: 75000, teardownTimeout: 45000, hookTimeout: 30000, llmRequestTimeout: 150000, fileOperationTimeout: 25000, networkOperationTimeout: 60000, databaseOperationTimeout: 30000, processStartupTimeout: 150000, processShutdownTimeout: 75000, resourceCleanupTimeout: 30000, warningThreshold: 0.75, maxRetries: 2, retryBackoffMs: 1500 } }; static initialize(customConfig) { if (process.env.NODE_ENV !== 'test' && !process.env.VITEST) { logger.warn('TimeoutManager should only be used in test environment'); return; } this.isTestEnvironment = true; this.detectEnvironment(); this.config = { ...this.environmentTimeouts[this.currentEnvironment] }; if (customConfig) { this.config = { ...this.config, ...customConfig }; } logger.debug({ environment: this.currentEnvironment, config: this.config }, 'TimeoutManager initialized'); } static detectEnvironment() { if (process.env.GITHUB_ACTIONS === 'true') { this.currentEnvironment = 'github'; } else if (process.env.CI === 'true') { this.currentEnvironment = 'ci'; } else if (process.env.DOCKER === 'true' || process.cwd().includes('docker')) { this.currentEnvironment = 'docker'; } else { this.currentEnvironment = 'local'; } logger.debug({ environment: this.currentEnvironment }, 'Test environment detected'); } static startOperation(operationId, operationType, customTimeoutMs, onTimeout) { if (!this.isTestEnvironment) { return; } this.clearOperation(operationId); const timeoutMs = customTimeoutMs || this.config[operationType]; const startTime = Date.now(); const warningTimeoutMs = timeoutMs * this.config.warningThreshold; const warningId = setTimeout(() => { const elapsed = Date.now() - startTime; logger.warn({ operationId, operationType, elapsed, timeout: timeoutMs, remainingMs: timeoutMs - elapsed }, 'Operation timeout warning - approaching limit'); }, warningTimeoutMs); const timeoutId = setTimeout(() => { const duration = Date.now() - startTime; const result = { operationId, startTime, endTime: Date.now(), duration, timedOut: true, warnings: [`Operation timed out after ${duration}ms (limit: ${timeoutMs}ms)`], errorMessage: `Operation '${operationId}' (${operationType}) timed out after ${duration}ms` }; this.completedOperations.set(operationId, result); this.activeTimeouts.delete(operationId); logger.error({ operationId, operationType, duration, timeoutMs }, 'Operation timed out'); if (onTimeout) { try { onTimeout(); } catch (error) { logger.error({ err: error, operationId }, 'Error in timeout handler'); } } }, timeoutMs); this.activeTimeouts.set(operationId, { operationId, timeoutId, startTime, timeoutMs, operationType, warningId, onTimeout }); logger.debug({ operationId, operationType, timeoutMs, warningThreshold: this.config.warningThreshold }, 'Operation timeout monitoring started'); } static completeOperation(operationId, success = true) { if (!this.isTestEnvironment) { return null; } const activeTimeout = this.activeTimeouts.get(operationId); if (!activeTimeout) { logger.debug({ operationId }, 'No active timeout found for operation'); return null; } clearTimeout(activeTimeout.timeoutId); if (activeTimeout.warningId) { clearTimeout(activeTimeout.warningId); } const endTime = Date.now(); const duration = endTime - activeTimeout.startTime; const result = { operationId, startTime: activeTimeout.startTime, endTime, duration, timedOut: false, warnings: [] }; const slowThreshold = activeTimeout.timeoutMs * 0.5; if (duration > slowThreshold) { result.warnings.push(`Operation took ${duration}ms (${Math.round(duration / activeTimeout.timeoutMs * 100)}% of timeout limit)`); } if (!success) { result.errorMessage = `Operation '${operationId}' failed after ${duration}ms`; } this.completedOperations.set(operationId, result); this.activeTimeouts.delete(operationId); logger.debug({ operationId, duration, success, warnings: result.warnings.length }, 'Operation completed'); return result; } static clearOperation(operationId) { if (!this.isTestEnvironment) { return; } const activeTimeout = this.activeTimeouts.get(operationId); if (activeTimeout) { clearTimeout(activeTimeout.timeoutId); if (activeTimeout.warningId) { clearTimeout(activeTimeout.warningId); } this.activeTimeouts.delete(operationId); logger.debug({ operationId }, 'Operation timeout cleared'); } } static getTimeout(operationType) { return this.config[operationType]; } static async withTimeout(operationId, operationType, operation, customTimeoutMs) { if (!this.isTestEnvironment) { return operation(); } return new Promise((resolve, reject) => { let completed = false; this.startOperation(operationId, operationType, customTimeoutMs, () => { if (!completed) { completed = true; reject(new Error(`Operation '${operationId}' timed out after ${customTimeoutMs || this.config[operationType]}ms`)); } }); Promise.resolve(operation()) .then((result) => { if (!completed) { completed = true; this.completeOperation(operationId, true); resolve(result); } }) .catch((error) => { if (!completed) { completed = true; this.completeOperation(operationId, false); reject(error); } }); }); } static getStats() { const completed = Array.from(this.completedOperations.values()); const timedOut = completed.filter(op => op.timedOut); const successful = completed.filter(op => !op.timedOut && op.duration); const avgDuration = successful.length > 0 ? successful.reduce((sum, op) => sum + (op.duration || 0), 0) / successful.length : 0; return { activeOperations: this.activeTimeouts.size, completedOperations: this.completedOperations.size, timedOutOperations: timedOut.length, averageDuration: Math.round(avgDuration), currentEnvironment: this.currentEnvironment, config: { ...this.config } }; } static getOperationReport(operationId) { return this.completedOperations.get(operationId) || null; } static getAllReports() { return Array.from(this.completedOperations.values()); } static reset() { if (!this.isTestEnvironment) { return; } for (const activeTimeout of this.activeTimeouts.values()) { clearTimeout(activeTimeout.timeoutId); if (activeTimeout.warningId) { clearTimeout(activeTimeout.warningId); } } this.activeTimeouts.clear(); this.completedOperations.clear(); logger.debug('TimeoutManager reset'); } static updateConfig(newConfig) { if (!this.isTestEnvironment) { return; } this.config = { ...this.config, ...newConfig }; logger.debug({ config: this.config }, 'TimeoutManager configuration updated'); } static getCurrentEnvironment() { return this.currentEnvironment; } static forceEnvironment(environment) { if (!this.isTestEnvironment) { return; } this.currentEnvironment = environment; this.config = { ...this.environmentTimeouts[environment] }; logger.debug({ environment }, 'Environment forced'); } } export function initializeTimeoutManager(config) { TimeoutManager.initialize(config); } export async function withTimeout(operationId, operationType, operation, customTimeoutMs) { return TimeoutManager.withTimeout(operationId, operationType, operation, customTimeoutMs); } export function getTimeout(operationType) { return TimeoutManager.getTimeout(operationType); } export function getTimeoutStats() { return TimeoutManager.getStats(); }