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.

581 lines (580 loc) 25 kB
import { EventEmitter } from 'events'; import { ErrorFactory, createErrorContext } from '../utils/enhanced-errors.js'; import { createSuccess, createFailure } from './unified-lifecycle-manager.js'; import logger from '../../../logger.js'; export function createTaskId(id) { if (!id || id.trim().length === 0) { throw new Error('Task ID cannot be empty'); } return id; } export function createAgentId(id) { if (!id || id.trim().length === 0) { throw new Error('Agent ID cannot be empty'); } return id; } export function createExecutionId(id) { if (!id || id.trim().length === 0) { throw new Error('Execution ID cannot be empty'); } return id; } export function createStreamId(id) { if (!id || id.trim().length === 0) { throw new Error('Stream ID cannot be empty'); } return id; } export class UnifiedTaskExecutionEngine extends EventEmitter { static instance = null; config; agents = new Map(); executions = new Map(); monitors = new Map(); streams = new Map(); schedulingQueue = []; schedulingTimer = null; isSchedulingActive = false; streamingQueues = new Map(); streamingTimers = new Map(); watchdogTimer = null; watchdogConfigs = new Map(); constructor(config) { super(); this.config = config; this.initializeDefaultWatchdogConfigs(); this.startScheduler(); if (config.watchdog.enabled) { this.startWatchdog(); } logger.info('Unified Task Execution Engine initialized'); } static getInstance(config) { if (!UnifiedTaskExecutionEngine.instance) { if (!config) { throw new Error('Configuration required for first initialization'); } UnifiedTaskExecutionEngine.instance = new UnifiedTaskExecutionEngine(config); } return UnifiedTaskExecutionEngine.instance; } static resetInstance() { if (UnifiedTaskExecutionEngine.instance) { UnifiedTaskExecutionEngine.instance.dispose(); UnifiedTaskExecutionEngine.instance = null; } } async registerAgent(agent) { try { this.agents.set(agent.id, { ...agent }); this.emit('agentRegistered', agent); logger.info(`Agent registered: ${agent.id}`); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to register agent: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedTaskExecutionEngine', 'registerAgent') .metadata({ agentId: agent.id }) .build(), { cause: error instanceof Error ? error : undefined })); } } async unregisterAgent(agentId) { try { const agent = this.agents.get(agentId); if (!agent) { return createFailure(ErrorFactory.createError('validation', `Agent not found: ${agentId}`, createErrorContext('UnifiedTaskExecutionEngine', 'unregisterAgent') .metadata({ agentId }) .build())); } for (const execution of this.executions.values()) { if (execution.agentId === agentId && execution.status === 'running') { await this.cancelExecution(execution.executionId); } } this.agents.delete(agentId); this.emit('agentUnregistered', agent); logger.info(`Agent unregistered: ${agentId}`); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to unregister agent: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedTaskExecutionEngine', 'unregisterAgent') .metadata({ agentId }) .build(), { cause: error instanceof Error ? error : undefined })); } } async updateAgentStatus(agentId, status, usage) { try { const agent = this.agents.get(agentId); if (!agent) { return createFailure(ErrorFactory.createError('validation', `Agent not found: ${agentId}`, createErrorContext('UnifiedTaskExecutionEngine', 'updateAgentStatus') .metadata({ agentId }) .build())); } agent.status = status; agent.metadata.lastHeartbeat = new Date(); if (usage) { Object.assign(agent.currentUsage, usage); } this.emit('agentStatusUpdated', agent); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to update agent status: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedTaskExecutionEngine', 'updateAgentStatus') .metadata({ agentId }) .build(), { cause: error instanceof Error ? error : undefined })); } } async submitTask(task, resourceRequirements) { try { const executionId = createExecutionId(`exec_${task.id}_${Date.now()}`); const taskId = createTaskId(task.id); const execution = { executionId, taskId, status: 'queued', priority: task.priority || 'medium', scheduledAt: new Date(), retryCount: 0, maxRetries: this.config.watchdog.maxRetries, resourceRequirements: { memoryMB: 256, cpuWeight: 1, estimatedDurationMinutes: (task.estimatedHours || 0.5) * 60, ...resourceRequirements } }; this.executions.set(executionId, execution); this.schedulingQueue.push(execution); this.emit('taskSubmitted', execution); logger.info(`Task submitted for execution: ${taskId}`); return createSuccess(executionId); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to submit task: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedTaskExecutionEngine', 'submitTask') .metadata({ taskId: task.id }) .build(), { cause: error instanceof Error ? error : undefined })); } } async cancelExecution(executionId) { try { const execution = this.executions.get(executionId); if (!execution) { return createFailure(ErrorFactory.createError('validation', `Execution not found: ${executionId}`, createErrorContext('UnifiedTaskExecutionEngine', 'cancelExecution') .metadata({ executionId }) .build())); } if (execution.status === 'completed' || execution.status === 'cancelled') { return createFailure(ErrorFactory.createError('validation', `Cannot cancel execution in status: ${execution.status}`, createErrorContext('UnifiedTaskExecutionEngine', 'cancelExecution') .metadata({ executionId, status: execution.status }) .build())); } execution.status = 'cancelled'; execution.completedAt = new Date(); const queueIndex = this.schedulingQueue.findIndex(e => e.executionId === executionId); if (queueIndex !== -1) { this.schedulingQueue.splice(queueIndex, 1); } this.monitors.delete(execution.taskId); this.emit('executionCancelled', execution); logger.info(`Execution cancelled: ${executionId}`); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to cancel execution: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedTaskExecutionEngine', 'cancelExecution') .metadata({ executionId }) .build(), { cause: error instanceof Error ? error : undefined })); } } getExecution(executionId) { return this.executions.get(executionId) || null; } getAllExecutions() { return Array.from(this.executions.values()); } getExecutionsByStatus(status) { return Array.from(this.executions.values()).filter(e => e.status === status); } startScheduler() { if (this.schedulingTimer) { clearInterval(this.schedulingTimer); } this.schedulingTimer = setInterval(() => { this.processSchedulingQueue().catch(error => { logger.error('Scheduling error:', error); }); }, this.config.scheduling.schedulingInterval); logger.info('Task scheduler started'); } async processSchedulingQueue() { if (this.isSchedulingActive || this.schedulingQueue.length === 0) { return; } this.isSchedulingActive = true; try { const sortedTasks = this.sortTasksByAlgorithm(this.schedulingQueue); const batchSize = Math.min(this.config.scheduling.batchSize, sortedTasks.length); const batch = sortedTasks.splice(0, batchSize); for (const execution of batch) { const agent = await this.selectOptimalAgent(execution); if (agent) { await this.assignTaskToAgent(execution, agent); } } for (const execution of batch) { const index = this.schedulingQueue.findIndex(e => e.executionId === execution.executionId); if (index !== -1) { this.schedulingQueue.splice(index, 1); } } } finally { this.isSchedulingActive = false; } } sortTasksByAlgorithm(tasks) { const sorted = [...tasks]; switch (this.config.scheduling.algorithm) { case 'priority_first': return sorted.sort((a, b) => this.comparePriority(b.priority, a.priority)); case 'earliest_deadline': return sorted.sort((a, b) => { const aDeadline = a.timeoutAt?.getTime() || Infinity; const bDeadline = b.timeoutAt?.getTime() || Infinity; return aDeadline - bDeadline; }); case 'shortest_job': return sorted.sort((a, b) => (a.resourceRequirements.estimatedDurationMinutes || 0) - (b.resourceRequirements.estimatedDurationMinutes || 0)); case 'resource_balanced': return sorted.sort((a, b) => this.compareResourceRequirements(a, b)); case 'hybrid_optimal': return sorted.sort((a, b) => this.calculateTaskScore(b) - this.calculateTaskScore(a)); default: return sorted; } } comparePriority(a, b) { const priorityOrder = { 'critical': 4, 'high': 3, 'medium': 2, 'low': 1 }; return priorityOrder[a] - priorityOrder[b]; } compareResourceRequirements(a, b) { const aScore = a.resourceRequirements.memoryMB + a.resourceRequirements.cpuWeight * 100; const bScore = b.resourceRequirements.memoryMB + b.resourceRequirements.cpuWeight * 100; return aScore - bScore; } calculateTaskScore(execution) { const priorityScore = this.comparePriority(execution.priority, 'low') * 25; const urgencyScore = execution.timeoutAt ? Math.max(0, 25 - (execution.timeoutAt.getTime() - Date.now()) / (1000 * 60 * 60)) : 0; const resourceScore = Math.max(0, 25 - execution.resourceRequirements.memoryMB / 100); const durationScore = Math.max(0, 25 - execution.resourceRequirements.estimatedDurationMinutes); return priorityScore + urgencyScore + resourceScore + durationScore; } async selectOptimalAgent(execution) { const availableAgents = Array.from(this.agents.values()) .filter(agent => agent.status === 'idle' && this.canAgentHandleTask(agent, execution)); if (availableAgents.length === 0) { return null; } return availableAgents.reduce((best, current) => { const bestScore = this.calculateAgentScore(best, execution); const currentScore = this.calculateAgentScore(current, execution); return currentScore > bestScore ? current : best; }); } canAgentHandleTask(agent, execution) { const memoryAvailable = agent.capacity.maxMemoryMB - agent.currentUsage.memoryMB; const cpuAvailable = agent.capacity.maxCpuWeight - agent.currentUsage.cpuWeight; const tasksAvailable = agent.capacity.maxConcurrentTasks - agent.currentUsage.activeTasks; return memoryAvailable >= execution.resourceRequirements.memoryMB && cpuAvailable >= execution.resourceRequirements.cpuWeight && tasksAvailable > 0; } calculateAgentScore(agent, _execution) { const memoryUtilization = agent.currentUsage.memoryMB / agent.capacity.maxMemoryMB; const cpuUtilization = agent.currentUsage.cpuWeight / agent.capacity.maxCpuWeight; const taskUtilization = agent.currentUsage.activeTasks / agent.capacity.maxConcurrentTasks; const utilizationScore = (1 - (memoryUtilization + cpuUtilization + taskUtilization) / 3) * 50; const performanceScore = agent.metadata.successRate * 50; return utilizationScore + performanceScore; } async assignTaskToAgent(execution, agent) { execution.agentId = agent.id; execution.status = 'running'; execution.startedAt = new Date(); agent.currentUsage.memoryMB += execution.resourceRequirements.memoryMB; agent.currentUsage.cpuWeight += execution.resourceRequirements.cpuWeight; agent.currentUsage.activeTasks += 1; agent.status = 'busy'; await this.startTaskMonitoring(execution); this.emit('taskAssigned', { execution, agent }); logger.info(`Task assigned: ${execution.taskId} -> ${agent.id}`); } initializeDefaultWatchdogConfigs() { const defaultConfigs = [ { taskType: 'default', timeoutMinutes: this.config.watchdog.defaultTimeout, warningThresholdMinutes: this.config.watchdog.defaultTimeout * 0.8, maxRetries: this.config.watchdog.maxRetries, escalationDelayMinutes: 5, healthCheckIntervalMinutes: this.config.watchdog.healthCheckInterval }, { taskType: 'quick', timeoutMinutes: 15, warningThresholdMinutes: 10, maxRetries: 2, escalationDelayMinutes: 2, healthCheckIntervalMinutes: 1 }, { taskType: 'long_running', timeoutMinutes: 120, warningThresholdMinutes: 90, maxRetries: 1, escalationDelayMinutes: 10, healthCheckIntervalMinutes: 5 } ]; for (const config of defaultConfigs) { this.watchdogConfigs.set(config.taskType, config); } } startWatchdog() { if (this.watchdogTimer) { clearInterval(this.watchdogTimer); } this.watchdogTimer = setInterval(() => { this.processWatchdogChecks().catch(error => { logger.error('Watchdog error:', error); }); }, this.config.watchdog.healthCheckInterval * 60 * 1000); logger.info('Execution watchdog started'); } async startTaskMonitoring(execution) { const config = this.watchdogConfigs.get('default'); const now = new Date(); const monitor = { taskId: execution.taskId, agentId: execution.agentId, startTime: now, lastHeartbeat: now, timeoutAt: new Date(now.getTime() + config.timeoutMinutes * 60 * 1000), warningAt: new Date(now.getTime() + config.warningThresholdMinutes * 60 * 1000), status: 'monitoring', retryCount: 0, escalationLevel: 0, taskType: 'default', estimatedDuration: execution.resourceRequirements.estimatedDurationMinutes }; this.monitors.set(execution.taskId, monitor); this.emit('monitoringStarted', monitor); } async processWatchdogChecks() { const now = new Date(); for (const monitor of this.monitors.values()) { const execution = Array.from(this.executions.values()) .find(e => e.taskId === monitor.taskId); if (!execution || execution.status !== 'running') { this.monitors.delete(monitor.taskId); continue; } if (now >= monitor.timeoutAt && monitor.status !== 'timeout') { await this.handleTaskTimeout(execution, monitor); } else if (now >= monitor.warningAt && monitor.status === 'monitoring') { await this.handleTaskWarning(execution, monitor); } } } async handleTaskTimeout(execution, monitor) { monitor.status = 'timeout'; execution.status = 'timeout'; execution.completedAt = new Date(); if (execution.agentId) { const agent = this.agents.get(execution.agentId); if (agent) { agent.currentUsage.memoryMB -= execution.resourceRequirements.memoryMB; agent.currentUsage.cpuWeight -= execution.resourceRequirements.cpuWeight; agent.currentUsage.activeTasks -= 1; if (agent.currentUsage.activeTasks === 0) { agent.status = 'idle'; } } } this.emit('taskTimeout', { execution, monitor }); logger.warn(`Task timeout: ${execution.taskId}`); if (execution.retryCount < execution.maxRetries) { await this.retryExecution(execution); } } async handleTaskWarning(execution, monitor) { monitor.status = 'warning'; this.emit('taskWarning', { execution, monitor }); logger.warn(`Task warning: ${execution.taskId} approaching timeout`); } async retryExecution(execution) { execution.retryCount += 1; execution.status = 'queued'; execution.agentId = undefined; execution.startedAt = undefined; execution.completedAt = undefined; this.schedulingQueue.push(execution); this.monitors.delete(execution.taskId); this.emit('executionRetry', execution); logger.info(`Retrying execution: ${execution.executionId} (attempt ${execution.retryCount})`); } async completeExecution(executionId, result) { try { const execution = this.executions.get(executionId); if (!execution) { return createFailure(ErrorFactory.createError('validation', `Execution not found: ${executionId}`, createErrorContext('UnifiedTaskExecutionEngine', 'completeExecution') .metadata({ executionId }) .build())); } execution.status = 'completed'; execution.completedAt = new Date(); execution.result = result; if (execution.startedAt) { execution.actualDuration = execution.completedAt.getTime() - execution.startedAt.getTime(); } if (execution.agentId) { const agent = this.agents.get(execution.agentId); if (agent) { agent.currentUsage.memoryMB -= execution.resourceRequirements.memoryMB; agent.currentUsage.cpuWeight -= execution.resourceRequirements.cpuWeight; agent.currentUsage.activeTasks -= 1; agent.metadata.totalTasksExecuted += 1; if (agent.currentUsage.activeTasks === 0) { agent.status = 'idle'; } const successCount = result?.success ? 1 : 0; agent.metadata.successRate = (agent.metadata.successRate * (agent.metadata.totalTasksExecuted - 1) + successCount) / agent.metadata.totalTasksExecuted; } } this.monitors.delete(execution.taskId); this.emit('executionCompleted', execution); logger.info(`Execution completed: ${executionId}`); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to complete execution: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedTaskExecutionEngine', 'completeExecution') .metadata({ executionId }) .build(), { cause: error instanceof Error ? error : undefined })); } } getExecutionStatistics() { const executions = Array.from(this.executions.values()); const total = executions.length; const byStatus = { queued: 0, running: 0, completed: 0, failed: 0, cancelled: 0, timeout: 0 }; let totalExecutionTime = 0; let completedCount = 0; let successCount = 0; for (const execution of executions) { byStatus[execution.status]++; if (execution.actualDuration) { totalExecutionTime += execution.actualDuration; completedCount++; if (execution.result?.success) { successCount++; } } } const averageExecutionTime = completedCount > 0 ? totalExecutionTime / completedCount : 0; const successRate = completedCount > 0 ? successCount / completedCount : 0; const agentUtilization = {}; for (const agent of this.agents.values()) { const utilization = agent.currentUsage.activeTasks / agent.capacity.maxConcurrentTasks; agentUtilization[agent.id] = utilization; } return { total, byStatus, averageExecutionTime, successRate, agentUtilization }; } dispose() { if (this.schedulingTimer) { clearInterval(this.schedulingTimer); this.schedulingTimer = null; } if (this.watchdogTimer) { clearInterval(this.watchdogTimer); this.watchdogTimer = null; } for (const timer of this.streamingTimers.values()) { clearInterval(timer); } this.streamingTimers.clear(); for (const execution of this.executions.values()) { if (execution.status === 'running' || execution.status === 'queued') { execution.status = 'cancelled'; } } this.agents.clear(); this.executions.clear(); this.monitors.clear(); this.streams.clear(); this.schedulingQueue.length = 0; this.streamingQueues.clear(); this.removeAllListeners(); logger.info('Unified Task Execution Engine disposed'); } } export function createDefaultConfig() { return { scheduling: { algorithm: 'hybrid_optimal', enableDynamicPriority: true, resourceConstraints: { maxMemoryMB: 8192, maxCpuWeight: 16, maxConcurrentTasks: 50, reservedMemoryMB: 1024, reservedCpuWeight: 2 }, batchSize: 10, schedulingInterval: 5000 }, streaming: { batchSize: 5, streamInterval: 2000, maxQueueSize: 100, priorityThreshold: 0.8, enableRealTimeStreaming: true, loadBalancingEnabled: true }, execution: { maxConcurrentExecutions: 20, enableLoadBalancing: true, enableResourceMonitoring: true, executionTimeout: 3600000 }, watchdog: { enabled: true, defaultTimeout: 30, healthCheckInterval: 1, maxRetries: 3, escalationEnabled: true }, lifecycle: { enableAutomation: true, transitionTimeout: 30000, enableStateHistory: true, enableDependencyTracking: true } }; }