mobile-debug-mcp 0.33.0 → 0.35.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 (448) hide show
  1. package/.agents/skills/mcp-builder/SKILL.md +44 -64
  2. package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
  3. package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
  4. package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
  5. package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
  6. package/.agents/skills/test-authoring/SKILL.md +7 -6
  7. package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
  8. package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
  9. package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
  10. package/.github/copilot-instructions.md +14 -36
  11. package/.github/workflows/ci.yml +23 -2
  12. package/AGENTS.md +12 -3
  13. package/CONTRIBUTING.md +79 -0
  14. package/README.md +158 -124
  15. package/agent-plugin/.claude-plugin/plugin.json +2 -2
  16. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  17. package/agent-plugin/.cursor-plugin/plugin.json +2 -2
  18. package/agent-plugin/.mcp.json +1 -1
  19. package/agent-plugin/README.md +20 -6
  20. package/agent-plugin/mcp.json +1 -1
  21. package/agent-plugin/plugin.json +1 -1
  22. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -4
  23. package/dist/contracts/execution-controls.js +24 -0
  24. package/dist/contracts/selector.js +8 -0
  25. package/dist/contracts/target.js +15 -0
  26. package/dist/contracts/ui.js +1 -0
  27. package/dist/debugger/android.js +79 -0
  28. package/dist/debugger/diagnostics.js +54 -0
  29. package/dist/debugger/guard.js +30 -0
  30. package/dist/debugger/protocol.js +33 -0
  31. package/dist/debugger/sessions.js +304 -0
  32. package/dist/debugger/worker.js +176 -0
  33. package/dist/interact/adjustment/adjustment-context.js +41 -0
  34. package/dist/interact/adjustment/adjustment-gestures.js +55 -0
  35. package/dist/interact/adjustment/adjustment-plan.js +18 -0
  36. package/dist/interact/adjustment/adjustment-resolution.js +127 -0
  37. package/dist/interact/adjustment/adjustment-result.js +19 -0
  38. package/dist/interact/adjustment/adjustment-semantic.js +64 -0
  39. package/dist/interact/adjustment/adjustment-verification.js +35 -0
  40. package/dist/interact/adjustment/adjustment.js +52 -0
  41. package/dist/interact/adjustment/control-geometry.js +87 -0
  42. package/dist/interact/adjustment/control-values.js +136 -0
  43. package/dist/interact/adjustment/slider-labels.js +84 -0
  44. package/dist/interact/assertions/screen-assertion.js +37 -0
  45. package/dist/interact/assertions/state-assertion-context.js +35 -0
  46. package/dist/interact/assertions/state-assertion.js +76 -0
  47. package/dist/interact/assertions/state-comparison.js +141 -0
  48. package/dist/interact/assertions/visibility-assertion.js +34 -0
  49. package/dist/interact/conditional-action.js +12 -7
  50. package/dist/interact/index.js +65 -3074
  51. package/dist/interact/input-service.js +1 -0
  52. package/dist/interact/interaction-trace.js +21 -0
  53. package/dist/interact/model.js +1 -0
  54. package/dist/interact/observation.js +1 -0
  55. package/dist/interact/targeting/candidate-ancestor.js +58 -0
  56. package/dist/interact/targeting/candidate-result.js +23 -0
  57. package/dist/interact/targeting/candidate-scoring.js +50 -0
  58. package/dist/interact/targeting/element-helpers.js +216 -0
  59. package/dist/interact/targeting/find-element.js +65 -0
  60. package/dist/interact/targeting/nearby-control.js +56 -0
  61. package/dist/interact/targeting/resolved-elements.js +21 -0
  62. package/dist/interact/targeting/tap-target.js +17 -0
  63. package/dist/interact/targeting/target-scope.js +104 -0
  64. package/dist/interact/waits/legacy-log-wait.js +38 -0
  65. package/dist/interact/waits/legacy-wait-observations.js +103 -0
  66. package/dist/interact/waits/legacy-wait-state.js +34 -0
  67. package/dist/interact/waits/legacy-wait.js +48 -0
  68. package/dist/interact/waits/screen-wait.js +49 -0
  69. package/dist/interact/waits/ui-change-helpers.js +157 -0
  70. package/dist/interact/waits/ui-change-state.js +18 -0
  71. package/dist/interact/waits/ui-change-wait.js +77 -0
  72. package/dist/interact/waits/ui-wait-condition.js +56 -0
  73. package/dist/interact/waits/ui-wait-state.js +38 -0
  74. package/dist/interact/waits/ui-wait.js +106 -0
  75. package/dist/manage/android.js +55 -51
  76. package/dist/manage/index.js +38 -75
  77. package/dist/manage/ios/build-types.js +1 -0
  78. package/dist/manage/ios/build.js +210 -0
  79. package/dist/manage/ios/project-discovery.js +29 -0
  80. package/dist/manage/ios/scheme.js +18 -0
  81. package/dist/manage/ios.js +42 -256
  82. package/dist/observe/android/context.js +1 -0
  83. package/dist/observe/android/current-screen.js +55 -0
  84. package/dist/observe/android/fingerprint.js +13 -0
  85. package/dist/observe/android/log-stream.js +132 -0
  86. package/dist/observe/android/logs.js +107 -0
  87. package/dist/observe/android/screenshot.js +70 -0
  88. package/dist/observe/android/ui-tree.js +107 -0
  89. package/dist/observe/android.js +16 -520
  90. package/dist/observe/evidence.js +122 -76
  91. package/dist/observe/index.js +16 -263
  92. package/dist/observe/ios/context.js +16 -0
  93. package/dist/observe/ios/fingerprint.js +12 -0
  94. package/dist/observe/ios/log-stream.js +105 -0
  95. package/dist/observe/ios/logs.js +168 -0
  96. package/dist/observe/ios/screenshot.js +22 -0
  97. package/dist/observe/ios/ui-elements.js +241 -0
  98. package/dist/observe/ios/ui-tree.js +137 -0
  99. package/dist/observe/ios.js +17 -715
  100. package/dist/observe/logs/model.js +1 -0
  101. package/dist/{network → observe/network}/index.js +2 -2
  102. package/dist/observe/readiness-service.js +13 -2
  103. package/dist/observe/readiness.js +44 -18
  104. package/dist/observe/screenshot-encoding.js +41 -0
  105. package/dist/observe/snapshot-collection.js +79 -0
  106. package/dist/observe/snapshot-logs.js +30 -0
  107. package/dist/observe/snapshot-metadata.js +43 -31
  108. package/dist/observe/snapshot-semantic.js +115 -0
  109. package/dist/observe/system-trace-android.js +135 -0
  110. package/dist/observe/system-trace.js +294 -0
  111. package/dist/runtime/action-results.js +270 -0
  112. package/dist/runtime/clock.js +4 -0
  113. package/dist/server/common.js +1 -256
  114. package/dist/server/definitions/assert.js +162 -0
  115. package/dist/server/definitions/diagnostic.js +118 -0
  116. package/dist/server/definitions/input.js +274 -0
  117. package/dist/server/definitions/manage.js +194 -0
  118. package/dist/server/definitions/observe.js +149 -0
  119. package/dist/server/definitions/target.js +138 -0
  120. package/dist/server/definitions/wait.js +105 -0
  121. package/dist/server/handlers/action-execution.js +163 -0
  122. package/dist/server/handlers/correlation.js +23 -0
  123. package/dist/server/handlers/debugger-handlers.js +48 -0
  124. package/dist/server/handlers/debugger-response.js +9 -0
  125. package/dist/server/handlers/diagnostic.js +69 -0
  126. package/dist/server/handlers/handler-types.js +1 -0
  127. package/dist/server/handlers/input.js +154 -0
  128. package/dist/server/handlers/interact.js +113 -0
  129. package/dist/server/handlers/journey-assertions.js +35 -0
  130. package/dist/server/handlers/journey.js +147 -0
  131. package/dist/server/handlers/manage.js +119 -0
  132. package/dist/server/handlers/observe.js +95 -0
  133. package/dist/server/handlers/payload.js +4 -0
  134. package/dist/server/handlers/tool-handlers.js +160 -0
  135. package/dist/server/schemas/condition-schema.js +2 -0
  136. package/dist/server/schemas/debugger-schema.js +47 -0
  137. package/dist/server/{journey-schema.js → schemas/journey-schema.js} +8 -22
  138. package/dist/server/schemas/system-trace-schema.js +17 -0
  139. package/dist/server/tool-definitions.js +62 -1134
  140. package/dist/server-core.js +4 -4
  141. package/dist/server.js +34 -0
  142. package/dist/system/android-status.js +101 -0
  143. package/dist/system/android.js +37 -27
  144. package/dist/system/index.js +13 -438
  145. package/dist/system/ios-status.js +132 -0
  146. package/dist/system/ios.js +11 -8
  147. package/dist/system/model.js +1 -0
  148. package/dist/system/probes.js +105 -0
  149. package/dist/system/status-summary.js +43 -0
  150. package/dist/system/unsupported-status.js +52 -0
  151. package/dist/utils/android/commands.js +89 -0
  152. package/dist/utils/android/gradle.js +139 -0
  153. package/dist/utils/android/logs.js +103 -0
  154. package/dist/utils/android/ui-tree.js +233 -0
  155. package/dist/utils/android/utils.js +6 -564
  156. package/dist/utils/cli/idb/idb-helper.js +4 -6
  157. package/dist/utils/cli/idb/install-idb.js +13 -9
  158. package/dist/utils/cli/ios/preflight-ios.js +42 -38
  159. package/dist/utils/exec.js +2 -0
  160. package/dist/utils/ios/utils.js +45 -46
  161. package/dist/utils/java.js +75 -57
  162. package/dist/utils/operation-budget.js +2 -0
  163. package/dist/utils/resolve-device.js +6 -2
  164. package/dist/utils/ui/index.js +26 -26
  165. package/docs/CHANGELOG.md +48 -0
  166. package/docs/README.md +38 -0
  167. package/docs/benchmarks/README.md +18 -0
  168. package/docs/benchmarks/interaction-latency/README.md +14 -0
  169. package/docs/benchmarks/interaction-latency/final.md +4 -1
  170. package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
  171. package/docs/benchmarks/ui-tree-performance/README.md +24 -16
  172. package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
  173. package/docs/code-structure.md +118 -0
  174. package/docs/specs/001-state-verification/spec.md +16 -14
  175. package/docs/specs/002-richer-element-identity/spec.md +12 -10
  176. package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
  177. package/docs/specs/004-action-verification-routing/spec.md +3 -3
  178. package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
  179. package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
  180. package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
  181. package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
  182. package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
  183. package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
  184. package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
  185. package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
  186. package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
  187. package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
  188. package/docs/specs/014-actionability-resolution/spec.md +20 -18
  189. package/docs/specs/015-adjustable-control-support/spec.md +17 -15
  190. package/docs/specs/016-environment-tools/spec.md +20 -20
  191. package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
  192. package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
  193. package/docs/specs/020-conditional-actions/intent.md +5 -3
  194. package/docs/specs/020-conditional-actions/plan.md +8 -9
  195. package/docs/specs/020-conditional-actions/spec.md +4 -2
  196. package/docs/specs/021-app-readiness-signals/intent.md +4 -2
  197. package/docs/specs/021-app-readiness-signals/plan.md +7 -6
  198. package/docs/specs/021-app-readiness-signals/spec.md +3 -1
  199. package/docs/specs/022-correlated-evidence/intent.md +4 -4
  200. package/docs/specs/022-correlated-evidence/plan.md +10 -11
  201. package/docs/specs/022-correlated-evidence/spec.md +3 -1
  202. package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
  203. package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
  204. package/docs/specs/023-android-system-traces/intent.md +41 -0
  205. package/docs/specs/023-android-system-traces/plan.md +115 -0
  206. package/docs/specs/023-android-system-traces/spec.md +122 -0
  207. package/docs/specs/024-android-heap-dump-capture/intent.md +41 -0
  208. package/docs/specs/025-android-runtime-debugger/intent.md +41 -0
  209. package/docs/specs/025-android-runtime-debugger/plan.md +106 -0
  210. package/docs/specs/025-android-runtime-debugger/spec.md +183 -0
  211. package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
  212. package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
  213. package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
  214. package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
  215. package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
  216. package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
  217. package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
  218. package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
  219. package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
  220. package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
  221. package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
  222. package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
  223. package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
  224. package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
  225. package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
  226. package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
  227. package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
  228. package/docs/specs/030-android-coroutine-validation/report.md +240 -0
  229. package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
  230. package/docs/tools/TOOLS.md +31 -16
  231. package/docs/tools/{evidence.md → correlated-evidence.md} +14 -3
  232. package/docs/tools/debugger.md +180 -0
  233. package/docs/tools/interact.md +42 -20
  234. package/docs/tools/manage.md +25 -44
  235. package/docs/tools/observe.md +64 -25
  236. package/docs/tools/system-traces.md +134 -0
  237. package/docs/tools/system.md +12 -5
  238. package/package.json +11 -8
  239. package/scripts/architecture-graph.mjs +69 -0
  240. package/scripts/check-architecture.mjs +47 -0
  241. package/scripts/code-quality-baseline.json +1 -0
  242. package/scripts/code-quality.mjs +161 -0
  243. package/src/contracts/execution-controls.ts +26 -0
  244. package/src/contracts/selector.ts +17 -0
  245. package/src/contracts/target.ts +19 -0
  246. package/src/contracts/ui.ts +94 -0
  247. package/src/debugger/android.ts +66 -0
  248. package/src/debugger/diagnostics.ts +43 -0
  249. package/src/debugger/guard.ts +31 -0
  250. package/src/debugger/java/DebuggerWorker.java +166 -0
  251. package/src/debugger/protocol.ts +37 -0
  252. package/src/debugger/sessions.ts +327 -0
  253. package/src/debugger/worker.ts +135 -0
  254. package/src/interact/adjustment/adjustment-context.ts +83 -0
  255. package/src/interact/adjustment/adjustment-gestures.ts +48 -0
  256. package/src/interact/adjustment/adjustment-plan.ts +23 -0
  257. package/src/interact/adjustment/adjustment-resolution.ts +104 -0
  258. package/src/interact/adjustment/adjustment-result.ts +34 -0
  259. package/src/interact/adjustment/adjustment-semantic.ts +53 -0
  260. package/src/interact/adjustment/adjustment-verification.ts +60 -0
  261. package/src/interact/adjustment/adjustment.ts +49 -0
  262. package/src/interact/adjustment/control-geometry.ts +120 -0
  263. package/src/interact/adjustment/control-values.ts +141 -0
  264. package/src/interact/adjustment/slider-labels.ts +84 -0
  265. package/src/interact/assertions/screen-assertion.ts +39 -0
  266. package/src/interact/assertions/state-assertion-context.ts +48 -0
  267. package/src/interact/assertions/state-assertion.ts +64 -0
  268. package/src/interact/assertions/state-comparison.ts +168 -0
  269. package/src/interact/assertions/visibility-assertion.ts +41 -0
  270. package/src/interact/conditional-action.ts +18 -12
  271. package/src/interact/index.ts +76 -3491
  272. package/src/interact/input-service.ts +11 -0
  273. package/src/interact/interaction-trace.ts +36 -0
  274. package/src/interact/model.ts +109 -0
  275. package/src/interact/observation.ts +7 -0
  276. package/src/interact/targeting/candidate-ancestor.ts +49 -0
  277. package/src/interact/targeting/candidate-result.ts +24 -0
  278. package/src/interact/targeting/candidate-scoring.ts +42 -0
  279. package/src/interact/targeting/element-helpers.ts +243 -0
  280. package/src/interact/targeting/find-element.ts +47 -0
  281. package/src/interact/targeting/nearby-control.ts +46 -0
  282. package/src/interact/targeting/resolved-elements.ts +20 -0
  283. package/src/interact/targeting/tap-target.ts +22 -0
  284. package/src/interact/targeting/target-scope.ts +121 -0
  285. package/src/interact/waits/legacy-log-wait.ts +30 -0
  286. package/src/interact/waits/legacy-wait-observations.ts +55 -0
  287. package/src/interact/waits/legacy-wait-state.ts +48 -0
  288. package/src/interact/waits/legacy-wait.ts +34 -0
  289. package/src/interact/waits/screen-wait.ts +37 -0
  290. package/src/interact/waits/ui-change-helpers.ts +184 -0
  291. package/src/interact/waits/ui-change-state.ts +26 -0
  292. package/src/interact/waits/ui-change-wait.ts +59 -0
  293. package/src/interact/waits/ui-wait-condition.ts +52 -0
  294. package/src/interact/waits/ui-wait-state.ts +55 -0
  295. package/src/interact/waits/ui-wait.ts +84 -0
  296. package/src/manage/android.ts +56 -50
  297. package/src/manage/index.ts +63 -93
  298. package/src/manage/ios/build-types.ts +14 -0
  299. package/src/manage/ios/build.ts +230 -0
  300. package/src/manage/ios/project-discovery.ts +26 -0
  301. package/src/manage/ios/scheme.ts +16 -0
  302. package/src/manage/ios.ts +65 -298
  303. package/src/observe/android/context.ts +1 -0
  304. package/src/observe/android/current-screen.ts +63 -0
  305. package/src/observe/android/fingerprint.ts +13 -0
  306. package/src/observe/android/log-stream.ts +127 -0
  307. package/src/observe/android/logs.ts +110 -0
  308. package/src/observe/android/screenshot.ts +79 -0
  309. package/src/observe/android/ui-tree.ts +113 -0
  310. package/src/observe/android.ts +17 -531
  311. package/src/observe/evidence.ts +130 -71
  312. package/src/observe/index.ts +24 -286
  313. package/src/observe/ios/context.ts +22 -0
  314. package/src/observe/ios/fingerprint.ts +12 -0
  315. package/src/observe/ios/log-stream.ts +103 -0
  316. package/src/observe/ios/logs.ts +179 -0
  317. package/src/observe/ios/screenshot.ts +26 -0
  318. package/src/observe/ios/ui-elements.ts +266 -0
  319. package/src/observe/ios/ui-tree.ts +143 -0
  320. package/src/observe/ios.ts +18 -749
  321. package/src/observe/logs/model.ts +11 -0
  322. package/src/{network → observe/network}/index.ts +2 -2
  323. package/src/observe/readiness-service.ts +10 -3
  324. package/src/observe/readiness.ts +49 -22
  325. package/src/observe/screenshot-encoding.ts +32 -0
  326. package/src/observe/snapshot-collection.ts +69 -0
  327. package/src/observe/snapshot-logs.ts +26 -0
  328. package/src/observe/snapshot-metadata.ts +51 -31
  329. package/src/observe/snapshot-semantic.ts +153 -0
  330. package/src/observe/system-trace-android.ts +133 -0
  331. package/src/observe/system-trace.ts +227 -0
  332. package/src/runtime/action-results.ts +376 -0
  333. package/src/runtime/clock.ts +9 -0
  334. package/src/server/common.ts +9 -359
  335. package/src/server/definitions/assert.ts +164 -0
  336. package/src/server/definitions/diagnostic.ts +120 -0
  337. package/src/server/definitions/input.ts +276 -0
  338. package/src/server/definitions/manage.ts +196 -0
  339. package/src/server/definitions/observe.ts +151 -0
  340. package/src/server/definitions/target.ts +140 -0
  341. package/src/server/definitions/wait.ts +107 -0
  342. package/src/server/handlers/action-execution.ts +164 -0
  343. package/src/server/handlers/correlation.ts +20 -0
  344. package/src/server/handlers/debugger-handlers.ts +39 -0
  345. package/src/server/handlers/debugger-response.ts +7 -0
  346. package/src/server/handlers/diagnostic.ts +79 -0
  347. package/src/server/handlers/handler-types.ts +7 -0
  348. package/src/server/handlers/input.ts +173 -0
  349. package/src/server/handlers/interact.ts +135 -0
  350. package/src/server/handlers/journey-assertions.ts +39 -0
  351. package/src/server/handlers/journey.ts +132 -0
  352. package/src/server/handlers/manage.ts +141 -0
  353. package/src/server/handlers/observe.ts +116 -0
  354. package/src/server/handlers/payload.ts +36 -0
  355. package/src/server/handlers/tool-handlers.ts +159 -0
  356. package/src/server/schemas/condition-schema.ts +2 -0
  357. package/src/server/schemas/debugger-schema.ts +43 -0
  358. package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +8 -22
  359. package/src/server/schemas/system-trace-schema.ts +17 -0
  360. package/src/server/tool-definitions.ts +69 -1140
  361. package/src/server-core.ts +4 -4
  362. package/src/server.ts +25 -1
  363. package/src/system/android-status.ts +124 -0
  364. package/src/system/android.ts +33 -21
  365. package/src/system/index.ts +21 -523
  366. package/src/system/ios-status.ts +147 -0
  367. package/src/system/ios.ts +14 -10
  368. package/src/system/model.ts +41 -0
  369. package/src/system/probes.ts +114 -0
  370. package/src/system/status-summary.ts +47 -0
  371. package/src/system/unsupported-status.ts +55 -0
  372. package/src/types.ts +16 -106
  373. package/src/utils/android/commands.ts +81 -0
  374. package/src/utils/android/gradle.ts +132 -0
  375. package/src/utils/android/logs.ts +103 -0
  376. package/src/utils/android/ui-tree.ts +239 -0
  377. package/src/utils/android/utils.ts +16 -556
  378. package/src/utils/cli/idb/check-idb.ts +4 -4
  379. package/src/utils/cli/idb/idb-helper.ts +6 -8
  380. package/src/utils/cli/idb/install-idb.ts +15 -9
  381. package/src/utils/cli/ios/preflight-ios.ts +39 -31
  382. package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
  383. package/src/utils/exec.ts +2 -0
  384. package/src/utils/ios/utils.ts +57 -61
  385. package/src/utils/java.ts +71 -51
  386. package/src/utils/operation-budget.ts +2 -0
  387. package/src/utils/resolve-device.ts +11 -6
  388. package/src/utils/ui/index.ts +28 -28
  389. package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
  390. package/test/device/automated/debugger/coroutine-support.ts +140 -0
  391. package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
  392. package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
  393. package/test/device/automated/debugger/runtime.android.test.ts +219 -0
  394. package/test/device/automated/interact/interaction.android.test.ts +56 -0
  395. package/test/device/automated/observe/system-trace.smoke.ts +104 -0
  396. package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
  397. package/test/fixtures/contracts/tool-definitions.json +3536 -0
  398. package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
  399. package/test/fixtures/coroutine-app/README.md +48 -0
  400. package/test/fixtures/coroutine-app/build.ts +35 -0
  401. package/test/fixtures/debugger-app/MainActivity.java +67 -0
  402. package/test/fixtures/debugger-app/MainActivity.kt +32 -0
  403. package/test/fixtures/debugger-app/NoLocals.java +7 -0
  404. package/test/fixtures/debugger-app/README.md +65 -0
  405. package/test/fixtures/debugger-app/build.ts +39 -0
  406. package/test/fixtures/latency-journey-app/README.md +2 -0
  407. package/test/fixtures/readiness-app/README.md +2 -0
  408. package/test/helpers/perfetto.ts +59 -0
  409. package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
  410. package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
  411. package/test/unit/debugger/diagnostics.test.ts +80 -0
  412. package/test/unit/debugger/guards.test.ts +50 -0
  413. package/test/unit/debugger/native.test.ts +33 -0
  414. package/test/unit/debugger/preparation.test.ts +32 -0
  415. package/test/unit/debugger/protocol.test.ts +15 -0
  416. package/test/unit/debugger/sessions.test.ts +83 -0
  417. package/test/unit/interact/adjustment_service.test.ts +42 -0
  418. package/test/unit/interact/adjustment_verification.test.ts +35 -0
  419. package/test/unit/interact/clock_dependencies.test.ts +63 -0
  420. package/test/unit/interact/element_helpers.test.ts +19 -0
  421. package/test/unit/interact/legacy_wait_service.test.ts +17 -0
  422. package/test/unit/interact/state_comparison.test.ts +20 -0
  423. package/test/unit/interact/target_scope.test.ts +35 -0
  424. package/test/unit/network/get_network_activity.test.ts +1 -1
  425. package/test/unit/observe/readiness.test.ts +3 -1
  426. package/test/unit/observe/screenshot-encoding.test.ts +30 -0
  427. package/test/unit/observe/snapshot-logs.test.ts +14 -0
  428. package/test/unit/observe/system-trace-android.test.ts +52 -0
  429. package/test/unit/observe/system-trace.test.ts +166 -0
  430. package/test/unit/quality/architecture.test.ts +60 -0
  431. package/test/unit/quality/code-quality.test.ts +58 -0
  432. package/test/unit/quality/interaction_boundaries.test.ts +24 -0
  433. package/test/unit/server/conditional_actions.test.ts +1 -1
  434. package/test/unit/server/conditional_schema.test.ts +2 -2
  435. package/test/unit/server/debugger-response.test.ts +63 -0
  436. package/test/unit/server/system-trace.test.ts +64 -0
  437. package/test/unit/server/tool_definitions_parity.test.ts +8 -0
  438. package/dist/server/tool-handlers.js +0 -893
  439. package/docs/ROADMAP.md +0 -646
  440. package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
  441. package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
  442. package/docs/specs/baseline-spec-v0.md +0 -312
  443. package/docs/specs/mcp-tooling-spec-v1.md +0 -446
  444. package/src/server/tool-handlers.ts +0 -923
  445. /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
  446. /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
  447. /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
  448. /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
