agent-device 0.20.9 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (492) hide show
  1. package/README.md +2 -2
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.9.apk → agent-device-android-ime-helper-0.21.0.apk} +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.0.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.9.manifest.json → agent-device-android-ime-helper-0.21.0.manifest.json} +4 -4
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.0.apk +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.0.apk.sha256 +1 -0
  7. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.9.manifest.json → agent-device-android-snapshot-helper-0.21.0.manifest.json} +6 -6
  8. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +35 -33
  9. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +6 -1
  10. package/apple/snapshot-bridge/LICENSE.idb +23 -0
  11. package/apple/snapshot-bridge/README.md +49 -0
  12. package/apple/snapshot-bridge/SnapshotBridge.m +280 -0
  13. package/apple/snapshot-bridge/SnapshotBridgeRuntime.h +33 -0
  14. package/apple/snapshot-bridge/SnapshotBridgeRuntime.m +382 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +58 -0
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +19 -0
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAlertVerification.swift +36 -0
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerStringNormalization.swift +8 -0
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +12 -1
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +100 -6
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +2 -2
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +21 -12
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +21 -88
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AlertObservation.swift +124 -0
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +110 -107
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +6 -14
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +85 -114
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +13 -6
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +72 -175
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +10 -11
  31. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXElementTypes.swift +12 -0
  32. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +78 -64
  33. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +17 -9
  34. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +47 -13
  35. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +232 -438
  36. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotBackendCapabilities.swift +98 -0
  37. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +229 -168
  38. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentationInvariant.swift +19 -0
  39. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +110 -0
  40. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +206 -0
  41. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +147 -25
  42. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +21 -18
  43. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +24 -307
  44. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +206 -0
  45. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +158 -0
  46. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputCandidatePolicy.swift +19 -0
  47. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +49 -16
  48. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +2 -11
  49. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +4 -19
  50. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +16 -20
  51. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +111 -0
  52. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +42 -81
  53. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +35 -82
  54. package/dist/apple/snapshot-presentation/Package.swift +21 -0
  55. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotGeometry.swift +56 -0
  56. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotModels.swift +259 -0
  57. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentation.swift +153 -0
  58. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationFailure.swift +27 -0
  59. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationInvariant.swift +91 -0
  60. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationProjection.swift +161 -0
  61. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotScopePolicy.swift +53 -0
  62. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFold.swift +242 -0
  63. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFoldProjection.swift +97 -0
  64. package/dist/src/adapter.js +1 -0
  65. package/dist/src/adb-executor.js +2 -0
  66. package/dist/src/adb-failure.js +1 -0
  67. package/dist/src/adb-host.js +1 -0
  68. package/dist/src/adb.js +1 -0
  69. package/dist/src/agent-browser-lifecycle.js +1 -0
  70. package/dist/src/agent-browser-process-record.js +1 -0
  71. package/dist/src/agent-browser-provider.js +1 -0
  72. package/dist/src/agent-browser-tool.js +1 -1
  73. package/dist/src/agent-device-client.js +2 -2
  74. package/dist/src/ai-sdk.d.ts +61 -0
  75. package/dist/src/ai-sdk.js +1 -0
  76. package/dist/src/alert-contract.js +1 -0
  77. package/dist/src/alert-runtime.js +1 -0
  78. package/dist/src/android-adb.d.ts +27 -19
  79. package/dist/src/android-adb.js +1 -1
  80. package/dist/src/android-input-ownership.js +1 -0
  81. package/dist/src/android-system-chrome.js +1 -0
  82. package/dist/src/android.js +1 -1
  83. package/dist/src/api.js +11 -0
  84. package/dist/src/app-catalog.js +1 -0
  85. package/dist/src/app-deployment-resolution.js +2 -0
  86. package/dist/src/app-helpers.js +1 -1
  87. package/dist/src/app-inventory-contract.js +1 -0
  88. package/dist/src/app-inventory.js +1 -0
  89. package/dist/src/app-launch.js +2 -0
  90. package/dist/src/app-lifecycle-facade.js +1 -0
  91. package/dist/src/app-lifecycle.js +3 -6
  92. package/dist/src/app-lifecycle2.js +2 -2
  93. package/dist/src/app-lifecycle3.js +1 -0
  94. package/dist/src/app-log-files.js +1 -1
  95. package/dist/src/app-log-reconnect.js +1 -1
  96. package/dist/src/app-log-runtime.js +1 -4
  97. package/dist/src/app-log-runtime2.js +4 -0
  98. package/dist/src/app-parsers.js +4 -0
  99. package/dist/src/app-resolution-cache.js +1 -0
  100. package/dist/src/app-resolution-facade.js +1 -0
  101. package/dist/src/app-resolution.js +2 -2
  102. package/dist/src/app-state.js +1 -2
  103. package/dist/src/application-lifecycle-interaction.js +1 -0
  104. package/dist/src/application-lifecycle-runtime.js +1 -0
  105. package/dist/src/apps-simctl.js +1 -0
  106. package/dist/src/archive.js +4 -0
  107. package/dist/src/artifact-download.js +1 -0
  108. package/dist/src/artifacts.d.ts +6 -2
  109. package/dist/src/artifacts.js +1 -1
  110. package/dist/src/async-lifecycle.js +1 -0
  111. package/dist/src/audio-probe-result.js +1 -0
  112. package/dist/src/audio-probe-runtime.js +1 -0
  113. package/dist/src/audio-probe-support.js +1 -0
  114. package/dist/src/auth-session.js +1 -1
  115. package/dist/src/auth.js +1 -1
  116. package/dist/src/back-mode.js +1 -0
  117. package/dist/src/{url.js → base-url.js} +1 -1
  118. package/dist/src/boot-diagnostics.js +2 -2
  119. package/dist/src/byte-limit-stream.js +1 -1
  120. package/dist/src/capabilities.js +1 -0
  121. package/dist/src/capture.js +1 -0
  122. package/dist/src/catalog.js +1 -0
  123. package/dist/src/cli-command-aliases.js +1 -0
  124. package/dist/src/cli-config.js +1 -1
  125. package/dist/src/cli-help.js +77 -51
  126. package/dist/src/cli-output.js +1 -1
  127. package/dist/src/cli-runner.js +1 -1
  128. package/dist/src/cli.js +5 -7
  129. package/dist/src/click-button.js +1 -0
  130. package/dist/src/client-metro-companion.js +1 -0
  131. package/dist/src/client-metro.js +1 -1
  132. package/dist/src/client-types.d.ts +1606 -0
  133. package/dist/src/clipboard.js +1 -0
  134. package/dist/src/code-signature-cache.js +1 -0
  135. package/dist/src/code-signature.js +1 -0
  136. package/dist/src/color.js +1 -0
  137. package/dist/src/command-metadata.js +1 -1
  138. package/dist/src/command-schema.js +3 -3
  139. package/dist/src/command-surface.js +1 -1
  140. package/dist/src/command-tools.js +2 -0
  141. package/dist/src/command.js +1 -0
  142. package/dist/src/{selector-vocabulary.js → compatibility-policy.js} +1 -1
  143. package/dist/src/config.js +1 -1
  144. package/dist/src/config2.js +1 -0
  145. package/dist/src/connection-runtime.js +1 -1
  146. package/dist/src/connection.js +5 -5
  147. package/dist/src/contracts.d.ts +1 -1
  148. package/dist/src/contracts.js +1 -1
  149. package/dist/src/daemon-client-lifecycle.js +1 -0
  150. package/dist/src/daemon-process.js +1 -0
  151. package/dist/src/daemon-registration.js +1 -0
  152. package/dist/src/daemon-shutdown-report.js +1 -1
  153. package/dist/src/daemon.js +2 -2
  154. package/dist/src/debug-symbols-facade.js +5 -0
  155. package/dist/src/deploy-result-target.js +1 -0
  156. package/dist/src/device-claim-inspection.js +1 -1
  157. package/dist/src/device-claim-rule.js +1 -0
  158. package/dist/src/device-input-state.d.ts +190 -3
  159. package/dist/src/device-inventory-context.js +1 -0
  160. package/dist/src/device-isolation.js +1 -0
  161. package/dist/src/device-release.js +1 -0
  162. package/dist/src/device-rotation.js +1 -0
  163. package/dist/src/device-selection-resolver.js +1 -0
  164. package/dist/src/device-session.js +1 -0
  165. package/dist/src/device.js +1 -1
  166. package/dist/src/device2.js +3 -2
  167. package/dist/src/diagnostics.js +3 -0
  168. package/dist/src/diagnostics2.js +1 -0
  169. package/dist/src/dispatch-resolve.js +1 -0
  170. package/dist/src/doctor-facade.js +1 -0
  171. package/dist/src/doctor-output.js +1 -0
  172. package/dist/src/doctor.js +1 -0
  173. package/dist/src/doctor2.js +1 -0
  174. package/dist/src/doctor3.js +1 -0
  175. package/dist/src/durable-resource.js +1 -0
  176. package/dist/src/entities.js +1 -1
  177. package/dist/src/error.js +2 -0
  178. package/dist/src/exec.js +1 -0
  179. package/dist/src/file.js +1 -0
  180. package/dist/src/find.js +1 -1
  181. package/dist/src/finders.js +1 -1
  182. package/dist/src/format.js +2 -0
  183. package/dist/src/generic-settle.js +1 -1
  184. package/dist/src/generic.js +1 -1
  185. package/dist/src/gesture-admission.js +1 -0
  186. package/dist/src/gesture-plan-types.js +1 -0
  187. package/dist/src/gesture-plan.js +1 -0
  188. package/dist/src/gesture-runtime.js +1 -0
  189. package/dist/src/harmonyos.js +1 -1
  190. package/dist/src/hdc.js +1 -1
  191. package/dist/src/helper.js +2 -2
  192. package/dist/src/host-file.js +1 -0
  193. package/dist/src/human-control-contract.js +1 -0
  194. package/dist/src/human-control.js +1 -0
  195. package/dist/src/index.d.ts +2 -1578
  196. package/dist/src/index.js +1 -1
  197. package/dist/src/input-actions.js +1 -4
  198. package/dist/src/input-actions2.js +2 -1
  199. package/dist/src/install-artifact-facade.js +1 -0
  200. package/dist/src/install-source-network-transport.js +1 -0
  201. package/dist/src/install-source.d.ts +3 -10
  202. package/dist/src/install-source2.js +1 -1
  203. package/dist/src/interaction-positionals.js +1 -0
  204. package/dist/src/interaction-ref-policy.js +1 -0
  205. package/dist/src/interaction.js +1 -1
  206. package/dist/src/interaction2.js +1 -1
  207. package/dist/src/interactor-operation-catalog.js +1 -0
  208. package/dist/src/interactor-types.js +1 -0
  209. package/dist/src/interactor.js +6 -2
  210. package/dist/src/interactor2.js +1 -1
  211. package/dist/src/interactors.js +1 -1
  212. package/dist/src/internal/bin.d.ts +1 -1
  213. package/dist/src/internal/bin.js +1 -2
  214. package/dist/src/internal/companion-tunnel.d.ts +1 -1
  215. package/dist/src/internal/companion-tunnel.js +1 -1
  216. package/dist/src/internal/daemon.d.ts +1 -1
  217. package/dist/src/internal/daemon.js +2 -4
  218. package/dist/src/internal/png-worker.d.ts +4 -4
  219. package/dist/src/internal/png-worker.js +1 -1
  220. package/dist/src/internal/run-script-http-child.d.ts +4 -0
  221. package/dist/src/internal/run-script-http-child.js +1 -0
  222. package/dist/src/internal/update-check-entry.d.ts +1 -1
  223. package/dist/src/inventory.js +4 -4
  224. package/dist/src/inventory2.js +1 -1
  225. package/dist/src/inventory3.js +1 -1
  226. package/dist/src/inventory5.js +1 -1
  227. package/dist/src/inventory6.js +1 -1
  228. package/dist/src/ios-client.js +16 -0
  229. package/dist/src/ios-snapshot-acquisition.js +1 -0
  230. package/dist/src/ios-snapshot-adapter.js +1 -0
  231. package/dist/src/ios-snapshot-planning.js +1 -0
  232. package/dist/src/ios-snapshot-runtime.js +1 -0
  233. package/dist/src/json.js +1 -0
  234. package/dist/src/keyboard-actions.js +1 -0
  235. package/dist/src/launch-console.js +1 -0
  236. package/dist/src/lease-context.js +1 -1
  237. package/dist/src/lease-scope.js +1 -0
  238. package/dist/src/lease.js +1 -1
  239. package/dist/src/limrun-runtime-dependencies.js +1 -1
  240. package/dist/src/limrun.d.ts +1618 -7
  241. package/dist/src/limrun.js +1 -1
  242. package/dist/src/linux.js +1 -1
  243. package/dist/src/local-interactor-operation-set.js +1 -0
  244. package/dist/src/location-coordinates.js +1 -1
  245. package/dist/src/logcat.js +1 -1
  246. package/dist/src/macos-facade.js +1 -0
  247. package/dist/src/managed-device-scope.js +1 -0
  248. package/dist/src/mechanics.js +19 -0
  249. package/dist/src/metro.d.ts +4 -2
  250. package/dist/src/metro.js +1 -1
  251. package/dist/src/metro2.js +1 -1
  252. package/dist/src/mobile-snapshot-semantics.js +1 -0
  253. package/dist/src/multitouch-support.js +1 -0
  254. package/dist/src/numeric.js +1 -0
  255. package/dist/src/observability.js +1 -1
  256. package/dist/src/observation.js +2 -0
  257. package/dist/src/output-path.js +1 -1
  258. package/dist/src/owner-identity.js +3 -1
  259. package/dist/src/parameterized-recorded-fill.js +1 -0
  260. package/dist/src/parser.js +1 -1
  261. package/dist/src/perf-facade.js +2 -0
  262. package/dist/src/perf-runtime-operation-builder.js +1 -0
  263. package/dist/src/perf-runtime-plan.js +1 -0
  264. package/dist/src/perf-runtime.js +1 -0
  265. package/dist/src/perf.js +1 -4
  266. package/dist/src/physical-device-control.js +1 -1
  267. package/dist/src/physical-device-facade.js +1 -0
  268. package/dist/src/platform-request-scope.js +1 -0
  269. package/dist/src/platform-runtime-android-adb-host.js +1 -0
  270. package/dist/src/platform-runtime-android-mechanics.js +1 -0
  271. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  272. package/dist/src/platform-runtime-app-log-process.js +1 -1
  273. package/dist/src/platform-runtime-apple-resources.js +1 -0
  274. package/dist/src/platform-runtime-host.js +1 -1
  275. package/dist/src/platform-runtime-managed-owner.js +1 -0
  276. package/dist/src/platform-runtime-network-web-transport.js +1 -1
  277. package/dist/src/platform-runtime-open-target.js +1 -1
  278. package/dist/src/platform-runtime-operation-host.js +4 -4
  279. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  280. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  281. package/dist/src/platform-runtime-screen-recording-apple-runner-host.js +1 -1
  282. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  283. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  284. package/dist/src/platform-runtime-screen-recording-apple-transport.js +1 -1
  285. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  286. package/dist/src/platform-runtime-screen-recording-harmony-host.js +1 -1
  287. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -0
  288. package/dist/src/platform-runtime-screen-recording-web-host.js +1 -1
  289. package/dist/src/platform-runtime-unavailable.js +1 -0
  290. package/dist/src/platform-runtime.js +1 -1
  291. package/dist/src/platform-runtime2.js +1 -1
  292. package/dist/src/plist.js +1 -1
  293. package/dist/src/png-worker-contract.js +1 -1
  294. package/dist/src/prepare-kind.js +1 -15
  295. package/dist/src/process-exit.js +1 -1
  296. package/dist/src/process.js +1 -0
  297. package/dist/src/provider-device-runtime.js +1 -0
  298. package/dist/src/provider-session-ownership.js +2 -0
  299. package/dist/src/provider-webdriver.js +1 -1
  300. package/dist/src/provider.js +1 -1
  301. package/dist/src/providers.js +1 -0
  302. package/dist/src/proxy.js +2 -2
  303. package/dist/src/react-native-overlay.js +1 -0
  304. package/dist/src/react-native.js +1 -1
  305. package/dist/src/record-runtime.js +1 -1
  306. package/dist/src/record-trace.js +1 -1
  307. package/dist/src/rect-center.js +1 -0
  308. package/dist/src/register-builtins.js +1 -0
  309. package/dist/src/registry.js +39 -28
  310. package/dist/src/remote-config2.js +1 -1
  311. package/dist/src/replay-divergence.js +2 -0
  312. package/dist/src/replay.js +2 -2
  313. package/dist/src/reporting.js +8 -8
  314. package/dist/src/request-admission.js +1 -0
  315. package/dist/src/request-cancel.js +1 -0
  316. package/dist/src/request-cancellation.js +1 -0
  317. package/dist/src/request-progress-protocol.js +1 -0
  318. package/dist/src/request-providers.js +1 -0
  319. package/dist/src/request-runtime-binding.js +1 -0
  320. package/dist/src/request.js +1 -0
  321. package/dist/src/result-serialization.js +1 -0
  322. package/dist/src/retry.js +1 -0
  323. package/dist/src/rolldown-runtime.js +1 -1
  324. package/dist/src/runner-cache-metadata.js +2 -0
  325. package/dist/src/runner-client.js +1 -1
  326. package/dist/src/runner-disposal.js +1 -1
  327. package/dist/src/runner-operations-facade.js +1 -0
  328. package/dist/src/runner-owner-facade.js +1 -0
  329. package/dist/src/runner-owner-state.js +1 -0
  330. package/dist/src/runner-presentation.js +1 -0
  331. package/dist/src/runner-selector-query.js +1 -0
  332. package/dist/src/runner.js +1 -0
  333. package/dist/src/{runtime-transport.js → runtime-transport-hints.js} +1 -1
  334. package/dist/src/runtime.js +3 -3
  335. package/dist/src/runtime2.js +4 -4
  336. package/dist/src/runtime3.js +1 -1
  337. package/dist/src/runtime4.js +4 -3
  338. package/dist/src/runtime5.js +1 -1
  339. package/dist/src/runtime6.js +1 -1
  340. package/dist/src/runtime7.js +1 -1
  341. package/dist/src/runtime8.js +1 -1
  342. package/dist/src/runtime9.js +1 -1
  343. package/dist/src/scoped-provider.js +1 -1
  344. package/dist/src/screen-recording-completion.js +1 -0
  345. package/dist/src/screen-recording-live-handle.js +1 -0
  346. package/dist/src/screen-recording-options.js +1 -0
  347. package/dist/src/screen-recording-resource-recovery.js +1 -0
  348. package/dist/src/screen-recording-runtime.js +1 -0
  349. package/dist/src/screenshot-density.js +1 -1
  350. package/dist/src/screenshot-result.js +1 -6
  351. package/dist/src/screenshot-runtime.js +1 -0
  352. package/dist/src/screenshot.js +1 -1
  353. package/dist/src/screenshot2.js +1 -0
  354. package/dist/src/screenshot3.js +5 -0
  355. package/dist/src/script.js +1 -0
  356. package/dist/src/scroll-command.js +1 -0
  357. package/dist/src/scroll-gesture.js +1 -0
  358. package/dist/src/scroll-indicator.js +1 -0
  359. package/dist/src/sdk-batch-runner.d.ts +23 -6
  360. package/dist/src/sdk-batch-runner.js +2 -3
  361. package/dist/src/sdk-batch.js +2 -0
  362. package/dist/src/sdk-contracts.d.ts +222 -99
  363. package/dist/src/sdk-contracts.js +2 -2
  364. package/dist/src/sdk-device.js +1 -1
  365. package/dist/src/sdk-finders.d.ts +1 -1
  366. package/dist/src/sdk-finders.js +1 -1
  367. package/dist/src/sdk-io.js +1 -1
  368. package/dist/src/sdk-remote-config.d.ts +1 -1
  369. package/dist/src/sdk-remote-config.js +1 -1
  370. package/dist/src/sdk-selectors.d.ts +539 -199
  371. package/dist/src/sdk-selectors.js +1 -1
  372. package/dist/src/selection.js +1 -0
  373. package/dist/src/selector-runtime.js +1 -1
  374. package/dist/src/selectors.js +1 -1
  375. package/dist/src/semantic-index.js +1 -0
  376. package/dist/src/server.js +10 -3
  377. package/dist/src/session-allocation.js +1 -0
  378. package/dist/src/session-artifact-paths.js +1 -0
  379. package/dist/src/session-paths.js +1 -0
  380. package/dist/src/session-repair-tombstone.js +1 -0
  381. package/dist/src/session-routing.js +1 -0
  382. package/dist/src/session-runtime-admission.js +1 -0
  383. package/dist/src/session-snapshot.js +1 -0
  384. package/dist/src/session-store.js +4 -0
  385. package/dist/src/session-target-evidence.js +1 -1
  386. package/dist/src/session2.js +5 -28
  387. package/dist/src/settings.js +1 -0
  388. package/dist/src/shared.js +2 -2
  389. package/dist/src/simctl-facade.js +1 -0
  390. package/dist/src/simctl.js +1 -1
  391. package/dist/src/simulator-facade.js +1 -0
  392. package/dist/src/simulator.js +1 -1
  393. package/dist/src/snapshot-capture-annotations.js +1 -0
  394. package/dist/src/snapshot-desktop-projection.js +1 -0
  395. package/dist/src/snapshot-diagnostics.js +1 -0
  396. package/dist/src/snapshot-process.js +1 -0
  397. package/dist/src/snapshot-quality-backend-capabilities.js +1 -0
  398. package/dist/src/snapshot-quality-verdict.js +1 -0
  399. package/dist/src/snapshot-runtime-binding.js +1 -0
  400. package/dist/src/snapshot-runtime.js +1 -1
  401. package/dist/src/snapshot-scope.js +1 -0
  402. package/dist/src/snapshot-state.js +1 -0
  403. package/dist/src/snapshot.js +1 -8
  404. package/dist/src/snapshot2.js +1 -1
  405. package/dist/src/snapshot3.js +1 -1
  406. package/dist/src/snapshot4.js +1 -0
  407. package/dist/src/src.js +1 -1
  408. package/dist/src/src2.js +1 -1
  409. package/dist/src/src3.js +1 -2
  410. package/dist/src/src4.js +1 -0
  411. package/dist/src/src5.js +1 -0
  412. package/dist/src/src6.js +1 -0
  413. package/dist/src/src7.js +2 -0
  414. package/dist/src/src8.js +115 -0
  415. package/dist/src/startup-recovery-fence.js +1 -0
  416. package/dist/src/status-markers.js +1 -0
  417. package/dist/src/string-enum.js +1 -1
  418. package/dist/src/success-text.js +1 -0
  419. package/dist/src/surface-snapshot.js +1 -0
  420. package/dist/src/surface-snapshot2.js +1 -0
  421. package/dist/src/takeover.js +5 -0
  422. package/dist/src/tool-provider-facade.js +1 -0
  423. package/dist/src/tool-provider.js +4 -3
  424. package/dist/src/tool-provider2.js +1 -1
  425. package/dist/src/tool-provider3.js +1 -0
  426. package/dist/src/toolchain-probe.js +2 -0
  427. package/dist/src/touch-runtime.js +1 -0
  428. package/dist/src/transport.js +3 -0
  429. package/dist/src/tree.js +1 -0
  430. package/dist/src/ttl-memo.js +1 -0
  431. package/dist/src/tv-remote.js +1 -0
  432. package/dist/src/type-text-runtime.js +1 -0
  433. package/dist/src/undici.js +99 -0
  434. package/dist/src/update-check.js +1 -1
  435. package/dist/src/verified-file.js +1 -1
  436. package/dist/src/version.js +1 -1
  437. package/dist/src/video.js +2 -2
  438. package/dist/src/web.js +2 -1
  439. package/dist/src/web2.js +1 -0
  440. package/dist/src/webdriver-ios-snapshot.js +1 -0
  441. package/dist/src/webdriver-source.js +1 -0
  442. package/linux/atspi-dump.py +54 -9
  443. package/package.json +73 -38
  444. package/server.json +9 -3
  445. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.9.apk.sha256 +0 -1
  446. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.9.apk +0 -0
  447. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.9.apk.sha256 +0 -1
  448. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CoordinateTextEntryTests.swift +0 -4
  449. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -75
  450. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +0 -6
  451. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+RecordingTests.swift +0 -4
  452. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SelectorMatchPolicyTests.swift +0 -4
  453. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +0 -4
  454. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryPolicyTests.swift +0 -4
  455. package/dist/src/appearance.js +0 -1
  456. package/dist/src/apps.js +0 -5
  457. package/dist/src/audio-probe.js +0 -1
  458. package/dist/src/cancel.js +0 -1
  459. package/dist/src/cli-grammar.js +0 -1
  460. package/dist/src/command-catalog.js +0 -1
  461. package/dist/src/debug-symbols.js +0 -5
  462. package/dist/src/device-input-state.js +0 -2
  463. package/dist/src/device-ready.js +0 -1
  464. package/dist/src/dispatch.js +0 -1
  465. package/dist/src/handler-utils.js +0 -1
  466. package/dist/src/helper-package-install.js +0 -1
  467. package/dist/src/host-process.js +0 -3
  468. package/dist/src/ime-lifecycle.js +0 -2
  469. package/dist/src/ime-recovery-marker.js +0 -1
  470. package/dist/src/input-actions3.js +0 -1
  471. package/dist/src/install-artifact.js +0 -1
  472. package/dist/src/install-artifact2.js +0 -1
  473. package/dist/src/interaction-common.js +0 -1
  474. package/dist/src/interaction-runtime.js +0 -1
  475. package/dist/src/interaction-snapshot.js +0 -1
  476. package/dist/src/manifest.js +0 -1
  477. package/dist/src/method-scope.js +0 -1
  478. package/dist/src/png-resize.js +0 -1
  479. package/dist/src/process-lock.js +0 -1
  480. package/dist/src/runner-lease.js +0 -1
  481. package/dist/src/runner-sequence.js +0 -1
  482. package/dist/src/screenshot-geometry.js +0 -1
  483. package/dist/src/sdk-android-adb.d.ts +0 -245
  484. package/dist/src/sdk-android-adb.js +0 -4
  485. package/dist/src/sdk.js +0 -1
  486. package/dist/src/setting-state.js +0 -1
  487. package/dist/src/shell-quote.js +0 -1
  488. package/dist/src/snapshot-session.js +0 -1
  489. package/dist/src/timing-safe-equal.js +0 -1
  490. package/dist/src/unsupported-interactor.js +0 -1
  491. package/dist/src/verdict.js +0 -1
  492. /package/dist/src/{recording-telemetry.js → telemetry.js} +0 -0
