agent-dbg 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bin/ndbg +0 -0
- package/.claude/settings.local.json +21 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
- package/CLAUDE.md +43 -0
- package/PROGRESS.md +261 -0
- package/README.md +67 -0
- package/biome.json +41 -0
- package/ndbg-spec.md +958 -0
- package/package.json +30 -0
- package/src/cdp/client.ts +198 -0
- package/src/cdp/types.ts +16 -0
- package/src/cli/parser.ts +287 -0
- package/src/cli/registry.ts +7 -0
- package/src/cli/types.ts +24 -0
- package/src/commands/attach.ts +47 -0
- package/src/commands/blackbox-ls.ts +38 -0
- package/src/commands/blackbox-rm.ts +57 -0
- package/src/commands/blackbox.ts +48 -0
- package/src/commands/break-ls.ts +57 -0
- package/src/commands/break-rm.ts +40 -0
- package/src/commands/break-toggle.ts +42 -0
- package/src/commands/break.ts +145 -0
- package/src/commands/breakable.ts +69 -0
- package/src/commands/catch.ts +38 -0
- package/src/commands/console.ts +61 -0
- package/src/commands/continue.ts +46 -0
- package/src/commands/eval.ts +70 -0
- package/src/commands/exceptions.ts +61 -0
- package/src/commands/hotpatch.ts +67 -0
- package/src/commands/launch.ts +69 -0
- package/src/commands/logpoint.ts +78 -0
- package/src/commands/pause.ts +46 -0
- package/src/commands/props.ts +77 -0
- package/src/commands/restart-frame.ts +36 -0
- package/src/commands/run-to.ts +70 -0
- package/src/commands/scripts.ts +57 -0
- package/src/commands/search.ts +73 -0
- package/src/commands/sessions.ts +71 -0
- package/src/commands/set-return.ts +49 -0
- package/src/commands/set.ts +61 -0
- package/src/commands/source.ts +59 -0
- package/src/commands/sourcemap.ts +66 -0
- package/src/commands/stack.ts +64 -0
- package/src/commands/state.ts +124 -0
- package/src/commands/status.ts +57 -0
- package/src/commands/step.ts +50 -0
- package/src/commands/stop.ts +27 -0
- package/src/commands/vars.ts +71 -0
- package/src/daemon/client.ts +147 -0
- package/src/daemon/entry.ts +242 -0
- package/src/daemon/paths.ts +26 -0
- package/src/daemon/server.ts +185 -0
- package/src/daemon/session-blackbox.ts +41 -0
- package/src/daemon/session-breakpoints.ts +492 -0
- package/src/daemon/session-execution.ts +121 -0
- package/src/daemon/session-inspection.ts +701 -0
- package/src/daemon/session-mutation.ts +197 -0
- package/src/daemon/session-state.ts +258 -0
- package/src/daemon/session.ts +938 -0
- package/src/daemon/spawn.ts +53 -0
- package/src/formatter/errors.ts +15 -0
- package/src/formatter/source.ts +74 -0
- package/src/formatter/stack.ts +70 -0
- package/src/formatter/values.ts +269 -0
- package/src/formatter/variables.ts +20 -0
- package/src/main.ts +45 -0
- package/src/protocol/messages.ts +316 -0
- package/src/refs/ref-table.ts +120 -0
- package/src/refs/resolver.ts +24 -0
- package/src/sourcemap/resolver.ts +318 -0
- package/tests/fixtures/async-app.js +34 -0
- package/tests/fixtures/console-app.js +12 -0
- package/tests/fixtures/error-app.js +28 -0
- package/tests/fixtures/exception-app.js +6 -0
- package/tests/fixtures/inspect-app.js +10 -0
- package/tests/fixtures/mutation-app.js +9 -0
- package/tests/fixtures/simple-app.js +50 -0
- package/tests/fixtures/step-app.js +13 -0
- package/tests/fixtures/ts-app/src/app.ts +21 -0
- package/tests/fixtures/ts-app/tsconfig.json +14 -0
- package/tests/integration/blackbox.test.ts +135 -0
- package/tests/integration/break-extras.test.ts +241 -0
- package/tests/integration/breakpoint.test.ts +217 -0
- package/tests/integration/console.test.ts +275 -0
- package/tests/integration/execution.test.ts +247 -0
- package/tests/integration/inspection.test.ts +311 -0
- package/tests/integration/mutation.test.ts +178 -0
- package/tests/integration/session.test.ts +223 -0
- package/tests/integration/source.test.ts +209 -0
- package/tests/integration/sourcemap.test.ts +214 -0
- package/tests/integration/state.test.ts +208 -0
- package/tests/unit/cdp-client.test.ts +422 -0
- package/tests/unit/daemon.test.ts +286 -0
- package/tests/unit/formatter.test.ts +716 -0
- package/tests/unit/parser.test.ts +105 -0
- package/tests/unit/refs.test.ts +383 -0
- package/tests/unit/sourcemap.test.ts +236 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type Protocol from "devtools-protocol/types/protocol.js";
|
|
2
|
+
import type { RemoteObject } from "../formatter/values.ts";
|
|
3
|
+
import { formatValue } from "../formatter/values.ts";
|
|
4
|
+
import type { DebugSession } from "./session.ts";
|
|
5
|
+
|
|
6
|
+
export async function setVariable(
|
|
7
|
+
session: DebugSession,
|
|
8
|
+
varName: string,
|
|
9
|
+
value: string,
|
|
10
|
+
options: { frame?: string } = {},
|
|
11
|
+
): Promise<{ name: string; oldValue?: string; newValue: string; type: string }> {
|
|
12
|
+
if (!session.cdp) {
|
|
13
|
+
throw new Error("No active debug session");
|
|
14
|
+
}
|
|
15
|
+
if (!session.isPaused()) {
|
|
16
|
+
throw new Error("Cannot set variable: process is not paused");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Determine which frame to evaluate in
|
|
20
|
+
let callFrameId: string;
|
|
21
|
+
if (options.frame) {
|
|
22
|
+
const entry = session.refs.resolve(options.frame);
|
|
23
|
+
if (entry?.remoteId) {
|
|
24
|
+
callFrameId = entry.remoteId;
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error(`Unknown frame ref: ${options.frame}`);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
const topFrame = session.pausedCallFrames[0];
|
|
30
|
+
if (!topFrame) {
|
|
31
|
+
throw new Error("No call frame available");
|
|
32
|
+
}
|
|
33
|
+
callFrameId = topFrame.callFrameId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try to get old value first (best-effort)
|
|
37
|
+
let oldValue: string | undefined;
|
|
38
|
+
try {
|
|
39
|
+
const oldResult = await session.cdp.send("Debugger.evaluateOnCallFrame", {
|
|
40
|
+
callFrameId,
|
|
41
|
+
expression: varName,
|
|
42
|
+
returnByValue: false,
|
|
43
|
+
generatePreview: true,
|
|
44
|
+
});
|
|
45
|
+
const oldRemote = oldResult.result as RemoteObject | undefined;
|
|
46
|
+
if (oldRemote) {
|
|
47
|
+
oldValue = formatValue(oldRemote);
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Old value not available
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Set the new value
|
|
54
|
+
const expression = `${varName} = ${value}`;
|
|
55
|
+
const setResult = await session.cdp.send("Debugger.evaluateOnCallFrame", {
|
|
56
|
+
callFrameId,
|
|
57
|
+
expression,
|
|
58
|
+
returnByValue: false,
|
|
59
|
+
generatePreview: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const evalResult = setResult.result as RemoteObject | undefined;
|
|
63
|
+
const exceptionDetails = setResult.exceptionDetails;
|
|
64
|
+
|
|
65
|
+
if (exceptionDetails) {
|
|
66
|
+
const exception = exceptionDetails.exception as RemoteObject | undefined;
|
|
67
|
+
const errorText = exception
|
|
68
|
+
? formatValue(exception)
|
|
69
|
+
: (exceptionDetails.text ?? "Assignment error");
|
|
70
|
+
throw new Error(errorText);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!evalResult) {
|
|
74
|
+
throw new Error("No result from assignment");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result: { name: string; oldValue?: string; newValue: string; type: string } = {
|
|
78
|
+
name: varName,
|
|
79
|
+
newValue: formatValue(evalResult),
|
|
80
|
+
type: evalResult.type,
|
|
81
|
+
};
|
|
82
|
+
if (oldValue !== undefined) {
|
|
83
|
+
result.oldValue = oldValue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function setReturnValue(
|
|
90
|
+
session: DebugSession,
|
|
91
|
+
value: string,
|
|
92
|
+
): Promise<{ value: string; type: string }> {
|
|
93
|
+
if (!session.cdp) {
|
|
94
|
+
throw new Error("No active debug session");
|
|
95
|
+
}
|
|
96
|
+
if (!session.isPaused()) {
|
|
97
|
+
throw new Error("Cannot set return value: process is not paused");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const topFrame = session.pausedCallFrames[0];
|
|
101
|
+
if (!topFrame) {
|
|
102
|
+
throw new Error("No call frame available");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const callFrameId = topFrame.callFrameId;
|
|
106
|
+
|
|
107
|
+
// Evaluate the value expression to get a RemoteObject
|
|
108
|
+
const evalResult = await session.cdp.send("Debugger.evaluateOnCallFrame", {
|
|
109
|
+
callFrameId,
|
|
110
|
+
expression: value,
|
|
111
|
+
returnByValue: false,
|
|
112
|
+
generatePreview: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const evalRemote = evalResult.result as RemoteObject | undefined;
|
|
116
|
+
const exceptionDetails = evalResult.exceptionDetails;
|
|
117
|
+
|
|
118
|
+
if (exceptionDetails) {
|
|
119
|
+
const exception = exceptionDetails.exception as RemoteObject | undefined;
|
|
120
|
+
const errorText = exception
|
|
121
|
+
? formatValue(exception)
|
|
122
|
+
: (exceptionDetails.text ?? "Evaluation error");
|
|
123
|
+
throw new Error(errorText);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!evalRemote) {
|
|
127
|
+
throw new Error("No result from evaluation");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Set the return value using the evaluated RemoteObject
|
|
131
|
+
// Cast to CallArgument — RemoteObject has the objectId/value/unserializableValue fields that CallArgument needs
|
|
132
|
+
const newValue: Protocol.Runtime.CallArgument = {};
|
|
133
|
+
if (evalRemote.objectId) {
|
|
134
|
+
newValue.objectId = evalRemote.objectId;
|
|
135
|
+
} else if (evalRemote.unserializableValue) {
|
|
136
|
+
newValue.unserializableValue = evalRemote.unserializableValue;
|
|
137
|
+
} else {
|
|
138
|
+
newValue.value = evalRemote.value;
|
|
139
|
+
}
|
|
140
|
+
await session.cdp.send("Debugger.setReturnValue", { newValue });
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
value: formatValue(evalRemote),
|
|
144
|
+
type: evalRemote.type,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function hotpatch(
|
|
149
|
+
session: DebugSession,
|
|
150
|
+
file: string,
|
|
151
|
+
newSource: string,
|
|
152
|
+
options: { dryRun?: boolean } = {},
|
|
153
|
+
): Promise<{ status: string; callFrames?: unknown[]; exceptionDetails?: unknown }> {
|
|
154
|
+
if (!session.cdp) {
|
|
155
|
+
throw new Error("No active debug session");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Find the script URL and then look up the scriptId
|
|
159
|
+
const scriptUrl = session.findScriptUrl(file);
|
|
160
|
+
if (!scriptUrl) {
|
|
161
|
+
throw new Error(`No loaded script matches "${file}"`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let scriptId: string | undefined;
|
|
165
|
+
for (const [sid, info] of session.scripts) {
|
|
166
|
+
if (info.url === scriptUrl) {
|
|
167
|
+
scriptId = sid;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!scriptId) {
|
|
173
|
+
throw new Error(`Could not find script ID for "${file}"`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const setSourceParams: Protocol.Debugger.SetScriptSourceRequest = {
|
|
177
|
+
scriptId,
|
|
178
|
+
scriptSource: newSource,
|
|
179
|
+
};
|
|
180
|
+
if (options.dryRun) {
|
|
181
|
+
setSourceParams.dryRun = true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const r = await session.cdp.send("Debugger.setScriptSource", setSourceParams);
|
|
185
|
+
|
|
186
|
+
const response: { status: string; callFrames?: unknown[]; exceptionDetails?: unknown } = {
|
|
187
|
+
status: r.status ?? "Ok",
|
|
188
|
+
};
|
|
189
|
+
if (r.callFrames) {
|
|
190
|
+
response.callFrames = r.callFrames;
|
|
191
|
+
}
|
|
192
|
+
if (r.exceptionDetails) {
|
|
193
|
+
response.exceptionDetails = r.exceptionDetails;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return response;
|
|
197
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { RemoteObject } from "../formatter/values.ts";
|
|
2
|
+
import { formatValue } from "../formatter/values.ts";
|
|
3
|
+
import type { DebugSession, StateOptions, StateSnapshot } from "./session.ts";
|
|
4
|
+
|
|
5
|
+
export async function buildState(
|
|
6
|
+
session: DebugSession,
|
|
7
|
+
options: StateOptions = {},
|
|
8
|
+
): Promise<StateSnapshot> {
|
|
9
|
+
if (session.sessionState !== "paused" || !session.cdp || !session.pauseInfo) {
|
|
10
|
+
return { status: session.sessionState };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Clear volatile refs at the START of building state
|
|
14
|
+
session.refs.clearVolatile();
|
|
15
|
+
|
|
16
|
+
const showAll = !options.vars && !options.stack && !options.breakpoints && !options.code;
|
|
17
|
+
const linesContext = options.lines ?? 3;
|
|
18
|
+
const snapshot: StateSnapshot = {
|
|
19
|
+
status: "paused",
|
|
20
|
+
reason: session.pauseInfo.reason,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Determine which frame to inspect
|
|
24
|
+
let frameIndex = 0;
|
|
25
|
+
if (options.frame) {
|
|
26
|
+
const entry = session.refs.resolve(options.frame);
|
|
27
|
+
if (entry?.meta?.frameIndex !== undefined) {
|
|
28
|
+
frameIndex = entry.meta.frameIndex as number;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const callFrames = session.pausedCallFrames;
|
|
33
|
+
const targetFrame = callFrames[frameIndex];
|
|
34
|
+
|
|
35
|
+
if (!targetFrame) {
|
|
36
|
+
return snapshot;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const frameLocation = targetFrame.location;
|
|
40
|
+
const frameScriptId = frameLocation?.scriptId;
|
|
41
|
+
const frameLine = frameLocation?.lineNumber ?? 0;
|
|
42
|
+
const frameColumn = frameLocation?.columnNumber;
|
|
43
|
+
let frameUrl = frameScriptId ? (session.scripts.get(frameScriptId)?.url ?? "") : "";
|
|
44
|
+
let displayLine = frameLine + 1; // CDP is 0-based, display 1-based
|
|
45
|
+
let displayColumn = frameColumn !== undefined ? frameColumn + 1 : undefined;
|
|
46
|
+
|
|
47
|
+
// Try source map translation for pause location (unless --generated)
|
|
48
|
+
if (frameScriptId && !options.generated) {
|
|
49
|
+
const resolved = session.resolveOriginalLocation(
|
|
50
|
+
frameScriptId,
|
|
51
|
+
frameLine + 1,
|
|
52
|
+
frameColumn ?? 0,
|
|
53
|
+
);
|
|
54
|
+
if (resolved) {
|
|
55
|
+
frameUrl = resolved.url;
|
|
56
|
+
displayLine = resolved.line;
|
|
57
|
+
displayColumn = resolved.column;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
snapshot.location = {
|
|
62
|
+
url: frameUrl,
|
|
63
|
+
line: displayLine,
|
|
64
|
+
};
|
|
65
|
+
if (displayColumn !== undefined) {
|
|
66
|
+
snapshot.location.column = displayColumn;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Source code
|
|
70
|
+
if (showAll || options.code) {
|
|
71
|
+
try {
|
|
72
|
+
if (frameScriptId) {
|
|
73
|
+
let scriptSource: string | null = null;
|
|
74
|
+
let useOriginalLines = false;
|
|
75
|
+
|
|
76
|
+
if (!options.generated) {
|
|
77
|
+
// Try to get original source from source map
|
|
78
|
+
const smOriginal = session.sourceMapResolver.toOriginal(
|
|
79
|
+
frameScriptId,
|
|
80
|
+
frameLine + 1,
|
|
81
|
+
frameColumn ?? 0,
|
|
82
|
+
);
|
|
83
|
+
if (smOriginal) {
|
|
84
|
+
scriptSource = session.sourceMapResolver.getOriginalSource(
|
|
85
|
+
frameScriptId,
|
|
86
|
+
smOriginal.source,
|
|
87
|
+
);
|
|
88
|
+
useOriginalLines = scriptSource !== null;
|
|
89
|
+
}
|
|
90
|
+
// Fallback: script has source map but line is unmapped — still show original source
|
|
91
|
+
if (!scriptSource) {
|
|
92
|
+
const primarySource = session.sourceMapResolver.getScriptOriginalUrl(frameScriptId);
|
|
93
|
+
if (primarySource) {
|
|
94
|
+
scriptSource = session.sourceMapResolver.getOriginalSource(
|
|
95
|
+
frameScriptId,
|
|
96
|
+
primarySource,
|
|
97
|
+
);
|
|
98
|
+
useOriginalLines = scriptSource !== null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!scriptSource) {
|
|
104
|
+
const sourceResult = await session.cdp.send("Debugger.getScriptSource", {
|
|
105
|
+
scriptId: frameScriptId,
|
|
106
|
+
});
|
|
107
|
+
scriptSource = sourceResult.scriptSource;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sourceLines = scriptSource.split("\n");
|
|
111
|
+
// Use original line for windowing if we have source-mapped content
|
|
112
|
+
const currentLine0 = useOriginalLines ? displayLine - 1 : frameLine;
|
|
113
|
+
const startLine = Math.max(0, currentLine0 - linesContext);
|
|
114
|
+
const endLine = Math.min(sourceLines.length - 1, currentLine0 + linesContext);
|
|
115
|
+
|
|
116
|
+
const lines: Array<{ line: number; text: string; current?: boolean }> = [];
|
|
117
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
118
|
+
const entry: { line: number; text: string; current?: boolean } = {
|
|
119
|
+
line: i + 1, // 1-based
|
|
120
|
+
text: sourceLines[i] ?? "",
|
|
121
|
+
};
|
|
122
|
+
if (i === currentLine0) {
|
|
123
|
+
entry.current = true;
|
|
124
|
+
}
|
|
125
|
+
lines.push(entry);
|
|
126
|
+
}
|
|
127
|
+
snapshot.source = { lines };
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
// Source not available
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Stack frames
|
|
135
|
+
if (showAll || options.stack) {
|
|
136
|
+
const stackFrames: Array<{
|
|
137
|
+
ref: string;
|
|
138
|
+
functionName: string;
|
|
139
|
+
file: string;
|
|
140
|
+
line: number;
|
|
141
|
+
column?: number;
|
|
142
|
+
isAsync?: boolean;
|
|
143
|
+
}> = [];
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < callFrames.length; i++) {
|
|
146
|
+
const frame = callFrames[i];
|
|
147
|
+
if (!frame) continue;
|
|
148
|
+
const callFrameId = frame.callFrameId;
|
|
149
|
+
const funcName = frame.functionName || "(anonymous)";
|
|
150
|
+
const loc = frame.location;
|
|
151
|
+
const sid = loc.scriptId;
|
|
152
|
+
const lineNum = loc.lineNumber + 1; // 1-based
|
|
153
|
+
const colNum = loc.columnNumber;
|
|
154
|
+
let url = session.scripts.get(sid)?.url ?? "";
|
|
155
|
+
let stackLine = lineNum;
|
|
156
|
+
let stackCol = colNum !== undefined ? colNum + 1 : undefined;
|
|
157
|
+
let resolvedName: string | null = null;
|
|
158
|
+
|
|
159
|
+
if (!options.generated) {
|
|
160
|
+
const resolved = session.resolveOriginalLocation(sid, lineNum, colNum ?? 0);
|
|
161
|
+
if (resolved) {
|
|
162
|
+
url = resolved.url;
|
|
163
|
+
stackLine = resolved.line;
|
|
164
|
+
stackCol = resolved.column;
|
|
165
|
+
}
|
|
166
|
+
// Get original function name from exact mapping
|
|
167
|
+
const smOriginal = session.sourceMapResolver.toOriginal(sid, lineNum, colNum ?? 0);
|
|
168
|
+
resolvedName = smOriginal?.name ?? null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const ref = session.refs.addFrame(callFrameId, funcName, { frameIndex: i });
|
|
172
|
+
|
|
173
|
+
const stackEntry: {
|
|
174
|
+
ref: string;
|
|
175
|
+
functionName: string;
|
|
176
|
+
file: string;
|
|
177
|
+
line: number;
|
|
178
|
+
column?: number;
|
|
179
|
+
isAsync?: boolean;
|
|
180
|
+
} = {
|
|
181
|
+
ref,
|
|
182
|
+
functionName: resolvedName ?? funcName,
|
|
183
|
+
file: url,
|
|
184
|
+
line: stackLine,
|
|
185
|
+
};
|
|
186
|
+
if (stackCol !== undefined) {
|
|
187
|
+
stackEntry.column = stackCol;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
stackFrames.push(stackEntry);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
snapshot.stack = stackFrames;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Local variables
|
|
197
|
+
if (showAll || options.vars) {
|
|
198
|
+
try {
|
|
199
|
+
const scopeChain = targetFrame.scopeChain;
|
|
200
|
+
if (scopeChain) {
|
|
201
|
+
const locals: Array<{ ref: string; name: string; value: string }> = [];
|
|
202
|
+
|
|
203
|
+
for (const scope of scopeChain) {
|
|
204
|
+
const scopeType = scope.type;
|
|
205
|
+
|
|
206
|
+
// By default only show "local" scope; with --all-scopes include "closure" too
|
|
207
|
+
if (scopeType === "local" || (options.allScopes && scopeType === "closure")) {
|
|
208
|
+
const scopeObj = scope.object;
|
|
209
|
+
const objectId = scopeObj.objectId;
|
|
210
|
+
if (!objectId) continue;
|
|
211
|
+
|
|
212
|
+
const propsResult = await session.cdp.send("Runtime.getProperties", {
|
|
213
|
+
objectId,
|
|
214
|
+
ownProperties: true,
|
|
215
|
+
generatePreview: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const properties = propsResult.result;
|
|
219
|
+
|
|
220
|
+
for (const prop of properties) {
|
|
221
|
+
const propName = prop.name;
|
|
222
|
+
const propValue = prop.value as RemoteObject | undefined;
|
|
223
|
+
|
|
224
|
+
if (!propValue) continue;
|
|
225
|
+
|
|
226
|
+
// Skip internal properties
|
|
227
|
+
if (propName.startsWith("__")) continue;
|
|
228
|
+
|
|
229
|
+
const remoteId = propValue.objectId ?? `primitive:${propName}`;
|
|
230
|
+
const ref = session.refs.addVar(remoteId as string, propName);
|
|
231
|
+
|
|
232
|
+
locals.push({
|
|
233
|
+
ref,
|
|
234
|
+
name: propName,
|
|
235
|
+
value: formatValue(propValue),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Skip "global" scope unless explicitly requested
|
|
241
|
+
if (scopeType === "global") continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
snapshot.locals = locals;
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Variables not available
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Breakpoint count
|
|
252
|
+
if (showAll || options.breakpoints) {
|
|
253
|
+
const bpEntries = session.refs.list("BP");
|
|
254
|
+
snapshot.breakpointCount = bpEntries.length;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return snapshot;
|
|
258
|
+
}
|