agent-device 0.20.10 → 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 (490) hide show
  1. package/README.md +2 -2
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.10.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.10.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.10.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 +16 -8
  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 +106 -103
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +6 -14
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +63 -91
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +13 -6
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +72 -155
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXElementTypes.swift +12 -0
  31. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +74 -78
  32. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +17 -9
  33. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +42 -11
  34. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +185 -461
  35. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotBackendCapabilities.swift +98 -0
  36. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +224 -164
  37. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentationInvariant.swift +19 -0
  38. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +110 -0
  39. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +206 -0
  40. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +147 -25
  41. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +1 -0
  42. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +24 -307
  43. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +206 -0
  44. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +158 -0
  45. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +49 -16
  46. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +2 -11
  47. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +4 -19
  48. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +16 -20
  49. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +111 -0
  50. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +42 -81
  51. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +35 -82
  52. package/dist/apple/snapshot-presentation/Package.swift +21 -0
  53. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotGeometry.swift +56 -0
  54. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotModels.swift +259 -0
  55. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentation.swift +153 -0
  56. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationFailure.swift +27 -0
  57. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationInvariant.swift +91 -0
  58. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationProjection.swift +161 -0
  59. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotScopePolicy.swift +53 -0
  60. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFold.swift +242 -0
  61. package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotVisibilityFoldProjection.swift +97 -0
  62. package/dist/src/adapter.js +1 -0
  63. package/dist/src/adb-executor.js +2 -0
  64. package/dist/src/adb-failure.js +1 -0
  65. package/dist/src/adb-host.js +1 -0
  66. package/dist/src/adb.js +1 -0
  67. package/dist/src/agent-browser-lifecycle.js +1 -0
  68. package/dist/src/agent-browser-process-record.js +1 -0
  69. package/dist/src/agent-browser-provider.js +1 -0
  70. package/dist/src/agent-browser-tool.js +1 -1
  71. package/dist/src/agent-device-client.js +2 -2
  72. package/dist/src/ai-sdk.d.ts +2 -2
  73. package/dist/src/ai-sdk.js +1 -1
  74. package/dist/src/alert-contract.js +1 -0
  75. package/dist/src/alert-runtime.js +1 -0
  76. package/dist/src/android-adb.d.ts +27 -19
  77. package/dist/src/android-adb.js +1 -1
  78. package/dist/src/android-input-ownership.js +1 -0
  79. package/dist/src/android-system-chrome.js +1 -0
  80. package/dist/src/android.js +1 -1
  81. package/dist/src/api.js +11 -0
  82. package/dist/src/app-catalog.js +1 -0
  83. package/dist/src/app-deployment-resolution.js +2 -0
  84. package/dist/src/app-helpers.js +1 -1
  85. package/dist/src/app-inventory-contract.js +1 -0
  86. package/dist/src/app-inventory.js +1 -0
  87. package/dist/src/app-launch.js +2 -0
  88. package/dist/src/app-lifecycle-facade.js +1 -0
  89. package/dist/src/app-lifecycle.js +3 -6
  90. package/dist/src/app-lifecycle2.js +2 -2
  91. package/dist/src/app-lifecycle3.js +1 -0
  92. package/dist/src/app-log-files.js +1 -1
  93. package/dist/src/app-log-reconnect.js +1 -1
  94. package/dist/src/app-log-runtime.js +1 -4
  95. package/dist/src/app-log-runtime2.js +4 -0
  96. package/dist/src/app-parsers.js +4 -0
  97. package/dist/src/app-resolution-cache.js +1 -0
  98. package/dist/src/app-resolution-facade.js +1 -0
  99. package/dist/src/app-resolution.js +2 -2
  100. package/dist/src/app-state.js +1 -2
  101. package/dist/src/application-lifecycle-interaction.js +1 -0
  102. package/dist/src/application-lifecycle-runtime.js +1 -0
  103. package/dist/src/apps-simctl.js +1 -0
  104. package/dist/src/archive.js +4 -0
  105. package/dist/src/artifact-download.js +1 -0
  106. package/dist/src/artifacts.d.ts +6 -2
  107. package/dist/src/artifacts.js +1 -1
  108. package/dist/src/async-lifecycle.js +1 -0
  109. package/dist/src/audio-probe-result.js +1 -0
  110. package/dist/src/audio-probe-runtime.js +1 -0
  111. package/dist/src/audio-probe-support.js +1 -0
  112. package/dist/src/auth-session.js +1 -1
  113. package/dist/src/auth.js +1 -1
  114. package/dist/src/back-mode.js +1 -0
  115. package/dist/src/{url.js → base-url.js} +1 -1
  116. package/dist/src/boot-diagnostics.js +2 -2
  117. package/dist/src/byte-limit-stream.js +1 -1
  118. package/dist/src/capabilities.js +1 -0
  119. package/dist/src/capture.js +1 -0
  120. package/dist/src/catalog.js +1 -0
  121. package/dist/src/cli-command-aliases.js +1 -0
  122. package/dist/src/cli-config.js +1 -1
  123. package/dist/src/cli-help.js +69 -50
  124. package/dist/src/cli-output.js +1 -1
  125. package/dist/src/cli-runner.js +1 -1
  126. package/dist/src/cli.js +4 -6
  127. package/dist/src/click-button.js +1 -0
  128. package/dist/src/client-metro-companion.js +1 -0
  129. package/dist/src/client-metro.js +1 -1
  130. package/dist/src/client-types.d.ts +255 -231
  131. package/dist/src/clipboard.js +1 -0
  132. package/dist/src/code-signature-cache.js +1 -0
  133. package/dist/src/code-signature.js +1 -0
  134. package/dist/src/color.js +1 -0
  135. package/dist/src/command-metadata.js +1 -1
  136. package/dist/src/command-schema.js +3 -3
  137. package/dist/src/command-surface.js +1 -1
  138. package/dist/src/command-tools.js +2 -2
  139. package/dist/src/command.js +1 -0
  140. package/dist/src/compatibility-policy.js +1 -0
  141. package/dist/src/config.js +1 -1
  142. package/dist/src/config2.js +1 -0
  143. package/dist/src/connection-runtime.js +1 -1
  144. package/dist/src/connection.js +5 -5
  145. package/dist/src/contracts.d.ts +1 -1
  146. package/dist/src/contracts.js +1 -1
  147. package/dist/src/daemon-client-lifecycle.js +1 -0
  148. package/dist/src/daemon-process.js +1 -0
  149. package/dist/src/daemon-registration.js +1 -0
  150. package/dist/src/daemon-shutdown-report.js +1 -1
  151. package/dist/src/daemon.js +2 -2
  152. package/dist/src/debug-symbols-facade.js +5 -0
  153. package/dist/src/deploy-result-target.js +1 -0
  154. package/dist/src/device-claim-inspection.js +1 -1
  155. package/dist/src/device-claim-rule.js +1 -0
  156. package/dist/src/device-input-state.d.ts +190 -3
  157. package/dist/src/device-inventory-context.js +1 -0
  158. package/dist/src/device-isolation.js +1 -0
  159. package/dist/src/device-release.js +1 -0
  160. package/dist/src/device-rotation.js +1 -0
  161. package/dist/src/device-selection-resolver.js +1 -0
  162. package/dist/src/device-session.js +1 -0
  163. package/dist/src/device.js +1 -1
  164. package/dist/src/device2.js +3 -2
  165. package/dist/src/diagnostics.js +3 -0
  166. package/dist/src/diagnostics2.js +1 -0
  167. package/dist/src/dispatch-resolve.js +1 -0
  168. package/dist/src/doctor-facade.js +1 -0
  169. package/dist/src/doctor-output.js +1 -0
  170. package/dist/src/doctor.js +1 -0
  171. package/dist/src/doctor2.js +1 -0
  172. package/dist/src/doctor3.js +1 -0
  173. package/dist/src/durable-resource.js +1 -0
  174. package/dist/src/entities.js +1 -1
  175. package/dist/src/error.js +2 -0
  176. package/dist/src/exec.js +1 -0
  177. package/dist/src/file.js +1 -0
  178. package/dist/src/find.js +1 -1
  179. package/dist/src/finders.js +1 -1
  180. package/dist/src/format.js +2 -0
  181. package/dist/src/generic-settle.js +1 -1
  182. package/dist/src/generic.js +1 -1
  183. package/dist/src/gesture-admission.js +1 -0
  184. package/dist/src/gesture-plan-types.js +1 -0
  185. package/dist/src/gesture-plan.js +1 -0
  186. package/dist/src/gesture-runtime.js +1 -0
  187. package/dist/src/harmonyos.js +1 -1
  188. package/dist/src/hdc.js +1 -1
  189. package/dist/src/helper.js +2 -2
  190. package/dist/src/host-file.js +1 -0
  191. package/dist/src/human-control-contract.js +1 -0
  192. package/dist/src/human-control.js +1 -0
  193. package/dist/src/index.d.ts +1 -1
  194. package/dist/src/index.js +1 -1
  195. package/dist/src/input-actions.js +1 -4
  196. package/dist/src/input-actions2.js +2 -1
  197. package/dist/src/install-artifact-facade.js +1 -0
  198. package/dist/src/install-source-network-transport.js +1 -0
  199. package/dist/src/install-source.d.ts +3 -10
  200. package/dist/src/install-source2.js +1 -1
  201. package/dist/src/interaction-positionals.js +1 -0
  202. package/dist/src/interaction-ref-policy.js +1 -0
  203. package/dist/src/interaction.js +1 -1
  204. package/dist/src/interaction2.js +1 -1
  205. package/dist/src/interactor-operation-catalog.js +1 -0
  206. package/dist/src/interactor-types.js +1 -0
  207. package/dist/src/interactor.js +6 -2
  208. package/dist/src/interactor2.js +1 -1
  209. package/dist/src/interactors.js +1 -1
  210. package/dist/src/internal/bin.d.ts +1 -1
  211. package/dist/src/internal/bin.js +1 -2
  212. package/dist/src/internal/companion-tunnel.d.ts +1 -1
  213. package/dist/src/internal/companion-tunnel.js +1 -1
  214. package/dist/src/internal/daemon.d.ts +1 -1
  215. package/dist/src/internal/daemon.js +2 -4
  216. package/dist/src/internal/png-worker.d.ts +4 -4
  217. package/dist/src/internal/png-worker.js +1 -1
  218. package/dist/src/internal/run-script-http-child.d.ts +4 -0
  219. package/dist/src/internal/run-script-http-child.js +1 -0
  220. package/dist/src/internal/update-check-entry.d.ts +1 -1
  221. package/dist/src/inventory.js +4 -4
  222. package/dist/src/inventory2.js +1 -1
  223. package/dist/src/inventory3.js +1 -1
  224. package/dist/src/inventory5.js +1 -1
  225. package/dist/src/inventory6.js +1 -1
  226. package/dist/src/ios-client.js +16 -0
  227. package/dist/src/ios-snapshot-acquisition.js +1 -0
  228. package/dist/src/ios-snapshot-adapter.js +1 -0
  229. package/dist/src/ios-snapshot-planning.js +1 -0
  230. package/dist/src/ios-snapshot-runtime.js +1 -0
  231. package/dist/src/json.js +1 -0
  232. package/dist/src/keyboard-actions.js +1 -0
  233. package/dist/src/launch-console.js +1 -0
  234. package/dist/src/lease-context.js +1 -1
  235. package/dist/src/lease-scope.js +1 -0
  236. package/dist/src/lease.js +1 -1
  237. package/dist/src/limrun-runtime-dependencies.js +1 -1
  238. package/dist/src/limrun.d.ts +1610 -7
  239. package/dist/src/limrun.js +1 -1
  240. package/dist/src/linux.js +1 -1
  241. package/dist/src/local-interactor-operation-set.js +1 -0
  242. package/dist/src/location-coordinates.js +1 -1
  243. package/dist/src/logcat.js +1 -1
  244. package/dist/src/macos-facade.js +1 -0
  245. package/dist/src/managed-device-scope.js +1 -0
  246. package/dist/src/mechanics.js +19 -0
  247. package/dist/src/metro.d.ts +4 -2
  248. package/dist/src/metro.js +1 -1
  249. package/dist/src/metro2.js +1 -1
  250. package/dist/src/mobile-snapshot-semantics.js +1 -1
  251. package/dist/src/multitouch-support.js +1 -0
  252. package/dist/src/numeric.js +1 -0
  253. package/dist/src/observability.js +1 -1
  254. package/dist/src/observation.js +2 -0
  255. package/dist/src/output-path.js +1 -1
  256. package/dist/src/owner-identity.js +3 -1
  257. package/dist/src/parameterized-recorded-fill.js +1 -0
  258. package/dist/src/parser.js +1 -1
  259. package/dist/src/perf-facade.js +2 -0
  260. package/dist/src/perf-runtime-operation-builder.js +1 -0
  261. package/dist/src/perf-runtime-plan.js +1 -0
  262. package/dist/src/perf-runtime.js +1 -0
  263. package/dist/src/perf.js +1 -4
  264. package/dist/src/physical-device-control.js +1 -1
  265. package/dist/src/physical-device-facade.js +1 -0
  266. package/dist/src/platform-request-scope.js +1 -0
  267. package/dist/src/platform-runtime-android-adb-host.js +1 -0
  268. package/dist/src/platform-runtime-android-mechanics.js +1 -0
  269. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  270. package/dist/src/platform-runtime-app-log-process.js +1 -1
  271. package/dist/src/platform-runtime-apple-resources.js +1 -0
  272. package/dist/src/platform-runtime-host.js +1 -1
  273. package/dist/src/platform-runtime-managed-owner.js +1 -0
  274. package/dist/src/platform-runtime-network-web-transport.js +1 -1
  275. package/dist/src/platform-runtime-open-target.js +1 -1
  276. package/dist/src/platform-runtime-operation-host.js +4 -4
  277. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  278. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  279. package/dist/src/platform-runtime-screen-recording-apple-runner-host.js +1 -1
  280. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  281. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  282. package/dist/src/platform-runtime-screen-recording-apple-transport.js +1 -1
  283. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  284. package/dist/src/platform-runtime-screen-recording-harmony-host.js +1 -1
  285. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -0
  286. package/dist/src/platform-runtime-screen-recording-web-host.js +1 -1
  287. package/dist/src/platform-runtime-unavailable.js +1 -0
  288. package/dist/src/platform-runtime.js +1 -1
  289. package/dist/src/platform-runtime2.js +1 -1
  290. package/dist/src/plist.js +1 -1
  291. package/dist/src/png-worker-contract.js +1 -1
  292. package/dist/src/prepare-kind.js +1 -0
  293. package/dist/src/process-exit.js +1 -1
  294. package/dist/src/process.js +1 -0
  295. package/dist/src/provider-device-runtime.js +1 -0
  296. package/dist/src/provider-session-ownership.js +2 -0
  297. package/dist/src/provider-webdriver.js +1 -1
  298. package/dist/src/provider.js +1 -1
  299. package/dist/src/providers.js +1 -0
  300. package/dist/src/proxy.js +2 -2
  301. package/dist/src/react-native-overlay.js +1 -0
  302. package/dist/src/react-native.js +1 -1
  303. package/dist/src/record-runtime.js +1 -1
  304. package/dist/src/record-trace.js +1 -1
  305. package/dist/src/rect-center.js +1 -0
  306. package/dist/src/register-builtins.js +1 -0
  307. package/dist/src/registry.js +39 -28
  308. package/dist/src/remote-config2.js +1 -1
  309. package/dist/src/replay-divergence.js +2 -0
  310. package/dist/src/replay.js +2 -2
  311. package/dist/src/reporting.js +8 -8
  312. package/dist/src/request-admission.js +1 -0
  313. package/dist/src/request-cancel.js +1 -0
  314. package/dist/src/request-cancellation.js +1 -0
  315. package/dist/src/request-progress-protocol.js +1 -0
  316. package/dist/src/request-providers.js +1 -0
  317. package/dist/src/request-runtime-binding.js +1 -0
  318. package/dist/src/request.js +1 -0
  319. package/dist/src/result-serialization.js +1 -0
  320. package/dist/src/retry.js +1 -0
  321. package/dist/src/rolldown-runtime.js +1 -1
  322. package/dist/src/runner-cache-metadata.js +2 -0
  323. package/dist/src/runner-client.js +1 -1
  324. package/dist/src/runner-disposal.js +1 -1
  325. package/dist/src/runner-operations-facade.js +1 -0
  326. package/dist/src/runner-owner-facade.js +1 -0
  327. package/dist/src/runner-owner-state.js +1 -0
  328. package/dist/src/runner-presentation.js +1 -0
  329. package/dist/src/runner-selector-query.js +1 -0
  330. package/dist/src/runner.js +1 -0
  331. package/dist/src/{runtime-transport.js → runtime-transport-hints.js} +1 -1
  332. package/dist/src/runtime.js +3 -3
  333. package/dist/src/runtime2.js +4 -4
  334. package/dist/src/runtime3.js +1 -1
  335. package/dist/src/runtime4.js +4 -3
  336. package/dist/src/runtime5.js +1 -1
  337. package/dist/src/runtime6.js +1 -1
  338. package/dist/src/runtime7.js +1 -1
  339. package/dist/src/runtime8.js +1 -1
  340. package/dist/src/runtime9.js +1 -1
  341. package/dist/src/scoped-provider.js +1 -1
  342. package/dist/src/screen-recording-completion.js +1 -0
  343. package/dist/src/screen-recording-live-handle.js +1 -0
  344. package/dist/src/screen-recording-options.js +1 -0
  345. package/dist/src/screen-recording-resource-recovery.js +1 -0
  346. package/dist/src/screen-recording-runtime.js +1 -0
  347. package/dist/src/screenshot-density.js +1 -1
  348. package/dist/src/screenshot-result.js +1 -6
  349. package/dist/src/screenshot-runtime.js +1 -0
  350. package/dist/src/screenshot.js +1 -1
  351. package/dist/src/screenshot2.js +1 -0
  352. package/dist/src/screenshot3.js +5 -0
  353. package/dist/src/script.js +1 -0
  354. package/dist/src/scroll-command.js +1 -0
  355. package/dist/src/scroll-gesture.js +1 -0
  356. package/dist/src/scroll-indicator.js +1 -0
  357. package/dist/src/sdk-batch-runner.d.ts +23 -6
  358. package/dist/src/sdk-batch-runner.js +2 -3
  359. package/dist/src/sdk-batch.js +2 -0
  360. package/dist/src/sdk-contracts.d.ts +127 -41
  361. package/dist/src/sdk-contracts.js +2 -2
  362. package/dist/src/sdk-device.js +1 -1
  363. package/dist/src/sdk-finders.js +1 -1
  364. package/dist/src/sdk-io.js +1 -1
  365. package/dist/src/sdk-remote-config.d.ts +1 -1
  366. package/dist/src/sdk-remote-config.js +1 -1
  367. package/dist/src/sdk-selectors.d.ts +472 -144
  368. package/dist/src/sdk-selectors.js +1 -1
  369. package/dist/src/selection.js +1 -0
  370. package/dist/src/selector-runtime.js +1 -1
  371. package/dist/src/selectors.js +1 -1
  372. package/dist/src/semantic-index.js +1 -0
  373. package/dist/src/server.js +4 -4
  374. package/dist/src/session-allocation.js +1 -0
  375. package/dist/src/session-artifact-paths.js +1 -0
  376. package/dist/src/session-paths.js +1 -0
  377. package/dist/src/session-repair-tombstone.js +1 -0
  378. package/dist/src/session-routing.js +1 -0
  379. package/dist/src/session-runtime-admission.js +1 -0
  380. package/dist/src/session-snapshot.js +1 -0
  381. package/dist/src/session-store.js +4 -0
  382. package/dist/src/session-target-evidence.js +1 -1
  383. package/dist/src/session2.js +5 -27
  384. package/dist/src/settings.js +1 -0
  385. package/dist/src/shared.js +2 -2
  386. package/dist/src/simctl-facade.js +1 -0
  387. package/dist/src/simctl.js +1 -1
  388. package/dist/src/simulator-facade.js +1 -0
  389. package/dist/src/simulator.js +1 -1
  390. package/dist/src/snapshot-capture-annotations.js +1 -0
  391. package/dist/src/snapshot-desktop-projection.js +1 -0
  392. package/dist/src/snapshot-diagnostics.js +1 -0
  393. package/dist/src/snapshot-process.js +1 -0
  394. package/dist/src/snapshot-quality-backend-capabilities.js +1 -0
  395. package/dist/src/snapshot-quality-verdict.js +1 -0
  396. package/dist/src/snapshot-runtime-binding.js +1 -0
  397. package/dist/src/snapshot-runtime.js +1 -1
  398. package/dist/src/snapshot-scope.js +1 -1
  399. package/dist/src/snapshot-state.js +1 -0
  400. package/dist/src/snapshot.js +1 -8
  401. package/dist/src/snapshot2.js +1 -1
  402. package/dist/src/snapshot3.js +1 -1
  403. package/dist/src/snapshot4.js +1 -0
  404. package/dist/src/src.js +1 -1
  405. package/dist/src/src2.js +1 -1
  406. package/dist/src/src3.js +1 -2
  407. package/dist/src/src4.js +1 -1
  408. package/dist/src/src5.js +1 -0
  409. package/dist/src/src6.js +1 -0
  410. package/dist/src/src7.js +2 -0
  411. package/dist/src/src8.js +115 -0
  412. package/dist/src/startup-recovery-fence.js +1 -0
  413. package/dist/src/status-markers.js +1 -0
  414. package/dist/src/string-enum.js +1 -1
  415. package/dist/src/success-text.js +1 -0
  416. package/dist/src/surface-snapshot.js +1 -0
  417. package/dist/src/surface-snapshot2.js +1 -0
  418. package/dist/src/takeover.js +5 -0
  419. package/dist/src/tool-provider-facade.js +1 -0
  420. package/dist/src/tool-provider.js +4 -3
  421. package/dist/src/tool-provider2.js +1 -1
  422. package/dist/src/tool-provider3.js +1 -1
  423. package/dist/src/toolchain-probe.js +2 -0
  424. package/dist/src/touch-runtime.js +1 -0
  425. package/dist/src/transport.js +3 -0
  426. package/dist/src/tree.js +1 -0
  427. package/dist/src/ttl-memo.js +1 -0
  428. package/dist/src/tv-remote.js +1 -0
  429. package/dist/src/type-text-runtime.js +1 -0
  430. package/dist/src/undici.js +99 -0
  431. package/dist/src/update-check.js +1 -1
  432. package/dist/src/verified-file.js +1 -1
  433. package/dist/src/version.js +1 -1
  434. package/dist/src/video.js +2 -2
  435. package/dist/src/web.js +2 -1
  436. package/dist/src/web2.js +1 -0
  437. package/dist/src/webdriver-ios-snapshot.js +1 -0
  438. package/dist/src/webdriver-source.js +1 -0
  439. package/linux/atspi-dump.py +54 -9
  440. package/package.json +56 -34
  441. package/server.json +9 -3
  442. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.10.apk.sha256 +0 -1
  443. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.10.apk +0 -0
  444. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.10.apk.sha256 +0 -1
  445. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CoordinateTextEntryTests.swift +0 -4
  446. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -168
  447. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +0 -6
  448. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+RecordingTests.swift +0 -4
  449. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SelectorMatchPolicyTests.swift +0 -4
  450. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentation.swift +0 -183
  451. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentationTests.swift +0 -0
  452. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +0 -4
  453. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryPolicyTests.swift +0 -4
  454. package/dist/src/appearance.js +0 -1
  455. package/dist/src/apps.js +0 -5
  456. package/dist/src/audio-probe.js +0 -1
  457. package/dist/src/cli-grammar.js +0 -1
  458. package/dist/src/command-catalog.js +0 -1
  459. package/dist/src/debug-symbols.js +0 -5
  460. package/dist/src/device-input-state.js +0 -2
  461. package/dist/src/device-ready.js +0 -1
  462. package/dist/src/dispatch.js +0 -1
  463. package/dist/src/handler-utils.js +0 -1
  464. package/dist/src/helper-package-install.js +0 -1
  465. package/dist/src/host-process.js +0 -3
  466. package/dist/src/ime-lifecycle.js +0 -2
  467. package/dist/src/ime-recovery-marker.js +0 -1
  468. package/dist/src/input-actions3.js +0 -1
  469. package/dist/src/install-artifact.js +0 -1
  470. package/dist/src/install-artifact2.js +0 -1
  471. package/dist/src/interaction-common.js +0 -1
  472. package/dist/src/interaction-runtime.js +0 -1
  473. package/dist/src/interaction-snapshot.js +0 -1
  474. package/dist/src/manifest.js +0 -1
  475. package/dist/src/method-scope.js +0 -1
  476. package/dist/src/png-resize.js +0 -1
  477. package/dist/src/process-lock.js +0 -1
  478. package/dist/src/runner-lease.js +0 -1
  479. package/dist/src/runner-sequence.js +0 -1
  480. package/dist/src/script-source-bundle.js +0 -16
  481. package/dist/src/sdk-android-adb.d.ts +0 -245
  482. package/dist/src/sdk-android-adb.js +0 -4
  483. package/dist/src/sdk.js +0 -1
  484. package/dist/src/setting-state.js +0 -1
  485. package/dist/src/shell-quote.js +0 -1
  486. package/dist/src/snapshot-session.js +0 -1
  487. package/dist/src/timing-safe-equal.js +0 -1
  488. package/dist/src/unsupported-interactor.js +0 -1
  489. package/dist/src/verdict.js +0 -1
  490. /package/dist/src/{recording-telemetry.js → telemetry.js} +0 -0
