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
|
@@ -1,234 +1,10 @@
|
|
|
1
|
-
import { promises as fsPromises
|
|
1
|
+
import { promises as fsPromises } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
// prefer command -v for POSIX
|
|
10
|
-
const res = spawnSync('command', ['-v', cmd], { encoding: 'utf8' });
|
|
11
|
-
if (res.status === 0 && res.stdout)
|
|
12
|
-
return res.stdout.trim();
|
|
13
|
-
}
|
|
14
|
-
catch (e) {
|
|
15
|
-
console.debug(`[findInPath] command -v ${cmd} failed: ${String(e)}`);
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
const res = spawnSync('which', [cmd], { encoding: 'utf8' });
|
|
19
|
-
if (res.status === 0 && res.stdout)
|
|
20
|
-
return res.stdout.trim();
|
|
21
|
-
}
|
|
22
|
-
catch (e) {
|
|
23
|
-
console.debug(`[findInPath] which ${cmd} failed: ${String(e)}`);
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
export function resolveAdbCmd() {
|
|
28
|
-
// Priority: explicit env ADB_PATH -> PATH -> known SDK locations -> fallback to 'adb'
|
|
29
|
-
if (process.env.ADB_PATH && process.env.ADB_PATH.trim())
|
|
30
|
-
return process.env.ADB_PATH;
|
|
31
|
-
const found = findInPath('adb');
|
|
32
|
-
if (found)
|
|
33
|
-
return found;
|
|
34
|
-
const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME;
|
|
35
|
-
if (sdkRoot) {
|
|
36
|
-
const candidate = path.join(sdkRoot, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
37
|
-
if (existsSync(candidate))
|
|
38
|
-
return candidate;
|
|
39
|
-
}
|
|
40
|
-
// common macOS user SDK path
|
|
41
|
-
const homeSdk = path.join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
42
|
-
if (existsSync(homeSdk))
|
|
43
|
-
return homeSdk;
|
|
44
|
-
return 'adb';
|
|
45
|
-
}
|
|
46
|
-
export function getAdbCmd() { return resolveAdbCmd(); }
|
|
47
|
-
export function ensureAdbAvailable() {
|
|
48
|
-
const adb = resolveAdbCmd();
|
|
49
|
-
try {
|
|
50
|
-
const res = spawnSync(adb, ['--version'], { encoding: 'utf8' });
|
|
51
|
-
if (res.status === 0) {
|
|
52
|
-
return { adbCmd: adb, ok: true, version: (res.stdout || res.stderr || '').trim() };
|
|
53
|
-
}
|
|
54
|
-
return { adbCmd: adb, ok: false, error: (res.stderr || res.stdout || '').trim() };
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
return { adbCmd: adb, ok: false, error: String(err) };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function mergeEnv(overrides) {
|
|
61
|
-
const env = {};
|
|
62
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
63
|
-
if (typeof value === 'string')
|
|
64
|
-
env[key] = value;
|
|
65
|
-
}
|
|
66
|
-
for (const [key, value] of Object.entries(overrides || {})) {
|
|
67
|
-
if (typeof value === 'string')
|
|
68
|
-
env[key] = value;
|
|
69
|
-
}
|
|
70
|
-
return env;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Prepare Gradle execution options for building an Android project.
|
|
74
|
-
* Returns execCmd (wrapper or gradle), base gradleArgs array, and spawn options including env.
|
|
75
|
-
*/
|
|
76
|
-
export async function prepareGradle(projectPath, envOverrides = {}) {
|
|
77
|
-
const env = mergeEnv(envOverrides);
|
|
78
|
-
const gradlewPath = path.join(projectPath, 'gradlew');
|
|
79
|
-
const gradleCmd = existsSync(gradlewPath) ? './gradlew' : 'gradle';
|
|
80
|
-
const execCmd = existsSync(gradlewPath) ? gradlewPath : gradleCmd;
|
|
81
|
-
// Start with a default task; callers may append/override via env flags
|
|
82
|
-
const gradleArgs = [env.MCP_GRADLE_TASK || 'assembleDebug'];
|
|
83
|
-
// Respect generic MCP_BUILD_JOBS and Android-specific MCP_GRADLE_WORKERS
|
|
84
|
-
const workers = env.MCP_GRADLE_WORKERS || env.MCP_BUILD_JOBS;
|
|
85
|
-
if (workers) {
|
|
86
|
-
gradleArgs.push(`--max-workers=${workers}`);
|
|
87
|
-
}
|
|
88
|
-
// Respect gradle cache env: default enabled; set MCP_GRADLE_CACHE=0 to disable
|
|
89
|
-
if (env.MCP_GRADLE_CACHE === '0') {
|
|
90
|
-
gradleArgs.push('-Dorg.gradle.caching=false');
|
|
91
|
-
}
|
|
92
|
-
const detectedJavaHome = await detectJavaHome().catch(() => undefined);
|
|
93
|
-
// Check for problematic org.gradle.java.home entries (env or properties) and avoid passing invalid values to Gradle
|
|
94
|
-
let gradleCheck;
|
|
95
|
-
try {
|
|
96
|
-
gradleCheck = await checkGradle();
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
gradleCheck = { gradleJavaHome: undefined, gradleValid: false, filesChecked: [], issues: [] };
|
|
100
|
-
}
|
|
101
|
-
// Ensure child processes can find Android platform-tools (adb, etc.) by
|
|
102
|
-
// prepending the platform-tools directory to PATH for spawned processes.
|
|
103
|
-
const adbPath = resolveAdbCmd();
|
|
104
|
-
let platformToolsDir = undefined;
|
|
105
|
-
try {
|
|
106
|
-
if (adbPath && adbPath !== 'adb' && existsSync(adbPath)) {
|
|
107
|
-
platformToolsDir = path.dirname(adbPath);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
console.debug(`[prepareGradle] error resolving adbPath: ${String(e)}`);
|
|
112
|
-
}
|
|
113
|
-
const pathParts = [];
|
|
114
|
-
// Prefer a detected (validated) Java home from the system/IDE
|
|
115
|
-
if (detectedJavaHome) {
|
|
116
|
-
if (env.JAVA_HOME !== detectedJavaHome) {
|
|
117
|
-
env.JAVA_HOME = detectedJavaHome;
|
|
118
|
-
}
|
|
119
|
-
const javaBin = path.join(detectedJavaHome, 'bin');
|
|
120
|
-
pathParts.push(javaBin);
|
|
121
|
-
gradleArgs.push(`-Dorg.gradle.java.home=${detectedJavaHome}`);
|
|
122
|
-
gradleArgs.push('--no-daemon');
|
|
123
|
-
env.GRADLE_JAVA_HOME = detectedJavaHome;
|
|
124
|
-
}
|
|
125
|
-
else if (gradleCheck && gradleCheck.gradleJavaHome) {
|
|
126
|
-
// There's an org.gradle.java.home configured somewhere (env or gradle.properties)
|
|
127
|
-
if (gradleCheck.gradleValid) {
|
|
128
|
-
const p = gradleCheck.gradleJavaHome;
|
|
129
|
-
const javaBin = path.join(p, 'bin');
|
|
130
|
-
if (!env.PATH || !env.PATH.includes(javaBin))
|
|
131
|
-
pathParts.push(javaBin);
|
|
132
|
-
gradleArgs.push(`-Dorg.gradle.java.home=${p}`);
|
|
133
|
-
gradleArgs.push('--no-daemon');
|
|
134
|
-
env.GRADLE_JAVA_HOME = p;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// Invalid gradle java home detected: avoid passing it to Gradle and remove from spawn env
|
|
138
|
-
console.debug(`[prepareGradle] Invalid org.gradle.java.home detected (${gradleCheck.gradleJavaHome}); removing from spawn env to avoid Gradle error.`);
|
|
139
|
-
try {
|
|
140
|
-
delete env.GRADLE_JAVA_HOME;
|
|
141
|
-
}
|
|
142
|
-
catch { }
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (platformToolsDir) {
|
|
146
|
-
// Prepend platform-tools so gradle and child tools find adb without modifying global env
|
|
147
|
-
if (!env.PATH || !env.PATH.includes(platformToolsDir)) {
|
|
148
|
-
pathParts.push(platformToolsDir);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
else if (process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME) {
|
|
152
|
-
const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME || '';
|
|
153
|
-
const candidate = path.join(sdkRoot, 'platform-tools');
|
|
154
|
-
if (existsSync(candidate) && (!env.PATH || !env.PATH.includes(candidate))) {
|
|
155
|
-
pathParts.push(candidate);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// also try common user sdk location
|
|
160
|
-
const homeSdkTools = path.join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools');
|
|
161
|
-
if (existsSync(homeSdkTools) && (!env.PATH || !env.PATH.includes(homeSdkTools))) {
|
|
162
|
-
pathParts.push(homeSdkTools);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (pathParts.length > 0) {
|
|
166
|
-
env.PATH = `${pathParts.join(path.delimiter)}${path.delimiter}${env.PATH || ''}`;
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
delete env.SHELL;
|
|
170
|
-
}
|
|
171
|
-
catch (e) {
|
|
172
|
-
console.debug('[prepareGradle] failed to delete SHELL from env:', String(e));
|
|
173
|
-
}
|
|
174
|
-
const useWrapper = existsSync(gradlewPath);
|
|
175
|
-
const spawnOpts = { cwd: projectPath, env };
|
|
176
|
-
if (useWrapper) {
|
|
177
|
-
try {
|
|
178
|
-
await fsPromises.chmod(gradlewPath, 0o755);
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
console.debug('[prepareGradle] chmod failed for gradlew:', String(e));
|
|
182
|
-
}
|
|
183
|
-
// Execute the wrapper directly without a shell to avoid shell tokenization of args (spaces in paths)
|
|
184
|
-
spawnOpts.shell = false;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
// Prefer executing gradle directly without invoking a shell to preserve argument boundaries
|
|
188
|
-
spawnOpts.shell = false;
|
|
189
|
-
}
|
|
190
|
-
return { execCmd, gradleArgs, spawnOpts };
|
|
191
|
-
}
|
|
192
|
-
// Helper to construct ADB args with optional device ID
|
|
193
|
-
function getAdbArgs(args, deviceId) {
|
|
194
|
-
if (deviceId) {
|
|
195
|
-
return ['-s', deviceId, ...args];
|
|
196
|
-
}
|
|
197
|
-
return args;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Determine an effective ADB timeout (ms) prioritizing:
|
|
201
|
-
* 1. provided customTimeout
|
|
202
|
-
* 2. MCP_ADB_TIMEOUT or ADB_TIMEOUT env vars
|
|
203
|
-
* 3. sensible per-command defaults
|
|
204
|
-
*/
|
|
205
|
-
function getAdbTimeout(args, customTimeout) {
|
|
206
|
-
if (typeof customTimeout === 'number' && !isNaN(customTimeout))
|
|
207
|
-
return customTimeout;
|
|
208
|
-
const envTimeout = parseInt(process.env.MCP_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '', 10);
|
|
209
|
-
if (!isNaN(envTimeout) && envTimeout > 0)
|
|
210
|
-
return envTimeout;
|
|
211
|
-
if (args.includes('logcat'))
|
|
212
|
-
return 10000;
|
|
213
|
-
if (args.includes('uiautomator') && args.includes('dump'))
|
|
214
|
-
return 20000;
|
|
215
|
-
return 120000;
|
|
216
|
-
}
|
|
217
|
-
export async function execAdb(args, deviceId, options = {}) {
|
|
218
|
-
const adbArgs = getAdbArgs(args, deviceId);
|
|
219
|
-
const timeoutMs = getAdbTimeout(args, options.timeout);
|
|
220
|
-
const res = await execCmd(getAdbCmd(), adbArgs, { timeout: timeoutMs, env: options.env, cwd: typeof options.cwd === 'string' ? options.cwd : undefined, shell: !!options.shell });
|
|
221
|
-
if (res.exitCode !== 0)
|
|
222
|
-
throw new Error(res.stderr || `Command failed with code ${res.exitCode}`);
|
|
223
|
-
return res.stdout;
|
|
224
|
-
}
|
|
225
|
-
// Spawn adb but return full streams and exit code so callers can implement fallbacks or stream output
|
|
226
|
-
export async function spawnAdb(args, deviceId, options = {}) {
|
|
227
|
-
const adbArgs = getAdbArgs(args, deviceId);
|
|
228
|
-
const timeoutMs = getAdbTimeout(args, options.timeout);
|
|
229
|
-
const res = await execCmd(getAdbCmd(), adbArgs, { timeout: timeoutMs, env: options.env, cwd: typeof options.cwd === 'string' ? options.cwd : undefined, shell: !!options.shell });
|
|
230
|
-
return { stdout: res.stdout, stderr: res.stderr, code: res.exitCode };
|
|
231
|
-
}
|
|
3
|
+
export { findInPath, resolveAdbCmd, getAdbCmd, ensureAdbAvailable, getAdbArgs, getAdbTimeout, execAdb, spawnAdb } from './commands.js';
|
|
4
|
+
import { execAdb } from './commands.js';
|
|
5
|
+
export { mergeEnv, prepareGradle } from './gradle.js';
|
|
6
|
+
export { parseBounds, getCenter, parseBooleanAttr, parseNumberAttr, normalizeClassName, inferAndroidRole, buildAndroidSelectorConfidence, buildAndroidSelector, buildAndroidSemantic, isSliderLikeAndroid, extractAndroidState, traverseNode } from './ui-tree.js';
|
|
7
|
+
export { parseLogLine } from './logs.js';
|
|
232
8
|
export function getDeviceInfo(deviceId, metadata = {}) {
|
|
233
9
|
return {
|
|
234
10
|
platform: 'android',
|
|
@@ -328,168 +104,7 @@ export async function listAndroidDevices(appId) {
|
|
|
328
104
|
return [];
|
|
329
105
|
}
|
|
330
106
|
}
|
|
331
|
-
// UI helper utilities shared by observe/interact
|
|
332
107
|
export const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
333
|
-
export function parseBounds(bounds) {
|
|
334
|
-
const match = bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
|
|
335
|
-
if (match) {
|
|
336
|
-
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), parseInt(match[4])];
|
|
337
|
-
}
|
|
338
|
-
return [0, 0, 0, 0];
|
|
339
|
-
}
|
|
340
|
-
export function getCenter(bounds) {
|
|
341
|
-
const [x1, y1, x2, y2] = bounds;
|
|
342
|
-
return [Math.floor((x1 + x2) / 2), Math.floor((y1 + y2) / 2)];
|
|
343
|
-
}
|
|
344
|
-
function parseBooleanAttr(value) {
|
|
345
|
-
if (value === true || value === 'true')
|
|
346
|
-
return true;
|
|
347
|
-
if (value === false || value === 'false')
|
|
348
|
-
return false;
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
function parseNumberAttr(value) {
|
|
352
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
353
|
-
return value;
|
|
354
|
-
if (typeof value !== 'string')
|
|
355
|
-
return null;
|
|
356
|
-
const parsed = Number(value);
|
|
357
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
358
|
-
}
|
|
359
|
-
function normalizeClassName(value) {
|
|
360
|
-
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
361
|
-
}
|
|
362
|
-
function inferAndroidRole(className) {
|
|
363
|
-
if (/seekbar|slider/.test(className))
|
|
364
|
-
return 'slider';
|
|
365
|
-
if (/stepper|numberpicker/.test(className))
|
|
366
|
-
return 'stepper';
|
|
367
|
-
if (/spinner|dropdown/.test(className))
|
|
368
|
-
return 'dropdown';
|
|
369
|
-
if (/segment|tablayout/.test(className))
|
|
370
|
-
return 'segmented_control';
|
|
371
|
-
if (/switch|toggle/.test(className))
|
|
372
|
-
return 'switch';
|
|
373
|
-
if (/checkbox/.test(className))
|
|
374
|
-
return 'checkbox';
|
|
375
|
-
if (/radiobutton|radio/.test(className))
|
|
376
|
-
return 'radio';
|
|
377
|
-
if (/edittext|textfield|search/.test(className))
|
|
378
|
-
return 'text_field';
|
|
379
|
-
if (/button|fab/.test(className))
|
|
380
|
-
return 'button';
|
|
381
|
-
if (/imageview|icon/.test(className))
|
|
382
|
-
return 'image';
|
|
383
|
-
if (/recyclerview|scroll|layout|viewgroup|frame/.test(className))
|
|
384
|
-
return 'container';
|
|
385
|
-
return null;
|
|
386
|
-
}
|
|
387
|
-
function buildAndroidSelectorConfidence(source) {
|
|
388
|
-
switch (source) {
|
|
389
|
-
case 'resource_id':
|
|
390
|
-
return { score: 1, reason: 'resource_id' };
|
|
391
|
-
case 'content_desc':
|
|
392
|
-
return { score: 0.9, reason: 'content_description' };
|
|
393
|
-
case 'text':
|
|
394
|
-
return { score: 0.6, reason: 'text_match' };
|
|
395
|
-
case 'class':
|
|
396
|
-
return { score: 0.35, reason: 'class_match' };
|
|
397
|
-
default:
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
function buildAndroidSelector(text, contentDescription, resourceId, className) {
|
|
402
|
-
if (resourceId)
|
|
403
|
-
return { value: resourceId, confidence: buildAndroidSelectorConfidence('resource_id') };
|
|
404
|
-
if (contentDescription)
|
|
405
|
-
return { value: contentDescription, confidence: buildAndroidSelectorConfidence('content_desc') };
|
|
406
|
-
if (text)
|
|
407
|
-
return { value: text, confidence: buildAndroidSelectorConfidence('text') };
|
|
408
|
-
if (className)
|
|
409
|
-
return { value: className, confidence: buildAndroidSelectorConfidence('class') };
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
function buildAndroidSemantic(clickable, className, role) {
|
|
413
|
-
const semantic = {
|
|
414
|
-
is_clickable: clickable,
|
|
415
|
-
is_container: /recyclerview|scroll|layout|viewgroup|frame/.test(className)
|
|
416
|
-
};
|
|
417
|
-
if (role === 'slider') {
|
|
418
|
-
semantic.semantic_role = 'slider';
|
|
419
|
-
semantic.adjustable = true;
|
|
420
|
-
semantic.supported_actions = ['adjust'];
|
|
421
|
-
semantic.state_shape = 'continuous';
|
|
422
|
-
}
|
|
423
|
-
else if (role === 'stepper') {
|
|
424
|
-
semantic.semantic_role = 'stepper';
|
|
425
|
-
semantic.adjustable = true;
|
|
426
|
-
semantic.supported_actions = ['increment', 'decrement'];
|
|
427
|
-
semantic.state_shape = 'discrete';
|
|
428
|
-
}
|
|
429
|
-
else if (role === 'dropdown') {
|
|
430
|
-
semantic.semantic_role = 'dropdown';
|
|
431
|
-
semantic.supported_actions = ['tap', 'expand'];
|
|
432
|
-
semantic.state_shape = 'semantic';
|
|
433
|
-
}
|
|
434
|
-
else if (role === 'segmented_control') {
|
|
435
|
-
semantic.semantic_role = 'segmented_control';
|
|
436
|
-
semantic.supported_actions = ['tap'];
|
|
437
|
-
semantic.state_shape = 'discrete';
|
|
438
|
-
}
|
|
439
|
-
else if (clickable) {
|
|
440
|
-
semantic.supported_actions = ['tap'];
|
|
441
|
-
}
|
|
442
|
-
return semantic;
|
|
443
|
-
}
|
|
444
|
-
function isSliderLikeAndroid(node) {
|
|
445
|
-
const className = String(node['@_class'] || '').toLowerCase();
|
|
446
|
-
return /seekbar|slider|range/i.test(className);
|
|
447
|
-
}
|
|
448
|
-
function extractAndroidState(node) {
|
|
449
|
-
const checked = parseBooleanAttr(node['@_checked']);
|
|
450
|
-
const selectedFlag = parseBooleanAttr(node['@_selected']);
|
|
451
|
-
const focused = parseBooleanAttr(node['@_focused']);
|
|
452
|
-
const expanded = parseBooleanAttr(node['@_expanded']);
|
|
453
|
-
const enabled = parseBooleanAttr(node['@_enabled']);
|
|
454
|
-
const textValue = typeof node['@_text'] === 'string' && node['@_text'].trim().length > 0 ? node['@_text'] : null;
|
|
455
|
-
const state = {};
|
|
456
|
-
if (checked !== null)
|
|
457
|
-
state.checked = checked;
|
|
458
|
-
if (selectedFlag !== null) {
|
|
459
|
-
state.selected = textValue || node['@_content-desc'] || true;
|
|
460
|
-
}
|
|
461
|
-
if (focused !== null)
|
|
462
|
-
state.focused = focused;
|
|
463
|
-
if (expanded !== null)
|
|
464
|
-
state.expanded = expanded;
|
|
465
|
-
if (enabled !== null)
|
|
466
|
-
state.enabled = enabled;
|
|
467
|
-
if (textValue && /edittext|textfield|search/i.test(String(node['@_class'] || ''))) {
|
|
468
|
-
state.text_value = textValue;
|
|
469
|
-
}
|
|
470
|
-
if (isSliderLikeAndroid(node)) {
|
|
471
|
-
const rawProgress = parseNumberAttr(node['@_progress']);
|
|
472
|
-
const max = parseNumberAttr(node['@_max']);
|
|
473
|
-
const fallbackValue = rawProgress ?? parseNumberAttr(node['@_value']) ?? parseNumberAttr(node['@_content-desc']);
|
|
474
|
-
const numericValue = rawProgress ?? fallbackValue;
|
|
475
|
-
if (numericValue !== null) {
|
|
476
|
-
state.raw_value = numericValue;
|
|
477
|
-
state.value_range = max !== null && max > 0 ? { min: 0, max } : null;
|
|
478
|
-
state.value = max !== null && max > 0 ? Math.round((numericValue / max) * 100) : numericValue;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
const numericValue = parseNumberAttr(node['@_value']);
|
|
483
|
-
if (numericValue !== null) {
|
|
484
|
-
state.value = numericValue;
|
|
485
|
-
state.raw_value = numericValue;
|
|
486
|
-
}
|
|
487
|
-
else if (textValue) {
|
|
488
|
-
state.value = textValue;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return Object.keys(state).length > 0 ? state : null;
|
|
492
|
-
}
|
|
493
108
|
export async function getScreenResolution(deviceId) {
|
|
494
109
|
try {
|
|
495
110
|
const output = await execAdb(['shell', 'wm', 'size'], deviceId);
|
|
@@ -503,176 +118,3 @@ export async function getScreenResolution(deviceId) {
|
|
|
503
118
|
}
|
|
504
119
|
return { width: 0, height: 0 };
|
|
505
120
|
}
|
|
506
|
-
export function traverseNode(node, elements, parentIndex = -1, depth = 0) {
|
|
507
|
-
if (!node)
|
|
508
|
-
return -1;
|
|
509
|
-
let currentIndex = -1;
|
|
510
|
-
if (node['@_class']) {
|
|
511
|
-
const text = node['@_text'] || null;
|
|
512
|
-
const contentDescription = node['@_content-desc'] || null;
|
|
513
|
-
const clickable = node['@_clickable'] === 'true';
|
|
514
|
-
const className = String(node['@_class'] || 'unknown');
|
|
515
|
-
const bounds = parseBounds(node['@_bounds'] || '[0,0][0,0]');
|
|
516
|
-
const state = extractAndroidState(node);
|
|
517
|
-
const role = inferAndroidRole(normalizeClassName(className));
|
|
518
|
-
const resourceId = typeof node['@_resource-id'] === 'string' && node['@_resource-id'].trim().length > 0 ? node['@_resource-id'] : null;
|
|
519
|
-
const stableId = resourceId ?? (typeof contentDescription === 'string' && contentDescription.trim().length > 0 ? contentDescription : null);
|
|
520
|
-
const testTag = stableId;
|
|
521
|
-
const selector = buildAndroidSelector(text, contentDescription, resourceId, normalizeClassName(className));
|
|
522
|
-
const semantic = buildAndroidSemantic(clickable, normalizeClassName(className), role);
|
|
523
|
-
const isUseful = clickable || (text && text.length > 0) || (contentDescription && contentDescription.length > 0);
|
|
524
|
-
if (isUseful) {
|
|
525
|
-
const element = {
|
|
526
|
-
text,
|
|
527
|
-
contentDescription,
|
|
528
|
-
type: className,
|
|
529
|
-
resourceId,
|
|
530
|
-
clickable,
|
|
531
|
-
enabled: node['@_enabled'] === 'true',
|
|
532
|
-
visible: true,
|
|
533
|
-
bounds,
|
|
534
|
-
center: getCenter(bounds),
|
|
535
|
-
depth,
|
|
536
|
-
state,
|
|
537
|
-
stable_id: stableId,
|
|
538
|
-
role,
|
|
539
|
-
test_tag: testTag,
|
|
540
|
-
selector,
|
|
541
|
-
semantic
|
|
542
|
-
};
|
|
543
|
-
if (parentIndex !== -1) {
|
|
544
|
-
element.parentId = parentIndex;
|
|
545
|
-
}
|
|
546
|
-
elements.push(element);
|
|
547
|
-
currentIndex = elements.length - 1;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
const nextParentIndex = currentIndex !== -1 ? currentIndex : parentIndex;
|
|
551
|
-
const nextDepth = currentIndex !== -1 ? depth + 1 : depth;
|
|
552
|
-
const childrenIndices = [];
|
|
553
|
-
if (node.node) {
|
|
554
|
-
if (Array.isArray(node.node)) {
|
|
555
|
-
node.node.forEach((child) => {
|
|
556
|
-
const childIndex = traverseNode(child, elements, nextParentIndex, nextDepth);
|
|
557
|
-
if (childIndex !== -1)
|
|
558
|
-
childrenIndices.push(childIndex);
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
const childIndex = traverseNode(node.node, elements, nextParentIndex, nextDepth);
|
|
563
|
-
if (childIndex !== -1)
|
|
564
|
-
childrenIndices.push(childIndex);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (currentIndex !== -1 && childrenIndices.length > 0) {
|
|
568
|
-
elements[currentIndex].children = childrenIndices;
|
|
569
|
-
}
|
|
570
|
-
return currentIndex;
|
|
571
|
-
}
|
|
572
|
-
// Log stream management (one stream per session)
|
|
573
|
-
// (Legacy active stream map removed from utils during refactor; Observe modules manage their own active streams.)
|
|
574
|
-
// Robust log line parser supporting multiple logcat formats
|
|
575
|
-
export function parseLogLine(line) {
|
|
576
|
-
const rawLine = line;
|
|
577
|
-
const normalizedLine = rawLine.replace(/\r?\n/g, ' ');
|
|
578
|
-
const entry = { timestamp: '', level: '', tag: '', message: rawLine, _iso: null, crash: false };
|
|
579
|
-
const nowYear = new Date().getFullYear();
|
|
580
|
-
const tryIso = (ts) => {
|
|
581
|
-
if (!ts)
|
|
582
|
-
return null;
|
|
583
|
-
// If it's already ISO
|
|
584
|
-
if (/^\d{4}-\d{2}-\d{2}T/.test(ts))
|
|
585
|
-
return ts;
|
|
586
|
-
// If format MM-DD HH:MM:SS(.sss)
|
|
587
|
-
const m = ts.match(/^(\d{2})-(\d{2})\s+(\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)$/);
|
|
588
|
-
if (m) {
|
|
589
|
-
const month = m[1];
|
|
590
|
-
const day = m[2];
|
|
591
|
-
const time = m[3];
|
|
592
|
-
const candidate = `${nowYear}-${month}-${day}T${time}`;
|
|
593
|
-
const d = new Date(candidate);
|
|
594
|
-
if (!isNaN(d.getTime()))
|
|
595
|
-
return d.toISOString();
|
|
596
|
-
}
|
|
597
|
-
// If format YYYY-MM-DD HH:MM:SS(.sss)
|
|
598
|
-
const m2 = ts.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)$/);
|
|
599
|
-
if (m2) {
|
|
600
|
-
const candidate = `${m2[1]}T${m2[2]}`;
|
|
601
|
-
const d = new Date(candidate);
|
|
602
|
-
if (!isNaN(d.getTime()))
|
|
603
|
-
return d.toISOString();
|
|
604
|
-
}
|
|
605
|
-
return null;
|
|
606
|
-
};
|
|
607
|
-
// Patterns to try (ordered)
|
|
608
|
-
const patterns = [
|
|
609
|
-
// MM-DD HH:MM:SS.mmm PID TID LEVEL/Tag: msg
|
|
610
|
-
{ re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\/([^:]+):\s*(.*)$/, groups: ['ts', 'pid', 'tid', 'level', 'tag', 'msg'] },
|
|
611
|
-
// MM-DD HH:MM:SS.mmm PID TID LEVEL Tag: msg (space between level and tag)
|
|
612
|
-
{ re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\s+([^:]+):\s*(.*)$/, groups: ['ts', 'pid', 'tid', 'level', 'tag', 'msg'] },
|
|
613
|
-
// YYYY-MM-DD full date with PID TID LEVEL/Tag
|
|
614
|
-
{ re: /^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\/([^:]+):\s*(.*)$/, groups: ['ts', 'pid', 'tid', 'level', 'tag', 'msg'] },
|
|
615
|
-
// YYYY-MM-DD with space separation
|
|
616
|
-
{ re: /^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+(\d+)\s+([VDIWE])\s+([^:]+):\s*(.*)$/, groups: ['ts', 'pid', 'tid', 'level', 'tag', 'msg'] },
|
|
617
|
-
// MM-DD PID LEVEL/Tag: msg
|
|
618
|
-
{ re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+([VDIWE])\/([^:]+):\s*(.*)$/, groups: ['ts', 'pid', 'level', 'tag', 'msg'] },
|
|
619
|
-
// MM-DD PID LEVEL Tag: msg (space)
|
|
620
|
-
{ re: /^(\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(\d+)\s+([VDIWE])\s+([^:]+):\s*(.*)$/, groups: ['ts', 'pid', 'level', 'tag', 'msg'] },
|
|
621
|
-
// Short form LEVEL/Tag: msg
|
|
622
|
-
{ re: /^([VDIWE])\/([^\(\:]+)(?:\([0-9]+\))?:\s*(.*)$/, groups: ['level', 'tag', 'msg'] },
|
|
623
|
-
// Short form LEVEL Tag: msg
|
|
624
|
-
{ re: /^([VDIWE])\s+([^\(\:]+)(?:\([0-9]+\))?:\s*(.*)$/, groups: ['level', 'tag', 'msg'] },
|
|
625
|
-
];
|
|
626
|
-
for (const p of patterns) {
|
|
627
|
-
const m = normalizedLine.match(p.re);
|
|
628
|
-
if (m) {
|
|
629
|
-
const g = p.groups;
|
|
630
|
-
const vals = {};
|
|
631
|
-
for (let i = 0; i < g.length; i++)
|
|
632
|
-
vals[g[i]] = m[i + 1];
|
|
633
|
-
const ts = vals.ts;
|
|
634
|
-
if (ts) {
|
|
635
|
-
const iso = tryIso(ts);
|
|
636
|
-
if (iso) {
|
|
637
|
-
entry.timestamp = ts;
|
|
638
|
-
entry._iso = iso;
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
entry.timestamp = ts;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
if (vals.level)
|
|
645
|
-
entry.level = vals.level;
|
|
646
|
-
if (vals.tag)
|
|
647
|
-
entry.tag = vals.tag.trim();
|
|
648
|
-
entry.message = vals.msg || entry.message;
|
|
649
|
-
// Crash heuristics
|
|
650
|
-
const msg = (entry.message || '').toString();
|
|
651
|
-
const crash = /FATAL EXCEPTION/i.test(msg) || /\b([A-Za-z0-9_$.]+Exception)\b/.test(msg);
|
|
652
|
-
if (crash) {
|
|
653
|
-
entry.crash = true;
|
|
654
|
-
const exMatch = msg.match(/\b([A-Za-z0-9_$.]+Exception)\b/);
|
|
655
|
-
if (exMatch)
|
|
656
|
-
entry.exception = exMatch[1];
|
|
657
|
-
}
|
|
658
|
-
return entry;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
// No pattern matched: attempt to extract level/tag like '... E/Tag: msg'
|
|
662
|
-
const alt = normalizedLine.match(/([VDIWE])\/([^:]+):\s*(.*)$/);
|
|
663
|
-
if (alt) {
|
|
664
|
-
entry.level = alt[1];
|
|
665
|
-
entry.tag = alt[2].trim();
|
|
666
|
-
entry.message = alt[3];
|
|
667
|
-
const msg = entry.message;
|
|
668
|
-
const crash = /FATAL EXCEPTION/i.test(msg) || /\b([A-Za-z0-9_$.]+Exception)\b/.test(msg);
|
|
669
|
-
if (crash) {
|
|
670
|
-
entry.crash = true;
|
|
671
|
-
const exMatch = msg.match(/\b([A-Za-z0-9_$.]+Exception)\b/);
|
|
672
|
-
if (exMatch)
|
|
673
|
-
entry.exception = exMatch[1];
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
return entry;
|
|
677
|
-
}
|
|
678
|
-
// Legacy readLogStreamLines shim removed. Use AndroidObserve.readLogStream(sessionId, limit, since) instead.
|
|
@@ -16,12 +16,10 @@ export function getConfiguredIdbPath() {
|
|
|
16
16
|
if (fs.existsSync(p)) {
|
|
17
17
|
const raw = fs.readFileSync(p, 'utf8');
|
|
18
18
|
const json = JSON.parse(raw);
|
|
19
|
-
if (json)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return json.IDB_PATH;
|
|
24
|
-
}
|
|
19
|
+
if (json?.idbPath)
|
|
20
|
+
return json.idbPath;
|
|
21
|
+
if (json?.IDB_PATH)
|
|
22
|
+
return json.IDB_PATH;
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
catch { }
|
|
@@ -37,15 +37,7 @@ async function main() {
|
|
|
37
37
|
else {
|
|
38
38
|
console.log('Auto-install enabled (MCP_AUTO_INSTALL_IDB=true or CI=true)');
|
|
39
39
|
}
|
|
40
|
-
const attempts =
|
|
41
|
-
if (commandWhich('pipx'))
|
|
42
|
-
attempts.push({ name: 'pipx', cmd: 'pipx', args: ['install', IDB_PKG] });
|
|
43
|
-
if (commandWhich('pip') || commandWhich('python3'))
|
|
44
|
-
attempts.push({ name: 'pip', cmd: commandWhich('pip') ? 'pip' : 'python3', args: commandWhich('pip') ? ['install', '--user', IDB_PKG] : ['-m', 'pip', 'install', '--user', IDB_PKG] });
|
|
45
|
-
// Add brew as a fallback on macOS if present (best-effort)
|
|
46
|
-
if (process.platform === 'darwin' && commandWhich('brew')) {
|
|
47
|
-
attempts.push({ name: 'brew', cmd: 'brew', args: ['install', 'idb'] });
|
|
48
|
-
}
|
|
40
|
+
const attempts = installerCandidates();
|
|
49
41
|
if (attempts.length === 0) {
|
|
50
42
|
console.error('No installer tool (pipx/pip/brew) detected. Please install pipx or pip and re-run.');
|
|
51
43
|
process.exit(2);
|
|
@@ -80,3 +72,15 @@ if (scriptPath === process.argv[1]) {
|
|
|
80
72
|
main().catch(e => { console.error('Installer failed:', e instanceof Error ? e.message : String(e)); process.exit(2); });
|
|
81
73
|
}
|
|
82
74
|
export { main };
|
|
75
|
+
function installerCandidates() {
|
|
76
|
+
const attempts = [];
|
|
77
|
+
if (commandWhich('pipx'))
|
|
78
|
+
attempts.push({ name: 'pipx', cmd: 'pipx', args: ['install', IDB_PKG] });
|
|
79
|
+
if (commandWhich('pip') || commandWhich('python3'))
|
|
80
|
+
attempts.push({ name: 'pip', cmd: commandWhich('pip') ? 'pip' : 'python3', args: commandWhich('pip') ? ['install', '--user', IDB_PKG] : ['-m', 'pip', 'install', '--user', IDB_PKG] });
|
|
81
|
+
// Add brew as a fallback on macOS if present (best-effort)
|
|
82
|
+
if (process.platform === 'darwin' && commandWhich('brew')) {
|
|
83
|
+
attempts.push({ name: 'brew', cmd: 'brew', args: ['install', 'idb'] });
|
|
84
|
+
}
|
|
85
|
+
return attempts;
|
|
86
|
+
}
|