@@ -0,0 +1,163 @@
1
+ import { payloadOf } from './payload.js';
2
+ import { correlatedHandler } from './correlation.js';
3
+ import { interactionControls } from '../../contracts/execution-controls.js';
4
+ import { currentEvidenceActionId, markEvidenceVerification } from '../../observe/evidence.js';
5
+ import { measure, withTiming } from '../../utils/timing.js';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { runWithBudget } from '../../utils/operation-budget.js';
8
+ import { validateExpectedState } from '../schemas/condition-schema.js';
9
+ import { conditionFailureCode, createConditionalAction, withConditionalAction } from '../../interact/conditional-action.js';
10
+ import { resolveTargetDevice, withResolvedDevice } from '../../utils/resolve-device.js';
11
+ import { ToolsObserve } from '../../observe/index.js';
12
+ import { recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js';
13
+ import { buildActionExecutionResult, wrapResponse, wrapToolError } from '../common.js';
14
+ export function compactAction(result, verificationMode, started = true, stateDelta, totalMs = 0) {
15
+ return {
16
+ action_id: result.action_id,
17
+ action_type: result.action_type,
18
+ success: !!result.success,
19
+ lifecycle_state: result.success ? (verificationMode === 'full' ? result.lifecycle_state : 'pending_verification') : 'failed',
20
+ ...(result.failure_code ? { failure_code: result.failure_code } : {}),
21
+ ...(typeof result.retryable === 'boolean' ? { retryable: result.retryable } : {}),
22
+ delivery_status: result.success ? 'delivered' : (started ? 'unknown' : 'not_delivered'),
23
+ dispatch_started: started,
24
+ verification: { mode: verificationMode, status: result.success ? (verificationMode === 'none' ? 'not_requested' : verificationMode === 'light' ? 'observed' : (result.lifecycle_state === 'verified' ? 'verified' : 'observed')) : 'failed' },
25
+ ...(stateDelta ? { state_delta: stateDelta } : {}),
26
+ timing: { total_ms: totalMs }
27
+ };
28
+ }
29
+ function createAction(type, args, timing) {
30
+ const started = performance.now();
31
+ const controls = interactionControls(args);
32
+ const expected = validateExpectedState(type, args);
33
+ const condition = expected ? createConditionalAction(args.platform, args.deviceId, expected) : undefined;
34
+ const budgetMs = controls.actionTimeoutMs ?? ({ start_app: 10000, restart_app: 15000, scroll_to_element: 10000 }[type] ?? 5000);
35
+ const unbounded = args._journey !== true && controls.verificationMode === 'full' && controls.responseMode === 'debug' && controls.actionTimeoutMs === undefined;
36
+ return {
37
+ type, args, timing, started, controls, condition, budgetMs, unbounded,
38
+ result: {}, dispatchStarted: false, blocked: false,
39
+ delta: undefined, observationAvailable: false
40
+ };
41
+ }
42
+ async function dispatchAction(s, execute) {
43
+ const { type, args, controls, condition, budgetMs } = s;
44
+ const dispatchWithBudget = (timeoutMs) => runWithBudget(timeoutMs, async (budget) => {
45
+ try {
46
+ if (!condition)
47
+ return payloadOf(await execute(args));
48
+ return await withConditionalAction(condition, async () => {
49
+ const device = await resolveTargetDevice({ platform: condition.platform, deviceId: condition.deviceId });
50
+ return withResolvedDevice(device, async () => payloadOf(await execute(args)));
51
+ });
52
+ }
53
+ finally {
54
+ s.dispatchStarted = budget.dispatchStarted;
55
+ }
56
+ });
57
+ try {
58
+ // Omitted controls retain the existing adapter budgets.
59
+ s.result = s.unbounded
60
+ ? await dispatchWithBudget(Infinity) : await dispatchWithBudget(budgetMs);
61
+ if (s.result.success)
62
+ s.dispatchStarted = true;
63
+ }
64
+ catch (error) {
65
+ if (!condition && controls.verificationMode === 'full' && controls.responseMode === 'debug' && controls.actionTimeoutMs === undefined && !/TIMEOUT|timed out/i.test(String(error)))
66
+ return wrapToolError(type, error);
67
+ s.result = { success: false, failure_code: /TIMEOUT|timed out/i.test(String(error)) ? 'ACTION_TIMEOUT' : 'UNKNOWN', error: String(error) };
68
+ }
69
+ }
70
+ function rejectCondition(s) {
71
+ const { condition, type } = s;
72
+ s.blocked = !!condition && ['mismatch', 'unavailable'].includes(condition.result.status);
73
+ if (s.blocked && condition) {
74
+ s.dispatchStarted = false;
75
+ s.result = buildActionExecutionResult({
76
+ actionType: type, sourceModule: 'server', device: condition.result.observed?.device,
77
+ selector: s.result?.target?.selector ?? null, resolved: s.result?.target?.resolved,
78
+ success: false, uiFingerprintBefore: s.result?.ui_fingerprint_before ?? null, uiFingerprintAfter: null,
79
+ failure: { failureCode: conditionFailureCode(condition), retryable: false },
80
+ traceSteps: [{ stage: 'verify', timestamp: Date.now(), result: 'failure', attempt_index: 0, metadata: { phase: 'precondition', dispatch_started: false } }]
81
+ });
82
+ }
83
+ }
84
+ function normalizeAction(s) {
85
+ const { type, args, timing, controls } = s;
86
+ rejectCondition(s);
87
+ if (s.result.failure_code === 'TIMEOUT' || /ACTION_TIMEOUT|timeout/i.test(String(s.result.error ?? s.result.reason ?? s.result.details?.error ?? '')))
88
+ s.result.failure_code = 'ACTION_TIMEOUT';
89
+ if (s.result.failure_code === 'ACTION_TIMEOUT')
90
+ s.result.retryable = !s.dispatchStarted;
91
+ if (!s.result.success && typeof s.result.retryable !== 'boolean')
92
+ s.result.retryable = !s.dispatchStarted;
93
+ s.result.action_id ??= currentEvidenceActionId() ?? randomUUID();
94
+ s.result.action_type ??= type;
95
+ recordObservationEvent({
96
+ step_id: args._stepId,
97
+ action_type: type,
98
+ dispatch_ms: timing.dispatch_ms,
99
+ verification_mode: controls.verificationMode,
100
+ dispatch_started: s.dispatchStarted
101
+ });
102
+ }
103
+ async function verifyAction(s) {
104
+ const { args, controls } = s;
105
+ if (s.result.success && controls.verificationMode === 'light') {
106
+ const verificationStarted = performance.now();
107
+ const tree = await withObservationContext({ stepId: args._stepId, purpose: 'verification' }, () => measure('post_observation_ms', () => runWithBudget(controls.verificationTimeoutMs, () => ToolsObserve.getUITreeHandler({ platform: args.platform ?? s.result.device?.platform, deviceId: args.deviceId ?? s.result.device?.id })))).catch(() => null);
108
+ markEvidenceVerification();
109
+ s.observationAvailable = !!tree && !tree.error;
110
+ recordObservationEvent({
111
+ verification_budget_ms: controls.verificationTimeoutMs,
112
+ verification_elapsed_ms: performance.now() - verificationStarted,
113
+ verification_outcome: s.observationAvailable ? 'observed' : 'unavailable',
114
+ verification_budget_expired: !s.observationAvailable && performance.now() - verificationStarted >= controls.verificationTimeoutMs
115
+ });
116
+ if (s.observationAvailable && tree?.device)
117
+ s.delta = ToolsObserve.getLatestStateDelta(tree.device.platform, tree.device.id) ?? undefined;
118
+ }
119
+ if (!s.blocked && controls.verificationMode === 'full' && s.dispatchStarted)
120
+ markEvidenceVerification();
121
+ }
122
+ function actionResponse(s) {
123
+ const { timing, started, controls, condition, budgetMs } = s;
124
+ const compact = compactAction(s.result, controls.verificationMode, s.dispatchStarted, s.delta, performance.now() - started);
125
+ if (s.blocked)
126
+ compact.verification.status = 'not_requested';
127
+ if (s.result.success && controls.verificationMode === 'light' && !s.observationAvailable)
128
+ compact.verification.status = 'unavailable';
129
+ const evidence = {
130
+ ...compact,
131
+ ...(condition ? {
132
+ condition: controls.responseMode === 'compact' && condition.result.status === 'matched'
133
+ ? {
134
+ ...condition.result, observed: condition.result.observed ? {
135
+ snapshot_session_id: condition.result.observed.snapshot_session_id,
136
+ snapshot_revision: condition.result.observed.snapshot_revision,
137
+ captured_at_ms: condition.result.observed.captured_at_ms
138
+ } : null
139
+ } : condition.result
140
+ } : {}),
141
+ ...(controls.verificationMode === 'light' ? { state_delta_available: !!s.delta } : {}),
142
+ ...(s.result.success && controls.verificationMode === 'light' && !s.observationAvailable ? { verification_diagnostic: { code: 'VERIFICATION_UNAVAILABLE', retryable: true } } : {})
143
+ };
144
+ return wrapResponse(controls.responseMode === 'compact' ? evidence : {
145
+ ...s.result, ...evidence, timing: { ...timing, total_ms: performance.now() - started },
146
+ effective_controls: { ...controls, actionTimeoutMs: s.unbounded ? null : budgetMs, timeout_bound: !(s.unbounded) }
147
+ }, controls.responseMode === 'debug');
148
+ }
149
+ export function actionHandler(type, execute) {
150
+ return correlatedHandler(type, async (args) => {
151
+ const timing = {};
152
+ return withTiming(timing, async () => {
153
+ const state = createAction(type, args, timing);
154
+ const error = await dispatchAction(state, execute);
155
+ if (error)
156
+ return error;
157
+ normalizeAction(state);
158
+ await verifyAction(state);
159
+ return actionResponse(state);
160
+ });
161
+ }, true);
162
+ }
163
+ export { interactionControls, correlatedHandler, payloadOf };
@@ -0,0 +1,23 @@
1
+ import { TraceError } from '../../observe/system-trace.js';
2
+ import { withEvidence, validateActionRef } from '../../observe/evidence.js';
3
+ import { wrapResponse, wrapToolError } from '../common.js';
4
+ export function correlatedHandler(name, handler, action = false, journey = false) {
5
+ return async (args) => {
6
+ const ref = validateActionRef(args.actionRef);
7
+ const { result, evidence } = await withEvidence({ action, journey, ref }, async () => {
8
+ try {
9
+ return await handler(args);
10
+ }
11
+ catch (error) {
12
+ return error instanceof TraceError ? { ...wrapResponse({ error: { tool: name, code: error.code, message: error.message } }), isError: true } : wrapToolError(name, error);
13
+ }
14
+ });
15
+ const item = result.content.find(item => item.type === 'text');
16
+ if (item?.text) {
17
+ const payload = JSON.parse(item.text);
18
+ payload.evidence = evidence;
19
+ item.text = JSON.stringify(payload);
20
+ }
21
+ return result;
22
+ };
23
+ }
@@ -0,0 +1,48 @@
1
+ import { debuggerResponse } from './debugger-response.js';
2
+ import { classify } from '../../debugger/diagnostics.js';
3
+ import { debuggerContracts } from '../schemas/debugger-schema.js';
4
+ import { debuggerSessions, debuggerCapabilities } from '../../debugger/sessions.js';
5
+ import { DebuggerError, withDebuggerBudget } from '../../debugger/worker.js';
6
+ import { wrapResponse } from '../common.js';
7
+ function failure(code, summary = {}, diagnostic, command = '', mode = 'debug') {
8
+ return {
9
+ ...wrapResponse(debuggerResponse(command, mode, {
10
+ debug_session_id: null, device: null, target: null, state: null,
11
+ captured_at_ms: Date.now(), capabilities: debuggerCapabilities, limitations: [], cleanup: null,
12
+ ...summary, success: false, error: { code, message: code, retryable: false, ...(diagnostic ? { diagnostic } : {}) }
13
+ })), isError: true
14
+ };
15
+ }
16
+ export async function handleDebugger(name, args, signal) {
17
+ const parsed = debuggerContracts[name].schema.safeParse(args);
18
+ if (!parsed.success)
19
+ return failure('INVALID_ARGUMENT');
20
+ const { responseMode, ...input } = parsed.data;
21
+ const a = input;
22
+ const command = debuggerContracts[name].command;
23
+ try {
24
+ if (signal?.aborted)
25
+ throw new DebuggerError('DEBUGGER_CANCELLED');
26
+ const result = await withDebuggerBudget(a.timeoutMs, signal, async () => command === 'capabilities' ? await debuggerSessions.capabilities(a.deviceId, a.appId, a.processId) :
27
+ command === 'attach' ? await debuggerSessions.attach(a.deviceId, a.appId, a.processId, a.timeoutMs) :
28
+ await debuggerSessions.command(a.debugSessionId, command, { ...a, signal: command === 'events' ? signal : undefined }, a.timeoutMs));
29
+ if (signal?.aborted && command === 'capabilities')
30
+ throw new DebuggerError('DEBUGGER_CANCELLED');
31
+ return { ...wrapResponse(debuggerResponse(command, responseMode, result)), ...(!result.success ? { isError: true } : {}) };
32
+ }
33
+ catch (e) {
34
+ const error = classify(e, 'unknown', 'DEBUGGER_WORKER_FAILED');
35
+ const code = error.code;
36
+ let summary = {};
37
+ if (a.debugSessionId) {
38
+ try {
39
+ summary = debuggerSessions.summary(a.debugSessionId);
40
+ }
41
+ catch { /* Unknown session */ }
42
+ }
43
+ return failure(code, {
44
+ debug_session_id: a.debugSessionId ?? null,
45
+ device: a.deviceId ? { platform: 'android', id: a.deviceId } : null, ...summary
46
+ }, code === 'DEBUGGER_CLEANUP_FAILED' ? summary.error?.diagnostic : error.diagnostic, command, responseMode);
47
+ }
48
+ }
@@ -0,0 +1,9 @@
1
+ /** Omit only repeated target metadata; preserve all execution and failure evidence. */
2
+ export function debuggerResponse(command, mode, payload) {
3
+ if (mode !== 'compact' || ['capabilities', 'attach', 'state'].includes(command) || payload.debug_session_id == null)
4
+ return payload;
5
+ const result = { ...payload };
6
+ for (const field of ['device', 'target', 'capabilities'])
7
+ delete result[field];
8
+ return result;
9
+ }
@@ -0,0 +1,69 @@
1
+ import { systemTraces, TraceError } from '../../observe/system-trace.js';
2
+ import { traceTargetSchema, traceStartSchema, traceControlSchema } from '../schemas/system-trace-schema.js';
3
+ import { resolveTargetDevice } from '../../utils/resolve-device.js';
4
+ import { ToolsManage } from '../../manage/index.js';
5
+ import { classifyActionOutcome } from '../../interact/classify.js';
6
+ import { ToolsNetwork } from '../../observe/network/index.js';
7
+ import { getSystemStatus } from '../../system/index.js';
8
+ import { getArrayArg, getBooleanArg, getStringArg, requireBooleanArg, requireStringArg, wrapResponse } from '../common.js';
9
+ export async function handleListDevices(args) {
10
+ const platform = getStringArg(args, 'platform');
11
+ const appId = getStringArg(args, 'appId');
12
+ const res = await ToolsManage.listDevicesHandler({ platform, appId });
13
+ return wrapResponse(res);
14
+ }
15
+ export async function handleGetSystemStatus() {
16
+ const result = await getSystemStatus();
17
+ return wrapResponse(result);
18
+ }
19
+ export function handleClassifyActionOutcome(args) {
20
+ const uiChanged = requireBooleanArg(args, 'uiChanged');
21
+ const expectedElementVisible = getBooleanArg(args, 'expectedElementVisible');
22
+ const actionType = getStringArg(args, 'actionType');
23
+ const networkRequests = getArrayArg(args, 'networkRequests');
24
+ const hasLogErrors = getBooleanArg(args, 'hasLogErrors');
25
+ const result = classifyActionOutcome({
26
+ uiChanged,
27
+ expectedElementVisible: expectedElementVisible ?? null,
28
+ actionType: actionType ?? null,
29
+ networkRequests: networkRequests ?? null,
30
+ hasLogErrors: hasLogErrors ?? null
31
+ });
32
+ return Promise.resolve(wrapResponse(result));
33
+ }
34
+ export async function handleGetNetworkActivity(args) {
35
+ const platform = requireStringArg(args, 'platform');
36
+ const deviceId = getStringArg(args, 'deviceId');
37
+ const result = await ToolsNetwork.getNetworkActivity({ platform, deviceId });
38
+ return wrapResponse(result);
39
+ }
40
+ export async function handleSystemTrace(name, args) {
41
+ const schema = name === 'start_system_trace' ? traceStartSchema : name === 'get_system_trace_capabilities' ? traceTargetSchema : traceControlSchema;
42
+ const parsed = schema.safeParse(args);
43
+ if (!parsed.success)
44
+ throw new TraceError('INVALID_ARGUMENT');
45
+ if (name === 'get_system_trace_capabilities' || name === 'start_system_trace') {
46
+ const target = parsed.data;
47
+ if (target.platform !== 'android') {
48
+ if (name === 'start_system_trace')
49
+ throw new TraceError('UNSUPPORTED_PLATFORM');
50
+ return wrapResponse({ supported: false, backend: 'perfetto', device: null, prerequisites: {}, reasons: ['UNSUPPORTED_PLATFORM'], enabled_sources: [], unavailable_categories: [], limitations: ['Android only'] });
51
+ }
52
+ let device;
53
+ try {
54
+ device = await resolveTargetDevice(target);
55
+ }
56
+ catch {
57
+ if (name === 'start_system_trace')
58
+ throw new TraceError('TRACE_UNSUPPORTED');
59
+ return wrapResponse({ supported: false, backend: 'perfetto', device: null, prerequisites: { device: false }, reasons: ['DEVICE_UNAVAILABLE'], enabled_sources: [], unavailable_categories: [], limitations: ['An authorised Android device is required'] });
60
+ }
61
+ if (name === 'get_system_trace_capabilities')
62
+ return wrapResponse(await systemTraces.capabilities(device.id));
63
+ const result = await systemTraces.start(device.id, target.durationMs);
64
+ return { ...wrapResponse(result), ...(result.state === 'failed' ? { isError: true } : {}) };
65
+ }
66
+ const operation = name === 'stop_system_trace' ? 'stop' : name === 'cancel_system_trace' ? 'cancel' : 'status';
67
+ const result = await systemTraces.control(parsed.data.traceId, operation);
68
+ return { ...wrapResponse(result), ...(['failed', 'cancelled'].includes(result.state) ? { isError: true } : {}) };
69
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,154 @@
1
+ import { measure } from '../../utils/timing.js';
2
+ import { runWithBudget } from '../../utils/operation-budget.js';
3
+ import { tapElementControlsSchema } from '../schemas/journey-schema.js';
4
+ import { notifyActionStart } from '../../interact/conditional-action.js';
5
+ import { ToolsInteract } from '../../interact/index.js';
6
+ import { recordObservationConsumer, recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js';
7
+ import { buildActionExecutionResult, captureActionFingerprint, getNumberArg, getStringArg, inferGenericFailure, inferScrollFailure, requireNumberArg, requireObjectArg, requireStringArg, wrapResponse } from '../common.js';
8
+ import { interactionControls, actionHandler } from './action-execution.js';
9
+ export async function executeTap(args) {
10
+ const platform = getStringArg(args, 'platform');
11
+ const x = requireNumberArg(args, 'x');
12
+ const y = requireNumberArg(args, 'y');
13
+ const deviceId = getStringArg(args, 'deviceId');
14
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
15
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
16
+ notifyActionStart();
17
+ const res = await measure('dispatch_ms', () => ToolsInteract.tapHandler({ platform, x, y, deviceId, timeoutMs: actionTimeoutMs }));
18
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
19
+ const result = buildActionExecutionResult({
20
+ actionType: 'tap',
21
+ sourceModule: 'server',
22
+ selector: { x, y },
23
+ success: !!res.success,
24
+ uiFingerprintBefore,
25
+ uiFingerprintAfter,
26
+ failure: res.success ? undefined : inferGenericFailure(res.error)
27
+ });
28
+ return wrapResponse(result);
29
+ }
30
+ export async function executeTapElement(args) {
31
+ const controls = tapElementControlsSchema.safeParse({ elementId: args.elementId, selector: args.selector, waitFor: args.waitFor });
32
+ if (!controls.success)
33
+ throw new Error(`INVALID_ARGUMENT: ${controls.error.message}`);
34
+ const elementId = controls.data.elementId;
35
+ const selector = controls.data.selector;
36
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
37
+ let resolvedId = elementId;
38
+ let freshTree;
39
+ if (selector) {
40
+ const waitFor = controls.data.waitFor;
41
+ const targetStarted = performance.now();
42
+ const resolutionTimeoutMs = waitFor?.timeoutMs ?? actionTimeoutMs ?? 5000;
43
+ const resolution = (await withObservationContext({ stepId: args._stepId, purpose: 'target' }, () => measure('target_resolution_ms', () => runWithBudget(resolutionTimeoutMs, () => ToolsInteract.waitForUIHandler({ selector, condition: waitFor?.condition ?? 'clickable', timeout_ms: resolutionTimeoutMs, poll_interval_ms: waitFor?.pollIntervalMs ?? 100, match: waitFor?.match, singleObservation: true, singleAttempt: !waitFor, rejectAmbiguous: !waitFor, resolveTapTarget: true, platform: getStringArg(args, 'platform'), deviceId: getStringArg(args, 'deviceId') })))));
44
+ recordObservationEvent({ step_id: args._stepId, purpose: 'target', target_resolution_ms: performance.now() - targetStarted, target_resolution_outcome: resolution.status });
45
+ if (resolution.status !== 'success' || !resolution.element?.elementId)
46
+ return wrapResponse({ success: false, action_type: 'tap_element', failure_code: resolution.error?.code ?? 'ELEMENT_NOT_FOUND', retryable: true });
47
+ resolvedId = resolution.element.elementId;
48
+ freshTree = resolution._tree;
49
+ }
50
+ notifyActionStart();
51
+ if (freshTree)
52
+ recordObservationConsumer(freshTree, 'target', args._stepId);
53
+ const res = await measure('dispatch_ms', () => ToolsInteract.tapElementHandler({ elementId: resolvedId, freshTree, verificationMode, timeoutMs: actionTimeoutMs, platform: getStringArg(args, 'platform'), deviceId: getStringArg(args, 'deviceId') }));
54
+ return wrapResponse(res);
55
+ }
56
+ export async function executeSwipe(args) {
57
+ const platform = getStringArg(args, 'platform') ?? 'android';
58
+ const x1 = requireNumberArg(args, 'x1');
59
+ const y1 = requireNumberArg(args, 'y1');
60
+ const x2 = requireNumberArg(args, 'x2');
61
+ const y2 = requireNumberArg(args, 'y2');
62
+ const duration = requireNumberArg(args, 'duration');
63
+ const deviceId = getStringArg(args, 'deviceId');
64
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
65
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
66
+ notifyActionStart();
67
+ const res = await measure('dispatch_ms', () => ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId, timeoutMs: actionTimeoutMs }));
68
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
69
+ const result = buildActionExecutionResult({
70
+ actionType: 'swipe',
71
+ sourceModule: 'server',
72
+ selector: { x1, y1, x2, y2, duration },
73
+ success: !!res.success,
74
+ uiFingerprintBefore,
75
+ uiFingerprintAfter,
76
+ failure: res.success ? undefined : inferGenericFailure(res.error)
77
+ });
78
+ return wrapResponse(result);
79
+ }
80
+ export async function executeScrollToElement(args) {
81
+ const platform = requireStringArg(args, 'platform');
82
+ const selector = requireObjectArg(args, 'selector');
83
+ const direction = getStringArg(args, 'direction');
84
+ const maxScrolls = getNumberArg(args, 'maxScrolls');
85
+ const scrollAmount = getNumberArg(args, 'scrollAmount');
86
+ const deviceId = getStringArg(args, 'deviceId');
87
+ const { verificationMode } = interactionControls(args);
88
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
89
+ notifyActionStart();
90
+ const res = await measure('dispatch_ms', () => ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId }));
91
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
92
+ const result = buildActionExecutionResult({
93
+ actionType: 'scroll_to_element',
94
+ sourceModule: 'server',
95
+ selector: selector ?? null,
96
+ resolved: res?.success && res?.element ? {
97
+ elementId: null,
98
+ text: res.element.text ?? null,
99
+ resource_id: res.element.resourceId ?? null,
100
+ accessibility_id: res.element.contentDesc ?? null,
101
+ class: res.element.className ?? null,
102
+ bounds: res.element.bounds ?? null,
103
+ index: null
104
+ } : null,
105
+ success: !!res.success,
106
+ uiFingerprintBefore,
107
+ uiFingerprintAfter,
108
+ failure: res.success ? undefined : inferScrollFailure(res.reason)
109
+ });
110
+ return wrapResponse(result);
111
+ }
112
+ export async function executeTypeText(args) {
113
+ const text = requireStringArg(args, 'text');
114
+ const deviceId = getStringArg(args, 'deviceId');
115
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
116
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
117
+ notifyActionStart();
118
+ const res = await measure('dispatch_ms', () => ToolsInteract.typeTextHandler({ text, deviceId, timeoutMs: actionTimeoutMs }));
119
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
120
+ const result = buildActionExecutionResult({
121
+ actionType: 'type_text',
122
+ sourceModule: 'server',
123
+ selector: { text },
124
+ success: !!res.success,
125
+ uiFingerprintBefore,
126
+ uiFingerprintAfter,
127
+ failure: res.success ? undefined : inferGenericFailure(res.error)
128
+ });
129
+ return wrapResponse(result);
130
+ }
131
+ export async function executePressBack(args) {
132
+ const deviceId = getStringArg(args, 'deviceId');
133
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
134
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
135
+ notifyActionStart();
136
+ const res = await measure('dispatch_ms', () => ToolsInteract.pressBackHandler({ deviceId, timeoutMs: actionTimeoutMs }));
137
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
138
+ const result = buildActionExecutionResult({
139
+ actionType: 'press_back',
140
+ sourceModule: 'server',
141
+ selector: { key: 'back' },
142
+ success: !!res.success,
143
+ uiFingerprintBefore,
144
+ uiFingerprintAfter,
145
+ failure: res.success ? undefined : inferGenericFailure(res.error)
146
+ });
147
+ return wrapResponse(result);
148
+ }
149
+ export const handleTap = actionHandler('tap', executeTap);
150
+ export const handleTapElement = actionHandler('tap_element', executeTapElement);
151
+ export const handleSwipe = actionHandler('swipe', executeSwipe);
152
+ export const handleScrollToElement = actionHandler('scroll_to_element', executeScrollToElement);
153
+ export const handleTypeText = actionHandler('type_text', executeTypeText);
154
+ export const handlePressBack = actionHandler('press_back', executePressBack);
@@ -0,0 +1,113 @@
1
+ import { markEvidenceVerification } from '../../observe/evidence.js';
2
+ import { withReadyWait } from '../../observe/readiness-service.js';
3
+ import { ToolsInteract } from '../../interact/index.js';
4
+ import { recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js';
5
+ import { getBooleanArg, getNumberArg, getObjectArg, getStringArg, requireNumberArg, requireObjectArg, requireStringArg, wrapResponse } from '../common.js';
6
+ export async function handleWaitForScreenChange(args) {
7
+ const platform = getStringArg(args, 'platform');
8
+ const previousFingerprint = requireStringArg(args, 'previousFingerprint');
9
+ const timeoutMs = getNumberArg(args, 'timeoutMs');
10
+ const pollIntervalMs = getNumberArg(args, 'pollIntervalMs');
11
+ const deviceId = getStringArg(args, 'deviceId');
12
+ const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId });
13
+ return wrapResponse(res);
14
+ }
15
+ export async function handleExpectScreen(args) {
16
+ const platform = getStringArg(args, 'platform');
17
+ const fingerprint = getStringArg(args, 'fingerprint');
18
+ const screen = getStringArg(args, 'screen');
19
+ const deviceId = getStringArg(args, 'deviceId');
20
+ const res = await ToolsInteract.expectScreenHandler({ platform, fingerprint, screen, deviceId });
21
+ return wrapResponse(res);
22
+ }
23
+ export async function handleExpectElementVisible(args) {
24
+ const selector = requireObjectArg(args, 'selector');
25
+ const element_id = getStringArg(args, 'element_id');
26
+ const timeout_ms = getNumberArg(args, 'timeout_ms');
27
+ const poll_interval_ms = getNumberArg(args, 'poll_interval_ms');
28
+ const platform = getStringArg(args, 'platform');
29
+ const deviceId = getStringArg(args, 'deviceId');
30
+ const res = await ToolsInteract.expectElementVisibleHandler({ selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId });
31
+ return wrapResponse(res);
32
+ }
33
+ export async function handleExpectState(args) {
34
+ const selector = getObjectArg(args, 'selector');
35
+ const element_id = getStringArg(args, 'element_id');
36
+ const property = requireStringArg(args, 'property');
37
+ const platform = getStringArg(args, 'platform');
38
+ const deviceId = getStringArg(args, 'deviceId');
39
+ if (!selector && !element_id) {
40
+ throw new Error('Missing selector or element_id argument');
41
+ }
42
+ if (!Object.prototype.hasOwnProperty.call(args, 'expected')) {
43
+ throw new Error('Missing expected argument');
44
+ }
45
+ const expected = args.expected;
46
+ const res = await ToolsInteract.expectStateHandler({ selector: selector ?? undefined, element_id: element_id ?? undefined, property, expected, platform, deviceId });
47
+ return wrapResponse(res);
48
+ }
49
+ export async function handleAdjustControl(args) {
50
+ const selector = getObjectArg(args, 'selector');
51
+ const element_id = getStringArg(args, 'element_id');
52
+ const property = getStringArg(args, 'property') ?? 'value';
53
+ const targetValue = requireNumberArg(args, 'targetValue');
54
+ const tolerance = getNumberArg(args, 'tolerance');
55
+ const maxAttempts = getNumberArg(args, 'maxAttempts');
56
+ const platform = getStringArg(args, 'platform');
57
+ const deviceId = getStringArg(args, 'deviceId');
58
+ if ((!selector && !element_id) || (selector && element_id)) {
59
+ throw new Error('Exactly one of selector or element_id argument is required');
60
+ }
61
+ const res = await ToolsInteract.adjustControlHandler({
62
+ selector: selector ?? undefined,
63
+ element_id: element_id ?? undefined,
64
+ property,
65
+ targetValue,
66
+ tolerance,
67
+ maxAttempts,
68
+ platform,
69
+ deviceId
70
+ });
71
+ markEvidenceVerification();
72
+ return wrapResponse(res);
73
+ }
74
+ export async function handleWaitForUI(args) {
75
+ if (args.requireReady !== undefined && typeof args.requireReady !== 'boolean')
76
+ throw new Error('INVALID_ARGUMENT: requireReady must be boolean');
77
+ const selector = getObjectArg(args, 'selector');
78
+ const condition = getStringArg(args, 'condition') ?? 'exists';
79
+ const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000;
80
+ const poll_interval_ms = getNumberArg(args, 'poll_interval_ms') ?? 300;
81
+ const match = getObjectArg(args, 'match');
82
+ const retry = getObjectArg(args, 'retry');
83
+ const platform = getStringArg(args, 'platform');
84
+ const deviceId = getStringArg(args, 'deviceId');
85
+ const execute = (targetPlatform = platform, targetId = deviceId) => ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform: targetPlatform, deviceId: targetId });
86
+ const res = args.requireReady === true ? await withReadyWait({ platform, deviceId }, timeout_ms, device => execute(device.platform, device.id), (code, elapsed, samples) => ({ status: 'timeout', error: { code, message: 'Readiness wait could not complete' }, metrics: { latency_ms: elapsed, poll_count: samples, attempts: samples ? 1 : 0 }, requested: { selector, condition, match }, observed: { matched_count: 0, condition_satisfied: false, selected_index: null, last_matched_element: null } })) : await execute();
87
+ return wrapResponse(res);
88
+ }
89
+ export async function handleWaitForUIChange(args) {
90
+ if (args.requireReady !== undefined && typeof args.requireReady !== 'boolean')
91
+ throw new Error('INVALID_ARGUMENT: requireReady must be boolean');
92
+ const platform = getStringArg(args, 'platform');
93
+ const deviceId = getStringArg(args, 'deviceId');
94
+ const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000;
95
+ const stability_window_ms = getNumberArg(args, 'stability_window_ms') ?? 300;
96
+ const expected_change = getStringArg(args, 'expected_change');
97
+ const scope = getStringArg(args, 'scope');
98
+ const target = getStringArg(args, 'target');
99
+ const execute = (targetPlatform = platform, targetId = deviceId) => ToolsInteract.waitForUIChangeHandler({ platform: targetPlatform, deviceId: targetId, timeout_ms, stability_window_ms, expected_change, scope, target });
100
+ const res = args.requireReady === true ? await withReadyWait({ platform, deviceId }, timeout_ms, device => execute(device.platform, device.id), (code, elapsed) => ({ success: false, timeout: true, observed_change: null, elapsed_ms: elapsed, reason: code, error: { code, message: 'Readiness wait could not complete' } })) : await execute();
101
+ return wrapResponse(res);
102
+ }
103
+ export async function handleFindElement(args) {
104
+ const query = requireStringArg(args, 'query');
105
+ const exact = getBooleanArg(args, 'exact') ?? false;
106
+ const timeoutMs = getNumberArg(args, 'timeoutMs') ?? 3000;
107
+ const platform = getStringArg(args, 'platform');
108
+ const deviceId = getStringArg(args, 'deviceId');
109
+ const targetStarted = performance.now();
110
+ const res = await withObservationContext({ purpose: 'target' }, () => ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId }));
111
+ recordObservationEvent({ target_resolution_ms: performance.now() - targetStarted, target_resolution_outcome: res.found ? 'found' : 'not_found', operation: 'find_element' });
112
+ return wrapResponse(res);
113
+ }
@@ -0,0 +1,35 @@
1
+ import { ToolsInteract } from '../../interact/index.js';
2
+ import { ToolsObserve } from '../../observe/index.js';
3
+ export async function expectElementAbsent({ selector, timeoutMs, pollIntervalMs, platform, deviceId }) {
4
+ const deadline = performance.now() + timeoutMs;
5
+ let consecutive = 0;
6
+ let observed;
7
+ while (performance.now() < deadline) {
8
+ const result = await ToolsInteract.waitForUIHandler({ selector, condition: 'not_exists', timeout_ms: 0, singleObservation: true, singleAttempt: true, platform, deviceId });
9
+ observed = result.observed;
10
+ consecutive = result.status === 'success' ? consecutive + 1 : 0;
11
+ if (consecutive === 2 && performance.now() <= deadline)
12
+ return { success: true, observed };
13
+ const delay = Math.max(100, pollIntervalMs);
14
+ if (performance.now() + delay >= deadline)
15
+ break;
16
+ await new Promise(resolve => setTimeout(resolve, delay));
17
+ }
18
+ return { success: false, failure_code: 'ASSERTION_TIMEOUT', observed };
19
+ }
20
+ export async function expectFingerprint({ fingerprint, timeoutMs, platform, deviceId }) {
21
+ const deadline = performance.now() + timeoutMs;
22
+ let consecutive = 0;
23
+ let observed = null;
24
+ while (performance.now() < deadline) {
25
+ const result = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId });
26
+ observed = result?.fingerprint ?? null;
27
+ consecutive = observed === fingerprint ? consecutive + 1 : 0;
28
+ if (consecutive === 2 && performance.now() <= deadline)
29
+ return { success: true, observed_fingerprint: observed };
30
+ if (performance.now() + 100 >= deadline)
31
+ break;
32
+ await new Promise(resolve => setTimeout(resolve, 100));
33
+ }
34
+ return { success: false, failure_code: 'ASSERTION_TIMEOUT', observed_fingerprint: observed };
35
+ }