@@ -0,0 +1,98 @@
1
+ import XCTest
2
+
3
+ enum SnapshotBackendEnvironment {
4
+ case simulator
5
+ case physicalDevice
6
+ }
7
+
8
+ enum SnapshotRegularDepthCapability: String {
9
+ /// The backend can stop acquisition at the requested regular presented-depth frontier.
10
+ case presentedFrontier = "presented-frontier"
11
+ /// The backend is flat; it can answer the root and one presented level, but has no hierarchy
12
+ /// from which to prove deeper regular depth.
13
+ case flat
14
+ /// The backend can return raw traversal depth, but cannot prove regular presented depth.
15
+ case rawOnly = "raw-only"
16
+ }
17
+
18
+ enum SnapshotBackendKind: String, CaseIterable {
19
+ case recursiveTree = "tree"
20
+ case querySweep = "queries"
21
+ case privateAX = "private-ax"
22
+
23
+ var isForceable: Bool {
24
+ switch self {
25
+ case .recursiveTree, .privateAX:
26
+ return true
27
+ case .querySweep:
28
+ return false
29
+ }
30
+ }
31
+
32
+ var hittableSemantics: String {
33
+ "geometric-actionability"
34
+ }
35
+
36
+ var usesXCTestAccessibilityChannel: Bool {
37
+ switch self {
38
+ case .recursiveTree, .querySweep:
39
+ return true
40
+ case .privateAX:
41
+ return false
42
+ }
43
+ }
44
+
45
+ /// The raw projection is the acquired tree, so only a backend that enumerates a hierarchy can
46
+ /// serve it. The query sweep answers an interactive element query: it has no hierarchy to
47
+ /// return, and planning it for `--raw` is exactly how a raw request gets answered with regular
48
+ /// membership.
49
+ var supportsRawProjection: Bool {
50
+ switch self {
51
+ case .recursiveTree, .privateAX:
52
+ return true
53
+ case .querySweep:
54
+ return false
55
+ }
56
+ }
57
+
58
+ var regularDepthCapability: SnapshotRegularDepthCapability {
59
+ switch self {
60
+ case .recursiveTree:
61
+ return .presentedFrontier
62
+ case .querySweep:
63
+ return .flat
64
+ case .privateAX:
65
+ return .rawOnly
66
+ }
67
+ }
68
+
69
+ func canServeRegularPresentedDepth(_ requestedDepth: Int?) -> Bool {
70
+ guard let requestedDepth else { return true }
71
+ switch regularDepthCapability {
72
+ case .presentedFrontier:
73
+ return true
74
+ case .flat:
75
+ return requestedDepth <= 1
76
+ case .rawOnly:
77
+ return false
78
+ }
79
+ }
80
+
81
+ var isAvailableOnCurrentPlatform: Bool {
82
+ #if os(iOS) && targetEnvironment(simulator)
83
+ return isAvailable(on: .simulator)
84
+ #else
85
+ return isAvailable(on: .physicalDevice)
86
+ #endif
87
+ }
88
+
89
+ func isAvailable(on environment: SnapshotBackendEnvironment) -> Bool {
90
+ switch self {
91
+ case .recursiveTree, .querySweep:
92
+ return true
93
+ case .privateAX:
94
+ return environment == .simulator
95
+ }
96
+ }
97
+ }
98
+
@@ -1,4 +1,5 @@
1
1
  import XCTest
