mobile-debug-mcp 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/.agents/skills/mcp-builder/SKILL.md +44 -64
  2. package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
  3. package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
  4. package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
  5. package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
  6. package/.agents/skills/test-authoring/SKILL.md +7 -6
  7. package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
  8. package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
  9. package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
  10. package/.github/copilot-instructions.md +14 -36
  11. package/.github/workflows/ci.yml +23 -2
  12. package/AGENTS.md +12 -3
  13. package/CONTRIBUTING.md +79 -0
  14. package/README.md +158 -124
  15. package/agent-plugin/.claude-plugin/plugin.json +2 -2
  16. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  17. package/agent-plugin/.cursor-plugin/plugin.json +2 -2
  18. package/agent-plugin/.mcp.json +1 -1
  19. package/agent-plugin/README.md +20 -6
  20. package/agent-plugin/mcp.json +1 -1
  21. package/agent-plugin/plugin.json +1 -1
  22. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -4
  23. package/dist/contracts/execution-controls.js +24 -0
  24. package/dist/contracts/selector.js +8 -0
  25. package/dist/contracts/target.js +15 -0
  26. package/dist/contracts/ui.js +1 -0
  27. package/dist/debugger/android.js +79 -0
  28. package/dist/debugger/diagnostics.js +54 -0
  29. package/dist/debugger/guard.js +30 -0
  30. package/dist/debugger/protocol.js +33 -0
  31. package/dist/debugger/sessions.js +304 -0
  32. package/dist/debugger/worker.js +176 -0
  33. package/dist/interact/adjustment/adjustment-context.js +41 -0
  34. package/dist/interact/adjustment/adjustment-gestures.js +55 -0
  35. package/dist/interact/adjustment/adjustment-plan.js +18 -0
  36. package/dist/interact/adjustment/adjustment-resolution.js +127 -0
  37. package/dist/interact/adjustment/adjustment-result.js +19 -0
  38. package/dist/interact/adjustment/adjustment-semantic.js +64 -0
  39. package/dist/interact/adjustment/adjustment-verification.js +35 -0
  40. package/dist/interact/adjustment/adjustment.js +52 -0
  41. package/dist/interact/adjustment/control-geometry.js +87 -0
  42. package/dist/interact/adjustment/control-values.js +136 -0
  43. package/dist/interact/adjustment/slider-labels.js +84 -0
  44. package/dist/interact/assertions/screen-assertion.js +37 -0
  45. package/dist/interact/assertions/state-assertion-context.js +35 -0
  46. package/dist/interact/assertions/state-assertion.js +76 -0
  47. package/dist/interact/assertions/state-comparison.js +141 -0
  48. package/dist/interact/assertions/visibility-assertion.js +34 -0
  49. package/dist/interact/conditional-action.js +12 -7
  50. package/dist/interact/index.js +65 -3074
  51. package/dist/interact/input-service.js +1 -0
  52. package/dist/interact/interaction-trace.js +21 -0
  53. package/dist/interact/model.js +1 -0
  54. package/dist/interact/observation.js +1 -0
  55. package/dist/interact/targeting/candidate-ancestor.js +58 -0
  56. package/dist/interact/targeting/candidate-result.js +23 -0
  57. package/dist/interact/targeting/candidate-scoring.js +50 -0
  58. package/dist/interact/targeting/element-helpers.js +216 -0
  59. package/dist/interact/targeting/find-element.js +65 -0
  60. package/dist/interact/targeting/nearby-control.js +56 -0
  61. package/dist/interact/targeting/resolved-elements.js +21 -0
  62. package/dist/interact/targeting/tap-target.js +17 -0
  63. package/dist/interact/targeting/target-scope.js +104 -0
  64. package/dist/interact/waits/legacy-log-wait.js +38 -0
  65. package/dist/interact/waits/legacy-wait-observations.js +103 -0
  66. package/dist/interact/waits/legacy-wait-state.js +34 -0
  67. package/dist/interact/waits/legacy-wait.js +48 -0
  68. package/dist/interact/waits/screen-wait.js +49 -0
  69. package/dist/interact/waits/ui-change-helpers.js +157 -0
  70. package/dist/interact/waits/ui-change-state.js +18 -0
  71. package/dist/interact/waits/ui-change-wait.js +77 -0
  72. package/dist/interact/waits/ui-wait-condition.js +56 -0
  73. package/dist/interact/waits/ui-wait-state.js +38 -0
  74. package/dist/interact/waits/ui-wait.js +106 -0
  75. package/dist/manage/android.js +55 -51
  76. package/dist/manage/index.js +38 -75
  77. package/dist/manage/ios/build-types.js +1 -0
  78. package/dist/manage/ios/build.js +210 -0
  79. package/dist/manage/ios/project-discovery.js +29 -0
  80. package/dist/manage/ios/scheme.js +18 -0
  81. package/dist/manage/ios.js +42 -256
  82. package/dist/observe/android/context.js +1 -0
  83. package/dist/observe/android/current-screen.js +55 -0
  84. package/dist/observe/android/fingerprint.js +13 -0
  85. package/dist/observe/android/log-stream.js +132 -0
  86. package/dist/observe/android/logs.js +107 -0
  87. package/dist/observe/android/screenshot.js +70 -0
  88. package/dist/observe/android/ui-tree.js +107 -0
  89. package/dist/observe/android.js +16 -520
  90. package/dist/observe/evidence.js +122 -76
  91. package/dist/observe/index.js +16 -263
  92. package/dist/observe/ios/context.js +16 -0
  93. package/dist/observe/ios/fingerprint.js +12 -0
  94. package/dist/observe/ios/log-stream.js +105 -0
  95. package/dist/observe/ios/logs.js +168 -0
  96. package/dist/observe/ios/screenshot.js +22 -0
  97. package/dist/observe/ios/ui-elements.js +241 -0
  98. package/dist/observe/ios/ui-tree.js +137 -0
  99. package/dist/observe/ios.js +17 -715
  100. package/dist/observe/logs/model.js +1 -0
  101. package/dist/{network → observe/network}/index.js +2 -2
  102. package/dist/observe/readiness-service.js +13 -2
  103. package/dist/observe/readiness.js +44 -18
  104. package/dist/observe/screenshot-encoding.js +41 -0
  105. package/dist/observe/snapshot-collection.js +79 -0
  106. package/dist/observe/snapshot-logs.js +30 -0
  107. package/dist/observe/snapshot-metadata.js +43 -31
  108. package/dist/observe/snapshot-semantic.js +115 -0
  109. package/dist/observe/system-trace-android.js +135 -0
  110. package/dist/observe/system-trace.js +294 -0
  111. package/dist/runtime/action-results.js +270 -0
  112. package/dist/runtime/clock.js +4 -0
  113. package/dist/server/common.js +1 -256
  114. package/dist/server/definitions/assert.js +162 -0
  115. package/dist/server/definitions/diagnostic.js +118 -0
  116. package/dist/server/definitions/input.js +274 -0
  117. package/dist/server/definitions/manage.js +194 -0
  118. package/dist/server/definitions/observe.js +149 -0
  119. package/dist/server/definitions/target.js +138 -0
  120. package/dist/server/definitions/wait.js +105 -0
  121. package/dist/server/handlers/action-execution.js +163 -0
  122. package/dist/server/handlers/correlation.js +23 -0
  123. package/dist/server/handlers/debugger-handlers.js +48 -0
  124. package/dist/server/handlers/debugger-response.js +9 -0
  125. package/dist/server/handlers/diagnostic.js +69 -0
  126. package/dist/server/handlers/handler-types.js +1 -0
  127. package/dist/server/handlers/input.js +154 -0
  128. package/dist/server/handlers/interact.js +113 -0
  129. package/dist/server/handlers/journey-assertions.js +35 -0
  130. package/dist/server/handlers/journey.js +147 -0
  131. package/dist/server/handlers/manage.js +119 -0
  132. package/dist/server/handlers/observe.js +95 -0
  133. package/dist/server/handlers/payload.js +4 -0
  134. package/dist/server/handlers/tool-handlers.js +160 -0
  135. package/dist/server/schemas/condition-schema.js +2 -0
  136. package/dist/server/schemas/debugger-schema.js +47 -0
  137. package/dist/server/{journey-schema.js → schemas/journey-schema.js} +8 -22
  138. package/dist/server/schemas/system-trace-schema.js +17 -0
  139. package/dist/server/tool-definitions.js +62 -1134
  140. package/dist/server-core.js +4 -4
  141. package/dist/server.js +34 -0
  142. package/dist/system/android-status.js +101 -0
  143. package/dist/system/android.js +37 -27
  144. package/dist/system/index.js +13 -438
  145. package/dist/system/ios-status.js +132 -0
  146. package/dist/system/ios.js +11 -8
  147. package/dist/system/model.js +1 -0
  148. package/dist/system/probes.js +105 -0
  149. package/dist/system/status-summary.js +43 -0
  150. package/dist/system/unsupported-status.js +52 -0
  151. package/dist/utils/android/commands.js +89 -0
  152. package/dist/utils/android/gradle.js +139 -0
  153. package/dist/utils/android/logs.js +103 -0
  154. package/dist/utils/android/ui-tree.js +233 -0
  155. package/dist/utils/android/utils.js +6 -564
  156. package/dist/utils/cli/idb/idb-helper.js +4 -6
  157. package/dist/utils/cli/idb/install-idb.js +13 -9
  158. package/dist/utils/cli/ios/preflight-ios.js +42 -38
  159. package/dist/utils/exec.js +2 -0
  160. package/dist/utils/ios/utils.js +45 -46
  161. package/dist/utils/java.js +75 -57
  162. package/dist/utils/operation-budget.js +2 -0
  163. package/dist/utils/resolve-device.js +6 -2
  164. package/dist/utils/ui/index.js +26 -26
  165. package/docs/CHANGELOG.md +48 -0
  166. package/docs/README.md +38 -0
  167. package/docs/benchmarks/README.md +18 -0
  168. package/docs/benchmarks/interaction-latency/README.md +14 -0
  169. package/docs/benchmarks/interaction-latency/final.md +4 -1
  170. package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
  171. package/docs/benchmarks/ui-tree-performance/README.md +24 -16
  172. package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
  173. package/docs/code-structure.md +118 -0
  174. package/docs/specs/001-state-verification/spec.md +16 -14
  175. package/docs/specs/002-richer-element-identity/spec.md +12 -10
  176. package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
  177. package/docs/specs/004-action-verification-routing/spec.md +3 -3
  178. package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
  179. package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
  180. package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
  181. package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
  182. package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
  183. package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
  184. package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
  185. package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
  186. package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
  187. package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
  188. package/docs/specs/014-actionability-resolution/spec.md +20 -18
  189. package/docs/specs/015-adjustable-control-support/spec.md +17 -15
  190. package/docs/specs/016-environment-tools/spec.md +20 -20
  191. package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
  192. package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
  193. package/docs/specs/020-conditional-actions/intent.md +5 -3
  194. package/docs/specs/020-conditional-actions/plan.md +8 -9
  195. package/docs/specs/020-conditional-actions/spec.md +4 -2
  196. package/docs/specs/021-app-readiness-signals/intent.md +4 -2
  197. package/docs/specs/021-app-readiness-signals/plan.md +7 -6
  198. package/docs/specs/021-app-readiness-signals/spec.md +3 -1
  199. package/docs/specs/022-correlated-evidence/intent.md +4 -4
  200. package/docs/specs/022-correlated-evidence/plan.md +10 -11
  201. package/docs/specs/022-correlated-evidence/spec.md +3 -1
  202. package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
  203. package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
  204. package/docs/specs/023-android-system-traces/intent.md +41 -0
  205. package/docs/specs/023-android-system-traces/plan.md +115 -0
  206. package/docs/specs/023-android-system-traces/spec.md +122 -0
  207. package/docs/specs/024-android-heap-dump-capture/intent.md +41 -0
  208. package/docs/specs/025-android-runtime-debugger/intent.md +41 -0
  209. package/docs/specs/025-android-runtime-debugger/plan.md +106 -0
  210. package/docs/specs/025-android-runtime-debugger/spec.md +183 -0
  211. package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
  212. package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
  213. package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
  214. package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
  215. package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
  216. package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
  217. package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
  218. package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
  219. package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
  220. package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
  221. package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
  222. package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
  223. package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
  224. package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
  225. package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
  226. package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
  227. package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
  228. package/docs/specs/030-android-coroutine-validation/report.md +240 -0
  229. package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
  230. package/docs/tools/TOOLS.md +31 -16
  231. package/docs/tools/{evidence.md → correlated-evidence.md} +14 -3
  232. package/docs/tools/debugger.md +180 -0
  233. package/docs/tools/interact.md +42 -20
  234. package/docs/tools/manage.md +25 -44
  235. package/docs/tools/observe.md +64 -25
  236. package/docs/tools/system-traces.md +134 -0
  237. package/docs/tools/system.md +12 -5
  238. package/package.json +11 -8
  239. package/scripts/architecture-graph.mjs +69 -0
  240. package/scripts/check-architecture.mjs +47 -0
  241. package/scripts/code-quality-baseline.json +1 -0
  242. package/scripts/code-quality.mjs +161 -0
  243. package/src/contracts/execution-controls.ts +26 -0
  244. package/src/contracts/selector.ts +17 -0
  245. package/src/contracts/target.ts +19 -0
  246. package/src/contracts/ui.ts +94 -0
  247. package/src/debugger/android.ts +66 -0
  248. package/src/debugger/diagnostics.ts +43 -0
  249. package/src/debugger/guard.ts +31 -0
  250. package/src/debugger/java/DebuggerWorker.java +166 -0
  251. package/src/debugger/protocol.ts +37 -0
  252. package/src/debugger/sessions.ts +327 -0
  253. package/src/debugger/worker.ts +135 -0
  254. package/src/interact/adjustment/adjustment-context.ts +83 -0
  255. package/src/interact/adjustment/adjustment-gestures.ts +48 -0
  256. package/src/interact/adjustment/adjustment-plan.ts +23 -0
  257. package/src/interact/adjustment/adjustment-resolution.ts +104 -0
  258. package/src/interact/adjustment/adjustment-result.ts +34 -0
  259. package/src/interact/adjustment/adjustment-semantic.ts +53 -0
  260. package/src/interact/adjustment/adjustment-verification.ts +60 -0
  261. package/src/interact/adjustment/adjustment.ts +49 -0
  262. package/src/interact/adjustment/control-geometry.ts +120 -0
  263. package/src/interact/adjustment/control-values.ts +141 -0
  264. package/src/interact/adjustment/slider-labels.ts +84 -0
  265. package/src/interact/assertions/screen-assertion.ts +39 -0
  266. package/src/interact/assertions/state-assertion-context.ts +48 -0
  267. package/src/interact/assertions/state-assertion.ts +64 -0
  268. package/src/interact/assertions/state-comparison.ts +168 -0
  269. package/src/interact/assertions/visibility-assertion.ts +41 -0
  270. package/src/interact/conditional-action.ts +18 -12
  271. package/src/interact/index.ts +76 -3491
  272. package/src/interact/input-service.ts +11 -0
  273. package/src/interact/interaction-trace.ts +36 -0
  274. package/src/interact/model.ts +109 -0
  275. package/src/interact/observation.ts +7 -0
  276. package/src/interact/targeting/candidate-ancestor.ts +49 -0
  277. package/src/interact/targeting/candidate-result.ts +24 -0
  278. package/src/interact/targeting/candidate-scoring.ts +42 -0
  279. package/src/interact/targeting/element-helpers.ts +243 -0
  280. package/src/interact/targeting/find-element.ts +47 -0
  281. package/src/interact/targeting/nearby-control.ts +46 -0
  282. package/src/interact/targeting/resolved-elements.ts +20 -0
  283. package/src/interact/targeting/tap-target.ts +22 -0
  284. package/src/interact/targeting/target-scope.ts +121 -0
  285. package/src/interact/waits/legacy-log-wait.ts +30 -0
  286. package/src/interact/waits/legacy-wait-observations.ts +55 -0
  287. package/src/interact/waits/legacy-wait-state.ts +48 -0
  288. package/src/interact/waits/legacy-wait.ts +34 -0
  289. package/src/interact/waits/screen-wait.ts +37 -0
  290. package/src/interact/waits/ui-change-helpers.ts +184 -0
  291. package/src/interact/waits/ui-change-state.ts +26 -0
  292. package/src/interact/waits/ui-change-wait.ts +59 -0
  293. package/src/interact/waits/ui-wait-condition.ts +52 -0
  294. package/src/interact/waits/ui-wait-state.ts +55 -0
  295. package/src/interact/waits/ui-wait.ts +84 -0
  296. package/src/manage/android.ts +56 -50
  297. package/src/manage/index.ts +63 -93
  298. package/src/manage/ios/build-types.ts +14 -0
  299. package/src/manage/ios/build.ts +230 -0
  300. package/src/manage/ios/project-discovery.ts +26 -0
  301. package/src/manage/ios/scheme.ts +16 -0
  302. package/src/manage/ios.ts +65 -298
  303. package/src/observe/android/context.ts +1 -0
  304. package/src/observe/android/current-screen.ts +63 -0
  305. package/src/observe/android/fingerprint.ts +13 -0
  306. package/src/observe/android/log-stream.ts +127 -0
  307. package/src/observe/android/logs.ts +110 -0
  308. package/src/observe/android/screenshot.ts +79 -0
  309. package/src/observe/android/ui-tree.ts +113 -0
  310. package/src/observe/android.ts +17 -531
  311. package/src/observe/evidence.ts +130 -71
  312. package/src/observe/index.ts +24 -286
  313. package/src/observe/ios/context.ts +22 -0
  314. package/src/observe/ios/fingerprint.ts +12 -0
  315. package/src/observe/ios/log-stream.ts +103 -0
  316. package/src/observe/ios/logs.ts +179 -0
  317. package/src/observe/ios/screenshot.ts +26 -0
  318. package/src/observe/ios/ui-elements.ts +266 -0
  319. package/src/observe/ios/ui-tree.ts +143 -0
  320. package/src/observe/ios.ts +18 -749
  321. package/src/observe/logs/model.ts +11 -0
  322. package/src/{network → observe/network}/index.ts +2 -2
  323. package/src/observe/readiness-service.ts +10 -3
  324. package/src/observe/readiness.ts +49 -22
  325. package/src/observe/screenshot-encoding.ts +32 -0
  326. package/src/observe/snapshot-collection.ts +69 -0
  327. package/src/observe/snapshot-logs.ts +26 -0
  328. package/src/observe/snapshot-metadata.ts +51 -31
  329. package/src/observe/snapshot-semantic.ts +153 -0
  330. package/src/observe/system-trace-android.ts +133 -0
  331. package/src/observe/system-trace.ts +227 -0
  332. package/src/runtime/action-results.ts +376 -0
  333. package/src/runtime/clock.ts +9 -0
  334. package/src/server/common.ts +9 -359
  335. package/src/server/definitions/assert.ts +164 -0
  336. package/src/server/definitions/diagnostic.ts +120 -0
  337. package/src/server/definitions/input.ts +276 -0
  338. package/src/server/definitions/manage.ts +196 -0
  339. package/src/server/definitions/observe.ts +151 -0
  340. package/src/server/definitions/target.ts +140 -0
  341. package/src/server/definitions/wait.ts +107 -0
  342. package/src/server/handlers/action-execution.ts +164 -0
  343. package/src/server/handlers/correlation.ts +20 -0
  344. package/src/server/handlers/debugger-handlers.ts +39 -0
  345. package/src/server/handlers/debugger-response.ts +7 -0
  346. package/src/server/handlers/diagnostic.ts +79 -0
  347. package/src/server/handlers/handler-types.ts +7 -0
  348. package/src/server/handlers/input.ts +173 -0
  349. package/src/server/handlers/interact.ts +135 -0
  350. package/src/server/handlers/journey-assertions.ts +39 -0
  351. package/src/server/handlers/journey.ts +132 -0
  352. package/src/server/handlers/manage.ts +141 -0
  353. package/src/server/handlers/observe.ts +116 -0
  354. package/src/server/handlers/payload.ts +36 -0
  355. package/src/server/handlers/tool-handlers.ts +159 -0
  356. package/src/server/schemas/condition-schema.ts +2 -0
  357. package/src/server/schemas/debugger-schema.ts +43 -0
  358. package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +8 -22
  359. package/src/server/schemas/system-trace-schema.ts +17 -0
  360. package/src/server/tool-definitions.ts +69 -1140
  361. package/src/server-core.ts +4 -4
  362. package/src/server.ts +25 -1
  363. package/src/system/android-status.ts +124 -0
  364. package/src/system/android.ts +33 -21
  365. package/src/system/index.ts +21 -523
  366. package/src/system/ios-status.ts +147 -0
  367. package/src/system/ios.ts +14 -10
  368. package/src/system/model.ts +41 -0
  369. package/src/system/probes.ts +114 -0
  370. package/src/system/status-summary.ts +47 -0
  371. package/src/system/unsupported-status.ts +55 -0
  372. package/src/types.ts +16 -106
  373. package/src/utils/android/commands.ts +81 -0
  374. package/src/utils/android/gradle.ts +132 -0
  375. package/src/utils/android/logs.ts +103 -0
  376. package/src/utils/android/ui-tree.ts +239 -0
  377. package/src/utils/android/utils.ts +16 -556
  378. package/src/utils/cli/idb/check-idb.ts +4 -4
  379. package/src/utils/cli/idb/idb-helper.ts +6 -8
  380. package/src/utils/cli/idb/install-idb.ts +15 -9
  381. package/src/utils/cli/ios/preflight-ios.ts +39 -31
  382. package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
  383. package/src/utils/exec.ts +2 -0
  384. package/src/utils/ios/utils.ts +57 -61
  385. package/src/utils/java.ts +71 -51
  386. package/src/utils/operation-budget.ts +2 -0
  387. package/src/utils/resolve-device.ts +11 -6
  388. package/src/utils/ui/index.ts +28 -28
  389. package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
  390. package/test/device/automated/debugger/coroutine-support.ts +140 -0
  391. package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
  392. package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
  393. package/test/device/automated/debugger/runtime.android.test.ts +219 -0
  394. package/test/device/automated/interact/interaction.android.test.ts +56 -0
  395. package/test/device/automated/observe/system-trace.smoke.ts +104 -0
  396. package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
  397. package/test/fixtures/contracts/tool-definitions.json +3536 -0
  398. package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
  399. package/test/fixtures/coroutine-app/README.md +48 -0
  400. package/test/fixtures/coroutine-app/build.ts +35 -0
  401. package/test/fixtures/debugger-app/MainActivity.java +67 -0
  402. package/test/fixtures/debugger-app/MainActivity.kt +32 -0
  403. package/test/fixtures/debugger-app/NoLocals.java +7 -0
  404. package/test/fixtures/debugger-app/README.md +65 -0
  405. package/test/fixtures/debugger-app/build.ts +39 -0
  406. package/test/fixtures/latency-journey-app/README.md +2 -0
  407. package/test/fixtures/readiness-app/README.md +2 -0
  408. package/test/helpers/perfetto.ts +59 -0
  409. package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
  410. package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
  411. package/test/unit/debugger/diagnostics.test.ts +80 -0
  412. package/test/unit/debugger/guards.test.ts +50 -0
  413. package/test/unit/debugger/native.test.ts +33 -0
  414. package/test/unit/debugger/preparation.test.ts +32 -0
  415. package/test/unit/debugger/protocol.test.ts +15 -0
  416. package/test/unit/debugger/sessions.test.ts +83 -0
  417. package/test/unit/interact/adjustment_service.test.ts +42 -0
  418. package/test/unit/interact/adjustment_verification.test.ts +35 -0
  419. package/test/unit/interact/clock_dependencies.test.ts +63 -0
  420. package/test/unit/interact/element_helpers.test.ts +19 -0
  421. package/test/unit/interact/legacy_wait_service.test.ts +17 -0
  422. package/test/unit/interact/state_comparison.test.ts +20 -0
  423. package/test/unit/interact/target_scope.test.ts +35 -0
  424. package/test/unit/network/get_network_activity.test.ts +1 -1
  425. package/test/unit/observe/readiness.test.ts +3 -1
  426. package/test/unit/observe/screenshot-encoding.test.ts +30 -0
  427. package/test/unit/observe/snapshot-logs.test.ts +14 -0
  428. package/test/unit/observe/system-trace-android.test.ts +52 -0
  429. package/test/unit/observe/system-trace.test.ts +166 -0
  430. package/test/unit/quality/architecture.test.ts +60 -0
  431. package/test/unit/quality/code-quality.test.ts +58 -0
  432. package/test/unit/quality/interaction_boundaries.test.ts +24 -0
  433. package/test/unit/server/conditional_actions.test.ts +1 -1
  434. package/test/unit/server/conditional_schema.test.ts +2 -2
  435. package/test/unit/server/debugger-response.test.ts +63 -0
  436. package/test/unit/server/system-trace.test.ts +64 -0
  437. package/test/unit/server/tool_definitions_parity.test.ts +8 -0
  438. package/dist/server/tool-handlers.js +0 -893
  439. package/docs/ROADMAP.md +0 -646
  440. package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
  441. package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
  442. package/docs/specs/baseline-spec-v0.md +0 -312
  443. package/docs/specs/mcp-tooling-spec-v1.md +0 -446
  444. package/src/server/tool-handlers.ts +0 -923
  445. /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
  446. /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
  447. /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
  448. /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
