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