mobile-debug-mcp 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/mcp-builder/SKILL.md +44 -64
- package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
- package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
- package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
- package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
- package/.agents/skills/test-authoring/SKILL.md +7 -6
- package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
- package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
- package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +23 -2
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +158 -124
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +20 -6
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -4
- package/dist/contracts/execution-controls.js +24 -0
- package/dist/contracts/selector.js +8 -0
- package/dist/contracts/target.js +15 -0
- package/dist/contracts/ui.js +1 -0
- package/dist/debugger/android.js +79 -0
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/guard.js +30 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +304 -0
- package/dist/debugger/worker.js +176 -0
- package/dist/interact/adjustment/adjustment-context.js +41 -0
- package/dist/interact/adjustment/adjustment-gestures.js +55 -0
- package/dist/interact/adjustment/adjustment-plan.js +18 -0
- package/dist/interact/adjustment/adjustment-resolution.js +127 -0
- package/dist/interact/adjustment/adjustment-result.js +19 -0
- package/dist/interact/adjustment/adjustment-semantic.js +64 -0
- package/dist/interact/adjustment/adjustment-verification.js +35 -0
- package/dist/interact/adjustment/adjustment.js +52 -0
- package/dist/interact/adjustment/control-geometry.js +87 -0
- package/dist/interact/adjustment/control-values.js +136 -0
- package/dist/interact/adjustment/slider-labels.js +84 -0
- package/dist/interact/assertions/screen-assertion.js +37 -0
- package/dist/interact/assertions/state-assertion-context.js +35 -0
- package/dist/interact/assertions/state-assertion.js +76 -0
- package/dist/interact/assertions/state-comparison.js +141 -0
- package/dist/interact/assertions/visibility-assertion.js +34 -0
- package/dist/interact/conditional-action.js +12 -7
- package/dist/interact/index.js +65 -3074
- package/dist/interact/input-service.js +1 -0
- package/dist/interact/interaction-trace.js +21 -0
- package/dist/interact/model.js +1 -0
- package/dist/interact/observation.js +1 -0
- package/dist/interact/targeting/candidate-ancestor.js +58 -0
- package/dist/interact/targeting/candidate-result.js +23 -0
- package/dist/interact/targeting/candidate-scoring.js +50 -0
- package/dist/interact/targeting/element-helpers.js +216 -0
- package/dist/interact/targeting/find-element.js +65 -0
- package/dist/interact/targeting/nearby-control.js +56 -0
- package/dist/interact/targeting/resolved-elements.js +21 -0
- package/dist/interact/targeting/tap-target.js +17 -0
- package/dist/interact/targeting/target-scope.js +104 -0
- package/dist/interact/waits/legacy-log-wait.js +38 -0
- package/dist/interact/waits/legacy-wait-observations.js +103 -0
- package/dist/interact/waits/legacy-wait-state.js +34 -0
- package/dist/interact/waits/legacy-wait.js +48 -0
- package/dist/interact/waits/screen-wait.js +49 -0
- package/dist/interact/waits/ui-change-helpers.js +157 -0
- package/dist/interact/waits/ui-change-state.js +18 -0
- package/dist/interact/waits/ui-change-wait.js +77 -0
- package/dist/interact/waits/ui-wait-condition.js +56 -0
- package/dist/interact/waits/ui-wait-state.js +38 -0
- package/dist/interact/waits/ui-wait.js +106 -0
- package/dist/manage/android.js +55 -51
- package/dist/manage/index.js +38 -75
- package/dist/manage/ios/build-types.js +1 -0
- package/dist/manage/ios/build.js +210 -0
- package/dist/manage/ios/project-discovery.js +29 -0
- package/dist/manage/ios/scheme.js +18 -0
- package/dist/manage/ios.js +42 -256
- package/dist/observe/android/context.js +1 -0
- package/dist/observe/android/current-screen.js +55 -0
- package/dist/observe/android/fingerprint.js +13 -0
- package/dist/observe/android/log-stream.js +132 -0
- package/dist/observe/android/logs.js +107 -0
- package/dist/observe/android/screenshot.js +70 -0
- package/dist/observe/android/ui-tree.js +107 -0
- package/dist/observe/android.js +16 -520
- package/dist/observe/evidence.js +122 -76
- package/dist/observe/index.js +16 -263
- package/dist/observe/ios/context.js +16 -0
- package/dist/observe/ios/fingerprint.js +12 -0
- package/dist/observe/ios/log-stream.js +105 -0
- package/dist/observe/ios/logs.js +168 -0
- package/dist/observe/ios/screenshot.js +22 -0
- package/dist/observe/ios/ui-elements.js +241 -0
- package/dist/observe/ios/ui-tree.js +137 -0
- package/dist/observe/ios.js +17 -715
- package/dist/observe/logs/model.js +1 -0
- package/dist/{network → observe/network}/index.js +2 -2
- package/dist/observe/readiness-service.js +13 -2
- package/dist/observe/readiness.js +44 -18
- package/dist/observe/screenshot-encoding.js +41 -0
- package/dist/observe/snapshot-collection.js +79 -0
- package/dist/observe/snapshot-logs.js +30 -0
- package/dist/observe/snapshot-metadata.js +43 -31
- package/dist/observe/snapshot-semantic.js +115 -0
- package/dist/observe/system-trace-android.js +135 -0
- package/dist/observe/system-trace.js +294 -0
- package/dist/runtime/action-results.js +270 -0
- package/dist/runtime/clock.js +4 -0
- package/dist/server/common.js +1 -256
- package/dist/server/definitions/assert.js +162 -0
- package/dist/server/definitions/diagnostic.js +118 -0
- package/dist/server/definitions/input.js +274 -0
- package/dist/server/definitions/manage.js +194 -0
- package/dist/server/definitions/observe.js +149 -0
- package/dist/server/definitions/target.js +138 -0
- package/dist/server/definitions/wait.js +105 -0
- package/dist/server/handlers/action-execution.js +163 -0
- package/dist/server/handlers/correlation.js +23 -0
- package/dist/server/handlers/debugger-handlers.js +48 -0
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +69 -0
- package/dist/server/handlers/handler-types.js +1 -0
- package/dist/server/handlers/input.js +154 -0
- package/dist/server/handlers/interact.js +113 -0
- package/dist/server/handlers/journey-assertions.js +35 -0
- package/dist/server/handlers/journey.js +147 -0
- package/dist/server/handlers/manage.js +119 -0
- package/dist/server/handlers/observe.js +95 -0
- package/dist/server/handlers/payload.js +4 -0
- package/dist/server/handlers/tool-handlers.js +160 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/schemas/debugger-schema.js +47 -0
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +8 -22
- package/dist/server/schemas/system-trace-schema.js +17 -0
- package/dist/server/tool-definitions.js +62 -1134
- package/dist/server-core.js +4 -4
- package/dist/server.js +34 -0
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -438
- package/dist/system/ios-status.js +132 -0
- package/dist/system/ios.js +11 -8
- package/dist/system/model.js +1 -0
- package/dist/system/probes.js +105 -0
- package/dist/system/status-summary.js +43 -0
- package/dist/system/unsupported-status.js +52 -0
- package/dist/utils/android/commands.js +89 -0
- package/dist/utils/android/gradle.js +139 -0
- package/dist/utils/android/logs.js +103 -0
- package/dist/utils/android/ui-tree.js +233 -0
- package/dist/utils/android/utils.js +6 -564
- package/dist/utils/cli/idb/idb-helper.js +4 -6
- package/dist/utils/cli/idb/install-idb.js +13 -9
- package/dist/utils/cli/ios/preflight-ios.js +42 -38
- package/dist/utils/exec.js +2 -0
- package/dist/utils/ios/utils.js +45 -46
- package/dist/utils/java.js +75 -57
- package/dist/utils/operation-budget.js +2 -0
- package/dist/utils/resolve-device.js +6 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +48 -0
- package/docs/README.md +38 -0
- package/docs/benchmarks/README.md +18 -0
- package/docs/benchmarks/interaction-latency/README.md +14 -0
- package/docs/benchmarks/interaction-latency/final.md +4 -1
- package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
- package/docs/benchmarks/ui-tree-performance/README.md +24 -16
- package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
- package/docs/code-structure.md +118 -0
- package/docs/specs/001-state-verification/spec.md +16 -14
- package/docs/specs/002-richer-element-identity/spec.md +12 -10
- package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
- package/docs/specs/004-action-verification-routing/spec.md +3 -3
- package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
- package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
- package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
- package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
- package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
- package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
- package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
- package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
- package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
- package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
- package/docs/specs/014-actionability-resolution/spec.md +20 -18
- package/docs/specs/015-adjustable-control-support/spec.md +17 -15
- package/docs/specs/016-environment-tools/spec.md +20 -20
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
- package/docs/specs/020-conditional-actions/intent.md +5 -3
- package/docs/specs/020-conditional-actions/plan.md +8 -9
- package/docs/specs/020-conditional-actions/spec.md +4 -2
- package/docs/specs/021-app-readiness-signals/intent.md +4 -2
- package/docs/specs/021-app-readiness-signals/plan.md +7 -6
- package/docs/specs/021-app-readiness-signals/spec.md +3 -1
- package/docs/specs/022-correlated-evidence/intent.md +4 -4
- package/docs/specs/022-correlated-evidence/plan.md +10 -11
- package/docs/specs/022-correlated-evidence/spec.md +3 -1
- package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
- package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
- package/docs/specs/023-android-system-traces/intent.md +41 -0
- package/docs/specs/023-android-system-traces/plan.md +115 -0
- package/docs/specs/023-android-system-traces/spec.md +122 -0
- package/docs/specs/024-android-heap-dump-capture/intent.md +41 -0
- package/docs/specs/025-android-runtime-debugger/intent.md +41 -0
- package/docs/specs/025-android-runtime-debugger/plan.md +106 -0
- package/docs/specs/025-android-runtime-debugger/spec.md +183 -0
- package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
- package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
- package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
- package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
- package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
- package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
- package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
- package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
- package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
- package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
- package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
- package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
- package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
- package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
- package/docs/specs/030-android-coroutine-validation/report.md +240 -0
- package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
- package/docs/tools/TOOLS.md +31 -16
- package/docs/tools/{evidence.md → correlated-evidence.md} +14 -3
- package/docs/tools/debugger.md +180 -0
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +64 -25
- package/docs/tools/system-traces.md +134 -0
- package/docs/tools/system.md +12 -5
- package/package.json +11 -8
- package/scripts/architecture-graph.mjs +69 -0
- package/scripts/check-architecture.mjs +47 -0
- package/scripts/code-quality-baseline.json +1 -0
- package/scripts/code-quality.mjs +161 -0
- package/src/contracts/execution-controls.ts +26 -0
- package/src/contracts/selector.ts +17 -0
- package/src/contracts/target.ts +19 -0
- package/src/contracts/ui.ts +94 -0
- package/src/debugger/android.ts +66 -0
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +31 -0
- package/src/debugger/java/DebuggerWorker.java +166 -0
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +327 -0
- package/src/debugger/worker.ts +135 -0
- package/src/interact/adjustment/adjustment-context.ts +83 -0
- package/src/interact/adjustment/adjustment-gestures.ts +48 -0
- package/src/interact/adjustment/adjustment-plan.ts +23 -0
- package/src/interact/adjustment/adjustment-resolution.ts +104 -0
- package/src/interact/adjustment/adjustment-result.ts +34 -0
- package/src/interact/adjustment/adjustment-semantic.ts +53 -0
- package/src/interact/adjustment/adjustment-verification.ts +60 -0
- package/src/interact/adjustment/adjustment.ts +49 -0
- package/src/interact/adjustment/control-geometry.ts +120 -0
- package/src/interact/adjustment/control-values.ts +141 -0
- package/src/interact/adjustment/slider-labels.ts +84 -0
- package/src/interact/assertions/screen-assertion.ts +39 -0
- package/src/interact/assertions/state-assertion-context.ts +48 -0
- package/src/interact/assertions/state-assertion.ts +64 -0
- package/src/interact/assertions/state-comparison.ts +168 -0
- package/src/interact/assertions/visibility-assertion.ts +41 -0
- package/src/interact/conditional-action.ts +18 -12
- package/src/interact/index.ts +76 -3491
- package/src/interact/input-service.ts +11 -0
- package/src/interact/interaction-trace.ts +36 -0
- package/src/interact/model.ts +109 -0
- package/src/interact/observation.ts +7 -0
- package/src/interact/targeting/candidate-ancestor.ts +49 -0
- package/src/interact/targeting/candidate-result.ts +24 -0
- package/src/interact/targeting/candidate-scoring.ts +42 -0
- package/src/interact/targeting/element-helpers.ts +243 -0
- package/src/interact/targeting/find-element.ts +47 -0
- package/src/interact/targeting/nearby-control.ts +46 -0
- package/src/interact/targeting/resolved-elements.ts +20 -0
- package/src/interact/targeting/tap-target.ts +22 -0
- package/src/interact/targeting/target-scope.ts +121 -0
- package/src/interact/waits/legacy-log-wait.ts +30 -0
- package/src/interact/waits/legacy-wait-observations.ts +55 -0
- package/src/interact/waits/legacy-wait-state.ts +48 -0
- package/src/interact/waits/legacy-wait.ts +34 -0
- package/src/interact/waits/screen-wait.ts +37 -0
- package/src/interact/waits/ui-change-helpers.ts +184 -0
- package/src/interact/waits/ui-change-state.ts +26 -0
- package/src/interact/waits/ui-change-wait.ts +59 -0
- package/src/interact/waits/ui-wait-condition.ts +52 -0
- package/src/interact/waits/ui-wait-state.ts +55 -0
- package/src/interact/waits/ui-wait.ts +84 -0
- package/src/manage/android.ts +56 -50
- package/src/manage/index.ts +63 -93
- package/src/manage/ios/build-types.ts +14 -0
- package/src/manage/ios/build.ts +230 -0
- package/src/manage/ios/project-discovery.ts +26 -0
- package/src/manage/ios/scheme.ts +16 -0
- package/src/manage/ios.ts +65 -298
- package/src/observe/android/context.ts +1 -0
- package/src/observe/android/current-screen.ts +63 -0
- package/src/observe/android/fingerprint.ts +13 -0
- package/src/observe/android/log-stream.ts +127 -0
- package/src/observe/android/logs.ts +110 -0
- package/src/observe/android/screenshot.ts +79 -0
- package/src/observe/android/ui-tree.ts +113 -0
- package/src/observe/android.ts +17 -531
- package/src/observe/evidence.ts +130 -71
- package/src/observe/index.ts +24 -286
- package/src/observe/ios/context.ts +22 -0
- package/src/observe/ios/fingerprint.ts +12 -0
- package/src/observe/ios/log-stream.ts +103 -0
- package/src/observe/ios/logs.ts +179 -0
- package/src/observe/ios/screenshot.ts +26 -0
- package/src/observe/ios/ui-elements.ts +266 -0
- package/src/observe/ios/ui-tree.ts +143 -0
- package/src/observe/ios.ts +18 -749
- package/src/observe/logs/model.ts +11 -0
- package/src/{network → observe/network}/index.ts +2 -2
- package/src/observe/readiness-service.ts +10 -3
- package/src/observe/readiness.ts +49 -22
- package/src/observe/screenshot-encoding.ts +32 -0
- package/src/observe/snapshot-collection.ts +69 -0
- package/src/observe/snapshot-logs.ts +26 -0
- package/src/observe/snapshot-metadata.ts +51 -31
- package/src/observe/snapshot-semantic.ts +153 -0
- package/src/observe/system-trace-android.ts +133 -0
- package/src/observe/system-trace.ts +227 -0
- package/src/runtime/action-results.ts +376 -0
- package/src/runtime/clock.ts +9 -0
- package/src/server/common.ts +9 -359
- package/src/server/definitions/assert.ts +164 -0
- package/src/server/definitions/diagnostic.ts +120 -0
- package/src/server/definitions/input.ts +276 -0
- package/src/server/definitions/manage.ts +196 -0
- package/src/server/definitions/observe.ts +151 -0
- package/src/server/definitions/target.ts +140 -0
- package/src/server/definitions/wait.ts +107 -0
- package/src/server/handlers/action-execution.ts +164 -0
- package/src/server/handlers/correlation.ts +20 -0
- package/src/server/handlers/debugger-handlers.ts +39 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +79 -0
- package/src/server/handlers/handler-types.ts +7 -0
- package/src/server/handlers/input.ts +173 -0
- package/src/server/handlers/interact.ts +135 -0
- package/src/server/handlers/journey-assertions.ts +39 -0
- package/src/server/handlers/journey.ts +132 -0
- package/src/server/handlers/manage.ts +141 -0
- package/src/server/handlers/observe.ts +116 -0
- package/src/server/handlers/payload.ts +36 -0
- package/src/server/handlers/tool-handlers.ts +159 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/schemas/debugger-schema.ts +43 -0
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +8 -22
- package/src/server/schemas/system-trace-schema.ts +17 -0
- package/src/server/tool-definitions.ts +69 -1140
- package/src/server-core.ts +4 -4
- package/src/server.ts +25 -1
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -523
- package/src/system/ios-status.ts +147 -0
- package/src/system/ios.ts +14 -10
- package/src/system/model.ts +41 -0
- package/src/system/probes.ts +114 -0
- package/src/system/status-summary.ts +47 -0
- package/src/system/unsupported-status.ts +55 -0
- package/src/types.ts +16 -106
- package/src/utils/android/commands.ts +81 -0
- package/src/utils/android/gradle.ts +132 -0
- package/src/utils/android/logs.ts +103 -0
- package/src/utils/android/ui-tree.ts +239 -0
- package/src/utils/android/utils.ts +16 -556
- package/src/utils/cli/idb/check-idb.ts +4 -4
- package/src/utils/cli/idb/idb-helper.ts +6 -8
- package/src/utils/cli/idb/install-idb.ts +15 -9
- package/src/utils/cli/ios/preflight-ios.ts +39 -31
- package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
- package/src/utils/exec.ts +2 -0
- package/src/utils/ios/utils.ts +57 -61
- package/src/utils/java.ts +71 -51
- package/src/utils/operation-budget.ts +2 -0
- package/src/utils/resolve-device.ts +11 -6
- package/src/utils/ui/index.ts +28 -28
- package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
- package/test/device/automated/debugger/coroutine-support.ts +140 -0
- package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
- package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
- package/test/device/automated/debugger/runtime.android.test.ts +219 -0
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/system-trace.smoke.ts +104 -0
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3536 -0
- package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
- package/test/fixtures/coroutine-app/README.md +48 -0
- package/test/fixtures/coroutine-app/build.ts +35 -0
- package/test/fixtures/debugger-app/MainActivity.java +67 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/NoLocals.java +7 -0
- package/test/fixtures/debugger-app/README.md +65 -0
- package/test/fixtures/debugger-app/build.ts +39 -0
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/helpers/perfetto.ts +59 -0
- package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
- package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
- package/test/unit/debugger/diagnostics.test.ts +80 -0
- package/test/unit/debugger/guards.test.ts +50 -0
- package/test/unit/debugger/native.test.ts +33 -0
- package/test/unit/debugger/preparation.test.ts +32 -0
- package/test/unit/debugger/protocol.test.ts +15 -0
- package/test/unit/debugger/sessions.test.ts +83 -0
- package/test/unit/interact/adjustment_service.test.ts +42 -0
- package/test/unit/interact/adjustment_verification.test.ts +35 -0
- package/test/unit/interact/clock_dependencies.test.ts +63 -0
- package/test/unit/interact/element_helpers.test.ts +19 -0
- package/test/unit/interact/legacy_wait_service.test.ts +17 -0
- package/test/unit/interact/state_comparison.test.ts +20 -0
- package/test/unit/interact/target_scope.test.ts +35 -0
- package/test/unit/network/get_network_activity.test.ts +1 -1
- package/test/unit/observe/readiness.test.ts +3 -1
- package/test/unit/observe/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -0
- package/test/unit/observe/system-trace-android.test.ts +52 -0
- package/test/unit/observe/system-trace.test.ts +166 -0
- package/test/unit/quality/architecture.test.ts +60 -0
- package/test/unit/quality/code-quality.test.ts +58 -0
- package/test/unit/quality/interaction_boundaries.test.ts +24 -0
- package/test/unit/server/conditional_actions.test.ts +1 -1
- package/test/unit/server/conditional_schema.test.ts +2 -2
- package/test/unit/server/debugger-response.test.ts +63 -0
- package/test/unit/server/system-trace.test.ts +64 -0
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/tool-handlers.js +0 -893
- package/docs/ROADMAP.md +0 -646
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
- package/docs/specs/baseline-spec-v0.md +0 -312
- package/docs/specs/mcp-tooling-spec-v1.md +0 -446
- package/src/server/tool-handlers.ts +0 -923
- /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
- /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
- /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
- /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
|
@@ -6,7 +6,7 @@ import { getIdbCmd, isIDBInstalled, commandWhich } from './idb-helper.js'
|
|
|
6
6
|
const IDB_PKG = 'fb-idb'
|
|
7
7
|
|
|
8
8
|
function runCommand(cmd: string, args: string[]): number {
|
|
9
|
-
const res = spawnSync(cmd, args, { stdio: 'inherit'
|
|
9
|
+
const res = spawnSync(cmd, args, { stdio: 'inherit' })
|
|
10
10
|
return typeof res.status === 'number' ? res.status : 1
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -42,14 +42,7 @@ async function main() {
|
|
|
42
42
|
console.log('Auto-install enabled (MCP_AUTO_INSTALL_IDB=true or CI=true)')
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const attempts
|
|
46
|
-
if (commandWhich('pipx')) attempts.push({ name: 'pipx', cmd: 'pipx', args: ['install', IDB_PKG] })
|
|
47
|
-
if (commandWhich('pip') || commandWhich('python3')) attempts.push({ name: 'pip', cmd: commandWhich('pip') ? 'pip' : 'python3', args: commandWhich('pip') ? ['install', '--user', IDB_PKG] : ['-m', 'pip', 'install', '--user', IDB_PKG] })
|
|
48
|
-
|
|
49
|
-
// Add brew as a fallback on macOS if present (best-effort)
|
|
50
|
-
if (process.platform === 'darwin' && commandWhich('brew')) {
|
|
51
|
-
attempts.push({ name: 'brew', cmd: 'brew', args: ['install', 'idb'] })
|
|
52
|
-
}
|
|
45
|
+
const attempts = installerCandidates()
|
|
53
46
|
|
|
54
47
|
if (attempts.length === 0) {
|
|
55
48
|
console.error('No installer tool (pipx/pip/brew) detected. Please install pipx or pip and re-run.')
|
|
@@ -88,3 +81,16 @@ if (scriptPath === process.argv[1]) {
|
|
|
88
81
|
}
|
|
89
82
|
|
|
90
83
|
export { main }
|
|
84
|
+
|
|
85
|
+
function installerCandidates() {
|
|
86
|
+
const attempts: { name: string; cmd: string; args: string[] }[] = []
|
|
87
|
+
if (commandWhich('pipx')) attempts.push({ name: 'pipx', cmd: 'pipx', args: ['install', IDB_PKG] })
|
|
88
|
+
if (commandWhich('pip') || commandWhich('python3')) attempts.push({ name: 'pip', cmd: commandWhich('pip') ? 'pip' : 'python3', args: commandWhich('pip') ? ['install', '--user', IDB_PKG] : ['-m', 'pip', 'install', '--user', IDB_PKG] })
|
|
89
|
+
|
|
90
|
+
// Add brew as a fallback on macOS if present (best-effort)
|
|
91
|
+
if (process.platform === 'darwin' && commandWhich('brew')) {
|
|
92
|
+
attempts.push({ name: 'brew', cmd: 'brew', args: ['install', 'idb'] })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return attempts
|
|
96
|
+
}
|
|
@@ -9,22 +9,21 @@ async function exists(p: string) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
async function findFirst(root: string, patterns: string[], maxDepth = 4): Promise<string | null> {
|
|
12
|
-
const queue: Array<{dir:string, depth:number}> = [{dir: root, depth:0}]
|
|
12
|
+
const queue: Array<{ dir: string, depth: number }> = [{ dir: root, depth: 0 }]
|
|
13
13
|
while (queue.length) {
|
|
14
|
-
const {dir, depth} = queue.shift()!
|
|
14
|
+
const { dir, depth } = queue.shift()!
|
|
15
15
|
try {
|
|
16
16
|
console.error('DEBUG findFirst: reading dir', dir, 'depth', depth)
|
|
17
17
|
const ents: fs.Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true })
|
|
18
|
-
console.error('DEBUG findFirst: entries', ents.map(e=>e.name))
|
|
18
|
+
console.error('DEBUG findFirst: entries', ents.map(e => e.name))
|
|
19
19
|
for (const e of ents) {
|
|
20
20
|
const full = path.join(dir, e.name)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
21
|
+
const matched = patterns.some(pattern => e.name.endsWith(pattern))
|
|
22
|
+
if (matched) {
|
|
23
|
+
console.error('DEBUG findFirst: matched', full)
|
|
24
|
+
return full
|
|
26
25
|
}
|
|
27
|
-
if (e.isDirectory() && depth < maxDepth) queue.push({dir: full, depth: depth+1})
|
|
26
|
+
if (e.isDirectory() && depth < maxDepth) queue.push({ dir: full, depth: depth + 1 })
|
|
28
27
|
}
|
|
29
28
|
} catch (err) {
|
|
30
29
|
console.error('DEBUG findFirst: read failed for', dir, err)
|
|
@@ -46,18 +45,7 @@ function startCompanionIfNeeded(companionPath: string | null, udid: string | nul
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
async function main() {
|
|
49
|
-
const
|
|
50
|
-
let projectArg: string | undefined
|
|
51
|
-
let udid: string | undefined
|
|
52
|
-
let startCompanion = false
|
|
53
|
-
let kmpBuild: any = null
|
|
54
|
-
for (let i=0;i<args.length;i++) {
|
|
55
|
-
const a = args[i]
|
|
56
|
-
if (a === '--project' && args[i+1]) { projectArg = args[i+1]; i++ }
|
|
57
|
-
else if (a === '--udid' && args[i+1]) { udid = args[i+1]; i++ }
|
|
58
|
-
else if (a === '--start-companion') startCompanion = true
|
|
59
|
-
else if (!projectArg) projectArg = a
|
|
60
|
-
}
|
|
48
|
+
const { projectArg, udid, startCompanion, kmpBuild } = argumentsForPreflight(process.argv.slice(2))
|
|
61
49
|
|
|
62
50
|
const cwd = process.cwd()
|
|
63
51
|
const projectRoot = projectArg ? path.resolve(projectArg) : cwd
|
|
@@ -98,13 +86,10 @@ async function main() {
|
|
|
98
86
|
const companionPath = commandWhich('idb_companion')
|
|
99
87
|
const companionAvailable = !!companionPath
|
|
100
88
|
|
|
101
|
-
const suggestions
|
|
102
|
-
if (!projectFile) suggestions.push('Provide correct project path or ensure .xcodeproj/.xcworkspace exists in project dir')
|
|
103
|
-
if (!kmpFramework) suggestions.push('Run KMP Gradle task to produce Shared.framework before xcodebuild (e.g., :shared:embedAndSignAppleFrameworkForXcode)')
|
|
104
|
-
if (!idbAvailable) suggestions.push('Ensure idb is in PATH or set MCP_IDB_PATH / IDB_PATH')
|
|
105
|
-
if (!companionAvailable) suggestions.push('Install idb_companion and ensure it is in PATH')
|
|
89
|
+
const suggestions = preflightSuggestions(projectFile, kmpFramework, idbAvailable, companionAvailable)
|
|
106
90
|
|
|
107
|
-
const
|
|
91
|
+
const companionInfo: { cmd: string | null; installed: boolean; start?: ReturnType<typeof startCompanionIfNeeded> } = { cmd: companionPath, installed: companionAvailable }
|
|
92
|
+
const result = {
|
|
108
93
|
ok: !!projectFile && !!idbAvailable,
|
|
109
94
|
project: {
|
|
110
95
|
root: projectRoot,
|
|
@@ -120,10 +105,7 @@ async function main() {
|
|
|
120
105
|
cmd: idbPath,
|
|
121
106
|
installed: idbAvailable
|
|
122
107
|
},
|
|
123
|
-
idb_companion:
|
|
124
|
-
cmd: companionPath,
|
|
125
|
-
installed: companionAvailable
|
|
126
|
-
},
|
|
108
|
+
idb_companion: companionInfo,
|
|
127
109
|
suggestions
|
|
128
110
|
}
|
|
129
111
|
|
|
@@ -142,3 +124,29 @@ main().catch(e => {
|
|
|
142
124
|
console.log(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }, null, 2))
|
|
143
125
|
process.exit(2)
|
|
144
126
|
})
|
|
127
|
+
|
|
128
|
+
function argumentsForPreflight(args: string[]) {
|
|
129
|
+
let projectArg: string | undefined
|
|
130
|
+
let udid: string | undefined
|
|
131
|
+
let startCompanion = false
|
|
132
|
+
const kmpBuild = null
|
|
133
|
+
for (let i = 0; i < args.length; i++) {
|
|
134
|
+
const a = args[i]
|
|
135
|
+
if (a === '--project' && args[i + 1]) { projectArg = args[i + 1]; i++ }
|
|
136
|
+
else if (a === '--udid' && args[i + 1]) { udid = args[i + 1]; i++ }
|
|
137
|
+
else if (a === '--start-companion') startCompanion = true
|
|
138
|
+
else if (!projectArg) projectArg = a
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { projectArg, udid, startCompanion, kmpBuild }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function preflightSuggestions(projectFile: string | null, kmpFramework: string | null, idbAvailable: boolean, companionAvailable: boolean) {
|
|
145
|
+
const suggestions: string[] = []
|
|
146
|
+
if (!projectFile) suggestions.push('Provide correct project path or ensure .xcodeproj/.xcworkspace exists in project dir')
|
|
147
|
+
if (!kmpFramework) suggestions.push('Run KMP Gradle task to produce Shared.framework before xcodebuild (e.g., :shared:embedAndSignAppleFrameworkForXcode)')
|
|
148
|
+
if (!idbAvailable) suggestions.push('Ensure idb is in PATH or set MCP_IDB_PATH / IDB_PATH')
|
|
149
|
+
if (!companionAvailable) suggestions.push('Install idb_companion and ensure it is in PATH')
|
|
150
|
+
|
|
151
|
+
return suggestions
|
|
152
|
+
}
|
|
@@ -7,7 +7,7 @@ async function main() {
|
|
|
7
7
|
const interact = new iOSInteract();
|
|
8
8
|
|
|
9
9
|
console.log('Fetching UI tree...');
|
|
10
|
-
const tree = await obs.getUITree(deviceId
|
|
10
|
+
const tree = await obs.getUITree(deviceId);
|
|
11
11
|
if (tree.error) {
|
|
12
12
|
console.error('getUITree error:', tree.error);
|
|
13
13
|
process.exit(2);
|
|
@@ -18,12 +18,12 @@ async function main() {
|
|
|
18
18
|
process.exit(3);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const clickable = tree.elements.find((e:
|
|
21
|
+
const clickable = tree.elements.find((e: import('../../../types.js').UIElement) => e.clickable) || tree.elements[0];
|
|
22
22
|
console.log('Using element:', clickable.text || '(no text)', 'clickable=', clickable.clickable, 'center=', clickable.center);
|
|
23
|
-
const [x,y] = clickable.center || [0,0];
|
|
23
|
+
const [x, y] = clickable.center || [0, 0];
|
|
24
24
|
|
|
25
25
|
console.log(`Tapping at ${x},${y}...`);
|
|
26
|
-
const res = await interact.tap(x, y, deviceId
|
|
26
|
+
const res = await interact.tap(x, y, deviceId);
|
|
27
27
|
console.log('Tap result:', res);
|
|
28
28
|
|
|
29
29
|
if (res.success) process.exit(0);
|
package/src/utils/exec.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js'
|
|
1
2
|
import { remainingBudget } from './operation-budget.js'
|
|
2
3
|
import { spawn } from 'child_process'
|
|
3
4
|
|
|
@@ -7,6 +8,7 @@ export async function execCmd(cmd: string, args: string[], opts: ExecOptions = {
|
|
|
7
8
|
const { env, cwd, shell } = opts
|
|
8
9
|
const timeout = remainingBudget(opts.timeout ?? 0)
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
11
|
+
checkDebuggerGuard()
|
|
10
12
|
const child = spawn(cmd, args, { env: { ...process.env, ...(env || {}) }, cwd, shell })
|
|
11
13
|
let stdout = ''
|
|
12
14
|
let stderr = ''
|
package/src/utils/ios/utils.ts
CHANGED
|
@@ -19,17 +19,14 @@ export function getConfiguredIdbPath(): string | undefined {
|
|
|
19
19
|
for (const p of cfgPaths) {
|
|
20
20
|
if (!p) continue
|
|
21
21
|
try {
|
|
22
|
-
if (fs.existsSync(p))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
} catch {}
|
|
22
|
+
if (!fs.existsSync(p)) continue
|
|
23
|
+
const raw = fs.readFileSync(p, 'utf8')
|
|
24
|
+
const json = JSON.parse(raw)
|
|
25
|
+
if (json?.idbPath) return json.idbPath
|
|
26
|
+
if (json?.IDB_PATH) return json.IDB_PATH
|
|
27
|
+
} catch { }
|
|
31
28
|
}
|
|
32
|
-
} catch {}
|
|
29
|
+
} catch { }
|
|
33
30
|
return undefined
|
|
34
31
|
}
|
|
35
32
|
|
|
@@ -38,13 +35,13 @@ export function getIdbCmd() {
|
|
|
38
35
|
if (cfg) return cfg
|
|
39
36
|
if (process.env.IDB_PATH) return process.env.IDB_PATH
|
|
40
37
|
try {
|
|
41
|
-
const p = execSync('which idb', { stdio: ['ignore','pipe','ignore'] }).toString().trim()
|
|
38
|
+
const p = execSync('which idb', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
|
|
42
39
|
if (p) return p
|
|
43
|
-
} catch {}
|
|
40
|
+
} catch { }
|
|
44
41
|
try {
|
|
45
|
-
const p2 = execSync('command -v idb', { stdio: ['ignore','pipe','ignore'] }).toString().trim()
|
|
42
|
+
const p2 = execSync('command -v idb', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
|
|
46
43
|
if (p2) return p2
|
|
47
|
-
} catch {}
|
|
44
|
+
} catch { }
|
|
48
45
|
// check common user locations
|
|
49
46
|
const common = [
|
|
50
47
|
`${process.env.HOME}/Library/Python/3.9/bin/idb`,
|
|
@@ -53,7 +50,7 @@ export function getIdbCmd() {
|
|
|
53
50
|
'/usr/local/bin/idb',
|
|
54
51
|
]
|
|
55
52
|
for (const c of common) {
|
|
56
|
-
try { execSync(`test -x ${c}`, { stdio: ['ignore','pipe','ignore'] }); return c } catch {}
|
|
53
|
+
try { execSync(`test -x ${c}`, { stdio: ['ignore', 'pipe', 'ignore'] }); return c } catch { }
|
|
57
54
|
}
|
|
58
55
|
return 'idb'
|
|
59
56
|
}
|
|
@@ -62,7 +59,7 @@ export async function isIDBInstalled(): Promise<boolean> {
|
|
|
62
59
|
const cmd = getIdbCmd()
|
|
63
60
|
if (!cmd) return false
|
|
64
61
|
try {
|
|
65
|
-
execSync(`${cmd} list-targets --json`, { stdio: ['ignore','pipe','pipe'], timeout: remainingBudget(5000), killSignal: 'SIGKILL' })
|
|
62
|
+
execSync(`${cmd} list-targets --json`, { stdio: ['ignore', 'pipe', 'pipe'], timeout: remainingBudget(5000), killSignal: 'SIGKILL' })
|
|
66
63
|
return true
|
|
67
64
|
} catch (e: unknown) {
|
|
68
65
|
console.debug(`[isIDBInstalled] idb probe failed for '${cmd}': ${e instanceof Error ? e.message : String(e)}`)
|
|
@@ -89,7 +86,7 @@ export function execCommand(args: string[], deviceId: string = "booted"): Promis
|
|
|
89
86
|
// Use spawn for better stream control and consistency with Android implementation
|
|
90
87
|
const budgetMs = remainingBudget()
|
|
91
88
|
const child = spawn(getXcrunCmd(), args)
|
|
92
|
-
|
|
89
|
+
|
|
93
90
|
let stdout = ''
|
|
94
91
|
let stderr = ''
|
|
95
92
|
|
|
@@ -106,8 +103,8 @@ export function execCommand(args: string[], deviceId: string = "booted"): Promis
|
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
const DEFAULT_XCRUN_LOG_TIMEOUT = parseInt(process.env.MCP_XCRUN_LOG_TIMEOUT || '', 10) || 30000 // env (ms) or default 30s
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
const DEFAULT_XCRUN_CMD_TIMEOUT = parseInt(process.env.MCP_XCRUN_TIMEOUT || '', 10) || 60000 // env (ms) or default 60s
|
|
107
|
+
const timeoutMs = budgetMs || (args.includes('log') ? DEFAULT_XCRUN_LOG_TIMEOUT : DEFAULT_XCRUN_CMD_TIMEOUT) // choose appropriate timeout
|
|
111
108
|
const timeout = setTimeout(() => {
|
|
112
109
|
child.kill()
|
|
113
110
|
reject(new Error(`Command timed out after ${timeoutMs}ms: ${getXcrunCmd()} ${args.join(' ')}`))
|
|
@@ -135,12 +132,12 @@ export function execCommandWithDiagnostics(args: string[], deviceId: string = "b
|
|
|
135
132
|
const DEFAULT_XCRUN_LOG_TIMEOUT = parseInt(process.env.MCP_XCRUN_LOG_TIMEOUT || '', 10) || 30000
|
|
136
133
|
const DEFAULT_XCRUN_CMD_TIMEOUT = parseInt(process.env.MCP_XCRUN_TIMEOUT || '', 10) || 60000
|
|
137
134
|
const timeoutMs = budgetMs || (args.includes('log') ? DEFAULT_XCRUN_LOG_TIMEOUT : DEFAULT_XCRUN_CMD_TIMEOUT)
|
|
138
|
-
const res = spawnSync(getXcrunCmd(), args, { encoding: 'utf8', timeout: timeoutMs })
|
|
135
|
+
const res = spawnSync(getXcrunCmd(), args, { encoding: 'utf8', timeout: timeoutMs })
|
|
139
136
|
const runResult = {
|
|
140
137
|
exitCode: typeof res.status === 'number' ? res.status : null,
|
|
141
138
|
stdout: res.stdout || '',
|
|
142
139
|
stderr: res.stderr || '',
|
|
143
|
-
envSnapshot: makeEnvSnapshot(['PATH','IDB_PATH','JAVA_HOME','HOME']),
|
|
140
|
+
envSnapshot: makeEnvSnapshot(['PATH', 'IDB_PATH', 'JAVA_HOME', 'HOME']),
|
|
144
141
|
command: getXcrunCmd(),
|
|
145
142
|
args,
|
|
146
143
|
deviceId
|
|
@@ -168,17 +165,17 @@ function parseRuntimeName(runtime: string): string {
|
|
|
168
165
|
try {
|
|
169
166
|
const parts = runtime.split('.')
|
|
170
167
|
const lastPart = parts[parts.length - 1] // e.g. "iOS-17-0"
|
|
171
|
-
|
|
168
|
+
|
|
172
169
|
// Split by hyphen to separate OS from version numbers
|
|
173
170
|
// e.g. "iOS-17-0" -> ["iOS", "17", "0"]
|
|
174
171
|
const segments = lastPart.split('-');
|
|
175
|
-
|
|
172
|
+
|
|
176
173
|
if (segments.length > 1) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
const os = segments[0]; // "iOS"
|
|
175
|
+
const version = segments.slice(1).join('.'); // "17.0"
|
|
176
|
+
return `${os} ${version}`;
|
|
180
177
|
}
|
|
181
|
-
|
|
178
|
+
|
|
182
179
|
return lastPart;
|
|
183
180
|
} catch {
|
|
184
181
|
return runtime
|
|
@@ -220,18 +217,17 @@ export async function getIOSDeviceMetadata(deviceId: string = "booted"): Promise
|
|
|
220
217
|
|
|
221
218
|
for (const runtime in devicesMap) {
|
|
222
219
|
const devices = devicesMap[runtime]
|
|
223
|
-
if (Array.isArray(devices))
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
220
|
+
if (!Array.isArray(devices)) continue
|
|
221
|
+
for (const device of devices) {
|
|
222
|
+
if (deviceId === "booted" || device.udid === deviceId) {
|
|
223
|
+
resolve({
|
|
224
|
+
platform: "ios",
|
|
225
|
+
id: device.udid,
|
|
226
|
+
osVersion: parseRuntimeName(runtime),
|
|
227
|
+
model: device.name,
|
|
228
|
+
simulator: true,
|
|
229
|
+
})
|
|
230
|
+
return
|
|
235
231
|
}
|
|
236
232
|
}
|
|
237
233
|
}
|
|
@@ -264,34 +260,34 @@ export async function listIOSDevices(appId?: string): Promise<DeviceInfo[]> {
|
|
|
264
260
|
const bmap = bdata.devices || {}
|
|
265
261
|
for (const rt in bmap) {
|
|
266
262
|
const devs = bmap[rt]
|
|
267
|
-
if (Array.isArray(devs))
|
|
263
|
+
if (!Array.isArray(devs)) continue
|
|
264
|
+
for (const d of devs) bootedSet.add(d.udid)
|
|
268
265
|
}
|
|
269
|
-
} catch {}
|
|
266
|
+
} catch { }
|
|
270
267
|
}
|
|
271
268
|
|
|
272
269
|
for (const runtime in devicesMap) {
|
|
273
270
|
const devices = devicesMap[runtime]
|
|
274
|
-
if (Array.isArray(devices))
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
271
|
+
if (!Array.isArray(devices)) continue
|
|
272
|
+
for (const device of devices) {
|
|
273
|
+
const info: DeviceInfo = {
|
|
274
|
+
platform: 'ios',
|
|
275
|
+
id: device.udid,
|
|
276
|
+
osVersion: parseRuntimeName(runtime),
|
|
277
|
+
model: device.name,
|
|
278
|
+
simulator: true,
|
|
279
|
+
booted: bootedSet.has(device.udid)
|
|
280
|
+
}
|
|
284
281
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
282
|
+
if (appId) {
|
|
283
|
+
// check if installed
|
|
284
|
+
const p = execCommand(['simctl', 'get_app_container', device.udid, appId, 'data'], device.udid)
|
|
285
|
+
.then(() => { info.appInstalled = true })
|
|
286
|
+
.catch(() => { info.appInstalled = false })
|
|
287
|
+
.then(() => { out.push(info) })
|
|
288
|
+
checks.push(p)
|
|
289
|
+
} else {
|
|
290
|
+
out.push(info)
|
|
295
291
|
}
|
|
296
292
|
}
|
|
297
293
|
}
|
package/src/utils/java.ts
CHANGED
|
@@ -21,40 +21,22 @@ function javaVersionOf(javaBin: string): string | undefined {
|
|
|
21
21
|
} catch (e: unknown) { console.debug('[javaVersionOf] java -version failed: ' + String(e)); return undefined }
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export async function detectJavaHome(): Promise<string | undefined> {
|
|
24
|
+
export async function detectJavaHome(options: { quiet?: boolean } = {}): Promise<string | undefined> {
|
|
25
|
+
const debug = (...args: unknown[]) => { if (!options.quiet) console.debug(...args) }
|
|
25
26
|
try {
|
|
26
27
|
// 1) Honor explicit ANDROID_STUDIO_JDK env (highest priority)
|
|
27
28
|
const envStudio = process.env.ANDROID_STUDIO_JDK || process.env.ANDROID_STUDIO_JBR
|
|
28
29
|
if (envStudio && existsSync(path.join(envStudio, 'bin', 'java'))) {
|
|
29
30
|
const v = javaVersionOf(path.join(envStudio, 'bin', 'java'))
|
|
30
31
|
if (isJavaVersionAcceptable(v)) {
|
|
31
|
-
|
|
32
|
+
debug('[java.detect] Using ANDROID_STUDIO_JDK from env:', envStudio)
|
|
32
33
|
return envStudio
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
debug('[java.detect] ANDROID_STUDIO_JDK present but java -version did not match expected versions')
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'/Applications/Android Studio.app/Contents/jbr',
|
|
40
|
-
'/Applications/Android Studio.app/Contents/jbr/Contents/Home',
|
|
41
|
-
'/Applications/Android Studio Preview.app/Contents/jbr',
|
|
42
|
-
'/Applications/Android Studio Preview.app/Contents/jbr/Contents/Home',
|
|
43
|
-
'/Applications/Android Studio Preview 2022.3.app/Contents/jbr',
|
|
44
|
-
'/Applications/Android Studio Preview 2022.3.app/Contents/jbr/Contents/Home',
|
|
45
|
-
'/Applications/Android Studio Preview 2023.1.app/Contents/jbr',
|
|
46
|
-
'/Applications/Android Studio Preview 2023.1.app/Contents/jbr/Contents/Home'
|
|
47
|
-
]
|
|
48
|
-
for (const p of jbrCandidates) {
|
|
49
|
-
const javaBin = path.join(p, 'bin', 'java')
|
|
50
|
-
if (existsSync(javaBin)) {
|
|
51
|
-
const v = javaVersionOf(javaBin)
|
|
52
|
-
if (isJavaVersionAcceptable(v)) {
|
|
53
|
-
console.debug('[java.detect] Found Android Studio JBR at:', p)
|
|
54
|
-
return p
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
38
|
+
const jbr = findStudioJava(debug)
|
|
39
|
+
if (jbr) return jbr
|
|
58
40
|
|
|
59
41
|
// 3) If JAVA_HOME set, validate it (accept 17 or 21)
|
|
60
42
|
if (process.env.JAVA_HOME) {
|
|
@@ -62,12 +44,12 @@ export async function detectJavaHome(): Promise<string | undefined> {
|
|
|
62
44
|
const javaBin = path.join(process.env.JAVA_HOME, 'bin', 'java')
|
|
63
45
|
const v = javaVersionOf(javaBin)
|
|
64
46
|
if (isJavaVersionAcceptable(v)) {
|
|
65
|
-
|
|
47
|
+
debug('[java.detect] Using JAVA_HOME from env:', process.env.JAVA_HOME)
|
|
66
48
|
return process.env.JAVA_HOME
|
|
67
49
|
}
|
|
68
|
-
|
|
50
|
+
debug('[java.detect] Existing JAVA_HOME does not appear to be acceptable Java (17/21), will search')
|
|
69
51
|
} catch {
|
|
70
|
-
|
|
52
|
+
debug('[java.detect] Failed to validate existing JAVA_HOME, searching for JDK')
|
|
71
53
|
}
|
|
72
54
|
}
|
|
73
55
|
|
|
@@ -79,37 +61,75 @@ export async function detectJavaHome(): Promise<string | undefined> {
|
|
|
79
61
|
try {
|
|
80
62
|
const out17 = execSync('/usr/libexec/java_home -v 17', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim()
|
|
81
63
|
if (out17) return out17
|
|
82
|
-
} catch (e: unknown) {
|
|
64
|
+
} catch (e: unknown) { debug('[java.detect] /usr/libexec/java_home -v 17 failed: ' + String(e)) }
|
|
83
65
|
try {
|
|
84
66
|
const out21 = execSync('/usr/libexec/java_home -v 21', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim()
|
|
85
67
|
if (out21) return out21
|
|
86
|
-
} catch (e: unknown) {
|
|
68
|
+
} catch (e: unknown) { debug('[java.detect] /usr/libexec/java_home -v 21 failed: ' + String(e)) }
|
|
87
69
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
70
|
+
const commonHome = findMacJavaHome(debug)
|
|
71
|
+
if (commonHome) return commonHome
|
|
72
|
+
|
|
73
|
+
return findLinuxJavaHome(debug)
|
|
74
|
+
} catch (e: unknown) {
|
|
75
|
+
debug('[java.detect] error detecting java home:', e instanceof Error ? e.message : String(e))
|
|
76
|
+
}
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findStudioJava(debug: (...args: unknown[]) => void): string | undefined {
|
|
81
|
+
// 2) Android Studio JBR candidates (prefer these over JAVA_HOME)
|
|
82
|
+
const jbrCandidates = [
|
|
83
|
+
'/Applications/Android Studio.app/Contents/jbr',
|
|
84
|
+
'/Applications/Android Studio.app/Contents/jbr/Contents/Home',
|
|
85
|
+
'/Applications/Android Studio Preview.app/Contents/jbr',
|
|
86
|
+
'/Applications/Android Studio Preview.app/Contents/jbr/Contents/Home',
|
|
87
|
+
'/Applications/Android Studio Preview 2022.3.app/Contents/jbr',
|
|
88
|
+
'/Applications/Android Studio Preview 2022.3.app/Contents/jbr/Contents/Home',
|
|
89
|
+
'/Applications/Android Studio Preview 2023.1.app/Contents/jbr',
|
|
90
|
+
'/Applications/Android Studio Preview 2023.1.app/Contents/jbr/Contents/Home'
|
|
91
|
+
]
|
|
92
|
+
for (const p of jbrCandidates) {
|
|
93
|
+
const javaBin = path.join(p, 'bin', 'java')
|
|
94
|
+
if (existsSync(javaBin)) {
|
|
95
|
+
const v = javaVersionOf(javaBin)
|
|
96
|
+
if (isJavaVersionAcceptable(v)) {
|
|
97
|
+
debug('[java.detect] Found Android Studio JBR at:', p)
|
|
98
|
+
return p
|
|
96
99
|
}
|
|
97
|
-
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
return undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findMacJavaHome(debug: (...args: unknown[]) => void): string | undefined {
|
|
107
|
+
// 6) macOS common JDK locations
|
|
108
|
+
try {
|
|
109
|
+
const homes = execSync('ls -1 /Library/Java/JavaVirtualMachines || true', { stdio: ['ignore', 'pipe', 'inherit'] }).toString().split(/\r?\n/).filter(Boolean)
|
|
110
|
+
for (const h of homes) {
|
|
111
|
+
if (h.toLowerCase().includes('17') || h.toLowerCase().includes('jdk-17') || h.toLowerCase().includes('21') || h.toLowerCase().includes('jdk-21')) {
|
|
112
|
+
const candidate = `/Library/Java/JavaVirtualMachines/${h}/Contents/Home`
|
|
113
|
+
return candidate
|
|
114
|
+
}
|
|
110
115
|
}
|
|
111
|
-
} catch (e: unknown) {
|
|
112
|
-
|
|
116
|
+
} catch (e: unknown) { debug('[java.detect] listing /Library/Java/JavaVirtualMachines failed: ' + String(e)) }
|
|
117
|
+
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function findLinuxJavaHome(debug: (...args: unknown[]) => void): string | undefined {
|
|
122
|
+
// 7) Linux locations
|
|
123
|
+
const linuxCandidates = [
|
|
124
|
+
'/usr/lib/jvm/java-17-openjdk-amd64',
|
|
125
|
+
'/usr/lib/jvm/java-17-openjdk',
|
|
126
|
+
'/usr/lib/jvm/zulu17',
|
|
127
|
+
'/usr/lib/jvm/temurin-17-jdk',
|
|
128
|
+
'/usr/lib/jvm/temurin-21-jdk',
|
|
129
|
+
'/usr/lib/jvm/java-21-openjdk-amd64'
|
|
130
|
+
]
|
|
131
|
+
for (const p of linuxCandidates) {
|
|
132
|
+
try { if (existsSync(p)) return p } catch (e: unknown) { debug(`[java.detect] checking linux candidate ${p} failed: ${String(e)}`) }
|
|
113
133
|
}
|
|
114
134
|
return undefined
|
|
115
135
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js'
|
|
1
2
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
3
|
|
|
3
4
|
export interface OperationBudget { deadline: number; dispatchStarted: boolean; parent?: OperationBudget }
|
|
@@ -36,6 +37,7 @@ export async function runWithSeparateBudget<T>(timeoutMs: number, operation: ()
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
export function markDispatchStarted() {
|
|
40
|
+
checkDebuggerGuard(true)
|
|
39
41
|
remainingBudget()
|
|
40
42
|
let budget = budgets.getStore()
|
|
41
43
|
while (budget) { budget.dispatchStarted = true; budget = budget.parent }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js'
|
|
1
2
|
import { bindEvidenceDevice } from '../observe/evidence.js'
|
|
2
3
|
import { retainConnectedDeviceSnapshots } from '../observe/snapshot-metadata.js'
|
|
3
4
|
import { measure } from './timing.js'
|
|
@@ -15,7 +16,7 @@ export interface ResolveOptions {
|
|
|
15
16
|
|
|
16
17
|
function parseNumericVersion(v: string): number {
|
|
17
18
|
if (!v) return 0
|
|
18
|
-
const m = v.match(/(\d+)(?:[\.\-](\d+))?/)
|
|
19
|
+
const m = v.match(/(\d+)(?:[\.\-](\d+))?/)
|
|
19
20
|
if (!m) return 0
|
|
20
21
|
const major = parseInt(m[1], 10) || 0
|
|
21
22
|
const minor = parseInt(m[2] || "0", 10) || 0
|
|
@@ -56,6 +57,7 @@ export function withResolvedDevice<T>(device: DeviceInfo, operation: () => Promi
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export async function resolveTargetDevice(opts: ResolveOptions): Promise<DeviceInfo> {
|
|
60
|
+
checkDebuggerGuard()
|
|
59
61
|
const device = await resolveDevice(opts)
|
|
60
62
|
bindEvidenceDevice(device)
|
|
61
63
|
return device
|
|
@@ -80,13 +82,17 @@ async function resolveDevice(opts: ResolveOptions): Promise<DeviceInfo> {
|
|
|
80
82
|
return found
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
return selectDevice(devices, platform, appId, prefer)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function selectDevice(devices: DeviceInfo[], platform: string, appId?: string, prefer?: ResolveOptions['prefer']): DeviceInfo {
|
|
83
89
|
let candidates = devices.slice()
|
|
84
90
|
|
|
85
91
|
if (prefer === "physical") candidates = candidates.filter(d => !d.simulator)
|
|
86
92
|
if (prefer === "emulator") candidates = candidates.filter(d => d.simulator)
|
|
87
93
|
|
|
88
94
|
if (appId) {
|
|
89
|
-
const installed = candidates.filter(d =>
|
|
95
|
+
const installed = candidates.filter(d => d.appInstalled)
|
|
90
96
|
if (installed.length > 0) candidates = installed
|
|
91
97
|
}
|
|
92
98
|
|
|
@@ -101,7 +107,7 @@ async function resolveDevice(opts: ResolveOptions): Promise<DeviceInfo> {
|
|
|
101
107
|
|
|
102
108
|
// Prefer booted iOS simulators if present
|
|
103
109
|
if (platform === 'ios') {
|
|
104
|
-
const booted = candidates.filter((d:
|
|
110
|
+
const booted = candidates.filter((d: DeviceInfo) => !!d.booted)
|
|
105
111
|
if (booted.length === 1) return booted[0]
|
|
106
112
|
if (booted.length > 1) return booted[0] // if multiple booted, pick the first
|
|
107
113
|
}
|
|
@@ -111,9 +117,8 @@ async function resolveDevice(opts: ResolveOptions): Promise<DeviceInfo> {
|
|
|
111
117
|
return candidates[0]
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
const list = candidates.map(d => ({ id: d.id, platform: d.platform, osVersion: d.osVersion, model: d.model, simulator: d.simulator, appInstalled:
|
|
115
|
-
const err = new Error(`Multiple matching devices found: ${JSON.stringify(list, null, 2)}`)
|
|
116
|
-
;(err as any).devices = list
|
|
120
|
+
const list = candidates.map(d => ({ id: d.id, platform: d.platform, osVersion: d.osVersion, model: d.model, simulator: d.simulator, appInstalled: d.appInstalled }))
|
|
121
|
+
const err = Object.assign(new Error(`Multiple matching devices found: ${JSON.stringify(list, null, 2)}`), { devices: list })
|
|
117
122
|
throw err
|
|
118
123
|
}
|
|
119
124
|
|