UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

368 lines • 13.3 kB
// Distributed Session Management System // Reduces over-centralized cleanup dependency and provides resilient session management /** * DistributedSessionManager - Solves the audit finding: * "Session cleanup relies entirely on main server - fragile pattern" * * Provides distributed session management where handlers can participate * in session lifecycle events and cleanup is not centralized */ export class DistributedSessionManager { sessions = new Map(); handlers = new Map(); externalSessionsMap; performanceData = { sessionCreations: 0, sessionDestructions: 0, totalCleanupTime: 0, lastCleanupTime: new Date() }; /** * Create a new session with distributed handler notification */ async createSession(sessionId) { if (this.sessions.has(sessionId)) { throw new Error(`Session ${sessionId} already exists`); } const session = { id: sessionId, createdAt: new Date(), lastUsed: new Date(), handlerCount: 0, handlerNames: [], resources: [] }; this.sessions.set(sessionId, session); this.performanceData.sessionCreations++; // Notify all handlers (don't let handler errors break session creation) try { await this.notifyHandlers(sessionId, 'onSessionCreate'); } catch (error) { console.warn(`Handler errors during session creation for ${sessionId}:`, error); } return session; } /** * Destroy a session with distributed cleanup */ async destroySession(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return; // Already destroyed } const startTime = Date.now(); try { // Notify all handlers about destruction await this.notifyHandlers(sessionId, 'onSessionDestroy'); // Remove session this.sessions.delete(sessionId); this.performanceData.sessionDestructions++; // Update performance metrics this.performanceData.totalCleanupTime += Date.now() - startTime; this.performanceData.lastCleanupTime = new Date(); } catch (error) { console.error(`Error destroying session ${sessionId}:`, error); // Force remove even if cleanup failed this.sessions.delete(sessionId); } } /** * Attach a handler to receive session events */ attachHandler(handler) { if (this.handlers.has(handler.name)) { throw new Error(`Handler ${handler.name} already attached`); } this.handlers.set(handler.name, handler); } /** * Detach a handler from session events */ detachHandler(handlerName) { return this.handlers.delete(handlerName); } /** * Notify all handlers about a session event */ async notifyHandlers(sessionId, eventType, error) { const promises = []; for (const [handlerName, handler] of this.handlers.entries()) { const eventHandler = handler[eventType]; if (typeof eventHandler === 'function') { promises.push((async () => { try { if (eventType === 'onSessionError' && error) { await eventHandler(sessionId, error); } else { await eventHandler(sessionId); } } catch (handlerError) { console.warn(`Handler ${handlerName} failed on ${eventType} for session ${sessionId}:`, handlerError); // Don't re-throw - just log and continue } })()); } } // Wait for all handlers to complete (or fail) const results = await Promise.allSettled(promises); // Don't throw errors - log them but continue const failedHandlers = results.filter(result => result.status === 'rejected'); if (failedHandlers.length > 0) { console.warn(`${failedHandlers.length} handlers failed during ${eventType} for session ${sessionId}`); } } /** * Get session by ID */ getSession(sessionId) { return this.sessions.get(sessionId); } /** * Check if session exists */ hasSession(sessionId) { return this.sessions.has(sessionId); } /** * Track session usage by a handler */ trackSessionUsage(sessionId, handlerName) { const session = this.sessions.get(sessionId); if (!session) { return; } session.lastUsed = new Date(); if (!session.handlerNames.includes(handlerName)) { session.handlerNames.push(handlerName); session.handlerCount++; } } /** * Get session usage statistics */ getSessionUsageStats(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return undefined; } return { totalHandlers: session.handlerCount, handlerNames: [...session.handlerNames], lastUsed: session.lastUsed, createdAt: session.createdAt }; } /** * Get count of registered handlers */ getHandlerCount() { return this.handlers.size; } /** * Get health report */ getHealthReport() { const totalSessions = this.sessions.size; const activeSessions = totalSessions; // All sessions in map are active const totalHandlers = this.handlers.size; const averageHandlersPerSession = totalSessions > 0 ? Array.from(this.sessions.values()).reduce((sum, session) => sum + session.handlerCount, 0) / totalSessions : 0; const orphanedSessions = this.detectOrphanedSessions().length; // Calculate health score (0-100) let healthScore = 100; if (orphanedSessions > 0) healthScore -= (orphanedSessions / totalSessions) * 30; if (totalSessions > 50) healthScore -= 20; // Too many sessions if (averageHandlersPerSession < 1) healthScore -= 10; // Sessions without handlers return { totalSessions, activeSessions, totalHandlers, averageHandlersPerSession, orphanedSessions, healthScore: Math.max(0, Math.min(100, healthScore)) }; } /** * Detect orphaned sessions (sessions not used recently) */ detectOrphanedSessions(ageThresholdMinutes = 30) { const now = Date.now(); const threshold = ageThresholdMinutes * 60 * 1000; const orphaned = []; for (const session of this.sessions.values()) { const age = now - session.lastUsed.getTime(); if (age > threshold) { orphaned.push(session); } } return orphaned; } /** * Clean up orphaned sessions */ async cleanupOrphanedSessions(ageThresholdMinutes = 30) { const orphanedSessions = this.detectOrphanedSessions(ageThresholdMinutes); const cleanupPromises = orphanedSessions.map(session => this.destroySession(session.id).catch(error => console.error(`Failed to cleanup orphaned session ${session.id}:`, error))); await Promise.allSettled(cleanupPromises); return orphanedSessions.length; } /** * Set external sessions map for integration */ setExternalSessionsMap(sessionsMap) { this.externalSessionsMap = sessionsMap; } /** * Import existing sessions from external map */ importExistingSessions() { if (!this.externalSessionsMap) { return []; } const importedSessions = []; for (const [sessionId, externalSession] of this.externalSessionsMap.entries()) { if (!this.sessions.has(sessionId)) { const session = { id: sessionId, createdAt: externalSession.createdAt || new Date(), lastUsed: externalSession.lastUsed || new Date(), handlerCount: 0, handlerNames: [], resources: [] }; this.sessions.set(sessionId, session); importedSessions.push(session); } } return importedSessions; } /** * Get performance metrics */ getPerformanceMetrics() { const totalSessions = this.sessions.size; const now = Date.now(); // Calculate average session age let totalAge = 0; for (const session of this.sessions.values()) { const age = now - session.createdAt.getTime(); totalAge += Math.max(age, 1); // Ensure at least 1ms age } const averageSessionAge = totalSessions > 0 ? totalAge / totalSessions : 0; // Estimate memory usage (rough calculation) const memoryUsage = Math.max(totalSessions * 1024 + this.handlers.size * 512, 1024); // bytes, at least 1KB // Calculate handler efficiency const totalHandlerAttachments = Array.from(this.sessions.values()).reduce((sum, session) => sum + session.handlerCount, 0); const handlerEfficiency = totalSessions > 0 ? totalHandlerAttachments / totalSessions : 0; // Calculate cleanup latency const cleanupLatency = this.performanceData.sessionDestructions > 0 ? this.performanceData.totalCleanupTime / this.performanceData.sessionDestructions : 0; return { totalSessions, averageSessionAge, memoryUsage, handlerEfficiency, cleanupLatency }; } /** * Get cleanup recommendations */ getCleanupRecommendations() { const totalSessions = this.sessions.size; const orphanedSessions = this.detectOrphanedSessions(); const healthReport = this.getHealthReport(); const recommendedActions = []; if (orphanedSessions.length > 0) { recommendedActions.push({ action: 'cleanup-orphaned-sessions', priority: 'high', description: `${orphanedSessions.length} orphaned sessions detected - cleanup recommended` }); } if (totalSessions > 100) { recommendedActions.push({ action: 'bulk-session-cleanup', priority: 'medium', description: 'High session count - consider bulk cleanup' }); } if (healthReport.averageHandlersPerSession < 1) { recommendedActions.push({ action: 'review-handler-registration', priority: 'medium', description: 'Sessions with no handlers detected - review handler registration' }); } const priority = orphanedSessions.length > 0 ? 'high' : totalSessions > 50 ? 'medium' : 'low'; return { totalSessions, recommendedActions, priority }; } /** * Destroy all sessions */ async destroyAllSessions() { const sessionIds = Array.from(this.sessions.keys()); const cleanupPromises = sessionIds.map(sessionId => this.destroySession(sessionId).catch(error => console.error(`Failed to destroy session ${sessionId}:`, error))); await Promise.allSettled(cleanupPromises); } /** * Integration with main server */ integrateWithServer() { // This method would integrate with the main server's session management // For now, it's a placeholder for the integration point } /** * Enhance existing server cleanup */ enhanceServerCleanup(originalCleanup) { return async (sessionId) => { // Run distributed cleanup first await this.destroySession(sessionId); // Then run original cleanup await originalCleanup(sessionId); }; } /** * Get server integration status */ getServerIntegrationStatus() { const healthReport = this.getHealthReport(); return { integrated: this.externalSessionsMap !== undefined, handlersRegistered: this.handlers.size, sessionsManaged: this.sessions.size, healthScore: healthReport.healthScore }; } /** * Clear all sessions and handlers (for testing) */ clear() { this.sessions.clear(); this.handlers.clear(); this.performanceData = { sessionCreations: 0, sessionDestructions: 0, totalCleanupTime: 0, lastCleanupTime: new Date() }; } } /** * Global distributed session manager instance */ export const globalDistributedSessionManager = new DistributedSessionManager(); //# sourceMappingURL=distributed-session-manager.js.map