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
@@ -1,228 +1,18 @@
1
- import { DeviceInfo, UIElement, UIElementSemanticMetadata, UIElementState, UIResolutionSelector, SelectorConfidence } from "../../types.js"
2
- import { promises as fsPromises, existsSync } from 'fs'
1
+ import { DeviceInfo } from "../../types.js"
2
+ import { promises as fsPromises } from 'fs'
3
3
  import path from 'path'
4
- import { detectJavaHome } from '../java.js'
5
- import { execCmd } from '../exec.js'
6
- import { spawnSync } from 'child_process'
7
- import { checkGradle } from '../../system/gradle.js'
8
-
9
- function findInPath(cmd: string): string | null {
10
- try {
11
- // prefer command -v for POSIX
12
- const res = spawnSync('command', ['-v', cmd], { encoding: 'utf8' })
13
- if (res.status === 0 && res.stdout) return res.stdout.trim()
14
- } catch (e: unknown) { console.debug(`[findInPath] command -v ${cmd} failed: ${String(e)}`) }
15
- try {
16
- const res = spawnSync('which', [cmd], { encoding: 'utf8' })
17
- if (res.status === 0 && res.stdout) return res.stdout.trim()
18
- } catch (e: unknown) { console.debug(`[findInPath] which ${cmd} failed: ${String(e)}`) }
19
- return null
20
- }
21
-
22
- export function resolveAdbCmd(): string {
23
- // Priority: explicit env ADB_PATH -> PATH -> known SDK locations -> fallback to 'adb'
24
- if (process.env.ADB_PATH && process.env.ADB_PATH.trim()) return process.env.ADB_PATH
25
- const found = findInPath('adb')
26
- if (found) return found
27
- const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME
28
- if (sdkRoot) {
29
- const candidate = path.join(sdkRoot, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb')
30
- if (existsSync(candidate)) return candidate
31
- }
32
- // common macOS user SDK path
33
- const homeSdk = path.join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb')
34
- if (existsSync(homeSdk)) return homeSdk
35
- return 'adb'
36
- }
37
-
38
- export function getAdbCmd() { return resolveAdbCmd() }
39
-
40
- export function ensureAdbAvailable() {
41
- const adb = resolveAdbCmd()
42
- try {
43
- const res = spawnSync(adb, ['--version'], { encoding: 'utf8' })
44
- if (res.status === 0) {
45
- return { adbCmd: adb, ok: true, version: (res.stdout || res.stderr || '').trim() }
46
- }
47
- return { adbCmd: adb, ok: false, error: (res.stderr || res.stdout || '').trim() }
48
- } catch (err: unknown) {
49
- return { adbCmd: adb, ok: false, error: String(err) }
50
- }
51
- }
52
-
53
- type GradleEnvOverrides = Record<string, string | undefined>
54
-
55
- function mergeEnv(overrides?: GradleEnvOverrides) {
56
- const env: Record<string, string> = {}
57
- for (const [key, value] of Object.entries(process.env)) {
58
- if (typeof value === 'string') env[key] = value
59
- }
60
- for (const [key, value] of Object.entries(overrides || {})) {
61
- if (typeof value === 'string') env[key] = value
62
- }
63
- return env
64
- }
65
-
66
- /**
67
- * Prepare Gradle execution options for building an Android project.
68
- * Returns execCmd (wrapper or gradle), base gradleArgs array, and spawn options including env.
69
- */
70
- export async function prepareGradle(projectPath: string, envOverrides: GradleEnvOverrides = {}): Promise<{ execCmd: string, gradleArgs: string[], spawnOpts: any }> {
71
- const env = mergeEnv(envOverrides)
72
- const gradlewPath = path.join(projectPath, 'gradlew')
73
- const gradleCmd = existsSync(gradlewPath) ? './gradlew' : 'gradle'
74
- const execCmd = existsSync(gradlewPath) ? gradlewPath : gradleCmd
75
-
76
- // Start with a default task; callers may append/override via env flags
77
- const gradleArgs: string[] = [ env.MCP_GRADLE_TASK || 'assembleDebug' ]
78
-
79
- // Respect generic MCP_BUILD_JOBS and Android-specific MCP_GRADLE_WORKERS
80
- const workers = env.MCP_GRADLE_WORKERS || env.MCP_BUILD_JOBS
81
- if (workers) {
82
- gradleArgs.push(`--max-workers=${workers}`)
83
- }
84
-
85
- // Respect gradle cache env: default enabled; set MCP_GRADLE_CACHE=0 to disable
86
- if (env.MCP_GRADLE_CACHE === '0') {
87
- gradleArgs.push('-Dorg.gradle.caching=false')
88
- }
89
-
90
- const detectedJavaHome = await detectJavaHome().catch(() => undefined)
91
- // Check for problematic org.gradle.java.home entries (env or properties) and avoid passing invalid values to Gradle
92
- let gradleCheck
93
- try {
94
- gradleCheck = await checkGradle()
95
- } catch {
96
- gradleCheck = { gradleJavaHome: undefined, gradleValid: false, filesChecked: [], issues: [] }
97
- }
98
-
99
- // Ensure child processes can find Android platform-tools (adb, etc.) by
100
- // prepending the platform-tools directory to PATH for spawned processes.
101
- const adbPath = resolveAdbCmd()
102
- let platformToolsDir: string | undefined = undefined
103
- try {
104
- if (adbPath && adbPath !== 'adb' && existsSync(adbPath)) {
105
- platformToolsDir = path.dirname(adbPath)
106
- }
107
- } catch (e: unknown) { console.debug(`[prepareGradle] error resolving adbPath: ${String(e)}`) }
108
-
109
- const pathParts: string[] = []
110
- // Prefer a detected (validated) Java home from the system/IDE
111
- if (detectedJavaHome) {
112
- if (env.JAVA_HOME !== detectedJavaHome) {
113
- env.JAVA_HOME = detectedJavaHome
114
- }
115
- const javaBin = path.join(detectedJavaHome, 'bin')
116
- pathParts.push(javaBin)
117
- gradleArgs.push(`-Dorg.gradle.java.home=${detectedJavaHome}`)
118
- gradleArgs.push('--no-daemon')
119
- env.GRADLE_JAVA_HOME = detectedJavaHome
120
- } else if (gradleCheck && gradleCheck.gradleJavaHome) {
121
- // There's an org.gradle.java.home configured somewhere (env or gradle.properties)
122
- if (gradleCheck.gradleValid) {
123
- const p = gradleCheck.gradleJavaHome as string
124
- const javaBin = path.join(p, 'bin')
125
- if (!env.PATH || !env.PATH.includes(javaBin)) pathParts.push(javaBin)
126
- gradleArgs.push(`-Dorg.gradle.java.home=${p}`)
127
- gradleArgs.push('--no-daemon')
128
- env.GRADLE_JAVA_HOME = p
129
- } else {
130
- // Invalid gradle java home detected: avoid passing it to Gradle and remove from spawn env
131
- console.debug(`[prepareGradle] Invalid org.gradle.java.home detected (${gradleCheck.gradleJavaHome}); removing from spawn env to avoid Gradle error.`)
132
- try { delete env.GRADLE_JAVA_HOME } catch { }
133
- }
134
- }
135
-
136
- if (platformToolsDir) {
137
- // Prepend platform-tools so gradle and child tools find adb without modifying global env
138
- if (!env.PATH || !env.PATH.includes(platformToolsDir)) {
139
- pathParts.push(platformToolsDir)
140
- }
141
- } else if (process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME) {
142
- const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME || ''
143
- const candidate = path.join(sdkRoot, 'platform-tools')
144
- if (existsSync(candidate) && (!env.PATH || !env.PATH.includes(candidate))) {
145
- pathParts.push(candidate)
146
- }
147
- } else {
148
- // also try common user sdk location
149
- const homeSdkTools = path.join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools')
150
- if (existsSync(homeSdkTools) && (!env.PATH || !env.PATH.includes(homeSdkTools))) {
151
- pathParts.push(homeSdkTools)
152
- }
153
- }
154
-
155
- if (pathParts.length > 0) {
156
- env.PATH = `${pathParts.join(path.delimiter)}${path.delimiter}${env.PATH || ''}`
157
- }
158
-
159
- try { delete env.SHELL } catch (e: unknown) { console.debug('[prepareGradle] failed to delete SHELL from env:', String(e)) }
160
-
161
- const useWrapper = existsSync(gradlewPath)
162
- const spawnOpts: any = { cwd: projectPath, env }
163
- if (useWrapper) {
164
- try { await fsPromises.chmod(gradlewPath, 0o755) } catch (e: unknown) { console.debug('[prepareGradle] chmod failed for gradlew:', String(e)) }
165
- // Execute the wrapper directly without a shell to avoid shell tokenization of args (spaces in paths)
166
- spawnOpts.shell = false
167
- } else {
168
- // Prefer executing gradle directly without invoking a shell to preserve argument boundaries
169
- spawnOpts.shell = false
170
- }
171
-
172
- return { execCmd, gradleArgs, spawnOpts }
173
- }
174
-
175
-
176
- // Helper to construct ADB args with optional device ID
177
- function getAdbArgs(args: string[], deviceId?: string): string[] {
178
- if (deviceId) {
179
- return ['-s', deviceId, ...args]
180
- }
181
- return args
182
- }
183
-
184
- /**
185
- * Determine an effective ADB timeout (ms) prioritizing:
186
- * 1. provided customTimeout
187
- * 2. MCP_ADB_TIMEOUT or ADB_TIMEOUT env vars
188
- * 3. sensible per-command defaults
189
- */
190
- function getAdbTimeout(args: string[], customTimeout?: number): number {
191
- if (typeof customTimeout === 'number' && !isNaN(customTimeout)) return customTimeout
192
- const envTimeout = parseInt(process.env.MCP_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '', 10)
193
- if (!isNaN(envTimeout) && envTimeout > 0) return envTimeout
194
- if (args.includes('logcat')) return 10000
195
- if (args.includes('uiautomator') && args.includes('dump')) return 20000
196
- return 120000
197
- }
198
-
199
- import type { SpawnOptions } from 'child_process'
200
-
201
- export type SpawnOptionsWithTimeout = SpawnOptions & { timeout?: number }
202
-
203
- export async function execAdb(args: string[], deviceId?: string, options: SpawnOptionsWithTimeout = {}): Promise<string> {
204
- const adbArgs = getAdbArgs(args, deviceId)
205
- const timeoutMs = getAdbTimeout(args, options.timeout)
206
- const res = await execCmd(getAdbCmd(), adbArgs, { timeout: timeoutMs, env: options.env as any, cwd: typeof options.cwd === 'string' ? options.cwd : undefined, shell: !!options.shell })
207
- if (res.exitCode !== 0) throw new Error(res.stderr || `Command failed with code ${res.exitCode}`)
208
- return res.stdout
209
- }
210
-
211
- // Spawn adb but return full streams and exit code so callers can implement fallbacks or stream output
212
- export async function spawnAdb(args: string[], deviceId?: string, options: SpawnOptionsWithTimeout = {}): Promise<{ stdout: string, stderr: string, code: number | null }> {
213
- const adbArgs = getAdbArgs(args, deviceId)
214
- const timeoutMs = getAdbTimeout(args, options.timeout)
215
- const res = await execCmd(getAdbCmd(), adbArgs, { timeout: timeoutMs, env: options.env as any, cwd: typeof options.cwd === 'string' ? options.cwd : undefined, shell: !!options.shell })
216
- return { stdout: res.stdout, stderr: res.stderr, code: res.exitCode }
217
- }
218
-
4
+ export { findInPath, resolveAdbCmd, getAdbCmd, ensureAdbAvailable, getAdbArgs, getAdbTimeout, type SpawnOptionsWithTimeout, execAdb, spawnAdb } from './commands.js'
5
+ import { execAdb } from './commands.js'
6
+ export { type GradleEnvOverrides, mergeEnv, prepareGradle } from './gradle.js'
7
+ export { parseBounds, getCenter, parseBooleanAttr, parseNumberAttr, normalizeClassName, inferAndroidRole, buildAndroidSelectorConfidence, buildAndroidSelector, buildAndroidSemantic, isSliderLikeAndroid, extractAndroidState, traverseNode } from './ui-tree.js'
8
+ export { parseLogLine } from './logs.js'
219
9
  export function getDeviceInfo(deviceId: string, metadata: Partial<DeviceInfo> = {}): DeviceInfo {
220
- return {
221
- platform: 'android',
222
- id: deviceId || 'default',
223
- osVersion: metadata.osVersion || '',
224
- model: metadata.model || '',
225
- simulator: metadata.simulator || false
10
+ return {
11
+ platform: 'android',
12
+ id: deviceId || 'default',
13
+ osVersion: metadata.osVersion || '',
14
+ model: metadata.model || '',
15
+ simulator: metadata.simulator || false
226
16
  }
227
17
  }
228
18
 
@@ -242,7 +32,8 @@ export async function getAndroidDeviceMetadata(appId: string, deviceId?: string)
242
32
  if (deviceLines.length === 1) {
243
33
  resolvedDeviceId = deviceLines[0];
244
34
  }
245
- } catch (e: unknown) { console.debug('[getAndroidDeviceMetadata] error detecting single device: ' + String(e))
35
+ } catch (e: unknown) {
36
+ console.debug('[getAndroidDeviceMetadata] error detecting single device: ' + String(e))
246
37
  }
247
38
  }
