agent-dbg 0.1.2 → 0.1.4

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 (36) hide show
  1. package/.claude/settings.local.json +29 -1
  2. package/.claude/skills/agent-dbg/SKILL.md +2 -0
  3. package/.claude/skills/agent-dbg/references/commands.md +2 -1
  4. package/TODO.md +299 -0
  5. package/demo/DEMO.md +71 -0
  6. package/demo/order-processor.js +35 -0
  7. package/dist/main.js +1480 -256
  8. package/package.json +3 -1
  9. package/src/commands/attach.ts +6 -5
  10. package/src/commands/break-fn.ts +41 -0
  11. package/src/commands/launch.ts +5 -6
  12. package/src/commands/logs.ts +58 -16
  13. package/src/daemon/client.ts +27 -5
  14. package/src/daemon/entry.ts +94 -46
  15. package/src/daemon/logger.ts +51 -0
  16. package/src/daemon/paths.ts +4 -0
  17. package/src/daemon/server.ts +76 -35
  18. package/src/daemon/session-breakpoints.ts +2 -1
  19. package/src/daemon/session-mutation.ts +1 -0
  20. package/src/daemon/session.ts +50 -10
  21. package/src/daemon/spawn.ts +47 -8
  22. package/src/dap/client.ts +252 -0
  23. package/src/dap/session.ts +1151 -0
  24. package/src/formatter/logs.ts +15 -0
  25. package/src/main.ts +1 -0
  26. package/src/protocol/messages.ts +12 -0
  27. package/tests/fixtures/dap/hello +0 -0
  28. package/tests/fixtures/dap/hello.c +8 -0
  29. package/tests/fixtures/dap/hello.dSYM/Contents/Info.plist +20 -0
  30. package/tests/fixtures/dap/hello.dSYM/Contents/Resources/DWARF/hello +0 -0
  31. package/tests/fixtures/dap/hello.dSYM/Contents/Resources/Relocations/aarch64/hello.yml +5 -0
  32. package/tests/fixtures/hotpatch-active-fn.js +7 -0
  33. package/tests/integration/daemon-logging.test.ts +155 -0
  34. package/tests/integration/mutation.test.ts +33 -0
  35. package/tests/unit/daemon-logger.test.ts +117 -0
  36. package/tests/unit/daemon.test.ts +60 -0
package/dist/main.js CHANGED
@@ -12,6 +12,1115 @@ var __export = (target, all) => {
12
12
  };
13
13
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
14
 