@@ -0,0 +1,158 @@
1
+ import XCTest
2
+
3
+ // Text-entry readiness: whether the target is actually able to receive text yet. The waits, the
4
+ // keyboard signals they read, and the focus corroboration that ends the hardware-keyboard window.
5
+ // RunnerTests+TextEntryFocus.swift is the only caller of the two non-private entries here; nothing
6
+ // in this file reaches back into it.
7
+ extension RunnerTests {
8
+ func focusedTextInput(app: XCUIApplication) -> XCUIElement? {
9
+ #if os(iOS)
10
+ // iOS focus predicates can return stale or misleading text-input matches
11
+ // under XCUITest, so text entry readiness is driven by tap/keyboard state.
12
+ return nil
13
+ #else
14
+ return safely("FOCUSED_INPUT_QUERY") {
15
+ let candidates = app
16
+ .descendants(matching: .any)
17
+ .matching(NSPredicate(format: "hasKeyboardFocus == 1"))
18
+ .allElementsBoundByIndex
19
+ for candidate in candidates where candidate.exists {
20
+ switch candidate.elementType {
21
+ case .textField, .secureTextField, .searchField, .textView:
22
+ return candidate
23
+ default:
24
+ continue
25
+ }
26
+ }
27
+ return nil
28
+ }
29
+ #endif
30
+ }
31
+
32
+ func waitForTextEntryReadiness(
33
+ app: XCUIApplication,
34
+ target: TextEntryTarget,
35
+ timeout: TimeInterval = TextEntryTiming.readinessTimeout
36
+ ) -> XCUIElement? {
37
+ #if os(iOS)
38
+ var latest = resolveTextEntryElement(app: app, target: target)
39
+ let keyboardVisibleAtEntry = isKeyboardVisible(app: app)
40
+ let deadline = Date().addingTimeInterval(timeout)
41
+ var hardwareKeyboardFallback = Date().addingTimeInterval(
42
+ min(TextEntryTiming.hardwareKeyboardFallbackTimeout, timeout)
43
+ )
44
+ var sawSoftwareKeyboard = false
45
+ while Date() < deadline {
46
+ if let focused = focusedTextInput(app: app) {
47
+ latest = focused
48
+ if isKeyboardVisible(app: app) {
49
+ return focused
50
+ }
51
+ }
52
+ // Fast-path on a keyboard hidden->visible transition: our tapped field gained focus, so
53
+ // return immediately instead of burning the full readinessTimeout (warmup-first-char echo
54
+ // + post-type verify/repair remain as drop safety nets). When the keyboard was ALREADY up
55
+ // (back-to-back fills), this isn't a focus signal — fall through to the settle/timeout so
56
+ // text isn't sent to the previously-focused field.
57
+ if keyboardBecameVisible(app: app, wasVisibleAtEntry: keyboardVisibleAtEntry) {
58
+ return latest
59
+ }
60
+ sawSoftwareKeyboard = sawSoftwareKeyboard || keyboardElementExists(app: app)
61
+ // A responder that takes no software keyboard (hardware keyboard connected, or a custom
62
+ // `inputView`) would otherwise burn the whole readinessTimeout waiting for one that is never
63
+ // coming. Leaving that window a bare wall-clock guess made readiness a function of ambient
64
+ // simulator state (#1874): on a loaded host the keyboard is merely late, and returning here
65
+ // handed the caller an element that had not taken focus yet. Ask the target itself instead,
66
+ // and re-arm rather than re-asking every poll — the query is cheap, not free.
67
+ if !sawSoftwareKeyboard, Date() >= hardwareKeyboardFallback, let candidate = latest {
68
+ if keyboardFocusConfirmed(app: app, element: candidate) {
69
+ return candidate
70
+ }
71
+ hardwareKeyboardFallback = Date().addingTimeInterval(
72
+ TextEntryTiming.hardwareKeyboardFallbackTimeout
73
+ )
74
+ }
75
+ sleepFor(TextEntryTiming.pollInterval)
76
+ }
77
+ return focusedTextInput(app: app) ?? latest
78
+ #else
79
+ return resolveTextEntryElement(app: app, target: target)
80
+ #endif
81
+ }
82
+
83
+ func waitForTextEntryReadinessAfterTap(app: XCUIApplication, element: XCUIElement) {
84
+ #if os(iOS)
85
+ switch element.elementType {
86
+ case .textField, .secureTextField, .searchField, .textView:
87
+ if waitForFocusedTextInput(app: app, timeout: TextEntryTiming.readinessTimeout) != nil {
88
+ return
89
+ }
90
+ let frame = element.frame
91
+ if !frame.isEmpty {
92
+ _ = tapAt(app: app, x: frame.midX, y: frame.midY)
93
+ _ = waitForFocusedTextInput(app: app, timeout: TextEntryTiming.readinessTimeout)
94
+ }
95
+ default:
96
+ return
97
+ }
98
+ #endif
99
+ }
100
+
101
+ private func waitForFocusedTextInput(app: XCUIApplication, timeout: TimeInterval) -> XCUIElement? {
102
+ let deadline = Date().addingTimeInterval(timeout)
103
+ while Date() < deadline {
104
+ if let focused = focusedTextInput(app: app) {
105
+ return focused
106
+ }
107
+ sleepFor(TextEntryTiming.pollInterval)
108
+ }
109
+ return focusedTextInput(app: app)
110
+ }
111
+
112
+ func keyboardBecameVisible(app: XCUIApplication, wasVisibleAtEntry: Bool) -> Bool {
113
+ return !wasVisibleAtEntry && isKeyboardVisible(app: app)
114
+ }
115
+
116
+ /// Positive evidence that this element — the one readiness is about to hand its caller — holds
117
+ /// keyboard focus. Readable even with no software keyboard on screen, which is what lets the
118
+ /// hardware-keyboard fallback stop guessing from a wall clock.
119
+ ///
120
+ /// This is the same app-wide predicate `focusedTextInput` refuses to trust on iOS, used the
121
+ /// other way round. There, the query PICKS the target, so a stale or unrelated match becomes
122
+ /// the field that gets typed into. Here the target is already chosen and the query only
123
+ /// corroborates it: at most one element holds keyboard focus, so an answer that is not this
124
+ /// element is a refusal, not a substitution. Every way of being wrong therefore ends as `false`
125
+ /// and costs the remaining readiness timeout — exactly what the wait would spend with no
126
+ /// fallback at all.
127
+ func keyboardFocusConfirmed(app: XCUIApplication, element: XCUIElement) -> Bool {
128
+ #if os(iOS)
129
+ return safely("TEXT_ENTRY_FOCUS_CONFIRMED", false) {
130
+ // An element that no longer resolves reads as identifier "" and frame `.zero`, which would
131
+ // match any focused element that also reports an empty frame. Require a real frame first,
132
+ // so a dead handle cannot corroborate anything.
133
+ let frame = element.frame
134
+ guard !frame.isEmpty else {
135
+ return false
136
+ }
137
+ let focused = app
138
+ .descendants(matching: .any)
139
+ .matching(NSPredicate(format: "hasKeyboardFocus == 1"))
140
+ .firstMatch
141
+ guard focused.exists else {
142
+ return false
143
+ }
144
+ return focused.identifier == element.identifier && focused.frame == frame
145
+ }
146
+ #else
147
+ return false
148
+ #endif
149
+ }
150
+
151
+ private func keyboardElementExists(app: XCUIApplication) -> Bool {
152
+ #if os(iOS)
153
+ return safely("KEYBOARD_EXISTS", false) { app.keyboards.firstMatch.exists }
154
+ #else
155
+ return false
156
+ #endif
157
+ }
158
+ }
@@ -15,6 +15,36 @@ extension RunnerTests {
15
15
  ) -> TextEntryResult {
16
16
  let totalStartedAt = Date()
17
17
  guard !text.isEmpty else {
18
+ // Replacing a field WITH the empty string is the clear-field primitive (#2063): the typing
19
+ // is vacuous but the clear is not. Returning here unconditionally left the old value in
20
+ // place while the response reported "typed". Append (`type`) and unrepaired entry stay
21
+ // no-ops, which is what an empty payload means for them.
22
+ if repairMode == .replacement {
23
+ guard let clearTarget = resolveTextEntryElement(app: app, target: target) else {
24
+ // No resolvable input means no clear happened — including the synthesized
25
+ // first-responder route, whose target carries no element. Success here would claim a
26
+ // clear the runner never performed.
27
+ logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: 0, mode: repairMode)
28
+ return TextEntryResult(
29
+ verified: nil,
30
+ repaired: false,
31
+ expectedText: "",
32
+ observedText: nil,
33
+ failure: .notFocused
34
+ )
35
+ }
36
+ clearTextInput(clearTarget)
37
+ // nil means the value is unreadable (secure fields), not "not empty": leave `verified`
38
+ // nil there rather than reporting a mismatch the runner cannot actually observe.
39
+ let observed = editableTextValue(for: clearTarget, treatingPlaceholderAsEmpty: true)
40
+ logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: 0, mode: repairMode)
41
+ return TextEntryResult(
42
+ verified: observed.map { $0.isEmpty },
43
+ repaired: false,
44
+ expectedText: "",
45
+ observedText: observed
46
+ )
47
+ }
18
48
  logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: 0, mode: repairMode)
