taphound 0.2.0-dev.1 → 0.2.0-dev.11
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/README.md +49 -149
- package/README.zh-CN.md +47 -147
- package/assets/skills/taphound-case-suite/SKILL.md +363 -0
- package/assets/skills/taphound-case-suite/schemas/base-flow-record.schema.json +22 -0
- package/assets/skills/taphound-case-suite/schemas/case-catalog.schema.json +26 -0
- package/assets/skills/taphound-case-suite/schemas/case-ledger.schema.json +158 -0
- package/assets/skills/taphound-case-suite/schemas/suite-input.schema.json +41 -0
- package/assets/skills/taphound-case-suite/schemas/transition.schema.json +61 -0
- package/assets/skills/taphound-case-suite/scripts/ledger.mjs +1207 -0
- package/assets/skills/taphound-case-suite/templates/base-flow-record.example.json +20 -0
- package/assets/skills/taphound-case-suite/templates/suite-input.example.json +27 -0
- package/assets/skills/taphound-case-suite/templates/transition.example.json +8 -0
- package/assets/skills/taphound-flash/SKILL.md +115 -0
- package/assets/skills/taphound-flash/scripts/flash.mjs +585 -0
- package/assets/skills/taphound-flash/templates/flash-plan.example.json +15 -0
- package/assets/skills/taphound-journey-brief-author/CONTEXT-GUIDE.md +471 -0
- package/assets/skills/taphound-journey-brief-author/SKILL.md +494 -0
- package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.md +96 -0
- package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.zh-CN.md +80 -0
- package/assets/skills/taphound-journey-brief-author/prompts/context-analyze-project.md +309 -0
- package/assets/skills/taphound-journey-brief-author/schemas/context-refresh-result.json +123 -0
- package/assets/skills/taphound-journey-brief-author/schemas/project-context-module.json +192 -0
- package/assets/skills/taphound-journey-brief-author/schemas/project-context.json +152 -0
- package/assets/skills/taphound-journey-brief-author/templates/project-context-module.example.json +58 -0
- package/assets/skills/taphound-journey-brief-author/templates/project-context.example.json +51 -0
- package/assets/skills/taphound-journey-brief-author/templates/taphound-journey-brief.template.md +63 -0
- package/assets/skills/taphound-journey-generator/SKILL.md +500 -0
- package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/prompts/check-completion.md +3 -0
- package/assets/skills/taphound-journey-generator/prompts/consume-journey-brief.md +52 -0
- package/assets/skills/taphound-journey-generator/prompts/generate-step.md +211 -0
- package/assets/skills/taphound-journey-generator/prompts/select-flow.md +91 -0
- package/assets/skills/taphound-journey-generator/references/bridge.md +35 -0
- package/assets/skills/taphound-journey-generator/references/mid-session.md +44 -0
- package/assets/skills/taphound-journey-generator/schemas/external-flow.json +37 -0
- package/assets/skills/taphound-journey-generator/schemas/flow.json +28 -0
- package/assets/skills/taphound-journey-generator/schemas/journey-source.json +25 -0
- package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/observe-output.json +13 -4
- package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/proposed-step-envelope.json +127 -5
- package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +561 -0
- package/assets/skills/taphound-journey-generator/templates/external-flow.example.json +22 -0
- package/assets/skills/taphound-journey-generator/templates/flow.example.json +22 -0
- package/assets/skills/taphound-journey-generator/templates/journey-source.example.json +17 -0
- package/assets/skills/taphound-journey-generator/templates/taphound-journey-brief.example.md +70 -0
- package/assets/skills/taphound-verify-change/SKILL.md +91 -0
- package/assets/skills/taphound-verify-change/references/accept.md +38 -0
- package/assets/skills/taphound-verify-change/references/preserve.md +349 -0
- package/assets/skills/taphound-verify-change/schemas/workflow-manifest.schema.json +295 -0
- package/assets/skills/taphound-verify-change/scripts/handoff.mjs +482 -0
- package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +505 -0
- package/dist/adapters/adb/adb-adapter.d.ts +16 -2
- package/dist/adapters/adb/adb-adapter.js +199 -0
- package/dist/adapters/adb/system-uiautomator-snapshot-provider.d.ts +13 -0
- package/dist/adapters/adb/system-uiautomator-snapshot-provider.js +122 -0
- package/dist/adapters/adb/ui-automator-parser.d.ts +3 -0
- package/dist/adapters/adb/ui-automator-parser.js +160 -0
- package/dist/adapters/adb/window-topology-parser.d.ts +2 -0
- package/dist/adapters/adb/window-topology-parser.js +101 -0
- package/dist/adapters/android-cli/android-cli-adapter.d.ts +16 -8
- package/dist/adapters/android-cli/android-cli-adapter.js +137 -15
- package/dist/adapters/android-cli/android-cli-snapshot-provider.d.ts +9 -0
- package/dist/adapters/android-cli/android-cli-snapshot-provider.js +99 -0
- package/dist/adapters/android-cli/layout-parser.js +12 -8
- package/dist/adapters/appium/appium-doctor.d.ts +7 -0
- package/dist/adapters/appium/appium-doctor.js +128 -0
- package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +34 -0
- package/dist/adapters/appium/appium-ui-snapshot-provider.js +240 -0
- package/dist/adapters/camera/camera-probe-adapter.d.ts +18 -0
- package/dist/adapters/camera/camera-probe-adapter.js +286 -0
- package/dist/adapters/filesystem/artifact-store.js +40 -1
- package/dist/adapters/filesystem/context-document-writer.d.ts +15 -0
- package/dist/adapters/filesystem/context-document-writer.js +112 -0
- package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
- package/dist/adapters/filesystem/external-flow-registry.js +325 -0
- package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
- package/dist/adapters/filesystem/generation-session-store.js +281 -855
- package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
- package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
- package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
- package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
- package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
- package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
- package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
- package/dist/adapters/filesystem/journey-composition-store.js +151 -0
- package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
- package/dist/adapters/filesystem/knowledge-registry.js +223 -0
- package/dist/adapters/filesystem/project-bound-file.js +3 -5
- package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
- package/dist/adapters/filesystem/project-file-inspector.js +57 -28
- package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
- package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
- package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
- package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
- package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
- package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
- package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
- package/dist/adapters/filesystem/skill-installer.js +40 -11
- package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
- package/dist/adapters/filesystem/workspace-layout.js +57 -0
- package/dist/adapters/git/node-git-diff.d.ts +17 -0
- package/dist/adapters/git/node-git-diff.js +114 -0
- package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
- package/dist/adapters/process/node-detached-process-launcher.js +96 -0
- package/dist/adapters/process/node-process-runner.js +11 -2
- package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
- package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
- package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
- package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
- package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
- package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
- package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
- package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
- package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
- package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
- package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
- package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
- package/dist/adapters/runtime/session-adb-view.js +193 -0
- package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
- package/dist/adapters/runtime/session-backed-ports.js +43 -0
- package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
- package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
- package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
- package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
- package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
- package/dist/adapters/ui/device-layout-temp.js +18 -0
- package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
- package/dist/adapters/ui/device-ui-environment.js +81 -0
- package/dist/adapters/ui/layout-normalization.d.ts +9 -0
- package/dist/adapters/ui/layout-normalization.js +44 -0
- package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
- package/dist/adapters/ui/ui-snapshot-error.js +12 -0
- package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
- package/dist/adapters/ui/ui-snapshot-support.js +12 -0
- package/dist/application/align/align-service.d.ts +42 -0
- package/dist/application/align/align-service.js +106 -0
- package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
- package/dist/application/assertion/expectation-evaluator.js +156 -18
- package/dist/application/assertion/guarded-expectation.d.ts +45 -0
- package/dist/application/assertion/guarded-expectation.js +74 -0
- package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
- package/dist/application/checkpoint/baseline-capturer.js +163 -0
- package/dist/application/checkpoint/baseline-error.d.ts +5 -0
- package/dist/application/checkpoint/baseline-error.js +8 -0
- package/dist/application/checkpoint/baseline-service.d.ts +31 -0
- package/dist/application/checkpoint/baseline-service.js +147 -0
- package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
- package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
- package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
- package/dist/application/checkpoint/regression-comparator.js +192 -0
- package/dist/application/collector/logcat-collector.d.ts +22 -1
- package/dist/application/collector/logcat-collector.js +103 -15
- package/dist/application/collector/logcat-event.d.ts +8 -0
- package/dist/application/collector/logcat-event.js +79 -0
- package/dist/application/collector/logcat-stop.d.ts +2 -0
- package/dist/application/collector/logcat-stop.js +11 -0
- package/dist/application/context/context-generator.d.ts +45 -0
- package/dist/application/context/context-generator.js +228 -0
- package/dist/application/context/context-loader.d.ts +40 -0
- package/dist/application/context/context-loader.js +223 -0
- package/dist/application/context/context-refresher.d.ts +59 -0
- package/dist/application/context/context-refresher.js +349 -0
- package/dist/application/context/context-rehasher.d.ts +36 -0
- package/dist/application/context/context-rehasher.js +110 -0
- package/dist/application/context/context-validator.d.ts +23 -1
- package/dist/application/context/context-validator.js +116 -9
- package/dist/application/context/evidence-hash.d.ts +1 -0
- package/dist/application/context/evidence-hash.js +125 -0
- package/dist/application/context/evidence-match.d.ts +9 -0
- package/dist/application/context/evidence-match.js +8 -0
- package/dist/application/context/shard-identity.d.ts +2 -0
- package/dist/application/context/shard-identity.js +7 -0
- package/dist/application/contract/contract-loader.d.ts +25 -0
- package/dist/application/contract/contract-loader.js +68 -0
- package/dist/application/contract/contract-review.d.ts +25 -0
- package/dist/application/contract/contract-review.js +51 -0
- package/dist/application/contract/contract-verifier.d.ts +53 -0
- package/dist/application/contract/contract-verifier.js +645 -0
- package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
- package/dist/application/devices/resolve-device-assignments.js +60 -0
- package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
- package/dist/application/diagnosis/failure-classifier.js +179 -0
- package/dist/application/doctor/doctor-service.d.ts +36 -3
- package/dist/application/doctor/doctor-service.js +255 -25
- package/dist/application/generation/generation-app-preparer.d.ts +20 -0
- package/dist/application/generation/generation-app-preparer.js +28 -0
- package/dist/application/generation/generation-config-service.d.ts +21 -0
- package/dist/application/generation/generation-config-service.js +53 -0
- package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
- package/dist/application/generation/generation-confirmation-service.js +109 -24
- package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
- package/dist/application/generation/generation-context-snapshot.js +29 -0
- package/dist/application/generation/generation-finalizer.d.ts +15 -9
- package/dist/application/generation/generation-finalizer.js +167 -70
- package/dist/application/generation/generation-publisher.js +12 -8
- package/dist/application/generation/generation-recovery-service.d.ts +35 -0
- package/dist/application/generation/generation-recovery-service.js +144 -0
- package/dist/application/generation/generation-reopen-service.d.ts +15 -0
- package/dist/application/generation/generation-reopen-service.js +37 -0
- package/dist/application/generation/generation-replace-service.d.ts +32 -0
- package/dist/application/generation/generation-replace-service.js +116 -0
- package/dist/application/generation/generation-starter.d.ts +44 -4
- package/dist/application/generation/generation-starter.js +175 -8
- package/dist/application/generation/generation-step-executor.d.ts +67 -5
- package/dist/application/generation/generation-step-executor.js +471 -108
- package/dist/application/generation/proposed-step-validator.js +96 -30
- package/dist/application/generation/replay-policy-loader.d.ts +16 -0
- package/dist/application/generation/replay-policy-loader.js +29 -0
- package/dist/application/generation/risk-evaluator.d.ts +4 -1
- package/dist/application/generation/risk-evaluator.js +118 -3
- package/dist/application/generation/runtime-observer.d.ts +39 -7
- package/dist/application/generation/runtime-observer.js +193 -24
- package/dist/application/impact/impact-resolver.d.ts +32 -0
- package/dist/application/impact/impact-resolver.js +160 -0
- package/dist/application/init/init-service.js +26 -13
- package/dist/application/interaction/action-executor.d.ts +3 -2
- package/dist/application/interaction/action-executor.js +11 -1
- package/dist/application/interaction/action-target.d.ts +17 -0
- package/dist/application/interaction/action-target.js +40 -0
- package/dist/application/interaction/external-step-runner.d.ts +153 -0
- package/dist/application/interaction/external-step-runner.js +332 -0
- package/dist/application/interaction/fallback-resolver.d.ts +6 -3
- package/dist/application/interaction/fallback-resolver.js +7 -5
- package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
- package/dist/application/interaction/scroll-to-executor.js +118 -36
- package/dist/application/journey/external-flow-resolver.d.ts +25 -0
- package/dist/application/journey/external-flow-resolver.js +22 -0
- package/dist/application/journey/journey-check-service.d.ts +54 -0
- package/dist/application/journey/journey-check-service.js +207 -0
- package/dist/application/journey/journey-promoter.d.ts +26 -0
- package/dist/application/journey/journey-promoter.js +123 -0
- package/dist/application/journey/journey-resolver.d.ts +42 -0
- package/dist/application/journey/journey-resolver.js +239 -0
- package/dist/application/journey/journey-retirer.d.ts +25 -0
- package/dist/application/journey/journey-retirer.js +87 -0
- package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
- package/dist/application/knowledge/anchor-resolver.js +110 -0
- package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
- package/dist/application/knowledge/knowledge-loader.js +36 -0
- package/dist/application/locator/layout-failure-summary.d.ts +2 -0
- package/dist/application/locator/layout-failure-summary.js +67 -0
- package/dist/application/locator/layout-traversal.d.ts +11 -0
- package/dist/application/locator/layout-traversal.js +18 -0
- package/dist/application/locator/locator-resolver.d.ts +13 -1
- package/dist/application/locator/locator-resolver.js +182 -18
- package/dist/application/observe/observe-service.d.ts +20 -0
- package/dist/application/observe/observe-service.js +91 -0
- package/dist/application/project/project-describer.d.ts +14 -6
- package/dist/application/project/project-describer.js +38 -5
- package/dist/application/recognition/screen-detector.d.ts +28 -0
- package/dist/application/recognition/screen-detector.js +111 -0
- package/dist/application/recorder/locator-selector.js +151 -17
- package/dist/application/recorder/recorder-service.d.ts +16 -4
- package/dist/application/recorder/recorder-service.js +420 -128
- package/dist/application/report/report-writer.js +1 -1
- package/dist/application/runtime/cold-launch.d.ts +28 -0
- package/dist/application/runtime/cold-launch.js +59 -0
- package/dist/application/runtime/step-runner.d.ts +49 -4
- package/dist/application/runtime/step-runner.js +721 -130
- package/dist/application/runtime/verify-runtime.d.ts +57 -7
- package/dist/application/runtime/verify-runtime.js +554 -174
- package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
- package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
- package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
- package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
- package/dist/application/ui/ui-stability-probe.d.ts +3 -0
- package/dist/application/ui/ui-stability-probe.js +8 -0
- package/dist/application/wait/idle-advice.d.ts +6 -0
- package/dist/application/wait/idle-advice.js +27 -0
- package/dist/application/wait/idle-profiles.d.ts +15 -0
- package/dist/application/wait/idle-profiles.js +75 -0
- package/dist/application/wait/idle-waiter.d.ts +28 -4
- package/dist/application/wait/idle-waiter.js +186 -15
- package/dist/cli/commands/align.d.ts +3 -0
- package/dist/cli/commands/align.js +124 -0
- package/dist/cli/commands/baseline.d.ts +3 -0
- package/dist/cli/commands/baseline.js +130 -0
- package/dist/cli/commands/context.js +300 -27
- package/dist/cli/commands/contract.d.ts +3 -0
- package/dist/cli/commands/contract.js +120 -0
- package/dist/cli/commands/doctor.js +16 -9
- package/dist/cli/commands/failure.d.ts +3 -0
- package/dist/cli/commands/failure.js +63 -0
- package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
- package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
- package/dist/cli/commands/generation/session-commands.d.ts +11 -0
- package/dist/cli/commands/generation/session-commands.js +472 -0
- package/dist/cli/commands/generation/shared.d.ts +1414 -0
- package/dist/cli/commands/generation/shared.js +291 -0
- package/dist/cli/commands/generation/step-commands.d.ts +8 -0
- package/dist/cli/commands/generation/step-commands.js +315 -0
- package/dist/cli/commands/generation.js +10 -507
- package/dist/cli/commands/impact.d.ts +3 -0
- package/dist/cli/commands/impact.js +87 -0
- package/dist/cli/commands/init.js +13 -6
- package/dist/cli/commands/journey.d.ts +3 -0
- package/dist/cli/commands/journey.js +382 -0
- package/dist/cli/commands/knowledge.d.ts +3 -0
- package/dist/cli/commands/knowledge.js +93 -0
- package/dist/cli/commands/observe.d.ts +3 -0
- package/dist/cli/commands/observe.js +127 -0
- package/dist/cli/commands/project.js +2 -1
- package/dist/cli/commands/record.js +18 -5
- package/dist/cli/commands/verify.js +273 -63
- package/dist/cli/dependencies.d.ts +125 -1
- package/dist/cli/dependencies.js +450 -51
- package/dist/cli/diff-verification.d.ts +41 -0
- package/dist/cli/diff-verification.js +192 -0
- package/dist/cli/main.js +49 -1
- package/dist/cli/output.d.ts +5 -3
- package/dist/cli/output.js +12 -0
- package/dist/cli/program.js +19 -1
- package/dist/cli/project-root.d.ts +1 -0
- package/dist/cli/project-root.js +6 -0
- package/dist/cli/runtime-selection.d.ts +22 -0
- package/dist/cli/runtime-selection.js +92 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +14 -0
- package/dist/cli/workspace-guard.d.ts +2 -0
- package/dist/cli/workspace-guard.js +3 -0
- package/dist/domain/binding-reference.d.ts +4 -0
- package/dist/domain/binding-reference.js +23 -0
- package/dist/domain/checkpoint.d.ts +404 -0
- package/dist/domain/checkpoint.js +297 -0
- package/dist/domain/config.d.ts +107 -1
- package/dist/domain/config.js +52 -6
- package/dist/domain/contract.d.ts +413 -0
- package/dist/domain/contract.js +196 -0
- package/dist/domain/external-flow.d.ts +493 -0
- package/dist/domain/external-flow.js +15 -0
- package/dist/domain/failure-classification.d.ts +176 -0
- package/dist/domain/failure-classification.js +169 -0
- package/dist/domain/failure.d.ts +2 -1
- package/dist/domain/failure.js +123 -0
- package/dist/domain/generation.d.ts +1671 -132
- package/dist/domain/generation.js +202 -13
- package/dist/domain/geometry.d.ts +13 -0
- package/dist/domain/geometry.js +16 -0
- package/dist/domain/impact.d.ts +71 -0
- package/dist/domain/impact.js +76 -0
- package/dist/domain/init.d.ts +12 -4
- package/dist/domain/init.js +24 -18
- package/dist/domain/journey-composition.d.ts +2285 -0
- package/dist/domain/journey-composition.js +85 -0
- package/dist/domain/journey-lifecycle.d.ts +10 -0
- package/dist/domain/journey-lifecycle.js +9 -0
- package/dist/domain/journey.d.ts +2419 -212
- package/dist/domain/journey.js +384 -14
- package/dist/domain/knowledge.d.ts +165 -0
- package/dist/domain/knowledge.js +188 -0
- package/dist/domain/layout.d.ts +34 -5
- package/dist/domain/layout.js +64 -6
- package/dist/domain/locator-evidence.d.ts +3 -0
- package/dist/domain/locator-evidence.js +36 -0
- package/dist/domain/logcat-event.d.ts +54 -0
- package/dist/domain/logcat-event.js +40 -0
- package/dist/domain/observation.d.ts +33 -0
- package/dist/domain/observation.js +18 -0
- package/dist/domain/project-context.d.ts +219 -1
- package/dist/domain/project-context.js +209 -6
- package/dist/domain/proposed-step.d.ts +340 -60
- package/dist/domain/proposed-step.js +23 -3
- package/dist/domain/report.d.ts +1455 -28
- package/dist/domain/report.js +211 -14
- package/dist/domain/runtime-snapshot.d.ts +58 -1
- package/dist/domain/runtime-snapshot.js +14 -3
- package/dist/domain/runtime.d.ts +50 -0
- package/dist/domain/runtime.js +36 -0
- package/dist/domain/system-app-profiles.d.ts +6 -0
- package/dist/domain/system-app-profiles.js +43 -0
- package/dist/domain/ui-backend.d.ts +28 -0
- package/dist/domain/ui-backend.js +33 -0
- package/dist/domain/ui-cache.d.ts +11 -0
- package/dist/domain/ui-cache.js +10 -0
- package/dist/domain/window-hierarchy.d.ts +84 -0
- package/dist/domain/window-hierarchy.js +172 -0
- package/dist/domain/workflow-manifest.d.ts +77 -0
- package/dist/domain/workflow-manifest.js +136 -0
- package/dist/domain/workspace.d.ts +28 -0
- package/dist/domain/workspace.js +72 -0
- package/dist/ports/adb.d.ts +34 -1
- package/dist/ports/align-prompt.d.ts +11 -0
- package/dist/ports/align-prompt.js +6 -0
- package/dist/ports/anchor-resolver.d.ts +30 -0
- package/dist/ports/anchor-resolver.js +1 -0
- package/dist/ports/annotated-screen-resolver.d.ts +4 -0
- package/dist/ports/annotated-screen-resolver.js +1 -0
- package/dist/ports/camera-probe.d.ts +23 -0
- package/dist/ports/camera-probe.js +8 -0
- package/dist/ports/context-document-writer.d.ts +24 -0
- package/dist/ports/context-document-writer.js +1 -0
- package/dist/ports/detached-process-launcher.d.ts +12 -0
- package/dist/ports/detached-process-launcher.js +1 -0
- package/dist/ports/external-flow-registry.d.ts +37 -0
- package/dist/ports/external-flow-registry.js +1 -0
- package/dist/ports/generation-prompt.d.ts +2 -1
- package/dist/ports/generation-session-store.d.ts +12 -2
- package/dist/ports/git-diff.d.ts +9 -0
- package/dist/ports/git-diff.js +1 -0
- package/dist/ports/journey-composition-store.d.ts +17 -0
- package/dist/ports/journey-composition-store.js +1 -0
- package/dist/ports/knowledge-registry.d.ts +20 -0
- package/dist/ports/knowledge-registry.js +1 -0
- package/dist/ports/process-runner.d.ts +7 -0
- package/dist/ports/project-file-inspector.d.ts +1 -0
- package/dist/ports/project-identity-inspector.d.ts +24 -0
- package/dist/ports/project-identity-inspector.js +1 -0
- package/dist/ports/project-inventory-inspector.d.ts +21 -0
- package/dist/ports/project-inventory-inspector.js +1 -0
- package/dist/ports/project-module-discoverer.d.ts +26 -0
- package/dist/ports/project-module-discoverer.js +1 -0
- package/dist/ports/recorder-prompt.d.ts +10 -1
- package/dist/ports/runtime-backend.d.ts +89 -0
- package/dist/ports/runtime-backend.js +13 -0
- package/dist/ports/runtime-capability.d.ts +12 -0
- package/dist/ports/runtime-capability.js +20 -0
- package/dist/ports/runtime-session-ports.d.ts +25 -0
- package/dist/ports/runtime-session-ports.js +1 -0
- package/dist/ports/screenshot.d.ts +11 -0
- package/dist/ports/screenshot.js +1 -0
- package/dist/ports/skill-installer.d.ts +2 -2
- package/dist/ports/ui-snapshot.d.ts +35 -0
- package/dist/ports/ui-snapshot.js +1 -0
- package/dist/ports/ui-stability.d.ts +19 -0
- package/dist/ports/ui-stability.js +1 -0
- package/dist/ports/workspace-layout.d.ts +4 -0
- package/dist/ports/workspace-layout.js +1 -0
- package/dist/shared/errors.d.ts +2 -0
- package/dist/shared/errors.js +22 -0
- package/dist/shared/paths.d.ts +2 -0
- package/dist/shared/paths.js +24 -0
- package/dist/shared/strings.d.ts +2 -0
- package/dist/shared/strings.js +16 -0
- package/package.json +6 -1
- package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
- package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
- package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
- package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
- package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
- package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
- package/dist/ports/android-cli.d.ts +0 -21
- /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { parseObservedActivityComponent } from "../../domain/activity.js";
|
|
2
2
|
import { isAppProcessName } from "../../domain/app-process.js";
|
|
3
|
+
import { WindowTopologySchema } from "../../domain/window-hierarchy.js";
|
|
4
|
+
import { parseWindowTopology } from "./window-topology-parser.js";
|
|
3
5
|
const PROCESS_ROW = /^(\d+)\s+(\S+)$/;
|
|
4
6
|
function deviceArgs(deviceSerial) {
|
|
5
7
|
return deviceSerial === undefined ? [] : ["-s", deviceSerial];
|
|
@@ -30,6 +32,30 @@ function inputTextChunks(text) {
|
|
|
30
32
|
chunks.push(text.slice(start));
|
|
31
33
|
return chunks;
|
|
32
34
|
}
|
|
35
|
+
const DEVICEKIT_PACKAGE_NAME = "com.mobilenext.devicekit";
|
|
36
|
+
const DEVICEKIT_CLIPBOARD_RECEIVER = `${DEVICEKIT_PACKAGE_NAME}/.ClipboardBroadcastReceiver`;
|
|
37
|
+
function isAsciiText(text) {
|
|
38
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
39
|
+
if (text.charCodeAt(index) > 0x7f) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function inputTextUnsupportedResult() {
|
|
46
|
+
return {
|
|
47
|
+
exitCode: 1,
|
|
48
|
+
signal: null,
|
|
49
|
+
stdout: "",
|
|
50
|
+
stderr: "ADB input text cannot type non-ASCII characters; install the"
|
|
51
|
+
+ " mobilenext devicekit app"
|
|
52
|
+
+ " (https://github.com/mobile-next/devicekit-android) to deliver"
|
|
53
|
+
+ " non-ASCII text through the clipboard",
|
|
54
|
+
durationMs: 0,
|
|
55
|
+
timedOut: false,
|
|
56
|
+
cancelled: false
|
|
57
|
+
};
|
|
58
|
+
}
|
|
33
59
|
export class AdbAdapter {
|
|
34
60
|
runner;
|
|
35
61
|
constructor(runner) {
|
|
@@ -58,6 +84,30 @@ export class AdbAdapter {
|
|
|
58
84
|
return { serial, status };
|
|
59
85
|
});
|
|
60
86
|
}
|
|
87
|
+
async deviceIdentity(identity) {
|
|
88
|
+
const property = async (name) => {
|
|
89
|
+
const result = await this.run(["-s", identity.deviceSerial, "shell", "getprop", name], identity.signal, identity.timeoutMs);
|
|
90
|
+
if (result.exitCode !== 0
|
|
91
|
+
|| result.spawnError !== undefined
|
|
92
|
+
|| result.cancelled
|
|
93
|
+
|| result.timedOut) {
|
|
94
|
+
throw new Error(`getprop ${name} failed on ${identity.deviceSerial}`);
|
|
95
|
+
}
|
|
96
|
+
return result.stdout.trim();
|
|
97
|
+
};
|
|
98
|
+
const [manufacturer, model, sdk] = await Promise.all([
|
|
99
|
+
property("ro.product.manufacturer"),
|
|
100
|
+
property("ro.product.model"),
|
|
101
|
+
property("ro.build.version.sdk")
|
|
102
|
+
]);
|
|
103
|
+
const sdkLevel = Number(sdk);
|
|
104
|
+
if (manufacturer.length === 0
|
|
105
|
+
|| model.length === 0
|
|
106
|
+
|| !Number.isSafeInteger(sdkLevel)) {
|
|
107
|
+
throw new Error(`Device ${identity.deviceSerial} returned an incomplete identity`);
|
|
108
|
+
}
|
|
109
|
+
return { manufacturer, model, sdkLevel };
|
|
110
|
+
}
|
|
61
111
|
async foregroundComponent(identity) {
|
|
62
112
|
const result = await this.run([
|
|
63
113
|
...deviceArgs(identity.deviceSerial),
|
|
@@ -101,6 +151,43 @@ export class AdbAdapter {
|
|
|
101
151
|
`${options.packageName}/${options.activity}`
|
|
102
152
|
], options.signal, options.timeoutMs);
|
|
103
153
|
}
|
|
154
|
+
async startActivityByIntent(options) {
|
|
155
|
+
const result = await this.run([
|
|
156
|
+
...deviceArgs(options.deviceSerial),
|
|
157
|
+
"shell",
|
|
158
|
+
"am",
|
|
159
|
+
"start",
|
|
160
|
+
"-W",
|
|
161
|
+
"-a",
|
|
162
|
+
options.action
|
|
163
|
+
], options.signal, options.timeoutMs);
|
|
164
|
+
if (result.exitCode === 0 && /^Error(:| type)/m.test(result.stdout)) {
|
|
165
|
+
return { ...result, exitCode: 1 };
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
async resolveLauncherActivity(options) {
|
|
170
|
+
const result = await this.run([
|
|
171
|
+
...deviceArgs(options.deviceSerial),
|
|
172
|
+
"shell",
|
|
173
|
+
"cmd",
|
|
174
|
+
"package",
|
|
175
|
+
"resolve-activity",
|
|
176
|
+
"--brief",
|
|
177
|
+
options.packageName
|
|
178
|
+
], options.signal, options.timeoutMs);
|
|
179
|
+
assertCommandUsable(result, "cmd package resolve-activity");
|
|
180
|
+
const match = /^(\S+)\/(\S+)$/m.exec(result.stdout);
|
|
181
|
+
if (match === null) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const packageName = match[1];
|
|
185
|
+
const activity = match[2];
|
|
186
|
+
if (packageName === undefined || activity === undefined) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
return { packageName, activity };
|
|
190
|
+
}
|
|
104
191
|
forceStop(identity) {
|
|
105
192
|
return this.run([
|
|
106
193
|
...deviceArgs(identity.deviceSerial),
|
|
@@ -141,6 +228,41 @@ export class AdbAdapter {
|
|
|
141
228
|
})
|
|
142
229
|
.sort((left, right) => left.pid - right.pid);
|
|
143
230
|
}
|
|
231
|
+
async windowTopology(identity) {
|
|
232
|
+
const result = await this.run([
|
|
233
|
+
...deviceArgs(identity.deviceSerial),
|
|
234
|
+
"shell",
|
|
235
|
+
"dumpsys",
|
|
236
|
+
"window",
|
|
237
|
+
"windows"
|
|
238
|
+
], identity.signal, identity.timeoutMs);
|
|
239
|
+
if (result.cancelled) {
|
|
240
|
+
throw new Error("ADB window topology was cancelled");
|
|
241
|
+
}
|
|
242
|
+
if (result.exitCode !== 0
|
|
243
|
+
|| result.timedOut
|
|
244
|
+
|| result.spawnError !== undefined) {
|
|
245
|
+
return WindowTopologySchema.parse({
|
|
246
|
+
version: 1,
|
|
247
|
+
status: "unavailable",
|
|
248
|
+
windows: [],
|
|
249
|
+
diagnostic: result.stderr.trim()
|
|
250
|
+
|| result.spawnError
|
|
251
|
+
|| (result.timedOut
|
|
252
|
+
? "ADB window topology timed out"
|
|
253
|
+
: `ADB window topology exited with code ${String(result.exitCode)}`)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const topology = parseWindowTopology(result.stdout, identity.packageName);
|
|
257
|
+
if (topology.windows.length > 0)
|
|
258
|
+
return topology;
|
|
259
|
+
return WindowTopologySchema.parse({
|
|
260
|
+
version: 1,
|
|
261
|
+
status: "unavailable",
|
|
262
|
+
windows: [],
|
|
263
|
+
diagnostic: "ADB window topology contained no visible touchable target-app windows"
|
|
264
|
+
});
|
|
265
|
+
}
|
|
144
266
|
tap(point, deviceSerial, signal) {
|
|
145
267
|
return this.run([
|
|
146
268
|
...deviceArgs(deviceSerial),
|
|
@@ -177,6 +299,9 @@ export class AdbAdapter {
|
|
|
177
299
|
], signal);
|
|
178
300
|
}
|
|
179
301
|
async inputText(text, deviceSerial, signal) {
|
|
302
|
+
if (!isAsciiText(text)) {
|
|
303
|
+
return this.inputNonAsciiText(text, deviceSerial, signal);
|
|
304
|
+
}
|
|
180
305
|
let result;
|
|
181
306
|
for (const chunk of inputTextChunks(text)) {
|
|
182
307
|
result = await this.run([
|
|
@@ -198,21 +323,95 @@ export class AdbAdapter {
|
|
|
198
323
|
}
|
|
199
324
|
return result;
|
|
200
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* `adb shell input text` cannot type non-ASCII characters (KeyCharacterMap
|
|
328
|
+
* resolves them to no events and the input command fails), so non-ASCII text
|
|
329
|
+
* is delivered through the mobilenext devicekit clipboard: set the clipboard
|
|
330
|
+
* by broadcast, paste it into the focused element with KEYCODE_PASTE, then
|
|
331
|
+
* clear the clipboard again.
|
|
332
|
+
*/
|
|
333
|
+
async inputNonAsciiText(text, deviceSerial, signal) {
|
|
334
|
+
const installed = await this.isInstalled({
|
|
335
|
+
packageName: DEVICEKIT_PACKAGE_NAME,
|
|
336
|
+
deviceSerial,
|
|
337
|
+
signal
|
|
338
|
+
});
|
|
339
|
+
if (!installed) {
|
|
340
|
+
return inputTextUnsupportedResult();
|
|
341
|
+
}
|
|
342
|
+
const encoded = Buffer.from(text, "utf8").toString("base64");
|
|
343
|
+
const commands = [
|
|
344
|
+
[
|
|
345
|
+
"am",
|
|
346
|
+
"broadcast",
|
|
347
|
+
"-a",
|
|
348
|
+
"devicekit.clipboard.set",
|
|
349
|
+
"-e",
|
|
350
|
+
"encoding",
|
|
351
|
+
"base64",
|
|
352
|
+
"-e",
|
|
353
|
+
"text",
|
|
354
|
+
quoteRemoteShellArgument(encoded),
|
|
355
|
+
"-n",
|
|
356
|
+
DEVICEKIT_CLIPBOARD_RECEIVER
|
|
357
|
+
],
|
|
358
|
+
["input", "keyevent", "KEYCODE_PASTE"],
|
|
359
|
+
[
|
|
360
|
+
"am",
|
|
361
|
+
"broadcast",
|
|
362
|
+
"-a",
|
|
363
|
+
"devicekit.clipboard.clear",
|
|
364
|
+
"-n",
|
|
365
|
+
DEVICEKIT_CLIPBOARD_RECEIVER
|
|
366
|
+
]
|
|
367
|
+
];
|
|
368
|
+
let result;
|
|
369
|
+
for (const command of commands) {
|
|
370
|
+
result = await this.run([
|
|
371
|
+
...deviceArgs(deviceSerial),
|
|
372
|
+
"shell",
|
|
373
|
+
...command
|
|
374
|
+
], signal);
|
|
375
|
+
if (result.exitCode !== 0
|
|
376
|
+
|| result.timedOut
|
|
377
|
+
|| result.cancelled
|
|
378
|
+
|| result.spawnError !== undefined) {
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (result === undefined) {
|
|
383
|
+
throw new Error("ADB input text produced no command");
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
201
387
|
startLogcat(options) {
|
|
202
388
|
return this.runner.start({
|
|
203
389
|
executable: "adb",
|
|
204
390
|
args: [
|
|
205
391
|
...deviceArgs(options.deviceSerial),
|
|
206
392
|
"logcat",
|
|
393
|
+
"-T",
|
|
394
|
+
"1",
|
|
207
395
|
"-v",
|
|
208
396
|
"threadtime"
|
|
209
397
|
],
|
|
210
398
|
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
211
399
|
}, {
|
|
212
400
|
onStdoutLine: options.onStdoutLine,
|
|
401
|
+
captureStdout: false,
|
|
402
|
+
captureStderr: false,
|
|
213
403
|
...(options.onStderrLine === undefined
|
|
214
404
|
? {}
|
|
215
405
|
: { onStderrLine: options.onStderrLine })
|
|
216
406
|
});
|
|
217
407
|
}
|
|
408
|
+
dumpLogcat(options) {
|
|
409
|
+
return this.run([
|
|
410
|
+
...deviceArgs(options.deviceSerial),
|
|
411
|
+
"logcat",
|
|
412
|
+
"-d",
|
|
413
|
+
"-t",
|
|
414
|
+
String(options.maxLines)
|
|
415
|
+
], options.signal, options.timeoutMs);
|
|
416
|
+
}
|
|
218
417
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProcessRunner } from "../../ports/process-runner.js";
|
|
2
|
+
import type { OpenUiSnapshotProviderOptions, UiSnapshotProvider, UiSnapshotProviderFactory } from "../../ports/ui-snapshot.js";
|
|
3
|
+
import { type UiSnapshotTiming } from "../ui/ui-snapshot-support.js";
|
|
4
|
+
interface SystemUiAutomatorFactoryOptions extends UiSnapshotTiming {
|
|
5
|
+
createLayoutPath?: (() => string) | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare class SystemUiAutomatorSnapshotProviderFactory implements UiSnapshotProviderFactory {
|
|
8
|
+
private readonly runner;
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(runner: ProcessRunner, options?: SystemUiAutomatorFactoryOptions);
|
|
11
|
+
open(options: OpenUiSnapshotProviderOptions): Promise<UiSnapshotProvider>;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
import { parseUiAutomatorLayout } from "./ui-automator-parser.js";
|
|
4
|
+
import { readDeviceUiEnvironment } from "../ui/device-ui-environment.js";
|
|
5
|
+
import { createDeviceLayoutPath, sweepDeviceLayoutTemp } from "../ui/device-layout-temp.js";
|
|
6
|
+
import { UiSnapshotError } from "../ui/ui-snapshot-error.js";
|
|
7
|
+
import { snapshotFromCapture } from "../ui/ui-snapshot-support.js";
|
|
8
|
+
function descriptor(sdkLevel) {
|
|
9
|
+
return {
|
|
10
|
+
id: "system-uiautomator",
|
|
11
|
+
adapterVersion: "system-uiautomator-v1",
|
|
12
|
+
engineVersion: `android-api-${String(sdkLevel)}`,
|
|
13
|
+
configSha256: createHash("sha256")
|
|
14
|
+
.update(JSON.stringify({ command: "uiautomator dump", version: 1 }))
|
|
15
|
+
.digest("hex")
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
class SystemUiAutomatorSnapshotProvider {
|
|
19
|
+
runner;
|
|
20
|
+
deviceSerial;
|
|
21
|
+
environment;
|
|
22
|
+
descriptor;
|
|
23
|
+
createLayoutPath;
|
|
24
|
+
timing;
|
|
25
|
+
closed = false;
|
|
26
|
+
constructor(runner, deviceSerial, environment, descriptor, createLayoutPath, timing) {
|
|
27
|
+
this.runner = runner;
|
|
28
|
+
this.deviceSerial = deviceSerial;
|
|
29
|
+
this.environment = environment;
|
|
30
|
+
this.descriptor = descriptor;
|
|
31
|
+
this.createLayoutPath = createLayoutPath;
|
|
32
|
+
this.timing = timing;
|
|
33
|
+
}
|
|
34
|
+
async capture(options) {
|
|
35
|
+
if (this.closed) {
|
|
36
|
+
throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, "UI snapshot provider is closed");
|
|
37
|
+
}
|
|
38
|
+
const startedAt = performance.now();
|
|
39
|
+
const path = this.createLayoutPath();
|
|
40
|
+
const deadline = startedAt + options.timeoutMs;
|
|
41
|
+
const run = (args, timeoutMs) => (this.runner.run({
|
|
42
|
+
executable: "adb",
|
|
43
|
+
args: ["-s", this.deviceSerial, ...args],
|
|
44
|
+
timeoutMs,
|
|
45
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
46
|
+
}));
|
|
47
|
+
try {
|
|
48
|
+
const dumped = await run(["shell", "uiautomator", "dump", path], options.timeoutMs);
|
|
49
|
+
if (dumped.exitCode !== 0
|
|
50
|
+
|| dumped.timedOut
|
|
51
|
+
|| dumped.cancelled
|
|
52
|
+
|| dumped.spawnError !== undefined) {
|
|
53
|
+
throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, dumped.stderr.trim() || dumped.spawnError || "UIAutomator dump failed", { terminal: dumped.timedOut || dumped.cancelled });
|
|
54
|
+
}
|
|
55
|
+
const xml = await run(["exec-out", "cat", path], Math.max(1, deadline - performance.now()));
|
|
56
|
+
if (xml.exitCode !== 0
|
|
57
|
+
|| xml.timedOut
|
|
58
|
+
|| xml.cancelled
|
|
59
|
+
|| xml.spawnError !== undefined) {
|
|
60
|
+
throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, xml.stderr.trim() || xml.spawnError || "UIAutomator source read failed", { terminal: xml.timedOut || xml.cancelled });
|
|
61
|
+
}
|
|
62
|
+
let roots;
|
|
63
|
+
try {
|
|
64
|
+
roots = parseUiAutomatorLayout(xml.stdout);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "UIAutomator returned malformed page source", { cause: error });
|
|
68
|
+
}
|
|
69
|
+
if (roots.length === 0) {
|
|
70
|
+
throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "UIAutomator returned an empty layout");
|
|
71
|
+
}
|
|
72
|
+
return snapshotFromCapture({
|
|
73
|
+
startedAt,
|
|
74
|
+
roots,
|
|
75
|
+
backend: this.descriptor,
|
|
76
|
+
viewport: this.environment.viewport,
|
|
77
|
+
timing: this.timing
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await this.runner.run({
|
|
82
|
+
executable: "adb",
|
|
83
|
+
args: ["-s", this.deviceSerial, "shell", "rm", "-f", path],
|
|
84
|
+
timeoutMs: 5000
|
|
85
|
+
}).catch(() => undefined);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
close() {
|
|
89
|
+
this.closed = true;
|
|
90
|
+
return sweepDeviceLayoutTemp(this.runner, this.deviceSerial);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export class SystemUiAutomatorSnapshotProviderFactory {
|
|
94
|
+
runner;
|
|
95
|
+
options;
|
|
96
|
+
constructor(runner, options = {}) {
|
|
97
|
+
this.runner = runner;
|
|
98
|
+
this.options = options;
|
|
99
|
+
}
|
|
100
|
+
async open(options) {
|
|
101
|
+
const environment = await readDeviceUiEnvironment(this.runner, "system-uiautomator", options);
|
|
102
|
+
await sweepDeviceLayoutTemp(this.runner, options.deviceSerial, options.signal);
|
|
103
|
+
const provider = new SystemUiAutomatorSnapshotProvider(this.runner, options.deviceSerial, environment, descriptor(environment.sdkLevel), this.options.createLayoutPath ?? createDeviceLayoutPath, this.options);
|
|
104
|
+
try {
|
|
105
|
+
await provider.capture({
|
|
106
|
+
reason: "evidence",
|
|
107
|
+
timeoutMs: options.timeoutMs,
|
|
108
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof UiSnapshotError
|
|
113
|
+
&& error.code === "UI_SNAPSHOT_FAILED"
|
|
114
|
+
&& !error.terminal
|
|
115
|
+
&& options.signal?.aborted !== true) {
|
|
116
|
+
throw new UiSnapshotError("UI_BACKEND_UNAVAILABLE", "system-uiautomator", error.message, { cause: error });
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return provider;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LayoutElementSchema } from "../../domain/layout.js";
|
|
3
|
+
import { normalizeBoundsEdges, normalizeResourceId } from "../ui/layout-normalization.js";
|
|
4
|
+
const NAMED_ENTITIES = {
|
|
5
|
+
quot: "\"",
|
|
6
|
+
apos: "'",
|
|
7
|
+
lt: "<",
|
|
8
|
+
gt: ">",
|
|
9
|
+
amp: "&"
|
|
10
|
+
};
|
|
11
|
+
const ENTITY_PATTERN = /&(?:#(\d+)|#x([\da-fA-F]+)|(quot|apos|lt|gt|amp));/g;
|
|
12
|
+
function decodeCodePoint(entity, codePoint) {
|
|
13
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
|
|
14
|
+
? String.fromCodePoint(codePoint)
|
|
15
|
+
: entity;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Single-pass XML entity decoding. Android's XML serializer escapes control
|
|
19
|
+
* characters such as newlines as numeric references (` `), so multi-line
|
|
20
|
+
* text must decode them to match Locators authored against the visible text.
|
|
21
|
+
*/
|
|
22
|
+
function decodeXml(value) {
|
|
23
|
+
if (!value.includes("&"))
|
|
24
|
+
return value;
|
|
25
|
+
return value.replace(ENTITY_PATTERN, (entity, decimal, hex, named) => {
|
|
26
|
+
if (decimal !== undefined) {
|
|
27
|
+
return decodeCodePoint(entity, Number.parseInt(decimal, 10));
|
|
28
|
+
}
|
|
29
|
+
if (hex !== undefined) {
|
|
30
|
+
return decodeCodePoint(entity, Number.parseInt(hex, 16));
|
|
31
|
+
}
|
|
32
|
+
return named === undefined ? entity : NAMED_ENTITIES[named] ?? entity;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function attributes(source) {
|
|
36
|
+
const result = {};
|
|
37
|
+
const pattern = /([A-Za-z_:][\w:.-]*)=(["'])(.*?)\2/g;
|
|
38
|
+
for (const match of source.matchAll(pattern)) {
|
|
39
|
+
const key = match[1];
|
|
40
|
+
const value = match[3];
|
|
41
|
+
if (key !== undefined && value !== undefined) {
|
|
42
|
+
result[key] = decodeXml(value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function booleanAttribute(values, key) {
|
|
48
|
+
const value = values[key];
|
|
49
|
+
return value === undefined ? undefined : value === "true";
|
|
50
|
+
}
|
|
51
|
+
function bounds(value) {
|
|
52
|
+
if (value === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const match = /^\[(\d+),(\d+)]\[(\d+),(\d+)]$/.exec(value);
|
|
55
|
+
if (match === null) {
|
|
56
|
+
throw new Error("Invalid UIAutomator bounds");
|
|
57
|
+
}
|
|
58
|
+
return normalizeBoundsEdges(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds one element without re-validating its subtree: `LayoutElementSchema`
|
|
62
|
+
* is recursive, so parsing every node would validate each subtree once per
|
|
63
|
+
* ancestor (O(nodes x depth)). The completed roots are validated exactly once
|
|
64
|
+
* in `parseAccessibilityLayout`.
|
|
65
|
+
*/
|
|
66
|
+
function node(values, id, children) {
|
|
67
|
+
const parsedBounds = bounds(values.bounds);
|
|
68
|
+
const text = values.text;
|
|
69
|
+
const contentDescription = values["content-desc"];
|
|
70
|
+
const center = parsedBounds === undefined
|
|
71
|
+
? undefined
|
|
72
|
+
: {
|
|
73
|
+
x: Math.round((parsedBounds.left + parsedBounds.right) / 2),
|
|
74
|
+
y: Math.round((parsedBounds.top + parsedBounds.bottom) / 2)
|
|
75
|
+
};
|
|
76
|
+
const resourceId = normalizeResourceId(values["resource-id"]);
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
...(resourceId === undefined ? {} : { resourceId }),
|
|
80
|
+
...(text === undefined || text.length === 0 ? {} : { text }),
|
|
81
|
+
...(contentDescription === undefined || contentDescription.length === 0
|
|
82
|
+
? {}
|
|
83
|
+
: { contentDescription }),
|
|
84
|
+
...(booleanAttribute(values, "clickable") === undefined
|
|
85
|
+
? {}
|
|
86
|
+
: { clickable: booleanAttribute(values, "clickable") }),
|
|
87
|
+
...(booleanAttribute(values, "long-clickable") === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: { longClickable: booleanAttribute(values, "long-clickable") }),
|
|
90
|
+
...(booleanAttribute(values, "scrollable") === undefined
|
|
91
|
+
? {}
|
|
92
|
+
: { scrollable: booleanAttribute(values, "scrollable") }),
|
|
93
|
+
...(booleanAttribute(values, "focusable") === undefined
|
|
94
|
+
? {}
|
|
95
|
+
: { focusable: booleanAttribute(values, "focusable") }),
|
|
96
|
+
...(booleanAttribute(values, "focused") === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { focused: booleanAttribute(values, "focused") }),
|
|
99
|
+
enabled: booleanAttribute(values, "enabled") ?? true,
|
|
100
|
+
...(center === undefined ? {} : { center }),
|
|
101
|
+
...(parsedBounds === undefined ? {} : { bounds: parsedBounds }),
|
|
102
|
+
children
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function parseAccessibilityLayout(xml, accepts) {
|
|
106
|
+
const roots = [];
|
|
107
|
+
const stack = [];
|
|
108
|
+
// Quoted attribute values may legally contain an unescaped `>`.
|
|
109
|
+
const tokenPattern = /<\/?([A-Za-z_][\w:.$-]*)\b(?:[^>"']|"[^"]*"|'[^']*')*\/?>/g;
|
|
110
|
+
let token;
|
|
111
|
+
let index = 0;
|
|
112
|
+
while ((token = tokenPattern.exec(xml)) !== null) {
|
|
113
|
+
const source = token[0];
|
|
114
|
+
const tag = token[1];
|
|
115
|
+
if (tag === undefined || !accepts(tag))
|
|
116
|
+
continue;
|
|
117
|
+
if (source.startsWith("</")) {
|
|
118
|
+
const current = stack.pop();
|
|
119
|
+
if (current === undefined || current.tag !== tag) {
|
|
120
|
+
throw new Error("Invalid UIAutomator node nesting");
|
|
121
|
+
}
|
|
122
|
+
const parsed = node(current.values, current.id, current.children);
|
|
123
|
+
const parent = stack.at(-1);
|
|
124
|
+
if (parent === undefined)
|
|
125
|
+
roots.push(parsed);
|
|
126
|
+
else
|
|
127
|
+
parent.children.push(parsed);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const selfClosing = source.endsWith("/>");
|
|
131
|
+
const current = {
|
|
132
|
+
tag,
|
|
133
|
+
values: attributes(source),
|
|
134
|
+
children: [],
|
|
135
|
+
id: `ui-${String(index)}`
|
|
136
|
+
};
|
|
137
|
+
index += 1;
|
|
138
|
+
if (selfClosing) {
|
|
139
|
+
const parsed = node(current.values, current.id, current.children);
|
|
140
|
+
const parent = stack.at(-1);
|
|
141
|
+
if (parent === undefined)
|
|
142
|
+
roots.push(parsed);
|
|
143
|
+
else
|
|
144
|
+
parent.children.push(parsed);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
stack.push(current);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (stack.length > 0) {
|
|
151
|
+
throw new Error("Invalid UIAutomator node nesting");
|
|
152
|
+
}
|
|
153
|
+
return z.array(LayoutElementSchema).parse(roots);
|
|
154
|
+
}
|
|
155
|
+
export function parseUiAutomatorLayout(xml) {
|
|
156
|
+
return parseAccessibilityLayout(xml, (tag) => tag === "node");
|
|
157
|
+
}
|
|
158
|
+
export function parseAppiumPageSource(xml) {
|
|
159
|
+
return parseAccessibilityLayout(xml, (tag) => tag !== "hierarchy");
|
|
160
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { WindowTopologySchema } from "../../domain/window-hierarchy.js";
|
|
2
|
+
const WINDOW_HEADER = /^\s*Window #\d+ Window\{(\S+)\s+u\d+\s+(.+)}:$/;
|
|
3
|
+
const FRAME = /\bframe=\[(-?\d+),(-?\d+)]\[(-?\d+),(-?\d+)]/;
|
|
4
|
+
function windowBlocks(stdout) {
|
|
5
|
+
const blocks = [];
|
|
6
|
+
let current;
|
|
7
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
8
|
+
if (WINDOW_HEADER.test(line)) {
|
|
9
|
+
current = [line];
|
|
10
|
+
blocks.push(current);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
current?.push(line);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return blocks;
|
|
17
|
+
}
|
|
18
|
+
function value(lines, pattern) {
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const match = pattern.exec(line);
|
|
21
|
+
if (match?.[1] !== undefined)
|
|
22
|
+
return match[1];
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function parseBounds(lines) {
|
|
27
|
+
const frames = lines.find((line) => line.includes("Frames:"));
|
|
28
|
+
const match = frames === undefined ? null : FRAME.exec(frames);
|
|
29
|
+
if (match?.[1] === undefined
|
|
30
|
+
|| match[2] === undefined
|
|
31
|
+
|| match[3] === undefined
|
|
32
|
+
|| match[4] === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const [left, top, right, bottom] = match.slice(1).map(Number);
|
|
36
|
+
if (left === undefined
|
|
37
|
+
|| top === undefined
|
|
38
|
+
|| right === undefined
|
|
39
|
+
|| bottom === undefined
|
|
40
|
+
|| left < 0
|
|
41
|
+
|| top < 0
|
|
42
|
+
|| right <= left
|
|
43
|
+
|| bottom <= top) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return { left, top, right, bottom };
|
|
47
|
+
}
|
|
48
|
+
function parseWindow(lines, packageName, focusedWindowId) {
|
|
49
|
+
const header = lines[0];
|
|
50
|
+
const headerMatch = header === undefined ? null : WINDOW_HEADER.exec(header);
|
|
51
|
+
if (headerMatch?.[1] === undefined
|
|
52
|
+
|| headerMatch[2] === undefined
|
|
53
|
+
|| value(lines, /\bpackage=(\S+)/) !== packageName
|
|
54
|
+
|| !lines.some((line) => /\bisVisible=true\b/.test(line))
|
|
55
|
+
|| !lines.some((line) => /\bisOnScreen=true\b/.test(line))
|
|
56
|
+
|| !lines.some((line) => /\bmHasSurface=true\b/.test(line))
|
|
57
|
+
|| !lines.some((line) => /\bisReadyForDisplay\(\)=true\b/.test(line))) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const attrs = lines.findIndex((line) => line.includes("mAttrs={"));
|
|
61
|
+
const attrsEnd = attrs < 0
|
|
62
|
+
? attrs
|
|
63
|
+
: lines.findIndex((line, index) => (index >= attrs && line.trim().endsWith("}")));
|
|
64
|
+
const attributeLines = attrs < 0
|
|
65
|
+
? []
|
|
66
|
+
: lines.slice(attrs, attrsEnd < attrs ? attrs + 1 : attrsEnd + 1);
|
|
67
|
+
const bounds = parseBounds(lines);
|
|
68
|
+
const layer = value(lines, /\bmBaseLayer=(\d+)/);
|
|
69
|
+
const type = value(attributeLines, /\bty=([A-Z0-9_]+)/);
|
|
70
|
+
return {
|
|
71
|
+
id: headerMatch[1],
|
|
72
|
+
title: headerMatch[2],
|
|
73
|
+
packageName,
|
|
74
|
+
...(type === undefined ? {} : { type }),
|
|
75
|
+
...(layer === undefined ? {} : { layer: Number(layer) }),
|
|
76
|
+
...(bounds === undefined ? {} : { bounds }),
|
|
77
|
+
touchable: !attributeLines.some((line) => /\bNOT_TOUCHABLE\b/.test(line)),
|
|
78
|
+
...(attributeLines.some((line) => /\bfl=/.test(line))
|
|
79
|
+
? {
|
|
80
|
+
focusable: !attributeLines.some((line) => /\bNOT_FOCUSABLE\b/.test(line))
|
|
81
|
+
}
|
|
82
|
+
: {}),
|
|
83
|
+
...(focusedWindowId === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: { focused: focusedWindowId === headerMatch[1] })
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function parseWindowTopology(stdout, packageName) {
|
|
89
|
+
const focusedWindowId = value(stdout.split(/\r?\n/), /\bmCurrentFocus=Window\{(\S+)/);
|
|
90
|
+
return WindowTopologySchema.parse({
|
|
91
|
+
version: 1,
|
|
92
|
+
status: "observed",
|
|
93
|
+
windows: windowBlocks(stdout)
|
|
94
|
+
.flatMap((block) => {
|
|
95
|
+
const window = parseWindow(block, packageName, focusedWindowId);
|
|
96
|
+
return window === undefined || !window.touchable ? [] : [window];
|
|
97
|
+
})
|
|
98
|
+
.sort((left, right) => ((right.layer ?? 0) - (left.layer ?? 0)
|
|
99
|
+
|| left.id.localeCompare(right.id)))
|
|
100
|
+
});
|
|
101
|
+
}
|