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.
Files changed (99) hide show
  1. package/.bin/ndbg +0 -0
  2. package/.claude/settings.local.json +21 -0
  3. package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
  4. package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
  5. package/CLAUDE.md +43 -0
  6. package/PROGRESS.md +261 -0
  7. package/README.md +67 -0
  8. package/biome.json +41 -0
  9. package/ndbg-spec.md +958 -0
  10. package/package.json +30 -0
  11. package/src/cdp/client.ts +198 -0
  12. package/src/cdp/types.ts +16 -0
  13. package/src/cli/parser.ts +287 -0
  14. package/src/cli/registry.ts +7 -0
  15. package/src/cli/types.ts +24 -0
  16. package/src/commands/attach.ts +47 -0
  17. package/src/commands/blackbox-ls.ts +38 -0
  18. package/src/commands/blackbox-rm.ts +57 -0
  19. package/src/commands/blackbox.ts +48 -0
  20. package/src/commands/break-ls.ts +57 -0
  21. package/src/commands/break-rm.ts +40 -0
  22. package/src/commands/break-toggle.ts +42 -0
  23. package/src/commands/break.ts +145 -0
  24. package/src/commands/breakable.ts +69 -0
  25. package/src/commands/catch.ts +38 -0
  26. package/src/commands/console.ts +61 -0
  27. package/src/commands/continue.ts +46 -0
  28. package/src/commands/eval.ts +70 -0
  29. package/src/commands/exceptions.ts +61 -0
  30. package/src/commands/hotpatch.ts +67 -0
  31. package/src/commands/launch.ts +69 -0
  32. package/src/commands/logpoint.ts +78 -0
  33. package/src/commands/pause.ts +46 -0
  34. package/src/commands/props.ts +77 -0
  35. package/src/commands/restart-frame.ts +36 -0
  36. package/src/commands/run-to.ts +70 -0
  37. package/src/commands/scripts.ts +57 -0
  38. package/src/commands/search.ts +73 -0
  39. package/src/commands/sessions.ts +71 -0
  40. package/src/commands/set-return.ts +49 -0
  41. package/src/commands/set.ts +61 -0
  42. package/src/commands/source.ts +59 -0
  43. package/src/commands/sourcemap.ts +66 -0
  44. package/src/commands/stack.ts +64 -0
  45. package/src/commands/state.ts +124 -0
  46. package/src/commands/status.ts +57 -0
  47. package/src/commands/step.ts +50 -0
  48. package/src/commands/stop.ts +27 -0
  49. package/src/commands/vars.ts +71 -0
  50. package/src/daemon/client.ts +147 -0
  51. package/src/daemon/entry.ts +242 -0
  52. package/src/daemon/paths.ts +26 -0
  53. package/src/daemon/server.ts +185 -0
  54. package/src/daemon/session-blackbox.ts +41 -0
  55. package/src/daemon/session-breakpoints.ts +492 -0
  56. package/src/daemon/session-execution.ts +121 -0
  57. package/src/daemon/session-inspection.ts +701 -0
  58. package/src/daemon/session-mutation.ts +197 -0
  59. package/src/daemon/session-state.ts +258 -0
  60. package/src/daemon/session.ts +938 -0
  61. package/src/daemon/spawn.ts +53 -0
  62. package/src/formatter/errors.ts +15 -0
  63. package/src/formatter/source.ts +74 -0
  64. package/src/formatter/stack.ts +70 -0
  65. package/src/formatter/values.ts +269 -0
  66. package/src/formatter/variables.ts +20 -0
  67. package/src/main.ts +45 -0
  68. package/src/protocol/messages.ts +316 -0
  69. package/src/refs/ref-table.ts +120 -0
  70. package/src/refs/resolver.ts +24 -0
  71. package/src/sourcemap/resolver.ts +318 -0
  72. package/tests/fixtures/async-app.js +34 -0
  73. package/tests/fixtures/console-app.js +12 -0
  74. package/tests/fixtures/error-app.js +28 -0
  75. package/tests/fixtures/exception-app.js +6 -0
  76. package/tests/fixtures/inspect-app.js +10 -0
  77. package/tests/fixtures/mutation-app.js +9 -0
  78. package/tests/fixtures/simple-app.js +50 -0
  79. package/tests/fixtures/step-app.js +13 -0
  80. package/tests/fixtures/ts-app/src/app.ts +21 -0
  81. package/tests/fixtures/ts-app/tsconfig.json +14 -0
  82. package/tests/integration/blackbox.test.ts +135 -0
  83. package/tests/integration/break-extras.test.ts +241 -0
  84. package/tests/integration/breakpoint.test.ts +217 -0
  85. package/tests/integration/console.test.ts +275 -0
  86. package/tests/integration/execution.test.ts +247 -0
  87. package/tests/integration/inspection.test.ts +311 -0
  88. package/tests/integration/mutation.test.ts +178 -0
  89. package/tests/integration/session.test.ts +223 -0
  90. package/tests/integration/source.test.ts +209 -0
  91. package/tests/integration/sourcemap.test.ts +214 -0
  92. package/tests/integration/state.test.ts +208 -0
  93. package/tests/unit/cdp-client.test.ts +422 -0
  94. package/tests/unit/daemon.test.ts +286 -0
  95. package/tests/unit/formatter.test.ts +716 -0
  96. package/tests/unit/parser.test.ts +105 -0
  97. package/tests/unit/refs.test.ts +383 -0
  98. package/tests/unit/sourcemap.test.ts +236 -0
  99. package/tsconfig.json +32 -0
