mobile-debug-mcp 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/mcp-builder/SKILL.md +44 -64
- package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
- package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
- package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
- package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
- package/.agents/skills/test-authoring/SKILL.md +7 -6
- package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
- package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
- package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +23 -2
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +158 -124
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +20 -6
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -4
- package/dist/contracts/execution-controls.js +24 -0
- package/dist/contracts/selector.js +8 -0
- package/dist/contracts/target.js +15 -0
- package/dist/contracts/ui.js +1 -0
- package/dist/debugger/android.js +79 -0
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/guard.js +30 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +304 -0
- package/dist/debugger/worker.js +176 -0
- package/dist/interact/adjustment/adjustment-context.js +41 -0
- package/dist/interact/adjustment/adjustment-gestures.js +55 -0
- package/dist/interact/adjustment/adjustment-plan.js +18 -0
- package/dist/interact/adjustment/adjustment-resolution.js +127 -0
- package/dist/interact/adjustment/adjustment-result.js +19 -0
- package/dist/interact/adjustment/adjustment-semantic.js +64 -0
- package/dist/interact/adjustment/adjustment-verification.js +35 -0
- package/dist/interact/adjustment/adjustment.js +52 -0
- package/dist/interact/adjustment/control-geometry.js +87 -0
- package/dist/interact/adjustment/control-values.js +136 -0
- package/dist/interact/adjustment/slider-labels.js +84 -0
- package/dist/interact/assertions/screen-assertion.js +37 -0
- package/dist/interact/assertions/state-assertion-context.js +35 -0
- package/dist/interact/assertions/state-assertion.js +76 -0
- package/dist/interact/assertions/state-comparison.js +141 -0
- package/dist/interact/assertions/visibility-assertion.js +34 -0
- package/dist/interact/conditional-action.js +12 -7
- package/dist/interact/index.js +65 -3074
- package/dist/interact/input-service.js +1 -0
- package/dist/interact/interaction-trace.js +21 -0
- package/dist/interact/model.js +1 -0
- package/dist/interact/observation.js +1 -0
- package/dist/interact/targeting/candidate-ancestor.js +58 -0
- package/dist/interact/targeting/candidate-result.js +23 -0
- package/dist/interact/targeting/candidate-scoring.js +50 -0
- package/dist/interact/targeting/element-helpers.js +216 -0
- package/dist/interact/targeting/find-element.js +65 -0
- package/dist/interact/targeting/nearby-control.js +56 -0
- package/dist/interact/targeting/resolved-elements.js +21 -0
- package/dist/interact/targeting/tap-target.js +17 -0
- package/dist/interact/targeting/target-scope.js +104 -0
- package/dist/interact/waits/legacy-log-wait.js +38 -0
- package/dist/interact/waits/legacy-wait-observations.js +103 -0
- package/dist/interact/waits/legacy-wait-state.js +34 -0
- package/dist/interact/waits/legacy-wait.js +48 -0
- package/dist/interact/waits/screen-wait.js +49 -0
- package/dist/interact/waits/ui-change-helpers.js +157 -0
- package/dist/interact/waits/ui-change-state.js +18 -0
- package/dist/interact/waits/ui-change-wait.js +77 -0
- package/dist/interact/waits/ui-wait-condition.js +56 -0
- package/dist/interact/waits/ui-wait-state.js +38 -0
- package/dist/interact/waits/ui-wait.js +106 -0
- package/dist/manage/android.js +55 -51
- package/dist/manage/index.js +38 -75
- package/dist/manage/ios/build-types.js +1 -0
- package/dist/manage/ios/build.js +210 -0
- package/dist/manage/ios/project-discovery.js +29 -0
- package/dist/manage/ios/scheme.js +18 -0
- package/dist/manage/ios.js +42 -256
- package/dist/observe/android/context.js +1 -0
- package/dist/observe/android/current-screen.js +55 -0
- package/dist/observe/android/fingerprint.js +13 -0
- package/dist/observe/android/log-stream.js +132 -0
- package/dist/observe/android/logs.js +107 -0
- package/dist/observe/android/screenshot.js +70 -0
- package/dist/observe/android/ui-tree.js +107 -0
- package/dist/observe/android.js +16 -520
- package/dist/observe/evidence.js +122 -76
- package/dist/observe/index.js +16 -263
- package/dist/observe/ios/context.js +16 -0
- package/dist/observe/ios/fingerprint.js +12 -0
- package/dist/observe/ios/log-stream.js +105 -0
- package/dist/observe/ios/logs.js +168 -0
- package/dist/observe/ios/screenshot.js +22 -0
- package/dist/observe/ios/ui-elements.js +241 -0
- package/dist/observe/ios/ui-tree.js +137 -0
- package/dist/observe/ios.js +17 -715
- package/dist/observe/logs/model.js +1 -0
- package/dist/{network → observe/network}/index.js +2 -2
- package/dist/observe/readiness-service.js +13 -2
- package/dist/observe/readiness.js +44 -18
- package/dist/observe/screenshot-encoding.js +41 -0
- package/dist/observe/snapshot-collection.js +79 -0
- package/dist/observe/snapshot-logs.js +30 -0
- package/dist/observe/snapshot-metadata.js +43 -31
- package/dist/observe/snapshot-semantic.js +115 -0
- package/dist/observe/system-trace-android.js +135 -0
- package/dist/observe/system-trace.js +294 -0
- package/dist/runtime/action-results.js +270 -0
- package/dist/runtime/clock.js +4 -0
- package/dist/server/common.js +1 -256
- package/dist/server/definitions/assert.js +162 -0
- package/dist/server/definitions/diagnostic.js +118 -0
- package/dist/server/definitions/input.js +274 -0
- package/dist/server/definitions/manage.js +194 -0
- package/dist/server/definitions/observe.js +149 -0
- package/dist/server/definitions/target.js +138 -0
- package/dist/server/definitions/wait.js +105 -0
- package/dist/server/handlers/action-execution.js +163 -0
- package/dist/server/handlers/correlation.js +23 -0
- package/dist/server/handlers/debugger-handlers.js +48 -0
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +69 -0
- package/dist/server/handlers/handler-types.js +1 -0
- package/dist/server/handlers/input.js +154 -0
- package/dist/server/handlers/interact.js +113 -0
- package/dist/server/handlers/journey-assertions.js +35 -0
- package/dist/server/handlers/journey.js +147 -0
- package/dist/server/handlers/manage.js +119 -0
- package/dist/server/handlers/observe.js +95 -0
- package/dist/server/handlers/payload.js +4 -0
- package/dist/server/handlers/tool-handlers.js +160 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/schemas/debugger-schema.js +47 -0
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +8 -22
- package/dist/server/schemas/system-trace-schema.js +17 -0
- package/dist/server/tool-definitions.js +62 -1134
- package/dist/server-core.js +4 -4
- package/dist/server.js +34 -0
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -438
- package/dist/system/ios-status.js +132 -0
- package/dist/system/ios.js +11 -8
- package/dist/system/model.js +1 -0
- package/dist/system/probes.js +105 -0
- package/dist/system/status-summary.js +43 -0
- package/dist/system/unsupported-status.js +52 -0
- package/dist/utils/android/commands.js +89 -0
- package/dist/utils/android/gradle.js +139 -0
- package/dist/utils/android/logs.js +103 -0
- package/dist/utils/android/ui-tree.js +233 -0
- package/dist/utils/android/utils.js +6 -564
- package/dist/utils/cli/idb/idb-helper.js +4 -6
- package/dist/utils/cli/idb/install-idb.js +13 -9
- package/dist/utils/cli/ios/preflight-ios.js +42 -38
- package/dist/utils/exec.js +2 -0
- package/dist/utils/ios/utils.js +45 -46
- package/dist/utils/java.js +75 -57
- package/dist/utils/operation-budget.js +2 -0
- package/dist/utils/resolve-device.js +6 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +48 -0
- package/docs/README.md +38 -0
- package/docs/benchmarks/README.md +18 -0
- package/docs/benchmarks/interaction-latency/README.md +14 -0
- package/docs/benchmarks/interaction-latency/final.md +4 -1
- package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
- package/docs/benchmarks/ui-tree-performance/README.md +24 -16
- package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
- package/docs/code-structure.md +118 -0
- package/docs/specs/001-state-verification/spec.md +16 -14
- package/docs/specs/002-richer-element-identity/spec.md +12 -10
- package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
- package/docs/specs/004-action-verification-routing/spec.md +3 -3
- package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
- package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
- package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
- package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
- package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
- package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
- package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
- package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
- package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
- package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
- package/docs/specs/014-actionability-resolution/spec.md +20 -18
- package/docs/specs/015-adjustable-control-support/spec.md +17 -15
- package/docs/specs/016-environment-tools/spec.md +20 -20
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
- package/docs/specs/020-conditional-actions/intent.md +5 -3
- package/docs/specs/020-conditional-actions/plan.md +8 -9
- package/docs/specs/020-conditional-actions/spec.md +4 -2
- package/docs/specs/021-app-readiness-signals/intent.md +4 -2
- package/docs/specs/021-app-readiness-signals/plan.md +7 -6
- package/docs/specs/021-app-readiness-signals/spec.md +3 -1
- package/docs/specs/022-correlated-evidence/intent.md +4 -4
- package/docs/specs/022-correlated-evidence/plan.md +10 -11
- package/docs/specs/022-correlated-evidence/spec.md +3 -1
- package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
- package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
- package/docs/specs/023-android-system-traces/intent.md +41 -0
- package/docs/specs/023-android-system-traces/plan.md +115 -0
- package/docs/specs/023-android-system-traces/spec.md +122 -0
- package/docs/specs/024-android-heap-dump-capture/intent.md +41 -0
- package/docs/specs/025-android-runtime-debugger/intent.md +41 -0
- package/docs/specs/025-android-runtime-debugger/plan.md +106 -0
- package/docs/specs/025-android-runtime-debugger/spec.md +183 -0
- package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
- package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
- package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
- package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
- package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
- package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
- package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
- package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
- package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
- package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
- package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
- package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
- package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
- package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
- package/docs/specs/030-android-coroutine-validation/report.md +240 -0
- package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
- package/docs/tools/TOOLS.md +31 -16
- package/docs/tools/{evidence.md → correlated-evidence.md} +14 -3
- package/docs/tools/debugger.md +180 -0
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +64 -25
- package/docs/tools/system-traces.md +134 -0
- package/docs/tools/system.md +12 -5
- package/package.json +11 -8
- package/scripts/architecture-graph.mjs +69 -0
- package/scripts/check-architecture.mjs +47 -0
- package/scripts/code-quality-baseline.json +1 -0
- package/scripts/code-quality.mjs +161 -0
- package/src/contracts/execution-controls.ts +26 -0
- package/src/contracts/selector.ts +17 -0
- package/src/contracts/target.ts +19 -0
- package/src/contracts/ui.ts +94 -0
- package/src/debugger/android.ts +66 -0
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +31 -0
- package/src/debugger/java/DebuggerWorker.java +166 -0
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +327 -0
- package/src/debugger/worker.ts +135 -0
- package/src/interact/adjustment/adjustment-context.ts +83 -0
- package/src/interact/adjustment/adjustment-gestures.ts +48 -0
- package/src/interact/adjustment/adjustment-plan.ts +23 -0
- package/src/interact/adjustment/adjustment-resolution.ts +104 -0
- package/src/interact/adjustment/adjustment-result.ts +34 -0
- package/src/interact/adjustment/adjustment-semantic.ts +53 -0
- package/src/interact/adjustment/adjustment-verification.ts +60 -0
- package/src/interact/adjustment/adjustment.ts +49 -0
- package/src/interact/adjustment/control-geometry.ts +120 -0
- package/src/interact/adjustment/control-values.ts +141 -0
- package/src/interact/adjustment/slider-labels.ts +84 -0
- package/src/interact/assertions/screen-assertion.ts +39 -0
- package/src/interact/assertions/state-assertion-context.ts +48 -0
- package/src/interact/assertions/state-assertion.ts +64 -0
- package/src/interact/assertions/state-comparison.ts +168 -0
- package/src/interact/assertions/visibility-assertion.ts +41 -0
- package/src/interact/conditional-action.ts +18 -12
- package/src/interact/index.ts +76 -3491
- package/src/interact/input-service.ts +11 -0
- package/src/interact/interaction-trace.ts +36 -0
- package/src/interact/model.ts +109 -0
- package/src/interact/observation.ts +7 -0
- package/src/interact/targeting/candidate-ancestor.ts +49 -0
- package/src/interact/targeting/candidate-result.ts +24 -0
- package/src/interact/targeting/candidate-scoring.ts +42 -0
- package/src/interact/targeting/element-helpers.ts +243 -0
- package/src/interact/targeting/find-element.ts +47 -0
- package/src/interact/targeting/nearby-control.ts +46 -0
- package/src/interact/targeting/resolved-elements.ts +20 -0
- package/src/interact/targeting/tap-target.ts +22 -0
- package/src/interact/targeting/target-scope.ts +121 -0
- package/src/interact/waits/legacy-log-wait.ts +30 -0
- package/src/interact/waits/legacy-wait-observations.ts +55 -0
- package/src/interact/waits/legacy-wait-state.ts +48 -0
- package/src/interact/waits/legacy-wait.ts +34 -0
- package/src/interact/waits/screen-wait.ts +37 -0
- package/src/interact/waits/ui-change-helpers.ts +184 -0
- package/src/interact/waits/ui-change-state.ts +26 -0
- package/src/interact/waits/ui-change-wait.ts +59 -0
- package/src/interact/waits/ui-wait-condition.ts +52 -0
- package/src/interact/waits/ui-wait-state.ts +55 -0
- package/src/interact/waits/ui-wait.ts +84 -0
- package/src/manage/android.ts +56 -50
- package/src/manage/index.ts +63 -93
- package/src/manage/ios/build-types.ts +14 -0
- package/src/manage/ios/build.ts +230 -0
- package/src/manage/ios/project-discovery.ts +26 -0
- package/src/manage/ios/scheme.ts +16 -0
- package/src/manage/ios.ts +65 -298
- package/src/observe/android/context.ts +1 -0
- package/src/observe/android/current-screen.ts +63 -0
- package/src/observe/android/fingerprint.ts +13 -0
- package/src/observe/android/log-stream.ts +127 -0
- package/src/observe/android/logs.ts +110 -0
- package/src/observe/android/screenshot.ts +79 -0
- package/src/observe/android/ui-tree.ts +113 -0
- package/src/observe/android.ts +17 -531
- package/src/observe/evidence.ts +130 -71
- package/src/observe/index.ts +24 -286
- package/src/observe/ios/context.ts +22 -0
- package/src/observe/ios/fingerprint.ts +12 -0
- package/src/observe/ios/log-stream.ts +103 -0
- package/src/observe/ios/logs.ts +179 -0
- package/src/observe/ios/screenshot.ts +26 -0
- package/src/observe/ios/ui-elements.ts +266 -0
- package/src/observe/ios/ui-tree.ts +143 -0
- package/src/observe/ios.ts +18 -749
- package/src/observe/logs/model.ts +11 -0
- package/src/{network → observe/network}/index.ts +2 -2
- package/src/observe/readiness-service.ts +10 -3
- package/src/observe/readiness.ts +49 -22
- package/src/observe/screenshot-encoding.ts +32 -0
- package/src/observe/snapshot-collection.ts +69 -0
- package/src/observe/snapshot-logs.ts +26 -0
- package/src/observe/snapshot-metadata.ts +51 -31
- package/src/observe/snapshot-semantic.ts +153 -0
- package/src/observe/system-trace-android.ts +133 -0
- package/src/observe/system-trace.ts +227 -0
- package/src/runtime/action-results.ts +376 -0
- package/src/runtime/clock.ts +9 -0
- package/src/server/common.ts +9 -359
- package/src/server/definitions/assert.ts +164 -0
- package/src/server/definitions/diagnostic.ts +120 -0
- package/src/server/definitions/input.ts +276 -0
- package/src/server/definitions/manage.ts +196 -0
- package/src/server/definitions/observe.ts +151 -0
- package/src/server/definitions/target.ts +140 -0
- package/src/server/definitions/wait.ts +107 -0
- package/src/server/handlers/action-execution.ts +164 -0
- package/src/server/handlers/correlation.ts +20 -0
- package/src/server/handlers/debugger-handlers.ts +39 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +79 -0
- package/src/server/handlers/handler-types.ts +7 -0
- package/src/server/handlers/input.ts +173 -0
- package/src/server/handlers/interact.ts +135 -0
- package/src/server/handlers/journey-assertions.ts +39 -0
- package/src/server/handlers/journey.ts +132 -0
- package/src/server/handlers/manage.ts +141 -0
- package/src/server/handlers/observe.ts +116 -0
- package/src/server/handlers/payload.ts +36 -0
- package/src/server/handlers/tool-handlers.ts +159 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/schemas/debugger-schema.ts +43 -0
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +8 -22
- package/src/server/schemas/system-trace-schema.ts +17 -0
- package/src/server/tool-definitions.ts +69 -1140
- package/src/server-core.ts +4 -4
- package/src/server.ts +25 -1
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -523
- package/src/system/ios-status.ts +147 -0
- package/src/system/ios.ts +14 -10
- package/src/system/model.ts +41 -0
- package/src/system/probes.ts +114 -0
- package/src/system/status-summary.ts +47 -0
- package/src/system/unsupported-status.ts +55 -0
- package/src/types.ts +16 -106
- package/src/utils/android/commands.ts +81 -0
- package/src/utils/android/gradle.ts +132 -0
- package/src/utils/android/logs.ts +103 -0
- package/src/utils/android/ui-tree.ts +239 -0
- package/src/utils/android/utils.ts +16 -556
- package/src/utils/cli/idb/check-idb.ts +4 -4
- package/src/utils/cli/idb/idb-helper.ts +6 -8
- package/src/utils/cli/idb/install-idb.ts +15 -9
- package/src/utils/cli/ios/preflight-ios.ts +39 -31
- package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
- package/src/utils/exec.ts +2 -0
- package/src/utils/ios/utils.ts +57 -61
- package/src/utils/java.ts +71 -51
- package/src/utils/operation-budget.ts +2 -0
- package/src/utils/resolve-device.ts +11 -6
- package/src/utils/ui/index.ts +28 -28
- package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
- package/test/device/automated/debugger/coroutine-support.ts +140 -0
- package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
- package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
- package/test/device/automated/debugger/runtime.android.test.ts +219 -0
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/system-trace.smoke.ts +104 -0
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3536 -0
- package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
- package/test/fixtures/coroutine-app/README.md +48 -0
- package/test/fixtures/coroutine-app/build.ts +35 -0
- package/test/fixtures/debugger-app/MainActivity.java +67 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/NoLocals.java +7 -0
- package/test/fixtures/debugger-app/README.md +65 -0
- package/test/fixtures/debugger-app/build.ts +39 -0
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/helpers/perfetto.ts +59 -0
- package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
- package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
- package/test/unit/debugger/diagnostics.test.ts +80 -0
- package/test/unit/debugger/guards.test.ts +50 -0
- package/test/unit/debugger/native.test.ts +33 -0
- package/test/unit/debugger/preparation.test.ts +32 -0
- package/test/unit/debugger/protocol.test.ts +15 -0
- package/test/unit/debugger/sessions.test.ts +83 -0
- package/test/unit/interact/adjustment_service.test.ts +42 -0
- package/test/unit/interact/adjustment_verification.test.ts +35 -0
- package/test/unit/interact/clock_dependencies.test.ts +63 -0
- package/test/unit/interact/element_helpers.test.ts +19 -0
- package/test/unit/interact/legacy_wait_service.test.ts +17 -0
- package/test/unit/interact/state_comparison.test.ts +20 -0
- package/test/unit/interact/target_scope.test.ts +35 -0
- package/test/unit/network/get_network_activity.test.ts +1 -1
- package/test/unit/observe/readiness.test.ts +3 -1
- package/test/unit/observe/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -0
- package/test/unit/observe/system-trace-android.test.ts +52 -0
- package/test/unit/observe/system-trace.test.ts +166 -0
- package/test/unit/quality/architecture.test.ts +60 -0
- package/test/unit/quality/code-quality.test.ts +58 -0
- package/test/unit/quality/interaction_boundaries.test.ts +24 -0
- package/test/unit/server/conditional_actions.test.ts +1 -1
- package/test/unit/server/conditional_schema.test.ts +2 -2
- package/test/unit/server/debugger-response.test.ts +63 -0
- package/test/unit/server/system-trace.test.ts +64 -0
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/tool-handlers.js +0 -893
- package/docs/ROADMAP.md +0 -646
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
- package/docs/specs/baseline-spec-v0.md +0 -312
- package/docs/specs/mcp-tooling-spec-v1.md +0 -446
- package/src/server/tool-handlers.ts +0 -923
- /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
- /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
- /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
- /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { isBuiltin } from 'node:module'
|
|
4
|
+
import { extractGraph } from 'archunit'
|
|
5
|
+
|
|
6
|
+
// ArchUnitTS 2.5.4 extracts static imports. Add re-exports, import types and
|
|
7
|
+
// literal dynamic imports using the same project's TypeScript resolution.
|
|
8
|
+
export async function architectureGraph(configFile = 'tsconfig.json') {
|
|
9
|
+
const configPath = path.resolve(configFile)
|
|
10
|
+
const root = path.dirname(configPath)
|
|
11
|
+
const config = ts.readConfigFile(configPath, ts.sys.readFile)
|
|
12
|
+
if (config.error) throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
|
|
13
|
+
const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, root)
|
|
14
|
+
if (parsed.errors.length) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
15
|
+
getCanonicalFileName: file => file, getCurrentDirectory: () => root, getNewLine: () => '\n'
|
|
16
|
+
}))
|
|
17
|
+
const graph = await extractGraph(configPath, { clearCache: true })
|
|
18
|
+
const local = file => path.relative(root, file).split(path.sep).join('/')
|
|
19
|
+
const edges = new Map(graph.map(edge => [JSON.stringify([edge.source, edge.target]), edge]))
|
|
20
|
+
const recorded = new Set()
|
|
21
|
+
for (const file of parsed.fileNames) {
|
|
22
|
+
const code = ts.sys.readFile(file)
|
|
23
|
+
if (code === undefined) throw new Error(`Cannot read architecture source: ${file}`)
|
|
24
|
+
const ast = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true)
|
|
25
|
+
const source = local(file)
|
|
26
|
+
function add(specifier, typeOnly = false) {
|
|
27
|
+
const module = specifier.text
|
|
28
|
+
const resolved = ts.resolveModuleName(module, file, parsed.options, ts.sys).resolvedModule
|
|
29
|
+
if (!resolved && !isBuiltin(module)) throw new Error(`Unresolved dependency: ${source} -> ${module}`)
|
|
30
|
+
const external = !resolved || !!resolved.isExternalLibraryImport
|
|
31
|
+
const target = resolved ? local(resolved.resolvedFileName) : module.replace(/^(?!node:)/, 'node:')
|
|
32
|
+
const key = JSON.stringify([source, target])
|
|
33
|
+
const kind = typeOnly ? 'type' : 'value'
|
|
34
|
+
const previous = edges.get(key)
|
|
35
|
+
// Replace the initial static classification, then union repeated dependencies.
|
|
36
|
+
if (previous && recorded.has(key)) {
|
|
37
|
+
previous.importKinds = [...new Set([...previous.importKinds, kind])]
|
|
38
|
+
} else edges.set(key, { source, target, external, importKinds: [kind] })
|
|
39
|
+
recorded.add(key)
|
|
40
|
+
}
|
|
41
|
+
function visit(node) {
|
|
42
|
+
if (ts.isImportDeclaration(node)) {
|
|
43
|
+
const clause = node.importClause
|
|
44
|
+
const bindings = clause?.namedBindings
|
|
45
|
+
const typeOnly = clause?.isTypeOnly || (!clause?.name && bindings && ts.isNamedImports(bindings) &&
|
|
46
|
+
bindings.elements.length > 0 && bindings.elements.every(element => element.isTypeOnly))
|
|
47
|
+
add(node.moduleSpecifier, !!typeOnly)
|
|
48
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
49
|
+
const bindings = node.exportClause
|
|
50
|
+
const typeOnly = node.isTypeOnly || (bindings && ts.isNamedExports(bindings) &&
|
|
51
|
+
bindings.elements.length > 0 && bindings.elements.every(element => element.isTypeOnly))
|
|
52
|
+
add(node.moduleSpecifier, !!typeOnly)
|
|
53
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
|
|
54
|
+
if (!node.moduleReference.expression) throw new Error(`Missing import target in ${source}`)
|
|
55
|
+
add(node.moduleReference.expression, node.isTypeOnly)
|
|
56
|
+
} else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
|
|
57
|
+
add(node.argument.literal, true)
|
|
58
|
+
} else if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
59
|
+
(ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
|
|
60
|
+
const argument = node.arguments[0]
|
|
61
|
+
if (!argument || !ts.isStringLiteralLike(argument)) throw new Error(`Nonliteral dependency in ${source}; architecture cannot resolve it`)
|
|
62
|
+
add(argument)
|
|
63
|
+
}
|
|
64
|
+
ts.forEachChild(node, visit)
|
|
65
|
+
}
|
|
66
|
+
visit(ast)
|
|
67
|
+
}
|
|
68
|
+
return [...edges.values()]
|
|
69
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { gatherDependOnFileViolations, gatherCycleViolations, projectEdges, perEdge, perInternalEdge } from 'archunit'
|
|
4
|
+
import { architectureGraph } from './architecture-graph.mjs'
|
|
5
|
+
|
|
6
|
+
export const architectureRules = [
|
|
7
|
+
{
|
|
8
|
+
name: 'Shared contracts stay independent of application implementations',
|
|
9
|
+
source: /^src\/contracts\//,
|
|
10
|
+
forbidden: /^src\/(?!contracts\/)/
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'Capabilities and shared modules cannot import the MCP server layer',
|
|
14
|
+
source: /^src\/(?!server(?:\/|-core\.ts$|\.ts$))/,
|
|
15
|
+
forbidden: /^src\/server(?:\/|-core\.ts$|\.ts$)/
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Pure interaction calculations cannot import native I/O or service facades',
|
|
19
|
+
source: /^src\/interact\/(?:adjustment\/(?:adjustment-plan|control-values|control-geometry|slider-labels)|targeting\/(?:candidate-scoring|candidate-ancestor|candidate-result|nearby-control|element-helpers|resolved-elements|tap-target|target-scope)|waits\/(?:ui-change-helpers|ui-wait-condition)|assertions\/state-comparison)\.ts$/,
|
|
20
|
+
forbidden: /^(?:node:(?:child_process|fs|net|http|https)(?:\/|$)|src\/(?:utils\/(?:android|ios)\/|(?:interact|observe|manage)\/(?:index|android|ios)\.ts$))/
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
const filter = regExp => ({ regExp, options: { target: 'path', matching: 'partial' } })
|
|
24
|
+
|
|
25
|
+
export function checkArchitecture(graph) {
|
|
26
|
+
const projected = projectEdges(graph, perEdge())
|
|
27
|
+
const results = architectureRules.map(rule => ({
|
|
28
|
+
name: rule.name,
|
|
29
|
+
violations: gatherDependOnFileViolations(projected, [filter(rule.source)], [filter(rule.forbidden)], true)
|
|
30
|
+
}))
|
|
31
|
+
const runtime = graph.filter(edge => edge.importKinds.includes('value') || !edge.importKinds.includes('type'))
|
|
32
|
+
results.push({ name: 'Production runtime dependencies must be cycle-free',
|
|
33
|
+
violations: gatherCycleViolations(projectEdges(runtime, perInternalEdge()), []) })
|
|
34
|
+
return results
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
const graph = await architectureGraph(process.argv[2])
|
|
39
|
+
const results = checkArchitecture(graph)
|
|
40
|
+
const failures = results.filter(result => result.violations.length)
|
|
41
|
+
for (const result of failures) console.error(`${result.name}\n${JSON.stringify(result.violations, null, 2)}`)
|
|
42
|
+
if (failures.length) process.exitCode = 1
|
|
43
|
+
else console.log(`Architecture: PASS (${results.length} rules; ${graph.filter(edge => edge.source !== edge.target).length} dependency edges, including type, dynamic and re-export dependencies)`)
|
|
44
|
+
}
|
|
45
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
46
|
+
main().catch(error => { console.error(error); process.exitCode = 1 })
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { ESLint } from 'eslint'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import ts from 'typescript'
|
|
4
|
+
import { parse } from '@typescript-eslint/parser'
|
|
5
|
+
import { readFile, readdir } from 'node:fs/promises'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
export const limits = Object.freeze({ 'max-lines': 500, 'class-lines': 300,
|
|
10
|
+
'max-lines-per-function': 80, complexity: 20, 'max-depth': 4, 'explicit-any': 0 })
|
|
11
|
+
const functionKinds = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'])
|
|
12
|
+
const measuredRules = new Set(['max-lines', 'max-lines-per-function', 'complexity', 'max-depth'])
|
|
13
|
+
|
|
14
|
+
function children(node) {
|
|
15
|
+
return Object.entries(node).filter(([key]) => !['parent', 'tokens', 'comments', 'loc', 'range'].includes(key))
|
|
16
|
+
.flatMap(([, value]) => Array.isArray(value) ? value : [value])
|
|
17
|
+
.filter(value => value && typeof value === 'object' && typeof value.type === 'string')
|
|
18
|
+
}
|
|
19
|
+
function label(node, parent) {
|
|
20
|
+
if (node.id?.name) return node.id.name
|
|
21
|
+
if (parent?.type === 'MethodDefinition' || parent?.type === 'Property' || parent?.type === 'PropertyDefinition') {
|
|
22
|
+
const name = parent.key.name ?? parent.key.value ?? '[computed]'
|
|
23
|
+
return ['get', 'set'].includes(parent.kind) ? `${parent.kind}:${name}` : name
|
|
24
|
+
}
|
|
25
|
+
if (parent?.type === 'VariableDeclarator') return parent.id.name ?? '[destructured]'
|
|
26
|
+
if (parent?.type === 'CallExpression' || parent?.type === 'NewExpression') {
|
|
27
|
+
const callee = parent.callee.name ?? parent.callee.property?.name ?? '[call]'
|
|
28
|
+
return `${callee}:argument${parent.arguments.indexOf(node)}`
|
|
29
|
+
}
|
|
30
|
+
return `[${node.type}]`
|
|
31
|
+
}
|
|
32
|
+
function syntax(code) {
|
|
33
|
+
const ast = parse(code, { loc: true, range: true, comment: true, sourceType: 'module', ecmaVersion: 'latest' })
|
|
34
|
+
const symbols = [], anys = [], imports = []
|
|
35
|
+
function visit(node, parent, scope = '<module>') {
|
|
36
|
+
if (['TSInterfaceDeclaration', 'TSTypeAliasDeclaration'].includes(node.type)) scope = `${scope}/${node.id.name}`
|
|
37
|
+
if (functionKinds.has(node.type) || node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
|
|
38
|
+
const anonymous = !node.id?.name && !['MethodDefinition', 'Property', 'PropertyDefinition', 'VariableDeclarator'].includes(parent?.type)
|
|
39
|
+
const suffix = anonymous ? ':' + createHash('sha256').update(code.slice(...node.range).replace(/\s/g, '')).digest('hex').slice(0, 12) : ''
|
|
40
|
+
scope = `${scope}/${label(node, parent)}${suffix}`
|
|
41
|
+
symbols.push({ node, identity: scope })
|
|
42
|
+
}
|
|
43
|
+
if (node.type === 'TSAnyKeyword') anys.push({ node, identity: scope })
|
|
44
|
+
if (['ImportDeclaration', 'ExportNamedDeclaration', 'ExportAllDeclaration', 'ImportExpression', 'TSImportType'].includes(node.type) && node.source) {
|
|
45
|
+
imports.push({ node, value: node.source.value })
|
|
46
|
+
}
|
|
47
|
+
if (node.type === 'CallExpression' && node.callee.name === 'require' && node.arguments[0]?.type === 'Literal') {
|
|
48
|
+
imports.push({ node, value: node.arguments[0].value })
|
|
49
|
+
}
|
|
50
|
+
for (const child of children(node)) visit(child, node, scope)
|
|
51
|
+
}
|
|
52
|
+
visit(ast, null)
|
|
53
|
+
return { ast, symbols, anys, imports }
|
|
54
|
+
}
|
|
55
|
+
function lineCount(code, comments, start = 0, end = code.length) {
|
|
56
|
+
const chars = code.split('')
|
|
57
|
+
for (const c of comments) for (let i = c.range[0]; i < c.range[1]; i++) if (chars[i] !== '\n') chars[i] = ' '
|
|
58
|
+
return chars.slice(start, end).join('').split('\n').filter(line => line.trim()).length
|
|
59
|
+
}
|
|
60
|
+
const eslint = new ESLint({ overrideConfig: [{ files: ['**/*.ts'], linterOptions: { noInlineConfig: true }, rules: {
|
|
61
|
+
'max-lines': ['error', { max: limits['max-lines'], skipBlankLines: true, skipComments: true }],
|
|
62
|
+
'max-lines-per-function': ['error', { max: limits['max-lines-per-function'], skipBlankLines: true, skipComments: true }],
|
|
63
|
+
complexity: ['error', { max: limits.complexity, variant: 'classic' }], 'max-depth': ['error', limits['max-depth']]
|
|
64
|
+
} }] })
|
|
65
|
+
|
|
66
|
+
export async function measure(file, code) {
|
|
67
|
+
if (file.endsWith('.java')) {
|
|
68
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, code)
|
|
69
|
+
const comments = []
|
|
70
|
+
for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
|
|
71
|
+
if ([ts.SyntaxKind.SingleLineCommentTrivia, ts.SyntaxKind.MultiLineCommentTrivia].includes(token)) {
|
|
72
|
+
comments.push({ range: [scanner.getTokenPos(), scanner.getTextPos()] })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const value = lineCount(code, comments)
|
|
76
|
+
return value > limits['max-lines'] ? [{ file, rule: 'max-lines', symbol: '<module>', value, line: 1 }] : []
|
|
77
|
+
}
|
|
78
|
+
const { ast, symbols, anys, imports } = syntax(code)
|
|
79
|
+
const findings = []
|
|
80
|
+
const add = (rule, symbol, value, line) => findings.push({ file, rule, symbol, value, line })
|
|
81
|
+
const identities = new Set()
|
|
82
|
+
for (const { node, identity } of symbols) {
|
|
83
|
+
if (identities.has(identity) && !identity.includes(':argument')) add('duplicate-symbol', identity, 1, node.loc.start.line)
|
|
84
|
+
identities.add(identity)
|
|
85
|
+
if (node.type.startsWith('Class')) {
|
|
86
|
+
const value = lineCount(code, ast.comments, ...node.range)
|
|
87
|
+
if (value > limits['class-lines']) add('class-lines', identity, value, node.loc.start.line)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const anyCounts = new Map()
|
|
91
|
+
for (const { node, identity } of anys) {
|
|
92
|
+
const current = anyCounts.get(identity) ?? { value: 0, line: node.loc.start.line }
|
|
93
|
+
current.value++; anyCounts.set(identity, current)
|
|
94
|
+
}
|
|
95
|
+
for (const [identity, { value, line }] of anyCounts) add('explicit-any', identity, value, line)
|
|
96
|
+
const [result] = await eslint.lintText(code, { filePath: file })
|
|
97
|
+
for (const m of result.messages.filter(m => measuredRules.has(m.ruleId))) {
|
|
98
|
+
const value = Number(/(?:has too many lines \(|complexity of |nested too deeply \()([\d]+)/.exec(m.message)?.[1])
|
|
99
|
+
if (!Number.isFinite(value)) throw new Error(`Cannot read ${m.ruleId} metric: ${m.message}`)
|
|
100
|
+
const candidates = symbols.filter(({ node }) => m.line >= node.loc.start.line && m.line <= node.loc.end.line)
|
|
101
|
+
.sort((a, b) => a.node.range[1] - a.node.range[0] - (b.node.range[1] - b.node.range[0]))
|
|
102
|
+
const symbol = m.ruleId === 'max-lines' ? '<module>' : candidates[0]?.identity ?? '<module>'
|
|
103
|
+
add(m.ruleId, symbol, value, m.line)
|
|
104
|
+
}
|
|
105
|
+
for (const comment of ast.comments) {
|
|
106
|
+
if (/(?:eslint-(?:disable|enable)|^\s*eslint\s)/.test(comment.value) &&
|
|
107
|
+
(!/eslint-(?:disable|enable)(?:-next-line|-line)?\s+(?!-)[\w@]/.test(comment.value) ||
|
|
108
|
+
/max-lines|complexity|max-depth|no-explicit-any|code-quality/.test(comment.value))) {
|
|
109
|
+
add('inline-suppression', '<module>', 1, comment.loc.start.line)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (file.startsWith('src/interact/') || file.startsWith('src/runtime/')) {
|
|
113
|
+
for (const { node, value } of imports) if (typeof value === 'string' && /(?:^|\/)server(?:\/|-core)/.test(value)) {
|
|
114
|
+
add('import-boundary', '<module>', 1, node.loc.start.line)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Multiple nested blocks can report depth in the same function; retain its maximum.
|
|
118
|
+
return [...findings.reduce((out, f) => {
|
|
119
|
+
const key = `${f.rule}:${f.symbol}`
|
|
120
|
+
if (!out.has(key) || out.get(key).value < f.value) out.set(key, f)
|
|
121
|
+
return out
|
|
122
|
+
}, new Map()).values()]
|
|
123
|
+
}
|
|
124
|
+
export function compare(findings, baseline) {
|
|
125
|
+
const errors = [], entries = new Map()
|
|
126
|
+
const key = f => JSON.stringify([f.file, f.rule, f.symbol])
|
|
127
|
+
for (const b of baseline) {
|
|
128
|
+
if (!b.file || !b.rule || !b.symbol || !b.reason || !Number.isInteger(b.value) || b.value < 1) {
|
|
129
|
+
errors.push('Invalid baseline entry'); continue
|
|
130
|
+
}
|
|
131
|
+
if (entries.has(key(b))) errors.push(`Duplicate baseline entry: ${key(b)}`)
|
|
132
|
+
entries.set(key(b), b)
|
|
133
|
+
}
|
|
134
|
+
for (const f of findings) {
|
|
135
|
+
const b = entries.get(key(f))
|
|
136
|
+
if (['duplicate-symbol', 'inline-suppression', 'import-boundary'].includes(f.rule) || !b || f.value > b.value) {
|
|
137
|
+
errors.push(`${f.file}:${f.line}: ${f.rule} ${f.symbol} = ${f.value}; allowed ${b?.value ?? limits[f.rule] ?? 0}`)
|
|
138
|
+
} else if (f.value < b.value) errors.push(`Lower baseline ceiling for ${key(f)}: ${b.value} -> ${f.value}`)
|
|
139
|
+
entries.delete(key(f))
|
|
140
|
+
}
|
|
141
|
+
for (const b of entries.values()) errors.push(`Remove obsolete baseline entry: ${key(b)}`)
|
|
142
|
+
return errors
|
|
143
|
+
}
|
|
144
|
+
export async function collect(root = 'src') {
|
|
145
|
+
const out = []
|
|
146
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
147
|
+
const file = path.posix.join(root, entry.name)
|
|
148
|
+
if (entry.isDirectory()) out.push(...await collect(file))
|
|
149
|
+
else if (/\.(ts|java)$/.test(file)) out.push(...await measure(file, await readFile(file, 'utf8')))
|
|
150
|
+
}
|
|
151
|
+
return out
|
|
152
|
+
}
|
|
153
|
+
async function main() {
|
|
154
|
+
const baseline = JSON.parse(await readFile(new URL('./code-quality-baseline.json', import.meta.url), 'utf8'))
|
|
155
|
+
const errors = compare(await collect(), baseline)
|
|
156
|
+
if (errors.length) { console.error(errors.join('\n')); process.exitCode = 1 }
|
|
157
|
+
else console.log('Code quality: PASS (size, complexity, nesting, types, boundaries and legacy ceilings)')
|
|
158
|
+
}
|
|
159
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
160
|
+
main().catch(error => { console.error(error); process.exitCode = 1 })
|
|
161
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const verificationModes = ['none', 'light', 'full'] as const
|
|
4
|
+
export const responseModes = ['compact', 'debug'] as const
|
|
5
|
+
export const verificationModeSchema = z.enum(verificationModes)
|
|
6
|
+
export const responseModeSchema = z.enum(responseModes)
|
|
7
|
+
export type VerificationMode = z.infer<typeof verificationModeSchema>
|
|
8
|
+
export type ResponseMode = z.infer<typeof responseModeSchema>
|
|
9
|
+
|
|
10
|
+
export const verificationModeJson = { type: 'string', enum: [...verificationModes] }
|
|
11
|
+
export const responseModeJson = { type: 'string', enum: [...responseModes] }
|
|
12
|
+
|
|
13
|
+
export function interactionControls(args: Record<string, unknown>) {
|
|
14
|
+
const verificationMode = ((typeof args.verificationMode === 'string' ? args.verificationMode : undefined) as VerificationMode | undefined) ?? 'full'
|
|
15
|
+
const responseMode = ((typeof args.responseMode === 'string' ? args.responseMode : undefined) as ResponseMode | undefined) ?? 'debug'
|
|
16
|
+
if (!verificationModes.includes(verificationMode) || !responseModes.includes(responseMode)) throw new Error('INVALID_ARGUMENT')
|
|
17
|
+
for (const key of ['verificationMode', 'responseMode']) if (args[key] !== undefined && typeof args[key] !== 'string') throw new Error('INVALID_ARGUMENT')
|
|
18
|
+
for (const key of ['actionTimeoutMs', 'verificationTimeoutMs']) if (args[key] !== undefined && (typeof args[key] !== 'number' || !Number.isInteger(args[key]))) throw new Error('INVALID_ARGUMENT')
|
|
19
|
+
const actionTimeoutMs = (typeof args.actionTimeoutMs === 'number' && Number.isFinite(args.actionTimeoutMs) ? args.actionTimeoutMs : undefined)
|
|
20
|
+
const verificationTimeoutMs = (typeof args.verificationTimeoutMs === 'number' && Number.isFinite(args.verificationTimeoutMs) ? args.verificationTimeoutMs : undefined)
|
|
21
|
+
return { verificationMode, responseMode,
|
|
22
|
+
actionTimeoutMs: actionTimeoutMs === undefined ? undefined : Math.max(100, Math.min(30000, actionTimeoutMs)),
|
|
23
|
+
verificationTimeoutMs: verificationTimeoutMs === undefined ? 3000 : Math.max(100, Math.min(60000, verificationTimeoutMs)) }
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export type UiSelector = {
|
|
4
|
+
text?: string
|
|
5
|
+
resource_id?: string
|
|
6
|
+
accessibility_id?: string
|
|
7
|
+
contains?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const selectorSchema = z.object({
|
|
11
|
+
text: z.string().min(1).optional(), resource_id: z.string().min(1).optional(),
|
|
12
|
+
accessibility_id: z.string().min(1).optional(), contains: z.boolean().optional()
|
|
13
|
+
}).strict().refine(value => !!(value.text || value.resource_id || value.accessibility_id), 'Selector requires a target')
|
|
14
|
+
|
|
15
|
+
export const selectorJson = { type: 'object', additionalProperties: false, properties: {
|
|
16
|
+
text: { type: 'string', minLength: 1 }, resource_id: { type: 'string', minLength: 1 }, accessibility_id: { type: 'string', minLength: 1 }, contains: { type: 'boolean' }
|
|
17
|
+
}, anyOf: ['text', 'resource_id', 'accessibility_id'].map(key => ({ required: [key] })) }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { selectorSchema as selector, selectorJson } from './selector.js'
|
|
3
|
+
|
|
4
|
+
export interface DeviceTarget { platform?: 'android' | 'ios'; deviceId?: string }
|
|
5
|
+
export type ScrollSelector = { text?: string; resourceId?: string; contentDesc?: string; className?: string }
|
|
6
|
+
const pollIntervalMs = z.number().int().min(50).max(1000).optional()
|
|
7
|
+
export const waitForSchema = z.object({
|
|
8
|
+
condition: z.enum(['exists', 'visible', 'clickable']).optional(),
|
|
9
|
+
timeoutMs: z.number().int().min(100).max(10000).optional(), pollIntervalMs,
|
|
10
|
+
match: z.object({ index: z.number().int().nonnegative().optional() }).strict().optional()
|
|
11
|
+
}).strict()
|
|
12
|
+
export const targetFields = { selector: selector.optional(), elementId: z.string().min(1).optional() }
|
|
13
|
+
export const exactlyOneTarget = (value: { selector?: unknown, elementId?: string }) => Number(!!value.selector) + Number(!!value.elementId) === 1
|
|
14
|
+
export const tapElementControlsSchema = z.object({
|
|
15
|
+
elementId: z.string().min(1).optional(), selector: selector.optional(), waitFor: waitForSchema.optional()
|
|
16
|
+
}).strict().refine(value => Number(!!value.elementId) + Number(!!value.selector) === 1 && (!value.elementId || !value.waitFor))
|
|
17
|
+
|
|
18
|
+
export const targetJson = { selector: selectorJson, elementId: { type: 'string', minLength: 1 } }
|
|
19
|
+
export const oneTargetJson = [{ required: ['selector'], not: { required: ['elementId'] } }, { required: ['elementId'], not: { required: ['selector'] } }]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface UIElementState {
|
|
2
|
+
checked?: boolean | null;
|
|
3
|
+
selected?: boolean | string | { id: string; label?: string } | null;
|
|
4
|
+
focused?: boolean | null;
|
|
5
|
+
expanded?: boolean | null;
|
|
6
|
+
enabled?: boolean | null;
|
|
7
|
+
text_value?: string | null;
|
|
8
|
+
value?: number | string | null;
|
|
9
|
+
raw_value?: number | string | null;
|
|
10
|
+
value_range?: {
|
|
11
|
+
min: number;
|
|
12
|
+
max: number;
|
|
13
|
+
step?: number | null;
|
|
14
|
+
} | null;
|
|
15
|
+
step?: number | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SelectorConfidence {
|
|
19
|
+
score: number;
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UIResolutionSelector {
|
|
24
|
+
value: string | null;
|
|
25
|
+
confidence: SelectorConfidence | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UIElementSemanticMetadata {
|
|
29
|
+
is_clickable: boolean;
|
|
30
|
+
is_container: boolean;
|
|
31
|
+
semantic_role?: 'slider' | 'stepper' | 'dropdown' | 'segmented_control' | 'custom_adjustable' | 'composite_control' | null;
|
|
32
|
+
supported_actions?: string[] | null;
|
|
33
|
+
adjustable?: boolean | null;
|
|
34
|
+
state_shape?: 'continuous' | 'discrete' | 'semantic' | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LoadingState {
|
|
38
|
+
active: boolean;
|
|
39
|
+
signal: string;
|
|
40
|
+
source: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SnapshotDelta {
|
|
44
|
+
previous_snapshot_revision: number | null;
|
|
45
|
+
added_elements: number;
|
|
46
|
+
removed_elements: number;
|
|
47
|
+
mutated_elements: number;
|
|
48
|
+
total_elements: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface StateDelta {
|
|
52
|
+
base_snapshot_revision: number | null;
|
|
53
|
+
snapshot_revision: number;
|
|
54
|
+
added: UIElement[];
|
|
55
|
+
removed: Array<{ stable_id?: string; resourceId?: string; contentDescription?: string; text?: string; index: number }>;
|
|
56
|
+
changed: UIElement[];
|
|
57
|
+
truncated: boolean;
|
|
58
|
+
omitted_changes?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UIElement {
|
|
62
|
+
text: string | null;
|
|
63
|
+
contentDescription: string | null;
|
|
64
|
+
type: string;
|
|
65
|
+
clickable: boolean;
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
visible: boolean;
|
|
68
|
+
bounds: [number, number, number, number];
|
|
69
|
+
resourceId: string | null;
|
|
70
|
+
parentId?: number;
|
|
71
|
+
children?: number[];
|
|
72
|
+
center?: [number, number];
|
|
73
|
+
depth?: number;
|
|
74
|
+
state?: UIElementState | null;
|
|
75
|
+
stable_id?: string | null;
|
|
76
|
+
role?: string | null;
|
|
77
|
+
test_tag?: string | null;
|
|
78
|
+
selector?: UIResolutionSelector | null;
|
|
79
|
+
semantic?: UIElementSemanticMetadata | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ReadinessSummary {
|
|
83
|
+
state: 'loading' | 'transitioning' | 'settled' | 'unknown' | 'unresponsive' | 'debugger_paused';
|
|
84
|
+
scope: 'device_ui';
|
|
85
|
+
basis: 'observed' | 'heuristic' | 'insufficient';
|
|
86
|
+
captured_at_ms: number | null;
|
|
87
|
+
sample_count: number;
|
|
88
|
+
stable_for_ms: number;
|
|
89
|
+
snapshot_session_id: string | null;
|
|
90
|
+
snapshot_revision: number | null;
|
|
91
|
+
evidence: Array<{ code: string; source: string; captured_at_ms: number | null; snapshot_revision: number | null }>;
|
|
92
|
+
limitations: string[];
|
|
93
|
+
capabilities: { loading: boolean; transitioning: boolean; settled: boolean; unresponsive: boolean; debugger_paused: boolean };
|
|
94
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { resolveAdbCmd } from '../utils/android/utils.js'
|
|
2
|
+
import { execFile } from 'node:child_process'
|
|
3
|
+
import { promisify } from 'node:util'
|
|
4
|
+
import { DebuggerError, debuggerRemaining, debuggerSignal, nativeError } from './worker.js'
|
|
5
|
+
const exec = promisify(execFile)
|
|
6
|
+
export interface DebugTarget { app_id: string; pid: number; process_name: string; start_identity: string; uid: number }
|
|
7
|
+
export interface DebugNative {
|
|
8
|
+
candidates(device: string, app: string): Promise<DebugTarget[]>
|
|
9
|
+
identity(device: string, target: DebugTarget): Promise<DebugTarget>
|
|
10
|
+
forward(device: string, pid: number): Promise<number>
|
|
11
|
+
removeForward(device: string, port: number, pid: number): Promise<void>
|
|
12
|
+
}
|
|
13
|
+
export class AndroidDebugNative implements DebugNative {
|
|
14
|
+
async adb(device: string, args: string[]) {
|
|
15
|
+
try { return (await exec(resolveAdbCmd(), ['-s', device, ...args], { timeout: debuggerRemaining(5000), signal: debuggerSignal(), maxBuffer: 1024 * 1024 })).stdout.trim() }
|
|
16
|
+
catch (e) { throw nativeError(e, 'DEVICE_UNAVAILABLE') }
|
|
17
|
+
}
|
|
18
|
+
async identity(device: string, target: DebugTarget): Promise<DebugTarget> {
|
|
19
|
+
try {
|
|
20
|
+
const stat = await this.adb(device, ['shell', 'cat', `/proc/${target.pid}/stat`])
|
|
21
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(/\s+/)
|
|
22
|
+
const status = await this.adb(device, ['shell', 'cat', `/proc/${target.pid}/status`])
|
|
23
|
+
const uid = Number(/^Uid:\s+(\d+)/m.exec(status)?.[1])
|
|
24
|
+
const name = (await this.adb(device, ['shell', 'cat', `/proc/${target.pid}/cmdline`])).split('\0')[0]
|
|
25
|
+
if (!/^\d+$/.test(fields[19] ?? '') || uid !== target.uid || name !== target.process_name || (target.start_identity && fields[19] !== target.start_identity)) throw new Error('identity')
|
|
26
|
+
return { ...target, start_identity: fields[19] }
|
|
27
|
+
} catch (e) { if (e instanceof DebuggerError && ['DEBUGGER_TIMEOUT', 'DEBUGGER_CANCELLED', 'DEVICE_UNAVAILABLE'].includes(e.code)) throw e; throw new DebuggerError('PROCESS_CHANGED') }
|
|
28
|
+
}
|
|
29
|
+
async candidates(device: string, app: string) {
|
|
30
|
+
if (!/^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/.test(app)) throw new DebuggerError('INVALID_ARGUMENT')
|
|
31
|
+
const packages = await this.adb(device, ['shell', 'cmd', 'package', 'list', 'packages', '-U', app])
|
|
32
|
+
const uid = Number(new RegExp(`^package:${app.replaceAll('.', '\\.')} uid:(\\d+)$`, 'm').exec(packages)?.[1])
|
|
33
|
+
if (!Number.isSafeInteger(uid)) throw new DebuggerError('PROCESS_NOT_FOUND')
|
|
34
|
+
// `adb jdwp` is a subscription, not a one-shot command. Bound the sample.
|
|
35
|
+
let jdwpOutput: string
|
|
36
|
+
try { jdwpOutput = (await exec(resolveAdbCmd(), ['-s', device, 'jdwp'], { timeout: debuggerRemaining(500), signal: debuggerSignal(), maxBuffer: 1024 * 1024 })).stdout }
|
|
37
|
+
catch (error: unknown) {
|
|
38
|
+
const e = error as { killed?: boolean; stderr?: string; stdout?: string }
|
|
39
|
+
if (debuggerSignal()?.aborted) throw new DebuggerError('DEBUGGER_CANCELLED')
|
|
40
|
+
debuggerRemaining(1)
|
|
41
|
+
if (!e.killed || e.stderr?.trim()) throw nativeError(e, 'DEVICE_UNAVAILABLE')
|
|
42
|
+
jdwpOutput = e.stdout ?? ''
|
|
43
|
+
}
|
|
44
|
+
const jdwp = new Set(jdwpOutput.split(/\s+/).filter(id => /^\d+$/.test(id)))
|
|
45
|
+
const ps = await this.adb(device, ['shell', 'ps', '-A', '-o', 'PID,NAME'])
|
|
46
|
+
const result: DebugTarget[] = []
|
|
47
|
+
for (const line of ps.split('\n').slice(1)) {
|
|
48
|
+
const [pid, name] = line.trim().split(/\s+/)
|
|
49
|
+
if (jdwp.has(pid) && (name === app || name.startsWith(app + ':'))) result.push(await this.identity(device, { app_id: app, pid: Number(pid), process_name: name, start_identity: '', uid }))
|
|
50
|
+
}
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
async forward(device: string, pid: number) {
|
|
54
|
+
const port = Number(await this.adb(device, ['forward', '--no-rebind', 'tcp:0', `jdwp:${pid}`]))
|
|
55
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new DebuggerError('DEBUGGER_ATTACH_FAILED')
|
|
56
|
+
return port
|
|
57
|
+
}
|
|
58
|
+
async removeForward(device: string, port: number, pid: number) {
|
|
59
|
+
const list = await this.adb(device, ['forward', '--list'])
|
|
60
|
+
const entry = list.split('\n').find(line => line.trim().split(/\s+/)[1] === `tcp:${port}`)
|
|
61
|
+
if (!entry) return
|
|
62
|
+
const [serial, local, remote] = entry.trim().split(/\s+/)
|
|
63
|
+
if (serial !== device || local !== `tcp:${port}` || remote !== `jdwp:${pid}`) throw new DebuggerError('DEBUGGER_CLEANUP_FAILED')
|
|
64
|
+
await this.adb(device, ['forward', '--remove', `tcp:${port}`])
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Only catalog values cross the debugger error boundary. Never inspect exception text. */
|
|
2
|
+
export const stages = ['prerequisite', 'compilation', 'worker_start', 'ipc', 'target_resolution', 'forwarding', 'attach', 'command', 'transport', 'cleanup', 'unknown'] as const
|
|
3
|
+
export const reasons = ['unavailable', 'unsupported', 'spawn_failed', 'process_exited', 'invalid_message', 'message_limit', 'write_failed', 'connection_refused', 'transport_lost', 'timeout', 'cancelled', 'operation_failed', 'unknown'] as const
|
|
4
|
+
export type DiagnosticStage = typeof stages[number]
|
|
5
|
+
export type DiagnosticReason = typeof reasons[number]
|
|
6
|
+
export interface DebuggerDiagnostic { stage: DiagnosticStage; reason: DiagnosticReason; recovery: string }
|
|
7
|
+
const recovery: Record<DiagnosticStage, string> = {
|
|
8
|
+
prerequisite: 'Check JDK 17+ availability, including java, javac and jdk.jdi.',
|
|
9
|
+
compilation: 'Check the JDK compiler and worker source/cache availability.',
|
|
10
|
+
worker_start: 'Check Java worker launch availability. Inspect session state and cleanup before attaching again.',
|
|
11
|
+
ipc: 'Inspect session state and cleanup. Detach before attempting a fresh attach.',
|
|
12
|
+
target_resolution: 'Check the explicit device and running debuggable app/process.',
|
|
13
|
+
forwarding: 'Check the explicit device and debugger forwarding availability.',
|
|
14
|
+
attach: 'Check the running debuggable target and whether another debugger is attached. Inspect cleanup before a fresh attach.',
|
|
15
|
+
command: 'Inspect debugger session state and cleanup before another state-changing command.',
|
|
16
|
+
transport: 'Inspect session state and cleanup. Detach before attempting a fresh attach; execution state may be unknown.',
|
|
17
|
+
cleanup: 'Retry detach_debugger and inspect cleanup.remaining_resources. Do not kill the app or ADB server.',
|
|
18
|
+
unknown: 'Inspect debugger session state and cleanup; the failure cause is unknown.'
|
|
19
|
+
}
|
|
20
|
+
export function diagnostic(stage: DiagnosticStage, reason: DiagnosticReason): DebuggerDiagnostic {
|
|
21
|
+
const guidance = reason === 'timeout' || reason === 'cancelled'
|
|
22
|
+
? 'Execution may be uncertain. Inspect session state and cleanup before another state-changing command.' : recovery[stage]
|
|
23
|
+
return { stage, reason, recovery: guidance }
|
|
24
|
+
}
|
|
25
|
+
export function parseDiagnostic(value: unknown): DebuggerDiagnostic {
|
|
26
|
+
if (!value || typeof value !== 'object') throw new Error('INVALID_WORKER_DIAGNOSTIC')
|
|
27
|
+
const { stage, reason } = value as Record<string, unknown>
|
|
28
|
+
if (!stages.includes(stage as DiagnosticStage) || !reasons.includes(reason as DiagnosticReason)) throw new Error('INVALID_WORKER_DIAGNOSTIC')
|
|
29
|
+
return diagnostic(stage as DiagnosticStage, reason as DiagnosticReason)
|
|
30
|
+
}
|
|
31
|
+
export class DebuggerError extends Error {
|
|
32
|
+
constructor(public code: string, public diagnostic?: DebuggerDiagnostic) { super(code) }
|
|
33
|
+
}
|
|
34
|
+
const validationCodes = new Set(['INVALID_ARGUMENT', 'DEBUGGER_BUSY', 'DEBUGGER_NOT_FOUND', 'DEBUGGER_PAUSED', 'DEBUGGER_NOT_PAUSED', 'DEBUGGER_STALE_REFERENCE'])
|
|
35
|
+
export function classify(error: unknown, stage: DiagnosticStage, fallback: string, reason: DiagnosticReason = 'unknown'): DebuggerError {
|
|
36
|
+
const existing = error instanceof DebuggerError ? error : new DebuggerError(fallback)
|
|
37
|
+
if (existing.diagnostic || validationCodes.has(existing.code)) return existing
|
|
38
|
+
const knownReason = existing.code === 'DEBUGGER_TIMEOUT' ? 'timeout' : existing.code === 'DEBUGGER_CANCELLED' ? 'cancelled' : reason
|
|
39
|
+
return new DebuggerError(existing.code, diagnostic(stage, knownReason))
|
|
40
|
+
}
|
|
41
|
+
export async function atStage<T>(stage: DiagnosticStage, fallback: string, operation: () => Promise<T>): Promise<T> {
|
|
42
|
+
try { return await operation() } catch (error) { throw classify(error, stage, fallback) }
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
import { DebuggerError } from './worker.js'
|
|
3
|
+
interface PauseEvidence {
|
|
4
|
+
debug_session_id: string
|
|
5
|
+
captured_at_ms: number
|
|
6
|
+
target: unknown
|
|
7
|
+
}
|
|
8
|
+
let pauseProvider: (device: string) => PauseEvidence | null = () => null
|
|
9
|
+
export function registerDebuggerPauseProvider(provider: typeof pauseProvider) { pauseProvider = provider }
|
|
10
|
+
const context = new AsyncLocalStorage<{ platform: string; device: string; dispatched: boolean }>()
|
|
11
|
+
export function withDebuggerGuard<T>(platform: string, device: string, operation: () => Promise<T>) {
|
|
12
|
+
return context.run({ platform, device, dispatched: false }, operation)
|
|
13
|
+
}
|
|
14
|
+
// Synchronous checks and native submission share a JS turn with pause updates.
|
|
15
|
+
export function checkDebuggerGuard(dispatch = false) {
|
|
16
|
+
const current = context.getStore()
|
|
17
|
+
if (!current || current.platform !== 'android') return
|
|
18
|
+
if (pauseProvider(current.device)) throw new DebuggerError('DEBUGGER_PAUSED')
|
|
19
|
+
if (dispatch) current.dispatched = true
|
|
20
|
+
}
|
|
21
|
+
export function guardDispatched() { return context.getStore()?.dispatched ?? false }
|
|
22
|
+
export function pausedReadiness(device: string) {
|
|
23
|
+
const paused = pauseProvider(device)
|
|
24
|
+
if (!paused) return null
|
|
25
|
+
return { state: 'debugger_paused' as const, scope: 'device_ui' as const, basis: 'observed' as const,
|
|
26
|
+
captured_at_ms: paused.captured_at_ms, sample_count: 0, stable_for_ms: 0, snapshot_session_id: null, snapshot_revision: null,
|
|
27
|
+
evidence: [{ code: 'DEBUGGER_PAUSED', source: 'debugger', captured_at_ms: paused.captured_at_ms, snapshot_revision: null }],
|
|
28
|
+
limitations: ['FOREGROUND_APP_UNVERIFIED', 'DEVICE_WIDE_UI_GUARD'],
|
|
29
|
+
capabilities: { loading: true, transitioning: true, settled: true, unresponsive: false, debugger_paused: true },
|
|
30
|
+
debug_session_id: paused.debug_session_id, target: paused.target }
|
|
31
|
+
}
|