19
49
  return TextEntryResult(verified: true, repaired: false, expectedText: "", observedText: "")
20
50
  }
@@ -104,11 +134,11 @@ extension RunnerTests {
104
134
  }
105
135
  }
106
136
 
107
- func typeIntoCurrentTarget(_ value: String) -> (element: XCUIElement?, dispatched: Bool, failure: TextEntryFailure?) {
137
+ func typeIntoCurrentTarget(_ value: String) -> (element: XCUIElement?, failure: TextEntryFailure?) {
108
138
  #if os(iOS)
109
139
  if shouldUseSynthesizedFirstResponderType {
110
140
  guard let currentTarget = resolveTextEntryElement(app: app, target: activeTarget) else {
111
- return (nil, false, .notFocused)
141
+ return (nil, .notFocused)
112
142
  }
113
143
  textEntryRoute = "synthesized-first-responder"
114
144
  NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder")
@@ -117,7 +147,7 @@ extension RunnerTests {
117
147
  case .continueTyping:
118
148
  // No refresh point: like the tap-witness target itself, the commit wait must observe
119
149
  // only the element the tap selected, never rediscover a different field.
120
- awaitSynthesizedFirstResponderCommit(
150
+ let commit = awaitSynthesizedFirstResponderCommit(
121
151
  app: app,
122
152
  target: TextEntryTarget(
123
153
  element: currentTarget,
@@ -128,9 +158,12 @@ extension RunnerTests {
128
158
  textBefore: textBefore,
129
159
  typedText: value
130
160
  )
131
- return (currentTarget, true, nil)
161
+ // The characters were posted, so the element stays on the tuple for the caller's
162
+ // bookkeeping; the failure is what makes the command refuse. Reporting ok here is the
163
+ // defect this route had — the deadline was indistinguishable from a commit.
164
+ return (currentTarget, Self.textEntryFailure(forCommitOutcome: commit))
132
165
  case .fallback:
133
- return (nil, false, .synthesisUnavailable)
166
+ return (nil, .synthesisUnavailable)
134
167
  case .raise(let message):
135
168
  NSException(
136
169
  name: NSExceptionName.internalInconsistencyException,
@@ -142,7 +175,7 @@ extension RunnerTests {
142
175
  if let currentTarget = resolveTextEntryElement(app: app, target: activeTarget) {
143
176
  textEntryRoute = "xctest-element"
144
177
  currentTarget.typeText(value)
145
- return (currentTarget, true, nil)
178
+ return (currentTarget, nil)
146
179
  } else if activeTarget.prefersFocusedElement && isKeyboardVisible(app: app) {
147
180
  #if os(iOS)
148
181
  textEntryRoute = "synthesized-first-responder"
@@ -167,9 +200,9 @@ extension RunnerTests {
167
200
  #else
168
201
  app.typeText(value)
169
202
  #endif
170
- return (resolveTextEntryElement(app: app, target: activeTarget), true, nil)
203
+ return (resolveTextEntryElement(app: app, target: activeTarget), nil)
171
204
  }
172
- return (nil, false, .notFocused)
205
+ return (nil, .notFocused)
173
206
  }
174
207
 
175
208
  func dispatchFailureResult(_ failure: TextEntryFailure) -> TextEntryResult {
@@ -203,8 +236,8 @@ extension RunnerTests {
203
236
  let delayedTypeStartedAt = Date()
204
237
  for (index, character) in characters.enumerated() {
205
238
  let dispatch = typeIntoCurrentTarget(String(character))
206
- guard dispatch.dispatched else {
207
- return dispatchFailureResult(dispatch.failure ?? .notFocused)
239
+ if let failure = dispatch.failure {
240
+ return dispatchFailureResult(failure)
208
241
  }
209
242
  typedTarget = dispatch.element ?? typedTarget
210
243
  if index + 1 < characters.count {
@@ -252,8 +285,8 @@ extension RunnerTests {
252
285
  let firstCharacter = String(characters[0])
253
286
  let firstStartedAt = Date()
254
287
  let firstDispatch = typeIntoCurrentTarget(firstCharacter)
255
- guard firstDispatch.dispatched else {
256
- return dispatchFailureResult(firstDispatch.failure ?? .notFocused)
288
+ if let failure = firstDispatch.failure {
289
+ return dispatchFailureResult(failure)
257
290
  }
258
291
  var firstTypedTarget = firstDispatch.element
259
292
  logTextEntryPhase(
@@ -281,8 +314,8 @@ extension RunnerTests {
281
314
  let remainingText = String(characters.dropFirst())
282
315
  let remainingStartedAt = Date()
283
316
  let remainingDispatch = typeIntoCurrentTarget(remainingText)
284
- guard remainingDispatch.dispatched else {
285
- return dispatchFailureResult(remainingDispatch.failure ?? .notFocused)
317
+ if let failure = remainingDispatch.failure {
318
+ return dispatchFailureResult(failure)
286
319
  }
287
320
  firstTypedTarget = remainingDispatch.element ?? firstTypedTarget
288
321
  logTextEntryPhase(
@@ -296,8 +329,8 @@ extension RunnerTests {
296
329
  } else {
297
330
  let typeStartedAt = Date()
298
331
  let dispatch = typeIntoCurrentTarget(text)
299
- guard dispatch.dispatched else {
300
- return dispatchFailureResult(dispatch.failure ?? .notFocused)
332
+ if let failure = dispatch.failure {
333
+ return dispatchFailureResult(failure)
301
334
  }
302
335
  typedTarget = dispatch.element
303
336
  logTextEntryPhase(
@@ -190,7 +190,7 @@ extension RunnerTests {
190
190
  command: Command,
191
191
  completion: @escaping ((data: Data, shouldFinish: Bool)) -> Void
192
192
  ) -> Bool {
193
- guard let commandId = normalizedInFlightCommandId(command.commandId) else { return false }
193
+ guard let commandId = command.commandId?.trimmedNonEmpty else { return false }
194
194
  inFlightCommandLock.lock()
195
195
  if inFlightCommandIds.contains(commandId) {
196
196
  inFlightCommandWaiters[commandId, default: []].append(completion)
@@ -213,7 +213,7 @@ extension RunnerTests {
213
213
  completion: ((data: Data, shouldFinish: Bool)) -> Void
214
214
  ) {
215
215
  var waiters: [((data: Data, shouldFinish: Bool)) -> Void] = []
216
- if let commandId = normalizedInFlightCommandId(command.commandId) {
216
+ if let commandId = command.commandId?.trimmedNonEmpty {
217
217
  inFlightCommandLock.lock()
218
218
  inFlightCommandIds.remove(commandId)
219
219
  waiters = inFlightCommandWaiters.removeValue(forKey: commandId) ?? []
@@ -225,15 +225,6 @@ extension RunnerTests {
225
225
  }
226
226
  }
227
227
 
228
- private func normalizedInFlightCommandId(_ commandId: String?) -> String? {
229
- guard let trimmed = commandId?.trimmingCharacters(in: .whitespacesAndNewlines),
230
- !trimmed.isEmpty
231
- else {
232
- return nil
233
- }
234
- return trimmed
235
- }
236
-
237
228
 
238
229
  // MARK: - Response Encoding
239
230
 
@@ -7,7 +7,7 @@ enum RunnerInteractionOutcome {
7
7
  case unsupported(message: String, hint: String?)
8
8
  }
9
9
 
10
- enum TvRemoteButton {
10
+ enum TvRemoteButton: String {
11
11
  case select
12
12
  case menu
13
13
  case home
@@ -18,6 +18,7 @@ enum TvRemoteButton {
18
18
  }
19
19
 
20
20
  extension RunnerTests {
21
+
21
22
  func resolveTvRemoteDoublePressDelay() -> TimeInterval {
22
23
  guard
23
24
  let raw = ProcessInfo.processInfo.environment["AGENT_DEVICE_TV_REMOTE_DOUBLE_PRESS_DELAY_MS"],
@@ -47,24 +48,8 @@ extension RunnerTests {
47
48
  }
48
49
 
49
50
  func tvRemoteButton(from raw: String?) -> TvRemoteButton? {
50
- switch raw?.lowercased() {
51
- case "select":
52
- return .select
53
- case "menu":
54
- return .menu
55
- case "home":
56
- return .home
57
- case "up":
58
- return .up
59
- case "down":
60
- return .down
61
- case "left":
62
- return .left
63
- case "right":
64
- return .right
65
- default:
66
- return nil
67
- }
51
+ guard let raw else { return nil }
52
+ return TvRemoteButton(rawValue: raw.lowercased())
68
53
  }
69
54
 
70
55
  func elementHasFocus(_ element: XCUIElement) -> Bool {
@@ -66,7 +66,11 @@ final class RunnerTests: XCTestCase {
66
66
  let minRecordingFps = 1
67
67
  let maxRecordingFps = 120
68
68
  var needsPostSnapshotInteractionDelay = false
69
- var needsFirstInteractionDelay = false
69
+ /// When the first interaction after an activation may run, on the monotonic uptime clock.
70
+ /// The guarantee is a minimum gap *since the activation*, not a pause at the interaction:
71
+ /// a caller that already spent that gap elsewhere (an agent's round trip is 190-260 ms)
72
+ /// has satisfied it and waits for nothing. `nil` = no activation is pending stabilization.
73
+ var firstInteractionReadyUptime: TimeInterval?
70
74
  var runnerAccessibilityHealth: RunnerAccessibilityHealth = .unknown
71
75
  var activeRecording: ScreenRecorder?
72
76
  let commandJournal = RunnerCommandJournal()
@@ -119,29 +123,21 @@ final class RunnerTests: XCTestCase {
119
123
  // Bounds the pre-plan SpringBoard system-modal probe, which can otherwise grind for tens of
120
124
  // seconds on remote-hosted consent dialogs and bypass the plan budget (#1244).
121
125
  let systemModalProbeBudget: TimeInterval = 4
126
+ // In-bundle unit tests (every `func test…` except `testCommand` below) compile only under
127
+ // `-D AGENT_DEVICE_RUNNER_UNIT_TESTS` and are classified by their `#if` guard (#1781 A7):
128
+ // - `#if AGENT_DEVICE_RUNNER_UNIT_TESTS` alone: a pure runner decision (rule table,
129
+ // geometry, parser, policy, journal, dispatch bookkeeping) that needs no launched app.
130
+ // Runs on the macOS host lane on every PR (ci.yml, no simulator) and on the iOS lanes.
131
+ // - `… && os(iOS)` (or a nested `#if os(iOS)`): runner/XCTest semantics — launches the
132
+ // host app, routes through SpringBoard, swizzles XCUIApplication, or asserts an
133
+ // iOS-only branch. Simulator lanes only (ios.yml PR list, xctest-nightly.yml).
134
+ // `pnpm check:xctest-selection` derives each lane's reachable set from these guards and
135
+ // fails when a declared test is reachable by no lane, so a test gated to a platform nothing
136
+ // runs (the old tvOS-only pair) cannot go dark silently.
122
137
  // Observability for the record(_:) suppression below: how many AX-broken-screen snapshot
123
138
  // issues this session muted, so wedge investigations see the volume without grepping logs.
124
139
  let suppressedIssueLock = NSLock()
125
140
  var suppressedAxSnapshotIssueCount = 0
126
- let interactiveTypes: Set<XCUIElement.ElementType> = [
127
- .button,
128
- .cell,
129
- .checkBox,
130
- .collectionView,
131
- .link,
132
- .menuItem,
133
- .picker,
134
- .searchField,
135
- .segmentedControl,
136
- .slider,
137
- .stepper,
138
- .switch,
139
- .tabBar,
140
- .textField,
141
- .secureTextField,
142
- .textView,
143
- .webView
144
- ]
145
141
  // Keep blocker actions narrow to avoid false positives from generic hittable containers.
146
142
  let actionableTypes: Set<XCUIElement.ElementType> = [
147
143
  .button,
@@ -0,0 +1,111 @@
1
+ import AVFoundation
2
+ import Foundation
3
+
4
+ /// Shared mechanics for the recording post-processing scripts (overlay burn-in, start trim).
5
+ /// Each script keeps its own argument grammar and export-quality policy; this file owns the
6
+ /// error vocabulary, flag-value reading, composition assembly, and the bounded export wait.
7
+
8
+ enum RecordingScriptError: Error, CustomStringConvertible {
9
+ case invalidArgs(String)
10
+ case invalidTrimRange
11
+ case missingVideoTrack
12
+ case exportFailed(String)
13
+
14
+ var description: String {
15
+ switch self {
16
+ case .invalidArgs(let message):
17
+ return message
18
+ case .invalidTrimRange:
19
+ return "Trim start must be before the end of the recording."
20
+ case .missingVideoTrack:
21
+ return "Input video does not contain a video track."
22
+ case .exportFailed(let message):
23
+ return message
24
+ }
25
+ }
26
+ }
27
+
28
+ func recordingOptionValue(_ arguments: [String], _ nextIndex: Int, _ flag: String) throws -> String {
29
+ guard nextIndex < arguments.count else {
30
+ throw RecordingScriptError.invalidArgs("\(flag) requires a value")
31
+ }
32
+ return arguments[nextIndex]
33
+ }
34
+
35
+ func removeStaleOutput(_ outputURL: URL) throws {
36
+ if FileManager.default.fileExists(atPath: outputURL.path) {
37
+ try FileManager.default.removeItem(at: outputURL)
38
+ }
39
+ }
40
+
41
+ func sourceVideoTrack(of asset: AVURLAsset) throws -> AVAssetTrack {
42
+ guard let track = asset.tracks(withMediaType: .video).first else {
43
+ throw RecordingScriptError.missingVideoTrack
44
+ }
45
+ return track
46
+ }
47
+
48
+ /// Copies `videoTrack` for `timeRange` into a fresh composition, carrying the optional audio track
49
+ /// along when present. The caller owns any `preferredTransform` policy: trim propagates the source
50
+ /// transform directly, while overlay re-applies it through a video-composition layer instruction
51
+ /// instead.
52
+ func makeRecordingComposition(
53
+ asset: AVURLAsset,
54
+ videoTrack: AVAssetTrack,
55
+ timeRange: CMTimeRange
56
+ ) throws -> AVMutableComposition {
57
+ let composition = AVMutableComposition()
58
+ guard let compositionVideoTrack = composition.addMutableTrack(
59
+ withMediaType: .video,
60
+ preferredTrackID: kCMPersistentTrackID_Invalid
61
+ ) else {
62
+ throw RecordingScriptError.exportFailed("Failed to create composition video track.")
63
+ }
64
+ try compositionVideoTrack.insertTimeRange(timeRange, of: videoTrack, at: .zero)
65
+
66
+ if let sourceAudioTrack = asset.tracks(withMediaType: .audio).first,
67
+ let compositionAudioTrack = composition.addMutableTrack(
68
+ withMediaType: .audio,
69
+ preferredTrackID: kCMPersistentTrackID_Invalid
70
+ ) {
71
+ try? compositionAudioTrack.insertTimeRange(timeRange, of: sourceAudioTrack, at: .zero)
72
+ }
73
+ return composition
74
+ }
75
+
76
+ func makeRecordingExporter(
77
+ _ composition: AVAsset,
78
+ presetName: String,
79
+ outputURL: URL,
80
+ videoComposition: AVMutableVideoComposition? = nil
81
+ ) throws -> AVAssetExportSession {
82
+ guard let exporter = AVAssetExportSession(asset: composition, presetName: presetName) else {
83
+ throw RecordingScriptError.exportFailed("Failed to create export session.")
84
+ }
85
+ exporter.outputURL = outputURL
86
+ exporter.outputFileType = .mp4
87
+ exporter.videoComposition = videoComposition
88
+ exporter.shouldOptimizeForNetworkUse = true
89
+ return exporter
90
+ }
91
+
92
+ /// Bounded asynchronous export: signals completion through a semaphore and cancels after 120s so a
93
+ /// wedged encoder cannot hang the recording pipeline past the caller's own timeout budget.
94
+ func runRecordingExport(
95
+ _ exporter: AVAssetExportSession,
96
+ timeoutMessage: String,
97
+ failureMessage: String
98
+ ) throws {
99
+ let semaphore = DispatchSemaphore(value: 0)
100
+ exporter.exportAsynchronously {
101
+ semaphore.signal()
102
+ }
103
+ if semaphore.wait(timeout: .now() + 120) == .timedOut {
104
+ exporter.cancelExport()
105
+ throw RecordingScriptError.exportFailed(timeoutMessage)
106
+ }
107
+
108
+ if exporter.status != .completed {
109
+ throw RecordingScriptError.exportFailed(exporter.error?.localizedDescription ?? failureMessage)
110
+ }
111
+ }