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/dist/observe/ios.js
CHANGED
|
@@ -1,269 +1,13 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { captureEvidence } from './evidence.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { computeScreenFingerprint } from '../utils/ui/index.js';
|
|
13
|
-
import { parsePngSize } from '../utils/image.js';
|
|
14
|
-
import { deriveSnapshotMetadata } from './snapshot-metadata.js';
|
|
15
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
16
|
-
let iosExecCommand = execCommand;
|
|
17
|
-
export function _setIOSExecCommandForTests(fn) {
|
|
18
|
-
iosExecCommand = fn;
|
|
19
|
-
}
|
|
20
|
-
export function _resetIOSExecCommandForTests() {
|
|
21
|
-
iosExecCommand = execCommand;
|
|
22
|
-
}
|
|
23
|
-
function parseIDBFrame(frame) {
|
|
24
|
-
if (!frame)
|
|
25
|
-
return [0, 0, 0, 0];
|
|
26
|
-
if (typeof frame === 'string') {
|
|
27
|
-
const nums = frame.match(/-?\d+(?:\.\d+)?/g);
|
|
28
|
-
if (!nums || nums.length < 4)
|
|
29
|
-
return [0, 0, 0, 0];
|
|
30
|
-
const x = Number(nums[0]);
|
|
31
|
-
const y = Number(nums[1]);
|
|
32
|
-
const w = Number(nums[2]);
|
|
33
|
-
const h = Number(nums[3]);
|
|
34
|
-
return [Math.round(x), Math.round(y), Math.round(x + w), Math.round(y + h)];
|
|
35
|
-
}
|
|
36
|
-
const x = Number(frame.x || 0);
|
|
37
|
-
const y = Number(frame.y || 0);
|
|
38
|
-
const w = Number(frame.width || frame.w || 0);
|
|
39
|
-
const h = Number(frame.height || frame.h || 0);
|
|
40
|
-
return [Math.round(x), Math.round(y), Math.round(x + w), Math.round(y + h)];
|
|
41
|
-
}
|
|
42
|
-
function getCenter(bounds) {
|
|
43
|
-
const [x1, y1, x2, y2] = bounds;
|
|
44
|
-
return [Math.floor((x1 + x2) / 2), Math.floor((y1 + y2) / 2)];
|
|
45
|
-
}
|
|
46
|
-
function parseIOSNumber(value) {
|
|
47
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
48
|
-
return value;
|
|
49
|
-
if (typeof value !== 'string')
|
|
50
|
-
return null;
|
|
51
|
-
const parsed = Number(value);
|
|
52
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
53
|
-
}
|
|
54
|
-
function normalizeIOSType(value) {
|
|
55
|
-
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
56
|
-
}
|
|
57
|
-
function inferIOSRole(type, traits) {
|
|
58
|
-
if (/slider|adjustable/.test(type) || traits.some((trait) => /adjustable|slider/.test(trait)))
|
|
59
|
-
return 'slider';
|
|
60
|
-
if (/stepper/.test(type))
|
|
61
|
-
return 'stepper';
|
|
62
|
-
if (/picker|pop up button|dropdown/.test(type))
|
|
63
|
-
return 'dropdown';
|
|
64
|
-
if (/segmented control/.test(type))
|
|
65
|
-
return 'segmented_control';
|
|
66
|
-
if (/button/.test(type) || traits.some((trait) => /button/.test(trait)))
|
|
67
|
-
return 'button';
|
|
68
|
-
if (/cell/.test(type))
|
|
69
|
-
return 'cell';
|
|
70
|
-
if (/switch/.test(type))
|
|
71
|
-
return 'switch';
|
|
72
|
-
if (/text field|textfield|search field/.test(type))
|
|
73
|
-
return 'text_field';
|
|
74
|
-
if (/image/.test(type))
|
|
75
|
-
return 'image';
|
|
76
|
-
if (/window|application|group|scroll view|collection view/.test(type))
|
|
77
|
-
return 'container';
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
function getIOSStableId(node) {
|
|
81
|
-
const candidates = [node.AXIdentifier, node.accessibilityIdentifier, node.identifier, node.AXUniqueId];
|
|
82
|
-
for (const candidate of candidates) {
|
|
83
|
-
if (typeof candidate === 'string' && candidate.trim().length > 0)
|
|
84
|
-
return candidate;
|
|
85
|
-
}
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
function buildIOSSelectorConfidence(source) {
|
|
89
|
-
switch (source) {
|
|
90
|
-
case 'identifier':
|
|
91
|
-
return { score: 1, reason: 'accessibility_identifier' };
|
|
92
|
-
case 'label':
|
|
93
|
-
return { score: 0.9, reason: 'label_match' };
|
|
94
|
-
case 'value':
|
|
95
|
-
return { score: 0.75, reason: 'value_match' };
|
|
96
|
-
case 'type':
|
|
97
|
-
return { score: 0.35, reason: 'type_match' };
|
|
98
|
-
default:
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function buildIOSSelector(type, label, value, stableId) {
|
|
103
|
-
if (stableId)
|
|
104
|
-
return { value: stableId, confidence: buildIOSSelectorConfidence('identifier') };
|
|
105
|
-
if (label)
|
|
106
|
-
return { value: label, confidence: buildIOSSelectorConfidence('label') };
|
|
107
|
-
if (value)
|
|
108
|
-
return { value: value, confidence: buildIOSSelectorConfidence('value') };
|
|
109
|
-
if (type)
|
|
110
|
-
return { value: type, confidence: buildIOSSelectorConfidence('type') };
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
function buildIOSSemantic(type, traits, role, value) {
|
|
114
|
-
const semantic = {
|
|
115
|
-
is_clickable: traits.includes("UIAccessibilityTraitButton") || /adjustable|slider/.test(type) || type === "Button" || type === "Cell",
|
|
116
|
-
is_container: /window|application|group|scroll view|collection view/.test(type)
|
|
117
|
-
};
|
|
118
|
-
if (role === 'slider') {
|
|
119
|
-
semantic.semantic_role = 'slider';
|
|
120
|
-
semantic.adjustable = true;
|
|
121
|
-
semantic.supported_actions = ['adjust'];
|
|
122
|
-
semantic.state_shape = 'continuous';
|
|
123
|
-
}
|
|
124
|
-
else if (role === 'stepper') {
|
|
125
|
-
semantic.semantic_role = 'stepper';
|
|
126
|
-
semantic.adjustable = true;
|
|
127
|
-
semantic.supported_actions = ['increment', 'decrement'];
|
|
128
|
-
semantic.state_shape = 'discrete';
|
|
129
|
-
}
|
|
130
|
-
else if (role === 'dropdown') {
|
|
131
|
-
semantic.semantic_role = 'dropdown';
|
|
132
|
-
semantic.supported_actions = ['tap', 'expand'];
|
|
133
|
-
semantic.state_shape = 'semantic';
|
|
134
|
-
}
|
|
135
|
-
else if (role === 'segmented_control') {
|
|
136
|
-
semantic.semantic_role = 'segmented_control';
|
|
137
|
-
semantic.supported_actions = ['tap'];
|
|
138
|
-
semantic.state_shape = 'discrete';
|
|
139
|
-
}
|
|
140
|
-
else if (traits.some((trait) => /adjustable|slider/i.test(trait)) || /adjustable|slider/.test(type)) {
|
|
141
|
-
semantic.semantic_role = 'custom_adjustable';
|
|
142
|
-
semantic.adjustable = true;
|
|
143
|
-
semantic.supported_actions = ['adjust'];
|
|
144
|
-
semantic.state_shape = 'continuous';
|
|
145
|
-
}
|
|
146
|
-
else if (semantic.is_clickable) {
|
|
147
|
-
semantic.supported_actions = ['tap'];
|
|
148
|
-
}
|
|
149
|
-
if (semantic.state_shape === undefined && semantic.adjustable && value !== null) {
|
|
150
|
-
const numericValue = parseIOSNumber(value);
|
|
151
|
-
if (numericValue !== null && numericValue >= 0 && numericValue <= 1) {
|
|
152
|
-
semantic.state_shape = 'continuous';
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return semantic;
|
|
156
|
-
}
|
|
157
|
-
function isIOSAdjustable(node, type, traits) {
|
|
158
|
-
return /slider|adjustable|stepper/i.test(type) || traits.some((trait) => /adjustable|slider/i.test(trait));
|
|
159
|
-
}
|
|
160
|
-
function extractIOSState(node, type, label, value, traits) {
|
|
161
|
-
const state = {};
|
|
162
|
-
const normalizedTraits = traits.map((trait) => String(trait).toLowerCase());
|
|
163
|
-
if (normalizedTraits.some((trait) => /selected/.test(trait))) {
|
|
164
|
-
state.selected = label || value || true;
|
|
165
|
-
}
|
|
166
|
-
if (normalizedTraits.some((trait) => /focused/.test(trait))) {
|
|
167
|
-
state.focused = true;
|
|
168
|
-
}
|
|
169
|
-
if (normalizedTraits.some((trait) => /enabled/.test(trait))) {
|
|
170
|
-
state.enabled = true;
|
|
171
|
-
}
|
|
172
|
-
if (normalizedTraits.some((trait) => /disabled/.test(trait))) {
|
|
173
|
-
state.enabled = false;
|
|
174
|
-
}
|
|
175
|
-
if (value && /textfield|search|text/i.test(type)) {
|
|
176
|
-
state.text_value = value;
|
|
177
|
-
}
|
|
178
|
-
if (isIOSAdjustable(node, type, traits)) {
|
|
179
|
-
const rawValue = parseIOSNumber(value);
|
|
180
|
-
if (rawValue !== null) {
|
|
181
|
-
state.raw_value = rawValue;
|
|
182
|
-
state.value = rawValue >= 0 && rawValue <= 1 ? Math.round(rawValue * 100) : rawValue;
|
|
183
|
-
}
|
|
184
|
-
else if (value) {
|
|
185
|
-
state.raw_value = value;
|
|
186
|
-
state.value = value;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else if (value) {
|
|
190
|
-
const numericValue = parseIOSNumber(value);
|
|
191
|
-
if (numericValue !== null) {
|
|
192
|
-
state.value = numericValue;
|
|
193
|
-
state.raw_value = numericValue;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
state.value = value;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return Object.keys(state).length > 0 ? state : null;
|
|
200
|
-
}
|
|
201
|
-
export function traverseIDBNode(node, elements, parentIndex = -1, depth = 0) {
|
|
202
|
-
if (!node)
|
|
203
|
-
return -1;
|
|
204
|
-
let currentIndex = -1;
|
|
205
|
-
const type = node.AXElementType || node.type || "unknown";
|
|
206
|
-
const label = node.AXLabel || node.label || null;
|
|
207
|
-
const value = node.AXValue || null;
|
|
208
|
-
const frame = node.AXFrame || node.frame;
|
|
209
|
-
const traits = node.AXTraits || [];
|
|
210
|
-
const state = extractIOSState(node, type, label, value, traits);
|
|
211
|
-
const normalizedType = normalizeIOSType(type);
|
|
212
|
-
const stableId = getIOSStableId(node);
|
|
213
|
-
const selector = buildIOSSelector(type, label, value, stableId);
|
|
214
|
-
const role = inferIOSRole(normalizedType, traits);
|
|
215
|
-
const semantic = buildIOSSemantic(normalizedType, traits, role, value);
|
|
216
|
-
const clickable = traits.includes("UIAccessibilityTraitButton") || type === "Button" || type === "Cell";
|
|
217
|
-
const isUseful = clickable || (label && label.length > 0) || (value && value.length > 0) || type === "Application" || type === "Window";
|
|
218
|
-
if (isUseful) {
|
|
219
|
-
const bounds = parseIDBFrame(frame);
|
|
220
|
-
const element = {
|
|
221
|
-
text: label,
|
|
222
|
-
contentDescription: value,
|
|
223
|
-
type: type,
|
|
224
|
-
resourceId: stableId,
|
|
225
|
-
clickable: clickable,
|
|
226
|
-
enabled: true,
|
|
227
|
-
visible: true,
|
|
228
|
-
bounds: bounds,
|
|
229
|
-
center: getCenter(bounds),
|
|
230
|
-
depth: depth,
|
|
231
|
-
state,
|
|
232
|
-
stable_id: stableId,
|
|
233
|
-
role,
|
|
234
|
-
test_tag: stableId,
|
|
235
|
-
selector,
|
|
236
|
-
semantic
|
|
237
|
-
};
|
|
238
|
-
if (parentIndex !== -1) {
|
|
239
|
-
element.parentId = parentIndex;
|
|
240
|
-
}
|
|
241
|
-
elements.push(element);
|
|
242
|
-
currentIndex = elements.length - 1;
|
|
243
|
-
}
|
|
244
|
-
const nextParentIndex = currentIndex !== -1 ? currentIndex : parentIndex;
|
|
245
|
-
const nextDepth = currentIndex !== -1 ? depth + 1 : depth;
|
|
246
|
-
const childrenIndices = [];
|
|
247
|
-
if (node.children && Array.isArray(node.children)) {
|
|
248
|
-
for (const child of node.children) {
|
|
249
|
-
const childIndex = traverseIDBNode(child, elements, nextParentIndex, nextDepth);
|
|
250
|
-
if (childIndex !== -1) {
|
|
251
|
-
childrenIndices.push(childIndex);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (currentIndex !== -1 && childrenIndices.length > 0) {
|
|
256
|
-
elements[currentIndex].children = childrenIndices;
|
|
257
|
-
}
|
|
258
|
-
return currentIndex;
|
|
259
|
-
}
|
|
260
|
-
const iosActiveLogStreams = new Map();
|
|
261
|
-
export function _setIOSActiveLogStream(sessionId, file, device) {
|
|
262
|
-
iosActiveLogStreams.set(sessionId, { proc: {}, file, device });
|
|
263
|
-
}
|
|
264
|
-
export function _clearIOSActiveLogStream(sessionId) {
|
|
265
|
-
iosActiveLogStreams.delete(sessionId);
|
|
266
|
-
}
|
|
2
|
+
import { getIOSDeviceMetadata } from "../utils/ios/utils.js";
|
|
3
|
+
import { iosActiveLogStreams } from './ios/context.js';
|
|
4
|
+
import { collectGetUITree } from './ios/ui-tree.js';
|
|
5
|
+
import { collectGetLogs } from './ios/logs.js';
|
|
6
|
+
import { collectCaptureScreenshot } from './ios/screenshot.js';
|
|
7
|
+
import { collectGetScreenFingerprint } from './ios/fingerprint.js';
|
|
8
|
+
import { startLogStream, stopLogStream, readLogStream, collectReadLogStream } from './ios/log-stream.js';
|
|
9
|
+
export { traverseIDBNode } from './ios/ui-elements.js';
|
|
10
|
+
export { _setIOSExecCommandForTests, _resetIOSExecCommandForTests, _setIOSActiveLogStream, _clearIOSActiveLogStream } from './ios/context.js';
|
|
267
11
|
export class iOSObserve {
|
|
268
12
|
getLogStreamDevice(sessionId = 'default') { return iosActiveLogStreams.get(sessionId)?.device ?? null; }
|
|
269
13
|
async getDeviceMetadata(deviceId = "booted") {
|
|
@@ -272,463 +16,21 @@ export class iOSObserve {
|
|
|
272
16
|
async getLogs(filters = {}) {
|
|
273
17
|
return captureEvidence('logs', { platform: 'ios', id: filters.deviceId ?? '' }, () => this.collectGetLogs(filters));
|
|
274
18
|
}
|
|
275
|
-
async collectGetLogs(filters = {}) {
|
|
276
|
-
const { appId, deviceId = 'booted', pid, tag, level, contains, since_seconds, limit } = filters;
|
|
277
|
-
const args = ['simctl', 'spawn', deviceId, 'log', 'show', '--style', 'syslog'];
|
|
278
|
-
// Default to last N seconds if no since_seconds provided; limit lines handled after parsing
|
|
279
|
-
const effectiveLimit = typeof limit === 'number' && limit > 0 ? limit : 50;
|
|
280
|
-
if (since_seconds) {
|
|
281
|
-
// log show accepts --last <time>
|
|
282
|
-
args.push('--last', `${since_seconds}s`);
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
// default to last 60s to keep quick
|
|
286
|
-
args.push('--last', '60s');
|
|
287
|
-
}
|
|
288
|
-
let processNameUsed = undefined;
|
|
289
|
-
if (appId) {
|
|
290
|
-
validateBundleId(appId);
|
|
291
|
-
// prefer matching the simple process name (last segment of bundle id), but also match full bundle id in subsystem
|
|
292
|
-
const parts = appId.split('.');
|
|
293
|
-
const simpleName = parts[parts.length - 1];
|
|
294
|
-
processNameUsed = simpleName;
|
|
295
|
-
// predicate: match process by simple name or full bundle id, or subsystem contains bundle id
|
|
296
|
-
args.push('--predicate', `process == "${simpleName}" or process == "${appId}" or subsystem contains "${appId}"`);
|
|
297
|
-
}
|
|
298
|
-
else if (tag) {
|
|
299
|
-
// predicate by subsystem/category
|
|
300
|
-
args.push('--predicate', `subsystem contains "${tag}"`);
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
// Attempt pid detection if appId provided and no explicit pid supplied — prefer process name derived from bundle id
|
|
304
|
-
let detectedPid = null;
|
|
305
|
-
if (appId && !pid) {
|
|
306
|
-
const parts = appId.split('.');
|
|
307
|
-
const simpleName = parts[parts.length - 1];
|
|
308
|
-
try {
|
|
309
|
-
const pgrepRes = await iosExecCommand(['simctl', 'spawn', deviceId, 'pgrep', '-f', simpleName], deviceId);
|
|
310
|
-
const out = pgrepRes && pgrepRes.output ? pgrepRes.output.trim() : '';
|
|
311
|
-
const firstLine = out.split(/\r?\n/).find(Boolean);
|
|
312
|
-
if (firstLine) {
|
|
313
|
-
const n = Number(firstLine.trim());
|
|
314
|
-
if (!isNaN(n) && n > 0)
|
|
315
|
-
detectedPid = n;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
// ignore pgrep failures — we'll fall back to process/bundle matching
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
const effectivePid = pid || detectedPid || null;
|
|
323
|
-
const result = await iosExecCommand(args, deviceId);
|
|
324
|
-
const device = await getIOSDeviceMetadata(deviceId);
|
|
325
|
-
const rawLines = result.output ? result.output.split(/\r?\n/).filter(Boolean) : [];
|
|
326
|
-
// Parse lines into structured entries. iOS log format: timestamp [PID:tid] <level> subsystem:category: message
|
|
327
|
-
const parsed = rawLines.map(line => {
|
|
328
|
-
// Example: 2023-08-12 12:34:56.789012+0000 pid <Debug> MyApp[123:456] <info> MySubsystem: MyCategory: Message here
|
|
329
|
-
// Simpler approach: try to extract ISO timestamp at start
|
|
330
|
-
let ts = null;
|
|
331
|
-
const tsMatch = line.match(/^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d+)?)/);
|
|
332
|
-
if (tsMatch) {
|
|
333
|
-
const d = new Date(tsMatch[1]);
|
|
334
|
-
if (!isNaN(d.getTime()))
|
|
335
|
-
ts = d.toISOString();
|
|
336
|
-
}
|
|
337
|
-
// level mapping
|
|
338
|
-
let lvl = 'INFO';
|
|
339
|
-
const lvlMatch = line.match(/\b(Debug|Info|Default|Error|Fault|Warning)\b/i);
|
|
340
|
-
if (lvlMatch) {
|
|
341
|
-
const map = { 'debug': 'DEBUG', 'info': 'INFO', 'default': 'DEBUG', 'error': 'ERROR', 'fault': 'ERROR', 'warning': 'WARN' };
|
|
342
|
-
lvl = map[(lvlMatch[1] || '').toLowerCase()] || 'INFO';
|
|
343
|
-
}
|
|
344
|
-
// subsystem/category -> tag
|
|
345
|
-
let tagVal = '';
|
|
346
|
-
const tagMatch = line.match(/\s([A-Za-z0-9_./-]+):\s/);
|
|
347
|
-
if (tagMatch)
|
|
348
|
-
tagVal = tagMatch[1];
|
|
349
|
-
// pid extraction
|
|
350
|
-
let pidNum = null;
|
|
351
|
-
const pidMatch = line.match(/\[(\d+):\d+\]/);
|
|
352
|
-
if (pidMatch)
|
|
353
|
-
pidNum = Number(pidMatch[1]);
|
|
354
|
-
// message: extract robustly after the subsystem/category token when available
|
|
355
|
-
let message = line;
|
|
356
|
-
if (tagMatch) {
|
|
357
|
-
// tagMatch[0] includes the delimiter (e.g. " MySubsystem: ") — use it to find the message start
|
|
358
|
-
const marker = tagMatch[0];
|
|
359
|
-
const idx = line.indexOf(marker);
|
|
360
|
-
if (idx !== -1) {
|
|
361
|
-
message = line.slice(idx + marker.length).trim();
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
// fallback: try to trim off common prefixes (timestamp, pid, level) and keep the rest
|
|
365
|
-
const afterPidMatch = line.match(/\]\s+/);
|
|
366
|
-
if (afterPidMatch) {
|
|
367
|
-
const afterPidIdx = line.indexOf(afterPidMatch[0]) + afterPidMatch[0].length;
|
|
368
|
-
message = line.slice(afterPidIdx).trim();
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
// remove leading level tokens like <Debug> and keep remainder
|
|
372
|
-
message = line.replace(/^.*?<.*?>\s*/, '').trim();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
// No tag found — strip obvious prefixes and keep remainder (preserve colons in message)
|
|
378
|
-
message = line.replace(/^.*?<.*?>\s*/, '').trim();
|
|
379
|
-
}
|
|
380
|
-
return { timestamp: ts, level: lvl, tag: tagVal, pid: pidNum, message };
|
|
381
|
-
});
|
|
382
|
-
// Apply contains filter
|
|
383
|
-
let filtered = parsed;
|
|
384
|
-
if (contains)
|
|
385
|
-
filtered = filtered.filter(e => e.message && e.message.includes(contains));
|
|
386
|
-
// Apply since_seconds already applied by log show, but double-check timestamps
|
|
387
|
-
if (since_seconds) {
|
|
388
|
-
const sinceMs = Date.now() - (since_seconds * 1000);
|
|
389
|
-
filtered = filtered.filter(e => e.timestamp && (new Date(e.timestamp).getTime() >= sinceMs));
|
|
390
|
-
}
|
|
391
|
-
// level filter
|
|
392
|
-
if (level) {
|
|
393
|
-
const L = level.toUpperCase();
|
|
394
|
-
filtered = filtered.filter(e => e.level && e.level.toUpperCase() === L);
|
|
395
|
-
}
|
|
396
|
-
// tag filter
|
|
397
|
-
if (tag)
|
|
398
|
-
filtered = filtered.filter(e => e.tag && e.tag.includes(tag));
|
|
399
|
-
// pid filter (use detected/effective pid if available)
|
|
400
|
-
const pidToFilter = effectivePid;
|
|
401
|
-
if (pidToFilter)
|
|
402
|
-
filtered = filtered.filter(e => e.pid === pidToFilter);
|
|
403
|
-
// If appId present but no predicate returned lines, try substring match
|
|
404
|
-
if (appId && filtered.length === 0) {
|
|
405
|
-
const matched = parsed.filter(e => (e.message && e.message.includes(appId)) || (e.tag && e.tag.includes(appId)) || (e.message && processNameUsed && e.message.includes(processNameUsed)));
|
|
406
|
-
if (matched.length > 0)
|
|
407
|
-
filtered = matched;
|
|
408
|
-
}
|
|
409
|
-
// Order oldest -> newest
|
|
410
|
-
filtered.sort((a, b) => {
|
|
411
|
-
const ta = a.timestamp ? new Date(a.timestamp).getTime() : 0;
|
|
412
|
-
const tb = b.timestamp ? new Date(b.timestamp).getTime() : 0;
|
|
413
|
-
return ta - tb;
|
|
414
|
-
});
|
|
415
|
-
const source = pidToFilter ? 'pid' : (appId ? 'process' : 'broad');
|
|
416
|
-
const meta = { appIdProvided: !!appId, processNameUsed: processNameUsed || null, detectedPid: detectedPid || null, filters: { tag, level, contains, since_seconds, limit: effectiveLimit }, pidExplicit: !!pid };
|
|
417
|
-
const limited = filtered.slice(-Math.max(0, effectiveLimit));
|
|
418
|
-
return { device, logs: limited, logCount: limited.length, source, meta };
|
|
419
|
-
}
|
|
420
|
-
catch (err) {
|
|
421
|
-
console.error('iOS getLogs failed:', err);
|
|
422
|
-
const device = await getIOSDeviceMetadata(deviceId);
|
|
423
|
-
return { device, logs: [], logCount: 0, source: 'broad', meta: { error: err instanceof Error ? err.message : String(err) } };
|
|
424
|
-
}
|
|
425
|
-
}
|
|
19
|
+
async collectGetLogs(filters = {}) { return collectGetLogs(filters); }
|
|
426
20
|
async captureScreenshot(deviceId = "booted") {
|
|
427
21
|
return captureEvidence('screenshot', { platform: 'ios', id: deviceId ?? '' }, () => this.collectCaptureScreenshot(deviceId));
|
|
428
22
|
}
|
|
429
|
-
async collectCaptureScreenshot(deviceId = "booted") {
|
|
430
|
-
const device = await getIOSDeviceMetadata(deviceId);
|
|
431
|
-
const tmpFile = `/tmp/mcp-ios-screenshot-${randomUUID()}.png`;
|
|
432
|
-
try {
|
|
433
|
-
await iosExecCommand(['simctl', 'io', deviceId, 'screenshot', tmpFile], deviceId);
|
|
434
|
-
const buffer = await fs.readFile(tmpFile);
|
|
435
|
-
const base64 = buffer.toString('base64');
|
|
436
|
-
const dims = parsePngSize(buffer);
|
|
437
|
-
// Try to generate WebP (preferred) and JPEG fallback using sharp (in-process, cross-platform)
|
|
438
|
-
try {
|
|
439
|
-
const sharpModule = await import('sharp');
|
|
440
|
-
const sharp = sharpModule && sharpModule.default ? sharpModule.default : sharpModule;
|
|
441
|
-
const img = sharp(buffer);
|
|
442
|
-
const meta = await img.metadata().catch((err) => { console.error('sharp.metadata failed:', err); return {}; });
|
|
443
|
-
// If image has alpha channel, prefer lossless PNG to preserve transparency
|
|
444
|
-
const hasAlpha = !!meta.hasAlpha || (meta.channels && meta.channels > 3);
|
|
445
|
-
// Generate WebP and JPEG buffers; log failures
|
|
446
|
-
let webpBuf = null;
|
|
447
|
-
let jpegBuf = null;
|
|
448
|
-
try {
|
|
449
|
-
webpBuf = await img.webp({ quality: 80 }).toBuffer();
|
|
450
|
-
}
|
|
451
|
-
catch (err) {
|
|
452
|
-
console.error('WebP conversion failed (iOS):', err instanceof Error ? err.message : String(err));
|
|
453
|
-
webpBuf = null;
|
|
454
|
-
}
|
|
455
|
-
try {
|
|
456
|
-
jpegBuf = await img.jpeg({ quality: 80 }).toBuffer();
|
|
457
|
-
}
|
|
458
|
-
catch (err) {
|
|
459
|
-
console.error('JPEG conversion failed (iOS):', err instanceof Error ? err.message : String(err));
|
|
460
|
-
jpegBuf = null;
|
|
461
|
-
}
|
|
462
|
-
await fs.rm(tmpFile).catch(() => { });
|
|
463
|
-
if (hasAlpha) {
|
|
464
|
-
// preserve alpha: return PNG if WebP not available
|
|
465
|
-
if (webpBuf) {
|
|
466
|
-
return { device, screenshot: webpBuf.toString('base64'), screenshot_mime: 'image/webp', screenshot_fallback: base64, screenshot_fallback_mime: 'image/png', resolution: { width: dims.width, height: dims.height } };
|
|
467
|
-
}
|
|
468
|
-
// if webp unavailable, return original PNG
|
|
469
|
-
return { device, screenshot: base64, screenshot_mime: 'image/png', resolution: { width: dims.width, height: dims.height } };
|
|
470
|
-
}
|
|
471
|
-
// No alpha: prefer webp, fall back to jpeg
|
|
472
|
-
if (webpBuf) {
|
|
473
|
-
return { device, screenshot: webpBuf.toString('base64'), screenshot_mime: 'image/webp', screenshot_fallback: jpegBuf ? jpegBuf.toString('base64') : undefined, screenshot_fallback_mime: jpegBuf ? 'image/jpeg' : undefined, resolution: { width: dims.width, height: dims.height } };
|
|
474
|
-
}
|
|
475
|
-
if (jpegBuf) {
|
|
476
|
-
return { device, screenshot: jpegBuf.toString('base64'), screenshot_mime: 'image/jpeg', resolution: { width: dims.width, height: dims.height } };
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
catch (err) {
|
|
480
|
-
console.error('Screenshot conversion pipeline failed (iOS):', err instanceof Error ? err.message : String(err));
|
|
481
|
-
// fall through to png fallback
|
|
482
|
-
}
|
|
483
|
-
await fs.rm(tmpFile).catch(() => { });
|
|
484
|
-
return {
|
|
485
|
-
device,
|
|
486
|
-
screenshot: base64,
|
|
487
|
-
screenshot_mime: 'image/png',
|
|
488
|
-
resolution: { width: dims.width, height: dims.height },
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
catch (e) {
|
|
492
|
-
await fs.rm(tmpFile).catch(() => { });
|
|
493
|
-
throw new Error(`Failed to capture screenshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
23
|
+
async collectCaptureScreenshot(deviceId = "booted") { return collectCaptureScreenshot(deviceId); }
|
|
496
24
|
async getUITree(deviceId = "booted") {
|
|
497
25
|
return captureEvidence('ui_tree', { platform: 'ios', id: deviceId ?? '' }, () => this.collectGetUITree(deviceId));
|
|
498
26
|
}
|
|
499
|
-
async collectGetUITree(deviceId = "booted") {
|
|
500
|
-
const measurement = createObservationMeasurement('ios');
|
|
501
|
-
let device;
|
|
502
|
-
try {
|
|
503
|
-
device = await measurement.timed('metadata_ms', () => getIOSDeviceMetadata(deviceId));
|
|
504
|
-
}
|
|
505
|
-
catch (error) {
|
|
506
|
-
measurement.setOutcome('unavailable');
|
|
507
|
-
measurement.finish();
|
|
508
|
-
throw error;
|
|
509
|
-
}
|
|
510
|
-
const deviceKey = `ios:${device.id}`;
|
|
511
|
-
const idbExists = await measurement.timed('capability_check_ms', () => isIDBInstalled());
|
|
512
|
-
if (!idbExists) {
|
|
513
|
-
const snapshotMetadata = deriveSnapshotMetadata(deviceKey, null, 'ui_tree');
|
|
514
|
-
measurement.setOutcome('unavailable');
|
|
515
|
-
measurement.finish();
|
|
516
|
-
return {
|
|
517
|
-
device,
|
|
518
|
-
screen: "",
|
|
519
|
-
resolution: { width: 0, height: 0 },
|
|
520
|
-
elements: [],
|
|
521
|
-
...snapshotMetadata,
|
|
522
|
-
error: "iOS UI tree retrieval requires 'idb' (iOS Device Bridge). Please install it via Homebrew: `brew tap facebook/fb && brew install idb-companion` and `pip3 install fb-idb`."
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
const targetUdid = (device.id && device.id !== 'booted') ? device.id : undefined;
|
|
526
|
-
let jsonContent = null;
|
|
527
|
-
let attempts = 0;
|
|
528
|
-
const maxAttempts = 3;
|
|
529
|
-
while (attempts < maxAttempts) {
|
|
530
|
-
remainingBudget();
|
|
531
|
-
attempts++;
|
|
532
|
-
try {
|
|
533
|
-
await measurement.timed('observation_wait_ms', () => delay(Math.min(remainingBudget(300 + (attempts * 100)), 300 + (attempts * 100))));
|
|
534
|
-
remainingBudget();
|
|
535
|
-
const args = ['ui', 'describe-all', '--json'];
|
|
536
|
-
if (targetUdid) {
|
|
537
|
-
args.push('--udid', targetUdid);
|
|
538
|
-
}
|
|
539
|
-
const output = await measurement.physicalRead(async () => {
|
|
540
|
-
const result = await execCmd(getIdbCmd(), args, { timeout: remainingBudget() });
|
|
541
|
-
if (result.stderr === 'ACTION_TIMEOUT')
|
|
542
|
-
throw new Error('ACTION_TIMEOUT');
|
|
543
|
-
if (result.exitCode !== 0)
|
|
544
|
-
throw new Error(`idb failed (code ${result.exitCode}): ${result.stderr}`);
|
|
545
|
-
if (!result.stdout.trim())
|
|
546
|
-
throw new Error('Empty idb UI tree output');
|
|
547
|
-
return result.stdout;
|
|
548
|
-
});
|
|
549
|
-
if (output && output.trim().length > 0) {
|
|
550
|
-
jsonContent = await measurement.timed('parse_ms', async () => JSON.parse(output));
|
|
551
|
-
break; // Success
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
catch (e) {
|
|
555
|
-
if (e instanceof Error && e.message === 'ACTION_TIMEOUT')
|
|
556
|
-
throw e;
|
|
557
|
-
console.error(`Attempt ${attempts} failed: ${e}`);
|
|
558
|
-
}
|
|
559
|
-
if (attempts === maxAttempts) {
|
|
560
|
-
const snapshotMetadata = deriveSnapshotMetadata(deviceKey, null, 'ui_tree');
|
|
561
|
-
measurement.setOutcome('unavailable');
|
|
562
|
-
measurement.finish();
|
|
563
|
-
return {
|
|
564
|
-
device,
|
|
565
|
-
screen: "",
|
|
566
|
-
resolution: { width: 0, height: 0 },
|
|
567
|
-
elements: [],
|
|
568
|
-
...snapshotMetadata,
|
|
569
|
-
error: `Failed to retrieve valid UI dump after ${maxAttempts} attempts.`
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
try {
|
|
574
|
-
const elements = await measurement.timed('conversion_ms', async () => {
|
|
575
|
-
const parsed = [];
|
|
576
|
-
if (Array.isArray(jsonContent)) {
|
|
577
|
-
for (const node of jsonContent)
|
|
578
|
-
traverseIDBNode(node, parsed);
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
traverseIDBNode(jsonContent, parsed);
|
|
582
|
-
}
|
|
583
|
-
return parsed;
|
|
584
|
-
});
|
|
585
|
-
let width = 0;
|
|
586
|
-
let height = 0;
|
|
587
|
-
if (elements.length > 0) {
|
|
588
|
-
const rootBounds = elements[0].bounds;
|
|
589
|
-
width = rootBounds[2] - rootBounds[0];
|
|
590
|
-
height = rootBounds[3] - rootBounds[1];
|
|
591
|
-
}
|
|
592
|
-
const snapshotMetadata = deriveSnapshotMetadata(deviceKey, {
|
|
593
|
-
screen: "",
|
|
594
|
-
resolution: { width, height },
|
|
595
|
-
elements
|
|
596
|
-
}, 'ui_tree');
|
|
597
|
-
measurement.setOutcome('observed');
|
|
598
|
-
const response = measurement.bindObservation({
|
|
599
|
-
device,
|
|
600
|
-
screen: "",
|
|
601
|
-
resolution: { width, height },
|
|
602
|
-
elements,
|
|
603
|
-
...snapshotMetadata
|
|
604
|
-
});
|
|
605
|
-
measurement.finish();
|
|
606
|
-
return response;
|
|
607
|
-
}
|
|
608
|
-
catch (e) {
|
|
609
|
-
const snapshotMetadata = deriveSnapshotMetadata(deviceKey, null, 'ui_tree');
|
|
610
|
-
measurement.setOutcome('unavailable');
|
|
611
|
-
measurement.finish();
|
|
612
|
-
return {
|
|
613
|
-
device,
|
|
614
|
-
screen: "",
|
|
615
|
-
resolution: { width: 0, height: 0 },
|
|
616
|
-
elements: [],
|
|
617
|
-
...snapshotMetadata,
|
|
618
|
-
error: `Failed to parse idb output: ${e instanceof Error ? e.message : String(e)}`
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
}
|
|
27
|
+
async collectGetUITree(deviceId = "booted") { return collectGetUITree(deviceId); }
|
|
622
28
|
async getScreenFingerprint(deviceId = 'booted') {
|
|
623
29
|
return captureEvidence('fingerprint', { platform: 'ios', id: deviceId ?? '' }, () => this.collectGetScreenFingerprint(deviceId));
|
|
624
30
|
}
|
|
625
|
-
async collectGetScreenFingerprint(deviceId = 'booted') {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return { ...computeScreenFingerprint(tree, null, 'ios', 50), captured_at_ms: tree.captured_at_ms };
|
|
631
|
-
}
|
|
632
|
-
catch (e) {
|
|
633
|
-
return { fingerprint: null, error: e instanceof Error ? e.message : String(e) };
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
async startLogStream(bundleId, deviceId = 'booted', sessionId = 'default') {
|
|
637
|
-
try {
|
|
638
|
-
const simple = bundleId.split('.').pop() || bundleId;
|
|
639
|
-
const predicate = `process == "${simple}" or process == "${bundleId}" or subsystem contains "${bundleId}"`;
|
|
640
|
-
if (iosActiveLogStreams.has(sessionId)) {
|
|
641
|
-
try {
|
|
642
|
-
iosActiveLogStreams.get(sessionId).proc.kill();
|
|
643
|
-
}
|
|
644
|
-
catch { }
|
|
645
|
-
iosActiveLogStreams.delete(sessionId);
|
|
646
|
-
}
|
|
647
|
-
const args = ['simctl', 'spawn', deviceId, 'log', 'stream', '--style', 'syslog', '--predicate', predicate];
|
|
648
|
-
const proc = spawn(getXcrunCmd(), args);
|
|
649
|
-
const tmpDir = process.env.TMPDIR || '/tmp';
|
|
650
|
-
const file = path.join(tmpDir, `mobile-debug-ios-log-${sessionId}.ndjson`);
|
|
651
|
-
const stream = createWriteStream(file, { flags: 'a' });
|
|
652
|
-
proc.stdout.on('data', (chunk) => {
|
|
653
|
-
const text = chunk.toString();
|
|
654
|
-
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
655
|
-
for (const l of lines) {
|
|
656
|
-
const entry = parseLogLine(l);
|
|
657
|
-
stream.write(JSON.stringify(entry) + '\n');
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
proc.stderr.on('data', (chunk) => {
|
|
661
|
-
const text = chunk.toString();
|
|
662
|
-
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
663
|
-
for (const l of lines) {
|
|
664
|
-
const entry = { timestamp: '', level: 'E', tag: 'xcrun', message: l };
|
|
665
|
-
stream.write(JSON.stringify(entry) + '\n');
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
proc.on('close', () => {
|
|
669
|
-
stream.end();
|
|
670
|
-
if (iosActiveLogStreams.get(sessionId)?.proc === proc)
|
|
671
|
-
iosActiveLogStreams.delete(sessionId);
|
|
672
|
-
});
|
|
673
|
-
iosActiveLogStreams.set(sessionId, { proc, file, device: { platform: 'ios', id: deviceId ?? '' } });
|
|
674
|
-
return { success: true, stream_started: true };
|
|
675
|
-
}
|
|
676
|
-
catch {
|
|
677
|
-
return { success: false, error: 'log_stream_start_failed' };
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
async stopLogStream(sessionId = 'default') {
|
|
681
|
-
const entry = iosActiveLogStreams.get(sessionId);
|
|
682
|
-
if (!entry)
|
|
683
|
-
return { success: true };
|
|
684
|
-
try {
|
|
685
|
-
entry.proc.kill();
|
|
686
|
-
}
|
|
687
|
-
catch { }
|
|
688
|
-
iosActiveLogStreams.delete(sessionId);
|
|
689
|
-
return { success: true };
|
|
690
|
-
}
|
|
691
|
-
async readLogStream(sessionId = 'default', limit = 100, since) {
|
|
692
|
-
return captureEvidence('logs', this.getLogStreamDevice(sessionId), () => this.collectReadLogStream(sessionId, limit, since));
|
|
693
|
-
}
|
|
694
|
-
async collectReadLogStream(sessionId = 'default', limit = 100, since) {
|
|
695
|
-
const entry = iosActiveLogStreams.get(sessionId);
|
|
696
|
-
if (!entry)
|
|
697
|
-
return { entries: [] };
|
|
698
|
-
try {
|
|
699
|
-
const data = await fsPromises.readFile(entry.file, 'utf8').catch(() => '');
|
|
700
|
-
if (!data)
|
|
701
|
-
return { entries: [], crash_summary: { crash_detected: false } };
|
|
702
|
-
const lines = data.split(/\r?\n/).filter(Boolean);
|
|
703
|
-
const parsed = lines.map(l => {
|
|
704
|
-
try {
|
|
705
|
-
return JSON.parse(l);
|
|
706
|
-
}
|
|
707
|
-
catch {
|
|
708
|
-
return { message: l, _iso: null, crash: false };
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
let filtered = parsed;
|
|
712
|
-
if (since) {
|
|
713
|
-
let sinceMs = null;
|
|
714
|
-
if (/^\d+$/.test(since))
|
|
715
|
-
sinceMs = Number(since);
|
|
716
|
-
else {
|
|
717
|
-
const sDate = new Date(since);
|
|
718
|
-
if (!isNaN(sDate.getTime()))
|
|
719
|
-
sinceMs = sDate.getTime();
|
|
720
|
-
}
|
|
721
|
-
if (sinceMs !== null) {
|
|
722
|
-
filtered = parsed.filter(p => p._iso && (new Date(p._iso).getTime() >= sinceMs));
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
const entries = filtered.slice(-Math.max(0, limit));
|
|
726
|
-
const crashEntry = entries.find(e => e.crash);
|
|
727
|
-
const crash_summary = crashEntry ? { crash_detected: true, exception: crashEntry.exception, sample: crashEntry.message } : { crash_detected: false };
|
|
728
|
-
return { entries, crash_summary };
|
|
729
|
-
}
|
|
730
|
-
catch {
|
|
731
|
-
return { entries: [], crash_summary: { crash_detected: false } };
|
|
732
|
-
}
|
|
733
|
-
}
|
|
31
|
+
async collectGetScreenFingerprint(deviceId = 'booted') { return collectGetScreenFingerprint(this, deviceId); }
|
|
32
|
+
async startLogStream(bundleId, deviceId = 'booted', sessionId = 'default') { return startLogStream(bundleId, deviceId, sessionId); }
|
|
33
|
+
async stopLogStream(sessionId = 'default') { return stopLogStream(sessionId); }
|
|
34
|
+
async readLogStream(sessionId = 'default', limit = 100, since) { return readLogStream(sessionId, limit, since); }
|
|
35
|
+
async collectReadLogStream(sessionId = 'default', limit = 100, since) { return collectReadLogStream(sessionId, limit, since); }
|
|
734
36
|
}
|