agent-dbg 0.1.3 → 0.1.5

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.
@@ -19,7 +19,33 @@
19
19
  "Bash(find:*)",
20
20
  "Bash(xargs sed:*)",
21
21
  "Bash(ps:*)",
22
- "Bash(ls:*)"
22
+ "Bash(ls:*)",
23
+ "WebSearch",
24
+ "Bash(lldb-dap:*)",
25
+ "WebFetch(domain:microsoft.github.io)",
26
+ "WebFetch(domain:github.com)",
27
+ "Bash(lldb:*)",
28
+ "Bash(xcode-select:*)",
29
+ "Bash(npx biome check:*)",
30
+ "Bash(brew install:*)",
31
+ "Bash(cc:*)",
32
+ "Bash(git add:*)",
33
+ "Bash(git reset:*)",
34
+ "Bash(git commit:*)",
35
+ "Bash(xcrun simctl:*)",
36
+ "Bash(kill:*)",
37
+ "Bash(socat:*)",
38
+ "Bash(for tag in 4 8 10 14 16 18)",
39
+ "Bash(do echo \"=== Tag $tag ===\")",
40
+ "Bash(done)",
41
+ "Bash(for ref in @v12 @v13 @v14 @v15 @v16 @v17)",
42
+ "Bash(do echo \"=== $ref ===\")",
43
+ "Bash(for ref in @v18 @v19 @v20 @v21 @v22 @v23)",
44
+ "Bash(for ref in @v34 @v35 @v36 @v37 @v38 @v39)",
45
+ "Bash(for ref in @v322 @v343 @v364)",
46
+ "Bash(for ref in @v365 @v366 @v367)",
47
+ "Bash(pgrep:*)",
48
+ "Bash(node:*)"
23
49
  ]
24
50
  }
25
51
  }
