mobile-debug-mcp 0.34.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 +4 -4
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +156 -129
- 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 +16 -6
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +27 -2
- 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/protocol.js +33 -0
- package/dist/debugger/sessions.js +104 -62
- package/dist/debugger/worker.js +53 -42
- 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 -3090
- 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 +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.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 +4 -2
- package/dist/observe/readiness.js +29 -10
- 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.js +22 -13
- 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/{debugger-handlers.js → handlers/debugger-handlers.js} +22 -12
- 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/{debugger-schema.js → schemas/debugger-schema.js} +11 -6
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
- package/dist/server/tool-definitions.js +58 -1135
- package/dist/server-core.js +2 -2
- 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 +45 -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 +44 -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/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/tools/TOOLS.md +31 -18
- package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
- package/docs/tools/debugger.md +76 -0
- 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 +9 -1
- 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 +2 -1
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +6 -1
- package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +314 -155
- package/src/debugger/worker.ts +51 -38
- 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 -3492
- 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 +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.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 +5 -3
- package/src/observe/readiness.ts +39 -18
- 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.ts +24 -15
- 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/{debugger-schema.ts → schemas/debugger-schema.ts} +13 -7
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
- package/src/server/{system-trace-schema.ts → schemas/system-trace-schema.ts} +3 -3
- package/src/server/tool-definitions.ts +67 -1143
- package/src/server-core.ts +2 -2
- package/src/server.ts +1 -1
- 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 +57 -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/interact/interaction.android.test.ts +56 -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 +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 +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/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -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/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/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -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/tool_definitions_parity.test.ts +8 -0
- 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/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/dist/server/{system-trace-schema.js → schemas/system-trace-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,173 @@
|
|
|
1
|
+
import type { UiSelector as ExpectElementSelectorArg } from '../../contracts/selector.js'
|
|
2
|
+
import { measure } from '../../utils/timing.js'
|
|
3
|
+
import { runWithBudget } from '../../utils/operation-budget.js'
|
|
4
|
+
import { tapElementControlsSchema } from '../schemas/journey-schema.js'
|
|
5
|
+
import { notifyActionStart } from '../../interact/conditional-action.js'
|
|
6
|
+
|
|
7
|
+
import { ToolsInteract } from '../../interact/index.js'
|
|
8
|
+
import { recordObservationConsumer, recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js'
|
|
9
|
+
import {
|
|
10
|
+
buildActionExecutionResult,
|
|
11
|
+
captureActionFingerprint,
|
|
12
|
+
getNumberArg,
|
|
13
|
+
getStringArg,
|
|
14
|
+
inferGenericFailure,
|
|
15
|
+
inferScrollFailure,
|
|
16
|
+
requireNumberArg,
|
|
17
|
+
requireObjectArg,
|
|
18
|
+
requireStringArg,
|
|
19
|
+
ToolCallArgs,
|
|
20
|
+
wrapResponse
|
|
21
|
+
} from '../common.js'
|
|
22
|
+
import type { PlatformArg, ScrollSelectorArg } from './handler-types.js'
|
|
23
|
+
import { interactionControls, actionHandler } from './action-execution.js'
|
|
24
|
+
|
|
25
|
+
export async function executeTap(args: ToolCallArgs) {
|
|
26
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
27
|
+
const x = requireNumberArg(args, 'x')
|
|
28
|
+
const y = requireNumberArg(args, 'y')
|
|
29
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
30
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args)
|
|
31
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
32
|
+
notifyActionStart()
|
|
33
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.tapHandler({ platform, x, y, deviceId, timeoutMs: actionTimeoutMs }))
|
|
34
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
35
|
+
const result = buildActionExecutionResult({
|
|
36
|
+
actionType: 'tap',
|
|
37
|
+
sourceModule: 'server',
|
|
38
|
+
selector: { x, y },
|
|
39
|
+
success: !!res.success,
|
|
40
|
+
uiFingerprintBefore,
|
|
41
|
+
uiFingerprintAfter,
|
|
42
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
43
|
+
})
|
|
44
|
+
return wrapResponse(result)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function executeTapElement(args: ToolCallArgs) {
|
|
48
|
+
const controls = tapElementControlsSchema.safeParse({ elementId: args.elementId, selector: args.selector, waitFor: args.waitFor })
|
|
49
|
+
if (!controls.success) throw new Error(`INVALID_ARGUMENT: ${controls.error.message}`)
|
|
50
|
+
const elementId = controls.data.elementId
|
|
51
|
+
const selector = controls.data.selector as ExpectElementSelectorArg | undefined
|
|
52
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args)
|
|
53
|
+
let resolvedId = elementId
|
|
54
|
+
let freshTree: import('../../interact/observation.js').UiTree | undefined
|
|
55
|
+
if (selector) {
|
|
56
|
+
const waitFor = controls.data.waitFor
|
|
57
|
+
const targetStarted = performance.now()
|
|
58
|
+
const resolutionTimeoutMs = waitFor?.timeoutMs ?? actionTimeoutMs ?? 5000
|
|
59
|
+
const resolution = (await withObservationContext({ stepId: args._stepId as string | undefined, purpose: 'target' }, () =>
|
|
60
|
+
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') as PlatformArg | undefined, deviceId: getStringArg(args, 'deviceId') })))))!
|
|
61
|
+
recordObservationEvent({ step_id: args._stepId as string | undefined, purpose: 'target', target_resolution_ms: performance.now() - targetStarted, target_resolution_outcome: resolution.status })
|
|
62
|
+
if (resolution.status !== 'success' || !resolution.element?.elementId) return wrapResponse({ success: false, action_type: 'tap_element', failure_code: resolution.error?.code ?? 'ELEMENT_NOT_FOUND', retryable: true })
|
|
63
|
+
resolvedId = resolution.element.elementId
|
|
64
|
+
freshTree = resolution._tree
|
|
65
|
+
}
|
|
66
|
+
notifyActionStart()
|
|
67
|
+
if (freshTree) recordObservationConsumer(freshTree, 'target', args._stepId as string | undefined)
|
|
68
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.tapElementHandler({ elementId: resolvedId!, freshTree, verificationMode, timeoutMs: actionTimeoutMs, platform: getStringArg(args, 'platform') as PlatformArg | undefined, deviceId: getStringArg(args, 'deviceId') }))
|
|
69
|
+
return wrapResponse(res)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function executeSwipe(args: ToolCallArgs) {
|
|
73
|
+
const platform = (getStringArg(args, 'platform') as PlatformArg | undefined) ?? 'android'
|
|
74
|
+
const x1 = requireNumberArg(args, 'x1')
|
|
75
|
+
const y1 = requireNumberArg(args, 'y1')
|
|
76
|
+
const x2 = requireNumberArg(args, 'x2')
|
|
77
|
+
const y2 = requireNumberArg(args, 'y2')
|
|
78
|
+
const duration = requireNumberArg(args, 'duration')
|
|
79
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
80
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args)
|
|
81
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
82
|
+
notifyActionStart()
|
|
83
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId, timeoutMs: actionTimeoutMs }))
|
|
84
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
85
|
+
const result = buildActionExecutionResult({
|
|
86
|
+
actionType: 'swipe',
|
|
87
|
+
sourceModule: 'server',
|
|
88
|
+
selector: { x1, y1, x2, y2, duration },
|
|
89
|
+
success: !!res.success,
|
|
90
|
+
uiFingerprintBefore,
|
|
91
|
+
uiFingerprintAfter,
|
|
92
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
93
|
+
})
|
|
94
|
+
return wrapResponse(result)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function executeScrollToElement(args: ToolCallArgs) {
|
|
98
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
99
|
+
const selector = requireObjectArg<ScrollSelectorArg>(args, 'selector')
|
|
100
|
+
const direction = getStringArg(args, 'direction') as 'down' | 'up' | undefined
|
|
101
|
+
const maxScrolls = getNumberArg(args, 'maxScrolls')
|
|
102
|
+
const scrollAmount = getNumberArg(args, 'scrollAmount')
|
|
103
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
104
|
+
const { verificationMode } = interactionControls(args)
|
|
105
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
106
|
+
notifyActionStart()
|
|
107
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId }))
|
|
108
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
109
|
+
const result = buildActionExecutionResult({
|
|
110
|
+
actionType: 'scroll_to_element',
|
|
111
|
+
sourceModule: 'server',
|
|
112
|
+
selector: selector ?? null,
|
|
113
|
+
resolved: res?.success && res?.element ? {
|
|
114
|
+
elementId: null,
|
|
115
|
+
text: res.element.text ?? null,
|
|
116
|
+
resource_id: res.element.resourceId ?? null,
|
|
117
|
+
accessibility_id: (res.element as typeof res.element & { contentDesc?: string }).contentDesc ?? null,
|
|
118
|
+
class: (res.element as typeof res.element & { className?: string }).className ?? null,
|
|
119
|
+
bounds: res.element.bounds ?? null,
|
|
120
|
+
index: null
|
|
121
|
+
} : null,
|
|
122
|
+
success: !!res.success,
|
|
123
|
+
uiFingerprintBefore,
|
|
124
|
+
uiFingerprintAfter,
|
|
125
|
+
failure: res.success ? undefined : inferScrollFailure(res.reason)
|
|
126
|
+
})
|
|
127
|
+
return wrapResponse(result)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function executeTypeText(args: ToolCallArgs) {
|
|
131
|
+
const text = requireStringArg(args, 'text')
|
|
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.typeTextHandler({ text, deviceId, timeoutMs: actionTimeoutMs }))
|
|
137
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint('android', deviceId)) : null
|
|
138
|
+
const result = buildActionExecutionResult({
|
|
139
|
+
actionType: 'type_text',
|
|
140
|
+
sourceModule: 'server',
|
|
141
|
+
selector: { text },
|
|
142
|
+
success: !!res.success,
|
|
143
|
+
uiFingerprintBefore,
|
|
144
|
+
uiFingerprintAfter,
|
|
145
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
146
|
+
})
|
|
147
|
+
return wrapResponse(result)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function executePressBack(args: ToolCallArgs) {
|
|
151
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
152
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args)
|
|
153
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint('android', deviceId)) : null
|
|
154
|
+
notifyActionStart()
|
|
155
|
+
const res = await measure('dispatch_ms', () => ToolsInteract.pressBackHandler({ deviceId, timeoutMs: actionTimeoutMs }))
|
|
156
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint('android', deviceId)) : null
|
|
157
|
+
const result = buildActionExecutionResult({
|
|
158
|
+
actionType: 'press_back',
|
|
159
|
+
sourceModule: 'server',
|
|
160
|
+
selector: { key: 'back' },
|
|
161
|
+
success: !!res.success,
|
|
162
|
+
uiFingerprintBefore,
|
|
163
|
+
uiFingerprintAfter,
|
|
164
|
+
failure: res.success ? undefined : inferGenericFailure(res.error)
|
|
165
|
+
})
|
|
166
|
+
return wrapResponse(result)
|
|
167
|
+
}
|
|
168
|
+
export const handleTap = actionHandler('tap', executeTap)
|
|
169
|
+
export const handleTapElement = actionHandler('tap_element', executeTapElement)
|
|
170
|
+
export const handleSwipe = actionHandler('swipe', executeSwipe)
|
|
171
|
+
export const handleScrollToElement = actionHandler('scroll_to_element', executeScrollToElement)
|
|
172
|
+
export const handleTypeText = actionHandler('type_text', executeTypeText)
|
|
173
|
+
export const handlePressBack = actionHandler('press_back', executePressBack)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { UiSelector as ExpectElementSelectorArg } from '../../contracts/selector.js'
|
|
2
|
+
import { markEvidenceVerification } from '../../observe/evidence.js'
|
|
3
|
+
import { withReadyWait } from '../../observe/readiness-service.js'
|
|
4
|
+
|
|
5
|
+
import { ToolsInteract } from '../../interact/index.js'
|
|
6
|
+
import { recordObservationEvent, withObservationContext } from '../../observe/performance-metrics.js'
|
|
7
|
+
import {
|
|
8
|
+
getBooleanArg,
|
|
9
|
+
getNumberArg,
|
|
10
|
+
getObjectArg,
|
|
11
|
+
getStringArg,
|
|
12
|
+
requireNumberArg,
|
|
13
|
+
requireObjectArg,
|
|
14
|
+
requireStringArg,
|
|
15
|
+
ToolCallArgs,
|
|
16
|
+
wrapResponse
|
|
17
|
+
} from '../common.js'
|
|
18
|
+
import type { PlatformArg, WaitForUiMatchArg, WaitForUiRetryArg } from './handler-types.js'
|
|
19
|
+
|
|
20
|
+
export async function handleWaitForScreenChange(args: ToolCallArgs) {
|
|
21
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
22
|
+
const previousFingerprint = requireStringArg(args, 'previousFingerprint')
|
|
23
|
+
const timeoutMs = getNumberArg(args, 'timeoutMs')
|
|
24
|
+
const pollIntervalMs = getNumberArg(args, 'pollIntervalMs')
|
|
25
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
26
|
+
const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId })
|
|
27
|
+
return wrapResponse(res)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handleExpectScreen(args: ToolCallArgs) {
|
|
31
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
32
|
+
const fingerprint = getStringArg(args, 'fingerprint')
|
|
33
|
+
const screen = getStringArg(args, 'screen')
|
|
34
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
35
|
+
const res = await ToolsInteract.expectScreenHandler({ platform, fingerprint, screen, deviceId })
|
|
36
|
+
return wrapResponse(res)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function handleExpectElementVisible(args: ToolCallArgs) {
|
|
40
|
+
const selector = requireObjectArg<ExpectElementSelectorArg>(args, 'selector')
|
|
41
|
+
const element_id = getStringArg(args, 'element_id')
|
|
42
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms')
|
|
43
|
+
const poll_interval_ms = getNumberArg(args, 'poll_interval_ms')
|
|
44
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
45
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
46
|
+
const res = await ToolsInteract.expectElementVisibleHandler({ selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId })
|
|
47
|
+
return wrapResponse(res)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function handleExpectState(args: ToolCallArgs) {
|
|
51
|
+
const selector = getObjectArg<ExpectElementSelectorArg>(args, 'selector')
|
|
52
|
+
const element_id = getStringArg(args, 'element_id')
|
|
53
|
+
const property = requireStringArg(args, 'property')
|
|
54
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
55
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
56
|
+
if (!selector && !element_id) {
|
|
57
|
+
throw new Error('Missing selector or element_id argument')
|
|
58
|
+
}
|
|
59
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'expected')) {
|
|
60
|
+
throw new Error('Missing expected argument')
|
|
61
|
+
}
|
|
62
|
+
const expected = args.expected as boolean | number | string | Record<string, unknown>
|
|
63
|
+
const res = await ToolsInteract.expectStateHandler({ selector: selector ?? undefined, element_id: element_id ?? undefined, property, expected, platform, deviceId })
|
|
64
|
+
return wrapResponse(res)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function handleAdjustControl(args: ToolCallArgs) {
|
|
68
|
+
const selector = getObjectArg<ExpectElementSelectorArg>(args, 'selector')
|
|
69
|
+
const element_id = getStringArg(args, 'element_id')
|
|
70
|
+
const property = getStringArg(args, 'property') ?? 'value'
|
|
71
|
+
const targetValue = requireNumberArg(args, 'targetValue')
|
|
72
|
+
const tolerance = getNumberArg(args, 'tolerance')
|
|
73
|
+
const maxAttempts = getNumberArg(args, 'maxAttempts')
|
|
74
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
75
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
76
|
+
if ((!selector && !element_id) || (selector && element_id)) {
|
|
77
|
+
throw new Error('Exactly one of selector or element_id argument is required')
|
|
78
|
+
}
|
|
79
|
+
const res = await ToolsInteract.adjustControlHandler({
|
|
80
|
+
selector: selector ?? undefined,
|
|
81
|
+
element_id: element_id ?? undefined,
|
|
82
|
+
property,
|
|
83
|
+
targetValue,
|
|
84
|
+
tolerance,
|
|
85
|
+
maxAttempts,
|
|
86
|
+
platform,
|
|
87
|
+
deviceId
|
|
88
|
+
})
|
|
89
|
+
markEvidenceVerification()
|
|
90
|
+
return wrapResponse(res)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function handleWaitForUI(args: ToolCallArgs) {
|
|
94
|
+
if (args.requireReady !== undefined && typeof args.requireReady !== 'boolean') throw new Error('INVALID_ARGUMENT: requireReady must be boolean')
|
|
95
|
+
const selector = getObjectArg<ExpectElementSelectorArg>(args, 'selector')
|
|
96
|
+
const condition = (getStringArg(args, 'condition') as 'exists' | 'not_exists' | 'visible' | 'clickable' | undefined) ?? 'exists'
|
|
97
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000
|
|
98
|
+
const poll_interval_ms = getNumberArg(args, 'poll_interval_ms') ?? 300
|
|
99
|
+
const match = getObjectArg<WaitForUiMatchArg>(args, 'match')
|
|
100
|
+
const retry = getObjectArg<WaitForUiRetryArg>(args, 'retry')
|
|
101
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
102
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
103
|
+
const execute = (targetPlatform = platform, targetId = deviceId) => ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform: targetPlatform, deviceId: targetId })
|
|
104
|
+
const res = args.requireReady === true ? await withReadyWait({ platform, deviceId }, timeout_ms,
|
|
105
|
+
device => execute(device.platform as PlatformArg, 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()
|
|
106
|
+
return wrapResponse(res)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function handleWaitForUIChange(args: ToolCallArgs) {
|
|
110
|
+
if (args.requireReady !== undefined && typeof args.requireReady !== 'boolean') throw new Error('INVALID_ARGUMENT: requireReady must be boolean')
|
|
111
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
112
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
113
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000
|
|
114
|
+
const stability_window_ms = getNumberArg(args, 'stability_window_ms') ?? 300
|
|
115
|
+
const expected_change = getStringArg(args, 'expected_change') as 'hierarchy_diff' | 'text_change' | 'state_change' | undefined
|
|
116
|
+
const scope = getStringArg(args, 'scope') as 'screen' | 'subtree' | undefined
|
|
117
|
+
const target = getStringArg(args, 'target')
|
|
118
|
+
const execute = (targetPlatform = platform, targetId = deviceId) => ToolsInteract.waitForUIChangeHandler({ platform: targetPlatform, deviceId: targetId, timeout_ms, stability_window_ms, expected_change, scope, target })
|
|
119
|
+
const res = args.requireReady === true ? await withReadyWait({ platform, deviceId }, timeout_ms,
|
|
120
|
+
device => execute(device.platform as PlatformArg, 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()
|
|
121
|
+
return wrapResponse(res)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function handleFindElement(args: ToolCallArgs) {
|
|
125
|
+
const query = requireStringArg(args, 'query')
|
|
126
|
+
const exact = getBooleanArg(args, 'exact') ?? false
|
|
127
|
+
const timeoutMs = getNumberArg(args, 'timeoutMs') ?? 3000
|
|
128
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
129
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
130
|
+
const targetStarted = performance.now()
|
|
131
|
+
const res = await withObservationContext({ purpose: 'target' }, () => ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId }))
|
|
132
|
+
recordObservationEvent({ target_resolution_ms: performance.now() - targetStarted, target_resolution_outcome: res.found ? 'found' : 'not_found', operation: 'find_element' })
|
|
133
|
+
return wrapResponse(res)
|
|
134
|
+
}
|
|
135
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { UiSelector as ExpectElementSelectorArg } from '../../contracts/selector.js'
|
|
2
|
+
|
|
3
|
+
import { ToolsInteract } from '../../interact/index.js'
|
|
4
|
+
import { ToolsObserve } from '../../observe/index.js'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import type { PlatformArg } from './handler-types.js'
|
|
8
|
+
|
|
9
|
+
export async function expectElementAbsent({ selector, timeoutMs, pollIntervalMs, platform, deviceId }: { selector: ExpectElementSelectorArg, timeoutMs: number, pollIntervalMs: number, platform: PlatformArg, deviceId?: string }) {
|
|
10
|
+
const deadline = performance.now() + timeoutMs
|
|
11
|
+
let consecutive = 0
|
|
12
|
+
let observed: import('../../interact/waits/ui-wait-state.js').UIWaitResult['observed']
|
|
13
|
+
while (performance.now() < deadline) {
|
|
14
|
+
const result = await ToolsInteract.waitForUIHandler({ selector, condition: 'not_exists', timeout_ms: 0, singleObservation: true, singleAttempt: true, platform, deviceId })
|
|
15
|
+
observed = result!.observed
|
|
16
|
+
consecutive = result!.status === 'success' ? consecutive + 1 : 0
|
|
17
|
+
if (consecutive === 2 && performance.now() <= deadline) return { success: true, observed }
|
|
18
|
+
const delay = Math.max(100, pollIntervalMs)
|
|
19
|
+
if (performance.now() + delay >= deadline) break
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
|
21
|
+
}
|
|
22
|
+
return { success: false, failure_code: 'ASSERTION_TIMEOUT', observed }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function expectFingerprint({ fingerprint, timeoutMs, platform, deviceId }: { fingerprint: string, timeoutMs: number, platform: PlatformArg, deviceId?: string }) {
|
|
26
|
+
const deadline = performance.now() + timeoutMs
|
|
27
|
+
let consecutive = 0
|
|
28
|
+
let observed: string | null = null
|
|
29
|
+
while (performance.now() < deadline) {
|
|
30
|
+
const result = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId })
|
|
31
|
+
observed = result?.fingerprint ?? null
|
|
32
|
+
consecutive = observed === fingerprint ? consecutive + 1 : 0
|
|
33
|
+
if (consecutive === 2 && performance.now() <= deadline) return { success: true, observed_fingerprint: observed }
|
|
34
|
+
if (performance.now() + 100 >= deadline) break
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
36
|
+
}
|
|
37
|
+
return { success: false, failure_code: 'ASSERTION_TIMEOUT', observed_fingerprint: observed }
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { z } from 'zod'
|
|
2
|
+
import { debuggerSessions } from '../../debugger/sessions.js'
|
|
3
|
+
import { systemTraces } from '../../observe/system-trace.js'
|
|
4
|
+
import { currentEvidenceCorrelationId, bindSystemTrace, refreshJourneyEvidence, completeJourneyVerification } from '../../observe/evidence.js'
|
|
5
|
+
import { runWithBudget } from '../../utils/operation-budget.js'
|
|
6
|
+
import { journeySchema } from '../schemas/journey-schema.js'
|
|
7
|
+
import { resolveTargetDevice, withResolvedDevice } from '../../utils/resolve-device.js'
|
|
8
|
+
import { ToolsInteract } from '../../interact/index.js'
|
|
9
|
+
import { ToolsObserve } from '../../observe/index.js'
|
|
10
|
+
import { withObservationContext } from '../../observe/performance-metrics.js'
|
|
11
|
+
import { wrapResponse, type ToolCallArgs } from '../common.js'
|
|
12
|
+
import { payloadOf, type ToolPayload } from './payload.js'
|
|
13
|
+
import { handleStartApp } from './manage.js'
|
|
14
|
+
import { handleTapElement } from './input.js'
|
|
15
|
+
import { expectElementAbsent, expectFingerprint } from './journey-assertions.js'
|
|
16
|
+
|
|
17
|
+
type Journey = z.infer<typeof journeySchema>
|
|
18
|
+
type Step = Journey['steps'][number]
|
|
19
|
+
type AssertionStep = Extract<Step, { type: 'assert' }>
|
|
20
|
+
interface StepResult {
|
|
21
|
+
id: string; type: Step['type']; status: string; success: boolean; reason?: string
|
|
22
|
+
result: ToolPayload | null; timing?: { total_ms: number }
|
|
23
|
+
}
|
|
24
|
+
interface Context {
|
|
25
|
+
input: Journey; deviceId: string; results: StepResult[]; stoppedAt: string | null; pendingLightStep: number | null
|
|
26
|
+
}
|
|
27
|
+
async function assertStep(step: AssertionStep, platform: Journey['platform'], deviceId: string): Promise<ToolPayload> {
|
|
28
|
+
const assertion = step.assertion
|
|
29
|
+
const timeoutMs = step.timeoutMs ?? 5000, pollIntervalMs = step.pollIntervalMs ?? 100
|
|
30
|
+
if (assertion.kind === 'element_visible') return ToolsInteract.expectElementVisibleHandler({ selector: assertion.selector, timeout_ms: timeoutMs, poll_interval_ms: pollIntervalMs, platform, deviceId })
|
|
31
|
+
if (assertion.kind === 'element_absent') return expectElementAbsent({ selector: assertion.selector, timeoutMs, pollIntervalMs, platform, deviceId })
|
|
32
|
+
if (assertion.kind === 'screen_fingerprint') return expectFingerprint({ fingerprint: assertion.fingerprint, timeoutMs, platform, deviceId })
|
|
33
|
+
if (assertion.kind === 'state_equals') return ToolsInteract.expectStateHandler({ selector: assertion.selector, element_id: assertion.elementId, property: assertion.property, expected: assertion.expected, platform, deviceId, stabilization_window_ms: timeoutMs, poll_interval_ms: pollIntervalMs, exact_budget: true })
|
|
34
|
+
return { success: false, failure_code: 'INVALID_ASSERTION' }
|
|
35
|
+
}
|
|
36
|
+
async function executeStep(s: Context, step: Step, covered: boolean): Promise<ToolPayload> {
|
|
37
|
+
const { platform, responseMode, defaults } = s.input, deviceId = s.deviceId
|
|
38
|
+
try {
|
|
39
|
+
if (step.type === 'tap') return payloadOf(await handleTapElement({ ...defaults, ...step, platform, deviceId, responseMode, _journey: true, _stepId: step.id, verificationMode: covered ? 'none' : step.verificationMode ?? defaults?.verificationMode ?? 'full' }))
|
|
40
|
+
if (step.type === 'wait') {
|
|
41
|
+
const result = (await withObservationContext({ stepId: step.id, purpose: 'target' }, () => ToolsInteract.waitForUIHandler({ selector: step.selector, condition: step.condition ?? 'exists', timeout_ms: step.timeoutMs ?? 5000, poll_interval_ms: step.pollIntervalMs ?? 100, platform, deviceId })))!
|
|
42
|
+
return { success: result.status === 'success', ...result }
|
|
43
|
+
}
|
|
44
|
+
if (step.type === 'assert') return await withObservationContext({ stepId: step.id, purpose: 'assertion' }, () => runWithBudget(step.timeoutMs ?? 5000, () => assertStep(step, platform, deviceId)))
|
|
45
|
+
if (step.type === 'start_app') return payloadOf(await handleStartApp({ ...defaults, platform, deviceId, appId: step.appId, responseMode, _journey: true, _stepId: step.id, verificationMode: step.verificationMode ?? defaults?.verificationMode ?? 'full' }))
|
|
46
|
+
return { success: false, failure_code: 'UNSUPPORTED_JOURNEY_STEP' }
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return { success: false, failure_code: step.type === 'assert' ? 'ASSERTION_TIMEOUT' : 'STEP_FAILED', error: error instanceof Error ? error.message : String(error) }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function lightVerification(s: Context, step: Step, result: ToolPayload, covered: boolean, success: boolean) {
|
|
52
|
+
if (covered) {
|
|
53
|
+
result.verification = { mode: 'light', status: success ? 'unavailable' :
|
|
54
|
+
(['mismatch', 'unavailable'].includes(result.condition?.status ?? '') ? 'not_requested' : 'failed') }
|
|
55
|
+
if (success) {
|
|
56
|
+
result.verification_diagnostic = { code: 'VERIFICATION_UNAVAILABLE', retryable: true }
|
|
57
|
+
s.pendingLightStep = s.results.length
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (step.type !== 'assert' || s.pendingLightStep === null) return
|
|
61
|
+
const pending = s.results[s.pendingLightStep].result!
|
|
62
|
+
if (typeof result.observed?.matched_count === 'number') {
|
|
63
|
+
pending.verification!.status = 'observed'
|
|
64
|
+
if (pending.evidence) completeJourneyVerification(pending.evidence.action_id!)
|
|
65
|
+
delete pending.verification_diagnostic
|
|
66
|
+
const delta = ToolsObserve.getLatestStateDelta(s.input.platform, s.deviceId)
|
|
67
|
+
if (delta) { pending.state_delta = delta; pending.state_delta_available = true }
|
|
68
|
+
}
|
|
69
|
+
s.pendingLightStep = null
|
|
70
|
+
}
|
|
71
|
+
function compactAssertion(result: ToolPayload, success: boolean) {
|
|
72
|
+
const errorCode = typeof result.error === 'object' ? result.error?.code : undefined
|
|
73
|
+
return { success, ...(result.failure_code ? { failure_code: result.failure_code } : {}),
|
|
74
|
+
...(errorCode ? { failure_code: errorCode } : {}),
|
|
75
|
+
...(result.observed?.matched_count !== undefined ? { matched_count: result.observed.matched_count } : {}),
|
|
76
|
+
...(result.observed_fingerprint !== undefined ? { matched: success } : {}),
|
|
77
|
+
...(result.actual_state ? { observed: result.actual_state } : {}) }
|
|
78
|
+
}
|
|
79
|
+
function coveredByAssertion(s: Context, step: Step, next?: Step) {
|
|
80
|
+
const verification = ('verificationMode' in step ? step.verificationMode : undefined) ?? s.input.defaults?.verificationMode ?? 'full'
|
|
81
|
+
return step.type === 'tap' && s.input.responseMode === 'compact' && verification === 'light' && next?.type === 'assert' && next.assertion.kind === 'element_visible'
|
|
82
|
+
}
|
|
83
|
+
async function runSteps(s: Context) {
|
|
84
|
+
const { platform, responseMode, steps } = s.input
|
|
85
|
+
for (let index = 0; index < steps.length; index++) {
|
|
86
|
+
const step = steps[index]
|
|
87
|
+
if (platform === 'android' && debuggerSessions.paused(s.deviceId)) {
|
|
88
|
+
s.results.push({ id: step.id, type: step.type, status: 'failed', success: false, result: { success: false, failure_code: 'DEBUGGER_PAUSED', dispatch_started: false, delivery_status: 'not_delivered' } })
|
|
89
|
+
s.stoppedAt = step.id
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
const covered = coveredByAssertion(s, step, steps[index + 1]), started = performance.now()
|
|
93
|
+
if (s.stoppedAt) {
|
|
94
|
+
s.results.push({ id: step.id, type: step.type, status: 'not_run', success: false, reason: 'PREVIOUS_STEP_FAILED', result: null, timing: { total_ms: 0 } })
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
let result = await executeStep(s, step, covered)
|
|
98
|
+
if (platform === 'android' && debuggerSessions.paused(s.deviceId)) { result.success = false; result.failure_code = 'DEBUGGER_PAUSED'; result.retryable = false }
|
|
99
|
+
if (step.type === 'assert' && !result?.success && /TIMEOUT/.test(result?.failure_code ?? '')) result.failure_code = 'ASSERTION_TIMEOUT'
|
|
100
|
+
const success = !!result?.success
|
|
101
|
+
lightVerification(s, step, result, covered, success)
|
|
102
|
+
if (responseMode === 'compact' && ['wait', 'assert'].includes(step.type)) result = compactAssertion(result, success)
|
|
103
|
+
s.results.push({ id: step.id, type: step.type, status: success ? 'passed' : 'failed', success, result, timing: { total_ms: performance.now() - started } })
|
|
104
|
+
if (!success) s.stoppedAt = step.id
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function journeyResponse(s: Context, started: number, finishTrace?: () => Promise<unknown>) {
|
|
108
|
+
const { platform, captureOnFailure, responseMode } = s.input
|
|
109
|
+
const failureSnapshot = s.stoppedAt && captureOnFailure && responseMode === 'debug'
|
|
110
|
+
? await ToolsObserve.captureDebugSnapshotHandler({ platform, deviceId: s.deviceId, includeScreenshot: false }).catch(() => null) : undefined
|
|
111
|
+
const timing: Record<string, number> = {}
|
|
112
|
+
for (const step of s.results) for (const [key, value] of Object.entries(step.result?.timing ?? {})) {
|
|
113
|
+
if (key !== 'total_ms' && typeof value === 'number') timing[key] = (timing[key] ?? 0) + value
|
|
114
|
+
}
|
|
115
|
+
refreshJourneyEvidence(s.results)
|
|
116
|
+
return wrapResponse({ ...(finishTrace ? { system_trace: await finishTrace() } : {}), success: s.stoppedAt === null, stopped_at_step_id: s.stoppedAt, steps: s.results, ...(failureSnapshot ? { failure_snapshot: failureSnapshot } : {}), timing: responseMode === 'compact' ? { total_ms: performance.now() - started } : { ...timing, total_ms: performance.now() - started } }, responseMode === 'debug')
|
|
117
|
+
}
|
|
118
|
+
export async function handleRunJourney(args: ToolCallArgs) {
|
|
119
|
+
const parsed = journeySchema.safeParse(args)
|
|
120
|
+
if (!parsed.success) throw new Error(`INVALID_ARGUMENT: ${parsed.error.message}`)
|
|
121
|
+
const device = await resolveTargetDevice({ platform: parsed.data.platform, deviceId: parsed.data.deviceId })
|
|
122
|
+
const finishTrace = parsed.data.systemTraceId ? await systemTraces.link(parsed.data.systemTraceId, device, currentEvidenceCorrelationId()!) : undefined
|
|
123
|
+
if (parsed.data.systemTraceId) bindSystemTrace(parsed.data.systemTraceId)
|
|
124
|
+
try {
|
|
125
|
+
return await withResolvedDevice(device, async () => {
|
|
126
|
+
const started = performance.now()
|
|
127
|
+
const state: Context = { input: parsed.data, deviceId: device.id, results: [], stoppedAt: null, pendingLightStep: null }
|
|
128
|
+
await runSteps(state)
|
|
129
|
+
return journeyResponse(state, started, finishTrace)
|
|
130
|
+
})
|
|
131
|
+
} finally { await finishTrace?.() }
|
|
132
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { measure } from '../../utils/timing.js'
|
|
2
|
+
import { notifyActionStart } from '../../interact/conditional-action.js'
|
|
3
|
+
import type {
|
|
4
|
+
InstallAppResponse,
|
|
5
|
+
ResetAppDataResponse,
|
|
6
|
+
TerminateAppResponse
|
|
7
|
+
} from '../../types.js'
|
|
8
|
+
import { AndroidManage, iOSManage, ToolsManage } from '../../manage/index.js'
|
|
9
|
+
import {
|
|
10
|
+
buildActionExecutionResult,
|
|
11
|
+
captureActionFingerprint,
|
|
12
|
+
getNumberArg,
|
|
13
|
+
getStringArg,
|
|
14
|
+
inferGenericFailure,
|
|
15
|
+
requireStringArg,
|
|
16
|
+
ToolCallArgs,
|
|
17
|
+
wrapResponse
|
|
18
|
+
} from '../common.js'
|
|
19
|
+
import type { PlatformArg, ProjectTypeArg } from './handler-types.js'
|
|
20
|
+
import { interactionControls, actionHandler } from './action-execution.js'
|
|
21
|
+
|
|
22
|
+
export async function executeStartApp(args: ToolCallArgs) {
|
|
23
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
24
|
+
const appId = requireStringArg(args, 'appId')
|
|
25
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
26
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args)
|
|
27
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
28
|
+
notifyActionStart()
|
|
29
|
+
const res = await measure('dispatch_ms', () => (platform === 'android' ? new AndroidManage().startApp(appId, deviceId, actionTimeoutMs ?? (verificationMode === 'full' && getStringArg(args, 'responseMode') === undefined && args._journey !== true ? undefined : 10000)) : new iOSManage().startApp(appId, deviceId, verificationMode === 'full')))
|
|
30
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
31
|
+
const result = buildActionExecutionResult({
|
|
32
|
+
actionType: 'start_app',
|
|
33
|
+
sourceModule: 'server',
|
|
34
|
+
device: res.device,
|
|
35
|
+
selector: { appId },
|
|
36
|
+
success: !!res.appStarted,
|
|
37
|
+
uiFingerprintBefore,
|
|
38
|
+
uiFingerprintAfter,
|
|
39
|
+
failure: res.appStarted ? undefined : inferGenericFailure(res.error),
|
|
40
|
+
details: {
|
|
41
|
+
launch_time_ms: res.launchTimeMs,
|
|
42
|
+
...(typeof res.output === 'string' ? { output: res.output } : {}),
|
|
43
|
+
...(res.device ? { device_id: res.device.id } : {}),
|
|
44
|
+
...(typeof res.error === 'string' ? { error: res.error } : {}),
|
|
45
|
+
...(res.diagnostics ? { diagnostics: res.diagnostics } : {}),
|
|
46
|
+
...(res.observedApp ? { observed_app: res.observedApp } : {})
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
return wrapResponse(result)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function handleTerminateApp(args: ToolCallArgs) {
|
|
53
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
54
|
+
const appId = requireStringArg(args, 'appId')
|
|
55
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
56
|
+
const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId))
|
|
57
|
+
const response: TerminateAppResponse = { device: res.device, appTerminated: res.appTerminated }
|
|
58
|
+
return wrapResponse(response)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function executeRestartApp(args: ToolCallArgs) {
|
|
62
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
63
|
+
const appId = requireStringArg(args, 'appId')
|
|
64
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
65
|
+
const { verificationMode, actionTimeoutMs } = interactionControls(args)
|
|
66
|
+
const uiFingerprintBefore = verificationMode === 'full' ? await measure('pre_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
67
|
+
notifyActionStart()
|
|
68
|
+
const bounded = args._journey === true || verificationMode !== 'full' || args.responseMode !== undefined || actionTimeoutMs !== undefined
|
|
69
|
+
const res = await measure('dispatch_ms', () => (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId, bounded, actionTimeoutMs ?? 15000) : new iOSManage().restartApp(appId, deviceId, verificationMode === 'full', bounded, actionTimeoutMs ?? 15000)))
|
|
70
|
+
const uiFingerprintAfter = verificationMode === 'full' ? await measure('post_observation_ms', () => captureActionFingerprint(platform, deviceId)) : null
|
|
71
|
+
const result = buildActionExecutionResult({
|
|
72
|
+
actionType: 'restart_app',
|
|
73
|
+
sourceModule: 'server',
|
|
74
|
+
device: res.device,
|
|
75
|
+
selector: { appId },
|
|
76
|
+
success: !!res.appRestarted,
|
|
77
|
+
uiFingerprintBefore,
|
|
78
|
+
uiFingerprintAfter,
|
|
79
|
+
failure: res.appRestarted ? undefined : inferGenericFailure(res.error),
|
|
80
|
+
details: {
|
|
81
|
+
launch_time_ms: res.launchTimeMs,
|
|
82
|
+
...(typeof res.output === 'string' ? { output: res.output } : {}),
|
|
83
|
+
...(typeof res.terminatedBeforeRestart === 'boolean' ? { terminated_before_restart: res.terminatedBeforeRestart } : {}),
|
|
84
|
+
...(typeof res.terminateError === 'string' ? { terminate_error: res.terminateError } : {}),
|
|
85
|
+
...(typeof res.error === 'string' ? { error: res.error } : {}),
|
|
86
|
+
...(res.diagnostics ? { diagnostics: res.diagnostics } : {}),
|
|
87
|
+
...(res.observedApp ? { observed_app: res.observedApp } : {})
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
return wrapResponse(result)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function handleResetAppData(args: ToolCallArgs) {
|
|
94
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
95
|
+
const appId = requireStringArg(args, 'appId')
|
|
96
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
97
|
+
const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId))
|
|
98
|
+
const response: ResetAppDataResponse = { device: res.device, dataCleared: res.dataCleared }
|
|
99
|
+
return wrapResponse(response)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function handleInstallApp(args: ToolCallArgs) {
|
|
103
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
104
|
+
const projectType = requireStringArg(args, 'projectType') as ProjectTypeArg
|
|
105
|
+
const appPath = requireStringArg(args, 'appPath')
|
|
106
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
107
|
+
const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId, projectType })
|
|
108
|
+
const response: InstallAppResponse = {
|
|
109
|
+
device: res.device,
|
|
110
|
+
installed: res.installed,
|
|
111
|
+
output: res.output,
|
|
112
|
+
error: res.error
|
|
113
|
+
}
|
|
114
|
+
return wrapResponse(response)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function handleBuildApp(args: ToolCallArgs) {
|
|
118
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
119
|
+
const projectType = requireStringArg(args, 'projectType') as ProjectTypeArg
|
|
120
|
+
const projectPath = requireStringArg(args, 'projectPath')
|
|
121
|
+
const variant = getStringArg(args, 'variant')
|
|
122
|
+
const res = await ToolsManage.buildAppHandler({ platform, projectPath, variant, projectType })
|
|
123
|
+
return wrapResponse(res)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function handleBuildAndInstall(args: ToolCallArgs) {
|
|
127
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
128
|
+
const projectType = requireStringArg(args, 'projectType') as ProjectTypeArg
|
|
129
|
+
const projectPath = requireStringArg(args, 'projectPath')
|
|
130
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
131
|
+
const timeout = getNumberArg(args, 'timeout')
|
|
132
|
+
const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout, projectType })
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{ type: 'text' as const, text: res.ndjson },
|
|
136
|
+
{ type: 'text' as const, text: JSON.stringify(res.result, null, 2) }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export const handleStartApp = actionHandler('start_app', executeStartApp)
|
|
141
|
+
export const handleRestartApp = actionHandler('restart_app', executeRestartApp)
|