UNPKG

devflow-ai

Version:

Enterprise-grade AI agent orchestration with swarm management UI dashboard

373 lines (334 loc) 10.2 kB
/** * JSON Output Aggregator for Non-Interactive Swarm Execution * Collects and formats swarm results into a comprehensive JSON structure */ import { EventEmitter } from 'events'; import { promises as fs } from 'node:fs'; import { generateId } from '../utils/helpers.js'; import { Logger } from '../core/logger.js'; import type { SwarmId, AgentId, TaskId, AgentState, TaskDefinition, SwarmResults, SwarmMetrics, TaskResult, } from './types.js'; export interface SwarmOutputAggregate { swarmId: string; objective: string; startTime: string; endTime: string; duration: number; status: 'completed' | 'failed' | 'timeout' | 'cancelled'; summary: { totalAgents: number; totalTasks: number; completedTasks: number; failedTasks: number; successRate: number; }; agents: AgentOutputData[]; tasks: TaskOutputData[]; results: { artifacts: Record<string, any>; outputs: string[]; errors: string[]; insights: string[]; }; metrics: SwarmMetrics; metadata: { strategy: string; mode: string; configuration: Record<string, any>; version: string; }; } export interface AgentOutputData { agentId: string; name: string; type: string; status: string; startTime: string; endTime?: string; duration?: number; tasksCompleted: number; outputs: string[]; errors: string[]; metrics: { tokensUsed?: number; memoryAccess: number; operationsPerformed: number; }; } export interface TaskOutputData { taskId: string; name: string; type: string; status: string; assignedAgent?: string; startTime: string; endTime?: string; duration?: number; priority: string; output?: string; result?: TaskResult; artifacts?: Record<string, any>; error?: string; } export class SwarmJsonOutputAggregator extends EventEmitter { private logger: Logger; private swarmId: string; private objective: string; private startTime: Date; private endTime?: Date; private configuration: Record<string, any>; // Data collection private agents: Map<string, AgentOutputData> = new Map(); private tasks: Map<string, TaskOutputData> = new Map(); private outputs: string[] = []; private errors: string[] = []; private insights: string[] = []; private artifacts: Record<string, any> = {}; private metrics: SwarmMetrics = this.initializeMetrics(); constructor(swarmId: string, objective: string, configuration: Record<string, any> = {}) { super(); this.swarmId = swarmId; this.objective = objective; this.configuration = configuration; this.startTime = new Date(); this.logger = new Logger( { level: 'info', format: 'json', destination: 'console' }, { component: 'SwarmJsonAggregator' }, ); this.logger.info('JSON output aggregator initialized', { swarmId, objective, timestamp: this.startTime.toISOString(), }); } // Agent tracking methods addAgent(agent: AgentState): void { // Handle null/undefined agent IDs gracefully if (!agent || !agent.id) { this.logger.warn('Attempted to add agent with null/undefined ID, skipping'); return; } const agentIdStr = typeof agent.id === 'string' ? agent.id : agent.id.id; const agentData: AgentOutputData = { agentId: agentIdStr, name: agent.name || agentIdStr, type: agent.type, status: agent.status, startTime: new Date().toISOString(), tasksCompleted: 0, outputs: [], errors: [], metrics: { memoryAccess: 0, operationsPerformed: 0, }, }; this.agents.set(agentIdStr, agentData); this.logger.debug('Agent added to output tracking', { agentId: agentIdStr }); } updateAgent(agentId: string, updates: Partial<AgentOutputData>): void { const agent = this.agents.get(agentId); if (agent) { Object.assign(agent, updates); this.logger.debug('Agent updated in output tracking', { agentId, updates }); } } addAgentOutput(agentId: string, output: string): void { const agent = this.agents.get(agentId); if (agent) { agent.outputs.push(output); agent.metrics.operationsPerformed++; } this.outputs.push(`[${agentId}] ${output}`); } addAgentError(agentId: string, error: string): void { const agent = this.agents.get(agentId); if (agent) { agent.errors.push(error); } this.errors.push(`[${agentId}] ${error}`); } // Task tracking methods addTask(task: TaskDefinition): void { // Handle null/undefined task IDs gracefully if (!task || !task.id) { this.logger.warn('Attempted to add task with null/undefined ID, skipping'); return; } const taskIdStr = typeof task.id === 'string' ? task.id : task.id.id; const taskData: TaskOutputData = { taskId: taskIdStr, name: task.name || taskIdStr, type: task.type, status: task.status, assignedAgent: task.assignedAt ? typeof task.assignedAt === 'string' ? task.assignedAt : task.assignedAt.toString() : undefined, startTime: new Date().toISOString(), priority: task.priority || 'normal', }; this.tasks.set(taskIdStr, taskData); this.logger.debug('Task added to output tracking', { taskId: taskIdStr }); } updateTask(taskId: string, updates: Partial<TaskOutputData>): void { const task = this.tasks.get(taskId); if (task) { Object.assign(task, updates); this.logger.debug('Task updated in output tracking', { taskId, updates }); } } completeTask(taskId: string, result: TaskResult): void { const task = this.tasks.get(taskId); if (task) { task.status = 'completed'; task.endTime = new Date().toISOString(); task.duration = task.startTime ? Date.now() - new Date(task.startTime).getTime() : 0; task.result = result; task.output = result.output; task.artifacts = result.artifacts; // Update agent completion count if (task.assignedAgent) { const agent = this.agents.get(task.assignedAgent); if (agent) { agent.tasksCompleted++; } } } } // Global tracking methods addInsight(insight: string): void { this.insights.push(insight); this.logger.debug('Insight added', { insight }); } addArtifact(key: string, artifact: any): void { this.artifacts[key] = artifact; this.logger.debug('Artifact added', { key }); } updateMetrics(updates: Partial<SwarmMetrics>): void { Object.assign(this.metrics, updates); } // Finalization and output finalize( status: 'completed' | 'failed' | 'timeout' | 'cancelled' = 'completed', ): SwarmOutputAggregate { this.endTime = new Date(); const duration = this.endTime.getTime() - this.startTime.getTime(); // Calculate summary statistics const totalTasks = this.tasks.size; const completedTasks = Array.from(this.tasks.values()).filter( (task) => task.status === 'completed', ).length; const failedTasks = Array.from(this.tasks.values()).filter( (task) => task.status === 'failed', ).length; const successRate = totalTasks > 0 ? completedTasks / totalTasks : 0; // Finalize agent data this.agents.forEach((agent) => { if (!agent.endTime) { agent.endTime = this.endTime.toISOString(); agent.duration = Date.now() - new Date(agent.startTime).getTime(); } }); const aggregate: SwarmOutputAggregate = { swarmId: this.swarmId, objective: this.objective, startTime: this.startTime.toISOString(), endTime: this.endTime.toISOString(), duration, status, summary: { totalAgents: this.agents.size, totalTasks, completedTasks, failedTasks, successRate, }, agents: Array.from(this.agents.values()), tasks: Array.from(this.tasks.values()), results: { artifacts: this.artifacts, outputs: this.outputs, errors: this.errors, insights: this.insights, }, metrics: this.metrics, metadata: { strategy: this.configuration.strategy || 'auto', mode: this.configuration.mode || 'centralized', configuration: this.configuration, version: '2.0.0-alpha', }, }; this.logger.info('Swarm output aggregation finalized', { swarmId: this.swarmId, status, duration, summary: aggregate.summary, }); return aggregate; } async saveToFile( filePath: string, status: 'completed' | 'failed' | 'timeout' | 'cancelled' = 'completed', ): Promise<void> { const aggregate = this.finalize(status); const json = JSON.stringify(aggregate, this.circularReplacer(), 2); await fs.writeFile(filePath, json, 'utf8'); this.logger.info('Swarm output saved to file', { filePath, size: json.length }); } getJsonOutput(status: 'completed' | 'failed' | 'timeout' | 'cancelled' = 'completed'): string { const aggregate = this.finalize(status); return JSON.stringify(aggregate, this.circularReplacer(), 2); } // Handle circular references in JSON serialization private circularReplacer(): (key: string, value: any) => any { const seen = new WeakSet(); return (key: string, value: any) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular]'; } seen.add(value); } return value; }; } private initializeMetrics(): SwarmMetrics { return { // Performance metrics throughput: 0, latency: 0, efficiency: 0, reliability: 0, // Quality metrics averageQuality: 0, defectRate: 0, reworkRate: 0, // Resource metrics resourceUtilization: { cpu: 0, memory: 0, disk: 0, network: 0, }, costEfficiency: 0, // Agent metrics agentUtilization: 0, agentSatisfaction: 0, collaborationEffectiveness: 0, // Timeline metrics scheduleVariance: 0, deadlineAdherence: 0, }; } }