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
@@ -6,7 +6,7 @@ import { getIdbCmd, isIDBInstalled, commandWhich } from './idb-helper.js'
6
6
  const IDB_PKG = 'fb-idb'
7
7
 
8
8
  function runCommand(cmd: string, args: string[]): number {
9
- const res = spawnSync(cmd, args, { stdio: 'inherit' as any })
9
+ const res = spawnSync(cmd, args, { stdio: 'inherit' })
10
10
  return typeof res.status === 'number' ? res.status : 1
11
11
  }
12
12
 
@@ -42,14 +42,7 @@ async function main() {
42
42
  console.log('Auto-install enabled (MCP_AUTO_INSTALL_IDB=true or CI=true)')
43
43
  }
44
44
 
45
- const attempts: { name: string; cmd: string; args: string[] }[] = []
46
- if (commandWhich('pipx')) attempts.push({ name: 'pipx', cmd: 'pipx', args: ['install', IDB_PKG] })
47
- if (commandWhich('pip') || commandWhich('python3')) attempts.push({ name: 'pip', cmd: commandWhich('pip') ? 'pip' : 'python3', args: commandWhich('pip') ? ['install', '--user', IDB_PKG] : ['-m', 'pip', 'install', '--user', IDB_PKG] })
48
-
49
- // Add brew as a fallback on macOS if present (best-effort)
50
- if (process.platform === 'darwin' && commandWhich('brew')) {
51
- attempts.push({ name: 'brew', cmd: 'brew', args: ['install', 'idb'] })
52
- }
45
+ const attempts = installerCandidates()
53
46
 
