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
* docs(plan): #186 Phase 6 TDD plan — foreign-flow arbitration + canonical maestro surface
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(plan): #186 Phase 6 — amendments from multi-LLM plan review
- BLOCKER: teardown grace (FOREIGN_GRACE_MS) — our own dying maestro driver
matches the detector for seconds after lease release; cache-busting can't
fix it, an arbiter-side lastFlowReleasedAt window does
- ps axww (mid-path udid truncation = production false negatives)
- RN_IOS_FOREIGN_GUARD knob (WARN stays as deprecated alias, loudly documented)
- udid-gated in-flight dedup + screenshot lastActive clarification
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(#186): ForeignFlowGate — TTL-cached fail-open foreign-maestro detection (+ps -ww)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(#186): BUSY_FOREIGN_FLOW — foreign maestro session refuses L2/L3 fast at the arbiter
Includes the plan-review teardown grace (FOREIGN_GRACE_MS): our own dying
maestro driver matches the detector for seconds after lease release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(#186): screenshot keeps its simctl fallback during a foreign flow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#201): snapshot the .app outside the device container before clearState
Live-gate finding: the resolver returned the INSTALLED container path as
--app-file, but clearState uninstalls the app and deletes that container
before maestro-runner reinstalls from it — 'No such file or directory'
mid-flow, app left uninstalled. The container resolution is now snapshotted
to a temp dir (APFS clonefile cp -Rc, plain copy fallback) that survives
the uninstall; DerivedData stays the fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(#186): canonical maestro surface + BUSY_FOREIGN_FLOW coexistence rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(#186): rebuilt dist + regenerated tool docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#186): bounded snapshot dir + stale knob doc line (PR review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#202 Phase 6 / #186 — foreign Maestro sessions become arbiter refusals; plugin maestro_run is the canonical surface.
7
+
8
+
While a foreign Maestro/XCUITest session drives the target simulator (UDID-scoped detection, 5 s TTL, fail-open), local `device_*` and flow tools refuse fast with `BUSY_FOREIGN_FLOW` (~50 ms measured) — pointing at the safe L1 reads — instead of colliding into the ~44 s runner-leak cascade. L1 introspection stays free; `device_screenshot` serves pixels via its simctl fallback; a ~10 s teardown grace after the plugin's own flows prevents self-false-positives while WDA dies. The two historical reasons to leave the plugin surface are live-gate-verified closed and #201 is closed — including a new fix: the clearState `--app-file` resolution is snapshotted outside the device container (the installed-container path used to be deleted by clearState itself before the reinstall could read it). `RN_IOS_FOREIGN_GUARD=0` disables both the warning and the refusal (`RN_IOS_FOREIGN_WARN=0` remains a deprecated alias). The foreign-runner `ps` scan now uses `-ww` (command-column truncation could silently drop the UDID → false negatives).
-**`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
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`.
109
109
-**MCP server died when Metro was restarted (all tools gone until session restart)** → Fixed since #202 Phase 5 (#264): the stdio supervisor holds no network sockets, so port-based kills (`lsof -ti tcp:8081 | xargs kill -9`) only take the worker, which respawns automatically (`cdp_status` → `bridge.workerRestarts`). If tools error with "worker is crash-looping", check the bridge log (`LOG_LEVEL=info` writes it) and restart the session. `RN_BRIDGE_SUPERVISOR=0` opts back into the legacy single-process bridge.
110
+
-**`BUSY_FOREIGN_FLOW` on device_*/maestro_run** → A foreign Maestro/XCUITest session (e.g. standalone maestro-mcp) is driving the same simulator. By design (#186): wait for it to finish (the guard clears within ~5 s of the foreign run ending), use L1 reads (`cdp_component_tree`, `cdp_store_state`) and `device_screenshot` meanwhile, or disable the guard with `RN_IOS_FOREIGN_GUARD=0` (`RN_IOS_FOREIGN_WARN=0` is a deprecated alias with the same effect). The first tap within ~10 s after your OWN `maestro_run` is exempt by design (WDA teardown grace).
110
111
-**"No booted simulator"** → Open Simulator.app or boot one via Xcode
|**L3 FLOW-REPLAY**|`maestro-runner` (Go + WDA) | whole-`.yaml` E2E flows |**exclusive**| owns the device for the flow's duration |
159
160
160
-
**Coexistence rule:** L1 reads never conflict with a foreign runner; L2 re-attaches rather than evicts; L3 owns the device. On `device_snapshot action=open`, if a foreign maestro session is detected (UDID-scoped) AND no local flow lease is held, the open result carries an informational `meta.foreignRunner` + `FOREIGN_RUNNER_ACTIVE` warning (`runners/external-runner-detect.ts`; opt out with `RN_IOS_FOREIGN_WARN=0`). See `docs-site` → "Using rn-dev-agent with maestro-mcp".
161
+
**Coexistence rule:** L1 reads never conflict with a foreign runner; L2 re-attaches rather than evicts; L3 owns the device. Since #202 Phase 6 (#186), a detected foreign Maestro session is an **arbiter input**, not just a warning: while it is live (UDID-scoped, 5 s-TTL `ps axww` scan via `lifecycle/foreign-flow-gate.ts`, fail-open), local L2 `device_*` and L3 flow tools refuse fast with `BUSY_FOREIGN_FLOW` (~50 ms measured, vs the ~44 s runner-leak cascade) while L1 reads stay free and `device_screenshot` serves pixels via its simctl fallback. A ~10 s teardown grace after the plugin's OWN flows prevents self-false-positives while WDA dies. The plugin's `maestro_run` is the **canonical** Maestro surface (it participates in the arbiter, parks the L2 runner, marks CDP stale, auto-repairs actions); the standalone maestro-mcp coexists for ad-hoc use and is refused against rather than collided with mid-flow. The device-open `FOREIGN_RUNNER_ACTIVE` warning remains. `RN_IOS_FOREIGN_GUARD=0` disables BOTH the warning and the refusal; the older `RN_IOS_FOREIGN_WARN=0` is a deprecated alias with the same (full) effect — if you set it to quiet the warning, know it now also drops the refusal. See `docs-site` → "Using rn-dev-agent with maestro-mcp".
161
162
162
163
**Device-session visibility + self-healing (#210, D1249).** The L2 `rn-fast-runner` is THE iOS `device_*` backend; Maestro/WDA is the L3 flow engine — **serialized, not competing** (there is no shared WDA session to "ride": maestro-runner spawns WDA per-flow and tears it down). Three reuse-first behaviors make the single iOS path coherent: (1) `cdp_status.deviceSession` reports `{ sessionOpen, rnFastRunner: 'alive'|'stale'|'dead', foreignRunner? }` (iOS-gated) so the runner's state is visible before any `device_*`; (2) `device_find/press/fill` **auto-spawn** the runner from the dispatch choke point when it's down and the XCUITest rig is **prebuilt** — a missing rig returns an actionable `RN_FAST_RUNNER_DOWN` (never a silent multi-minute `xcodebuild`); (3) `device_screenshot` **falls back to `xcrun simctl io screenshot`** whenever the runner can't serve it — including while a Maestro flow owns the device (it runs unleased via the arbiter's `FLOW_FALLBACK_TOOLS` allowlist; simctl is OS-level and can't conflict with WDA). Mid-flow needs map to non-conflicting mechanisms: **pixels → simctl**, **tree/state → `cdp_component_tree`/`cdp_store_state`** (CDP introspection coexists with a flow by design), **taps → not mid-flow** (the arbiter refuses them on purpose). A WDA W3C client was rejected — it would add a *second* XCUITest backend, the opposite of unifying.
**Foreign runners** (e.g. the standalone `maestro-mcp`): L1 reads are always safe; on an L2 leak the device-session re-attaches rather than evicts (#188); `device_snapshot action=open` surfaces an informational `FOREIGN_RUNNER_ACTIVE` warning when a foreign session is present and no local flow is running.
93
+
**Foreign runners** (e.g. the standalone `maestro-mcp`): L1 reads are always safe; on an L2 leak the device-session re-attaches rather than evicts (#188); `device_snapshot action=open` surfaces an informational `FOREIGN_RUNNER_ACTIVE` warning. Since #186, a LIVE foreign session (UDID-scoped, 5 s-TTL `ps` scan, fail-open) also makes local `device_*` and flow tools refuse fast with `BUSY_FOREIGN_FLOW` (~50 ms, vs the ~44 s runner-leak cascade) while CDP reads stay free and `device_screenshot` falls back to simctl; the plugin's `maestro_run`is the canonical Maestro surface. Opt out with `RN_IOS_FOREIGN_GUARD=0`.
Copy file name to clipboardExpand all lines: docs-site/src/content/docs/guides/maestro-interop.mdx
+6-2Lines changed: 6 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,9 +16,13 @@ When an L2 call sees the runner-leak sentinel after a maestro run, the device-se
16
16
17
17
## The `FOREIGN_RUNNER_ACTIVE` warning
18
18
19
-
When you `device_snapshot action=open` while a foreign maestro automation session is driving the same simulator (and rn-dev-agent is not itself running a flow), the open result carries an informational `meta.foreignRunner` and a `FOREIGN_RUNNER_ACTIVE` warning. It is a **heads-up only** — it does not block or change the open. It tells you the simulator is contended so you can expect a re-foreground if you interleave `device_*`.
19
+
When you `device_snapshot action=open` while a foreign maestro automation session is driving the same simulator (and rn-dev-agent is not itself running a flow), the open result carries an informational `meta.foreignRunner` and a `FOREIGN_RUNNER_ACTIVE` warning. The open itself is never blocked.
20
20
21
-
The detection is UDID-scoped (it ignores maestro flows on other simulators and the idle maestro-mcp server). Opt out with `RN_IOS_FOREIGN_WARN=0`. Note: immediately after your *own*`maestro_run`, you may briefly see the warning while maestro's driver is still exiting — that's expected.
21
+
Since #186 shipped, the contention handling goes further: while a foreign session is live (UDID-scoped detection, ~5 s cache), rn-dev-agent's `device_*` taps and `maestro_run` flows **refuse fast with `BUSY_FOREIGN_FLOW`** (~50 ms) instead of colliding into the multi-second runner-leak recovery cascade. L1 reads (`cdp_component_tree`, `cdp_store_state`, `cdp_navigation_state`) keep working throughout, and `device_screenshot` serves pixels via its OS-level simctl fallback. The guard clears within ~5 s of the foreign run ending; the first tap within ~10 s after rn-dev-agent's **own** flows is exempt (WDA teardown grace). Disable with `RN_IOS_FOREIGN_GUARD=0` (`RN_IOS_FOREIGN_WARN=0` is a deprecated alias — it disables the refusal too, not just this warning).
22
+
23
+
**Division of labor:** rn-dev-agent's `maestro_run` is the canonical Maestro surface — it participates in the device arbiter, parks the L2 interaction runner for the flow, marks CDP stale afterwards, and auto-repairs saved actions. Use the standalone maestro-mcp for ad-hoc exploration; when both target one simulator, rn-dev-agent now steps back cleanly instead of fighting.
24
+
25
+
The detection is UDID-scoped (it ignores maestro flows on other simulators and the idle maestro-mcp server). Opt out with `RN_IOS_FOREIGN_GUARD=0` (disables the refusal too). Note: immediately after your *own*`maestro_run`, you may briefly see the warning while maestro's driver is still exiting — that's expected, and the refusal guard explicitly exempts that window.
Copy file name to clipboardExpand all lines: docs-site/src/content/docs/tools/cdp/cdp_run_action.mdx
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,7 @@ Replay a learned action by id with end-to-end auto-repair. Loads the action from
16
16
|`timeoutMs`|`number`| No ||| Maestro execution timeout per attempt (ms). Default 120_000. |
17
17
|`trigger`| `enum: agent | ci | human` | No ||| RunRecord trigger annotation. Default "agent". CI calls should pass "ci". |
18
18
|`forceReload`|`boolean`| No ||| GH #173: when true (default), acknowledge any human edit to the YAML as the new baseline before running so downstream repair does not abort with STALE_TARGET. Pass false for the strict Phase 129 "respect external edits" behavior (useful for CI replays of fixed baselines). |
19
+
|`params`|`Record<string, unknown>`| No ||| Parameter bindings for the action\\'s $\{VAR\} placeholders, forwarded to maestro as -e KEY=VALUE on the first attempt AND the post-repair retry (GH #116). Keys must match /^[A-Z_][A-Z0-9_]*$/ (validated in maestro_run). |
Copy file name to clipboardExpand all lines: docs-site/src/content/docs/tools/testing/maestro_run.mdx
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,6 +17,7 @@ Execute a Maestro flow via maestro-runner. Pass flowPath for an existing .yaml f
17
17
|`appId`|`string`| No ||| App bundle ID (auto-detected from app.json) |
18
18
|`appFile`|`string`| No ||| iOS only — path to a built .app/.ipa for maestro-runner to reinstall on clearState. Auto-resolved from the flow appId when omitted (GH#201). |
19
19
|`timeoutMs`|`number`| No |`120000`| min: 5000, max: 300000, integer | Execution timeout in ms |
20
+
|`params`|`Record<string, unknown>`| No ||| GH #116: parameter bindings forwarded as -e KEY=VALUE for $\{KEY\} placeholders in the flow. Keys must match /^[A-Z_][A-Z0-9_]*$/ (validated in the handler). |
0 commit comments