15
+ // src/refs/ref-table.ts
16
+ class RefTable {
17
+ entries = new Map;
18
+ counters = {
19
+ v: 1,
20
+ f: 0,
21
+ o: 1,
22
+ BP: 1,
23
+ LP: 1,
24
+ HS: 1
25
+ };
26
+ addVar(remoteId, name, meta) {
27
+ return this.add("v", remoteId, name, meta);
28
+ }
29
+ addFrame(remoteId, name, meta) {
30
+ return this.add("f", remoteId, name, meta);
31
+ }
32
+ addObject(remoteId, name, meta) {
33
+ return this.add("o", remoteId, name, meta);
34
+ }
35
+ addBreakpoint(remoteId, meta) {
36
+ return this.add("BP", remoteId, undefined, meta);
37
+ }
38
+ addLogpoint(remoteId, meta) {
39
+ return this.add("LP", remoteId, undefined, meta);
40
+ }
41
+ addHeapSnapshot(remoteId, meta) {
42
+ return this.add("HS", remoteId, undefined, meta);
43
+ }
44
+ resolve(ref) {
45
+ return this.entries.get(ref);
46
+ }
47
+ resolveId(ref) {
48
+ return this.entries.get(ref)?.remoteId;
49
+ }
50
+ clearVolatile() {
51
+ for (const [key, entry] of this.entries) {
52
+ if (entry.type === "v" || entry.type === "f") {
53
+ this.entries.delete(key);
54
+ }
55
+ }
56
+ this.counters.v = 1;
57
+ this.counters.f = 0;
58
+ }
59
+ clearObjects() {
60
+ for (const [key, entry] of this.entries) {
61
+ if (entry.type === "o") {
62
+ this.entries.delete(key);
63
+ }
64
+ }
65
+ this.counters.o = 1;
66
+ }
67
+ clearAll() {
68
+ this.entries.clear();
69
+ this.counters = { v: 1, f: 0, o: 1, BP: 1, LP: 1, HS: 1 };
70
+ }
71
+ list(type) {
72
+ const result = [];
73
+ for (const entry of this.entries.values()) {
74
+ if (entry.type === type) {
75
+ result.push(entry);
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+ remove(ref) {
81
+ return this.entries.delete(ref);
82
+ }
83
+ add(type, remoteId, name, meta) {
84
+ const num = this.counters[type];
85
+ this.counters[type] = num + 1;
86
+ const ref = `${PREFIXES[type]}${num}`;
87
+ const entry = { ref, type, remoteId };
88
+ if (name !== undefined) {
89
+ entry.name = name;
90
+ }
91
+ if (meta !== undefined) {
92
+ entry.meta = meta;
93
+ }
94
+ this.entries.set(ref, entry);
95
+ return ref;
96
+ }
97
+ }
98
+ var PREFIXES;
99
+ var init_ref_table = __esm(() => {
100
+ PREFIXES = {
101
+ v: "@v",
102
+ f: "@f",
103
+ o: "@o",
104
+ BP: "BP#",
105
+ LP: "LP#",
106
+ HS: "HS#"
107
+ };
108
+ });
109
+
110
+ // src/dap/client.ts
111
+ class DapClient {
112
+ proc;
113
+ nextSeq = 1;
114
+ pending = new Map;
115
+ listeners = new Map;
116
+ isConnected = false;
117
+ buffer = "";
118
+ constructor(proc) {
119
+ this.proc = proc;
120
+ this.isConnected = true;
121
+ this.readLoop();
122
+ this.drainStderr();
123
+ }
124
+ static spawn(command) {
125
+ const [cmd, ...args] = command;
126
+ if (!cmd) {
127
+ throw new Error("DapClient.spawn: command array must not be empty");
128
+ }
129
+ const proc = Bun.spawn([cmd, ...args], {
130
+ stdin: "pipe",
131
+ stdout: "pipe",
132
+ stderr: "pipe"
133
+ });
134
+ return new DapClient(proc);
135
+ }
136
+ async send(command, args) {
137
+ if (!this.isConnected) {
138
+ throw new Error("DAP client is not connected");
139
+ }
140
+ const seq = this.nextSeq++;
141
+ const request = {
142
+ seq,
143
+ type: "request",
144
+ command
145
+ };
146
+ if (args !== undefined) {
147
+ request.arguments = args;
148
+ }
149
+ return new Promise((resolve, reject) => {
150
+ const timer = setTimeout(() => {
151
+ this.pending.delete(seq);
152
+ reject(new Error(`DAP request timed out: ${command} (seq=${seq})`));
153
+ }, DEFAULT_TIMEOUT_MS);
154
+ this.pending.set(seq, { resolve, reject, timer });
155
+ this.writeMessage(request);
156
+ });
157
+ }
158
+ on(event, handler) {
159
+ let handlers = this.listeners.get(event);
160
+ if (!handlers) {
161
+ handlers = new Set;
162
+ this.listeners.set(event, handlers);
163
+ }
164
+ handlers.add(handler);
165
+ }
166
+ off(event, handler) {
167
+ const handlers = this.listeners.get(event);
168
+ if (handlers) {
169
+ handlers.delete(handler);
170
+ if (handlers.size === 0) {
171
+ this.listeners.delete(event);
172
+ }
173
+ }
174
+ }
175
+ disconnect() {
176
+ if (!this.isConnected) {
177
+ return;
178
+ }
179
+ this.isConnected = false;
180
+ const error = new Error("DAP client disconnected");
181
+ for (const [id, pending] of this.pending) {
182
+ clearTimeout(pending.timer);
183
+ pending.reject(error);
184
+ this.pending.delete(id);
185
+ }
186
+ this.listeners.clear();
187
+ try {
188
+ this.proc.stdin.end();
189
+ } catch {}
190
+ try {
191
+ this.proc.kill();
192
+ } catch {}
193
+ }
194
+ get connected() {
195
+ return this.isConnected;
196
+ }
197
+ get pid() {
198
+ return this.proc.pid;
199
+ }
200
+ writeMessage(msg) {
201
+ const json = JSON.stringify(msg);
202
+ const header = `Content-Length: ${Buffer.byteLength(json, "utf-8")}\r
203
+ \r
204
+ `;
205
+ try {
206
+ this.proc.stdin.write(header + json);
207
+ } catch {
208
+ this.isConnected = false;
209
+ }
210
+ }
211
+ async readLoop() {
212
+ const reader = this.proc.stdout.getReader();
213
+ const decoder = new TextDecoder;
214
+ try {
215
+ while (this.isConnected) {
216
+ const { done, value } = await reader.read();
217
+ if (done)
218
+ break;
219
+ this.buffer += decoder.decode(value, { stream: true });
220
+ this.processBuffer();
221
+ }
222
+ } catch {} finally {
223
+ this.isConnected = false;
224
+ const error = new Error("DAP adapter process terminated");
225
+ for (const [id, pending] of this.pending) {
226
+ clearTimeout(pending.timer);
227
+ pending.reject(error);
228
+ this.pending.delete(id);
229
+ }
230
+ }
231
+ }
232
+ processBuffer() {
233
+ while (true) {
234
+ const headerEnd = this.buffer.indexOf(`\r
235
+ \r
236
+ `);
237
+ if (headerEnd === -1)
238
+ return;
239
+ const header = this.buffer.slice(0, headerEnd);
240
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
241
+ if (!match?.[1]) {
242
+ this.buffer = this.buffer.slice(headerEnd + 4);
243
+ continue;
244
+ }
245
+ const contentLength = parseInt(match[1], 10);
246
+ const bodyStart = headerEnd + 4;
247
+ const bodyEnd = bodyStart + contentLength;
248
+ if (this.buffer.length < bodyEnd)
249
+ return;
250
+ const body = this.buffer.slice(bodyStart, bodyEnd);
251
+ this.buffer = this.buffer.slice(bodyEnd);
252
+ this.handleMessage(body);
253
+ }
254
+ }
255
+ handleMessage(data) {
256
+ let parsed;
257
+ try {
258
+ parsed = JSON.parse(data);
259
+ } catch {
260
+ return;
261
+ }
262
+ if (parsed.type === "response") {
263
+ const response = parsed;
264
+ const pending = this.pending.get(response.request_seq);
265
+ if (!pending)
266
+ return;
267
+ this.pending.delete(response.request_seq);
268
+ clearTimeout(pending.timer);
269
+ if (!response.success) {
270
+ pending.reject(new Error(`DAP error (${response.command}): ${response.message ?? "unknown error"}`));
271
+ } else {
272
+ pending.resolve(response);
273
+ }
274
+ } else if (parsed.type === "event") {
275
+ const event = parsed;
276
+ const handlers = this.listeners.get(event.event);
277
+ if (handlers) {
278
+ for (const handler of handlers) {
279
+ handler(event.body);
280
+ }
281
+ }
282
+ }
283
+ }
284
+ async drainStderr() {
285
+ const reader = this.proc.stderr.getReader();
286
+ try {
287
+ while (true) {
288
+ const { done } = await reader.read();
289
+ if (done)
290
+ break;
291
+ }
292
+ } catch {}
293
+ }
294
+ }
295
+ var DEFAULT_TIMEOUT_MS = 30000;
296
+
297
+ // src/dap/session.ts
298
+ function resolveAdapterCommand(runtime) {
299
+ switch (runtime) {
300
+ case "lldb":
301
+ case "lldb-dap": {
302
+ const brewPath = "/opt/homebrew/opt/llvm/bin/lldb-dap";
303
+ return [brewPath];
304
+ }
305
+ case "codelldb":
306
+ return ["codelldb", "--port", "0"];
307
+ default:
308
+ return [runtime];
309
+ }
310
+ }
311
+
312
+ class DapSession {
313
+ dap = null;
314
+ refs = new RefTable;
315
+ _state = "idle";
316
+ _pauseInfo = null;
317
+ _session;
318
+ _runtime;
319
+ _startTime = Date.now();
320
+ _threadId = 1;
321
+ _stackFrames = [];
322
+ _consoleMessages = [];
323
+ _exceptionEntries = [];
324
+ capabilities = {};
325
+ breakpoints = new Map;
326
+ allBreakpoints = [];
327
+ functionBreakpoints = [];
328
+ stoppedWaiter = null;
329
+ _stackFetchPromise = null;
330
+ constructor(session, runtime) {
331
+ this._session = session;
332
+ this._runtime = runtime;
333
+ }
334
+ async launch(command, options = {}) {
335
+ if (this._state !== "idle") {
336
+ throw new Error("Session already has an active debug target");
337
+ }
338
+ const adapterCmd = resolveAdapterCommand(this._runtime);
339
+ this.dap = DapClient.spawn(adapterCmd);
340
+ this.setupEventHandlers();
341
+ await this.initializeAdapter();
342
+ const program = options.program ?? command[0];
343
+ const programArgs = options.args ?? command.slice(1);
344
+ const launchArgs = {
345
+ program,
346
+ args: programArgs,
347
+ stopOnEntry: options.brk ?? true,
348
+ cwd: process.cwd()
349
+ };
350
+ await this.dap.send("launch", launchArgs);
351
+ await this.dap.send("configurationDone");
352
+ if (options.brk !== false) {
353
+ await this.waitForStop(5000);
354
+ }
355
+ const result = {
356
+ pid: this.dap.pid,
357
+ wsUrl: `dap://${this._runtime}`,
358
+ paused: this.isPaused()
359
+ };
360
+ if (this._pauseInfo) {
361
+ result.pauseInfo = this._pauseInfo;
362
+ }
363
+ return result;
364
+ }
365
+ async attach(target) {
366
+ if (this._state !== "idle") {
367
+ throw new Error("Session already has an active debug target");
368
+ }
369
+ const adapterCmd = resolveAdapterCommand(this._runtime);
370
+ this.dap = DapClient.spawn(adapterCmd);
371
+ this.setupEventHandlers();
372
+ await this.initializeAdapter();
373
+ const pid = parseInt(target, 10);
374
+ const attachArgs = Number.isNaN(pid) ? { program: target, waitFor: true } : { pid };
375
+ await this.dap.send("attach", attachArgs);
376
+ await this.dap.send("configurationDone");
377
+ await this.waitForStop(5000).catch(() => {});
378
+ if (this._state === "idle") {
379
+ this._state = "running";
380
+ }
381
+ return { wsUrl: `dap://${this._runtime}/${target}` };
382
+ }
383
+ getStatus() {
384
+ return {
385
+ session: this._session,
386
+ state: this._state,
387
+ pid: this.dap?.pid,
388
+ wsUrl: this.dap ? `dap://${this._runtime}` : undefined,
389
+ pauseInfo: this._pauseInfo ?? undefined,
390
+ uptime: Math.floor((Date.now() - this._startTime) / 1000),
391
+ scriptCount: 0
392
+ };
393
+ }
394
+ async stop() {
395
+ if (this.dap) {
396
+ try {
397
+ await this.dap.send("disconnect", { terminateDebuggee: true });
398
+ } catch {}
399
+ this.dap.disconnect();
400
+ this.dap = null;
401
+ }
402
+ this._state = "idle";
403
+ this._pauseInfo = null;
404
+ this._stackFrames = [];
405
+ this.refs.clearAll();
406
+ this.breakpoints.clear();
407
+ this.allBreakpoints = [];
408
+ this.functionBreakpoints = [];
409
+ this._consoleMessages = [];
410
+ this._exceptionEntries = [];
411
+ }
412
+ async continue() {
413
+ this.requireConnected();
414
+ this.requirePaused();
415
+ const waiter = this.createStoppedWaiter(30000);
416
+ await this.getDap().send("continue", { threadId: this._threadId });
417
+ this._state = "running";
418
+ this._pauseInfo = null;
419
+ this._stackFrames = [];
420
+ this.refs.clearVolatile();
421
+ await waiter;
422
+ if (this.isPaused())
423
+ await this.fetchStackTrace();
424
+ }
425
+ async step(mode) {
426
+ this.requireConnected();
427
+ this.requirePaused();
428
+ const waiter = this.createStoppedWaiter(30000);
429
+ const command = mode === "into" ? "stepIn" : mode === "out" ? "stepOut" : "next";
430
+ await this.getDap().send(command, { threadId: this._threadId });
431
+ this._state = "running";
432
+ this._pauseInfo = null;
433
+ this.refs.clearVolatile();
434
+ await waiter;
435
+ if (this.isPaused())
436
+ await this.fetchStackTrace();
437
+ }
438
+ async pause() {
439
+ this.requireConnected();
440
+ if (this._state !== "running") {
441
+ throw new Error("Cannot pause: target is not running");
442
+ }
443
+ const waiter = this.createStoppedWaiter(5000);
444
+ await this.getDap().send("pause", { threadId: this._threadId });
445
+ await waiter;
446
+ if (this.isPaused())
447
+ await this.fetchStackTrace();
448
+ }
449
+ async setBreakpoint(file, line, options) {
450
+ this.requireConnected();
451
+ const entry = {
452
+ ref: "",
453
+ file,
454
+ line,
455
+ condition: options?.condition,
456
+ hitCondition: options?.hitCount ? String(options.hitCount) : undefined,
457
+ verified: false
458
+ };
459
+ let fileBreakpoints = this.breakpoints.get(file);
460
+ if (!fileBreakpoints) {
461
+ fileBreakpoints = [];
462
+ this.breakpoints.set(file, fileBreakpoints);
463
+ }
464
+ fileBreakpoints.push(entry);
465
+ this.allBreakpoints.push(entry);
466
+ const ref = this.refs.addBreakpoint(`dap-bp:${file}:${line}`, {
467
+ file,
468
+ line
469
+ });
470
+ entry.ref = ref;
471
+ await this.syncFileBreakpoints(file);
472
+ return {
473
+ ref,
474
+ location: { url: file, line: entry.actualLine ?? line }
475
+ };
476
+ }
477
+ async removeBreakpoint(ref) {
478
+ this.requireConnected();
479
+ const entry = this.allBreakpoints.find((bp) => bp.ref === ref);
480
+ if (!entry) {
481
+ throw new Error(`Unknown breakpoint ref: ${ref}`);
482
+ }
483
+ const fileBreakpoints = this.breakpoints.get(entry.file);
484
+ if (fileBreakpoints) {
485
+ const idx = fileBreakpoints.indexOf(entry);
486
+ if (idx !== -1)
487
+ fileBreakpoints.splice(idx, 1);
488
+ if (fileBreakpoints.length === 0) {
489
+ this.breakpoints.delete(entry.file);
490
+ }
491
+ }
492
+ const allIdx = this.allBreakpoints.indexOf(entry);
493
+ if (allIdx !== -1)
494
+ this.allBreakpoints.splice(allIdx, 1);
495
+ this.refs.remove(ref);
496
+ await this.syncFileBreakpoints(entry.file);
497
+ }
498
+ async removeAllBreakpoints() {
499
+ this.requireConnected();
500
+ const files = [...this.breakpoints.keys()];
501
+ this.breakpoints.clear();
502
+ this.allBreakpoints = [];
503
+ this.functionBreakpoints = [];
504
+ for (const entry of this.refs.list("BP")) {
505
+ this.refs.remove(entry.ref);
506
+ }
507
+ for (const file of files) {
508
+ await this.getDap().send("setBreakpoints", {
509
+ source: { path: file },
510
+ breakpoints: []
511
+ });
512
+ }
513
+ await this.getDap().send("setFunctionBreakpoints", { breakpoints: [] });
514
+ }
515
+ listBreakpoints() {
516
+ const fileBps = this.allBreakpoints.map((bp) => ({
517
+ ref: bp.ref,
518
+ type: "BP",
519
+ url: bp.file,
520
+ line: bp.actualLine ?? bp.line,
521
+ condition: bp.condition
522
+ }));
523
+ const fnBps = this.functionBreakpoints.map((bp) => ({
524
+ ref: bp.ref,
525
+ type: "BP",
526
+ url: bp.name,
527
+ line: 0,
528
+ condition: bp.condition
529
+ }));
530
+ return [...fileBps, ...fnBps];
531
+ }
532
+ async setFunctionBreakpoint(name, options) {
533
+ this.requireConnected();
534
+ const entry = {
535
+ ref: "",
536
+ name,
537
+ condition: options?.condition,
538
+ hitCondition: options?.hitCount ? String(options.hitCount) : undefined,
539
+ verified: false
540
+ };
541
+ this.functionBreakpoints.push(entry);
542
+ const ref = this.refs.addBreakpoint(`dap-fn:${name}`, {
543
+ file: name,
544
+ line: 0
545
+ });
546
+ entry.ref = ref;
547
+ await this.syncFunctionBreakpoints();
548
+ return { ref };
549
+ }
550
+ async removeFunctionBreakpoint(ref) {
551
+ this.requireConnected();
552
+ const idx = this.functionBreakpoints.findIndex((bp) => bp.ref === ref);
553
+ if (idx === -1) {
554
+ throw new Error(`Unknown function breakpoint ref: ${ref}`);
555
+ }
556
+ this.functionBreakpoints.splice(idx, 1);
557
+ this.refs.remove(ref);
558
+ await this.syncFunctionBreakpoints();
559
+ }
560
+ async syncFunctionBreakpoints() {
561
+ const dapBps = this.functionBreakpoints.map((bp) => ({
562
+ name: bp.name,
563
+ condition: bp.condition,
564
+ hitCondition: bp.hitCondition
565
+ }));
566
+ const response = await this.getDap().send("setFunctionBreakpoints", {
567
+ breakpoints: dapBps
568
+ });
569
+ const body = response.body;
570
+ const resultBps = body?.breakpoints ?? [];
571
+ for (let i = 0;i < this.functionBreakpoints.length; i++) {
572
+ const entry = this.functionBreakpoints[i];
573
+ const result = resultBps[i];
574
+ if (entry && result) {
575
+ entry.verified = result.verified ?? false;
576
+ }
577
+ }
578
+ }
579
+ async eval(expression, options = {}) {
580
+ this.requireConnected();
581
+ this.requirePaused();
582
+ await this.ensureStack();
583
+ const frameId = this.resolveFrameId(options.frame);
584
+ const response = await this.getDap().send("evaluate", {
585
+ expression,
586
+ frameId,
587
+ context: "repl"
588
+ });
589
+ const body = response.body;
590
+ const remoteId = body.variablesReference > 0 ? String(body.variablesReference) : `eval:${Date.now()}`;
591
+ const ref = this.refs.addVar(remoteId, expression);
592
+ return {
593
+ ref,
594
+ type: body.type ?? "unknown",
595
+ value: body.result,
596
+ objectId: body.variablesReference > 0 ? String(body.variablesReference) : undefined
597
+ };
598
+ }
599
+ async getVars(options = {}) {
600
+ this.requireConnected();
601
+ this.requirePaused();
602
+ await this.ensureStack();
603
+ const frameId = this.resolveFrameId(options.frame);
604
+ const scopesResponse = await this.getDap().send("scopes", { frameId });
605
+ const scopes = scopesResponse.body.scopes;
606
+ const result = [];
607
+ const scopesToFetch = options.allScopes ? scopes : scopes.filter((s) => !s.expensive).slice(0, 2);
608
+ for (const scope of scopesToFetch) {
609
+ const varsResponse = await this.getDap().send("variables", {
610
+ variablesReference: scope.variablesReference
611
+ });
612
+ const variables = varsResponse.body.variables;
613
+ for (const v of variables) {
614
+ if (options.names && !options.names.includes(v.name))
615
+ continue;
616
+ const remoteId = v.variablesReference > 0 ? String(v.variablesReference) : `var:${v.name}:${Date.now()}`;
617
+ const ref = this.refs.addVar(remoteId, v.name);
618
+ result.push({
619
+ ref,
620
+ name: v.name,
621
+ type: v.type ?? "unknown",
622
+ value: v.value
623
+ });
624
+ }
625
+ }
626
+ return result;
627
+ }
628
+ async getProps(ref, _options = {}) {
629
+ this.requireConnected();
630
+ const remoteId = this.refs.resolveId(ref);
631
+ if (!remoteId) {
632
+ throw new Error(`Unknown ref: ${ref}`);
633
+ }
634
+ const variablesReference = parseInt(remoteId, 10);
635
+ if (Number.isNaN(variablesReference) || variablesReference <= 0) {
636
+ return [];
637
+ }
638
+ const response = await this.getDap().send("variables", { variablesReference });
639
+ const variables = response.body.variables;
640
+ return variables.map((v) => {
641
+ const childRemoteId = v.variablesReference > 0 ? String(v.variablesReference) : `prop:${v.name}:${Date.now()}`;
642
+ const childRef = v.variablesReference > 0 ? this.refs.addVar(childRemoteId, v.name) : undefined;
643
+ return {
644
+ ref: childRef,
645
+ name: v.name,
646
+ type: v.type ?? "unknown",
647
+ value: v.value,
648
+ isOwn: true
649
+ };
650
+ });
651
+ }
652
+ getStack(_options = {}) {
653
+ return this._stackFrames.map((frame) => {
654
+ const ref = this.refs.addFrame(String(frame.id), frame.name);
655
+ return {
656
+ ref,
657
+ functionName: frame.name,
658
+ file: frame.file ?? "<unknown>",
659
+ line: frame.line,
660
+ column: frame.column > 0 ? frame.column : undefined
661
+ };
662
+ });
663
+ }
664
+ async getSource(options = {}) {
665
+ const file = options.file ?? this._pauseInfo?.url;
666
+ if (!file) {
667
+ throw new Error("No source file available. Specify a file path.");
668
+ }
669
+ let content;
670
+ try {
671
+ content = await Bun.file(file).text();
672
+ } catch {
673
+ throw new Error(`Cannot read source file: ${file}`);
674
+ }
675
+ const allLines = content.split(`
676
+ `);
677
+ const currentLine = this._pauseInfo?.line;
678
+ const windowSize = options.lines ?? 10;
679
+ let startLine;
680
+ let endLine;
681
+ if (options.all) {
682
+ startLine = 1;
683
+ endLine = allLines.length;
684
+ } else if (currentLine !== undefined) {
685
+ startLine = Math.max(1, currentLine - windowSize);
686
+ endLine = Math.min(allLines.length, currentLine + windowSize);
687
+ } else {
688
+ startLine = 1;
689
+ endLine = Math.min(allLines.length, windowSize * 2);
690
+ }
691
+ const lines = [];
692
+ for (let i = startLine;i <= endLine; i++) {
693
+ const lineObj = {
694
+ line: i,
695
+ text: allLines[i - 1] ?? ""
696
+ };
697
+ if (currentLine !== undefined && i === currentLine) {
698
+ lineObj.current = true;
699
+ }
700
+ lines.push(lineObj);
701
+ }
702
+ return { url: file, lines };
703
+ }
704
+ async buildState(options = {}) {
705
+ if (this.isPaused())
706
+ await this.ensureStack();
707
+ const snapshot = {
708
+ status: this._state
709
+ };
710
+ if (this._state === "paused" && this._pauseInfo) {
711
+ snapshot.reason = this._pauseInfo.reason;
712
+ if (this._pauseInfo.url && this._pauseInfo.line !== undefined) {
713
+ snapshot.location = {
714
+ url: this._pauseInfo.url,
715
+ line: this._pauseInfo.line,
716
+ column: this._pauseInfo.column
717
+ };
718
+ }
719
+ }
720
+ if (this._state === "paused" && options.code !== false) {
721
+ try {
722
+ const source = await this.getSource({ lines: options.lines });
723
+ snapshot.source = source;
724
+ } catch {}
725
+ }
726
+ if (this._state === "paused" && (options.vars !== false || !options.compact)) {
727
+ try {
728
+ const vars = await this.getVars({ frame: options.frame, allScopes: options.allScopes });
729
+ snapshot.vars = vars.map((v) => ({
730
+ ref: v.ref,
731
+ name: v.name,
732
+ value: v.value,
733
+ scope: "local"
734
+ }));
735
+ } catch {}
736
+ }
737
+ if (this._state === "paused" && options.stack !== false) {
738
+ try {
739
+ snapshot.stack = this.getStack();
740
+ } catch {}
741
+ }
742
+ if (options.breakpoints !== false) {
743
+ snapshot.breakpointCount = this.allBreakpoints.length;
744
+ }
745
+ return snapshot;
746
+ }
747
+ getConsoleMessages(options = {}) {
748
+ let msgs = this._consoleMessages;
749
+ if (options.level) {
750
+ msgs = msgs.filter((m) => m.level === options.level);
751
+ }
752
+ if (options.since !== undefined) {
753
+ const since = options.since;
754
+ msgs = msgs.filter((m) => m.timestamp >= since);
755
+ }
756
+ if (options.clear) {
757
+ this._consoleMessages = [];
758
+ }
759
+ return msgs;
760
+ }
761
+ getExceptions(options = {}) {
762
+ let entries = this._exceptionEntries;
763
+ if (options.since !== undefined) {
764
+ const since = options.since;
765
+ entries = entries.filter((e) => e.timestamp >= since);
766
+ }
767
+ return entries;
768
+ }
769
+ async setLogpoint(_file, _line, _template, _options) {
770
+ throw new Error("Logpoints are not supported in DAP mode. Use breakpoints with conditions instead.");
771
+ }
772
+ async setExceptionPause(mode) {
773
+ this.requireConnected();
774
+ const available = this.capabilities.exceptionBreakpointFilters ?? [];
775
+ const filterIds = available.map((f) => f.filter);
776
+ let filters;
777
+ if (mode === "none") {
778
+ filters = [];
779
+ } else if (mode === "all") {
780
+ filters = filterIds;
781
+ } else {
782
+ filters = filterIds.filter((id) => id.includes(mode));
783
+ if (filters.length === 0)
784
+ filters = filterIds;
785
+ }
786
+ await this.getDap().send("setExceptionBreakpoints", { filters });
787
+ }
788
+ async toggleBreakpoint(_ref) {
789
+ throw new Error("Breakpoint toggling is not yet supported in DAP mode. Use break-rm and break.");
790
+ }
791
+ async getBreakableLocations(_file, _startLine, _endLine) {
792
+ throw new Error("Breakable locations are not supported in DAP mode.");
793
+ }
794
+ async hotpatch(_file, _source, _options) {
795
+ throw new Error("Hot-patching is not supported in DAP mode.");
796
+ }
797
+ async searchInScripts(_query, _options) {
798
+ throw new Error("Script search is not supported in DAP mode. Use your shell to search source files.");
799
+ }
800
+ async setVariable(varName, value, options = {}) {
801
+ this.requireConnected();
802
+ this.requirePaused();
803
+ await this.ensureStack();
804
+ const frameId = this.resolveFrameId(options.frame);
805
+ const scopesResponse = await this.getDap().send("scopes", { frameId });
806
+ const scopes = scopesResponse.body.scopes;
807
+ for (const scope of scopes) {
808
+ try {
809
+ const response = await this.getDap().send("setVariable", {
810
+ variablesReference: scope.variablesReference,
811
+ name: varName,
812
+ value
813
+ });
814
+ const body = response.body;
815
+ return { name: varName, newValue: body.value, type: body.type ?? "unknown" };
816
+ } catch {}
817
+ }
818
+ throw new Error(`Variable "${varName}" not found in any scope`);
819
+ }
820
+ async setReturnValue(_value) {
821
+ throw new Error("Setting return values is not supported in DAP mode.");
822
+ }
823
+ async restartFrame(_frameRef) {
824
+ throw new Error("Frame restart is not supported in DAP mode.");
825
+ }
826
+ async runTo(_file, _line) {
827
+ throw new Error("Run-to-location is not yet supported in DAP mode. Set a breakpoint and continue.");
828
+ }
829
+ getScripts(_filter) {
830
+ return [];
831
+ }
832
+ async addBlackbox(_patterns) {
833
+ throw new Error("Blackboxing is not supported in DAP mode.");
834
+ }
835
+ listBlackbox() {
836
+ return [];
837
+ }
838
+ async removeBlackbox(_patterns) {
839
+ throw new Error("Blackboxing is not supported in DAP mode.");
840
+ }
841
+ async restart() {
842
+ throw new Error("Restart is not yet supported in DAP mode. Use stop + launch.");
843
+ }
844
+ get sourceMapResolver() {
845
+ return {
846
+ findScriptForSource: () => null,
847
+ getInfo: () => null,
848
+ getAllInfos: () => [],
849
+ setDisabled: () => {}
850
+ };
851
+ }
852
+ isPaused() {
853
+ return this._state === "paused";
854
+ }
855
+ async ensureStack() {
856
+ if (this.isPaused() && this._stackFrames.length === 0) {
857
+ await this.fetchStackTrace();
858
+ }
859
+ }
860
+ getDap() {
861
+ if (!this.dap || !this.dap.connected) {
862
+ throw new Error("Not connected to a debug adapter. Use launch or attach first.");
863
+ }
864
+ return this.dap;
865
+ }
866
+ requireConnected() {
867
+ this.getDap();
868
+ }
869
+ requirePaused() {
870
+ if (!this.isPaused()) {
871
+ throw new Error("Target is not paused. Use pause or wait for a breakpoint.");
872
+ }
873
+ }
874
+ async initializeAdapter() {
875
+ const response = await this.getDap().send("initialize", {
876
+ adapterID: this._runtime,
877
+ clientID: "agent-dbg",
878
+ clientName: "agent-dbg",
879
+ linesStartAt1: true,
880
+ columnsStartAt1: true,
881
+ pathFormat: "path",
882
+ supportsVariableType: true
883
+ });
884
+ this.capabilities = response.body ?? {};
885
+ }
886
+ setupEventHandlers() {
887
+ const dap = this.getDap();
888
+ dap.on("stopped", (body) => {
889
+ const event = body;
890
+ this._state = "paused";
891
+ if (event.threadId !== undefined) {
892
+ this._threadId = event.threadId;
893
+ }
894
+ this._pauseInfo = {
895
+ reason: event.reason
896
+ };
897
+ if (this.stoppedWaiter) {
898
+ this.stoppedWaiter.resolve();
899
+ this.stoppedWaiter = null;
900
+ } else {
901
+ this.fetchStackTrace().catch(() => {});
902
+ }
903
+ });
904
+ dap.on("continued", (_body) => {
905
+ this._state = "running";
906
+ this._pauseInfo = null;
907
+ this._stackFrames = [];
908
+ this.refs.clearVolatile();
909
+ });
910
+ dap.on("terminated", (_body) => {
911
+ this._state = "idle";
912
+ this._pauseInfo = null;
913
+ this._stackFrames = [];
914
+ this.stoppedWaiter?.resolve();
915
+ this.stoppedWaiter = null;
916
+ });
917
+ dap.on("exited", (_body) => {
918
+ this._state = "idle";
919
+ this._pauseInfo = null;
920
+ this.stoppedWaiter?.resolve();
921
+ this.stoppedWaiter = null;
922
+ });
923
+ dap.on("output", (body) => {
924
+ const event = body;
925
+ const category = event.category ?? "console";
926
+ if (category === "stdout" || category === "console") {
927
+ this._consoleMessages.push({
928
+ timestamp: Date.now(),
929
+ level: "log",
930
+ text: event.output.trimEnd(),
931
+ url: event.source?.path,
932
+ line: event.line
933
+ });
934
+ } else if (category === "stderr") {
935
+ this._consoleMessages.push({
936
+ timestamp: Date.now(),
937
+ level: "error",
938
+ text: event.output.trimEnd(),
939
+ url: event.source?.path,
940
+ line: event.line
941
+ });
942
+ }
943
+ if (this._consoleMessages.length > 1000) {
944
+ this._consoleMessages.shift();
945
+ }
946
+ });
947
+ }
948
+ async fetchStackTrace() {
949
+ if (this._stackFetchPromise) {
950
+ await this._stackFetchPromise;
951
+ return;
952
+ }
953
+ this._stackFetchPromise = this._fetchStackTraceImpl();
954
+ try {
955
+ await this._stackFetchPromise;
956
+ } finally {
957
+ this._stackFetchPromise = null;
958
+ }
959
+ }
960
+ async _fetchStackTraceImpl() {
961
+ if (!this.dap || this._state !== "paused")
962
+ return;
963
+ try {
964
+ const response = await this.dap.send("stackTrace", {
965
+ threadId: this._threadId,
966
+ startFrame: 0,
967
+ levels: 50
968
+ });
969
+ const body = response.body;
970
+ this._stackFrames = body.stackFrames.map((f) => ({
971
+ id: f.id,
972
+ name: f.name,
973
+ file: f.source?.path ?? f.source?.name,
974
+ line: f.line,
975
+ column: f.column
976
+ }));
977
+ const topFrame = this._stackFrames[0];
978
+ if (topFrame && this._pauseInfo) {
979
+ this._pauseInfo.url = topFrame.file;
980
+ this._pauseInfo.line = topFrame.line;
981
+ this._pauseInfo.column = topFrame.column > 0 ? topFrame.column : undefined;
982
+ this._pauseInfo.callFrameCount = this._stackFrames.length;
983
+ }
984
+ } catch {}
985
+ }
986
+ resolveFrameId(frameRef) {
987
+ if (!frameRef) {
988
+ const topFrame = this._stackFrames[0];
989
+ if (!topFrame) {
990
+ throw new Error("No stack frames available");
991
+ }
992
+ return topFrame.id;
993
+ }
994
+ const remoteId = this.refs.resolveId(frameRef);
995
+ if (!remoteId) {
996
+ throw new Error(`Unknown frame ref: ${frameRef}`);
997
+ }
998
+ return parseInt(remoteId, 10);
999
+ }
1000
+ async syncFileBreakpoints(file) {
1001
+ const entries = this.breakpoints.get(file) ?? [];
1002
+ const dapBreakpoints = entries.map((bp) => {
1003
+ const sbp = { line: bp.line };
1004
+ if (bp.condition)
1005
+ sbp.condition = bp.condition;
1006
+ if (bp.hitCondition)
1007
+ sbp.hitCondition = bp.hitCondition;
1008
+ return sbp;
1009
+ });
1010
+ const response = await this.getDap().send("setBreakpoints", {
1011
+ source: { path: file },
1012
+ breakpoints: dapBreakpoints
1013
+ });
1014
+ const body = response.body;
1015
+ for (let i = 0;i < entries.length && i < body.breakpoints.length; i++) {
1016
+ const bp = body.breakpoints[i];
1017
+ const entry = entries[i];
1018
+ if (bp && entry) {
1019
+ entry.dapId = bp.id;
1020
+ entry.verified = bp.verified;
1021
+ entry.actualLine = bp.line ?? entry.line;
1022
+ }
1023
+ }
1024
+ }
1025
+ createStoppedWaiter(timeoutMs) {
1026
+ return new Promise((resolve, reject) => {
1027
+ const timer = setTimeout(() => {
1028
+ this.stoppedWaiter = null;
1029
+ resolve();
1030
+ }, timeoutMs);
1031
+ this.stoppedWaiter = {
1032
+ resolve: () => {
1033
+ clearTimeout(timer);
1034
+ this.stoppedWaiter = null;
1035
+ resolve();
1036
+ },
1037
+ reject: (e) => {
1038
+ clearTimeout(timer);
1039
+ this.stoppedWaiter = null;
1040
+ reject(e);
1041
+ }
1042
+ };
1043
+ });
1044
+ }
1045
+ async waitForStop(timeoutMs) {
1046
+ if (!this.isPaused()) {
1047
+ await this.createStoppedWaiter(timeoutMs);
1048
+ }
1049
+ if (this.isPaused() && this._stackFrames.length === 0) {
1050
+ await this.fetchStackTrace();
1051
+ }
1052
+ }
1053
+ }
1054
+ var init_session = __esm(() => {
1055
+ init_ref_table();
1056
+ });
1057
+
1058
+ // src/daemon/logger.ts
1059
+ import { appendFileSync, writeFileSync } from "fs";
1060
+
1061
+ class DaemonLogger {
1062
+ logPath;
1063
+ constructor(logPath) {
1064
+ this.logPath = logPath;
1065
+ writeFileSync(logPath, "");
1066
+ }
1067
+ info(event, message, data) {
1068
+ this.write("info", event, message, data);
1069
+ }
1070
+ warn(event, message, data) {
1071
+ this.write("warn", event, message, data);
1072
+ }
1073
+ error(event, message, data) {
1074
+ this.write("error", event, message, data);
1075
+ }
1076
+ debug(event, message, data) {
1077
+ this.write("debug", event, message, data);
1078
+ }
1079
+ clear() {
1080
+ writeFileSync(this.logPath, "");
1081
+ }
1082
+ write(level, event, message, data) {
1083
+ const entry = { ts: Date.now(), level, event, message };
1084
+ if (data !== undefined) {
1085
+ entry.data = data;
1086
+ }
1087
+ appendFileSync(this.logPath, `${JSON.stringify(entry)}
1088
+ `);
1089
+ }
1090
+ }
1091
+ var init_logger = () => {};
1092
+
1093
+ // src/daemon/paths.ts
1094
+ import { existsSync, mkdirSync } from "fs";
1095
+ import { join } from "path";
1096
+ function getSocketDir() {
1097
+ const xdgRuntime = process.env.XDG_RUNTIME_DIR;
1098
+ if (xdgRuntime) {
1099
+ return join(xdgRuntime, "agent-dbg");
1100
+ }
1101
+ const tmpdir = process.env.TMPDIR || "/tmp";
1102
+ return join(tmpdir, `agent-dbg-${process.getuid?.() ?? 0}`);
1103
+ }
1104
+ function getSocketPath(session) {
1105
+ return join(getSocketDir(), `${session}.sock`);
1106
+ }
1107
+ function getLockPath(session) {
1108
+ return join(getSocketDir(), `${session}.lock`);
1109
+ }
1110
+ function getLogPath(session) {
1111
+ return join(getSocketDir(), `${session}.cdp.log`);
1112
+ }
1113
+ function getDaemonLogPath(session) {
1114
+ return join(getSocketDir(), `${session}.daemon.log`);
1115
+ }
1116
+ function ensureSocketDir() {
1117
+ const dir = getSocketDir();
1118
+ if (!existsSync(dir)) {
1119
+ mkdirSync(dir, { recursive: true });
1120
+ }
1121
+ }
1122
+ var init_paths = () => {};
1123
+
15
1124
  // node_modules/@zod/core/dist/esm/core.js
16
1125
  function $constructor(name, initializer) {
17
1126
  class _ {
@@ -6035,7 +7144,7 @@ var init_esm2 = __esm(() => {
6035
7144
  });
6036
7145
 
6037
7146
  // src/protocol/messages.ts
6038
- var PingRequest, LaunchRequest, AttachRequest, StatusRequest, StateRequest, ContinueRequest, StepRequest, PauseRequest, RunToRequest, BreakRequest, BreakRmRequest, BreakLsRequest, LogpointRequest, CatchRequest, SourceRequest, ScriptsRequest, StackRequest, SearchRequest, ConsoleRequest, ExceptionsRequest, EvalRequest, VarsRequest, PropsRequest, BlackboxRequest, BlackboxLsRequest, BlackboxRmRequest, SetRequest, SetReturnRequest, HotpatchRequest, BreakToggleRequest, BreakableRequest, RestartFrameRequest, SourcemapRequest, SourcemapDisableRequest, RestartRequest, StopRequest, DaemonRequestSchema, SuccessResponse, ErrorResponse, DaemonResponseSchema;
7147
+ var PingRequest, LaunchRequest, AttachRequest, StatusRequest, StateRequest, ContinueRequest, StepRequest, PauseRequest, RunToRequest, BreakRequest, BreakFnRequest, BreakRmRequest, BreakLsRequest, LogpointRequest, CatchRequest, SourceRequest, ScriptsRequest, StackRequest, SearchRequest, ConsoleRequest, ExceptionsRequest, EvalRequest, VarsRequest, PropsRequest, BlackboxRequest, BlackboxLsRequest, BlackboxRmRequest, SetRequest, SetReturnRequest, HotpatchRequest, BreakToggleRequest, BreakableRequest, RestartFrameRequest, SourcemapRequest, SourcemapDisableRequest, RestartRequest, StopRequest, DaemonRequestSchema, SuccessResponse, ErrorResponse, DaemonResponseSchema;
6039
7148
  var init_messages = __esm(() => {
6040
7149
  init_esm2();
6041
7150
  PingRequest = exports_external.object({ cmd: exports_external.literal("ping") });
@@ -6044,13 +7153,15 @@ var init_messages = __esm(() => {
6044
7153
  args: exports_external.object({
6045
7154
  command: exports_external.array(exports_external.string()),
6046
7155
  brk: exports_external.optional(exports_external.boolean()),
6047
- port: exports_external.optional(exports_external.number())
7156
+ port: exports_external.optional(exports_external.number()),
7157
+ runtime: exports_external.optional(exports_external.string())
6048
7158
  })
6049
7159
  });
6050
7160
  AttachRequest = exports_external.object({
6051
7161
  cmd: exports_external.literal("attach"),
6052
7162
  args: exports_external.object({
6053
- target: exports_external.string()
7163
+ target: exports_external.string(),
7164
+ runtime: exports_external.optional(exports_external.string())
6054
7165
  })
6055
7166
  });
6056
7167
  StatusRequest = exports_external.object({ cmd: exports_external.literal("status") });
@@ -6091,7 +7202,15 @@ var init_messages = __esm(() => {
6091
7202
  line: exports_external.number(),
6092
7203
  condition: exports_external.optional(exports_external.string()),
6093
7204
  hitCount: exports_external.optional(exports_external.number()),
6094
- urlRegex: exports_external.optional(exports_external.string())
7205
+ urlRegex: exports_external.optional(exports_external.string()),
7206
+ column: exports_external.optional(exports_external.number())
7207
+ })
7208
+ });
7209
+ BreakFnRequest = exports_external.object({
7210
+ cmd: exports_external.literal("break-fn"),
7211
+ args: exports_external.object({
7212
+ name: exports_external.string(),
7213
+ condition: exports_external.optional(exports_external.string())
6095
7214
  })
6096
7215
  });
6097
7216
  BreakRmRequest = exports_external.object({
@@ -6269,6 +7388,7 @@ var init_messages = __esm(() => {
6269
7388
  PauseRequest,
6270
7389
  RunToRequest,
6271
7390
  BreakRequest,
7391
+ BreakFnRequest,
6272
7392
  BreakRmRequest,
6273
7393
  BreakLsRequest,
6274
7394
  LogpointRequest,
@@ -6308,36 +7428,8 @@ var init_messages = __esm(() => {
6308
7428
  DaemonResponseSchema = exports_external.union([SuccessResponse, ErrorResponse]);
6309
7429
  });
6310
7430
 
6311
- // src/daemon/paths.ts
6312
- import { existsSync, mkdirSync } from "fs";
6313
- import { join } from "path";
6314
- function getSocketDir() {
6315
- const xdgRuntime = process.env.XDG_RUNTIME_DIR;
6316
- if (xdgRuntime) {
6317
- return join(xdgRuntime, "agent-dbg");
6318
- }
6319
- const tmpdir = process.env.TMPDIR || "/tmp";
6320
- return join(tmpdir, `agent-dbg-${process.getuid?.() ?? 0}`);
6321
- }
6322
- function getSocketPath(session) {
6323
- return join(getSocketDir(), `${session}.sock`);
6324
- }
6325
- function getLockPath(session) {
6326
- return join(getSocketDir(), `${session}.lock`);
6327
- }
6328
- function getLogPath(session) {
6329
- return join(getSocketDir(), `${session}.cdp.log`);
6330
- }
6331
- function ensureSocketDir() {
6332
- const dir = getSocketDir();
6333
- if (!existsSync(dir)) {
6334
- mkdirSync(dir, { recursive: true });
6335
- }
6336
- }
6337
- var init_paths = () => {};
6338
-
6339
7431
  // src/daemon/server.ts
6340
- import { existsSync as existsSync2, unlinkSync, writeFileSync } from "fs";
7432
+ import { existsSync as existsSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
6341
7433
 
6342
7434
  class DaemonServer {
6343
7435
  session;
@@ -6347,11 +7439,13 @@ class DaemonServer {
6347
7439
  listener = null;
6348
7440
  socketPath;
6349
7441
  lockPath;
7442
+ logger;
6350
7443
  constructor(session, options) {
6351
7444
  this.session = session;
6352
7445
  this.idleTimeout = options.idleTimeout;
6353
7446
  this.socketPath = getSocketPath(session);
6354
7447
  this.lockPath = getLockPath(session);
7448
+ this.logger = options.logger ?? null;
6355
7449
  }
6356
7450
  onRequest(handler) {
6357
7451
  this.handler = handler;
@@ -6368,13 +7462,13 @@ class DaemonServer {
6368
7462
  if (existsSync2(this.socketPath)) {
6369
7463
  unlinkSync(this.socketPath);
6370
7464
  }
6371
- writeFileSync(this.lockPath, String(process.pid));
7465
+ writeFileSync2(this.lockPath, String(process.pid));
6372
7466
  const server = this;
6373
7467
  this.listener = Bun.listen({
6374
7468
  unix: this.socketPath,
6375
7469
  socket: {
6376
7470
  open(socket) {
6377
- socket.data = { buffer: "" };
7471
+ socket.data = { buffer: "", pendingWrite: null, pendingOffset: 0 };
6378
7472
  server.resetIdleTimer();
6379
7473
  },
6380
7474
  data(socket, data) {
@@ -6387,68 +7481,81 @@ class DaemonServer {
6387
7481
  socket.data.buffer = socket.data.buffer.slice(newlineIdx + 1);
6388
7482
  server.handleMessage(socket, line);
6389
7483
  },
7484
+ drain(socket) {
7485
+ server.flushPending(socket);
7486
+ },
6390
7487
  close() {},
6391
7488
  error(_socket, error3) {
7489
+ server.logger?.error("socket.error", error3.message);
6392
7490
  console.error(`[daemon] socket error: ${error3.message}`);
6393
7491
  }
6394
7492
  }
6395
7493
  });
6396
7494
  this.resetIdleTimer();
6397
7495
  }
7496
+ flushPending(socket) {
7497
+ const data = socket.data;
7498
+ if (!data.pendingWrite)
7499
+ return;
7500
+ while (data.pendingOffset < data.pendingWrite.length) {
7501
+ const written = socket.write(data.pendingWrite.subarray(data.pendingOffset));
7502
+ if (written === 0) {
7503
+ return;
7504
+ }
7505
+ data.pendingOffset += written;
7506
+ }
7507
+ data.pendingWrite = null;
7508
+ data.pendingOffset = 0;
7509
+ socket.end();
7510
+ }
7511
+ sendResponse(socket, response) {
7512
+ const payload = Buffer.from(`${JSON.stringify(response)}
7513
+ `);
7514
+ const written = socket.write(payload);
7515
+ if (written < payload.length) {
7516
+ socket.data.pendingWrite = payload;
7517
+ socket.data.pendingOffset = written;
7518
+ } else {
7519
+ socket.end();
7520
+ }
7521
+ }
6398
7522
  handleMessage(socket, line) {
6399
7523
  let json2;
6400
7524
  try {
6401
7525
  json2 = JSON.parse(line);
6402
7526
  } catch {
6403
- const errResponse = {
6404
- ok: false,
6405
- error: "Invalid JSON"
6406
- };
6407
- socket.write(`${JSON.stringify(errResponse)}
6408
- `);
6409
- socket.end();
7527
+ this.sendResponse(socket, { ok: false, error: "Invalid JSON" });
6410
7528
  return;
6411
7529
  }
6412
7530
  const parsed = DaemonRequestSchema.safeParse(json2);
6413
7531
  if (!parsed.success) {
6414
7532
  const obj = json2;
6415
7533
  const cmd = obj && typeof obj === "object" && typeof obj.cmd === "string" ? obj.cmd : undefined;
6416
- const errResponse = cmd ? {
7534
+ this.sendResponse(socket, cmd ? {
6417
7535
  ok: false,
6418
7536
  error: `Unknown command: ${cmd}`,
6419
7537
  suggestion: "-> Try: agent-dbg --help"
6420
7538
  } : {
6421
7539
  ok: false,
6422
7540
  error: "Invalid request: must have { cmd: string, args: object }"
6423
- };
6424
- socket.write(`${JSON.stringify(errResponse)}
6425
- `);
6426
- socket.end();
7541
+ });
6427
7542
  return;
6428
7543
  }
6429
7544
  const request = parsed.data;
6430
7545
  if (!this.handler) {
6431
- const errResponse = {
7546
+ this.sendResponse(socket, {
6432
7547
  ok: false,
6433
7548
  error: "No request handler registered"
6434
- };
6435
- socket.write(`${JSON.stringify(errResponse)}
6436
- `);
6437
- socket.end();
7549
+ });
6438
7550
  return;
6439
7551
  }
6440
7552
  this.handler(request).then((response) => {
6441
- socket.write(`${JSON.stringify(response)}
6442
- `);
6443
- socket.end();
7553
+ this.sendResponse(socket, response);
6444
7554
  }).catch((err) => {
6445
- const errResponse = {
7555
+ this.sendResponse(socket, {
6446
7556
  ok: false,
6447
7557
  error: err instanceof Error ? err.message : String(err)
6448
- };
6449
- socket.write(`${JSON.stringify(errResponse)}
6450
- `);
6451
- socket.end();
7558
+ });
6452
7559
  });
6453
7560
  }
6454
7561
  resetIdleTimer() {
@@ -6457,6 +7564,7 @@ class DaemonServer {
6457
7564
  }
6458
7565
  if (this.idleTimeout > 0) {
6459
7566
  this.idleTimer = setTimeout(() => {
7567
+ this.logger?.info("daemon.idle", `Idle timeout reached (${this.idleTimeout}s), shutting down`);
6460
7568
  this.stop();
6461
7569
  }, this.idleTimeout * 1000);
6462
7570
  }
@@ -6540,7 +7648,7 @@ class CdpClient {
6540
7648
  this.pending.delete(id);
6541
7649
  this.sentMethods.delete(id);
6542
7650
  reject(new Error(`CDP request timed out: ${method} (id=${id})`));
6543
- }, DEFAULT_TIMEOUT_MS);
7651
+ }, DEFAULT_TIMEOUT_MS2);
6544
7652
  this.pending.set(id, { resolve, reject, timer });
6545
7653
  this.ws.send(JSON.stringify(request));
6546
7654
  });
@@ -6644,17 +7752,17 @@ class CdpClient {
6644
7752
  }
6645
7753
  }
6646
7754
  }
6647
- var DEFAULT_TIMEOUT_MS = 30000;
7755
+ var DEFAULT_TIMEOUT_MS2 = 30000;
6648
7756
 
6649
7757
  // src/cdp/logger.ts
6650
- import { appendFileSync, writeFileSync as writeFileSync2 } from "fs";
7758
+ import { appendFileSync as appendFileSync2, writeFileSync as writeFileSync3 } from "fs";
6651
7759
 
6652
7760
  class CdpLogger {
6653
7761
  logPath;
6654
7762
  pending = new Map;
6655
7763
  constructor(logPath) {
6656
7764
  this.logPath = logPath;
6657
- writeFileSync2(logPath, "");
7765
+ writeFileSync3(logPath, "");
6658
7766
  }
6659
7767
  logSend(id, method, params) {
6660
7768
  this.pending.set(id, { method, sentAt: Date.now() });
@@ -6687,14 +7795,14 @@ class CdpLogger {
6687
7795
  this.append(entry);
6688
7796
  }
6689
7797
  clear() {
6690
- writeFileSync2(this.logPath, "");
7798
+ writeFileSync3(this.logPath, "");
6691
7799
  }
6692
7800
  append(entry) {
6693
- appendFileSync(this.logPath, `${JSON.stringify(entry)}
7801
+ appendFileSync2(this.logPath, `${JSON.stringify(entry)}
6694
7802
  `);
6695
7803
  }
6696
7804
  }
6697
- var init_logger = () => {};
7805
+ var init_logger2 = () => {};
6698
7806
 
6699
7807
  // src/formatter/values.ts
6700
7808
  function truncate(str, max) {
@@ -6901,101 +8009,6 @@ function formatValue(obj, maxLen = 80) {
6901
8009
  return truncate(obj.description ?? String(obj.value ?? obj.type), maxLen);
6902
8010
  }
6903
8011
 
6904
- // src/refs/ref-table.ts
6905
- class RefTable {
6906
- entries = new Map;
6907
- counters = {
6908
- v: 1,
6909
- f: 0,
6910
- o: 1,
6911
- BP: 1,
6912
- LP: 1,
6913
- HS: 1
6914
- };
6915
- addVar(remoteId, name, meta) {
6916
- return this.add("v", remoteId, name, meta);
6917
- }
6918
- addFrame(remoteId, name, meta) {
6919
- return this.add("f", remoteId, name, meta);
6920
- }
6921
- addObject(remoteId, name, meta) {
6922
- return this.add("o", remoteId, name, meta);
6923
- }
6924
- addBreakpoint(remoteId, meta) {
6925
- return this.add("BP", remoteId, undefined, meta);
6926
- }
6927
- addLogpoint(remoteId, meta) {
6928
- return this.add("LP", remoteId, undefined, meta);
6929
- }
6930
- addHeapSnapshot(remoteId, meta) {
6931
- return this.add("HS", remoteId, undefined, meta);
6932
- }
6933
- resolve(ref) {
6934
- return this.entries.get(ref);
6935
- }
6936
- resolveId(ref) {
6937
- return this.entries.get(ref)?.remoteId;
6938
- }
6939
- clearVolatile() {
6940
- for (const [key, entry] of this.entries) {
6941
- if (entry.type === "v" || entry.type === "f") {
6942
- this.entries.delete(key);
6943
- }
6944
- }
6945
- this.counters.v = 1;
6946
- this.counters.f = 0;
6947
- }
6948
- clearObjects() {
6949
- for (const [key, entry] of this.entries) {
6950
- if (entry.type === "o") {
6951
- this.entries.delete(key);
6952
- }
6953
- }
6954
- this.counters.o = 1;
6955
- }
6956
- clearAll() {
6957
- this.entries.clear();
6958
- this.counters = { v: 1, f: 0, o: 1, BP: 1, LP: 1, HS: 1 };
6959
- }
6960
- list(type) {
6961
- const result = [];
6962
- for (const entry of this.entries.values()) {
6963
- if (entry.type === type) {
6964
- result.push(entry);
6965
- }
6966
- }
6967
- return result;
6968
- }
6969
- remove(ref) {
6970
- return this.entries.delete(ref);
6971
- }
6972
- add(type, remoteId, name, meta) {
6973
- const num = this.counters[type];
6974
- this.counters[type] = num + 1;
6975
- const ref = `${PREFIXES[type]}${num}`;
6976
- const entry = { ref, type, remoteId };
6977
- if (name !== undefined) {
6978
- entry.name = name;
6979
- }
6980
- if (meta !== undefined) {
6981
- entry.meta = meta;
6982
- }
6983
- this.entries.set(ref, entry);
6984
- return ref;
6985
- }
6986
- }
6987
- var PREFIXES;
6988
- var init_ref_table = __esm(() => {
6989
- PREFIXES = {
6990
- v: "@v",
6991
- f: "@f",
6992
- o: "@o",
6993
- BP: "BP#",
6994
- LP: "LP#",
6995
- HS: "HS#"
6996
- };
6997
- });
6998
-
6999
8012
  // node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
7000
8013
  function decodeInteger(reader, relative) {
7001
8014
  let value = 0;
@@ -8819,7 +9832,8 @@ async function hotpatch(session, file2, newSource, options = {}) {
8819
9832
  }
8820
9833
  const setSourceParams = {
8821
9834
  scriptId,
8822
- scriptSource: newSource
9835
+ scriptSource: newSource,
9836
+ allowTopFrameEditing: true
8823
9837
  };
8824
9838
  if (options.dryRun) {
8825
9839
  setSourceParams.dryRun = true;
@@ -9040,10 +10054,12 @@ class DebugSession {
9040
10054
  launchCommand = null;
9041
10055
  launchOptions = null;
9042
10056
  cdpLogger;
9043
- constructor(session) {
10057
+ daemonLogger;
10058
+ constructor(session, options) {
9044
10059
  this.session = session;
9045
10060
  ensureSocketDir();
9046
10061
  this.cdpLogger = new CdpLogger(getLogPath(session));
10062
+ this.daemonLogger = options?.daemonLogger ?? new DaemonLogger(getDaemonLogPath(session));
9047
10063
  }
9048
10064
  async launch(command, options = {}) {
9049
10065
  if (this.state !== "idle") {
@@ -9062,13 +10078,20 @@ class DebugSession {
9062
10078
  const spawnArgs = [runtime, inspectFlag, ...rest];
9063
10079
  const proc = Bun.spawn(spawnArgs, {
9064
10080
  stdin: "ignore",
9065
- stdout: "pipe",
10081
+ stdout: "ignore",
9066
10082
  stderr: "pipe"
9067
10083
  });
9068
10084
  this.childProcess = proc;
10085
+ this.daemonLogger.info("child.spawn", `Spawned process pid=${proc.pid}`, {
10086
+ pid: proc.pid,
10087
+ command: spawnArgs
10088
+ });
9069
10089
  this.monitorProcessExit(proc);
9070
10090
  const wsUrl = await this.readInspectorUrl(proc.stderr);
9071
10091
  this.wsUrl = wsUrl;
10092
+ this.daemonLogger.info("inspector.detected", `Inspector URL: ${wsUrl}`, {
10093
+ wsUrl
10094
+ });
9072
10095
  await this.connectCdp(wsUrl);
9073
10096
  if (brk) {
9074
10097
  await this.waitForBrkPause();
@@ -9382,8 +10405,10 @@ class DebugSession {
9382
10405
  }
9383
10406
  }
9384
10407
  async connectCdp(wsUrl) {
10408
+ this.daemonLogger.debug("cdp.connecting", `Connecting to ${wsUrl}`);
9385
10409
  const cdp = await CdpClient.connect(wsUrl, this.cdpLogger);
9386
10410
  this.cdp = cdp;
10411
+ this.daemonLogger.info("cdp.connected", `CDP connected to ${wsUrl}`);
9387
10412
  this.setupCdpEventHandlers(cdp);
9388
10413
  await cdp.enableDomains();
9389
10414
  if (this.blackboxPatterns.length > 0) {
@@ -9490,7 +10515,11 @@ class DebugSession {
9490
10515
  });
9491
10516
  }
9492
10517
  monitorProcessExit(proc) {
9493
- proc.exited.then(() => {
10518
+ proc.exited.then((exitCode) => {
10519
+ this.daemonLogger.info("child.exit", `Process exited with code ${exitCode ?? "unknown"}`, {
10520
+ pid: proc.pid,
10521
+ exitCode: exitCode ?? null
10522
+ });
9494
10523
  this.childProcess = null;
9495
10524
  if (this.cdp) {
9496
10525
  this.cdp.disconnect();
@@ -9499,7 +10528,10 @@ class DebugSession {
9499
10528
  this.state = "idle";
9500
10529
  this.pauseInfo = null;
9501
10530
  this.onProcessExit?.();
9502
- }).catch(() => {
10531
+ }).catch((err) => {
10532
+ this.daemonLogger.error("child.exit.error", `Error waiting for process exit: ${err}`, {
10533
+ pid: proc.pid
10534
+ });
9503
10535
  this.childProcess = null;
9504
10536
  this.state = "idle";
9505
10537
  this.pauseInfo = null;
@@ -9518,16 +10550,22 @@ class DebugSession {
9518
10550
  if (done) {
9519
10551
  break;
9520
10552
  }
9521
- accumulated += decoder.decode(value, { stream: true });
10553
+ const chunk = decoder.decode(value, { stream: true });
10554
+ accumulated += chunk;
10555
+ this.daemonLogger.debug("child.stderr", chunk.trimEnd());
9522
10556
  const match = INSPECTOR_URL_REGEX.exec(accumulated);
9523
10557
  if (match?.[1]) {
9524
10558
  clearTimeout(timeout);
9525
- reader.releaseLock();
10559
+ this.drainReader(reader);
9526
10560
  return match[1];
9527
10561
  }
9528
10562
  }
9529
10563
  } catch {}
9530
10564
  clearTimeout(timeout);
10565
+ this.daemonLogger.error("inspector.failed", "Failed to detect inspector URL", {
10566
+ stderr: accumulated.slice(0, 2000),
10567
+ timeoutMs: INSPECTOR_TIMEOUT_MS
10568
+ });
9531
10569
  throw new Error(`Failed to detect inspector URL within ${INSPECTOR_TIMEOUT_MS}ms. Stderr: ${accumulated.slice(0, 500)}`);
9532
10570
  }
9533
10571
  async discoverWsUrl(port) {
@@ -9552,12 +10590,22 @@ class DebugSession {
9552
10590
  }
9553
10591
  return wsUrl;
9554
10592
  }
10593
+ drainReader(reader) {
10594
+ const pump = () => {
10595
+ reader.read().then(({ done }) => {
10596
+ if (!done)
10597
+ pump();
10598
+ }).catch(() => {});
10599
+ };
10600
+ pump();
10601
+ }
9555
10602
  }
9556
10603
  var INSPECTOR_URL_REGEX, INSPECTOR_TIMEOUT_MS = 5000;
9557
- var init_session = __esm(() => {
9558
- init_logger();
10604
+ var init_session2 = __esm(() => {
10605
+ init_logger2();
9559
10606
  init_ref_table();
9560
10607
  init_resolver();
10608
+ init_logger();
9561
10609
  init_paths();
9562
10610
  init_session_inspection();
9563
10611
  init_session_mutation();
@@ -9567,10 +10615,19 @@ var init_session = __esm(() => {
9567
10615
 
9568
10616
  // src/daemon/entry.ts
9569
10617
  var exports_entry = {};
9570
- var daemonIdx, session, timeout = 300, timeoutIdx, server, debugSession;
10618
+ function isDapRuntime(runtime) {
10619
+ return runtime !== undefined && runtime !== "node";
10620
+ }
10621
+ function activeSession() {
10622
+ return dapSession ?? cdpSession;
10623
+ }
10624
+ var daemonIdx, session, timeout = 300, timeoutIdx, daemonLogger, server, cdpSession, dapSession = null;
9571
10625
  var init_entry = __esm(async () => {
9572
- init_server();
9573
10626
  init_session();
10627
+ init_logger();
10628
+ init_paths();
10629
+ init_server();
10630
+ init_session2();
9574
10631
  daemonIdx = process.argv.indexOf("--daemon");
9575
10632
  session = daemonIdx !== -1 ? process.argv[daemonIdx + 1] : process.argv[2];
9576
10633
  if (!session) {
@@ -9587,53 +10644,70 @@ var init_entry = __esm(async () => {
9587
10644
  }
9588
10645
  }
9589
10646
  }
9590
- server = new DaemonServer(session, { idleTimeout: timeout });
9591
- debugSession = new DebugSession(session);
10647
+ ensureSocketDir();
10648
+ daemonLogger = new DaemonLogger(getDaemonLogPath(session));
10649
+ daemonLogger.info("daemon.start", `Daemon starting for session "${session}"`, {
10650
+ pid: process.pid,
10651
+ session,
10652
+ timeout
10653
+ });
10654
+ server = new DaemonServer(session, { idleTimeout: timeout, logger: daemonLogger });
10655
+ cdpSession = new DebugSession(session, { daemonLogger });
9592
10656
  server.onRequest(async (req) => {
9593
10657
  switch (req.cmd) {
9594
10658
  case "ping":
9595
10659
  return { ok: true, data: "pong" };
9596
10660
  case "launch": {
9597
- const { command, brk = true, port } = req.args;
9598
- const result = await debugSession.launch(command, { brk, port });
10661
+ const { command, brk = true, port, runtime } = req.args;
10662
+ if (isDapRuntime(runtime)) {
10663
+ dapSession = new DapSession(session, runtime);
10664
+ const result2 = await dapSession.launch(command, { brk });
10665
+ return { ok: true, data: result2 };
10666
+ }
10667
+ const result = await cdpSession.launch(command, { brk, port });
9599
10668
  return { ok: true, data: result };
9600
10669
  }
9601
10670
  case "attach": {
9602
- const { target } = req.args;
9603
- const result = await debugSession.attach(target);
10671
+ const { target, runtime } = req.args;
10672
+ if (isDapRuntime(runtime)) {
10673
+ dapSession = new DapSession(session, runtime);
10674
+ const result2 = await dapSession.attach(target);
10675
+ return { ok: true, data: result2 };
10676
+ }
10677
+ const result = await cdpSession.attach(target);
9604
10678
  return { ok: true, data: result };
9605
10679
  }
9606
10680
  case "status":
9607
- return { ok: true, data: debugSession.getStatus() };
10681
+ return { ok: true, data: activeSession().getStatus() };
9608
10682
  case "state": {
9609
- const stateResult = await debugSession.buildState(req.args);
10683
+ const stateResult = await activeSession().buildState(req.args);
9610
10684
  return { ok: true, data: stateResult };
9611
10685
  }
9612
10686
  case "continue": {
9613
- await debugSession.continue();
9614
- const stateAfter = await debugSession.buildState();
10687
+ await activeSession().continue();
10688
+ const stateAfter = await activeSession().buildState();
9615
10689
  return { ok: true, data: stateAfter };
9616
10690
  }
9617
10691
  case "step": {
9618
10692
  const { mode = "over" } = req.args;
9619
- await debugSession.step(mode);
9620
- const stateAfter = await debugSession.buildState();
10693
+ await activeSession().step(mode);
10694
+ const stateAfter = await activeSession().buildState();
9621
10695
  return { ok: true, data: stateAfter };
9622
10696
  }
9623
10697
  case "pause": {
9624
- await debugSession.pause();
9625
- const stateAfter = await debugSession.buildState();
10698
+ await activeSession().pause();
10699
+ const stateAfter = await activeSession().buildState();
9626
10700
  return { ok: true, data: stateAfter };
9627
10701
  }
9628
10702
  case "run-to": {
9629
10703
  const { file: file2, line } = req.args;
9630
- await debugSession.runTo(file2, line);
9631
- const stateAfter = await debugSession.buildState();
10704
+ await activeSession().runTo(file2, line);
10705
+ const stateAfter = await activeSession().buildState();
9632
10706
  return { ok: true, data: stateAfter };
9633
10707
  }
9634
10708
  case "break": {
9635
10709
  const { file: file2, line, condition, hitCount, urlRegex: urlRegex2, column } = req.args;
9636
- const bpResult = await debugSession.setBreakpoint(file2, line, {
10710
+ const bpResult = await activeSession().setBreakpoint(file2, line, {
9637
10711
  condition,
9638
10712
  hitCount,
9639
10713
  urlRegex: urlRegex2,
@@ -9641,20 +10715,35 @@ var init_entry = __esm(async () => {
9641
10715
  });
9642
10716
  return { ok: true, data: bpResult };
9643
10717
  }
10718
+ case "break-fn": {
10719
+ const session2 = activeSession();
10720
+ if (!("setFunctionBreakpoint" in session2)) {
10721
+ return {
10722
+ ok: false,
10723
+ error: "Function breakpoints are only supported with DAP runtimes (e.g. --runtime lldb)",
10724
+ suggestion: "Use 'break <file>:<line>' for CDP sessions"
10725
+ };
10726
+ }
10727
+ const { name, condition } = req.args;
10728
+ const bpResult = await session2.setFunctionBreakpoint(name, {
10729
+ condition
10730
+ });
10731
+ return { ok: true, data: bpResult };
10732
+ }
9644
10733
  case "break-rm": {
9645
10734
  const { ref } = req.args;
9646
10735
  if (ref === "all") {
9647
- await debugSession.removeAllBreakpoints();
10736
+ await activeSession().removeAllBreakpoints();
9648
10737
  return { ok: true, data: "all removed" };
9649
10738
  }
9650
- await debugSession.removeBreakpoint(ref);
10739
+ await activeSession().removeBreakpoint(ref);
9651
10740
  return { ok: true, data: "removed" };
9652
10741
  }
9653
10742
  case "break-ls":
9654
- return { ok: true, data: debugSession.listBreakpoints() };
10743
+ return { ok: true, data: activeSession().listBreakpoints() };
9655
10744
  case "logpoint": {
9656
10745
  const { file: file2, line, template, condition, maxEmissions } = req.args;
9657
- const lpResult = await debugSession.setLogpoint(file2, line, template, {
10746
+ const lpResult = await activeSession().setLogpoint(file2, line, template, {
9658
10747
  condition,
9659
10748
  maxEmissions
9660
10749
  });
@@ -9662,114 +10751,115 @@ var init_entry = __esm(async () => {
9662
10751
  }
9663
10752
  case "catch": {
9664
10753
  const { mode } = req.args;
9665
- await debugSession.setExceptionPause(mode);
10754
+ await activeSession().setExceptionPause(mode);
9666
10755
  return { ok: true, data: mode };
9667
10756
  }
9668
10757
  case "source": {
9669
- const sourceResult = await debugSession.getSource(req.args);
10758
+ const sourceResult = await activeSession().getSource(req.args);
9670
10759
  return { ok: true, data: sourceResult };
9671
10760
  }
9672
10761
  case "scripts": {
9673
10762
  const { filter } = req.args;
9674
- const scriptsResult = debugSession.getScripts(filter);
10763
+ const scriptsResult = activeSession().getScripts(filter);
9675
10764
  return { ok: true, data: scriptsResult };
9676
10765
  }
9677
10766
  case "stack": {
9678
- const stackResult = debugSession.getStack(req.args);
10767
+ const stackResult = activeSession().getStack(req.args);
9679
10768
  return { ok: true, data: stackResult };
9680
10769
  }
9681
10770
  case "search": {
9682
10771
  const { query, ...searchOptions } = req.args;
9683
- const searchResult = await debugSession.searchInScripts(query, searchOptions);
10772
+ const searchResult = await activeSession().searchInScripts(query, searchOptions);
9684
10773
  return { ok: true, data: searchResult };
9685
10774
  }
9686
10775
  case "console": {
9687
- const consoleResult = debugSession.getConsoleMessages(req.args);
10776
+ const consoleResult = activeSession().getConsoleMessages(req.args);
9688
10777
  return { ok: true, data: consoleResult };
9689
10778
  }
9690
10779
  case "exceptions": {
9691
- const exceptionsResult = debugSession.getExceptions(req.args);
10780
+ const exceptionsResult = activeSession().getExceptions(req.args);
9692
10781
  return { ok: true, data: exceptionsResult };
9693
10782
  }
9694
10783
  case "eval": {
9695
10784
  const { expression, ...evalOptions } = req.args;
9696
- const evalResult = await debugSession.eval(expression, evalOptions);
10785
+ const evalResult = await activeSession().eval(expression, evalOptions);
9697
10786
  return { ok: true, data: evalResult };
9698
10787
  }
9699
10788
  case "vars": {
9700
- const varsResult = await debugSession.getVars(req.args);
10789
+ const varsResult = await activeSession().getVars(req.args);
9701
10790
  return { ok: true, data: varsResult };
9702
10791
  }
9703
10792
  case "props": {
9704
10793
  const { ref, ...propsOptions } = req.args;
9705
- const propsResult = await debugSession.getProps(ref, propsOptions);
10794
+ const propsResult = await activeSession().getProps(ref, propsOptions);
9706
10795
  return { ok: true, data: propsResult };
9707
10796
  }
9708
10797
  case "blackbox": {
9709
10798
  const { patterns } = req.args;
9710
- const result = await debugSession.addBlackbox(patterns);
10799
+ const result = await activeSession().addBlackbox(patterns);
9711
10800
  return { ok: true, data: result };
9712
10801
  }
9713
10802
  case "blackbox-ls": {
9714
- return { ok: true, data: debugSession.listBlackbox() };
10803
+ return { ok: true, data: activeSession().listBlackbox() };
9715
10804
  }
9716
10805
  case "blackbox-rm": {
9717
10806
  const { patterns } = req.args;
9718
- const result = await debugSession.removeBlackbox(patterns);
10807
+ const result = await activeSession().removeBlackbox(patterns);
9719
10808
  return { ok: true, data: result };
9720
10809
  }
9721
10810
  case "set": {
9722
10811
  const { name, value, frame } = req.args;
9723
- const result = await debugSession.setVariable(name, value, { frame });
10812
+ const result = await activeSession().setVariable(name, value, { frame });
9724
10813
  return { ok: true, data: result };
9725
10814
  }
9726
10815
  case "set-return": {
9727
10816
  const { value } = req.args;
9728
- const result = await debugSession.setReturnValue(value);
10817
+ const result = await activeSession().setReturnValue(value);
9729
10818
  return { ok: true, data: result };
9730
10819
  }
9731
10820
  case "hotpatch": {
9732
10821
  const { file: file2, source, dryRun } = req.args;
9733
- const result = await debugSession.hotpatch(file2, source, { dryRun });
10822
+ const result = await activeSession().hotpatch(file2, source, { dryRun });
9734
10823
  return { ok: true, data: result };
9735
10824
  }
9736
10825
  case "break-toggle": {
9737
10826
  const { ref } = req.args;
9738
- const toggleResult = await debugSession.toggleBreakpoint(ref);
10827
+ const toggleResult = await activeSession().toggleBreakpoint(ref);
9739
10828
  return { ok: true, data: toggleResult };
9740
10829
  }
9741
10830
  case "breakable": {
9742
10831
  const { file: file2, startLine, endLine } = req.args;
9743
- const breakableResult = await debugSession.getBreakableLocations(file2, startLine, endLine);
10832
+ const breakableResult = await activeSession().getBreakableLocations(file2, startLine, endLine);
9744
10833
  return { ok: true, data: breakableResult };
9745
10834
  }
9746
10835
  case "restart-frame": {
9747
10836
  const { frameRef } = req.args;
9748
- const restartResult = await debugSession.restartFrame(frameRef);
10837
+ const restartResult = await activeSession().restartFrame(frameRef);
9749
10838
  return { ok: true, data: restartResult };
9750
10839
  }
9751
10840
  case "sourcemap": {
9752
10841
  const { file: smFile } = req.args;
9753
10842
  if (smFile) {
9754
- const match = debugSession.sourceMapResolver.findScriptForSource(smFile);
10843
+ const match = activeSession().sourceMapResolver.findScriptForSource(smFile);
9755
10844
  if (match) {
9756
- const info = debugSession.sourceMapResolver.getInfo(match.scriptId);
10845
+ const info = activeSession().sourceMapResolver.getInfo(match.scriptId);
9757
10846
  return { ok: true, data: info ? [info] : [] };
9758
10847
  }
9759
10848
  return { ok: true, data: [] };
9760
10849
  }
9761
- return { ok: true, data: debugSession.sourceMapResolver.getAllInfos() };
10850
+ return { ok: true, data: activeSession().sourceMapResolver.getAllInfos() };
9762
10851
  }
9763
10852
  case "sourcemap-disable": {
9764
- debugSession.sourceMapResolver.setDisabled(true);
10853
+ activeSession().sourceMapResolver.setDisabled(true);
9765
10854
  return { ok: true, data: "disabled" };
9766
10855
  }
9767
10856
  case "restart": {
9768
- const result = await debugSession.restart();
10857
+ const result = await activeSession().restart();
9769
10858
  return { ok: true, data: result };
9770
10859
  }
9771
10860
  case "stop":
9772
- await debugSession.stop();
10861
+ await activeSession().stop();
10862
+ dapSession = null;
9773
10863
  setTimeout(() => {
9774
10864
  server.stop();
9775
10865
  process.exit(0);
@@ -9790,7 +10880,7 @@ var init_registry = __esm(() => {
9790
10880
  });
9791
10881
 
9792
10882
  // src/daemon/client.ts
9793
- import { existsSync as existsSync3, readdirSync } from "fs";
10883
+ import { existsSync as existsSync3, readdirSync, readFileSync, unlinkSync as unlinkSync2 } from "fs";
9794
10884
 
9795
10885
  class DaemonClient {
9796
10886
  session;
@@ -9810,9 +10900,9 @@ class DaemonClient {
9810
10900
  const timer = setTimeout(() => {
9811
10901
  if (!settled) {
9812
10902
  settled = true;
9813
- reject(new Error(`Request timed out after ${DEFAULT_TIMEOUT_MS2}ms`));
10903
+ reject(new Error(`Request timed out after ${DEFAULT_TIMEOUT_MS3}ms`));
9814
10904
  }
9815
- }, DEFAULT_TIMEOUT_MS2);
10905
+ }, DEFAULT_TIMEOUT_MS3);
9816
10906
  Bun.connect({
9817
10907
  unix: socketPath,
9818
10908
  socket: {
@@ -9890,12 +10980,28 @@ class DaemonClient {
9890
10980
  if (!existsSync3(socketPath)) {
9891
10981
  return false;
9892
10982
  }
10983
+ const lockPath = getLockPath(session2);
10984
+ if (!existsSync3(lockPath)) {
10985
+ return false;
10986
+ }
9893
10987
  try {
10988
+ const pid = parseInt(readFileSync(lockPath, "utf-8"), 10);
10989
+ if (Number.isNaN(pid))
10990
+ return false;
10991
+ process.kill(pid, 0);
9894
10992
  return true;
9895
10993
  } catch {
9896
10994
  return false;
9897
10995
  }
9898
10996
  }
10997
+ static cleanStaleFiles(session2) {
10998
+ const socketPath = getSocketPath(session2);
10999
+ const lockPath = getLockPath(session2);
11000
+ if (existsSync3(socketPath))
11001
+ unlinkSync2(socketPath);
11002
+ if (existsSync3(lockPath))
11003
+ unlinkSync2(lockPath);
11004
+ }
9899
11005
  static async isAlive(session2) {
9900
11006
  const socketPath = getSocketPath(session2);
9901
11007
  if (!existsSync3(socketPath)) {
@@ -9918,20 +11024,20 @@ class DaemonClient {
9918
11024
  return files.filter((f) => f.endsWith(".sock")).map((f) => f.slice(0, -5));
9919
11025
  }
9920
11026
  }
9921
- var DEFAULT_TIMEOUT_MS2 = 30000;
11027
+ var DEFAULT_TIMEOUT_MS3 = 30000;
9922
11028
  var init_client = __esm(() => {
9923
11029
  init_messages();
9924
11030
  init_paths();
9925
11031
  });
9926
11032
 
9927
11033
  // src/daemon/spawn.ts
9928
- import { existsSync as existsSync4 } from "fs";
11034
+ import { existsSync as existsSync4, openSync, readFileSync as readFileSync2 } from "fs";
9929
11035
  async function spawnDaemon(session2, options = {}) {
9930
11036
  const socketPath = getSocketPath(session2);
9931
11037
  const spawnArgs = [];
9932
11038
  const execPath = process.execPath;
9933
11039
  const scriptPath = process.argv[1];
9934
- if (scriptPath && scriptPath.endsWith(".ts")) {
11040
+ if (scriptPath && (scriptPath.endsWith(".ts") || scriptPath.endsWith(".js"))) {
9935
11041
  spawnArgs.push(execPath, "run", scriptPath);
9936
11042
  } else {
9937
11043
  spawnArgs.push(execPath);
@@ -9940,11 +11046,13 @@ async function spawnDaemon(session2, options = {}) {
9940
11046
  if (options.timeout !== undefined) {
9941
11047
  spawnArgs.push("--timeout", String(options.timeout));
9942
11048
  }
11049
+ ensureSocketDir();
11050
+ const logFd = openSync(getDaemonLogPath(session2), "a");
9943
11051
  const proc = Bun.spawn(spawnArgs, {
9944
11052
  detached: true,
9945
11053
  stdin: "ignore",
9946
- stdout: "ignore",
9947
- stderr: "ignore"
11054
+ stdout: logFd,
11055
+ stderr: logFd
9948
11056
  });
9949
11057
  proc.unref();
9950
11058
  const deadline = Date.now() + SPAWN_TIMEOUT_MS;
@@ -9954,10 +11062,34 @@ async function spawnDaemon(session2, options = {}) {
9954
11062
  }
9955
11063
  await Bun.sleep(POLL_INTERVAL_MS);
9956
11064
  }
9957
- throw new Error(`Daemon for session "${session2}" failed to start within ${SPAWN_TIMEOUT_MS}ms`);
11065
+ const logPath = getDaemonLogPath(session2);
11066
+ let logTail = "";
11067
+ try {
11068
+ const log = readFileSync2(logPath, "utf-8");
11069
+ const lines = log.trimEnd().split(`
11070
+ `);
11071
+ logTail = lines.slice(-20).join(`
11072
+ `);
11073
+ } catch {}
11074
+ const details = [
11075
+ `Daemon for session "${session2}" failed to start within ${SPAWN_TIMEOUT_MS}ms`,
11076
+ `Spawn command: ${spawnArgs.join(" ")}`,
11077
+ `Socket path: ${socketPath}`,
11078
+ logTail ? `Daemon log (last 20 lines):
11079
+ ${logTail}` : `No daemon log at ${logPath}`
11080
+ ].join(`
11081
+ `);
11082
+ throw new Error(details);
11083
+ }
11084
+ async function ensureDaemon(session2, options) {
11085
+ if (DaemonClient.isRunning(session2))
11086
+ return;
11087
+ DaemonClient.cleanStaleFiles(session2);
11088
+ await spawnDaemon(session2, options);
9958
11089
  }
9959
11090
  var POLL_INTERVAL_MS = 50, SPAWN_TIMEOUT_MS = 5000;
9960
11091
  var init_spawn = __esm(() => {
11092
+ init_client();
9961
11093
  init_paths();
9962
11094
  });
9963
11095
 
@@ -9995,17 +11127,16 @@ var init_launch = __esm(() => {
9995
11127
  const brk = args.flags.brk === true;
9996
11128
  const port = typeof args.flags.port === "string" ? parseInt(args.flags.port, 10) : undefined;
9997
11129
  const timeout2 = typeof args.flags.timeout === "string" ? parseInt(args.flags.timeout, 10) : undefined;
11130
+ const runtime = typeof args.flags.runtime === "string" ? args.flags.runtime : undefined;
9998
11131
  const command = args.subcommand ? [args.subcommand, ...args.positionals] : [...args.positionals];
9999
11132
  if (command.length === 0) {
10000
11133
  console.error("No command specified");
10001
11134
  console.error(" -> Try: agent-dbg launch --brk node app.js");
10002
11135
  return 1;
10003
11136
  }
10004
- if (!DaemonClient.isRunning(session2)) {
10005
- await spawnDaemon(session2, { timeout: timeout2 });
10006
- }
11137
+ await ensureDaemon(session2, { timeout: timeout2 });
10007
11138
  const client = new DaemonClient(session2);
10008
- const response = await client.request("launch", { command, brk, port });
11139
+ const response = await client.request("launch", { command, brk, port, runtime });
10009
11140
  if (!response.ok) {
10010
11141
  console.error(`${response.error}`);
10011
11142
  if (response.suggestion)
@@ -10038,6 +11169,7 @@ var init_attach = __esm(() => {
10038
11169
  registerCommand("attach", async (args) => {
10039
11170
  const session2 = args.global.session;
10040
11171
  const target = args.subcommand ?? args.positionals[0];
11172
+ const runtime = typeof args.flags.runtime === "string" ? args.flags.runtime : undefined;
10041
11173
  if (!target) {
10042
11174
  console.error("No target specified");
10043
11175
  console.error(" -> Try: agent-dbg attach <ws-url | port>");
@@ -10049,9 +11181,9 @@ var init_attach = __esm(() => {
10049
11181
  return 1;
10050
11182
  }
10051
11183
  const timeout2 = typeof args.flags.timeout === "string" ? parseInt(args.flags.timeout, 10) : undefined;
10052
- await spawnDaemon(session2, { timeout: timeout2 });
11184
+ await ensureDaemon(session2, { timeout: timeout2 });
10053
11185
  const client = new DaemonClient(session2);
10054
- const response = await client.request("attach", { target });
11186
+ const response = await client.request("attach", { target, runtime });
10055
11187
  if (!response.ok) {
10056
11188
  console.error(`${response.error}`);
10057
11189
  if (response.suggestion)
@@ -10811,6 +11943,44 @@ var init_break = __esm(() => {
10811
11943
  });
10812
11944
  });
10813
11945
 
11946
+ // src/commands/break-fn.ts
11947
+ var exports_break_fn = {};
11948
+ var init_break_fn = __esm(() => {
11949
+ init_registry();
11950
+ init_client();
11951
+ registerCommand("break-fn", async (args) => {
11952
+ const session2 = args.global.session;
11953
+ if (!DaemonClient.isRunning(session2)) {
11954
+ console.error(`No active session "${session2}"`);
11955
+ console.error(" -> Try: agent-dbg launch --brk --runtime lldb ./program");
11956
+ return 1;
11957
+ }
11958
+ const name = args.subcommand;
11959
+ if (!name) {
11960
+ console.error("Usage: agent-dbg break-fn <function-name>");
11961
+ console.error(" Example: agent-dbg break-fn __assert_rtn");
11962
+ console.error(" Example: agent-dbg break-fn 'yoga::Style::operator=='");
11963
+ return 1;
11964
+ }
11965
+ const condition = typeof args.flags.condition === "string" ? args.flags.condition : undefined;
11966
+ const client = new DaemonClient(session2);
11967
+ const response = await client.request("break-fn", { name, condition });
11968
+ if (!response.ok) {
11969
+ console.error(`${response.error}`);
11970
+ if (response.suggestion)
11971
+ console.error(` ${response.suggestion}`);
11972
+ return 1;
11973
+ }
11974
+ const data = response.data;
11975
+ if (args.global.json) {
11976
+ console.log(JSON.stringify(data, null, 2));
11977
+ } else {
11978
+ console.log(`${data.ref} fn:${name}`);
11979
+ }
11980
+ return 0;
11981
+ });
11982
+ });
11983
+
10814
11984
  // src/commands/break-rm.ts
10815
11985
  var exports_break_rm = {};
10816
11986
  var init_break_rm = __esm(() => {
@@ -12006,7 +13176,13 @@ function formatLogEntry(entry) {
12006
13176
  const summary = summarizer ? summarizer(entry) : summarizeParams(entry);
12007
13177
  return `${time3} <- ${entry.method}${summary ? ` ${summary}` : ""}`;
12008
13178
  }
12009
- var eventSummarizers, responseSummarizers;
13179
+ function formatDaemonLogEntry(entry) {
13180
+ const time3 = `[${formatTime(entry.ts)}]`;
13181
+ const level = levelColors[entry.level] ?? entry.level.toUpperCase();
13182
+ const data = entry.data ? ` ${truncate2(JSON.stringify(entry.data), 120)}` : "";
13183
+ return `${time3} ${level} ${entry.event}: ${entry.message}${data}`;
13184
+ }
13185
+ var eventSummarizers, responseSummarizers, levelColors;
12010
13186
  var init_logs = __esm(() => {
12011
13187
  eventSummarizers = {
12012
13188
  "Debugger.scriptParsed": (e) => {
@@ -12063,6 +13239,12 @@ var init_logs = __esm(() => {
12063
13239
  return `breakpointId=${r.breakpointId}`;
12064
13240
  }
12065
13241
  };
13242
+ levelColors = {
13243
+ info: "INFO ",
13244
+ warn: "WARN ",
13245
+ error: "ERROR",
13246
+ debug: "DEBUG"
13247
+ };
12066
13248
  });
12067
13249
 
12068
13250
  // src/commands/logs.ts
@@ -12070,13 +13252,25 @@ var exports_logs = {};
12070
13252
  import {
12071
13253
  closeSync,
12072
13254
  existsSync as existsSync5,
12073
- openSync,
12074
- readFileSync,
13255
+ openSync as openSync2,
13256
+ readFileSync as readFileSync3,
12075
13257
  readSync,
12076
13258
  watch,
12077
- writeFileSync as writeFileSync3
13259
+ writeFileSync as writeFileSync4
12078
13260
  } from "fs";
12079
- function parseEntries(text) {
13261
+ function parseCdpEntries(text) {
13262
+ const entries = [];
13263
+ for (const line of text.split(`
13264
+ `)) {
13265
+ if (!line.trim())
13266
+ continue;
13267
+ try {
13268
+ entries.push(JSON.parse(line));
13269
+ } catch {}
13270
+ }
13271
+ return entries;
13272
+ }
13273
+ function parseDaemonEntries(text) {
12080
13274
  const entries = [];
12081
13275
  for (const line of text.split(`
12082
13276
  `)) {
@@ -12091,7 +13285,10 @@ function parseEntries(text) {
12091
13285
  function filterByDomain(entries, domain) {
12092
13286
  return entries.filter((e) => e.method.startsWith(`${domain}.`));
12093
13287
  }
12094
- function printEntries(entries, json2) {
13288
+ function filterByLevel(entries, level) {
13289
+ return entries.filter((e) => e.level === level);
13290
+ }
13291
+ function printCdpEntries(entries, json2) {
12095
13292
  for (const entry of entries) {
12096
13293
  if (json2) {
12097
13294
  console.log(JSON.stringify(entry));
@@ -12100,37 +13297,56 @@ function printEntries(entries, json2) {
12100
13297
  }
12101
13298
  }
12102
13299
  }
13300
+ function printDaemonEntries(entries, json2) {
13301
+ for (const entry of entries) {
13302
+ if (json2) {
13303
+ console.log(JSON.stringify(entry));
13304
+ } else {
13305
+ console.log(formatDaemonLogEntry(entry));
13306
+ }
13307
+ }
13308
+ }
12103
13309
  var init_logs2 = __esm(() => {
12104
13310
  init_registry();
12105
13311
  init_paths();
12106
13312
  init_logs();
12107
13313
  registerCommand("logs", async (args) => {
12108
13314
  const session2 = args.global.session;
12109
- const logPath = getLogPath(session2);
13315
+ const isDaemon = args.flags.daemon === true;
13316
+ const logPath = isDaemon ? getDaemonLogPath(session2) : getLogPath(session2);
12110
13317
  if (args.flags.clear === true) {
12111
13318
  if (existsSync5(logPath)) {
12112
- writeFileSync3(logPath, "");
12113
- console.log("Log cleared");
13319
+ writeFileSync4(logPath, "");
13320
+ console.log(`${isDaemon ? "Daemon log" : "Log"} cleared`);
12114
13321
  } else {
12115
- console.log("No log file to clear");
13322
+ console.log(`No ${isDaemon ? "daemon " : ""}log file to clear`);
12116
13323
  }
12117
13324
  return 0;
12118
13325
  }
12119
13326
  if (!existsSync5(logPath)) {
12120
- console.error(`No log file for session "${session2}"`);
13327
+ console.error(`No ${isDaemon ? "daemon " : ""}log file for session "${session2}"`);
12121
13328
  console.error(" -> Try: agent-dbg launch --brk node app.js");
12122
13329
  return 1;
12123
13330
  }
12124
13331
  const isJson = args.global.json;
12125
13332
  const domain = typeof args.flags.domain === "string" ? args.flags.domain : undefined;
13333
+ const level = typeof args.flags.level === "string" ? args.flags.level : undefined;
12126
13334
  const limit = typeof args.flags.limit === "string" ? parseInt(args.flags.limit, 10) : 50;
12127
13335
  const follow = args.flags.follow === true;
12128
- const content = readFileSync(logPath, "utf-8");
12129
- let entries = parseEntries(content);
12130
- if (domain)
12131
- entries = filterByDomain(entries, domain);
12132
- const sliced = follow ? entries : entries.slice(-limit);
12133
- printEntries(sliced, isJson);
13336
+ const content = readFileSync3(logPath, "utf-8");
13337
+ if (isDaemon) {
13338
+ let entries = parseDaemonEntries(content);
13339
+ if (level)
13340
+ entries = filterByLevel(entries, level);
13341
+ const sliced = follow ? entries : entries.slice(-limit);
13342
+ printDaemonEntries(sliced, isJson);
13343
+ } else {
13344
+ let entries = parseCdpEntries(content);
13345
+ if (domain)
13346
+ entries = filterByDomain(entries, domain);
13347
+ const sliced = follow ? entries : entries.slice(-limit);
13348
+ printCdpEntries(sliced, isJson);
13349
+ }
12134
13350
  if (!follow)
12135
13351
  return 0;
12136
13352
  let offset = Buffer.byteLength(content, "utf-8");
@@ -12140,15 +13356,22 @@ var init_logs2 = __esm(() => {
12140
13356
  const size = Bun.file(logPath).size;
12141
13357
  if (size <= offset)
12142
13358
  return;
12143
- const fd = openSync(logPath, "r");
13359
+ const fd = openSync2(logPath, "r");
12144
13360
  const buf = Buffer.alloc(size - offset);
12145
13361
  readSync(fd, buf, 0, buf.length, offset);
12146
13362
  closeSync(fd);
12147
13363
  offset = size;
12148
- let newEntries = parseEntries(buf.toString("utf-8"));
12149
- if (domain)
12150
- newEntries = filterByDomain(newEntries, domain);
12151
- printEntries(newEntries, isJson);
13364
+ if (isDaemon) {
13365
+ let newEntries = parseDaemonEntries(buf.toString("utf-8"));
13366
+ if (level)
13367
+ newEntries = filterByLevel(newEntries, level);
13368
+ printDaemonEntries(newEntries, isJson);
13369
+ } else {
13370
+ let newEntries = parseCdpEntries(buf.toString("utf-8"));
13371
+ if (domain)
13372
+ newEntries = filterByDomain(newEntries, domain);
13373
+ printCdpEntries(newEntries, isJson);
13374
+ }
12152
13375
  } catch {}
12153
13376
  };
12154
13377
  watcher = watch(logPath, () => {
@@ -12469,6 +13692,7 @@ if (process.argv.includes("--daemon")) {
12469
13692
  await Promise.resolve().then(() => (init_pause(), exports_pause));
12470
13693
  await Promise.resolve().then(() => (init_run_to(), exports_run_to));
12471
13694
  await Promise.resolve().then(() => (init_break(), exports_break));
13695
+ await Promise.resolve().then(() => (init_break_fn(), exports_break_fn));
12472
13696
  await Promise.resolve().then(() => (init_break_rm(), exports_break_rm));
12473
13697
  await Promise.resolve().then(() => (init_break_ls(), exports_break_ls));
12474
13698
  await Promise.resolve().then(() => (init_logpoint(), exports_logpoint));