agent-dbg 0.1.0 → 0.1.2

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 (71) hide show
  1. package/.claude/settings.local.json +7 -5
  2. package/.claude/skills/agent-dbg/SKILL.md +116 -0
  3. package/.claude/skills/agent-dbg/references/commands.md +173 -0
  4. package/.vscode/launch.json +19 -0
  5. package/CLAUDE.md +2 -2
  6. package/PROGRESS.md +91 -91
  7. package/README.md +45 -17
  8. package/{ndbg-spec.md → SPEC.md} +152 -152
  9. package/dist/main.js +12500 -0
  10. package/package.json +3 -3
  11. package/src/cdp/client.ts +18 -4
  12. package/src/cdp/logger.ts +69 -0
  13. package/src/cli/parser.ts +54 -43
  14. package/src/commands/attach.ts +2 -2
  15. package/src/commands/blackbox-ls.ts +1 -1
  16. package/src/commands/blackbox-rm.ts +3 -3
  17. package/src/commands/blackbox.ts +2 -2
  18. package/src/commands/break-ls.ts +3 -2
  19. package/src/commands/break-rm.ts +2 -2
  20. package/src/commands/break-toggle.ts +2 -2
  21. package/src/commands/break.ts +46 -17
  22. package/src/commands/breakable.ts +2 -2
  23. package/src/commands/catch.ts +2 -2
  24. package/src/commands/console.ts +1 -1
  25. package/src/commands/continue.ts +5 -18
  26. package/src/commands/eval.ts +2 -2
  27. package/src/commands/exceptions.ts +1 -1
  28. package/src/commands/hotpatch.ts +2 -2
  29. package/src/commands/launch.ts +7 -11
  30. package/src/commands/logpoint.ts +7 -6
  31. package/src/commands/logs.ts +116 -0
  32. package/src/commands/pause.ts +5 -18
  33. package/src/commands/print-state.ts +85 -0
  34. package/src/commands/props.ts +2 -2
  35. package/src/commands/restart-frame.ts +1 -1
  36. package/src/commands/restart.ts +42 -0
  37. package/src/commands/run-to.ts +7 -20
  38. package/src/commands/scripts.ts +1 -1
  39. package/src/commands/search.ts +4 -3
  40. package/src/commands/set-return.ts +2 -2
  41. package/src/commands/set.ts +3 -3
  42. package/src/commands/source.ts +3 -2
  43. package/src/commands/sourcemap.ts +1 -1
  44. package/src/commands/stack.ts +1 -1
  45. package/src/commands/state.ts +3 -73
  46. package/src/commands/status.ts +3 -2
  47. package/src/commands/step.ts +5 -18
  48. package/src/commands/vars.ts +3 -1
  49. package/src/daemon/entry.ts +16 -6
  50. package/src/daemon/paths.ts +6 -2
  51. package/src/daemon/server.ts +1 -1
  52. package/src/daemon/session-breakpoints.ts +7 -2
  53. package/src/daemon/session-inspection.ts +6 -10
  54. package/src/daemon/session-state.ts +6 -5
  55. package/src/daemon/session.ts +23 -3
  56. package/src/formatter/logs.ts +110 -0
  57. package/src/formatter/path.ts +27 -0
  58. package/src/formatter/stack.ts +5 -2
  59. package/src/formatter/variables.ts +48 -1
  60. package/src/main.ts +2 -0
  61. package/src/protocol/messages.ts +3 -0
  62. package/tests/fixtures/async-app.js +1 -1
  63. package/tests/fixtures/error-app.js +1 -1
  64. package/tests/fixtures/simple-app.js +1 -1
  65. package/tests/fixtures/ts-app/src/app.ts +4 -0
  66. package/tests/integration/state.test.ts +7 -7
  67. package/tests/unit/daemon.test.ts +1 -1
  68. package/tests/unit/formatter.test.ts +8 -4
  69. package/.bin/ndbg +0 -0
  70. package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +0 -116
  71. package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +0 -173
@@ -1,19 +1,14 @@
1
1
  import { registerCommand } from "../cli/registry.ts";
2
2
  import { DaemonClient } from "../daemon/client.ts";
