mobile-debug-mcp 0.34.0 → 0.36.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 (500) 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/.cursor-plugin/marketplace.json +1 -1
  11. package/.github/copilot-instructions.md +14 -36
  12. package/.github/workflows/ci.yml +4 -4
  13. package/AGENTS.md +12 -3
  14. package/CONTRIBUTING.md +79 -0
  15. package/README.md +218 -129
  16. package/agent-plugin/.antigravity-plugin/README.md +48 -0
  17. package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
  18. package/agent-plugin/.claude-plugin/plugin.json +2 -2
  19. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  20. package/agent-plugin/.cursor-plugin/plugin.json +2 -2
  21. package/agent-plugin/.mcp.json +1 -1
  22. package/agent-plugin/README.md +50 -11
  23. package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
  24. package/agent-plugin/cursor/LICENSE +201 -0
  25. package/agent-plugin/cursor/README.md +9 -0
  26. package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +253 -0
  27. package/agent-plugin/mcp.json +1 -1
  28. package/agent-plugin/mcp_config.json +13 -0
  29. package/agent-plugin/plugin.json +1 -1
  30. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +62 -17
  31. package/dist/contracts/execution-controls.js +24 -0
  32. package/dist/contracts/selector.js +8 -0
  33. package/dist/contracts/target.js +15 -0
  34. package/dist/contracts/ui.js +1 -0
  35. package/dist/debugger/android.js +2 -1
  36. package/dist/debugger/diagnostics.js +54 -0
  37. package/dist/debugger/guard.js +4 -4
  38. package/dist/debugger/ios-worker.js +62 -0
  39. package/dist/debugger/ios.js +57 -0
  40. package/dist/debugger/protocol.js +33 -0
  41. package/dist/debugger/sessions.js +133 -74
  42. package/dist/debugger/worker.js +59 -46
  43. package/dist/interact/adjustment/adjustment-context.js +41 -0
  44. package/dist/interact/adjustment/adjustment-gestures.js +55 -0
  45. package/dist/interact/adjustment/adjustment-plan.js +18 -0
  46. package/dist/interact/adjustment/adjustment-resolution.js +127 -0
  47. package/dist/interact/adjustment/adjustment-result.js +19 -0
  48. package/dist/interact/adjustment/adjustment-semantic.js +64 -0
  49. package/dist/interact/adjustment/adjustment-verification.js +35 -0
  50. package/dist/interact/adjustment/adjustment.js +52 -0
  51. package/dist/interact/adjustment/control-geometry.js +87 -0
  52. package/dist/interact/adjustment/control-values.js +136 -0
  53. package/dist/interact/adjustment/slider-labels.js +84 -0
  54. package/dist/interact/assertions/screen-assertion.js +37 -0
  55. package/dist/interact/assertions/state-assertion-context.js +35 -0
  56. package/dist/interact/assertions/state-assertion.js +76 -0
  57. package/dist/interact/assertions/state-comparison.js +141 -0
  58. package/dist/interact/assertions/visibility-assertion.js +34 -0
  59. package/dist/interact/conditional-action.js +12 -7
  60. package/dist/interact/index.js +77 -3092
  61. package/dist/interact/input-service.js +1 -0
  62. package/dist/interact/interaction-trace.js +21 -0
  63. package/dist/interact/model.js +1 -0
  64. package/dist/interact/observation.js +1 -0
  65. package/dist/interact/targeting/candidate-ancestor.js +58 -0
  66. package/dist/interact/targeting/candidate-result.js +23 -0
  67. package/dist/interact/targeting/candidate-scoring.js +50 -0
  68. package/dist/interact/targeting/element-helpers.js +216 -0
  69. package/dist/interact/targeting/find-element.js +65 -0
  70. package/dist/interact/targeting/nearby-control.js +56 -0
  71. package/dist/interact/targeting/resolved-elements.js +21 -0
  72. package/dist/interact/targeting/tap-target.js +17 -0
  73. package/dist/interact/targeting/target-scope.js +104 -0
  74. package/dist/interact/waits/legacy-log-wait.js +38 -0
  75. package/dist/interact/waits/legacy-wait-observations.js +103 -0
  76. package/dist/interact/waits/legacy-wait-state.js +34 -0
  77. package/dist/interact/waits/legacy-wait.js +48 -0
  78. package/dist/interact/waits/screen-wait.js +49 -0
  79. package/dist/interact/waits/ui-change-helpers.js +157 -0
  80. package/dist/interact/waits/ui-change-state.js +18 -0
  81. package/dist/interact/waits/ui-change-wait.js +77 -0
  82. package/dist/interact/waits/ui-wait-condition.js +56 -0
  83. package/dist/interact/waits/ui-wait-state.js +38 -0
  84. package/dist/interact/waits/ui-wait.js +106 -0
  85. package/dist/manage/android.js +55 -51
  86. package/dist/manage/index.js +38 -75
  87. package/dist/manage/ios/build-types.js +1 -0
  88. package/dist/manage/ios/build.js +210 -0
  89. package/dist/manage/ios/project-discovery.js +29 -0
  90. package/dist/manage/ios/scheme.js +18 -0
  91. package/dist/manage/ios.js +44 -256
  92. package/dist/observe/android/context.js +1 -0
  93. package/dist/observe/android/current-screen.js +55 -0
  94. package/dist/observe/android/fingerprint.js +13 -0
  95. package/dist/observe/android/log-stream.js +132 -0
  96. package/dist/observe/android/logs.js +107 -0
  97. package/dist/observe/android/screenshot.js +70 -0
  98. package/dist/observe/android/ui-tree.js +107 -0
  99. package/dist/observe/android.js +16 -520
  100. package/dist/observe/evidence.js +114 -75
  101. package/dist/observe/index.js +16 -263
  102. package/dist/observe/ios/context.js +16 -0
  103. package/dist/observe/ios/fingerprint.js +12 -0
  104. package/dist/observe/ios/log-stream.js +105 -0
  105. package/dist/observe/ios/logs.js +168 -0
  106. package/dist/observe/ios/screenshot.js +22 -0
  107. package/dist/observe/ios/ui-elements.js +241 -0
  108. package/dist/observe/ios/ui-tree.js +137 -0
  109. package/dist/observe/ios-trace/artifact.js +63 -0
  110. package/dist/observe/ios-trace/capabilities.js +86 -0
  111. package/dist/observe/ios-trace/native.js +93 -0
  112. package/dist/observe/ios-trace/notification.js +35 -0
  113. package/dist/observe/ios-trace/process.js +98 -0
  114. package/dist/observe/ios-trace/sessions.js +244 -0
  115. package/dist/observe/ios.js +17 -715
  116. package/dist/observe/logs/model.js +1 -0
  117. package/dist/{network → observe/network}/index.js +2 -2
  118. package/dist/observe/readiness-service.js +5 -3
  119. package/dist/observe/readiness.js +33 -14
  120. package/dist/observe/screenshot-encoding.js +41 -0
  121. package/dist/observe/snapshot-collection.js +79 -0
  122. package/dist/observe/snapshot-logs.js +30 -0
  123. package/dist/observe/snapshot-metadata.js +43 -31
  124. package/dist/observe/snapshot-semantic.js +115 -0
  125. package/dist/observe/system-trace-error.js +7 -0
  126. package/dist/observe/system-trace-router.js +81 -0
  127. package/dist/observe/system-trace.js +26 -21
  128. package/dist/runtime/action-results.js +270 -0
  129. package/dist/runtime/clock.js +4 -0
  130. package/dist/server/common.js +1 -256
  131. package/dist/server/definitions/assert.js +162 -0
  132. package/dist/server/definitions/diagnostic.js +118 -0
  133. package/dist/server/definitions/input.js +274 -0
  134. package/dist/server/definitions/manage.js +194 -0
  135. package/dist/server/definitions/observe.js +149 -0
  136. package/dist/server/definitions/target.js +138 -0
  137. package/dist/server/definitions/wait.js +105 -0
  138. package/dist/server/handlers/action-execution.js +163 -0
  139. package/dist/server/handlers/correlation.js +23 -0
  140. package/dist/server/handlers/debugger-handlers.js +67 -0
  141. package/dist/server/handlers/debugger-response.js +9 -0
  142. package/dist/server/handlers/diagnostic.js +77 -0
  143. package/dist/server/handlers/handler-types.js +1 -0
  144. package/dist/server/handlers/input.js +154 -0
  145. package/dist/server/handlers/interact.js +113 -0
  146. package/dist/server/handlers/journey-assertions.js +35 -0
  147. package/dist/server/handlers/journey.js +147 -0
  148. package/dist/server/handlers/manage.js +119 -0
  149. package/dist/server/handlers/observe.js +95 -0
  150. package/dist/server/handlers/payload.js +4 -0
  151. package/dist/server/handlers/tool-handlers.js +170 -0
  152. package/dist/server/schemas/condition-schema.js +2 -0
  153. package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +20 -12
  154. package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
  155. package/dist/server/schemas/system-trace-schema.js +22 -0
  156. package/dist/server/tool-definitions.js +58 -1135
  157. package/dist/server-core.js +2 -2
  158. package/dist/server.js +4 -4
  159. package/dist/system/android-status.js +101 -0
  160. package/dist/system/android.js +37 -27
  161. package/dist/system/index.js +13 -439
  162. package/dist/system/ios-status.js +132 -0
  163. package/dist/system/ios.js +11 -8
  164. package/dist/system/model.js +1 -0
  165. package/dist/system/probes.js +105 -0
  166. package/dist/system/status-summary.js +43 -0
  167. package/dist/system/unsupported-status.js +52 -0
  168. package/dist/utils/android/commands.js +89 -0
  169. package/dist/utils/android/gradle.js +139 -0
  170. package/dist/utils/android/logs.js +103 -0
  171. package/dist/utils/android/ui-tree.js +233 -0
  172. package/dist/utils/android/utils.js +6 -564
  173. package/dist/utils/cli/idb/idb-helper.js +4 -6
  174. package/dist/utils/cli/idb/install-idb.js +13 -9
  175. package/dist/utils/cli/ios/preflight-ios.js +42 -38
  176. package/dist/utils/ios/utils.js +48 -46
  177. package/dist/utils/java.js +65 -49
  178. package/dist/utils/resolve-device.js +4 -2
  179. package/dist/utils/ui/index.js +26 -26
  180. package/docs/CHANGELOG.md +67 -1
  181. package/docs/README.md +38 -0
  182. package/docs/agent-plugin-installation-verification.md +39 -0
  183. package/docs/benchmarks/README.md +18 -0
  184. package/docs/benchmarks/interaction-latency/README.md +14 -0
  185. package/docs/benchmarks/interaction-latency/final.md +4 -1
  186. package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
  187. package/docs/benchmarks/ui-tree-performance/README.md +24 -16
  188. package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
  189. package/docs/code-structure.md +120 -0
  190. package/docs/specs/001-state-verification/spec.md +16 -14
  191. package/docs/specs/002-richer-element-identity/spec.md +12 -10
  192. package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
  193. package/docs/specs/004-action-verification-routing/spec.md +3 -3
  194. package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
  195. package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
  196. package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
  197. package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
  198. package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
  199. package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
  200. package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
  201. package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
  202. package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
  203. package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
  204. package/docs/specs/014-actionability-resolution/spec.md +20 -18
  205. package/docs/specs/015-adjustable-control-support/spec.md +17 -15
  206. package/docs/specs/016-environment-tools/spec.md +20 -20
  207. package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
  208. package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
  209. package/docs/specs/020-conditional-actions/intent.md +5 -3
  210. package/docs/specs/020-conditional-actions/plan.md +8 -9
  211. package/docs/specs/020-conditional-actions/spec.md +4 -2
  212. package/docs/specs/021-app-readiness-signals/intent.md +4 -2
  213. package/docs/specs/021-app-readiness-signals/plan.md +7 -6
  214. package/docs/specs/021-app-readiness-signals/spec.md +3 -1
  215. package/docs/specs/022-correlated-evidence/intent.md +4 -4
  216. package/docs/specs/022-correlated-evidence/plan.md +10 -11
  217. package/docs/specs/022-correlated-evidence/spec.md +3 -1
  218. package/docs/specs/023-android-system-traces/intent.md +3 -2
  219. package/docs/specs/023-android-system-traces/plan.md +5 -7
  220. package/docs/specs/023-android-system-traces/spec.md +3 -1
  221. package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
  222. package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
  223. package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
  224. package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
  225. package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
  226. package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
  227. package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
  228. package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
  229. package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
  230. package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
  231. package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
  232. package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
  233. package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
  234. package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
  235. package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
  236. package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
  237. package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
  238. package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
  239. package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
  240. package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
  241. package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
  242. package/docs/specs/030-android-coroutine-validation/report.md +240 -0
  243. package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
  244. package/docs/specs/031-ios-system-traces/intent.md +38 -0
  245. package/docs/specs/031-ios-system-traces/investigation.md +92 -0
  246. package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
  247. package/docs/specs/031-ios-system-traces/plan.md +138 -0
  248. package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
  249. package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
  250. package/docs/specs/031-ios-system-traces/spec.md +135 -0
  251. package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
  252. package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
  253. package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
  254. package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
  255. package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
  256. package/docs/tools/TOOLS.md +31 -18
  257. package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
  258. package/docs/tools/debugger.md +120 -4
  259. package/docs/tools/interact.md +42 -20
  260. package/docs/tools/manage.md +25 -44
  261. package/docs/tools/observe.md +55 -23
  262. package/docs/tools/system-traces.md +89 -14
  263. package/docs/tools/system.md +12 -5
  264. package/package.json +12 -8
  265. package/scripts/agent-plugin.mjs +55 -5
  266. package/scripts/architecture-graph.mjs +69 -0
  267. package/scripts/check-architecture.mjs +47 -0
  268. package/scripts/code-quality-baseline.json +1 -0
  269. package/scripts/code-quality.mjs +161 -0
  270. package/src/contracts/execution-controls.ts +26 -0
  271. package/src/contracts/selector.ts +17 -0
  272. package/src/contracts/target.ts +19 -0
  273. package/src/contracts/ui.ts +94 -0
  274. package/src/debugger/android.ts +3 -2
  275. package/src/debugger/diagnostics.ts +43 -0
  276. package/src/debugger/guard.ts +10 -5
  277. package/src/debugger/ios-worker.ts +49 -0
  278. package/src/debugger/ios.ts +45 -0
  279. package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
  280. package/src/debugger/lldb/breakpoints.py +51 -0
  281. package/src/debugger/lldb/inspection.py +109 -0
  282. package/src/debugger/lldb/worker.py +203 -0
  283. package/src/debugger/protocol.ts +37 -0
  284. package/src/debugger/sessions.ts +326 -155
  285. package/src/debugger/worker.ts +57 -41
  286. package/src/interact/adjustment/adjustment-context.ts +83 -0
  287. package/src/interact/adjustment/adjustment-gestures.ts +48 -0
  288. package/src/interact/adjustment/adjustment-plan.ts +23 -0
  289. package/src/interact/adjustment/adjustment-resolution.ts +104 -0
  290. package/src/interact/adjustment/adjustment-result.ts +34 -0
  291. package/src/interact/adjustment/adjustment-semantic.ts +53 -0
  292. package/src/interact/adjustment/adjustment-verification.ts +60 -0
  293. package/src/interact/adjustment/adjustment.ts +49 -0
  294. package/src/interact/adjustment/control-geometry.ts +120 -0
  295. package/src/interact/adjustment/control-values.ts +141 -0
  296. package/src/interact/adjustment/slider-labels.ts +84 -0
  297. package/src/interact/assertions/screen-assertion.ts +39 -0
  298. package/src/interact/assertions/state-assertion-context.ts +48 -0
  299. package/src/interact/assertions/state-assertion.ts +64 -0
  300. package/src/interact/assertions/state-comparison.ts +168 -0
  301. package/src/interact/assertions/visibility-assertion.ts +41 -0
  302. package/src/interact/conditional-action.ts +18 -12
  303. package/src/interact/index.ts +89 -3494
  304. package/src/interact/input-service.ts +11 -0
  305. package/src/interact/interaction-trace.ts +36 -0
  306. package/src/interact/model.ts +109 -0
  307. package/src/interact/observation.ts +7 -0
  308. package/src/interact/targeting/candidate-ancestor.ts +49 -0
  309. package/src/interact/targeting/candidate-result.ts +24 -0
  310. package/src/interact/targeting/candidate-scoring.ts +42 -0
  311. package/src/interact/targeting/element-helpers.ts +243 -0
  312. package/src/interact/targeting/find-element.ts +47 -0
  313. package/src/interact/targeting/nearby-control.ts +46 -0
  314. package/src/interact/targeting/resolved-elements.ts +20 -0
  315. package/src/interact/targeting/tap-target.ts +22 -0
  316. package/src/interact/targeting/target-scope.ts +121 -0
  317. package/src/interact/waits/legacy-log-wait.ts +30 -0
  318. package/src/interact/waits/legacy-wait-observations.ts +55 -0
  319. package/src/interact/waits/legacy-wait-state.ts +48 -0
  320. package/src/interact/waits/legacy-wait.ts +34 -0
  321. package/src/interact/waits/screen-wait.ts +37 -0
  322. package/src/interact/waits/ui-change-helpers.ts +184 -0
  323. package/src/interact/waits/ui-change-state.ts +26 -0
  324. package/src/interact/waits/ui-change-wait.ts +59 -0
  325. package/src/interact/waits/ui-wait-condition.ts +52 -0
  326. package/src/interact/waits/ui-wait-state.ts +55 -0
  327. package/src/interact/waits/ui-wait.ts +84 -0
  328. package/src/manage/android.ts +56 -50
  329. package/src/manage/index.ts +63 -93
  330. package/src/manage/ios/build-types.ts +14 -0
  331. package/src/manage/ios/build.ts +230 -0
  332. package/src/manage/ios/project-discovery.ts +26 -0
  333. package/src/manage/ios/scheme.ts +16 -0
  334. package/src/manage/ios.ts +67 -298
  335. package/src/observe/android/context.ts +1 -0
  336. package/src/observe/android/current-screen.ts +63 -0
  337. package/src/observe/android/fingerprint.ts +13 -0
  338. package/src/observe/android/log-stream.ts +127 -0
  339. package/src/observe/android/logs.ts +110 -0
  340. package/src/observe/android/screenshot.ts +79 -0
  341. package/src/observe/android/ui-tree.ts +113 -0
  342. package/src/observe/android.ts +17 -531
  343. package/src/observe/evidence.ts +122 -70
  344. package/src/observe/index.ts +24 -286
  345. package/src/observe/ios/context.ts +22 -0
  346. package/src/observe/ios/fingerprint.ts +12 -0
  347. package/src/observe/ios/log-stream.ts +103 -0
  348. package/src/observe/ios/logs.ts +179 -0
  349. package/src/observe/ios/screenshot.ts +26 -0
  350. package/src/observe/ios/ui-elements.ts +266 -0
  351. package/src/observe/ios/ui-tree.ts +143 -0
  352. package/src/observe/ios-trace/artifact.ts +55 -0
  353. package/src/observe/ios-trace/capabilities.ts +74 -0
  354. package/src/observe/ios-trace/native.ts +72 -0
  355. package/src/observe/ios-trace/notification.ts +30 -0
  356. package/src/observe/ios-trace/process.ts +82 -0
  357. package/src/observe/ios-trace/sessions.ts +204 -0
  358. package/src/observe/ios.ts +18 -749
  359. package/src/observe/logs/model.ts +11 -0
  360. package/src/{network → observe/network}/index.ts +2 -2
  361. package/src/observe/readiness-service.ts +6 -4
  362. package/src/observe/readiness.ts +42 -21
  363. package/src/observe/screenshot-encoding.ts +32 -0
  364. package/src/observe/snapshot-collection.ts +69 -0
  365. package/src/observe/snapshot-logs.ts +26 -0
  366. package/src/observe/snapshot-metadata.ts +51 -31
  367. package/src/observe/snapshot-semantic.ts +153 -0
  368. package/src/observe/system-trace-error.ts +3 -0
  369. package/src/observe/system-trace-router.ts +61 -0
  370. package/src/observe/system-trace.ts +28 -19
  371. package/src/runtime/action-results.ts +376 -0
  372. package/src/runtime/clock.ts +9 -0
  373. package/src/server/common.ts +9 -359
  374. package/src/server/definitions/assert.ts +164 -0
  375. package/src/server/definitions/diagnostic.ts +120 -0
  376. package/src/server/definitions/input.ts +276 -0
  377. package/src/server/definitions/manage.ts +196 -0
  378. package/src/server/definitions/observe.ts +151 -0
  379. package/src/server/definitions/target.ts +140 -0
  380. package/src/server/definitions/wait.ts +107 -0
  381. package/src/server/handlers/action-execution.ts +164 -0
  382. package/src/server/handlers/correlation.ts +20 -0
  383. package/src/server/handlers/debugger-handlers.ts +58 -0
  384. package/src/server/handlers/debugger-response.ts +7 -0
  385. package/src/server/handlers/diagnostic.ts +86 -0
  386. package/src/server/handlers/handler-types.ts +7 -0
  387. package/src/server/handlers/input.ts +173 -0
  388. package/src/server/handlers/interact.ts +135 -0
  389. package/src/server/handlers/journey-assertions.ts +39 -0
  390. package/src/server/handlers/journey.ts +132 -0
  391. package/src/server/handlers/manage.ts +141 -0
  392. package/src/server/handlers/observe.ts +116 -0
  393. package/src/server/handlers/payload.ts +36 -0
  394. package/src/server/handlers/tool-handlers.ts +170 -0
  395. package/src/server/schemas/condition-schema.ts +2 -0
  396. package/src/server/schemas/debugger-schema.ts +46 -0
  397. package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
  398. package/src/server/schemas/system-trace-schema.ts +23 -0
  399. package/src/server/tool-definitions.ts +67 -1143
  400. package/src/server-core.ts +2 -2
  401. package/src/server.ts +4 -4
  402. package/src/system/android-status.ts +124 -0
  403. package/src/system/android.ts +33 -21
  404. package/src/system/index.ts +21 -525
  405. package/src/system/ios-status.ts +147 -0
  406. package/src/system/ios.ts +14 -10
  407. package/src/system/model.ts +41 -0
  408. package/src/system/probes.ts +114 -0
  409. package/src/system/status-summary.ts +47 -0
  410. package/src/system/unsupported-status.ts +55 -0
  411. package/src/types.ts +16 -106
  412. package/src/utils/android/commands.ts +81 -0
  413. package/src/utils/android/gradle.ts +132 -0
  414. package/src/utils/android/logs.ts +103 -0
  415. package/src/utils/android/ui-tree.ts +239 -0
  416. package/src/utils/android/utils.ts +16 -556
  417. package/src/utils/cli/idb/check-idb.ts +4 -4
  418. package/src/utils/cli/idb/idb-helper.ts +6 -8
  419. package/src/utils/cli/idb/install-idb.ts +15 -9
  420. package/src/utils/cli/ios/preflight-ios.ts +39 -31
  421. package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
  422. package/src/utils/ios/utils.ts +60 -61
  423. package/src/utils/java.ts +62 -43
  424. package/src/utils/resolve-device.ts +9 -6
  425. package/src/utils/ui/index.ts +28 -28
  426. package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
  427. package/test/device/automated/debugger/coroutine-support.ts +140 -0
  428. package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
  429. package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
  430. package/test/device/automated/debugger/runtime.android.test.ts +19 -3
  431. package/test/device/automated/debugger/runtime.ios.test.ts +221 -0
  432. package/test/device/automated/interact/interaction.android.test.ts +56 -0
  433. package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
  434. package/test/device/automated/observe/system-trace.smoke.ts +1 -1
  435. package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
  436. package/test/fixtures/contracts/tool-definitions.json +3696 -0
  437. package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
  438. package/test/fixtures/coroutine-app/README.md +48 -0
  439. package/test/fixtures/coroutine-app/build.ts +35 -0
  440. package/test/fixtures/debugger-app/MainActivity.java +21 -0
  441. package/test/fixtures/debugger-app/MainActivity.kt +32 -0
  442. package/test/fixtures/debugger-app/README.md +52 -0
  443. package/test/fixtures/debugger-app/build.ts +25 -15
  444. package/test/fixtures/ios-debugger-app/Main.swift +95 -0
  445. package/test/fixtures/ios-debugger-app/README.md +27 -0
  446. package/test/fixtures/ios-debugger-app/build.ts +21 -0
  447. package/test/fixtures/latency-journey-app/README.md +2 -0
  448. package/test/fixtures/readiness-app/README.md +2 -0
  449. package/test/helpers/ios-trace-shutdown.ts +29 -0
  450. package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
  451. package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
  452. package/test/unit/debugger/diagnostics.test.ts +80 -0
  453. package/test/unit/debugger/guards.test.ts +4 -0
  454. package/test/unit/debugger/handle-guards.test.ts +88 -0
  455. package/test/unit/debugger/ios-preparation.test.ts +57 -0
  456. package/test/unit/debugger/ios.test.ts +104 -0
  457. package/test/unit/debugger/lldb-worker.py +166 -0
  458. package/test/unit/debugger/lldb-worker.test.ts +10 -0
  459. package/test/unit/debugger/preparation.test.ts +32 -0
  460. package/test/unit/debugger/protocol.test.ts +15 -0
  461. package/test/unit/debugger/sessions.test.ts +1 -1
  462. package/test/unit/interact/adjustment_service.test.ts +42 -0
  463. package/test/unit/interact/adjustment_verification.test.ts +35 -0
  464. package/test/unit/interact/clock_dependencies.test.ts +63 -0
  465. package/test/unit/interact/element_helpers.test.ts +19 -0
  466. package/test/unit/interact/legacy_wait_service.test.ts +17 -0
  467. package/test/unit/interact/state_comparison.test.ts +20 -0
  468. package/test/unit/interact/target_scope.test.ts +35 -0
  469. package/test/unit/network/get_network_activity.test.ts +1 -1
  470. package/test/unit/observe/ios-trace-native.test.ts +90 -0
  471. package/test/unit/observe/ios-trace-sessions.test.ts +131 -0
  472. package/test/unit/observe/readiness.test.ts +1 -1
  473. package/test/unit/observe/screenshot-encoding.test.ts +30 -0
  474. package/test/unit/observe/snapshot-logs.test.ts +14 -0
  475. package/test/unit/observe/system-trace-router.test.ts +49 -0
  476. package/test/unit/quality/agent-plugin-export.test.ts +38 -0
  477. package/test/unit/quality/architecture.test.ts +60 -0
  478. package/test/unit/quality/code-quality.test.ts +58 -0
  479. package/test/unit/quality/interaction_boundaries.test.ts +24 -0
  480. package/test/unit/server/conditional_actions.test.ts +1 -1
  481. package/test/unit/server/conditional_schema.test.ts +2 -2
  482. package/test/unit/server/debugger-response.test.ts +63 -0
  483. package/test/unit/server/system-trace.test.ts +12 -3
  484. package/test/unit/server/tool_definitions_parity.test.ts +8 -0
  485. package/dist/server/debugger-handlers.js +0 -38
  486. package/dist/server/system-trace-schema.js +0 -17
  487. package/dist/server/tool-handlers.js +0 -1000
  488. package/docs/ROADMAP.md +0 -646
  489. package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
  490. package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
  491. package/docs/specs/baseline-spec-v0.md +0 -312
  492. package/docs/specs/mcp-tooling-spec-v1.md +0 -446
  493. package/src/server/debugger-handlers.ts +0 -29
  494. package/src/server/debugger-schema.ts +0 -37
  495. package/src/server/system-trace-schema.ts +0 -17
  496. package/src/server/tool-handlers.ts +0 -1008
  497. /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
  498. /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
  499. /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
  500. /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