248
39
 
@@ -252,7 +43,7 @@ export async function getAndroidDeviceMetadata(appId: string, deviceId?: string)
252
43
  execAdb(['shell', 'getprop', 'ro.product.model'], resolvedDeviceId).catch(() => ''),
253
44
  execAdb(['shell', 'getprop', 'ro.kernel.qemu'], resolvedDeviceId).catch(() => '0')
254
45
  ])
255
-
46
+
256
47
  const simulator = simOutput === '1'
257
48
  return { platform: 'android', id: resolvedDeviceId || 'default', osVersion, model, simulator }
258
49
  } catch (e: unknown) {
@@ -275,7 +66,6 @@ export async function findApk(dir: string): Promise<string | undefined> {
275
66
  return undefined
276
67
  }
277
68
 
278
-
279
69
  export async function listAndroidDevices(appId?: string): Promise<DeviceInfo[]> {
280
70
  try {
281
71
  const devicesOutput = await execAdb(['devices', '-l'])
@@ -307,157 +97,8 @@ export async function listAndroidDevices(appId?: string): Promise<DeviceInfo[]>
307
97
  } catch (e: unknown) { console.debug('[listAndroidDevices] failed to list devices: ' + String(e)); return [] }
308
98
  }
309
99
 
310
- // UI helper utilities shared by observe/interact
311
100
  export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
312
101
 
313
- export function parseBounds(bounds: string): [number, number, number, number] {
314
- const match = bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
315
- if (match) {
316
- return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), parseInt(match[4])];
317
- }
318
- return [0, 0, 0, 0];
319
- }
320
-
321
- export function getCenter(bounds: [number, number, number, number]): [number, number] {
322
- const [x1, y1, x2, y2] = bounds;
323
- return [Math.floor((x1 + x2) / 2), Math.floor((y1 + y2) / 2)];
324
- }
325
-
326
- function parseBooleanAttr(value: unknown): boolean | null {
327
- if (value === true || value === 'true') return true
328
- if (value === false || value === 'false') return false
329
- return null
330
- }
331
-
332
- function parseNumberAttr(value: unknown): number | null {
333
- if (typeof value === 'number' && Number.isFinite(value)) return value
334
- if (typeof value !== 'string') return null
335
- const parsed = Number(value)
336
- return Number.isFinite(parsed) ? parsed : null
337
- }
338
-
339
- function normalizeClassName(value: unknown): string {
340
- return typeof value === 'string' ? value.trim().toLowerCase() : ''
341
- }
342
-
343
- function inferAndroidRole(className: string): string | null {
344
- if (/seekbar|slider/.test(className)) return 'slider'
345
- if (/stepper|numberpicker/.test(className)) return 'stepper'
346
- if (/spinner|dropdown/.test(className)) return 'dropdown'
347
- if (/segment|tablayout/.test(className)) return 'segmented_control'
348
- if (/switch|toggle/.test(className)) return 'switch'
349
- if (/checkbox/.test(className)) return 'checkbox'
350
- if (/radiobutton|radio/.test(className)) return 'radio'
351
- if (/edittext|textfield|search/.test(className)) return 'text_field'
352
- if (/button|fab/.test(className)) return 'button'
353
- if (/imageview|icon/.test(className)) return 'image'
354
- if (/recyclerview|scroll|layout|viewgroup|frame/.test(className)) return 'container'
355
- return null
356
- }
357
-
358
- function buildAndroidSelectorConfidence(source: 'resource_id' | 'content_desc' | 'text' | 'class' | 'none'): SelectorConfidence | null {
359
- switch (source) {
360
- case 'resource_id':
361
- return { score: 1, reason: 'resource_id' }
362
- case 'content_desc':
363
- return { score: 0.9, reason: 'content_description' }
364
- case 'text':
365
- return { score: 0.6, reason: 'text_match' }
366
- case 'class':
367
- return { score: 0.35, reason: 'class_match' }
368
- default:
369
- return null
370
- }
371
- }
372
-
373
- function buildAndroidSelector(text: string | null, contentDescription: string | null, resourceId: string | null, className: string): UIResolutionSelector | null {
374
- if (resourceId) return { value: resourceId, confidence: buildAndroidSelectorConfidence('resource_id') }
375
- if (contentDescription) return { value: contentDescription, confidence: buildAndroidSelectorConfidence('content_desc') }
376
- if (text) return { value: text, confidence: buildAndroidSelectorConfidence('text') }
377
- if (className) return { value: className, confidence: buildAndroidSelectorConfidence('class') }
378
- return null
379
- }
380
-
381
- function buildAndroidSemantic(clickable: boolean, className: string, role: string | null): UIElementSemanticMetadata {
382
- const semantic: UIElementSemanticMetadata = {
383
- is_clickable: clickable,
384
- is_container: /recyclerview|scroll|layout|viewgroup|frame/.test(className)
385
- }
386
-
387
- if (role === 'slider') {
388
- semantic.semantic_role = 'slider'
389
- semantic.adjustable = true
390
- semantic.supported_actions = ['adjust']
391
- semantic.state_shape = 'continuous'
392
- } else if (role === 'stepper') {
393
- semantic.semantic_role = 'stepper'
394
- semantic.adjustable = true
395
- semantic.supported_actions = ['increment', 'decrement']
396
- semantic.state_shape = 'discrete'
397
- } else if (role === 'dropdown') {
398
- semantic.semantic_role = 'dropdown'
399
- semantic.supported_actions = ['tap', 'expand']
400
- semantic.state_shape = 'semantic'
401
- } else if (role === 'segmented_control') {
402
- semantic.semantic_role = 'segmented_control'
403
- semantic.supported_actions = ['tap']
404
- semantic.state_shape = 'discrete'
405
- } else if (clickable) {
406
- semantic.supported_actions = ['tap']
407
- }
408
-
409
- return semantic
410
- }
411
-
412
- function isSliderLikeAndroid(node: any): boolean {
413
- const className = String(node['@_class'] || '').toLowerCase()
414
- return /seekbar|slider|range/i.test(className)
415
- }
416
-
417
- function extractAndroidState(node: any): UIElementState | null {
418
- const checked = parseBooleanAttr(node['@_checked'])
419
- const selectedFlag = parseBooleanAttr(node['@_selected'])
420
- const focused = parseBooleanAttr(node['@_focused'])
421
- const expanded = parseBooleanAttr(node['@_expanded'])
422
- const enabled = parseBooleanAttr(node['@_enabled'])
423
- const textValue = typeof node['@_text'] === 'string' && node['@_text'].trim().length > 0 ? node['@_text'] : null
424
- const state: UIElementState = {}
425
-
426
- if (checked !== null) state.checked = checked
427
- if (selectedFlag !== null) {
428
- state.selected = textValue || node['@_content-desc'] || true
429
- }
430
- if (focused !== null) state.focused = focused
431
- if (expanded !== null) state.expanded = expanded
432
- if (enabled !== null) state.enabled = enabled
433
-
434
- if (textValue && /edittext|textfield|search/i.test(String(node['@_class'] || ''))) {
435
- state.text_value = textValue
436
- }
437
-
438
- if (isSliderLikeAndroid(node)) {
439
- const rawProgress = parseNumberAttr(node['@_progress'])
440
- const max = parseNumberAttr(node['@_max'])
441
- const fallbackValue = rawProgress ?? parseNumberAttr(node['@_value']) ?? parseNumberAttr(node['@_content-desc'])
442
- const numericValue = rawProgress ?? fallbackValue
443
- if (numericValue !== null) {
444
- state.raw_value = numericValue
445
- state.value_range = max !== null && max > 0 ? { min: 0, max } : null
446
- state.value = max !== null && max > 0 ? Math.round((numericValue / max) * 100) : numericValue
447
- }
448
- } else {
449
- const numericValue = parseNumberAttr(node['@_value'])
450
- if (numericValue !== null) {
451
- state.value = numericValue
452
- state.raw_value = numericValue
453
- } else if (textValue) {
454
- state.value = textValue
455
- }
456
- }
457
-
458
- return Object.keys(state).length > 0 ? state : null
459
- }
460
-
461
102
  export async function getScreenResolution(deviceId?: string): Promise<{ width: number; height: number }> {
462
103
  try {
463
104
  const output = await execAdb(['shell', 'wm', 'size'], deviceId);
@@ -468,184 +109,3 @@ export async function getScreenResolution(deviceId?: string): Promise<{ width: n
468
109
  } catch (e: unknown) { console.debug('[getScreenResolution] failed to detect screen resolution: ' + String(e)) }
469
110
  return { width: 0, height: 0 };
470
111
  }
471
-
472
- export function traverseNode(node: any, elements: UIElement[], parentIndex: number = -1, depth: number = 0): number {
473
- if (!node) return -1;
474
-
475
- let currentIndex = -1;
476
-
477
- if (node['@_class']) {
478
- const text = node['@_text'] || null;
479
- const contentDescription = node['@_content-desc'] || null;
480
- const clickable = node['@_clickable'] === 'true';
481
- const className = String(node['@_class'] || 'unknown');
482
- const bounds = parseBounds(node['@_bounds'] || '[0,0][0,0]');
483
- const state = extractAndroidState(node);
484
- const role = inferAndroidRole(normalizeClassName(className));
485
- const resourceId = typeof node['@_resource-id'] === 'string' && node['@_resource-id'].trim().length > 0 ? node['@_resource-id'] : null
486
- const stableId = resourceId ?? (typeof contentDescription === 'string' && contentDescription.trim().length > 0 ? contentDescription : null)
487
- const testTag = stableId
488
- const selector = buildAndroidSelector(text, contentDescription, resourceId, normalizeClassName(className))
489
- const semantic = buildAndroidSemantic(clickable, normalizeClassName(className), role)
490
-
491
- const isUseful = clickable || (text && text.length > 0) || (contentDescription && contentDescription.length > 0);
492
-
493
- if (isUseful) {
494
- const element: UIElement = {
495
- text,
496
- contentDescription,
497
- type: className,
498
- resourceId,
499
- clickable,
500
- enabled: node['@_enabled'] === 'true',
501
- visible: true,
502
- bounds,
503
- center: getCenter(bounds),
504
- depth,
505
- state,
506
- stable_id: stableId,
507
- role,
508
- test_tag: testTag,
509
- selector,
510
- semantic
511
- };
512
-
513
- if (parentIndex !== -1) {
514
- element.parentId = parentIndex;
515
- }
516
-
517
- elements.push(element);
518
- currentIndex = elements.length - 1;
519
- }
520
- }
521
-
522
- const nextParentIndex = currentIndex !== -1 ? currentIndex : parentIndex;
523
- const nextDepth = currentIndex !== -1 ? depth + 1 : depth;
524
-
525
- const childrenIndices: number[] = [];
526
-
527
- if (node.node) {
528
- if (Array.isArray(node.node)) {
529
- node.node.forEach((child: any) => {
530
- const childIndex = traverseNode(child, elements, nextParentIndex, nextDepth);
531
- if (childIndex !== -1) childrenIndices.push(childIndex);
532
- });
533
- } else {
534
- const childIndex = traverseNode(node.node, elements, nextParentIndex, nextDepth);
535
- if (childIndex !== -1) childrenIndices.push(childIndex);
536
- }
537
- }
538
-
539
- if (currentIndex !== -1 && childrenIndices.length > 0) {
540
- elements[currentIndex].children = childrenIndices;
541
- }
542
-
543
- return currentIndex;
544
- }
545
-
546
- // Log stream management (one stream per session)
547
-
548
- // (Legacy active stream map removed from utils during refactor; Observe modules manage their own active streams.)
549
-
550
- // Robust log line parser supporting multiple logcat formats
551
- export function parseLogLine(line: string) { // Collapse internal newlines so multiline stack traces are parseable as a single entry
552
- const rawLine = line
553
- const normalizedLine = rawLine.replace(/\r?\n/g, ' ')
554
- const entry: any = { timestamp: '', level: '', tag: '', message: rawLine, _iso: null, crash: false }
555
-
556
- const nowYear = new Date().getFullYear()
557
-
558
- const tryIso = (ts: string) => {
559
- if (!ts) return null
560
- // If it's already ISO
561
- if (/^\d{4}-\d{2}-\d{2}T/.test(ts)) return ts
562
- // If format MM-DD HH:MM:SS(.sss)
563
- const m = ts.match(/^(\d{2})-(\d{2})\s+(\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)$/)
564
- if (m) {
565
- const month = m[1]
566
- const day = m[2]
567
- const time = m[3]
568
- const candidate = `${nowYear}-${month}-${day}T${time}`
569
- const d = new Date(candidate)
570
- if (!isNaN(d.getTime())) return d.toISOString()
571
- }
572
- // If format YYYY-MM-DD HH:MM:SS(.sss)
573
- const m2 = ts.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)$/)
574
- if (m2) {
575
- const candidate = `${m2[1]}T${m2[2]}`
576
- const d = new Date(candidate)
577
- if (!isNaN(d.getTime())) return d.toISOString()
578
- }
579
- return null
580
- }
581
-
582
- // Patterns to try (ordered)
583
- const patterns: Array<{re: RegExp, groups: string[]}> = [
584
- // MM-DD HH:MM:SS.mmm PID TID LEVEL/Tag: msg
585
- { re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\/([^:]+):\s*(.*)$/, groups: ['ts','pid','tid','level','tag','msg'] },
586
- // MM-DD HH:MM:SS.mmm PID TID LEVEL Tag: msg (space between level and tag)
587
- { re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\s+([^:]+):\s*(.*)$/, groups: ['ts','pid','tid','level','tag','msg'] },
588
- // YYYY-MM-DD full date with PID TID LEVEL/Tag
589
- { re: /^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\/([^:]+):\s*(.*)$/, groups: ['ts','pid','tid','level','tag','msg'] },
590
- // YYYY-MM-DD with space separation
591
- { re: /^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\s+([^:]+):\s*(.*)$/, groups: ['ts','pid','tid','level','tag','msg'] },
592
- // MM-DD PID LEVEL/Tag: msg
593
- { re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+([VDIWE])\/([^:]+):\s*(.*)$/, groups: ['ts','pid','level','tag','msg'] },
594
- // MM-DD PID LEVEL Tag: msg (space)
595
- { re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+([VDIWE])\s+([^:]+):\s*(.*)$/, groups: ['ts','pid','level','tag','msg'] },
596
- // Short form LEVEL/Tag: msg
597
- { re: /^([VDIWE])\/([^\(\:]+)(?:\([0-9]+\))?:\s*(.*)$/, groups: ['level','tag','msg'] },
598
- // Short form LEVEL Tag: msg
599
- { re: /^([VDIWE])\s+([^\(\:]+)(?:\([0-9]+\))?:\s*(.*)$/, groups: ['level','tag','msg'] },
600
- ]
601
-
602
- for (const p of patterns) {
603
- const m = normalizedLine.match(p.re)
604
- if (m) {
605
- const g = p.groups
606
- const vals: any = {}
607
- for (let i=0;i<g.length;i++) vals[g[i]] = m[i+1]
608
- const ts = vals.ts
609
- if (ts) {
610
- const iso = tryIso(ts)
611
- if (iso) {
612
- entry.timestamp = ts
613
- entry._iso = iso
614
- } else {
615
- entry.timestamp = ts
616
- }
617
- }
618
- if (vals.level) entry.level = vals.level
619
- if (vals.tag) entry.tag = vals.tag.trim()
620
- entry.message = vals.msg || entry.message
621
- // Crash heuristics
622
- const msg = (entry.message || '').toString()
623
- const crash = /FATAL EXCEPTION/i.test(msg) || /\b([A-Za-z0-9_$.]+Exception)\b/.test(msg)
624
- if (crash) {
625
- entry.crash = true
626
- const exMatch = msg.match(/\b([A-Za-z0-9_$.]+Exception)\b/)
627
- if (exMatch) entry.exception = exMatch[1]
628
- }
629
- return entry
630
- }
631
- }
632
-
633
- // No pattern matched: attempt to extract level/tag like '... E/Tag: msg'
634
- const alt = normalizedLine.match(/([VDIWE])\/([^:]+):\s*(.*)$/)
635
- if (alt) {
636
- entry.level = alt[1]
637
- entry.tag = alt[2].trim()
638
- entry.message = alt[3]
639
- const msg = entry.message
640
- const crash = /FATAL EXCEPTION/i.test(msg) || /\b([A-Za-z0-9_$.]+Exception)\b/.test(msg)
641
- if (crash) {
642
- entry.crash = true
643
- const exMatch = msg.match(/\b([A-Za-z0-9_$.]+Exception)\b/)
644
- if (exMatch) entry.exception = exMatch[1]
645
- }
646
- }
647
-
648
- return entry
649
- }
650
-
651
- // Legacy readLogStreamLines shim removed. Use AndroidObserve.readLogStream(sessionId, limit, since) instead.
@@ -7,7 +7,7 @@ function which(cmd: string): string | null {
7
7
  try {
8
8
  const r = spawnSync('command', ['-v', cmd], { stdio: ['ignore', 'pipe', 'ignore'] })
9
9
  if (r && r.status === 0 && r.stdout) return r.stdout.toString().trim()
10
- } catch {}
10
+ } catch { }
11
11
  try {
12
12
  return execSync(`which ${cmd}`, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
13
13
  } catch {
@@ -15,7 +15,7 @@ function which(cmd: string): string | null {
15
15
  }
16
16
  }
17
17
 
18
- function print(...args: any[]) {
18
+ function print(...args: unknown[]) {
19
19
  console.log(...args)
20
20
  }
21
21
 
@@ -25,7 +25,7 @@ async function runInstaller() {
25
25
  const runner = which('npx') ? 'npx' : which('tsx') ? 'tsx' : null
26
26
  if (runner) {
27
27
  const args = runner === 'npx' ? ['tsx', './src/utils/cli/idb/install-idb.ts'] : ['./src/utils/cli/idb/install-idb.ts']
28
- const res = spawnSync(runner, args, { stdio: 'inherit' as any })
28
+ const res = spawnSync(runner, args, { stdio: 'inherit' })
29
29
  return typeof res.status === 'number' ? res.status === 0 : false
30
30
  }
31
31
 
@@ -60,7 +60,7 @@ try {
60
60
  }
61
61
  const companion = which('idb_companion')
62
62
  print('which idb_companion:', companion)
63
- if (companion) try { print('idb_companion --version:', execSync('idb_companion --version', { stdio: ['ignore','pipe','ignore'] }).toString().trim()) } catch (e) { print('idb_companion --version: (failed)', e instanceof Error ? e.message : String(e)) }
63
+ if (companion) try { print('idb_companion --version:', execSync('idb_companion --version', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()) } catch (e) { print('idb_companion --version: (failed)', e instanceof Error ? e.message : String(e)) }
64
64
  process.exit(0)
65
65
  }
66
66
 
@@ -14,12 +14,10 @@ export function getConfiguredIdbPath(): string | undefined {
14
14
  if (fs.existsSync(p)) {
15
15
  const raw = fs.readFileSync(p, 'utf8')
16
16
  const json = JSON.parse(raw)
17
- if (json) {
18
- if (json.idbPath) return json.idbPath
19
- if (json.IDB_PATH) return json.IDB_PATH
20
- }
17
+ if (json?.idbPath) return json.idbPath
18
+ if (json?.IDB_PATH) return json.IDB_PATH
21
19
  }
22
- } catch {}
20
+ } catch { }
23
21
  }
24
22
  return undefined
25
23
  }
@@ -28,11 +26,11 @@ export function commandWhich(cmd: string): string | null {
28
26
  try {
29
27
  const r = spawnSync('command', ['-v', cmd], { stdio: ['ignore', 'pipe', 'ignore'] })
30
28
  if (r && r.status === 0 && r.stdout) return r.stdout.toString().trim()
31
- } catch {}
29
+ } catch { }
32
30
  try {
33
31
  const p = execSync(`which ${cmd}`, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
34
32
  if (p) return p
35
- } catch {}
33
+ } catch { }
36
34
  return null
37
35
  }
38
36
 
@@ -54,7 +52,7 @@ export function getIdbCmd(): string | null {
54
52
  ]
55
53
  for (const c of common) {
56
54
  if (!c) continue
57
- try { execSync(`test -x ${c}`, { stdio: ['ignore', 'pipe', 'ignore'] }); return c } catch {}
55
+ try { execSync(`test -x ${c}`, { stdio: ['ignore', 'pipe', 'ignore'] }); return c } catch { }
58
56
  }
59
57
  return null
60
58
  }