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,116 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { ToolsObserve } from '../../observe/index.js'
|
|
4
|
+
import { withObservationContext } from '../../observe/performance-metrics.js'
|
|
5
|
+
import {
|
|
6
|
+
getBooleanArg,
|
|
7
|
+
getNumberArg,
|
|
8
|
+
getStringArg,
|
|
9
|
+
requireStringArg,
|
|
10
|
+
ToolCallArgs,
|
|
11
|
+
wrapResponse
|
|
12
|
+
} from '../common.js'
|
|
13
|
+
import type { PlatformArg, ResponseMode } from './handler-types.js'
|
|
14
|
+
|
|
15
|
+
export async function handleGetLogs(args: ToolCallArgs) {
|
|
16
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
17
|
+
const appId = getStringArg(args, 'appId')
|
|
18
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
19
|
+
const pid = getNumberArg(args, 'pid')
|
|
20
|
+
const tag = getStringArg(args, 'tag')
|
|
21
|
+
const level = getStringArg(args, 'level')
|
|
22
|
+
const contains = getStringArg(args, 'contains')
|
|
23
|
+
const since_seconds = getNumberArg(args, 'since_seconds')
|
|
24
|
+
const limit = getNumberArg(args, 'limit')
|
|
25
|
+
const lines = getNumberArg(args, 'lines')
|
|
26
|
+
const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines })
|
|
27
|
+
const filtered = !!(pid || tag || level || contains || since_seconds || appId)
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{ type: 'text' as const, text: JSON.stringify({ device: res.device, result: { count: res.logCount, filtered, crashLines: (res.crashLines || []), source: res.source, meta: res.meta || {} } }, null, 2) },
|
|
31
|
+
{ type: 'text' as const, text: JSON.stringify({ logs: res.logs }, null, 2) }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function handleCaptureScreenshot(args: ToolCallArgs) {
|
|
37
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
38
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
39
|
+
const started = performance.now()
|
|
40
|
+
const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
|
|
41
|
+
const screenshotMs = performance.now() - started
|
|
42
|
+
const mime = res.screenshot_mime || 'image/png'
|
|
43
|
+
const content: Array<{ type: 'text' | 'image'; text?: string; data?: string; mimeType?: string }> = [
|
|
44
|
+
{ type: 'text', text: JSON.stringify({ device: res.device, result: { resolution: res.resolution, mimeType: mime }, timing: { total_ms: screenshotMs, screenshot_ms: screenshotMs } }, null, 2) },
|
|
45
|
+
{ type: 'image', data: res.screenshot, mimeType: mime }
|
|
46
|
+
]
|
|
47
|
+
if (res.screenshot_fallback) {
|
|
48
|
+
content.push({ type: 'text', text: JSON.stringify({ note: 'JPEG fallback included for compatibility', mimeType: res.screenshot_fallback_mime || 'image/jpeg' }) })
|
|
49
|
+
content.push({ type: 'image', data: res.screenshot_fallback, mimeType: res.screenshot_fallback_mime || 'image/jpeg' })
|
|
50
|
+
}
|
|
51
|
+
return { content }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function handleCaptureDebugSnapshot(args: ToolCallArgs) {
|
|
55
|
+
const reason = getStringArg(args, 'reason')
|
|
56
|
+
const includeLogs = getBooleanArg(args, 'includeLogs')
|
|
57
|
+
const logLines = getNumberArg(args, 'logLines')
|
|
58
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
59
|
+
const appId = getStringArg(args, 'appId')
|
|
60
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
61
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
62
|
+
const includeScreenshot = getBooleanArg(args, 'includeScreenshot')
|
|
63
|
+
const res = await ToolsObserve.captureDebugSnapshotHandler({ reason, includeLogs, includeScreenshot, logLines, platform, appId, deviceId, sessionId })
|
|
64
|
+
return wrapResponse(res)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function handleGetUITree(args: ToolCallArgs) {
|
|
68
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
69
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
70
|
+
const responseMode = (getStringArg(args, 'responseMode') as ResponseMode | undefined) ?? 'debug'
|
|
71
|
+
const sinceSnapshotRevision = getNumberArg(args, 'sinceSnapshotRevision')
|
|
72
|
+
const metricsStepId = process.env.MOBILE_DEBUG_MCP_UI_TREE_METRICS === '1' ? getStringArg(args, '_metricsStepId') : undefined
|
|
73
|
+
const res = await withObservationContext({ stepId: metricsStepId, purpose: metricsStepId?.includes('post-response') ? 'assertion' : 'debug' }, () =>
|
|
74
|
+
ToolsObserve.getUITreeHandler({ platform, deviceId, responseMode, sinceSnapshotRevision }))
|
|
75
|
+
return wrapResponse(res)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function handleGetCurrentScreen(args: ToolCallArgs) {
|
|
79
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
80
|
+
const res = await ToolsObserve.getCurrentScreenHandler({ deviceId })
|
|
81
|
+
return wrapResponse(res)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function handleGetScreenFingerprint(args: ToolCallArgs) {
|
|
85
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
86
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
87
|
+
const res = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId })
|
|
88
|
+
return wrapResponse(res)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function handleStartLogStream(args: ToolCallArgs) {
|
|
92
|
+
const platform = (getStringArg(args, 'platform') as PlatformArg | undefined) ?? 'android'
|
|
93
|
+
const packageName = requireStringArg(args, 'packageName')
|
|
94
|
+
const level = (getStringArg(args, 'level') as 'error' | 'warn' | 'info' | 'debug' | undefined) ?? 'error'
|
|
95
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
96
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
97
|
+
const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId })
|
|
98
|
+
return wrapResponse(res)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function handleReadLogStream(args: ToolCallArgs) {
|
|
102
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
103
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
104
|
+
const limit = getNumberArg(args, 'limit')
|
|
105
|
+
const since = getStringArg(args, 'since')
|
|
106
|
+
const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since })
|
|
107
|
+
return wrapResponse(res)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function handleStopLogStream(args: ToolCallArgs) {
|
|
111
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
112
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
113
|
+
const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId })
|
|
114
|
+
return wrapResponse(res)
|
|
115
|
+
}
|
|
116
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ActionExecutionResult, DeviceInfo, ActionConditionResult, TimingBreakdown } from '../../types.js'
|
|
2
|
+
import type { ToolCallResult } from '../common.js'
|
|
3
|
+
|
|
4
|
+
// Internal decoded MCP payloads vary by capability. Keep the fields inspected by
|
|
5
|
+
// orchestration explicit while preserving untouched capability-specific fields.
|
|
6
|
+
export interface ToolPayload {
|
|
7
|
+
success?: boolean
|
|
8
|
+
action_id?: string
|
|
9
|
+
action_type?: string
|
|
10
|
+
lifecycle_state?: string
|
|
11
|
+
failure_code?: string
|
|
12
|
+
retryable?: boolean
|
|
13
|
+
device?: DeviceInfo
|
|
14
|
+
target?: ActionExecutionResult['target']
|
|
15
|
+
ui_fingerprint_before?: string | null
|
|
16
|
+
error?: string | { code?: string; message?: string }
|
|
17
|
+
reason?: string
|
|
18
|
+
details?: { error?: unknown }
|
|
19
|
+
dispatch_started?: boolean
|
|
20
|
+
delivery_status?: string
|
|
21
|
+
state_delta?: unknown
|
|
22
|
+
state_delta_available?: boolean
|
|
23
|
+
actual_state?: unknown
|
|
24
|
+
observed_fingerprint?: string | null
|
|
25
|
+
status?: string
|
|
26
|
+
condition?: ActionConditionResult
|
|
27
|
+
verification?: { mode?: string; status: string }
|
|
28
|
+
evidence?: { action_id?: string }
|
|
29
|
+
verification_diagnostic?: { code: string; retryable: boolean }
|
|
30
|
+
observed?: { matched_count?: number | null }
|
|
31
|
+
timing?: TimingBreakdown
|
|
32
|
+
}
|
|
33
|
+
export function payloadOf(response: ToolCallResult): ToolPayload {
|
|
34
|
+
const text = response.content.find(item => item.type === 'text')?.text
|
|
35
|
+
return text ? JSON.parse(text) as ToolPayload : null as unknown as ToolPayload
|
|
36
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ToolsInteract } from '../../interact/index.js'
|
|
2
|
+
import type { DeviceInfo } from '../../types.js'
|
|
3
|
+
import { handleDebugger } from './debugger-handlers.js'
|
|
4
|
+
import { debuggerContracts } from '../schemas/debugger-schema.js'
|
|
5
|
+
import { debuggerSessions, iosDebuggerSessions } from '../../debugger/sessions.js'
|
|
6
|
+
import { withDebuggerGuard, guardDispatched } from '../../debugger/guard.js'
|
|
7
|
+
import { TraceError } from '../../observe/system-trace.js'
|
|
8
|
+
import { systemTraceTools } from '../schemas/system-trace-schema.js'
|
|
9
|
+
import { withTiming } from '../../utils/timing.js'
|
|
10
|
+
import { readinessSchema, readinessWaitSchema } from '../schemas/readiness-schema.js'
|
|
11
|
+
import { getAppReadiness } from '../../observe/readiness-service.js'
|
|
12
|
+
import { validateExpectedState } from '../schemas/condition-schema.js'
|
|
13
|
+
import { resolveTargetDevice, withResolvedDevice } from '../../utils/resolve-device.js'
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
ToolCallArgs,
|
|
17
|
+
ToolCallResult,
|
|
18
|
+
ToolHandler,
|
|
19
|
+
wrapResponse,
|
|
20
|
+
wrapToolError
|
|
21
|
+
} from '../common.js'
|
|
22
|
+
import { handleTerminateApp, handleResetAppData, handleInstallApp, handleBuildApp, handleBuildAndInstall, handleStartApp, handleRestartApp } from './manage.js'
|
|
23
|
+
import { handleGetLogs, handleCaptureScreenshot, handleCaptureDebugSnapshot, handleGetUITree, handleGetCurrentScreen, handleGetScreenFingerprint, handleStartLogStream, handleReadLogStream, handleStopLogStream } from './observe.js'
|
|
24
|
+
import { handleWaitForScreenChange, handleExpectScreen, handleExpectElementVisible, handleExpectState, handleAdjustControl, handleWaitForUI, handleWaitForUIChange, handleFindElement } from './interact.js'
|
|
25
|
+
import { handleTap, handleTapElement, handleSwipe, handleScrollToElement, handleTypeText, handlePressBack } from './input.js'
|
|
26
|
+
import { handleRunJourney } from './journey.js'
|
|
27
|
+
import { handleListDevices, handleGetSystemStatus, handleClassifyActionOutcome, handleGetNetworkActivity, handleSystemTrace } from './diagnostic.js'
|
|
28
|
+
import { correlatedHandler } from './action-execution.js'
|
|
29
|
+
|
|
30
|
+
const systemTraceToolNames = new Set(systemTraceTools.map(tool => tool.name))
|
|
31
|
+
|
|
32
|
+
export const toolHandlers: Record<string, ToolHandler> = {
|
|
33
|
+
...Object.fromEntries(systemTraceTools.map(({ name }) => [name, (args: ToolCallArgs) => handleSystemTrace(name, args)])),
|
|
34
|
+
get_app_readiness: async args => {
|
|
35
|
+
const parsed = readinessSchema.safeParse(args)
|
|
36
|
+
if (!parsed.success) throw new Error('INVALID_ARGUMENT: invalid readiness arguments')
|
|
37
|
+
return wrapResponse(await getAppReadiness(parsed.data))
|
|
38
|
+
},
|
|
39
|
+
wait_for_app_ready: async args => {
|
|
40
|
+
const parsed = readinessWaitSchema.safeParse(args)
|
|
41
|
+
if (!parsed.success) throw new Error('INVALID_ARGUMENT: invalid readiness arguments')
|
|
42
|
+
return wrapResponse(await getAppReadiness(parsed.data, true))
|
|
43
|
+
},
|
|
44
|
+
start_app: handleStartApp,
|
|
45
|
+
terminate_app: handleTerminateApp,
|
|
46
|
+
restart_app: handleRestartApp,
|
|
47
|
+
reset_app_data: handleResetAppData,
|
|
48
|
+
install_app: handleInstallApp,
|
|
49
|
+
build_app: handleBuildApp,
|
|
50
|
+
build_and_install: handleBuildAndInstall,
|
|
51
|
+
get_logs: handleGetLogs,
|
|
52
|
+
list_devices: handleListDevices,
|
|
53
|
+
get_system_status: handleGetSystemStatus,
|
|
54
|
+
capture_screenshot: handleCaptureScreenshot,
|
|
55
|
+
capture_debug_snapshot: handleCaptureDebugSnapshot,
|
|
56
|
+
get_ui_tree: handleGetUITree,
|
|
57
|
+
get_current_screen: handleGetCurrentScreen,
|
|
58
|
+
get_screen_fingerprint: handleGetScreenFingerprint,
|
|
59
|
+
wait_for_screen_change: handleWaitForScreenChange,
|
|
60
|
+
wait_for_ui_change: handleWaitForUIChange,
|
|
61
|
+
expect_screen: handleExpectScreen,
|
|
62
|
+
expect_element_visible: handleExpectElementVisible,
|
|
63
|
+
expect_state: handleExpectState,
|
|
64
|
+
adjust_control: handleAdjustControl,
|
|
65
|
+
wait_for_ui: handleWaitForUI,
|
|
66
|
+
find_element: handleFindElement,
|
|
67
|
+
tap: handleTap,
|
|
68
|
+
tap_element: handleTapElement,
|
|
69
|
+
run_journey: handleRunJourney,
|
|
70
|
+
swipe: handleSwipe,
|
|
71
|
+
scroll_to_element: handleScrollToElement,
|
|
72
|
+
type_text: handleTypeText,
|
|
73
|
+
press_back: handlePressBack,
|
|
74
|
+
start_log_stream: handleStartLogStream,
|
|
75
|
+
read_log_stream: handleReadLogStream,
|
|
76
|
+
stop_log_stream: handleStopLogStream,
|
|
77
|
+
classify_action_outcome: handleClassifyActionOutcome,
|
|
78
|
+
get_network_activity: handleGetNetworkActivity
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const name of ['capture_screenshot', 'get_ui_tree', 'get_screen_fingerprint', 'capture_debug_snapshot', 'get_logs', 'read_log_stream', 'run_journey', 'adjust_control']) {
|
|
82
|
+
toolHandlers[name] = correlatedHandler(name, toolHandlers[name], name === 'adjust_control', name === 'run_journey')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveGuardDevice(name: string, args: ToolCallArgs) {
|
|
86
|
+
const platform = args.platform as 'android' | 'ios' | undefined
|
|
87
|
+
const deviceId = args.deviceId as string | undefined
|
|
88
|
+
const stored = name === 'tap_element' && typeof args.elementId === 'string'
|
|
89
|
+
? ToolsInteract.getResolvedElementTarget(args.elementId, platform, deviceId) : null
|
|
90
|
+
return resolveTargetDevice(stored ?? { platform: platform === 'ios' ? 'ios' : 'android', deviceId })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function dispatchGuarded(device: DeviceInfo, args: ToolCallArgs, handler: ToolHandler, addTiming: (response: ToolCallResult) => ToolCallResult) {
|
|
94
|
+
const sessions = device.platform === 'ios' ? iosDebuggerSessions : debuggerSessions
|
|
95
|
+
return withResolvedDevice(device, () => withDebuggerGuard(device.platform, device.id, async () => {
|
|
96
|
+
const before = sessions.paused(device.id)
|
|
97
|
+
if (before) return wrapResponse({ success: false, failure_code: 'DEBUGGER_PAUSED', delivery_status: 'not_delivered', dispatch_started: false, debugger: before })
|
|
98
|
+
let response: ToolCallResult
|
|
99
|
+
try { response = await handler(args) } catch (error) {
|
|
100
|
+
if (!sessions.paused(device.id)) throw error
|
|
101
|
+
response = wrapResponse({ success: false })
|
|
102
|
+
}
|
|
103
|
+
const pause = sessions.paused(device.id)
|
|
104
|
+
if (pause) {
|
|
105
|
+
const item = response.content.find(item => item.type === 'text')
|
|
106
|
+
if (item?.text) {
|
|
107
|
+
const payload = JSON.parse(item.text)
|
|
108
|
+
Object.assign(payload, { success: false, failure_code: 'DEBUGGER_PAUSED', retryable: false,
|
|
109
|
+
dispatch_started: guardDispatched(), delivery_status: payload.success ? 'delivered' : guardDispatched() ? 'unknown' : 'not_delivered',
|
|
110
|
+
verification: { status: 'blocked', reason: 'DEBUGGER_PAUSED' }, debugger: pause })
|
|
111
|
+
item.text = JSON.stringify(payload)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return addTiming(response)
|
|
115
|
+
}))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function attachPausedObservation(name: string, args: ToolCallArgs, response: ToolCallResult) {
|
|
119
|
+
if (['capture_screenshot', 'get_ui_tree', 'get_screen_fingerprint', 'capture_debug_snapshot', 'get_logs', 'get_current_screen', 'read_log_stream'].includes(name)) {
|
|
120
|
+
const item = response.content.find(item => item.type === 'text')
|
|
121
|
+
if (item?.text) {
|
|
122
|
+
try {
|
|
123
|
+
const payload = JSON.parse(item.text)
|
|
124
|
+
const device = payload.device ?? payload.evidence?.device
|
|
125
|
+
const id = device?.id ?? args.deviceId
|
|
126
|
+
const sessions = (device?.platform ?? args.platform) === 'ios' ? iosDebuggerSessions : debuggerSessions
|
|
127
|
+
const pause = id ? sessions.paused(String(id)) : null
|
|
128
|
+
if (pause) item.text = JSON.stringify({ ...payload, debugger: pause })
|
|
129
|
+
} catch { /* Non-JSON observation */ }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function handleToolCall(name: string, args: ToolCallArgs = {}, signal?: AbortSignal) {
|
|
135
|
+
if (Object.hasOwn(debuggerContracts, name)) return handleDebugger(name, args, signal)
|
|
136
|
+
const handler = toolHandlers[name]
|
|
137
|
+
if (!handler) throw new Error(`Unknown tool: ${name}`)
|
|
138
|
+
|
|
139
|
+
const started = performance.now()
|
|
140
|
+
const measured: Record<string, number> = {}
|
|
141
|
+
const addTiming = (response: ToolCallResult) => {
|
|
142
|
+
if (!['start_app', 'restart_app', 'tap', 'tap_element', 'swipe', 'scroll_to_element', 'type_text', 'press_back', 'wait_for_ui', 'get_ui_tree', 'capture_screenshot', 'capture_debug_snapshot'].includes(name)) return response
|
|
143
|
+
const item = response.content.find(item => item.type === 'text')
|
|
144
|
+
if (item?.text) {
|
|
145
|
+
try {
|
|
146
|
+
const value = JSON.parse(item.text)
|
|
147
|
+
value.timing = { ...measured, ...value.timing, total_ms: performance.now() - started }
|
|
148
|
+
item.text = JSON.stringify(value)
|
|
149
|
+
} catch { /* Non-JSON text responses retain their original content. */ }
|
|
150
|
+
}
|
|
151
|
+
return response
|
|
152
|
+
}
|
|
153
|
+
return withTiming(measured, async () => {
|
|
154
|
+
try {
|
|
155
|
+
if (!systemTraceToolNames.has(name)) validateExpectedState(name, args)
|
|
156
|
+
const guarded = ['start_app', 'terminate_app', 'restart_app', 'reset_app_data', 'install_app', 'build_and_install', 'tap', 'tap_element', 'swipe', 'scroll_to_element', 'type_text', 'press_back', 'adjust_control', 'run_journey', 'wait_for_ui', 'wait_for_ui_change', 'wait_for_screen_change', 'expect_screen', 'expect_element_visible', 'expect_state'].includes(name)
|
|
157
|
+
if (guarded && (debuggerSessions.hasActiveSessions() || iosDebuggerSessions.hasActiveSessions())) {
|
|
158
|
+
const device = await resolveGuardDevice(name, args)
|
|
159
|
+
return dispatchGuarded(device, args, handler, addTiming)
|
|
160
|
+
}
|
|
161
|
+
const response = await handler(args)
|
|
162
|
+
attachPausedObservation(name, args, response)
|
|
163
|
+
return addTiming(response)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof TraceError) return { ...wrapResponse({ error: { tool: name, code: error.code, message: error.message } }), isError: true }
|
|
166
|
+
console.error(`Error executing tool ${name}:`, error)
|
|
167
|
+
return addTiming(wrapToolError(name, error))
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
const text = z.string().min(1).max(512)
|
|
3
|
+
const integer = z.number().int().safe().positive()
|
|
4
|
+
const timeout = z.number().int().min(100).max(30000)
|
|
5
|
+
const iosUdid = /^[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12}$/
|
|
6
|
+
const target = { platform: z.enum(['android', 'ios']), deviceId: text, appId: text.regex(/^[A-Za-z][\w-]*(?:\.[A-Za-z][\w-]*)+$/), processId: integer.optional() }
|
|
7
|
+
const session = { debugSessionId: text }
|
|
8
|
+
const stop = { ...session, stopId: text }
|
|
9
|
+
const specs = {
|
|
10
|
+
get_debugger_capabilities: { fields: target, command: 'capabilities', description: 'Probe optional platform debugger prerequisites and matching running debuggable processes; does not attach.' },
|
|
11
|
+
attach_debugger: { fields: target, command: 'attach', description: 'Attach an owned Android JDI or iOS simulator LLDB session. Requires an explicit device and debuggable app; choose processId when ambiguous. Stops suspend all app threads. Auto-detach: 60s paused, 5m idle, 30m total.' },
|
|
12
|
+
get_debugger_state: { fields: session, command: 'state', description: 'Read debugger state, current stop and breakpoint/cleanup status.' },
|
|
13
|
+
set_debugger_breakpoint: { fields: { ...session, className: text.regex(/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/).optional(), sourceFile: text.regex(/^\/[^\0\r\n]+$/).optional(), line: integer }, command: 'set', description: 'Set an exact source breakpoint: Android className or iOS absolute sourceFile (exactly one), plus line; unloaded classes/images remain pending. No silent line relocation.' },
|
|
14
|
+
remove_debugger_breakpoint: { fields: { ...session, breakpointId: text }, command: 'remove', description: 'Remove an owned breakpoint; repeated removal succeeds.' },
|
|
15
|
+
wait_for_debugger_event: { fields: { ...session, afterSequence: z.number().int().safe().min(0).optional() }, command: 'events', description: 'Wait for bounded debugger events after a sequence cursor. Empty timeout does not affect execution; overflow is explicit.' },
|
|
16
|
+
get_debugger_stack: { fields: { ...stop, threadId: text, offset: z.number().int().min(0).max(999).optional(), limit: z.number().int().min(1).max(100).optional() }, command: 'stack', description: 'Read bounded stack frames for a stopped thread; IDs expire on resume.' },
|
|
17
|
+
get_debugger_locals: { fields: { ...stop, frameId: text }, command: 'locals', description: 'Read at most 100 shallow locals. Sensitive values returned only on request; strings truncate at 256 characters; iOS aggregates return type summaries. No evaluation or field traversal.' },
|
|
18
|
+
step_debugger: { fields: { ...stop, threadId: text, kind: z.enum(['into', 'over', 'out']) }, command: 'step', description: 'Acknowledge one-shot line stepping and resume. Wait for a subsequent stop event separately.' },
|
|
19
|
+
resume_debugger: { fields: stop, command: 'resume', description: 'Resume owned debugger suspension and immediately expire stopped references.' },
|
|
20
|
+
detach_debugger: { fields: session, command: 'detach', description: 'Dispose the owned debugger connection, release owned suspension and remove its owned transport. Idempotent; retries incomplete cleanup.' }
|
|
21
|
+
}
|
|
22
|
+
// Derive JSON schemas and runtime validation from the same small Zod field vocabulary.
|
|
23
|
+
function jsonField(schema: z.ZodType): Record<string, unknown> {
|
|
24
|
+
if (schema instanceof z.ZodOptional) return jsonField(schema.unwrap() as z.ZodType)
|
|
25
|
+
if (schema instanceof z.ZodLiteral) return { type: 'string', enum: [schema.value] }
|
|
26
|
+
if (schema instanceof z.ZodEnum) return { type: 'string', enum: schema.options }
|
|
27
|
+
if (schema instanceof z.ZodString) {
|
|
28
|
+
const json = z.toJSONSchema(schema)
|
|
29
|
+
return { type: 'string', minLength: 1, maxLength: 512, ...(json.pattern ? { pattern: json.pattern } : {}) }
|
|
30
|
+
}
|
|
31
|
+
if (schema instanceof z.ZodNumber) return { type: 'integer', minimum: schema.minValue, maximum: schema.maxValue }
|
|
32
|
+
throw new Error('Unsupported debugger field schema')
|
|
33
|
+
}
|
|
34
|
+
export const debuggerContracts = Object.fromEntries(Object.entries(specs).map(([name, spec]) => [name, {
|
|
35
|
+
...spec, schema: z.object({ ...spec.fields, responseMode: z.enum(['compact', 'debug']).default('debug'), timeoutMs: timeout.default(name === 'wait_for_debugger_event' ? 1000 : 10000) }).strict().refine(a => name !== 'set_debugger_breakpoint' || (('className' in a && a.className !== undefined) !== ('sourceFile' in a && a.sourceFile !== undefined)), { message: 'Exactly one breakpoint source selector is required' }).refine(a => !('platform' in a) || a.platform !== 'ios' || ('deviceId' in a && typeof a.deviceId === 'string' && iosUdid.test(a.deviceId)), { message: 'iOS requires an explicit simulator UDID' })
|
|
36
|
+
}]))
|
|
37
|
+
export const debuggerTools = Object.entries(debuggerContracts).map(([name, spec]) => ({
|
|
38
|
+
name, description: spec.description,
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object' as const, additionalProperties: false,
|
|
41
|
+
...(['get_debugger_capabilities', 'attach_debugger'].includes(name) ? { allOf: [{ if: { properties: { platform: { const: 'ios' } } }, then: { properties: { deviceId: { pattern: iosUdid.source } } } }] } : {}),
|
|
42
|
+
...(name === 'set_debugger_breakpoint' ? { oneOf: [{ required: ['className'], not: { required: ['sourceFile'] } }, { required: ['sourceFile'], not: { required: ['className'] } }] } : {}),
|
|
43
|
+
required: Object.entries(spec.fields).filter(([, field]) => !field.isOptional()).map(([key]) => key),
|
|
44
|
+
properties: { responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' }, ...Object.fromEntries(Object.entries(spec.fields).map(([key, field]) => [key, jsonField(field)])), timeoutMs: { type: 'integer', minimum: 100, maximum: 30000, default: name === 'wait_for_debugger_event' ? 1000 : 10000 } }
|
|
45
|
+
}
|
|
46
|
+
}))
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
+
import { verificationModeSchema, responseModeSchema, verificationModeJson } from '../../contracts/execution-controls.js'
|
|
2
|
+
import { waitForSchema as waitFor, targetFields as target, exactlyOneTarget, targetJson, oneTargetJson } from '../../contracts/target.js'
|
|
3
|
+
export { tapElementControlsSchema } from '../../contracts/target.js'
|
|
4
|
+
import { selectorSchema as selector, selectorJson } from '../../contracts/selector.js'
|
|
1
5
|
import { z } from 'zod'
|
|
2
6
|
import { expectedStateSchema, expectedStateJson } from './condition-schema.js'
|
|
3
7
|
|
|
4
|
-
const
|
|
5
|
-
text: z.string().min(1).optional(), resource_id: z.string().min(1).optional(),
|
|
6
|
-
accessibility_id: z.string().min(1).optional(), contains: z.boolean().optional()
|
|
7
|
-
}).strict().refine(value => !!(value.text || value.resource_id || value.accessibility_id), 'Selector requires a target')
|
|
8
|
-
const verificationMode = z.enum(['none', 'light', 'full'])
|
|
8
|
+
const verificationMode = verificationModeSchema
|
|
9
9
|
const timeoutMs = z.number().int().min(100).max(60000).optional()
|
|
10
10
|
const pollIntervalMs = z.number().int().min(50).max(1000).optional()
|
|
11
|
-
const waitFor = z.object({
|
|
12
|
-
condition: z.enum(['exists', 'visible', 'clickable']).optional(),
|
|
13
|
-
timeoutMs: z.number().int().min(100).max(10000).optional(), pollIntervalMs,
|
|
14
|
-
match: z.object({ index: z.number().int().nonnegative().optional() }).strict().optional()
|
|
15
|
-
}).strict()
|
|
16
|
-
const target = { selector: selector.optional(), elementId: z.string().min(1).optional() }
|
|
17
|
-
const exactlyOneTarget = (value: { selector?: unknown, elementId?: string }) => Number(!!value.selector) + Number(!!value.elementId) === 1
|
|
18
11
|
const assertion = z.union([
|
|
19
12
|
z.object({ kind: z.literal('element_visible'), selector }).strict(),
|
|
20
13
|
z.object({ kind: z.literal('element_absent'), selector }).strict(),
|
|
@@ -28,25 +21,17 @@ const step = z.union([
|
|
|
28
21
|
z.object({ id, type: z.literal('wait'), selector, condition: z.enum(['exists', 'not_exists', 'visible', 'clickable']).optional(), timeoutMs, pollIntervalMs }).strict(),
|
|
29
22
|
z.object({ id, type: z.literal('assert'), assertion, timeoutMs, pollIntervalMs }).strict()
|
|
30
23
|
])
|
|
31
|
-
export const tapElementControlsSchema = z.object({
|
|
32
|
-
elementId: z.string().min(1).optional(), selector: selector.optional(), waitFor: waitFor.optional()
|
|
33
|
-
}).strict().refine(value => Number(!!value.elementId) + Number(!!value.selector) === 1 && (!value.elementId || !value.waitFor))
|
|
34
24
|
|
|
35
25
|
export const journeySchema = z.object({
|
|
36
26
|
platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional(),
|
|
37
27
|
systemTraceId: z.string().uuid().optional(),
|
|
38
|
-
responseMode:
|
|
28
|
+
responseMode: responseModeSchema.default('compact'), captureOnFailure: z.boolean().default(false),
|
|
39
29
|
defaults: z.object({ verificationMode: verificationMode.optional(), actionTimeoutMs: z.number().int().finite().optional(), verificationTimeoutMs: z.number().int().finite().optional() }).strict().optional(),
|
|
40
30
|
steps: z.array(step).min(1).max(50)
|
|
41
31
|
}).strict().refine(value => new Set(value.steps.map(step => step.id)).size === value.steps.length, 'Step IDs must be unique')
|
|
42
32
|
|
|
43
|
-
const selectorJson = { type: 'object', additionalProperties: false, properties: {
|
|
44
|
-
text: { type: 'string', minLength: 1 }, resource_id: { type: 'string', minLength: 1 }, accessibility_id: { type: 'string', minLength: 1 }, contains: { type: 'boolean' }
|
|
45
|
-
}, anyOf: ['text', 'resource_id', 'accessibility_id'].map(key => ({ required: [key] })) }
|
|
46
|
-
const targetJson = { selector: selectorJson, elementId: { type: 'string', minLength: 1 } }
|
|
47
|
-
const oneTargetJson = [{ required: ['selector'], not: { required: ['elementId'] } }, { required: ['elementId'], not: { required: ['selector'] } }]
|
|
48
33
|
const intervalJson = { timeoutMs: { type: 'integer', minimum: 100, maximum: 60000 }, pollIntervalMs: { type: 'integer', minimum: 50, maximum: 1000 } }
|
|
49
|
-
const modeJson =
|
|
34
|
+
const modeJson = verificationModeJson
|
|
50
35
|
const objectJson = (properties: Record<string, unknown>, required: string[], extra = {}) => ({ type: 'object', additionalProperties: false, properties, required, ...extra })
|
|
51
36
|
export const journeyStepsJson = {
|
|
52
37
|
type: 'array', minItems: 1, maxItems: 50, items: { oneOf: [
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
const target = { platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional(), appId: z.string().regex(/^[a-zA-Z0-9._-]+$/).optional(), template: z.enum(['System Trace', 'Time Profiler', 'Animation Hitches', 'Hangs']).optional() }
|
|
3
|
+
const validTarget = (value: { platform: string; deviceId?: string; appId?: string; template?: string }) =>
|
|
4
|
+
value.platform === 'ios' ? !!value.deviceId : value.appId === undefined && value.template === undefined
|
|
5
|
+
export const traceTargetSchema = z.object(target).strict().refine(validTarget)
|
|
6
|
+
export const traceStartSchema = z.object({ ...target, durationMs: z.number().int().min(1000).max(120000).default(30000) }).strict().refine(validTarget).refine(value => value.platform !== 'ios' || !!value.appId)
|
|
7
|
+
export const traceControlSchema = z.object({ traceId: z.string().uuid() }).strict()
|
|
8
|
+
const properties = { platform: { type: 'string', enum: ['android', 'ios'] }, deviceId: { type: 'string', minLength: 1 }, appId: { type: 'string', minLength: 1, pattern: '^[a-zA-Z0-9._-]+$', description: 'iOS only: running development app bundle identifier; required for iOS start.' }, template: { type: 'string', enum: ['System Trace', 'Time Profiler', 'Animation Hitches', 'Hangs'], description: 'iOS only. Default Time Profiler; initial simulator support is Time Profiler only.' } }
|
|
9
|
+
const platformRules = (start: boolean) => ({ oneOf: [
|
|
10
|
+
{ properties: { platform: { const: 'android' } }, not: { anyOf: [{ required: ['appId'] }, { required: ['template'] }] } },
|
|
11
|
+
{ properties: { platform: { const: 'ios' } }, required: start ? ['deviceId', 'appId'] : ['deviceId'] }
|
|
12
|
+
] })
|
|
13
|
+
export const systemTraceTools = [
|
|
14
|
+
{ name: 'get_system_trace_capabilities', description: 'Probe Android Perfetto or iOS Instruments prerequisites without recording. iOS requires explicit booted simulator deviceId; reports template matrix and unknown profiling permissions.',
|
|
15
|
+
inputSchema: { type: 'object' as const, additionalProperties: false, required: ['platform'], properties, ...platformRules(false) } },
|
|
16
|
+
{ name: 'start_system_trace', description: 'Start bounded Android Perfetto or iOS simulator Time Profiler recording, returning an owned trace_id after readiness. iOS requires explicit deviceId and running appId. Run an existing journey with systemTraceId, then stop to retrieve a local artifact or cancel to discard. Traces can contain sensitive system metadata, logs and paths; local user-managed retention, no upload.',
|
|
17
|
+
inputSchema: { type: 'object' as const, additionalProperties: false, required: ['platform'], properties: { ...properties, durationMs: { type: 'integer', minimum: 1000, maximum: 120000, default: 30000 } }, ...platformRules(true) } },
|
|
18
|
+
...['get_system_trace_status', 'stop_system_trace', 'cancel_system_trace'].map(name => ({ name,
|
|
19
|
+
description: name === 'get_system_trace_status' ? 'Reconcile an owned trace and retrieve its state; finalise duration-completed capture.' :
|
|
20
|
+
name === 'stop_system_trace' ? 'Stop owned capture gracefully and retrieve a local Perfetto file or validated Instruments .trace bundle. Retry after a transfer/cleanup failure.' : 'Stop owned capture and discard partial files. Already completed local artifacts are retained.',
|
|
21
|
+
inputSchema: { type: 'object' as const, additionalProperties: false, required: ['traceId'], properties: { traceId: { type: 'string', format: 'uuid' } } }
|
|
22
|
+
}))
|
|
23
|
+
]
|