mega-minds
Version:
Enhanced multi-agent workflow system for Claude Code projects with automated handoff management and Claude Code hooks integration
304 lines (260 loc) • 11.3 kB
JavaScript
// lib/core/AgentStateBroadcaster.js
// Broadcasts agent state changes via file system for real-time monitoring
// Implements PRD Core Feature #1: Real-Time Agent Coordination
const fs = require('fs-extra');
const path = require('path');
/**
* Broadcasts agent state changes via file system
* Creates a communication channel between Claude Code and mega-minds
* Per PRD: Enable seamless communication and handoff tracking between AI agents
*/
class AgentStateBroadcaster {
constructor(projectPath) {
this.projectPath = projectPath;
this.stateDir = path.join(projectPath, '.mega-minds', 'state');
this.agentStateFile = path.join(this.stateDir, 'active-agents.json');
this.handoffQueueFile = path.join(this.stateDir, 'handoff-queue.json');
this.systemStateFile = path.join(this.stateDir, 'system-status.json');
this.workProgressFile = path.join(this.stateDir, 'work-progress.json');
}
/**
* Initialize the state broadcasting system
*/
async initialize(agentStateTracker = null) {
await fs.ensureDir(this.stateDir);
// Initialize state files - load existing agent state if available
let currentActiveAgents = {};
if (agentStateTracker) {
try {
currentActiveAgents = await agentStateTracker.getAllAgentStates();
} catch (error) {
console.warn('⚠️ Could not load existing agent state:', error.message);
}
}
await this.updateAgentState(currentActiveAgents);
await this.updateHandoffQueue([]);
await this.updateSystemState({
status: 'healthy',
memoryUsage: process.memoryUsage(),
timestamp: new Date().toISOString()
});
await this.updateWorkProgress({});
console.log('📡 Agent State Broadcaster initialized');
console.log(`📁 State files location: ${this.stateDir}`);
}
/**
* Broadcast current agent states to file system
* PRD Requirement: Agent activation/deactivation tracking with timestamps
*/
async updateAgentState(activeAgents) {
const stateData = {
timestamp: new Date().toISOString(),
activeAgents: activeAgents,
totalActiveCount: Object.keys(activeAgents).length,
agentList: Object.keys(activeAgents),
lastUpdate: new Date().toISOString(),
// Add agent workload information per PRD
workloadSummary: this.calculateWorkloadSummary(activeAgents)
};
await fs.writeJSON(this.agentStateFile, stateData, { spaces: 2 });
console.log(`📡 Agent state broadcast: ${Object.keys(activeAgents).length} active agents`);
// Warn if approaching agent limit per PRD memory management
if (stateData.totalActiveCount >= 2) {
console.warn(`⚠️ Agent limit reached (${stateData.totalActiveCount}/2). Consider completing work before activating more agents.`);
}
return stateData;
}
/**
* Broadcast handoff queue changes
* PRD Requirement: Handoff event recording with complete audit trail
*/
async updateHandoffQueue(handoffs) {
const queueData = {
timestamp: new Date().toISOString(),
handoffs: handoffs,
queueLength: handoffs.length,
pendingCount: handoffs.filter(h => h.status === 'initiated').length,
acknowledgedCount: handoffs.filter(h => h.status === 'acknowledged').length,
inProgressCount: handoffs.filter(h => h.status === 'in_progress').length,
completedCount: handoffs.filter(h => h.status === 'completed').length,
failedCount: handoffs.filter(h => h.status === 'failed').length,
// Calculate acknowledgment rate per PRD metrics
acknowledgmentRate: this.calculateAcknowledgmentRate(handoffs),
averageCompletionTime: this.calculateAverageCompletionTime(handoffs)
};
await fs.writeJSON(this.handoffQueueFile, queueData, { spaces: 2 });
console.log(`📤 Handoff queue broadcast: ${handoffs.length} total handoffs`);
// Alert on failed handoffs per PRD requirement (30-second detection)
if (queueData.failedCount > 0) {
console.warn(`⚠️ ${queueData.failedCount} failed handoff(s) detected`);
}
return queueData;
}
/**
* Broadcast system status (memory, performance, etc.)
* PRD Requirement: Real-time memory monitoring with configurable thresholds
*/
async updateSystemState(systemInfo) {
const memoryStatus = this.getMemoryStatus();
const statusData = {
timestamp: new Date().toISOString(),
...systemInfo,
memoryStatus: memoryStatus,
performanceMetrics: await this.getPerformanceMetrics(),
// Add session info per PRD
sessionActive: await this.checkSessionActive(),
systemHealth: this.calculateSystemHealth(memoryStatus)
};
await fs.writeJSON(this.systemStateFile, statusData, { spaces: 2 });
// Trigger warnings per PRD thresholds
if (memoryStatus.status === 'warning') {
console.warn(`⚠️ Memory warning: ${memoryStatus.heapUsedMB}MB used (threshold: 2000MB)`);
} else if (memoryStatus.status === 'critical') {
console.error(`🚨 CRITICAL memory: ${memoryStatus.heapUsedMB}MB used (threshold: 3500MB)`);
}
return statusData;
}
/**
* Broadcast work progress updates
* PRD Requirement: Work progress monitoring with status updates
*/
async updateWorkProgress(progressData) {
const workData = {
timestamp: new Date().toISOString(),
agents: progressData,
overallProgress: this.calculateOverallProgress(progressData),
activeWorkItems: Object.keys(progressData).length,
completedItems: Object.values(progressData).filter(p => p.progress === 100).length,
blockedItems: Object.values(progressData).filter(p => p.status === 'blocked').length
};
await fs.writeJSON(this.workProgressFile, workData, { spaces: 2 });
console.log(`📊 Work progress broadcast: ${workData.overallProgress}% overall`);
return workData;
}
/**
* Get current memory status with thresholds from PRD
*/
getMemoryStatus() {
const usage = process.memoryUsage();
const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
const heapTotalMB = Math.round(usage.heapTotal / 1024 / 1024);
// PRD thresholds: Warning at 2GB, Critical at 3.5GB
return {
heapUsedMB,
heapTotalMB,
heapPercentage: Math.round((heapUsedMB / heapTotalMB) * 100),
status: heapUsedMB > 3500 ? 'critical' :
heapUsedMB > 2000 ? 'warning' : 'healthy',
timestamp: new Date().toISOString(),
thresholds: {
warning: 2000,
critical: 3500
}
};
}
/**
* Get performance metrics for monitoring
*/
async getPerformanceMetrics() {
const startTime = process.hrtime();
// Simple performance check
await new Promise(resolve => setTimeout(resolve, 10));
const elapsed = process.hrtime(startTime);
const responseTime = elapsed[0] * 1000 + elapsed[1] / 1000000; // Convert to ms
return {
responseTime,
handoffProcessingTime: 0, // To be implemented with actual handoff processing
agentResponseTime: 0, // To be implemented with agent metrics
systemLoad: process.cpuUsage(),
timestamp: new Date().toISOString()
};
}
/**
* Calculate workload summary for active agents
* @private
*/
calculateWorkloadSummary(activeAgents) {
const summary = {
light: 0,
medium: 0,
heavy: 0
};
for (const agent of Object.values(activeAgents)) {
const workload = agent.workload || 'medium';
summary[workload]++;
}
return summary;
}
/**
* Calculate acknowledgment rate for handoffs
* @private
*/
calculateAcknowledgmentRate(handoffs) {
if (handoffs.length === 0) return 0;
const acknowledged = handoffs.filter(h =>
h.acknowledgmentReceived || h.status === 'acknowledged' ||
h.status === 'in_progress' || h.status === 'completed'
).length;
return Math.round((acknowledged / handoffs.length) * 100);
}
/**
* Calculate average completion time for handoffs
* @private
*/
calculateAverageCompletionTime(handoffs) {
const completed = handoffs.filter(h => h.status === 'completed' && h.completionTime);
if (completed.length === 0) return 0;
const totalTime = completed.reduce((sum, h) => {
const start = new Date(h.timestamp);
const end = new Date(h.completionTime);
return sum + (end - start);
}, 0);
return Math.round(totalTime / completed.length / 1000); // Return in seconds
}
/**
* Calculate overall progress from individual agent progress
* @private
*/
calculateOverallProgress(progressData) {
const agents = Object.values(progressData);
if (agents.length === 0) return 0;
const totalProgress = agents.reduce((sum, agent) => sum + (agent.progress || 0), 0);
return Math.round(totalProgress / agents.length);
}
/**
* Check if a session is currently active
* @private
*/
async checkSessionActive() {
const sessionFile = path.join(this.projectPath, '.mega-minds', 'sessions', 'active-session.json');
return await fs.pathExists(sessionFile);
}
/**
* Calculate overall system health
* @private
*/
calculateSystemHealth(memoryStatus) {
if (memoryStatus.status === 'critical') return 'critical';
if (memoryStatus.status === 'warning') return 'degraded';
return 'healthy';
}
/**
* Get all current state data for dashboard or monitoring
*/
async getAllStateData() {
const [agentState, handoffQueue, systemState, workProgress] = await Promise.all([
fs.pathExists(this.agentStateFile) ? fs.readJSON(this.agentStateFile) : {},
fs.pathExists(this.handoffQueueFile) ? fs.readJSON(this.handoffQueueFile) : {},
fs.pathExists(this.systemStateFile) ? fs.readJSON(this.systemStateFile) : {},
fs.pathExists(this.workProgressFile) ? fs.readJSON(this.workProgressFile) : {}
]);
return {
agents: agentState,
handoffs: handoffQueue,
system: systemState,
progress: workProgress,
timestamp: new Date().toISOString()
};
}
}
module.exports = AgentStateBroadcaster;