package/src/manage/ios.ts CHANGED
@@ -1,315 +1,70 @@
1
+ import { checkDebuggerGuard } from '../debugger/guard.js'
2
+ import { buildIOS } from './ios/build.js'
3
+ import type { IOSBuildOptions } from './ios/build-types.js'
4
+ export type { IOSBuildOptions } from './ios/build-types.js'
1
5
  import { evidenceDispatch } from '../observe/evidence.js'
2
6
  import { hasFiniteBudget, markDispatchStarted, remainingBudget, runWithBudget } from '../utils/operation-budget.js'
3
7
  import { promises as fs } from "fs"
4
- import { spawn, spawnSync } from "child_process"
8
+ import { spawn } from "child_process"
5
9
  import { StartAppResponse, TerminateAppResponse, RestartAppResponse, ResetAppDataResponse, InstallAppResponse } from "../types.js"
6
10
  import { execCommand, execCommandWithDiagnostics, getIOSDeviceMetadata, validateBundleId, getIdbCmd, findAppBundle } from "../utils/ios/utils.js"
7
11
  import { iOSObserve } from "../observe/ios.js"
8
- import path from "path"
9
-
10
- type BuildEnv = Record<string, string | undefined>
11
-
12
- export interface IOSBuildOptions {
13
- workspace?: string
14
- project?: string
15
- scheme?: string
16
- destinationUDID?: string
17
- derivedDataPath?: string
18
- buildJobs?: number
19
- forceClean?: boolean
20
- xcodeCmd?: string
21
- env?: BuildEnv
22
- }
23
12
 
