agent-device 0.21.23 → 0.21.25

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 (435) hide show
  1. package/README.md +6 -6
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.23.apk → agent-device-android-ime-helper-0.21.25.apk} +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.25.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.23.manifest.json → agent-device-android-ime-helper-0.21.25.manifest.json} +4 -4
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.25.apk +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.25.apk.sha256 +1 -0
  7. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.25.manifest.json +16 -0
  8. package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +12 -6
  9. package/apple/macos-helper/Tests/AgentDeviceMacOSHelperTests/RectResponseTests.swift +21 -0
  10. package/apple/snapshot-bridge/README.md +9 -7
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +159 -4
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerHttpRequestFraming.swift +139 -0
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerListenerPeer.swift +41 -0
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerListenerToken.swift +25 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandDispatch.swift +56 -0
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +14 -5
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +18 -11
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +8 -0
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +12 -0
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +25 -1
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCaptureTarget.swift +1 -1
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotExecution.swift +29 -5
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +0 -3
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +35 -13
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift +59 -26
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +19 -5
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +50 -106
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +142 -54
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +4 -1
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +21 -19
  31. package/dist/src/absence-observation-errors.js +1 -0
  32. package/dist/src/adapter.js +1 -1
  33. package/dist/src/adb-executor.js +2 -2
  34. package/dist/src/adb-failure.js +2 -2
  35. package/dist/src/adb.js +1 -1
  36. package/dist/src/agent-browser-lifecycle.js +1 -1
  37. package/dist/src/agent-browser-process-record.js +1 -1
  38. package/dist/src/agent-browser-provider.js +1 -1
  39. package/dist/src/agent-browser-tool.js +1 -1
  40. package/dist/src/agent-device-client.js +2 -2
  41. package/dist/src/alert.js +1 -1
  42. package/dist/src/android-adb.d.ts +3 -2
  43. package/dist/src/android-adb.js +1 -1
  44. package/dist/src/android-input-ownership.js +1 -1
  45. package/dist/src/android-system-surface-disclosure.js +1 -0
  46. package/dist/src/android-text-entry.js +1 -0
  47. package/dist/src/android.js +1 -1
  48. package/dist/src/app-backend.js +1 -1
  49. package/dist/src/app-helpers.js +1 -1
  50. package/dist/src/app-inventory.d.ts +4 -0
  51. package/dist/src/app-launch.js +1 -1
  52. package/dist/src/app-lifecycle.js +4 -3
  53. package/dist/src/app-lifecycle2.js +2 -2
  54. package/dist/src/app-log-files.js +1 -1
  55. package/dist/src/app-log-reconnect.js +1 -1
  56. package/dist/src/app-log-runtime2.js +3 -3
  57. package/dist/src/app-resolution-facade.js +1 -1
  58. package/dist/src/app-resolution.js +2 -2
  59. package/dist/src/app-state-runtime.d.ts +15 -0
  60. package/dist/src/apple-runner-platform.js +1 -1
  61. package/dist/src/application-lifecycle-interaction.js +1 -1
  62. package/dist/src/artifact-download.js +1 -1
  63. package/dist/src/auth.js +1 -1
  64. package/dist/src/aws-device-farm-connection.js +1 -0
  65. package/dist/src/back-mode.js +1 -1
  66. package/dist/src/backend-snapshot-options.js +1 -1
  67. package/dist/src/boot-diagnostics.js +1 -1
  68. package/dist/src/browserstack-connection.js +1 -0
  69. package/dist/src/capture-disclosure.js +1 -0
  70. package/dist/src/cli-config.js +1 -1
  71. package/dist/src/cli-help.js +36 -17
  72. package/dist/src/cli-output.js +1 -1
  73. package/dist/src/cli-runner.js +3 -1
  74. package/dist/src/cli.js +3 -4
  75. package/dist/src/client-companion-tunnel.js +1 -0
  76. package/dist/src/client-metro.js +1 -1
  77. package/dist/src/client-types.d.ts +15 -7
  78. package/dist/src/cloud-artifacts.d.ts +3 -2
  79. package/dist/src/code-signature-cache.js +1 -1
  80. package/dist/src/code-signature.js +1 -1
  81. package/dist/src/command-metadata.js +73 -1
  82. package/dist/src/command-policy.js +1 -0
  83. package/dist/src/command-schema.js +3 -3
  84. package/dist/src/command-surface.js +1 -1
  85. package/dist/src/command-tools.js +2 -2
  86. package/dist/src/connection-runtime.js +1 -1
  87. package/dist/src/connection-verification.js +1 -0
  88. package/dist/src/connection.js +5 -5
  89. package/dist/src/contracts.d.ts +1 -1
  90. package/dist/src/contracts.js +1 -1
  91. package/dist/src/daemon-client-lease-beat.js +1 -1
  92. package/dist/src/daemon-client-lifecycle.js +1 -1
  93. package/dist/src/daemon-client-liveness-probe.js +1 -1
  94. package/dist/src/daemon-policy-file.js +1 -1
  95. package/dist/src/daemon-registration-owner.js +1 -1
  96. package/dist/src/daemon-registration.js +1 -1
  97. package/dist/src/daemon-resolution.js +3 -1
  98. package/dist/src/debug-symbols-facade.js +1 -1
  99. package/dist/src/device-claim-inspection.js +1 -1
  100. package/dist/src/device-input-state.d.ts +6 -17
  101. package/dist/src/device-inventory-context.js +1 -1
  102. package/dist/src/device-readiness-runtime.js +1 -1
  103. package/dist/src/device-release.js +1 -1
  104. package/dist/src/device-selection-resolver.js +1 -1
  105. package/dist/src/device-shell.js +1 -1
  106. package/dist/src/device.js +3 -1
  107. package/dist/src/devicectl.js +1 -1
  108. package/dist/src/diagnostics.js +1 -1
  109. package/dist/src/doctor-facade.js +1 -1
  110. package/dist/src/durable-capture-resource.js +1 -1
  111. package/dist/src/durable-resource.js +1 -1
  112. package/dist/src/engine-eval-script.js +1 -1
  113. package/dist/src/error.js +1 -1
  114. package/dist/src/exec.js +1 -1
  115. package/dist/src/expo-dev-client-launch.js +1 -0
  116. package/dist/src/file.js +3 -1
  117. package/dist/src/find.js +1 -1
  118. package/dist/src/finders.js +1 -1
  119. package/dist/src/generic-settle.js +1 -1
  120. package/dist/src/generic.js +1 -1
  121. package/dist/src/gesture-admission.js +1 -1
  122. package/dist/src/gesture-plan-types.d.ts +2 -5
  123. package/dist/src/gesture-plan.js +1 -1
  124. package/dist/src/gesture-runtime.js +1 -1
  125. package/dist/src/harmonyos.js +1 -1
  126. package/dist/src/hdc.js +1 -1
  127. package/dist/src/helper.js +2 -2
  128. package/dist/src/human-control.js +1 -1
  129. package/dist/src/ime-rebind.js +1 -1
  130. package/dist/src/ime-state.js +1 -1
  131. package/dist/src/index.d.ts +4 -4
  132. package/dist/src/index.js +1 -1
  133. package/dist/src/input-actions.js +1 -1
  134. package/dist/src/input-actions2.js +2 -2
  135. package/dist/src/install-source-network-transport.js +1 -1
  136. package/dist/src/install-source2.js +1 -1
  137. package/dist/src/interaction-error.js +1 -0
  138. package/dist/src/interaction-positionals.js +1 -1
  139. package/dist/src/interaction.js +1 -1
  140. package/dist/src/interaction2.js +1 -1
  141. package/dist/src/interactor-operation-catalog.js +1 -1
  142. package/dist/src/interactor.js +5 -5
  143. package/dist/src/interactor2.js +1 -1
  144. package/dist/src/interactors.js +1 -1
  145. package/dist/src/internal/bin.js +1 -1
  146. package/dist/src/internal/daemon.js +5 -1
  147. package/dist/src/internal/png-worker.js +1 -1
  148. package/dist/src/inventory.js +2 -2
  149. package/dist/src/inventory2.js +1 -1
  150. package/dist/src/inventory6.js +1 -1
  151. package/dist/src/io.d.ts +2 -2
  152. package/dist/src/ios-client.js +1 -1
  153. package/dist/src/ios-settings.js +1 -1
  154. package/dist/src/ios-snapshot-acquisition.js +1 -1
  155. package/dist/src/ios-snapshot-adapter.js +1 -1
  156. package/dist/src/ios-snapshot-engine.js +1 -1
  157. package/dist/src/ios-snapshot-runtime.js +1 -1
  158. package/dist/src/keyed-lock.js +1 -1
  159. package/dist/src/launch-confirmation.js +1 -1
  160. package/dist/src/lease.js +1 -1
  161. package/dist/src/limrun-runtime-dependencies.js +1 -1
  162. package/dist/src/limrun.d.ts +5 -564
  163. package/dist/src/limrun.js +1 -1
  164. package/dist/src/linux.js +1 -1
  165. package/dist/src/load.js +1 -1
  166. package/dist/src/local-interactor-operation-set.js +1 -1
  167. package/dist/src/log-stream.js +9 -9
  168. package/dist/src/mechanics.js +16 -16
  169. package/dist/src/metro.js +1 -1
  170. package/dist/src/metro2.js +1 -1
  171. package/dist/src/mobile-snapshot-semantics.js +1 -1
  172. package/dist/src/observability.js +1 -1
  173. package/dist/src/observation.js +1 -2
  174. package/dist/src/open-target-resolution.js +1 -1
  175. package/dist/src/output-path.js +1 -1
  176. package/dist/src/overlay.js +1 -3
  177. package/dist/src/perf-facade.js +1 -1
  178. package/dist/src/perf-runtime-operation-builder.js +1 -1
  179. package/dist/src/perf-runtime.js +1 -1
  180. package/dist/src/perf-target.js +1 -1
  181. package/dist/src/physical-device-control.js +1 -1
  182. package/dist/src/platform-request-scope.js +1 -1
  183. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  184. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  185. package/dist/src/platform-runtime-apple-resources.js +1 -0
  186. package/dist/src/platform-runtime-claim-recovery.js +1 -0
  187. package/dist/src/platform-runtime-daemon-services.js +1 -0
  188. package/dist/src/platform-runtime-host.js +1 -1
  189. package/dist/src/platform-runtime-network-web-transport.js +1 -1
  190. package/dist/src/platform-runtime-open-target.js +1 -1
  191. package/dist/src/platform-runtime-operation-host.js +4 -4
  192. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  193. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  194. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  195. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  196. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  197. package/dist/src/platform-runtime-screen-recording-harmony-host.js +1 -1
  198. package/dist/src/platform-runtime-screen-recording-web-host.js +1 -1
  199. package/dist/src/platform-runtime-unavailable.js +1 -1
  200. package/dist/src/platform-runtime.js +1 -1
  201. package/dist/src/platform-runtime2.js +1 -1
  202. package/dist/src/plugin.js +1 -0
  203. package/dist/src/plugins/webdriver.d.ts +111 -0
  204. package/dist/src/plugins/webdriver.js +1 -0
  205. package/dist/src/plugins.d.ts +2 -1
  206. package/dist/src/plugins2.js +1 -1
  207. package/dist/src/png-crop-bytes.js +1 -1
  208. package/dist/src/png-resize.js +1 -0
  209. package/dist/src/png-worker-client.js +1 -1
  210. package/dist/src/png-worker-contract.js +1 -1
  211. package/dist/src/process-entry.js +1 -1
  212. package/dist/src/process-exit.js +1 -1
  213. package/dist/src/process.js +1 -1
  214. package/dist/src/provider-credential-fingerprint.js +1 -1
  215. package/dist/src/provider-device-admission.js +1 -1
  216. package/dist/src/provider-device-runtime.d.ts +556 -0
  217. package/dist/src/provider-policy.js +1 -0
  218. package/dist/src/provider-profile-fields.js +1 -1
  219. package/dist/src/provider-webdriver.js +1 -2
  220. package/dist/src/proxy.js +2 -2
  221. package/dist/src/quality-warnings.js +1 -1
  222. package/dist/src/react-devtools.js +2 -0
  223. package/dist/src/react-native-overlay2.js +3 -0
  224. package/dist/src/react-native.js +1 -1
  225. package/dist/src/record-runtime.js +1 -1
  226. package/dist/src/record-trace.js +1 -1
  227. package/dist/src/recording.js +2 -2
  228. package/dist/src/register-builtins.js +1 -1
  229. package/dist/src/remote-bridge.js +1 -0
  230. package/dist/src/remote-config-core.js +1 -0
  231. package/dist/src/remote-config.js +1 -1
  232. package/dist/src/remote-config2.js +1 -1
  233. package/dist/src/replay-backend-registry.js +1 -0
  234. package/dist/src/replay-divergence.js +1 -1
  235. package/dist/src/replay-maestro-backend.js +117 -0
  236. package/dist/src/replay-script-selection.js +1 -0
  237. package/dist/src/replay.js +1 -3
  238. package/dist/src/replay2.js +1 -0
  239. package/dist/src/reporting.js +3 -3
  240. package/dist/src/request-cancel.js +1 -1
  241. package/dist/src/request-providers.js +1 -1
  242. package/dist/src/request-runtime-binding.js +1 -1
  243. package/dist/src/result-serialization.js +1 -1
  244. package/dist/src/retry.js +1 -1
  245. package/dist/src/retry2.js +1 -0
  246. package/dist/src/runner-artifact-manifest.js +1 -1
  247. package/dist/src/runner-cache-retention.js +1 -0
  248. package/dist/src/runner-cache-trim.js +1 -0
  249. package/dist/src/runner-client.js +1 -1
  250. package/dist/src/runner-dev-tools-security.js +1 -1
  251. package/dist/src/runner-device-readiness.js +1 -1
  252. package/dist/src/runner-device-set.js +1 -0
  253. package/dist/src/runner-error-classification.js +3 -3
  254. package/dist/src/runner-exchange.js +1 -1
  255. package/dist/src/runner-lease.js +1 -0
  256. package/dist/src/runner-operations-facade.js +1 -1
  257. package/dist/src/runner-retention.js +1 -0
  258. package/dist/src/runner-start-budget.js +1 -1
  259. package/dist/src/runner-startup-transport.js +1 -0
  260. package/dist/src/runner-toolchain-probe.js +2 -0
  261. package/dist/src/runner.js +2 -1
  262. package/dist/src/runtime-command-surface.js +1 -0
  263. package/dist/src/runtime-deployment.js +1 -0
  264. package/dist/src/runtime-factory.js +1 -0
  265. package/dist/src/runtime-session.js +1 -0
  266. package/dist/src/runtime-transport-hints.js +1 -1
  267. package/dist/src/runtime.js +2 -1
  268. package/dist/src/runtime10.js +2 -2
  269. package/dist/src/runtime2.js +4 -4
  270. package/dist/src/runtime3.js +1 -1
  271. package/dist/src/runtime4.js +5 -5
  272. package/dist/src/runtime5.js +1 -1
  273. package/dist/src/runtime6.js +1 -1
  274. package/dist/src/runtime7.js +1 -1
  275. package/dist/src/runtime8.js +1 -1
  276. package/dist/src/runtime9.js +1 -1
  277. package/dist/src/screen-recording-transport.js +1 -1
  278. package/dist/src/screenshot-density.js +1 -1
  279. package/dist/src/screenshot-image.js +1 -1
  280. package/dist/src/screenshot-runtime.js +1 -1
  281. package/dist/src/screenshot3.js +5 -5
  282. package/dist/src/sdk-batch-runner.js +2 -2
  283. package/dist/src/sdk-batch.js +2 -2
  284. package/dist/src/sdk-contracts.d.ts +20 -29
  285. package/dist/src/sdk-contracts.js +2 -2
  286. package/dist/src/sdk-device.js +1 -1
  287. package/dist/src/sdk-finders.d.ts +1 -1
  288. package/dist/src/sdk-finders.js +1 -1
  289. package/dist/src/sdk-io.d.ts +4 -2
  290. package/dist/src/sdk-remote-config.d.ts +4 -0
  291. package/dist/src/sdk-remote-config.js +1 -1
  292. package/dist/src/sdk-selectors.d.ts +5 -6
  293. package/dist/src/sdk-selectors.js +1 -1
  294. package/dist/src/selection.js +0 -0
  295. package/dist/src/selector-pipeline-policy.js +1 -1
  296. package/dist/src/selector-runtime.js +1 -1
  297. package/dist/src/selectors.js +1 -1
  298. package/dist/src/server.js +2 -2
  299. package/dist/src/session-allocation.js +1 -1
  300. package/dist/src/session-replay-runtime-failure-response.js +1 -0
  301. package/dist/src/session-runtime-admission.js +1 -1
  302. package/dist/src/session.js +1 -1
  303. package/dist/src/session2.js +5 -5
  304. package/dist/src/settings.js +1 -1
  305. package/dist/src/settings2.js +1 -1
  306. package/dist/src/simctl-settings.js +1 -1
  307. package/dist/src/simctl.js +1 -1
  308. package/dist/src/simulator-boot.js +1 -1
  309. package/dist/src/simulator-state.js +1 -1
  310. package/dist/src/simulator.js +1 -1
  311. package/dist/src/snapshot-capture-annotations.js +1 -1
  312. package/dist/src/snapshot-chrome.js +1 -0
  313. package/dist/src/snapshot-desktop-projection.js +1 -1
  314. package/dist/src/snapshot-evidence.js +1 -0
  315. package/dist/src/snapshot-node-lookup.js +1 -0
  316. package/dist/src/snapshot-occlusion.js +1 -0
  317. package/dist/src/snapshot-quality-verdict.js +1 -0
  318. package/dist/src/snapshot-runtime-binding.js +1 -1
  319. package/dist/src/snapshot-runtime.js +1 -1
  320. package/dist/src/snapshot-state.js +1 -1
  321. package/dist/src/snapshot.js +1 -1
  322. package/dist/src/snapshot2.js +1 -1
  323. package/dist/src/snapshot3.js +1 -1
  324. package/dist/src/src.js +5 -1
  325. package/dist/src/src2.js +1 -1
  326. package/dist/src/src3.js +2 -1
  327. package/dist/src/store.js +1 -1
  328. package/dist/src/surface-snapshot2.js +1 -1
  329. package/dist/src/target-evidence-tree.js +1 -0
  330. package/dist/src/target-evidence.js +1 -1
  331. package/dist/src/text-entry-focus.js +1 -1
  332. package/dist/src/tool-provider2.js +1 -1
  333. package/dist/src/touch-runtime.js +1 -1
  334. package/dist/src/touch-runtime2.js +1 -1
  335. package/dist/src/tree.js +1 -1
  336. package/dist/src/tv-remote.js +1 -1
  337. package/dist/src/type-text-runtime.js +1 -1
  338. package/dist/src/undici.js +8 -8
  339. package/dist/src/update-check.js +1 -1
  340. package/dist/src/vars.js +1 -1
  341. package/dist/src/version.js +1 -1
  342. package/dist/src/video.js +2 -2
  343. package/dist/src/wait.js +1 -0
  344. package/dist/src/web.js +1 -1
  345. package/dist/src/web2.js +1 -1
  346. package/package.json +22 -19
  347. package/server.json +2 -2
  348. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.23.apk.sha256 +0 -1
  349. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.23.apk +0 -0
  350. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.23.apk.sha256 +0 -1
  351. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.23.manifest.json +0 -16
  352. package/dist/src/adb-transport.js +0 -1
  353. package/dist/src/alert-runtime.js +0 -1
  354. package/dist/src/app-deployment-resolution.js +0 -2
  355. package/dist/src/app-inventory-contract.js +0 -1
  356. package/dist/src/app-log-session-resource.js +0 -1
  357. package/dist/src/application-lifecycle-runtime.js +0 -1
  358. package/dist/src/auth-session.js +0 -1
  359. package/dist/src/cache.js +0 -1
  360. package/dist/src/capabilities.js +0 -1
  361. package/dist/src/capture.js +0 -1
  362. package/dist/src/catalog.js +0 -1
  363. package/dist/src/click-button.js +0 -1
  364. package/dist/src/client-metro-companion.js +0 -1
  365. package/dist/src/client-options.js +0 -3
  366. package/dist/src/compatibility-policy.js +0 -1
  367. package/dist/src/daemon-artifacts.js +0 -1
  368. package/dist/src/daemon-client-transport.js +0 -1
  369. package/dist/src/daemon-http.js +0 -1
  370. package/dist/src/device-claim-rule.js +0 -1
  371. package/dist/src/device-rotation.js +0 -1
  372. package/dist/src/device-session.js +0 -3
  373. package/dist/src/device2.js +0 -1
  374. package/dist/src/device3.js +0 -3
  375. package/dist/src/dispatch-resolve.js +0 -4
  376. package/dist/src/format.js +0 -1
  377. package/dist/src/gesture-plan-types.js +0 -1
  378. package/dist/src/host.js +0 -1
  379. package/dist/src/human-control-contract.js +0 -1
  380. package/dist/src/interaction-ref-policy.js +0 -1
  381. package/dist/src/interactor-operation-binding.js +0 -1
  382. package/dist/src/keyboard-actions.js +0 -1
  383. package/dist/src/lease-context.js +0 -1
  384. package/dist/src/lease-registry-scope.js +0 -1
  385. package/dist/src/lease-scope.js +0 -1
  386. package/dist/src/managed-device-scope.js +0 -1
  387. package/dist/src/observe-until.js +0 -1
  388. package/dist/src/output-common.js +0 -5
  389. package/dist/src/owner-identity.js +0 -3
  390. package/dist/src/path-resolution.js +0 -1
  391. package/dist/src/perf-runtime-plan.js +0 -1
  392. package/dist/src/platform-runtime-app-log-process.js +0 -1
  393. package/dist/src/platform-runtime-managed-owner.js +0 -1
  394. package/dist/src/platform-runtime-screen-recording-process-host.js +0 -1
  395. package/dist/src/png.js +0 -1
  396. package/dist/src/prepare-kind.js +0 -1
  397. package/dist/src/provider-device-runtimes.js +0 -1
  398. package/dist/src/provider-session-ownership.js +0 -2
  399. package/dist/src/providers.js +0 -1
  400. package/dist/src/registry.js +0 -68
  401. package/dist/src/request-admission.js +0 -1
  402. package/dist/src/request-cancellation.js +0 -1
  403. package/dist/src/request-progress-protocol.js +0 -1
  404. package/dist/src/runner-cache-metadata.js +0 -3
  405. package/dist/src/runner-disposal.js +0 -1
  406. package/dist/src/runner-session-types.js +0 -1
  407. package/dist/src/runtime-admission.js +0 -1
  408. package/dist/src/screen-recording-admission-ledger.js +0 -1
  409. package/dist/src/screen-recording-resource-recovery.js +0 -1
  410. package/dist/src/screenshot-result.js +0 -1
  411. package/dist/src/script.js +0 -1
  412. package/dist/src/scroll-command.js +0 -1
  413. package/dist/src/scroll-gesture.js +0 -1
  414. package/dist/src/scroll-indicator.js +0 -1
  415. package/dist/src/session-artifact-paths.js +0 -1
  416. package/dist/src/session-paths.js +0 -1
  417. package/dist/src/session-repair-tombstone.js +0 -1
  418. package/dist/src/session-routing.js +0 -1
  419. package/dist/src/session-snapshot.js +0 -1
  420. package/dist/src/snapshot4.js +0 -1
  421. package/dist/src/src4.js +0 -1
  422. package/dist/src/src5.js +0 -1
  423. package/dist/src/src6.js +0 -1
  424. package/dist/src/src7.js +0 -2
  425. package/dist/src/src8.js +0 -115
  426. package/dist/src/string-enum.js +0 -1
  427. package/dist/src/success-text.js +0 -1
  428. package/dist/src/timeouts.js +0 -1
  429. package/dist/src/touch-reference-frame.js +0 -1
  430. package/dist/src/transport.js +0 -3
  431. package/dist/src/ttl-memo.js +0 -1
  432. package/dist/src/verified-file.js +0 -1
  433. package/dist/src/viewport.js +0 -1
  434. package/dist/src/window-state.js +0 -1
  435. /package/dist/src/{doctor-output.js → doctor-lines.js} +0 -0
