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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
import { parseAppiumPageSource } from "../adb/ui-automator-parser.js";
|
|
4
|
+
import { readDeviceUiEnvironment } from "../ui/device-ui-environment.js";
|
|
5
|
+
import { UiSnapshotError } from "../ui/ui-snapshot-error.js";
|
|
6
|
+
import { snapshotFromCapture } from "../ui/ui-snapshot-support.js";
|
|
7
|
+
function loopbackEndpoint(value) {
|
|
8
|
+
const endpoint = new URL(value);
|
|
9
|
+
if (endpoint.protocol !== "http:"
|
|
10
|
+
|| !["127.0.0.1", "localhost", "[::1]"].includes(endpoint.hostname)
|
|
11
|
+
|| endpoint.username !== ""
|
|
12
|
+
|| endpoint.password !== "") {
|
|
13
|
+
throw new Error("Appium endpoint must be an unauthenticated loopback HTTP URL");
|
|
14
|
+
}
|
|
15
|
+
return endpoint;
|
|
16
|
+
}
|
|
17
|
+
export class FetchAppiumHttpClient {
|
|
18
|
+
endpoint;
|
|
19
|
+
constructor(endpoint) {
|
|
20
|
+
this.endpoint = endpoint;
|
|
21
|
+
}
|
|
22
|
+
async request(input) {
|
|
23
|
+
const timeout = AbortSignal.timeout(Math.trunc(input.timeoutMs));
|
|
24
|
+
const signal = input.signal === undefined
|
|
25
|
+
? timeout
|
|
26
|
+
: AbortSignal.any([input.signal, timeout]);
|
|
27
|
+
const response = await fetch(new URL(input.path.slice(1), this.endpoint), {
|
|
28
|
+
method: input.method,
|
|
29
|
+
...(input.body === undefined
|
|
30
|
+
? {}
|
|
31
|
+
: {
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify(input.body)
|
|
34
|
+
}),
|
|
35
|
+
signal
|
|
36
|
+
});
|
|
37
|
+
const payload = await response.json();
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Appium HTTP ${String(response.status)}`);
|
|
40
|
+
}
|
|
41
|
+
return { value: payload.value };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function objectValue(value) {
|
|
45
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
46
|
+
throw new Error("Appium returned an invalid response");
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
class AppiumUiSnapshotProvider {
|
|
51
|
+
http;
|
|
52
|
+
sessionId;
|
|
53
|
+
environment;
|
|
54
|
+
descriptor;
|
|
55
|
+
closePromise;
|
|
56
|
+
lastSourceSignature;
|
|
57
|
+
constructor(http, sessionId, environment, descriptor) {
|
|
58
|
+
this.http = http;
|
|
59
|
+
this.sessionId = sessionId;
|
|
60
|
+
this.environment = environment;
|
|
61
|
+
this.descriptor = descriptor;
|
|
62
|
+
}
|
|
63
|
+
reset() {
|
|
64
|
+
this.lastSourceSignature = undefined;
|
|
65
|
+
}
|
|
66
|
+
async sample(options) {
|
|
67
|
+
void options.deviceSerial;
|
|
68
|
+
const startedAt = performance.now();
|
|
69
|
+
const snapshot = await this.capture({
|
|
70
|
+
reason: "idle",
|
|
71
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
72
|
+
timeoutMs: options.timeoutMs ?? 5000
|
|
73
|
+
});
|
|
74
|
+
const signature = createHash("sha256")
|
|
75
|
+
.update(JSON.stringify(snapshot.roots))
|
|
76
|
+
.digest("hex");
|
|
77
|
+
const previous = this.lastSourceSignature;
|
|
78
|
+
this.lastSourceSignature = signature;
|
|
79
|
+
return {
|
|
80
|
+
changes: previous === signature ? [] : [{ layoutSha256: signature }],
|
|
81
|
+
layout: snapshot.roots,
|
|
82
|
+
backend: "uiautomator",
|
|
83
|
+
durationMs: performance.now() - startedAt
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async capture(options) {
|
|
87
|
+
if (this.closePromise !== undefined) {
|
|
88
|
+
throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, "Appium UI snapshot provider is closed");
|
|
89
|
+
}
|
|
90
|
+
const startedAt = performance.now();
|
|
91
|
+
let source;
|
|
92
|
+
try {
|
|
93
|
+
source = (await this.http.request({
|
|
94
|
+
method: "GET",
|
|
95
|
+
path: `/session/${this.sessionId}/source`,
|
|
96
|
+
timeoutMs: options.timeoutMs,
|
|
97
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
98
|
+
})).value;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, `Appium page source capture failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error, terminal: true });
|
|
102
|
+
}
|
|
103
|
+
if (typeof source !== "string") {
|
|
104
|
+
throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "Appium returned a non-string page source");
|
|
105
|
+
}
|
|
106
|
+
let roots;
|
|
107
|
+
try {
|
|
108
|
+
roots = parseAppiumPageSource(source);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "Appium returned malformed page source", { cause: error });
|
|
112
|
+
}
|
|
113
|
+
if (roots.length === 0) {
|
|
114
|
+
throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "Appium returned an empty layout");
|
|
115
|
+
}
|
|
116
|
+
return snapshotFromCapture({
|
|
117
|
+
startedAt,
|
|
118
|
+
roots,
|
|
119
|
+
backend: this.descriptor,
|
|
120
|
+
viewport: this.environment.viewport,
|
|
121
|
+
timing: {}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
close() {
|
|
125
|
+
this.closePromise ??= this.http.request({
|
|
126
|
+
method: "DELETE",
|
|
127
|
+
path: `/session/${this.sessionId}`,
|
|
128
|
+
timeoutMs: 5000
|
|
129
|
+
}).then(() => undefined);
|
|
130
|
+
return this.closePromise;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export class AppiumUiSnapshotProviderFactory {
|
|
134
|
+
runner;
|
|
135
|
+
endpoint;
|
|
136
|
+
http;
|
|
137
|
+
settings;
|
|
138
|
+
constructor(runner, http, options = {}) {
|
|
139
|
+
this.runner = runner;
|
|
140
|
+
this.endpoint = loopbackEndpoint(options.endpoint ?? "http://127.0.0.1:4723/");
|
|
141
|
+
this.http = http ?? new FetchAppiumHttpClient(this.endpoint);
|
|
142
|
+
this.settings = {
|
|
143
|
+
mapTestTagToResourceId: options.mapTestTagToResourceId ?? false
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async probe(timeoutMs = 2000) {
|
|
147
|
+
try {
|
|
148
|
+
const response = await this.http.request({
|
|
149
|
+
method: "GET",
|
|
150
|
+
path: "/status",
|
|
151
|
+
timeoutMs
|
|
152
|
+
});
|
|
153
|
+
return response.value !== undefined;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async open(options) {
|
|
160
|
+
const environment = await readDeviceUiEnvironment(this.runner, "appium-uiautomator2", options);
|
|
161
|
+
let provisionalSessionId;
|
|
162
|
+
try {
|
|
163
|
+
const status = objectValue((await this.http.request({
|
|
164
|
+
method: "GET",
|
|
165
|
+
path: "/status",
|
|
166
|
+
timeoutMs: options.timeoutMs,
|
|
167
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
168
|
+
})).value);
|
|
169
|
+
const build = status.build === undefined ? {} : objectValue(status.build);
|
|
170
|
+
const engineVersion = typeof build.version === "string"
|
|
171
|
+
? build.version
|
|
172
|
+
: "unknown";
|
|
173
|
+
const created = objectValue((await this.http.request({
|
|
174
|
+
method: "POST",
|
|
175
|
+
path: "/session",
|
|
176
|
+
timeoutMs: options.timeoutMs,
|
|
177
|
+
body: {
|
|
178
|
+
capabilities: {
|
|
179
|
+
alwaysMatch: {
|
|
180
|
+
platformName: "Android",
|
|
181
|
+
"appium:automationName": "UiAutomator2",
|
|
182
|
+
"appium:udid": options.deviceSerial,
|
|
183
|
+
"appium:noReset": true,
|
|
184
|
+
"appium:autoLaunch": false,
|
|
185
|
+
"appium:autoGrantPermissions": false,
|
|
186
|
+
"appium:fullReset": false,
|
|
187
|
+
"appium:shouldTerminateApp": false
|
|
188
|
+
},
|
|
189
|
+
firstMatch: [{}]
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
193
|
+
})).value);
|
|
194
|
+
const sessionId = typeof created.sessionId === "string"
|
|
195
|
+
? created.sessionId
|
|
196
|
+
: undefined;
|
|
197
|
+
if (sessionId === undefined) {
|
|
198
|
+
throw new Error("Appium did not return a session id");
|
|
199
|
+
}
|
|
200
|
+
provisionalSessionId = sessionId;
|
|
201
|
+
await this.http.request({
|
|
202
|
+
method: "POST",
|
|
203
|
+
path: `/session/${sessionId}/appium/settings`,
|
|
204
|
+
timeoutMs: options.timeoutMs,
|
|
205
|
+
body: { settings: this.settings },
|
|
206
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
207
|
+
});
|
|
208
|
+
const descriptor = {
|
|
209
|
+
id: "appium-uiautomator2",
|
|
210
|
+
adapterVersion: "appium-uiautomator2-v1",
|
|
211
|
+
engineVersion,
|
|
212
|
+
configSha256: createHash("sha256").update(JSON.stringify({
|
|
213
|
+
endpoint: this.endpoint.origin,
|
|
214
|
+
settings: this.settings,
|
|
215
|
+
capabilitiesVersion: 1
|
|
216
|
+
})).digest("hex")
|
|
217
|
+
};
|
|
218
|
+
const provider = new AppiumUiSnapshotProvider(this.http, sessionId, environment, descriptor);
|
|
219
|
+
await provider.capture({
|
|
220
|
+
reason: "evidence",
|
|
221
|
+
timeoutMs: options.timeoutMs,
|
|
222
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
223
|
+
});
|
|
224
|
+
provisionalSessionId = undefined;
|
|
225
|
+
return provider;
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (provisionalSessionId !== undefined) {
|
|
229
|
+
await this.http.request({
|
|
230
|
+
method: "DELETE",
|
|
231
|
+
path: `/session/${provisionalSessionId}`,
|
|
232
|
+
timeoutMs: 5000
|
|
233
|
+
}).catch(() => undefined);
|
|
234
|
+
}
|
|
235
|
+
if (error instanceof UiSnapshotError)
|
|
236
|
+
throw error;
|
|
237
|
+
throw new UiSnapshotError("UI_BACKEND_UNAVAILABLE", "appium-uiautomator2", "Appium UiAutomator2 session could not be opened", { cause: error });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type RuntimeSessionOpener } from "../../ports/runtime-backend.js";
|
|
2
|
+
import type { RuntimeSessionPortViewsFactory } from "../../ports/runtime-session-ports.js";
|
|
3
|
+
import type { UiSnapshotProviderFactory } from "../../ports/ui-snapshot.js";
|
|
4
|
+
import type { CameraProbeInput, CameraProbePort, CameraProbeResult } from "../../ports/camera-probe.js";
|
|
5
|
+
export interface CameraProbeAdapterDeps {
|
|
6
|
+
sessions: RuntimeSessionOpener;
|
|
7
|
+
sessionPorts: RuntimeSessionPortViewsFactory;
|
|
8
|
+
uiSnapshots: UiSnapshotProviderFactory;
|
|
9
|
+
now: () => number;
|
|
10
|
+
sleep: (ms: number) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare class CameraProbeAdapter implements CameraProbePort {
|
|
13
|
+
private readonly deps;
|
|
14
|
+
constructor(deps: CameraProbeAdapterDeps);
|
|
15
|
+
probe(input: CameraProbeInput): Promise<CameraProbeResult>;
|
|
16
|
+
private probeWith;
|
|
17
|
+
private waitForStableForeground;
|
|
18
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { withRuntimeSession } from "../../ports/runtime-backend.js";
|
|
2
|
+
import { CameraProbeError } from "../../ports/camera-probe.js";
|
|
3
|
+
const SHUTTER_KEYWORDS = ["shutter", "快门", "capture", "拍照"];
|
|
4
|
+
const CONFIRM_KEYWORDS = [
|
|
5
|
+
"done",
|
|
6
|
+
"confirm",
|
|
7
|
+
"accept",
|
|
8
|
+
"ok",
|
|
9
|
+
"save",
|
|
10
|
+
"checkmark",
|
|
11
|
+
"完成",
|
|
12
|
+
"确认",
|
|
13
|
+
"接受",
|
|
14
|
+
"确定",
|
|
15
|
+
"保存"
|
|
16
|
+
];
|
|
17
|
+
const CONFIRM_RESOURCE_ID_SUBSTRINGS = ["done", "confirm", "accept", "save", "ok"];
|
|
18
|
+
const SHUTTER_RESOURCE_ID_SUBSTRINGS = ["shutter"];
|
|
19
|
+
const IMAGE_CAPTURE_ACTION = "android.media.action.IMAGE_CAPTURE";
|
|
20
|
+
const CAMERA_FOREGROUND_TIMEOUT_MS = 8000;
|
|
21
|
+
const CAMERA_FOREGROUND_POLL_INTERVAL_MS = 500;
|
|
22
|
+
const LAYOUT_SETTLE_MS = 1500;
|
|
23
|
+
const RESOLVER_KEYWORDS = ["resolver", "chooser"];
|
|
24
|
+
const AOSP_CAMERA_PACKAGES = ["com.android.camera", "com.android.camera2"];
|
|
25
|
+
function flatten(elements) {
|
|
26
|
+
const result = [];
|
|
27
|
+
const visit = (nodes) => {
|
|
28
|
+
for (const node of nodes) {
|
|
29
|
+
result.push(node);
|
|
30
|
+
visit(node.children);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
visit(elements);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
function matchesAnyKeyword(value, keywords) {
|
|
37
|
+
const lower = value.toLowerCase();
|
|
38
|
+
return keywords.some((keyword) => lower.includes(keyword.toLowerCase()));
|
|
39
|
+
}
|
|
40
|
+
function resourceEntryName(resourceId) {
|
|
41
|
+
const separator = resourceId.lastIndexOf(":id/");
|
|
42
|
+
return (separator >= 0 ? resourceId.slice(separator + 4) : resourceId).toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
function matchesResourceId(resourceId, token) {
|
|
45
|
+
const entryName = resourceEntryName(resourceId);
|
|
46
|
+
const tokens = entryName.split(/[^a-z0-9]+/u);
|
|
47
|
+
return tokens.includes(token.toLowerCase());
|
|
48
|
+
}
|
|
49
|
+
function selectByResourceIdPriority(candidates, resourceIdTokens) {
|
|
50
|
+
for (const token of resourceIdTokens) {
|
|
51
|
+
const matches = candidates.filter((node) => (node.resourceId !== undefined
|
|
52
|
+
&& matchesResourceId(node.resourceId, token)));
|
|
53
|
+
if (matches.length > 0) {
|
|
54
|
+
return matches;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
function buttonMatch(node) {
|
|
60
|
+
if (node.resourceId === undefined) {
|
|
61
|
+
return "no_resource_id";
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
resourceId: node.resourceId,
|
|
65
|
+
...(node.contentDescription === undefined
|
|
66
|
+
? {}
|
|
67
|
+
: { contentDescription: node.contentDescription })
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function findButton(layout, keywords, resourceIdSubstrings) {
|
|
71
|
+
const eligible = flatten(layout).filter((node) => node.enabled && node.clickable === true);
|
|
72
|
+
const candidates = eligible.filter((node) => (node.contentDescription !== undefined
|
|
73
|
+
&& matchesAnyKeyword(node.contentDescription, keywords)));
|
|
74
|
+
if (candidates.length === 0) {
|
|
75
|
+
const byResourceId = selectByResourceIdPriority(eligible, resourceIdSubstrings);
|
|
76
|
+
if (byResourceId.length === 0) {
|
|
77
|
+
return "none";
|
|
78
|
+
}
|
|
79
|
+
if (byResourceId.length === 1) {
|
|
80
|
+
const node = byResourceId[0];
|
|
81
|
+
return node === undefined ? "none" : buttonMatch(node);
|
|
82
|
+
}
|
|
83
|
+
return "ambiguous";
|
|
84
|
+
}
|
|
85
|
+
if (candidates.length === 1) {
|
|
86
|
+
const node = candidates[0];
|
|
87
|
+
return node === undefined ? "none" : buttonMatch(node);
|
|
88
|
+
}
|
|
89
|
+
const byResourceId = selectByResourceIdPriority(candidates, resourceIdSubstrings);
|
|
90
|
+
if (byResourceId.length === 1) {
|
|
91
|
+
const node = byResourceId[0];
|
|
92
|
+
return node === undefined ? "none" : buttonMatch(node);
|
|
93
|
+
}
|
|
94
|
+
return "ambiguous";
|
|
95
|
+
}
|
|
96
|
+
function centerOf(element) {
|
|
97
|
+
if (element.center !== undefined) {
|
|
98
|
+
return element.center;
|
|
99
|
+
}
|
|
100
|
+
if (element.bounds !== undefined) {
|
|
101
|
+
return {
|
|
102
|
+
x: Math.round((element.bounds.left + element.bounds.right) / 2),
|
|
103
|
+
y: Math.round((element.bounds.top + element.bounds.bottom) / 2)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
throw new CameraProbeError("ALIGN_SHUTTER_NOT_FOUND", "Shutter element has no center or bounds");
|
|
107
|
+
}
|
|
108
|
+
function isResolverOrChooser(packageName) {
|
|
109
|
+
const lower = packageName.toLowerCase();
|
|
110
|
+
return RESOLVER_KEYWORDS.some((keyword) => lower.includes(keyword));
|
|
111
|
+
}
|
|
112
|
+
function isAospCameraPackage(packageName) {
|
|
113
|
+
return AOSP_CAMERA_PACKAGES.includes(packageName);
|
|
114
|
+
}
|
|
115
|
+
function deviceIdentity(deviceSerial, signal) {
|
|
116
|
+
return {
|
|
117
|
+
packageName: "",
|
|
118
|
+
deviceSerial,
|
|
119
|
+
...(signal === undefined ? {} : { signal })
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export class CameraProbeAdapter {
|
|
123
|
+
deps;
|
|
124
|
+
constructor(deps) {
|
|
125
|
+
this.deps = deps;
|
|
126
|
+
}
|
|
127
|
+
probe(input) {
|
|
128
|
+
return withRuntimeSession(this.deps.sessions, input.deviceSerial, input.signal, (session) => this.probeWith(this.deps.sessionPorts(session).adb, input));
|
|
129
|
+
}
|
|
130
|
+
async probeWith(adb, input) {
|
|
131
|
+
const { deviceSerial, signal } = input;
|
|
132
|
+
let cameraPackage;
|
|
133
|
+
const uiSnapshotProvider = await this.deps.uiSnapshots.open({
|
|
134
|
+
deviceSerial,
|
|
135
|
+
timeoutMs: CAMERA_FOREGROUND_TIMEOUT_MS,
|
|
136
|
+
...(signal === undefined ? {} : { signal })
|
|
137
|
+
});
|
|
138
|
+
try {
|
|
139
|
+
let preForeground = await adb.foregroundComponent(deviceIdentity(deviceSerial, signal));
|
|
140
|
+
if (isAospCameraPackage(preForeground.packageName)
|
|
141
|
+
|| isResolverOrChooser(preForeground.packageName)) {
|
|
142
|
+
await adb.forceStop({
|
|
143
|
+
packageName: preForeground.packageName,
|
|
144
|
+
deviceSerial,
|
|
145
|
+
...(signal === undefined ? {} : { signal })
|
|
146
|
+
});
|
|
147
|
+
preForeground = await adb.foregroundComponent(deviceIdentity(deviceSerial, signal));
|
|
148
|
+
}
|
|
149
|
+
const startResult = await adb.startActivityByIntent({
|
|
150
|
+
action: IMAGE_CAPTURE_ACTION,
|
|
151
|
+
deviceSerial,
|
|
152
|
+
...(signal === undefined ? {} : { signal })
|
|
153
|
+
});
|
|
154
|
+
if (startResult.exitCode !== 0) {
|
|
155
|
+
throw new CameraProbeError("ALIGN_CAMERA_INTENT_FAILED", `am start exited with code ${String(startResult.exitCode)}: ${startResult.stderr || startResult.stdout}`);
|
|
156
|
+
}
|
|
157
|
+
const deadline = this.deps.now() + CAMERA_FOREGROUND_TIMEOUT_MS;
|
|
158
|
+
let current = preForeground;
|
|
159
|
+
while (this.deps.now() < deadline) {
|
|
160
|
+
current = await adb.foregroundComponent(deviceIdentity(deviceSerial, signal));
|
|
161
|
+
if (isResolverOrChooser(current.packageName)) {
|
|
162
|
+
throw new CameraProbeError("ALIGN_CAMERA_INTENT_FAILED", `IMAGE_CAPTURE landed on system resolver/chooser (${current.packageName}); set a default camera app`);
|
|
163
|
+
}
|
|
164
|
+
if (current.packageName !== preForeground.packageName) {
|
|
165
|
+
cameraPackage = current.packageName;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
await this.deps.sleep(CAMERA_FOREGROUND_POLL_INTERVAL_MS);
|
|
169
|
+
}
|
|
170
|
+
if (cameraPackage === undefined) {
|
|
171
|
+
throw new CameraProbeError("ALIGN_CAMERA_NOT_LAUNCHED", "Camera package did not become foreground within 8s");
|
|
172
|
+
}
|
|
173
|
+
await uiSnapshotProvider.capture({
|
|
174
|
+
reason: "locate",
|
|
175
|
+
timeoutMs: CAMERA_FOREGROUND_TIMEOUT_MS,
|
|
176
|
+
...(signal === undefined ? {} : { signal })
|
|
177
|
+
});
|
|
178
|
+
await this.deps.sleep(LAYOUT_SETTLE_MS);
|
|
179
|
+
const initialLayout = (await uiSnapshotProvider.capture({
|
|
180
|
+
reason: "locate",
|
|
181
|
+
timeoutMs: CAMERA_FOREGROUND_TIMEOUT_MS,
|
|
182
|
+
...(signal === undefined ? {} : { signal })
|
|
183
|
+
})).roots;
|
|
184
|
+
const preShutterForeground = await this.waitForStableForeground(adb, deviceSerial, signal);
|
|
185
|
+
if (preShutterForeground.packageName !== cameraPackage) {
|
|
186
|
+
throw new CameraProbeError("ALIGN_CAMERA_NOT_LAUNCHED", `Camera package left the foreground before shutter discovery: ${preShutterForeground.packageName}`);
|
|
187
|
+
}
|
|
188
|
+
const shutter = findButton(initialLayout, SHUTTER_KEYWORDS, SHUTTER_RESOURCE_ID_SUBSTRINGS);
|
|
189
|
+
if (shutter === "none") {
|
|
190
|
+
throw new CameraProbeError("ALIGN_SHUTTER_NOT_FOUND", "No enabled clickable element matched shutter contentDescription or resourceId");
|
|
191
|
+
}
|
|
192
|
+
if (shutter === "ambiguous") {
|
|
193
|
+
throw new CameraProbeError("ALIGN_SHUTTER_AMBIGUOUS", "Multiple shutter candidates could not be disambiguated by resourceId");
|
|
194
|
+
}
|
|
195
|
+
if (shutter === "no_resource_id") {
|
|
196
|
+
throw new CameraProbeError("ALIGN_SHUTTER_NO_RESOURCE_ID", "Shutter candidate has no resourceId for deterministic replay");
|
|
197
|
+
}
|
|
198
|
+
const shutterElement = flatten(initialLayout).find((node) => node.resourceId === shutter.resourceId);
|
|
199
|
+
if (shutterElement === undefined) {
|
|
200
|
+
throw new CameraProbeError("ALIGN_SHUTTER_NOT_FOUND", `Shutter resourceId ${shutter.resourceId} not present in layout`);
|
|
201
|
+
}
|
|
202
|
+
await adb.tap(centerOf(shutterElement), deviceSerial, signal);
|
|
203
|
+
await uiSnapshotProvider.capture({
|
|
204
|
+
reason: "locate",
|
|
205
|
+
timeoutMs: CAMERA_FOREGROUND_TIMEOUT_MS,
|
|
206
|
+
...(signal === undefined ? {} : { signal })
|
|
207
|
+
});
|
|
208
|
+
await this.deps.sleep(LAYOUT_SETTLE_MS);
|
|
209
|
+
const postShutterLayout = (await uiSnapshotProvider.capture({
|
|
210
|
+
reason: "locate",
|
|
211
|
+
timeoutMs: CAMERA_FOREGROUND_TIMEOUT_MS,
|
|
212
|
+
...(signal === undefined ? {} : { signal })
|
|
213
|
+
})).roots;
|
|
214
|
+
const postShutterForeground = await this.waitForStableForeground(adb, deviceSerial, signal);
|
|
215
|
+
const activityName = preShutterForeground.activity;
|
|
216
|
+
if (postShutterForeground.packageName !== cameraPackage) {
|
|
217
|
+
return {
|
|
218
|
+
packageName: cameraPackage,
|
|
219
|
+
activityName,
|
|
220
|
+
shutterResourceId: shutter.resourceId,
|
|
221
|
+
...(shutter.contentDescription !== undefined
|
|
222
|
+
? { shutterContentDescription: shutter.contentDescription }
|
|
223
|
+
: {})
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const confirm = findButton(postShutterLayout, CONFIRM_KEYWORDS, CONFIRM_RESOURCE_ID_SUBSTRINGS);
|
|
227
|
+
if (confirm === "ambiguous") {
|
|
228
|
+
throw new CameraProbeError("ALIGN_CONFIRM_AMBIGUOUS", "Multiple confirm candidates could not be disambiguated by resourceId");
|
|
229
|
+
}
|
|
230
|
+
if (confirm === "none") {
|
|
231
|
+
throw new CameraProbeError("ALIGN_CONFIRM_NOT_FOUND", "Camera remained foreground after capture, but no deterministic confirm button was found");
|
|
232
|
+
}
|
|
233
|
+
if (confirm === "no_resource_id") {
|
|
234
|
+
throw new CameraProbeError("ALIGN_CONFIRM_NO_RESOURCE_ID", "Confirm candidate has no resourceId for deterministic replay");
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
packageName: cameraPackage,
|
|
238
|
+
activityName,
|
|
239
|
+
shutterResourceId: shutter.resourceId,
|
|
240
|
+
...(shutter.contentDescription !== undefined
|
|
241
|
+
? { shutterContentDescription: shutter.contentDescription }
|
|
242
|
+
: {}),
|
|
243
|
+
confirmResourceId: confirm.resourceId,
|
|
244
|
+
...(confirm.contentDescription !== undefined
|
|
245
|
+
? { confirmContentDescription: confirm.contentDescription }
|
|
246
|
+
: {}),
|
|
247
|
+
confirmActivityName: postShutterForeground.activity
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
try {
|
|
252
|
+
await uiSnapshotProvider.close();
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
void null;
|
|
256
|
+
}
|
|
257
|
+
if (cameraPackage !== undefined) {
|
|
258
|
+
try {
|
|
259
|
+
await adb.forceStop({
|
|
260
|
+
packageName: cameraPackage,
|
|
261
|
+
deviceSerial,
|
|
262
|
+
...(signal === undefined ? {} : { signal })
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
void null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async waitForStableForeground(adb, deviceSerial, signal) {
|
|
272
|
+
const deadline = this.deps.now() + CAMERA_FOREGROUND_TIMEOUT_MS;
|
|
273
|
+
let previous;
|
|
274
|
+
while (this.deps.now() < deadline) {
|
|
275
|
+
const current = await adb.foregroundComponent(deviceIdentity(deviceSerial, signal));
|
|
276
|
+
if (previous !== undefined
|
|
277
|
+
&& previous.packageName === current.packageName
|
|
278
|
+
&& previous.activity === current.activity) {
|
|
279
|
+
return current;
|
|
280
|
+
}
|
|
281
|
+
previous = current;
|
|
282
|
+
await this.deps.sleep(Math.min(CAMERA_FOREGROUND_POLL_INTERVAL_MS, Math.max(0, deadline - this.deps.now())));
|
|
283
|
+
}
|
|
284
|
+
throw new CameraProbeError("ALIGN_CAMERA_NOT_LAUNCHED", "Foreground Activity did not stabilize within 8s");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
import { access, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, mkdtemp, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { isErrnoException } from "../../shared/errors.js";
|
|
3
4
|
function assertRunDirectoryName(name) {
|
|
4
5
|
if (!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
5
6
|
throw new Error("Invalid artifact run directory name");
|
|
6
7
|
}
|
|
7
8
|
}
|
|
9
|
+
async function projectedRealPath(path) {
|
|
10
|
+
const missing = [];
|
|
11
|
+
let current = resolve(path);
|
|
12
|
+
for (;;) {
|
|
13
|
+
try {
|
|
14
|
+
return resolve(await realpath(current), ...missing.reverse());
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (!isErrnoException(error) || error.code !== "ENOENT") {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
const parent = dirname(current);
|
|
21
|
+
if (parent === current) {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
missing.push(current.slice(parent.length + 1));
|
|
25
|
+
current = parent;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function assertArtifactAuthorityBoundary(path) {
|
|
30
|
+
const canonical = await projectedRealPath(path);
|
|
31
|
+
const segments = canonical.split(/[\\/]+/);
|
|
32
|
+
const workspaceIndex = segments.lastIndexOf(".taphound");
|
|
33
|
+
if (workspaceIndex < 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const namespace = segments[workspaceIndex + 1];
|
|
37
|
+
if (namespace === "build") {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (namespace === "local"
|
|
41
|
+
&& segments[workspaceIndex + 2] !== undefined) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Artifact path inside .taphound/ must stay under .taphound/build/ or .taphound/local/<id>/: ${path}`);
|
|
45
|
+
}
|
|
8
46
|
function safePath(root, relativePath) {
|
|
9
47
|
const candidate = resolve(root, relativePath);
|
|
10
48
|
const fromRoot = relative(root, candidate);
|
|
@@ -65,6 +103,7 @@ class FileSystemArtifactSession {
|
|
|
65
103
|
export class FileSystemArtifactStore {
|
|
66
104
|
begin = async (baseDirectory, runDirectoryName) => {
|
|
67
105
|
assertRunDirectoryName(runDirectoryName);
|
|
106
|
+
await assertArtifactAuthorityBoundary(baseDirectory);
|
|
68
107
|
await mkdir(baseDirectory, { recursive: true });
|
|
69
108
|
const temporaryDirectory = await mkdtemp(join(baseDirectory, `.${runDirectoryName}.tmp-`));
|
|
70
109
|
await mkdir(join(temporaryDirectory, "steps"), { recursive: true });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ContextDocumentWrite, ContextDocumentWriter } from "../../ports/context-document-writer.js";
|
|
2
|
+
export declare class FileSystemContextDocumentWriter implements ContextDocumentWriter {
|
|
3
|
+
readonly writeContextDocument: (input: {
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
relativePath: string;
|
|
6
|
+
document: unknown;
|
|
7
|
+
}) => Promise<ContextDocumentWrite>;
|
|
8
|
+
readonly writeContextDocumentBatch: (input: {
|
|
9
|
+
projectRoot: string;
|
|
10
|
+
documents: readonly {
|
|
11
|
+
relativePath: string;
|
|
12
|
+
document: unknown;
|
|
13
|
+
}[];
|
|
14
|
+
}) => Promise<ContextDocumentWrite[]>;
|
|
15
|
+
}
|