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
JavaScript
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