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
@@ -8,7 +8,8 @@ const orders = new Map();
8
8
  const stamp = () => ({ wall: Date.now(), mono: performance.now() });
9
9
  const deviceKey = (d) => `${d.platform}:${d.id}`;
10
10
  const sameDevice = (a, b) => !!a && !!b && deviceKey(a) === deviceKey(b);
11
- function validDevice(d) {
11
+ function validDevice(value) {
12
+ const d = value;
12
13
  return d && ['android', 'ios'].includes(d.platform) && typeof d.id === 'string' && d.id && !['default', 'booted'].includes(d.id)
13
14
  ? { platform: d.platform, id: d.id } : null;
14
15
  }
@@ -106,11 +107,13 @@ export function omitEvidence(kind) {
106
107
  if (!current)
107
108
  return;
108
109
  const start = stamp();
109
- current.observations.push({ value: {
110
+ current.observations.push({
111
+ value: {
110
112
  evidence_id: randomUUID(), kind, status: 'omitted', capture_started_at_ms: null,
111
113
  capture_completed_at_ms: null, source_captured_at_ms: null, age_ms: null,
112
114
  freshness: kind === 'logs' ? 'not_applicable' : 'unknown', freshness_reasons: [], device: current.device
113
- }, start, end: null, order: null, startSequence: 0, endSequence: 0, overlapping: false, reused: false });
115
+ }, start, end: null, order: null, startSequence: 0, endSequence: 0, overlapping: false, reused: false
116
+ });
114
117
  }
115
118
  export async function captureEvidence(kind, device, operation) {
116
119
  const current = context.getStore();
@@ -121,45 +124,19 @@ export async function captureEvidence(kind, device, operation) {
121
124
  bindEvidenceDevice(target);
122
125
  const order = orderFor(target);
123
126
  const start = stamp();
124
- const acquisition = { value: {
127
+ const acquisition = {
128
+ value: {
125
129
  evidence_id: randomUUID(), kind, status: 'failed', capture_started_at_ms: start.wall,
126
130
  capture_completed_at_ms: null, source_captured_at_ms: null, age_ms: null,
127
131
  freshness: 'unknown', freshness_reasons: [], device: target
128
132
  }, start, end: null, order, startSequence: order?.sequence ?? 0,
129
- endSequence: 0, overlapping: !!order?.active, reused: false };
133
+ endSequence: 0, overlapping: !!order?.active, reused: false
134
+ };
130
135
  current.observations.push(acquisition);
131
136
  try {
132
- const result = await operation();
133
- const value = acquisition.value;
134
- const resolved = validDevice(result?.device) ?? target;
135
- value.device = resolved;
136
- if (resolved)
137
- bindEvidenceDevice(resolved);
138
- const failure = result?.error ?? result?.meta?.error;
139
- value.status = failure || result?.success === false || (kind === 'fingerprint' && !result?.fingerprint) ? 'failed' : 'captured';
140
- if (failure)
141
- value.error = failure;
142
- const source = result?.captured_at_ms;
143
- if (Number.isFinite(source) && source > 0) {
144
- value.source_captured_at_ms = source;
145
- acquisition.reused = source < start.wall;
146
- }
147
- if (kind === 'ui_tree') {
148
- value.snapshot_session_id = result?.snapshot_session_id;
149
- value.snapshot_revision = result?.snapshot_revision;
150
- }
151
- if (kind === 'logs') {
152
- const entries = result?.logs ?? result?.entries ?? [];
153
- const times = entries.map((e) => {
154
- const t = e?.timestamp ?? e?._iso;
155
- return typeof t === 'number' ? t : typeof t === 'string' ? Date.parse(t) : NaN;
156
- }).filter((t) => Number.isFinite(t));
157
- value.clock = 'device_or_unknown';
158
- value.event_time_comparable = false;
159
- value.earliest_entry_at_ms = times.length ? times.reduce((a, b) => Math.min(a, b)) : null;
160
- value.latest_entry_at_ms = times.length ? times.reduce((a, b) => Math.max(a, b)) : null;
161
- }
162
- return result;
137
+ const original = await operation();
138
+ recordObservationResult(acquisition, original, kind, target, start);
139
+ return original;
163
140
  }
164
141
  catch (error) {
165
142
  acquisition.value.error = error instanceof Error ? error.message : String(error);
@@ -189,46 +166,13 @@ function assemble(current) {
189
166
  const link = association(current);
190
167
  const action = link.association === 'unavailable' ? null : current.action;
191
168
  const clockChanged = end.wall < current.start.wall;
192
- const observations = current.observations.map(a => {
193
- const v = { ...a.value, freshness_reasons: [] };
194
- if (v.status === 'omitted')
195
- return v;
196
- const componentAction = sameDevice(v.device, action?.device ?? null) ? action : null;
197
- const sourceMono = a.end && v.source_captured_at_ms !== null ? a.end.mono + v.source_captured_at_ms - a.end.wall : a.end?.mono;
198
- const reason = (freshness, code) => { v.freshness = freshness; v.freshness_reasons.push(code); };
199
- if (v.kind === 'logs')
200
- reason('unknown', 'DEVICE_CLOCK_UNVERIFIED');
201
- else if (clockChanged || (a.end && a.end.wall < a.start.wall))
202
- reason('unknown', 'CLOCK_DISCONTINUITY');
203
- else if (v.source_captured_at_ms !== null && a.end && v.source_captured_at_ms > a.end.wall)
204
- reason('unknown', 'CLOCK_DISCONTINUITY');
205
- else if (!v.device || !a.order || orders.get(deviceKey(v.device)) !== a.order || v.status !== 'captured' || !a.end)
206
- reason('unknown', 'CAPTURE_TIME_UNKNOWN');
207
- else if (componentAction?.dispatchStart && sourceMono !== undefined && sourceMono < componentAction.dispatchStart.mono)
208
- reason('stale', 'BEFORE_ACTION');
209
- else if (a.order && (a.order.sequence > a.endSequence || (a.reused && sourceMono !== undefined && sourceMono < a.order.lastStarted)))
210
- reason('stale', 'SUPERSEDED_BY_ACTION');
211
- else if (a.overlapping)
212
- reason('unknown', 'OVERLAPPING_ACTION');
213
- else if (a.reused)
214
- reason('unknown', 'REUSED_OBSERVATION');
215
- else {
216
- v.freshness = 'fresh';
217
- }
218
- if (!clockChanged && a.end && a.end.wall >= a.start.wall && v.source_captured_at_ms !== null && v.source_captured_at_ms <= end.wall) {
219
- v.age_ms = Math.max(0, end.mono - a.end.mono + a.end.wall - v.source_captured_at_ms);
220
- }
221
- return { ...v, session_id: sessionId, correlation_id: componentAction?.correlationId ?? current.correlationId,
222
- action_id: componentAction?.id ?? null, association: componentAction ? 'internal' : link.association };
223
- });
169
+ const observations = current.observations.map(a => assembleObservation(a, action, current, link, end, clockChanged));
224
170
  return {
225
171
  session_id: sessionId, correlation_id: action?.correlationId ?? current.correlationId,
226
172
  action_id: action?.id ?? null, device: current.device, ...link,
227
173
  ...(current.ref ? { requested_action_ref: current.ref } : {}),
228
174
  operation_started_at_ms: current.start.wall, operation_completed_at_ms: current.end?.wall ?? end.wall,
229
- dispatch_started_at_ms: action?.dispatchStart?.wall ?? null,
230
- dispatch_completed_at_ms: action?.dispatchEnd?.wall ?? null,
231
- verification_completed_at_ms: current.verification?.wall ?? action?.verification?.wall ?? null,
175
+ ...actionTimes(action, current),
232
176
  observations, trace_context: { supported: !!current.systemTraceId, trace_id: current.systemTraceId ?? null, markers: [] }
233
177
  };
234
178
  }
@@ -236,10 +180,14 @@ export async function withEvidence(options, operation) {
236
180
  prune();
237
181
  const parent = context.getStore();
238
182
  const id = randomUUID();
239
- const record = options.action ? { id, correlationId: parent?.correlationId ?? id, device: null,
240
- dispatchStart: null, dispatchEnd: null, verification: null, completed: null } : options.ref?.sessionId === sessionId ? active.get(options.ref.actionId) ?? history.get(options.ref.actionId) ?? null : null;
241
- const current = { systemTraceId: parent?.systemTraceId, correlationId: options.action ? record.correlationId : options.journey ? id : randomUUID(),
242
- action: record, ref: options.ref, device: null, start: stamp(), end: null, verification: null, observations: [], children: [], isAction: !!options.action };
183
+ const record = options.action ? {
184
+ id, correlationId: parent?.correlationId ?? id, device: null,
185
+ dispatchStart: null, dispatchEnd: null, verification: null, completed: null
186
+ } : options.ref?.sessionId === sessionId ? active.get(options.ref.actionId) ?? history.get(options.ref.actionId) ?? null : null;
187
+ const current = {
188
+ systemTraceId: parent?.systemTraceId, correlationId: options.action ? record.correlationId : options.journey ? id : randomUUID(),
189
+ action: record, ref: options.ref, device: null, start: stamp(), end: null, verification: null, observations: [], children: [], isAction: !!options.action
190
+ };
243
191
  if (options.action && record)
244
192
  active.set(id, record);
245
193
  if (parent && options.action)
@@ -278,3 +226,94 @@ export function completeJourneyVerification(actionId) {
278
226
  child.action.verification = child.verification;
279
227
  }
280
228
  }
229
+ function recordObservationResult(acquisition, result, kind, target, start) {
230
+ const value = acquisition.value;
231
+ const resolved = validDevice(result?.device) ?? target;
232
+ value.device = resolved;
233
+ if (resolved)
234
+ bindEvidenceDevice(resolved);
235
+ const failure = result?.error ?? result?.meta?.error;
236
+ value.status = failure || result?.success === false || (kind === 'fingerprint' && !result?.fingerprint) ? 'failed' : 'captured';
237
+ if (failure)
238
+ value.error = failure;
239
+ recordCaptureTime(acquisition, result, start);
240
+ if (kind === 'ui_tree') {
241
+ value.snapshot_session_id = result?.snapshot_session_id;
242
+ value.snapshot_revision = result?.snapshot_revision;
243
+ }
244
+ if (kind === 'logs')
245
+ recordLogWindow(value, result);
246
+ }
247
+ function recordLogWindow(value, result) {
248
+ const entries = result?.logs ?? result?.entries ?? [];
249
+ const times = entries.map((e) => {
250
+ const t = e?.timestamp ?? e?._iso;
251
+ return typeof t === 'number' ? t : typeof t === 'string' ? Date.parse(t) : NaN;
252
+ }).filter((t) => Number.isFinite(t));
253
+ value.clock = 'device_or_unknown';
254
+ value.event_time_comparable = false;
255
+ value.earliest_entry_at_ms = times.length ? times.reduce((a, b) => Math.min(a, b)) : null;
256
+ value.latest_entry_at_ms = times.length ? times.reduce((a, b) => Math.max(a, b)) : null;
257
+ }
258
+ function assembleObservation(a, action, current, link, end, clockChanged) {
259
+ const v = { ...a.value, freshness_reasons: [] };
260
+ if (v.status === 'omitted')
261
+ return v;
262
+ const componentAction = sameDevice(v.device, action?.device ?? null) ? action : null;
263
+ const sourceMono = a.end && v.source_captured_at_ms !== null ? a.end.mono + v.source_captured_at_ms - a.end.wall : a.end?.mono;
264
+ const freshness = observationFreshness(a, componentAction, sourceMono, clockChanged);
265
+ if (freshness) {
266
+ v.freshness = freshness[0];
267
+ v.freshness_reasons.push(freshness[1]);
268
+ }
269
+ else
270
+ v.freshness = 'fresh';
271
+ if (!clockChanged && a.end && a.end.wall >= a.start.wall && v.source_captured_at_ms !== null && v.source_captured_at_ms <= end.wall) {
272
+ v.age_ms = Math.max(0, end.mono - a.end.mono + a.end.wall - v.source_captured_at_ms);
273
+ }
274
+ return {
275
+ ...v, session_id: sessionId, correlation_id: componentAction?.correlationId ?? current.correlationId,
276
+ action_id: componentAction?.id ?? null, association: componentAction ? 'internal' : link.association
277
+ };
278
+ }
279
+ function observationFreshness(a, action, sourceMono, clockChanged) {
280
+ const v = a.value;
281
+ if (v.kind === 'logs')
282
+ return ['unknown', 'DEVICE_CLOCK_UNVERIFIED'];
283
+ if (clockDiscontinuity(a, clockChanged))
284
+ return ['unknown', 'CLOCK_DISCONTINUITY'];
285
+ if (!knownCapture(a))
286
+ return ['unknown', 'CAPTURE_TIME_UNKNOWN'];
287
+ if (action?.dispatchStart && sourceMono !== undefined && sourceMono < action.dispatchStart.mono)
288
+ return ['stale', 'BEFORE_ACTION'];
289
+ if (superseded(a, sourceMono))
290
+ return ['stale', 'SUPERSEDED_BY_ACTION'];
291
+ if (a.overlapping)
292
+ return ['unknown', 'OVERLAPPING_ACTION'];
293
+ if (a.reused)
294
+ return ['unknown', 'REUSED_OBSERVATION'];
295
+ return null;
296
+ }
297
+ function clockDiscontinuity(a, changed) {
298
+ return changed || !!(a.end && a.end.wall < a.start.wall) || !!(a.value.source_captured_at_ms !== null && a.end && a.value.source_captured_at_ms > a.end.wall);
299
+ }
300
+ function knownCapture(a) {
301
+ return !!a.value.device && !!a.order && orders.get(deviceKey(a.value.device)) === a.order && a.value.status === 'captured' && !!a.end;
302
+ }
303
+ function superseded(a, sourceMono) {
304
+ return !!a.order && (a.order.sequence > a.endSequence || (a.reused && sourceMono !== undefined && sourceMono < a.order.lastStarted));
305
+ }
306
+ function actionTimes(action, current) {
307
+ return {
308
+ dispatch_started_at_ms: action?.dispatchStart?.wall ?? null,
309
+ dispatch_completed_at_ms: action?.dispatchEnd?.wall ?? null,
310
+ verification_completed_at_ms: current.verification?.wall ?? action?.verification?.wall ?? null,
311
+ };
312
+ }
313
+ function recordCaptureTime(acquisition, result, start) {
314
+ const source = result?.captured_at_ms;
315
+ if (typeof source === 'number' && Number.isFinite(source) && source > 0) {
316
+ acquisition.value.source_captured_at_ms = source;
317
+ acquisition.reused = source < start.wall;
318
+ }
319
+ }
@@ -1,118 +1,12 @@
1
- import { omitEvidence } from './evidence.js';
2
1
  import { withResolvedDevice } from '../utils/resolve-device.js';
3
- import { measure } from '../utils/timing.js';
4
2
  import { ReadinessTracker } from './readiness.js';
5
3
  import { resolveTargetDevice } from '../utils/resolve-device.js';
6
4
  import { AndroidObserve } from './android.js';
7
5
  import { iOSObserve } from './ios.js';
8
- import { deriveSnapshotMetadata, getLatestStateDelta, getStateDelta } from './snapshot-metadata.js';
6
+ import { getLatestStateDelta, getStateDelta } from './snapshot-metadata.js';
9
7
  export { AndroidObserve } from './android.js';
10
8
  export { iOSObserve } from './ios.js';
11
- function normalizeHint(value) {
12
- if (value === null || value === undefined)
13
- return '';
14
- return String(value).trim().replace(/\s+/g, ' ').toLowerCase();
15
- }
16
- function titleCase(value) {
17
- return value
18
- .replace(/[_-]+/g, ' ')
19
- .replace(/\s+/g, ' ')
20
- .trim()
21
- .replace(/\b\w/g, (match) => match.toUpperCase());
22
- }
23
- function shortActivityName(activity) {
24
- if (!activity)
25
- return null;
26
- const trimmed = String(activity).trim();
27
- if (!trimmed)
28
- return null;
29
- const lastSegment = trimmed.split('.').pop() || trimmed;
30
- const withoutSuffix = lastSegment.replace(/Activity$/, '');
31
- return withoutSuffix ? titleCase(withoutSuffix) : titleCase(lastSegment);
32
- }
33
- function collectSnapshotTexts(tree) {
34
- const elements = Array.isArray(tree?.elements) ? tree.elements : [];
35
- const texts = [];
36
- const actionables = [];
37
- for (const element of elements) {
38
- const rawText = element?.text ?? element?.contentDescription ?? element?.contentDesc ?? element?.accessibilityLabel ?? element?.resourceId ?? element?.id ?? '';
39
- const text = normalizeHint(rawText);
40
- if (text)
41
- texts.push(text);
42
- if (element?.clickable && element?.enabled !== false && text) {
43
- actionables.push(text);
44
- }
45
- }
46
- return {
47
- texts: Array.from(new Set(texts)),
48
- actionables: Array.from(new Set(actionables))
49
- };
50
- }
51
- function inferSnapshotScreen(raw) {
52
- const tree = raw.ui_tree;
53
- const treeScreen = normalizeHint(tree?.screen);
54
- if (treeScreen)
55
- return titleCase(treeScreen);
56
- const activity = shortActivityName(raw.activity);
57
- if (activity)
58
- return activity;
59
- const { texts } = collectSnapshotTexts(tree);
60
- if (texts.length > 0)
61
- return titleCase(texts[0]);
62
- return null;
63
- }
64
- function deriveSnapshotSemantic(raw) {
65
- const tree = raw.ui_tree;
66
- const { texts, actionables } = collectSnapshotTexts(tree);
67
- const screenFromTree = normalizeHint(tree?.screen);
68
- const activityHint = normalizeHint(raw.activity);
69
- const screen = inferSnapshotScreen(raw);
70
- if (!screen && !activityHint && texts.length === 0 && !raw.logs.length)
71
- return null;
72
- const hasErrorLogs = raw.logs.some((entry) => /error|fatal exception|exception|failed/i.test(entry.message));
73
- const hasLoadingSignals = texts.some((text) => /loading|please wait|spinner|progress/i.test(text));
74
- const hasPrimaryText = texts.some((text) => /sign in|log in|login|home|checkout|settings|menu|profile|search/i.test(text));
75
- const hasScreenshot = typeof raw.screenshot === 'string' && raw.screenshot.length > 0;
76
- const hasUiTree = !!tree && Array.isArray(tree.elements);
77
- const signals = {
78
- has_activity: !!activityHint,
79
- has_ui_tree: hasUiTree,
80
- has_screenshot: hasScreenshot,
81
- has_visible_text: texts.length > 0,
82
- has_clickable_elements: actionables.length > 0,
83
- has_error_logs: hasErrorLogs,
84
- has_loading_signals: hasLoadingSignals,
85
- has_primary_text: hasPrimaryText
86
- };
87
- const warnings = [];
88
- if (screenFromTree && activityHint && screenFromTree !== activityHint) {
89
- warnings.push('ui_tree.screen and activity hints differ');
90
- }
91
- if (!hasUiTree)
92
- warnings.push('ui tree unavailable');
93
- if (!activityHint)
94
- warnings.push('activity unavailable');
95
- if (hasErrorLogs)
96
- warnings.push('error signals present in logs');
97
- const evidenceScore = (hasUiTree ? 0.35 : 0) +
98
- (screen ? 0.2 : 0) +
99
- (activityHint ? 0.15 : 0) +
100
- (actionables.length > 0 ? 0.15 : 0) +
101
- (texts.length > 0 ? 0.1 : 0) +
102
- (hasScreenshot ? 0.05 : 0) +
103
- (hasErrorLogs ? -0.15 : 0) +
104
- (hasLoadingSignals ? -0.05 : 0);
105
- const confidence = Math.max(0, Math.min(1, Number(evidenceScore.toFixed(2))));
106
- if (!screen && confidence < 0.3)
107
- return null;
108
- return {
109
- screen,
110
- signals,
111
- actions_available: actionables.length > 0 ? actionables.slice(0, 10) : null,
112
- confidence,
113
- warnings
114
- };
115
- }
9
+ import { collectDebugSnapshot } from './snapshot-collection.js';
116
10
  export class ToolsObserve {
117
11
  static getLatestStateDelta(platform, deviceId) {
118
12
  return getLatestStateDelta(`${platform}:${deviceId}`);
@@ -172,24 +66,13 @@ export class ToolsObserve {
172
66
  if (level && !['VERBOSE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].includes(level.toString().toUpperCase())) {
173
67
  return { device: resolved, logs: [], crashLines: [], logCount: 0, error: { code: 'INVALID_FILTER', message: `Unsupported level filter: ${level}` } };
174
68
  }
175
- if (observe instanceof AndroidObserve) {
176
- const response = await observe.getLogs(filters);
177
- const logs = Array.isArray(response.logs) ? response.logs : [];
178
- const crashLines = logs.filter(entry => /FATAL EXCEPTION/i.test(entry.message));
179
- const anyFilterApplied = !!(appId || pid || tag || level || contains || since_seconds);
180
- if (anyFilterApplied && logs.length === 0)
181
- return { device: response.device, logs: [], crashLines: [], logCount: 0, source: response.source, meta: response.meta, error: { code: 'LOGS_UNAVAILABLE', message: 'No logs match filters' } };
182
- return { device: response.device, logs, crashLines, logCount: response.logCount, source: response.source, meta: response.meta };
183
- }
184
- else {
185
- const resp = await observe.getLogs(filters);
186
- const logs = Array.isArray(resp.logs) ? resp.logs : [];
187
- const crashLines = logs.filter(entry => /FATAL EXCEPTION/i.test(entry.message));
188
- const anyFilterApplied = !!(appId || pid || tag || level || contains || since_seconds);
189
- if (anyFilterApplied && logs.length === 0)
190
- return { device: resp.device, logs: [], crashLines: [], logCount: 0, source: resp.source, meta: resp.meta, error: { code: 'LOGS_UNAVAILABLE', message: 'No logs match filters' } };
191
- return { device: resp.device, logs, crashLines, logCount: resp.logCount, source: resp.source, meta: resp.meta };
192
- }
69
+ const response = await observe.getLogs(filters);
70
+ const logs = Array.isArray(response.logs) ? response.logs : [];
71
+ const crashLines = logs.filter(entry => /FATAL EXCEPTION/i.test(entry.message));
72
+ const anyFilterApplied = !!(appId || pid || tag || level || contains || since_seconds);
73
+ if (anyFilterApplied && logs.length === 0)
74
+ return { device: response.device, logs: [], crashLines: [], logCount: 0, source: response.source, meta: response.meta, error: { code: 'LOGS_UNAVAILABLE', message: 'No logs match filters' } };
75
+ return { device: response.device, logs, crashLines, logCount: response.logCount, source: response.source, meta: response.meta };
193
76
  }
194
77
  static async startLogStreamHandler({ platform, packageName, level, sessionId, deviceId }) {
195
78
  const sid = sessionId || 'default';
@@ -232,146 +115,16 @@ export class ToolsObserve {
232
115
  static async getScreenFingerprintHandler({ platform, deviceId } = {}) {
233
116
  const { observe, resolved } = await ToolsObserve.resolveObserve(platform, deviceId);
234
117
  // Both observes implement getScreenFingerprint
235
- return await observe.getScreenFingerprint(resolved.id);
118
+ return await (observe).getScreenFingerprint(resolved.id);
236
119
  }
237
120
  static async captureDebugSnapshotHandler({ reason, includeLogs = true, includeScreenshot = true, logLines = 200, platform, appId, deviceId, sessionId } = {}) {
238
121
  const { resolved } = await ToolsObserve.resolveObserve(platform, deviceId, appId);
239
- return withResolvedDevice(resolved, () => ToolsObserve.collectDebugSnapshot({ reason, includeLogs, includeScreenshot, logLines,
240
- platform: resolved.platform, appId, deviceId: resolved.id, sessionId }));
122
+ return withResolvedDevice(resolved, () => ToolsObserve.collectDebugSnapshot({
123
+ reason, includeLogs, includeScreenshot, logLines,
124
+ platform: resolved.platform, appId, deviceId: resolved.id, sessionId
125
+ }));
241
126
  }
242
- static async collectDebugSnapshot({ reason, includeLogs = true, includeScreenshot = true, logLines = 200, platform, appId, deviceId, sessionId } = {}) {
243
- if (!includeScreenshot)
244
- omitEvidence('screenshot');
245
- if (!includeLogs)
246
- omitEvidence('logs');
247
- const timestamp = Date.now();
248
- const raw = {
249
- timestamp,
250
- snapshot_revision: 0,
251
- captured_at_ms: timestamp,
252
- reason: reason || '',
253
- activity: null,
254
- fingerprint: null,
255
- screenshot: null,
256
- ui_tree: null,
257
- logs: []
258
- };
259
- // Parallel fetches for performance: screenshot, current screen, fingerprint, ui tree, and log stream/get logs
260
- const sid = sessionId || 'default';
261
- const tasks = {
262
- screenshot: includeScreenshot ? measure('screenshot_ms', () => ToolsObserve.captureScreenshotHandler({ platform, deviceId })) : Promise.resolve(null),
263
- currentScreen: (!platform || platform === 'android') ? ToolsObserve.getCurrentScreenHandler({ deviceId }) : Promise.resolve(null),
264
- fingerprint: ToolsObserve.getScreenFingerprintHandler({ platform, deviceId }),
265
- uiTree: ToolsObserve.getUITreeHandler({ platform, deviceId }),
266
- readLogStream: includeLogs ? ToolsObserve.readLogStreamHandler({ platform, deviceId, sessionId: sid, limit: logLines }) : Promise.resolve({ entries: [] }),
267
- };
268
- const results = await Promise.allSettled(Object.values(tasks));
269
- const keys = Object.keys(tasks);
270
- // Map results back to keys
271
- for (let i = 0; i < results.length; i++) {
272
- const key = keys[i];
273
- const res = results[i];
274
- if (res.status === 'fulfilled') {
275
- const val = res.value;
276
- if (key === 'screenshot') {
277
- raw.screenshot = val && val.screenshot ? val.screenshot : null;
278
- }
279
- else if (key === 'currentScreen') {
280
- raw.activity = val && ((val.activity || val.shortActivity)) ? (val.activity || val.shortActivity) : raw.activity || '';
281
- }
282
- else if (key === 'fingerprint') {
283
- if (val && val.fingerprint)
284
- raw.fingerprint = val.fingerprint;
285
- if (val && val.activity)
286
- raw.activity = raw.activity || val.activity;
287
- if (val && val.error)
288
- raw.fingerprint_error = val.error;
289
- }
290
- else if (key === 'uiTree') {
291
- raw.ui_tree = val;
292
- if (val && val.error)
293
- raw.ui_tree_error = val.error;
294
- }
295
- else if (key === 'readLogStream') {
296
- // handle below after evaluating fallback
297
- // temporarily attach to out._streamEntries
298
- raw.logs = Array.isArray(val?.entries) ? val.entries : [];
299
- }
300
- }
301
- else {
302
- const errMsg = res.reason instanceof Error ? res.reason.message : String(res.reason);
303
- if (key === 'screenshot')
304
- raw.screenshot_error = errMsg;
305
- if (key === 'currentScreen')
306
- raw.activity_error = errMsg;
307
- if (key === 'fingerprint') {
308
- raw.fingerprint = null;
309
- raw.fingerprint_error = errMsg;
310
- }
311
- if (key === 'uiTree') {
312
- raw.ui_tree = null;
313
- raw.ui_tree_error = errMsg;
314
- }
315
- if (key === 'readLogStream') {
316
- raw.logs = [];
317
- raw.logs_error = errMsg;
318
- }
319
- }
320
- }
321
- // Logs: prefer stream entries, fallback to snapshot logs when empty
322
- if (includeLogs) {
323
- try {
324
- let entries = Array.isArray(raw.logs) ? raw.logs : [];
325
- if (!entries || entries.length === 0) {
326
- const gl = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, lines: logLines });
327
- const snapshotLogs = (gl && gl.logs) ? gl.logs : [];
328
- // raw may be structured entries or strings
329
- entries = snapshotLogs.slice(-Math.max(0, logLines)).map(item => {
330
- if (!item)
331
- return { timestamp: null, level: 'INFO', message: '' };
332
- if (typeof item === 'string') {
333
- const level = /\b(FATAL EXCEPTION|ERROR| E )\b/i.test(item) ? 'ERROR' : /\b(WARN| W )\b/i.test(item) ? 'WARN' : 'INFO';
334
- return { timestamp: null, level, message: item };
335
- }
336
- const msg = item.message || item.msg || JSON.stringify(item);
337
- const levelRaw = item.level || item.levelName || item._level || '';
338
- const level = (levelRaw && String(levelRaw)).toUpperCase() || (/\bERROR\b/i.test(msg) ? 'ERROR' : /\bWARN\b/i.test(msg) ? 'WARN' : 'INFO');
339
- const ts = item.timestamp || item._iso || null;
340
- const tsNum = (ts && typeof ts === 'string') ? (isNaN(new Date(ts).getTime()) ? null : new Date(ts).getTime()) : (typeof ts === 'number' ? ts : null);
341
- return { timestamp: tsNum, level, message: msg };
342
- });
343
- }
344
- else {
345
- entries = entries.map(ent => {
346
- const msg = (ent && (ent.message || ent.msg)) ? (ent.message || ent.msg) : (typeof ent === 'string' ? ent : JSON.stringify(ent));
347
- const levelRaw = (ent && (ent.level || ent.levelName || ent._level)) ? (ent.level || ent.levelName || ent._level) : '';
348
- const level = (levelRaw && String(levelRaw)).toString().toUpperCase() || (/\bERROR\b/i.test(msg) ? 'ERROR' : /\bWARN\b/i.test(msg) ? 'WARN' : 'INFO');
349
- let tsNum = null;
350
- const maybeIso = ent && (ent._iso || ent.timestamp);
351
- if (maybeIso && typeof maybeIso === 'string') {
352
- const d = new Date(maybeIso);
353
- if (!isNaN(d.getTime()))
354
- tsNum = d.getTime();
355
- }
356
- return { timestamp: tsNum, level, message: msg };
357
- });
358
- }
359
- raw.logs = entries;
360
- }
361
- catch (e) {
362
- raw.logs = [];
363
- raw.logs_error = e instanceof Error ? e.message : String(e);
364
- }
365
- }
366
- const snapshotDeviceKey = raw.ui_tree?.device
367
- ? `${raw.ui_tree.device.platform}:${raw.ui_tree.device.id}`
368
- : `${platform || 'unknown'}:${deviceId || 'default'}`;
369
- const snapshotMetadata = deriveSnapshotMetadata(snapshotDeviceKey, raw.ui_tree, 'snapshot', raw.ui_tree?.snapshot_revision ? null : (raw.fingerprint || raw.activity || null));
370
- raw.snapshot_revision = raw.ui_tree?.snapshot_revision ?? snapshotMetadata.snapshot_revision;
371
- raw.captured_at_ms = raw.ui_tree?.captured_at_ms ?? snapshotMetadata.captured_at_ms;
372
- raw.snapshot_delta = raw.ui_tree?.snapshot_delta ?? snapshotMetadata.snapshot_delta ?? null;
373
- raw.loading_state = raw.ui_tree?.loading_state ?? snapshotMetadata.loading_state;
374
- const semantic = deriveSnapshotSemantic(raw);
375
- return semantic ? { raw, semantic } : { raw };
127
+ static collectDebugSnapshot(input = {}) {
128
+ return collectDebugSnapshot(input, ToolsObserve);
376
129
  }
377
130
  }
@@ -0,0 +1,16 @@
1
+ import { execCommand } from "../../utils/ios/utils.js";
2
+ export const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3
+ export let iosExecCommand = execCommand;
4
+ export function _setIOSExecCommandForTests(fn) {
5
+ iosExecCommand = fn;
6
+ }
7
+ export function _resetIOSExecCommandForTests() {
8
+ iosExecCommand = execCommand;
9
+ }
10
+ export const iosActiveLogStreams = new Map();
11
+ export function _setIOSActiveLogStream(sessionId, file, device) {
12
+ iosActiveLogStreams.set(sessionId, { proc: { kill: () => false }, file, device });
13
+ }
14
+ export function _clearIOSActiveLogStream(sessionId) {
15
+ iosActiveLogStreams.delete(sessionId);
16
+ }
@@ -0,0 +1,12 @@
1
+ import { computeScreenFingerprint } from '../../utils/ui/index.js';
2
+ export async function collectGetScreenFingerprint(source, deviceId = 'booted') {
3
+ try {
4
+ const tree = await source.getUITree(deviceId);
5
+ if (!tree || tree.error)
6
+ return { fingerprint: null, error: tree && tree.error };
7
+ return { ...computeScreenFingerprint(tree, null, 'ios', 50), captured_at_ms: tree.captured_at_ms };
8
+ }
9
+ catch (e) {
10
+ return { fingerprint: null, error: e instanceof Error ? e.message : String(e) };
11
+ }
12
+ }