/** * AgentOrchestrator * Central management system for all LocalGreenChain agents * * Responsibilities: * - Start/stop all agents * - Coordinate inter-agent communication * - Aggregate metrics and alerts * - Handle agent failures and restarts * - Provide unified status dashboard */ import { BaseAgent } from './BaseAgent'; import { AgentMetrics, AgentAlert, AgentConfig, AgentStatus } from './types'; import { getPlantLineageAgent, PlantLineageAgent } from './PlantLineageAgent'; import { getTransportTrackerAgent, TransportTrackerAgent } from './TransportTrackerAgent'; import { getDemandForecastAgent, DemandForecastAgent } from './DemandForecastAgent'; import { getVerticalFarmAgent, VerticalFarmAgent } from './VerticalFarmAgent'; import { getEnvironmentAnalysisAgent, EnvironmentAnalysisAgent } from './EnvironmentAnalysisAgent'; import { getMarketMatchingAgent, MarketMatchingAgent } from './MarketMatchingAgent'; import { getSustainabilityAgent, SustainabilityAgent } from './SustainabilityAgent'; import { getNetworkDiscoveryAgent, NetworkDiscoveryAgent } from './NetworkDiscoveryAgent'; import { getQualityAssuranceAgent, QualityAssuranceAgent } from './QualityAssuranceAgent'; import { getGrowerAdvisoryAgent, GrowerAdvisoryAgent } from './GrowerAdvisoryAgent'; interface OrchestratorConfig { autoStart: boolean; enabledAgents: string[]; alertAggregationIntervalMs: number; healthCheckIntervalMs: number; maxAlertsPerAgent: number; } interface AgentHealth { agentId: string; agentName: string; status: AgentStatus; lastRunAt: string | null; tasksCompleted: number; tasksFailed: number; errorRate: number; avgExecutionMs: number; isHealthy: boolean; } interface OrchestratorStatus { isRunning: boolean; startedAt: string | null; uptime: number; totalAgents: number; runningAgents: number; healthyAgents: number; totalTasksCompleted: number; totalTasksFailed: number; activeAlerts: number; } interface AgentSummary { id: string; name: string; description: string; status: AgentStatus; priority: string; intervalMs: number; metrics: AgentMetrics; alertCount: number; } export class AgentOrchestrator { private static instance: AgentOrchestrator; private config: OrchestratorConfig; private agents: Map = new Map(); private isRunning: boolean = false; private startedAt: string | null = null; private healthCheckInterval: NodeJS.Timeout | null = null; private alertAggregationInterval: NodeJS.Timeout | null = null; private aggregatedAlerts: AgentAlert[] = []; private eventHandlers: Map void)[]> = new Map(); private constructor(config?: Partial) { this.config = { autoStart: false, enabledAgents: [ 'plant-lineage-agent', 'transport-tracker-agent', 'demand-forecast-agent', 'vertical-farm-agent', 'environment-analysis-agent', 'market-matching-agent', 'sustainability-agent', 'network-discovery-agent', 'quality-assurance-agent', 'grower-advisory-agent' ], alertAggregationIntervalMs: 60000, healthCheckIntervalMs: 30000, maxAlertsPerAgent: 50, ...config }; this.initializeAgents(); } public static getInstance(config?: Partial): AgentOrchestrator { if (!AgentOrchestrator.instance) { AgentOrchestrator.instance = new AgentOrchestrator(config); } return AgentOrchestrator.instance; } /** * Initialize all agents */ private initializeAgents(): void { const agentFactories: Record BaseAgent> = { 'plant-lineage-agent': getPlantLineageAgent, 'transport-tracker-agent': getTransportTrackerAgent, 'demand-forecast-agent': getDemandForecastAgent, 'vertical-farm-agent': getVerticalFarmAgent, 'environment-analysis-agent': getEnvironmentAnalysisAgent, 'market-matching-agent': getMarketMatchingAgent, 'sustainability-agent': getSustainabilityAgent, 'network-discovery-agent': getNetworkDiscoveryAgent, 'quality-assurance-agent': getQualityAssuranceAgent, 'grower-advisory-agent': getGrowerAdvisoryAgent }; for (const agentId of this.config.enabledAgents) { const factory = agentFactories[agentId]; if (factory) { const agent = factory(); this.agents.set(agentId, agent); console.log(`[Orchestrator] Registered agent: ${agent.config.name}`); } } } /** * Start all agents */ async startAll(): Promise { if (this.isRunning) { console.log('[Orchestrator] Already running'); return; } console.log('[Orchestrator] Starting all agents...'); this.isRunning = true; this.startedAt = new Date().toISOString(); // Start agents in priority order const sortedAgents = this.getSortedAgentsByPriority(); for (const agent of sortedAgents) { if (agent.config.enabled) { try { await agent.start(); console.log(`[Orchestrator] Started: ${agent.config.name}`); } catch (error) { console.error(`[Orchestrator] Failed to start ${agent.config.name}:`, error); } } } // Start health check interval this.healthCheckInterval = setInterval(() => { this.performHealthCheck(); }, this.config.healthCheckIntervalMs); // Start alert aggregation interval this.alertAggregationInterval = setInterval(() => { this.aggregateAlerts(); }, this.config.alertAggregationIntervalMs); this.emit('orchestrator_started', { agentCount: this.agents.size }); console.log(`[Orchestrator] All agents started (${this.agents.size} total)`); } /** * Stop all agents */ async stopAll(): Promise { if (!this.isRunning) { console.log('[Orchestrator] Not running'); return; } console.log('[Orchestrator] Stopping all agents...'); // Clear intervals if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); this.healthCheckInterval = null; } if (this.alertAggregationInterval) { clearInterval(this.alertAggregationInterval); this.alertAggregationInterval = null; } // Stop all agents for (const agent of this.agents.values()) { try { await agent.stop(); console.log(`[Orchestrator] Stopped: ${agent.config.name}`); } catch (error) { console.error(`[Orchestrator] Error stopping ${agent.config.name}:`, error); } } this.isRunning = false; this.emit('orchestrator_stopped', {}); console.log('[Orchestrator] All agents stopped'); } /** * Start a specific agent */ async startAgent(agentId: string): Promise { const agent = this.agents.get(agentId); if (!agent) { console.error(`[Orchestrator] Agent not found: ${agentId}`); return false; } try { await agent.start(); return true; } catch (error) { console.error(`[Orchestrator] Failed to start ${agentId}:`, error); return false; } } /** * Stop a specific agent */ async stopAgent(agentId: string): Promise { const agent = this.agents.get(agentId); if (!agent) { console.error(`[Orchestrator] Agent not found: ${agentId}`); return false; } try { await agent.stop(); return true; } catch (error) { console.error(`[Orchestrator] Failed to stop ${agentId}:`, error); return false; } } /** * Restart a specific agent */ async restartAgent(agentId: string): Promise { await this.stopAgent(agentId); return this.startAgent(agentId); } /** * Get sorted agents by priority */ private getSortedAgentsByPriority(): BaseAgent[] { const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; return Array.from(this.agents.values()).sort((a, b) => priorityOrder[a.config.priority] - priorityOrder[b.config.priority] ); } /** * Perform health check on all agents */ private performHealthCheck(): void { for (const [agentId, agent] of this.agents) { const health = this.getAgentHealth(agentId); if (!health.isHealthy) { console.warn(`[Orchestrator] Unhealthy agent detected: ${agent.config.name}`); // Auto-restart if error rate is high if (health.errorRate > 50 && agent.status === 'running') { console.log(`[Orchestrator] Auto-restarting: ${agent.config.name}`); this.restartAgent(agentId); } } } } /** * Aggregate alerts from all agents */ private aggregateAlerts(): void { this.aggregatedAlerts = []; for (const agent of this.agents.values()) { const alerts = agent.getAlerts() .filter(a => !a.acknowledged) .slice(-this.config.maxAlertsPerAgent); this.aggregatedAlerts.push(...alerts); } // Sort by severity and timestamp const severityOrder = { critical: 0, error: 1, warning: 2, info: 3 }; this.aggregatedAlerts.sort((a, b) => { const severityDiff = severityOrder[a.severity] - severityOrder[b.severity]; if (severityDiff !== 0) return severityDiff; return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); }); // Emit event if critical alerts const criticalAlerts = this.aggregatedAlerts.filter(a => a.severity === 'critical'); if (criticalAlerts.length > 0) { this.emit('critical_alerts', { count: criticalAlerts.length, alerts: criticalAlerts }); } } /** * Get agent health */ getAgentHealth(agentId: string): AgentHealth { const agent = this.agents.get(agentId); if (!agent) { return { agentId, agentName: 'Unknown', status: 'idle', lastRunAt: null, tasksCompleted: 0, tasksFailed: 0, errorRate: 0, avgExecutionMs: 0, isHealthy: false }; } const metrics = agent.getMetrics(); const totalTasks = metrics.tasksCompleted + metrics.tasksFailed; const errorRate = totalTasks > 0 ? (metrics.tasksFailed / totalTasks) * 100 : 0; // Check if agent is responding (last run within 2x interval) const lastRunTime = metrics.lastRunAt ? new Date(metrics.lastRunAt).getTime() : 0; const maxTimeSinceRun = agent.config.intervalMs * 2; const isResponding = agent.status !== 'running' || (Date.now() - lastRunTime < maxTimeSinceRun); const isHealthy = agent.status !== 'error' && errorRate < 30 && isResponding; return { agentId, agentName: agent.config.name, status: agent.status, lastRunAt: metrics.lastRunAt, tasksCompleted: metrics.tasksCompleted, tasksFailed: metrics.tasksFailed, errorRate: Math.round(errorRate), avgExecutionMs: Math.round(metrics.averageExecutionMs), isHealthy }; } /** * Get orchestrator status */ getStatus(): OrchestratorStatus { const healthStatuses = Array.from(this.agents.keys()).map(id => this.getAgentHealth(id)); const runningAgents = healthStatuses.filter(h => h.status === 'running').length; const healthyAgents = healthStatuses.filter(h => h.isHealthy).length; const totalCompleted = healthStatuses.reduce((sum, h) => sum + h.tasksCompleted, 0); const totalFailed = healthStatuses.reduce((sum, h) => sum + h.tasksFailed, 0); return { isRunning: this.isRunning, startedAt: this.startedAt, uptime: this.startedAt ? Date.now() - new Date(this.startedAt).getTime() : 0, totalAgents: this.agents.size, runningAgents, healthyAgents, totalTasksCompleted: totalCompleted, totalTasksFailed: totalFailed, activeAlerts: this.aggregatedAlerts.filter(a => !a.acknowledged).length }; } /** * Get all agent summaries */ getAgentSummaries(): AgentSummary[] { return Array.from(this.agents.values()).map(agent => ({ id: agent.config.id, name: agent.config.name, description: agent.config.description, status: agent.status, priority: agent.config.priority, intervalMs: agent.config.intervalMs, metrics: agent.getMetrics(), alertCount: agent.getAlerts().filter(a => !a.acknowledged).length })); } /** * Get all alerts */ getAlerts(severity?: string): AgentAlert[] { if (severity) { return this.aggregatedAlerts.filter(a => a.severity === severity); } return this.aggregatedAlerts; } /** * Acknowledge an alert */ acknowledgeAlert(alertId: string): boolean { const alert = this.aggregatedAlerts.find(a => a.id === alertId); if (alert) { alert.acknowledged = true; return true; } return false; } /** * Get specific agent */ getAgent(agentId: string): T | null { return this.agents.get(agentId) as T || null; } /** * Get typed agent instances */ getPlantLineageAgent(): PlantLineageAgent | null { return this.getAgent('plant-lineage-agent'); } getTransportTrackerAgent(): TransportTrackerAgent | null { return this.getAgent('transport-tracker-agent'); } getDemandForecastAgent(): DemandForecastAgent | null { return this.getAgent('demand-forecast-agent'); } getVerticalFarmAgent(): VerticalFarmAgent | null { return this.getAgent('vertical-farm-agent'); } getEnvironmentAnalysisAgent(): EnvironmentAnalysisAgent | null { return this.getAgent('environment-analysis-agent'); } getMarketMatchingAgent(): MarketMatchingAgent | null { return this.getAgent('market-matching-agent'); } getSustainabilityAgent(): SustainabilityAgent | null { return this.getAgent('sustainability-agent'); } getNetworkDiscoveryAgent(): NetworkDiscoveryAgent | null { return this.getAgent('network-discovery-agent'); } getQualityAssuranceAgent(): QualityAssuranceAgent | null { return this.getAgent('quality-assurance-agent'); } getGrowerAdvisoryAgent(): GrowerAdvisoryAgent | null { return this.getAgent('grower-advisory-agent'); } /** * Register event handler */ on(event: string, handler: (data: any) => void): void { const handlers = this.eventHandlers.get(event) || []; handlers.push(handler); this.eventHandlers.set(event, handlers); } /** * Emit event */ private emit(event: string, data: any): void { const handlers = this.eventHandlers.get(event) || []; for (const handler of handlers) { try { handler(data); } catch (error) { console.error(`[Orchestrator] Event handler error for ${event}:`, error); } } } /** * Get dashboard data */ getDashboard(): { status: OrchestratorStatus; agents: AgentSummary[]; health: AgentHealth[]; recentAlerts: AgentAlert[]; metrics: { totalTasksToday: number; avgErrorRate: number; avgExecutionTime: number; }; } { const status = this.getStatus(); const agents = this.getAgentSummaries(); const health = Array.from(this.agents.keys()).map(id => this.getAgentHealth(id)); const recentAlerts = this.aggregatedAlerts.slice(0, 10); const avgErrorRate = health.length > 0 ? health.reduce((sum, h) => sum + h.errorRate, 0) / health.length : 0; const avgExecutionTime = health.length > 0 ? health.reduce((sum, h) => sum + h.avgExecutionMs, 0) / health.length : 0; return { status, agents, health, recentAlerts, metrics: { totalTasksToday: status.totalTasksCompleted, avgErrorRate: Math.round(avgErrorRate), avgExecutionTime: Math.round(avgExecutionTime) } }; } } /** * Get orchestrator singleton */ export function getOrchestrator(config?: Partial): AgentOrchestrator { return AgentOrchestrator.getInstance(config); } /** * Start all agents */ export async function startAllAgents(): Promise { const orchestrator = getOrchestrator(); await orchestrator.startAll(); } /** * Stop all agents */ export async function stopAllAgents(): Promise { const orchestrator = getOrchestrator(); await orchestrator.stopAll(); }