24
13
  export class iOSManage {
25
- async build(projectPath: string, optsOrVariant?: string | IOSBuildOptions): Promise<{ artifactPath: string, output?: string } | { error: string, diagnostics?: any }> {
26
- // Support legacy variant string as second arg
27
- const opts: IOSBuildOptions = typeof optsOrVariant === 'string' ? {} : (optsOrVariant || {})
28
- const env = { ...process.env, ...(opts.env || {}) }
29
-
30
- try {
31
- // Look for an Xcode workspace or project at the provided path. If not present, scan subdirectories (limited depth)
32
- async function findProject(root: string, maxDepth = 4): Promise<{ dir: string, workspace?: string, proj?: string } | null> {
33
- try {
34
- const ents = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
35
- for (const e of ents) {
36
- // .xcworkspace and .xcodeproj are directories on disk (bundles), not regular files
37
- if (e.name.endsWith('.xcworkspace')) return { dir: root, workspace: e.name }
38
- if (e.name.endsWith('.xcodeproj')) return { dir: root, proj: e.name }
39
- }
40
- } catch {}
41
-
42
- if (maxDepth <= 0) return null
14
+ async build(projectPath: string, optsOrVariant?: string | IOSBuildOptions) { return buildIOS(projectPath, optsOrVariant) }
43
15
 
44
- try {
45
- const ents = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
46
- for (const e of ents) {
47
- if (e.isDirectory()) {
48
- const candidate = await findProject(path.join(root, e.name), maxDepth - 1)
49
- if (candidate) return candidate
50
- }
51
- }
52
- } catch {}
53
-
54
- return null
55
- }
56
-
57
- // Resolve projectPath to an absolute path to avoid cwd-relative resolution issues
58
- const absProjectPath = path.resolve(projectPath)
59
-
60
- // If caller supplied explicit workspace/project, prefer those and set projectRootDir accordingly
61
- let projectRootDir = absProjectPath
62
- let workspace: string | undefined = opts.workspace
63
- let proj: string | undefined = opts.project
64
-
65
- if (workspace) {
66
- // normalize workspace path and set root to its parent
67
- workspace = path.isAbsolute(workspace) ? workspace : path.join(absProjectPath, workspace)
68
- projectRootDir = path.dirname(workspace)
69
- workspace = path.basename(workspace)
70
- } else if (proj) {
71
- proj = path.isAbsolute(proj) ? proj : path.join(absProjectPath, proj)
72
- projectRootDir = path.dirname(proj)
73
- proj = path.basename(proj)
74
- } else {
75
- const projectInfo = await findProject(absProjectPath, 4)
76
- if (!projectInfo) return { error: 'No Xcode project or workspace found' }
77
- projectRootDir = projectInfo.dir || absProjectPath
78
- workspace = projectInfo.workspace
79
- proj = projectInfo.proj
80
- }
81
-
82
- // Determine destination: prefer explicit option, then env var, otherwise use booted simulator UDID
83
- let destinationUDID = opts.destinationUDID || env.MCP_XCODE_DESTINATION_UDID || env.MCP_XCODE_DESTINATION || ''
84
- if (!destinationUDID) {
85
- try {
86
- const meta = await getIOSDeviceMetadata('booted')
87
- if (meta && meta.id) destinationUDID = meta.id
88
- } catch {}
89
- }
16
+ async installApp(appPath: string, deviceId: string = "booted"): Promise<InstallAppResponse> {
17
+ const device = await getIOSDeviceMetadata(deviceId)
90
18
 
91
- // Determine xcode command early so it can be used when detecting schemes
92
- const xcodeCmd = opts.xcodeCmd || env.XCODEBUILD_PATH || 'xcodebuild'
19
+ try {
20
+ const toInstall = await this.installBundle(appPath)
93
21
 
94
- // Determine available schemes by querying xcodebuild -list rather than guessing
95
- async function detectScheme(xcodeCmdInner: string, workspacePath?: string, projectPathFull?: string, cwd?: string): Promise<string | null> {
22
+ try {
23
+ const res = await execCommand(['simctl', 'install', deviceId, toInstall], deviceId)
24
+ return { device, installed: true, output: res.output }
25
+ } catch (e) {
26
+ // Gather diagnostics for simctl failure
27
+ const diag = execCommandWithDiagnostics(['simctl', 'install', deviceId, toInstall], deviceId)
96
28
  try {
97
- const args = workspacePath ? ['-list', '-workspace', workspacePath] : ['-list', '-project', projectPathFull!]
98
- // Run xcodebuild directly to list schemes
99
- const res = spawnSync(xcodeCmdInner, args, { cwd: cwd || projectRootDir, encoding: 'utf8', timeout: 20000 })
100
- const out = res.stdout || ''
101
- const schemesMatch = out.match(/Schemes:\s*\n([\s\S]*?)(?:\n\n|$)/m)
102
- if (schemesMatch) {
103
- const block = schemesMatch[1]
104
- const schemes = block.split(/\n/).map(s => s.trim()).filter(Boolean)
105
- if (schemes.length) return schemes[0]
29
+ const child = spawn(getIdbCmd(), ['list-targets', '--json'])
30
+ const idbExists = await new Promise<boolean>((resolve) => {
31
+ child.on('error', () => resolve(false));
32
+ child.on('close', (code) => resolve(code === 0));
33
+ });
34
+ if (idbExists) {
35
+ // attempt idb install via spawn but include diagnostics
36
+ await installWithIdb(toInstall, device.id)
37
+ return { device, installed: true }
106
38
  }
107
- } catch {}
108
- return null
109
- }
110
-
111
- // Prepare build flags and paths (support incremental builds)
112
- let buildArgs: string[]
113
- let chosenScheme: string | null = opts.scheme || null
114
-
115
- // Derived data and result bundle (agent-configurable)
116
- const derivedDataPath = opts.derivedDataPath || env.MCP_DERIVED_DATA || path.join(projectRootDir, 'build', 'DerivedData')
117
- // Use unique result bundle path by default to avoid collisions
118
- const resultBundlePath = env.MCP_XCODE_RESULTBUNDLE_PATH || path.join(projectRootDir, 'build', 'xcresults', `ResultBundle-${Date.now()}-${Math.random().toString(36).slice(2)}.xcresult`)
119
- const xcodeJobs = typeof opts.buildJobs === 'number' ? opts.buildJobs : (parseInt(env.MCP_XCODE_JOBS || '', 10) || 4)
120
- const forceClean = typeof opts.forceClean === 'boolean' ? opts.forceClean : env.MCP_FORCE_CLEAN === '1'
121
-
122
- // ensure result dirs exist
123
- await fs.mkdir(path.dirname(resultBundlePath), { recursive: true }).catch(() => {})
124
- await fs.mkdir(derivedDataPath, { recursive: true }).catch(() => {})
125
- // remove any pre-existing result bundle path to avoid xcodebuild complaining
126
- await fs.rm(resultBundlePath, { recursive: true, force: true }).catch(() => {})
127
-
128
- if (workspace) {
129
- const workspacePath = path.join(projectRootDir, workspace)
130
- if (!chosenScheme) chosenScheme = await detectScheme(xcodeCmd, workspacePath, undefined, projectRootDir)
131
- const scheme = chosenScheme || workspace.replace(/\.xcworkspace$/, '')
132
- buildArgs = ['-workspace', workspacePath, '-scheme', scheme, '-configuration', 'Debug', '-sdk', 'iphonesimulator', 'build']
133
- } else {
134
- const projectPathFull = path.join(projectRootDir, proj!)
135
- if (!chosenScheme) chosenScheme = await detectScheme(xcodeCmd, undefined, projectPathFull, projectRootDir)
136
- const scheme = chosenScheme || proj!.replace(/\.xcodeproj$/, '')
137
- buildArgs = ['-project', projectPathFull, '-scheme', scheme, '-configuration', 'Debug', '-sdk', 'iphonesimulator', 'build']
138
- }
139
-
140
- // Insert clean if explicitly requested via env or opts
141
- if (forceClean) {
142
- const idx = buildArgs.indexOf('build')
143
- if (idx >= 0) buildArgs.splice(idx, 0, 'clean')
144
- }
145
-
146
- // If we have a destination UDID, add an explicit destination to avoid xcodebuild picking an ambiguous target
147
- if (destinationUDID) {
148
- buildArgs.push('-destination', `platform=iOS Simulator,id=${destinationUDID}`)
149
- }
150
-
151
- // Add derived data and result bundle for diagnostics and faster incremental builds
152
- buildArgs.push('-derivedDataPath', derivedDataPath)
153
- buildArgs.push('-resultBundlePath', resultBundlePath)
154
- // parallelisation and jobs
155
- buildArgs.push('-parallelizeTargets')
156
- buildArgs.push('-jobs', String(xcodeJobs))
157
-
158
- // Prepare results directory for backwards-compatible logs
159
- const resultsDir = path.join(projectPath, 'build-results')
160
- // Remove any stale results to avoid xcodebuild complaining about existing result bundles
161
- await fs.rm(resultsDir, { recursive: true, force: true }).catch(() => {})
162
- await fs.mkdir(resultsDir, { recursive: true }).catch(() => {})
163
-
164
-
165
- const XCODEBUILD_TIMEOUT = parseInt(env.MCP_XCODEBUILD_TIMEOUT || '', 10) || 180000 // default 3 minutes
166
- const MAX_RETRIES = parseInt(env.MCP_XCODEBUILD_RETRIES || '', 10) || 1
167
-
168
- const tries = MAX_RETRIES + 1
169
- let lastStdout = ''
170
- let lastStderr = ''
171
- let lastErr: any = null
172
-
173
- for (let attempt = 1; attempt <= tries; attempt++) {
174
- // Run xcodebuild with a watchdog
175
- const res = await new Promise<{ code: number | null, stdout: string, stderr: string, killedByWatchdog?: boolean }>((resolve) => {
176
- const proc = spawn(xcodeCmd, buildArgs, { cwd: projectRootDir, env })
177
- let stdout = ''
178
- let stderr = ''
179
-
180
- proc.stdout?.on('data', d => stdout += d.toString())
181
- proc.stderr?.on('data', d => stderr += d.toString())
182
-
183
- let killed = false
184
- const to = setTimeout(() => {
185
- killed = true
186
- try { proc.kill('SIGKILL') } catch {}
187
- }, XCODEBUILD_TIMEOUT)
188
-
189
- proc.on('close', (code) => {
190
- clearTimeout(to)
191
- resolve({ code, stdout, stderr, killedByWatchdog: killed })
192
- })
193
- proc.on('error', (err) => {
194
- clearTimeout(to)
195
- resolve({ code: null, stdout, stderr: String(err), killedByWatchdog: killed })
196
- })
197
- })
198
-
199
- lastStdout = res.stdout
200
- lastStderr = res.stderr
201
-
202
- if (res.code === 0) {
203
- // success — clear any previous error and stop retrying
204
- lastErr = null
205
- break
206
- }
207
-
208
- // record the failure for reporting
209
- lastErr = new Error(res.stderr || `xcodebuild failed with code ${res.code}`)
210
- // Attach exit code and watchdog info so diagnostics can include them
211
- ;(lastErr as any).code = res.code
212
- ;(lastErr as any).exitCode = res.code
213
- ;(lastErr as any).killedByWatchdog = !!res.killedByWatchdog
214
-
215
- // write logs for diagnostics (helpful whether killed or not)
216
- try {
217
- await fs.writeFile(path.join(resultsDir, `xcodebuild-${attempt}.stdout.log`), res.stdout).catch(() => {})
218
- await fs.writeFile(path.join(resultsDir, `xcodebuild-${attempt}.stderr.log`), res.stderr).catch(() => {})
219
- } catch {}
220
-
221
- // If killed by watchdog and there are remaining attempts, continue to retry
222
- if (res.killedByWatchdog && attempt < tries) {
223
- continue
224
- }
225
-
226
- // no more retries or not a watchdog kill — break to report lastErr
227
- if (attempt >= tries) break
228
- }
229
-
230
- if (lastErr) {
231
- // Include diagnostics and result bundle path when available; provide structured info useful for agents
232
- const invokedCommand = `${xcodeCmd} ${buildArgs.map(a => a.includes(' ') ? `"${a}"` : a).join(' ')}`
233
- const envSnapshot = { PATH: env.PATH }
234
- return { error: `xcodebuild failed: ${lastErr.message}. See build-results for logs.`, output: `stdout:\n${lastStdout}\nstderr:\n${lastStderr}`, diagnostics: { exitCode: (lastErr as any).code || null, invokedCommand, cwd: projectRootDir, envSnapshot } }
39
+ } catch { }
40
+ return { device, installed: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
235
41
  }
236
-
237
- // Try to locate built .app. First search project tree, then DerivedData if necessary
238
- const built = await findAppBundle(projectPath)
239
- if (built) return { artifactPath: built }
240
-
241
- // Fallback: search DerivedData for matching product
242
- const dd = path.join(process.env.HOME || '', 'Library', 'Developer', 'Xcode', 'DerivedData')
243
- try {
244
- const entries = await fs.readdir(dd).catch(() => [])
245
- for (const e of entries) {
246
- const candidate = path.join(dd, e)
247
- const found = await findAppBundle(candidate).catch(() => undefined)
248
- if (found) return { artifactPath: found }
249
- }
250
- } catch {}
251
-
252
- return { error: 'Could not find .app after build' }
253
42
  } catch (e) {
254
- return { error: e instanceof Error ? e.message : String(e) }
43
+ return { device, installed: false, error: e instanceof Error ? e.message : String(e) }
255
44
  }
256
45
  }
257
46
 
258
- async installApp(appPath: string, deviceId: string = "booted"): Promise<InstallAppResponse> {
259
- const device = await getIOSDeviceMetadata(deviceId)
47
+ private async installBundle(appPath: string): Promise<string> {
48
+ let toInstall = appPath
260
49
 
261
- try {
262
- let toInstall = appPath
263
-
264
- const stat = await fs.stat(appPath).catch(() => null)
265
- if (stat && stat.isDirectory()) {
266
- if (appPath.endsWith('.app')) {
267
- toInstall = appPath
50
+ const stat = await fs.stat(appPath).catch(() => null)
51
+ if (stat && stat.isDirectory()) {
52
+ if (appPath.endsWith('.app')) {
53
+ toInstall = appPath
54
+ } else {
55
+ const found = await findAppBundle(appPath)
56
+ if (found) {
57
+ toInstall = found
268
58
  } else {
269
- const found = await findAppBundle(appPath)
270
- if (found) {
271
- toInstall = found
272
- } else {
273
- // Reuse the existing build() implementation to avoid duplicating the xcodebuild logic
274
- const buildRes = await this.build(appPath)
275
- if ((buildRes as any).error) throw new Error((buildRes as any).error)
276
- toInstall = (buildRes as any).artifactPath
277
- }
59
+ // Reuse the existing build() implementation to avoid duplicating the xcodebuild logic
60
+ const buildRes = await this.build(appPath)
61
+ if ('error' in buildRes) throw new Error(buildRes.error)
62
+ toInstall = buildRes.artifactPath
278
63
  }
279
64
  }
280
-
281
- try {
282
- const res = await execCommand(['simctl', 'install', deviceId, toInstall], deviceId)
283
- return { device, installed: true, output: res.output }
284
- } catch (e) {
285
- // Gather diagnostics for simctl failure
286
- const diag = execCommandWithDiagnostics(['simctl', 'install', deviceId, toInstall], deviceId)
287
- try {
288
- const child = spawn(getIdbCmd(), ['list-targets', '--json'])
289
- const idbExists = await new Promise<boolean>((resolve) => {
290
- child.on('error', () => resolve(false));
291
- child.on('close', (code) => resolve(code === 0));
292
- });
293
- if (idbExists) {
294
- // attempt idb install via spawn but include diagnostics
295
- await new Promise<void>((resolve, reject) => {
296
- const proc = spawn(getIdbCmd(), ['install', toInstall, '--udid', device.id]);
297
- let stderr = '';
298
- proc.stderr.on('data', d => stderr += d.toString());
299
- proc.on('close', code => {
300
- if (code === 0) resolve();
301
- else reject(new Error(stderr || `idb install failed with code ${code}`));
302
- });
303
- proc.on('error', err => reject(err));
304
- });
305
- return { device, installed: true }
306
- }
307
- } catch {}
308
- return { device, installed: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
309
- }
310
- } catch (e) {
311
- return { device, installed: false, error: e instanceof Error ? e.message : String(e) }
312
65
  }
66
+
67
+ return toInstall
313
68
  }
314
69
 
315
70
  async startApp(bundleId: string, deviceId: string = "booted", observe = true): Promise<StartAppResponse> {
@@ -336,7 +91,7 @@ export class iOSManage {
336
91
  } catch (e: unknown) {
337
92
  const device = await getIOSDeviceMetadata(deviceId)
338
93
  const diagnostics = hasFiniteBudget() ? undefined : execCommandWithDiagnostics(['simctl', 'launch', deviceId, bundleId], deviceId)
339
- return { device, appStarted: false, launchTimeMs: performance.now() - started, error: e instanceof Error ? e.message : String(e), ...(diagnostics ? { diagnostics } : {}) } as any
94
+ return { device, appStarted: false, launchTimeMs: performance.now() - started, error: e instanceof Error ? e.message : String(e), ...(diagnostics ? { diagnostics } : {}) }
340
95
  }
341
96
  }
342
97
 
@@ -350,7 +105,7 @@ export class iOSManage {
350
105
  } catch (e: unknown) {
351
106
  const diag = hasFiniteBudget() ? undefined : execCommandWithDiagnostics(['simctl', 'terminate', deviceId, bundleId], deviceId)
352
107
  const device = await getIOSDeviceMetadata(deviceId)
353
- return { device, appTerminated: false, error: e instanceof Error ? e.message : String(e), ...(diag ? { diagnostics: diag } : {}) } as any
108
+ return { device, appTerminated: false, error: e instanceof Error ? e.message : String(e), ...(diag ? { diagnostics: diag } : {}) }
354
109
  }
355
110
  }
356
111
 
@@ -387,19 +142,33 @@ export class iOSManage {
387
142
  const libraryPath = `${dataPath}/Library`
388
143
  const documentsPath = `${dataPath}/Documents`
389
144
  const tmpPath = `${dataPath}/tmp`
390
- await fs.rm(libraryPath, { recursive: true, force: true }).catch(() => {})
391
- await fs.rm(documentsPath, { recursive: true, force: true }).catch(() => {})
392
- await fs.rm(tmpPath, { recursive: true, force: true }).catch(() => {})
393
- await fs.mkdir(libraryPath, { recursive: true }).catch(() => {})
394
- await fs.mkdir(documentsPath, { recursive: true }).catch(() => {})
395
- await fs.mkdir(tmpPath, { recursive: true }).catch(() => {})
145
+ await fs.rm(libraryPath, { recursive: true, force: true }).catch(() => { })
146
+ await fs.rm(documentsPath, { recursive: true, force: true }).catch(() => { })
147
+ await fs.rm(tmpPath, { recursive: true, force: true }).catch(() => { })
148
+ await fs.mkdir(libraryPath, { recursive: true }).catch(() => { })
149
+ await fs.mkdir(documentsPath, { recursive: true }).catch(() => { })
150
+ await fs.mkdir(tmpPath, { recursive: true }).catch(() => { })
396
151
  return { device, dataCleared: true }
397
152
  } catch (e) {
398
153
  throw new Error(`Failed to clear data for ${bundleId}: ${e instanceof Error ? e.message : String(e)}`)
399
154
  }
400
155
  } catch (e: unknown) {
401
156
  const diag = execCommandWithDiagnostics(['simctl', 'get_app_container', deviceId, bundleId, 'data'], deviceId)
402
- return { device, dataCleared: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag } as any
157
+ return { device, dataCleared: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
403
158
  }
404
159
  }
405
160
  }
161
+
162
+ async function installWithIdb(toInstall: string, deviceId: string) {
163
+ await new Promise<void>((resolve, reject) => {
164
+ checkDebuggerGuard(true)
165
+ const proc = spawn(getIdbCmd(), ['install', toInstall, '--udid', deviceId]);
166
+ let stderr = '';
167
+ proc.stderr.on('data', d => stderr += d.toString());
168
+ proc.on('close', code => {
169
+ if (code === 0) resolve();
170
+ else reject(new Error(stderr || `idb install failed with code ${code}`));
171
+ });
172
+ proc.on('error', err => reject(err));
173
+ });
174
+ }
@@ -0,0 +1 @@
1
+ export const activeLogStreams: Map<string, { proc: import('node:child_process').ChildProcess, file: string, device?: { platform: 'android' | 'ios'; id: string } }> = new Map()
@@ -0,0 +1,63 @@
1
+ import { GetCurrentScreenResponse } from "../../types.js"
2
+ import { execAdb, getAndroidDeviceMetadata, getDeviceInfo } from "../../utils/android/utils.js"
3
+ export async function collectGetCurrentScreen(deviceId?: string): Promise<GetCurrentScreenResponse> {
4
+ const metadata = await getAndroidDeviceMetadata("", deviceId)
5
+ const deviceInfo = getDeviceInfo(deviceId || 'default', metadata)
6
+
7
+ try {
8
+ const output = await execAdb(['shell', 'dumpsys', 'activity', 'activities'], deviceId, { timeout: 10000 })
9
+ const lines = output.split('\n');
10
+ let resumedLine = lines.find(line => /^\s*mResumedActivity:/.test(line));
11
+
12
+ if (!resumedLine) {
13
+ resumedLine = lines.find(line => /^\s*ResumedActivity:/.test(line));
14
+ }
15
+
16
+ if (!resumedLine) {
17
+ return {
18
+ device: deviceInfo,
19
+ package: "",
20
+ activity: "",
21
+ shortActivity: "",
22
+ error: "Could not find 'mResumedActivity' in dumpsys output"
23
+ }
24
+ }
25
+
26
+ const match = resumedLine.match(/ActivityRecord\{[^ ]*(?:\s+[^ ]+)*\s+([^\/ ]+)\/([^ \{}]+)[^}]*\}/);
27
+
28
+ if (match) {
29
+ const packageName = match[1];
30
+ let activityName = match[2];
31
+
32
+ if (activityName.startsWith('.')) {
33
+ activityName = packageName + activityName;
34
+ }
35
+
36
+ const shortActivity = activityName.split('.').pop() || activityName;
37
+
38
+ return {
39
+ device: deviceInfo,
40
+ package: packageName,
41
+ activity: activityName,
42
+ shortActivity: shortActivity
43
+ };
44
+ } else {
45
+ return {
46
+ device: deviceInfo,
47
+ package: "",
48
+ activity: "",
49
+ shortActivity: "",
50
+ error: `Found resumed activity line but failed to parse: '${resumedLine.trim()}'`
51
+ }
52
+ }
53
+
54
+ } catch (e) {
55
+ return {
56
+ device: deviceInfo,
57
+ package: "",
58
+ activity: "",
59
+ shortActivity: "",
60
+ error: e instanceof Error ? e.message : String(e)
61
+ };
62
+ }
63
+ }
@@ -0,0 +1,13 @@
1
+ import { computeScreenFingerprint } from "../../utils/ui/index.js"
2
+ import type { AndroidObserve } from '../android.js'
3
+ export async function collectGetScreenFingerprint(source: Pick<AndroidObserve, 'getUITree' | 'getCurrentScreen'>, deviceId?: string): Promise<{ fingerprint: string | null; activity?: string; error?: string; captured_at_ms?: number }> {
4
+ try {
5
+ const tree = await source.getUITree(deviceId)
6
+ if (!tree || tree.error) return { fingerprint: null, error: tree.error }
7
+
8
+ const current = await source.getCurrentScreen(deviceId).catch(() => null)
9
+ return { ...computeScreenFingerprint(tree, current, 'android', 50), captured_at_ms: tree.captured_at_ms }
10
+ } catch (e) {
11
+ return { fingerprint: null, error: e instanceof Error ? e.message : String(e) }
12
+ }
13
+ }
@@ -0,0 +1,127 @@
1
+ import type { StreamLogEntry } from '../logs/model.js'
2
+ import { spawn } from "child_process"
3
+ import { getAdbCmd, execAdb, parseLogLine } from "../../utils/android/utils.js"
4
+ import { createWriteStream } from "fs"
5
+ import { promises as fsPromises } from "fs"
6
+ import path from "path"
7
+ import { activeLogStreams } from './context.js'
8
+ export async function startLogStream(packageName: string, level: 'error' | 'warn' | 'info' | 'debug' = 'error', deviceId?: string, sessionId: string = 'default') {
9
+ try {
10
+ const pidOutput = await execAdb(['shell', 'pidof', packageName], deviceId).catch(() => '')
11
+ const pid = (pidOutput || '').trim()
12
+ if (!pid) return { success: false, error: 'app_not_running' }
13
+
14
+ const levelMap: Record<string, string> = { error: '*:E', warn: '*:W', info: '*:I', debug: '*:D' }
15
+ const filter = levelMap[level] || levelMap['error']
16
+
17
+ if (activeLogStreams.has(sessionId)) {
18
+ try { activeLogStreams.get(sessionId)!.proc.kill() } catch { }
19
+ activeLogStreams.delete(sessionId)
20
+ }
21
+
22
+ const args = [...(deviceId ? ['-s', deviceId] : []), 'logcat', `--pid=${pid}`, filter]
23
+ const proc = spawn(getAdbCmd(), args)
24
+
25
+ const tmpDir = process.env.TMPDIR || '/tmp'
26
+ const file = path.join(tmpDir, `mobile-debug-log-${sessionId}.ndjson`)
27
+ const stream = createWriteStream(file, { flags: 'a' })
28
+
29
+ proc.stdout.on('data', (chunk) => {
30
+ const text = chunk.toString()
31
+ const lines = text.split(/\r?\n/).filter(Boolean)
32
+ for (const l of lines) {
33
+ const entry = parseLogLine(l)
34
+ stream.write(JSON.stringify(entry) + '\n')
35
+ }
36
+ })
37
+
38
+ proc.stderr.on('data', (chunk) => {
39
+ const text = chunk.toString()
40
+ const lines = text.split(/\r?\n/).filter(Boolean)
41
+ for (const l of lines) {
42
+ const entry = { timestamp: '', level: 'E', tag: 'adb', message: l }
43
+ stream.write(JSON.stringify(entry) + '\n')
44
+ }
45
+ })
46
+
47
+ proc.on('close', () => {
48
+ stream.end()
49
+ if (activeLogStreams.get(sessionId)?.proc === proc) activeLogStreams.delete(sessionId)
50
+ })
51
+
52
+ activeLogStreams.set(sessionId, { proc, file, device: { platform: 'android', id: deviceId ?? '' } })
53
+ return { success: true, stream_started: true }
54
+ } catch (e) {
55
+ return { success: false, error: e instanceof Error ? e.message : String(e) }
56
+ }
57
+ }
58
+
59
+ export async function stopLogStream(sessionId: string = 'default') {
60
+ const entry = activeLogStreams.get(sessionId)
61
+ if (!entry) return { success: true }
62
+ try { entry.proc.kill() } catch { }
63
+ activeLogStreams.delete(sessionId)
64
+ return { success: true }
65
+ }
66
+
67
+ export async function collectReadLogStream(sessionId: string = 'default', limit: number = 100, since?: string) {
68
+ const entry = activeLogStreams.get(sessionId)
69
+ let file: string | undefined
70
+ if (entry && entry.file) file = entry.file
71
+ else {
72
+ const tmpDir = process.env.TMPDIR || '/tmp'
73
+ const candidate = path.join(tmpDir, `mobile-debug-log-${sessionId}.ndjson`)
74
+ file = candidate
75
+ }
76
+
77
+ try {
78
+ const data = await fsPromises.readFile(file, 'utf8').catch(() => '')
79
+ if (!data) return { entries: [], crash_summary: { crash_detected: false } }
80
+ const lines = data.split(/\r?\n/).filter(Boolean)
81
+
82
+ const parsed = lines.map(l => {
83
+ try {
84
+ const obj = JSON.parse(l) as StreamLogEntry
85
+ if (typeof obj._iso === 'undefined') {
86
+ let iso: string | null = null
87
+ if (obj.timestamp) {
88
+ const d = new Date(obj.timestamp)
89
+ if (!isNaN(d.getTime())) iso = d.toISOString()
90
+ }
91
+ obj._iso = iso
92
+ }
93
+ if (typeof obj.crash === 'undefined') {
94
+ const msg = (obj.message || '').toString()
95
+ const exMatch = msg.match(/\b([A-Za-z0-9_$.]+Exception)\b/)
96
+ if (/FATAL EXCEPTION/i.test(msg) || exMatch) {
97
+ obj.crash = true
98
+ if (exMatch) obj.exception = exMatch[1]
99
+ } else {
100
+ obj.crash = false
101
+ }
102
+ }
103
+ return obj
104
+ } catch {
105
+ return { message: l, _iso: null, crash: false }
106
+ }
107
+ })
108
+
109
+ let filtered = parsed
110
+ if (since) {
111
+ let sinceMs: number | null = null
112
+ if (/^\d+$/.test(since)) sinceMs = Number(since)
113
+ else {
114
+ const sDate = new Date(since)
115
+ if (!isNaN(sDate.getTime())) sinceMs = sDate.getTime()
116
+ }
117
+ if (sinceMs !== null) filtered = parsed.filter(p => p._iso && (new Date(p._iso).getTime() >= sinceMs))
118
+ }
119
+
120
+ const entries = filtered.slice(-Math.max(0, limit))
121
+ const crashEntry = entries.find(e => e.crash)
122
+ const crash_summary = crashEntry ? { crash_detected: true, exception: crashEntry.exception, sample: crashEntry.message } : { crash_detected: false }
123
+ return { entries, crash_summary }
124
+ } catch {
125
+ return { entries: [], crash_summary: { crash_detected: false } }
126
+ }
127
+ }