@@ -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
  }
@@ -52,6 +53,13 @@ export function validateActionRef(value) {
52
53
  throw new Error('INVALID_ARGUMENT: invalid actionRef');
53
54
  return { sessionId: ref.sessionId, actionId: ref.actionId };
54
55
  }
56
+ export function evidenceSessionId() { return sessionId; }
57
+ export function currentEvidenceCorrelationId() { return context.getStore()?.correlationId; }
58
+ export function bindSystemTrace(traceId) {
59
+ const current = context.getStore();
60
+ if (current)
61
+ current.systemTraceId = traceId;
62
+ }
55
63
  export function currentEvidenceActionId() { return context.getStore()?.action?.id; }
56
64
  export function bindEvidenceDevice(device) {
57
65
  const current = context.getStore();
@@ -99,11 +107,13 @@ export function omitEvidence(kind) {
99
107
  if (!current)
100
108
  return;
101
109
  const start = stamp();
102
- current.observations.push({ value: {
110
+ current.observations.push({
111
+ value: {
103
112
  evidence_id: randomUUID(), kind, status: 'omitted', capture_started_at_ms: null,
104
113
  capture_completed_at_ms: null, source_captured_at_ms: null, age_ms: null,
105
114
  freshness: kind === 'logs' ? 'not_applicable' : 'unknown', freshness_reasons: [], device: current.device
106
- }, 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
+ });
107
117
  }
108
118
  export async function captureEvidence(kind, device, operation) {
109
119
  const current = context.getStore();
@@ -114,45 +124,19 @@ export async function captureEvidence(kind, device, operation) {
114
124
  bindEvidenceDevice(target);
115
125
  const order = orderFor(target);
116
126
  const start = stamp();
117
- const acquisition = { value: {
127
+ const acquisition = {
128
+ value: {
118
129
  evidence_id: randomUUID(), kind, status: 'failed', capture_started_at_ms: start.wall,
119
130
  capture_completed_at_ms: null, source_captured_at_ms: null, age_ms: null,
120
131
  freshness: 'unknown', freshness_reasons: [], device: target
121
132
  }, start, end: null, order, startSequence: order?.sequence ?? 0,
122
- endSequence: 0, overlapping: !!order?.active, reused: false };
133
+ endSequence: 0, overlapping: !!order?.active, reused: false
134
+ };
123
135
  current.observations.push(acquisition);
124
136
  try {
125
- const result = await operation();
126
- const value = acquisition.value;
127
- const resolved = validDevice(result?.device) ?? target;
128
- value.device = resolved;
129
- if (resolved)
130
- bindEvidenceDevice(resolved);
131
- const failure = result?.error ?? result?.meta?.error;
132
- value.status = failure || result?.success === false || (kind === 'fingerprint' && !result?.fingerprint) ? 'failed' : 'captured';
133
- if (failure)
134
- value.error = failure;
135
- const source = result?.captured_at_ms;
136
- if (Number.isFinite(source) && source > 0) {
137
- value.source_captured_at_ms = source;
138
- acquisition.reused = source < start.wall;
139
- }
140
- if (kind === 'ui_tree') {
141
- value.snapshot_session_id = result?.snapshot_session_id;
142
- value.snapshot_revision = result?.snapshot_revision;
143
- }
144
- if (kind === 'logs') {
145
- const entries = result?.logs ?? result?.entries ?? [];
146
- const times = entries.map((e) => {
147
- const t = e?.timestamp ?? e?._iso;
148
- return typeof t === 'number' ? t : typeof t === 'string' ? Date.parse(t) : NaN;
149
- }).filter((t) => Number.isFinite(t));
150
- value.clock = 'device_or_unknown';
151
- value.event_time_comparable = false;
152
- value.earliest_entry_at_ms = times.length ? times.reduce((a, b) => Math.min(a, b)) : null;
153
- value.latest_entry_at_ms = times.length ? times.reduce((a, b) => Math.max(a, b)) : null;
154
- }
155
- return result;
137
+ const original = await operation();
138
+ recordObservationResult(acquisition, original, kind, target, start);
139
+ return original;
156
140
  }
157
141
  catch (error) {
158
142
  acquisition.value.error = error instanceof Error ? error.message : String(error);
@@ -182,57 +166,28 @@ function assemble(current) {
182
166
  const link = association(current);
183
167
  const action = link.association === 'unavailable' ? null : current.action;
184
168
  const clockChanged = end.wall < current.start.wall;
185
- const observations = current.observations.map(a => {
186
- const v = { ...a.value, freshness_reasons: [] };
187
- if (v.status === 'omitted')
188
- return v;
189
- const componentAction = sameDevice(v.device, action?.device ?? null) ? action : null;
190
- const sourceMono = a.end && v.source_captured_at_ms !== null ? a.end.mono + v.source_captured_at_ms - a.end.wall : a.end?.mono;
191
- const reason = (freshness, code) => { v.freshness = freshness; v.freshness_reasons.push(code); };
192
- if (v.kind === 'logs')
193
- reason('unknown', 'DEVICE_CLOCK_UNVERIFIED');
194
- else if (clockChanged || (a.end && a.end.wall < a.start.wall))
195
- reason('unknown', 'CLOCK_DISCONTINUITY');
196
- else if (v.source_captured_at_ms !== null && a.end && v.source_captured_at_ms > a.end.wall)
197
- reason('unknown', 'CLOCK_DISCONTINUITY');
198
- else if (!v.device || !a.order || orders.get(deviceKey(v.device)) !== a.order || v.status !== 'captured' || !a.end)
199
- reason('unknown', 'CAPTURE_TIME_UNKNOWN');
200
- else if (componentAction?.dispatchStart && sourceMono !== undefined && sourceMono < componentAction.dispatchStart.mono)
201
- reason('stale', 'BEFORE_ACTION');
202
- else if (a.order && (a.order.sequence > a.endSequence || (a.reused && sourceMono !== undefined && sourceMono < a.order.lastStarted)))
203
- reason('stale', 'SUPERSEDED_BY_ACTION');
204
- else if (a.overlapping)
205
- reason('unknown', 'OVERLAPPING_ACTION');
206
- else if (a.reused)
207
- reason('unknown', 'REUSED_OBSERVATION');
208
- else {
209
- v.freshness = 'fresh';
210
- }
211
- if (!clockChanged && a.end && a.end.wall >= a.start.wall && v.source_captured_at_ms !== null && v.source_captured_at_ms <= end.wall) {
212
- v.age_ms = Math.max(0, end.mono - a.end.mono + a.end.wall - v.source_captured_at_ms);
213
- }
214
- return { ...v, session_id: sessionId, correlation_id: componentAction?.correlationId ?? current.correlationId,
215
- action_id: componentAction?.id ?? null, association: componentAction ? 'internal' : link.association };
216
- });
169
+ const observations = current.observations.map(a => assembleObservation(a, action, current, link, end, clockChanged));
217
170
  return {
218
171
  session_id: sessionId, correlation_id: action?.correlationId ?? current.correlationId,
219
172
  action_id: action?.id ?? null, device: current.device, ...link,
220
173
  ...(current.ref ? { requested_action_ref: current.ref } : {}),
221
174
  operation_started_at_ms: current.start.wall, operation_completed_at_ms: current.end?.wall ?? end.wall,
222
- dispatch_started_at_ms: action?.dispatchStart?.wall ?? null,
223
- dispatch_completed_at_ms: action?.dispatchEnd?.wall ?? null,
224
- verification_completed_at_ms: current.verification?.wall ?? action?.verification?.wall ?? null,
225
- observations, trace_context: { supported: false, trace_id: null, markers: [] }
175
+ ...actionTimes(action, current),
176
+ observations, trace_context: { supported: !!current.systemTraceId, trace_id: current.systemTraceId ?? null, markers: [] }
226
177
  };
227
178
  }
228
179
  export async function withEvidence(options, operation) {
229
180
  prune();
230
181
  const parent = context.getStore();
231
182
  const id = randomUUID();
232
- const record = options.action ? { id, correlationId: parent?.correlationId ?? id, device: null,
233
- dispatchStart: null, dispatchEnd: null, verification: null, completed: null } : options.ref?.sessionId === sessionId ? active.get(options.ref.actionId) ?? history.get(options.ref.actionId) ?? null : null;
234
- const current = { correlationId: options.action ? record.correlationId : options.journey ? id : randomUUID(),
235
- 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
+ };
236
191
  if (options.action && record)
237
192
  active.set(id, record);
238
193
  if (parent && options.action)
@@ -271,3 +226,94 @@ export function completeJourneyVerification(actionId) {
271
226
  child.action.verification = child.verification;
272
227
  }
273
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
+ }