package/README.md CHANGED
@@ -31,7 +31,7 @@ agent-device doctor
31
31
  agent-device help workflow
32
32
  ```
33
33
 
34
- Run `doctor` yourself before handing the CLI to an agent; `help workflow` links to the guides for debugging, replay, and profiling, and the installed help always matches the installed version.
34
+ Run `doctor` yourself before you hand the CLI to an agent. `help workflow` links to the guides for debugging, replay, and profiling.
35
35
 
36
36
  ### Drive an app from the CLI
37
37
 
@@ -61,7 +61,7 @@ agent-device screenshot ./contact-form.png
61
61
  agent-device close
62
62
  ```
63
63
 
64
- Refs are only valid from the latest output: after a `--settle` command, use the refs in its diff, and take a new snapshot only if the diff omits what you need. Snapshots come from the app's accessibility tree, so clear labels, roles, and test IDs make agent runs more reliable; use screenshots and video as evidence or when accessibility data is poor.
64
+ Refs are valid only in the latest output: after a `--settle` command, use the refs in its diff, and take a new snapshot only if the diff omits what you need. Snapshots come from the app's accessibility tree, so clear labels, roles, and test IDs make agent runs more reliable; use screenshots and video as evidence or when accessibility data is poor.
65
65
 
66
66
  ![agent-device demo showing Codex using agent-device to create a new contact in the iOS Contacts app from a simple prompt](./website/docs/public/agent-device-contacts.gif)
