Conversation
…erify it with CoreDevice
Add `agent-device fold <closed|half-open|open>` and `client.command.fold({ pose })`.
ADR 0025 recorded that no official host API sets the hinge pose and left it to the
operator. The operator's own control is the pose action bar in the Xcode Device Hub
window, whose controls are ordinary accessibility buttons; the macOS helper gains a
`device-hub pose` subcommand that finds Device Hub in the process table (LaunchServices
registers the trampolined app with pid -1, which is why System Events saw no nodes),
reopens its window with a reopen event when it shows none, selects the simulator through
the sidebar row keyed `TableRow.Device.<udid>`, and presses the control.
The press is not the evidence. The Apple owner reads the hinge angle back through
`devicectl device motion hinge-angle` until it agrees with the request: closed is 0°, open
is 180°, and half-open is any angle between them reported only once two consecutive reads
agree the hinge stopped moving, because a hinge sweeps through half-open angles on its way
to either stop (the live run read 175.1° one stream after pressing Book, 130° after it
settled). The stream never ends on its own, so each read is bounded by devicectl's smallest
`--timeout` of five seconds. The response carries the verified pose, the angle, and the lit
panel's point size, so an agent sees its refs are stale without another capture.
`setFoldPose` is a host-side device-runtime operation rather than an interactor method: it
needs no XCUITest runner, and the leaf fact admits iPhone/iPad simulators while the
operation itself refuses a single-panel simulator from CoreDevice's display table. Every
other platform and provider states its own refusal cell.
Verified through the built CLI on an iOS 27.1 iPhone Duo simulator: open → 180° on LCD-1
(669x951pt, screenshot 951x669), half-open → 130°, closed → 0° on LCD (466x678pt,
screenshot 466x678), from a Device Hub that showed no window for the device; an iPhone 17
refuses with UNSUPPORTED_OPERATION before anything is pressed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… and conformance tables The contracts export snapshot names the new `fold-runtime` subpath, the six-platform coverage declaration and its Android evidence row classify the command, the runtime-binding conformance table resolves it, and the provider output guard drives it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Size Report
Startup median (7 runs, lower is better):
|
|
This looks close, but one refusal path contradicts itself and the coverage gate is failing because of where the pose vocabulary got exported. At 37e7778, The Coverage job fails on 11 eager-closure-budget assertions, all tracing to one new edge: packages/contracts/src/facades/device.ts -> packages/contracts/src/fold-pose.ts (https://github.com/callstack/agent-device/blob/37e7778/packages/contracts/src/facades/device.ts). That pulls fold-pose exports onto the import path of packages/platform-android/src/mechanics.ts, provider-limrun, provider-webdriver, session-journal, and src/cli.ts, none of which can pose a hinge. Can the pose vocabulary move to a module those entries already evaluate, or be reached only through the Is the Apple-only per-platform seam here the same shape as Not blocking, take or leave: The PR body says 70 files touched where the diff shows 78, and describes I could not run anything on macOS, so the Device Hub press, the trampolined-pid discovery, the reopen, and the sidebar row selection are unverified from the diff, and the 313-line Swift helper has no test covering them. The live angles and timings in the PR body are narrative without an attached run, so I checked the code paths they describe but not the readings themselves. Line references above are pinned to 37e7778. Before this merges, the fold-pose exports need to move off packages/contracts/src/facades/device.ts so Coverage passes without rebaselining the budget, and the half-open refusal needs to stop firing when the observed angle already matches the requested pose. |
Summary
Adds
agent-device fold <closed|half-open|open>andclient.command.fold({ pose })for the iPhone Duo simulator. ADR 0025 concluded no official host API sets the hinge pose and left it to the operator. This PR sets it the way the operator does: the macOS helper presses the pose control in the Xcode Device Hub window through accessibility (the earlier "zero accessibility nodes" finding was System Events seeing the trampolined app as pid 0), and the Apple owner trusts only CoreDevice's read-back ofdevicectl device motion hinge-angle.closedandopenare the hinge end stops;half-openis reported only once two consecutive reads agree the hinge stopped, because the first read after pressing Book caught it at 175.1°.The helper resolves Device Hub from the process table, reopens its window when a headless boot left none, and selects the simulator through the sidebar row keyed
TableRow.Device.<udid>, so twins sharing a name cannot be confused. The response reports the verified pose, the angle, and the lit panel's point size, so an agent sees its refs are stale.setFoldPoseis a host-side device-runtime operation (no runner); every other platform and provider states a refusal cell. 70 files touched; the gross diff exceeds the 1,000-line budget because of the ADR update, tests, and the Swift helper subcommand.Validation
Tested at 37e7778:
pnpm check:affected --runpasses (format, lint, typecheck, layering, di-seams, fallow, mcp-metadata, build, package, integration-node, macos-coverage, vitest-related 698 files, integration-progress, replay-compat, daemon-wire-compat, affected-selector).Live, through the built CLI on an iOS 27.1 iPhone Duo simulator booted headlessly (Device Hub showed no window for it): open → 180° on LCD-1, screenshot 951x669; half-open → 130°; closed → 0° on LCD, screenshot 466x678; each 10–16s. An iPhone 17 simulator refuses with
UNSUPPORTED_OPERATION(single-panel-device) before anything is pressed. Not exercised: a second Xcode running its own Device Hub, and physical foldables (refused by the leaf fact).🤖 Generated with Claude Code