mobile-debug-mcp 0.34.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 (432) 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 +4 -4
  12. package/AGENTS.md +12 -3
  13. package/CONTRIBUTING.md +79 -0
  14. package/README.md +156 -129
  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 +16 -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 +27 -2
  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 +2 -1
  28. package/dist/debugger/diagnostics.js +54 -0
  29. package/dist/debugger/protocol.js +33 -0
  30. package/dist/debugger/sessions.js +104 -62
  31. package/dist/debugger/worker.js +53 -42
  32. package/dist/interact/adjustment/adjustment-context.js +41 -0
  33. package/dist/interact/adjustment/adjustment-gestures.js +55 -0
  34. package/dist/interact/adjustment/adjustment-plan.js +18 -0
  35. package/dist/interact/adjustment/adjustment-resolution.js +127 -0
  36. package/dist/interact/adjustment/adjustment-result.js +19 -0
  37. package/dist/interact/adjustment/adjustment-semantic.js +64 -0
  38. package/dist/interact/adjustment/adjustment-verification.js +35 -0
  39. package/dist/interact/adjustment/adjustment.js +52 -0
  40. package/dist/interact/adjustment/control-geometry.js +87 -0
  41. package/dist/interact/adjustment/control-values.js +136 -0
  42. package/dist/interact/adjustment/slider-labels.js +84 -0
  43. package/dist/interact/assertions/screen-assertion.js +37 -0
  44. package/dist/interact/assertions/state-assertion-context.js +35 -0
  45. package/dist/interact/assertions/state-assertion.js +76 -0
  46. package/dist/interact/assertions/state-comparison.js +141 -0
  47. package/dist/interact/assertions/visibility-assertion.js +34 -0
  48. package/dist/interact/conditional-action.js +12 -7
  49. package/dist/interact/index.js +65 -3090
  50. package/dist/interact/input-service.js +1 -0
  51. package/dist/interact/interaction-trace.js +21 -0
  52. package/dist/interact/model.js +1 -0
  53. package/dist/interact/observation.js +1 -0
  54. package/dist/interact/targeting/candidate-ancestor.js +58 -0
  55. package/dist/interact/targeting/candidate-result.js +23 -0
  56. package/dist/interact/targeting/candidate-scoring.js +50 -0
  57. package/dist/interact/targeting/element-helpers.js +216 -0
  58. package/dist/interact/targeting/find-element.js +65 -0
  59. package/dist/interact/targeting/nearby-control.js +56 -0
  60. package/dist/interact/targeting/resolved-elements.js +21 -0
  61. package/dist/interact/targeting/tap-target.js +17 -0
  62. package/dist/interact/targeting/target-scope.js +104 -0
  63. package/dist/interact/waits/legacy-log-wait.js +38 -0
  64. package/dist/interact/waits/legacy-wait-observations.js +103 -0
  65. package/dist/interact/waits/legacy-wait-state.js +34 -0
  66. package/dist/interact/waits/legacy-wait.js +48 -0
  67. package/dist/interact/waits/screen-wait.js +49 -0
  68. package/dist/interact/waits/ui-change-helpers.js +157 -0
  69. package/dist/interact/waits/ui-change-state.js +18 -0
  70. package/dist/interact/waits/ui-change-wait.js +77 -0
  71. package/dist/interact/waits/ui-wait-condition.js +56 -0
  72. package/dist/interact/waits/ui-wait-state.js +38 -0
  73. package/dist/interact/waits/ui-wait.js +106 -0
  74. package/dist/manage/android.js +55 -51
  75. package/dist/manage/index.js +38 -75
  76. package/dist/manage/ios/build-types.js +1 -0
  77. package/dist/manage/ios/build.js +210 -0
  78. package/dist/manage/ios/project-discovery.js +29 -0
  79. package/dist/manage/ios/scheme.js +18 -0
  80. package/dist/manage/ios.js +42 -256
  81. package/dist/observe/android/context.js +1 -0
  82. package/dist/observe/android/current-screen.js +55 -0
  83. package/dist/observe/android/fingerprint.js +13 -0
  84. package/dist/observe/android/log-stream.js +132 -0
  85. package/dist/observe/android/logs.js +107 -0
  86. package/dist/observe/android/screenshot.js +70 -0
  87. package/dist/observe/android/ui-tree.js +107 -0
  88. package/dist/observe/android.js +16 -520
  89. package/dist/observe/evidence.js +114 -75
  90. package/dist/observe/index.js +16 -263
  91. package/dist/observe/ios/context.js +16 -0
  92. package/dist/observe/ios/fingerprint.js +12 -0
  93. package/dist/observe/ios/log-stream.js +105 -0
  94. package/dist/observe/ios/logs.js +168 -0
  95. package/dist/observe/ios/screenshot.js +22 -0
  96. package/dist/observe/ios/ui-elements.js +241 -0
  97. package/dist/observe/ios/ui-tree.js +137 -0
  98. package/dist/observe/ios.js +17 -715
  99. package/dist/observe/logs/model.js +1 -0
  100. package/dist/{network → observe/network}/index.js +2 -2
  101. package/dist/observe/readiness-service.js +4 -2
  102. package/dist/observe/readiness.js +29 -10
  103. package/dist/observe/screenshot-encoding.js +41 -0
  104. package/dist/observe/snapshot-collection.js +79 -0
  105. package/dist/observe/snapshot-logs.js +30 -0
  106. package/dist/observe/snapshot-metadata.js +43 -31
  107. package/dist/observe/snapshot-semantic.js +115 -0
  108. package/dist/observe/system-trace.js +22 -13
  109. package/dist/runtime/action-results.js +270 -0
  110. package/dist/runtime/clock.js +4 -0
  111. package/dist/server/common.js +1 -256
  112. package/dist/server/definitions/assert.js +162 -0
  113. package/dist/server/definitions/diagnostic.js +118 -0
  114. package/dist/server/definitions/input.js +274 -0
  115. package/dist/server/definitions/manage.js +194 -0
  116. package/dist/server/definitions/observe.js +149 -0
  117. package/dist/server/definitions/target.js +138 -0
  118. package/dist/server/definitions/wait.js +105 -0
  119. package/dist/server/handlers/action-execution.js +163 -0
  120. package/dist/server/handlers/correlation.js +23 -0
  121. package/dist/server/{debugger-handlers.js → handlers/debugger-handlers.js} +22 -12
  122. package/dist/server/handlers/debugger-response.js +9 -0
  123. package/dist/server/handlers/diagnostic.js +69 -0
  124. package/dist/server/handlers/handler-types.js +1 -0
  125. package/dist/server/handlers/input.js +154 -0
  126. package/dist/server/handlers/interact.js +113 -0
  127. package/dist/server/handlers/journey-assertions.js +35 -0
  128. package/dist/server/handlers/journey.js +147 -0
  129. package/dist/server/handlers/manage.js +119 -0
  130. package/dist/server/handlers/observe.js +95 -0
  131. package/dist/server/handlers/payload.js +4 -0
  132. package/dist/server/handlers/tool-handlers.js +160 -0
  133. package/dist/server/schemas/condition-schema.js +2 -0
  134. package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +11 -6
  135. package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
  136. package/dist/server/tool-definitions.js +58 -1135
  137. package/dist/server-core.js +2 -2
  138. package/dist/system/android-status.js +101 -0
  139. package/dist/system/android.js +37 -27
  140. package/dist/system/index.js +13 -439
  141. package/dist/system/ios-status.js +132 -0
  142. package/dist/system/ios.js +11 -8
  143. package/dist/system/model.js +1 -0
  144. package/dist/system/probes.js +105 -0
  145. package/dist/system/status-summary.js +43 -0
  146. package/dist/system/unsupported-status.js +52 -0
  147. package/dist/utils/android/commands.js +89 -0
  148. package/dist/utils/android/gradle.js +139 -0
  149. package/dist/utils/android/logs.js +103 -0
  150. package/dist/utils/android/ui-tree.js +233 -0
  151. package/dist/utils/android/utils.js +6 -564
  152. package/dist/utils/cli/idb/idb-helper.js +4 -6
  153. package/dist/utils/cli/idb/install-idb.js +13 -9
  154. package/dist/utils/cli/ios/preflight-ios.js +42 -38
  155. package/dist/utils/ios/utils.js +45 -46
  156. package/dist/utils/java.js +65 -49
  157. package/dist/utils/resolve-device.js +4 -2
  158. package/dist/utils/ui/index.js +26 -26
  159. package/docs/CHANGELOG.md +44 -0
  160. package/docs/README.md +38 -0
  161. package/docs/benchmarks/README.md +18 -0
  162. package/docs/benchmarks/interaction-latency/README.md +14 -0
  163. package/docs/benchmarks/interaction-latency/final.md +4 -1
  164. package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
  165. package/docs/benchmarks/ui-tree-performance/README.md +24 -16
  166. package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
  167. package/docs/code-structure.md +118 -0
  168. package/docs/specs/001-state-verification/spec.md +16 -14
  169. package/docs/specs/002-richer-element-identity/spec.md +12 -10
  170. package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
  171. package/docs/specs/004-action-verification-routing/spec.md +3 -3
  172. package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
  173. package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
  174. package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
  175. package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
  176. package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
  177. package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
  178. package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
  179. package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
  180. package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
  181. package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
  182. package/docs/specs/014-actionability-resolution/spec.md +20 -18
  183. package/docs/specs/015-adjustable-control-support/spec.md +17 -15
  184. package/docs/specs/016-environment-tools/spec.md +20 -20
  185. package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
  186. package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
  187. package/docs/specs/020-conditional-actions/intent.md +5 -3
  188. package/docs/specs/020-conditional-actions/plan.md +8 -9
  189. package/docs/specs/020-conditional-actions/spec.md +4 -2
  190. package/docs/specs/021-app-readiness-signals/intent.md +4 -2
  191. package/docs/specs/021-app-readiness-signals/plan.md +7 -6
  192. package/docs/specs/021-app-readiness-signals/spec.md +3 -1
  193. package/docs/specs/022-correlated-evidence/intent.md +4 -4
  194. package/docs/specs/022-correlated-evidence/plan.md +10 -11
  195. package/docs/specs/022-correlated-evidence/spec.md +3 -1
  196. package/docs/specs/023-android-system-traces/intent.md +3 -2
  197. package/docs/specs/023-android-system-traces/plan.md +5 -7
  198. package/docs/specs/023-android-system-traces/spec.md +3 -1
  199. package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
  200. package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
  201. package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
  202. package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
  203. package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
  204. package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
  205. package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
  206. package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
  207. package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
  208. package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
  209. package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
  210. package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
  211. package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
  212. package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
  213. package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
  214. package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
  215. package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
  216. package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
  217. package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
  218. package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
  219. package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
  220. package/docs/specs/030-android-coroutine-validation/report.md +240 -0
  221. package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
  222. package/docs/tools/TOOLS.md +31 -18
  223. package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
  224. package/docs/tools/debugger.md +76 -0
  225. package/docs/tools/interact.md +42 -20
  226. package/docs/tools/manage.md +25 -44
  227. package/docs/tools/observe.md +55 -23
  228. package/docs/tools/system-traces.md +9 -1
  229. package/docs/tools/system.md +12 -5
  230. package/package.json +11 -8
  231. package/scripts/architecture-graph.mjs +69 -0
  232. package/scripts/check-architecture.mjs +47 -0
  233. package/scripts/code-quality-baseline.json +1 -0
  234. package/scripts/code-quality.mjs +161 -0
  235. package/src/contracts/execution-controls.ts +26 -0
  236. package/src/contracts/selector.ts +17 -0
  237. package/src/contracts/target.ts +19 -0
  238. package/src/contracts/ui.ts +94 -0
  239. package/src/debugger/android.ts +2 -1
  240. package/src/debugger/diagnostics.ts +43 -0
  241. package/src/debugger/guard.ts +6 -1
  242. package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
  243. package/src/debugger/protocol.ts +37 -0
  244. package/src/debugger/sessions.ts +314 -155
  245. package/src/debugger/worker.ts +51 -38
  246. package/src/interact/adjustment/adjustment-context.ts +83 -0
  247. package/src/interact/adjustment/adjustment-gestures.ts +48 -0
  248. package/src/interact/adjustment/adjustment-plan.ts +23 -0
  249. package/src/interact/adjustment/adjustment-resolution.ts +104 -0
  250. package/src/interact/adjustment/adjustment-result.ts +34 -0
  251. package/src/interact/adjustment/adjustment-semantic.ts +53 -0
  252. package/src/interact/adjustment/adjustment-verification.ts +60 -0
  253. package/src/interact/adjustment/adjustment.ts +49 -0
  254. package/src/interact/adjustment/control-geometry.ts +120 -0
  255. package/src/interact/adjustment/control-values.ts +141 -0
  256. package/src/interact/adjustment/slider-labels.ts +84 -0
  257. package/src/interact/assertions/screen-assertion.ts +39 -0
  258. package/src/interact/assertions/state-assertion-context.ts +48 -0
  259. package/src/interact/assertions/state-assertion.ts +64 -0
  260. package/src/interact/assertions/state-comparison.ts +168 -0
  261. package/src/interact/assertions/visibility-assertion.ts +41 -0
  262. package/src/interact/conditional-action.ts +18 -12
  263. package/src/interact/index.ts +76 -3492
  264. package/src/interact/input-service.ts +11 -0
  265. package/src/interact/interaction-trace.ts +36 -0
  266. package/src/interact/model.ts +109 -0
  267. package/src/interact/observation.ts +7 -0
  268. package/src/interact/targeting/candidate-ancestor.ts +49 -0
  269. package/src/interact/targeting/candidate-result.ts +24 -0
  270. package/src/interact/targeting/candidate-scoring.ts +42 -0
  271. package/src/interact/targeting/element-helpers.ts +243 -0
  272. package/src/interact/targeting/find-element.ts +47 -0
  273. package/src/interact/targeting/nearby-control.ts +46 -0
  274. package/src/interact/targeting/resolved-elements.ts +20 -0
  275. package/src/interact/targeting/tap-target.ts +22 -0
  276. package/src/interact/targeting/target-scope.ts +121 -0
  277. package/src/interact/waits/legacy-log-wait.ts +30 -0
  278. package/src/interact/waits/legacy-wait-observations.ts +55 -0
  279. package/src/interact/waits/legacy-wait-state.ts +48 -0
  280. package/src/interact/waits/legacy-wait.ts +34 -0
  281. package/src/interact/waits/screen-wait.ts +37 -0
  282. package/src/interact/waits/ui-change-helpers.ts +184 -0
  283. package/src/interact/waits/ui-change-state.ts +26 -0
  284. package/src/interact/waits/ui-change-wait.ts +59 -0
  285. package/src/interact/waits/ui-wait-condition.ts +52 -0
  286. package/src/interact/waits/ui-wait-state.ts +55 -0
  287. package/src/interact/waits/ui-wait.ts +84 -0
  288. package/src/manage/android.ts +56 -50
  289. package/src/manage/index.ts +63 -93
  290. package/src/manage/ios/build-types.ts +14 -0
  291. package/src/manage/ios/build.ts +230 -0
  292. package/src/manage/ios/project-discovery.ts +26 -0
  293. package/src/manage/ios/scheme.ts +16 -0
  294. package/src/manage/ios.ts +65 -298
  295. package/src/observe/android/context.ts +1 -0
  296. package/src/observe/android/current-screen.ts +63 -0
  297. package/src/observe/android/fingerprint.ts +13 -0
  298. package/src/observe/android/log-stream.ts +127 -0
  299. package/src/observe/android/logs.ts +110 -0
  300. package/src/observe/android/screenshot.ts +79 -0
  301. package/src/observe/android/ui-tree.ts +113 -0
  302. package/src/observe/android.ts +17 -531
  303. package/src/observe/evidence.ts +122 -70
  304. package/src/observe/index.ts +24 -286
  305. package/src/observe/ios/context.ts +22 -0
  306. package/src/observe/ios/fingerprint.ts +12 -0
  307. package/src/observe/ios/log-stream.ts +103 -0
  308. package/src/observe/ios/logs.ts +179 -0
  309. package/src/observe/ios/screenshot.ts +26 -0
  310. package/src/observe/ios/ui-elements.ts +266 -0
  311. package/src/observe/ios/ui-tree.ts +143 -0
  312. package/src/observe/ios.ts +18 -749
  313. package/src/observe/logs/model.ts +11 -0
  314. package/src/{network → observe/network}/index.ts +2 -2
  315. package/src/observe/readiness-service.ts +5 -3
  316. package/src/observe/readiness.ts +39 -18
  317. package/src/observe/screenshot-encoding.ts +32 -0
  318. package/src/observe/snapshot-collection.ts +69 -0
  319. package/src/observe/snapshot-logs.ts +26 -0
  320. package/src/observe/snapshot-metadata.ts +51 -31
  321. package/src/observe/snapshot-semantic.ts +153 -0
  322. package/src/observe/system-trace.ts +24 -15
  323. package/src/runtime/action-results.ts +376 -0
  324. package/src/runtime/clock.ts +9 -0
  325. package/src/server/common.ts +9 -359
  326. package/src/server/definitions/assert.ts +164 -0
  327. package/src/server/definitions/diagnostic.ts +120 -0
  328. package/src/server/definitions/input.ts +276 -0
  329. package/src/server/definitions/manage.ts +196 -0
  330. package/src/server/definitions/observe.ts +151 -0
  331. package/src/server/definitions/target.ts +140 -0
  332. package/src/server/definitions/wait.ts +107 -0
  333. package/src/server/handlers/action-execution.ts +164 -0
  334. package/src/server/handlers/correlation.ts +20 -0
  335. package/src/server/handlers/debugger-handlers.ts +39 -0
  336. package/src/server/handlers/debugger-response.ts +7 -0
  337. package/src/server/handlers/diagnostic.ts +79 -0
  338. package/src/server/handlers/handler-types.ts +7 -0
  339. package/src/server/handlers/input.ts +173 -0
  340. package/src/server/handlers/interact.ts +135 -0
  341. package/src/server/handlers/journey-assertions.ts +39 -0
  342. package/src/server/handlers/journey.ts +132 -0
  343. package/src/server/handlers/manage.ts +141 -0
  344. package/src/server/handlers/observe.ts +116 -0
  345. package/src/server/handlers/payload.ts +36 -0
  346. package/src/server/handlers/tool-handlers.ts +159 -0
  347. package/src/server/schemas/condition-schema.ts +2 -0
  348. package/src/server/{debugger-schema.ts → schemas/debugger-schema.ts} +13 -7
  349. package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
  350. package/src/server/{system-trace-schema.ts → schemas/system-trace-schema.ts} +3 -3
  351. package/src/server/tool-definitions.ts +67 -1143
  352. package/src/server-core.ts +2 -2
  353. package/src/server.ts +1 -1
  354. package/src/system/android-status.ts +124 -0
  355. package/src/system/android.ts +33 -21
  356. package/src/system/index.ts +21 -525
  357. package/src/system/ios-status.ts +147 -0
  358. package/src/system/ios.ts +14 -10
  359. package/src/system/model.ts +41 -0
  360. package/src/system/probes.ts +114 -0
  361. package/src/system/status-summary.ts +47 -0
  362. package/src/system/unsupported-status.ts +55 -0
  363. package/src/types.ts +16 -106
  364. package/src/utils/android/commands.ts +81 -0
  365. package/src/utils/android/gradle.ts +132 -0
  366. package/src/utils/android/logs.ts +103 -0
  367. package/src/utils/android/ui-tree.ts +239 -0
  368. package/src/utils/android/utils.ts +16 -556
  369. package/src/utils/cli/idb/check-idb.ts +4 -4
  370. package/src/utils/cli/idb/idb-helper.ts +6 -8
  371. package/src/utils/cli/idb/install-idb.ts +15 -9
  372. package/src/utils/cli/ios/preflight-ios.ts +39 -31
  373. package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
  374. package/src/utils/ios/utils.ts +57 -61
  375. package/src/utils/java.ts +62 -43
  376. package/src/utils/resolve-device.ts +9 -6
  377. package/src/utils/ui/index.ts +28 -28
  378. package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
  379. package/test/device/automated/debugger/coroutine-support.ts +140 -0
  380. package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
  381. package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
  382. package/test/device/automated/debugger/runtime.android.test.ts +19 -3
  383. package/test/device/automated/interact/interaction.android.test.ts +56 -0
  384. package/test/device/automated/observe/system-trace.smoke.ts +1 -1
  385. package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
  386. package/test/fixtures/contracts/tool-definitions.json +3536 -0
  387. package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
  388. package/test/fixtures/coroutine-app/README.md +48 -0
  389. package/test/fixtures/coroutine-app/build.ts +35 -0
  390. package/test/fixtures/debugger-app/MainActivity.java +21 -0
  391. package/test/fixtures/debugger-app/MainActivity.kt +32 -0
  392. package/test/fixtures/debugger-app/README.md +52 -0
  393. package/test/fixtures/debugger-app/build.ts +25 -15
  394. package/test/fixtures/latency-journey-app/README.md +2 -0
  395. package/test/fixtures/readiness-app/README.md +2 -0
  396. package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
  397. package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
  398. package/test/unit/debugger/diagnostics.test.ts +80 -0
  399. package/test/unit/debugger/guards.test.ts +4 -0
  400. package/test/unit/debugger/preparation.test.ts +32 -0
  401. package/test/unit/debugger/protocol.test.ts +15 -0
  402. package/test/unit/debugger/sessions.test.ts +1 -1
  403. package/test/unit/interact/adjustment_service.test.ts +42 -0
  404. package/test/unit/interact/adjustment_verification.test.ts +35 -0
  405. package/test/unit/interact/clock_dependencies.test.ts +63 -0
  406. package/test/unit/interact/element_helpers.test.ts +19 -0
  407. package/test/unit/interact/legacy_wait_service.test.ts +17 -0
  408. package/test/unit/interact/state_comparison.test.ts +20 -0
  409. package/test/unit/interact/target_scope.test.ts +35 -0
  410. package/test/unit/network/get_network_activity.test.ts +1 -1
  411. package/test/unit/observe/screenshot-encoding.test.ts +30 -0
  412. package/test/unit/observe/snapshot-logs.test.ts +14 -0
  413. package/test/unit/quality/architecture.test.ts +60 -0
  414. package/test/unit/quality/code-quality.test.ts +58 -0
  415. package/test/unit/quality/interaction_boundaries.test.ts +24 -0
  416. package/test/unit/server/conditional_actions.test.ts +1 -1
  417. package/test/unit/server/conditional_schema.test.ts +2 -2
  418. package/test/unit/server/debugger-response.test.ts +63 -0
  419. package/test/unit/server/tool_definitions_parity.test.ts +8 -0
  420. package/dist/server/tool-handlers.js +0 -1000
  421. package/docs/ROADMAP.md +0 -646
  422. package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
  423. package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
  424. package/docs/specs/baseline-spec-v0.md +0 -312
  425. package/docs/specs/mcp-tooling-spec-v1.md +0 -446
  426. package/src/server/debugger-handlers.ts +0 -29
  427. package/src/server/tool-handlers.ts +0 -1008
  428. /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
  429. /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
  430. /package/dist/server/{system-trace-schema.js → schemas/system-trace-schema.js} +0 -0
  431. /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
  432. /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
