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.

1,096 lines 85.7 kB
import { SentinelProtocol } from '../cli/sentinel-protocol.js'; import { EnhancedError, AgentError, TaskExecutionError, ValidationError, ResourceError, createErrorContext } from '../utils/enhanced-errors.js'; import { AppError } from '../../../utils/errors.js'; import { MemoryManager } from '../../code-map-generator/cache/memoryManager.js'; import { transportManager } from '../../../services/transport-manager/index.js'; import { getTimeoutManager } from '../utils/timeout-manager.js'; import { AgentIntegrationBridge } from './agent-integration-bridge.js'; import { WorkflowAwareAgentManager } from './workflow-aware-agent-manager.js'; import { OperationCircuitBreaker } from '../../../utils/operation-circuit-breaker.js'; import { InitializationMonitor } from '../../../utils/initialization-monitor.js'; import logger from '../../../logger.js'; class UniversalAgentCommunicationChannel { agentRegistry = null; taskQueue = null; responseProcessor = null; websocketServer = null; httpAgentAPI = null; sseNotifier = null; isInitialized = false; dependenciesPromise = null; constructor() { this.scheduleAsyncInitialization(); } scheduleAsyncInitialization() { process.nextTick(() => { this.dependenciesPromise = this.initializeDependencies().catch(error => { logger.error({ err: error }, 'Failed to initialize UniversalAgentCommunicationChannel dependencies'); }); }); } async ensureDependencies() { if (this.dependenciesPromise) { await this.dependenciesPromise; } } async initializeDependencies() { try { const { websocketServer } = await import('../../../services/websocket-server/index.js'); const { httpAgentAPI } = await import('../../../services/http-agent-api/index.js'); const { sseNotifier } = await import('../../../services/sse-notifier/index.js'); this.websocketServer = websocketServer; this.httpAgentAPI = httpAgentAPI; this.sseNotifier = sseNotifier; await this.ensureTransportServicesStarted(); this.logTransportEndpoints(); try { const AgentRegistryModule = await import('../../agent-registry/index.js'); const AgentTaskQueueModule = await import('../../agent-tasks/index.js'); const AgentResponseProcessorModule = await import('../../agent-response/index.js'); const AgentRegistry = AgentRegistryModule?.AgentRegistry?.getInstance(); const AgentTaskQueue = AgentTaskQueueModule?.AgentTaskQueue?.getInstance(); const AgentResponseProcessor = AgentResponseProcessorModule?.AgentResponseProcessor?.getInstance(); if (AgentRegistry && AgentTaskQueue && AgentResponseProcessor) { this.agentRegistry = AgentRegistry; this.taskQueue = AgentTaskQueue; this.responseProcessor = AgentResponseProcessor; logger.info('Universal agent communication channel initialized with all transports and agent modules'); } else { logger.warn('Some agent modules could not be imported due to circular dependencies, using fallback implementations'); if (AgentRegistry) { this.agentRegistry = AgentRegistry; } else { this.agentRegistry = this.createFallbackAgentRegistry(); } if (AgentTaskQueue) { this.taskQueue = AgentTaskQueue; } else { this.taskQueue = this.createFallbackTaskQueue(); } if (AgentResponseProcessor) { this.responseProcessor = AgentResponseProcessor; } else { this.responseProcessor = this.createFallbackResponseProcessor(); } logger.info('Universal agent communication channel initialized with mixed agent modules and fallbacks'); } } catch (agentModuleError) { logger.warn({ err: agentModuleError }, 'Agent modules not available, using fallback implementations'); this.agentRegistry = this.createFallbackAgentRegistry(); this.taskQueue = this.createFallbackTaskQueue(); this.responseProcessor = this.createFallbackResponseProcessor(); logger.info('Universal agent communication channel initialized with fallback agent modules'); } this.isInitialized = true; } catch (error) { logger.error({ err: error }, 'Failed to initialize universal communication channel'); this.websocketServer = null; this.httpAgentAPI = null; this.sseNotifier = null; this.agentRegistry = this.createFallbackAgentRegistry(); this.taskQueue = this.createFallbackTaskQueue(); this.responseProcessor = this.createFallbackResponseProcessor(); this.isInitialized = true; logger.warn('Universal agent communication channel initialized with minimal fallback implementations'); } } createFallbackAgentRegistry() { return { getAgent: async (agentId) => { logger.debug({ agentId }, 'Fallback agent registry: getAgent called'); return { id: agentId, transportType: 'stdio', status: 'online', lastSeen: Date.now(), httpEndpoint: undefined }; }, getInstance: () => this.agentRegistry }; } createFallbackTaskQueue() { const fallbackQueue = new Map(); return { addTask: async (agentId, taskAssignment) => { logger.debug({ agentId, taskAssignment }, 'Fallback task queue: addTask called'); if (!fallbackQueue.has(agentId)) { fallbackQueue.set(agentId, []); } fallbackQueue.get(agentId).push(taskAssignment); return `task-${Date.now()}`; }, }; } createFallbackResponseProcessor() { return { getAgentResponses: async (agentId) => { logger.debug({ agentId }, 'Fallback response processor: getAgentResponses called'); return []; } }; } async sendTask(agentId, taskPayload) { try { await this.ensureDependencies(); const agent = await this.agentRegistry?.getAgent(agentId); if (!agent) { logger.error({ agentId }, 'Agent not found - cannot send task'); return false; } const taskId = this.extractTaskIdFromPayload(taskPayload); const taskAssignment = { agentId: agentId, sentinelPayload: taskPayload, priority: 'normal', estimatedDuration: 1800000, metadata: { assignedBy: 'agent-orchestrator', assignedAt: Date.now() } }; let success = false; switch (agent.transportType) { case 'stdio': await this.taskQueue?.addTask(agentId, taskAssignment); success = true; break; case 'sse': { await this.taskQueue?.addTask(agentId, taskAssignment); const sessionId = agent.metadata?.preferences?.sessionId; if (this.sseNotifier && sessionId) { try { await this.sseNotifier.sendEvent(sessionId, 'taskAssigned', { agentId, taskId, taskPayload, priority: taskAssignment.priority, assignedAt: taskAssignment.metadata?.assignedAt || Date.now(), deadline: (typeof taskAssignment.metadata?.assignedAt === 'number' ? taskAssignment.metadata.assignedAt : Date.now()) + (24 * 60 * 60 * 1000), metadata: taskAssignment.metadata }); logger.info({ agentId, taskId, sessionId }, 'Task sent to agent via SSE notification'); await this.sseNotifier.broadcastEvent('taskAssignmentUpdate', { agentId, taskId, priority: taskAssignment.priority, assignedAt: taskAssignment.metadata?.assignedAt || Date.now(), transportType: 'sse' }); } catch (sseError) { logger.warn({ err: sseError, agentId, taskId }, 'SSE task notification failed, task still queued for polling'); } } else { logger.debug({ agentId, taskId, hasSSENotifier: !!this.sseNotifier, hasSessionId: !!sessionId }, 'SSE notification not available, task queued for polling only'); } success = true; break; } case 'websocket': if (this.websocketServer && this.websocketServer.isAgentConnected(agentId)) { try { success = await this.sendTaskViaWebSocket(agentId, taskId, taskPayload, taskAssignment.priority, Date.now()); if (success) { logger.info({ agentId, taskId }, 'Task sent to agent via WebSocket'); } else { logger.warn({ agentId, taskId }, 'WebSocket task delivery returned false, falling back to task queue'); await this.taskQueue?.addTask(agentId, taskAssignment); success = true; } } catch (error) { logger.warn({ err: error, agentId }, 'WebSocket task delivery failed, falling back to task queue'); await this.taskQueue?.addTask(agentId, taskAssignment); success = true; } } else { logger.warn({ agentId, hasWebSocketServer: !!this.websocketServer, isAgentConnected: this.websocketServer ? this.websocketServer.isAgentConnected(agentId) : false }, 'WebSocket server not available or agent not connected, falling back to task queue'); await this.taskQueue?.addTask(agentId, taskAssignment); success = true; } break; case 'http': if (this.httpAgentAPI && agent.httpEndpoint) { try { success = await this.sendTaskViaHTTP(agent, agentId, taskId, taskPayload, taskAssignment.priority); if (success) { logger.info({ agentId, taskId, httpEndpoint: agent.httpEndpoint }, 'Task sent to agent via HTTP'); } else { logger.warn({ agentId, taskId, httpEndpoint: agent.httpEndpoint }, 'HTTP task delivery returned false, falling back to task queue'); await this.taskQueue?.addTask(agentId, taskAssignment); success = true; } } catch (error) { logger.warn({ err: error, agentId, httpEndpoint: agent.httpEndpoint }, 'HTTP task delivery failed, falling back to task queue'); await this.taskQueue?.addTask(agentId, taskAssignment); success = true; } } else { logger.warn({ agentId, hasHttpAPI: !!this.httpAgentAPI, hasDeliverMethod: !!(this.httpAgentAPI && 'deliverTaskToAgent' in this.httpAgentAPI), hasEndpoint: !!agent.httpEndpoint, httpEndpoint: agent.httpEndpoint }, 'HTTP API not available or agent has no endpoint, falling back to task queue'); await this.taskQueue?.addTask(agentId, taskAssignment); success = true; } break; default: logger.error({ agentId, transportType: agent.transportType }, 'Unknown transport type'); return false; } if (success) { logger.info({ agentId, taskId, transportType: agent.transportType, payloadLength: taskPayload.length }, 'Task sent to agent via universal communication channel'); } return success; } catch (error) { logger.error({ err: error, agentId }, 'Failed to send task to agent'); return false; } } async receiveResponse(agentId, timeout = 30000) { try { await this.ensureDependencies(); const startTime = Date.now(); while (Date.now() - startTime < timeout) { const responses = await this.responseProcessor?.getAgentResponses(agentId) || []; if (responses.length > 0) { const latestResponse = responses[responses.length - 1]; const formattedResponse = this.formatAgentResponse(latestResponse); logger.debug({ agentId, taskId: latestResponse.task_id, status: latestResponse.status }, 'Agent response received'); return formattedResponse; } await new Promise(resolve => setTimeout(resolve, 100)); } throw new Error(`Timeout waiting for response from agent ${agentId}`); } catch (error) { logger.error({ err: error, agentId }, 'Failed to receive response from agent'); throw error; } } async isAgentReachable(agentId) { try { await this.ensureDependencies(); const agent = await this.agentRegistry?.getAgent(agentId); if (!agent) { return false; } let isReachable = false; const now = Date.now(); const lastSeen = agent.lastSeen || 0; const maxInactivity = 5 * 60 * 1000; switch (agent.transportType) { case 'stdio': case 'sse': isReachable = agent.status === 'online' && (now - lastSeen) < maxInactivity; break; case 'websocket': if (this.websocketServer) { isReachable = this.websocketServer.isAgentConnected(agentId) && agent.status === 'online' && (now - lastSeen) < maxInactivity; } break; case 'http': { const hasHttpEndpoint = !!(agent.httpEndpoint && this.httpAgentAPI); isReachable = agent.status === 'online' && (now - lastSeen) < maxInactivity && hasHttpEndpoint; if (!hasHttpEndpoint) { logger.debug({ agentId, hasEndpoint: !!agent.httpEndpoint, hasHttpAPI: !!this.httpAgentAPI }, 'HTTP agent missing endpoint or API service'); } break; } default: isReachable = false; } logger.debug({ agentId, transportType: agent.transportType, status: agent.status, lastSeen: new Date(lastSeen).toISOString(), isReachable }, 'Agent reachability check'); return isReachable; } catch (error) { logger.error({ err: error, agentId }, 'Failed to check agent reachability'); return false; } } async close() { try { logger.info('Universal agent communication channel closed'); } catch (error) { logger.error({ err: error }, 'Error closing universal communication channel'); } } async ensureTransportServicesStarted() { try { try { const { transportCoordinator } = await import('../../../services/transport-coordinator.js'); await transportCoordinator.ensureTransportsStarted(); logger.debug('Transport services ensured through coordinator'); } catch (error) { logger.warn('Failed to ensure transport services through coordinator:', error); const transportStatus = transportManager.getStatus(); if (!transportStatus.isStarted && !transportStatus.startupInProgress) { logger.info('Fallback: Starting transport services directly...'); transportManager.configure({ websocket: { enabled: true, port: 8080, path: '/agent-ws' }, http: { enabled: true, port: 3001, cors: true }, sse: { enabled: true }, stdio: { enabled: true } }); await transportManager.startAll(); } } const allocatedPorts = transportManager.getAllocatedPorts(); if (!allocatedPorts.websocket && this.websocketServer) { logger.warn('WebSocket service not allocated port, may not be available'); } if (!allocatedPorts.http && this.httpAgentAPI) { logger.warn('HTTP service not allocated port, may not be available'); } } catch (error) { logger.warn({ err: error }, 'Failed to ensure transport services are started, continuing with fallback'); } } logTransportEndpoints() { try { const allocatedPorts = transportManager.getAllocatedPorts(); const endpoints = transportManager.getServiceEndpoints(); logger.info({ allocatedPorts, endpoints, note: 'Agent orchestrator using dynamic port allocation' }, 'Transport endpoints available for agent communication'); } catch (error) { logger.warn({ err: error }, 'Failed to get transport endpoint information'); } } getTransportStatus() { try { const allocatedPorts = transportManager.getAllocatedPorts(); const endpoints = transportManager.getServiceEndpoints(); return { websocket: { available: !!allocatedPorts.websocket, port: allocatedPorts.websocket, endpoint: endpoints.websocket }, http: { available: !!allocatedPorts.http, port: allocatedPorts.http, endpoint: endpoints.http }, sse: { available: !!allocatedPorts.sse, port: allocatedPorts.sse, endpoint: endpoints.sse }, stdio: { available: true } }; } catch (error) { logger.warn({ err: error }, 'Failed to get transport status'); return { websocket: { available: false }, http: { available: false }, sse: { available: false }, stdio: { available: true } }; } } extractTaskIdFromPayload(taskPayload) { try { const lines = taskPayload.split('\n'); const jsonStart = lines.findIndex(line => line.includes('{')); const jsonEnd = lines.findIndex(line => line.includes('### VIBE_TASK_END')); if (jsonStart === -1 || jsonEnd === -1) { return 'unknown'; } const jsonPayload = lines.slice(jsonStart, jsonEnd).join('\n'); const taskData = JSON.parse(jsonPayload); return taskData.metadata?.task_id || taskData.task?.id || 'unknown'; } catch (error) { logger.debug({ err: error }, 'Failed to extract task ID from payload'); return 'unknown'; } } async getAgentResponses(agentId) { try { const { AgentResponseProcessor } = await import('../../agent-response/index.js'); const responseProcessor = AgentResponseProcessor.getInstance(); const agentResponses = []; const orchestrator = AgentOrchestrator.getInstance(); for (const [taskId, assignment] of orchestrator.getAssignmentsMap().entries()) { if (assignment.agentId === agentId) { const response = await responseProcessor.getResponse(taskId); if (response) { agentResponses.push(response); } } } return agentResponses; } catch (error) { logger.warn({ err: error, agentId }, 'Failed to get agent responses through unified processor'); return []; } } formatAgentResponse(response) { try { let formattedResponse = `VIBE_STATUS: ${response.status}\n`; if (response.message) { formattedResponse += response.message; } if (response.completion_details) { const details = response.completion_details; if (details.files_modified && details.files_modified.length > 0) { formattedResponse += `\nFiles modified: ${details.files_modified.join(', ')}`; } if (details.tests_passed !== undefined) { formattedResponse += `\nTests passed: ${details.tests_passed}`; } if (details.build_successful !== undefined) { formattedResponse += `\nBuild successful: ${details.build_successful}`; } if (details.notes) { formattedResponse += `\nNotes: ${details.notes}`; } } return formattedResponse; } catch (error) { logger.error({ err: error }, 'Failed to format agent response'); return `VIBE_STATUS: ERROR\nFailed to format response: ${error instanceof Error ? error.message : 'Unknown error'}`; } } async sendTaskViaWebSocket(agentId, taskId, sentinelPayload, priority, _assignedAt) { if (!this.websocketServer) { return false; } return this.websocketServer.sendTaskToAgent(agentId, { taskId, task: sentinelPayload, priority: priority === 'urgent' ? 3 : priority === 'high' ? 2 : priority === 'normal' ? 1 : 0 }); } async sendTaskViaHTTP(agent, agentId, taskId, taskPayload, priority) { if (!this.httpAgentAPI || !agent.httpEndpoint) { return false; } if (!('deliverTaskToAgent' in this.httpAgentAPI) || typeof this.httpAgentAPI.deliverTaskToAgent !== 'function') { logger.warn('HTTPAgentAPIServer.deliverTaskToAgent is not accessible'); return false; } try { const parsedPayload = JSON.parse(taskPayload); const taskPayloadObj = { type: parsedPayload.type || 'task', description: parsedPayload.description || '', parameters: parsedPayload.parameters || {}, context: parsedPayload.context || {} }; const headers = { 'Content-Type': 'application/json' }; const httpAuthToken = agent.metadata && typeof agent.metadata === 'object' && 'preferences' in agent.metadata && agent.metadata.preferences ? agent.metadata.preferences.httpAuthToken : undefined; if (httpAuthToken && typeof httpAuthToken === 'string') { headers['Authorization'] = `Bearer ${httpAuthToken}`; } const response = await fetch(agent.httpEndpoint, { method: 'POST', headers, body: JSON.stringify({ taskId, taskPayload: taskPayloadObj, priority: priority, deadline: Date.now() + 24 * 60 * 60 * 1000, assignedAt: Date.now() }) }); return response.ok; } catch (error) { logger.error({ err: error, agentId, taskId }, 'Failed to parse task payload for HTTP delivery'); return false; } } } export class AgentOrchestrator { static instance = null; static isInitializing = false; agents = new Map(); assignments = new Map(); taskQueue = []; sentinelProtocol; memoryManager; config; heartbeatTimer; agentHeartbeatMisses = new Map(); integrationBridge; workflowAwareManager; isBridgeRegistration = false; activeExecutions = new Map(); communicationChannel; executionMonitors = new Map(); sseNotifier = null; taskCompletionCallbacks = new Map(); constructor(config) { const timeoutManager = getTimeoutManager(); this.config = { heartbeatInterval: 30000, taskTimeout: timeoutManager.getTimeout('taskExecution'), maxRetries: timeoutManager.getRetryConfig().maxRetries, loadBalancingStrategy: 'capability_based', enableHealthChecks: true, conflictResolutionStrategy: 'queue', heartbeatTimeoutMultiplier: 3, enableAdaptiveTimeouts: true, maxHeartbeatMisses: 5, ...config }; this.sentinelProtocol = new SentinelProtocol({ timeout_minutes: this.config.taskTimeout / 60000 }); this.memoryManager = new MemoryManager(); this.communicationChannel = new UniversalAgentCommunicationChannel(); this.integrationBridge = AgentIntegrationBridge.getInstance(); this.workflowAwareManager = WorkflowAwareAgentManager.getInstance({ baseHeartbeatInterval: this.config.heartbeatInterval, enableAdaptiveTimeouts: this.config.enableAdaptiveTimeouts, maxGracePeriods: this.config.maxHeartbeatMisses }); this.initializeSSENotifier().catch(error => { logger.warn({ err: error }, 'Failed to initialize SSE notifier'); }); this.startHeartbeatMonitoring(); this.workflowAwareManager.startMonitoring().catch(error => { logger.warn({ err: error }, 'Failed to start workflow-aware agent monitoring'); }); this.integrationBridge.startAutoSync(60000); this.registerSchedulerCallback().catch(error => { logger.warn({ err: error }, 'Failed to register scheduler callback during initialization'); }); logger.info({ config: this.config }, 'Agent orchestrator initialized with integration bridge'); } async initializeSSENotifier() { try { const { sseNotifier } = await import('../../../services/sse-notifier/index.js'); this.sseNotifier = sseNotifier; logger.debug('SSE notifier initialized for agent orchestrator'); } catch (error) { logger.warn({ err: error }, 'Failed to initialize SSE notifier'); this.sseNotifier = null; } } static getInstance(config) { if (AgentOrchestrator.isInitializing) { logger.warn('Circular initialization detected in AgentOrchestrator, using safe fallback'); return AgentOrchestrator.createSafeFallback(); } if (!AgentOrchestrator.instance) { const monitor = InitializationMonitor.getInstance(); monitor.startServiceInitialization('AgentOrchestrator', [ 'TransportManager', 'MemoryManager', 'AgentIntegrationBridge', 'WorkflowAwareAgentManager' ], { config }); AgentOrchestrator.isInitializing = true; try { monitor.startPhase('AgentOrchestrator', 'constructor'); AgentOrchestrator.instance = new AgentOrchestrator(config); monitor.endPhase('AgentOrchestrator', 'constructor'); monitor.endServiceInitialization('AgentOrchestrator'); } catch (error) { monitor.endPhase('AgentOrchestrator', 'constructor', error); monitor.endServiceInitialization('AgentOrchestrator', error); throw error; } finally { AgentOrchestrator.isInitializing = false; } } return AgentOrchestrator.instance; } static createSafeFallback() { const fallback = Object.create(AgentOrchestrator.prototype); fallback.agents = new Map(); fallback.assignments = new Map(); fallback.taskQueue = []; fallback.agentHeartbeatMisses = new Map(); fallback.isBridgeRegistration = false; fallback.registerAgent = async () => { logger.warn('AgentOrchestrator fallback: registerAgent called during initialization'); }; fallback.assignTask = async () => { logger.warn('AgentOrchestrator fallback: assignTask called during initialization'); return null; }; fallback.getAgents = async () => { logger.warn('AgentOrchestrator fallback: getAgents called during initialization'); return []; }; return fallback; } async registerAgent(agentInfo) { const result = await OperationCircuitBreaker.safeExecute(`registerAgent_${agentInfo.id}`, async () => { const fullAgentInfo = { ...agentInfo, lastHeartbeat: new Date(), performance: { tasksCompleted: 0, averageCompletionTime: 0, successRate: 1.0 } }; this.agents.set(agentInfo.id, fullAgentInfo); if (!this.isBridgeRegistration) { try { await this.integrationBridge.registerAgent({ id: agentInfo.id, name: agentInfo.name, capabilities: agentInfo.capabilities.map(cap => cap.toString()), status: agentInfo.status === 'available' ? 'online' : agentInfo.status, maxConcurrentTasks: agentInfo.maxConcurrentTasks, currentTasks: agentInfo.currentTasks, transportType: agentInfo.metadata.preferences?.transportType || 'stdio', sessionId: agentInfo.metadata.preferences?.sessionId, pollingInterval: agentInfo.metadata.preferences?.pollingInterval, registeredAt: Date.now(), lastSeen: Date.now(), lastHeartbeat: fullAgentInfo.lastHeartbeat, performance: fullAgentInfo.performance, httpEndpoint: agentInfo.metadata.preferences?.httpEndpoint, httpAuthToken: agentInfo.metadata.preferences?.httpAuthToken, metadata: agentInfo.metadata }); logger.info({ agentId: agentInfo.id, capabilities: agentInfo.capabilities }, 'Agent registered in both orchestrator and registry via integration bridge'); } catch (bridgeError) { logger.warn({ err: bridgeError, agentId: agentInfo.id }, 'Integration bridge registration failed, agent registered in orchestrator only'); } } this.memoryManager.getMemoryStats(); return true; }, () => { logger.warn({ agentId: agentInfo.id }, 'Agent registration failed due to circuit breaker, using fallback (agent not registered)'); return false; }, { failureThreshold: 3, timeout: 30000, operationTimeout: 10000 }); if (!result.success && result.error) { throw new AppError('Agent registration failed', { cause: result.error }); } } async unregisterAgent(agentId) { try { const agent = this.agents.get(agentId); if (!agent) { const errorContext = createErrorContext('AgentOrchestrator', 'unassignTask') .agentId(agentId) .build(); throw new ValidationError(`Agent not found: ${agentId}`, errorContext, { field: 'agentId', expectedFormat: 'Valid agent ID', actualValue: agentId }); } await this.reassignAgentTasks(agentId); this.agents.delete(agentId); logger.info({ agentId }, 'Agent unregistered'); } catch (error) { logger.error({ err: error, agentId }, 'Failed to unregister agent'); throw new AppError('Agent unregistration failed', { cause: error }); } } updateAgentHeartbeat(agentId, status) { const agent = this.agents.get(agentId); if (agent) { const oldStatus = agent.status; agent.lastHeartbeat = new Date(); if (status) { agent.status = status; } this.agentHeartbeatMisses.delete(agentId); const agentState = this.workflowAwareManager.getAgentState(agentId); if (agentState) { this.workflowAwareManager.updateAgentProgress(agentId, agentState.progressPercentage, { heartbeatUpdate: new Date(), orchestratorStatus: status }).catch(error => { logger.warn({ err: error, agentId }, 'Failed to update workflow-aware manager on heartbeat'); }); } else if (status === 'available') { this.workflowAwareManager.registerAgentActivity(agentId, 'idle', { metadata: { autoRegisteredOnHeartbeat: true } }).catch(error => { logger.warn({ err: error, agentId }, 'Failed to register agent activity on heartbeat'); }); } if (status && status !== oldStatus) { this.integrationBridge.propagateStatusChange(agentId, status, 'orchestrator') .catch(error => { logger.warn({ err: error, agentId, status }, 'Failed to propagate status change from heartbeat update'); }); } logger.debug({ agentId, status }, 'Agent heartbeat updated with workflow awareness'); } } getAdaptiveTaskTimeout(task) { if (!this.config.enableAdaptiveTimeouts) { return this.config.taskTimeout; } const timeoutManager = getTimeoutManager(); const complexity = this.determineTaskComplexity(task); const estimatedHours = task.estimatedHours || 1; return timeoutManager.getComplexityAdjustedTimeout('taskExecution', complexity, estimatedHours); } determineTaskComplexity(task) { const estimatedHours = task.estimatedHours || 1; const priority = task.priority || 'medium'; const dependencies = task.dependencies?.length || 0; let complexityScore = 0; if (estimatedHours <= 1) complexityScore += 1; else if (estimatedHours <= 4) complexityScore += 2; else if (estimatedHours <= 8) complexityScore += 3; else complexityScore += 4; if (priority === 'critical') complexityScore += 2; else if (priority === 'high') complexityScore += 1; if (dependencies > 5) complexityScore += 2; else if (dependencies > 2) complexityScore += 1; if (task.type === 'development' || task.type === 'deployment') complexityScore += 2; else if (task.type === 'testing' || task.type === 'documentation') complexityScore -= 1; if (complexityScore <= 2) return 'simple'; else if (complexityScore <= 4) return 'moderate'; else if (complexityScore <= 6) return 'complex'; else return 'critical'; } async assignTask(task, context, epicTitle) { const errorContext = createErrorContext('AgentOrchestrator', 'assignTask') .taskId(task.id) .metadata({ taskType: task.type, taskPriority: task.priority, availableAgents: this.agents.size, queuedTasks: this.taskQueue.length }) .build(); try { if (!task.id || task.id.trim() === '') { throw new ValidationError('Task ID is required for assignment', errorContext, { field: 'task.id', expectedFormat: 'Non-empty string', actualValue: task.id }); } if (!task.title || task.title.trim() === '') { throw new ValidationError('Task title is required for assignment', errorContext, { field: 'task.title', expectedFormat: 'Non-empty string', actualValue: task.title }); } const availableAgent = this.selectBestAgent(task); if (!availableAgent) { if (this.agents.size === 0) { throw new ResourceError('No agents are registered in the system', errorContext, { resourceType: 'agents', availableAmount: 0, requiredAmount: 1 }); } this.taskQueue.push(task.id); logger.info({ taskId: task.id }, 'Task queued - no available agents'); return null; } if (task.type && !this.isAgentCapableOfTask(availableAgent, task)) { throw new AgentError(`Agent ${availableAgent.id} lacks required capabilities for task type: ${task.type}`, errorContext, { agentType: availableAgent.capabilities.join(', '), agentStatus: availableAgent.status, capabilities: availableAgent.capabilities }); } const assignment = { id: `assignment_${task.id}_${Date.now()}`, taskId: task.id, task: task, agentId: availableAgent.id, assignedAt: new Date(), expectedCompletionAt: new Date(Date.now() + this.config.taskTimeout), status: 'assigned', attempts: 1, lastStatusUpdate: new Date(), priority: this.mapTaskPriorityToAssignmentPriority(task.priority), estimatedDuration: task.estimatedHours * 60 * 60 * 1000, deadline: new Date(Date.now() + this.config.taskTimeout), context: { projectId: task.projectId, epicId: task.epicId, dependencies: task.dependencies, resources: [], constraints: [] }, metadata: { assignedBy: 'agent-orchestrator', assignedAt: Date.now(), executionId: `exec_${task.id}_${Date.now()}`, retryCount: 0, maxRetries: this.config.maxRetries } }; const oldStatus = availableAgent.status; availableAgent.currentTasks.push(task.id); if (availableAgent.currentTasks.length >= availableAgent.maxConcurrentTasks) { availableAgent.status = 'busy'; } if (availableAgent.status !== oldStatus) { this.integrationBridge.propagateStatusChange(availableAgent.id, availableAgent.status, 'orchestrator') .catch(error => { logger.warn({ err: error, agentId: availableAgent.id, status: availableAgent.status }, 'Failed to propagate status change from task assignment'); }); } this.integrationBridge.propagateTaskStatusChange(availableAgent.id, task.id, 'assigned', 'orchestrator') .catch(error => { logger.warn({ err: error, agentId: availableAgent.id, taskId: task.id }, 'Failed to propagate task assignment'); }); this.assignments.set(task.id, assignment); this.workflowAwareManager.registerAgentActivity(availableAgent.id, 'task_execution', { workflowId: task.projectId, sessionId: context.sessionId || `session_${Date.now()}`, expectedDuration: assignment.estimatedDuration, isWorkflowCritical: false, metadata: { taskId: task.id, taskType: task.type, priority: task.priority, assignmentId: assignment.id } }).catch(error => { logger.warn({ err: error, agentId: availableAgent.id, taskId: task.id }, 'Failed to register task execution activity'); }); try { const taskPayload = this.sentinelProtocol.formatTaskForAgent(task, context, epicTitle); logger.info({ taskId: task.id, agentId: availableAgent.id, payload: taskPayload.substring(0, 200) + '...' }, 'Task assigned to agent with workflow awareness'); } catch (formatError) { this.assignments.delete(task.id); availableAgent.currentTasks = availableAgent.currentTasks.filter(id => id !== task.id); if (availableAgent.currentTasks.length < availableAgent.maxConcurrentTasks) { availableAgent.status = 'available'; } throw new TaskExecutionError(`Failed to format task for agent: ${formatError instanceof Error ? formatError.message : String(formatError)}`, errorContext, { cause: formatError instanceof Error ? formatError : undefined, agentCapabilities: availableAgent.capabilities, retryable: true }); } return assignment; } catch (error) { if (error instanceof EnhancedError) { throw error; } throw new AgentError(`Task assignment failed: ${error instanceof Error ? error.message : String(error)}`, errorContext, { cause: error instanceof Error ? error : undefined }); } } async executeTask(task, context, options = {}) { const executionId = `exec_${task.id}_${Date.now()}`; const startTime = new Date(); if (!task.id || task.id.trim() === '') { return { success: false, status: 'failed', message: 'Invalid task: Task ID is required', startTime, endTime: new Date(), error: 'Invalid task ID', metadata: { executionId, attempts: 0 } }; } if (!task.title || task.title.trim() === '') { return { success: false, status: 'failed', message: 'Invalid task: Task title is required', startTime, endTime: new Date(), error: 'Invalid task title', metadata: { executionId, attempts: 0 } }; } const execOptions = { timeout: this.config.taskTimeout, maxRetries: this.config.maxRetries, enableMonitoring: true, ...options }; logger.info({ taskId: task.id, executionId, options: execOptions }, 'Starting task execution'); try { const assignment = await this.assignTask(task, context); if (!assignment) { const result = { success: false, status: 'queued', message: 'No available agents. Task queued for execution when agents become available.', startTime, queued: true, metadata: { executionId, attempts: 0 } }; this.activeExecutions.set(executionId, result); return result; } const taskPayload = this.sentinelProtocol.formatTaskForAgent(task, context); const deliverySuccess = await this.communicationChannel.sendTask(assignment.agentId, taskPayload); if (!deliverySuccess) { await this.handleExecutionFailure(assignment, 'Task delivery failed'); return { success: false, status: 'failed', message: 'Failed to deliver task to agent', startTime, endTime: new Date(), assignment, error: 'Task delivery failed', metadata: { executionId, attempts: assignment.attempts, agentId: assignment.agentId } }; } const result = await this.monitorTaskExecution(assignment, execOptions, executionId, startTime); this.activeExecutions.set(executionId, result); logger.info({ taskId: task.id, executionId, status: result.status, duration: result.endTime ? result.endTime.getTime() - startTime.getTime() : undefined }, 'Task execution completed'); return result; } catch (error) { logger.error({ err: error, taskId: task.id, executionId }, 'Task execution failed with error'); const result = { success: false, status: 'failed', message: `Execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`, startTime, endTime: new Date(), error: error instanceof Error ? error.message : String(error), metadata: { executionId, attempts: 1 } }; this.activeExecutions.set(executionId, result); return result; } } async monitorTaskExecution(assignment, options, executionId, startTime) { const timeout = options.timeout || this.config.taskTimeout; const maxRetries = options.maxRetries || this.config.maxRetries; return new Promise((resolve) => { let attempts = 0; let monitoringHandle; const cleanup = () => { if (timeoutHandle) clearTimeout(timeoutHandle); if (monitoringHandle) clearInterval(monitoringHandle); this.executionMonitors.delete(executionId); }; cons