Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.818Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/01-flow.yaml","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:1:01-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.819Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":2}
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.819Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.820Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":1}
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.820Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.820Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_attempt_start","ts":"2026-09-20T20:37:45.965Z","replayPath":"/tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-IAQdpt/01-flow.yaml","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:1:01-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-20T20:37:45.967Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":2}
{"type":"replay_test_finalize_start","ts":"2026-09-20T20:37:45.967Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-20T20:37:45.967Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_cleanup_start","ts":"2026-09-20T20:37:45.967Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T20:37:45.967Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":0}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/01-flow.yaml
file: /tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-IAQdpt/01-flow.yaml
session: default:test:req-maestro-test-wire-true:1-01-flow:attempt-1
attempt: 1/1
status: passed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.820Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/02-flow.yaml","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:2:02-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":1}
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_attempt_start","ts":"2026-09-20T20:37:45.968Z","replayPath":"/tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-IAQdpt/02-flow.yaml","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:2:02-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-20T20:37:45.968Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
{"type":"replay_test_finalize_start","ts":"2026-09-20T20:37:45.969Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-20T20:37:45.969Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_cleanup_start","ts":"2026-09-20T20:37:45.969Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T20:37:45.969Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/02-flow.yaml
file: /tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-IAQdpt/02-flow.yaml
session: default:test:req-maestro-test-wire-true:2-02-flow:attempt-1
attempt: 1/1
status: passed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.823Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/01-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:1:01-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_attempt_start","ts":"2026-09-20T20:37:45.971Z","replayPath":"/tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-XmcOFG/01-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:1:01-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-20T20:37:45.971Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
{"type":"replay_test_finalize_start","ts":"2026-09-20T20:37:45.971Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-20T20:37:45.971Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_cleanup_start","ts":"2026-09-20T20:37:45.971Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T20:37:45.971Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/01-flow.yaml
file: /tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-XmcOFG/01-flow.yaml
session: default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1
attempt: 1/1
status: passed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.824Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/02-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:2:02-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}
{"type":"replay_test_attempt_start","ts":"2026-09-20T20:37:45.972Z","replayPath":"/tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-XmcOFG/02-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:2:02-flow.yaml:attempt:1"}
{"type":"replay_test_attempt_stop","ts":"2026-09-20T20:37:45.972Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
{"type":"replay_test_finalize_start","ts":"2026-09-20T20:37:45.972Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
{"type":"replay_test_finalize_stop","ts":"2026-09-20T20:37:45.973Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":1}
{"type":"replay_test_cleanup_start","ts":"2026-09-20T20:37:45.973Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T20:37:45.973Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/02-flow.yaml
file: /tmp/agent-device-test-run-94227-XxlCsl/agent-device-maestro-remote-test-XmcOFG/02-flow.yaml
session: default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1
attempt: 1/1
status: passed
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
and every stop the host applies mid-hold — a cancelled request, a dropped client, the deadline —
reaches the helper as SIGTERM first so it releases the button before it exits; SIGKILL only
follows a helper that has not exited a second later.
- Added (ios): `fold <closed|half-open|open>` and `client.command.fold({ pose })` put a foldable
iPhone simulator (iPhone Duo) into a hinge pose. ADR 0025 recorded that no official host API sets
the pose and left it to the operator; the pose is now set the way the operator did it, by pressing
the pose control in the Xcode Device Hub window through macOS accessibility, and it is confirmed
the way ADR 0025 asked for, by reading the hinge angle back from CoreDevice
(`devicectl device motion hinge-angle`) until it agrees with the request. The response reports
the verified pose, the hinge angle, and the panel the device now lights with its point size, so an
agent can see that its refs and coordinates are stale without another capture. The macOS helper
gained a `device-hub pose` subcommand that finds Device Hub in the process table (LaunchServices
registers the trampolined app with no pid), reopens its window when it shows none, and selects the
simulator through the sidebar row keyed by its UDID so two simulators sharing a name cannot be
confused. Simulator-only; a single-panel simulator refuses with `UNSUPPORTED_OPERATION`, and every
other platform states its own refusal cell.
- Added (diff): `diff screenshot` accepts a JPEG baseline or current image. Both inputs had to be PNG,
so a capture exported by another tool had to be converted first and a HarmonyOS capture — which the
platform serves as JPEG under whatever name the command was given — could never be compared. Each
Expand Down
9 changes: 8 additions & 1 deletion apple/macos-helper/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ let package = Package(
.target(
name: "AgentDeviceMacOSInput"
),
.target(
name: "AgentDeviceMacOSDeviceHub"
),
.executableTarget(
name: "AgentDeviceMacOSHelper",
dependencies: ["AgentDeviceMacOSInput"]
dependencies: ["AgentDeviceMacOSInput", "AgentDeviceMacOSDeviceHub"]
),
.testTarget(
name: "AgentDeviceMacOSInputTests",
dependencies: ["AgentDeviceMacOSInput"]
),
.testTarget(
name: "AgentDeviceMacOSDeviceHubTests",
dependencies: ["AgentDeviceMacOSDeviceHub"]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Foundation

/// The decisions the Device Hub pose press makes without a window server: which control a pose
/// names, which window belongs to a device, and which sidebar row identifies it. They live apart
/// from the helper executable so the tests can exercise them without an accessibility session.

/// Device Hub's three pose presets for a foldable simulator, named as the helper's `--pose`
/// argument spells them. Each maps to the accessibility description of its action-bar button.
public enum DeviceHubPose: String, CaseIterable, Sendable {
case closed
case book
case open

public init?(argument: String) {
self.init(rawValue: argument.lowercased())
}

/// The `AXDescription` Device Hub gives the button for this pose.
public var controlDescription: String {
switch self {
case .closed: return "Closed"
case .book: return "Book"
case .open: return "Open"
}
}
}

/// Device Hub is launched through a trampoline, so LaunchServices registers it with no process
/// identifier; the process table is matched on the executable path instead.
public let deviceHubExecutableSuffix = "/DeviceHub.app/Contents/MacOS/DeviceHub"

/// The separator Device Hub puts between the device name and the OS in a window title.
public let deviceHubWindowTitleSeparator = " – "

/// Device Hub titles a device window `<device name> – <OS> <version>`, so a window shows the
/// device when its title is the name or the name followed by that exact separator. A bare
/// space would also match a simulator whose name merely extends this one ("iPhone Duo Lab").
public func deviceHubWindowShows(deviceName: String, title: String?) -> Bool {
guard let title else { return false }
return title == deviceName || title.hasPrefix(deviceName + deviceHubWindowTitleSeparator)
}

/// The accessibility identifier of the sidebar row for one simulator. The UDID is the one device
/// identity Device Hub exposes that two simulators sharing a name cannot confuse.
public func deviceHubDeviceRowIdentifier(udid: String) -> String {
return "TableRow.Device.\(udid)"
}
Loading
Loading