@@ -0,0 +1,275 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { DebugSession } from "../../src/daemon/session.ts";
3
+
4
+ /**
5
+ * Polls until the session reaches the expected state, or times out.
6
+ */
7
+ async function waitForState(
8
+ session: DebugSession,
9
+ state: "idle" | "running" | "paused",
10
+ timeoutMs = 5000,
11
+ ): Promise<void> {
12
+ const deadline = Date.now() + timeoutMs;
13
+ while (session.sessionState !== state && Date.now() < deadline) {
14
+ await Bun.sleep(50);
15
+ }
16
+ }
17
+
18
+ describe("Console capture", () => {
19
+ test("captures console.log, console.warn, and console.error messages", async () => {
20
+ const session = new DebugSession("test-console-capture");
21
+ try {
22
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
23
+ await waitForState(session, "paused");
24
+
25
+ // Continue to the debugger statement — this will execute the console calls
26
+ await session.continue();
27
+ await waitForState(session, "paused", 5000);
28
+
29
+ // Allow a small delay for console events to be processed
30
+ await Bun.sleep(100);
31
+
32
+ const messages = session.getConsoleMessages();
33
+ expect(messages.length).toBeGreaterThanOrEqual(3);
34
+
35
+ const levels = messages.map((m) => m.level);
36
+ expect(levels).toContain("log");
37
+ expect(levels).toContain("warning");
38
+ expect(levels).toContain("error");
39
+
40
+ // Check that text is captured
41
+ const logMsg = messages.find((m) => m.text.includes("hello from app"));
42
+ expect(logMsg).toBeDefined();
43
+
44
+ const warnMsg = messages.find((m) => m.text.includes("warning message"));
45
+ expect(warnMsg).toBeDefined();
46
+
47
+ const errMsg = messages.find((m) => m.text.includes("error message"));
48
+ expect(errMsg).toBeDefined();
49
+ } finally {
50
+ await session.stop();
51
+ }
52
+ });
53
+
54
+ test("captures console messages with objects", async () => {
55
+ const session = new DebugSession("test-console-objects");
56
+ try {
57
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
58
+ await waitForState(session, "paused");
59
+
60
+ await session.continue();
61
+ await waitForState(session, "paused", 5000);
62
+ await Bun.sleep(100);
63
+
64
+ const messages = session.getConsoleMessages();
65
+ const objectMsg = messages.find((m) => m.text.includes("object:"));
66
+ expect(objectMsg).toBeDefined();
67
+ // The object should contain something about key/value
68
+ expect(objectMsg?.text).toContain("key");
69
+ } finally {
70
+ await session.stop();
71
+ }
72
+ });
73
+
74
+ test("filters console messages by level", async () => {
75
+ const session = new DebugSession("test-console-filter");
76
+ try {
77
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
78
+ await waitForState(session, "paused");
79
+
80
+ await session.continue();
81
+ await waitForState(session, "paused", 5000);
82
+ await Bun.sleep(100);
83
+
84
+ const errorMessages = session.getConsoleMessages({ level: "error" });
85
+ expect(errorMessages.length).toBeGreaterThanOrEqual(1);
86
+ for (const msg of errorMessages) {
87
+ expect(msg.level).toBe("error");
88
+ }
89
+
90
+ const warnMessages = session.getConsoleMessages({ level: "warning" });
91
+ expect(warnMessages.length).toBeGreaterThanOrEqual(1);
92
+ for (const msg of warnMessages) {
93
+ expect(msg.level).toBe("warning");
94
+ }
95
+ } finally {
96
+ await session.stop();
97
+ }
98
+ });
99
+
100
+ test("console --since returns only last N entries", async () => {
101
+ const session = new DebugSession("test-console-since");
102
+ try {
103
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
104
+ await waitForState(session, "paused");
105
+
106
+ await session.continue();
107
+ await waitForState(session, "paused", 5000);
108
+ await Bun.sleep(100);
109
+
110
+ const allMessages = session.getConsoleMessages();
111
+ expect(allMessages.length).toBeGreaterThanOrEqual(3);
112
+
113
+ const lastTwo = session.getConsoleMessages({ since: 2 });
114
+ expect(lastTwo.length).toBe(2);
115
+
116
+ // The last two messages should match the tail of all messages
117
+ expect(lastTwo[0]?.text).toBe(allMessages[allMessages.length - 2]?.text);
118
+ expect(lastTwo[1]?.text).toBe(allMessages[allMessages.length - 1]?.text);
119
+ } finally {
120
+ await session.stop();
121
+ }
122
+ });
123
+
124
+ test("console --clear clears the buffer after returning", async () => {
125
+ const session = new DebugSession("test-console-clear");
126
+ try {
127
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
128
+ await waitForState(session, "paused");
129
+
130
+ await session.continue();
131
+ await waitForState(session, "paused", 5000);
132
+ await Bun.sleep(100);
133
+
134
+ const messages = session.getConsoleMessages({ clear: true });
135
+ expect(messages.length).toBeGreaterThanOrEqual(3);
136
+
137
+ // After clearing, buffer should be empty
138
+ const afterClear = session.getConsoleMessages();
139
+ expect(afterClear.length).toBe(0);
140
+ } finally {
141
+ await session.stop();
142
+ }
143
+ });
144
+
145
+ test("clearConsole() empties the buffer", async () => {
146
+ const session = new DebugSession("test-console-clear-method");
147
+ try {
148
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
149
+ await waitForState(session, "paused");
150
+
151
+ await session.continue();
152
+ await waitForState(session, "paused", 5000);
153
+ await Bun.sleep(100);
154
+
155
+ expect(session.getConsoleMessages().length).toBeGreaterThan(0);
156
+ session.clearConsole();
157
+ expect(session.getConsoleMessages().length).toBe(0);
158
+ } finally {
159
+ await session.stop();
160
+ }
161
+ });
162
+
163
+ test("stop() clears console and exception buffers", async () => {
164
+ const session = new DebugSession("test-console-stop-clears");
165
+ try {
166
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
167
+ await waitForState(session, "paused");
168
+
169
+ await session.continue();
170
+ await waitForState(session, "paused", 5000);
171
+ await Bun.sleep(100);
172
+
173
+ expect(session.getConsoleMessages().length).toBeGreaterThan(0);
174
+ } finally {
175
+ await session.stop();
176
+ }
177
+
178
+ // After stop, buffers should be cleared
179
+ expect(session.getConsoleMessages().length).toBe(0);
180
+ expect(session.getExceptions().length).toBe(0);
181
+ });
182
+ });
183
+
184
+ describe("Exception capture", () => {
185
+ test("captures uncaught exceptions", async () => {
186
+ const session = new DebugSession("test-exception-capture");
187
+ try {
188
+ await session.launch(["node", "tests/fixtures/exception-app.js"], { brk: true });
189
+ await waitForState(session, "paused");
190
+
191
+ // Continue — the setTimeout will throw after 50ms and the process will crash
192
+ await session.continue();
193
+ // Wait for the process to exit (state becomes idle)
194
+ await waitForState(session, "idle", 5000);
195
+ await Bun.sleep(100);
196
+
197
+ const exceptions = session.getExceptions();
198
+ expect(exceptions.length).toBeGreaterThanOrEqual(1);
199
+
200
+ const entry = exceptions[0];
201
+ expect(entry).toBeDefined();
202
+ expect(entry?.text).toContain("Uncaught");
203
+ expect(entry?.description).toContain("uncaught!");
204
+ } finally {
205
+ await session.stop();
206
+ }
207
+ });
208
+
209
+ test("exception entries have timestamp", async () => {
210
+ const session = new DebugSession("test-exception-timestamp");
211
+ const before = Date.now();
212
+ try {
213
+ await session.launch(["node", "tests/fixtures/exception-app.js"], { brk: true });
214
+ await waitForState(session, "paused");
215
+
216
+ await session.continue();
217
+ await waitForState(session, "idle", 5000);
218
+ await Bun.sleep(100);
219
+
220
+ const exceptions = session.getExceptions();
221
+ expect(exceptions.length).toBeGreaterThanOrEqual(1);
222
+
223
+ const entry = exceptions[0];
224
+ expect(entry).toBeDefined();
225
+ expect(entry!.timestamp).toBeGreaterThanOrEqual(before);
226
+ expect(entry!.timestamp).toBeLessThanOrEqual(Date.now());
227
+ } finally {
228
+ await session.stop();
229
+ }
230
+ });
231
+
232
+ test("exceptions --since returns only last N entries", async () => {
233
+ const session = new DebugSession("test-exception-since");
234
+ try {
235
+ await session.launch(["node", "tests/fixtures/exception-app.js"], { brk: true });
236
+ await waitForState(session, "paused");
237
+
238
+ await session.continue();
239
+ await waitForState(session, "idle", 5000);
240
+ await Bun.sleep(100);
241
+
242
+ const allExceptions = session.getExceptions();
243
+ expect(allExceptions.length).toBeGreaterThanOrEqual(1);
244
+
245
+ const lastOne = session.getExceptions({ since: 1 });
246
+ expect(lastOne.length).toBe(1);
247
+ expect(lastOne[0]?.text).toBe(allExceptions[allExceptions.length - 1]?.text);
248
+ } finally {
249
+ await session.stop();
250
+ }
251
+ });
252
+
253
+ test("console messages have timestamps", async () => {
254
+ const session = new DebugSession("test-console-timestamps");
255
+ const before = Date.now();
256
+ try {
257
+ await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
258
+ await waitForState(session, "paused");
259
+
260
+ await session.continue();
261
+ await waitForState(session, "paused", 5000);
262
+ await Bun.sleep(100);
263
+
264
+ const messages = session.getConsoleMessages();
265
+ expect(messages.length).toBeGreaterThan(0);
266
+
267
+ for (const msg of messages) {
268
+ expect(msg.timestamp).toBeGreaterThanOrEqual(before);
269
+ expect(msg.timestamp).toBeLessThanOrEqual(Date.now());
270
+ }
271
+ } finally {
272
+ await session.stop();
273
+ }
274
+ });
275
+ });
@@ -0,0 +1,247 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { DebugSession } from "../../src/daemon/session.ts";
3
+
4
+ /**
5
+ * Polls until the session reaches the expected state, or times out.
6
+ */
7
+ async function waitForState(
8
+ session: DebugSession,
9
+ state: "idle" | "running" | "paused",
10
+ timeoutMs = 5000,
11
+ ): Promise<void> {
12
+ const deadline = Date.now() + timeoutMs;
13
+ while (session.sessionState !== state && Date.now() < deadline) {
14
+ await Bun.sleep(50);
15
+ }
16
+ }
17
+
18
+ describe("Execution control", () => {
19
+ test("continue resumes and process finishes", async () => {
20
+ const session = new DebugSession("test-exec-continue");
21
+ try {
22
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
23
+ await waitForState(session, "paused");
24
+ expect(session.sessionState).toBe("paused");
25
+
26
+ // Continue — the script should run to completion
27
+ await session.continue();
28
+
29
+ // The process should have finished (idle) or be finishing
30
+ await waitForState(session, "idle", 5000);
31
+ expect(["idle", "running"]).toContain(session.sessionState);
32
+ } finally {
33
+ await session.stop();
34
+ }
35
+ });
36
+
37
+ test("continue resumes and hits next breakpoint", async () => {
38
+ const session = new DebugSession("test-exec-continue-bp");
39
+ try {
40
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
41
+ await waitForState(session, "paused");
42
+ expect(session.sessionState).toBe("paused");
43
+
44
+ // Set a breakpoint on line 12 (const d = a + b + c;), CDP 0-based: 11
45
+ const cdp = session.cdp;
46
+ expect(cdp).not.toBeNull();
47
+ await cdp!.send("Debugger.setBreakpointByUrl", {
48
+ lineNumber: 11,
49
+ urlRegex: "step-app\\.js",
50
+ });
51
+
52
+ // Continue — should hit the breakpoint
53
+ await session.continue();
54
+ expect(session.sessionState).toBe("paused");
55
+
56
+ const status = session.getStatus();
57
+ expect(status.state).toBe("paused");
58
+ } finally {
59
+ await session.stop();
60
+ }
61
+ });
62
+
63
+ test("step over advances one line", async () => {
64
+ const session = new DebugSession("test-exec-step-over");
65
+ try {
66
+ await session.launch(["node", "tests/fixtures/step-app.js"], {
67
+ brk: true,
68
+ });
69
+ await waitForState(session, "paused");
70
+
71
+ const statusBefore = session.getStatus();
72
+ const lineBefore = statusBefore.pauseInfo?.line;
73
+ expect(lineBefore).toBeDefined();
74
+
75
+ // Step over — should advance to the next line
76
+ await session.step("over");
77
+
78
+ expect(session.sessionState).toBe("paused");
79
+ const statusAfter = session.getStatus();
80
+ expect(statusAfter.pauseInfo).toBeDefined();
81
+ expect(statusAfter.pauseInfo?.line).toBeDefined();
82
+
83
+ // The line should have changed (advanced by at least one)
84
+ if (lineBefore !== undefined && statusAfter.pauseInfo?.line !== undefined) {
85
+ expect(statusAfter.pauseInfo.line).toBeGreaterThan(lineBefore);
86
+ }
87
+ } finally {
88
+ await session.stop();
89
+ }
90
+ });
91
+
92
+ test("step into enters a function", async () => {
93
+ const session = new DebugSession("test-exec-step-into");
94
+ try {
95
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
96
+ await waitForState(session, "paused");
97
+
98
+ // Step to the helper() call line: const c = helper(a);
99
+ // Initial pause is at the first executable statement.
100
+ // Step over until we reach the line with helper() call.
101
+ // step-app.js line 11 (1-based) / 10 (0-based): const c = helper(a);
102
+ let currentLine = session.getStatus().pauseInfo?.line ?? 0;
103
+ while (currentLine < 10 && session.sessionState === "paused") {
104
+ await session.step("over");
105
+ currentLine = session.getStatus().pauseInfo?.line ?? currentLine;
106
+ }
107
+ expect(currentLine).toBe(10); // 0-based line for const c = helper(a);
108
+
109
+ // Now step into the function call. V8 may pause at sub-expressions
110
+ // before entering the function, so keep stepping into until the line
111
+ // moves to inside the helper function (0-based lines 3-5).
112
+ await session.step("into");
113
+ expect(session.sessionState).toBe("paused");
114
+
115
+ let line = session.getStatus().pauseInfo?.line;
116
+ // If we're still on the same line, step into once more
117
+ if (line !== undefined && line >= 10) {
118
+ await session.step("into");
119
+ line = session.getStatus().pauseInfo?.line;
120
+ }
121
+
122
+ expect(line).toBeDefined();
123
+ // Should now be inside helper function body (0-based lines 3, 4, or 5)
124
+ if (line !== undefined) {
125
+ expect(line).toBeLessThan(10);
126
+ }
127
+ } finally {
128
+ await session.stop();
129
+ }
130
+ });
131
+
132
+ test("step out exits current function", async () => {
133
+ const session = new DebugSession("test-exec-step-out");
134
+ try {
135
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
136
+ await waitForState(session, "paused");
137
+
138
+ // Step to the helper() call and step into it
139
+ let currentLine = session.getStatus().pauseInfo?.line ?? 0;
140
+ while (currentLine < 10 && session.sessionState === "paused") {
141
+ await session.step("over");
142
+ currentLine = session.getStatus().pauseInfo?.line ?? currentLine;
143
+ }
144
+
145
+ // Step into the function
146
+ await session.step("into");
147
+ let line = session.getStatus().pauseInfo?.line;
148
+ if (line !== undefined && line >= 10) {
149
+ await session.step("into");
150
+ line = session.getStatus().pauseInfo?.line;
151
+ }
152
+
153
+ expect(session.sessionState).toBe("paused");
154
+ const lineInside = session.getStatus().pauseInfo?.line;
155
+ expect(lineInside).toBeDefined();
156
+ expect(lineInside).toBeLessThan(10); // inside helper
157
+
158
+ // Step out of the function
159
+ await session.step("out");
160
+
161
+ expect(session.sessionState).toBe("paused");
162
+ const statusOutside = session.getStatus();
163
+ expect(statusOutside.pauseInfo).toBeDefined();
164
+ // After stepping out, we should be back in the main scope
165
+ if (statusOutside.pauseInfo?.line !== undefined) {
166
+ expect(statusOutside.pauseInfo.line).toBeGreaterThanOrEqual(10);
167
+ }
168
+ } finally {
169
+ await session.stop();
170
+ }
171
+ });
172
+
173
+ test("pause interrupts running process", async () => {
174
+ const session = new DebugSession("test-exec-pause");
175
+ try {
176
+ await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
177
+ // Process should be running
178
+ expect(session.sessionState).toBe("running");
179
+
180
+ // Pause the running process
181
+ await session.pause();
182
+
183
+ expect(session.sessionState).toBe("paused");
184
+ const status = session.getStatus();
185
+ expect(status.state).toBe("paused");
186
+ } finally {
187
+ await session.stop();
188
+ }
189
+ });
190
+
191
+ test("continue throws when not paused", async () => {
192
+ const session = new DebugSession("test-exec-continue-err");
193
+ try {
194
+ await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
195
+ expect(session.sessionState).toBe("running");
196
+
197
+ await expect(session.continue()).rejects.toThrow("not paused");
198
+ } finally {
199
+ await session.stop();
200
+ }
201
+ });
202
+
203
+ test("step throws when not paused", async () => {
204
+ const session = new DebugSession("test-exec-step-err");
205
+ try {
206
+ await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
207
+ expect(session.sessionState).toBe("running");
208
+
209
+ await expect(session.step("over")).rejects.toThrow("not paused");
210
+ } finally {
211
+ await session.stop();
212
+ }
213
+ });
214
+
215
+ test("pause throws when not running", async () => {
216
+ const session = new DebugSession("test-exec-pause-err");
217
+ try {
218
+ await session.launch(["node", "-e", "setTimeout(() => {}, 30000)"], { brk: true });
219
+ await waitForState(session, "paused");
220
+
221
+ await expect(session.pause()).rejects.toThrow("not running");
222
+ } finally {
223
+ await session.stop();
224
+ }
225
+ });
226
+
227
+ test("run-to stops at the specified line", async () => {
228
+ const session = new DebugSession("test-exec-run-to");
229
+ try {
230
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
231
+ await waitForState(session, "paused");
232
+
233
+ // Run to line 12 (const d = a + b + c;), CDP 0-based: 11
234
+ await session.runTo("step-app.js", 12);
235
+
236
+ expect(session.sessionState).toBe("paused");
237
+ const status = session.getStatus();
238
+ expect(status.pauseInfo).toBeDefined();
239
+ // CDP line is 0-based, so line 12 = index 11
240
+ if (status.pauseInfo?.line !== undefined) {
241
+ expect(status.pauseInfo.line).toBe(11);
242
+ }
243
+ } finally {
244
+ await session.stop();
245
+ }
246
+ });
247
+ });