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
package/src/manage/ios.ts
CHANGED
|
@@ -1,315 +1,70 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js'
|
|
2
|
+
import { buildIOS } from './ios/build.js'
|
|
3
|
+
import type { IOSBuildOptions } from './ios/build-types.js'
|
|
4
|
+
export type { IOSBuildOptions } from './ios/build-types.js'
|
|
1
5
|
import { evidenceDispatch } from '../observe/evidence.js'
|
|
2
6
|
import { hasFiniteBudget, markDispatchStarted, remainingBudget, runWithBudget } from '../utils/operation-budget.js'
|
|
3
7
|
import { promises as fs } from "fs"
|
|
4
|
-
import { spawn
|
|
8
|
+
import { spawn } from "child_process"
|
|
5
9
|
import { StartAppResponse, TerminateAppResponse, RestartAppResponse, ResetAppDataResponse, InstallAppResponse } from "../types.js"
|
|
6
10
|
import { execCommand, execCommandWithDiagnostics, getIOSDeviceMetadata, validateBundleId, getIdbCmd, findAppBundle } from "../utils/ios/utils.js"
|
|
7
11
|
import { iOSObserve } from "../observe/ios.js"
|
|
8
|
-
import path from "path"
|
|
9
|
-
|
|
10
|
-
type BuildEnv = Record<string, string | undefined>
|
|
11
|
-
|
|
12
|
-
export interface IOSBuildOptions {
|
|
13
|
-
workspace?: string
|
|
14
|
-
project?: string
|
|
15
|
-
scheme?: string
|
|
16
|
-
destinationUDID?: string
|
|
17
|
-
derivedDataPath?: string
|
|
18
|
-
buildJobs?: number
|
|
19
|
-
forceClean?: boolean
|
|
20
|
-
xcodeCmd?: string
|
|
21
|
-
env?: BuildEnv
|
|
22
|
-
}
|
|
23
12
|
|
|
24
13
|
export class iOSManage {
|
|
25
|
-
async build(projectPath: string, optsOrVariant?: string | IOSBuildOptions)
|
|
26
|
-
// Support legacy variant string as second arg
|
|
27
|
-
const opts: IOSBuildOptions = typeof optsOrVariant === 'string' ? {} : (optsOrVariant || {})
|
|
28
|
-
const env = { ...process.env, ...(opts.env || {}) }
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// Look for an Xcode workspace or project at the provided path. If not present, scan subdirectories (limited depth)
|
|
32
|
-
async function findProject(root: string, maxDepth = 4): Promise<{ dir: string, workspace?: string, proj?: string } | null> {
|
|
33
|
-
try {
|
|
34
|
-
const ents = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
|
|
35
|
-
for (const e of ents) {
|
|
36
|
-
// .xcworkspace and .xcodeproj are directories on disk (bundles), not regular files
|
|
37
|
-
if (e.name.endsWith('.xcworkspace')) return { dir: root, workspace: e.name }
|
|
38
|
-
if (e.name.endsWith('.xcodeproj')) return { dir: root, proj: e.name }
|
|
39
|
-
}
|
|
40
|
-
} catch {}
|
|
41
|
-
|
|
42
|
-
if (maxDepth <= 0) return null
|
|
14
|
+
async build(projectPath: string, optsOrVariant?: string | IOSBuildOptions) { return buildIOS(projectPath, optsOrVariant) }
|
|
43
15
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for (const e of ents) {
|
|
47
|
-
if (e.isDirectory()) {
|
|
48
|
-
const candidate = await findProject(path.join(root, e.name), maxDepth - 1)
|
|
49
|
-
if (candidate) return candidate
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
} catch {}
|
|
53
|
-
|
|
54
|
-
return null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Resolve projectPath to an absolute path to avoid cwd-relative resolution issues
|
|
58
|
-
const absProjectPath = path.resolve(projectPath)
|
|
59
|
-
|
|
60
|
-
// If caller supplied explicit workspace/project, prefer those and set projectRootDir accordingly
|
|
61
|
-
let projectRootDir = absProjectPath
|
|
62
|
-
let workspace: string | undefined = opts.workspace
|
|
63
|
-
let proj: string | undefined = opts.project
|
|
64
|
-
|
|
65
|
-
if (workspace) {
|
|
66
|
-
// normalize workspace path and set root to its parent
|
|
67
|
-
workspace = path.isAbsolute(workspace) ? workspace : path.join(absProjectPath, workspace)
|
|
68
|
-
projectRootDir = path.dirname(workspace)
|
|
69
|
-
workspace = path.basename(workspace)
|
|
70
|
-
} else if (proj) {
|
|
71
|
-
proj = path.isAbsolute(proj) ? proj : path.join(absProjectPath, proj)
|
|
72
|
-
projectRootDir = path.dirname(proj)
|
|
73
|
-
proj = path.basename(proj)
|
|
74
|
-
} else {
|
|
75
|
-
const projectInfo = await findProject(absProjectPath, 4)
|
|
76
|
-
if (!projectInfo) return { error: 'No Xcode project or workspace found' }
|
|
77
|
-
projectRootDir = projectInfo.dir || absProjectPath
|
|
78
|
-
workspace = projectInfo.workspace
|
|
79
|
-
proj = projectInfo.proj
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Determine destination: prefer explicit option, then env var, otherwise use booted simulator UDID
|
|
83
|
-
let destinationUDID = opts.destinationUDID || env.MCP_XCODE_DESTINATION_UDID || env.MCP_XCODE_DESTINATION || ''
|
|
84
|
-
if (!destinationUDID) {
|
|
85
|
-
try {
|
|
86
|
-
const meta = await getIOSDeviceMetadata('booted')
|
|
87
|
-
if (meta && meta.id) destinationUDID = meta.id
|
|
88
|
-
} catch {}
|
|
89
|
-
}
|
|
16
|
+
async installApp(appPath: string, deviceId: string = "booted"): Promise<InstallAppResponse> {
|
|
17
|
+
const device = await getIOSDeviceMetadata(deviceId)
|
|
90
18
|
|
|
91
|
-
|
|
92
|
-
const
|
|
19
|
+
try {
|
|
20
|
+
const toInstall = await this.installBundle(appPath)
|
|
93
21
|
|
|
94
|
-
|
|
95
|
-
|
|
22
|
+
try {
|
|
23
|
+
const res = await execCommand(['simctl', 'install', deviceId, toInstall], deviceId)
|
|
24
|
+
return { device, installed: true, output: res.output }
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Gather diagnostics for simctl failure
|
|
27
|
+
const diag = execCommandWithDiagnostics(['simctl', 'install', deviceId, toInstall], deviceId)
|
|
96
28
|
try {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
29
|
+
const child = spawn(getIdbCmd(), ['list-targets', '--json'])
|
|
30
|
+
const idbExists = await new Promise<boolean>((resolve) => {
|
|
31
|
+
child.on('error', () => resolve(false));
|
|
32
|
+
child.on('close', (code) => resolve(code === 0));
|
|
33
|
+
});
|
|
34
|
+
if (idbExists) {
|
|
35
|
+
// attempt idb install via spawn but include diagnostics
|
|
36
|
+
await installWithIdb(toInstall, device.id)
|
|
37
|
+
return { device, installed: true }
|
|
106
38
|
}
|
|
107
|
-
} catch {}
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Prepare build flags and paths (support incremental builds)
|
|
112
|
-
let buildArgs: string[]
|
|
113
|
-
let chosenScheme: string | null = opts.scheme || null
|
|
114
|
-
|
|
115
|
-
// Derived data and result bundle (agent-configurable)
|
|
116
|
-
const derivedDataPath = opts.derivedDataPath || env.MCP_DERIVED_DATA || path.join(projectRootDir, 'build', 'DerivedData')
|
|
117
|
-
// Use unique result bundle path by default to avoid collisions
|
|
118
|
-
const resultBundlePath = env.MCP_XCODE_RESULTBUNDLE_PATH || path.join(projectRootDir, 'build', 'xcresults', `ResultBundle-${Date.now()}-${Math.random().toString(36).slice(2)}.xcresult`)
|
|
119
|
-
const xcodeJobs = typeof opts.buildJobs === 'number' ? opts.buildJobs : (parseInt(env.MCP_XCODE_JOBS || '', 10) || 4)
|
|
120
|
-
const forceClean = typeof opts.forceClean === 'boolean' ? opts.forceClean : env.MCP_FORCE_CLEAN === '1'
|
|
121
|
-
|
|
122
|
-
// ensure result dirs exist
|
|
123
|
-
await fs.mkdir(path.dirname(resultBundlePath), { recursive: true }).catch(() => {})
|
|
124
|
-
await fs.mkdir(derivedDataPath, { recursive: true }).catch(() => {})
|
|
125
|
-
// remove any pre-existing result bundle path to avoid xcodebuild complaining
|
|
126
|
-
await fs.rm(resultBundlePath, { recursive: true, force: true }).catch(() => {})
|
|
127
|
-
|
|
128
|
-
if (workspace) {
|
|
129
|
-
const workspacePath = path.join(projectRootDir, workspace)
|
|
130
|
-
if (!chosenScheme) chosenScheme = await detectScheme(xcodeCmd, workspacePath, undefined, projectRootDir)
|
|
131
|
-
const scheme = chosenScheme || workspace.replace(/\.xcworkspace$/, '')
|
|
132
|
-
buildArgs = ['-workspace', workspacePath, '-scheme', scheme, '-configuration', 'Debug', '-sdk', 'iphonesimulator', 'build']
|
|
133
|
-
} else {
|
|
134
|
-
const projectPathFull = path.join(projectRootDir, proj!)
|
|
135
|
-
if (!chosenScheme) chosenScheme = await detectScheme(xcodeCmd, undefined, projectPathFull, projectRootDir)
|
|
136
|
-
const scheme = chosenScheme || proj!.replace(/\.xcodeproj$/, '')
|
|
137
|
-
buildArgs = ['-project', projectPathFull, '-scheme', scheme, '-configuration', 'Debug', '-sdk', 'iphonesimulator', 'build']
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Insert clean if explicitly requested via env or opts
|
|
141
|
-
if (forceClean) {
|
|
142
|
-
const idx = buildArgs.indexOf('build')
|
|
143
|
-
if (idx >= 0) buildArgs.splice(idx, 0, 'clean')
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// If we have a destination UDID, add an explicit destination to avoid xcodebuild picking an ambiguous target
|
|
147
|
-
if (destinationUDID) {
|
|
148
|
-
buildArgs.push('-destination', `platform=iOS Simulator,id=${destinationUDID}`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Add derived data and result bundle for diagnostics and faster incremental builds
|
|
152
|
-
buildArgs.push('-derivedDataPath', derivedDataPath)
|
|
153
|
-
buildArgs.push('-resultBundlePath', resultBundlePath)
|
|
154
|
-
// parallelisation and jobs
|
|
155
|
-
buildArgs.push('-parallelizeTargets')
|
|
156
|
-
buildArgs.push('-jobs', String(xcodeJobs))
|
|
157
|
-
|
|
158
|
-
// Prepare results directory for backwards-compatible logs
|
|
159
|
-
const resultsDir = path.join(projectPath, 'build-results')
|
|
160
|
-
// Remove any stale results to avoid xcodebuild complaining about existing result bundles
|
|
161
|
-
await fs.rm(resultsDir, { recursive: true, force: true }).catch(() => {})
|
|
162
|
-
await fs.mkdir(resultsDir, { recursive: true }).catch(() => {})
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const XCODEBUILD_TIMEOUT = parseInt(env.MCP_XCODEBUILD_TIMEOUT || '', 10) || 180000 // default 3 minutes
|
|
166
|
-
const MAX_RETRIES = parseInt(env.MCP_XCODEBUILD_RETRIES || '', 10) || 1
|
|
167
|
-
|
|
168
|
-
const tries = MAX_RETRIES + 1
|
|
169
|
-
let lastStdout = ''
|
|
170
|
-
let lastStderr = ''
|
|
171
|
-
let lastErr: any = null
|
|
172
|
-
|
|
173
|
-
for (let attempt = 1; attempt <= tries; attempt++) {
|
|
174
|
-
// Run xcodebuild with a watchdog
|
|
175
|
-
const res = await new Promise<{ code: number | null, stdout: string, stderr: string, killedByWatchdog?: boolean }>((resolve) => {
|
|
176
|
-
const proc = spawn(xcodeCmd, buildArgs, { cwd: projectRootDir, env })
|
|
177
|
-
let stdout = ''
|
|
178
|
-
let stderr = ''
|
|
179
|
-
|
|
180
|
-
proc.stdout?.on('data', d => stdout += d.toString())
|
|
181
|
-
proc.stderr?.on('data', d => stderr += d.toString())
|
|
182
|
-
|
|
183
|
-
let killed = false
|
|
184
|
-
const to = setTimeout(() => {
|
|
185
|
-
killed = true
|
|
186
|
-
try { proc.kill('SIGKILL') } catch {}
|
|
187
|
-
}, XCODEBUILD_TIMEOUT)
|
|
188
|
-
|
|
189
|
-
proc.on('close', (code) => {
|
|
190
|
-
clearTimeout(to)
|
|
191
|
-
resolve({ code, stdout, stderr, killedByWatchdog: killed })
|
|
192
|
-
})
|
|
193
|
-
proc.on('error', (err) => {
|
|
194
|
-
clearTimeout(to)
|
|
195
|
-
resolve({ code: null, stdout, stderr: String(err), killedByWatchdog: killed })
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
lastStdout = res.stdout
|
|
200
|
-
lastStderr = res.stderr
|
|
201
|
-
|
|
202
|
-
if (res.code === 0) {
|
|
203
|
-
// success — clear any previous error and stop retrying
|
|
204
|
-
lastErr = null
|
|
205
|
-
break
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// record the failure for reporting
|
|
209
|
-
lastErr = new Error(res.stderr || `xcodebuild failed with code ${res.code}`)
|
|
210
|
-
// Attach exit code and watchdog info so diagnostics can include them
|
|
211
|
-
;(lastErr as any).code = res.code
|
|
212
|
-
;(lastErr as any).exitCode = res.code
|
|
213
|
-
;(lastErr as any).killedByWatchdog = !!res.killedByWatchdog
|
|
214
|
-
|
|
215
|
-
// write logs for diagnostics (helpful whether killed or not)
|
|
216
|
-
try {
|
|
217
|
-
await fs.writeFile(path.join(resultsDir, `xcodebuild-${attempt}.stdout.log`), res.stdout).catch(() => {})
|
|
218
|
-
await fs.writeFile(path.join(resultsDir, `xcodebuild-${attempt}.stderr.log`), res.stderr).catch(() => {})
|
|
219
|
-
} catch {}
|
|
220
|
-
|
|
221
|
-
// If killed by watchdog and there are remaining attempts, continue to retry
|
|
222
|
-
if (res.killedByWatchdog && attempt < tries) {
|
|
223
|
-
continue
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// no more retries or not a watchdog kill — break to report lastErr
|
|
227
|
-
if (attempt >= tries) break
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (lastErr) {
|
|
231
|
-
// Include diagnostics and result bundle path when available; provide structured info useful for agents
|
|
232
|
-
const invokedCommand = `${xcodeCmd} ${buildArgs.map(a => a.includes(' ') ? `"${a}"` : a).join(' ')}`
|
|
233
|
-
const envSnapshot = { PATH: env.PATH }
|
|
234
|
-
return { error: `xcodebuild failed: ${lastErr.message}. See build-results for logs.`, output: `stdout:\n${lastStdout}\nstderr:\n${lastStderr}`, diagnostics: { exitCode: (lastErr as any).code || null, invokedCommand, cwd: projectRootDir, envSnapshot } }
|
|
39
|
+
} catch { }
|
|
40
|
+
return { device, installed: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
|
|
235
41
|
}
|
|
236
|
-
|
|
237
|
-
// Try to locate built .app. First search project tree, then DerivedData if necessary
|
|
238
|
-
const built = await findAppBundle(projectPath)
|
|
239
|
-
if (built) return { artifactPath: built }
|
|
240
|
-
|
|
241
|
-
// Fallback: search DerivedData for matching product
|
|
242
|
-
const dd = path.join(process.env.HOME || '', 'Library', 'Developer', 'Xcode', 'DerivedData')
|
|
243
|
-
try {
|
|
244
|
-
const entries = await fs.readdir(dd).catch(() => [])
|
|
245
|
-
for (const e of entries) {
|
|
246
|
-
const candidate = path.join(dd, e)
|
|
247
|
-
const found = await findAppBundle(candidate).catch(() => undefined)
|
|
248
|
-
if (found) return { artifactPath: found }
|
|
249
|
-
}
|
|
250
|
-
} catch {}
|
|
251
|
-
|
|
252
|
-
return { error: 'Could not find .app after build' }
|
|
253
42
|
} catch (e) {
|
|
254
|
-
return { error: e instanceof Error ? e.message : String(e) }
|
|
43
|
+
return { device, installed: false, error: e instanceof Error ? e.message : String(e) }
|
|
255
44
|
}
|
|
256
45
|
}
|
|
257
46
|
|
|
258
|
-
async
|
|
259
|
-
|
|
47
|
+
private async installBundle(appPath: string): Promise<string> {
|
|
48
|
+
let toInstall = appPath
|
|
260
49
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
50
|
+
const stat = await fs.stat(appPath).catch(() => null)
|
|
51
|
+
if (stat && stat.isDirectory()) {
|
|
52
|
+
if (appPath.endsWith('.app')) {
|
|
53
|
+
toInstall = appPath
|
|
54
|
+
} else {
|
|
55
|
+
const found = await findAppBundle(appPath)
|
|
56
|
+
if (found) {
|
|
57
|
+
toInstall = found
|
|
268
58
|
} else {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// Reuse the existing build() implementation to avoid duplicating the xcodebuild logic
|
|
274
|
-
const buildRes = await this.build(appPath)
|
|
275
|
-
if ((buildRes as any).error) throw new Error((buildRes as any).error)
|
|
276
|
-
toInstall = (buildRes as any).artifactPath
|
|
277
|
-
}
|
|
59
|
+
// Reuse the existing build() implementation to avoid duplicating the xcodebuild logic
|
|
60
|
+
const buildRes = await this.build(appPath)
|
|
61
|
+
if ('error' in buildRes) throw new Error(buildRes.error)
|
|
62
|
+
toInstall = buildRes.artifactPath
|
|
278
63
|
}
|
|
279
64
|
}
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
const res = await execCommand(['simctl', 'install', deviceId, toInstall], deviceId)
|
|
283
|
-
return { device, installed: true, output: res.output }
|
|
284
|
-
} catch (e) {
|
|
285
|
-
// Gather diagnostics for simctl failure
|
|
286
|
-
const diag = execCommandWithDiagnostics(['simctl', 'install', deviceId, toInstall], deviceId)
|
|
287
|
-
try {
|
|
288
|
-
const child = spawn(getIdbCmd(), ['list-targets', '--json'])
|
|
289
|
-
const idbExists = await new Promise<boolean>((resolve) => {
|
|
290
|
-
child.on('error', () => resolve(false));
|
|
291
|
-
child.on('close', (code) => resolve(code === 0));
|
|
292
|
-
});
|
|
293
|
-
if (idbExists) {
|
|
294
|
-
// attempt idb install via spawn but include diagnostics
|
|
295
|
-
await new Promise<void>((resolve, reject) => {
|
|
296
|
-
const proc = spawn(getIdbCmd(), ['install', toInstall, '--udid', device.id]);
|
|
297
|
-
let stderr = '';
|
|
298
|
-
proc.stderr.on('data', d => stderr += d.toString());
|
|
299
|
-
proc.on('close', code => {
|
|
300
|
-
if (code === 0) resolve();
|
|
301
|
-
else reject(new Error(stderr || `idb install failed with code ${code}`));
|
|
302
|
-
});
|
|
303
|
-
proc.on('error', err => reject(err));
|
|
304
|
-
});
|
|
305
|
-
return { device, installed: true }
|
|
306
|
-
}
|
|
307
|
-
} catch {}
|
|
308
|
-
return { device, installed: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
|
|
309
|
-
}
|
|
310
|
-
} catch (e) {
|
|
311
|
-
return { device, installed: false, error: e instanceof Error ? e.message : String(e) }
|
|
312
65
|
}
|
|
66
|
+
|
|
67
|
+
return toInstall
|
|
313
68
|
}
|
|
314
69
|
|
|
315
70
|
async startApp(bundleId: string, deviceId: string = "booted", observe = true): Promise<StartAppResponse> {
|
|
@@ -336,7 +91,7 @@ export class iOSManage {
|
|
|
336
91
|
} catch (e: unknown) {
|
|
337
92
|
const device = await getIOSDeviceMetadata(deviceId)
|
|
338
93
|
const diagnostics = hasFiniteBudget() ? undefined : execCommandWithDiagnostics(['simctl', 'launch', deviceId, bundleId], deviceId)
|
|
339
|
-
return { device, appStarted: false, launchTimeMs: performance.now() - started, error: e instanceof Error ? e.message : String(e), ...(diagnostics ? { diagnostics } : {}) }
|
|
94
|
+
return { device, appStarted: false, launchTimeMs: performance.now() - started, error: e instanceof Error ? e.message : String(e), ...(diagnostics ? { diagnostics } : {}) }
|
|
340
95
|
}
|
|
341
96
|
}
|
|
342
97
|
|
|
@@ -350,7 +105,7 @@ export class iOSManage {
|
|
|
350
105
|
} catch (e: unknown) {
|
|
351
106
|
const diag = hasFiniteBudget() ? undefined : execCommandWithDiagnostics(['simctl', 'terminate', deviceId, bundleId], deviceId)
|
|
352
107
|
const device = await getIOSDeviceMetadata(deviceId)
|
|
353
|
-
return { device, appTerminated: false, error: e instanceof Error ? e.message : String(e), ...(diag ? { diagnostics: diag } : {}) }
|
|
108
|
+
return { device, appTerminated: false, error: e instanceof Error ? e.message : String(e), ...(diag ? { diagnostics: diag } : {}) }
|
|
354
109
|
}
|
|
355
110
|
}
|
|
356
111
|
|
|
@@ -387,19 +142,33 @@ export class iOSManage {
|
|
|
387
142
|
const libraryPath = `${dataPath}/Library`
|
|
388
143
|
const documentsPath = `${dataPath}/Documents`
|
|
389
144
|
const tmpPath = `${dataPath}/tmp`
|
|
390
|
-
await fs.rm(libraryPath, { recursive: true, force: true }).catch(() => {})
|
|
391
|
-
await fs.rm(documentsPath, { recursive: true, force: true }).catch(() => {})
|
|
392
|
-
await fs.rm(tmpPath, { recursive: true, force: true }).catch(() => {})
|
|
393
|
-
await fs.mkdir(libraryPath, { recursive: true }).catch(() => {})
|
|
394
|
-
await fs.mkdir(documentsPath, { recursive: true }).catch(() => {})
|
|
395
|
-
await fs.mkdir(tmpPath, { recursive: true }).catch(() => {})
|
|
145
|
+
await fs.rm(libraryPath, { recursive: true, force: true }).catch(() => { })
|
|
146
|
+
await fs.rm(documentsPath, { recursive: true, force: true }).catch(() => { })
|
|
147
|
+
await fs.rm(tmpPath, { recursive: true, force: true }).catch(() => { })
|
|
148
|
+
await fs.mkdir(libraryPath, { recursive: true }).catch(() => { })
|
|
149
|
+
await fs.mkdir(documentsPath, { recursive: true }).catch(() => { })
|
|
150
|
+
await fs.mkdir(tmpPath, { recursive: true }).catch(() => { })
|
|
396
151
|
return { device, dataCleared: true }
|
|
397
152
|
} catch (e) {
|
|
398
153
|
throw new Error(`Failed to clear data for ${bundleId}: ${e instanceof Error ? e.message : String(e)}`)
|
|
399
154
|
}
|
|
400
155
|
} catch (e: unknown) {
|
|
401
156
|
const diag = execCommandWithDiagnostics(['simctl', 'get_app_container', deviceId, bundleId, 'data'], deviceId)
|
|
402
|
-
return { device, dataCleared: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
|
|
157
|
+
return { device, dataCleared: false, error: e instanceof Error ? e.message : String(e), diagnostics: diag }
|
|
403
158
|
}
|
|
404
159
|
}
|
|
405
160
|
}
|
|
161
|
+
|
|
162
|
+
async function installWithIdb(toInstall: string, deviceId: string) {
|
|
163
|
+
await new Promise<void>((resolve, reject) => {
|
|
164
|
+
checkDebuggerGuard(true)
|
|
165
|
+
const proc = spawn(getIdbCmd(), ['install', toInstall, '--udid', deviceId]);
|
|
166
|
+
let stderr = '';
|
|
167
|
+
proc.stderr.on('data', d => stderr += d.toString());
|
|
168
|
+
proc.on('close', code => {
|
|
169
|
+
if (code === 0) resolve();
|
|
170
|
+
else reject(new Error(stderr || `idb install failed with code ${code}`));
|
|
171
|
+
});
|
|
172
|
+
proc.on('error', err => reject(err));
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const activeLogStreams: Map<string, { proc: import('node:child_process').ChildProcess, file: string, device?: { platform: 'android' | 'ios'; id: string } }> = new Map()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { GetCurrentScreenResponse } from "../../types.js"
|
|
2
|
+
import { execAdb, getAndroidDeviceMetadata, getDeviceInfo } from "../../utils/android/utils.js"
|
|
3
|
+
export async function collectGetCurrentScreen(deviceId?: string): Promise<GetCurrentScreenResponse> {
|
|
4
|
+
const metadata = await getAndroidDeviceMetadata("", deviceId)
|
|
5
|
+
const deviceInfo = getDeviceInfo(deviceId || 'default', metadata)
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const output = await execAdb(['shell', 'dumpsys', 'activity', 'activities'], deviceId, { timeout: 10000 })
|
|
9
|
+
const lines = output.split('\n');
|
|
10
|
+
let resumedLine = lines.find(line => /^\s*mResumedActivity:/.test(line));
|
|
11
|
+
|
|
12
|
+
if (!resumedLine) {
|
|
13
|
+
resumedLine = lines.find(line => /^\s*ResumedActivity:/.test(line));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!resumedLine) {
|
|
17
|
+
return {
|
|
18
|
+
device: deviceInfo,
|
|
19
|
+
package: "",
|
|
20
|
+
activity: "",
|
|
21
|
+
shortActivity: "",
|
|
22
|
+
error: "Could not find 'mResumedActivity' in dumpsys output"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const match = resumedLine.match(/ActivityRecord\{[^ ]*(?:\s+[^ ]+)*\s+([^\/ ]+)\/([^ \{}]+)[^}]*\}/);
|
|
27
|
+
|
|
28
|
+
if (match) {
|
|
29
|
+
const packageName = match[1];
|
|
30
|
+
let activityName = match[2];
|
|
31
|
+
|
|
32
|
+
if (activityName.startsWith('.')) {
|
|
33
|
+
activityName = packageName + activityName;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const shortActivity = activityName.split('.').pop() || activityName;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
device: deviceInfo,
|
|
40
|
+
package: packageName,
|
|
41
|
+
activity: activityName,
|
|
42
|
+
shortActivity: shortActivity
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
return {
|
|
46
|
+
device: deviceInfo,
|
|
47
|
+
package: "",
|
|
48
|
+
activity: "",
|
|
49
|
+
shortActivity: "",
|
|
50
|
+
error: `Found resumed activity line but failed to parse: '${resumedLine.trim()}'`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return {
|
|
56
|
+
device: deviceInfo,
|
|
57
|
+
package: "",
|
|
58
|
+
activity: "",
|
|
59
|
+
shortActivity: "",
|
|
60
|
+
error: e instanceof Error ? e.message : String(e)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { computeScreenFingerprint } from "../../utils/ui/index.js"
|
|
2
|
+
import type { AndroidObserve } from '../android.js'
|
|
3
|
+
export async function collectGetScreenFingerprint(source: Pick<AndroidObserve, 'getUITree' | 'getCurrentScreen'>, deviceId?: string): Promise<{ fingerprint: string | null; activity?: string; error?: string; captured_at_ms?: number }> {
|
|
4
|
+
try {
|
|
5
|
+
const tree = await source.getUITree(deviceId)
|
|
6
|
+
if (!tree || tree.error) return { fingerprint: null, error: tree.error }
|
|
7
|
+
|
|
8
|
+
const current = await source.getCurrentScreen(deviceId).catch(() => null)
|
|
9
|
+
return { ...computeScreenFingerprint(tree, current, 'android', 50), captured_at_ms: tree.captured_at_ms }
|
|
10
|
+
} catch (e) {
|
|
11
|
+
return { fingerprint: null, error: e instanceof Error ? e.message : String(e) }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { StreamLogEntry } from '../logs/model.js'
|
|
2
|
+
import { spawn } from "child_process"
|
|
3
|
+
import { getAdbCmd, execAdb, parseLogLine } from "../../utils/android/utils.js"
|
|
4
|
+
import { createWriteStream } from "fs"
|
|
5
|
+
import { promises as fsPromises } from "fs"
|
|
6
|
+
import path from "path"
|
|
7
|
+
import { activeLogStreams } from './context.js'
|
|
8
|
+
export async function startLogStream(packageName: string, level: 'error' | 'warn' | 'info' | 'debug' = 'error', deviceId?: string, sessionId: string = 'default') {
|
|
9
|
+
try {
|
|
10
|
+
const pidOutput = await execAdb(['shell', 'pidof', packageName], deviceId).catch(() => '')
|
|
11
|
+
const pid = (pidOutput || '').trim()
|
|
12
|
+
if (!pid) return { success: false, error: 'app_not_running' }
|
|
13
|
+
|
|
14
|
+
const levelMap: Record<string, string> = { error: '*:E', warn: '*:W', info: '*:I', debug: '*:D' }
|
|
15
|
+
const filter = levelMap[level] || levelMap['error']
|
|
16
|
+
|
|
17
|
+
if (activeLogStreams.has(sessionId)) {
|
|
18
|
+
try { activeLogStreams.get(sessionId)!.proc.kill() } catch { }
|
|
19
|
+
activeLogStreams.delete(sessionId)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const args = [...(deviceId ? ['-s', deviceId] : []), 'logcat', `--pid=${pid}`, filter]
|
|
23
|
+
const proc = spawn(getAdbCmd(), args)
|
|
24
|
+
|
|
25
|
+
const tmpDir = process.env.TMPDIR || '/tmp'
|
|
26
|
+
const file = path.join(tmpDir, `mobile-debug-log-${sessionId}.ndjson`)
|
|
27
|
+
const stream = createWriteStream(file, { flags: 'a' })
|
|
28
|
+
|
|
29
|
+
proc.stdout.on('data', (chunk) => {
|
|
30
|
+
const text = chunk.toString()
|
|
31
|
+
const lines = text.split(/\r?\n/).filter(Boolean)
|
|
32
|
+
for (const l of lines) {
|
|
33
|
+
const entry = parseLogLine(l)
|
|
34
|
+
stream.write(JSON.stringify(entry) + '\n')
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
proc.stderr.on('data', (chunk) => {
|
|
39
|
+
const text = chunk.toString()
|
|
40
|
+
const lines = text.split(/\r?\n/).filter(Boolean)
|
|
41
|
+
for (const l of lines) {
|
|
42
|
+
const entry = { timestamp: '', level: 'E', tag: 'adb', message: l }
|
|
43
|
+
stream.write(JSON.stringify(entry) + '\n')
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
proc.on('close', () => {
|
|
48
|
+
stream.end()
|
|
49
|
+
if (activeLogStreams.get(sessionId)?.proc === proc) activeLogStreams.delete(sessionId)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
activeLogStreams.set(sessionId, { proc, file, device: { platform: 'android', id: deviceId ?? '' } })
|
|
53
|
+
return { success: true, stream_started: true }
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function stopLogStream(sessionId: string = 'default') {
|
|
60
|
+
const entry = activeLogStreams.get(sessionId)
|
|
61
|
+
if (!entry) return { success: true }
|
|
62
|
+
try { entry.proc.kill() } catch { }
|
|
63
|
+
activeLogStreams.delete(sessionId)
|
|
64
|
+
return { success: true }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function collectReadLogStream(sessionId: string = 'default', limit: number = 100, since?: string) {
|
|
68
|
+
const entry = activeLogStreams.get(sessionId)
|
|
69
|
+
let file: string | undefined
|
|
70
|
+
if (entry && entry.file) file = entry.file
|
|
71
|
+
else {
|
|
72
|
+
const tmpDir = process.env.TMPDIR || '/tmp'
|
|
73
|
+
const candidate = path.join(tmpDir, `mobile-debug-log-${sessionId}.ndjson`)
|
|
74
|
+
file = candidate
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const data = await fsPromises.readFile(file, 'utf8').catch(() => '')
|
|
79
|
+
if (!data) return { entries: [], crash_summary: { crash_detected: false } }
|
|
80
|
+
const lines = data.split(/\r?\n/).filter(Boolean)
|
|
81
|
+
|
|
82
|
+
const parsed = lines.map(l => {
|
|
83
|
+
try {
|
|
84
|
+
const obj = JSON.parse(l) as StreamLogEntry
|
|
85
|
+
if (typeof obj._iso === 'undefined') {
|
|
86
|
+
let iso: string | null = null
|
|
87
|
+
if (obj.timestamp) {
|
|
88
|
+
const d = new Date(obj.timestamp)
|
|
89
|
+
if (!isNaN(d.getTime())) iso = d.toISOString()
|
|
90
|
+
}
|
|
91
|
+
obj._iso = iso
|
|
92
|
+
}
|
|
93
|
+
if (typeof obj.crash === 'undefined') {
|
|
94
|
+
const msg = (obj.message || '').toString()
|
|
95
|
+
const exMatch = msg.match(/\b([A-Za-z0-9_$.]+Exception)\b/)
|
|
96
|
+
if (/FATAL EXCEPTION/i.test(msg) || exMatch) {
|
|
97
|
+
obj.crash = true
|
|
98
|
+
if (exMatch) obj.exception = exMatch[1]
|
|
99
|
+
} else {
|
|
100
|
+
obj.crash = false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return obj
|
|
104
|
+
} catch {
|
|
105
|
+
return { message: l, _iso: null, crash: false }
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
let filtered = parsed
|
|
110
|
+
if (since) {
|
|
111
|
+
let sinceMs: number | null = null
|
|
112
|
+
if (/^\d+$/.test(since)) sinceMs = Number(since)
|
|
113
|
+
else {
|
|
114
|
+
const sDate = new Date(since)
|
|
115
|
+
if (!isNaN(sDate.getTime())) sinceMs = sDate.getTime()
|
|
116
|
+
}
|
|
117
|
+
if (sinceMs !== null) filtered = parsed.filter(p => p._iso && (new Date(p._iso).getTime() >= sinceMs))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const entries = filtered.slice(-Math.max(0, limit))
|
|
121
|
+
const crashEntry = entries.find(e => e.crash)
|
|
122
|
+
const crash_summary = crashEntry ? { crash_detected: true, exception: crashEntry.exception, sample: crashEntry.message } : { crash_detected: false }
|
|
123
|
+
return { entries, crash_summary }
|
|
124
|
+
} catch {
|
|
125
|
+
return { entries: [], crash_summary: { crash_detected: false } }
|
|
126
|
+
}
|
|
127
|
+
}
|