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,241 @@
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("break-toggle", () => {
19
+ test("toggle disables and re-enables a breakpoint", async () => {
20
+ const session = new DebugSession("test-break-toggle");
21
+ try {
22
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
23
+ await waitForState(session, "paused");
24
+
25
+ const bp = await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
26
+ expect(bp.ref).toBe("BP#1");
27
+
28
+ // Should have 1 active breakpoint
29
+ let list = session.listBreakpoints();
30
+ expect(list.length).toBe(1);
31
+ expect(list[0]?.disabled).toBeUndefined();
32
+
33
+ // Disable the breakpoint
34
+ const disableResult = await session.toggleBreakpoint("BP#1");
35
+ expect(disableResult.state).toBe("disabled");
36
+
37
+ // Should be listed as disabled
38
+ list = session.listBreakpoints();
39
+ expect(list.length).toBe(1);
40
+ expect(list[0]?.disabled).toBe(true);
41
+
42
+ // Re-enable the breakpoint
43
+ const enableResult = await session.toggleBreakpoint("BP#1");
44
+ expect(enableResult.state).toBe("enabled");
45
+
46
+ // Should be listed as enabled (no disabled flag)
47
+ list = session.listBreakpoints();
48
+ expect(list.length).toBe(1);
49
+ expect(list[0]?.disabled).toBeUndefined();
50
+ } finally {
51
+ await session.stop();
52
+ }
53
+ });
54
+
55
+ test("toggle all disables and re-enables all breakpoints", async () => {
56
+ const session = new DebugSession("test-break-toggle-all");
57
+ try {
58
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
59
+ await waitForState(session, "paused");
60
+
61
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
62
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 11);
63
+
64
+ let list = session.listBreakpoints();
65
+ expect(list.length).toBe(2);
66
+
67
+ // Disable all
68
+ const disableResult = await session.toggleBreakpoint("all");
69
+ expect(disableResult.state).toBe("disabled");
70
+
71
+ list = session.listBreakpoints();
72
+ expect(list.length).toBe(2);
73
+ for (const bp of list) {
74
+ expect(bp.disabled).toBe(true);
75
+ }
76
+
77
+ // Re-enable all
78
+ const enableResult = await session.toggleBreakpoint("all");
79
+ expect(enableResult.state).toBe("enabled");
80
+
81
+ list = session.listBreakpoints();
82
+ expect(list.length).toBe(2);
83
+ for (const bp of list) {
84
+ expect(bp.disabled).toBeUndefined();
85
+ }
86
+ } finally {
87
+ await session.stop();
88
+ }
89
+ });
90
+
91
+ test("toggle unknown ref throws error", async () => {
92
+ const session = new DebugSession("test-break-toggle-unknown");
93
+ try {
94
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
95
+ await waitForState(session, "paused");
96
+
97
+ await expect(session.toggleBreakpoint("BP#99")).rejects.toThrow("Unknown breakpoint ref");
98
+ } finally {
99
+ await session.stop();
100
+ }
101
+ });
102
+ });
103
+
104
+ describe("breakable", () => {
105
+ test("returns valid breakable locations", async () => {
106
+ const session = new DebugSession("test-breakable");
107
+ try {
108
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
109
+ await waitForState(session, "paused");
110
+
111
+ const locations = await session.getBreakableLocations("tests/fixtures/simple-app.js", 4, 8);
112
+
113
+ expect(Array.isArray(locations)).toBe(true);
114
+ expect(locations.length).toBeGreaterThan(0);
115
+
116
+ // All locations should have line and column
117
+ for (const loc of locations) {
118
+ expect(loc.line).toBeGreaterThanOrEqual(4);
119
+ expect(loc.line).toBeLessThanOrEqual(8);
120
+ expect(loc.column).toBeGreaterThan(0);
121
+ }
122
+ } finally {
123
+ await session.stop();
124
+ }
125
+ });
126
+
127
+ test("throws for unknown file", async () => {
128
+ const session = new DebugSession("test-breakable-unknown");
129
+ try {
130
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
131
+ await waitForState(session, "paused");
132
+
133
+ await expect(session.getBreakableLocations("nonexistent.js", 1, 5)).rejects.toThrow(
134
+ "No loaded script matches",
135
+ );
136
+ } finally {
137
+ await session.stop();
138
+ }
139
+ });
140
+
141
+ test("throws without CDP connection", async () => {
142
+ const session = new DebugSession("test-breakable-no-cdp");
143
+ await expect(session.getBreakableLocations("file.js", 1, 5)).rejects.toThrow(
144
+ "No active debug session",
145
+ );
146
+ });
147
+ });
148
+
149
+ describe("restart-frame", () => {
150
+ test("restarts the current frame", async () => {
151
+ const session = new DebugSession("test-restart-frame");
152
+ try {
153
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
154
+ await waitForState(session, "paused");
155
+
156
+ // Step to get inside helper function
157
+ let currentLine = session.getStatus().pauseInfo?.line ?? 0;
158
+ while (currentLine < 10 && session.sessionState === "paused") {
159
+ await session.step("over");
160
+ currentLine = session.getStatus().pauseInfo?.line ?? currentLine;
161
+ }
162
+
163
+ // Step into helper()
164
+ await session.step("into");
165
+ let line = session.getStatus().pauseInfo?.line;
166
+ if (line !== undefined && line >= 10) {
167
+ await session.step("into");
168
+ line = session.getStatus().pauseInfo?.line;
169
+ }
170
+
171
+ expect(session.sessionState).toBe("paused");
172
+ const lineInHelper = session.getStatus().pauseInfo?.line;
173
+ expect(lineInHelper).toBeDefined();
174
+
175
+ // Restart the frame
176
+ const result = await session.restartFrame();
177
+ expect(result.status).toBe("restarted");
178
+ expect(session.sessionState).toBe("paused");
179
+ } finally {
180
+ await session.stop();
181
+ }
182
+ });
183
+
184
+ test("throws when not paused", async () => {
185
+ const session = new DebugSession("test-restart-frame-not-paused");
186
+ try {
187
+ await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
188
+ expect(session.sessionState).toBe("running");
189
+
190
+ await expect(session.restartFrame()).rejects.toThrow("not paused");
191
+ } finally {
192
+ await session.stop();
193
+ }
194
+ });
195
+ });
196
+
197
+ describe("break --continue", () => {
198
+ test("setBreakpoint then continue works", async () => {
199
+ const session = new DebugSession("test-break-continue");
200
+ try {
201
+ await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
202
+ await waitForState(session, "paused");
203
+
204
+ // Set a breakpoint on line 12
205
+ const bp = await session.setBreakpoint("tests/fixtures/step-app.js", 12);
206
+ expect(bp.ref).toBe("BP#1");
207
+
208
+ // Continue to the breakpoint (simulating --continue flag behavior)
209
+ await session.continue();
210
+
211
+ expect(session.sessionState).toBe("paused");
212
+ const status = session.getStatus();
213
+ expect(status.pauseInfo).toBeDefined();
214
+ // Should be at or near line 12 (0-based line 11)
215
+ if (status.pauseInfo?.line !== undefined) {
216
+ expect(status.pauseInfo.line).toBe(11);
217
+ }
218
+ } finally {
219
+ await session.stop();
220
+ }
221
+ });
222
+ });
223
+
224
+ describe("break --pattern", () => {
225
+ test("set breakpoint with urlRegex", async () => {
226
+ const session = new DebugSession("test-break-pattern");
227
+ try {
228
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
229
+ await waitForState(session, "paused");
230
+
231
+ const result = await session.setBreakpoint("simple-app", 5, {
232
+ urlRegex: "simple-app\\.js",
233
+ });
234
+
235
+ expect(result.ref).toBe("BP#1");
236
+ expect(result.location.line).toBeGreaterThan(0);
237
+ } finally {
238
+ await session.stop();
239
+ }
240
+ });
241
+ });
@@ -0,0 +1,217 @@
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 = 2000,
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("Breakpoint integration", () => {
19
+ test("set breakpoint by file:line", async () => {
20
+ const session = new DebugSession("test-bp-set");
21
+ try {
22
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
23
+ await waitForState(session, "paused");
24
+
25
+ const result = await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
26
+ expect(result.ref).toBe("BP#1");
27
+ expect(result.location.line).toBeGreaterThan(0);
28
+ expect(result.location.url).toContain("simple-app.js");
29
+ } finally {
30
+ await session.stop();
31
+ }
32
+ });
33
+
34
+ test("list breakpoints returns set breakpoints", async () => {
35
+ const session = new DebugSession("test-bp-list");
36
+ try {
37
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
38
+ await waitForState(session, "paused");
39
+
40
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
41
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 11);
42
+
43
+ const list = session.listBreakpoints();
44
+ expect(list.length).toBe(2);
45
+ expect(list[0]?.ref).toBe("BP#1");
46
+ expect(list[1]?.ref).toBe("BP#2");
47
+ expect(list[0]?.type).toBe("BP");
48
+ expect(list[1]?.type).toBe("BP");
49
+ } finally {
50
+ await session.stop();
51
+ }
52
+ });
53
+
54
+ test("remove breakpoint", async () => {
55
+ const session = new DebugSession("test-bp-rm");
56
+ try {
57
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
58
+ await waitForState(session, "paused");
59
+
60
+ const result = await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
61
+
62
+ let list = session.listBreakpoints();
63
+ expect(list.length).toBe(1);
64
+
65
+ await session.removeBreakpoint(result.ref);
66
+
67
+ list = session.listBreakpoints();
68
+ expect(list.length).toBe(0);
69
+ } finally {
70
+ await session.stop();
71
+ }
72
+ });
73
+
74
+ test("remove all breakpoints", async () => {
75
+ const session = new DebugSession("test-bp-rm-all");
76
+ try {
77
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
78
+ await waitForState(session, "paused");
79
+
80
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
81
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 11);
82
+
83
+ let list = session.listBreakpoints();
84
+ expect(list.length).toBe(2);
85
+
86
+ await session.removeAllBreakpoints();
87
+
88
+ list = session.listBreakpoints();
89
+ expect(list.length).toBe(0);
90
+ } finally {
91
+ await session.stop();
92
+ }
93
+ });
94
+
95
+ test("set conditional breakpoint", async () => {
96
+ const session = new DebugSession("test-bp-cond");
97
+ try {
98
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
99
+ await waitForState(session, "paused");
100
+
101
+ const result = await session.setBreakpoint("tests/fixtures/simple-app.js", 5, {
102
+ condition: "name === 'World'",
103
+ });
104
+
105
+ expect(result.ref).toBe("BP#1");
106
+
107
+ const list = session.listBreakpoints();
108
+ expect(list.length).toBe(1);
109
+ expect(list[0]?.condition).toBe("name === 'World'");
110
+ } finally {
111
+ await session.stop();
112
+ }
113
+ });
114
+
115
+ test("set breakpoint with hit count", async () => {
116
+ const session = new DebugSession("test-bp-hit");
117
+ try {
118
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
119
+ await waitForState(session, "paused");
120
+
121
+ const result = await session.setBreakpoint("tests/fixtures/simple-app.js", 5, {
122
+ hitCount: 3,
123
+ });
124
+
125
+ expect(result.ref).toBe("BP#1");
126
+
127
+ const list = session.listBreakpoints();
128
+ expect(list.length).toBe(1);
129
+ expect(list[0]?.hitCount).toBe(3);
130
+ } finally {
131
+ await session.stop();
132
+ }
133
+ });
134
+
135
+ test("set exception pause mode", async () => {
136
+ const session = new DebugSession("test-bp-catch");
137
+ try {
138
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
139
+ await waitForState(session, "paused");
140
+
141
+ // Should not throw for any valid mode
142
+ await session.setExceptionPause("all");
143
+ await session.setExceptionPause("uncaught");
144
+ await session.setExceptionPause("caught");
145
+ await session.setExceptionPause("none");
146
+ } finally {
147
+ await session.stop();
148
+ }
149
+ });
150
+
151
+ test("set logpoint", async () => {
152
+ const session = new DebugSession("test-lp-set");
153
+ try {
154
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
155
+ await waitForState(session, "paused");
156
+
157
+ const result = await session.setLogpoint(
158
+ "tests/fixtures/simple-app.js",
159
+ 5,
160
+ '"greet called with:", name',
161
+ );
162
+
163
+ expect(result.ref).toBe("LP#1");
164
+ expect(result.location.url).toContain("simple-app.js");
165
+
166
+ const list = session.listBreakpoints();
167
+ expect(list.length).toBe(1);
168
+ expect(list[0]?.type).toBe("LP");
169
+ expect(list[0]?.template).toBe('"greet called with:", name');
170
+ } finally {
171
+ await session.stop();
172
+ }
173
+ });
174
+
175
+ test("mixed breakpoints and logpoints in list", async () => {
176
+ const session = new DebugSession("test-bp-lp-mix");
177
+ try {
178
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
179
+ await waitForState(session, "paused");
180
+
181
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 5);
182
+ await session.setLogpoint("tests/fixtures/simple-app.js", 11, '"add called"');
183
+ await session.setBreakpoint("tests/fixtures/simple-app.js", 38);
184
+
185
+ const list = session.listBreakpoints();
186
+ expect(list.length).toBe(3);
187
+
188
+ const types = list.map((bp) => bp.type);
189
+ expect(types).toContain("BP");
190
+ expect(types).toContain("LP");
191
+ } finally {
192
+ await session.stop();
193
+ }
194
+ });
195
+
196
+ test("remove unknown ref throws error", async () => {
197
+ const session = new DebugSession("test-bp-rm-unknown");
198
+ try {
199
+ await session.launch(["node", "tests/fixtures/simple-app.js"], { brk: true });
200
+ await waitForState(session, "paused");
201
+
202
+ await expect(session.removeBreakpoint("BP#99")).rejects.toThrow("Unknown ref");
203
+ } finally {
204
+ await session.stop();
205
+ }
206
+ });
207
+
208
+ test("setBreakpoint without CDP throws error", async () => {
209
+ const session = new DebugSession("test-bp-no-cdp");
210
+ await expect(session.setBreakpoint("file.js", 1)).rejects.toThrow("No active debug session");
211
+ });
212
+
213
+ test("setExceptionPause without CDP throws error", async () => {
214
+ const session = new DebugSession("test-catch-no-cdp");
215
+ await expect(session.setExceptionPause("all")).rejects.toThrow("No active debug session");
216
+ });
217
+ });