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,20 +1,31 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { appProcessPids, primaryAppPid } from "../../domain/app-process.js";
|
|
4
|
-
import { GenerationSessionSchema, GenerationInFlightSchema, expandProposedStepVariables, hashGenerationConfirmationEvidence } from "../../domain/generation.js";
|
|
4
|
+
import { GenerationSessionSchema, GenerationInFlightSchema, expandProposedStepVariables, hashGenerationConfirmationEvidence, isGenerationConfirmationExpired } from "../../domain/generation.js";
|
|
5
5
|
import { JourneyStepSchema } from "../../domain/journey.js";
|
|
6
|
+
import { uiBackendIdAsSelection } from "../../domain/ui-backend.js";
|
|
7
|
+
import { assessWindowHierarchy } from "../../domain/window-hierarchy.js";
|
|
6
8
|
import { ProposedStepSchema, hashProposedStep } from "../../domain/proposed-step.js";
|
|
7
9
|
import { RuntimeSnapshotSchema, hashRuntimeSnapshot } from "../../domain/runtime-snapshot.js";
|
|
10
|
+
import { isKnownSystemPackage, isSystemScenario } from "../../domain/system-app-profiles.js";
|
|
8
11
|
import { ExpectationEvaluator } from "../assertion/expectation-evaluator.js";
|
|
12
|
+
import { GuardedExpectationObservations, settledExpectationForeground } from "../assertion/guarded-expectation.js";
|
|
9
13
|
import { LogcatCollector } from "../collector/logcat-collector.js";
|
|
14
|
+
import { logcatStopFailed } from "../collector/logcat-stop.js";
|
|
15
|
+
import { resolveActionTarget } from "../interaction/action-target.js";
|
|
10
16
|
import { ActionExecutor } from "../interaction/action-executor.js";
|
|
11
17
|
import { ScrollToExecutor } from "../interaction/scroll-to-executor.js";
|
|
18
|
+
import { BridgeRunner, ExternalStepRunner, idleTimeoutDetails } from "../interaction/external-step-runner.js";
|
|
12
19
|
import { resolveLocator } from "../locator/locator-resolver.js";
|
|
13
20
|
import { IdleWaiter } from "../wait/idle-waiter.js";
|
|
21
|
+
import { deviceIdentityResolver } from "../wait/idle-profiles.js";
|
|
22
|
+
import { withIdleAdvice } from "../wait/idle-advice.js";
|
|
14
23
|
import { summarizeProposedStep } from "./generation-confirmation-service.js";
|
|
15
24
|
import { GenerationOperationError } from "./generation-starter.js";
|
|
16
25
|
import { RiskEvaluator } from "./risk-evaluator.js";
|
|
17
26
|
import { hasExactlyOneEnabledFocusedElement } from "./focused-input.js";
|
|
27
|
+
import { closeUiSnapshotProvider } from "../ui/ui-snapshot-lifecycle.js";
|
|
28
|
+
import { uiStabilityProbe } from "../ui/ui-stability-probe.js";
|
|
18
29
|
class StepCancelledError extends Error {
|
|
19
30
|
name = "StepCancelledError";
|
|
20
31
|
}
|
|
@@ -64,7 +75,8 @@ function asFailure(error) {
|
|
|
64
75
|
&& typeof error.code === "string") {
|
|
65
76
|
return {
|
|
66
77
|
code: error.code,
|
|
67
|
-
message: error instanceof Error ? error.message : error.code
|
|
78
|
+
message: error instanceof Error ? error.message : error.code,
|
|
79
|
+
...("details" in error ? { details: error.details } : {})
|
|
68
80
|
};
|
|
69
81
|
}
|
|
70
82
|
return {
|
|
@@ -72,8 +84,8 @@ function asFailure(error) {
|
|
|
72
84
|
message: error instanceof Error ? error.message : "Unknown step failure"
|
|
73
85
|
};
|
|
74
86
|
}
|
|
75
|
-
function fail(code, message) {
|
|
76
|
-
throw Object.assign(new Error(message), { code });
|
|
87
|
+
function fail(code, message, details) {
|
|
88
|
+
throw Object.assign(new Error(message), { code, ...(details === undefined ? {} : { details }) });
|
|
77
89
|
}
|
|
78
90
|
function sameSession(left, right) {
|
|
79
91
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
@@ -94,7 +106,7 @@ function exactApprovedChallenge(session, proposal, snapshot, source, now) {
|
|
|
94
106
|
source
|
|
95
107
|
})
|
|
96
108
|
|| challenge.actionSummary !== summarizeProposedStep(proposal)
|
|
97
|
-
||
|
|
109
|
+
|| isGenerationConfirmationExpired(challenge, now)) {
|
|
98
110
|
throw new GenerationOperationError("RISK_CONFIRMATION_REQUIRED", "An exact non-expired approved confirmation is required");
|
|
99
111
|
}
|
|
100
112
|
return challenge;
|
|
@@ -125,38 +137,41 @@ function assertBaseAuthorization(session, proposal, snapshot) {
|
|
|
125
137
|
throw new GenerationOperationError("SNAPSHOT_STALE", "Proposal is not bound to the authoritative generation snapshot");
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
|
-
function executableStep(proposal, variables, after) {
|
|
140
|
+
function executableStep(proposal, variables, after, extra) {
|
|
129
141
|
const expanded = expandProposedStepVariables(proposal, variables);
|
|
130
|
-
const action = Object.fromEntries(Object.entries(expanded).filter(([key]) => key !== "binding" && key !== "activity"));
|
|
142
|
+
const action = Object.fromEntries(Object.entries(expanded).filter(([key]) => key !== "binding" && key !== "activity" && key !== "flow"));
|
|
131
143
|
return JourneyStepSchema.parse({
|
|
132
144
|
...action,
|
|
145
|
+
...(extra === undefined ? {} : extra),
|
|
133
146
|
activity: {
|
|
134
147
|
before: expanded.activity.before,
|
|
135
148
|
after
|
|
136
149
|
}
|
|
137
150
|
});
|
|
138
151
|
}
|
|
139
|
-
function requireTarget(layout, step) {
|
|
140
|
-
if (step.action !== "click"
|
|
152
|
+
function requireTarget(layout, step, viewport) {
|
|
153
|
+
if ((step.action !== "click"
|
|
141
154
|
&& step.action !== "longClick"
|
|
142
|
-
&& step.action !== "swipe")
|
|
155
|
+
&& step.action !== "swipe")
|
|
156
|
+
|| step.locator === undefined) {
|
|
143
157
|
return undefined;
|
|
144
158
|
}
|
|
145
|
-
const resolution =
|
|
159
|
+
const resolution = resolveActionTarget(layout, step.action, step.locator, viewport);
|
|
146
160
|
if (resolution.status !== "found") {
|
|
147
161
|
fail(resolution.code, resolution.message);
|
|
148
162
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
return resolution.target;
|
|
164
|
+
}
|
|
165
|
+
function requireBridgeTrigger(layout, step, viewport) {
|
|
166
|
+
const resolution = resolveLocator(layout, step.triggerLocator, {
|
|
167
|
+
requiredCapability: "clickable",
|
|
168
|
+
viewport
|
|
169
|
+
});
|
|
170
|
+
if (resolution.status !== "found") {
|
|
171
|
+
fail(resolution.code, resolution.message);
|
|
155
172
|
}
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|| resolution.element.bounds === undefined)) {
|
|
159
|
-
fail("ACTION_FAILED", "swipe target lacks scrollable bounds");
|
|
173
|
+
if (resolution.element.clickable !== true) {
|
|
174
|
+
fail("ACTION_FAILED", "bridge trigger target is not clickable");
|
|
160
175
|
}
|
|
161
176
|
return {
|
|
162
177
|
point: resolution.point,
|
|
@@ -168,16 +183,127 @@ function requireTarget(layout, step) {
|
|
|
168
183
|
export class GenerationStepExecutor {
|
|
169
184
|
dependencies;
|
|
170
185
|
riskEvaluator = new RiskEvaluator();
|
|
186
|
+
currentViewport;
|
|
187
|
+
currentUiSnapshot;
|
|
171
188
|
constructor(dependencies) {
|
|
172
189
|
this.dependencies = dependencies;
|
|
173
190
|
}
|
|
191
|
+
boundUiSnapshotProvider() {
|
|
192
|
+
const uiSnapshotProvider = this.dependencies.uiSnapshotProvider;
|
|
193
|
+
if (uiSnapshotProvider === undefined) {
|
|
194
|
+
throw new Error("Generation UI snapshot provider is not bound");
|
|
195
|
+
}
|
|
196
|
+
return uiSnapshotProvider;
|
|
197
|
+
}
|
|
198
|
+
boundViews() {
|
|
199
|
+
const views = this.dependencies.views;
|
|
200
|
+
if (views === undefined) {
|
|
201
|
+
throw new Error("Generation runtime session views are not bound");
|
|
202
|
+
}
|
|
203
|
+
return views;
|
|
204
|
+
}
|
|
205
|
+
requireCurrentViewport() {
|
|
206
|
+
if (this.currentViewport === undefined) {
|
|
207
|
+
throw new Error("Generation UI snapshot viewport is unavailable");
|
|
208
|
+
}
|
|
209
|
+
return this.currentViewport;
|
|
210
|
+
}
|
|
211
|
+
requireCurrentUiSnapshot() {
|
|
212
|
+
if (this.currentUiSnapshot === undefined) {
|
|
213
|
+
throw new Error("Generation UI snapshot metadata is unavailable");
|
|
214
|
+
}
|
|
215
|
+
return this.currentUiSnapshot;
|
|
216
|
+
}
|
|
217
|
+
async captureLayout(reason, timeoutMs, signal) {
|
|
218
|
+
const uiSnapshotProvider = this.boundUiSnapshotProvider();
|
|
219
|
+
const snapshot = await uiSnapshotProvider.capture({
|
|
220
|
+
reason,
|
|
221
|
+
freshness: "sameMutationEpoch",
|
|
222
|
+
timeoutMs,
|
|
223
|
+
...(signal === undefined ? {} : { signal })
|
|
224
|
+
});
|
|
225
|
+
this.currentViewport = snapshot.viewport;
|
|
226
|
+
this.currentUiSnapshot = {
|
|
227
|
+
observationId: snapshot.observationId,
|
|
228
|
+
capturedAt: snapshot.capturedAt,
|
|
229
|
+
durationMs: snapshot.durationMs,
|
|
230
|
+
backend: snapshot.backend,
|
|
231
|
+
viewport: snapshot.viewport
|
|
232
|
+
};
|
|
233
|
+
return snapshot.roots;
|
|
234
|
+
}
|
|
174
235
|
execute = async (input) => {
|
|
236
|
+
if (this.dependencies.uiSnapshotProvider === undefined) {
|
|
237
|
+
if (this.dependencies.sessions === undefined
|
|
238
|
+
|| this.dependencies.sessionPorts === undefined
|
|
239
|
+
|| this.dependencies.createFreshnessGuard === undefined) {
|
|
240
|
+
throw new Error("Generation UI snapshot provider factory is unavailable");
|
|
241
|
+
}
|
|
242
|
+
const session = GenerationSessionSchema.parse(await this.dependencies.store.read(input.generationId));
|
|
243
|
+
const idle = session.idlePolicy === undefined
|
|
244
|
+
? this.dependencies.idle
|
|
245
|
+
: session.idlePolicy;
|
|
246
|
+
const deviceSession = await this.dependencies.sessions.openSession({
|
|
247
|
+
deviceSerial: session.target.deviceSerial,
|
|
248
|
+
...(input.signal === undefined ? {} : { signal: input.signal })
|
|
249
|
+
});
|
|
250
|
+
try {
|
|
251
|
+
const views = this.dependencies.sessionPorts(deviceSession);
|
|
252
|
+
const boundBackendSelection = uiBackendIdAsSelection(session.bindings.uiBackend.id);
|
|
253
|
+
const uiSnapshotProvider = await deviceSession.openUiSnapshots({
|
|
254
|
+
timeoutMs: idle.timeoutMs,
|
|
255
|
+
...(boundBackendSelection === undefined
|
|
256
|
+
? {}
|
|
257
|
+
: { backend: boundBackendSelection }),
|
|
258
|
+
...(this.dependencies.uiCacheEnabled === undefined
|
|
259
|
+
? {}
|
|
260
|
+
: { cacheEnabled: this.dependencies.uiCacheEnabled }),
|
|
261
|
+
...(input.signal === undefined ? {} : { signal: input.signal })
|
|
262
|
+
});
|
|
263
|
+
try {
|
|
264
|
+
return await new GenerationStepExecutor({
|
|
265
|
+
...this.dependencies,
|
|
266
|
+
idle,
|
|
267
|
+
freshnessGuard: this.dependencies.createFreshnessGuard(uiSnapshotProvider, views),
|
|
268
|
+
views,
|
|
269
|
+
uiSnapshotProvider
|
|
270
|
+
}).execute(input);
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
await closeUiSnapshotProvider(uiSnapshotProvider);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
await deviceSession.close();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const freshnessGuard = this.dependencies.freshnessGuard;
|
|
281
|
+
if (freshnessGuard === undefined) {
|
|
282
|
+
throw new Error("Generation freshness guard is unavailable");
|
|
283
|
+
}
|
|
284
|
+
const executionStartedAt = this.dependencies.clock.now();
|
|
285
|
+
const timing = {
|
|
286
|
+
freshnessCheckMs: 0,
|
|
287
|
+
evidenceSetupMs: 0,
|
|
288
|
+
logcatStartMs: 0,
|
|
289
|
+
preActionObservationMs: 0,
|
|
290
|
+
actionExecutionMs: 0,
|
|
291
|
+
idleWaitMs: 0,
|
|
292
|
+
postActionObservationMs: 0,
|
|
293
|
+
expectationMs: 0,
|
|
294
|
+
logcatStopMs: 0,
|
|
295
|
+
totalMs: 0
|
|
296
|
+
};
|
|
175
297
|
const proposal = ProposedStepSchema.parse(input.proposal);
|
|
176
298
|
const snapshot = RuntimeSnapshotSchema.parse(input.snapshot);
|
|
177
299
|
const source = z.enum(["planner", "manualOverride"]).parse(input.source);
|
|
178
300
|
const session = GenerationSessionSchema.parse(await this.dependencies.store.read(input.generationId));
|
|
301
|
+
if (JSON.stringify(session.bindings.uiBackend)
|
|
302
|
+
!== JSON.stringify(this.boundUiSnapshotProvider().descriptor)) {
|
|
303
|
+
throw new GenerationOperationError("CONFIG_INVALID", "Generation UI backend does not match the authoritative session");
|
|
304
|
+
}
|
|
179
305
|
assertBaseAuthorization(session, proposal, snapshot);
|
|
180
|
-
const risk = this.riskEvaluator.evaluate(proposal
|
|
306
|
+
const risk = this.riskEvaluator.evaluate(proposal, session.target.interactionPolicy, snapshot);
|
|
181
307
|
if (risk.effectiveRisk === "forbidden") {
|
|
182
308
|
throw new GenerationOperationError("ACTION_FORBIDDEN", `${proposal.action} is forbidden by the Core interaction policy`);
|
|
183
309
|
}
|
|
@@ -208,7 +334,15 @@ export class GenerationStepExecutor {
|
|
|
208
334
|
}
|
|
209
335
|
let fresh;
|
|
210
336
|
try {
|
|
211
|
-
|
|
337
|
+
const freshnessStartedAt = this.dependencies.clock.now();
|
|
338
|
+
fresh = await freshnessGuard.assertFresh(proposal.binding, input.signal, approved,
|
|
339
|
+
// A wait proposal has no Locator target; a pure Layout change (a
|
|
340
|
+
// loading indicator that settled between bind and execute) is not
|
|
341
|
+
// a reason to force a re-observe round trip.
|
|
342
|
+
proposal.action === "wait"
|
|
343
|
+
? { layoutChangeFrom: snapshot }
|
|
344
|
+
: undefined);
|
|
345
|
+
timing.freshnessCheckMs = (this.dependencies.clock.now() - freshnessStartedAt);
|
|
212
346
|
}
|
|
213
347
|
catch (error) {
|
|
214
348
|
await clearApproved();
|
|
@@ -240,7 +374,15 @@ export class GenerationStepExecutor {
|
|
|
240
374
|
stepIndex: session.candidateSteps.length,
|
|
241
375
|
snapshotHash: proposal.binding.snapshotHash,
|
|
242
376
|
proposalHash: hashProposedStep(proposal),
|
|
243
|
-
attemptId: this.dependencies.generateAttemptId()
|
|
377
|
+
attemptId: this.dependencies.generateAttemptId(),
|
|
378
|
+
...(approved === undefined
|
|
379
|
+
? {}
|
|
380
|
+
: {
|
|
381
|
+
confirmation: {
|
|
382
|
+
challengeId: approved.challengeId,
|
|
383
|
+
approvalMode: approved.approvalMode ?? "localTty"
|
|
384
|
+
}
|
|
385
|
+
})
|
|
244
386
|
});
|
|
245
387
|
}
|
|
246
388
|
catch (error) {
|
|
@@ -274,10 +416,12 @@ export class GenerationStepExecutor {
|
|
|
274
416
|
let proposalEvidenceSha256;
|
|
275
417
|
let snapshotEvidenceSha256;
|
|
276
418
|
try {
|
|
419
|
+
const evidenceStartedAt = this.dependencies.clock.now();
|
|
277
420
|
await this.dependencies.store.writeEvidence(session.id, proposalEvidencePath, proposal);
|
|
278
421
|
proposalEvidenceSha256 = sha256(canonicalEvidenceBytes(proposal));
|
|
279
422
|
await this.dependencies.store.writeEvidence(session.id, snapshotEvidencePath, snapshot);
|
|
280
423
|
snapshotEvidenceSha256 = sha256(canonicalEvidenceBytes(snapshot));
|
|
424
|
+
timing.evidenceSetupMs = (this.dependencies.clock.now() - evidenceStartedAt);
|
|
281
425
|
}
|
|
282
426
|
catch (error) {
|
|
283
427
|
await this.markRecovery(session.id, inFlight);
|
|
@@ -287,31 +431,52 @@ export class GenerationStepExecutor {
|
|
|
287
431
|
await this.markRecovery(session.id, inFlight);
|
|
288
432
|
return cancellation();
|
|
289
433
|
}
|
|
290
|
-
const logcat = new LogcatCollector(this.
|
|
434
|
+
const logcat = new LogcatCollector(this.boundViews().adb, this.dependencies.clock);
|
|
291
435
|
let logcatStarted = false;
|
|
292
436
|
let finalStep;
|
|
293
437
|
let outcome;
|
|
438
|
+
let stableLayout;
|
|
439
|
+
let postActionRuntime;
|
|
440
|
+
let bridgeEscapedPackageName;
|
|
441
|
+
let bridgeExternalSteps;
|
|
294
442
|
const stepStartedAt = this.dependencies.clock.now();
|
|
295
443
|
try {
|
|
444
|
+
const preActionStartedAt = this.dependencies.clock.now();
|
|
445
|
+
const preAction = await this.observeLive(session, authoritativePid, proposal.activity.before, input.signal,
|
|
446
|
+
// The freshness guard already tolerated a pure Layout change for
|
|
447
|
+
// wait proposals, so the pre-action hash gate must not re-impose
|
|
448
|
+
// it; package, process, and Activity checks above still apply.
|
|
449
|
+
proposal.action === "wait" ? undefined : fresh, this.dependencies.idle.timeoutMs, this.adoptFreshSnapshotUi(fresh));
|
|
450
|
+
timing.preActionObservationMs = (this.dependencies.clock.now() - preActionStartedAt);
|
|
451
|
+
throwIfCancelled(input.signal);
|
|
452
|
+
const logcatStartedAt = this.dependencies.clock.now();
|
|
296
453
|
await logcat.start({
|
|
297
454
|
deviceSerial: session.target.deviceSerial,
|
|
455
|
+
pids: preAction.pids,
|
|
298
456
|
...(input.signal === undefined ? {} : { signal: input.signal })
|
|
299
457
|
});
|
|
458
|
+
timing.logcatStartMs = (this.dependencies.clock.now() - logcatStartedAt);
|
|
300
459
|
logcatStarted = true;
|
|
301
460
|
throwIfCancelled(input.signal);
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
461
|
+
const proposedFlow = proposal.action === "bridge"
|
|
462
|
+
? proposal.flow
|
|
463
|
+
: undefined;
|
|
305
464
|
const provisional = executableStep(proposal, session.variables, proposal.activity.before);
|
|
306
|
-
const actionExecutor = new ActionExecutor(this.
|
|
307
|
-
const idleWaiter = new IdleWaiter(this.
|
|
465
|
+
const actionExecutor = new ActionExecutor(this.boundViews().adb, session.target.deviceSerial, () => this.boundUiSnapshotProvider().invalidate?.("beforeAction"));
|
|
466
|
+
const idleWaiter = new IdleWaiter(uiStabilityProbe(this.boundUiSnapshotProvider(), this.boundViews().uiStability), this.dependencies.clock, session.target.deviceSerial, session.target.packageName, deviceIdentityResolver(this.boundViews().adb, {
|
|
467
|
+
packageName: session.target.packageName,
|
|
468
|
+
deviceSerial: session.target.deviceSerial,
|
|
469
|
+
timeoutMs: this.dependencies.idle.timeoutMs
|
|
470
|
+
}));
|
|
308
471
|
if (provisional.action === "scrollTo") {
|
|
472
|
+
const actionStartedAt = this.dependencies.clock.now();
|
|
309
473
|
const scroll = await new ScrollToExecutor({
|
|
310
|
-
|
|
474
|
+
uiSnapshotProvider: this.boundUiSnapshotProvider(),
|
|
311
475
|
actionExecutor,
|
|
312
476
|
idleWaiter,
|
|
313
477
|
deviceSerial: session.target.deviceSerial,
|
|
314
478
|
idle: this.dependencies.idle,
|
|
479
|
+
viewport: () => this.currentViewport,
|
|
315
480
|
beforeSwipe: async () => {
|
|
316
481
|
const guarded = await this.observeLive(session, authoritativePid, proposal.activity.before, input.signal);
|
|
317
482
|
return guarded.layout;
|
|
@@ -321,6 +486,9 @@ export class GenerationStepExecutor {
|
|
|
321
486
|
},
|
|
322
487
|
requireLiveContainerCapability: true
|
|
323
488
|
}).execute(provisional, input.signal, preAction.layout);
|
|
489
|
+
const scrollDurationMs = (this.dependencies.clock.now() - actionStartedAt);
|
|
490
|
+
timing.idleWaitMs = scroll.idleDurationMs;
|
|
491
|
+
timing.actionExecutionMs = Math.max(0, scrollDurationMs - timing.idleWaitMs);
|
|
324
492
|
throwIfCancelled(input.signal);
|
|
325
493
|
if (scroll.status === "cancelled") {
|
|
326
494
|
outcome = {
|
|
@@ -329,11 +497,77 @@ export class GenerationStepExecutor {
|
|
|
329
497
|
};
|
|
330
498
|
}
|
|
331
499
|
else if (scroll.status === "failed") {
|
|
332
|
-
fail(scroll.code, scroll.message
|
|
500
|
+
fail(scroll.code, scroll.message, scroll.idle === undefined
|
|
501
|
+
? undefined
|
|
502
|
+
: { idle: scroll.idle });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else if (provisional.action === "bridge") {
|
|
506
|
+
throwIfCancelled(input.signal);
|
|
507
|
+
await this.assertForegroundIdentity(session, authoritativePid, proposal.activity.before, input.signal);
|
|
508
|
+
throwIfCancelled(input.signal);
|
|
509
|
+
const triggerTarget = requireBridgeTrigger(preAction.layout, provisional, preAction.viewport);
|
|
510
|
+
const scenario = provisional.scenario;
|
|
511
|
+
const bridge = await new BridgeRunner({
|
|
512
|
+
adb: this.boundViews().adb,
|
|
513
|
+
clock: this.dependencies.clock,
|
|
514
|
+
actionExecutor,
|
|
515
|
+
externalSteps: this.externalStepRunner(session),
|
|
516
|
+
idleWaiter,
|
|
517
|
+
idle: this.dependencies.idle,
|
|
518
|
+
packageName: session.target.packageName,
|
|
519
|
+
deviceSerial: session.target.deviceSerial
|
|
520
|
+
}).run({
|
|
521
|
+
trigger: {
|
|
522
|
+
step: JourneyStepSchema.parse({
|
|
523
|
+
action: "click",
|
|
524
|
+
locator: provisional.triggerLocator,
|
|
525
|
+
activity: {
|
|
526
|
+
before: provisional.activity.before,
|
|
527
|
+
after: provisional.activity.before
|
|
528
|
+
}
|
|
529
|
+
}),
|
|
530
|
+
target: triggerTarget
|
|
531
|
+
},
|
|
532
|
+
escapeTimeoutMs: provisional.escapeTimeoutMs ?? 3000,
|
|
533
|
+
returnTimeoutMs: provisional.returnTimeoutMs,
|
|
534
|
+
acceptEscape: (escapedPackageName) => {
|
|
535
|
+
if (isSystemScenario(scenario)
|
|
536
|
+
&& !isKnownSystemPackage(scenario, escapedPackageName)) {
|
|
537
|
+
fail("SCENARIO_PACKAGE_MISMATCH", `Escaped package "${escapedPackageName}" is not a known ${scenario} package`);
|
|
538
|
+
}
|
|
539
|
+
return undefined;
|
|
540
|
+
},
|
|
541
|
+
external: {
|
|
542
|
+
kind: "steps",
|
|
543
|
+
steps: (escapedPackageName) => (proposedFlow === undefined
|
|
544
|
+
? Promise.resolve(undefined)
|
|
545
|
+
: this.resolveExternalFlow(session, proposedFlow, escapedPackageName, input.signal))
|
|
546
|
+
},
|
|
547
|
+
signal: input.signal
|
|
548
|
+
});
|
|
549
|
+
timing.actionExecutionMs = bridge.timing.actionMs;
|
|
550
|
+
timing.idleWaitMs = bridge.timing.waitMs;
|
|
551
|
+
if (bridge.status === "cancelled") {
|
|
552
|
+
outcome = {
|
|
553
|
+
status: "cancelled",
|
|
554
|
+
failure: {
|
|
555
|
+
code: "RECOVERY_REQUIRED",
|
|
556
|
+
message: "Step was cancelled"
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
else if (bridge.status === "failed") {
|
|
561
|
+
fail(bridge.code, bridge.message, bridge.details);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
bridgeEscapedPackageName = bridge.escapedPackageName;
|
|
565
|
+
bridgeExternalSteps = bridge.externalSteps;
|
|
566
|
+
stableLayout = bridge.idle.layout;
|
|
333
567
|
}
|
|
334
568
|
}
|
|
335
569
|
else {
|
|
336
|
-
const target = requireTarget(preAction.layout, provisional);
|
|
570
|
+
const target = requireTarget(preAction.layout, provisional, preAction.viewport);
|
|
337
571
|
if (provisional.action === "inputText") {
|
|
338
572
|
if (!hasExactlyOneEnabledFocusedElement(preAction.layout)) {
|
|
339
573
|
fail("ACTION_FAILED", "inputText requires exactly one enabled focused Layout element");
|
|
@@ -343,7 +577,9 @@ export class GenerationStepExecutor {
|
|
|
343
577
|
throwIfCancelled(input.signal);
|
|
344
578
|
await this.assertForegroundIdentity(session, authoritativePid, proposal.activity.before, input.signal);
|
|
345
579
|
throwIfCancelled(input.signal);
|
|
580
|
+
const actionStartedAt = this.dependencies.clock.now();
|
|
346
581
|
const action = await actionExecutor.execute(provisional, target, input.signal);
|
|
582
|
+
timing.actionExecutionMs = (this.dependencies.clock.now() - actionStartedAt);
|
|
347
583
|
if (isCancelled(input.signal)) {
|
|
348
584
|
outcome = {
|
|
349
585
|
status: "cancelled",
|
|
@@ -358,7 +594,9 @@ export class GenerationStepExecutor {
|
|
|
358
594
|
}
|
|
359
595
|
}
|
|
360
596
|
if (outcome === undefined) {
|
|
597
|
+
const idleStartedAt = this.dependencies.clock.now();
|
|
361
598
|
const idle = await idleWaiter.waitUntilIdle(this.dependencies.idle, input.signal);
|
|
599
|
+
timing.idleWaitMs = (this.dependencies.clock.now() - idleStartedAt);
|
|
362
600
|
if (idle.status === "cancelled") {
|
|
363
601
|
outcome = {
|
|
364
602
|
status: "cancelled",
|
|
@@ -369,55 +607,42 @@ export class GenerationStepExecutor {
|
|
|
369
607
|
};
|
|
370
608
|
}
|
|
371
609
|
else if (idle.status === "timeout") {
|
|
372
|
-
fail(idle.code, "Layout did not become stable before timeout");
|
|
610
|
+
fail(idle.code, withIdleAdvice("Layout did not become stable before timeout", idle), idleTimeoutDetails(idle));
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
stableLayout = idle.layout;
|
|
373
614
|
}
|
|
374
615
|
throwIfCancelled(input.signal);
|
|
375
616
|
}
|
|
376
617
|
}
|
|
377
618
|
if (outcome === undefined) {
|
|
378
619
|
throwIfCancelled(input.signal);
|
|
379
|
-
const
|
|
620
|
+
const postActionStartedAt = this.dependencies.clock.now();
|
|
621
|
+
const after = await this.observeLive(session, authoritativePid, undefined, input.signal, undefined, this.dependencies.idle.timeoutMs, stableLayout);
|
|
622
|
+
timing.postActionObservationMs = (this.dependencies.clock.now() - postActionStartedAt);
|
|
623
|
+
postActionRuntime = after;
|
|
380
624
|
logcat.scopeToPids(after.pids);
|
|
381
|
-
|
|
625
|
+
const bridgeExtra = {};
|
|
626
|
+
if (bridgeEscapedPackageName !== undefined) {
|
|
627
|
+
bridgeExtra.escapedPackageName = bridgeEscapedPackageName;
|
|
628
|
+
}
|
|
629
|
+
if (bridgeExternalSteps !== undefined) {
|
|
630
|
+
bridgeExtra.externalSteps = bridgeExternalSteps;
|
|
631
|
+
bridgeExtra.replayMode = "auto";
|
|
632
|
+
}
|
|
633
|
+
finalStep = executableStep(proposal, session.variables, after.activity, Object.keys(bridgeExtra).length === 0 ? undefined : bridgeExtra);
|
|
382
634
|
if (finalStep.expect !== undefined) {
|
|
383
|
-
const
|
|
635
|
+
const expectationStartedAt = this.dependencies.clock.now();
|
|
636
|
+
const observations = new GuardedExpectationObservations({
|
|
637
|
+
activity: (observation) => this.assertForegroundIdentity(session, authoritativePid, undefined, observation.signal, observation.timeoutMs),
|
|
638
|
+
layout: (observation) => this.observeLive(session, authoritativePid, after.activity, observation.signal, undefined, observation.timeoutMs),
|
|
639
|
+
rethrow: (error) => error instanceof StepCancelledError
|
|
640
|
+
}, after);
|
|
641
|
+
const expectation = await new ExpectationEvaluator(this.boundViews().adb, this.boundUiSnapshotProvider(), logcat, this.dependencies.clock).evaluate(finalStep.expect, {
|
|
384
642
|
packageName: session.target.packageName,
|
|
385
643
|
deviceSerial: session.target.deviceSerial,
|
|
386
644
|
stepStartedAt
|
|
387
|
-
}, input.signal,
|
|
388
|
-
activity: async (observation) => {
|
|
389
|
-
try {
|
|
390
|
-
const activity = await this.assertForegroundIdentity(session, authoritativePid, undefined, observation.signal, observation.timeoutMs);
|
|
391
|
-
return { status: "observed", activity };
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
if (error instanceof StepCancelledError)
|
|
395
|
-
throw error;
|
|
396
|
-
return {
|
|
397
|
-
status: "failed",
|
|
398
|
-
message: error instanceof Error
|
|
399
|
-
? error.message
|
|
400
|
-
: "Generated Expect Activity observation failed"
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
},
|
|
404
|
-
layout: async (observation) => {
|
|
405
|
-
try {
|
|
406
|
-
const guarded = await this.observeLive(session, authoritativePid, after.activity, observation.signal, undefined, observation.timeoutMs);
|
|
407
|
-
return { status: "observed", layout: guarded.layout };
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
if (error instanceof StepCancelledError)
|
|
411
|
-
throw error;
|
|
412
|
-
return {
|
|
413
|
-
status: "failed",
|
|
414
|
-
message: error instanceof Error
|
|
415
|
-
? error.message
|
|
416
|
-
: "Generated Expect Layout observation failed"
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
});
|
|
645
|
+
}, input.signal, observations.boundary());
|
|
421
646
|
if (expectation.status === "cancelled") {
|
|
422
647
|
outcome = {
|
|
423
648
|
status: "cancelled",
|
|
@@ -432,11 +657,10 @@ export class GenerationStepExecutor {
|
|
|
432
657
|
}
|
|
433
658
|
else {
|
|
434
659
|
throwIfCancelled(input.signal);
|
|
435
|
-
await this.
|
|
436
|
-
? finalStep.expect.value
|
|
437
|
-
: after.activity, input.signal);
|
|
660
|
+
postActionRuntime = await this.settledExpectationRuntime(session, authoritativePid, finalStep.expect, after, observations.lastLayoutObservation(), input.signal);
|
|
438
661
|
throwIfCancelled(input.signal);
|
|
439
662
|
}
|
|
663
|
+
timing.expectationMs = (this.dependencies.clock.now() - expectationStartedAt);
|
|
440
664
|
}
|
|
441
665
|
}
|
|
442
666
|
}
|
|
@@ -447,15 +671,13 @@ export class GenerationStepExecutor {
|
|
|
447
671
|
}
|
|
448
672
|
let stopFailure;
|
|
449
673
|
if (logcatStarted) {
|
|
674
|
+
const logcatStopStartedAt = this.dependencies.clock.now();
|
|
450
675
|
try {
|
|
451
676
|
const stopped = await logcat.stop();
|
|
452
677
|
if (isCancelled(input.signal)) {
|
|
453
678
|
outcome = cancellation();
|
|
454
679
|
}
|
|
455
|
-
if (stopped
|
|
456
|
-
|| stopped.timedOut
|
|
457
|
-
|| stopped.cancelled
|
|
458
|
-
|| stopped.spawnError !== undefined) {
|
|
680
|
+
if (logcatStopFailed(stopped)) {
|
|
459
681
|
stopFailure = {
|
|
460
682
|
code: "COLLECTION_FAILED",
|
|
461
683
|
message: stopped.stderr.trim()
|
|
@@ -472,12 +694,15 @@ export class GenerationStepExecutor {
|
|
|
472
694
|
: "Logcat collector failed to stop"
|
|
473
695
|
};
|
|
474
696
|
}
|
|
697
|
+
timing.logcatStopMs = (this.dependencies.clock.now() - logcatStopStartedAt);
|
|
475
698
|
}
|
|
476
699
|
outcome ??= stopFailure === undefined
|
|
477
700
|
? { status: "succeeded", step: finalStep }
|
|
478
701
|
: { status: "failed", failure: stopFailure };
|
|
702
|
+
timing.totalMs = this.dependencies.clock.now() - executionStartedAt;
|
|
703
|
+
outcome = { ...outcome, timing };
|
|
479
704
|
const log = logcatStarted
|
|
480
|
-
? logcat.
|
|
705
|
+
? logcat.rawLines().map((line) => line.raw).join("\n")
|
|
481
706
|
: "";
|
|
482
707
|
try {
|
|
483
708
|
await this.dependencies.store.writeTextEvidence(session.id, executionPath(inFlight, "logcat.txt"), log.length === 0 ? "" : `${log}\n`);
|
|
@@ -506,6 +731,9 @@ export class GenerationStepExecutor {
|
|
|
506
731
|
path: snapshotEvidencePath,
|
|
507
732
|
sha256: snapshotEvidenceSha256
|
|
508
733
|
},
|
|
734
|
+
...(inFlight.confirmation === undefined
|
|
735
|
+
? {}
|
|
736
|
+
: { confirmation: inFlight.confirmation }),
|
|
509
737
|
outcome,
|
|
510
738
|
...(stopFailure === undefined
|
|
511
739
|
? {}
|
|
@@ -553,25 +781,95 @@ export class GenerationStepExecutor {
|
|
|
553
781
|
}
|
|
554
782
|
throw new GenerationOperationError("RECOVERY_REQUIRED", "Step completion state is ambiguous and requires recovery");
|
|
555
783
|
}
|
|
556
|
-
|
|
784
|
+
if (this.dependencies.observeNext === undefined
|
|
785
|
+
|| postActionRuntime === undefined) {
|
|
786
|
+
return outcome;
|
|
787
|
+
}
|
|
788
|
+
const nextObservationStartedAt = this.dependencies.clock.now();
|
|
789
|
+
try {
|
|
790
|
+
const nextObservation = await this.dependencies.observeNext({
|
|
791
|
+
generationId: session.id,
|
|
792
|
+
runtime: {
|
|
793
|
+
foregroundPackageName: postActionRuntime.foregroundPackageName,
|
|
794
|
+
activity: postActionRuntime.activity,
|
|
795
|
+
pid: postActionRuntime.pid,
|
|
796
|
+
layout: postActionRuntime.layout,
|
|
797
|
+
windowHierarchy: postActionRuntime.windowHierarchy,
|
|
798
|
+
uiSnapshot: postActionRuntime.uiSnapshot
|
|
799
|
+
},
|
|
800
|
+
...(input.signal === undefined ? {} : { signal: input.signal })
|
|
801
|
+
});
|
|
802
|
+
return {
|
|
803
|
+
...outcome,
|
|
804
|
+
timing: {
|
|
805
|
+
...timing,
|
|
806
|
+
nextObservationMs: (this.dependencies.clock.now() - nextObservationStartedAt)
|
|
807
|
+
},
|
|
808
|
+
nextObservation
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
return {
|
|
813
|
+
...outcome,
|
|
814
|
+
timing: {
|
|
815
|
+
...timing,
|
|
816
|
+
nextObservationMs: (this.dependencies.clock.now() - nextObservationStartedAt)
|
|
817
|
+
},
|
|
818
|
+
nextObservationFailure: asFailure(error)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
557
821
|
}
|
|
558
822
|
await this.markRecovery(session.id, inFlight);
|
|
559
823
|
return outcome;
|
|
560
824
|
};
|
|
561
|
-
|
|
562
|
-
|
|
825
|
+
/**
|
|
826
|
+
* The freshness guard captured this Layout and proved it equals the
|
|
827
|
+
* authoritative binding hash, and nothing mutates the device between that
|
|
828
|
+
* capture and the pre-action observation, so re-capturing it would only
|
|
829
|
+
* repeat the most expensive device call of the step.
|
|
830
|
+
*/
|
|
831
|
+
adoptFreshSnapshotUi(fresh) {
|
|
832
|
+
this.currentViewport = fresh.viewport;
|
|
833
|
+
this.currentUiSnapshot = {
|
|
834
|
+
observationId: fresh.uiObservationId,
|
|
835
|
+
capturedAt: fresh.capturedAt,
|
|
836
|
+
durationMs: fresh.uiCaptureDurationMs,
|
|
837
|
+
backend: fresh.uiBackend,
|
|
838
|
+
viewport: fresh.viewport
|
|
839
|
+
};
|
|
840
|
+
return fresh.layout;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* The post-action runtime after a passing expectation: an `element`
|
|
844
|
+
* expectation already observed it, and a proven `activity` expectation on
|
|
845
|
+
* the post-action Activity adds nothing to re-observe. Otherwise the
|
|
846
|
+
* settled foreground is observed again.
|
|
847
|
+
*/
|
|
848
|
+
async settledExpectationRuntime(session, expectedPid, expectation, after, lastLayoutObservation, signal) {
|
|
849
|
+
const settled = settledExpectationForeground(expectation, after.activity);
|
|
850
|
+
if (expectation.type === "element") {
|
|
851
|
+
return lastLayoutObservation ?? after;
|
|
852
|
+
}
|
|
853
|
+
if (settled.proven && settled.activity === after.activity) {
|
|
854
|
+
return after;
|
|
855
|
+
}
|
|
856
|
+
return this.observeLive(session, expectedPid, settled.activity, signal);
|
|
857
|
+
}
|
|
858
|
+
async observeLive(session, expectedPid, expectedActivity, signal, authoritativeSnapshot, timeoutMs = this.dependencies.idle.timeoutMs, stableLayout) {
|
|
859
|
+
const deadline = this.dependencies.clock.now()
|
|
860
|
+
+ Math.max(timeoutMs, this.dependencies.idle.timeoutMs);
|
|
563
861
|
const identity = () => ({
|
|
564
862
|
packageName: session.target.packageName,
|
|
565
863
|
deviceSerial: session.target.deviceSerial,
|
|
566
864
|
...(signal === undefined ? {} : { signal }),
|
|
567
865
|
timeoutMs: Math.max(1, deadline - this.dependencies.clock.now())
|
|
568
866
|
});
|
|
569
|
-
const foreground = await this.
|
|
867
|
+
const foreground = await this.boundViews().adb.foregroundComponent(identity());
|
|
570
868
|
throwIfCancelled(signal);
|
|
571
869
|
if (foreground.packageName !== session.target.packageName) {
|
|
572
870
|
fail("PACKAGE_ESCAPE", "Foreground package escaped generation target");
|
|
573
871
|
}
|
|
574
|
-
const processes = await this.
|
|
872
|
+
const processes = await this.boundViews().adb.appProcesses(identity());
|
|
575
873
|
const pid = primaryAppPid(processes, session.target.packageName);
|
|
576
874
|
throwIfCancelled(signal);
|
|
577
875
|
if (pid === null || pid !== expectedPid) {
|
|
@@ -581,26 +879,33 @@ export class GenerationStepExecutor {
|
|
|
581
879
|
&& foreground.activity !== expectedActivity) {
|
|
582
880
|
fail("SNAPSHOT_STALE", "Generation Activity changed unexpectedly");
|
|
583
881
|
}
|
|
584
|
-
const layout = await
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
.foregroundComponent(identity());
|
|
882
|
+
const [layout, topology] = await Promise.all([
|
|
883
|
+
stableLayout === undefined
|
|
884
|
+
? this.captureLayout("locate", Math.max(1, deadline - this.dependencies.clock.now()), signal)
|
|
885
|
+
: Promise.resolve(stableLayout),
|
|
886
|
+
this.boundViews().adb.windowTopology(identity())
|
|
887
|
+
]);
|
|
888
|
+
const windowHierarchy = assessWindowHierarchy(topology, layout);
|
|
592
889
|
throwIfCancelled(signal);
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
890
|
+
// A Layout captured here must be bracketed by a second identity check.
|
|
891
|
+
// A settled Layout passed in was captured before the checks above, so
|
|
892
|
+
// they already confirm it and a second pair would observe nothing new.
|
|
893
|
+
if (stableLayout === undefined) {
|
|
894
|
+
const confirmedForeground = await this.boundViews().adb
|
|
895
|
+
.foregroundComponent(identity());
|
|
896
|
+
throwIfCancelled(signal);
|
|
897
|
+
if (confirmedForeground.packageName !== session.target.packageName) {
|
|
898
|
+
fail("PACKAGE_ESCAPE", "Foreground package escaped generation target");
|
|
899
|
+
}
|
|
900
|
+
const confirmedPid = primaryAppPid(await this.boundViews().adb.appProcesses(identity()), session.target.packageName);
|
|
901
|
+
throwIfCancelled(signal);
|
|
902
|
+
if (confirmedPid === null || confirmedPid !== expectedPid) {
|
|
903
|
+
fail("APP_CRASHED", "Generation process identity changed");
|
|
904
|
+
}
|
|
905
|
+
if (confirmedForeground.activity
|
|
906
|
+
!== (expectedActivity ?? foreground.activity)) {
|
|
907
|
+
fail("SNAPSHOT_STALE", "Generation Activity changed unexpectedly");
|
|
908
|
+
}
|
|
604
909
|
}
|
|
605
910
|
if (authoritativeSnapshot !== undefined) {
|
|
606
911
|
const liveHash = hashRuntimeSnapshot({
|
|
@@ -608,7 +913,10 @@ export class GenerationStepExecutor {
|
|
|
608
913
|
foregroundPackageName: foreground.packageName,
|
|
609
914
|
activity: foreground.activity,
|
|
610
915
|
pid,
|
|
611
|
-
layout
|
|
916
|
+
layout,
|
|
917
|
+
...(authoritativeSnapshot.windowHierarchy === undefined
|
|
918
|
+
? {}
|
|
919
|
+
: { windowHierarchy })
|
|
612
920
|
});
|
|
613
921
|
const authoritativeHash = hashRuntimeSnapshot(authoritativeSnapshot);
|
|
614
922
|
if (liveHash !== authoritativeHash) {
|
|
@@ -620,18 +928,22 @@ export class GenerationStepExecutor {
|
|
|
620
928
|
activity: foreground.activity,
|
|
621
929
|
pid,
|
|
622
930
|
pids: appProcessPids(processes),
|
|
623
|
-
layout
|
|
931
|
+
layout,
|
|
932
|
+
viewport: this.requireCurrentViewport(),
|
|
933
|
+
uiSnapshot: this.requireCurrentUiSnapshot(),
|
|
934
|
+
windowHierarchy
|
|
624
935
|
};
|
|
625
936
|
}
|
|
626
937
|
async assertForegroundIdentity(session, expectedPid, expectedActivity, signal, timeoutMs = this.dependencies.idle.timeoutMs) {
|
|
627
|
-
const deadline = this.dependencies.clock.now()
|
|
938
|
+
const deadline = this.dependencies.clock.now()
|
|
939
|
+
+ Math.max(timeoutMs, this.dependencies.idle.timeoutMs);
|
|
628
940
|
const identity = () => ({
|
|
629
941
|
packageName: session.target.packageName,
|
|
630
942
|
deviceSerial: session.target.deviceSerial,
|
|
631
943
|
...(signal === undefined ? {} : { signal }),
|
|
632
944
|
timeoutMs: Math.max(1, deadline - this.dependencies.clock.now())
|
|
633
945
|
});
|
|
634
|
-
const foreground = await this.
|
|
946
|
+
const foreground = await this.boundViews().adb.foregroundComponent(identity());
|
|
635
947
|
throwIfCancelled(signal);
|
|
636
948
|
if (foreground.packageName !== session.target.packageName) {
|
|
637
949
|
fail("PACKAGE_ESCAPE", "Foreground package escaped generation target");
|
|
@@ -640,7 +952,7 @@ export class GenerationStepExecutor {
|
|
|
640
952
|
&& foreground.activity !== expectedActivity) {
|
|
641
953
|
fail("SNAPSHOT_STALE", "Generation Activity changed before mutation");
|
|
642
954
|
}
|
|
643
|
-
const pid = primaryAppPid(await this.
|
|
955
|
+
const pid = primaryAppPid(await this.boundViews().adb.appProcesses(identity()), session.target.packageName);
|
|
644
956
|
throwIfCancelled(signal);
|
|
645
957
|
if (pid === null || pid !== expectedPid) {
|
|
646
958
|
fail("APP_CRASHED", "Generation process identity changed");
|
|
@@ -665,4 +977,55 @@ export class GenerationStepExecutor {
|
|
|
665
977
|
});
|
|
666
978
|
await this.dependencies.store.update(generationId, latest.revision, recovery);
|
|
667
979
|
}
|
|
980
|
+
/** Resolves and validates the bound External Flow for the escaped app. */
|
|
981
|
+
async resolveExternalFlow(session, flowName, escapedPackageName, signal) {
|
|
982
|
+
if (this.dependencies.externalFlowResolver === undefined) {
|
|
983
|
+
fail("EXTERNAL_FLOW_NOT_FOUND", "External Flow resolver is unavailable in this runtime");
|
|
984
|
+
}
|
|
985
|
+
const binding = session.externalFlows.find((entry) => entry.name === flowName);
|
|
986
|
+
if (binding === undefined) {
|
|
987
|
+
fail("EXTERNAL_FLOW_NOT_FOUND", `External Flow "${flowName}" is not bound to generation session ${session.id}`);
|
|
988
|
+
}
|
|
989
|
+
const resolution = await this.dependencies.externalFlowResolver.resolve({
|
|
990
|
+
projectRoot: this.dependencies.projectRoot,
|
|
991
|
+
name: flowName
|
|
992
|
+
});
|
|
993
|
+
if (resolution.flowSha256 !== binding.flowSha256) {
|
|
994
|
+
fail("EXTERNAL_FLOW_STALE", `External Flow "${flowName}" has changed since session start`);
|
|
995
|
+
}
|
|
996
|
+
if (resolution.flow.escapedPackageName !== binding.escapedPackageName) {
|
|
997
|
+
fail("EXTERNAL_PACKAGE_MISMATCH", `External Flow "${flowName}" escaped package does not match session binding`);
|
|
998
|
+
}
|
|
999
|
+
if (resolution.flow.escapedPackageName !== escapedPackageName) {
|
|
1000
|
+
fail("EXTERNAL_PACKAGE_MISMATCH", `Escaped package "${escapedPackageName}" does not match Flow package "${resolution.flow.escapedPackageName}"`);
|
|
1001
|
+
}
|
|
1002
|
+
if (resolution.flow.expectedEscapeActivity !== undefined) {
|
|
1003
|
+
const foreground = await this.boundViews().adb.foregroundComponent({
|
|
1004
|
+
packageName: escapedPackageName,
|
|
1005
|
+
deviceSerial: session.target.deviceSerial,
|
|
1006
|
+
...(signal === undefined ? {} : { signal }),
|
|
1007
|
+
timeoutMs: 5000
|
|
1008
|
+
});
|
|
1009
|
+
if (foreground.activity !== resolution.flow.expectedEscapeActivity) {
|
|
1010
|
+
fail("EXTERNAL_ACTIVITY_MISMATCH", `External escape Activity "${foreground.activity}" does not match expected "${resolution.flow.expectedEscapeActivity}"`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return resolution.flow.steps;
|
|
1014
|
+
}
|
|
1015
|
+
externalStepRunner(session) {
|
|
1016
|
+
return new ExternalStepRunner({
|
|
1017
|
+
adb: this.boundViews().adb,
|
|
1018
|
+
actionExecutor: new ActionExecutor(this.boundViews().adb, session.target.deviceSerial, () => this.boundUiSnapshotProvider().invalidate?.("beforeAction")),
|
|
1019
|
+
uiSnapshotProvider: this.boundUiSnapshotProvider(),
|
|
1020
|
+
captureLayout: (reason, timeoutMs, signal) => this.captureLayout(reason, timeoutMs, signal),
|
|
1021
|
+
createIdleWaiter: (packageName) => new IdleWaiter(uiStabilityProbe(this.boundUiSnapshotProvider(), this.boundViews().uiStability), this.dependencies.clock, session.target.deviceSerial, packageName, deviceIdentityResolver(this.boundViews().adb, {
|
|
1022
|
+
packageName,
|
|
1023
|
+
deviceSerial: session.target.deviceSerial,
|
|
1024
|
+
timeoutMs: this.dependencies.idle.timeoutMs
|
|
1025
|
+
})),
|
|
1026
|
+
idle: this.dependencies.idle,
|
|
1027
|
+
viewport: () => this.currentViewport,
|
|
1028
|
+
deviceSerial: session.target.deviceSerial
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
668
1031
|
}
|