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
@@ -0,0 +1,49 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { getXcrunCmd } from '../utils/ios/utils.js'
3
+ import { iosExec } from './ios.js'
4
+ import { DebuggerProcess, type WorkerEvent, type DebugWorker } from './worker.js'
5
+ import { DebuggerError } from './diagnostics.js'
6
+ export const iosCapabilities = { attach: true, breakpoints: true, stacks: true, locals: true, step: true, resume: true,
7
+ evaluation: false, mutation: false, coroutines: false, native: false, async_execution_inspection: true, async_stepping: true }
8
+ export const iosLimitations = ['IOS_SIMULATOR_ONLY', 'DEBUG_METADATA_REQUIRED', 'SHALLOW_LOCALS', 'ALL_THREADS_SUSPEND', 'DEVICE_WIDE_UI_GUARD', 'ASYNC_STEPPING_TOOLCHAIN_DEPENDENT', 'AGGREGATE_VALUES_TYPE_ONLY', 'NO_TASK_ENUMERATION']
9
+ export const iosWorkerSource = fileURLToPath(new URL('../../src/debugger/lldb/worker.py', import.meta.url))
10
+ let prepared: Promise<{ python: string; env: NodeJS.ProcessEnv }> | undefined
11
+ export function prepareIOSWorker() {
12
+ prepared ??= (async () => {
13
+ if (process.platform !== 'darwin') throw new DebuggerError('DEBUGGER_UNSUPPORTED')
14
+ const pythonPath = await iosExec(getXcrunCmd(), ['lldb', '-P'])
15
+ const python = await iosExec(getXcrunCmd(), ['--find', 'python3'])
16
+ // Ignore user Python startup/customisation and LLDB init files.
17
+ const env = { PATH: process.env.PATH, HOME: process.env.HOME, PYTHONPATH: pythonPath, PYTHONNOUSERSITE: '1' }
18
+ const ready = await iosExec(python, ['-s', '-B', '-c', 'import lldb; d=lldb.SBDebugger.Create(False); print("ready" if d.IsValid() else "unavailable"); lldb.SBDebugger.Destroy(d)'], env)
19
+ if (ready !== 'ready') throw new DebuggerError('DEBUGGER_UNSUPPORTED')
20
+ return { python, env }
21
+ })().catch(error => { prepared = undefined; throw error })
22
+ return prepared
23
+ }
24
+ export async function createIOSWorker(onEvent: (event: WorkerEvent) => void) {
25
+ const { python, env } = await prepareIOSWorker()
26
+ let released = false
27
+ const worker = new DebuggerProcess(python, '', event => {
28
+ if (event.event === 'terminated' && ['process_exit', 'detached', 'pause_limit', 'idle_limit', 'session_limit'].includes(String(event.reason))) released = true
29
+ onEvent(event)
30
+ }, { args: ['-u', '-s', '-B', iosWorkerSource], env })
31
+ return new IOSWorker(worker, () => released)
32
+ }
33
+ /** A dead worker is not proof that its owned suspension was released. */
34
+ export class IOSWorker implements DebugWorker {
35
+ private detached = false
36
+ constructor(private worker: DebugWorker, private released: () => boolean) {}
37
+ async call(command: string, args: Record<string, unknown> = {}, timeout = 10000) {
38
+ const result = await this.worker.call(command, args, timeout)
39
+ if (command === 'detach' && result.detached === true) this.detached = true
40
+ return result
41
+ }
42
+ async close() {
43
+ if (!this.detached && !this.released()) {
44
+ try { await this.call('detach', {}, 1000) } catch { /* Report uncertainty after bounded transport shutdown. */ }
45
+ }
46
+ await this.worker.close()
47
+ if (!this.detached && !this.released()) throw new DebuggerError('DEBUGGER_CLEANUP_FAILED')
48
+ }
49
+ }
@@ -0,0 +1,45 @@
1
+ import { execFile } from 'node:child_process'
2
+ import { promisify } from 'node:util'
3
+ import path from 'node:path'
4
+ import { realpath } from 'node:fs/promises'
5
+ import { getXcrunCmd } from '../utils/ios/utils.js'
6
+ import type { DebugNative, DebugTarget } from './android.js'
7
+ import { DebuggerError, debuggerRemaining, debuggerSignal, nativeError } from './worker.js'
8
+ const exec = promisify(execFile)
9
+ export async function iosExec(file: string, args: string[], env?: NodeJS.ProcessEnv) {
10
+ try { return (await exec(file, args, { timeout: debuggerRemaining(5000), signal: debuggerSignal(), maxBuffer: 1024 * 1024, ...(env ? { env } : {}) })).stdout.trim() }
11
+ catch (e) { throw nativeError(e, 'DEVICE_UNAVAILABLE') }
12
+ }
13
+ export class IOSDebugNative implements DebugNative {
14
+ constructor(private run = iosExec) {}
15
+ private sim(args: string[]) { return this.run(getXcrunCmd(), ['simctl', ...args]) }
16
+ async candidates(device: string, app: string): Promise<DebugTarget[]> {
17
+ if (!/^[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12}$/.test(device) || !/^[A-Za-z0-9][A-Za-z0-9.-]+$/.test(app)) throw new DebuggerError('INVALID_ARGUMENT')
18
+ const inventory = JSON.parse(await this.sim(['list', 'devices', 'booted', '--json'])) as { devices: Record<string, { udid: string; state: string }[]> }
19
+ if (!Object.values(inventory.devices).flat().some(d => d.udid === device && d.state === 'Booted')) throw new DebuggerError('DEBUGGER_UNSUPPORTED')
20
+ const services = await this.sim(['spawn', device, 'launchctl', 'list'])
21
+ const pids = this.processes(services, app)
22
+ if (!pids.length) return []
23
+ const container = await this.sim(['get_app_container', device, app, 'app'])
24
+ const executable = await this.run('/usr/bin/plutil', ['-extract', 'CFBundleExecutable', 'raw', '-o', '-', path.join(container, 'Info.plist')])
25
+ if (path.basename(executable) !== executable || !executable) throw new DebuggerError('PROCESS_NOT_FOUND')
26
+ const executablePath = await realpath(path.join(container, executable))
27
+ return Promise.all(pids.map(pid => this.identity(device, { app_id: app, pid, process_name: executable, executable_path: executablePath, start_identity: '', uid: null })))
28
+ }
29
+ private processes(services: string, app: string) {
30
+ return services.split('\n').flatMap(line => {
31
+ const [pid, , label] = line.trim().split(/\s+/)
32
+ return /^\d+$/.test(pid) && label?.startsWith(`UIKitApplication:${app}[`) ? [Number(pid)] : []
33
+ })
34
+ }
35
+ async identity(device: string, target: DebugTarget): Promise<DebugTarget> {
36
+ const services = await this.sim(['spawn', device, 'launchctl', 'list'])
37
+ if (!this.processes(services, target.app_id).includes(target.pid)) throw new DebuggerError('PROCESS_CHANGED')
38
+ const command = await this.run('/bin/ps', ['-ww', '-p', String(target.pid), '-o', 'comm='])
39
+ const started = await this.run('/bin/ps', ['-p', String(target.pid), '-o', 'lstart='])
40
+ if (command !== target.executable_path || !started || (target.start_identity && started !== target.start_identity)) throw new DebuggerError('PROCESS_CHANGED')
41
+ return { ...target, start_identity: started }
42
+ }
43
+ async forward(): Promise<number> { throw new DebuggerError('DEBUGGER_UNSUPPORTED') }
44
+ async removeForward() { throw new DebuggerError('DEBUGGER_UNSUPPORTED') }
45
+ }
@@ -139,9 +139,15 @@ public final class DebuggerWorker {
139
139
  try(Reader reader=new InputStreamReader(System.in,StandardCharsets.UTF_8)){
140
140
  StringBuilder line=new StringBuilder();int ch;
141
141
  while((ch=reader.read())!=-1){if(ch!='\n'){line.append((char)ch);if(line.length()>MAX_LINE)throw new IOException("IPC_LIMIT");continue;}
142
- Object id=null;
143
- try{@SuppressWarnings("unchecked") Map<String,Object>a=(Map<String,Object>)new Json(line.toString()).value();id=a.get("id");synchronized(lock){Object result=command(a);send(obj("id",id,"result",result));}}
144
- catch(Exception e){String msg=e.getMessage();String code=msg!=null&&msg.matches("(?:DEBUGGER|INVALID|PROCESS|DEVICE)_[A-Z_]+")?msg:e instanceof VMDisconnectedException?"DEBUGGER_DISCONNECTED":"DEBUGGER_WORKER_FAILED";send(obj("id",id,"error",obj("code",code)));}
142
+ Object id=null;String commandName="";
143
+ try{@SuppressWarnings("unchecked") Map<String,Object>a=(Map<String,Object>)new Json(line.toString()).value();id=a.get("id");commandName=str(a,"command");synchronized(lock){Object result=command(a);send(obj("id",id,"result",result));}}
144
+ catch(Exception e){
145
+ String msg=e.getMessage();
146
+ String code=msg!=null&&msg.matches("(?:DEBUGGER|INVALID|PROCESS|DEVICE)_[A-Z_]+")?msg:e instanceof VMDisconnectedException?"DEBUGGER_DISCONNECTED":"DEBUGGER_WORKER_FAILED";
147
+ String stage=commandName.equals("attach")?"attach":e instanceof VMDisconnectedException?"transport":"command";
148
+ String reason=e instanceof java.net.ConnectException?"connection_refused":e instanceof VMDisconnectedException?"transport_lost":e instanceof java.net.SocketTimeoutException || e instanceof TransportTimeoutException?"timeout":"operation_failed";
149
+ send(obj("id",id,"error",obj("code",code,"diagnostic",obj("stage",stage,"reason",reason))));
150
+ }
145
151
  line.setLength(0);
146
152
  }
147
153
  }finally{synchronized(lock){finish("parent_closed");}watchdog.shutdownNow();}
@@ -0,0 +1,51 @@
1
+ """Owned source breakpoints with exact-line checking, including late image loads."""
2
+ import os
3
+ import uuid
4
+ import lldb
5
+
6
+
7
+ class Breakpoints:
8
+ def __init__(self, target):
9
+ self.target = target
10
+ self.owned = {}
11
+
12
+ def describe(self, key):
13
+ bp, source, line = self.owned[key]
14
+ locations = []
15
+ rejected = False
16
+ for loc in bp:
17
+ entry = loc.GetAddress().GetLineEntry()
18
+ actual = str(entry.GetFileSpec())
19
+ exact = entry.IsValid() and os.path.normpath(actual) == source and entry.GetLine() == line
20
+ loc.SetEnabled(exact)
21
+ if exact:
22
+ locations.append({"source_file": actual, "line": line})
23
+ else:
24
+ rejected = True
25
+ known_source = self.target.FindCompileUnits(lldb.SBFileSpec(source)).GetSize() > 0
26
+ status = "resolved" if locations else "rejected" if rejected or known_source else "pending"
27
+ return {"breakpoint_id": key, "status": status, "locations": locations}
28
+
29
+ def set(self, args):
30
+ source = os.path.normpath(args["sourceFile"])
31
+ bp = self.target.BreakpointCreateByLocation(lldb.SBFileSpec(source), args["line"], 0, 0, lldb.SBFileSpecList(), False)
32
+ key = str(uuid.uuid4())
33
+ self.owned[key] = (bp, source, args["line"])
34
+ result = self.describe(key)
35
+ if result["status"] == "rejected":
36
+ self.target.BreakpointDelete(bp.GetID())
37
+ del self.owned[key]
38
+ return result
39
+
40
+ def refresh(self):
41
+ return [self.describe(key) for key in list(self.owned)]
42
+
43
+ def remove(self, key):
44
+ if key in self.owned:
45
+ bp, _, _ = self.owned.pop(key)
46
+ self.target.BreakpointDelete(bp.GetID())
47
+ return {"removed": True}
48
+
49
+ def clear(self):
50
+ for key in list(self.owned):
51
+ self.remove(key)
@@ -0,0 +1,109 @@
1
+ """Bounded, static inspection. Never evaluate expressions or object descriptions."""
2
+ import uuid
3
+ import re
4
+ import lldb
5
+
6
+
7
+ def location(frame):
8
+ entry = frame.GetLineEntry()
9
+ file = entry.GetFileSpec()
10
+ source = str(file)[:512] if file.IsValid() else None
11
+ return {"source_file": source, "line": entry.GetLine() if entry.IsValid() else None,
12
+ "method_name": (frame.GetFunctionName() or "")[:512], "class_name": None}
13
+
14
+
15
+ class Inspection:
16
+ def __init__(self):
17
+ self.invalidate()
18
+
19
+ def invalidate(self):
20
+ self.stop_id = None
21
+ self.threads = {}
22
+ self.frames = {}
23
+
24
+ def stopped(self, process):
25
+ self.invalidate()
26
+ self.stop_id = str(uuid.uuid4())
27
+ selected = None
28
+ chosen = process.GetSelectedThread()
29
+ threads = [chosen] if chosen.IsValid() else []
30
+ for index in range(min(process.GetNumThreads(), 100)):
31
+ thread = process.GetThreadAtIndex(index)
32
+ if not chosen.IsValid() or thread.GetThreadID() != chosen.GetThreadID():
33
+ threads.append(thread)
34
+ for thread in threads[:100]:
35
+ key = str(uuid.uuid4())
36
+ self.threads[key] = thread
37
+ if selected is None and thread.GetStopReason() not in (lldb.eStopReasonNone, lldb.eStopReasonInvalid):
38
+ selected = key
39
+ if selected is None or process.GetState() != lldb.eStateStopped:
40
+ self.invalidate()
41
+ return None
42
+ thread = self.threads.get(selected)
43
+ return {"event": "stopped", "stop_id": self.stop_id, "thread_id": selected,
44
+ "reason": stop_reason(thread),
45
+ "location": location(thread.GetFrameAtIndex(0)) if thread else None,
46
+ "threads": [{"thread_id": key, "name": (t.GetName() or "")[:256]} for key, t in self.threads.items()],
47
+ "threads_truncated": process.GetNumThreads() > 100}
48
+
49
+ def thread(self, args):
50
+ if args.get("stopId") != self.stop_id or args.get("threadId") not in self.threads:
51
+ raise ValueError("DEBUGGER_STALE_REFERENCE")
52
+ return self.threads[args["threadId"]]
53
+
54
+ def stack(self, args):
55
+ thread = self.thread(args)
56
+ offset, limit = args.get("offset", 0), args.get("limit", 20)
57
+ count = min(thread.GetNumFrames(), 1000)
58
+ frames = []
59
+ for index in range(offset, min(count, offset + limit)):
60
+ frame = thread.GetFrameAtIndex(index)
61
+ key = self.stop_id + ":" + args["threadId"] + ":" + str(index)
62
+ if key not in self.frames and len(self.frames) >= 1000:
63
+ break
64
+ self.frames[key] = frame
65
+ frames.append({"frame_id": key, "index": index, **location(frame)})
66
+ return {"frames": frames, "truncated": count > offset + len(frames) or thread.GetNumFrames() > 1000}
67
+
68
+ def locals(self, args):
69
+ if args.get("stopId") != self.stop_id or args.get("frameId") not in self.frames:
70
+ raise ValueError("DEBUGGER_STALE_REFERENCE")
71
+ frame = self.frames[args["frameId"]]
72
+ values = frame.GetVariables(True, True, False, True, lldb.eNoDynamicValues)
73
+ result = []
74
+ for index in range(min(values.GetSize(), 100)):
75
+ value = values.GetValueAtIndex(index).GetNonSyntheticValue()
76
+ name, kind = (value.GetName() or "")[:256], (value.GetTypeName() or "")[:256]
77
+ if not value.GetError().Success():
78
+ result.append({"name": name, "type": kind, "availability": "unavailable"})
79
+ continue
80
+ primitive = value.GetType().GetTypeFlags() & (lldb.eTypeIsScalar | lldb.eTypeIsInteger | lldb.eTypeIsFloat)
81
+ text = value.GetValue() if primitive else None
82
+ # Swift value wrappers are not C scalars. Decode only standard primitive storage.
83
+ if kind in ("Swift.Int", "Swift.Int32", "Swift.Int64", "Swift.UInt", "Swift.Bool", "Swift.Double", "Swift.Float"):
84
+ storage = value.GetChildMemberWithName("_value").GetNonSyntheticValue()
85
+ if storage.IsValid() and storage.GetError().Success():
86
+ text = storage.GetValue()
87
+ if kind == "Swift.Bool" and text in ("0", "1"):
88
+ text = "true" if text == "1" else "false"
89
+ result.append({"name": name, "type": kind, "value": text[:256] if text else None,
90
+ "kind": "primitive" if text is not None else "object",
91
+ "truncated": bool(text and len(text) > 256),
92
+ "object_id": self.object_identity(value, kind) if text is None else None})
93
+ return {"availability": "available" if result else "unknown", "locals": result,
94
+ "reason": None if result else "EMPTY_OR_UNAVAILABLE_VARIABLE_METADATA",
95
+ "truncated": values.GetSize() > 100}
96
+
97
+ def object_identity(self, value, kind):
98
+ pointer = value.GetValue()
99
+ if pointer and re.fullmatch(r"0x[0-9a-fA-F]+", pointer):
100
+ return str(uuid.uuid5(uuid.UUID(self.stop_id), kind + ":" + pointer))
101
+ return None
102
+
103
+
104
+ def stop_reason(thread):
105
+ if thread is None:
106
+ return "unknown"
107
+ return {lldb.eStopReasonBreakpoint: "breakpoint", lldb.eStopReasonPlanComplete: "step",
108
+ lldb.eStopReasonSignal: "signal", lldb.eStopReasonException: "exception",
109
+ lldb.eStopReasonWatchpoint: "watchpoint", lldb.eStopReasonExec: "exec"}.get(thread.GetStopReason(), "unknown")
@@ -0,0 +1,203 @@
1
+ """Private JSON worker for simulator LLDB. Stdout is exclusively bounded IPC."""
2
+ import json
3
+ import os
4
+ import queue
5
+ import sys
6
+ import threading
7
+ import time
8
+ import lldb
9
+ from inspection import Inspection
10
+ from breakpoints import Breakpoints
11
+
12
+ requests = queue.Queue(maxsize=64)
13
+ output_lock = threading.Lock()
14
+
15
+
16
+ def send(value):
17
+ data = json.dumps(value, separators=(",", ":"))
18
+ if len(data.encode()) > 1024 * 1024:
19
+ raise ValueError("DEBUGGER_WORKER_FAILED")
20
+ with output_lock:
21
+ sys.stdout.write(data + "\n")
22
+ sys.stdout.flush()
23
+
24
+
25
+ def read_input():
26
+ try:
27
+ while True:
28
+ line = sys.stdin.buffer.readline(1024 * 1024 + 1)
29
+ if not line:
30
+ break
31
+ if len(line) > 1024 * 1024:
32
+ break
33
+ requests.put(json.loads(line), timeout=1)
34
+ except Exception:
35
+ pass
36
+ finally:
37
+ # EOF must remain observable even when the queue was filled by a dead parent.
38
+ eof.set()
39
+
40
+
41
+ eof = threading.Event()
42
+
43
+
44
+ class Worker:
45
+ def __init__(self):
46
+ self.debugger = lldb.SBDebugger.Create(False)
47
+ self.debugger.SetAsync(True)
48
+ self.debugger.SetUseColor(False)
49
+ lldb.SBDebugger.SetInternalVariable("target.load-script-from-symbol-file", "false", self.debugger.GetInstanceName())
50
+ self.output = open(os.devnull, "w")
51
+ self.debugger.SetOutputFileHandle(self.output, False)
52
+ self.debugger.SetErrorFileHandle(self.output, False)
53
+ self.process = None
54
+ self.attached = False
55
+ self.breakpoints = None
56
+ self.inspection = Inspection()
57
+ self.created = self.activity = time.monotonic()
58
+ self.paused_at = None
59
+ self.last_stop = 0
60
+ self.terminal = False
61
+
62
+ def attach(self, args):
63
+ self.debugger.SetAsync(False)
64
+ target = self.debugger.CreateTarget(args["executablePath"])
65
+ if not target.IsValid():
66
+ raise ValueError("DEBUGGER_WORKER_FAILED")
67
+ self.breakpoints = Breakpoints(target)
68
+ error = lldb.SBError()
69
+ self.process = target.AttachToProcessWithID(self.debugger.GetListener(), args["pid"], error)
70
+ self.debugger.SetAsync(True)
71
+ self.attached = self.process.IsValid()
72
+ if not error.Success() or not self.process.IsValid():
73
+ raise ValueError("DEBUGGER_WORKER_FAILED")
74
+ actual = self.process.GetProcessInfo().GetExecutableFile()
75
+ if not actual.IsValid() or os.path.realpath(str(actual)) != os.path.realpath(args["executablePath"]):
76
+ self.process.Detach()
77
+ raise ValueError("DEBUGGER_WORKER_FAILED")
78
+ self.last_stop = self.process.GetStopID()
79
+ if not self.process.Continue().Success():
80
+ raise ValueError("DEBUGGER_WORKER_FAILED")
81
+ return {"attached": True}
82
+
83
+ def detach(self):
84
+ self.inspection.invalidate()
85
+ if self.breakpoints:
86
+ self.breakpoints.clear()
87
+ if self.attached:
88
+ if not self.process.IsValid():
89
+ raise ValueError("DEBUGGER_WORKER_FAILED")
90
+ if self.process.GetState() not in (lldb.eStateExited, lldb.eStateDetached):
91
+ error = self.process.Detach()
92
+ if not error.Success():
93
+ raise ValueError("DEBUGGER_WORKER_FAILED")
94
+ self.attached = False
95
+ self.terminal = True
96
+ return {"detached": True}
97
+
98
+ def poll(self):
99
+ # Removing process events updates LLDB's public state/thread snapshot.
100
+ # Polling GetState alone can leave the initial attach stop cached.
101
+ for _ in range(32):
102
+ event = lldb.SBEvent()
103
+ if not self.debugger.GetListener().GetNextEvent(event):
104
+ break
105
+ if lldb.SBProcess.EventIsProcessEvent(event):
106
+ lldb.SBProcess.GetStateFromEvent(event)
107
+ if not self.process or self.terminal:
108
+ return
109
+ state = self.process.GetState()
110
+ if state in (lldb.eStateExited, lldb.eStateDetached):
111
+ self.inspection.invalidate()
112
+ self.terminal = True
113
+ send({"event": "terminated", "reason": "process_exit" if state == lldb.eStateExited else "detached"})
114
+ elif state == lldb.eStateStopped and self.process.GetStopID() != self.last_stop:
115
+ self.breakpoints.refresh()
116
+ event = self.inspection.stopped(self.process)
117
+ if event:
118
+ self.last_stop = self.process.GetStopID()
119
+ self.paused_at = time.monotonic()
120
+ send(event)
121
+
122
+ def command(self, args):
123
+ command = args["command"]
124
+ if command == "attach":
125
+ return self.attach(args)
126
+ if command == "detach":
127
+ return self.detach()
128
+ if not self.process or self.terminal:
129
+ raise ValueError("DEBUGGER_DISCONNECTED")
130
+ if command in ("heartbeat", "state"):
131
+ return {"breakpoints": self.breakpoints.refresh()}
132
+ if command == "set":
133
+ return self.breakpoints.set(args)
134
+ if command == "remove":
135
+ return self.breakpoints.remove(args["breakpointId"])
136
+ if command in ("stack", "locals", "step", "resume"):
137
+ if args.get("stopId") != self.inspection.stop_id:
138
+ raise ValueError("DEBUGGER_STALE_REFERENCE")
139
+ if self.process.GetState() != lldb.eStateStopped:
140
+ raise ValueError("DEBUGGER_NOT_PAUSED")
141
+ if command == "stack":
142
+ return self.inspection.stack(args)
143
+ if command == "locals":
144
+ return self.inspection.locals(args)
145
+ if command in ("step", "resume"):
146
+ thread = self.inspection.thread(args) if command == "step" else None
147
+ self.inspection.invalidate()
148
+ self.paused_at = None
149
+ if command == "resume":
150
+ if not self.process.Continue().Success():
151
+ raise ValueError("DEBUGGER_WORKER_FAILED")
152
+ else:
153
+ error = lldb.SBError()
154
+ if args["kind"] == "into":
155
+ thread.StepInto(None, lldb.LLDB_INVALID_LINE_NUMBER, error, lldb.eOnlyDuringStepping)
156
+ elif args["kind"] == "over":
157
+ thread.StepOver(lldb.eOnlyDuringStepping, error)
158
+ else:
159
+ thread.StepOut(error)
160
+ if not error.Success():
161
+ raise ValueError("DEBUGGER_WORKER_FAILED")
162
+ return {"resumed": True}
163
+ raise ValueError("INVALID_ARGUMENT")
164
+
165
+ def expired(self):
166
+ now = time.monotonic()
167
+ return ("pause_limit" if self.paused_at and now - self.paused_at >= 60 else
168
+ "idle_limit" if now - self.activity >= 300 else
169
+ "session_limit" if now - self.created >= 1800 else None)
170
+
171
+
172
+ def main():
173
+ worker = Worker()
174
+ threading.Thread(target=read_input, daemon=True).start()
175
+ try:
176
+ while not eof.is_set():
177
+ worker.poll()
178
+ reason = worker.expired()
179
+ if reason:
180
+ worker.detach()
181
+ send({"event": "terminated", "reason": reason})
182
+ break
183
+ try:
184
+ args = requests.get(timeout=0.02)
185
+ except queue.Empty:
186
+ continue
187
+ worker.activity = time.monotonic()
188
+ try:
189
+ result = worker.command(args)
190
+ send({"id": args["id"], "result": result})
191
+ except Exception as error:
192
+ allowed = {"INVALID_ARGUMENT", "DEBUGGER_NOT_PAUSED", "DEBUGGER_STALE_REFERENCE", "DEBUGGER_DISCONNECTED"}
193
+ code = str(error) if isinstance(error, ValueError) and str(error) in allowed else "DEBUGGER_WORKER_FAILED"
194
+ send({"id": args["id"], "error": {"code": code}})
195
+ finally:
196
+ try:
197
+ worker.detach()
198
+ finally:
199
+ lldb.SBDebugger.Destroy(worker.debugger)
200
+
201
+
202
+ if __name__ == "__main__":
203
+ main()
@@ -0,0 +1,37 @@
1
+ import { parseDiagnostic, type DebuggerDiagnostic } from './diagnostics.js'
2
+ /** Worker messages contain private debuggee data; never include them in errors. */
3
+ export interface WorkerEvent extends Record<string, unknown> {
4
+ event: string
5
+ reason?: string
6
+ diagnostic?: DebuggerDiagnostic
7
+ stop_id?: string
8
+ }
9
+ export type WorkerReply = Record<string, unknown>
10
+ export type WorkerMessage =
11
+ | { kind: 'event'; value: WorkerEvent }
12
+ | { kind: 'reply'; id: number; result: WorkerReply; error?: { code?: string; diagnostic?: DebuggerDiagnostic } }
13
+
14
+ const workerCodes = new Set(['INVALID_ARGUMENT', 'DEBUGGER_BUSY', 'DEBUGGER_NOT_FOUND', 'DEBUGGER_NOT_PAUSED', 'DEBUGGER_STALE_REFERENCE', 'DEBUGGER_DISCONNECTED', 'DEBUGGER_WORKER_FAILED'])
15
+ function record(value: unknown): value is Record<string, unknown> {
16
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
17
+ }
18
+ export function parseWorkerMessage(value: unknown): WorkerMessage {
19
+ if (!record(value)) throw new Error('INVALID_WORKER_MESSAGE')
20
+ if (value.event) {
21
+ if (typeof value.event !== 'string') throw new Error('INVALID_WORKER_EVENT')
22
+ for (const field of ['reason', 'stop_id']) {
23
+ if (value[field] !== undefined && typeof value[field] !== 'string') throw new Error('INVALID_WORKER_EVENT')
24
+ }
25
+ const event = { ...value } as WorkerEvent
26
+ if (value.diagnostic !== undefined) event.diagnostic = parseDiagnostic(value.diagnostic)
27
+ return { kind: 'event', value: event }
28
+ }
29
+ if (typeof value.id !== 'number' || !Number.isSafeInteger(value.id) || value.id < 1) throw new Error('INVALID_WORKER_REPLY')
30
+ if (value.error) {
31
+ if (!record(value.error) || (value.error.code !== undefined && typeof value.error.code !== 'string')) throw new Error('INVALID_WORKER_ERROR')
32
+ if (value.error.code !== undefined && !workerCodes.has(value.error.code as string)) throw new Error('INVALID_WORKER_ERROR')
33
+ return { kind: 'reply', id: value.id, result: {}, error: { ...(value.error.code === undefined ? {} : { code: value.error.code as string }), ...(value.error.diagnostic === undefined ? {} : { diagnostic: parseDiagnostic(value.error.diagnostic) }) } }
34
+ }
35
+ if (!record(value.result)) throw new Error('INVALID_WORKER_RESULT')
36
+ return { kind: 'reply', id: value.id, result: value.result }
37
+ }