mobile-debug-mcp 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/.cursor-plugin/marketplace.json +1 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +4 -4
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +218 -129
- package/agent-plugin/.antigravity-plugin/README.md +48 -0
- package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
- 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 +50 -11
- package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
- package/agent-plugin/cursor/LICENSE +201 -0
- package/agent-plugin/cursor/README.md +9 -0
- package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +253 -0
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/mcp_config.json +13 -0
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +62 -17
- 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 +2 -1
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/guard.js +4 -4
- package/dist/debugger/ios-worker.js +62 -0
- package/dist/debugger/ios.js +57 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +133 -74
- package/dist/debugger/worker.js +59 -46
- 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 +77 -3092
- 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 +44 -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 +114 -75
- 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-trace/artifact.js +63 -0
- package/dist/observe/ios-trace/capabilities.js +86 -0
- package/dist/observe/ios-trace/native.js +93 -0
- package/dist/observe/ios-trace/notification.js +35 -0
- package/dist/observe/ios-trace/process.js +98 -0
- package/dist/observe/ios-trace/sessions.js +244 -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 +5 -3
- package/dist/observe/readiness.js +33 -14
- 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-error.js +7 -0
- package/dist/observe/system-trace-router.js +81 -0
- package/dist/observe/system-trace.js +26 -21
- 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 +67 -0
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +77 -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 +170 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +20 -12
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
- package/dist/server/schemas/system-trace-schema.js +22 -0
- package/dist/server/tool-definitions.js +58 -1135
- package/dist/server-core.js +2 -2
- package/dist/server.js +4 -4
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -439
- 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/ios/utils.js +48 -46
- package/dist/utils/java.js +65 -49
- package/dist/utils/resolve-device.js +4 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +67 -1
- package/docs/README.md +38 -0
- package/docs/agent-plugin-installation-verification.md +39 -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 +120 -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/intent.md +3 -2
- package/docs/specs/023-android-system-traces/plan.md +5 -7
- package/docs/specs/023-android-system-traces/spec.md +3 -1
- package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
- package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
- package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
- package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
- 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/specs/031-ios-system-traces/intent.md +38 -0
- package/docs/specs/031-ios-system-traces/investigation.md +92 -0
- package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
- package/docs/specs/031-ios-system-traces/plan.md +138 -0
- package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
- package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
- package/docs/specs/031-ios-system-traces/spec.md +135 -0
- package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
- package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
- package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
- package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
- package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
- package/docs/tools/TOOLS.md +31 -18
- package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
- package/docs/tools/debugger.md +120 -4
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +55 -23
- package/docs/tools/system-traces.md +89 -14
- package/docs/tools/system.md +12 -5
- package/package.json +12 -8
- package/scripts/agent-plugin.mjs +55 -5
- 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 +3 -2
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +10 -5
- package/src/debugger/ios-worker.ts +49 -0
- package/src/debugger/ios.ts +45 -0
- package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
- package/src/debugger/lldb/breakpoints.py +51 -0
- package/src/debugger/lldb/inspection.py +109 -0
- package/src/debugger/lldb/worker.py +203 -0
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +326 -155
- package/src/debugger/worker.ts +57 -41
- 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 +89 -3494
- 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 +67 -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 +122 -70
- 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-trace/artifact.ts +55 -0
- package/src/observe/ios-trace/capabilities.ts +74 -0
- package/src/observe/ios-trace/native.ts +72 -0
- package/src/observe/ios-trace/notification.ts +30 -0
- package/src/observe/ios-trace/process.ts +82 -0
- package/src/observe/ios-trace/sessions.ts +204 -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 +6 -4
- package/src/observe/readiness.ts +42 -21
- 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-error.ts +3 -0
- package/src/observe/system-trace-router.ts +61 -0
- package/src/observe/system-trace.ts +28 -19
- 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 +58 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +86 -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 +170 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/schemas/debugger-schema.ts +46 -0
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
- package/src/server/schemas/system-trace-schema.ts +23 -0
- package/src/server/tool-definitions.ts +67 -1143
- package/src/server-core.ts +2 -2
- package/src/server.ts +4 -4
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -525
- 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/ios/utils.ts +60 -61
- package/src/utils/java.ts +62 -43
- package/src/utils/resolve-device.ts +9 -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 +19 -3
- package/test/device/automated/debugger/runtime.ios.test.ts +221 -0
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
- package/test/device/automated/observe/system-trace.smoke.ts +1 -1
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3696 -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 +21 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/README.md +52 -0
- package/test/fixtures/debugger-app/build.ts +25 -15
- package/test/fixtures/ios-debugger-app/Main.swift +95 -0
- package/test/fixtures/ios-debugger-app/README.md +27 -0
- package/test/fixtures/ios-debugger-app/build.ts +21 -0
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/helpers/ios-trace-shutdown.ts +29 -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 +4 -0
- package/test/unit/debugger/handle-guards.test.ts +88 -0
- package/test/unit/debugger/ios-preparation.test.ts +57 -0
- package/test/unit/debugger/ios.test.ts +104 -0
- package/test/unit/debugger/lldb-worker.py +166 -0
- package/test/unit/debugger/lldb-worker.test.ts +10 -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 +1 -1
- 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/ios-trace-native.test.ts +90 -0
- package/test/unit/observe/ios-trace-sessions.test.ts +131 -0
- package/test/unit/observe/readiness.test.ts +1 -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-router.test.ts +49 -0
- package/test/unit/quality/agent-plugin-export.test.ts +38 -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 +12 -3
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/debugger-handlers.js +0 -38
- package/dist/server/system-trace-schema.js +0 -17
- package/dist/server/tool-handlers.js +0 -1000
- 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/debugger-handlers.ts +0 -29
- package/src/server/debugger-schema.ts +0 -37
- package/src/server/system-trace-schema.ts +0 -17
- package/src/server/tool-handlers.ts +0 -1008
- /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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { payloadOf } from './payload.js';
|
|
2
|
+
import { correlatedHandler } from './correlation.js';
|
|
3
|
+
import { interactionControls } from '../../contracts/execution-controls.js';
|
|
4
|
+
import { currentEvidenceActionId, markEvidenceVerification } from '../../observe/evidence.js';
|
|
5
|
+
import { measure, withTiming } from '../../utils/timing.js';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { runWithBudget } from '../../utils/operation-budget.js';
|
|
8
|
+
import { validateExpectedState } from '../schemas/condition-schema.js';
|
|
9
|
+
import { conditionFailureCode, createConditionalAction, withConditionalAction } from '../../interact/conditional-action.js';
|
|
10
|
+
import { resolveTargetDevice, withResolvedDevice } from '../../utils/resolve-device.js';
|
|
11
|
+
import { ToolsObserve } from '../../observe/index.js';
|
|
12
|
+
import { recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js';
|
|
13
|
+
import { buildActionExecutionResult, wrapResponse, wrapToolError } from '../common.js';
|
|
14
|
+
export function compactAction(result, verificationMode, started = true, stateDelta, totalMs = 0) {
|
|
15
|
+
return {
|
|
16
|
+
action_id: result.action_id,
|
|
17
|
+
action_type: result.action_type,
|
|
18
|
+
success: !!result.success,
|
|
19
|
+
lifecycle_state: result.success ? (verificationMode === 'full' ? result.lifecycle_state : 'pending_verification') : 'failed',
|
|
20
|
+
...(result.failure_code ? { failure_code: result.failure_code } : {}),
|
|
21
|
+
...(typeof result.retryable === 'boolean' ? { retryable: result.retryable } : {}),
|
|
22
|
+
delivery_status: result.success ? 'delivered' : (started ? 'unknown' : 'not_delivered'),
|
|
23
|
+
dispatch_started: started,
|
|
24
|
+
verification: { mode: verificationMode, status: result.success ? (verificationMode === 'none' ? 'not_requested' : verificationMode === 'light' ? 'observed' : (result.lifecycle_state === 'verified' ? 'verified' : 'observed')) : 'failed' },
|
|
25
|
+
...(stateDelta ? { state_delta: stateDelta } : {}),
|
|
26
|
+
timing: { total_ms: totalMs }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createAction(type, args, timing) {
|
|
30
|
+
const started = performance.now();
|
|
31
|
+
const controls = interactionControls(args);
|
|
32
|
+
const expected = validateExpectedState(type, args);
|
|
33
|
+
const condition = expected ? createConditionalAction(args.platform, args.deviceId, expected) : undefined;
|
|
34
|
+
const budgetMs = controls.actionTimeoutMs ?? ({ start_app: 10000, restart_app: 15000, scroll_to_element: 10000 }[type] ?? 5000);
|
|
35
|
+
const unbounded = args._journey !== true && controls.verificationMode === 'full' && controls.responseMode === 'debug' && controls.actionTimeoutMs === undefined;
|
|
36
|
+
return {
|
|
37
|
+
type, args, timing, started, controls, condition, budgetMs, unbounded,
|
|
38
|
+
result: {}, dispatchStarted: false, blocked: false,
|
|
39
|
+
delta: undefined, observationAvailable: false
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function dispatchAction(s, execute) {
|
|
43
|
+
const { type, args, controls, condition, budgetMs } = s;
|
|
44
|
+
const dispatchWithBudget = (timeoutMs) => runWithBudget(timeoutMs, async (budget) => {
|
|
45
|
+
try {
|
|
46
|
+
if (!condition)
|
|
47
|
+
return payloadOf(await execute(args));
|
|
48
|
+
return await withConditionalAction(condition, async () => {
|
|
49
|
+
const device = await resolveTargetDevice({ platform: condition.platform, deviceId: condition.deviceId });
|
|
50
|
+
return withResolvedDevice(device, async () => payloadOf(await execute(args)));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
s.dispatchStarted = budget.dispatchStarted;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
// Omitted controls retain the existing adapter budgets.
|
|
59
|
+
s.result = s.unbounded
|
|
60
|
+
? await dispatchWithBudget(Infinity) : await dispatchWithBudget(budgetMs);
|
|
61
|
+
if (s.result.success)
|
|
62
|
+
s.dispatchStarted = true;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (!condition && controls.verificationMode === 'full' && controls.responseMode === 'debug' && controls.actionTimeoutMs === undefined && !/TIMEOUT|timed out/i.test(String(error)))
|
|
66
|
+
return wrapToolError(type, error);
|
|
67
|
+
s.result = { success: false, failure_code: /TIMEOUT|timed out/i.test(String(error)) ? 'ACTION_TIMEOUT' : 'UNKNOWN', error: String(error) };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function rejectCondition(s) {
|
|
71
|
+
const { condition, type } = s;
|
|
72
|
+
s.blocked = !!condition && ['mismatch', 'unavailable'].includes(condition.result.status);
|
|
73
|
+
if (s.blocked && condition) {
|
|
74
|
+
s.dispatchStarted = false;
|
|
75
|
+
s.result = buildActionExecutionResult({
|
|
76
|
+
actionType: type, sourceModule: 'server', device: condition.result.observed?.device,
|
|
77
|
+
selector: s.result?.target?.selector ?? null, resolved: s.result?.target?.resolved,
|
|
78
|
+
success: false, uiFingerprintBefore: s.result?.ui_fingerprint_before ?? null, uiFingerprintAfter: null,
|
|
79
|
+
failure: { failureCode: conditionFailureCode(condition), retryable: false },
|
|
80
|
+
traceSteps: [{ stage: 'verify', timestamp: Date.now(), result: 'failure', attempt_index: 0, metadata: { phase: 'precondition', dispatch_started: false } }]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function normalizeAction(s) {
|
|
85
|
+
const { type, args, timing, controls } = s;
|
|
86
|
+
rejectCondition(s);
|
|
87
|
+
if (s.result.failure_code === 'TIMEOUT' || /ACTION_TIMEOUT|timeout/i.test(String(s.result.error ?? s.result.reason ?? s.result.details?.error ?? '')))
|
|
88
|
+
s.result.failure_code = 'ACTION_TIMEOUT';
|
|
89
|
+
if (s.result.failure_code === 'ACTION_TIMEOUT')
|
|
90
|
+
s.result.retryable = !s.dispatchStarted;
|
|
91
|
+
if (!s.result.success && typeof s.result.retryable !== 'boolean')
|
|
92
|
+
s.result.retryable = !s.dispatchStarted;
|
|
93
|
+
s.result.action_id ??= currentEvidenceActionId() ?? randomUUID();
|
|
94
|
+
s.result.action_type ??= type;
|
|
95
|
+
recordObservationEvent({
|
|
96
|
+
step_id: args._stepId,
|
|
97
|
+
action_type: type,
|
|
98
|
+
dispatch_ms: timing.dispatch_ms,
|
|
99
|
+
verification_mode: controls.verificationMode,
|
|
100
|
+
dispatch_started: s.dispatchStarted
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async function verifyAction(s) {
|
|
104
|
+
const { args, controls } = s;
|
|
105
|
+
if (s.result.success && controls.verificationMode === 'light') {
|
|
106
|
+
const verificationStarted = performance.now();
|
|
107
|
+
const tree = await withObservationContext({ stepId: args._stepId, purpose: 'verification' }, () => measure('post_observation_ms', () => runWithBudget(controls.verificationTimeoutMs, () => ToolsObserve.getUITreeHandler({ platform: args.platform ?? s.result.device?.platform, deviceId: args.deviceId ?? s.result.device?.id })))).catch(() => null);
|
|
108
|
+
markEvidenceVerification();
|
|
109
|
+
s.observationAvailable = !!tree && !tree.error;
|
|
110
|
+
recordObservationEvent({
|
|
111
|
+
verification_budget_ms: controls.verificationTimeoutMs,
|
|
112
|
+
verification_elapsed_ms: performance.now() - verificationStarted,
|
|
113
|
+
verification_outcome: s.observationAvailable ? 'observed' : 'unavailable',
|
|
114
|
+
verification_budget_expired: !s.observationAvailable && performance.now() - verificationStarted >= controls.verificationTimeoutMs
|
|
115
|
+
});
|
|
116
|
+
if (s.observationAvailable && tree?.device)
|
|
117
|
+
s.delta = ToolsObserve.getLatestStateDelta(tree.device.platform, tree.device.id) ?? undefined;
|
|
118
|
+
}
|
|
119
|
+
if (!s.blocked && controls.verificationMode === 'full' && s.dispatchStarted)
|
|
120
|
+
markEvidenceVerification();
|
|
121
|
+
}
|
|
122
|
+
function actionResponse(s) {
|
|
123
|
+
const { timing, started, controls, condition, budgetMs } = s;
|
|
124
|
+
const compact = compactAction(s.result, controls.verificationMode, s.dispatchStarted, s.delta, performance.now() - started);
|
|
125
|
+
if (s.blocked)
|
|
126
|
+
compact.verification.status = 'not_requested';
|
|
127
|
+
if (s.result.success && controls.verificationMode === 'light' && !s.observationAvailable)
|
|
128
|
+
compact.verification.status = 'unavailable';
|
|
129
|
+
const evidence = {
|
|
130
|
+
...compact,
|
|
131
|
+
...(condition ? {
|
|
132
|
+
condition: controls.responseMode === 'compact' && condition.result.status === 'matched'
|
|
133
|
+
? {
|
|
134
|
+
...condition.result, observed: condition.result.observed ? {
|
|
135
|
+
snapshot_session_id: condition.result.observed.snapshot_session_id,
|
|
136
|
+
snapshot_revision: condition.result.observed.snapshot_revision,
|
|
137
|
+
captured_at_ms: condition.result.observed.captured_at_ms
|
|
138
|
+
} : null
|
|
139
|
+
} : condition.result
|
|
140
|
+
} : {}),
|
|
141
|
+
...(controls.verificationMode === 'light' ? { state_delta_available: !!s.delta } : {}),
|
|
142
|
+
...(s.result.success && controls.verificationMode === 'light' && !s.observationAvailable ? { verification_diagnostic: { code: 'VERIFICATION_UNAVAILABLE', retryable: true } } : {})
|
|
143
|
+
};
|
|
144
|
+
return wrapResponse(controls.responseMode === 'compact' ? evidence : {
|
|
145
|
+
...s.result, ...evidence, timing: { ...timing, total_ms: performance.now() - started },
|
|
146
|
+
effective_controls: { ...controls, actionTimeoutMs: s.unbounded ? null : budgetMs, timeout_bound: !(s.unbounded) }
|
|
147
|
+
}, controls.responseMode === 'debug');
|
|
148
|
+
}
|
|
149
|
+
export function actionHandler(type, execute) {
|
|
150
|
+
return correlatedHandler(type, async (args) => {
|
|
151
|
+
const timing = {};
|
|
152
|
+
return withTiming(timing, async () => {
|
|
153
|
+
const state = createAction(type, args, timing);
|
|
154
|
+
const error = await dispatchAction(state, execute);
|
|
155
|
+
if (error)
|
|
156
|
+
return error;
|
|
157
|
+
normalizeAction(state);
|
|
158
|
+
await verifyAction(state);
|
|
159
|
+
return actionResponse(state);
|
|
160
|
+
});
|
|
161
|
+
}, true);
|
|
162
|
+
}
|
|
163
|
+
export { interactionControls, correlatedHandler, payloadOf };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { TraceError } from '../../observe/system-trace.js';
|
|
2
|
+
import { withEvidence, validateActionRef } from '../../observe/evidence.js';
|
|
3
|
+
import { wrapResponse, wrapToolError } from '../common.js';
|
|
4
|
+
export function correlatedHandler(name, handler, action = false, journey = false) {
|
|
5
|
+
return async (args) => {
|
|
6
|
+
const ref = validateActionRef(args.actionRef);
|
|
7
|
+
const { result, evidence } = await withEvidence({ action, journey, ref }, async () => {
|
|
8
|
+
try {
|
|
9
|
+
return await handler(args);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return error instanceof TraceError ? { ...wrapResponse({ error: { tool: name, code: error.code, message: error.message } }), isError: true } : wrapToolError(name, error);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const item = result.content.find(item => item.type === 'text');
|
|
16
|
+
if (item?.text) {
|
|
17
|
+
const payload = JSON.parse(item.text);
|
|
18
|
+
payload.evidence = evidence;
|
|
19
|
+
item.text = JSON.stringify(payload);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { iosCapabilities } from '../../debugger/ios-worker.js';
|
|
2
|
+
import { debuggerResponse } from './debugger-response.js';
|
|
3
|
+
import { classify } from '../../debugger/diagnostics.js';
|
|
4
|
+
import { debuggerContracts } from '../schemas/debugger-schema.js';
|
|
5
|
+
import { debuggerSessions, iosDebuggerSessions, debuggerCapabilities } from '../../debugger/sessions.js';
|
|
6
|
+
import { DebuggerError, withDebuggerBudget } from '../../debugger/worker.js';
|
|
7
|
+
import { wrapResponse } from '../common.js';
|
|
8
|
+
function failure(code, summary = {}, diagnostic, command = '', mode = 'debug') {
|
|
9
|
+
return {
|
|
10
|
+
...wrapResponse(debuggerResponse(command, mode, {
|
|
11
|
+
debug_session_id: null, device: null, target: null, state: null,
|
|
12
|
+
captured_at_ms: Date.now(), capabilities: debuggerCapabilities, limitations: [], cleanup: null,
|
|
13
|
+
...summary, success: false, error: { code, message: code, retryable: false, ...(diagnostic ? { diagnostic } : {}) }
|
|
14
|
+
})), isError: true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function handleDebugger(name, args, signal) {
|
|
18
|
+
const parsed = debuggerContracts[name].schema.safeParse(args);
|
|
19
|
+
if (!parsed.success)
|
|
20
|
+
return failure('INVALID_ARGUMENT');
|
|
21
|
+
const { responseMode, ...input } = parsed.data;
|
|
22
|
+
const a = input;
|
|
23
|
+
const command = debuggerContracts[name].command;
|
|
24
|
+
const sessions = selectSessions(a);
|
|
25
|
+
try {
|
|
26
|
+
if (signal?.aborted)
|
|
27
|
+
throw new DebuggerError('DEBUGGER_CANCELLED');
|
|
28
|
+
const result = await withDebuggerBudget(a.timeoutMs, signal, async () => command === 'capabilities' ? await sessions.capabilities(a.deviceId, a.appId, a.processId) :
|
|
29
|
+
command === 'attach' ? await sessions.attach(a.deviceId, a.appId, a.processId, a.timeoutMs) :
|
|
30
|
+
await sessions.command(a.debugSessionId, command, { ...a, signal: command === 'events' ? signal : undefined }, a.timeoutMs));
|
|
31
|
+
if (signal?.aborted && command === 'capabilities')
|
|
32
|
+
throw new DebuggerError('DEBUGGER_CANCELLED');
|
|
33
|
+
const payload = sessions === iosDebuggerSessions ? iosDiagnostics(result) : result;
|
|
34
|
+
return { ...wrapResponse(debuggerResponse(command, responseMode, payload)), ...(!result.success ? { isError: true } : {}) };
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
const error = classify(e, 'unknown', 'DEBUGGER_WORKER_FAILED');
|
|
38
|
+
const code = error.code;
|
|
39
|
+
let summary = {};
|
|
40
|
+
if (a.debugSessionId) {
|
|
41
|
+
try {
|
|
42
|
+
summary = sessions.summary(a.debugSessionId);
|
|
43
|
+
}
|
|
44
|
+
catch { /* Unknown session */ }
|
|
45
|
+
}
|
|
46
|
+
const response = failure(code, {
|
|
47
|
+
...(sessions === iosDebuggerSessions ? { capabilities: iosCapabilities } : {}),
|
|
48
|
+
debug_session_id: a.debugSessionId ?? null,
|
|
49
|
+
device: a.deviceId ? { platform: a.platform ?? 'android', id: a.deviceId } : null, ...summary
|
|
50
|
+
}, code === 'DEBUGGER_CLEANUP_FAILED' ? summary.error?.diagnostic : error.diagnostic, command, responseMode);
|
|
51
|
+
if (sessions === iosDebuggerSessions)
|
|
52
|
+
response.content[0].text = JSON.stringify(iosDiagnostics(JSON.parse(response.content[0].text)));
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function selectSessions(a) {
|
|
57
|
+
return a.platform === 'ios' || (a.debugSessionId && iosDebuggerSessions.owns(a.debugSessionId)) ? iosDebuggerSessions : debuggerSessions;
|
|
58
|
+
}
|
|
59
|
+
function iosRecovery(d) {
|
|
60
|
+
return { ...d, recovery: d.recovery.replace('JDK 17+ availability, including java, javac and jdk.jdi.', 'Xcode and its Swift-enabled LLDB/Python availability.').replace('JDK compiler and worker source/cache', 'LLDB worker source').replace('Java worker', 'LLDB worker').replace('ADB server', 'simulator') };
|
|
61
|
+
}
|
|
62
|
+
function iosDiagnostics(value) {
|
|
63
|
+
const result = { ...value };
|
|
64
|
+
const error = value.error;
|
|
65
|
+
return { ...result, ...(error?.diagnostic ? { error: { ...error, diagnostic: iosRecovery(error.diagnostic) } } : {}),
|
|
66
|
+
...(Array.isArray(value.diagnostics) ? { diagnostics: value.diagnostics.map(iosRecovery) } : {}) };
|
|
67
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Omit only repeated target metadata; preserve all execution and failure evidence. */
|
|
2
|
+
export function debuggerResponse(command, mode, payload) {
|
|
3
|
+
if (mode !== 'compact' || ['capabilities', 'attach', 'state'].includes(command) || payload.debug_session_id == null)
|
|
4
|
+
return payload;
|
|
5
|
+
const result = { ...payload };
|
|
6
|
+
for (const field of ['device', 'target', 'capabilities'])
|
|
7
|
+
delete result[field];
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { systemTraces, TraceError } from '../../observe/system-trace.js';
|
|
2
|
+
import { traceTargetSchema, traceStartSchema, traceControlSchema } from '../schemas/system-trace-schema.js';
|
|
3
|
+
import { resolveTargetDevice } from '../../utils/resolve-device.js';
|
|
4
|
+
import { ToolsManage } from '../../manage/index.js';
|
|
5
|
+
import { classifyActionOutcome } from '../../interact/classify.js';
|
|
6
|
+
import { ToolsNetwork } from '../../observe/network/index.js';
|
|
7
|
+
import { getSystemStatus } from '../../system/index.js';
|
|
8
|
+
import { getArrayArg, getBooleanArg, getStringArg, requireBooleanArg, requireStringArg, wrapResponse } from '../common.js';
|
|
9
|
+
export async function handleListDevices(args) {
|
|
10
|
+
const platform = getStringArg(args, 'platform');
|
|
11
|
+
const appId = getStringArg(args, 'appId');
|
|
12
|
+
const res = await ToolsManage.listDevicesHandler({ platform, appId });
|
|
13
|
+
return wrapResponse(res);
|
|
14
|
+
}
|
|
15
|
+
export async function handleGetSystemStatus() {
|
|
16
|
+
const result = await getSystemStatus();
|
|
17
|
+
return wrapResponse(result);
|
|
18
|
+
}
|
|
19
|
+
export function handleClassifyActionOutcome(args) {
|
|
20
|
+
const uiChanged = requireBooleanArg(args, 'uiChanged');
|
|
21
|
+
const expectedElementVisible = getBooleanArg(args, 'expectedElementVisible');
|
|
22
|
+
const actionType = getStringArg(args, 'actionType');
|
|
23
|
+
const networkRequests = getArrayArg(args, 'networkRequests');
|
|
24
|
+
const hasLogErrors = getBooleanArg(args, 'hasLogErrors');
|
|
25
|
+
const result = classifyActionOutcome({
|
|
26
|
+
uiChanged,
|
|
27
|
+
expectedElementVisible: expectedElementVisible ?? null,
|
|
28
|
+
actionType: actionType ?? null,
|
|
29
|
+
networkRequests: networkRequests ?? null,
|
|
30
|
+
hasLogErrors: hasLogErrors ?? null
|
|
31
|
+
});
|
|
32
|
+
return Promise.resolve(wrapResponse(result));
|
|
33
|
+
}
|
|
34
|
+
export async function handleGetNetworkActivity(args) {
|
|
35
|
+
const platform = requireStringArg(args, 'platform');
|
|
36
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
37
|
+
const result = await ToolsNetwork.getNetworkActivity({ platform, deviceId });
|
|
38
|
+
return wrapResponse(result);
|
|
39
|
+
}
|
|
40
|
+
export async function handleSystemTrace(name, args) {
|
|
41
|
+
const schema = name === 'start_system_trace' ? traceStartSchema : name === 'get_system_trace_capabilities' ? traceTargetSchema : traceControlSchema;
|
|
42
|
+
const parsed = schema.safeParse(args);
|
|
43
|
+
if (!parsed.success)
|
|
44
|
+
throw new TraceError('INVALID_ARGUMENT');
|
|
45
|
+
if (name === 'get_system_trace_capabilities' || name === 'start_system_trace') {
|
|
46
|
+
const target = parsed.data;
|
|
47
|
+
if (target.platform === 'android' && (target.appId !== undefined || target.template !== undefined))
|
|
48
|
+
throw new TraceError('INVALID_ARGUMENT');
|
|
49
|
+
if (target.platform === 'ios') {
|
|
50
|
+
return handleIosTrace(name, target);
|
|
51
|
+
}
|
|
52
|
+
let device;
|
|
53
|
+
try {
|
|
54
|
+
device = await resolveTargetDevice(target);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
if (name === 'start_system_trace')
|
|
58
|
+
throw new TraceError('TRACE_UNSUPPORTED');
|
|
59
|
+
return wrapResponse({ supported: false, backend: 'perfetto', device: null, prerequisites: { device: false }, reasons: ['DEVICE_UNAVAILABLE'], enabled_sources: [], unavailable_categories: [], limitations: ['An authorised Android device is required'] });
|
|
60
|
+
}
|
|
61
|
+
if (name === 'get_system_trace_capabilities')
|
|
62
|
+
return wrapResponse(await systemTraces.capabilities(device.id));
|
|
63
|
+
const result = await systemTraces.start(device.id, target.durationMs);
|
|
64
|
+
return { ...wrapResponse(result), ...(result.state === 'failed' ? { isError: true } : {}) };
|
|
65
|
+
}
|
|
66
|
+
const operation = name === 'stop_system_trace' ? 'stop' : name === 'cancel_system_trace' ? 'cancel' : 'status';
|
|
67
|
+
const result = await systemTraces.control(parsed.data.traceId, operation);
|
|
68
|
+
return { ...wrapResponse(result), ...(['failed', 'cancelled'].includes(result.state) ? { isError: true } : {}) };
|
|
69
|
+
}
|
|
70
|
+
async function handleIosTrace(name, target) {
|
|
71
|
+
if (!target.deviceId || (name === 'start_system_trace' && !target.appId))
|
|
72
|
+
throw new TraceError('INVALID_ARGUMENT');
|
|
73
|
+
if (name === 'get_system_trace_capabilities')
|
|
74
|
+
return wrapResponse(await systemTraces.iosCapabilities(target.deviceId, target.appId, target.template));
|
|
75
|
+
const result = await systemTraces.start(target.deviceId, target.durationMs, { appId: target.appId, template: target.template });
|
|
76
|
+
return { ...wrapResponse(result), ...(result.state === 'failed' ? { isError: true } : {}) };
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { measure } from '../../utils/timing.js';
|
|
2
|
+
import { runWithBudget } from '../../utils/operation-budget.js';
|
|
3
|
+
import { tapElementControlsSchema } from '../schemas/journey-schema.js';
|
|
4
|
+
import { notifyActionStart } from '../../interact/conditional-action.js';
|
|
5
|
+
import { ToolsInteract } from '../../interact/index.js';
|
|
6
|
+
import { recordObservationConsumer, recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js';
|
|
7
|
+
import { buildActionExecutionResult, captureActionFingerprint, getNumberArg, getStringArg, inferGenericFailure, inferScrollFailure, requireNumberArg, requireObjectArg, requireStringArg, wrapResponse } from '../common.js';
|
|
8
|
+
import { interactionControls, actionHandler } from './action-execution.js';
|
|
9
|
+
export async function executeTap(args) {
|
|
10
|
+
const platform = getStringArg(args, 'platform');
|
|
11
|
+
const x = requireNumberArg(args, 'x');
|
|
12
|
+
const y = requireNumberArg(args, 'y');
|
|
13
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
14
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args);
|
|
15
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
|
|
16
|
+
notifyActionStart();
|
|
17
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.tapHandler({ platform, x, y, deviceId, timeoutMs: actionTimeoutMs }));
|
|
18
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
|
|
19
|
+
const result = buildActionExecutionResult({
|
|
20
|
+
actionType: 'tap',
|
|
21
|
+
sourceModule: 'server',
|
|
22
|
+
selector: { x, y },
|
|
23
|
+
success: !!res.success,
|
|
24
|
+
uiFingerprintBefore,
|
|
25
|
+
uiFingerprintAfter,
|
|
26
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
27
|
+
});
|
|
28
|
+
return wrapResponse(result);
|
|
29
|
+
}
|
|
30
|
+
export async function executeTapElement(args) {
|
|
31
|
+
const controls = tapElementControlsSchema.safeParse({ elementId: args.elementId, selector: args.selector, waitFor: args.waitFor });
|
|
32
|
+
if (!controls.success)
|
|
33
|
+
throw new Error(`INVALID_ARGUMENT: ${controls.error.message}`);
|
|
34
|
+
const elementId = controls.data.elementId;
|
|
35
|
+
const selector = controls.data.selector;
|
|
36
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args);
|
|
37
|
+
let resolvedId = elementId;
|
|
38
|
+
let freshTree;
|
|
39
|
+
if (selector) {
|
|
40
|
+
const waitFor = controls.data.waitFor;
|
|
41
|
+
const targetStarted = performance.now();
|
|
42
|
+
const resolutionTimeoutMs = waitFor?.timeoutMs ?? actionTimeoutMs ?? 5000;
|
|
43
|
+
const resolution = (await withObservationContext({ stepId: args._stepId, purpose: 'target' }, () => measure('target_resolution_ms', () => runWithBudget(resolutionTimeoutMs, () => ToolsInteract.waitForUIHandler({ selector, condition: waitFor?.condition ?? 'clickable', timeout_ms: resolutionTimeoutMs, poll_interval_ms: waitFor?.pollIntervalMs ?? 100, match: waitFor?.match, singleObservation: true, singleAttempt: !waitFor, rejectAmbiguous: !waitFor, resolveTapTarget: true, platform: getStringArg(args, 'platform'), deviceId: getStringArg(args, 'deviceId') })))));
|
|
44
|
+
recordObservationEvent({ step_id: args._stepId, purpose: 'target', target_resolution_ms: performance.now() - targetStarted, target_resolution_outcome: resolution.status });
|
|
45
|
+
if (resolution.status !== 'success' || !resolution.element?.elementId)
|
|
46
|
+
return wrapResponse({ success: false, action_type: 'tap_element', failure_code: resolution.error?.code ?? 'ELEMENT_NOT_FOUND', retryable: true });
|
|
47
|
+
resolvedId = resolution.element.elementId;
|
|
48
|
+
freshTree = resolution._tree;
|
|
49
|
+
}
|
|
50
|
+
notifyActionStart();
|
|
51
|
+
if (freshTree)
|
|
52
|
+
recordObservationConsumer(freshTree, 'target', args._stepId);
|
|
53
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.tapElementHandler({ elementId: resolvedId, freshTree, verificationMode, timeoutMs: actionTimeoutMs, platform: getStringArg(args, 'platform'), deviceId: getStringArg(args, 'deviceId') }));
|
|
54
|
+
return wrapResponse(res);
|
|
55
|
+
}
|
|
56
|
+
export async function executeSwipe(args) {
|
|
57
|
+
const platform = getStringArg(args, 'platform') ?? 'android';
|
|
58
|
+
const x1 = requireNumberArg(args, 'x1');
|
|
59
|
+
const y1 = requireNumberArg(args, 'y1');
|
|
60
|
+
const x2 = requireNumberArg(args, 'x2');
|
|
61
|
+
const y2 = requireNumberArg(args, 'y2');
|
|
62
|
+
const duration = requireNumberArg(args, 'duration');
|
|
63
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
64
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args);
|
|
65
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
|
|
66
|
+
notifyActionStart();
|
|
67
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId, timeoutMs: actionTimeoutMs }));
|
|
68
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
|
|
69
|
+
const result = buildActionExecutionResult({
|
|
70
|
+
actionType: 'swipe',
|
|
71
|
+
sourceModule: 'server',
|
|
72
|
+
selector: { x1, y1, x2, y2, duration },
|
|
73
|
+
success: !!res.success,
|
|
74
|
+
uiFingerprintBefore,
|
|
75
|
+
uiFingerprintAfter,
|
|
76
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
77
|
+
});
|
|
78
|
+
return wrapResponse(result);
|
|
79
|
+
}
|
|
80
|
+
export async function executeScrollToElement(args) {
|
|
81
|
+
const platform = requireStringArg(args, 'platform');
|
|
82
|
+
const selector = requireObjectArg(args, 'selector');
|
|
83
|
+
const direction = getStringArg(args, 'direction');
|
|
84
|
+
const maxScrolls = getNumberArg(args, 'maxScrolls');
|
|
85
|
+
const scrollAmount = getNumberArg(args, 'scrollAmount');
|
|
86
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
87
|
+
const { verificationMode } = interactionControls(args);
|
|
88
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
|
|
89
|
+
notifyActionStart();
|
|
90
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId }));
|
|
91
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null;
|
|
92
|
+
const result = buildActionExecutionResult({
|
|
93
|
+
actionType: 'scroll_to_element',
|
|
94
|
+
sourceModule: 'server',
|
|
95
|
+
selector: selector ?? null,
|
|
96
|
+
resolved: res?.success && res?.element ? {
|
|
97
|
+
elementId: null,
|
|
98
|
+
text: res.element.text ?? null,
|
|
99
|
+
resource_id: res.element.resourceId ?? null,
|
|
100
|
+
accessibility_id: res.element.contentDesc ?? null,
|
|
101
|
+
class: res.element.className ?? null,
|
|
102
|
+
bounds: res.element.bounds ?? null,
|
|
103
|
+
index: null
|
|
104
|
+
} : null,
|
|
105
|
+
success: !!res.success,
|
|
106
|
+
uiFingerprintBefore,
|
|
107
|
+
uiFingerprintAfter,
|
|
108
|
+
failure: res.success ? undefined : inferScrollFailure(res.reason)
|
|
109
|
+
});
|
|
110
|
+
return wrapResponse(result);
|
|
111
|
+
}
|
|
112
|
+
export async function executeTypeText(args) {
|
|
113
|
+
const text = requireStringArg(args, 'text');
|
|
114
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
115
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args);
|
|
116
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
|
|
117
|
+
notifyActionStart();
|
|
118
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.typeTextHandler({ text, deviceId, timeoutMs: actionTimeoutMs }));
|
|
119
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
|
|
120
|
+
const result = buildActionExecutionResult({
|
|
121
|
+
actionType: 'type_text',
|
|
122
|
+
sourceModule: 'server',
|
|
123
|
+
selector: { text },
|
|
124
|
+
success: !!res.success,
|
|
125
|
+
uiFingerprintBefore,
|
|
126
|
+
uiFingerprintAfter,
|
|
127
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
128
|
+
});
|
|
129
|
+
return wrapResponse(result);
|
|
130
|
+
}
|
|
131
|
+
export async function executePressBack(args) {
|
|
132
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
133
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args);
|
|
134
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
|
|
135
|
+
notifyActionStart();
|
|
136
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.pressBackHandler({ deviceId, timeoutMs: actionTimeoutMs }));
|
|
137
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint('android', deviceId)) : null;
|
|
138
|
+
const result = buildActionExecutionResult({
|
|
139
|
+
actionType: 'press_back',
|
|
140
|
+
sourceModule: 'server',
|
|
141
|
+
selector: { key: 'back' },
|
|
142
|
+
success: !!res.success,
|
|
143
|
+
uiFingerprintBefore,
|
|
144
|
+
uiFingerprintAfter,
|
|
145
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
146
|
+
});
|
|
147
|
+
return wrapResponse(result);
|
|
148
|
+
}
|
|
149
|
+
export const handleTap = actionHandler('tap', executeTap);
|
|
150
|
+
export const handleTapElement = actionHandler('tap_element', executeTapElement);
|
|
151
|
+
export const handleSwipe = actionHandler('swipe', executeSwipe);
|
|
152
|
+
export const handleScrollToElement = actionHandler('scroll_to_element', executeScrollToElement);
|
|
153
|
+
export const handleTypeText = actionHandler('type_text', executeTypeText);
|
|
154
|
+
export const handlePressBack = actionHandler('press_back', executePressBack);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { markEvidenceVerification } from '../../observe/evidence.js';
|
|
2
|
+
import { withReadyWait } from '../../observe/readiness-service.js';
|
|
3
|
+
import { ToolsInteract } from '../../interact/index.js';
|
|
4
|
+
import { recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js';
|
|
5
|
+
import { getBooleanArg, getNumberArg, getObjectArg, getStringArg, requireNumberArg, requireObjectArg, requireStringArg, wrapResponse } from '../common.js';
|
|
6
|
+
export async function handleWaitForScreenChange(args) {
|
|
7
|
+
const platform = getStringArg(args, 'platform');
|
|
8
|
+
const previousFingerprint = requireStringArg(args, 'previousFingerprint');
|
|
9
|
+
const timeoutMs = getNumberArg(args, 'timeoutMs');
|
|
10
|
+
const pollIntervalMs = getNumberArg(args, 'pollIntervalMs');
|
|
11
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
12
|
+
const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId });
|
|
13
|
+
return wrapResponse(res);
|
|
14
|
+
}
|
|
15
|
+
export async function handleExpectScreen(args) {
|
|
16
|
+
const platform = getStringArg(args, 'platform');
|
|
17
|
+
const fingerprint = getStringArg(args, 'fingerprint');
|
|
18
|
+
const screen = getStringArg(args, 'screen');
|
|
19
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
20
|
+
const res = await ToolsInteract.expectScreenHandler({ platform, fingerprint, screen, deviceId });
|
|
21
|
+
return wrapResponse(res);
|
|
22
|
+
}
|
|
23
|
+
export async function handleExpectElementVisible(args) {
|
|
24
|
+
const selector = requireObjectArg(args, 'selector');
|
|
25
|
+
const element_id = getStringArg(args, 'element_id');
|
|
26
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms');
|
|
27
|
+
const poll_interval_ms = getNumberArg(args, 'poll_interval_ms');
|
|
28
|
+
const platform = getStringArg(args, 'platform');
|
|
29
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
30
|
+
const res = await ToolsInteract.expectElementVisibleHandler({ selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId });
|
|
31
|
+
return wrapResponse(res);
|
|
32
|
+
}
|
|
33
|
+
export async function handleExpectState(args) {
|
|
34
|
+
const selector = getObjectArg(args, 'selector');
|
|
35
|
+
const element_id = getStringArg(args, 'element_id');
|
|
36
|
+
const property = requireStringArg(args, 'property');
|
|
37
|
+
const platform = getStringArg(args, 'platform');
|
|
38
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
39
|
+
if (!selector && !element_id) {
|
|
40
|
+
throw new Error('Missing selector or element_id argument');
|
|
41
|
+
}
|
|
42
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'expected')) {
|
|
43
|
+
throw new Error('Missing expected argument');
|
|
44
|
+
}
|
|
45
|
+
const expected = args.expected;
|
|
46
|
+
const res = await ToolsInteract.expectStateHandler({ selector: selector ?? undefined, element_id: element_id ?? undefined, property, expected, platform, deviceId });
|
|
47
|
+
return wrapResponse(res);
|
|
48
|
+
}
|
|
49
|
+
export async function handleAdjustControl(args) {
|
|
50
|
+
const selector = getObjectArg(args, 'selector');
|
|
51
|
+
const element_id = getStringArg(args, 'element_id');
|
|
52
|
+
const property = getStringArg(args, 'property') ?? 'value';
|
|
53
|
+
const targetValue = requireNumberArg(args, 'targetValue');
|
|
54
|
+
const tolerance = getNumberArg(args, 'tolerance');
|
|
55
|
+
const maxAttempts = getNumberArg(args, 'maxAttempts');
|
|
56
|
+
const platform = getStringArg(args, 'platform');
|
|
57
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
58
|
+
if ((!selector && !element_id) || (selector && element_id)) {
|
|
59
|
+
throw new Error('Exactly one of selector or element_id argument is required');
|
|
60
|
+
}
|
|
61
|
+
const res = await ToolsInteract.adjustControlHandler({
|
|
62
|
+
selector: selector ?? undefined,
|
|
63
|
+
element_id: element_id ?? undefined,
|
|
64
|
+
property,
|
|
65
|
+
targetValue,
|
|
66
|
+
tolerance,
|
|
67
|
+
maxAttempts,
|
|
68
|
+
platform,
|
|
69
|
+
deviceId
|
|
70
|
+
});
|
|
71
|
+
markEvidenceVerification();
|
|
72
|
+
return wrapResponse(res);
|
|
73
|
+
}
|
|
74
|
+
export async function handleWaitForUI(args) {
|
|
75
|
+
if (args.requireReady !== undefined && typeof args.requireReady !== 'boolean')
|
|
76
|
+
throw new Error('INVALID_ARGUMENT: requireReady must be boolean');
|
|
77
|
+
const selector = getObjectArg(args, 'selector');
|
|
78
|
+
const condition = getStringArg(args, 'condition') ?? 'exists';
|
|
79
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000;
|
|
80
|
+
const poll_interval_ms = getNumberArg(args, 'poll_interval_ms') ?? 300;
|
|
81
|
+
const match = getObjectArg(args, 'match');
|
|
82
|
+
const retry = getObjectArg(args, 'retry');
|
|
83
|
+
const platform = getStringArg(args, 'platform');
|
|
84
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
85
|
+
const execute = (targetPlatform = platform, targetId = deviceId) => ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform: targetPlatform, deviceId: targetId });
|
|
86
|
+
const res = args.requireReady === true ? await withReadyWait({ platform, deviceId }, timeout_ms, device => execute(device.platform, device.id), (code, elapsed, samples) => ({ status: 'timeout', error: { code, message: 'Readiness wait could not complete' }, metrics: { latency_ms: elapsed, poll_count: samples, attempts: samples ? 1 : 0 }, requested: { selector, condition, match }, observed: { matched_count: 0, condition_satisfied: false, selected_index: null, last_matched_element: null } })) : await execute();
|
|
87
|
+
return wrapResponse(res);
|
|
88
|
+
}
|
|
89
|
+
export async function handleWaitForUIChange(args) {
|
|
90
|
+
if (args.requireReady !== undefined && typeof args.requireReady !== 'boolean')
|
|
91
|
+
throw new Error('INVALID_ARGUMENT: requireReady must be boolean');
|
|
92
|
+
const platform = getStringArg(args, 'platform');
|
|
93
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
94
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000;
|
|
95
|
+
const stability_window_ms = getNumberArg(args, 'stability_window_ms') ?? 300;
|
|
96
|
+
const expected_change = getStringArg(args, 'expected_change');
|
|
97
|
+
const scope = getStringArg(args, 'scope');
|
|
98
|
+
const target = getStringArg(args, 'target');
|
|
99
|
+
const execute = (targetPlatform = platform, targetId = deviceId) => ToolsInteract.waitForUIChangeHandler({ platform: targetPlatform, deviceId: targetId, timeout_ms, stability_window_ms, expected_change, scope, target });
|
|
100
|
+
const res = args.requireReady === true ? await withReadyWait({ platform, deviceId }, timeout_ms, device => execute(device.platform, device.id), (code, elapsed) => ({ success: false, timeout: true, observed_change: null, elapsed_ms: elapsed, reason: code, error: { code, message: 'Readiness wait could not complete' } })) : await execute();
|
|
101
|
+
return wrapResponse(res);
|
|
102
|
+
}
|
|
103
|
+
export async function handleFindElement(args) {
|
|
104
|
+
const query = requireStringArg(args, 'query');
|
|
105
|
+
const exact = getBooleanArg(args, 'exact') ?? false;
|
|
106
|
+
const timeoutMs = getNumberArg(args, 'timeoutMs') ?? 3000;
|
|
107
|
+
const platform = getStringArg(args, 'platform');
|
|
108
|
+
const deviceId = getStringArg(args, 'deviceId');
|
|
109
|
+
const targetStarted = performance.now();
|
|
110
|
+
const res = await withObservationContext({ purpose: 'target' }, () => ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId }));
|
|
111
|
+
recordObservationEvent({ target_resolution_ms: performance.now() - targetStarted, target_resolution_outcome: res.found ? 'found' : 'not_found', operation: 'find_element' });
|
|
112
|
+
return wrapResponse(res);
|
|
113
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ToolsInteract } from '../../interact/index.js';
|
|
2
|
+
import { ToolsObserve } from '../../observe/index.js';
|
|
3
|
+
export async function expectElementAbsent({ selector, timeoutMs, pollIntervalMs, platform, deviceId }) {
|
|
4
|
+
const deadline = performance.now() + timeoutMs;
|
|
5
|
+
let consecutive = 0;
|
|
6
|
+
let observed;
|
|
7
|
+
while (performance.now() < deadline) {
|
|
8
|
+
const result = await ToolsInteract.waitForUIHandler({ selector, condition: 'not_exists', timeout_ms: 0, singleObservation: true, singleAttempt: true, platform, deviceId });
|
|
9
|
+
observed = result.observed;
|
|
10
|
+
consecutive = result.status === 'success' ? consecutive + 1 : 0;
|
|
11
|
+
if (consecutive === 2 && performance.now() <= deadline)
|
|
12
|
+
return { success: true, observed };
|
|
13
|
+
const delay = Math.max(100, pollIntervalMs);
|
|
14
|
+
if (performance.now() + delay >= deadline)
|
|
15
|
+
break;
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
17
|
+
}
|
|
18
|
+
return { success: false, failure_code: 'ASSERTION_TIMEOUT', observed };
|
|
19
|
+
}
|
|
20
|
+
export async function expectFingerprint({ fingerprint, timeoutMs, platform, deviceId }) {
|
|
21
|
+
const deadline = performance.now() + timeoutMs;
|
|
22
|
+
let consecutive = 0;
|
|
23
|
+
let observed = null;
|
|
24
|
+
while (performance.now() < deadline) {
|
|
25
|
+
const result = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId });
|
|
26
|
+
observed = result?.fingerprint ?? null;
|
|
27
|
+
consecutive = observed === fingerprint ? consecutive + 1 : 0;
|
|
28
|
+
if (consecutive === 2 && performance.now() <= deadline)
|
|
29
|
+
return { success: true, observed_fingerprint: observed };
|
|
30
|
+
if (performance.now() + 100 >= deadline)
|
|
31
|
+
break;
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
33
|
+
}
|
|
34
|
+
return { success: false, failure_code: 'ASSERTION_TIMEOUT', observed_fingerprint: observed };
|
|
35
|
+
}
|