@@ -0,0 +1,147 @@
1
+ import { debuggerSessions } from '../../debugger/sessions.js';
2
+ import { systemTraces } from '../../observe/system-trace.js';
3
+ import { currentEvidenceCorrelationId, bindSystemTrace, refreshJourneyEvidence, completeJourneyVerification } from '../../observe/evidence.js';
4
+ import { runWithBudget } from '../../utils/operation-budget.js';
5
+ import { journeySchema } from '../schemas/journey-schema.js';
6
+ import { resolveTargetDevice, withResolvedDevice } from '../../utils/resolve-device.js';
7
+ import { ToolsInteract } from '../../interact/index.js';
8
+ import { ToolsObserve } from '../../observe/index.js';
9
+ import { withObservationContext } from '../../observe/performance-metrics.js';
10
+ import { wrapResponse } from '../common.js';
11
+ import { payloadOf } from './payload.js';
12
+ import { handleStartApp } from './manage.js';
13
+ import { handleTapElement } from './input.js';
14
+ import { expectElementAbsent, expectFingerprint } from './journey-assertions.js';
15
+ async function assertStep(step, platform, deviceId) {
16
+ const assertion = step.assertion;
17
+ const timeoutMs = step.timeoutMs ?? 5000, pollIntervalMs = step.pollIntervalMs ?? 100;
18
+ if (assertion.kind === 'element_visible')
19
+ return ToolsInteract.expectElementVisibleHandler({ selector: assertion.selector, timeout_ms: timeoutMs, poll_interval_ms: pollIntervalMs, platform, deviceId });
20
+ if (assertion.kind === 'element_absent')
21
+ return expectElementAbsent({ selector: assertion.selector, timeoutMs, pollIntervalMs, platform, deviceId });
22
+ if (assertion.kind === 'screen_fingerprint')
23
+ return expectFingerprint({ fingerprint: assertion.fingerprint, timeoutMs, platform, deviceId });
24
+ if (assertion.kind === 'state_equals')
25
+ return ToolsInteract.expectStateHandler({ selector: assertion.selector, element_id: assertion.elementId, property: assertion.property, expected: assertion.expected, platform, deviceId, stabilization_window_ms: timeoutMs, poll_interval_ms: pollIntervalMs, exact_budget: true });
26
+ return { success: false, failure_code: 'INVALID_ASSERTION' };
27
+ }
28
+ async function executeStep(s, step, covered) {
29
+ const { platform, responseMode, defaults } = s.input, deviceId = s.deviceId;
30
+ try {
31
+ if (step.type === 'tap')
32
+ return payloadOf(await handleTapElement({ ...defaults, ...step, platform, deviceId, responseMode, _journey: true, _stepId: step.id, verificationMode: covered ? 'none' : step.verificationMode ?? defaults?.verificationMode ?? 'full' }));
33
+ if (step.type === 'wait') {
34
+ const result = (await withObservationContext({ stepId: step.id, purpose: 'target' }, () => ToolsInteract.waitForUIHandler({ selector: step.selector, condition: step.condition ?? 'exists', timeout_ms: step.timeoutMs ?? 5000, poll_interval_ms: step.pollIntervalMs ?? 100, platform, deviceId })));
35
+ return { success: result.status === 'success', ...result };
36
+ }
37
+ if (step.type === 'assert')
38
+ return await withObservationContext({ stepId: step.id, purpose: 'assertion' }, () => runWithBudget(step.timeoutMs ?? 5000, () => assertStep(step, platform, deviceId)));
39
+ if (step.type === 'start_app')
40
+ return payloadOf(await handleStartApp({ ...defaults, platform, deviceId, appId: step.appId, responseMode, _journey: true, _stepId: step.id, verificationMode: step.verificationMode ?? defaults?.verificationMode ?? 'full' }));
41
+ return { success: false, failure_code: 'UNSUPPORTED_JOURNEY_STEP' };
42
+ }
43
+ catch (error) {
44
+ return { success: false, failure_code: step.type === 'assert' ? 'ASSERTION_TIMEOUT' : 'STEP_FAILED', error: error instanceof Error ? error.message : String(error) };
45
+ }
46
+ }
47
+ function lightVerification(s, step, result, covered, success) {
48
+ if (covered) {
49
+ result.verification = { mode: 'light', status: success ? 'unavailable' :
50
+ (['mismatch', 'unavailable'].includes(result.condition?.status ?? '') ? 'not_requested' : 'failed') };
51
+ if (success) {
52
+ result.verification_diagnostic = { code: 'VERIFICATION_UNAVAILABLE', retryable: true };
53
+ s.pendingLightStep = s.results.length;
54
+ }
55
+ }
56
+ if (step.type !== 'assert' || s.pendingLightStep === null)
57
+ return;
58
+ const pending = s.results[s.pendingLightStep].result;
59
+ if (typeof result.observed?.matched_count === 'number') {
60
+ pending.verification.status = 'observed';
61
+ if (pending.evidence)
62
+ completeJourneyVerification(pending.evidence.action_id);
63
+ delete pending.verification_diagnostic;
64
+ const delta = ToolsObserve.getLatestStateDelta(s.input.platform, s.deviceId);
65
+ if (delta) {
66
+ pending.state_delta = delta;
67
+ pending.state_delta_available = true;
68
+ }
69
+ }
70
+ s.pendingLightStep = null;
71
+ }
72
+ function compactAssertion(result, success) {
73
+ const errorCode = typeof result.error === 'object' ? result.error?.code : undefined;
74
+ return { success, ...(result.failure_code ? { failure_code: result.failure_code } : {}),
75
+ ...(errorCode ? { failure_code: errorCode } : {}),
76
+ ...(result.observed?.matched_count !== undefined ? { matched_count: result.observed.matched_count } : {}),
77
+ ...(result.observed_fingerprint !== undefined ? { matched: success } : {}),
78
+ ...(result.actual_state ? { observed: result.actual_state } : {}) };
79
+ }
80
+ function coveredByAssertion(s, step, next) {
81
+ const verification = ('verificationMode' in step ? step.verificationMode : undefined) ?? s.input.defaults?.verificationMode ?? 'full';
82
+ return step.type === 'tap' && s.input.responseMode === 'compact' && verification === 'light' && next?.type === 'assert' && next.assertion.kind === 'element_visible';
83
+ }
84
+ async function runSteps(s) {
85
+ const { platform, responseMode, steps } = s.input;
86
+ for (let index = 0; index < steps.length; index++) {
87
+ const step = steps[index];
88
+ if (platform === 'android' && debuggerSessions.paused(s.deviceId)) {
89
+ s.results.push({ id: step.id, type: step.type, status: 'failed', success: false, result: { success: false, failure_code: 'DEBUGGER_PAUSED', dispatch_started: false, delivery_status: 'not_delivered' } });
90
+ s.stoppedAt = step.id;
91
+ break;
92
+ }
93
+ const covered = coveredByAssertion(s, step, steps[index + 1]), started = performance.now();
94
+ if (s.stoppedAt) {
95
+ s.results.push({ id: step.id, type: step.type, status: 'not_run', success: false, reason: 'PREVIOUS_STEP_FAILED', result: null, timing: { total_ms: 0 } });
96
+ continue;
97
+ }
98
+ let result = await executeStep(s, step, covered);
99
+ if (platform === 'android' && debuggerSessions.paused(s.deviceId)) {
100
+ result.success = false;
101
+ result.failure_code = 'DEBUGGER_PAUSED';
102
+ result.retryable = false;
103
+ }
104
+ if (step.type === 'assert' && !result?.success && /TIMEOUT/.test(result?.failure_code ?? ''))
105
+ result.failure_code = 'ASSERTION_TIMEOUT';
106
+ const success = !!result?.success;
107
+ lightVerification(s, step, result, covered, success);
108
+ if (responseMode === 'compact' && ['wait', 'assert'].includes(step.type))
109
+ result = compactAssertion(result, success);
110
+ s.results.push({ id: step.id, type: step.type, status: success ? 'passed' : 'failed', success, result, timing: { total_ms: performance.now() - started } });
111
+ if (!success)
112
+ s.stoppedAt = step.id;
113
+ }
114
+ }
115
+ async function journeyResponse(s, started, finishTrace) {
116
+ const { platform, captureOnFailure, responseMode } = s.input;
117
+ const failureSnapshot = s.stoppedAt && captureOnFailure && responseMode === 'debug'
118
+ ? await ToolsObserve.captureDebugSnapshotHandler({ platform, deviceId: s.deviceId, includeScreenshot: false }).catch(() => null) : undefined;
119
+ const timing = {};
120
+ for (const step of s.results)
121
+ for (const [key, value] of Object.entries(step.result?.timing ?? {})) {
122
+ if (key !== 'total_ms' && typeof value === 'number')
123
+ timing[key] = (timing[key] ?? 0) + value;
124
+ }
125
+ refreshJourneyEvidence(s.results);
126
+ return wrapResponse({ ...(finishTrace ? { system_trace: await finishTrace() } : {}), success: s.stoppedAt === null, stopped_at_step_id: s.stoppedAt, steps: s.results, ...(failureSnapshot ? { failure_snapshot: failureSnapshot } : {}), timing: responseMode === 'compact' ? { total_ms: performance.now() - started } : { ...timing, total_ms: performance.now() - started } }, responseMode === 'debug');
127
+ }
128
+ export async function handleRunJourney(args) {
129
+ const parsed = journeySchema.safeParse(args);
130
+ if (!parsed.success)
131
+ throw new Error(`INVALID_ARGUMENT: ${parsed.error.message}`);
132
+ const device = await resolveTargetDevice({ platform: parsed.data.platform, deviceId: parsed.data.deviceId });
133
+ const finishTrace = parsed.data.systemTraceId ? await systemTraces.link(parsed.data.systemTraceId, device, currentEvidenceCorrelationId()) : undefined;
134
+ if (parsed.data.systemTraceId)
135
+ bindSystemTrace(parsed.data.systemTraceId);
136
+ try {
137
+ return await withResolvedDevice(device, async () => {
138
+ const started = performance.now();
139
+ const state = { input: parsed.data, deviceId: device.id, results: [], stoppedAt: null, pendingLightStep: null };
140
+ await runSteps(state);
141
+ return journeyResponse(state, started, finishTrace);
142
+ });
143
+ }
144
+ finally {
145
+ await finishTrace?.();
146
+ }
147
+ }
@@ -0,0 +1,119 @@
1
+ import { measure } from '../../utils/timing.js';
2
+ import { notifyActionStart } from '../../interact/conditional-action.js';
3
+ import { AndroidManage, iOSManage, ToolsManage } from '../../manage/index.js';
4
+ import { buildActionExecutionResult, captureActionFingerprint, getNumberArg, getStringArg, inferGenericFailure, requireStringArg, wrapResponse } from '../common.js';
5
+ import { interactionControls, actionHandler } from './action-execution.js';
6
+ export async function executeStartApp(args) {
7
+ const platform = requireStringArg(args, 'platform');
8
+ const appId = requireStringArg(args, 'appId');
9
+ const deviceId = getStringArg(args, 'deviceId');
10
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
11
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
12
+ notifyActionStart();
13
+ const res = await measure('dispatch_ms', () => (platform === 'android' ? new AndroidManage().startApp(appId, deviceId, actionTimeoutMs ?? (verificationMode === 'full' && getStringArg(args, 'responseMode') === undefined && args._journey !== true ? undefined : 10000)) : new iOSManage().startApp(appId, deviceId, verificationMode === 'full')));
14
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
15
+ const result = buildActionExecutionResult({
16
+ actionType: 'start_app',
17
+ sourceModule: 'server',
18
+ device: res.device,
19
+ selector: { appId },
20
+ success: !!res.appStarted,
21
+ uiFingerprintBefore,
22
+ uiFingerprintAfter,
23
+ failure: res.appStarted ? undefined : inferGenericFailure(res.error),
24
+ details: {
25
+ launch_time_ms: res.launchTimeMs,
26
+ ...(typeof res.output === 'string' ? { output: res.output } : {}),
27
+ ...(res.device ? { device_id: res.device.id } : {}),
28
+ ...(typeof res.error === 'string' ? { error: res.error } : {}),
29
+ ...(res.diagnostics ? { diagnostics: res.diagnostics } : {}),
30
+ ...(res.observedApp ? { observed_app: res.observedApp } : {})
31
+ }
32
+ });
33
+ return wrapResponse(result);
34
+ }
35
+ export async function handleTerminateApp(args) {
36
+ const platform = requireStringArg(args, 'platform');
37
+ const appId = requireStringArg(args, 'appId');
38
+ const deviceId = getStringArg(args, 'deviceId');
39
+ const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId));
40
+ const response = { device: res.device, appTerminated: res.appTerminated };
41
+ return wrapResponse(response);
42
+ }
43
+ export async function executeRestartApp(args) {
44
+ const platform = requireStringArg(args, 'platform');
45
+ const appId = requireStringArg(args, 'appId');
46
+ const deviceId = getStringArg(args, 'deviceId');
47
+ const { verificationMode, actionTimeoutMs } = interactionControls(args);
48
+ const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
49
+ notifyActionStart();
50
+ const bounded = args._journey === true || verificationMode !== 'full' || args.responseMode !== undefined || actionTimeoutMs !== undefined;
51
+ const res = await measure('dispatch_ms', () => (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId, bounded, actionTimeoutMs ?? 15000) : new iOSManage().restartApp(appId, deviceId, verificationMode === 'full', bounded, actionTimeoutMs ?? 15000)));
52
+ const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
53
+ const result = buildActionExecutionResult({
54
+ actionType: 'restart_app',
55
+ sourceModule: 'server',
56
+ device: res.device,
57
+ selector: { appId },
58
+ success: !!res.appRestarted,
59
+ uiFingerprintBefore,
60
+ uiFingerprintAfter,
61
+ failure: res.appRestarted ? undefined : inferGenericFailure(res.error),
62
+ details: {
63
+ launch_time_ms: res.launchTimeMs,
64
+ ...(typeof res.output === 'string' ? { output: res.output } : {}),
65
+ ...(typeof res.terminatedBeforeRestart === 'boolean' ? { terminated_before_restart: res.terminatedBeforeRestart } : {}),
66
+ ...(typeof res.terminateError === 'string' ? { terminate_error: res.terminateError } : {}),
67
+ ...(typeof res.error === 'string' ? { error: res.error } : {}),
68
+ ...(res.diagnostics ? { diagnostics: res.diagnostics } : {}),
69
+ ...(res.observedApp ? { observed_app: res.observedApp } : {})
70
+ }
71
+ });
72
+ return wrapResponse(result);
73
+ }
74
+ export async function handleResetAppData(args) {
75
+ const platform = requireStringArg(args, 'platform');
76
+ const appId = requireStringArg(args, 'appId');
77
+ const deviceId = getStringArg(args, 'deviceId');
78
+ const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId));
79
+ const response = { device: res.device, dataCleared: res.dataCleared };
80
+ return wrapResponse(response);
81
+ }
82
+ export async function handleInstallApp(args) {
83
+ const platform = requireStringArg(args, 'platform');
84
+ const projectType = requireStringArg(args, 'projectType');
85
+ const appPath = requireStringArg(args, 'appPath');
86
+ const deviceId = getStringArg(args, 'deviceId');
87
+ const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId, projectType });
88
+ const response = {
89
+ device: res.device,
90
+ installed: res.installed,
91
+ output: res.output,
92
+ error: res.error
93
+ };
94
+ return wrapResponse(response);
95
+ }
96
+ export async function handleBuildApp(args) {
97
+ const platform = requireStringArg(args, 'platform');
98
+ const projectType = requireStringArg(args, 'projectType');
99
+ const projectPath = requireStringArg(args, 'projectPath');
100
+ const variant = getStringArg(args, 'variant');
101
+ const res = await ToolsManage.buildAppHandler({ platform, projectPath, variant, projectType });
102
+ return wrapResponse(res);
103
+ }
104
+ export async function handleBuildAndInstall(args) {
105
+ const platform = requireStringArg(args, 'platform');
106
+ const projectType = requireStringArg(args, 'projectType');
107
+ const projectPath = requireStringArg(args, 'projectPath');
108
+ const deviceId = getStringArg(args, 'deviceId');
109
+ const timeout = getNumberArg(args, 'timeout');
110
+ const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout, projectType });
111
+ return {
112
+ content: [
113
+ { type: 'text', text: res.ndjson },
114
+ { type: 'text', text: JSON.stringify(res.result, null, 2) }
115
+ ]
116
+ };
117
+ }
118
+ export const handleStartApp = actionHandler('start_app', executeStartApp);
119
+ export const handleRestartApp = actionHandler('restart_app', executeRestartApp);
@@ -0,0 +1,95 @@
1
+ import { ToolsObserve } from '../../observe/index.js';
2
+ import { withObservationContext } from '../../observe/performance-metrics.js';
3
+ import { getBooleanArg, getNumberArg, getStringArg, requireStringArg, wrapResponse } from '../common.js';
4
+ export async function handleGetLogs(args) {
5
+ const platform = requireStringArg(args, 'platform');
6
+ const appId = getStringArg(args, 'appId');
7
+ const deviceId = getStringArg(args, 'deviceId');
8
+ const pid = getNumberArg(args, 'pid');
9
+ const tag = getStringArg(args, 'tag');
10
+ const level = getStringArg(args, 'level');
11
+ const contains = getStringArg(args, 'contains');
12
+ const since_seconds = getNumberArg(args, 'since_seconds');
13
+ const limit = getNumberArg(args, 'limit');
14
+ const lines = getNumberArg(args, 'lines');
15
+ const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines });
16
+ const filtered = !!(pid || tag || level || contains || since_seconds || appId);
17
+ return {
18
+ content: [
19
+ { type: 'text', text: JSON.stringify({ device: res.device, result: { count: res.logCount, filtered, crashLines: (res.crashLines || []), source: res.source, meta: res.meta || {} } }, null, 2) },
20
+ { type: 'text', text: JSON.stringify({ logs: res.logs }, null, 2) }
21
+ ]
22
+ };
23
+ }
24
+ export async function handleCaptureScreenshot(args) {
25
+ const platform = requireStringArg(args, 'platform');
26
+ const deviceId = getStringArg(args, 'deviceId');
27
+ const started = performance.now();
28
+ const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId });
29
+ const screenshotMs = performance.now() - started;
30
+ const mime = res.screenshot_mime || 'image/png';
31
+ const content = [
32
+ { type: 'text', text: JSON.stringify({ device: res.device, result: { resolution: res.resolution, mimeType: mime }, timing: { total_ms: screenshotMs, screenshot_ms: screenshotMs } }, null, 2) },
33
+ { type: 'image', data: res.screenshot, mimeType: mime }
34
+ ];
35
+ if (res.screenshot_fallback) {
36
+ content.push({ type: 'text', text: JSON.stringify({ note: 'JPEG fallback included for compatibility', mimeType: res.screenshot_fallback_mime || 'image/jpeg' }) });
37
+ content.push({ type: 'image', data: res.screenshot_fallback, mimeType: res.screenshot_fallback_mime || 'image/jpeg' });
38
+ }
39
+ return { content };
40
+ }
41
+ export async function handleCaptureDebugSnapshot(args) {
42
+ const reason = getStringArg(args, 'reason');
43
+ const includeLogs = getBooleanArg(args, 'includeLogs');
44
+ const logLines = getNumberArg(args, 'logLines');
45
+ const platform = getStringArg(args, 'platform');
46
+ const appId = getStringArg(args, 'appId');
47
+ const deviceId = getStringArg(args, 'deviceId');
48
+ const sessionId = getStringArg(args, 'sessionId');
49
+ const includeScreenshot = getBooleanArg(args, 'includeScreenshot');
50
+ const res = await ToolsObserve.captureDebugSnapshotHandler({ reason, includeLogs, includeScreenshot, logLines, platform, appId, deviceId, sessionId });
51
+ return wrapResponse(res);
52
+ }
53
+ export async function handleGetUITree(args) {
54
+ const platform = requireStringArg(args, 'platform');
55
+ const deviceId = getStringArg(args, 'deviceId');
56
+ const responseMode = getStringArg(args, 'responseMode') ?? 'debug';
57
+ const sinceSnapshotRevision = getNumberArg(args, 'sinceSnapshotRevision');
58
+ const metricsStepId = process.env.MOBILE_DEBUG_MCP_UI_TREE_METRICS === '1' ? getStringArg(args, '_metricsStepId') : undefined;
59
+ const res = await withObservationContext({ stepId: metricsStepId, purpose: metricsStepId?.includes('post-response') ? 'assertion' : 'debug' }, () => ToolsObserve.getUITreeHandler({ platform, deviceId, responseMode, sinceSnapshotRevision }));
60
+ return wrapResponse(res);
61
+ }
62
+ export async function handleGetCurrentScreen(args) {
63
+ const deviceId = getStringArg(args, 'deviceId');
64
+ const res = await ToolsObserve.getCurrentScreenHandler({ deviceId });
65
+ return wrapResponse(res);
66
+ }
67
+ export async function handleGetScreenFingerprint(args) {
68
+ const platform = getStringArg(args, 'platform');
69
+ const deviceId = getStringArg(args, 'deviceId');
70
+ const res = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId });
71
+ return wrapResponse(res);
72
+ }
73
+ export async function handleStartLogStream(args) {
74
+ const platform = getStringArg(args, 'platform') ?? 'android';
75
+ const packageName = requireStringArg(args, 'packageName');
76
+ const level = getStringArg(args, 'level') ?? 'error';
77
+ const sessionId = getStringArg(args, 'sessionId');
78
+ const deviceId = getStringArg(args, 'deviceId');
79
+ const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId });
80
+ return wrapResponse(res);
81
+ }
82
+ export async function handleReadLogStream(args) {
83
+ const platform = getStringArg(args, 'platform');
84
+ const sessionId = getStringArg(args, 'sessionId');
85
+ const limit = getNumberArg(args, 'limit');
86
+ const since = getStringArg(args, 'since');
87
+ const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since });
88
+ return wrapResponse(res);
89
+ }
90
+ export async function handleStopLogStream(args) {
91
+ const platform = getStringArg(args, 'platform');
92
+ const sessionId = getStringArg(args, 'sessionId');
93
+ const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId });
94
+ return wrapResponse(res);
95
+ }
@@ -0,0 +1,4 @@
1
+ export function payloadOf(response) {
2
+ const text = response.content.find(item => item.type === 'text')?.text;
3
+ return text ? JSON.parse(text) : null;
4
+ }
@@ -0,0 +1,160 @@
1
+ import { handleDebugger } from './debugger-handlers.js';
2
+ import { debuggerContracts } from '../schemas/debugger-schema.js';
3
+ import { debuggerSessions } from '../../debugger/sessions.js';
4
+ import { withDebuggerGuard, guardDispatched } from '../../debugger/guard.js';
5
+ import { TraceError } from '../../observe/system-trace.js';
6
+ import { systemTraceTools } from '../schemas/system-trace-schema.js';
7
+ import { withTiming } from '../../utils/timing.js';
8
+ import { readinessSchema, readinessWaitSchema } from '../schemas/readiness-schema.js';
9
+ import { getAppReadiness } from '../../observe/readiness-service.js';
10
+ import { validateExpectedState } from '../schemas/condition-schema.js';
11
+ import { resolveTargetDevice, withResolvedDevice } from '../../utils/resolve-device.js';
12
+ import { wrapResponse, wrapToolError } from '../common.js';
13
+ import { handleTerminateApp, handleResetAppData, handleInstallApp, handleBuildApp, handleBuildAndInstall, handleStartApp, handleRestartApp } from './manage.js';
14
+ import { handleGetLogs, handleCaptureScreenshot, handleCaptureDebugSnapshot, handleGetUITree, handleGetCurrentScreen, handleGetScreenFingerprint, handleStartLogStream, handleReadLogStream, handleStopLogStream } from './observe.js';
15
+ import { handleWaitForScreenChange, handleExpectScreen, handleExpectElementVisible, handleExpectState, handleAdjustControl, handleWaitForUI, handleWaitForUIChange, handleFindElement } from './interact.js';
16
+ import { handleTap, handleTapElement, handleSwipe, handleScrollToElement, handleTypeText, handlePressBack } from './input.js';
17
+ import { handleRunJourney } from './journey.js';
18
+ import { handleListDevices, handleGetSystemStatus, handleClassifyActionOutcome, handleGetNetworkActivity, handleSystemTrace } from './diagnostic.js';
19
+ import { correlatedHandler } from './action-execution.js';
20
+ const systemTraceToolNames = new Set(systemTraceTools.map(tool => tool.name));
21
+ export const toolHandlers = {
22
+ ...Object.fromEntries(systemTraceTools.map(({ name }) => [name, (args) => handleSystemTrace(name, args)])),
23
+ get_app_readiness: async (args) => {
24
+ const parsed = readinessSchema.safeParse(args);
25
+ if (!parsed.success)
26
+ throw new Error('INVALID_ARGUMENT: invalid readiness arguments');
27
+ return wrapResponse(await getAppReadiness(parsed.data));
28
+ },
29
+ wait_for_app_ready: async (args) => {
30
+ const parsed = readinessWaitSchema.safeParse(args);
31
+ if (!parsed.success)
32
+ throw new Error('INVALID_ARGUMENT: invalid readiness arguments');
33
+ return wrapResponse(await getAppReadiness(parsed.data, true));
34
+ },
35
+ start_app: handleStartApp,
36
+ terminate_app: handleTerminateApp,
37
+ restart_app: handleRestartApp,
38
+ reset_app_data: handleResetAppData,
39
+ install_app: handleInstallApp,
40
+ build_app: handleBuildApp,
41
+ build_and_install: handleBuildAndInstall,
42
+ get_logs: handleGetLogs,
43
+ list_devices: handleListDevices,
44
+ get_system_status: handleGetSystemStatus,
45
+ capture_screenshot: handleCaptureScreenshot,
46
+ capture_debug_snapshot: handleCaptureDebugSnapshot,
47
+ get_ui_tree: handleGetUITree,
48
+ get_current_screen: handleGetCurrentScreen,
49
+ get_screen_fingerprint: handleGetScreenFingerprint,
50
+ wait_for_screen_change: handleWaitForScreenChange,
51
+ wait_for_ui_change: handleWaitForUIChange,
52
+ expect_screen: handleExpectScreen,
53
+ expect_element_visible: handleExpectElementVisible,
54
+ expect_state: handleExpectState,
55
+ adjust_control: handleAdjustControl,
56
+ wait_for_ui: handleWaitForUI,
57
+ find_element: handleFindElement,
58
+ tap: handleTap,
59
+ tap_element: handleTapElement,
60
+ run_journey: handleRunJourney,
61
+ swipe: handleSwipe,
62
+ scroll_to_element: handleScrollToElement,
63
+ type_text: handleTypeText,
64
+ press_back: handlePressBack,
65
+ start_log_stream: handleStartLogStream,
66
+ read_log_stream: handleReadLogStream,
67
+ stop_log_stream: handleStopLogStream,
68
+ classify_action_outcome: handleClassifyActionOutcome,
69
+ get_network_activity: handleGetNetworkActivity
70
+ };
71
+ for (const name of ['capture_screenshot', 'get_ui_tree', 'get_screen_fingerprint', 'capture_debug_snapshot', 'get_logs', 'read_log_stream', 'run_journey', 'adjust_control']) {
72
+ toolHandlers[name] = correlatedHandler(name, toolHandlers[name], name === 'adjust_control', name === 'run_journey');
73
+ }
74
+ async function dispatchGuarded(device, args, handler, addTiming) {
75
+ return withResolvedDevice(device, () => withDebuggerGuard('android', device.id, async () => {
76
+ const before = debuggerSessions.paused(device.id);
77
+ if (before)
78
+ return wrapResponse({ success: false, failure_code: 'DEBUGGER_PAUSED', delivery_status: 'not_delivered', dispatch_started: false, debugger: before });
79
+ let response;
80
+ try {
81
+ response = await handler(args);
82
+ }
83
+ catch (error) {
84
+ if (!debuggerSessions.paused(device.id))
85
+ throw error;
86
+ response = wrapResponse({ success: false });
87
+ }
88
+ const pause = debuggerSessions.paused(device.id);
89
+ if (pause) {
90
+ const item = response.content.find(item => item.type === 'text');
91
+ if (item?.text) {
92
+ const payload = JSON.parse(item.text);
93
+ Object.assign(payload, { success: false, failure_code: 'DEBUGGER_PAUSED', retryable: false,
94
+ dispatch_started: guardDispatched(), delivery_status: payload.success ? 'delivered' : guardDispatched() ? 'unknown' : 'not_delivered',
95
+ verification: { status: 'blocked', reason: 'DEBUGGER_PAUSED' }, debugger: pause });
96
+ item.text = JSON.stringify(payload);
97
+ }
98
+ }
99
+ return addTiming(response);
100
+ }));
101
+ }
102
+ function attachPausedObservation(name, args, response) {
103
+ if (['capture_screenshot', 'get_ui_tree', 'get_screen_fingerprint', 'capture_debug_snapshot', 'get_logs', 'get_current_screen', 'read_log_stream'].includes(name) && args.platform !== 'ios') {
104
+ const item = response.content.find(item => item.type === 'text');
105
+ if (item?.text) {
106
+ try {
107
+ const payload = JSON.parse(item.text);
108
+ const device = payload.device ?? payload.evidence?.device;
109
+ const id = device?.id ?? args.deviceId;
110
+ const pause = id && device?.platform !== 'ios' ? debuggerSessions.paused(String(id)) : null;
111
+ if (pause)
112
+ item.text = JSON.stringify({ ...payload, debugger: pause });
113
+ }
114
+ catch { /* Non-JSON observation */ }
115
+ }
116
+ }
117
+ }
118
+ export async function handleToolCall(name, args = {}, signal) {
119
+ if (Object.hasOwn(debuggerContracts, name))
120
+ return handleDebugger(name, args, signal);
121
+ const handler = toolHandlers[name];
122
+ if (!handler)
123
+ throw new Error(`Unknown tool: ${name}`);
124
+ const started = performance.now();
125
+ const measured = {};
126
+ const addTiming = (response) => {
127
+ if (!['start_app', 'restart_app', 'tap', 'tap_element', 'swipe', 'scroll_to_element', 'type_text', 'press_back', 'wait_for_ui', 'get_ui_tree', 'capture_screenshot', 'capture_debug_snapshot'].includes(name))
128
+ return response;
129
+ const item = response.content.find(item => item.type === 'text');
130
+ if (item?.text) {
131
+ try {
132
+ const value = JSON.parse(item.text);
133
+ value.timing = { ...measured, ...value.timing, total_ms: performance.now() - started };
134
+ item.text = JSON.stringify(value);
135
+ }
136
+ catch { /* Non-JSON text responses retain their original content. */ }
137
+ }
138
+ return response;
139
+ };
140
+ return withTiming(measured, async () => {
141
+ try {
142
+ if (!systemTraceToolNames.has(name))
143
+ validateExpectedState(name, args);
144
+ const guarded = ['start_app', 'terminate_app', 'restart_app', 'reset_app_data', 'install_app', 'build_and_install', 'tap', 'tap_element', 'swipe', 'scroll_to_element', 'type_text', 'press_back', 'adjust_control', 'run_journey', 'wait_for_ui', 'wait_for_ui_change', 'wait_for_screen_change', 'expect_screen', 'expect_element_visible', 'expect_state'].includes(name);
145
+ if (guarded && args.platform !== 'ios' && debuggerSessions.hasActiveSessions()) {
146
+ const device = await resolveTargetDevice({ platform: 'android', deviceId: args.deviceId });
147
+ return dispatchGuarded(device, args, handler, addTiming);
148
+ }
149
+ const response = await handler(args);
150
+ attachPausedObservation(name, args, response);
151
+ return addTiming(response);
152
+ }
153
+ catch (error) {
154
+ if (error instanceof TraceError)
155
+ return { ...wrapResponse({ error: { tool: name, code: error.code, message: error.message } }), isError: true };
156
+ console.error(`Error executing tool ${name}:`, error);
157
+ return addTiming(wrapToolError(name, error));
158
+ }
159
+ });
160
+ }
@@ -0,0 +1,2 @@
1
+ // Compatibility export for existing server consumers.
2
+ export * from '../../contracts/condition.js';
@@ -26,17 +26,22 @@ function jsonField(schema) {
26
26
  return { type: 'string', enum: [schema.value] };
27
27
  if (schema instanceof z.ZodEnum)
28
28
  return { type: 'string', enum: schema.options };
29
- if (schema instanceof z.ZodString)
30
- return { type: 'string', minLength: 1, maxLength: 512, ...Object.assign({}, ...schema._def.checks.filter(c => c.kind === 'regex').map(c => ({ pattern: c.regex.source }))) };
29
+ if (schema instanceof z.ZodString) {
30
+ const json = z.toJSONSchema(schema);
31
+ return { type: 'string', minLength: 1, maxLength: 512, ...(json.pattern ? { pattern: json.pattern } : {}) };
32
+ }
31
33
  if (schema instanceof z.ZodNumber)
32
34
  return { type: 'integer', minimum: schema.minValue, maximum: schema.maxValue };
33
35
  throw new Error('Unsupported debugger field schema');
34
36
  }
