UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

649 lines (647 loc) 19.4 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import blessed from "blessed"; import { logger } from "../../core/monitoring/logger.js"; import { SwarmDashboard } from "../../integrations/ralph/monitoring/swarm-dashboard.js"; import { SwarmRegistry } from "../../integrations/ralph/monitoring/swarm-registry.js"; import { execSync } from "child_process"; class SwarmTUI { screen; commitsTable; statusBox; agentsTable; metricsBox; logBox; swarmCoordinator = null; swarmDashboard = null; refreshInterval = null; commitMetrics = /* @__PURE__ */ new Map(); constructor() { const isGhostty = process.env.TERM_PROGRAM === "ghostty" || process.env.TERM?.includes("ghostty"); const isBasicTerm = process.env.TERM === "dumb" || process.env.TERM === "unknown"; this.screen = blessed.screen({ smartCSR: !isGhostty, // Disable smart CSR for ghostty title: "Ralph Swarm Monitor", terminal: isGhostty ? "xterm-256color" : void 0, fullUnicode: !isBasicTerm, dockBorders: false, ignoreDockContrast: true, useBCE: false, // Disable background color erase for compatibility forceUnicode: false, debug: false }); this.screen.on("error", (err) => { logger.error("TUI screen error:", err); console.log( "\u26A0\uFE0F TUI display error detected. Try setting TERM=xterm-256color" ); console.log( " Alternative: Use stackmemory ralph status for text-based monitoring" ); }); this.createUI(); this.setupKeyHandlers(); logger.info("SwarmTUI initialized"); } createUI() { const container = blessed.box({ parent: this.screen, top: 0, left: 0, width: "100%", height: "100%", style: { bg: "black" } }); const isGhostty = process.env.TERM_PROGRAM === "ghostty" || process.env.TERM?.includes("ghostty"); const safeColors = isGhostty; blessed.box({ parent: container, top: 0, left: 0, width: "100%", height: 3, content: "\u{1F9BE} Ralph Swarm Monitor - Real-time Swarm Operations", style: safeColors ? { fg: "white", bold: false } : { bg: "blue", fg: "white", bold: true }, border: { type: "line" } }); this.statusBox = blessed.box({ parent: container, top: 3, left: "50%", width: "50%", height: 8, label: " Swarm Status ", content: "No active swarm", style: { bg: "black", fg: "white" }, border: { type: "line", fg: "cyan" } }); this.metricsBox = blessed.box({ parent: container, top: 3, left: 0, width: "50%", height: 8, label: " Performance Metrics ", content: "Waiting for data...", style: { bg: "black", fg: "white" }, border: { type: "line", fg: "green" } }); this.agentsTable = blessed.table({ parent: container, top: 11, left: 0, width: "50%", height: 12, label: " Active Agents ", style: { bg: "black", fg: "white", header: { bg: "blue", fg: "white", bold: true }, cell: { selected: { bg: "blue", fg: "white" } } }, border: { type: "line", fg: "yellow" }, data: [["Role", "Status", "Iteration", "Task", "Last Active"]] }); this.commitsTable = blessed.table({ parent: container, top: 11, left: "50%", width: "50%", height: 12, label: " Recent Commits ", style: { bg: "black", fg: "white", header: { bg: "blue", fg: "white", bold: true }, cell: { selected: { bg: "blue", fg: "white" } } }, border: { type: "line", fg: "magenta" }, data: [["Agent", "Message", "Lines +/-", "Time"]] }); this.logBox = blessed.log({ parent: container, top: 23, left: 0, width: "100%", height: "100%-23", label: " Swarm Logs ", style: { bg: "black", fg: "white" }, border: { type: "line", fg: "white" }, scrollable: true, alwaysScroll: true, mouse: true }); blessed.box({ parent: container, bottom: 0, left: 0, width: "100%", height: 1, content: "q=quit | r=refresh | s=start swarm | t=stop swarm | h=help | c=clear logs | d=detect swarms", style: { bg: "white", fg: "black" } }); } setupKeyHandlers() { this.screen.key(["escape", "q", "C-c"], () => { this.cleanup(); process.exit(0); }); this.screen.key(["r"], () => { this.refreshData(); this.logBox.log("Manual refresh triggered"); }); this.screen.key(["s"], () => { this.startSwarmInteractive(); }); this.screen.key(["t"], () => { this.stopSwarmInteractive(); }); this.screen.key(["h"], () => { this.showHelp(); }); this.screen.key(["c"], () => { this.clearLogs(); }); this.screen.key(["d"], () => { this.showDetectedSwarms(); }); } /** * Initialize swarm monitoring */ async initialize(swarmCoordinator, swarmId) { try { if (swarmId) { const registry = SwarmRegistry.getInstance(); const swarm = registry.getSwarm(swarmId); if (swarm) { this.swarmCoordinator = swarm.coordinator; this.logBox.log(`Connected to swarm: ${swarmId}`); } else { this.logBox.log(`Swarm not found: ${swarmId}`); } } else if (swarmCoordinator) { this.swarmCoordinator = swarmCoordinator; } else { const registry = SwarmRegistry.getInstance(); const activeSwarms = registry.listActiveSwarms(); if (activeSwarms.length > 0) { this.swarmCoordinator = activeSwarms[0].coordinator; this.logBox.log(`Auto-connected to swarm: ${activeSwarms[0].id}`); } } if (this.swarmCoordinator) { this.swarmDashboard = new SwarmDashboard(this.swarmCoordinator); this.swarmDashboard.startMonitoring(2e3); this.swarmDashboard.on("metricsUpdated", (metrics) => { this.updateUI(metrics); }); } this.refreshInterval = setInterval(() => { this.refreshData(); }, 3e3); this.logBox.log("SwarmTUI monitoring initialized"); } catch (error) { logger.error("Failed to initialize SwarmTUI", error); this.logBox.log(`Error: ${error.message}`); } } /** * Start the TUI display */ start() { this.screen.render(); this.logBox.log("Ralph Swarm Monitor started"); this.logBox.log("Monitoring for active swarms..."); } /** * Refresh all data */ async refreshData() { try { await this.updateCommitMetrics(); if (this.swarmCoordinator) { const status = this.getSwarmStatus(); this.updateStatusDisplay(status); } else { await this.detectActiveSwarms(); } this.screen.render(); } catch (error) { logger.error("Failed to refresh TUI data", error); this.logBox.log(`Refresh error: ${error.message}`); } } /** * Update commit metrics for all agents */ async updateCommitMetrics() { try { const gitLog = execSync( 'git log --oneline --since="1 hour ago" --pretty=format:"%H|%an|%s|%ct" --numstat', { encoding: "utf8", cwd: process.cwd() } ); const commits = this.parseGitCommits(gitLog); this.updateCommitsTable(commits); } catch { this.logBox.log("No recent commits found"); } } /** * Parse git log output into commit data */ parseGitCommits(gitLog) { const commits = []; const lines = gitLog.split("\n").filter(Boolean); let currentCommit = null; for (const line of lines) { if (line.includes("|")) { const [hash, author, message, timestamp] = line.split("|"); currentCommit = { hash: hash.substring(0, 8), agent: this.extractAgentFromAuthor(author), message: message.substring(0, 50), timestamp: parseInt(timestamp), linesAdded: 0, linesDeleted: 0 }; } else if (currentCommit && line.match(/^\d+\s+\d+/)) { const [added, deleted] = line.split(" ")[0].split(" "); currentCommit.linesAdded += parseInt(added) || 0; currentCommit.linesDeleted += parseInt(deleted) || 0; commits.push({ ...currentCommit }); currentCommit = null; } } return commits.slice(0, 10); } /** * Extract agent info from git author */ extractAgentFromAuthor(author) { const agentMatch = author.match(/\[(\w+)\]/); if (agentMatch) { return agentMatch[1]; } const roles = [ "developer", "tester", "optimizer", "documenter", "architect" ]; for (const role of roles) { if (author.toLowerCase().includes(role)) { return role; } } return "user"; } /** * Update commits table display */ updateCommitsTable(commits) { const tableData = [["Agent", "Message", "Lines +/-", "Time"]]; for (const commit of commits) { const timeAgo = this.formatTimeAgo(commit.timestamp * 1e3); const linesChange = `+${commit.linesAdded}/-${commit.linesDeleted}`; tableData.push([commit.agent, commit.message, linesChange, timeAgo]); } this.commitsTable.setData(tableData); } /** * Get current swarm status */ getSwarmStatus() { if (!this.swarmCoordinator) return null; const usage = this.swarmCoordinator.getResourceUsage(); const swarmState = this.swarmCoordinator.swarmState; if (!swarmState) return null; return { swarmId: swarmState.id, status: swarmState.status, startTime: swarmState.startTime, uptime: Date.now() - swarmState.startTime, agents: usage.activeAgents ? Array.from( this.swarmCoordinator.activeAgents?.values() || [] ).map((agent) => ({ id: agent.id, role: agent.role, status: agent.status, currentTask: agent.currentTask, iteration: agent.performance?.tasksCompleted || 0, lastActivity: agent.performance?.lastFreshStart || Date.now() })) : [], performance: { throughput: swarmState.performance?.throughput || 0, efficiency: swarmState.performance?.efficiency || 0, totalTasks: swarmState.totalTaskCount || 0, completedTasks: swarmState.completedTaskCount || 0 } }; } /** * Update status display */ updateStatusDisplay(status) { if (!status) { this.statusBox.setContent("No active swarm detected"); this.agentsTable.setData([ ["Role", "Status", "Iteration", "Task", "Last Active"] ]); this.metricsBox.setContent("Waiting for swarm data..."); return; } const uptimeStr = this.formatDuration(status.uptime); const statusContent = `Swarm: ${status.swarmId.substring(0, 8)} Status: ${status.status.toUpperCase()} Uptime: ${uptimeStr} Agents: ${status.agents.length}`; this.statusBox.setContent(statusContent); const agentData = [["Role", "Status", "Iteration", "Task", "Last Active"]]; for (const agent of status.agents) { const lastActivity = this.formatTimeAgo(agent.lastActivity); const task = agent.currentTask ? agent.currentTask.substring(0, 20) : "idle"; agentData.push([ agent.role, agent.status, agent.iteration.toString(), task, lastActivity ]); } this.agentsTable.setData(agentData); const metricsContent = `Throughput: ${status.performance.throughput.toFixed(2)} tasks/min Efficiency: ${(status.performance.efficiency * 100).toFixed(1)}% Tasks: ${status.performance.completedTasks}/${status.performance.totalTasks} Success Rate: ${status.performance.efficiency > 0 ? (status.performance.efficiency * 100).toFixed(1) : "N/A"}%`; this.metricsBox.setContent(metricsContent); } /** * Update UI with metrics from dashboard */ updateUI(metrics) { this.logBox.log( `Metrics updated: ${metrics.status} - ${metrics.activeAgents} agents` ); } /** * Detect active swarms in the system */ async detectActiveSwarms() { try { const registry = SwarmRegistry.getInstance(); const activeSwarms = registry.listActiveSwarms(); const stats = registry.getStatistics(); if (activeSwarms.length > 0) { let statusContent = `Available Swarms (${activeSwarms.length}): `; for (const swarm of activeSwarms.slice(0, 3)) { const uptime = this.formatDuration(Date.now() - swarm.startTime); statusContent += `\u2022 ${swarm.id.substring(0, 8)}: ${swarm.status} (${uptime}) `; } if (activeSwarms.length > 3) { statusContent += `... and ${activeSwarms.length - 3} more`; } this.statusBox.setContent(statusContent); this.logBox.log( `Found ${activeSwarms.length} active swarms in registry` ); } else { const ralphProcesses = execSync( 'ps aux | grep "ralph" | grep -v grep', { encoding: "utf8" } ); if (ralphProcesses.trim()) { this.logBox.log("Detected Ralph processes running"); this.statusBox.setContent( "External Ralph processes detected\n(Use swarm coordinator for full monitoring)" ); } else { this.statusBox.setContent(`No active swarms detected Total swarms: ${stats.totalSwarms} Completed: ${stats.completedSwarms} Run: stackmemory ralph swarm <task>`); } } } catch { } } /** * Format time ago string */ formatTimeAgo(timestamp) { const diff = Date.now() - timestamp; const minutes = Math.floor(diff / 6e4); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ago`; if (hours > 0) return `${hours}h ago`; if (minutes > 0) return `${minutes}m ago`; return "just now"; } /** * Format duration string */ formatDuration(ms) { const seconds = Math.floor(ms / 1e3); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) return `${hours}h ${minutes % 60}m`; if (minutes > 0) return `${minutes}m ${seconds % 60}s`; return `${seconds}s`; } /** * Start swarm interactively */ startSwarmInteractive() { this.logBox.log("\u{1F680} Start Swarm Interactive Mode:"); this.logBox.log( 'Example: stackmemory ralph swarm "Implement feature" --agents developer,tester' ); this.logBox.log( 'Tip: Run the command in another terminal, then press "d" to detect it' ); } /** * Stop swarm interactively */ stopSwarmInteractive() { if (this.swarmCoordinator) { this.logBox.log("\u{1F6D1} Stopping current swarm..."); this.logBox.log( "Note: Swarm stopping not yet implemented - use Ctrl+C in swarm terminal" ); } else { this.logBox.log("\u274C No active swarm coordinator to stop"); this.logBox.log("External Ralph processes must be stopped manually"); } } /** * Show help dialog */ showHelp() { this.logBox.log("\u{1F9BE} Ralph Swarm Monitor - Help"); this.logBox.log(""); this.logBox.log("Keyboard Shortcuts:"); this.logBox.log(" q, Esc, Ctrl+C - Quit TUI"); this.logBox.log(" r - Refresh data manually"); this.logBox.log(" s - Show start swarm commands"); this.logBox.log(" t - Stop current swarm"); this.logBox.log(" h - Show this help"); this.logBox.log(" c - Clear log output"); this.logBox.log(" d - Detect and list available swarms"); this.logBox.log(""); this.logBox.log("Usage:"); this.logBox.log( " stackmemory ralph tui # Auto-detect swarms" ); this.logBox.log( " stackmemory ralph tui --swarm-id <id> # Monitor specific swarm" ); this.logBox.log(""); this.logBox.log("Starting Swarms:"); this.logBox.log( ' stackmemory ralph swarm "Task description" --agents developer,tester' ); this.logBox.log(""); } /** * Clear log output */ clearLogs() { this.logBox.setContent(""); this.logBox.log("\u{1F4DD} Logs cleared - monitoring continues..."); } /** * Show detected swarms */ async showDetectedSwarms() { this.logBox.log("\u{1F50D} Detecting active swarms..."); try { const registry = SwarmRegistry.getInstance(); const activeSwarms = registry.listActiveSwarms(); const stats = registry.getStatistics(); this.logBox.log(""); this.logBox.log("\u{1F4CA} Swarm Registry Status:"); this.logBox.log(` Total swarms: ${stats.totalSwarms}`); this.logBox.log(` Active swarms: ${stats.activeSwarms}`); this.logBox.log(` Completed swarms: ${stats.completedSwarms}`); if (activeSwarms.length > 0) { this.logBox.log(""); this.logBox.log("\u{1F9BE} Active Swarms:"); for (const swarm of activeSwarms) { const uptime = this.formatDuration(Date.now() - swarm.startTime); this.logBox.log(` \u2022 ${swarm.id}: ${swarm.description} (${uptime})`); } this.logBox.log(""); this.logBox.log("\u{1F4A1} Use --swarm-id to connect to specific swarm"); } else { this.logBox.log(""); this.logBox.log("\u274C No active swarms in registry"); try { const ralphProcesses = execSync( 'ps aux | grep "ralph" | grep -v grep', { encoding: "utf8" } ); if (ralphProcesses.trim()) { this.logBox.log("\u{1F50D} External Ralph processes detected:"); ralphProcesses.split("\n").filter((line) => line.trim()).forEach((line) => { const parts = line.split(/\s+/); this.logBox.log( ` PID ${parts[1]}: ${parts.slice(10).join(" ").slice(0, 60)}` ); }); } } catch { this.logBox.log("\u{1F50D} No external Ralph processes found"); } this.logBox.log(""); this.logBox.log( '\u{1F4A1} Start a swarm: stackmemory ralph swarm "Task" --agents developer' ); } } catch (error) { this.logBox.log(`\u274C Detection failed: ${error.message}`); } } /** * Cleanup resources */ cleanup() { if (this.refreshInterval) { clearInterval(this.refreshInterval); } if (this.swarmDashboard) { this.swarmDashboard.stopMonitoring(); } logger.info("SwarmTUI cleaned up"); } } var swarm_monitor_default = SwarmTUI; export { SwarmTUI, swarm_monitor_default as default }; //# sourceMappingURL=swarm-monitor.js.map