Skip to main content
Glama

Watchtower DAP Windows Debugging

by rlaksana
session-manager.ts11.9 kB
import { EventEmitter } from 'events'; import { Logger } from './logger'; import { ConfigManager } from './config'; import { SessionInfo } from '../schemas/session.schemas'; import { MetricsCollector } from './metrics'; import { DapStartRequest, DapAttachRequest } from '../schemas/dap-tools.schemas'; /** * Session Manager * * Manages debugging sessions, handles capability negotiation, * and ensures proper session lifecycle management. */ export class SessionManager extends EventEmitter { private logger: Logger; private config: ConfigManager; private metrics: MetricsCollector; private sessions: Map<string, SessionInfo> = new Map(); private nextSessionId: number = 1; private sessionTimeouts: Map<string, NodeJS.Timeout> = new Map(); constructor() { super(); this.logger = new Logger('SessionManager'); this.config = ConfigManager.getInstance(); this.metrics = MetricsCollector.getInstance(); this.setupSessionCleanup(); } /** * Initialize a new debugging session */ async initializeSession(config: any, capabilities: Record<string, any> = {}): Promise<string> { this.logger.info('Initializing new debugging session'); this.metrics.startTimer('session.initialize'); const sessionId = this.generateSessionId(); const startTime = Date.now(); const session: SessionInfo = { sessionId, startTime, status: 'initializing', config, capabilities, currentThreadId: undefined, currentFrame: undefined, lastActivity: startTime, createdAt: startTime, updatedAt: startTime, }; this.sessions.set(sessionId, session); // Set session timeout this.setSessionTimeout(sessionId); this.logger.debug(`Session ${sessionId} initialized`); this.emit('sessionCreated', session); this.metrics.increment('sessions.total'); this.metrics.record('sessions.active', this.sessions.size); return sessionId; } /** * Activate a session */ async activateSession(sessionId: string, threadId?: number): Promise<void> { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } session.status = 'active'; session.lastActivity = Date.now(); session.updatedAt = Date.now(); if (threadId) { session.currentThreadId = threadId; } // Reset timeout this.resetSessionTimeout(sessionId); this.logger.info(`Session ${sessionId} activated`); this.emit('sessionActivated', session); this.metrics.increment('sessions.active', 1); this.metrics.record('sessions.active', this.sessions.size); } /** * Stop a session */ async stopSession(sessionId: string): Promise<void> { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } session.status = 'stopped'; session.lastActivity = Date.now(); session.updatedAt = Date.now(); this.clearSessionTimeout(sessionId); this.sessions.set(sessionId, session); this.logger.info(`Session ${sessionId} stopped`); this.emit('sessionStopped', session); this.metrics.increment('sessions.active', -1); this.metrics.record('sessions.active', this.sessions.size); } /** * Terminate a session */ async terminateSession(sessionId: string): Promise<void> { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } session.status = 'terminated'; session.lastActivity = Date.now(); session.updatedAt = Date.now(); this.clearSessionTimeout(sessionId); this.sessions.delete(sessionId); this.logger.info(`Session ${sessionId} terminated`); this.emit('sessionTerminated', session); this.metrics.increment('sessions.active', -1); this.metrics.record('sessions.active', this.sessions.size); } /** * Update session activity */ updateSessionActivity(sessionId: string, threadId?: number, frame?: any): void { const session = this.sessions.get(sessionId); if (!session) { this.logger.warn(`Session ${sessionId} not found for activity update`); return; } session.lastActivity = Date.now(); session.updatedAt = Date.now(); if (threadId !== undefined) { session.currentThreadId = threadId; } if (frame) { session.currentFrame = frame; } this.resetSessionTimeout(sessionId); this.sessions.set(sessionId, session); this.metrics.record('sessions.active', this.sessions.size); } /** * Get session information */ getSession(sessionId: string): SessionInfo | undefined { return this.sessions.get(sessionId); } /** * Get all active sessions */ getActiveSessions(): SessionInfo[] { return Array.from(this.sessions.values()).filter( session => session.status === 'active' || session.status === 'initializing' ); } /** * Get all sessions */ getAllSessions(): SessionInfo[] { return Array.from(this.sessions.values()); } /** * Get session count */ getSessionCount(): number { return this.sessions.size; } /** * Check if session exists and is active */ isSessionActive(sessionId: string): boolean { const session = this.sessions.get(sessionId); return session?.status === 'active'; } /** * Handle DAP launch request */ async handleLaunchRequest( request: DapStartRequest ): Promise<{ sessionId: string; capabilities: Record<string, any> }> { const capabilities = await this.negotiateCapabilities(request); const sessionId = await this.initializeSession(request.arguments, capabilities); this.logger.info(`Launch request received for session ${sessionId}`); this.metrics.increment('dap.start.count'); return { sessionId, capabilities }; } /** * Handle DAP attach request */ async handleAttachRequest( request: DapAttachRequest ): Promise<{ sessionId: string; capabilities: Record<string, any> }> { const capabilities = await this.negotiateCapabilities(request); const sessionId = await this.initializeSession(request.arguments, capabilities); this.logger.info(`Attach request received for session ${sessionId}`); this.metrics.increment('dap.attach.count'); return { sessionId, capabilities }; } /** * Negotiate capabilities with the debug adapter */ private async negotiateCapabilities( request: DapStartRequest | DapAttachRequest ): Promise<Record<string, any>> { const capabilities: Record<string, any> = { supportsConfigurationDoneRequest: true, supportsEvaluateForHovers: true, supportsSetVariable: true, supportsRestartRequest: true, supportsStepBack: false, supportsDataBreakpoints: false, supportsInstructionBreakpoints: false, supportsCompletionsQuery: false, supportsModulesQuery: false, supportsGotoTargetsRequest: false, supportsLoadedSourcesRequest: false, supportsTerminateDebuggee: true, supportsTerminateRequest: true, supportsDelayedStackTraceLoading: false, supportsConditionalBreakpoints: true, supportsHitConditionalBreakpoints: true, supportsLogPoints: true, supportsExceptionInfoRequest: true, supportsSetExpression: false, supportsTerminateThreadsRequest: false, supportsSetFunctionBreakpoints: false, }; // Language-specific capabilities const config = request.arguments; if (config.runtimeExecutable?.includes('node') || (config as any).program?.includes('node')) { // Node.js specific capabilities capabilities['supportsTerminateRequest'] = true; capabilities['supportsExceptionOptions'] = true; capabilities['supportsExceptionFilterOptions'] = false; } return capabilities; } /** * Generate unique session ID */ private generateSessionId(): string { return `session_${this.nextSessionId++}_${Date.now()}`; } /** * Set session timeout */ private setSessionTimeout(sessionId: string): void { const timeout = this.config.get('debugging.sessionTimeout', 3600000); const timeoutId = setTimeout(() => { this.handleSessionTimeout(sessionId); }, timeout); this.sessionTimeouts.set(sessionId, timeoutId); } /** * Reset session timeout */ private resetSessionTimeout(sessionId: string): void { if (this.sessionTimeouts.has(sessionId)) { clearTimeout(this.sessionTimeouts.get(sessionId)); this.setSessionTimeout(sessionId); } } /** * Clear session timeout */ private clearSessionTimeout(sessionId: string): void { if (this.sessionTimeouts.has(sessionId)) { clearTimeout(this.sessionTimeouts.get(sessionId)); this.sessionTimeouts.delete(sessionId); } } /** * Handle session timeout */ private async handleSessionTimeout(sessionId: string): Promise<void> { const session = this.sessions.get(sessionId); if (session && (session.status === 'active' || session.status === 'initializing')) { this.logger.warn(`Session ${sessionId} timed out, terminating`); await this.terminateSession(sessionId); this.emit('sessionTimeout', session); } } /** * Setup periodic session cleanup */ private setupSessionCleanup(): void { // Check for inactive sessions every 5 minutes setInterval(() => { this.cleanupInactiveSessions(); }, 300000); } /** * Clean up inactive sessions */ private cleanupInactiveSessions(): void { const now = Date.now(); const timeout = this.config.get('debugging.sessionTimeout', 3600000); const sessionsToTerminate: string[] = []; for (const [sessionId, session] of Array.from(this.sessions.entries())) { if (session.status === 'active' && now - session.lastActivity > timeout) { sessionsToTerminate.push(sessionId); } } if (sessionsToTerminate.length > 0) { this.logger.info(`Cleaning up ${sessionsToTerminate.length} inactive sessions`); sessionsToTerminate.forEach(sessionId => { this.handleSessionTimeout(sessionId); }); } } /** * Shutdown all sessions */ async shutdown(): Promise<void> { this.logger.info('Shutting down all sessions'); const sessionIds = Array.from(this.sessions.keys()); const terminationPromises = sessionIds.map(sessionId => this.terminateSession(sessionId).catch(error => { this.logger.error(`Error terminating session ${sessionId}:`, error); }) ); await Promise.all(terminationPromises); this.clearAllTimeouts(); this.sessions.clear(); this.nextSessionId = 1; this.logger.info('All sessions shut down'); this.emit('shutdownComplete'); } /** * Clear all timeouts */ private clearAllTimeouts(): void { for (const timeoutId of Array.from(this.sessionTimeouts.values())) { clearTimeout(timeoutId); } this.sessionTimeouts.clear(); } /** * Get session statistics */ getSessionStats(): { total: number; active: number; initializing: number; stopped: number; terminated: number; averageSessionTime: number; } { const sessions = Array.from(this.sessions.values()); const now = Date.now(); return { total: sessions.length, active: sessions.filter(s => s.status === 'active').length, initializing: sessions.filter(s => s.status === 'initializing').length, stopped: sessions.filter(s => s.status === 'stopped').length, terminated: sessions.filter(s => s.status === 'terminated').length, averageSessionTime: sessions.length > 0 ? sessions.reduce((sum, s) => sum + (now - s.startTime), 0) / sessions.length : 0, }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rlaksana/mcp-watchtower'

If you have feedback or need assistance with the MCP directory API, please join our Discord server