devflow-ai
Version:
Enterprise-grade AI agent orchestration with swarm management UI dashboard
373 lines (334 loc) • 10.2 kB
text/typescript
/**
* 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,
};
}
}