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,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
- || now.getTime() >= new Date(challenge.expiresAt).getTime()) {
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 = resolveLocator(layout, step.locator);
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
- if (step.action === "click" && resolution.element.clickable !== true) {
150
- fail("ACTION_FAILED", "click target is not clickable");
151
- }
152
- if (step.action === "longClick"
153
- && resolution.element.longClickable !== true) {
154
- fail("ACTION_FAILED", "longClick target is not longClickable");
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 (step.action === "swipe"
157
- && (resolution.element.scrollable !== true
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.action, session.target.interactionPolicy);
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
- fresh = await this.dependencies.freshnessGuard.assertFresh(proposal.binding, input.signal, approved);
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.dependencies.adb, this.dependencies.clock);
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 preAction = await this.observeLive(session, authoritativePid, proposal.activity.before, input.signal, fresh);
303
- logcat.scopeToPids(preAction.pids);
304
- throwIfCancelled(input.signal);
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.dependencies.adb, session.target.deviceSerial);
307
- const idleWaiter = new IdleWaiter(this.dependencies.androidCli, this.dependencies.clock, session.target.deviceSerial);
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
- androidCli: this.dependencies.androidCli,
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 after = await this.observeLive(session, authoritativePid, undefined, input.signal);
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
- finalStep = executableStep(proposal, session.variables, after.activity);
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 expectation = await new ExpectationEvaluator(this.dependencies.adb, this.dependencies.androidCli, logcat, this.dependencies.clock).evaluate(finalStep.expect, {
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.observeLive(session, authoritativePid, finalStep.expect.type === "activity"
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.exitCode !== 0
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.lines().map((line) => line.raw).join("\n")
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
- return outcome;
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
- async observeLive(session, expectedPid, expectedActivity, signal, authoritativeSnapshot, timeoutMs = this.dependencies.idle.timeoutMs) {
562
- const deadline = this.dependencies.clock.now() + timeoutMs;
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.dependencies.adb.foregroundComponent(identity());
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.dependencies.adb.appProcesses(identity());
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 this.dependencies.androidCli.layout({
585
- deviceSerial: session.target.deviceSerial,
586
- ...(signal === undefined ? {} : { signal }),
587
- timeoutMs: Math.max(1, deadline - this.dependencies.clock.now())
588
- });
589
- throwIfCancelled(signal);
590
- const confirmedForeground = await this.dependencies.adb
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
- if (confirmedForeground.packageName !== session.target.packageName) {
594
- fail("PACKAGE_ESCAPE", "Foreground package escaped generation target");
595
- }
596
- const confirmedPid = primaryAppPid(await this.dependencies.adb.appProcesses(identity()), session.target.packageName);
597
- throwIfCancelled(signal);
598
- if (confirmedPid === null || confirmedPid !== expectedPid) {
599
- fail("APP_CRASHED", "Generation process identity changed");
600
- }
601
- if (confirmedForeground.activity
602
- !== (expectedActivity ?? foreground.activity)) {
603
- fail("SNAPSHOT_STALE", "Generation Activity changed unexpectedly");
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() + timeoutMs;
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.dependencies.adb.foregroundComponent(identity());
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.dependencies.adb.appProcesses(identity()), session.target.packageName);
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
  }