taphound 0.2.0-dev.1 → 0.2.0-dev.12

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.
Files changed (467) hide show
  1. package/README.md +49 -149
  2. package/README.zh-CN.md +47 -147
  3. package/assets/skills/taphound-case-suite/SKILL.md +363 -0
  4. package/assets/skills/taphound-case-suite/schemas/base-flow-record.schema.json +22 -0
  5. package/assets/skills/taphound-case-suite/schemas/case-catalog.schema.json +26 -0
  6. package/assets/skills/taphound-case-suite/schemas/case-ledger.schema.json +158 -0
  7. package/assets/skills/taphound-case-suite/schemas/suite-input.schema.json +41 -0
  8. package/assets/skills/taphound-case-suite/schemas/transition.schema.json +61 -0
  9. package/assets/skills/taphound-case-suite/scripts/ledger.mjs +1207 -0
  10. package/assets/skills/taphound-case-suite/templates/base-flow-record.example.json +20 -0
  11. package/assets/skills/taphound-case-suite/templates/suite-input.example.json +27 -0
  12. package/assets/skills/taphound-case-suite/templates/transition.example.json +8 -0
  13. package/assets/skills/taphound-flash/SKILL.md +115 -0
  14. package/assets/skills/taphound-flash/scripts/flash.mjs +585 -0
  15. package/assets/skills/taphound-flash/templates/flash-plan.example.json +15 -0
  16. package/assets/skills/taphound-journey-brief-author/CONTEXT-GUIDE.md +471 -0
  17. package/assets/skills/taphound-journey-brief-author/SKILL.md +494 -0
  18. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.md +96 -0
  19. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.zh-CN.md +80 -0
  20. package/assets/skills/taphound-journey-brief-author/prompts/context-analyze-project.md +309 -0
  21. package/assets/skills/taphound-journey-brief-author/schemas/context-refresh-result.json +123 -0
  22. package/assets/skills/taphound-journey-brief-author/schemas/project-context-module.json +192 -0
  23. package/assets/skills/taphound-journey-brief-author/schemas/project-context.json +152 -0
  24. package/assets/skills/taphound-journey-brief-author/templates/project-context-module.example.json +58 -0
  25. package/assets/skills/taphound-journey-brief-author/templates/project-context.example.json +51 -0
  26. package/assets/skills/taphound-journey-brief-author/templates/taphound-journey-brief.template.md +63 -0
  27. package/assets/skills/taphound-journey-generator/SKILL.md +507 -0
  28. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/prompts/check-completion.md +3 -0
  29. package/assets/skills/taphound-journey-generator/prompts/consume-journey-brief.md +52 -0
  30. package/assets/skills/taphound-journey-generator/prompts/generate-step.md +211 -0
  31. package/assets/skills/taphound-journey-generator/prompts/select-flow.md +91 -0
  32. package/assets/skills/taphound-journey-generator/references/bridge.md +35 -0
  33. package/assets/skills/taphound-journey-generator/references/mid-session.md +44 -0
  34. package/assets/skills/taphound-journey-generator/schemas/external-flow.json +37 -0
  35. package/assets/skills/taphound-journey-generator/schemas/flow.json +28 -0
  36. package/assets/skills/taphound-journey-generator/schemas/journey-source.json +25 -0
  37. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/observe-output.json +13 -4
  38. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/proposed-step-envelope.json +127 -5
  39. package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +578 -0
  40. package/assets/skills/taphound-journey-generator/templates/external-flow.example.json +22 -0
  41. package/assets/skills/taphound-journey-generator/templates/flow.example.json +22 -0
  42. package/assets/skills/taphound-journey-generator/templates/journey-source.example.json +17 -0
  43. package/assets/skills/taphound-journey-generator/templates/taphound-journey-brief.example.md +70 -0
  44. package/assets/skills/taphound-verify-change/SKILL.md +91 -0
  45. package/assets/skills/taphound-verify-change/references/accept.md +38 -0
  46. package/assets/skills/taphound-verify-change/references/preserve.md +357 -0
  47. package/assets/skills/taphound-verify-change/schemas/workflow-manifest.schema.json +295 -0
  48. package/assets/skills/taphound-verify-change/scripts/handoff.mjs +482 -0
  49. package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +505 -0
  50. package/dist/adapters/adb/adb-adapter.d.ts +16 -2
  51. package/dist/adapters/adb/adb-adapter.js +199 -0
  52. package/dist/adapters/adb/system-uiautomator-snapshot-provider.d.ts +13 -0
  53. package/dist/adapters/adb/system-uiautomator-snapshot-provider.js +122 -0
  54. package/dist/adapters/adb/ui-automator-parser.d.ts +3 -0
  55. package/dist/adapters/adb/ui-automator-parser.js +160 -0
  56. package/dist/adapters/adb/window-topology-parser.d.ts +2 -0
  57. package/dist/adapters/adb/window-topology-parser.js +101 -0
  58. package/dist/adapters/android-cli/android-cli-adapter.d.ts +16 -8
  59. package/dist/adapters/android-cli/android-cli-adapter.js +137 -15
  60. package/dist/adapters/android-cli/android-cli-snapshot-provider.d.ts +9 -0
  61. package/dist/adapters/android-cli/android-cli-snapshot-provider.js +99 -0
  62. package/dist/adapters/android-cli/layout-parser.js +12 -8
  63. package/dist/adapters/appium/appium-doctor.d.ts +7 -0
  64. package/dist/adapters/appium/appium-doctor.js +128 -0
  65. package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +42 -0
  66. package/dist/adapters/appium/appium-ui-snapshot-provider.js +309 -0
  67. package/dist/adapters/camera/camera-probe-adapter.d.ts +18 -0
  68. package/dist/adapters/camera/camera-probe-adapter.js +286 -0
  69. package/dist/adapters/filesystem/artifact-store.js +40 -1
  70. package/dist/adapters/filesystem/context-document-writer.d.ts +15 -0
  71. package/dist/adapters/filesystem/context-document-writer.js +112 -0
  72. package/dist/adapters/filesystem/diagnostics-journal.d.ts +17 -0
  73. package/dist/adapters/filesystem/diagnostics-journal.js +130 -0
  74. package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
  75. package/dist/adapters/filesystem/external-flow-registry.js +325 -0
  76. package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
  77. package/dist/adapters/filesystem/generation-session-store.js +281 -855
  78. package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
  79. package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
  80. package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
  81. package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
  82. package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
  83. package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
  84. package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
  85. package/dist/adapters/filesystem/journey-composition-store.js +151 -0
  86. package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
  87. package/dist/adapters/filesystem/knowledge-registry.js +223 -0
  88. package/dist/adapters/filesystem/project-bound-file.js +3 -5
  89. package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
  90. package/dist/adapters/filesystem/project-file-inspector.js +57 -28
  91. package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
  92. package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
  93. package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
  94. package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
  95. package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
  96. package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
  97. package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
  98. package/dist/adapters/filesystem/skill-installer.js +40 -11
  99. package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
  100. package/dist/adapters/filesystem/workspace-layout.js +57 -0
  101. package/dist/adapters/git/node-git-diff.d.ts +17 -0
  102. package/dist/adapters/git/node-git-diff.js +114 -0
  103. package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
  104. package/dist/adapters/process/node-detached-process-launcher.js +96 -0
  105. package/dist/adapters/process/node-process-runner.js +11 -2
  106. package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
  107. package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
  108. package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
  109. package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
  110. package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
  111. package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
  112. package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
  113. package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
  114. package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
  115. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
  116. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
  117. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
  118. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
  119. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
  120. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
  121. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
  122. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
  123. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
  124. package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
  125. package/dist/adapters/runtime/session-adb-view.js +193 -0
  126. package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
  127. package/dist/adapters/runtime/session-backed-ports.js +43 -0
  128. package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
  129. package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
  130. package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
  131. package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
  132. package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
  133. package/dist/adapters/ui/device-layout-temp.js +18 -0
  134. package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
  135. package/dist/adapters/ui/device-ui-environment.js +81 -0
  136. package/dist/adapters/ui/layout-normalization.d.ts +9 -0
  137. package/dist/adapters/ui/layout-normalization.js +44 -0
  138. package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
  139. package/dist/adapters/ui/ui-snapshot-error.js +12 -0
  140. package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
  141. package/dist/adapters/ui/ui-snapshot-support.js +12 -0
  142. package/dist/application/align/align-service.d.ts +42 -0
  143. package/dist/application/align/align-service.js +106 -0
  144. package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
  145. package/dist/application/assertion/expectation-evaluator.js +156 -18
  146. package/dist/application/assertion/guarded-expectation.d.ts +45 -0
  147. package/dist/application/assertion/guarded-expectation.js +74 -0
  148. package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
  149. package/dist/application/checkpoint/baseline-capturer.js +163 -0
  150. package/dist/application/checkpoint/baseline-error.d.ts +5 -0
  151. package/dist/application/checkpoint/baseline-error.js +8 -0
  152. package/dist/application/checkpoint/baseline-service.d.ts +31 -0
  153. package/dist/application/checkpoint/baseline-service.js +147 -0
  154. package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
  155. package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
  156. package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
  157. package/dist/application/checkpoint/regression-comparator.js +192 -0
  158. package/dist/application/collector/logcat-collector.d.ts +22 -1
  159. package/dist/application/collector/logcat-collector.js +103 -15
  160. package/dist/application/collector/logcat-event.d.ts +8 -0
  161. package/dist/application/collector/logcat-event.js +79 -0
  162. package/dist/application/collector/logcat-stop.d.ts +2 -0
  163. package/dist/application/collector/logcat-stop.js +11 -0
  164. package/dist/application/context/context-generator.d.ts +45 -0
  165. package/dist/application/context/context-generator.js +228 -0
  166. package/dist/application/context/context-loader.d.ts +40 -0
  167. package/dist/application/context/context-loader.js +223 -0
  168. package/dist/application/context/context-refresher.d.ts +59 -0
  169. package/dist/application/context/context-refresher.js +349 -0
  170. package/dist/application/context/context-rehasher.d.ts +36 -0
  171. package/dist/application/context/context-rehasher.js +110 -0
  172. package/dist/application/context/context-validator.d.ts +23 -1
  173. package/dist/application/context/context-validator.js +116 -9
  174. package/dist/application/context/evidence-hash.d.ts +1 -0
  175. package/dist/application/context/evidence-hash.js +125 -0
  176. package/dist/application/context/evidence-match.d.ts +9 -0
  177. package/dist/application/context/evidence-match.js +8 -0
  178. package/dist/application/context/shard-identity.d.ts +2 -0
  179. package/dist/application/context/shard-identity.js +7 -0
  180. package/dist/application/contract/contract-loader.d.ts +25 -0
  181. package/dist/application/contract/contract-loader.js +68 -0
  182. package/dist/application/contract/contract-review.d.ts +25 -0
  183. package/dist/application/contract/contract-review.js +51 -0
  184. package/dist/application/contract/contract-verifier.d.ts +53 -0
  185. package/dist/application/contract/contract-verifier.js +645 -0
  186. package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
  187. package/dist/application/devices/resolve-device-assignments.js +60 -0
  188. package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
  189. package/dist/application/diagnosis/failure-classifier.js +179 -0
  190. package/dist/application/diagnostics/diagnostics-exporter.d.ts +29 -0
  191. package/dist/application/diagnostics/diagnostics-exporter.js +283 -0
  192. package/dist/application/diagnostics/ui-capture-telemetry.d.ts +16 -0
  193. package/dist/application/diagnostics/ui-capture-telemetry.js +81 -0
  194. package/dist/application/doctor/doctor-service.d.ts +36 -3
  195. package/dist/application/doctor/doctor-service.js +255 -25
  196. package/dist/application/generation/generation-app-preparer.d.ts +20 -0
  197. package/dist/application/generation/generation-app-preparer.js +28 -0
  198. package/dist/application/generation/generation-config-service.d.ts +21 -0
  199. package/dist/application/generation/generation-config-service.js +53 -0
  200. package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
  201. package/dist/application/generation/generation-confirmation-service.js +109 -24
  202. package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
  203. package/dist/application/generation/generation-context-snapshot.js +29 -0
  204. package/dist/application/generation/generation-finalizer.d.ts +15 -9
  205. package/dist/application/generation/generation-finalizer.js +167 -70
  206. package/dist/application/generation/generation-publisher.js +12 -8
  207. package/dist/application/generation/generation-recovery-service.d.ts +35 -0
  208. package/dist/application/generation/generation-recovery-service.js +144 -0
  209. package/dist/application/generation/generation-reopen-service.d.ts +15 -0
  210. package/dist/application/generation/generation-reopen-service.js +37 -0
  211. package/dist/application/generation/generation-replace-service.d.ts +32 -0
  212. package/dist/application/generation/generation-replace-service.js +116 -0
  213. package/dist/application/generation/generation-starter.d.ts +44 -4
  214. package/dist/application/generation/generation-starter.js +175 -8
  215. package/dist/application/generation/generation-step-executor.d.ts +67 -5
  216. package/dist/application/generation/generation-step-executor.js +471 -108
  217. package/dist/application/generation/proposed-step-validator.js +96 -30
  218. package/dist/application/generation/replay-policy-loader.d.ts +16 -0
  219. package/dist/application/generation/replay-policy-loader.js +29 -0
  220. package/dist/application/generation/risk-evaluator.d.ts +4 -1
  221. package/dist/application/generation/risk-evaluator.js +118 -3
  222. package/dist/application/generation/runtime-observer.d.ts +39 -7
  223. package/dist/application/generation/runtime-observer.js +193 -24
  224. package/dist/application/impact/impact-resolver.d.ts +32 -0
  225. package/dist/application/impact/impact-resolver.js +160 -0
  226. package/dist/application/init/init-service.js +26 -13
  227. package/dist/application/interaction/action-executor.d.ts +3 -2
  228. package/dist/application/interaction/action-executor.js +11 -1
  229. package/dist/application/interaction/action-target.d.ts +17 -0
  230. package/dist/application/interaction/action-target.js +40 -0
  231. package/dist/application/interaction/external-step-runner.d.ts +153 -0
  232. package/dist/application/interaction/external-step-runner.js +332 -0
  233. package/dist/application/interaction/fallback-resolver.d.ts +6 -3
  234. package/dist/application/interaction/fallback-resolver.js +7 -5
  235. package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
  236. package/dist/application/interaction/scroll-to-executor.js +118 -36
  237. package/dist/application/journey/external-flow-resolver.d.ts +25 -0
  238. package/dist/application/journey/external-flow-resolver.js +22 -0
  239. package/dist/application/journey/journey-check-service.d.ts +54 -0
  240. package/dist/application/journey/journey-check-service.js +207 -0
  241. package/dist/application/journey/journey-promoter.d.ts +26 -0
  242. package/dist/application/journey/journey-promoter.js +123 -0
  243. package/dist/application/journey/journey-resolver.d.ts +42 -0
  244. package/dist/application/journey/journey-resolver.js +239 -0
  245. package/dist/application/journey/journey-retirer.d.ts +25 -0
  246. package/dist/application/journey/journey-retirer.js +87 -0
  247. package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
  248. package/dist/application/knowledge/anchor-resolver.js +110 -0
  249. package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
  250. package/dist/application/knowledge/knowledge-loader.js +36 -0
  251. package/dist/application/locator/layout-failure-summary.d.ts +2 -0
  252. package/dist/application/locator/layout-failure-summary.js +67 -0
  253. package/dist/application/locator/layout-traversal.d.ts +11 -0
  254. package/dist/application/locator/layout-traversal.js +18 -0
  255. package/dist/application/locator/locator-resolver.d.ts +13 -1
  256. package/dist/application/locator/locator-resolver.js +182 -18
  257. package/dist/application/observe/observe-service.d.ts +20 -0
  258. package/dist/application/observe/observe-service.js +91 -0
  259. package/dist/application/project/project-describer.d.ts +14 -6
  260. package/dist/application/project/project-describer.js +38 -5
  261. package/dist/application/recognition/screen-detector.d.ts +28 -0
  262. package/dist/application/recognition/screen-detector.js +111 -0
  263. package/dist/application/recorder/locator-selector.js +151 -17
  264. package/dist/application/recorder/recorder-service.d.ts +16 -4
  265. package/dist/application/recorder/recorder-service.js +420 -128
  266. package/dist/application/report/report-writer.d.ts +5 -0
  267. package/dist/application/report/report-writer.js +17 -1
  268. package/dist/application/runtime/cold-launch.d.ts +28 -0
  269. package/dist/application/runtime/cold-launch.js +59 -0
  270. package/dist/application/runtime/step-runner.d.ts +49 -4
  271. package/dist/application/runtime/step-runner.js +721 -130
  272. package/dist/application/runtime/verify-runtime.d.ts +57 -7
  273. package/dist/application/runtime/verify-runtime.js +577 -175
  274. package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
  275. package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
  276. package/dist/application/ui/observed-ui-snapshot-provider.d.ts +15 -0
  277. package/dist/application/ui/observed-ui-snapshot-provider.js +66 -0
  278. package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
  279. package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
  280. package/dist/application/ui/ui-stability-probe.d.ts +9 -0
  281. package/dist/application/ui/ui-stability-probe.js +24 -0
  282. package/dist/application/wait/idle-advice.d.ts +6 -0
  283. package/dist/application/wait/idle-advice.js +27 -0
  284. package/dist/application/wait/idle-profiles.d.ts +15 -0
  285. package/dist/application/wait/idle-profiles.js +75 -0
  286. package/dist/application/wait/idle-waiter.d.ts +28 -4
  287. package/dist/application/wait/idle-waiter.js +201 -15
  288. package/dist/cli/commands/align.d.ts +3 -0
  289. package/dist/cli/commands/align.js +124 -0
  290. package/dist/cli/commands/baseline.d.ts +3 -0
  291. package/dist/cli/commands/baseline.js +130 -0
  292. package/dist/cli/commands/context.js +300 -27
  293. package/dist/cli/commands/contract.d.ts +3 -0
  294. package/dist/cli/commands/contract.js +120 -0
  295. package/dist/cli/commands/diagnose.d.ts +3 -0
  296. package/dist/cli/commands/diagnose.js +84 -0
  297. package/dist/cli/commands/doctor.js +16 -9
  298. package/dist/cli/commands/failure.d.ts +3 -0
  299. package/dist/cli/commands/failure.js +63 -0
  300. package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
  301. package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
  302. package/dist/cli/commands/generation/session-commands.d.ts +11 -0
  303. package/dist/cli/commands/generation/session-commands.js +472 -0
  304. package/dist/cli/commands/generation/shared.d.ts +1414 -0
  305. package/dist/cli/commands/generation/shared.js +291 -0
  306. package/dist/cli/commands/generation/step-commands.d.ts +8 -0
  307. package/dist/cli/commands/generation/step-commands.js +315 -0
  308. package/dist/cli/commands/generation.js +10 -507
  309. package/dist/cli/commands/impact.d.ts +3 -0
  310. package/dist/cli/commands/impact.js +87 -0
  311. package/dist/cli/commands/init.js +13 -6
  312. package/dist/cli/commands/journey.d.ts +3 -0
  313. package/dist/cli/commands/journey.js +382 -0
  314. package/dist/cli/commands/knowledge.d.ts +3 -0
  315. package/dist/cli/commands/knowledge.js +93 -0
  316. package/dist/cli/commands/observe.d.ts +3 -0
  317. package/dist/cli/commands/observe.js +127 -0
  318. package/dist/cli/commands/project.js +2 -1
  319. package/dist/cli/commands/record.js +18 -5
  320. package/dist/cli/commands/verify.js +326 -64
  321. package/dist/cli/dependencies.d.ts +136 -1
  322. package/dist/cli/dependencies.js +504 -52
  323. package/dist/cli/diagnostics-recorder.d.ts +31 -0
  324. package/dist/cli/diagnostics-recorder.js +82 -0
  325. package/dist/cli/diff-verification.d.ts +41 -0
  326. package/dist/cli/diff-verification.js +192 -0
  327. package/dist/cli/main.d.ts +1 -1
  328. package/dist/cli/main.js +107 -2
  329. package/dist/cli/output.d.ts +5 -3
  330. package/dist/cli/output.js +12 -0
  331. package/dist/cli/program.js +21 -1
  332. package/dist/cli/project-root.d.ts +1 -0
  333. package/dist/cli/project-root.js +6 -0
  334. package/dist/cli/runtime-selection.d.ts +22 -0
  335. package/dist/cli/runtime-selection.js +92 -0
  336. package/dist/cli/version.d.ts +1 -0
  337. package/dist/cli/version.js +14 -0
  338. package/dist/cli/workspace-guard.d.ts +2 -0
  339. package/dist/cli/workspace-guard.js +3 -0
  340. package/dist/domain/binding-reference.d.ts +4 -0
  341. package/dist/domain/binding-reference.js +23 -0
  342. package/dist/domain/checkpoint.d.ts +404 -0
  343. package/dist/domain/checkpoint.js +297 -0
  344. package/dist/domain/config.d.ts +107 -1
  345. package/dist/domain/config.js +52 -6
  346. package/dist/domain/contract.d.ts +413 -0
  347. package/dist/domain/contract.js +196 -0
  348. package/dist/domain/diagnostics.d.ts +1396 -0
  349. package/dist/domain/diagnostics.js +193 -0
  350. package/dist/domain/external-flow.d.ts +493 -0
  351. package/dist/domain/external-flow.js +15 -0
  352. package/dist/domain/failure-classification.d.ts +176 -0
  353. package/dist/domain/failure-classification.js +169 -0
  354. package/dist/domain/failure.d.ts +2 -1
  355. package/dist/domain/failure.js +123 -0
  356. package/dist/domain/generation.d.ts +1671 -132
  357. package/dist/domain/generation.js +202 -13
  358. package/dist/domain/geometry.d.ts +13 -0
  359. package/dist/domain/geometry.js +16 -0
  360. package/dist/domain/impact.d.ts +71 -0
  361. package/dist/domain/impact.js +76 -0
  362. package/dist/domain/init.d.ts +12 -4
  363. package/dist/domain/init.js +24 -18
  364. package/dist/domain/journey-composition.d.ts +2285 -0
  365. package/dist/domain/journey-composition.js +85 -0
  366. package/dist/domain/journey-lifecycle.d.ts +10 -0
  367. package/dist/domain/journey-lifecycle.js +9 -0
  368. package/dist/domain/journey.d.ts +2419 -212
  369. package/dist/domain/journey.js +384 -14
  370. package/dist/domain/knowledge.d.ts +165 -0
  371. package/dist/domain/knowledge.js +188 -0
  372. package/dist/domain/layout.d.ts +34 -5
  373. package/dist/domain/layout.js +64 -6
  374. package/dist/domain/locator-evidence.d.ts +3 -0
  375. package/dist/domain/locator-evidence.js +36 -0
  376. package/dist/domain/logcat-event.d.ts +54 -0
  377. package/dist/domain/logcat-event.js +40 -0
  378. package/dist/domain/observation.d.ts +33 -0
  379. package/dist/domain/observation.js +18 -0
  380. package/dist/domain/project-context.d.ts +219 -1
  381. package/dist/domain/project-context.js +209 -6
  382. package/dist/domain/proposed-step.d.ts +340 -60
  383. package/dist/domain/proposed-step.js +23 -3
  384. package/dist/domain/report.d.ts +1455 -28
  385. package/dist/domain/report.js +211 -14
  386. package/dist/domain/runtime-snapshot.d.ts +58 -1
  387. package/dist/domain/runtime-snapshot.js +14 -3
  388. package/dist/domain/runtime.d.ts +50 -0
  389. package/dist/domain/runtime.js +36 -0
  390. package/dist/domain/system-app-profiles.d.ts +6 -0
  391. package/dist/domain/system-app-profiles.js +43 -0
  392. package/dist/domain/ui-backend.d.ts +28 -0
  393. package/dist/domain/ui-backend.js +33 -0
  394. package/dist/domain/ui-cache.d.ts +11 -0
  395. package/dist/domain/ui-cache.js +10 -0
  396. package/dist/domain/verify-receipt.d.ts +17 -0
  397. package/dist/domain/verify-receipt.js +17 -0
  398. package/dist/domain/window-hierarchy.d.ts +84 -0
  399. package/dist/domain/window-hierarchy.js +172 -0
  400. package/dist/domain/workflow-manifest.d.ts +77 -0
  401. package/dist/domain/workflow-manifest.js +136 -0
  402. package/dist/domain/workspace.d.ts +30 -0
  403. package/dist/domain/workspace.js +74 -0
  404. package/dist/ports/adb.d.ts +34 -1
  405. package/dist/ports/align-prompt.d.ts +11 -0
  406. package/dist/ports/align-prompt.js +6 -0
  407. package/dist/ports/anchor-resolver.d.ts +30 -0
  408. package/dist/ports/anchor-resolver.js +1 -0
  409. package/dist/ports/annotated-screen-resolver.d.ts +4 -0
  410. package/dist/ports/annotated-screen-resolver.js +1 -0
  411. package/dist/ports/camera-probe.d.ts +23 -0
  412. package/dist/ports/camera-probe.js +8 -0
  413. package/dist/ports/context-document-writer.d.ts +24 -0
  414. package/dist/ports/context-document-writer.js +1 -0
  415. package/dist/ports/detached-process-launcher.d.ts +12 -0
  416. package/dist/ports/detached-process-launcher.js +1 -0
  417. package/dist/ports/diagnostics.d.ts +15 -0
  418. package/dist/ports/diagnostics.js +1 -0
  419. package/dist/ports/external-flow-registry.d.ts +37 -0
  420. package/dist/ports/external-flow-registry.js +1 -0
  421. package/dist/ports/generation-prompt.d.ts +2 -1
  422. package/dist/ports/generation-session-store.d.ts +12 -2
  423. package/dist/ports/git-diff.d.ts +9 -0
  424. package/dist/ports/git-diff.js +1 -0
  425. package/dist/ports/journey-composition-store.d.ts +17 -0
  426. package/dist/ports/journey-composition-store.js +1 -0
  427. package/dist/ports/knowledge-registry.d.ts +20 -0
  428. package/dist/ports/knowledge-registry.js +1 -0
  429. package/dist/ports/process-runner.d.ts +7 -0
  430. package/dist/ports/project-file-inspector.d.ts +1 -0
  431. package/dist/ports/project-identity-inspector.d.ts +24 -0
  432. package/dist/ports/project-identity-inspector.js +1 -0
  433. package/dist/ports/project-inventory-inspector.d.ts +21 -0
  434. package/dist/ports/project-inventory-inspector.js +1 -0
  435. package/dist/ports/project-module-discoverer.d.ts +26 -0
  436. package/dist/ports/project-module-discoverer.js +1 -0
  437. package/dist/ports/recorder-prompt.d.ts +10 -1
  438. package/dist/ports/runtime-backend.d.ts +89 -0
  439. package/dist/ports/runtime-backend.js +13 -0
  440. package/dist/ports/runtime-capability.d.ts +12 -0
  441. package/dist/ports/runtime-capability.js +20 -0
  442. package/dist/ports/runtime-session-ports.d.ts +25 -0
  443. package/dist/ports/runtime-session-ports.js +1 -0
  444. package/dist/ports/screenshot.d.ts +11 -0
  445. package/dist/ports/screenshot.js +1 -0
  446. package/dist/ports/skill-installer.d.ts +2 -2
  447. package/dist/ports/ui-snapshot.d.ts +35 -0
  448. package/dist/ports/ui-snapshot.js +1 -0
  449. package/dist/ports/ui-stability.d.ts +19 -0
  450. package/dist/ports/ui-stability.js +1 -0
  451. package/dist/ports/workspace-layout.d.ts +4 -0
  452. package/dist/ports/workspace-layout.js +1 -0
  453. package/dist/shared/errors.d.ts +2 -0
  454. package/dist/shared/errors.js +22 -0
  455. package/dist/shared/paths.d.ts +2 -0
  456. package/dist/shared/paths.js +24 -0
  457. package/dist/shared/strings.d.ts +2 -0
  458. package/dist/shared/strings.js +16 -0
  459. package/package.json +7 -2
  460. package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
  461. package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
  462. package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
  463. package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
  464. package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
  465. package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
  466. package/dist/ports/android-cli.d.ts +0 -21
  467. /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