54
47
  if (attempts.length === 0) {
55
48
  console.error('No installer tool (pipx/pip/brew) detected. Please install pipx or pip and re-run.')
@@ -88,3 +81,16 @@ if (scriptPath === process.argv[1]) {
88
81
  }
89
82
 
90
83
  export { main }
84
+
85
+ function installerCandidates() {
86
+ const attempts: { name: string; cmd: string; args: string[] }[] = []
87
+ if (commandWhich('pipx')) attempts.push({ name: 'pipx', cmd: 'pipx', args: ['install', IDB_PKG] })
88
+ if (commandWhich('pip') || commandWhich('python3')) attempts.push({ name: 'pip', cmd: commandWhich('pip') ? 'pip' : 'python3', args: commandWhich('pip') ? ['install', '--user', IDB_PKG] : ['-m', 'pip', 'install', '--user', IDB_PKG] })
89
+
90
+ // Add brew as a fallback on macOS if present (best-effort)
91
+ if (process.platform === 'darwin' && commandWhich('brew')) {
92
+ attempts.push({ name: 'brew', cmd: 'brew', args: ['install', 'idb'] })
93
+ }
94
+
95
+ return attempts
96
+ }
@@ -9,22 +9,21 @@ async function exists(p: string) {
9
9
  }
10
10
 
11
11
  async function findFirst(root: string, patterns: string[], maxDepth = 4): Promise<string | null> {
12
- const queue: Array<{dir:string, depth:number}> = [{dir: root, depth:0}]
12
+ const queue: Array<{ dir: string, depth: number }> = [{ dir: root, depth: 0 }]
13
13
  while (queue.length) {
14
- const {dir, depth} = queue.shift()!
14
+ const { dir, depth } = queue.shift()!
15
15
  try {
16
16
  console.error('DEBUG findFirst: reading dir', dir, 'depth', depth)
17
17
  const ents: fs.Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true })
18
- console.error('DEBUG findFirst: entries', ents.map(e=>e.name))
18
+ console.error('DEBUG findFirst: entries', ents.map(e => e.name))
19
19
  for (const e of ents) {
20
20
  const full = path.join(dir, e.name)
21
- for (const p of patterns) {
22
- if (e.name.endsWith(p)) {
23
- console.error('DEBUG findFirst: matched', full)
24
- return full
25
- }
21
+ const matched = patterns.some(pattern => e.name.endsWith(pattern))
22
+ if (matched) {
23
+ console.error('DEBUG findFirst: matched', full)
24
+ return full
26
25
  }
27
- if (e.isDirectory() && depth < maxDepth) queue.push({dir: full, depth: depth+1})
26
+ if (e.isDirectory() && depth < maxDepth) queue.push({ dir: full, depth: depth + 1 })
28
27
  }
29
28
  } catch (err) {
30
29
  console.error('DEBUG findFirst: read failed for', dir, err)
@@ -46,18 +45,7 @@ function startCompanionIfNeeded(companionPath: string | null, udid: string | nul
46
45
  }
47
46
 
48
47
  async function main() {
49
- const args = process.argv.slice(2)
50
- let projectArg: string | undefined
51
- let udid: string | undefined
52
- let startCompanion = false
53
- let kmpBuild: any = null
54
- for (let i=0;i<args.length;i++) {
55
- const a = args[i]
56
- if (a === '--project' && args[i+1]) { projectArg = args[i+1]; i++ }
57
- else if (a === '--udid' && args[i+1]) { udid = args[i+1]; i++ }
58
- else if (a === '--start-companion') startCompanion = true
59
- else if (!projectArg) projectArg = a
60
- }
48
+ const { projectArg, udid, startCompanion, kmpBuild } = argumentsForPreflight(process.argv.slice(2))
61
49
 
62
50
  const cwd = process.cwd()
63
51
  const projectRoot = projectArg ? path.resolve(projectArg) : cwd
@@ -98,13 +86,10 @@ async function main() {
98
86
  const companionPath = commandWhich('idb_companion')
99
87
  const companionAvailable = !!companionPath
100
88
 
101
- const suggestions: string[] = []
102
- if (!projectFile) suggestions.push('Provide correct project path or ensure .xcodeproj/.xcworkspace exists in project dir')
103
- if (!kmpFramework) suggestions.push('Run KMP Gradle task to produce Shared.framework before xcodebuild (e.g., :shared:embedAndSignAppleFrameworkForXcode)')
104
- if (!idbAvailable) suggestions.push('Ensure idb is in PATH or set MCP_IDB_PATH / IDB_PATH')
105
- if (!companionAvailable) suggestions.push('Install idb_companion and ensure it is in PATH')
89
+ const suggestions = preflightSuggestions(projectFile, kmpFramework, idbAvailable, companionAvailable)
106
90
 
107
- const result: any = {
91
+ const companionInfo: { cmd: string | null; installed: boolean; start?: ReturnType<typeof startCompanionIfNeeded> } = { cmd: companionPath, installed: companionAvailable }
92
+ const result = {
108
93
  ok: !!projectFile && !!idbAvailable,
109
94
  project: {
110
95
  root: projectRoot,
@@ -120,10 +105,7 @@ async function main() {
120
105
  cmd: idbPath,
121
106
  installed: idbAvailable
122
107
  },
123
- idb_companion: {
124
- cmd: companionPath,
125
- installed: companionAvailable
126
- },
108
+ idb_companion: companionInfo,
127
109
  suggestions
128
110
  }
129
111
 
@@ -142,3 +124,29 @@ main().catch(e => {
142
124
  console.log(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }, null, 2))
143
125
  process.exit(2)
144
126
  })
127
+
128
+ function argumentsForPreflight(args: string[]) {
129
+ let projectArg: string | undefined
130
+ let udid: string | undefined
131
+ let startCompanion = false
132
+ const kmpBuild = null
133
+ for (let i = 0; i < args.length; i++) {
134
+ const a = args[i]
135
+ if (a === '--project' && args[i + 1]) { projectArg = args[i + 1]; i++ }
136
+ else if (a === '--udid' && args[i + 1]) { udid = args[i + 1]; i++ }
137
+ else if (a === '--start-companion') startCompanion = true
138
+ else if (!projectArg) projectArg = a
139
+ }
140
+
141
+ return { projectArg, udid, startCompanion, kmpBuild }
142
+ }
143
+
144
+ function preflightSuggestions(projectFile: string | null, kmpFramework: string | null, idbAvailable: boolean, companionAvailable: boolean) {
145
+ const suggestions: string[] = []
146
+ if (!projectFile) suggestions.push('Provide correct project path or ensure .xcodeproj/.xcworkspace exists in project dir')
147
+ if (!kmpFramework) suggestions.push('Run KMP Gradle task to produce Shared.framework before xcodebuild (e.g., :shared:embedAndSignAppleFrameworkForXcode)')
148
+ if (!idbAvailable) suggestions.push('Ensure idb is in PATH or set MCP_IDB_PATH / IDB_PATH')
149
+ if (!companionAvailable) suggestions.push('Install idb_companion and ensure it is in PATH')
150
+
151
+ return suggestions
152
+ }
@@ -7,7 +7,7 @@ async function main() {
7
7
  const interact = new iOSInteract();
8
8
 
9
9
  console.log('Fetching UI tree...');
10
- const tree = await obs.getUITree(deviceId as any);
10
+ const tree = await obs.getUITree(deviceId);
11
11
  if (tree.error) {
12
12
  console.error('getUITree error:', tree.error);
13
13
  process.exit(2);
@@ -18,12 +18,12 @@ async function main() {
18
18
  process.exit(3);
19
19
  }
20
20
 
21
- const clickable = tree.elements.find((e: any) => e.clickable) || tree.elements[0];
21
+ const clickable = tree.elements.find((e: import('../../../types.js').UIElement) => e.clickable) || tree.elements[0];
22
22
  console.log('Using element:', clickable.text || '(no text)', 'clickable=', clickable.clickable, 'center=', clickable.center);
23
- const [x,y] = clickable.center || [0,0];
23
+ const [x, y] = clickable.center || [0, 0];
24
24
 
25
25
  console.log(`Tapping at ${x},${y}...`);
26
- const res = await interact.tap(x, y, deviceId as any);
26
+ const res = await interact.tap(x, y, deviceId);
27
27
  console.log('Tap result:', res);
28
28
 
29
29
  if (res.success) process.exit(0);
package/src/utils/exec.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { checkDebuggerGuard } from '../debugger/guard.js'
1
2
  import { remainingBudget } from './operation-budget.js'
2
3
  import { spawn } from 'child_process'
3
4
 
@@ -7,6 +8,7 @@ export async function execCmd(cmd: string, args: string[], opts: ExecOptions = {
7
8
  const { env, cwd, shell } = opts
8
9
  const timeout = remainingBudget(opts.timeout ?? 0)
9
10
  return new Promise((resolve, reject) => {
11
+ checkDebuggerGuard()
10
12
  const child = spawn(cmd, args, { env: { ...process.env, ...(env || {}) }, cwd, shell })
11
13
  let stdout = ''
12
14
  let stderr = ''
@@ -19,17 +19,14 @@ export function getConfiguredIdbPath(): string | undefined {
19
19
  for (const p of cfgPaths) {
20
20
  if (!p) continue
21
21
  try {
22
- if (fs.existsSync(p)) {
23
- const raw = fs.readFileSync(p, 'utf8')
24
- const json = JSON.parse(raw)
25
- if (json) {
26
- if (json.idbPath) return json.idbPath
27
- if (json.IDB_PATH) return json.IDB_PATH
28
- }
29
- }
30
- } catch {}
22
+ if (!fs.existsSync(p)) continue
23
+ const raw = fs.readFileSync(p, 'utf8')
24
+ const json = JSON.parse(raw)
25
+ if (json?.idbPath) return json.idbPath
26
+ if (json?.IDB_PATH) return json.IDB_PATH
27
+ } catch { }
31
28
  }
32
- } catch {}
29
+ } catch { }
33
30
  return undefined
34
31
  }
35
32
 
@@ -38,13 +35,13 @@ export function getIdbCmd() {
38
35
  if (cfg) return cfg
39
36
  if (process.env.IDB_PATH) return process.env.IDB_PATH
40
37
  try {
41
- const p = execSync('which idb', { stdio: ['ignore','pipe','ignore'] }).toString().trim()
38
+ const p = execSync('which idb', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
42
39
  if (p) return p
43
- } catch {}
40
+ } catch { }
44
41
  try {
45
- const p2 = execSync('command -v idb', { stdio: ['ignore','pipe','ignore'] }).toString().trim()
42
+ const p2 = execSync('command -v idb', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
46
43
  if (p2) return p2
47
- } catch {}
44
+ } catch { }
48
45
  // check common user locations
49
46
  const common = [
50
47
  `${process.env.HOME}/Library/Python/3.9/bin/idb`,
@@ -53,7 +50,7 @@ export function getIdbCmd() {
53
50
  '/usr/local/bin/idb',
54
51
  ]
55
52
  for (const c of common) {
56
- try { execSync(`test -x ${c}`, { stdio: ['ignore','pipe','ignore'] }); return c } catch {}
53
+ try { execSync(`test -x ${c}`, { stdio: ['ignore', 'pipe', 'ignore'] }); return c } catch { }
57
54
  }
58
55
  return 'idb'
59
56
  }
@@ -62,7 +59,7 @@ export async function isIDBInstalled(): Promise<boolean> {
62
59
  const cmd = getIdbCmd()
63
60
  if (!cmd) return false
64
61
  try {
65
- execSync(`${cmd} list-targets --json`, { stdio: ['ignore','pipe','pipe'], timeout: remainingBudget(5000), killSignal: 'SIGKILL' })
62
+ execSync(`${cmd} list-targets --json`, { stdio: ['ignore', 'pipe', 'pipe'], timeout: remainingBudget(5000), killSignal: 'SIGKILL' })
66
63
  return true
67
64
  } catch (e: unknown) {
68
65
  console.debug(`[isIDBInstalled] idb probe failed for '${cmd}': ${e instanceof Error ? e.message : String(e)}`)
@@ -89,7 +86,7 @@ export function execCommand(args: string[], deviceId: string = "booted"): Promis
89
86
  // Use spawn for better stream control and consistency with Android implementation
90
87
  const budgetMs = remainingBudget()
91
88
  const child = spawn(getXcrunCmd(), args)
92
-
89
+
93
90
  let stdout = ''
94
91
  let stderr = ''
95
92
 
@@ -106,8 +103,8 @@ export function execCommand(args: string[], deviceId: string = "booted"): Promis
106
103
  }
107
104
 
108
105
  const DEFAULT_XCRUN_LOG_TIMEOUT = parseInt(process.env.MCP_XCRUN_LOG_TIMEOUT || '', 10) || 30000 // env (ms) or default 30s
109
- const DEFAULT_XCRUN_CMD_TIMEOUT = parseInt(process.env.MCP_XCRUN_TIMEOUT || '', 10) || 60000 // env (ms) or default 60s
110
- const timeoutMs = budgetMs || (args.includes('log') ? DEFAULT_XCRUN_LOG_TIMEOUT : DEFAULT_XCRUN_CMD_TIMEOUT) // choose appropriate timeout
106
+ const DEFAULT_XCRUN_CMD_TIMEOUT = parseInt(process.env.MCP_XCRUN_TIMEOUT || '', 10) || 60000 // env (ms) or default 60s
107
+ const timeoutMs = budgetMs || (args.includes('log') ? DEFAULT_XCRUN_LOG_TIMEOUT : DEFAULT_XCRUN_CMD_TIMEOUT) // choose appropriate timeout
111
108
  const timeout = setTimeout(() => {
112
109
  child.kill()
113
110
  reject(new Error(`Command timed out after ${timeoutMs}ms: ${getXcrunCmd()} ${args.join(' ')}`))
@@ -135,12 +132,12 @@ export function execCommandWithDiagnostics(args: string[], deviceId: string = "b
135
132
  const DEFAULT_XCRUN_LOG_TIMEOUT = parseInt(process.env.MCP_XCRUN_LOG_TIMEOUT || '', 10) || 30000
136
133
  const DEFAULT_XCRUN_CMD_TIMEOUT = parseInt(process.env.MCP_XCRUN_TIMEOUT || '', 10) || 60000
137
134
  const timeoutMs = budgetMs || (args.includes('log') ? DEFAULT_XCRUN_LOG_TIMEOUT : DEFAULT_XCRUN_CMD_TIMEOUT)
138
- const res = spawnSync(getXcrunCmd(), args, { encoding: 'utf8', timeout: timeoutMs }) as any
135
+ const res = spawnSync(getXcrunCmd(), args, { encoding: 'utf8', timeout: timeoutMs })
139
136
  const runResult = {
140
137
  exitCode: typeof res.status === 'number' ? res.status : null,
141
138
  stdout: res.stdout || '',
142
139
  stderr: res.stderr || '',
143
- envSnapshot: makeEnvSnapshot(['PATH','IDB_PATH','JAVA_HOME','HOME']),
140
+ envSnapshot: makeEnvSnapshot(['PATH', 'IDB_PATH', 'JAVA_HOME', 'HOME']),
144
141
  command: getXcrunCmd(),
145
142
  args,
146
143
  deviceId
@@ -168,17 +165,17 @@ function parseRuntimeName(runtime: string): string {
168
165
  try {
169
166
  const parts = runtime.split('.')
170
167
  const lastPart = parts[parts.length - 1] // e.g. "iOS-17-0"
171
-
168
+
172
169
  // Split by hyphen to separate OS from version numbers
173
170
  // e.g. "iOS-17-0" -> ["iOS", "17", "0"]
174
171
  const segments = lastPart.split('-');
175
-
172
+
176
173
  if (segments.length > 1) {
177
- const os = segments[0]; // "iOS"
178
- const version = segments.slice(1).join('.'); // "17.0"
179
- return `${os} ${version}`;
174
+ const os = segments[0]; // "iOS"
175
+ const version = segments.slice(1).join('.'); // "17.0"
176
+ return `${os} ${version}`;
180
177
  }
181
-
178
+
182
179
  return lastPart;
183
180
  } catch {
184
181
  return runtime
@@ -220,18 +217,17 @@ export async function getIOSDeviceMetadata(deviceId: string = "booted"): Promise
220
217
 
221
218
  for (const runtime in devicesMap) {
222
219
  const devices = devicesMap[runtime]
223
- if (Array.isArray(devices)) {
224
- for (const device of devices) {
225
- if (deviceId === "booted" || device.udid === deviceId) {
226
- resolve({
227
- platform: "ios",
228
- id: device.udid,
229
- osVersion: parseRuntimeName(runtime),
230
- model: device.name,
231
- simulator: true,
232
- })
233
- return
234
- }
220
+ if (!Array.isArray(devices)) continue
221
+ for (const device of devices) {
222
+ if (deviceId === "booted" || device.udid === deviceId) {
223
+ resolve({
224
+ platform: "ios",
225
+ id: device.udid,
226
+ osVersion: parseRuntimeName(runtime),
227
+ model: device.name,
228
+ simulator: true,
229
+ })
230
+ return
235
231
  }
236
232
  }
237
233
  }
@@ -264,34 +260,34 @@ export async function listIOSDevices(appId?: string): Promise<DeviceInfo[]> {
264
260
  const bmap = bdata.devices || {}
265
261
  for (const rt in bmap) {
266
262
  const devs = bmap[rt]
267
- if (Array.isArray(devs)) for (const d of devs) bootedSet.add(d.udid)
263
+ if (!Array.isArray(devs)) continue
264
+ for (const d of devs) bootedSet.add(d.udid)
268
265
  }
269
- } catch {}
266
+ } catch { }
270
267
  }
271
268
 
272
269
  for (const runtime in devicesMap) {
273
270
  const devices = devicesMap[runtime]
274
- if (Array.isArray(devices)) {
275
- for (const device of devices) {
276
- const info: any = {
277
- platform: 'ios',
278
- id: device.udid,
279
- osVersion: parseRuntimeName(runtime),
280
- model: device.name,
281
- simulator: true,
282
- booted: bootedSet.has(device.udid)
283
- }
271
+ if (!Array.isArray(devices)) continue
272
+ for (const device of devices) {
273
+ const info: DeviceInfo = {
274
+ platform: 'ios',
275
+ id: device.udid,
276
+ osVersion: parseRuntimeName(runtime),
277
+ model: device.name,
278
+ simulator: true,
279
+ booted: bootedSet.has(device.udid)
280
+ }
284
281
 
285
- if (appId) {
286
- // check if installed
287
- const p = execCommand(['simctl', 'get_app_container', device.udid, appId, 'data'], device.udid)
288
- .then(() => { info.appInstalled = true })
289
- .catch(() => { info.appInstalled = false })
290
- .then(() => { out.push(info) })
291
- checks.push(p)
292
- } else {
293
- out.push(info)
294
- }
282
+ if (appId) {
283
+ // check if installed
284
+ const p = execCommand(['simctl', 'get_app_container', device.udid, appId, 'data'], device.udid)
285
+ .then(() => { info.appInstalled = true })
286
+ .catch(() => { info.appInstalled = false })
287
+ .then(() => { out.push(info) })
288
+ checks.push(p)
289
+ } else {
290
+ out.push(info)
295
291
  }
296
292
  }
297
293
  }
package/src/utils/java.ts CHANGED
@@ -21,40 +21,22 @@ function javaVersionOf(javaBin: string): string | undefined {
21
21
  } catch (e: unknown) { console.debug('[javaVersionOf] java -version failed: ' + String(e)); return undefined }
22
22
  }
23
23
 
24
- export async function detectJavaHome(): Promise<string | undefined> {
24
+ export async function detectJavaHome(options: { quiet?: boolean } = {}): Promise<string | undefined> {
25
+ const debug = (...args: unknown[]) => { if (!options.quiet) console.debug(...args) }
25
26
  try {
26
27
  // 1) Honor explicit ANDROID_STUDIO_JDK env (highest priority)
27
28
  const envStudio = process.env.ANDROID_STUDIO_JDK || process.env.ANDROID_STUDIO_JBR
28
29
  if (envStudio && existsSync(path.join(envStudio, 'bin', 'java'))) {
29
30
  const v = javaVersionOf(path.join(envStudio, 'bin', 'java'))
30
31
  if (isJavaVersionAcceptable(v)) {
31
- console.debug('[java.detect] Using ANDROID_STUDIO_JDK from env:', envStudio)
32
+ debug('[java.detect] Using ANDROID_STUDIO_JDK from env:', envStudio)
32
33
  return envStudio
33
34
  }
34
- console.debug('[java.detect] ANDROID_STUDIO_JDK present but java -version did not match expected versions')
35
+ debug('[java.detect] ANDROID_STUDIO_JDK present but java -version did not match expected versions')
35
36
  }
36
37
 
37
- // 2) Android Studio JBR candidates (prefer these over JAVA_HOME)
38
- const jbrCandidates = [
39
- '/Applications/Android Studio.app/Contents/jbr',
40
- '/Applications/Android Studio.app/Contents/jbr/Contents/Home',
41
- '/Applications/Android Studio Preview.app/Contents/jbr',
42
- '/Applications/Android Studio Preview.app/Contents/jbr/Contents/Home',
43
- '/Applications/Android Studio Preview 2022.3.app/Contents/jbr',
44
- '/Applications/Android Studio Preview 2022.3.app/Contents/jbr/Contents/Home',
45
- '/Applications/Android Studio Preview 2023.1.app/Contents/jbr',
46
- '/Applications/Android Studio Preview 2023.1.app/Contents/jbr/Contents/Home'
47
- ]
48
- for (const p of jbrCandidates) {
49
- const javaBin = path.join(p, 'bin', 'java')
50
- if (existsSync(javaBin)) {
51
- const v = javaVersionOf(javaBin)
52
- if (isJavaVersionAcceptable(v)) {
53
- console.debug('[java.detect] Found Android Studio JBR at:', p)
54
- return p
55
- }
56
- }
57
- }
38
+ const jbr = findStudioJava(debug)
39
+ if (jbr) return jbr
58
40
 
59
41
  // 3) If JAVA_HOME set, validate it (accept 17 or 21)
60
42
  if (process.env.JAVA_HOME) {
@@ -62,12 +44,12 @@ export async function detectJavaHome(): Promise<string | undefined> {
62
44
  const javaBin = path.join(process.env.JAVA_HOME, 'bin', 'java')
63
45
  const v = javaVersionOf(javaBin)
64
46
  if (isJavaVersionAcceptable(v)) {
65
- console.debug('[java.detect] Using JAVA_HOME from env:', process.env.JAVA_HOME)
47
+ debug('[java.detect] Using JAVA_HOME from env:', process.env.JAVA_HOME)
66
48
  return process.env.JAVA_HOME
67
49
  }
68
- console.debug('[java.detect] Existing JAVA_HOME does not appear to be acceptable Java (17/21), will search')
50
+ debug('[java.detect] Existing JAVA_HOME does not appear to be acceptable Java (17/21), will search')
69
51
  } catch {
70
- console.debug('[java.detect] Failed to validate existing JAVA_HOME, searching for JDK')
52
+ debug('[java.detect] Failed to validate existing JAVA_HOME, searching for JDK')
71
53
  }
72
54
  }
73
55
 
@@ -79,37 +61,75 @@ export async function detectJavaHome(): Promise<string | undefined> {
79
61
  try {
80
62
  const out17 = execSync('/usr/libexec/java_home -v 17', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim()
81
63
  if (out17) return out17
82
- } catch (e: unknown) { console.debug('[java.detect] /usr/libexec/java_home -v 17 failed: ' + String(e)) }
64
+ } catch (e: unknown) { debug('[java.detect] /usr/libexec/java_home -v 17 failed: ' + String(e)) }
83
65
  try {
84
66
  const out21 = execSync('/usr/libexec/java_home -v 21', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim()
85
67
  if (out21) return out21
86
- } catch (e: unknown) { console.debug('[java.detect] /usr/libexec/java_home -v 21 failed: ' + String(e)) }
68
+ } catch (e: unknown) { debug('[java.detect] /usr/libexec/java_home -v 21 failed: ' + String(e)) }
87
69
 
88
- // 6) macOS common JDK locations
89
- try {
90
- const homes = execSync('ls -1 /Library/Java/JavaVirtualMachines || true', { stdio: ['ignore', 'pipe', 'inherit'] }).toString().split(/\r?\n/).filter(Boolean)
91
- for (const h of homes) {
92
- if (h.toLowerCase().includes('17') || h.toLowerCase().includes('jdk-17') || h.toLowerCase().includes('21') || h.toLowerCase().includes('jdk-21')) {
93
- const candidate = `/Library/Java/JavaVirtualMachines/${h}/Contents/Home`
94
- return candidate
95
- }
70
+ const commonHome = findMacJavaHome(debug)
71
+ if (commonHome) return commonHome
72
+
73
+ return findLinuxJavaHome(debug)
74
+ } catch (e: unknown) {
75
+ debug('[java.detect] error detecting java home:', e instanceof Error ? e.message : String(e))
76
+ }
77
+ return undefined
78
+ }
79
+
80
+ function findStudioJava(debug: (...args: unknown[]) => void): string | undefined {
81
+ // 2) Android Studio JBR candidates (prefer these over JAVA_HOME)
82
+ const jbrCandidates = [
83
+ '/Applications/Android Studio.app/Contents/jbr',
84
+ '/Applications/Android Studio.app/Contents/jbr/Contents/Home',
85
+ '/Applications/Android Studio Preview.app/Contents/jbr',
86
+ '/Applications/Android Studio Preview.app/Contents/jbr/Contents/Home',
87
+ '/Applications/Android Studio Preview 2022.3.app/Contents/jbr',
88
+ '/Applications/Android Studio Preview 2022.3.app/Contents/jbr/Contents/Home',
89
+ '/Applications/Android Studio Preview 2023.1.app/Contents/jbr',
90
+ '/Applications/Android Studio Preview 2023.1.app/Contents/jbr/Contents/Home'
91
+ ]
92
+ for (const p of jbrCandidates) {
93
+ const javaBin = path.join(p, 'bin', 'java')
94
+ if (existsSync(javaBin)) {
95
+ const v = javaVersionOf(javaBin)
96
+ if (isJavaVersionAcceptable(v)) {
97
+ debug('[java.detect] Found Android Studio JBR at:', p)
98
+ return p
96
99
  }
97
- } catch (e: unknown) { console.debug('[java.detect] listing /Library/Java/JavaVirtualMachines failed: ' + String(e)) }
100
+ }
101
+ }
98
102
 
99
- // 7) Linux locations
100
- const linuxCandidates = [
101
- '/usr/lib/jvm/java-17-openjdk-amd64',
102
- '/usr/lib/jvm/java-17-openjdk',
103
- '/usr/lib/jvm/zulu17',
104
- '/usr/lib/jvm/temurin-17-jdk',
105
- '/usr/lib/jvm/temurin-21-jdk',
106
- '/usr/lib/jvm/java-21-openjdk-amd64'
107
- ]
108
- for (const p of linuxCandidates) {
109
- try { if (existsSync(p)) return p } catch (e: unknown) { console.debug(`[java.detect] checking linux candidate ${p} failed: ${String(e)}`) }
103
+ return undefined
104
+ }
105
+
106
+ function findMacJavaHome(debug: (...args: unknown[]) => void): string | undefined {
107
+ // 6) macOS common JDK locations
108
+ try {
109
+ const homes = execSync('ls -1 /Library/Java/JavaVirtualMachines || true', { stdio: ['ignore', 'pipe', 'inherit'] }).toString().split(/\r?\n/).filter(Boolean)
110
+ for (const h of homes) {
111
+ if (h.toLowerCase().includes('17') || h.toLowerCase().includes('jdk-17') || h.toLowerCase().includes('21') || h.toLowerCase().includes('jdk-21')) {
112
+ const candidate = `/Library/Java/JavaVirtualMachines/${h}/Contents/Home`
113
+ return candidate
114
+ }
110
115
  }
111
- } catch (e: unknown) {
112
- console.debug('[java.detect] error detecting java home:', e instanceof Error ? e.message : String(e))
116
+ } catch (e: unknown) { debug('[java.detect] listing /Library/Java/JavaVirtualMachines failed: ' + String(e)) }
117
+
118
+ return undefined
119
+ }
120
+
121
+ function findLinuxJavaHome(debug: (...args: unknown[]) => void): string | undefined {
122
+ // 7) Linux locations
123
+ const linuxCandidates = [
124
+ '/usr/lib/jvm/java-17-openjdk-amd64',
125
+ '/usr/lib/jvm/java-17-openjdk',
126
+ '/usr/lib/jvm/zulu17',
127
+ '/usr/lib/jvm/temurin-17-jdk',
128
+ '/usr/lib/jvm/temurin-21-jdk',
129
+ '/usr/lib/jvm/java-21-openjdk-amd64'
130
+ ]
131
+ for (const p of linuxCandidates) {
132
+ try { if (existsSync(p)) return p } catch (e: unknown) { debug(`[java.detect] checking linux candidate ${p} failed: ${String(e)}`) }
113
133
  }
114
134
  return undefined
115
135
  }
@@ -1,3 +1,4 @@
1
+ import { checkDebuggerGuard } from '../debugger/guard.js'
1
2
  import { AsyncLocalStorage } from 'node:async_hooks'
2
3
 
3
4
  export interface OperationBudget { deadline: number; dispatchStarted: boolean; parent?: OperationBudget }
@@ -36,6 +37,7 @@ export async function runWithSeparateBudget<T>(timeoutMs: number, operation: ()
36
37
  }
37
38
  }
38
39
  export function markDispatchStarted() {
40
+ checkDebuggerGuard(true)
39
41
  remainingBudget()
40
42
  let budget = budgets.getStore()
41
43
  while (budget) { budget.dispatchStarted = true; budget = budget.parent }
@@ -1,3 +1,4 @@
1
+ import { checkDebuggerGuard } from '../debugger/guard.js'
1
2
  import { bindEvidenceDevice } from '../observe/evidence.js'
2
3
  import { retainConnectedDeviceSnapshots } from '../observe/snapshot-metadata.js'
3
4
  import { measure } from './timing.js'
@@ -15,7 +16,7 @@ export interface ResolveOptions {
15
16
 
16
17
  function parseNumericVersion(v: string): number {
17
18
  if (!v) return 0
18
- const m = v.match(/(\d+)(?:[\.\-](\d+))?/)
19
+ const m = v.match(/(\d+)(?:[\.\-](\d+))?/)
19
20
  if (!m) return 0
20
21
  const major = parseInt(m[1], 10) || 0
21
22
  const minor = parseInt(m[2] || "0", 10) || 0
@@ -56,6 +57,7 @@ export function withResolvedDevice<T>(device: DeviceInfo, operation: () => Promi
56
57
  }
57
58
 
58
59
  export async function resolveTargetDevice(opts: ResolveOptions): Promise<DeviceInfo> {
60
+ checkDebuggerGuard()
59
61
  const device = await resolveDevice(opts)
60
62
  bindEvidenceDevice(device)
61
63
  return device
@@ -80,13 +82,17 @@ async function resolveDevice(opts: ResolveOptions): Promise<DeviceInfo> {
80
82
  return found
81
83
  }
82
84
 
85
+ return selectDevice(devices, platform, appId, prefer)
86
+ }
87
+
88
+ function selectDevice(devices: DeviceInfo[], platform: string, appId?: string, prefer?: ResolveOptions['prefer']): DeviceInfo {
83
89
  let candidates = devices.slice()
84
90
 
85
91
  if (prefer === "physical") candidates = candidates.filter(d => !d.simulator)
86
92
  if (prefer === "emulator") candidates = candidates.filter(d => d.simulator)
87
93
 
88
94
  if (appId) {
89
- const installed = candidates.filter(d => (d as any).appInstalled)
95
+ const installed = candidates.filter(d => d.appInstalled)
90
96
  if (installed.length > 0) candidates = installed
91
97
  }
92
98
 
@@ -101,7 +107,7 @@ async function resolveDevice(opts: ResolveOptions): Promise<DeviceInfo> {
101
107
 
102
108
  // Prefer booted iOS simulators if present
103
109
  if (platform === 'ios') {
104
- const booted = candidates.filter((d: any) => !!d.booted)
110
+ const booted = candidates.filter((d: DeviceInfo) => !!d.booted)
105
111
  if (booted.length === 1) return booted[0]
106
112
  if (booted.length > 1) return booted[0] // if multiple booted, pick the first
107
113
  }
@@ -111,9 +117,8 @@ async function resolveDevice(opts: ResolveOptions): Promise<DeviceInfo> {
111
117
  return candidates[0]
112
118
  }
113
119
 
114
- const list = candidates.map(d => ({ id: d.id, platform: d.platform, osVersion: d.osVersion, model: d.model, simulator: d.simulator, appInstalled: (d as any).appInstalled }))
115
- const err = new Error(`Multiple matching devices found: ${JSON.stringify(list, null, 2)}`)
116
- ;(err as any).devices = list
120
+ const list = candidates.map(d => ({ id: d.id, platform: d.platform, osVersion: d.osVersion, model: d.model, simulator: d.simulator, appInstalled: d.appInstalled }))
121
+ const err = Object.assign(new Error(`Multiple matching devices found: ${JSON.stringify(list, null, 2)}`), { devices: list })
117
122
  throw err
118
123
  }
119
124