feat(screenshot): select the XCTest runner capture with --capture-backend runner

VI
15h ago
+126-16

Summary

On an iOS simulator with a Dynamic Island, simctl io screenshot draws the island into every capture. No --mask policy (ignored, alpha, black) removes it: the island is in the framebuffer. XCUIScreen, which Maestro and other XCTest tools capture with, leaves it out. So a baseline made by such a tool cannot be matched by agent-device's default capture: the pill lands in the top rows.

The runner capture already existed for the Maestro port, behind the internal flags.maestro.screenshotCaptureBackend. This PR makes it public:

agent-device screenshot page.png --capture-backend runner
await client.capture.screenshot({ path, captureBackend: 'runner' }); // MCP: captureBackend
  • Refused on non-Apple targets (UNSUPPORTED_OPERATION), with --pixel-density (INVALID_ARGS: the runner encodes the device density and skips normalization), and for any value but runner.
  • The Maestro port uses the public flag; flags.maestro.screenshotCaptureBackend is removed.
  • 17 files. Docs: website/docs/docs/commands.md. Not recorded in .ad scripts.

Validation

Tested SHA 4b8e1fdd1.

  • New/changed cases (projections, CLI parse, SDK/MCP reader and writer, daemon context, admission refusals, Maestro port) were red before the change; the 7 affected files pass (139 tests).
  • check:affected --run --head 4b8e1fdd1: format, lint, typecheck, layering, di-seams, fallow, native-dupes, mcp-metadata, build and package passed. integration-node failed on smoke-daemon-clean "forced retirement…". It also fails 2/3 runs on main on this host. Later checks did not run, so this is a draft; CI is the authority.
  • On a real iPhone 17 Pro simulator (iOS 26.2): default capture vs runner capture against a Maestro baseline, pill region RMSE 0.246 vs 0.021.

🤖 Generated with Claude Code