@@ -1,11 +1,22 @@
1
1
  import { appProcessPids, primaryAppPid } from "../../domain/app-process.js";
2
+ import { captureLogcatEvent } from "../collector/logcat-event.js";
3
+ import { bindingName } from "../../domain/binding-reference.js";
2
4
  import { ActionExecutor } from "../interaction/action-executor.js";
3
5
  import { FallbackResolver } from "../interaction/fallback-resolver.js";
4
6
  import { ScrollToExecutor } from "../interaction/scroll-to-executor.js";
7
+ import { BridgeRunner, ExternalStepRunner } from "../interaction/external-step-runner.js";
5
8
  import { resolveLocator } from "../locator/locator-resolver.js";
9
+ import { resolveActionTarget } from "../interaction/action-target.js";
6
10
  import { ExpectationEvaluator } from "../assertion/expectation-evaluator.js";
11
+ import { GuardedExpectationObservations, settledExpectationForeground } from "../assertion/guarded-expectation.js";
7
12
  import { IdleWaiter } from "../wait/idle-waiter.js";
13
+ import { deviceIdentityResolver } from "../wait/idle-profiles.js";
14
+ import { withIdleAdvice } from "../wait/idle-advice.js";
8
15
  import { hasExactlyOneEnabledFocusedElement } from "../generation/focused-input.js";