35
37
  export const debuggerContracts = Object.fromEntries(Object.entries(specs).map(([name, spec]) => [name, {
36
- ...spec, schema: z.object({ ...spec.fields, timeoutMs: timeout.default(name === 'wait_for_debugger_event' ? 1000 : 10000) }).strict()
38
+ ...spec, schema: z.object({ ...spec.fields, responseMode: z.enum(['compact', 'debug']).default('debug'), timeoutMs: timeout.default(name === 'wait_for_debugger_event' ? 1000 : 10000) }).strict()
37
39
  }]));
38
- export const debuggerTools = Object.entries(debuggerContracts).map(([name, spec]) => ({ name, description: spec.description,
39
- inputSchema: { type: 'object', additionalProperties: false,
40
+ export const debuggerTools = Object.entries(debuggerContracts).map(([name, spec]) => ({
41
+ name, description: spec.description,
42
+ inputSchema: {
43
+ type: 'object', additionalProperties: false,
40
44
  required: Object.entries(spec.fields).filter(([, field]) => !field.isOptional()).map(([key]) => key),
41
- properties: { ...Object.fromEntries(Object.entries(spec.fields).map(([key, field]) => [key, jsonField(field)])), timeoutMs: { type: 'integer', minimum: 100, maximum: 30000, default: name === 'wait_for_debugger_event' ? 1000 : 10000 } } }
45
+ properties: { responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' }, ...Object.fromEntries(Object.entries(spec.fields).map(([key, field]) => [key, jsonField(field)])), timeoutMs: { type: 'integer', minimum: 100, maximum: 30000, default: name === 'wait_for_debugger_event' ? 1000 : 10000 } }
46
+ }
42
47
  }));
@@ -1,19 +1,12 @@
1
+ import { verificationModeSchema, responseModeSchema, verificationModeJson } from '../../contracts/execution-controls.js';
2
+ import { waitForSchema as waitFor, targetFields as target, exactlyOneTarget, targetJson, oneTargetJson } from '../../contracts/target.js';
3
+ export { tapElementControlsSchema } from '../../contracts/target.js';
4
+ import { selectorSchema as selector, selectorJson } from '../../contracts/selector.js';
1
5
  import { z } from 'zod';
2
6
  import { expectedStateSchema, expectedStateJson } from './condition-schema.js';
3
- const selector = z.object({
4
- text: z.string().min(1).optional(), resource_id: z.string().min(1).optional(),
5
- accessibility_id: z.string().min(1).optional(), contains: z.boolean().optional()
6
- }).strict().refine(value => !!(value.text || value.resource_id || value.accessibility_id), 'Selector requires a target');
7
- const verificationMode = z.enum(['none', 'light', 'full']);
7
+ const verificationMode = verificationModeSchema;
8
8
  const timeoutMs = z.number().int().min(100).max(60000).optional();
9
9
  const pollIntervalMs = z.number().int().min(50).max(1000).optional();
10
- const waitFor = z.object({
11
- condition: z.enum(['exists', 'visible', 'clickable']).optional(),
12
- timeoutMs: z.number().int().min(100).max(10000).optional(), pollIntervalMs,
13
- match: z.object({ index: z.number().int().nonnegative().optional() }).strict().optional()
14
- }).strict();
15
- const target = { selector: selector.optional(), elementId: z.string().min(1).optional() };
16
- const exactlyOneTarget = (value) => Number(!!value.selector) + Number(!!value.elementId) === 1;
17
10
  const assertion = z.union([
18
11
  z.object({ kind: z.literal('element_visible'), selector }).strict(),
19
12
  z.object({ kind: z.literal('element_absent'), selector }).strict(),
@@ -27,23 +20,15 @@ const step = z.union([
27
20
  z.object({ id, type: z.literal('wait'), selector, condition: z.enum(['exists', 'not_exists', 'visible', 'clickable']).optional(), timeoutMs, pollIntervalMs }).strict(),
28
21
  z.object({ id, type: z.literal('assert'), assertion, timeoutMs, pollIntervalMs }).strict()
29
22
  ]);
30
- export const tapElementControlsSchema = z.object({
31
- elementId: z.string().min(1).optional(), selector: selector.optional(), waitFor: waitFor.optional()
32
- }).strict().refine(value => Number(!!value.elementId) + Number(!!value.selector) === 1 && (!value.elementId || !value.waitFor));
33
23
  export const journeySchema = z.object({
34
24
  platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional(),
35
25
  systemTraceId: z.string().uuid().optional(),
36
- responseMode: z.enum(['compact', 'debug']).default('compact'), captureOnFailure: z.boolean().default(false),
26
+ responseMode: responseModeSchema.default('compact'), captureOnFailure: z.boolean().default(false),
37
27
  defaults: z.object({ verificationMode: verificationMode.optional(), actionTimeoutMs: z.number().int().finite().optional(), verificationTimeoutMs: z.number().int().finite().optional() }).strict().optional(),
38
28
  steps: z.array(step).min(1).max(50)
39
29
  }).strict().refine(value => new Set(value.steps.map(step => step.id)).size === value.steps.length, 'Step IDs must be unique');
40
- const selectorJson = { type: 'object', additionalProperties: false, properties: {
41
- text: { type: 'string', minLength: 1 }, resource_id: { type: 'string', minLength: 1 }, accessibility_id: { type: 'string', minLength: 1 }, contains: { type: 'boolean' }
42
- }, anyOf: ['text', 'resource_id', 'accessibility_id'].map(key => ({ required: [key] })) };
43
- const targetJson = { selector: selectorJson, elementId: { type: 'string', minLength: 1 } };
44
- const oneTargetJson = [{ required: ['selector'], not: { required: ['elementId'] } }, { required: ['elementId'], not: { required: ['selector'] } }];
45
30
  const intervalJson = { timeoutMs: { type: 'integer', minimum: 100, maximum: 60000 }, pollIntervalMs: { type: 'integer', minimum: 50, maximum: 1000 } };
46
- const modeJson = { type: 'string', enum: ['none', 'light', 'full'] };
31
+ const modeJson = verificationModeJson;
47
32
  const objectJson = (properties, required, extra = {}) => ({ type: 'object', additionalProperties: false, properties, required, ...extra });
48
33
  export const journeyStepsJson = {
49
34
  type: 'array', minItems: 1, maxItems: 50, items: { oneOf: [