You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(cdp): debugger-seat opt-out + silent hook-mode network transport (#267)
* docs(spec): debugger-seat opt-out + silent hook-mode network transport
External feedback: the CDP bridge evicts React Native DevTools from the
single debugger seat (0ms-reconnect + background poll re-grab it), and
hook-mode network capture spams __RN_NET__ console lines into Metro logs
and the user's DevTools.
Approved design: keep agent-first default, add autoConnect opt-out
(RN_CDP_AUTOCONNECT env var > .rn-agent/config.json > default true)
disabling only background reconnect paths; replace the __RN_NET__
console.log transport with an in-app ring buffer drained on demand.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(plan): TDD implementation plan for debugger-seat opt-out + buffered net transport
Amendments applied from the multi-LLM plan review (external providers
failed — Gemini 429, Codex usage limit — findings are file-verified
coordinator research): DeviceBufferManager import/constructor blocker,
foreground-reclaim seat policy for cdp_status/recoverWedge documented,
wait_for_network drain throttled to 500ms, status payload shape
reconciled to top-level autoConnect, duplicate-id dedup added.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cdp): resolve autoConnect from RN_CDP_AUTOCONNECT env + .rn-agent/config.json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cdp): gate background reconnect paths behind autoConnect (passive mode)
Add optional `isAutoConnectEnabled` to `ReconnectContext`; when present and
false, `handleClose` sets state to disconnected and returns without launching
the reconnect loop, and `startBackgroundPoll` skips arming the timer —
yielding the single RN debugger seat to a human DevTools session. All
foreground/on-demand connect paths remain ungated. Existing consumers without
the callback keep today's behavior (backward-compatible). 5/5 new unit tests
pass; gh-208-status-storm-preempt regression clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cdp): gate armed background-poll callback in passive mode; dedupe gate via isPassive
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cdp): surface autoConnect resolution in cdp_status and wire passive mode into CDPClient
Adds CDPClient.autoConnectState (lazy-resolved once per process via resolveAutoConnect()),
passes isAutoConnectEnabled into buildReconnectCtx() so passive-mode gating from Task 2
is live, and surfaces { enabled, source } as cdp_status.autoConnect. Carry-over fixes:
load-bearing comment on the 'env' in deps test-seam guard in resolveAutoConnect, and a
new test covering the real process.env.RN_CDP_AUTOCONNECT branch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cdp): pin autoConnectState cache contract; report autoConnect on status failure paths
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(cdp): extract applyNetworkHookEntry with duplicate-id dedup
Pulls the request/response application logic out of parseNetworkHookMessage
into a new exported applyNetworkHookEntry function so Tasks 5/6 (buffered
drain path) can share identical entry-application semantics with the legacy
console-event path. Adds a getByKey dedup guard on 'request' entries to
prevent double-push during the one-release back-compat window where both
paths may report the same id.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cdp): buffered in-app transport for hook-mode network capture (no console spam)
Replaces the console.log __RN_NET__ transport with an in-app ring buffer
(__RN_AGENT_NET_BUF__, capped at 100 entries, idempotent on reinject).
Adds RN_FORCE_NETWORK_HOOK=1 test seam for live-verification on RN >= 0.83.
Adds 6 new tests (2 hardening pin-tests + 4 NETWORK_CB_BUFFERED_SCRIPT contract
tests); full suite: 1883/1883 pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cdp): on-demand drain of the in-app network hook buffer
Adds drainNetworkHookBuffer() (src/cdp/net-hook-drain.ts) — a fail-open
swap-and-return drain of globalThis.__RN_AGENT_NET_BUF__ that applies entries
via applyNetworkHookEntry into DeviceBufferManager; returns count of entries
applied, never throws.
Carry-over fixes from Task 5 review:
- Hot-path guard in NETWORK_CB_BUFFERED_SCRIPT: try/catch + Array.isArray
repair so a corrupted buffer can never break the user's fetch/XHR
- Seam double-feed fix in setup.ts: RN_FORCE_NETWORK_HOOK seam now also calls
Network.disable (best-effort) so CDP events don't double-feed the buffer
- Post-seam log accuracy: seam moved above the logger.info line so the logged
network mode reflects the post-seam value
- Test-file carry-over: mid-file import moved to top alongside other imports
18/18 targeted tests pass; 1890/1890 full suite pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cdp): drain hook-mode network buffer on demand in the network-reading tools
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cdp): cover network-body drain path, honest wait deadline, hook-mode granularity docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: DevTools coexistence (autoConnect opt-out) + changeset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cdp): app-side timestamps for buffered hook entries; docs + status envelope polish
- NETWORK_CB_BUFFERED_SCRIPT now pushes { t, d, ts: Date.now() } so each
entry carries its fire-time epoch ms; drain path forwards ts as atMs to
applyNetworkHookEntry, which uses new Date(atMs ?? Date.now()).toISOString()
— fixes false-positive since-pinning in cdp_wait_for_network for batched
drains (Fix 1, 8 new TDD tests in net-hook-buffered-transport.test.js)
- docs: remove device_* from the passive-mode seat-reclaim example (they
don't touch CDP); add known-limitation note for cdp_open_devtools proxy
not auto-resuming in passive mode (Fix 2, maestro-interop.mdx)
- status.ts: PICKER_BLOCKING failure envelope now includes autoConnect state
consistent with the other two failure paths (Fix 3)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- New opt-out for background auto-reconnect: `RN_CDP_AUTOCONNECT=0` or `.rn-agent/config.json``{ "cdp": { "autoConnect": false } }`. In passive mode the bridge yields the single RN debugger seat to the visual DevTools and reconnects only on explicit tool calls. Resolved mode is visible in `cdp_status` → `autoConnect` and `/doctor`.
9
+
- Hook-mode network capture (RN < 0.83 fallback) no longer transports entries via `console.log("__RN_NET__:…")` — entries go to an in-app ring buffer drained on demand, so Metro logs and the user's DevTools console stay clean.
Copy file name to clipboardExpand all lines: CLAUDE-MD-TEMPLATE.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -493,6 +493,7 @@ Tool calls must follow this sequence to avoid race conditions:
493
493
|`cdp_reload` reports `reconnected: false`| Wait 5-10s | New Hermes target not yet registered |`cdp_connect force: true`; if ambiguous target, pass `targetId:`|
494
494
|`device_screenshot` captures the wrong platform | — | Multi-device routing bug | Pass `platform:` explicitly, or fall back to raw `adb screencap` / `simctl io`|
495
495
|`cdp_interact accessibilityLabel="..."` fails (label matching is fuzzy) | Prefer testID-keyed calls: `cdp_interact(testID="...")` or `device_batch` with `testID=` field. Fall back to `device_snapshot` + `device_press(ref="@eN")` only when no testID exists. | Label matching unreliable; testID matching is exact and fiber-tree-resolved | — |
496
+
| "Disconnected due to opening a second DevTools window" / React Native DevTools keeps getting kicked |`cdp_status` → `autoConnect` field | RN allows one debugger frontend per app; bridge auto-reconnects by default (agent-first) | Set `RN_CDP_AUTOCONNECT=0` or `.rn-agent/config.json` → `{ "cdp": { "autoConnect": false } }`. Bridge then reconnects only on explicit tool calls and yields again when DevTools reopens. Note: **any** CDP tool call — including `cdp_status` — reclaims the seat while it runs; passive mode only stops *background* re-grabs. |
-**"rn-fast-runner did not become ready" / no `.xctestrun` at the expected path** → The runner self-builds on first use (cold `xcodebuild test`), so this now usually means the cold build itself timed out or failed — not a missing prebuild. The cold-build ready timeout is 360s; a slower machine or a build error (check Xcode/simulator state) can still trip it. To take the build out of the hot path, pre-build once with `xcodebuild build-for-testing` (see Prerequisites). The build artifacts live at `scripts/rn-fast-runner/build/DerivedData/`.
106
106
-**Legacy `AgentDeviceRunner` re-appears on the simulator** → A stale `~/.agent-device/daemon.json` is respawning the upstream runner. Since #202 the plugin terminates stale `AgentDeviceRunner` processes at session-open by default (scoped to the target simulator UDID) and clears orphaned `~/.agent-device/daemon.{json,lock}`, so this should self-heal. If you've opted out via `RN_DEVICE_KILL_LEGACY=0`, either drop that override or clean up one-time: `pkill -f AgentDeviceRunner && rm -f ~/.agent-device/daemon.json ~/.agent-device/daemon.lock`.
107
107
-**`RnFastRunner` / `RnFastRunnerUITests-Runner` icons appear on the simulator** → Expected, not clutter. iOS device control is an XCUITest rig (D1219), so running it installs two apps: `RnFastRunner` (the minimal host app, bundle `dev.lykhoyda.rndevagent.fastrunner`) and `RnFastRunnerUITests-Runner` (the XCUITest harness — same pattern as WebDriverAgent's `WebDriverAgentRunner`). The Runner hosts the `POST /command` HTTP server on port 22088 and drives YOUR app via `XCUIApplication(bundleIdentifier:)` — it never drives itself. It stays installed/running on purpose so subsequent `device_*` calls are fast; leave it. (Contrast the legacy `AgentDeviceRunner` above, which IS unwanted.)
108
+
-**"Disconnected due to opening a second DevTools window" / React Native DevTools keeps getting kicked** → RN allows exactly one debugger frontend per app, and the bridge auto-reconnects by default (agent-first). To let the visual DevTools hold the seat, set `RN_CDP_AUTOCONNECT=0` (or `.rn-agent/config.json` → `{ "cdp": { "autoConnect": false } }`). The bridge then reconnects only when a CDP tool actually runs, and yields again once you reopen DevTools. Note: **any** CDP tool call — including `cdp_status` — reclaims the seat while it runs; passive mode only stops *background* re-grabs. Check the resolved mode in `cdp_status` → `autoConnect`.
108
109
-**"No booted simulator"** → Open Simulator.app or boot one via Xcode
Run the environment-diagnostic checklist from the `rn-setup` skill. Walk all 14 prerequisite checks (Node.js version, CDP bridge dependencies, **rn-fast-runner build (iOS)**, **agent-device CLI (Android)**, maestro-runner, iOS simulator, Android emulator, Metro dev server, CDP connection, **injected `__RN_AGENT` helpers**, ffmpeg, physical-device prerequisites, **plugin version freshness**, **Vercel rules sync freshness**) and surface install commands for any missing dependencies.
7
+
Run the environment-diagnostic checklist from the `rn-setup` skill. Walk all 15 prerequisite checks (Node.js version, CDP bridge dependencies, **rn-fast-runner build (iOS)**, **agent-device CLI (Android)**, maestro-runner, iOS simulator, Android emulator, Metro dev server, CDP connection, **injected `__RN_AGENT` helpers**, ffmpeg, physical-device prerequisites, **plugin version freshness**, **Vercel rules sync freshness**, **CDP auto-reconnect mode**) and surface install commands for any missing dependencies.
8
8
9
9
iOS device automation is owned by the in-tree `rn-fast-runner` XCTest project (D1219, PR #164) — `agent-device` is Android-only after that change. Mark `agent-device` as N/A on iOS-only setups and `rn-fast-runner` as N/A on Android-only / non-macOS setups.
10
10
11
11
**This command is read-only.** It diagnoses the current environment and recommends fixes. It does NOT modify any files in the user's project, inject documentation, or instrument source code.
12
12
13
-
Present results as a 14-row table. For any RED rows, give the user the exact install command (with `nvm` / `sudo` / `brew` flag selection based on their environment). For the `rn-fast-runner` row: if NEEDS_BUILD, note that the runner self-builds on first use (a slow cold build) and OFFER to run the one-time `xcodebuild build-for-testing` now to move that cost out of the first interaction (the rn-setup skill section 3 has the exact form + the offer prompt). Building the runner writes only to plugin-internal `scripts/rn-fast-runner/build/` — it does not touch the user's project, so the offer is consistent with this command's read-only-on-your-project contract. For the helpers row specifically, do NOT suggest retrying `cdp_status` — the bridge already auto-retried injection. If MISSING, recommend `device_*` fallbacks or `cdp_reload`. For the **plugin version** row: if BEHIND, surface `/plugin update rn-dev-agent` and let the user decide whether to update before continuing. If OFFLINE (GitHub unreachable), skip without failing — plugin works fine without the upstream check. For the **Vercel rules sync** row: if STALE (>30 days since last sync) or MISSING (no `third_party/vercel-labs/agent-skills/UPSTREAM.lock.json`), surface the resync command — do NOT auto-run; the user runs `node ${CLAUDE_PLUGIN_ROOT}/scripts/sync-vercel-skills.mjs --ref <sha>` themselves.
13
+
Present results as a 15-row table. For any RED rows, give the user the exact install command (with `nvm` / `sudo` / `brew` flag selection based on their environment). For the `rn-fast-runner` row: if NEEDS_BUILD, note that the runner self-builds on first use (a slow cold build) and OFFER to run the one-time `xcodebuild build-for-testing` now to move that cost out of the first interaction (the rn-setup skill section 3 has the exact form + the offer prompt). Building the runner writes only to plugin-internal `scripts/rn-fast-runner/build/` — it does not touch the user's project, so the offer is consistent with this command's read-only-on-your-project contract. For the helpers row specifically, do NOT suggest retrying `cdp_status` — the bridge already auto-retried injection. If MISSING, recommend `device_*` fallbacks or `cdp_reload`. For the **plugin version** row: if BEHIND, surface `/plugin update rn-dev-agent` and let the user decide whether to update before continuing. If OFFLINE (GitHub unreachable), skip without failing — plugin works fine without the upstream check. For the **Vercel rules sync** row: if STALE (>30 days since last sync) or MISSING (no `third_party/vercel-labs/agent-skills/UPSTREAM.lock.json`), surface the resync command — do NOT auto-run; the user runs `node ${CLAUDE_PLUGIN_ROOT}/scripts/sync-vercel-skills.mjs --ref <sha>` themselves. For the **CDP auto-reconnect** row: read `autoConnect` from `cdp_status` output; report `ON (default)`, `OFF (env)`, or `OFF (config)`. OFF is informational (YELLOW, not RED) with note "React Native DevTools can hold the debugger seat; the bridge reconnects only on tool calls".
14
14
15
15
If the user wants the plugin to also inject project instructions (CLAUDE.md template, nav-ref, store exposure) — point them at `/rn-dev-agent:setup` instead.
Copy file name to clipboardExpand all lines: docs-site/src/content/docs/guides/maestro-interop.mdx
+25Lines changed: 25 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,3 +29,28 @@ A `maestro_run` / `maestro_test_all` flow (rn-dev-agent's own L3) takes the devi
29
29
## Why there's no shared "WDA session" to ride (#210)
30
30
31
31
A natural question: since maestro drives the device fine via WebDriverAgent, can `device_*` "ride" that WDA session instead of rn-fast-runner? No — maestro-runner spawns WDA **per flow** and tears it down when the flow exits, so there's no persistent session to attach to, and a WDA client would add a *second* XCUITest backend (the opposite of unifying). The coherent model is: **rn-fast-runner is the `device_*` backend** (it auto-spawns when prebuilt — `cdp_status.deviceSession` shows its `alive`/`stale`/`dead` state), **maestro/WDA is the flow engine**, and they're serialized. Mid-flow, use simctl for pixels and CDP reads for state/tree.
32
+
33
+
## Using rn-dev-agent with React Native DevTools
34
+
35
+
React Native (Hermes) allows **exactly one debugger frontend** per app at a time. By default, rn-dev-agent is agent-first: its CDP bridge auto-reconnects in the background after each disconnect, which means opening the visual React Native DevTools window will kick the bridge out — and the bridge will immediately reclaim the seat.
36
+
37
+
**To let React Native DevTools hold the debugger seat**, disable background auto-reconnect with one of two opt-out surfaces:
38
+
39
+
-**Environment variable** (takes precedence over everything):
40
+
```bash
41
+
RN_CDP_AUTOCONNECT=0 # also accepts 'false'; '1'/'true' force it back on
42
+
```
43
+
-**Project config file** (`.rn-agent/config.json` in your project root, env wins):
44
+
```json
45
+
{ "cdp": { "autoConnect": false } }
46
+
```
47
+
48
+
In passive mode the bridge **yields the seat** and reconnects only when a CDP tool actually runs (e.g. `cdp_status`, `cdp_component_tree`). Once you reopen React Native DevTools after a tool call, the bridge yields again automatically.
49
+
50
+
**Known limitation:** in passive mode, a previously-opened DevTools-sharing proxy (`cdp_open_devtools`) is not auto-resumed after a disconnect — re-run `cdp_open_devtools` if you use it.
51
+
52
+
**Important caveat:***any* CDP tool call — including `cdp_status` — reclaims the seat while it runs. Passive mode stops only *background* re-grabs (the close-triggered reconnect loop and the background poll). If you need uninterrupted DevTools access, avoid running CDP tools while the window is open.
53
+
54
+
The resolved auto-connect mode is always visible in `cdp_status` → top-level `autoConnect` field: `{ enabled: true|false, source: 'default'|'env'|'config' }`. The `/rn-dev-agent:doctor` command also reports it as the **CDP auto-reconnect** row (YELLOW when OFF — informational, not an error).
55
+
56
+
**Hook-mode network capture note:** on React Native < 0.83 (or when `RN_FORCE_NETWORK_HOOK=1` is set), network entries are captured by in-app `fetch`/XHR wrappers. As of this release that capture **no longer writes `__RN_NET__:…` lines to the console** — entries go directly to an in-app ring buffer (`globalThis.__RN_AGENT_NET_BUF__`, capped at 100) that the network tools drain on demand. Metro logs and your React Native DevTools console will no longer contain hook-mode network noise. No user action is required; the change is transparent.
0 commit comments