2
+ import AgentDeviceSnapshotPresentation
2
3
 
3
4
  // MARK: - Snapshot capture plans (ADR 0004)
4
5
  //
@@ -14,9 +15,11 @@ struct SnapshotQuality: Codable {
14
15
  let state: String
15
16
  /// Backend that produced the returned payload: tree | queries | private-ax.
16
17
  let backend: String
17
- /// Why recovery ran (first failure) or why the payload is degraded.
18
+ /// Why recovery ran (first failure), why the payload is degraded, or why an internal backend
19
+ /// selection was honored.
18
20
  let reason: String?
19
- /// Machine-readable reason: ax-rejected | sparse-tree | budget | no-nodes.
21
+ /// Machine-readable reason: ax-rejected | sparse-tree | budget | no-nodes | capture-failed |
22
+ /// presentation-failed | deferred | requested-backend.
20
23
  let reasonCode: String?
21
24
  /// Private AX ladder cap when the accepted tree is shallower than requested.
22
25
  let effectiveDepth: Int?
@@ -24,49 +27,8 @@ struct SnapshotQuality: Codable {
24
27
  let collapsedLeafIndexes: [Int]?
25
28
  /// Coverage of the bounded custom-action pass, when the capture asked for one.
26
29
  let customActions: SnapshotCustomActionCoverage?
27
- }
28
-
29
- /// How much of the merged-element set the custom-action pass actually read. An
30
- /// unread element renders exactly like one with no actions, so a partial pass
31
- /// has to say so — otherwise absence reads as proof of absence.
32
- struct SnapshotCustomActionCoverage: Codable {
33
- let read: Int
34
- let candidates: Int
35
- /// Elements whose action list was clipped by the per-element output caps. A
36
- /// clipped list looks complete, so it is disclosed on the same principle as
37
- /// an unread element.
38
- let truncated: Int
39
- /// The pass stopped early because an earlier read is still hung. Distinct from
40
- /// a budget stop: the remedy is waiting, not scrolling.
41
- let blocked: Bool
42
- }
43
-
44
- enum SnapshotBackendKind: String, CaseIterable {
45
- case recursiveTree = "tree"
46
- case querySweep = "queries"
47
- case privateAX = "private-ax"
48
-
49
- var usesXCTestAccessibilityChannel: Bool {
50
- switch self {
51
- case .recursiveTree, .querySweep:
52
- return true
53
- case .privateAX:
54
- return false
55
- }
56
- }
57
-
58
- var isAvailableOnCurrentPlatform: Bool {
59
- switch self {
60
- case .recursiveTree, .querySweep:
61
- return true
62
- case .privateAX:
63
- #if os(iOS) && targetEnvironment(simulator)
64
- return true
65
- #else
66
- return false
67
- #endif
68
- }
69
- }
30
+ /// Response-level timing for the accepted backend attempt, never repeated per node.
31
+ var timing: SnapshotCaptureTiming? = nil
70
32
  }
71
33
 
72
34
  enum SnapshotXCTestChannelPlanState: Equatable {
@@ -79,6 +41,9 @@ struct EffectiveSnapshotCapturePlan {
79
41
  let plan: [SnapshotBackendKind]
80
42
  let xCTestChannelState: SnapshotXCTestChannelPlanState
81
43
  let treeCaptureSliceBudgetOverride: TimeInterval?
44
+ /// Non-nil only when the plan was narrowed by an explicit internal backend preference. This
45
+ /// keeps the quality marker tied to the plan decision rather than to an untrusted request field.
46
+ let preferredBackend: SnapshotBackendKind?
82
47
  }
83
48
 
84
49
  /// What the plan runner does when every backend failed or stayed sparse.
@@ -93,13 +58,26 @@ enum SnapshotCaptureTerminalPolicy {
93
58
 
94
59
  struct SnapshotBackendCapture {
95
60
  let payload: DataPayload
96
- /// Set by the private AX backend when the ladder accepted a shallower depth than requested.
97
61
  let effectiveDepth: Int?
98
- /// Set by the private AX backend when the capture asked for custom actions.
99
62
  var customActions: SnapshotCustomActionCoverage? = nil
63
+ var qualityPayload: DataPayload? = nil
64
+ var timing: SnapshotCaptureTiming? = nil
100
65
  }
101
66
 
102
67
  extension RunnerTests {
68
+ static func makeSnapshotBackendCapture(
69
+ from result: AgentDeviceSnapshotPresentation.SnapshotPresentationResult
70
+ ) -> SnapshotBackendCapture {
71
+ SnapshotBackendCapture(
72
+ payload: DataPayload(nodes: result.nodes, truncated: result.truncated),
73
+ effectiveDepth: result.effectiveDepth,
74
+ customActions: result.customActions,
75
+ qualityPayload: result.qualityNodes.map {
76
+ DataPayload(nodes: $0, truncated: result.truncated)
77
+ }
78
+ )
79
+ }
80
+
103
81
  static let sparseRecoveryTruncatedNodeThreshold = 8
104
82
  /// Umbrella wall-clock budget for one capture plan. Individual backends bound themselves,
105
83
  /// but chained recovery tiers must never stack past the 30s main-thread watchdog: when the
@@ -115,7 +93,10 @@ extension RunnerTests {
115
93
  // MARK: Plan definitions
116
94
 
117
95
  static let regularVisiblePlan: [SnapshotBackendKind] = [.recursiveTree, .querySweep, .privateAX]
118
- static let rawDiagnosticPlan: [SnapshotBackendKind] = [.recursiveTree, .privateAX]
96
+ /// Derived from the backend trait rather than hand-listed: a backend that cannot serve the raw
97
+ /// projection drops out of the raw plan by construction, and a new one joins it by declaring the
98
+ /// trait instead of by someone remembering this line.
99
+ static let rawDiagnosticPlan: [SnapshotBackendKind] = regularVisiblePlan.filter(\.supportsRawProjection)
119
100
 
120
101
  // MARK: XCTest accessibility channel penalty (cross-attempt memory, #1105/#1156)
121
102
  //
@@ -175,8 +156,15 @@ extension RunnerTests {
175
156
  /// cause that does not exist.
176
157
  static func xcTestChannelStateFirstFailure(
177
158
  _ state: SnapshotXCTestChannelPlanState,
178
- requestPinnedBackend: Bool = false
159
+ requestPinnedBackend: Bool = false,
160
+ preferredBackend: String? = nil
179
161
  ) -> (reason: String, code: String)? {
162
+ if state == .normal && preferredBackend == SnapshotBackendKind.recursiveTree.rawValue {
163
+ return (
164
+ "the recursive XCTest tree backend was explicitly selected for this capture",
165
+ "requested-backend"
166
+ )
167
+ }
180
168
  switch state {
181
169
  case .normal:
182
170
  return nil
@@ -210,19 +198,36 @@ extension RunnerTests {
210
198
  penalized || preferredBackend == SnapshotBackendKind.privateAX.rawValue
211
199
  }
212
200
 
213
- /// Pure plan-reorder rule: a penalized XCTest accessibility channel uses independent backends
214
- /// when the platform has one, otherwise it keeps XCTest work on a short probe. The raw
215
- /// diagnostic plan keeps tree-first errors, and unknown plans are left untouched.
201
+ /// Pure plan-reorder rule: an internal preferred backend pins the regular plan to that backend;
202
+ /// this is the only force seam used by same-backend evidence and conformance captures. A
203
+ /// penalized XCTest accessibility channel uses independent backends when the platform has one,
204
+ /// otherwise it keeps XCTest work on a short probe. The raw diagnostic plan keeps tree-first
205
+ /// errors, and unknown plans are left untouched.
216
206
  static func effectiveSnapshotCapturePlan(
217
207
  _ plan: [SnapshotBackendKind],
218
208
  xCTestChannelPenalized: Bool,
219
- availableBackends: Set<SnapshotBackendKind> = Set(SnapshotBackendKind.allCases)
209
+ availableBackends: Set<SnapshotBackendKind> = Set(SnapshotBackendKind.allCases),
210
+ preferredBackend: String? = nil
220
211
  ) -> EffectiveSnapshotCapturePlan {
212
+ if
213
+ plan == Self.regularVisiblePlan,
214
+ let preferred = preferredBackend.flatMap(SnapshotBackendKind.init(rawValue:)),
215
+ preferred.isForceable,
216
+ availableBackends.contains(preferred)
217
+ {
218
+ return EffectiveSnapshotCapturePlan(
219
+ plan: [preferred],
220
+ xCTestChannelState: preferred == .privateAX ? .deferredToIndependentBackend : .normal,
221
+ treeCaptureSliceBudgetOverride: nil,
222
+ preferredBackend: preferred
223
+ )
224
+ }
221
225
  guard xCTestChannelPenalized, plan == Self.regularVisiblePlan else {
222
226
  return EffectiveSnapshotCapturePlan(
223
227
  plan: plan,
224
228
  xCTestChannelState: .normal,
225
- treeCaptureSliceBudgetOverride: nil
229
+ treeCaptureSliceBudgetOverride: nil,
230
+ preferredBackend: nil
226
231
  )
227
232
  }
228
233
  let availablePlan = plan.filter { availableBackends.contains($0) }
@@ -231,13 +236,15 @@ extension RunnerTests {
231
236
  return EffectiveSnapshotCapturePlan(
232
237
  plan: recoveryPlan,
233
238
  xCTestChannelState: .deferredToIndependentBackend,
234
- treeCaptureSliceBudgetOverride: nil
239
+ treeCaptureSliceBudgetOverride: nil,
240
+ preferredBackend: nil
235
241
  )
236
242
  }
237
243
  return EffectiveSnapshotCapturePlan(
238
244
  plan: availablePlan.filter(\.usesXCTestAccessibilityChannel),
239
245
  xCTestChannelState: .boundedXCTestProbe,
240
- treeCaptureSliceBudgetOverride: Self.penalizedXCTestProbeTreeSliceBudget
246
+ treeCaptureSliceBudgetOverride: Self.penalizedXCTestProbeTreeSliceBudget,
247
+ preferredBackend: nil
241
248
  )
242
249
  }
243
250
 
@@ -250,7 +257,7 @@ extension RunnerTests {
250
257
  func runSnapshotCapturePlan(
251
258
  _ plan: [SnapshotBackendKind],
252
259
  app: XCUIApplication,
253
- options: SnapshotOptions,
260
+ options: PresentationOptions,
254
261
  terminal: SnapshotCaptureTerminalPolicy,
255
262
  deadline: Date? = nil
256
263
  ) throws -> DataPayload {
@@ -279,14 +286,16 @@ extension RunnerTests {
279
286
  let effective = Self.effectiveSnapshotCapturePlan(
280
287
  plan,
281
288
  xCTestChannelPenalized: xCTestChannelPenalized,
282
- availableBackends: Set(SnapshotBackendKind.allCases.filter(\.isAvailableOnCurrentPlatform))
289
+ availableBackends: Set(SnapshotBackendKind.allCases.filter(\.isAvailableOnCurrentPlatform)),
290
+ preferredBackend: options.preferredBackend
283
291
  )
284
292
  let effectivePlan = effective.plan
285
293
  // Only a customActions-implied pin is request-pinned; the daemon's
286
294
  // same-backend evidence probe pins for its own reasons and keeps 'deferred'.
287
295
  firstFailure = Self.xcTestChannelStateFirstFailure(
288
296
  effective.xCTestChannelState,
289
- requestPinnedBackend: options.customActions && !xCTestChannelPenalizedByBreaker
297
+ requestPinnedBackend: options.customActions && !xCTestChannelPenalizedByBreaker,
298
+ preferredBackend: effective.preferredBackend?.rawValue
290
299
  )
291
300
  switch effective.xCTestChannelState {
292
301
  case .normal:
@@ -317,45 +326,22 @@ extension RunnerTests {
317
326
  }
318
327
  continue
319
328
  }
320
- let capture: SnapshotBackendCapture
321
- let backendStartedAt = Date()
322
- do {
323
- guard
324
- let result = try captureWithBackend(
325
- kind,
326
- app: app,
327
- options: options,
328
- deadline: deadline,
329
- treeCaptureSliceBudgetOverride: effective.treeCaptureSliceBudgetOverride
330
- )
331
- else {
332
- recordSlowXCTestSnapshotBackendIfNeeded(
333
- kind,
334
- startedAt: backendStartedAt,
335
- penaltySuppressed: suppressXCTestPenalty
336
- )
337
- continue
338
- }
339
- capture = result
340
- recordSlowXCTestSnapshotBackendIfNeeded(
341
- kind,
342
- startedAt: backendStartedAt,
343
- penaltySuppressed: suppressXCTestPenalty
344
- )
345
- } catch let failure as SnapshotCaptureFailure {
346
- recordXCTestSnapshotBackendFailureIfNeeded(
347
- kind,
348
- failure: failure,
349
- penaltySuppressed: suppressXCTestPenalty
350
- )
351
- recordSlowXCTestSnapshotBackendIfNeeded(
352
- kind,
353
- startedAt: backendStartedAt,
354
- penaltySuppressed: suppressXCTestPenalty
355
- )
329
+ let attempt = try captureWithBackend(
330
+ kind,
331
+ app: app,
332
+ options: options,
333
+ deadline: deadline,
334
+ treeCaptureSliceBudgetOverride: effective.treeCaptureSliceBudgetOverride
335
+ )
336
+ recordXCTestSnapshotBackendAttemptIfNeeded(
337
+ kind,
338
+ attempt: attempt,
339
+ penaltySuppressed: suppressXCTestPenalty
340
+ )
341
+ if case let .failed(failure, phase: _) = attempt.outcome {
356
342
  if Self.isAxSnapshotFailure(failure) { axFailure = failure }
357
343
  if firstFailure == nil {
358
- firstFailure = (failure.message, Self.isAxSnapshotFailure(failure) ? "ax-rejected" : "capture-failed")
344
+ firstFailure = (failure.message, Self.snapshotQualityReasonCode(for: failure))
359
345
  }
360
346
  NSLog(
361
347
  "AGENT_DEVICE_RUNNER_SNAPSHOT_BACKEND_FAILED backend=%@ error=%@",
@@ -364,8 +350,9 @@ extension RunnerTests {
364
350
  )
365
351
  continue
366
352
  }
353
+ guard case let .captured(capture) = attempt.outcome else { continue }
367
354
 
368
- if let sparseReason = Self.sparsePayloadReason(capture.payload) {
355
+ if let sparseReason = Self.sparsePayloadReason(capture.qualityPayload ?? capture.payload) {
369
356
  if firstFailure == nil { firstFailure = sparseReason }
370
357
  if Self.payloadNodeCount(capture.payload) > Self.payloadNodeCount(best?.capture.payload) {
371
358
  best = (kind, capture)
@@ -385,7 +372,7 @@ extension RunnerTests {
385
372
  capture,
386
373
  backend: kind,
387
374
  state: recovered ? "recovered" : "healthy",
388
- reason: recovered ? firstFailure : nil
375
+ reason: recovered || firstFailure?.code == "requested-backend" ? firstFailure : nil
389
376
  )
390
377
  }
391
378
 
@@ -419,77 +406,136 @@ extension RunnerTests {
419
406
  return fallbackPayload
420
407
  }
421
408
 
422
- /// Marks XCTest-backed snapshot tiers as penalized when one attempt ground past the slow-capture
423
- /// threshold — even a successful one: the next capture of this screen must not re-grind.
424
- private func recordSlowXCTestSnapshotBackendIfNeeded(
425
- _ kind: SnapshotBackendKind,
426
- startedAt: Date,
427
- penaltySuppressed: Bool
428
- ) {
429
- guard !penaltySuppressed else { return }
430
- guard kind.usesXCTestAccessibilityChannel else { return }
431
- let elapsed = Date().timeIntervalSince(startedAt)
432
- guard elapsed > snapshotXCTestSlowCaptureThreshold else { return }
433
- penalizeSnapshotXCTestChannel(
434
- bundleId: currentBundleId,
435
- reason: "slow_\(kind.rawValue)_capture_\(Int(elapsed * 1000))ms"
436
- )
437
- }
438
-
439
- private func recordXCTestSnapshotBackendFailureIfNeeded(
440
- _ kind: SnapshotBackendKind,
441
- failure: SnapshotCaptureFailure,
442
- penaltySuppressed: Bool
443
- ) {
444
- guard !penaltySuppressed else { return }
445
- guard kind.usesXCTestAccessibilityChannel, failure.code == Self.xCTestSnapshotTimeoutCode else { return }
446
- penalizeSnapshotXCTestChannel(
447
- bundleId: currentBundleId,
448
- reason: "\(kind.rawValue)_backend_timeout"
449
- )
450
- }
451
-
452
409
  private func captureWithBackend(
453
410
  _ kind: SnapshotBackendKind,
454
411
  app: XCUIApplication,
455
- options: SnapshotOptions,
412
+ options: PresentationOptions,
456
413
  deadline: Date,
457
414
  treeCaptureSliceBudgetOverride: TimeInterval?
458
- ) throws -> SnapshotBackendCapture? {
459
- switch kind {
460
- case .recursiveTree:
461
- guard
462
- let context = try makeSnapshotTraversalContext(
463
- app: app,
464
- options: options,
465
- captureDeadline: deadline,
466
- treeCaptureSliceBudgetOverride: treeCaptureSliceBudgetOverride
467
- )
468
- else {
469
- return nil
470
- }
471
- let payload = try runMainThreadWork(
472
- command: nil,
473
- timeout: min(treeCaptureSliceBudget, max(0.5, deadline.timeIntervalSinceNow)),
474
- timeoutError: snapshotMainThreadTimeoutError("processing tree snapshot")
475
- ) {
476
- options.raw
477
- ? try self.rawTreeSnapshotPayload(context: context, options: options)
478
- : self.recursiveTreeSnapshotPayload(context: context, options: options)
415
+ ) throws -> SnapshotBackendAttempt {
416
+ let hint = SnapshotPresentation.captureHint(for: options)
417
+ var timer = SnapshotPhaseTimer()
418
+ // Scoped depth is relative to a presentation-selected root, so its hint stays broad and the
419
+ // backend capability gate applies only to an unscoped regular frontier.
420
+ let requestedRegularDepth = options.raw || SnapshotScopePolicy.isActive(options.scope)
421
+ ? nil
422
+ : options.depth
423
+ guard kind.canServeRegularPresentedDepth(requestedRegularDepth) else {
424
+ NSLog(
425
+ "AGENT_DEVICE_RUNNER_SNAPSHOT_BACKEND_DEPTH_UNSUPPORTED backend=%@ depth=%ld",
426
+ kind.rawValue,
427
+ requestedRegularDepth ?? -1
428
+ )
429
+ return SnapshotBackendAttempt(
430
+ outcome: .noCapture,
431
+ timing: timer.timing
432
+ )
433
+ }
434
+ let acquisition: SnapshotAcquisition?
435
+ do {
436
+ acquisition = try timer.measure(.acquisition) {
437
+ switch kind {
438
+ case .recursiveTree:
439
+ guard
440
+ let context = try self.makeSnapshotTraversalContext(
441
+ app: app,
442
+ hint: hint,
443
+ captureDeadline: deadline,
444
+ treeCaptureSliceBudgetOverride: treeCaptureSliceBudgetOverride
445
+ )
446
+ else {
447
+ return nil
448
+ }
449
+ return try self.runMainThreadWork(
450
+ timeout: min(self.treeCaptureSliceBudget, max(0.5, deadline.timeIntervalSinceNow)),
451
+ timeoutError: self.snapshotMainThreadTimeoutError("processing tree snapshot")
452
+ ) {
453
+ hint.isRaw
454
+ ? try self.rawTreeSnapshotAcquisition(context: context, hint: hint)
455
+ : self.recursiveTreeSnapshotAcquisition(context: context, hint: hint)
456
+ }
457
+ case .querySweep:
458
+ return try self.runMainThreadWork(
459
+ timeout: min(Self.flatInteractiveFallbackBudget, max(0.1, deadline.timeIntervalSinceNow)),
460
+ timeoutError: self.snapshotMainThreadTimeoutError("running query-sweep snapshot")
461
+ ) {
462
+ self.querySweepSnapshotAcquisition(
463
+ app: app,
464
+ hint: hint,
465
+ planDeadline: deadline
466
+ )
467
+ }
468
+ case .privateAX:
469
+ return self.privateAXSnapshotAcquisition(
470
+ app: app,
471
+ hint: hint,
472
+ deadline: deadline
473
+ )
474
+ }
479
475
  }
480
- return SnapshotBackendCapture(payload: payload, effectiveDepth: nil)
481
- case .querySweep:
482
- let payload = try runMainThreadWork(
483
- command: nil,
484
- timeout: min(Self.flatInteractiveFallbackBudget, max(0.1, deadline.timeIntervalSinceNow)),
485
- timeoutError: snapshotMainThreadTimeoutError("running query-sweep snapshot")
486
- ) {
487
- self.snapshotFlatInteractive(app: app, options: options, planDeadline: deadline)
476
+ } catch let failure as SnapshotCaptureFailure {
477
+ return SnapshotBackendAttempt(
478
+ outcome: .failed(failure, phase: .acquisition),
479
+ timing: timer.timing
480
+ )
481
+ }
482
+ guard let acquisition else {
483
+ return SnapshotBackendAttempt(
484
+ outcome: .noCapture,
485
+ timing: timer.timing
486
+ )
487
+ }
488
+
489
+ let presented: SnapshotBackendCapture
490
+ do {
491
+ presented = try timer.measure(.presentation) {
492
+ guard let result = try SnapshotPresentation.present(acquisition, options: options) else {
493
+ NSLog(
494
+ "AGENT_DEVICE_RUNNER_SNAPSHOT_PROJECTION_MISMATCH requested=%@ acquired=%@",
495
+ hint.projection.rawValue,
496
+ acquisition.hint.projection.rawValue
497
+ )
498
+ throw Self.snapshotProjectionMismatchFailure(
499
+ kind,
500
+ requested: hint.projection,
501
+ acquired: acquisition.hint.projection
502
+ )
503
+ }
504
+ return Self.makeSnapshotBackendCapture(from: result)
488
505
  }
489
- return SnapshotBackendCapture(payload: payload, effectiveDepth: nil)
490
- case .privateAX:
491
- return privateAXSnapshotCapture(app: app, options: options, deadline: deadline)
506
+ } catch let failure as SnapshotPresentationFailure {
507
+ return SnapshotBackendAttempt(
508
+ outcome: .failed(Self.snapshotCaptureFailure(for: failure), phase: .presentation),
509
+ timing: timer.timing
510
+ )
511
+ } catch let failure as SnapshotCaptureFailure {
512
+ return SnapshotBackendAttempt(
513
+ outcome: .failed(failure, phase: .presentation),
514
+ timing: timer.timing
515
+ )
492
516
  }
517
+
518
+ var capture = presented
519
+ capture.timing = timer.timing
520
+ return SnapshotBackendAttempt(
521
+ outcome: .captured(capture),
522
+ timing: timer.timing
523
+ )
524
+ }
525
+
526
+ /// A backend that answers a request with the other projection loses its tier and says why, so
527
+ /// the miss lands in the quality verdict instead of shipping as a correct-looking capture.
528
+ static func snapshotProjectionMismatchFailure(
529
+ _ kind: SnapshotBackendKind,
530
+ requested: CaptureHint.Projection,
531
+ acquired: CaptureHint.Projection
532
+ ) -> SnapshotCaptureFailure {
533
+ SnapshotCaptureFailure(
534
+ code: "IOS_SNAPSHOT_PROJECTION_MISMATCH",
535
+ message:
536
+ "the \(kind.rawValue) backend returned a \(acquired.rawValue) capture for a \(requested.rawValue) snapshot request",
537
+ hint: "This is a runner bug: report it with the failing command and the app under test."
538
+ )
493
539
  }
494
540
 
495
541
  // MARK: Quality classifier (the single source of "is this snapshot degraded")
@@ -529,7 +575,7 @@ extension RunnerTests {
529
575
  }
530
576
  }
531
577
 
532
- static func isSparseApplicationWindowTree(_ nodes: [SnapshotNode]) -> Bool {
578
+ static func isSparseApplicationWindowTree(_ nodes: [PresentedNode]) -> Bool {
533
579
  guard !nodes.isEmpty else { return false }
534
580
  let rootRects = nodes.compactMap { node in
535
581
  node.type == "Application" || node.type == "Window" ? node.rect : nil
@@ -564,7 +610,7 @@ extension RunnerTests {
564
610
  /// A leaf whose label joins many short segments is a container marked as an accessibility
565
611
  /// element: the platform folds every descendant into one merged node. Nothing below it can
566
612
  /// be addressed — by automation or by assistive tech. This is app-side; no backend recovers it.
567
- static func collapsedLeafIndexes(_ nodes: [SnapshotNode]) -> [Int]? {
613
+ static func collapsedLeafIndexes(_ nodes: [PresentedNode]) -> [Int]? {
568
614
  let parents = Set(nodes.compactMap { $0.parentIndex })
569
615
  let collapsed = nodes.filter { node in
570
616
  guard !parents.contains(node.index) else { return false }
@@ -592,13 +638,22 @@ extension RunnerTests {
592
638
  reasonCode: reason?.code,
593
639
  effectiveDepth: capture.effectiveDepth,
594
640
  collapsedLeafIndexes: Self.collapsedLeafIndexes(payload.nodes ?? []),
595
- customActions: capture.customActions
641
+ customActions: capture.customActions,
642
+ timing: capture.timing
596
643
  )
597
644
  return DataPayload(
598
645
  // Legacy human text for older daemons that read message instead of snapshotQuality.
599
646
  message: Self.legacyQualityMessage(quality) ?? payload.message,
600
647
  nodes: payload.nodes,
601
- truncated: payload.truncated == true || state != "healthy" || capture.effectiveDepth != nil,
648
+ // Completeness, never provenance: a whole tree that a later backend produced (state
649
+ // "recovered") stays untruncated, so strict absence reads can trust it. Only a real cap
650
+ // (payload truncation, a depth-limited private AX capture) or a sparse terminal payload
651
+ // is truncated.
652
+ truncated: payload.truncated == true || state == "sparse" || capture.effectiveDepth != nil,
653
+ qualityPayload: capture.qualityPayload.flatMap { quality in
654
+ guard let nodes = quality.nodes else { return nil }
655
+ return SnapshotQualityPayload(nodes: nodes, truncated: quality.truncated == true)
656
+ },
602
657
  snapshotQuality: quality,
603
658
  runnerFatal: payload.runnerFatal,
604
659
  runnerFatalReason: payload.runnerFatalReason
@@ -640,9 +695,15 @@ extension RunnerTests {
640
695
  else { return nil }
641
696
  var parts: [String] = []
642
697
  if quality.state == "recovered" {
643
- let meaning = quality.reasonCode == "budget" || quality.reasonCode == "deferred"
644
- ? " The primary capture ran out of its time budget (busy app or simulator); the recovered tree is authoritative for this screen."
645
- : " This usually means the app publishes an unhealthy accessibility tree — fixing the app's accessibility is the real cure. Treat screenshot as visual truth when this warning appears."
698
+ let meaning: String
699
+ switch quality.reasonCode {
700
+ case "budget", "deferred":
701
+ meaning = " The primary capture ran out of its time budget (busy app or simulator); the recovered tree is authoritative for this screen."
702
+ case "presentation-failed":
703
+ meaning = " The runner rejected a regular presentation because its cumulative clip invariant failed; report this as a runner bug and treat screenshot as visual truth."
704
+ default:
705
+ meaning = " This usually means the app publishes an unhealthy accessibility tree — fixing the app's accessibility is the real cure. Treat screenshot as visual truth when this warning appears."
706
+ }
646
707
  parts.append(
647
708
  "Detected an overly complex or slow accessibility tree. Fell back to the \(quality.backend) snapshot backend"
648
709
  + (quality.reason.map { " after: \($0)." } ?? ".")
@@ -0,0 +1,19 @@
1
+ import AgentDeviceSnapshotPresentation
2
+
3
+ extension RunnerTests {
4
+ static func snapshotCaptureFailure(
5
+ for failure: SnapshotPresentationFailure
6
+ ) -> SnapshotCaptureFailure {
7
+ SnapshotCaptureFailure(
8
+ code: failure.code,
9
+ message: failure.message,
10
+ hint: failure.hint,
11
+ qualityReasonCode: failure.qualityReasonCode
12
+ )
13
+ }
14
+
15
+ static func snapshotQualityReasonCode(for failure: SnapshotCaptureFailure) -> String {
16
+ failure.qualityReasonCode
17
+ ?? (Self.isAxSnapshotFailure(failure) ? "ax-rejected" : "capture-failed")
18
+ }
19
+ }