package/TODO.md ADDED
@@ -0,0 +1,299 @@
1
+ # TODO — Improvements from React Native Crash Debugging
2
+
3
+ ## Context
4
+
5
+ While debugging a React Native iOS crash (SIGABRT in Yoga C++ caused by `width: 'fit-content'` in a StyleSheet), we used agent-dbg with `--runtime lldb` (DAP mode) to attach to the running app, catch the exception, and walk the stack to identify the problematic component.
6
+
7
+ The full workflow was:
8
+ 1. `agent-dbg attach --runtime lldb <pid>` to connect to the RN app
9
+ 2. `agent-dbg catch all` to break on exceptions
10
+ 3. Trigger the crash in the app (toggle theme)
11
+ 4. `agent-dbg stack` to get 50 frames from `__pthread_kill` through Yoga to unistyles
12
+ 5. `agent-dbg vars --frame @fN` + `agent-dbg props @vN` repeated many times to drill into `ShadowNodeFamily` objects and find which component had the bad style
13
+
14
+ This revealed several friction points, ordered by impact for AI agent usage.
15
+
16
+ ---
17
+
18
+ ## High Impact
19
+
20
+ ### 1. Deep object inspection (`props --depth N`)
21
+
22
+ **Issue:** Inspecting a nested object graph requires one `props` call per level. To identify which of 6 React Native nodes had the bad style, we needed ~20 sequential commands: `props @v386` → find entry → `props @v40` → find ShadowNodeFamily → `props @v41` → find `componentName_`. Multiply by 6 nodes. Each call is a full daemon round-trip, burning tokens and time for an AI agent.
23
+
24
+ **Example:**
25
+ ```bash
26
+ # Current: 4 round-trips to reach componentName_
27
+ agent-dbg props @v386 # tagToProps map → 6 entries
28
+ agent-dbg props @v40 # entry 0 → {first: 4, second: @v41}
29
+ agent-dbg props @v41 # second → ShadowNodeFamily object
30
+ agent-dbg props @v42 # ShadowNodeFamily → {tag_: 4, componentName_: "View", ...}
31
+
32
+ # Desired: 1 round-trip
33
+ agent-dbg props @v386 --depth 3
34
+ # Returns the full tree expanded 3 levels deep
35
+ ```
36
+
37
+ **Suggested fix:** Add `--depth N` (default 1) to `props` and `vars` commands. In `getProps()`, after fetching immediate children, recursively call `getProps()` on any child that has a `variablesReference > 0` (DAP) or is an object type (CDP), up to the depth limit. Cap at depth 5 to avoid runaway expansion. Return a nested JSON structure instead of a flat list.
38
+
39
+ ---
40
+
41
+ ### 2. Watchpoints / data breakpoints
42
+
43
+ **Issue:** DAP supports `dataBreakpoints` — break when a specific memory address is written to. This would directly answer "when does this variable get set to the bad value?" instead of the multi-breakpoint stepping approach we tried (set BP at clone loop, step through each clone, check props each time). LLDB natively supports watchpoints (`watchpoint set variable foo`).
44
+
45
+ **Example:**
46
+ ```bash
47
+ # After finding the suspicious variable:
48
+ agent-dbg vars --frame @f288
49
+ # @v50 tagToProps size=6
50
+
51
+ # Desired: break when a specific tag's props are modified
52
+ agent-dbg watch @v50 --write
53
+ # → WP#1 set on tagToProps (0x7ff8a0012340), break on write
54
+
55
+ # Continue execution → breaks exactly when the bad style is applied
56
+ agent-dbg continue
57
+ # Paused: watchpoint WP#1 hit — tagToProps modified at ShadowTreeManager.cpp:34
58
+ ```
59
+
60
+ **Suggested fix:** Add a `watch` command that sends DAP `dataBreakpointInfo` (to check if a variable supports data breakpoints) followed by `setDataBreakpoints`. Track watchpoints in a `watchpoints` array similar to `functionBreakpoints`. For CDP mode, this isn't natively supported — return an error with suggestion.
61
+
62
+ ---
63
+
64
+ ### 3. Loaded modules / libraries
65
+
66
+ **Issue:** We spent ~30 minutes trying to eval C++ expressions at React framework frames (`this->tag_`, `this->getComponentName()`) before realizing the framework binary had no debug symbols (built on CI, `.o` files missing). There was no way to know upfront which frames had usable debug info and which didn't.
67
+
68
+ **Example:**
69
+ ```bash
70
+ # Current: blind trial-and-error
71
+ agent-dbg eval 'this->tag_' --frame @f300
72
+ # Error: invalid use of 'this' outside of a non-static member function
73
+ # (Why? No debug symbols — but the error message doesn't say that)
74
+
75
+ # Desired: check what has symbols first
76
+ agent-dbg modules
77
+ # MODULE SYMBOLS PATH
78
+ # MyApp full /path/to/MyApp.app/MyApp
79
+ # React.framework stripped /path/to/React.framework/React
80
+ # hermes.framework full /path/to/hermes.framework/hermes
81
+ # react-native-unistyles.framework full /path/to/unistyles.framework/...
82
+ ```
83
+
84
+ **Suggested fix:** Add a `modules` command that sends DAP `modules` request. Display module name, symbol status (full/stripped/none), version, and path. Optionally filter: `agent-dbg modules --filter yoga`. For CDP, return the loaded scripts list (already available via `getScripts`).
85
+
86
+ ---
87
+
88
+ ### 4. Attach by process name
89
+
90
+ **Issue:** Every debugging session required manually finding the PID (`xcrun simctl spawn booted launchctl list | grep MyApp`, or `pgrep -f MyApp`). DAP's attach request accepts a process `name` field, but we only exposed `target` (which expects a PID or WebSocket URL).
91
+
92
+ **Example:**
93
+ ```bash
94
+ # Current: manual PID lookup
95
+ pgrep -f "debug-rn-wrong-dimensions" # → 12345
96
+ agent-dbg attach --runtime lldb 12345
97
+
98
+ # Desired: attach by name
99
+ agent-dbg attach --runtime lldb --name "debug-rn-wrong-dimensions"
100
+ # Attached to PID 12345 (debug-rn-wrong-dimensions)
101
+ ```
102
+
103
+ **Suggested fix:** In `DapSession.attach()`, detect if `target` is numeric (PID) or a string (process name). If string, pass `{ name: target }` instead of `{ pid: parseInt(target) }` in the DAP attach request. LLDB-DAP supports both. For CDP, keep existing behavior (target is always a WebSocket URL or port).
104
+
105
+ ---
106
+
107
+ ### 5. `run-to` for DAP
108
+
109
+ **Issue:** `run-to` is not implemented for DAP sessions. We had to manually set a breakpoint, continue, then remove it — 3 commands instead of 1. This pattern was used repeatedly in the investigation scripts.
110
+
111
+ **Example:**
112
+ ```bash
113
+ # Current: 3 commands
114
+ agent-dbg break ShadowTreeManager.cpp:22
115
+ # BP#5 set
116
+ agent-dbg continue
117
+ # Paused at ShadowTreeManager.cpp:22
118
+ agent-dbg break-rm BP#5
119
+
120
+ # Desired: 1 command
121
+ agent-dbg run-to ShadowTreeManager.cpp:22
122
+ # Paused at ShadowTreeManager.cpp:22 (temporary breakpoint auto-removed)
123
+ ```
124
+
125
+ **Suggested fix:** Implement `DapSession.runTo(file, line)` by: (1) setting a temporary breakpoint via `setBreakpoints` (add to existing file breakpoints), (2) calling `continue`, (3) on stop, removing the temporary breakpoint. Alternatively, some DAP adapters support `goto` targets — check adapter capabilities first.
126
+
127
+ ---
128
+
129
+ ## Medium Impact
130
+
131
+ ### 6. Disassembly view
132
+
133
+ **Issue:** When source code isn't available (framework built on CI without debug symbols), the `source` command returns nothing useful. Disassembly is the only way to understand what's executing. DAP has a `disassemble` request that returns instruction-level code, but we never exposed it.
134
+
135
+ **Example:**
136
+ ```bash
137
+ # Current: no source available
138
+ agent-dbg source --frame @f300
139
+ # Error: no source available for this frame
140
+
141
+ # Desired: fall back to disassembly
142
+ agent-dbg disassemble --frame @f300
143
+ # 0x1a2b3c40 stp x29, x30, [sp, #-16]!
144
+ # 0x1a2b3c44 mov x29, sp
145
+ # 0x1a2b3c48 bl 0x1a2b4000 ; yoga::Style::operator==
146
+ # → 0x1a2b3c4c cbz w0, 0x1a2b3c60 ; <-- current instruction
147
+ # 0x1a2b3c50 ldr x8, [x19, #24]
148
+ ```
149
+
150
+ **Suggested fix:** Add a `disassemble` command that sends DAP `disassemble` request with the current frame's `instructionPointerReference`. Format output with addresses, mnemonics, and an arrow marking the current instruction. For CDP, this isn't applicable — return an error.
151
+
152
+ ---
153
+
154
+ ### 7. Memory read
155
+
156
+ **Issue:** We couldn't inspect `folly::dynamic` internals because LLDB's expression evaluator didn't understand F14 hash maps. But with raw memory access + known struct layouts, we could have manually decoded the data. DAP has `readMemory` request.
157
+
158
+ **Example:**
159
+ ```bash
160
+ # Current: opaque object
161
+ agent-dbg eval '*(folly::dynamic::ObjectImpl*)(0x7ff8a0012340)'
162
+ # Error: no member named 'ObjectImpl' in namespace 'folly::dynamic'
163
+
164
+ # Desired: read raw memory
165
+ agent-dbg memory 0x7ff8a0012340 --count 128
166
+ # 0x7ff8a001_2340: 06 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 ................
167
+ # 0x7ff8a001_2350: 40 56 01 a0 f8 7f 00 00 00 00 00 00 00 00 00 00 @V..............
168
+ ```
169
+
170
+ **Suggested fix:** Add a `memory` command that sends DAP `readMemory` request. Accept address (hex) and byte count. Format as hex dump with ASCII sidebar. For CDP, not applicable.
171
+
172
+ ---
173
+
174
+ ### 8. Exception breakpoint filters
175
+
176
+ **Issue:** Our `catch` command only supports `all/none/uncaught`. When debugging the RN crash, `catch all` caught every signal (including harmless SIGTRAPs from thread creation), requiring manual `continue` past irrelevant stops. DAP supports filter-based exception breakpoints — LLDB can catch only `SIGABRT`.
177
+
178
+ **Example:**
179
+ ```bash
180
+ # Current: catch everything
181
+ agent-dbg catch all
182
+ # Stops on SIGTRAP (thread created) — irrelevant, continue
183
+ # Stops on SIGTRAP (breakpoint) — irrelevant, continue
184
+ # Stops on SIGABRT — the actual crash
185
+
186
+ # Desired: catch specific signals
187
+ agent-dbg catch --filter SIGABRT
188
+ # Only stops on SIGABRT
189
+ ```
190
+
191
+ **Suggested fix:** Query the adapter's `exceptionBreakpointFilters` capability (returned in `initialize` response) and expose them. Add `--filter <name>` flag to `catch` command. Send `setExceptionBreakpoints` with selected filter IDs instead of blanket all/none.
192
+
193
+ ---
194
+
195
+ ### 9. Restart for DAP
196
+
197
+ **Issue:** After each investigation attempt, we had to `stop` the session and re-launch/re-attach manually. `restart` isn't implemented for DAP.
198
+
199
+ **Example:**
200
+ ```bash
201
+ # Current: 2 commands + re-specify args
202
+ agent-dbg stop
203
+ agent-dbg attach --runtime lldb 12345
204
+
205
+ # Desired: 1 command
206
+ agent-dbg restart
207
+ # Re-attached to PID 12345
208
+ ```
209
+
210
+ **Suggested fix:** Store the original launch/attach arguments in `DapSession`. On `restart()`, call `stop()` then replay the original `launch()` or `attach()` call. Some DAP adapters also support the `restart` request natively — check capabilities first.
211
+
212
+ ---
213
+
214
+ ### 10. Stack filtering
215
+
216
+ **Issue:** The crash produced 50 stack frames, but only ~5 were relevant (yoga layout, unistyles shadow tree update). An AI agent has to parse all 50 to find the interesting ones, wasting tokens.
217
+
218
+ **Example:**
219
+ ```bash
220
+ # Current: 50 frames, most are system/framework noise
221
+ agent-dbg stack
222
+ # @f0 __pthread_kill (libsystem_kernel.dylib)
223
+ # @f1 pthread_kill (libsystem_pthread.dylib)
224
+ # ... 45 more framework frames ...
225
+ # @f47 -[UIApplication sendAction:to:from:forEvent:]
226
+ # @f48 UIApplicationMain
227
+
228
+ # Desired: filter by keyword
229
+ agent-dbg stack --filter yoga
230
+ # @f5 yoga::StyleValuePool::getLength (yoga.cpp:1234)
231
+ # @f6 yoga::Node::setStyle (Node.cpp:567)
232
+ # @f8 facebook::react::updateYogaProps (Props.cpp:89)
233
+
234
+ # Or show only user code (non-system frames)
235
+ agent-dbg stack --user
236
+ # @f10 margelo::nitro::unistyles::HybridStyleSheet::onPlatformDependenciesChange
237
+ # @f12 margelo::nitro::unistyles::ShadowTreeManager::updateShadowTree
238
+ ```
239
+
240
+ **Suggested fix:** Add `--filter <keyword>` flag to `stack` command that filters frames by function name substring match. Add `--user` flag that excludes frames from system libraries (heuristic: exclude paths containing `/usr/lib/`, `libsystem_`, `UIKit`, `CoreFoundation`, etc.). Both are client-side filters on the existing stack data — no protocol changes needed.
241
+
242
+ ---
243
+
244
+ ## Low Impact
245
+
246
+ ### 11. Toggle breakpoint for DAP
247
+
248
+ **Issue:** `break-toggle` throws "not yet supported" for DAP. DAP tracks enabled/disabled state per breakpoint — when re-sending `setBreakpoints`, each breakpoint can have an `enabled` field set to `false`.
249
+
250
+ **Example:**
251
+ ```bash
252
+ agent-dbg break-toggle BP#3
253
+ # BP#3 disabled (was enabled)
254
+ ```
255
+
256
+ **Suggested fix:** Add an `enabled` field to `DapBreakpointEntry`. On toggle, flip the flag and re-sync breakpoints for that file via `setBreakpoints`, including `enabled: false` for the toggled one.
257
+
258
+ ---
259
+
260
+ ### 12. Loaded sources for DAP
261
+
262
+ **Issue:** `scripts` returns an empty array for DAP sessions. DAP has a `loadedSources` request that returns all source files known to the debugger.
263
+
264
+ **Example:**
265
+ ```bash
266
+ agent-dbg scripts --filter unistyles
267
+ # (empty — no scripts tracked in DAP mode)
268
+
269
+ # Desired:
270
+ # ShadowTreeManager.cpp /path/to/unistyles/ShadowTreeManager.cpp
271
+ # HybridStyleSheet.cpp /path/to/unistyles/HybridStyleSheet.cpp
272
+ ```
273
+
274
+ **Suggested fix:** Implement `DapSession.getScripts(filter?)` by sending DAP `loadedSources` request. Cache the result (it doesn't change often). Apply the filter client-side on the returned source names/paths.
275
+
276
+ ---
277
+
278
+ ### 13. Better eval error suggestions
279
+
280
+ **Issue:** When `eval` fails, the error message comes raw from the debugger with no actionable suggestions. For example, `this->tag_` fails with "invalid use of 'this' outside of a non-static member function" — but doesn't tell you *why* (no debug symbols) or *what to try instead*.
281
+
282
+ **Example:**
283
+ ```bash
284
+ # Current:
285
+ agent-dbg eval 'this->tag_' --frame @f300
286
+ # Error: invalid use of 'this' outside of a non-static member function
287
+
288
+ # Desired:
289
+ agent-dbg eval 'this->tag_' --frame @f300
290
+ # Error: invalid use of 'this' outside of a non-static member function
291
+ # -> This frame may lack debug symbols. Try 'agent-dbg modules' to check.
292
+ # -> Try accessing the variable directly: 'tag_'
293
+ # -> Try a different frame: 'agent-dbg eval 'this->tag_' --frame @f301'
294
+ ```
295
+
296
+ **Suggested fix:** In the eval error handler, pattern-match common LLDB error messages and append contextual suggestions. Examples:
297
+ - "invalid use of this" → suggest checking modules for symbols, try variable directly
298
+ - "no member named" → suggest `props` to list available members
299
+ - "use of undeclared identifier" → suggest `vars` to see what's in scope
package/demo/DEMO.md ADDED
@@ -0,0 +1,71 @@
1
+ # Demo: AI Agent Debugging with agent-dbg
2
+
3
+ ## Overview
4
+
5
+ This demo shows Claude Code autonomously debugging a buggy Node.js app using `agent-dbg` — finding root causes, inspecting variables, and hot-patching fixes **without restarting the process**.
6
+
7
+ **Total demo time: ~3 minutes**
8
+
9
+ ## The Buggy App
10
+
11
+ `order-processor.js` processes customer orders, applies discount codes, and calculates totals. It has **two bugs**:
12
+
13
+ | Bug | Type | Symptom |
14
+ |-----|------|---------|
15
+ | **Bug 1** — `calculateSubtotal` loop uses `<=` instead of `<` | Off-by-one | Crashes with `TypeError: Cannot read properties of undefined` |
16
+ | **Bug 2** — `applyDiscount` returns `subtotal * rate` instead of `subtotal * (1 - rate)` | Logic error | Returns the discount amount ($20) instead of the discounted price ($80) |
17
+
18
+ ### Expected correct output
19
+ ```
20
+ ORD-001: subtotal $125, total $100 (125 * 0.80)
21
+ ORD-002: subtotal $75, total $75 (no discount)
22
+ ORD-003: subtotal $225, total $180 (225 * 0.80)
23
+ ```
24
+
25
+ ## Running the Demo
26
+
27
+ ### Prompt to give Claude Code
28
+
29
+ ```
30
+ This order processor is crashing and the totals look wrong. Debug it with agent-dbg and fix both bugs without restarting the process.
31
+
32
+ The app is at demo/order-processor.js
33
+ ```
34
+
35
+ ### What the agent will do (key moments to narrate)
36
+
37
+ 1. **Launch with `--brk`** — Agent starts the app paused at the first line using `agent-dbg launch --brk demo/order-processor.js`
38
+
39
+ 2. **Set breakpoints** — Agent sets a breakpoint inside `calculateSubtotal` and continues execution
40
+
41
+ 3. **Hit the crash** — The off-by-one `<=` causes a `TypeError` on `items[2]` when `items.length` is 2. Agent inspects the error, sees `i` equals `items.length`, identifies the off-by-one
42
+
43
+ 4. **Hot-patch Bug 1** — Agent uses `agent-dbg hotpatch` to fix `i <= items.length` to `i < items.length` **in the running process** (no restart!)
44
+
45
+ 5. **Continue and inspect** — Agent restarts the frame or continues. Orders process without crashing, but the discount amounts look wrong
46
+
47
+ 6. **Investigate Bug 2** — Agent sets a breakpoint in `applyDiscount`, inspects `subtotal`, `rate`, and the return value. Evals `subtotal * rate` vs `subtotal * (1 - rate)` to confirm the formula is wrong
48
+
49
+ 7. **Hot-patch Bug 2** — Agent patches `return subtotal * rate` to `return subtotal * (1 - rate)`
50
+
51
+ 8. **Verify** — Agent continues execution, sees the correct totals in the output
52
+
53
+ ### Talking Points
54
+
55
+ - **No restart needed**: Both fixes were applied to the live process via hot-patching
56
+ - **Agent reasoning**: The agent didn't just fix a crash — it reasoned about the discount math to catch the subtle logic bug
57
+ - **Token-efficient output**: agent-dbg's compact output format keeps the agent's context window lean
58
+ - **@ref system**: Variables use short references (@v1, @v2...) that the agent can use in subsequent commands without copying long object IDs
59
+
60
+ ## Manual Verification
61
+
62
+ ```bash
63
+ # Should crash (Bug 1)
64
+ node demo/order-processor.js
65
+
66
+ # After fixing Bug 1 (change <= to <), should show wrong discounts (Bug 2)
67
+ # ORD-001 total will be 25 instead of 100
68
+
69
+ # After fixing both bugs, correct output
70
+ node demo/order-processor.js
71
+ ```
@@ -0,0 +1,35 @@
1
+ // Order Processor — calculates totals and applies discount codes
2
+ const orders = [
3
+ { id: "ORD-001", items: [{ name: "Widget", price: 25, qty: 3 }, { name: "Gadget", price: 50, qty: 1 }], discount: "SAVE20" },
4
+ { id: "ORD-002", items: [{ name: "Gizmo", price: 15, qty: 5 }], discount: null },
5
+ { id: "ORD-003", items: [{ name: "Doohickey", price: 100, qty: 2 }, { name: "Widget", price: 25, qty: 1 }], discount: "SAVE20" },
6
+ ];
7
+
8
+ const discountCodes = { SAVE20: 0.20, HALF: 0.50, VIP: 0.30 };
9
+
10
+ function calculateSubtotal(items) {
11
+ let subtotal = 0;
12
+ for (let i = 0; i < items.length; i++) {
13
+ subtotal += items[i].price * items[i].qty;
14
+ }
15
+ return subtotal;
16
+ }
17
+
18
+ function applyDiscount(subtotal, code) {
19
+ if (!code) return subtotal;
20
+ const rate = discountCodes[code];
21
+ return subtotal * (1 - rate);
22
+ }
23
+
24
+ function processOrders(orders) {
25
+ const results = [];
26
+ for (const order of orders) {
27
+ const subtotal = calculateSubtotal(order.items);
28
+ const total = applyDiscount(subtotal, order.discount);
29
+ results.push({ id: order.id, subtotal, total });
30
+ }
31
+ return results;
32
+ }
33
+
34
+ const results = processOrders(orders);
35
+ console.log("Order Results:", JSON.stringify(results, null, 2));