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
JavaScript
// 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