UNPKG

@ai-capabilities-suite/mcp-debugger-core

Version:

Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.

328 lines 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionRecorder = exports.PrivacyMode = exports.SessionEventType = void 0; /** * Session event types for recording */ var SessionEventType; (function (SessionEventType) { SessionEventType["SESSION_START"] = "session_start"; SessionEventType["SESSION_END"] = "session_end"; SessionEventType["BREAKPOINT_SET"] = "breakpoint_set"; SessionEventType["BREAKPOINT_HIT"] = "breakpoint_hit"; SessionEventType["BREAKPOINT_REMOVED"] = "breakpoint_removed"; SessionEventType["STEP_OVER"] = "step_over"; SessionEventType["STEP_INTO"] = "step_into"; SessionEventType["STEP_OUT"] = "step_out"; SessionEventType["CONTINUE"] = "continue"; SessionEventType["PAUSE"] = "pause"; SessionEventType["VARIABLE_INSPECT"] = "variable_inspect"; SessionEventType["EXPRESSION_EVALUATE"] = "expression_evaluate"; SessionEventType["STACK_TRACE"] = "stack_trace"; SessionEventType["ERROR"] = "error"; })(SessionEventType || (exports.SessionEventType = SessionEventType = {})); /** * Privacy mode for session recording */ var PrivacyMode; (function (PrivacyMode) { PrivacyMode["FULL"] = "full"; PrivacyMode["MASKED"] = "masked"; PrivacyMode["MINIMAL"] = "minimal"; PrivacyMode["DISABLED"] = "disabled"; })(PrivacyMode || (exports.PrivacyMode = PrivacyMode = {})); /** * Session recorder for advanced observability * Records debugging session events for replay and analysis */ class SessionRecorder { constructor(privacyMode = PrivacyMode.MASKED, storageConfig) { this.recordings = new Map(); this.metadata = new Map(); this.sensitivePatterns = [ /password/i, /token/i, /secret/i, /apikey/i, /api_key/i, ]; this.privacyMode = privacyMode; this.storageConfig = { maxRecordings: storageConfig?.maxRecordings || 100, maxEventsPerRecording: storageConfig?.maxEventsPerRecording || 10000, retentionDays: storageConfig?.retentionDays || 7, }; } /** * Start recording a session */ startRecording(sessionId) { if (this.privacyMode === PrivacyMode.DISABLED) { return; } this.recordings.set(sessionId, []); this.metadata.set(sessionId, { sessionId, startTime: Date.now(), eventCount: 0, privacyMode: this.privacyMode, }); this.recordEvent(sessionId, SessionEventType.SESSION_START, {}); } /** * Stop recording a session */ stopRecording(sessionId) { if (this.privacyMode === PrivacyMode.DISABLED) { return; } const meta = this.metadata.get(sessionId); if (meta) { meta.endTime = Date.now(); meta.duration = meta.endTime - meta.startTime; } this.recordEvent(sessionId, SessionEventType.SESSION_END, {}); } /** * Check if data contains sensitive information */ containsSensitiveData(data) { if (typeof data === 'string') { return this.sensitivePatterns.some((pattern) => pattern.test(data)); } if (typeof data === 'object' && data !== null) { for (const key in data) { if (this.sensitivePatterns.some((pattern) => pattern.test(key))) { return true; } if (this.containsSensitiveData(data[key])) { return true; } } } return false; } /** * Mask sensitive data in event data */ maskSensitiveData(data) { if (typeof data === 'string') { if (this.containsSensitiveData(data)) { return '***MASKED***'; } return data; } if (Array.isArray(data)) { return data.map((item) => this.maskSensitiveData(item)); } if (typeof data === 'object' && data !== null) { const masked = {}; for (const key in data) { if (this.sensitivePatterns.some((pattern) => pattern.test(key))) { masked[key] = '***MASKED***'; } else { masked[key] = this.maskSensitiveData(data[key]); } } return masked; } return data; } /** * Process event data based on privacy mode */ processEventData(data) { switch (this.privacyMode) { case PrivacyMode.DISABLED: return { processedData: {}, masked: false }; case PrivacyMode.MINIMAL: return { processedData: {}, masked: false }; case PrivacyMode.MASKED: const hasSensitive = this.containsSensitiveData(data); return { processedData: hasSensitive ? this.maskSensitiveData(data) : data, masked: hasSensitive, }; case PrivacyMode.FULL: return { processedData: data, masked: false }; default: return { processedData: {}, masked: false }; } } /** * Record an event */ recordEvent(sessionId, type, data) { if (this.privacyMode === PrivacyMode.DISABLED) { return; } const events = this.recordings.get(sessionId); if (!events) { return; } // Check event limit if (events.length >= this.storageConfig.maxEventsPerRecording) { // Remove oldest event events.shift(); } const { processedData, masked } = this.processEventData(data); const event = { type, timestamp: Date.now(), sessionId, data: processedData, masked, }; events.push(event); // Update metadata const meta = this.metadata.get(sessionId); if (meta) { meta.eventCount = events.length; } } /** * Get recording for a session */ getRecording(sessionId) { const events = this.recordings.get(sessionId); const meta = this.metadata.get(sessionId); if (!events || !meta) { return undefined; } return { metadata: { ...meta }, events: [...events], }; } /** * Get all recordings */ getAllRecordings() { const recordings = []; for (const sessionId of this.recordings.keys()) { const recording = this.getRecording(sessionId); if (recording) { recordings.push(recording); } } return recordings; } /** * Delete a recording */ deleteRecording(sessionId) { const hadRecording = this.recordings.has(sessionId); this.recordings.delete(sessionId); this.metadata.delete(sessionId); return hadRecording; } /** * Clear old recordings based on retention policy */ pruneOldRecordings() { const now = Date.now(); const retentionMs = this.storageConfig.retentionDays * 24 * 60 * 60 * 1000; let prunedCount = 0; for (const [sessionId, meta] of this.metadata.entries()) { const age = now - meta.startTime; if (age > retentionMs) { this.deleteRecording(sessionId); prunedCount++; } } return prunedCount; } /** * Enforce recording limit */ enforceRecordingLimit() { const recordings = this.getAllRecordings(); if (recordings.length <= this.storageConfig.maxRecordings) { return 0; } // Sort by start time (oldest first) recordings.sort((a, b) => a.metadata.startTime - b.metadata.startTime); // Delete oldest recordings const toDelete = recordings.length - this.storageConfig.maxRecordings; let deletedCount = 0; for (let i = 0; i < toDelete; i++) { this.deleteRecording(recordings[i].metadata.sessionId); deletedCount++; } return deletedCount; } /** * Export recording as JSON */ exportRecording(sessionId) { const recording = this.getRecording(sessionId); if (!recording) { return undefined; } return JSON.stringify(recording, null, 2); } /** * Import recording from JSON */ importRecording(json) { try { const recording = JSON.parse(json); this.recordings.set(recording.metadata.sessionId, recording.events); this.metadata.set(recording.metadata.sessionId, recording.metadata); return true; } catch (error) { return false; } } /** * Get recording statistics */ getStatistics() { const recordings = this.getAllRecordings(); if (recordings.length === 0) { return { totalRecordings: 0, totalEvents: 0, averageEventsPerRecording: 0, }; } const totalEvents = recordings.reduce((sum, r) => sum + r.metadata.eventCount, 0); const startTimes = recordings.map((r) => r.metadata.startTime); return { totalRecordings: recordings.length, totalEvents, oldestRecording: Math.min(...startTimes), newestRecording: Math.max(...startTimes), averageEventsPerRecording: totalEvents / recordings.length, }; } /** * Set privacy mode */ setPrivacyMode(mode) { this.privacyMode = mode; } /** * Get current privacy mode */ getPrivacyMode() { return this.privacyMode; } /** * Add custom sensitive pattern */ addSensitivePattern(pattern) { this.sensitivePatterns.push(pattern); } /** * Clear all recordings */ clearAllRecordings() { this.recordings.clear(); this.metadata.clear(); } } exports.SessionRecorder = SessionRecorder; //# sourceMappingURL=session-recorder.js.map