@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
JavaScript
"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