UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

523 lines (441 loc) 15.2 kB
/** * Hive Mind Session Manager * Handles session persistence and resume functionality for swarms */ import Database from 'better-sqlite3'; import path from 'path'; import { existsSync, mkdirSync } from 'fs'; import { readFile, writeFile } from 'fs/promises'; import chalk from 'chalk'; import { cwd } from '../../node-compat.js'; export class HiveMindSessionManager { constructor(hiveMindDir = null) { this.hiveMindDir = hiveMindDir || path.join(cwd(), '.hive-mind'); this.sessionsDir = path.join(this.hiveMindDir, 'sessions'); this.dbPath = path.join(this.hiveMindDir, 'hive.db'); // Ensure directories exist this.ensureDirectories(); // Initialize database connection this.db = new Database(this.dbPath); this.initializeSchema(); } /** * Ensure required directories exist */ ensureDirectories() { if (!existsSync(this.hiveMindDir)) { mkdirSync(this.hiveMindDir, { recursive: true }); } if (!existsSync(this.sessionsDir)) { mkdirSync(this.sessionsDir, { recursive: true }); } } /** * Initialize database schema for sessions */ initializeSchema() { this.db.exec(` CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, swarm_id TEXT NOT NULL, swarm_name TEXT NOT NULL, objective TEXT, status TEXT DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, paused_at DATETIME, resumed_at DATETIME, completion_percentage REAL DEFAULT 0, checkpoint_data TEXT, metadata TEXT, FOREIGN KEY (swarm_id) REFERENCES swarms(id) ); CREATE TABLE IF NOT EXISTS session_checkpoints ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL, checkpoint_name TEXT NOT NULL, checkpoint_data TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES sessions(id) ); CREATE TABLE IF NOT EXISTS session_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, log_level TEXT DEFAULT 'info', message TEXT, agent_id TEXT, data TEXT, FOREIGN KEY (session_id) REFERENCES sessions(id) ); `); } /** * Create a new session for a swarm */ createSession(swarmId, swarmName, objective, metadata = {}) { const sessionId = `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; const stmt = this.db.prepare(` INSERT INTO sessions (id, swarm_id, swarm_name, objective, metadata) VALUES (?, ?, ?, ?, ?) `); stmt.run(sessionId, swarmId, swarmName, objective, JSON.stringify(metadata)); // Log session creation this.logSessionEvent(sessionId, 'info', 'Session created', null, { swarmId, swarmName, objective }); return sessionId; } /** * Save session checkpoint */ async saveCheckpoint(sessionId, checkpointName, checkpointData) { const checkpointId = `checkpoint-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; // Save to database const stmt = this.db.prepare(` INSERT INTO session_checkpoints (id, session_id, checkpoint_name, checkpoint_data) VALUES (?, ?, ?, ?) `); stmt.run(checkpointId, sessionId, checkpointName, JSON.stringify(checkpointData)); // Update session checkpoint data and timestamp const updateStmt = this.db.prepare(` UPDATE sessions SET checkpoint_data = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); updateStmt.run(JSON.stringify(checkpointData), sessionId); // Save checkpoint file for backup const checkpointFile = path.join(this.sessionsDir, `${sessionId}-${checkpointName}.json`); await writeFile(checkpointFile, JSON.stringify({ sessionId, checkpointId, checkpointName, timestamp: new Date().toISOString(), data: checkpointData }, null, 2)); this.logSessionEvent(sessionId, 'info', `Checkpoint saved: ${checkpointName}`, null, { checkpointId }); return checkpointId; } /** * Get active sessions */ getActiveSessions() { const stmt = this.db.prepare(` SELECT s.*, COUNT(DISTINCT a.id) as agent_count, COUNT(DISTINCT t.id) as task_count, SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) as completed_tasks FROM sessions s LEFT JOIN agents a ON s.swarm_id = a.swarm_id LEFT JOIN tasks t ON s.swarm_id = t.swarm_id WHERE s.status = 'active' OR s.status = 'paused' GROUP BY s.id ORDER BY s.updated_at DESC `); const sessions = stmt.all(); // Parse JSON fields return sessions.map(session => ({ ...session, metadata: session.metadata ? JSON.parse(session.metadata) : {}, checkpoint_data: session.checkpoint_data ? JSON.parse(session.checkpoint_data) : null, completion_percentage: session.task_count > 0 ? Math.round((session.completed_tasks / session.task_count) * 100) : 0 })); } /** * Get session by ID with full details */ getSession(sessionId) { const session = this.db.prepare(` SELECT * FROM sessions WHERE id = ? `).get(sessionId); if (!session) { return null; } // Get associated swarm data const swarm = this.db.prepare(` SELECT * FROM swarms WHERE id = ? `).get(session.swarm_id); // Get agents const agents = this.db.prepare(` SELECT * FROM agents WHERE swarm_id = ? `).all(session.swarm_id); // Get tasks const tasks = this.db.prepare(` SELECT * FROM tasks WHERE swarm_id = ? `).all(session.swarm_id); // Get checkpoints const checkpoints = this.db.prepare(` SELECT * FROM session_checkpoints WHERE session_id = ? ORDER BY created_at DESC `).all(sessionId); // Get recent logs const recentLogs = this.db.prepare(` SELECT * FROM session_logs WHERE session_id = ? ORDER BY timestamp DESC LIMIT 50 `).all(sessionId); return { ...session, metadata: session.metadata ? JSON.parse(session.metadata) : {}, checkpoint_data: session.checkpoint_data ? JSON.parse(session.checkpoint_data) : null, swarm, agents, tasks, checkpoints: checkpoints.map(cp => ({ ...cp, checkpoint_data: JSON.parse(cp.checkpoint_data) })), recentLogs, statistics: { totalAgents: agents.length, activeAgents: agents.filter(a => a.status === 'active' || a.status === 'busy').length, totalTasks: tasks.length, completedTasks: tasks.filter(t => t.status === 'completed').length, pendingTasks: tasks.filter(t => t.status === 'pending').length, inProgressTasks: tasks.filter(t => t.status === 'in_progress').length, completionPercentage: tasks.length > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / tasks.length) * 100) : 0 } }; } /** * Pause a session */ pauseSession(sessionId) { const stmt = this.db.prepare(` UPDATE sessions SET status = 'paused', paused_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); const result = stmt.run(sessionId); if (result.changes > 0) { this.logSessionEvent(sessionId, 'info', 'Session paused'); // Update swarm status const session = this.db.prepare('SELECT swarm_id FROM sessions WHERE id = ?').get(sessionId); if (session) { this.db.prepare('UPDATE swarms SET status = ? WHERE id = ?').run('paused', session.swarm_id); } } return result.changes > 0; } /** * Resume a paused session */ async resumeSession(sessionId) { const session = this.getSession(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } if (session.status !== 'paused') { throw new Error(`Session ${sessionId} is not paused (status: ${session.status})`); } // Update session status const stmt = this.db.prepare(` UPDATE sessions SET status = 'active', resumed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); stmt.run(sessionId); // Update swarm status this.db.prepare('UPDATE swarms SET status = ? WHERE id = ?').run('active', session.swarm_id); // Update agent statuses this.db.prepare(` UPDATE agents SET status = CASE WHEN role = 'queen' THEN 'active' ELSE 'idle' END WHERE swarm_id = ? `).run(session.swarm_id); this.logSessionEvent(sessionId, 'info', 'Session resumed', null, { pausedDuration: session.paused_at ? new Date() - new Date(session.paused_at) : null }); return session; } /** * Mark session as completed */ completeSession(sessionId) { const stmt = this.db.prepare(` UPDATE sessions SET status = 'completed', updated_at = CURRENT_TIMESTAMP, completion_percentage = 100 WHERE id = ? `); const result = stmt.run(sessionId); if (result.changes > 0) { this.logSessionEvent(sessionId, 'info', 'Session completed'); // Update swarm status const session = this.db.prepare('SELECT swarm_id FROM sessions WHERE id = ?').get(sessionId); if (session) { this.db.prepare('UPDATE swarms SET status = ? WHERE id = ?').run('completed', session.swarm_id); } } return result.changes > 0; } /** * Archive old sessions */ async archiveSessions(daysOld = 30) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysOld); const sessionsToArchive = this.db.prepare(` SELECT * FROM sessions WHERE status = 'completed' AND updated_at < ? `).all(cutoffDate.toISOString()); const archiveDir = path.join(this.sessionsDir, 'archive'); if (!existsSync(archiveDir)) { mkdirSync(archiveDir, { recursive: true }); } for (const session of sessionsToArchive) { const sessionData = this.getSession(session.id); const archiveFile = path.join(archiveDir, `${session.id}-archive.json`); await writeFile(archiveFile, JSON.stringify(sessionData, null, 2)); // Remove from database this.db.prepare('DELETE FROM session_logs WHERE session_id = ?').run(session.id); this.db.prepare('DELETE FROM session_checkpoints WHERE session_id = ?').run(session.id); this.db.prepare('DELETE FROM sessions WHERE id = ?').run(session.id); } return sessionsToArchive.length; } /** * Log session event */ logSessionEvent(sessionId, logLevel, message, agentId = null, data = null) { const stmt = this.db.prepare(` INSERT INTO session_logs (session_id, log_level, message, agent_id, data) VALUES (?, ?, ?, ?, ?) `); stmt.run(sessionId, logLevel, message, agentId, data ? JSON.stringify(data) : null); } /** * Get session logs */ getSessionLogs(sessionId, limit = 100, offset = 0) { const stmt = this.db.prepare(` SELECT * FROM session_logs WHERE session_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ? `); const logs = stmt.all(sessionId, limit, offset); return logs.map(log => ({ ...log, data: log.data ? JSON.parse(log.data) : null })); } /** * Update session progress */ updateSessionProgress(sessionId, completionPercentage) { const stmt = this.db.prepare(` UPDATE sessions SET completion_percentage = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); stmt.run(completionPercentage, sessionId); } /** * Generate session summary */ generateSessionSummary(sessionId) { const session = this.getSession(sessionId); if (!session) { return null; } const duration = session.paused_at && session.resumed_at ? new Date(session.updated_at) - new Date(session.created_at) - (new Date(session.resumed_at) - new Date(session.paused_at)) : new Date(session.updated_at) - new Date(session.created_at); const tasksByType = session.agents.reduce((acc, agent) => { const agentTasks = session.tasks.filter(t => t.agent_id === agent.id); if (!acc[agent.type]) { acc[agent.type] = { total: 0, completed: 0, inProgress: 0, pending: 0 }; } acc[agent.type].total += agentTasks.length; acc[agent.type].completed += agentTasks.filter(t => t.status === 'completed').length; acc[agent.type].inProgress += agentTasks.filter(t => t.status === 'in_progress').length; acc[agent.type].pending += agentTasks.filter(t => t.status === 'pending').length; return acc; }, {}); return { sessionId: session.id, swarmName: session.swarm_name, objective: session.objective, status: session.status, duration: Math.round(duration / 1000 / 60), // minutes statistics: session.statistics, tasksByType, checkpointCount: session.checkpoints.length, lastCheckpoint: session.checkpoints[0] || null, timeline: { created: session.created_at, lastUpdated: session.updated_at, paused: session.paused_at, resumed: session.resumed_at } }; } /** * Export session data */ async exportSession(sessionId, exportPath = null) { const session = this.getSession(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } const exportFile = exportPath || path.join(this.sessionsDir, `${sessionId}-export.json`); await writeFile(exportFile, JSON.stringify(session, null, 2)); return exportFile; } /** * Import session data */ async importSession(importPath) { const sessionData = JSON.parse(await readFile(importPath, 'utf8')); // Create new session with imported data const newSessionId = this.createSession( sessionData.swarm_id, sessionData.swarm_name, sessionData.objective, sessionData.metadata ); // Import checkpoints for (const checkpoint of sessionData.checkpoints || []) { await this.saveCheckpoint(newSessionId, checkpoint.checkpoint_name, checkpoint.checkpoint_data); } // Import logs for (const log of sessionData.recentLogs || []) { this.logSessionEvent( newSessionId, log.log_level, log.message, log.agent_id, log.data ? JSON.parse(log.data) : null ); } return newSessionId; } /** * Clean up and close database connection */ close() { this.db.close(); } } // Export for use in other modules export default HiveMindSessionManager;