UNPKG

mega-minds

Version:

Enhanced multi-agent workflow system for Claude Code projects with automated handoff management and Claude Code hooks integration

457 lines (392 loc) 12.8 kB
/** * Statusline Provider for Mega-Minds Claude Code Integration * Provides real-time status information for Claude Code's statusline * * Features: * - Agent activity monitoring * - Memory pressure tracking * - Performance metrics * - Security status indicators */ const fs = require('fs-extra'); const path = require('path'); const { performance } = require('perf_hooks'); class StatuslineProvider { constructor(aiDevTeam = null, config = {}) { this.aiDevTeam = aiDevTeam; this.config = { updateInterval: 300, // 300ms minimum as per Claude docs maxStatusLength: 100, enableColors: true, enableEmojis: true, showPerformance: true, showMemory: true, ...config }; this.lastUpdate = 0; this.cache = null; this.performanceMetrics = { lastGenerationTime: 0, averageResponseTime: 0, totalRequests: 0 }; } /** * Generate statusline content for Claude Code * @param {Object} contextData - Optional context data from Claude * @returns {Promise<string>} Formatted statusline text */ async generateStatus(contextData = {}) { const now = performance.now(); // Respect minimum update interval if (now - this.lastUpdate < this.config.updateInterval && this.cache) { return this.cache; } try { const startTime = performance.now(); // Gather system status const status = await this.collectSystemStatus(contextData); // Format statusline const statusText = this.formatStatusline(status); // Update metrics and cache this.updatePerformanceMetrics(performance.now() - startTime); this.cache = statusText; this.lastUpdate = now; return statusText; } catch (error) { return this.generateErrorStatus(error); } } /** * Collect comprehensive system status * @param {Object} contextData - Context from Claude Code * @returns {Promise<Object>} Status object */ async collectSystemStatus(contextData) { const status = { timestamp: new Date().toISOString(), agents: await this.getAgentStatus(), memory: await this.getMemoryStatus(), performance: this.getPerformanceStatus(), session: this.getSessionStatus(contextData), security: await this.getSecurityStatus(), queue: await this.getQueueStatus() }; return status; } /** * Get agent activity status * @returns {Promise<Object>} Agent status information */ async getAgentStatus() { try { if (!this.aiDevTeam || !this.aiDevTeam.agentState) { return { active: 0, total: 0, status: 'unknown' }; } const activeAgents = await this.aiDevTeam.agentState.getActiveAgents(); const totalAgents = await this.aiDevTeam.agentState.getTotalAgents(); return { active: activeAgents.length || 0, total: totalAgents || 0, names: activeAgents.map(agent => agent.shortName || agent.name).slice(0, 2), status: activeAgents.length > 0 ? 'active' : 'idle' }; } catch (error) { return { active: 0, total: 0, status: 'error', error: error.message }; } } /** * Get memory status and pressure information * @returns {Promise<Object>} Memory status */ async getMemoryStatus() { try { if (!this.aiDevTeam || !this.aiDevTeam.memory) { return { usage: 'unknown', pressure: 'unknown', status: 'unavailable' }; } const memoryInfo = await this.aiDevTeam.memory.getStatus(); const pressure = await this.aiDevTeam.memory.getPressureLevel(); return { current: memoryInfo.current || 0, limit: memoryInfo.limit || 3500, percentage: Math.round((memoryInfo.current / memoryInfo.limit) * 100) || 0, pressure: pressure || 'normal', status: this.determineMemoryStatus(memoryInfo.current, memoryInfo.limit) }; } catch (error) { return { usage: 'error', pressure: 'unknown', status: 'error' }; } } /** * Get performance metrics * @returns {Object} Performance information */ getPerformanceStatus() { return { lastResponseTime: Math.round(this.performanceMetrics.lastGenerationTime), averageResponseTime: Math.round(this.performanceMetrics.averageResponseTime), totalRequests: this.performanceMetrics.totalRequests, status: this.determinePerformanceStatus() }; } /** * Get session information from Claude context * @param {Object} contextData - Claude context data * @returns {Object} Session status */ getSessionStatus(contextData) { return { id: contextData.sessionId || 'unknown', model: contextData.model || 'unknown', workingDir: this.getWorkingDirectory(), gitBranch: this.getGitBranch(), cost: contextData.cost || null }; } /** * Get security status indicators * @returns {Promise<Object>} Security status */ async getSecurityStatus() { try { return { permissionsValid: true, agentsBounded: await this.validateAgentBoundaries(), memorySecure: await this.validateMemorySecurity(), status: 'secure' }; } catch (error) { return { permissionsValid: false, status: 'warning', error: error.message }; } } /** * Get queue status for handoffs * @returns {Promise<Object>} Queue information */ async getQueueStatus() { try { if (!this.aiDevTeam || !this.aiDevTeam.dispatcher) { return { pending: 0, status: 'unknown' }; } const queueLength = await this.aiDevTeam.dispatcher.getQueueLength() || 0; return { pending: queueLength, status: queueLength > 5 ? 'congested' : queueLength > 0 ? 'processing' : 'clear' }; } catch (error) { return { pending: 0, status: 'error' }; } } /** * Format statusline text with colors and emojis * @param {Object} status - System status object * @returns {string} Formatted statusline */ formatStatusline(status) { const parts = []; // Agent status if (this.config.enableEmojis) { const agentEmoji = status.agents.status === 'active' ? '🤖' : '💤'; parts.push(`${agentEmoji} ${status.agents.active}/${status.agents.total}`); } else { parts.push(`Agents: ${status.agents.active}/${status.agents.total}`); } // Memory status with color coding if (this.config.showMemory && status.memory.percentage) { const memoryColor = this.getMemoryColor(status.memory.percentage); const memoryEmoji = this.getMemoryEmoji(status.memory.percentage); if (this.config.enableColors) { parts.push(`${memoryEmoji} ${memoryColor}${status.memory.percentage}%\x1b[0m`); } else { parts.push(`${memoryEmoji} ${status.memory.percentage}%`); } } // Queue status if (status.queue.pending > 0) { const queueEmoji = this.config.enableEmojis ? '🔄' : 'Q:'; parts.push(`${queueEmoji} ${status.queue.pending}`); } // Performance indicator if (this.config.showPerformance && status.performance.lastResponseTime) { const perfEmoji = this.getPerformanceEmoji(status.performance.lastResponseTime); parts.push(`${perfEmoji} ${status.performance.lastResponseTime}ms`); } // Security status const securityEmoji = status.security.status === 'secure' ? '🛡️' : '⚠️'; if (this.config.enableEmojis) { parts.push(securityEmoji); } // Git branch if available if (status.session.gitBranch && status.session.gitBranch !== 'unknown') { parts.push(`📋 ${status.session.gitBranch}`); } let statusText = parts.join(' | '); // Truncate if too long if (statusText.length > this.config.maxStatusLength) { statusText = statusText.substring(0, this.config.maxStatusLength - 3) + '...'; } return statusText; } /** * Helper methods for status determination */ determineMemoryStatus(current, limit) { const percentage = (current / limit) * 100; if (percentage > 90) return 'critical'; if (percentage > 75) return 'warning'; if (percentage > 50) return 'moderate'; return 'healthy'; } determinePerformanceStatus() { const avgTime = this.performanceMetrics.averageResponseTime; if (avgTime > 1000) return 'slow'; if (avgTime > 500) return 'moderate'; return 'fast'; } getMemoryColor(percentage) { if (percentage > 90) return '\x1b[31m'; // Red if (percentage > 75) return '\x1b[33m'; // Yellow if (percentage > 50) return '\x1b[36m'; // Cyan return '\x1b[32m'; // Green } getMemoryEmoji(percentage) { if (!this.config.enableEmojis) return 'Mem:'; if (percentage > 90) return '🔴'; if (percentage > 75) return '🟡'; if (percentage > 50) return '🔵'; return '🟢'; } getPerformanceEmoji(responseTime) { if (!this.config.enableEmojis) return 'Perf:'; if (responseTime > 1000) return '🐌'; if (responseTime > 500) return '🚶'; return '⚡'; } /** * Utility methods */ getWorkingDirectory() { try { return path.basename(process.cwd()); } catch (error) { return 'unknown'; } } getGitBranch() { try { const { execSync } = require('child_process'); return execSync('git branch --show-current', { encoding: 'utf8', stdio: 'pipe', timeout: 1000 }).trim(); } catch (error) { return 'unknown'; } } async validateAgentBoundaries() { // Validate that agents are operating within defined boundaries return true; // Simplified for now } async validateMemorySecurity() { // Validate memory access patterns and security return true; // Simplified for now } updatePerformanceMetrics(duration) { this.performanceMetrics.lastGenerationTime = duration; this.performanceMetrics.totalRequests++; // Calculate rolling average const alpha = 0.1; // Smoothing factor this.performanceMetrics.averageResponseTime = (alpha * duration) + ((1 - alpha) * this.performanceMetrics.averageResponseTime); } generateErrorStatus(error) { const errorEmoji = this.config.enableEmojis ? '❌' : 'ERROR:'; return `${errorEmoji} mega-minds status error`; } /** * Generate statusline script for Claude Code * @param {string} projectPath - Project directory * @returns {Promise<string>} Script content */ async generateStatuslineScript(projectPath) { const scriptContent = `#!/usr/bin/env node /** * Mega-Minds Statusline Script for Claude Code * Auto-generated by StatuslineProvider */ const path = require('path'); const fs = require('fs'); // Read context data from stdin let contextData = {}; try { const stdin = process.stdin.read(); if (stdin) { contextData = JSON.parse(stdin.toString()); } } catch (error) { // Ignore JSON parse errors - use empty context } // Initialize mega-minds components try { const { StatuslineProvider } = require('mega-minds/lib/claude-integration/StatuslineProvider'); const { AIDevTeam } = require('mega-minds/lib/core/AIDevTeam'); const team = new AIDevTeam(process.cwd()); const provider = new StatuslineProvider(team, { enableColors: true, enableEmojis: true, showPerformance: true }); provider.generateStatus(contextData).then(status => { console.log(status); }).catch(error => { console.log('🤖 mega-minds ready'); }); } catch (error) { // Fallback statusline if mega-minds not available const dir = path.basename(process.cwd()); console.log(\`🤖 \${dir} | mega-minds\`); } `; const scriptPath = path.join(projectPath, '.claude', 'statusline.js'); await fs.writeFile(scriptPath, scriptContent, { mode: 0o755 }); return scriptPath; } /** * Get provider statistics and configuration * @returns {Object} Provider stats */ getStats() { return { version: '2.1.0', providerName: 'StatuslineProvider', config: this.config, metrics: this.performanceMetrics, features: [ 'Real-time agent monitoring', 'Memory pressure tracking', 'Performance metrics', 'Security status', 'Queue management', 'Git integration' ] }; } /** * Reset metrics and cache */ reset() { this.cache = null; this.lastUpdate = 0; this.performanceMetrics = { lastGenerationTime: 0, averageResponseTime: 0, totalRequests: 0 }; } } module.exports = { StatuslineProvider };