3
3
  import type { StateSnapshot } from "../daemon/session.ts";
4
- import type { SourceLine } from "../formatter/source.ts";
5
- import { formatSource } from "../formatter/source.ts";
6
- import type { StackFrame } from "../formatter/stack.ts";
7
- import { formatStack } from "../formatter/stack.ts";
8
- import type { Variable } from "../formatter/variables.ts";
9
- import { formatVariables } from "../formatter/variables.ts";
4
+ import { printState } from "./print-state.ts";
10
5
 
11
6
  registerCommand("state", async (args) => {
12
7
  const session = args.global.session;
13
8
 
14
9
  if (!DaemonClient.isRunning(session)) {
15
10
  console.error(`No active session "${session}"`);
16
- console.error(" -> Try: ndbg launch --brk node app.js");
11
+ console.error(" -> Try: agent-dbg launch --brk node app.js");
17
12
  return 1;
18
13
  }
19
14
 
@@ -53,72 +48,7 @@ registerCommand("state", async (args) => {
53
48
  return 0;
54
49
  }
55
50
 
56
- // Non-paused states
57
- if (data.status !== "paused") {
58
- const icon = data.status === "running" ? "\u25B6" : "\u25CB";
59
- console.log(`${icon} ${data.status === "running" ? "Running" : "Idle"}`);
60
- return 0;
61
- }
62
-
63
- // Paused state — header
64
- const loc = data.location
65
- ? `${data.location.url}:${data.location.line}${data.location.column !== undefined ? `:${data.location.column}` : ""}`
66
- : "unknown";
67
- const reason = data.reason ?? "unknown";
68
- console.log(`\u23F8 Paused at ${loc} (${reason})`);
69
-
70
- const showAll = !stateArgs.vars && !stateArgs.stack && !stateArgs.breakpoints && !stateArgs.code;
71
-
72
- // Source section
73
- if ((showAll || stateArgs.code) && data.source?.lines) {
74
- console.log("");
75
- console.log("Source:");
76
- const sourceLines: SourceLine[] = data.source.lines.map((l) => ({
77
- lineNumber: l.line,
78
- content: l.text,
79
- isCurrent: l.current,
80
- currentColumn: l.current ? data.location?.column : undefined,
81
- }));
82
- console.log(formatSource(sourceLines));
83
- }
84
-
85
- // Locals section
86
- if ((showAll || stateArgs.vars) && data.locals) {
87
- console.log("");
88
- console.log("Locals:");
89
- const vars: Variable[] = data.locals.map((v) => ({
90
- ref: v.ref,
91
- name: v.name,
92
- value: v.value,
93
- }));
94
- const formatted = formatVariables(vars);
95
- if (formatted) {
96
- console.log(formatted);
97
- } else {
98
- console.log(" (no locals)");
99
- }
100
- }
101
-
102
- // Stack section
103
- if ((showAll || stateArgs.stack) && data.stack) {
104
- console.log("");
105
- console.log("Stack:");
106
- const frames: StackFrame[] = data.stack.map((f) => ({
107
- ref: f.ref,
108
- functionName: f.functionName,
109
- file: f.file,
110
- line: f.line,
111
- column: f.column,
112
- isAsync: f.isAsync,
113
- }));
114
- console.log(formatStack(frames));
115
- }
116
-
117
- // Breakpoints section
118
- if ((showAll || stateArgs.breakpoints) && data.breakpointCount !== undefined) {
119
- console.log("");
120
- console.log(`Breakpoints: ${data.breakpointCount} active`);
121
- }
51
+ printState(data);
122
52
 
123
53
  return 0;
124
54
  });
@@ -1,12 +1,13 @@
1
1
  import { registerCommand } from "../cli/registry.ts";
2
2
  import { DaemonClient } from "../daemon/client.ts";
3
+ import { shortPath } from "../formatter/path.ts";
3
4
 
4
5
  registerCommand("status", async (args) => {
5
6
  const session = args.global.session;
6
7
 
7
8
  if (!DaemonClient.isRunning(session)) {
8
9
  console.error(`No active session "${session}"`);
9
- console.error(" -> Try: ndbg launch --brk node app.js");
10
+ console.error(" -> Try: agent-dbg launch --brk node app.js");
10
11
  return 1;
11
12
  }
12
13
 
@@ -47,7 +48,7 @@ registerCommand("status", async (args) => {
47
48
 
48
49
  if (data.pauseInfo) {
49
50
  const loc = data.pauseInfo.url
50
- ? `${data.pauseInfo.url}:${data.pauseInfo.line}${data.pauseInfo.column !== undefined ? `:${data.pauseInfo.column}` : ""}`
51
+ ? `${shortPath(data.pauseInfo.url)}:${data.pauseInfo.line}${data.pauseInfo.column !== undefined ? `:${data.pauseInfo.column}` : ""}`
51
52
  : "unknown";
52
53
  console.log(` Paused: ${data.pauseInfo.reason} at ${loc}`);
53
54
  }
@@ -1,13 +1,14 @@
1
1
  import { registerCommand } from "../cli/registry.ts";
2
2
  import { DaemonClient } from "../daemon/client.ts";
3
- import type { SessionStatus } from "../daemon/session.ts";
3
+ import type { StateSnapshot } from "../daemon/session.ts";
4
+ import { printState } from "./print-state.ts";
4
5
 
5
6
  registerCommand("step", async (args) => {
6
7
  const session = args.global.session;
7
8
 
8
9
  if (!DaemonClient.isRunning(session)) {
9
10
  console.error(`No active session "${session}"`);
10
- console.error(" -> Try: ndbg launch --brk node app.js");
11
+ console.error(" -> Try: agent-dbg launch --brk node app.js");
11
12
  return 1;
12
13
  }
13
14
 
@@ -24,27 +25,13 @@ registerCommand("step", async (args) => {
24
25
  return 1;
25
26
  }
26
27
 
27
- const data = response.data as SessionStatus;
28
+ const data = response.data as StateSnapshot;
28
29
 
29
30
  if (args.global.json) {
30
31
  console.log(JSON.stringify(data, null, 2));
31
32
  } else {
32
- printStatus(data);
33
+ printState(data);
33
34
  }
34
35
 
35
36
  return 0;
36
37
  });
37
-
38
- function printStatus(data: SessionStatus): void {
39
- if (data.state === "paused" && data.pauseInfo) {
40
- const col = data.pauseInfo.column !== undefined ? `:${data.pauseInfo.column + 1}` : "";
41
- const loc = data.pauseInfo.url
42
- ? `${data.pauseInfo.url}:${(data.pauseInfo.line ?? 0) + 1}${col}`
43
- : "unknown";
44
- console.log(`Paused at ${loc} (${data.pauseInfo.reason})`);
45
- } else if (data.state === "running") {
46
- console.log("Running");
47
- } else {
48
- console.log(`${data.state}`);
49
- }
50
- }
@@ -8,7 +8,7 @@ registerCommand("vars", async (args) => {
8
8
 
9
9
  if (!DaemonClient.isRunning(session)) {
10
10
  console.error(`No active session "${session}"`);
11
- console.error(" -> Try: ndbg launch --brk node app.js");
11
+ console.error(" -> Try: agent-dbg launch --brk node app.js");
12
12
  return 1;
13
13
  }
14
14
 
@@ -45,6 +45,7 @@ registerCommand("vars", async (args) => {
45
45
  name: string;
46
46
  type: string;
47
47
  value: string;
48
+ scope: string;
48
49
  }>;
49
50
 
50
51
  if (args.global.json) {
@@ -61,6 +62,7 @@ registerCommand("vars", async (args) => {
61
62
  ref: v.ref,
62
63
  name: v.name,
63
64
  value: v.value,
65
+ scope: v.scope,
64
66
  }));
65
67
  const formatted = formatVariables(vars);
66
68
  if (formatted) {
@@ -6,7 +6,7 @@ import { DebugSession } from "./session.ts";
6
6
  const daemonIdx = process.argv.indexOf("--daemon");
7
7
  const session = daemonIdx !== -1 ? process.argv[daemonIdx + 1] : process.argv[2];
8
8
  if (!session) {
9
- console.error("Usage: ndbg --daemon <session> [--timeout <seconds>]");
9
+ console.error("Usage: agent-dbg --daemon <session> [--timeout <seconds>]");
10
10
  process.exit(1);
11
11
  }
12
12
 
@@ -52,32 +52,37 @@ server.onRequest(async (req: DaemonRequest): Promise<DaemonResponse> => {
52
52
 
53
53
  case "continue": {
54
54
  await debugSession.continue();
55
- return { ok: true, data: debugSession.getStatus() };
55
+ const stateAfter = await debugSession.buildState();
56
+ return { ok: true, data: stateAfter };
56
57
  }
57
58
 
58
59
  case "step": {
59
60
  const { mode = "over" } = req.args;
60
61
  await debugSession.step(mode);
61
- return { ok: true, data: debugSession.getStatus() };
62
+ const stateAfter = await debugSession.buildState();
63
+ return { ok: true, data: stateAfter };
62
64
  }
63
65
 
64
66
  case "pause": {
65
67
  await debugSession.pause();
66
- return { ok: true, data: debugSession.getStatus() };
68
+ const stateAfter = await debugSession.buildState();
69
+ return { ok: true, data: stateAfter };
67
70
  }
68
71
 
69
72
  case "run-to": {
70
73
  const { file, line } = req.args;
71
74
  await debugSession.runTo(file, line);
72
- return { ok: true, data: debugSession.getStatus() };
75
+ const stateAfter = await debugSession.buildState();
76
+ return { ok: true, data: stateAfter };
73
77
  }
74
78
 
75
79
  case "break": {
76
- const { file, line, condition, hitCount, urlRegex } = req.args;
80
+ const { file, line, condition, hitCount, urlRegex, column } = req.args;
77
81
  const bpResult = await debugSession.setBreakpoint(file, line, {
78
82
  condition,
79
83
  hitCount,
80
84
  urlRegex,
85
+ column,
81
86
  });
82
87
  return { ok: true, data: bpResult };
83
88
  }
@@ -229,6 +234,11 @@ server.onRequest(async (req: DaemonRequest): Promise<DaemonResponse> => {
229
234
  return { ok: true, data: "disabled" };
230
235
  }
231
236
 
237
+ case "restart": {
238
+ const result = await debugSession.restart();
239
+ return { ok: true, data: result };
240
+ }
241
+
232
242
  case "stop":
233
243
  await debugSession.stop();
234
244
  setTimeout(() => {
@@ -4,10 +4,10 @@ import { join } from "node:path";
4
4
  export function getSocketDir(): string {
5
5
  const xdgRuntime = process.env.XDG_RUNTIME_DIR;
6
6
  if (xdgRuntime) {
7
- return join(xdgRuntime, "ndbg");
7
+ return join(xdgRuntime, "agent-dbg");
8
8
  }
9
9
  const tmpdir = process.env.TMPDIR || "/tmp";
10
- return join(tmpdir, `ndbg-${process.getuid?.() ?? 0}`);
10
+ return join(tmpdir, `agent-dbg-${process.getuid?.() ?? 0}`);
11
11
  }
12
12
 
13
13
  export function getSocketPath(session: string): string {
@@ -18,6 +18,10 @@ export function getLockPath(session: string): string {
18
18
  return join(getSocketDir(), `${session}.lock`);
19
19
  }
20
20
 
21
+ export function getLogPath(session: string): string {
22
+ return join(getSocketDir(), `${session}.cdp.log`);
23
+ }
24
+
21
25
  export function ensureSocketDir(): void {
22
26
  const dir = getSocketDir();
23
27
  if (!existsSync(dir)) {
@@ -106,7 +106,7 @@ export class DaemonServer {
106
106
  ? {
107
107
  ok: false,
108
108
  error: `Unknown command: ${cmd}`,
109
- suggestion: "-> Try: ndbg --help",
109
+ suggestion: "-> Try: agent-dbg --help",
110
110
  }
111
111
  : {
112
112
  ok: false,
@@ -5,7 +5,7 @@ export async function setBreakpoint(
5
5
  session: DebugSession,
6
6
  file: string,
7
7
  line: number,
8
- options?: { condition?: string; hitCount?: number; urlRegex?: string },
8
+ options?: { condition?: string; hitCount?: number; urlRegex?: string; column?: number },
9
9
  ): Promise<{ ref: string; location: { url: string; line: number; column?: number } }> {
10
10
  if (!session.cdp) {
11
11
  throw new Error("No active debug session");
@@ -17,14 +17,16 @@ export async function setBreakpoint(
17
17
  let originalFile: string | null = null;
18
18
  let originalLine: number | null = null;
19
19
  let actualLine = line;
20
+ let actualColumn: number | undefined = options?.column !== undefined ? options.column - 1 : undefined; // user column is 1-based
20
21
  let actualFile = file;
21
22
 
22
23
  if (!options?.urlRegex) {
23
- const generated = session.sourceMapResolver.toGenerated(file, line, 0);
24
+ const generated = session.sourceMapResolver.toGenerated(file, line, actualColumn ?? 0);
24
25
  if (generated) {
25
26
  originalFile = file;
26
27
  originalLine = line;
27
28
  actualLine = generated.line;
29
+ actualColumn = generated.column;
28
30
  // Find the URL of the generated script
29
31
  const scriptInfo = session.scripts.get(generated.scriptId);
30
32
  if (scriptInfo) {
@@ -36,6 +38,9 @@ export async function setBreakpoint(
36
38
  const params: Protocol.Debugger.SetBreakpointByUrlRequest = {
37
39
  lineNumber: actualLine - 1, // CDP uses 0-based lines
38
40
  };
41
+ if (actualColumn !== undefined) {
42
+ params.columnNumber = actualColumn;
43
+ }
39
44
 
40
45
  let url: string | null = null;
41
46
  if (options?.urlRegex) {
@@ -54,7 +54,7 @@ export async function evalExpression(
54
54
  for (const ref of refMatches) {
55
55
  const remoteId = session.refs.resolveId(ref);
56
56
  if (remoteId) {
57
- const argName = `__ndbg_ref_${ref.slice(1)}`;
57
+ const argName = `__adbg_ref_${ref.slice(1)}`;
58
58
  resolvedExpression = resolvedExpression.replace(ref, argName);
59
59
  refEntries.push({
60
60
  ref,
@@ -139,7 +139,7 @@ export async function evalExpression(
139
139
  export async function getVars(
140
140
  session: DebugSession,
141
141
  options: { frame?: string; names?: string[]; allScopes?: boolean } = {},
142
- ): Promise<Array<{ ref: string; name: string; type: string; value: string }>> {
142
+ ): Promise<Array<{ ref: string; name: string; type: string; value: string; scope: string }>> {
143
143
  if (!session.cdp) {
144
144
  throw new Error("No active debug session");
145
145
  }
@@ -174,19 +174,14 @@ export async function getVars(
174
174
  name: string;
175
175
  type: string;
176
176
  value: string;
177
+ scope: string;
177
178
  }> = [];
178
179
 
179
180
  for (const scope of scopeChain) {
180
181
  const scopeType = scope.type;
181
182
 
182
- // Include local, module, block, and script scopes by default.
183
- // Include closure scope only with allScopes. Always skip global.
184
- const includeScope =
185
- scopeType === "local" ||
186
- scopeType === "module" ||
187
- scopeType === "block" ||
188
- scopeType === "script" ||
189
- (options.allScopes && scopeType === "closure");
183
+ // Show all scopes except "global" (too noisy thousands of entries)
184
+ const includeScope = scopeType !== "global";
190
185
 
191
186
  if (includeScope) {
192
187
  const scopeObj = scope.object;
@@ -223,6 +218,7 @@ export async function getVars(
223
218
  name: propName,
224
219
  type: propValue.type,
225
220
  value: formatValue(propValue),
221
+ scope: scopeType,
226
222
  });
227
223
  }
228
224
  }
@@ -198,13 +198,13 @@ export async function buildState(
198
198
  try {
199
199
  const scopeChain = targetFrame.scopeChain;
200
200
  if (scopeChain) {
201
- const locals: Array<{ ref: string; name: string; value: string }> = [];
201
+ const vars: Array<{ ref: string; name: string; value: string; scope: string }> = [];
202
202
 
203
203
  for (const scope of scopeChain) {
204
204
  const scopeType = scope.type;
205
205
 
206
- // By default only show "local" scope; with --all-scopes include "closure" too
207
- if (scopeType === "local" || (options.allScopes && scopeType === "closure")) {
206
+ // Show all scopes except "global" (too noisy thousands of entries)
207
+ if (scopeType !== "global") {
208
208
  const scopeObj = scope.object;
209
209
  const objectId = scopeObj.objectId;
210
210
  if (!objectId) continue;
@@ -229,10 +229,11 @@ export async function buildState(
229
229
  const remoteId = propValue.objectId ?? `primitive:${propName}`;
230
230
  const ref = session.refs.addVar(remoteId as string, propName);
231
231
 
232
- locals.push({
232
+ vars.push({
233
233
  ref,
234
234
  name: propName,
235
235
  value: formatValue(propValue),
236
+ scope: scopeType,
236
237
  });
237
238
  }
238
239
  }
@@ -241,7 +242,7 @@ export async function buildState(
241
242
  if (scopeType === "global") continue;
242
243
  }
243
244
 
244
- snapshot.locals = locals;
245
+ snapshot.vars = vars;
245
246
  }
246
247
  } catch {
247
248
  // Variables not available
@@ -1,10 +1,12 @@
1
1
  import type { Subprocess } from "bun";
2
2
  import type Protocol from "devtools-protocol/types/protocol.js";
3
3
  import { CdpClient } from "../cdp/client.ts";
4
+ import { CdpLogger } from "../cdp/logger.ts";
4
5
  import type { RemoteObject } from "../formatter/values.ts";
5
6
  import { formatValue } from "../formatter/values.ts";
6
7
  import { RefTable } from "../refs/ref-table.ts";
7
8
  import { SourceMapResolver } from "../sourcemap/resolver.ts";
9
+ import { ensureSocketDir, getLogPath } from "./paths.ts";
8
10
  import {
9
11
  addBlackbox as addBlackboxImpl,
10
12
  listBlackbox as listBlackboxImpl,
@@ -73,7 +75,7 @@ export interface StateSnapshot {
73
75
  reason?: string;
74
76
  location?: { url: string; line: number; column?: number };
75
77
  source?: { lines: Array<{ line: number; text: string; current?: boolean }> };
76
- locals?: Array<{ ref: string; name: string; value: string }>;
78
+ vars?: Array<{ ref: string; name: string; value: string; scope: string }>;
77
79
  stack?: Array<{
78
80
  ref: string;
79
81
  functionName: string;
@@ -152,9 +154,14 @@ export class DebugSession {
152
154
  blackboxPatterns: string[] = [];
153
155
  disabledBreakpoints: Map<string, { breakpointId: string; meta: Record<string, unknown> }> =
154
156
  new Map();
157
+ launchCommand: string[] | null = null;
158
+ launchOptions: { brk?: boolean; port?: number } | null = null;
159
+ cdpLogger: CdpLogger;
155
160
 
156
161
  constructor(session: string) {
157
162
  this.session = session;
163
+ ensureSocketDir();
164
+ this.cdpLogger = new CdpLogger(getLogPath(session));
158
165
  }
159
166
 
160
167
  // ── Session lifecycle ─────────────────────────────────────────────
@@ -171,6 +178,9 @@ export class DebugSession {
171
178
  throw new Error("Command array must not be empty");
172
179
  }
173
180
 
181
+ this.launchCommand = command;
182
+ this.launchOptions = options;
183
+
174
184
  const brk = options.brk ?? true;
175
185
  const port = options.port ?? 0;
176
186
  const inspectFlag = brk ? `--inspect-brk=${port}` : `--inspect=${port}`;
@@ -311,6 +321,16 @@ export class DebugSession {
311
321
  this.sourceMapResolver.clear();
312
322
  }
313
323
 
324
+ async restart(): Promise<LaunchResult> {
325
+ if (!this.launchCommand) {
326
+ throw new Error("No previous launch to restart. Use 'launch' first.");
327
+ }
328
+ const command = this.launchCommand;
329
+ const options = this.launchOptions ?? {};
330
+ await this.stop();
331
+ return this.launch(command, options);
332
+ }
333
+
314
334
  get sessionState(): "idle" | "running" | "paused" {
315
335
  return this.state;
316
336
  }
@@ -653,7 +673,7 @@ export class DebugSession {
653
673
 
654
674
  buildBreakpointCondition(condition?: string, hitCount?: number): string | undefined {
655
675
  if (hitCount && hitCount > 0) {
656
- const countVar = `__ndbg_bp_count_${Date.now()}`;
676
+ const countVar = `__adbg_bp_count_${Date.now()}`;
657
677
  const hitExpr = `(typeof ${countVar} === "undefined" ? (${countVar} = 1) : ++${countVar}) >= ${hitCount}`;
658
678
  if (condition) {
659
679
  return `(${hitExpr}) && (${condition})`;
@@ -721,7 +741,7 @@ export class DebugSession {
721
741
  }
722
742
 
723
743
  private async connectCdp(wsUrl: string): Promise<void> {
724
- const cdp = await CdpClient.connect(wsUrl);
744
+ const cdp = await CdpClient.connect(wsUrl, this.cdpLogger);
725
745
  this.cdp = cdp;
726
746
 
727
747
  // Set up event handlers before enabling domains so we don't miss any events
@@ -0,0 +1,110 @@
1
+ import type { CdpLogEntry } from "../cdp/logger.ts";
2
+
3
+ function formatTime(ts: number): string {
4
+ const d = new Date(ts);
5
+ const h = String(d.getHours()).padStart(2, "0");
6
+ const m = String(d.getMinutes()).padStart(2, "0");
7
+ const s = String(d.getSeconds()).padStart(2, "0");
8
+ const ms = String(d.getMilliseconds()).padStart(3, "0");
9
+ return `${h}:${m}:${s}.${ms}`;
10
+ }
11
+
12
+ function truncate(s: string, max: number): string {
13
+ return s.length > max ? `${s.slice(0, max - 3)}...` : s;
14
+ }
15
+
16
+ type Summarizer = (entry: CdpLogEntry) => string;
17
+
18
+ const eventSummarizers: Record<string, Summarizer> = {
19
+ "Debugger.scriptParsed": (e) => {
20
+ const p = e.params ?? {};
21
+ const url = (p.url as string) || "(anonymous)";
22
+ const lines = p.endLine != null ? Number(p.endLine) + 1 : "?";
23
+ const hasMap = p.sourceMapURL ? "yes" : "no";
24
+ return `scriptId=${p.scriptId} url=${url} lines=${lines} sourceMap=${hasMap}`;
25
+ },
26
+ "Debugger.paused": (e) => {
27
+ const p = e.params ?? {};
28
+ const reason = (p.reason as string) ?? "unknown";
29
+ const frames = p.callFrames as Array<Record<string, unknown>> | undefined;
30
+ const top = frames?.[0];
31
+ let loc = "";
32
+ if (top) {
33
+ const location = top.location as Record<string, unknown> | undefined;
34
+ const url = (top.url as string) || `script:${location?.scriptId}`;
35
+ loc = ` location=${url}:${location?.lineNumber}`;
36
+ }
37
+ const count = frames?.length ?? 0;
38
+ return `reason=${reason}${loc} callFrames=${count}`;
39
+ },
40
+ "Debugger.resumed": () => "",
41
+ "Runtime.consoleAPICalled": (e) => {
42
+ const p = e.params ?? {};
43
+ const type = (p.type as string) ?? "log";
44
+ const args = p.args as Array<unknown> | undefined;
45
+ return `type=${type} args=${args?.length ?? 0}`;
46
+ },
47
+ "Runtime.exceptionThrown": (e) => {
48
+ const p = e.params ?? {};
49
+ const detail = p.exceptionDetails as Record<string, unknown> | undefined;
50
+ const text = (detail?.text as string) ?? "";
51
+ return truncate(text, 80);
52
+ },
53
+ "Runtime.executionContextCreated": (e) => {
54
+ const p = e.params ?? {};
55
+ const ctx = p.context as Record<string, unknown> | undefined;
56
+ return `contextId=${ctx?.id}`;
57
+ },
58
+ "Runtime.executionContextDestroyed": (e) => {
59
+ const p = e.params ?? {};
60
+ return `executionContextId=${p.executionContextId}`;
61
+ },
62
+ };
63
+
64
+ const responseSummarizers: Record<string, Summarizer> = {
65
+ "Debugger.setBreakpointByUrl": (e) => {
66
+ const r = (e.result ?? {}) as Record<string, unknown>;
67
+ return `breakpointId=${r.breakpointId}`;
68
+ },
69
+ "Debugger.setBreakpoint": (e) => {
70
+ const r = (e.result ?? {}) as Record<string, unknown>;
71
+ return `breakpointId=${r.breakpointId}`;
72
+ },
73
+ };
74
+
75
+ function summarizeParams(entry: CdpLogEntry): string {
76
+ const p = entry.params;
77
+ if (!p || Object.keys(p).length === 0) return "";
78
+ return truncate(JSON.stringify(p), 120);
79
+ }
80
+
81
+ function summarizeResult(entry: CdpLogEntry): string {
82
+ const summarizer = responseSummarizers[entry.method];
83
+ if (summarizer) return summarizer(entry);
84
+ // Don't dump large results by default
85
+ return "";
86
+ }
87
+
88
+ export function formatLogEntry(entry: CdpLogEntry): string {
89
+ const time = `[${formatTime(entry.ts)}]`;
90
+
91
+ if (entry.dir === "send") {
92
+ const params = summarizeParams(entry);
93
+ return `${time} -> ${entry.method}${params ? ` ${params}` : ""}`;
94
+ }
95
+
96
+ if (entry.dir === "recv") {
97
+ const idStr = entry.id != null ? ` #${entry.id}` : "";
98
+ const msStr = entry.ms != null ? ` (${entry.ms}ms)` : "";
99
+ if (entry.error) {
100
+ return `${time} <- ${entry.method}${idStr}${msStr} ERROR: ${entry.error.message}`;
101
+ }
102
+ const summary = summarizeResult(entry);
103
+ return `${time} <- ${entry.method}${idStr}${msStr}${summary ? ` ${summary}` : ""}`;
104
+ }
105
+
106
+ // event
107
+ const summarizer = eventSummarizers[entry.method];
108
+ const summary = summarizer ? summarizer(entry) : summarizeParams(entry);
109
+ return `${time} <- ${entry.method}${summary ? ` ${summary}` : ""}`;
110
+ }
@@ -0,0 +1,27 @@
1
+ import { relative } from "node:path";
2
+
3
+ const cwd = process.cwd();
4
+
5
+ /** Shorten a file path for display: strip file:// prefix, make relative to cwd. */
6
+ export function shortPath(path: string): string {
7
+ // Strip file:// protocol
8
+ if (path.startsWith("file://")) {
9
+ path = path.slice(7);
10
+ }
11
+
12
+ // Keep node: and other protocol URLs as-is
13
+ if (path.includes("://") || path.startsWith("node:")) {
14
+ return path;
15
+ }
16
+
17
+ // Make relative to cwd
18
+ if (path.startsWith("/")) {
19
+ const rel = relative(cwd, path);
20
+ // Only use relative if it's actually shorter and doesn't escape too far
21
+ if (!rel.startsWith("../../..") && rel.length < path.length) {
22
+ return rel.startsWith("..") ? rel : `./${rel}`;
23
+ }
24
+ }
25
+
26
+ return path;
27
+ }
@@ -1,3 +1,5 @@
1
+ import { shortPath } from "./path.ts";
2
+
1
3
  export interface StackFrame {
2
4
  ref: string;
3
5
  functionName: string;
@@ -59,10 +61,11 @@ export function formatStack(frames: StackFrame[]): string {
59
61
 
60
62
  const ref = frame.ref.padEnd(maxRefLen);
61
63
  const name = frame.functionName.padEnd(maxNameLen);
64
+ const file = shortPath(frame.file);
62
65
  const loc =
63
66
  frame.column !== undefined
64
- ? `${frame.file}:${frame.line}:${frame.column}`
65
- : `${frame.file}:${frame.line}`;
67
+ ? `${file}:${frame.line}:${frame.column}`
68
+ : `${file}:${frame.line}`;
66
69
  outputLines.push(`${ref} ${name} ${loc}`);
67
70
  }
68
71