16
+ const WAIT_UNTIL_POLL_INTERVAL_MS = 100;
17
+ function isAborted(signal) {
18
+ return signal?.aborted === true;
19
+ }
9
20
  function stepPath(index, suffix) {
10
21
  return `steps/${String(index + 1).padStart(3, "0")}-${suffix}`;
11
22
  }
@@ -27,18 +38,32 @@ export class StepRunner {
27
38
  idleWaiter;
28
39
  expectationEvaluator;
29
40
  scrollToExecutor;
41
+ currentViewport;
42
+ bindings;
30
43
  constructor(options) {
31
44
  this.options = options;
32
- this.actionExecutor = new ActionExecutor(options.adb, options.deviceSerial);
33
- this.fallbackResolver = new FallbackResolver(options.androidCli, options.deviceSerial);
34
- this.idleWaiter = new IdleWaiter(options.androidCli, options.clock, options.deviceSerial);
35
- this.expectationEvaluator = new ExpectationEvaluator(options.adb, options.androidCli, options.logcat, options.clock);
45
+ this.bindings = options.bindings ?? new Map();
46
+ this.actionExecutor = new ActionExecutor(options.adb, options.deviceSerial, () => {
47
+ this.advanceDeviceEpoch();
48
+ options.uiSnapshotProvider.invalidate?.("beforeAction");
49
+ });
50
+ this.fallbackResolver = new FallbackResolver(options.screenshots, options.annotatedScreens, options.deviceSerial);
51
+ this.idleWaiter = new IdleWaiter(options.uiStability, options.clock, options.deviceSerial, options.packageName, deviceIdentityResolver(options.adb, {
52
+ packageName: options.packageName,
53
+ deviceSerial: options.deviceSerial,
54
+ timeoutMs: options.idle.timeoutMs
55
+ }));
56
+ this.expectationEvaluator = new ExpectationEvaluator(options.adb, options.uiSnapshotProvider, options.logcat, options.clock);
36
57
  this.scrollToExecutor = new ScrollToExecutor({
37
- androidCli: options.androidCli,
58
+ uiSnapshotProvider: options.uiSnapshotProvider,
38
59
  actionExecutor: this.actionExecutor,
39
60
  idleWaiter: this.idleWaiter,
40
61
  deviceSerial: options.deviceSerial,
41
62
  idle: options.idle,
63
+ viewport: () => this.currentViewport,
64
+ ...(options.anchorResolver === undefined
65
+ ? {}
66
+ : { anchorResolver: options.anchorResolver }),
42
67
  ...(options.generatedReplayPolicy === true
43
68
  ? {
44
69
  readLayout: async () => (this.captureGeneratedLayout(this.currentSignal)),
@@ -56,17 +81,41 @@ export class StepRunner {
56
81
  ...(signal === undefined ? {} : { signal }),
57
82
  timeoutMs: this.options.idle.timeoutMs
58
83
  });
84
+ /**
85
+ * Device facts observed since the last Layout capture, device mutation, or
86
+ * completed action. Within one epoch nothing TapHound did could have
87
+ * changed them, so a repeated check observes nothing new: every capture
88
+ * still gets a check after it, and every mutation a check before it.
89
+ */
90
+ deviceEpoch = 0;
91
+ foregroundProof;
92
+ pidProof;
93
+ advanceDeviceEpoch() {
94
+ this.deviceEpoch += 1;
95
+ }
59
96
  primaryPid = async (identity) => {
97
+ if (this.pidProof?.epoch === this.deviceEpoch) {
98
+ return this.pidProof.pid;
99
+ }
60
100
  const processes = await this.options.adb.appProcesses(identity);
61
101
  this.options.logcat.scopeToPids(appProcessPids(processes));
62
- return primaryAppPid(processes, this.options.packageName);
102
+ const pid = primaryAppPid(processes, this.options.packageName);
103
+ this.pidProof = { pid, epoch: this.deviceEpoch };
104
+ return pid;
63
105
  };
64
106
  async generatedForeground(expectedActivity, code, signal) {
107
+ const proof = this.foregroundProof;
108
+ if (proof?.epoch === this.deviceEpoch
109
+ && proof.packageName === this.options.packageName
110
+ && proof.activity === expectedActivity) {
111
+ return { packageName: proof.packageName, activity: proof.activity };
112
+ }
65
113
  const foreground = await this.options.adb.foregroundComponent(this.identity(signal));
66
114
  if (foreground.packageName !== this.options.packageName
67
115
  || foreground.activity !== expectedActivity) {
68
116
  throw Object.assign(new Error(`Expected foreground ${this.options.packageName}/${expectedActivity}, found ${foreground.packageName}/${foreground.activity}`), { code });
69
117
  }
118
+ this.foregroundProof = { ...foreground, epoch: this.deviceEpoch };
70
119
  return foreground;
71
120
  }
72
121
  async assertGeneratedForeground(expectedActivity, code = "ACTIVITY_BEFORE_MISMATCH", signal) {
@@ -74,62 +123,63 @@ export class StepRunner {
74
123
  }
75
124
  currentExpectedActivity = "";
76
125
  currentSignal;
77
- async observeExpectedActivity(expectedActivity, expectedPid, input) {
78
- const deadline = this.options.clock.now() + input.timeoutMs;
79
- const identity = () => ({
80
- packageName: this.options.packageName,
81
- deviceSerial: this.options.deviceSerial,
82
- ...(input.signal === undefined ? {} : { signal: input.signal }),
83
- timeoutMs: Math.max(1, deadline - this.options.clock.now())
126
+ async captureLayout(reason, timeoutMs, signal, freshness = "sameMutationEpoch") {
127
+ this.advanceDeviceEpoch();
128
+ const snapshot = await this.options.uiSnapshotProvider.capture({
129
+ reason,
130
+ freshness,
131
+ timeoutMs,
132
+ ...(signal === undefined ? {} : { signal })
84
133
  });
85
- const foreground = await this.options.adb.foregroundComponent(identity());
86
- if (foreground.packageName !== this.options.packageName
87
- || (expectedActivity !== undefined
88
- && foreground.activity !== expectedActivity)) {
89
- return {
90
- status: "failed",
91
- message: `Generated Expect foreground changed to ${foreground.packageName}/${foreground.activity}`
92
- };
93
- }
94
- const pid = await this.primaryPid(identity());
95
- if (pid !== expectedPid) {
96
- return {
97
- status: "failed",
98
- message: `Generated Expect process changed from ${String(expectedPid)} to ${String(pid)}`
99
- };
100
- }
101
- return { status: "observed", activity: foreground.activity };
134
+ this.currentViewport = snapshot.viewport;
135
+ return snapshot.roots;
102
136
  }
103
- async observeExpectedLayout(expectedActivity, expectedPid, input) {
104
- const deadline = this.options.clock.now() + input.timeoutMs;
105
- const observationInput = () => ({
106
- ...(input.signal === undefined ? {} : { signal: input.signal }),
107
- timeoutMs: Math.max(1, deadline - this.options.clock.now())
108
- });
109
- const before = await this.observeExpectedActivity(expectedActivity, expectedPid, observationInput());
110
- if (before.status === "failed")
111
- return before;
112
- const layout = await this.options.androidCli.layout({
113
- deviceSerial: this.options.deviceSerial,
114
- ...(input.signal === undefined ? {} : { signal: input.signal }),
115
- timeoutMs: observationInput().timeoutMs
116
- });
117
- const after = await this.observeExpectedActivity(expectedActivity, expectedPid, observationInput());
118
- return after.status === "failed"
119
- ? after
120
- : { status: "observed", layout };
137
+ /**
138
+ * Generated Replay observes Expect under the process identity it replays:
139
+ * every observation re-checks the foreground package and primary PID, and
140
+ * a Layout is re-checked after capture at the post-action Activity.
141
+ */
142
+ expectationProbe(expectedActivity, expectedPid) {
143
+ const guard = async (activity, input, deadline) => {
144
+ // Expect watches the app change on its own over time: never reuse an
145
+ // earlier observation.
146
+ this.advanceDeviceEpoch();
147
+ const identity = () => ({
148
+ packageName: this.options.packageName,
149
+ deviceSerial: this.options.deviceSerial,
150
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
151
+ timeoutMs: Math.max(1, deadline - this.options.clock.now())
152
+ });
153
+ const foreground = await this.options.adb.foregroundComponent(identity());
154
+ if (foreground.packageName !== this.options.packageName
155
+ || (activity !== undefined && foreground.activity !== activity)) {
156
+ throw new Error(`Generated Expect foreground changed to ${foreground.packageName}/${foreground.activity}`);
157
+ }
158
+ const pid = await this.primaryPid(identity());
159
+ if (pid !== expectedPid) {
160
+ throw new Error(`Generated Expect process changed from ${String(expectedPid)} to ${String(pid)}`);
161
+ }
162
+ return foreground.activity;
163
+ };
164
+ return {
165
+ activity: (input) => guard(undefined, input, this.options.clock.now() + input.timeoutMs),
166
+ layout: async (input) => {
167
+ const deadline = this.options.clock.now() + input.timeoutMs;
168
+ await guard(expectedActivity, input, deadline);
169
+ const layout = await this.captureLayout("expect", Math.max(1, deadline - this.options.clock.now()), input.signal);
170
+ await guard(expectedActivity, input, deadline);
171
+ return { layout };
172
+ }
173
+ };
121
174
  }
122
- async captureGeneratedLayout(signal) {
175
+ async captureGeneratedLayout(signal, freshness = "sameMutationEpoch") {
123
176
  await this.assertGeneratedForeground(this.currentExpectedActivity, "ACTIVITY_BEFORE_MISMATCH", signal);
124
- const layout = await this.options.androidCli.layout({
125
- deviceSerial: this.options.deviceSerial,
126
- ...(signal === undefined ? {} : { signal }),
127
- timeoutMs: this.options.idle.timeoutMs
128
- });
177
+ const layout = await this.captureLayout("locate", this.options.idle.timeoutMs, signal, freshness);
129
178
  await this.assertGeneratedForeground(this.currentExpectedActivity, "ACTIVITY_BEFORE_MISMATCH", signal);
130
179
  return layout;
131
180
  }
132
181
  async run(step, index, signal) {
182
+ const originalStep = step;
133
183
  const startedAt = this.options.clock.now();
134
184
  const logcatPath = stepPath(index, "logcat.txt");
135
185
  const activityReport = {
@@ -150,9 +200,31 @@ export class StepRunner {
150
200
  finishedAtMs: startedAt,
151
201
  durationMs: 0,
152
202
  activity: activityReport,
153
- logcatPath
203
+ logcatPath,
204
+ ...(this.options.deviceRole === undefined
205
+ ? {}
206
+ : { device: this.options.deviceRole }),
207
+ ...(step.replayMode !== undefined ? { replayMode: step.replayMode } : {})
154
208
  };
155
209
  const finish = async (status, failure) => {
210
+ if (report.locator !== undefined) {
211
+ const requested = originalStep.action === "bridge"
212
+ ? originalStep.triggerLocator
213
+ : "locator" in originalStep
214
+ ? originalStep.locator
215
+ : undefined;
216
+ if (requested !== undefined) {
217
+ report.locator.requested = requested;
218
+ }
219
+ if (report.locator.message !== undefined) {
220
+ report.locator.message = this.redact(report.locator.message);
221
+ }
222
+ }
223
+ if (report.expectation?.message !== undefined) {
224
+ report.expectation.message = this.redact(report.expectation.message);
225
+ }
226
+ if (failure !== undefined)
227
+ failure.message = this.redact(failure.message);
156
228
  const finishedAt = this.options.clock.now();
157
229
  report.finishedAtMs = finishedAt;
158
230
  report.durationMs = finishedAt - startedAt;
@@ -165,9 +237,13 @@ export class StepRunner {
165
237
  if (status === "failed" && failure !== undefined) {
166
238
  return { status, report, failure };
167
239
  }
168
- return status === "cancelled"
169
- ? { status, report }
170
- : { status: "passed", report };
240
+ if (status === "cancelled") {
241
+ return { status, report };
242
+ }
243
+ if (status === "manualRequired") {
244
+ return { status, report };
245
+ }
246
+ return { status: "passed", report };
171
247
  };
172
248
  const fail = async (code, message) => finish("failed", {
173
249
  code,
@@ -204,13 +280,27 @@ export class StepRunner {
204
280
  if (before !== step.activity.before) {
205
281
  return fail("ACTIVITY_BEFORE_MISMATCH", `Expected Activity ${step.activity.before}, found ${before}`);
206
282
  }
283
+ try {
284
+ step = this.resolveStepBindings(step);
285
+ }
286
+ catch {
287
+ return fail("EXPECT_LOGCAT_FAILED", "Replay binding is missing or invalid");
288
+ }
207
289
  const generatedPid = this.options.generatedReplayPolicy === true
208
290
  ? await this.primaryPid(identity)
209
291
  : undefined;
292
+ // The stable post-action Layout; generated Replay's first element Expect
293
+ // observation reuses it after the post-action identity checks.
294
+ let settledLayout;
210
295
  if (this.options.generatedReplayPolicy === true
211
296
  && generatedPid === null) {
212
297
  return fail("APP_CRASHED", "Generated replay process is not running");
213
298
  }
299
+ if (step.action === "wait" && step.markerId !== undefined) {
300
+ const startedAtMs = this.options.clock.now();
301
+ this.options.markers?.set(step.markerId, startedAtMs);
302
+ report.marker = { id: step.markerId, startedAtMs };
303
+ }
214
304
  let target;
215
305
  if (step.action === "scrollTo") {
216
306
  const scroll = await this.scrollToExecutor.execute(step, signal);
@@ -224,25 +314,25 @@ export class StepRunner {
224
314
  if (scroll.status === "failed") {
225
315
  if (scroll.idle !== undefined) {
226
316
  await this.options.artifacts.writeJson(stepPath(index, "layout-diff.json"), scroll.idle.lastDiff);
227
- report.idle = {
317
+ report.idle = idleStepReport({
318
+ ...scroll.idle,
228
319
  status: "timeout",
229
- polls: scroll.idle.polls,
230
- lastDiff: [...scroll.idle.lastDiff]
231
- };
320
+ code: "IDLE_TIMEOUT"
321
+ });
232
322
  }
233
323
  return fail(scroll.code, scroll.message);
234
324
  }
235
325
  }
236
- else {
326
+ else if (step.action === "bridge") {
327
+ if (this.options.manualReplay === false && step.replayMode !== "auto") {
328
+ report.locator = { status: "notRun", fallbackUsed: false };
329
+ return finish("manualRequired");
330
+ }
237
331
  let layout;
238
332
  try {
239
333
  layout = this.options.generatedReplayPolicy === true
240
334
  ? await this.captureGeneratedLayout(signal)
241
- : await this.options.androidCli.layout({
242
- deviceSerial: this.options.deviceSerial,
243
- ...(signal === undefined ? {} : { signal }),
244
- timeoutMs: this.options.idle.timeoutMs
245
- });
335
+ : await this.captureLayout("locate", this.options.idle.timeoutMs, signal);
246
336
  }
247
337
  catch (error) {
248
338
  if (error !== null
@@ -253,56 +343,427 @@ export class StepRunner {
253
343
  }
254
344
  throw error;
255
345
  }
256
- if (step.action === "click"
257
- || step.action === "longClick"
258
- || step.action === "swipe") {
259
- const resolution = resolveLocator(layout, step.locator);
346
+ const triggerResolution = resolveLocator(layout, step.triggerLocator, {
347
+ requiredCapability: "clickable",
348
+ viewport: this.currentViewport
349
+ });
350
+ if (triggerResolution.status !== "found") {
351
+ report.locator = {
352
+ status: "failed",
353
+ requested: step.triggerLocator,
354
+ fallbackUsed: false,
355
+ message: triggerResolution.message
356
+ };
357
+ return fail(triggerResolution.code, triggerResolution.message);
358
+ }
359
+ if (triggerResolution.element.clickable !== true) {
360
+ return fail("ACTION_FAILED", "bridge trigger target is not clickable");
361
+ }
362
+ const triggerTarget = {
363
+ point: triggerResolution.point,
364
+ ...(triggerResolution.element.bounds === undefined
365
+ ? {}
366
+ : { bounds: triggerResolution.element.bounds })
367
+ };
368
+ report.locator = {
369
+ status: "found",
370
+ requested: step.triggerLocator,
371
+ matchedBy: triggerResolution.matchedBy,
372
+ fallbackUsed: false
373
+ };
374
+ if (this.options.generatedReplayPolicy === true) {
375
+ try {
376
+ await this.assertGeneratedForeground(step.activity.before, "ACTIVITY_BEFORE_MISMATCH", signal);
377
+ }
378
+ catch (error) {
379
+ return fail("ACTIVITY_BEFORE_MISMATCH", errorMessage(error));
380
+ }
381
+ }
382
+ const recordedEscape = step.escapedPackageName;
383
+ const bridge = await new BridgeRunner({
384
+ adb: this.options.adb,
385
+ clock: this.options.clock,
386
+ actionExecutor: this.actionExecutor,
387
+ externalSteps: this.externalStepRunner(index),
388
+ idleWaiter: this.idleWaiter,
389
+ idle: this.options.idle,
390
+ packageName: this.options.packageName,
391
+ deviceSerial: this.options.deviceSerial
392
+ }).run({
393
+ trigger: {
394
+ step: {
395
+ action: "click",
396
+ locator: step.triggerLocator,
397
+ activity: {
398
+ before: step.activity.before,
399
+ after: step.activity.before
400
+ }
401
+ },
402
+ target: triggerTarget
403
+ },
404
+ escapeTimeoutMs: step.escapeTimeoutMs ?? 3000,
405
+ returnTimeoutMs: step.returnTimeoutMs,
406
+ // Replay must reach the same external app the Journey recorded.
407
+ acceptEscape: (escapedPackageName) => (recordedEscape === undefined || escapedPackageName === recordedEscape
408
+ ? undefined
409
+ : {
410
+ code: "EXTERNAL_PACKAGE_MISMATCH",
411
+ message: `Bridge escaped to "${escapedPackageName}", but the Journey recorded "${recordedEscape}"`
412
+ }),
413
+ external: {
414
+ kind: "steps",
415
+ steps: () => (Promise.resolve(step.externalSteps))
416
+ },
417
+ signal
418
+ });
419
+ if (bridge.status === "cancelled") {
420
+ return finish("cancelled");
421
+ }
422
+ if (bridge.status === "failed") {
423
+ if (bridge.idle !== undefined) {
424
+ report.idle = idleStepReport(bridge.idle);
425
+ await this.options.artifacts.writeJson(stepPath(index, "layout-diff.json"), bridge.idle.lastDiff);
426
+ }
427
+ return fail(bridge.code, bridge.message);
428
+ }
429
+ report.idle = idleStepReport(bridge.idle);
430
+ settledLayout = bridge.idle.layout;
431
+ }
432
+ else if (step.action === "wait"
433
+ && step.until !== undefined
434
+ && step.timeoutMs !== undefined) {
435
+ const deadline = startedAt + step.timeoutMs;
436
+ for (;;) {
437
+ if (isAborted(signal)) {
438
+ return finish("cancelled");
439
+ }
440
+ let layout;
441
+ try {
442
+ layout = this.options.generatedReplayPolicy === true
443
+ ? await this.captureGeneratedLayout(signal)
444
+ : await this.captureLayout("expect", Math.max(1, Math.min(this.options.idle.timeoutMs, deadline - this.options.clock.now())), signal);
445
+ }
446
+ catch (error) {
447
+ if (error !== null
448
+ && typeof error === "object"
449
+ && "code" in error
450
+ && error.code === "ACTIVITY_BEFORE_MISMATCH") {
451
+ return fail("ACTIVITY_BEFORE_MISMATCH", errorMessage(error));
452
+ }
453
+ throw error;
454
+ }
455
+ const resolution = resolveLocator(layout, step.until.element, { requireEnabled: false });
260
456
  if (resolution.status === "found") {
261
- target = {
262
- point: resolution.point,
263
- ...(resolution.element.bounds === undefined
264
- ? {}
265
- : { bounds: resolution.element.bounds })
266
- };
267
457
  report.locator = {
268
458
  status: "found",
459
+ requested: step.until.element,
269
460
  matchedBy: resolution.matchedBy,
270
461
  fallbackUsed: false
271
462
  };
463
+ break;
464
+ }
465
+ if (this.options.clock.now() >= deadline) {
466
+ report.locator = {
467
+ status: "failed",
468
+ requested: step.until.element,
469
+ fallbackUsed: false,
470
+ message: resolution.message
471
+ };
472
+ return fail("WAIT_TIMEOUT", `Wait condition did not match before timeout: ${resolution.message}`);
473
+ }
474
+ try {
475
+ await this.options.clock.sleep(WAIT_UNTIL_POLL_INTERVAL_MS, signal);
476
+ }
477
+ catch (error) {
478
+ if (isAborted(signal)) {
479
+ return finish("cancelled");
480
+ }
481
+ throw error;
272
482
  }
273
- else {
274
- const annotatedPath = stepPath(index, "fallback-annotated.png");
275
- const fallback = await this.fallbackResolver.resolve(step, this.options.artifacts.path(annotatedPath), signal);
276
- if (fallback.status !== "found") {
277
- const code = fallback.status === "failed"
278
- ? fallback.code
279
- : resolution.code;
280
- const message = fallback.status === "failed"
281
- ? fallback.message
282
- : resolution.message;
483
+ }
484
+ }
485
+ else {
486
+ let layout;
487
+ try {
488
+ layout = this.options.generatedReplayPolicy === true
489
+ ? await this.captureGeneratedLayout(signal)
490
+ : await this.captureLayout("locate", this.options.idle.timeoutMs, signal);
491
+ }
492
+ catch (error) {
493
+ if (error !== null
494
+ && typeof error === "object"
495
+ && "code" in error
496
+ && error.code === "ACTIVITY_BEFORE_MISMATCH") {
497
+ return fail("ACTIVITY_BEFORE_MISMATCH", errorMessage(error));
498
+ }
499
+ throw error;
500
+ }
501
+ if (step.action === "click"
502
+ || step.action === "longClick"
503
+ || step.action === "swipe") {
504
+ let anchorFallback;
505
+ if (step.anchor !== undefined) {
506
+ const anchorResolver = this.options.anchorResolver;
507
+ if (anchorResolver === undefined) {
283
508
  report.locator = {
284
509
  status: "failed",
285
- fallbackUsed: fallback.status === "failed"
286
- && fallback.label !== undefined
287
- && fallback.annotatedScreenshotPath !== undefined,
288
- ...(fallback.status === "failed" && fallback.label !== undefined
289
- ? { fallbackLabel: fallback.label }
290
- : {}),
291
- ...(fallback.status === "failed"
292
- && fallback.annotatedScreenshotPath !== undefined
293
- ? { annotatedScreenshotPath: annotatedPath }
294
- : {}),
295
- message
510
+ fallbackUsed: false,
511
+ message: `Step targets Knowledge anchor ${step.anchor} but no anchor resolver is configured`
512
+ };
513
+ return fail("ANCHOR_NOT_FOUND", `Step targets Knowledge anchor ${step.anchor} but no anchor resolver is configured`);
514
+ }
515
+ const anchorResolution = await anchorResolver.resolve({
516
+ anchorId: step.anchor,
517
+ layout,
518
+ ...(this.currentViewport === undefined
519
+ ? {}
520
+ : { viewport: this.currentViewport }),
521
+ ...(signal === undefined ? {} : { signal })
522
+ });
523
+ if (anchorResolution.status === "found") {
524
+ target = {
525
+ point: anchorResolution.point ?? {
526
+ x: 0,
527
+ y: 0
528
+ },
529
+ ...(anchorResolution.bounds === undefined
530
+ ? {}
531
+ : { bounds: anchorResolution.bounds })
532
+ };
533
+ report.locator = {
534
+ status: "found",
535
+ matchedBy: "anchor",
536
+ anchorId: step.anchor,
537
+ fallbackUsed: false,
538
+ anchor: {
539
+ status: "resolved",
540
+ ...(anchorResolution.resolvedBy === undefined
541
+ ? {}
542
+ : { resolvedBy: anchorResolution.resolvedBy })
543
+ }
544
+ };
545
+ }
546
+ else if (step.locator === undefined) {
547
+ report.locator = {
548
+ status: "failed",
549
+ anchorId: step.anchor,
550
+ fallbackUsed: false,
551
+ anchor: {
552
+ status: "failed",
553
+ ...(anchorResolution.message === undefined
554
+ ? {}
555
+ : { message: anchorResolution.message })
556
+ },
557
+ message: anchorResolution.message
558
+ };
559
+ return fail("ANCHOR_NOT_FOUND", anchorResolution.message
560
+ ?? `Knowledge anchor ${step.anchor} did not resolve`);
561
+ }
562
+ else {
563
+ anchorFallback = {
564
+ status: "locatorFallback",
565
+ ...(anchorResolution.message === undefined
566
+ ? {}
567
+ : { message: anchorResolution.message })
296
568
  };
297
- return fail(code, message);
298
569
  }
299
- target = targetForPoint(fallback.point);
570
+ }
571
+ if (target === undefined) {
572
+ if (step.locator === undefined) {
573
+ report.locator = {
574
+ status: "failed",
575
+ fallbackUsed: false,
576
+ message: "Step has no runtime locator fallback"
577
+ };
578
+ return fail("LOCATOR_NOT_FOUND", "Step has no runtime locator fallback");
579
+ }
580
+ const locator = step.locator;
581
+ const action = step.action;
582
+ // Generated Replay targets exactly as Generation did; a recorded
583
+ // Journey keeps the Recorder's element-center semantics.
584
+ const locate = () => {
585
+ if (this.options.generatedReplayPolicy !== true) {
586
+ return resolveLocator(layout, locator, {
587
+ viewport: this.currentViewport
588
+ });
589
+ }
590
+ const resolved = resolveActionTarget(layout, action, locator, this.currentViewport);
591
+ return resolved.status === "found" ? resolved.located : resolved;
592
+ };
593
+ let resolution = locate();
594
+ const locatorDeadline = this.options.clock.now()
595
+ + this.options.idle.timeoutMs;
596
+ while (resolution.status !== "found"
597
+ && resolution.code === "LOCATOR_NOT_FOUND"
598
+ && resolution.evidenceMismatch !== true
599
+ && this.options.clock.now() < locatorDeadline) {
600
+ try {
601
+ await this.options.clock.sleep(Math.min(WAIT_UNTIL_POLL_INTERVAL_MS, locatorDeadline - this.options.clock.now()), signal);
602
+ }
603
+ catch (error) {
604
+ if (isAborted(signal)) {
605
+ return finish("cancelled");
606
+ }
607
+ throw error;
608
+ }
609
+ if (isAborted(signal)) {
610
+ return finish("cancelled");
611
+ }
612
+ try {
613
+ layout = this.options.generatedReplayPolicy === true
614
+ ? await this.captureGeneratedLayout(signal, "forceFresh")
615
+ : await this.captureLayout("locate", Math.max(1, locatorDeadline - this.options.clock.now()), signal, "forceFresh");
616
+ }
617
+ catch (error) {
618
+ if (error !== null
619
+ && typeof error === "object"
620
+ && "code" in error
621
+ && error.code === "ACTIVITY_BEFORE_MISMATCH") {
622
+ return fail("ACTIVITY_BEFORE_MISMATCH", errorMessage(error));
623
+ }
624
+ throw error;
625
+ }
626
+ resolution = locate();
627
+ }
628
+ if (resolution.status === "found") {
629
+ target = {
630
+ point: resolution.point,
631
+ ...(resolution.element.bounds === undefined
632
+ ? {}
633
+ : { bounds: resolution.element.bounds })
634
+ };
635
+ report.locator = {
636
+ status: "found",
637
+ requested: step.locator,
638
+ matchedBy: resolution.matchedBy,
639
+ fallbackUsed: false,
640
+ ...(step.anchor === undefined ? {} : { anchorId: step.anchor }),
641
+ ...(anchorFallback === undefined
642
+ ? {}
643
+ : { anchor: { status: anchorFallback.status, ...(anchorFallback.message === undefined ? {} : { message: anchorFallback.message }) } })
644
+ };
645
+ }
646
+ else {
647
+ if (resolution.evidenceMismatch === true) {
648
+ report.locator = {
649
+ status: "failed",
650
+ requested: step.locator,
651
+ fallbackUsed: false,
652
+ message: resolution.message
653
+ };
654
+ return fail(resolution.code, resolution.message);
655
+ }
656
+ const annotatedPath = stepPath(index, "fallback-annotated.png");
657
+ const fallback = await this.fallbackResolver.resolve(step, this.options.artifacts.path(annotatedPath), signal);
658
+ if (fallback.status !== "found") {
659
+ const code = fallback.status === "failed"
660
+ ? fallback.code
661
+ : resolution.code;
662
+ const message = fallback.status === "failed"
663
+ ? fallback.message
664
+ : resolution.message;
665
+ report.locator = {
666
+ status: "failed",
667
+ requested: step.locator,
668
+ fallbackUsed: fallback.status === "failed"
669
+ && fallback.label !== undefined
670
+ && fallback.annotatedScreenshotPath !== undefined,
671
+ ...(fallback.status === "failed" && fallback.label !== undefined
672
+ ? { fallbackLabel: fallback.label }
673
+ : {}),
674
+ ...(fallback.status === "failed"
675
+ && fallback.annotatedScreenshotPath !== undefined
676
+ ? { annotatedScreenshotPath: annotatedPath }
677
+ : {}),
678
+ message
679
+ };
680
+ return fail(code, message);
681
+ }
682
+ target = targetForPoint(fallback.point);
683
+ report.locator = {
684
+ status: "found",
685
+ requested: step.locator,
686
+ fallbackUsed: true,
687
+ fallbackLabel: fallback.label,
688
+ annotatedScreenshotPath: annotatedPath
689
+ };
690
+ }
691
+ }
692
+ }
693
+ if (step.action === "inputText"
694
+ && step.anchor !== undefined) {
695
+ const anchorResolver = this.options.anchorResolver;
696
+ if (anchorResolver === undefined) {
300
697
  report.locator = {
301
- status: "found",
302
- fallbackUsed: true,
303
- fallbackLabel: fallback.label,
304
- annotatedScreenshotPath: annotatedPath
698
+ status: "failed",
699
+ fallbackUsed: false,
700
+ message: `Step targets Knowledge anchor ${step.anchor} but no anchor resolver is configured`
305
701
  };
702
+ return fail("ANCHOR_NOT_FOUND", `Step targets Knowledge anchor ${step.anchor} but no anchor resolver is configured`);
703
+ }
704
+ const anchorResolution = await anchorResolver.resolve({
705
+ anchorId: step.anchor,
706
+ layout,
707
+ ...(this.currentViewport === undefined
708
+ ? {}
709
+ : { viewport: this.currentViewport }),
710
+ ...(signal === undefined ? {} : { signal })
711
+ });
712
+ if (anchorResolution.status !== "found") {
713
+ report.locator = {
714
+ status: "failed",
715
+ anchorId: step.anchor,
716
+ fallbackUsed: false,
717
+ anchor: {
718
+ status: "failed",
719
+ ...(anchorResolution.message === undefined
720
+ ? {}
721
+ : { message: anchorResolution.message })
722
+ },
723
+ message: anchorResolution.message
724
+ };
725
+ return fail(anchorResolution.status === "ambiguous"
726
+ ? "ANCHOR_AMBIGUOUS"
727
+ : anchorResolution.status === "visualOnly"
728
+ ? "RUNTIME_CAPABILITY_MISSING"
729
+ : "ANCHOR_NOT_FOUND", anchorResolution.message
730
+ ?? `Knowledge anchor ${step.anchor} did not resolve`);
731
+ }
732
+ if (anchorResolution.point === undefined) {
733
+ report.locator = {
734
+ status: "failed",
735
+ anchorId: step.anchor,
736
+ fallbackUsed: false,
737
+ anchor: { status: "failed", message: "anchor element has no bounds" },
738
+ message: "anchor element has no bounds"
739
+ };
740
+ return fail("ANCHOR_NOT_FOUND", `Knowledge anchor ${step.anchor} resolved to an element without bounds`);
741
+ }
742
+ this.advanceDeviceEpoch();
743
+ const tapped = await this.options.adb.tap(anchorResolution.point, this.options.deviceSerial, signal);
744
+ if (tapped.exitCode !== 0) {
745
+ return fail("ACTION_FAILED", tapped.stderr.trim() || "Failed to focus the input anchor");
746
+ }
747
+ target = {
748
+ point: anchorResolution.point,
749
+ ...(anchorResolution.bounds === undefined
750
+ ? {}
751
+ : { bounds: anchorResolution.bounds })
752
+ };
753
+ report.locator = {
754
+ status: "found",
755
+ matchedBy: "anchor",
756
+ anchorId: step.anchor,
757
+ fallbackUsed: false,
758
+ anchor: {
759
+ status: "resolved",
760
+ ...(anchorResolution.resolvedBy === undefined
761
+ ? {}
762
+ : { resolvedBy: anchorResolution.resolvedBy })
763
+ }
764
+ };
765
+ if (this.options.requireFocusedInput === true) {
766
+ layout = await this.captureLayout("locate", this.options.idle.timeoutMs, signal);
306
767
  }
307
768
  }
308
769
  if (step.action === "inputText"
@@ -323,24 +784,18 @@ export class StepRunner {
323
784
  return fail(action.code, action.message);
324
785
  }
325
786
  const idle = await this.idleWaiter.waitUntilIdle(this.options.idle, signal);
326
- report.idle = idle.status === "timeout"
327
- ? {
328
- status: "timeout",
329
- polls: idle.polls,
330
- lastDiff: [...idle.lastDiff]
331
- }
332
- : {
333
- status: idle.status,
334
- polls: idle.polls
335
- };
787
+ report.idle = idleStepReport(idle);
336
788
  if (idle.status === "cancelled") {
337
789
  return finish("cancelled");
338
790
  }
339
791
  if (idle.status === "timeout") {
340
792
  await this.options.artifacts.writeJson(stepPath(index, "layout-diff.json"), idle.lastDiff);
341
- return fail(idle.code, "Layout did not become stable before timeout");
793
+ return fail(idle.code, withIdleAdvice("Layout did not become stable before timeout", idle));
342
794
  }
795
+ settledLayout = idle.layout;
343
796
  }
797
+ // Whatever the step did (including a plain wait) may have changed the app.
798
+ this.advanceDeviceEpoch();
344
799
  const pid = await this.primaryPid(identity);
345
800
  if (pid === null) {
346
801
  return fail("APP_CRASHED", "App process is no longer running");
@@ -374,16 +829,22 @@ export class StepRunner {
374
829
  return fail("ACTIVITY_AFTER_MISMATCH", `Expected Activity ${step.activity.after}, found ${after}`);
375
830
  }
376
831
  if (step.expect !== undefined) {
832
+ const observations = generatedPid === undefined || generatedPid === null
833
+ ? undefined
834
+ : new GuardedExpectationObservations(this.expectationProbe(step.activity.after, generatedPid), settledLayout === undefined ? undefined : { layout: settledLayout });
377
835
  const expectation = await this.expectationEvaluator.evaluate(step.expect, {
378
836
  packageName: this.options.packageName,
379
837
  deviceSerial: this.options.deviceSerial,
380
- stepStartedAt: startedAt
381
- }, signal, generatedPid === undefined || generatedPid === null
382
- ? {}
383
- : {
384
- activity: (input) => this.observeExpectedActivity(undefined, generatedPid, input),
385
- layout: (input) => this.observeExpectedLayout(step.activity.after, generatedPid, input)
386
- });
838
+ stepStartedAt: startedAt,
839
+ ...(this.options.runStartedAt === undefined
840
+ ? {}
841
+ : { runStartedAt: this.options.runStartedAt }),
842
+ ...(this.options.markers === undefined
843
+ ? {}
844
+ : { markers: this.options.markers })
845
+ }, signal, observations?.boundary() ?? {});
846
+ // Time passed while Expect waited; later checks must observe again.
847
+ this.advanceDeviceEpoch();
387
848
  if (expectation.status === "cancelled") {
388
849
  report.expectation = {
389
850
  type: expectation.type,
@@ -394,23 +855,27 @@ export class StepRunner {
394
855
  report.expectation = expectation.status === "passed"
395
856
  ? {
396
857
  type: expectation.type,
397
- status: "passed"
858
+ status: "passed",
859
+ ...(expectation.logcatEvent === undefined
860
+ ? {}
861
+ : { logcatEvent: expectation.logcatEvent })
398
862
  }
399
863
  : {
400
864
  type: expectation.type,
401
865
  status: "failed",
402
866
  code: expectation.code,
403
- message: expectation.message
867
+ message: expectation.message,
868
+ ...(expectation.logcatEvent === undefined
869
+ ? {}
870
+ : { logcatEvent: expectation.logcatEvent })
404
871
  };
405
872
  if (expectation.status === "failed") {
406
873
  return fail(expectation.code, expectation.message);
407
874
  }
408
- if (this.options.generatedReplayPolicy === true) {
409
- const expectedActivity = step.expect.type === "activity"
410
- ? step.expect.value
411
- : step.activity.after;
875
+ const settled = settledExpectationForeground(step.expect, step.activity.after);
876
+ if (this.options.generatedReplayPolicy === true && !settled.proven) {
412
877
  try {
413
- await this.assertGeneratedForeground(expectedActivity, "ACTIVITY_AFTER_MISMATCH", signal);
878
+ await this.assertGeneratedForeground(settled.activity, "ACTIVITY_AFTER_MISMATCH", signal);
414
879
  const guardedPid = await this.primaryPid(identity);
415
880
  if (guardedPid !== generatedPid) {
416
881
  return await fail("ACTIVITY_AFTER_MISMATCH", "Generated replay process changed after Expect");
@@ -420,9 +885,135 @@ export class StepRunner {
420
885
  return fail("ACTIVITY_AFTER_MISMATCH", errorMessage(error));
421
886
  }
422
887
  }
888
+ if (step.expect.type === "logcatEvent"
889
+ && step.expect.capture !== undefined) {
890
+ const line = this.expectationEvaluator.matchedEventFor(expectation);
891
+ const evidence = expectation.logcatEvent;
892
+ const value = line === undefined
893
+ ? undefined
894
+ : captureLogcatEvent(line, step.expect, step.expect.capture);
895
+ if (value === undefined || evidence?.matchedLineSha256 === undefined
896
+ || this.bindings.has(step.expect.capture.name)) {
897
+ return fail("EXPECT_LOGCAT_FAILED", "Matched event Capture is invalid or duplicated");
898
+ }
899
+ this.bindings.set(step.expect.capture.name, {
900
+ value,
901
+ valueType: step.expect.capture.valueType,
902
+ sourceStepIndex: index,
903
+ window: evidence.window,
904
+ startedAtMs: evidence.startedAtMs,
905
+ evidenceSha256: evidence.matchedLineSha256
906
+ });
907
+ report.expectation.capture = {
908
+ name: step.expect.capture.name,
909
+ valueType: step.expect.capture.valueType,
910
+ length: value.length,
911
+ sourceStepIndex: index,
912
+ window: evidence.window,
913
+ startedAtMs: evidence.startedAtMs,
914
+ evidenceSha256: evidence.matchedLineSha256
915
+ };
916
+ }
423
917
  }
424
918
  return finish("passed");
425
919
  }
920
+ redact(message) {
921
+ let sanitized = message;
922
+ for (const { value } of this.bindings.values()) {
923
+ sanitized = sanitized.replaceAll(value, "[bound value]");
924
+ }
925
+ return sanitized;
926
+ }
927
+ resolveStepBindings(step) {
928
+ const lookup = (value) => {
929
+ const name = bindingName(value);
930
+ if (name === undefined)
931
+ return undefined;
932
+ const binding = this.bindings.get(name);
933
+ if (binding === undefined)
934
+ throw new Error("Missing Replay binding");
935
+ return binding;
936
+ };
937
+ const resolve = (value) => {
938
+ const binding = lookup(value);
939
+ if (binding === undefined)
940
+ return value;
941
+ return binding.value;
942
+ };
943
+ let next = step;
944
+ if (step.action === "inputText") {
945
+ next = { ...step, text: resolve(step.text) };
946
+ }
947
+ else if ("locator" in step && step.locator?.text !== undefined) {
948
+ next = { ...step, locator: { ...step.locator, text: resolve(step.locator.text) } };
949
+ }
950
+ if (step.expect?.type === "logcatEvent"
951
+ && typeof step.expect.correlation?.value === "string") {
952
+ next = {
953
+ ...next,
954
+ expect: {
955
+ ...step.expect,
956
+ correlation: {
957
+ ...step.expect.correlation,
958
+ value: lookup(step.expect.correlation.value)?.valueType === "integer"
959
+ ? Number(resolve(step.expect.correlation.value))
960
+ : resolve(step.expect.correlation.value)
961
+ }
962
+ }
963
+ };
964
+ }
965
+ return next;
966
+ }
967
+ externalStepRunner(stepIndex) {
968
+ return new ExternalStepRunner({
969
+ adb: this.options.adb,
970
+ actionExecutor: this.actionExecutor,
971
+ uiSnapshotProvider: this.options.uiSnapshotProvider,
972
+ captureLayout: (reason, timeoutMs, signal) => this.captureLayout(reason, timeoutMs, signal),
973
+ createIdleWaiter: (packageName) => new IdleWaiter(this.options.uiStability, this.options.clock, this.options.deviceSerial, packageName, deviceIdentityResolver(this.options.adb, {
974
+ packageName,
975
+ deviceSerial: this.options.deviceSerial,
976
+ timeoutMs: this.options.idle.timeoutMs
977
+ })),
978
+ idle: this.options.idle,
979
+ viewport: () => this.currentViewport,
980
+ deviceSerial: this.options.deviceSerial,
981
+ onIdleTimeout: async (externalIndex, idle) => {
982
+ await this.options.artifacts.writeJson(stepPath(stepIndex, `external-${String(externalIndex + 1).padStart(3, "0")}-layout-diff.json`), idle.lastDiff);
983
+ }
984
+ });
985
+ }
986
+ }
987
+ function idleStepReport(idle) {
988
+ if (idle.status === "cancelled") {
989
+ return {
990
+ status: "cancelled",
991
+ polls: idle.polls,
992
+ durationMs: idle.durationMs
993
+ };
994
+ }
995
+ return idle.status === "timeout"
996
+ ? {
997
+ status: "timeout",
998
+ polls: idle.polls,
999
+ durationMs: idle.durationMs,
1000
+ samplingDurationMs: idle.samplingDurationMs,
1001
+ strategy: idle.strategy,
1002
+ ...(idle.backend === undefined ? {} : { backendId: idle.backend }),
1003
+ fallbackUsed: idle.fallbackUsed,
1004
+ frameActivityDetected: idle.frameActivityDetected,
1005
+ lastDiff: [...idle.lastDiff]
1006
+ }
1007
+ : {
1008
+ status: "stable",
1009
+ polls: idle.polls,
1010
+ durationMs: idle.durationMs,
1011
+ samplingDurationMs: idle.samplingDurationMs,
1012
+ ...(idle.backend === undefined ? {} : { backendId: idle.backend }),
1013
+ strategy: idle.strategy,
1014
+ fallbackUsed: idle.fallbackUsed,
1015
+ frameActivityDetected: idle.frameActivityDetected
1016
+ };
426
1017
  }
427
1018
  function errorMessage(error) {
428
1019
  return error instanceof Error ? error.message : String(error);