67
67
 
@@ -126,7 +126,7 @@ With the CLI installed, prompts like these work end to end:
126
126
  ## Next steps
127
127
 
128
128
  - [AI Agent Setup](https://oss.callstack.com/agent-device/docs/agent-setup): skills, project rules, and per-client setup for Cursor, Codex, Claude Code, Windsurf, and others.
129
- - [Quick Start](https://oss.callstack.com/agent-device/docs/quick-start): a guided run on the bundled Expo test app with screenshots, replay, and performance data.
129
+ - [Quick Start](https://oss.callstack.com/agent-device/docs/quick-start): open an app, read its UI, and interact with it from the command line.
130
130
  - [Replay & E2E](https://oss.callstack.com/agent-device/docs/replay-e2e) and [Debugging & Profiling](https://oss.callstack.com/agent-device/docs/debugging-profiling): repeatable tests and bug hunting.
131
131
 
132
132
  ## Where to run agent-device
@@ -137,15 +137,15 @@ The same session and evidence model works at every step: the agent explores the
137
137
  | --- | --- | --- |
138
138
  | Local | Trying commands and debugging apps on simulators, emulators, physical devices, macOS, and Linux. | Follow the Quick Start. |
139
139
  | CI/CD | Automated pull request and merge validation with replay scripts and captured artifacts. | Try the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml). |
140
- | Cloud / remote | Linux runners, managed devices, and remote jobs. | Set up a [remote proxy](https://oss.callstack.com/agent-device/docs/remote-proxy), connect a [device cloud](https://oss.callstack.com/agent-device/docs/device-clouds) (BrowserStack, AWS Device Farm, Limrun), or [contact Callstack](mailto:hello@callstack.com) for team QA. |
140
+ | Cloud / remote | Linux runners, managed devices, and remote jobs. | Set up a [remote proxy](https://oss.callstack.com/agent-device/docs/remote-proxy), connect a [device cloud](https://oss.callstack.com/agent-device/docs/device-clouds) (BrowserStack, AWS Device Farm, TestMu AI, Limrun), or [contact Callstack](mailto:hello@callstack.com) for team QA. |
141
141
 
142
142
  ## How it works
143
143
 
144
144
  `agent-device` keeps device state in sessions. It uses a local accessibility bridge for iOS Simulator snapshots and XCTest for iOS interactions, physical iOS, and tvOS; ADB and the snapshot helper on Android; HDC and ArkUI `uitest` on HarmonyOS; Vega CLI/VDA on the Vega Virtual Device; a local helper on macOS; and AT-SPI on Linux.
145
145
 
146
- Support depth varies by target. Newer backends such as HarmonyOS and Vega OS cover a subset of commands; run `agent-device capabilities --platform <platform>` to see what a target supports.
146
+ Support depth varies by target. Some backends, such as HarmonyOS and Vega OS, cover a subset of commands; run `agent-device capabilities --platform <platform>` to see what a target supports.
147
147
 
148
- Sessions are scoped to the caller's git worktree, and host-local device claims stop parallel agents from taking over each other's simulators and emulators. Inspect ownership without a daemon via `agent-device device status`, and settle provably dead owners with `agent-device device release --stale`. The same commands drive hosted devices on [BrowserStack, AWS Device Farm, and Limrun](https://oss.callstack.com/agent-device/docs/device-clouds).
148
+ Sessions are scoped to the caller's git worktree, and host-local device claims stop parallel agents from taking over each other's simulators and emulators. Run `agent-device device status` to inspect ownership without a daemon, and `agent-device device release --stale` to release claims whose owner is provably no longer running. The same commands drive hosted devices on [BrowserStack, AWS Device Farm, TestMu AI, and Limrun](https://oss.callstack.com/agent-device/docs/device-clouds).
149
149
 
150
150
  `agent-device` uses the inspect-act-verify process from Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser) for mobile, TV, and desktop apps. Basic `--platform web` support runs `agent-browser` in the same session and replay system.
151
151
 
@@ -0,0 +1 @@
1
+ 2b1c6a028b28e426c4bed80449b6dd1eff69985f1741a58d033e30f8b9fa5bf1 agent-device-android-ime-helper-0.21.25.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.23",
4
- "assetName": "agent-device-android-ime-helper-0.21.23.apk",
5
- "sha256": "0268a637de0047d0c8d2e6af9dc0603f96a308dda4369b3f0c2e52a76e67c517",
3
+ "version": "0.21.25",
4
+ "assetName": "agent-device-android-ime-helper-0.21.25.apk",
5
+ "sha256": "2b1c6a028b28e426c4bed80449b6dd1eff69985f1741a58d033e30f8b9fa5bf1",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21023,
7
+ "versionCode": 21025,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ 79175080eb71f444224c844a599e75e839d6456790eb96d175fc887f85237a12 agent-device-android-snapshot-helper-0.21.25.apk
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "android-snapshot-helper",
3
+ "version": "0.21.25",
4
+ "releaseTag": "v0.21.25",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.25.apk",
6
+ "apkUrl": "https://github.com/callstack/agent-device/releases/download/v0.21.25/agent-device-android-snapshot-helper-0.21.25.apk",
7
+ "sha256": "79175080eb71f444224c844a599e75e839d6456790eb96d175fc887f85237a12",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.25.apk.sha256",
9
+ "packageName": "com.callstack.agentdevice.snapshothelper",
10
+ "versionCode": 21025,
11
+ "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
+ "minSdk": 23,
13
+ "targetSdk": 36,
14
+ "outputFormat": "uiautomator-xml",
15
+ "statusProtocol": "android-snapshot-helper-v1"
16
+ }
@@ -795,12 +795,18 @@ func rectAttribute(_ element: AXUIElement) -> RectResponse? {
795
795
  return nil
796
796
  }
797
797
 
798
- return RectResponse(
799
- x: Double(position.x),
800
- y: Double(position.y),
801
- width: Double(size.width),
802
- height: Double(size.height)
803
- )
798
+ return finiteRectResponse(position: position, size: size)
799
+ }
800
+
801
+ /// JSONEncoder throws on inf/NaN, which fails the whole snapshot. SwiftUI reports lazy
802
+ /// containers scrolled out of view as an AXOpaqueProviderGroup at (inf, inf, 0, 0).
803
+ func finiteRectResponse(position: CGPoint, size: CGSize) -> RectResponse? {
804
+ let x = Double(position.x)
805
+ let y = Double(position.y)
806
+ let width = Double(size.width)
807
+ let height = Double(size.height)
808
+ guard x.isFinite, y.isFinite, width.isFinite, height.isFinite else { return nil }
809
+ return RectResponse(x: x, y: y, width: width, height: height)
804
810
  }
805
811
 
806
812
  private func accessibilityAxValue(_ value: CFTypeRef?) -> AXValue? {
@@ -0,0 +1,21 @@
1
+ import XCTest
2
+
3
+ @testable import AgentDeviceMacOSHelper
4
+
5
+ final class RectResponseTests: XCTestCase {
6
+ func testFiniteRectKeepsItsValues() throws {
7
+ let rect = try XCTUnwrap(
8
+ finiteRectResponse(position: CGPoint(x: 1, y: 2), size: CGSize(width: 3, height: 4)))
9
+ XCTAssertEqual([rect.x, rect.y, rect.width, rect.height], [1, 2, 3, 4])
10
+ }
11
+
12
+ func testNonFiniteComponentYieldsNoRect() {
13
+ let nonFinite: [CGFloat] = [.infinity, -.infinity, .nan]
14
+ for value in nonFinite {
15
+ XCTAssertNil(finiteRectResponse(position: CGPoint(x: value, y: 0), size: .zero))
16
+ XCTAssertNil(finiteRectResponse(position: CGPoint(x: 0, y: value), size: .zero))
17
+ XCTAssertNil(finiteRectResponse(position: .zero, size: CGSize(width: value, height: 0)))
18
+ XCTAssertNil(finiteRectResponse(position: .zero, size: CGSize(width: 0, height: value)))
19
+ }
20
+ }
21
+ }
@@ -50,13 +50,15 @@ disabled after fallback until that app relaunches.
50
50
 
51
51
  ## Remote content
52
52
 
53
- The reader snapshots one process. A WebKit page — Safari's or a `WKWebView`'s —
54
- lives in a WebContent process and appears in that tree as an `AXRemoteElement`
55
- leaf under the web view. The guest returns the leaf as delivered; the host
56
- refuses a tree in which such a leaf sits under a web view and reaches the
57
- viewport, or reports no frame (`remote-content-boundary`), and routes that app
58
- generation to XCTest, which resolves remote elements (#2484). A zero-area or
59
- off-screen leaf is published: it hosts nothing the capture can miss.
53
+ The reader snapshots one process. Two hosts put another process's UI into that
54
+ tree as an `AXRemoteElement` leaf: a WebKit page — Safari's or a `WKWebView`'s —
55
+ lives in a WebContent process under the web view, and a scene-hosted remote view
56
+ controller, such as a share extension presented over the app, lives in the
57
+ extension's process under a `_UISceneHostingView`. The guest returns the leaf as
58
+ delivered; the host refuses a tree in which such a leaf sits under either host
59
+ and reaches the viewport, or reports no frame (`remote-content-boundary`), and
60
+ routes that app generation to XCTest, which resolves remote elements (#2484). A
61
+ zero-area or off-screen leaf is published: it hosts nothing the capture can miss.
60
62
 
61
63
  ## Bounded depth recovery
62
64
 
@@ -71,6 +71,7 @@ int main(int argc, const char *argv[]) {
71
71
  // took focus from an input that has stopped answering the accessibility query that found it.
72
72
  @property(nonatomic, strong) UILabel *textEntryFocusWitness;
73
73
  @property(nonatomic, assign) NSUInteger textEntryWriteBacks;
74
+ @property(nonatomic, assign) NSUInteger textEntryTotalEdits;
74
75
  @property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
75
76
  @property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
76
77
  @property(nonatomic, assign) NSTimeInterval textEntryBurstStartTime;
@@ -97,6 +98,122 @@ int main(int argc, const char *argv[]) {
97
98
  return [NSString stringWithFormat:@"%lu of 6 digits", (unsigned long)self.text.length];
98
99
  }
99
100
  @end
101
+
102
+ // A field that reformats its own text on every edit into `## ### ## ##` digit groups, the way a
103
+ // digit-grouping formatter in a Flutter or React Native input does. The accessibility value is
104
+ // what the user sees — the formatted string — so it never equals the digits a `fill` sent (#2634).
105
+ // The field owns the delegate it installs, keeping the formatter out of the view controller's
106
+ // own delegate callbacks.
107
+ @interface AgentDeviceDigitGroupingTextField : UITextField <UITextFieldDelegate>
108
+ @end
109
+
110
+ @interface AgentDeviceCurrencyTextField : UITextField <UITextFieldDelegate>
111
+ @property(nonatomic, strong) UILabel *entryStatus;
112
+ @property(nonatomic, assign) NSUInteger insertedDigits;
113
+ @end
114
+
115
+ @interface AgentDeviceSubmitClearingTextField : UITextField <UITextFieldDelegate>
116
+ @property(nonatomic, strong) UILabel *entryStatus;
117
+ @property(nonatomic, assign) NSUInteger submits;
118
+ @end
119
+
120
+ static NSString *AgentDeviceFormatGroupedDigits(NSString *raw) {
121
+ NSMutableString *digits = [NSMutableString string];
122
+ NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
123
+ for (NSString *chunk in [raw componentsSeparatedByCharactersInSet:nonDigits]) {
124
+ [digits appendString:chunk];
125
+ }
126
+ NSMutableArray<NSNumber *> *groupWidths = [NSMutableArray arrayWithArray:@[@2, @3, @2, @2]];
127
+ NSMutableString *formatted = [NSMutableString string];
128
+ NSUInteger cursor = 0;
129
+ for (NSNumber *width in groupWidths) {
130
+ if (cursor >= digits.length) {
131
+ break;
132
+ }
133
+ if (formatted.length > 0) {
134
+ [formatted appendString:@" "];
135
+ }
136
+ NSUInteger end = MIN(cursor + width.unsignedIntegerValue, digits.length);
137
+ [formatted appendString:[digits substringWithRange:NSMakeRange(cursor, end - cursor)]];
138
+ cursor = end;
139
+ }
140
+ // Digits past the last group keep grouping by threes rather than vanishing from the field.
141
+ while (cursor < digits.length) {
142
+ NSUInteger end = MIN(cursor + 3, digits.length);
143
+ [formatted appendString:[NSString stringWithFormat:@" %@", [digits substringWithRange:NSMakeRange(cursor, end - cursor)]]];
144
+ cursor = end;
145
+ }
146
+ return formatted;
147
+ }
148
+
149
+ static NSUInteger AgentDeviceDigitCount(NSString *text, NSUInteger limit) {
150
+ NSUInteger count = 0;
151
+ for (NSUInteger index = 0; index < limit && index < text.length; index++) {
152
+ if ([NSCharacterSet.decimalDigitCharacterSet characterIsMember:[text characterAtIndex:index]]) {
153
+ count += 1;
154
+ }
155
+ }
156
+ return count;
157
+ }
158
+
159
+ @implementation AgentDeviceDigitGroupingTextField
160
+ - (BOOL)textField:(UITextField *)textField
161
+ shouldChangeCharactersInRange:(NSRange)range
162
+ replacementString:(NSString *)string {
163
+ NSString *combined = [textField.text stringByReplacingCharactersInRange:range withString:string];
164
+ NSString *formatted = AgentDeviceFormatGroupedDigits(combined);
165
+ textField.text = formatted;
166
+ // Assigning `text` drops the caret at the end, which a real grouping formatter does not do:
167
+ // a mid-string edit must keep editing where the user put the caret. The digits before the
168
+ // caret survive the reformat unchanged, so the caret belongs before the digit that follows
169
+ // them — past the separators the grouping inserted behind them — and at the end when it
170
+ // followed all of them. Typed digits count as before the caret; a delete does not move it.
171
+ NSUInteger digitsBeforeCaret =
172
+ AgentDeviceDigitCount(textField.text, range.location) + AgentDeviceDigitCount(string, string.length);
173
+ NSUInteger caretOffset = formatted.length;
174
+ NSUInteger seen = 0;
175
+ for (NSUInteger index = 0; index < formatted.length; index++) {
176
+ if (![NSCharacterSet.decimalDigitCharacterSet characterIsMember:[formatted characterAtIndex:index]]) {
177
+ continue;
178
+ }
179
+ seen += 1;
180
+ if (seen == digitsBeforeCaret + 1) {
181
+ caretOffset = index;
182
+ break;
183
+ }
184
+ }
185
+ textField.selectedTextRange = [textField textRangeFromPosition:[textField positionFromPosition:textField.beginningOfDocument offset:caretOffset]
186
+ toPosition:[textField positionFromPosition:textField.beginningOfDocument offset:caretOffset]];
187
+ return NO;
188
+ }
189
+ @end
190
+
191
+ @implementation AgentDeviceCurrencyTextField
192
+ - (BOOL)textField:(UITextField *)textField
193
+ shouldChangeCharactersInRange:(NSRange)range
194
+ replacementString:(NSString *)string {
195
+ NSString *combined = [textField.text stringByReplacingCharactersInRange:range withString:string];
196
+ NSMutableString *digits = [NSMutableString string];
197
+ for (NSString *part in [combined componentsSeparatedByCharactersInSet:NSCharacterSet.decimalDigitCharacterSet.invertedSet]) {
198
+ [digits appendString:part];
199
+ }
200
+ unsigned long long cents = digits.longLongValue;
201
+ textField.text = [NSString stringWithFormat:@"$%llu.%02llu", cents / 100, cents % 100];
202
+ self.insertedDigits += AgentDeviceDigitCount(string, string.length);
203
+ self.entryStatus.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.insertedDigits];
204
+ textField.selectedTextRange = [textField textRangeFromPosition:textField.endOfDocument toPosition:textField.endOfDocument];
205
+ return NO;
206
+ }
207
+ @end
208
+
209
+ @implementation AgentDeviceSubmitClearingTextField
210
+ - (BOOL)textFieldShouldReturn:(UITextField *)textField {
211
+ self.submits += 1;
212
+ self.entryStatus.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.submits];
213
+ textField.text = @"";
214
+ return YES;
215
+ }
216
+ @end
100
217
  #endif
101
218
 
102
219
  @implementation AgentDeviceRunnerViewController
@@ -202,11 +319,12 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
202
319
  - (void)updateTextEntryWriteBackStatus {
203
320
  NSTimeInterval burstSpan = self.textEntryLastEditTime - self.textEntryBurstStartTime;
204
321
  self.textEntryWriteBackStatus.text = [NSString
205
- stringWithFormat:@"write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
322
+ stringWithFormat:@"write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu total-edits=%lu",
206
323
  (unsigned long)self.textEntryWriteBacks,
207
324
  (unsigned long)self.textEntryBurstEdits,
208
325
  (unsigned long)llround(burstSpan * 1000),
209
- (unsigned long)llround(self.textEntryBurstMinGap * 1000)];
326
+ (unsigned long)llround(self.textEntryBurstMinGap * 1000),
327
+ (unsigned long)self.textEntryTotalEdits];
210
328
  }
211
329
 
212
330
  - (void)presentAlertFixtureReplacement:(BOOL)replacement {
@@ -292,6 +410,7 @@ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
292
410
  // typed faster than the app renders loses the characters that arrived while a render was in
293
411
  // flight, and the field settles stable short of the request.
294
412
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-app-owned-value"]) {
413
+ self.textEntryTotalEdits += 1;
295
414
  NSTimeInterval now = NSProcessInfo.processInfo.systemUptime;
296
415
  NSTimeInterval gap = now - self.textEntryLastEditTime;
297
416
  BOOL overtookARender = self.textEntryRenderedValue != nil && gap < self.textEntryAcknowledgeWindowSeconds;
@@ -408,8 +527,28 @@ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
408
527
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-regression"]) {
409
528
  BOOL digitCountValue =
410
529
  [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-digit-count-value"];
411
- UITextField *textField =
412
- digitCountValue ? [[AgentDeviceDigitCountTextField alloc] init] : [[UITextField alloc] init];
530
+ BOOL digitGroupingValue =
531
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-digit-grouping-value"];
532
+ BOOL currencyValue =
533
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-currency-value"];
534
+ BOOL submitClears =
535
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-submit-clears"];
536
+ UITextField *textField;
537
+ if (digitCountValue) {
538
+ textField = [[AgentDeviceDigitCountTextField alloc] init];
539
+ } else if (digitGroupingValue) {
540
+ textField = [[AgentDeviceDigitGroupingTextField alloc] init];
541
+ } else if (currencyValue) {
542
+ textField = [[AgentDeviceCurrencyTextField alloc] init];
543
+ textField.text = @"$0.00";
544
+ } else if (submitClears) {
545
+ textField = [[AgentDeviceSubmitClearingTextField alloc] init];
546
+ } else {
547
+ textField = [[UITextField alloc] init];
548
+ }
549
+ if (digitGroupingValue || currencyValue || submitClears) {
550
+ textField.delegate = (id<UITextFieldDelegate>)textField;
551
+ }
413
552
  if (![NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
414
553
  textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
415
554
  }
@@ -434,6 +573,22 @@ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
434
573
  [textField.widthAnchor constraintEqualToConstant:240],
435
574
  [textField.heightAnchor constraintEqualToConstant:AgentDeviceTextEntryFieldHeight],
436
575
  ]];
576
+ if (currencyValue || submitClears) {
577
+ UILabel *entryStatus = [[UILabel alloc] init];
578
+ entryStatus.accessibilityIdentifier = @"agent-device-text-entry-events";
579
+ entryStatus.text = @"0";
580
+ entryStatus.translatesAutoresizingMaskIntoConstraints = NO;
581
+ [self.view addSubview:entryStatus];
582
+ [NSLayoutConstraint activateConstraints:@[
583
+ [entryStatus.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
584
+ [entryStatus.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:12],
585
+ ]];
586
+ if (currencyValue) {
587
+ ((AgentDeviceCurrencyTextField *)textField).entryStatus = entryStatus;
588
+ } else {
589
+ ((AgentDeviceSubmitClearingTextField *)textField).entryStatus = entryStatus;
590
+ }
591
+ }
437
592
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unqueryable-on-focus"]) {
438
593
  self.textEntryFocusWitness = [[UILabel alloc] init];
439
594
  self.textEntryFocusWitness.accessibilityIdentifier = @"agent-device-text-entry-focus";
@@ -0,0 +1,139 @@
1
+ import Foundation
2
+
3
+
4
+
5
+ enum RunnerHttpRequestRejection: Equatable {
6
+ case malformed(String)
7
+ case headTooLarge
8
+ case unauthorized
9
+ case transferEncodingUnsupported
10
+ case lengthRequired
11
+ case bodyTooLarge
12
+ case methodNotAllowed
13
+ case notFound
14
+ case timedOut
15
+
16
+ var status: Int {
17
+ switch self {
18
+ case .malformed, .transferEncodingUnsupported: return 400
19
+ case .unauthorized: return 401
20
+ case .notFound: return 404
21
+ case .methodNotAllowed: return 405
22
+ case .timedOut: return 408
23
+ case .lengthRequired: return 411
24
+ case .bodyTooLarge: return 413
25
+ case .headTooLarge: return 431
26
+ }
27
+ }
28
+
29
+ var code: String {
30
+ self == .unauthorized ? "UNAUTHORIZED" : "INVALID_ARGS"
31
+ }
32
+
33
+ var message: String {
34
+ switch self {
35
+ case .malformed(let part): return "runner request is malformed: \(part)"
36
+ case .headTooLarge:
37
+ return "runner request head exceeds \(RunnerHttpRequestFraming.maxHeadBytes) bytes"
38
+ case .unauthorized: return "runner request is missing a valid listener token"
39
+ case .transferEncodingUnsupported: return "runner requests must not use Transfer-Encoding"
40
+ case .lengthRequired: return "runner requests must declare one Content-Length"
41
+ case .bodyTooLarge:
42
+ return "runner request body exceeds \(RunnerHttpRequestFraming.maxBodyBytes) bytes"
43
+ case .methodNotAllowed: return "runner accepts only POST"
44
+ case .notFound: return "runner serves only \(RunnerHttpRequestFraming.commandPath)"
45
+ case .timedOut: return "runner request did not arrive before its deadline"
46
+ }
47
+ }
48
+ }
49
+
50
+
51
+ struct RunnerHttpAdmittedHead: Equatable {
52
+ let bodyOffset: Int
53
+ let contentLength: Int
54
+ }
55
+
56
+ enum RunnerHttpHeadVerdict: Equatable {
57
+ case incomplete
58
+ case admitted(RunnerHttpAdmittedHead)
59
+ case rejected(RunnerHttpRequestRejection)
60
+ }
61
+
62
+
63
+
64
+ enum RunnerHttpRequestFraming {
65
+ static let commandPath = "/command"
66
+ static let maxHeadBytes = 16 * 1024
67
+ static let maxBodyBytes = 2 * 1024 * 1024
68
+ private static let headTerminator = Data("\r\n\r\n".utf8)
69
+
70
+
71
+
72
+ static func admitHead(
73
+ _ buffer: Data,
74
+ token: RunnerListenerToken,
75
+ alreadyScanned: Int = 0
76
+ ) -> RunnerHttpHeadVerdict {
77
+ let searchStart = buffer.startIndex + max(0, alreadyScanned - (headTerminator.count - 1))
78
+ guard let terminator = buffer.range(of: headTerminator, in: searchStart..<buffer.endIndex) else {
79
+ return buffer.count > maxHeadBytes ? .rejected(.headTooLarge) : .incomplete
80
+ }
81
+ if terminator.lowerBound - buffer.startIndex > maxHeadBytes { return .rejected(.headTooLarge) }
82
+ guard
83
+ let head = String(data: buffer[buffer.startIndex..<terminator.lowerBound], encoding: .utf8)
84
+ else {
85
+ return .rejected(.malformed("head is not UTF-8"))
86
+ }
87
+ var lines = head.components(separatedBy: "\r\n")
88
+ let requestLine = lines.removeFirst().split(separator: " ", omittingEmptySubsequences: false)
89
+ guard requestLine.count == 3, requestLine[2] == "HTTP/1.1" || requestLine[2] == "HTTP/1.0" else {
90
+ return .rejected(.malformed("request line"))
91
+ }
92
+ var headers: [String: [String]] = [:]
93
+ for line in lines {
94
+ guard let colon = line.firstIndex(of: ":") else {
95
+ return .rejected(.malformed("header line"))
96
+ }
97
+ let name = line[line.startIndex..<colon]
98
+ guard !name.isEmpty, name.allSatisfy(isHeaderNameCharacter) else {
99
+ return .rejected(.malformed("header name"))
100
+ }
101
+ let value = line[line.index(after: colon)...].trimmingCharacters(in: CharacterSet(charactersIn: " \t"))
102
+ headers[name.lowercased(), default: []].append(value)
103
+ }
104
+
105
+ guard let presented = headers[RunnerListenerToken.requestHeaderName], presented.count == 1,
106
+ token.matches(presented[0])
107
+ else {
108
+ return .rejected(.unauthorized)
109
+ }
110
+ guard requestLine[0] == "POST" else { return .rejected(.methodNotAllowed) }
111
+ guard requestLine[1] == Substring(commandPath) else { return .rejected(.notFound) }
112
+ if headers["transfer-encoding"] != nil { return .rejected(.transferEncodingUnsupported) }
113
+ guard let lengths = headers["content-length"], lengths.count == 1 else {
114
+ return .rejected(.lengthRequired)
115
+ }
116
+ let declared = lengths[0]
117
+ guard !declared.isEmpty, declared.utf8.allSatisfy({ $0 >= 0x30 && $0 <= 0x39 }) else {
118
+ return .rejected(.malformed("Content-Length"))
119
+ }
120
+ guard declared.utf8.count <= 10, let contentLength = Int(declared), contentLength <= maxBodyBytes
121
+ else {
122
+ return .rejected(.bodyTooLarge)
123
+ }
124
+ return .admitted(
125
+ RunnerHttpAdmittedHead(
126
+ bodyOffset: terminator.upperBound - buffer.startIndex,
127
+ contentLength: contentLength
128
+ )
129
+ )
130
+ }
131
+
132
+ private static func isHeaderNameCharacter(_ character: Character) -> Bool {
133
+ guard let ascii = character.asciiValue else { return false }
134
+ switch ascii {
135
+ case 0x30...0x39, 0x41...0x5A, 0x61...0x7A: return true
136
+ default: return "!#$%&'*+-.^_`|~".utf8.contains(ascii)
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,41 @@
1
+ import Network
2
+
3
+
4
+
5
+
6
+
7
+
8
+ enum RunnerListenerPeer: Equatable {
9
+
10
+
11
+ case loopback
12
+ case tunnel
13
+ case directNetwork
14
+
15
+ static func of(_ connection: NWConnection) -> RunnerListenerPeer {
16
+ classify(
17
+ isLoopbackAddress: isLoopback(connection.endpoint),
18
+ interfaceTypes: connection.currentPath?.availableInterfaces.map(\.type) ?? []
19
+ )
20
+ }
21
+
22
+ static func classify(
23
+ isLoopbackAddress: Bool,
24
+ interfaceTypes: [NWInterface.InterfaceType]
25
+ ) -> RunnerListenerPeer {
26
+ if isLoopbackAddress { return .loopback }
27
+
28
+
29
+ if !interfaceTypes.isEmpty && interfaceTypes.allSatisfy({ $0 == .other }) { return .tunnel }
30
+ return .directNetwork
31
+ }
32
+
33
+ static func isLoopback(_ endpoint: NWEndpoint) -> Bool {
34
+ guard case .hostPort(let host, _) = endpoint else { return false }
35
+ switch host {
36
+ case .ipv4(let address): return address.isLoopback
37
+ case .ipv6(let address): return address.isLoopback || (address.asIPv4?.isLoopback ?? false)
38
+ default: return false
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,25 @@
1
+ import Foundation
2
+
3
+
4
+ struct RunnerListenerToken {
5
+ static let requestHeaderName = "x-agent-device-runner-token"
6
+ static let minimumByteCount = 32
7
+
8
+ private let expected: [UInt8]
9
+
10
+ init?(_ token: String?) {
11
+ guard let token, token.utf8.count >= Self.minimumByteCount else { return nil }
12
+ expected = Array(token.utf8)
13
+ }
14
+
15
+
16
+ func matches(_ presented: String) -> Bool {
17
+ let candidate = Array(presented.utf8)
18
+ var difference = candidate.count ^ expected.count
19
+ for index in 0..<expected.count {
20
+ let byte = index < candidate.count ? candidate[index] : 0
21
+ difference |= Int(byte ^ expected[index])
22
+ }
23
+ return difference == 0
24
+ }
25
+ }