UNPKG

claude-flow-tbowman01

Version:

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

318 lines 11 kB
/** * Connection State Manager for MCP * Persists connection state across disconnections */ import { promises as fs } from 'node:fs'; import { join } from 'node:path'; export class ConnectionStateManager { logger; currentState; connectionHistory = []; metrics = { totalConnections: 0, totalDisconnections: 0, totalReconnections: 0, averageSessionDuration: 0, averageReconnectionTime: 0, connectionHistory: [], }; persistenceTimer; statePath; metricsPath; defaultConfig = { enablePersistence: true, stateDirectory: '.mcp-state', maxHistorySize: 1000, persistenceInterval: 60000, // 1 minute }; constructor(logger, config) { this.logger = logger; this.config = { ...this.defaultConfig, ...config }; this.statePath = join(this.config.stateDirectory, 'connection-state.json'); this.metricsPath = join(this.config.stateDirectory, 'connection-metrics.json'); this.initialize().catch((error) => { this.logger.error('Failed to initialize state manager', error); }); } config; /** * Initialize the state manager */ async initialize() { if (!this.config.enablePersistence) { return; } try { // Ensure state directory exists await fs.mkdir(this.config.stateDirectory, { recursive: true }); // Load existing state await this.loadState(); await this.loadMetrics(); // Start persistence timer this.startPersistenceTimer(); this.logger.info('Connection state manager initialized', { stateDirectory: this.config.stateDirectory, }); } catch (error) { this.logger.error('Failed to initialize state manager', error); } } /** * Save current connection state */ saveState(state) { this.currentState = { ...state, metadata: { ...state.metadata, lastSaved: new Date().toISOString(), }, }; this.logger.debug('Connection state saved', { sessionId: state.sessionId, pendingRequests: state.pendingRequests.length, }); // Persist immediately if critical if (state.pendingRequests.length > 0) { this.persistState().catch((error) => { this.logger.error('Failed to persist critical state', error); }); } } /** * Restore previous connection state */ restoreState() { if (!this.currentState) { this.logger.debug('No state to restore'); return null; } this.logger.info('Restoring connection state', { sessionId: this.currentState.sessionId, pendingRequests: this.currentState.pendingRequests.length, }); return { ...this.currentState }; } /** * Record a connection event */ recordEvent(event) { const fullEvent = { ...event, timestamp: new Date(), }; this.connectionHistory.push(fullEvent); // Trim history if needed if (this.connectionHistory.length > this.config.maxHistorySize) { this.connectionHistory = this.connectionHistory.slice(-this.config.maxHistorySize); } // Update metrics this.updateMetrics(fullEvent); this.logger.debug('Connection event recorded', { type: event.type, sessionId: event.sessionId, }); } /** * Get connection metrics */ getMetrics() { return { ...this.metrics, connectionHistory: [...this.connectionHistory], }; } /** * Clear a specific session state */ clearSession(sessionId) { if (this.currentState?.sessionId === sessionId) { this.currentState = undefined; this.logger.info('Session state cleared', { sessionId }); this.persistState().catch((error) => { this.logger.error('Failed to persist cleared state', error); }); } } /** * Add a pending request */ addPendingRequest(request) { if (!this.currentState) { this.logger.warn('No active state to add pending request'); return; } this.currentState.pendingRequests.push(request); this.logger.debug('Pending request added', { requestId: request.id, method: request.method, total: this.currentState.pendingRequests.length, }); } /** * Remove a pending request */ removePendingRequest(requestId) { if (!this.currentState) { return; } this.currentState.pendingRequests = this.currentState.pendingRequests.filter((req) => req.id !== requestId); } /** * Get pending requests */ getPendingRequests() { return this.currentState?.pendingRequests || []; } /** * Update session metadata */ updateMetadata(metadata) { if (!this.currentState) { return; } this.currentState.metadata = { ...this.currentState.metadata, ...metadata, }; } /** * Calculate session duration */ getSessionDuration(sessionId) { const connectEvent = this.connectionHistory.find((e) => e.sessionId === sessionId && e.type === 'connect'); const disconnectEvent = this.connectionHistory.find((e) => e.sessionId === sessionId && e.type === 'disconnect'); if (!connectEvent) { return null; } const endTime = disconnectEvent ? disconnectEvent.timestamp : new Date(); return endTime.getTime() - connectEvent.timestamp.getTime(); } /** * Get reconnection time for a session */ getReconnectionTime(sessionId) { const disconnectEvent = this.connectionHistory.find((e) => e.sessionId === sessionId && e.type === 'disconnect'); const reconnectEvent = this.connectionHistory.find((e) => e.sessionId === sessionId && e.type === 'reconnect' && e.timestamp > (disconnectEvent?.timestamp || new Date(0))); if (!disconnectEvent || !reconnectEvent) { return null; } return reconnectEvent.timestamp.getTime() - disconnectEvent.timestamp.getTime(); } updateMetrics(event) { switch (event.type) { case 'connect': this.metrics.totalConnections++; break; case 'disconnect': this.metrics.totalDisconnections++; // Calculate session duration const duration = this.getSessionDuration(event.sessionId); if (duration !== null) { this.metrics.lastConnectionDuration = duration; // Update average const totalDuration = this.metrics.averageSessionDuration * (this.metrics.totalDisconnections - 1) + duration; this.metrics.averageSessionDuration = totalDuration / this.metrics.totalDisconnections; } break; case 'reconnect': this.metrics.totalReconnections++; // Calculate reconnection time const reconnectTime = this.getReconnectionTime(event.sessionId); if (reconnectTime !== null) { // Update average const totalTime = this.metrics.averageReconnectionTime * (this.metrics.totalReconnections - 1) + reconnectTime; this.metrics.averageReconnectionTime = totalTime / this.metrics.totalReconnections; } break; } } async loadState() { try { const data = await fs.readFile(this.statePath, 'utf-8'); const state = JSON.parse(data); // Convert date strings back to Date objects state.lastConnected = new Date(state.lastConnected); if (state.lastDisconnected) { state.lastDisconnected = new Date(state.lastDisconnected); } this.currentState = state; this.logger.info('Connection state loaded', { sessionId: state.sessionId, pendingRequests: state.pendingRequests.length, }); } catch (error) { if (error.code !== 'ENOENT') { this.logger.error('Failed to load connection state', error); } } } async loadMetrics() { try { const data = await fs.readFile(this.metricsPath, 'utf-8'); const loaded = JSON.parse(data); // Convert date strings back to Date objects loaded.connectionHistory = loaded.connectionHistory.map((event) => ({ ...event, timestamp: new Date(event.timestamp), })); this.metrics = loaded; this.connectionHistory = loaded.connectionHistory; this.logger.info('Connection metrics loaded', { totalConnections: this.metrics.totalConnections, historySize: this.connectionHistory.length, }); } catch (error) { if (error.code !== 'ENOENT') { this.logger.error('Failed to load connection metrics', error); } } } async persistState() { if (!this.config.enablePersistence) { return; } try { if (this.currentState) { await fs.writeFile(this.statePath, JSON.stringify(this.currentState, null, 2), 'utf-8'); } // Also persist metrics await fs.writeFile(this.metricsPath, JSON.stringify({ ...this.metrics, connectionHistory: this.connectionHistory, }, null, 2), 'utf-8'); this.logger.debug('State and metrics persisted'); } catch (error) { this.logger.error('Failed to persist state', error); } } startPersistenceTimer() { if (this.persistenceTimer) { return; } this.persistenceTimer = setInterval(() => { this.persistState().catch((error) => { this.logger.error('Periodic persistence failed', error); }); }, this.config.persistenceInterval); } /** * Cleanup resources */ async cleanup() { if (this.persistenceTimer) { clearInterval(this.persistenceTimer); this.persistenceTimer = undefined; } // Final persistence await this.persistState(); } } //# sourceMappingURL=connection-state-manager.js.map