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.

181 lines 6.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VariableInspector = void 0; /** * Handles variable inspection and expression evaluation using CDP */ class VariableInspector { constructor(inspector) { this.inspector = inspector; this.sourceMapManager = null; } /** * Set the source map manager for variable name mapping * Requirements: 7.4 */ setSourceMapManager(sourceMapManager) { this.sourceMapManager = sourceMapManager; } /** * Evaluate an expression in the current execution context * @param expression The JavaScript expression to evaluate * @param callFrameId Optional call frame ID (uses top frame if not provided) * @returns The evaluation result with type information */ async evaluateExpression(expression, callFrameId) { try { let result; if (callFrameId) { // Evaluate in specific call frame context result = await this.inspector.send('Debugger.evaluateOnCallFrame', { callFrameId, expression, returnByValue: false, // Get object reference for complex objects generatePreview: true, }); } else { // Get current call frames to find the top frame const pausedData = await this.getCurrentPausedState(); if (!pausedData || !pausedData.callFrames || pausedData.callFrames.length === 0) { throw new Error('Process is not paused or no call frames available'); } const topFrame = pausedData.callFrames[0]; result = await this.inspector.send('Debugger.evaluateOnCallFrame', { callFrameId: topFrame.callFrameId, expression, returnByValue: false, generatePreview: true, }); } if (result.exceptionDetails) { throw new Error(`Expression evaluation failed: ${result.exceptionDetails.exception?.description || 'Unknown error'}`); } return this.serializeRemoteObject(result.result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to evaluate expression: ${error.message}`); } throw error; } } /** * Get properties of an object * @param objectId The CDP object ID * @param options Inspection options * @returns Array of property descriptors */ async getObjectProperties(objectId, options = {}) { const { ownProperties = true, accessorPropertiesOnly = false } = options; try { const result = await this.inspector.send('Runtime.getProperties', { objectId, ownProperties, accessorPropertiesOnly, generatePreview: true, }); if (!result.result) { return []; } return result.result.map((prop) => ({ name: prop.name, value: prop.value ? this.extractValue(prop.value) : undefined, type: prop.value?.type || 'undefined', writable: prop.writable, enumerable: prop.enumerable, configurable: prop.configurable, })); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get object properties: ${error.message}`); } throw error; } } /** * Inspect an object with nested property resolution * @param objectId The CDP object ID * @param maxDepth Maximum depth to traverse (default: 2) * @returns Nested object structure */ async inspectObject(objectId, maxDepth = 2) { return this.inspectObjectRecursive(objectId, maxDepth, 0); } /** * Recursively inspect an object up to a maximum depth */ async inspectObjectRecursive(objectId, maxDepth, currentDepth) { if (currentDepth >= maxDepth) { return { _truncated: 'Max depth reached' }; } const properties = await this.getObjectProperties(objectId); const result = {}; for (const prop of properties) { if (prop.type === 'object' && prop.value && typeof prop.value === 'object' && prop.value.objectId) { // Recursively inspect nested objects result[prop.name] = await this.inspectObjectRecursive(prop.value.objectId, maxDepth, currentDepth + 1); } else { result[prop.name] = prop.value; } } return result; } /** * Get the current paused state with call frames */ async getCurrentPausedState() { // We need to track the last paused event // For now, we'll return null and require callFrameId to be passed // In a real implementation, we'd cache the last Debugger.paused event return null; } /** * Serialize a CDP RemoteObject to our EvaluationResult format */ serializeRemoteObject(remoteObject) { const result = { value: this.extractValue(remoteObject), type: remoteObject.type, description: remoteObject.description, }; if (remoteObject.objectId) { result.objectId = remoteObject.objectId; } return result; } /** * Extract the actual value from a CDP RemoteObject */ extractValue(remoteObject) { if (remoteObject.type === 'undefined') { return undefined; } if (remoteObject.type === 'object' && remoteObject.subtype === 'null') { return null; } // Check if value is explicitly provided if ('value' in remoteObject) { return remoteObject.value; } // For objects without a value, return a description if (remoteObject.type === 'object' || remoteObject.type === 'function') { return { type: remoteObject.type, subtype: remoteObject.subtype, description: remoteObject.description, objectId: remoteObject.objectId, }; } return remoteObject.description || `[${remoteObject.type}]`; } } exports.VariableInspector = VariableInspector; //# sourceMappingURL=variable-inspector.js.map