taphound 0.2.0-dev.1 → 0.2.0-dev.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) 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 +500 -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 +561 -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 +349 -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 +34 -0
  66. package/dist/adapters/appium/appium-ui-snapshot-provider.js +240 -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/external-flow-registry.d.ts +17 -0
  73. package/dist/adapters/filesystem/external-flow-registry.js +325 -0
  74. package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
  75. package/dist/adapters/filesystem/generation-session-store.js +281 -855
  76. package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
  77. package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
  78. package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
  79. package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
  80. package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
  81. package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
  82. package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
  83. package/dist/adapters/filesystem/journey-composition-store.js +151 -0
  84. package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
  85. package/dist/adapters/filesystem/knowledge-registry.js +223 -0
  86. package/dist/adapters/filesystem/project-bound-file.js +3 -5
  87. package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
  88. package/dist/adapters/filesystem/project-file-inspector.js +57 -28
  89. package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
  90. package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
  91. package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
  92. package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
  93. package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
  94. package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
  95. package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
  96. package/dist/adapters/filesystem/skill-installer.js +40 -11
  97. package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
  98. package/dist/adapters/filesystem/workspace-layout.js +57 -0
  99. package/dist/adapters/git/node-git-diff.d.ts +17 -0
  100. package/dist/adapters/git/node-git-diff.js +114 -0
  101. package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
  102. package/dist/adapters/process/node-detached-process-launcher.js +96 -0
  103. package/dist/adapters/process/node-process-runner.js +11 -2
  104. package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
  105. package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
  106. package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
  107. package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
  108. package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
  109. package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
  110. package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
  111. package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
  112. package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
  113. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
  114. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
  115. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
  116. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
  117. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
  118. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
  119. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
  120. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
  121. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
  122. package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
  123. package/dist/adapters/runtime/session-adb-view.js +193 -0
  124. package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
  125. package/dist/adapters/runtime/session-backed-ports.js +43 -0
  126. package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
  127. package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
  128. package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
  129. package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
  130. package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
  131. package/dist/adapters/ui/device-layout-temp.js +18 -0
  132. package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
  133. package/dist/adapters/ui/device-ui-environment.js +81 -0
  134. package/dist/adapters/ui/layout-normalization.d.ts +9 -0
  135. package/dist/adapters/ui/layout-normalization.js +44 -0
  136. package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
  137. package/dist/adapters/ui/ui-snapshot-error.js +12 -0
  138. package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
  139. package/dist/adapters/ui/ui-snapshot-support.js +12 -0
  140. package/dist/application/align/align-service.d.ts +42 -0
  141. package/dist/application/align/align-service.js +106 -0
  142. package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
  143. package/dist/application/assertion/expectation-evaluator.js +156 -18
  144. package/dist/application/assertion/guarded-expectation.d.ts +45 -0
  145. package/dist/application/assertion/guarded-expectation.js +74 -0
  146. package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
  147. package/dist/application/checkpoint/baseline-capturer.js +163 -0
  148. package/dist/application/checkpoint/baseline-error.d.ts +5 -0
  149. package/dist/application/checkpoint/baseline-error.js +8 -0
  150. package/dist/application/checkpoint/baseline-service.d.ts +31 -0
  151. package/dist/application/checkpoint/baseline-service.js +147 -0
  152. package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
  153. package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
  154. package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
  155. package/dist/application/checkpoint/regression-comparator.js +192 -0
  156. package/dist/application/collector/logcat-collector.d.ts +22 -1
  157. package/dist/application/collector/logcat-collector.js +103 -15
  158. package/dist/application/collector/logcat-event.d.ts +8 -0
  159. package/dist/application/collector/logcat-event.js +79 -0
  160. package/dist/application/collector/logcat-stop.d.ts +2 -0
  161. package/dist/application/collector/logcat-stop.js +11 -0
  162. package/dist/application/context/context-generator.d.ts +45 -0
  163. package/dist/application/context/context-generator.js +228 -0
  164. package/dist/application/context/context-loader.d.ts +40 -0
  165. package/dist/application/context/context-loader.js +223 -0
  166. package/dist/application/context/context-refresher.d.ts +59 -0
  167. package/dist/application/context/context-refresher.js +349 -0
  168. package/dist/application/context/context-rehasher.d.ts +36 -0
  169. package/dist/application/context/context-rehasher.js +110 -0
  170. package/dist/application/context/context-validator.d.ts +23 -1
  171. package/dist/application/context/context-validator.js +116 -9
  172. package/dist/application/context/evidence-hash.d.ts +1 -0
  173. package/dist/application/context/evidence-hash.js +125 -0
  174. package/dist/application/context/evidence-match.d.ts +9 -0
  175. package/dist/application/context/evidence-match.js +8 -0
  176. package/dist/application/context/shard-identity.d.ts +2 -0
  177. package/dist/application/context/shard-identity.js +7 -0
  178. package/dist/application/contract/contract-loader.d.ts +25 -0
  179. package/dist/application/contract/contract-loader.js +68 -0
  180. package/dist/application/contract/contract-review.d.ts +25 -0
  181. package/dist/application/contract/contract-review.js +51 -0
  182. package/dist/application/contract/contract-verifier.d.ts +53 -0
  183. package/dist/application/contract/contract-verifier.js +645 -0
  184. package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
  185. package/dist/application/devices/resolve-device-assignments.js +60 -0
  186. package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
  187. package/dist/application/diagnosis/failure-classifier.js +179 -0
  188. package/dist/application/doctor/doctor-service.d.ts +36 -3
  189. package/dist/application/doctor/doctor-service.js +255 -25
  190. package/dist/application/generation/generation-app-preparer.d.ts +20 -0
  191. package/dist/application/generation/generation-app-preparer.js +28 -0
  192. package/dist/application/generation/generation-config-service.d.ts +21 -0
  193. package/dist/application/generation/generation-config-service.js +53 -0
  194. package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
  195. package/dist/application/generation/generation-confirmation-service.js +109 -24
  196. package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
  197. package/dist/application/generation/generation-context-snapshot.js +29 -0
  198. package/dist/application/generation/generation-finalizer.d.ts +15 -9
  199. package/dist/application/generation/generation-finalizer.js +167 -70
  200. package/dist/application/generation/generation-publisher.js +12 -8
  201. package/dist/application/generation/generation-recovery-service.d.ts +35 -0
  202. package/dist/application/generation/generation-recovery-service.js +144 -0
  203. package/dist/application/generation/generation-reopen-service.d.ts +15 -0
  204. package/dist/application/generation/generation-reopen-service.js +37 -0
  205. package/dist/application/generation/generation-replace-service.d.ts +32 -0
  206. package/dist/application/generation/generation-replace-service.js +116 -0
  207. package/dist/application/generation/generation-starter.d.ts +44 -4
  208. package/dist/application/generation/generation-starter.js +175 -8
  209. package/dist/application/generation/generation-step-executor.d.ts +67 -5
  210. package/dist/application/generation/generation-step-executor.js +471 -108
  211. package/dist/application/generation/proposed-step-validator.js +96 -30
  212. package/dist/application/generation/replay-policy-loader.d.ts +16 -0
  213. package/dist/application/generation/replay-policy-loader.js +29 -0
  214. package/dist/application/generation/risk-evaluator.d.ts +4 -1
  215. package/dist/application/generation/risk-evaluator.js +118 -3
  216. package/dist/application/generation/runtime-observer.d.ts +39 -7
  217. package/dist/application/generation/runtime-observer.js +193 -24
  218. package/dist/application/impact/impact-resolver.d.ts +32 -0
  219. package/dist/application/impact/impact-resolver.js +160 -0
  220. package/dist/application/init/init-service.js +26 -13
  221. package/dist/application/interaction/action-executor.d.ts +3 -2
  222. package/dist/application/interaction/action-executor.js +11 -1
  223. package/dist/application/interaction/action-target.d.ts +17 -0
  224. package/dist/application/interaction/action-target.js +40 -0
  225. package/dist/application/interaction/external-step-runner.d.ts +153 -0
  226. package/dist/application/interaction/external-step-runner.js +332 -0
  227. package/dist/application/interaction/fallback-resolver.d.ts +6 -3
  228. package/dist/application/interaction/fallback-resolver.js +7 -5
  229. package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
  230. package/dist/application/interaction/scroll-to-executor.js +118 -36
  231. package/dist/application/journey/external-flow-resolver.d.ts +25 -0
  232. package/dist/application/journey/external-flow-resolver.js +22 -0
  233. package/dist/application/journey/journey-check-service.d.ts +54 -0
  234. package/dist/application/journey/journey-check-service.js +207 -0
  235. package/dist/application/journey/journey-promoter.d.ts +26 -0
  236. package/dist/application/journey/journey-promoter.js +123 -0
  237. package/dist/application/journey/journey-resolver.d.ts +42 -0
  238. package/dist/application/journey/journey-resolver.js +239 -0
  239. package/dist/application/journey/journey-retirer.d.ts +25 -0
  240. package/dist/application/journey/journey-retirer.js +87 -0
  241. package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
  242. package/dist/application/knowledge/anchor-resolver.js +110 -0
  243. package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
  244. package/dist/application/knowledge/knowledge-loader.js +36 -0
  245. package/dist/application/locator/layout-failure-summary.d.ts +2 -0
  246. package/dist/application/locator/layout-failure-summary.js +67 -0
  247. package/dist/application/locator/layout-traversal.d.ts +11 -0
  248. package/dist/application/locator/layout-traversal.js +18 -0
  249. package/dist/application/locator/locator-resolver.d.ts +13 -1
  250. package/dist/application/locator/locator-resolver.js +182 -18
  251. package/dist/application/observe/observe-service.d.ts +20 -0
  252. package/dist/application/observe/observe-service.js +91 -0
  253. package/dist/application/project/project-describer.d.ts +14 -6
  254. package/dist/application/project/project-describer.js +38 -5
  255. package/dist/application/recognition/screen-detector.d.ts +28 -0
  256. package/dist/application/recognition/screen-detector.js +111 -0
  257. package/dist/application/recorder/locator-selector.js +151 -17
  258. package/dist/application/recorder/recorder-service.d.ts +16 -4
  259. package/dist/application/recorder/recorder-service.js +420 -128
  260. package/dist/application/report/report-writer.js +1 -1
  261. package/dist/application/runtime/cold-launch.d.ts +28 -0
  262. package/dist/application/runtime/cold-launch.js +59 -0
  263. package/dist/application/runtime/step-runner.d.ts +49 -4
  264. package/dist/application/runtime/step-runner.js +721 -130
  265. package/dist/application/runtime/verify-runtime.d.ts +57 -7
  266. package/dist/application/runtime/verify-runtime.js +554 -174
  267. package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
  268. package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
  269. package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
  270. package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
  271. package/dist/application/ui/ui-stability-probe.d.ts +3 -0
  272. package/dist/application/ui/ui-stability-probe.js +8 -0
  273. package/dist/application/wait/idle-advice.d.ts +6 -0
  274. package/dist/application/wait/idle-advice.js +27 -0
  275. package/dist/application/wait/idle-profiles.d.ts +15 -0
  276. package/dist/application/wait/idle-profiles.js +75 -0
  277. package/dist/application/wait/idle-waiter.d.ts +28 -4
  278. package/dist/application/wait/idle-waiter.js +186 -15
  279. package/dist/cli/commands/align.d.ts +3 -0
  280. package/dist/cli/commands/align.js +124 -0
  281. package/dist/cli/commands/baseline.d.ts +3 -0
  282. package/dist/cli/commands/baseline.js +130 -0
  283. package/dist/cli/commands/context.js +300 -27
  284. package/dist/cli/commands/contract.d.ts +3 -0
  285. package/dist/cli/commands/contract.js +120 -0
  286. package/dist/cli/commands/doctor.js +16 -9
  287. package/dist/cli/commands/failure.d.ts +3 -0
  288. package/dist/cli/commands/failure.js +63 -0
  289. package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
  290. package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
  291. package/dist/cli/commands/generation/session-commands.d.ts +11 -0
  292. package/dist/cli/commands/generation/session-commands.js +472 -0
  293. package/dist/cli/commands/generation/shared.d.ts +1414 -0
  294. package/dist/cli/commands/generation/shared.js +291 -0
  295. package/dist/cli/commands/generation/step-commands.d.ts +8 -0
  296. package/dist/cli/commands/generation/step-commands.js +315 -0
  297. package/dist/cli/commands/generation.js +10 -507
  298. package/dist/cli/commands/impact.d.ts +3 -0
  299. package/dist/cli/commands/impact.js +87 -0
  300. package/dist/cli/commands/init.js +13 -6
  301. package/dist/cli/commands/journey.d.ts +3 -0
  302. package/dist/cli/commands/journey.js +382 -0
  303. package/dist/cli/commands/knowledge.d.ts +3 -0
  304. package/dist/cli/commands/knowledge.js +93 -0
  305. package/dist/cli/commands/observe.d.ts +3 -0
  306. package/dist/cli/commands/observe.js +127 -0
  307. package/dist/cli/commands/project.js +2 -1
  308. package/dist/cli/commands/record.js +18 -5
  309. package/dist/cli/commands/verify.js +273 -63
  310. package/dist/cli/dependencies.d.ts +125 -1
  311. package/dist/cli/dependencies.js +450 -51
  312. package/dist/cli/diff-verification.d.ts +41 -0
  313. package/dist/cli/diff-verification.js +192 -0
  314. package/dist/cli/main.js +49 -1
  315. package/dist/cli/output.d.ts +5 -3
  316. package/dist/cli/output.js +12 -0
  317. package/dist/cli/program.js +19 -1
  318. package/dist/cli/project-root.d.ts +1 -0
  319. package/dist/cli/project-root.js +6 -0
  320. package/dist/cli/runtime-selection.d.ts +22 -0
  321. package/dist/cli/runtime-selection.js +92 -0
  322. package/dist/cli/version.d.ts +1 -0
  323. package/dist/cli/version.js +14 -0
  324. package/dist/cli/workspace-guard.d.ts +2 -0
  325. package/dist/cli/workspace-guard.js +3 -0
  326. package/dist/domain/binding-reference.d.ts +4 -0
  327. package/dist/domain/binding-reference.js +23 -0
  328. package/dist/domain/checkpoint.d.ts +404 -0
  329. package/dist/domain/checkpoint.js +297 -0
  330. package/dist/domain/config.d.ts +107 -1
  331. package/dist/domain/config.js +52 -6
  332. package/dist/domain/contract.d.ts +413 -0
  333. package/dist/domain/contract.js +196 -0
  334. package/dist/domain/external-flow.d.ts +493 -0
  335. package/dist/domain/external-flow.js +15 -0
  336. package/dist/domain/failure-classification.d.ts +176 -0
  337. package/dist/domain/failure-classification.js +169 -0
  338. package/dist/domain/failure.d.ts +2 -1
  339. package/dist/domain/failure.js +123 -0
  340. package/dist/domain/generation.d.ts +1671 -132
  341. package/dist/domain/generation.js +202 -13
  342. package/dist/domain/geometry.d.ts +13 -0
  343. package/dist/domain/geometry.js +16 -0
  344. package/dist/domain/impact.d.ts +71 -0
  345. package/dist/domain/impact.js +76 -0
  346. package/dist/domain/init.d.ts +12 -4
  347. package/dist/domain/init.js +24 -18
  348. package/dist/domain/journey-composition.d.ts +2285 -0
  349. package/dist/domain/journey-composition.js +85 -0
  350. package/dist/domain/journey-lifecycle.d.ts +10 -0
  351. package/dist/domain/journey-lifecycle.js +9 -0
  352. package/dist/domain/journey.d.ts +2419 -212
  353. package/dist/domain/journey.js +384 -14
  354. package/dist/domain/knowledge.d.ts +165 -0
  355. package/dist/domain/knowledge.js +188 -0
  356. package/dist/domain/layout.d.ts +34 -5
  357. package/dist/domain/layout.js +64 -6
  358. package/dist/domain/locator-evidence.d.ts +3 -0
  359. package/dist/domain/locator-evidence.js +36 -0
  360. package/dist/domain/logcat-event.d.ts +54 -0
  361. package/dist/domain/logcat-event.js +40 -0
  362. package/dist/domain/observation.d.ts +33 -0
  363. package/dist/domain/observation.js +18 -0
  364. package/dist/domain/project-context.d.ts +219 -1
  365. package/dist/domain/project-context.js +209 -6
  366. package/dist/domain/proposed-step.d.ts +340 -60
  367. package/dist/domain/proposed-step.js +23 -3
  368. package/dist/domain/report.d.ts +1455 -28
  369. package/dist/domain/report.js +211 -14
  370. package/dist/domain/runtime-snapshot.d.ts +58 -1
  371. package/dist/domain/runtime-snapshot.js +14 -3
  372. package/dist/domain/runtime.d.ts +50 -0
  373. package/dist/domain/runtime.js +36 -0
  374. package/dist/domain/system-app-profiles.d.ts +6 -0
  375. package/dist/domain/system-app-profiles.js +43 -0
  376. package/dist/domain/ui-backend.d.ts +28 -0
  377. package/dist/domain/ui-backend.js +33 -0
  378. package/dist/domain/ui-cache.d.ts +11 -0
  379. package/dist/domain/ui-cache.js +10 -0
  380. package/dist/domain/window-hierarchy.d.ts +84 -0
  381. package/dist/domain/window-hierarchy.js +172 -0
  382. package/dist/domain/workflow-manifest.d.ts +77 -0
  383. package/dist/domain/workflow-manifest.js +136 -0
  384. package/dist/domain/workspace.d.ts +28 -0
  385. package/dist/domain/workspace.js +72 -0
  386. package/dist/ports/adb.d.ts +34 -1
  387. package/dist/ports/align-prompt.d.ts +11 -0
  388. package/dist/ports/align-prompt.js +6 -0
  389. package/dist/ports/anchor-resolver.d.ts +30 -0
  390. package/dist/ports/anchor-resolver.js +1 -0
  391. package/dist/ports/annotated-screen-resolver.d.ts +4 -0
  392. package/dist/ports/annotated-screen-resolver.js +1 -0
  393. package/dist/ports/camera-probe.d.ts +23 -0
  394. package/dist/ports/camera-probe.js +8 -0
  395. package/dist/ports/context-document-writer.d.ts +24 -0
  396. package/dist/ports/context-document-writer.js +1 -0
  397. package/dist/ports/detached-process-launcher.d.ts +12 -0
  398. package/dist/ports/detached-process-launcher.js +1 -0
  399. package/dist/ports/external-flow-registry.d.ts +37 -0
  400. package/dist/ports/external-flow-registry.js +1 -0
  401. package/dist/ports/generation-prompt.d.ts +2 -1
  402. package/dist/ports/generation-session-store.d.ts +12 -2
  403. package/dist/ports/git-diff.d.ts +9 -0
  404. package/dist/ports/git-diff.js +1 -0
  405. package/dist/ports/journey-composition-store.d.ts +17 -0
  406. package/dist/ports/journey-composition-store.js +1 -0
  407. package/dist/ports/knowledge-registry.d.ts +20 -0
  408. package/dist/ports/knowledge-registry.js +1 -0
  409. package/dist/ports/process-runner.d.ts +7 -0
  410. package/dist/ports/project-file-inspector.d.ts +1 -0
  411. package/dist/ports/project-identity-inspector.d.ts +24 -0
  412. package/dist/ports/project-identity-inspector.js +1 -0
  413. package/dist/ports/project-inventory-inspector.d.ts +21 -0
  414. package/dist/ports/project-inventory-inspector.js +1 -0
  415. package/dist/ports/project-module-discoverer.d.ts +26 -0
  416. package/dist/ports/project-module-discoverer.js +1 -0
  417. package/dist/ports/recorder-prompt.d.ts +10 -1
  418. package/dist/ports/runtime-backend.d.ts +89 -0
  419. package/dist/ports/runtime-backend.js +13 -0
  420. package/dist/ports/runtime-capability.d.ts +12 -0
  421. package/dist/ports/runtime-capability.js +20 -0
  422. package/dist/ports/runtime-session-ports.d.ts +25 -0
  423. package/dist/ports/runtime-session-ports.js +1 -0
  424. package/dist/ports/screenshot.d.ts +11 -0
  425. package/dist/ports/screenshot.js +1 -0
  426. package/dist/ports/skill-installer.d.ts +2 -2
  427. package/dist/ports/ui-snapshot.d.ts +35 -0
  428. package/dist/ports/ui-snapshot.js +1 -0
  429. package/dist/ports/ui-stability.d.ts +19 -0
  430. package/dist/ports/ui-stability.js +1 -0
  431. package/dist/ports/workspace-layout.d.ts +4 -0
  432. package/dist/ports/workspace-layout.js +1 -0
  433. package/dist/shared/errors.d.ts +2 -0
  434. package/dist/shared/errors.js +22 -0
  435. package/dist/shared/paths.d.ts +2 -0
  436. package/dist/shared/paths.js +24 -0
  437. package/dist/shared/strings.d.ts +2 -0
  438. package/dist/shared/strings.js +16 -0
  439. package/package.json +6 -1
  440. package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
  441. package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
  442. package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
  443. package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
  444. package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
  445. package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
  446. package/dist/ports/android-cli.d.ts +0 -21
  447. /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
@@ -1,22 +1,23 @@
1
1
  import { parse } from "node:path";
2
2
  import { normalizeActivity } from "../../domain/activity.js";
3
3
  import { primaryAppPid } from "../../domain/app-process.js";
4
- import { JourneySchema, JourneyStepSchema } from "../../domain/journey.js";
4
+ import { DEFAULT_DEVICE_ROLE, ExternalStepSchema, JourneySchema, JourneyStepSchema } from "../../domain/journey.js";
5
5
  import { ActionExecutor } from "../interaction/action-executor.js";
6
+ import { BridgeRunner, ExternalStepRunner } from "../interaction/external-step-runner.js";
6
7
  import { resolveLocator } from "../locator/locator-resolver.js";
7
- import { launchFailure } from "../runtime/launch-failure.js";
8
- import { ProcessWaiter } from "../runtime/process-waiter.js";
8
+ import { coldLaunchApp } from "../runtime/cold-launch.js";
9
9
  import { IdleWaiter } from "../wait/idle-waiter.js";
10
+ import { deviceIdentityResolver } from "../wait/idle-profiles.js";
10
11
  import { listLocatableTargets, listRecorderTargets } from "./locator-selector.js";
12
+ import { closeUiSnapshotProvider } from "../ui/ui-snapshot-lifecycle.js";
13
+ /** How long the Recorder waits for a bridge trigger to leave the app. */
14
+ const BRIDGE_ESCAPE_TIMEOUT_MS = 3000;
11
15
  function failedCommand(result) {
12
16
  return result.exitCode !== 0
13
17
  || result.timedOut
14
18
  || result.cancelled
15
19
  || result.spawnError !== undefined;
16
20
  }
17
- function commandMessage(result, fallback) {
18
- return result.stderr.trim() || result.spawnError || fallback;
19
- }
20
21
  function annotatedPath(outputPath) {
21
22
  const output = parse(outputPath);
22
23
  return `${output.dir}/${output.name}.annotated.png`;
@@ -53,139 +54,163 @@ export class RecorderService {
53
54
  ...(input.signal === undefined ? {} : { signal: input.signal }),
54
55
  timeoutMs: input.config.idle.timeoutMs
55
56
  };
56
- if (!await this.dependencies.adb.isInstalled(identity)) {
57
- return {
58
- status: "failed",
59
- stepsRecorded: 0,
60
- message: `Package ${input.config.run.packageName} is not installed on ${input.deviceSerial}`
61
- };
62
- }
63
- const stopped = await this.dependencies.adb.forceStop(identity);
64
- if (failedCommand(stopped)) {
65
- return {
66
- status: "failed",
67
- stepsRecorded: 0,
68
- message: commandMessage(stopped, "App reset failed")
69
- };
70
- }
71
- const launchError = launchFailure(await this.dependencies.adb.launchActivity({
72
- packageName: input.config.run.packageName,
73
- activity: launchActivity,
74
- deviceSerial: input.deviceSerial,
75
- ...(input.signal === undefined ? {} : { signal: input.signal }),
76
- timeoutMs: input.config.idle.timeoutMs
77
- }));
78
- if (launchError !== undefined) {
79
- return {
80
- status: "failed",
81
- stepsRecorded: 0,
82
- message: launchError
83
- };
84
- }
85
- const processReadiness = await new ProcessWaiter(this.dependencies.adb, this.dependencies.clock).wait({
86
- packageName: input.config.run.packageName,
57
+ const session = await this.dependencies.sessions.openSession({
87
58
  deviceSerial: input.deviceSerial,
88
- pollIntervalMs: input.config.idle.pollIntervalMs,
89
- timeoutMs: input.config.idle.timeoutMs,
90
59
  ...(input.signal === undefined ? {} : { signal: input.signal })
91
60
  });
92
- if (processReadiness.status === "cancelled") {
93
- return { status: "cancelled", stepsRecorded: 0 };
94
- }
95
- if (processReadiness.status === "timeout") {
96
- return {
97
- status: "failed",
98
- stepsRecorded: 0,
99
- message: "App process was not found after launch"
100
- };
101
- }
102
- const idleWaiter = new IdleWaiter(this.dependencies.androidCli, this.dependencies.clock, input.deviceSerial);
103
- await this.dependencies.androidCli.layout({
104
- deviceSerial: input.deviceSerial,
105
- ...(input.signal === undefined ? {} : { signal: input.signal }),
106
- timeoutMs: input.config.idle.timeoutMs
107
- });
108
- const startupIdle = await idleWaiter.waitUntilIdle(input.config.idle, input.signal);
109
- if (startupIdle.status === "cancelled") {
110
- return { status: "cancelled", stepsRecorded: 0 };
111
- }
112
- if (startupIdle.status === "timeout") {
113
- return {
114
- status: "failed",
115
- stepsRecorded: 0,
116
- message: "Layout did not become stable before timeout"
117
- };
118
- }
119
- const steps = [];
120
- const executor = new ActionExecutor(this.dependencies.adb, input.deviceSerial);
121
- for (;;) {
122
- const layout = await this.dependencies.androidCli.layout({
61
+ try {
62
+ const views = this.dependencies.sessionPorts(session);
63
+ if (!await views.adb.isInstalled(identity)) {
64
+ return {
65
+ status: "failed",
66
+ stepsRecorded: 0,
67
+ message: `Package ${input.config.run.packageName} is not installed on ${input.deviceSerial}`
68
+ };
69
+ }
70
+ const launched = await coldLaunchApp(views.adb, this.dependencies.clock, {
71
+ packageName: input.config.run.packageName,
72
+ activity: launchActivity,
123
73
  deviceSerial: input.deviceSerial,
124
- ...(input.signal === undefined ? {} : { signal: input.signal }),
125
- timeoutMs: input.config.idle.timeoutMs
74
+ pollIntervalMs: input.config.idle.pollIntervalMs,
75
+ timeoutMs: input.config.idle.timeoutMs,
76
+ signal: input.signal
126
77
  });
127
- const action = await this.dependencies.prompt.selectAction();
128
- if (action === "cancel") {
129
- return { status: "cancelled", stepsRecorded: steps.length };
78
+ if (launched.status === "cancelled") {
79
+ return { status: "cancelled", stepsRecorded: 0 };
80
+ }
81
+ if (launched.status === "failed") {
82
+ return {
83
+ status: "failed",
84
+ stepsRecorded: 0,
85
+ message: launched.message
86
+ };
130
87
  }
131
- if (action === "finish") {
132
- if (steps.length === 0) {
88
+ const uiSnapshotProvider = await session.openUiSnapshots({
89
+ timeoutMs: input.config.ui?.snapshotTimeoutMs
90
+ ?? input.config.idle.timeoutMs,
91
+ backend: input.config.ui?.backend ?? "auto",
92
+ cacheEnabled: input.config.ui?.cacheEnabled ?? true,
93
+ ...(input.signal === undefined ? {} : { signal: input.signal })
94
+ });
95
+ try {
96
+ const idleWaiter = new IdleWaiter(views.uiStability, this.dependencies.clock, input.deviceSerial, input.config.run.packageName, deviceIdentityResolver(views.adb, {
97
+ packageName: input.config.run.packageName,
98
+ deviceSerial: input.deviceSerial,
99
+ timeoutMs: input.config.idle.timeoutMs
100
+ }));
101
+ await uiSnapshotProvider.capture({
102
+ reason: "locate",
103
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
104
+ timeoutMs: input.config.idle.timeoutMs
105
+ });
106
+ const startupIdle = await idleWaiter.waitUntilIdle(input.config.idle, input.signal);
107
+ if (startupIdle.status === "cancelled") {
108
+ return { status: "cancelled", stepsRecorded: 0 };
109
+ }
110
+ if (startupIdle.status === "timeout") {
133
111
  return {
134
112
  status: "failed",
135
113
  stepsRecorded: 0,
136
- message: "Journey requires at least one successful step"
114
+ message: "Layout did not become stable before timeout"
137
115
  };
138
116
  }
139
- const journey = JourneySchema.parse({
140
- version: 1,
141
- name: input.journeyName,
142
- steps
143
- });
144
- await this.dependencies.journeyWriter.write(input.outputPath, journey);
145
- return { status: "completed", stepsRecorded: steps.length, journey };
146
- }
147
- const prepared = await this.prepareAction(action, layout, input);
148
- if (prepared === undefined) {
149
- continue;
150
- }
151
- // scrollTo captures `before` post-scroll by design: it does not navigate, so before==after in practice (replay checks `before` pre-scroll but the Activity is unchanged).
152
- const before = normalizeActivity(input.config.run.packageName, await this.dependencies.adb.currentActivity(identity));
153
- if (prepared.draft.action !== "scrollTo") {
154
- const execution = await executor.execute(prepared.draft, actionTarget(prepared.target), input.signal);
155
- if (execution.status === "failed") {
156
- await this.dependencies.prompt.notifyFailure(execution.message);
157
- continue;
158
- }
159
- const idle = await idleWaiter.waitUntilIdle(input.config.idle, input.signal);
160
- if (idle.status !== "stable") {
161
- if (idle.status === "cancelled") {
117
+ const steps = [];
118
+ const executor = new ActionExecutor(views.adb, input.deviceSerial, () => uiSnapshotProvider.invalidate?.("beforeAction"));
119
+ for (;;) {
120
+ const layout = (await uiSnapshotProvider.capture({
121
+ reason: "locate",
122
+ freshness: "sameMutationEpoch",
123
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
124
+ timeoutMs: input.config.idle.timeoutMs
125
+ })).roots;
126
+ const action = await this.dependencies.prompt.selectAction();
127
+ if (action === "cancel") {
162
128
  return { status: "cancelled", stepsRecorded: steps.length };
163
129
  }
164
- return {
165
- status: "failed",
166
- stepsRecorded: steps.length,
167
- message: "Layout did not become stable before timeout"
168
- };
130
+ if (action === "finish") {
131
+ if (steps.length === 0) {
132
+ return {
133
+ status: "failed",
134
+ stepsRecorded: 0,
135
+ message: "Journey requires at least one successful step"
136
+ };
137
+ }
138
+ const journey = JourneySchema.parse({
139
+ version: 2,
140
+ name: input.journeyName,
141
+ devices: [{ role: DEFAULT_DEVICE_ROLE }],
142
+ steps
143
+ });
144
+ await this.dependencies.journeyWriter.write(input.outputPath, journey);
145
+ return { status: "completed", stepsRecorded: steps.length, journey };
146
+ }
147
+ if (action === "bridgeTrigger") {
148
+ const result = await this.recordBridgeStep(layout, input, identity, uiSnapshotProvider, views);
149
+ if (result.status === "cancelled") {
150
+ return { status: "cancelled", stepsRecorded: steps.length };
151
+ }
152
+ if (result.status === "failed") {
153
+ return {
154
+ status: "failed",
155
+ stepsRecorded: steps.length,
156
+ message: result.message
157
+ };
158
+ }
159
+ if (result.status === "recorded") {
160
+ steps.push(result.step);
161
+ }
162
+ continue;
163
+ }
164
+ const prepared = await this.prepareAction(action, layout, input, uiSnapshotProvider, views);
165
+ if (prepared === undefined) {
166
+ continue;
167
+ }
168
+ // scrollTo captures `before` post-scroll by design: it does not navigate, so before==after in practice (replay checks `before` pre-scroll but the Activity is unchanged).
169
+ const before = normalizeActivity(input.config.run.packageName, await views.adb.currentActivity(identity));
170
+ if (prepared.draft.action !== "scrollTo") {
171
+ const execution = await executor.execute(prepared.draft, actionTarget(prepared.target), input.signal);
172
+ if (execution.status === "failed") {
173
+ await this.dependencies.prompt.notifyFailure(execution.message);
174
+ continue;
175
+ }
176
+ const idle = await idleWaiter.waitUntilIdle(input.config.idle, input.signal);
177
+ if (idle.status !== "stable") {
178
+ if (idle.status === "cancelled") {
179
+ return { status: "cancelled", stepsRecorded: steps.length };
180
+ }
181
+ return {
182
+ status: "failed",
183
+ stepsRecorded: steps.length,
184
+ message: "Layout did not become stable before timeout"
185
+ };
186
+ }
187
+ }
188
+ const processes = await views.adb.appProcesses(identity);
189
+ if (primaryAppPid(processes, input.config.run.packageName) === null) {
190
+ return {
191
+ status: "failed",
192
+ stepsRecorded: steps.length,
193
+ message: "App process crashed after the recorded Action"
194
+ };
195
+ }
196
+ const after = normalizeActivity(input.config.run.packageName, await views.adb.currentActivity(identity));
197
+ steps.push(JourneyStepSchema.parse({
198
+ ...prepared.draft,
199
+ activity: { before, after }
200
+ }));
169
201
  }
170
202
  }
171
- const processes = await this.dependencies.adb.appProcesses(identity);
172
- if (primaryAppPid(processes, input.config.run.packageName) === null) {
173
- return {
174
- status: "failed",
175
- stepsRecorded: steps.length,
176
- message: "App process crashed after the recorded Action"
177
- };
203
+ finally {
204
+ await closeUiSnapshotProvider(uiSnapshotProvider);
178
205
  }
179
- const after = normalizeActivity(input.config.run.packageName, await this.dependencies.adb.currentActivity(identity));
180
- steps.push(JourneyStepSchema.parse({
181
- ...prepared.draft,
182
- activity: { before, after }
183
- }));
206
+ }
207
+ finally {
208
+ await session.close();
184
209
  }
185
210
  }
186
- async prepareAction(action, layout, input) {
211
+ async prepareAction(action, layout, input, uiSnapshotProvider, views) {
187
212
  if (action === "scrollTo") {
188
- return this.prepareScrollTo(layout, input);
213
+ return this.prepareScrollTo(layout, input, uiSnapshotProvider, views);
189
214
  }
190
215
  if (action === "inputText") {
191
216
  return {
@@ -223,7 +248,7 @@ export class RecorderService {
223
248
  }
224
249
  let fallback;
225
250
  const screenshotPath = annotatedPath(input.outputPath);
226
- const capture = await this.dependencies.androidCli.captureScreen({
251
+ const capture = await views.screenshots.capture({
227
252
  outputPath: screenshotPath,
228
253
  annotate: true,
229
254
  deviceSerial: input.deviceSerial,
@@ -256,8 +281,8 @@ export class RecorderService {
256
281
  }
257
282
  };
258
283
  }
259
- async prepareScrollTo(layout, input) {
260
- const containers = listRecorderTargets(layout, "swipe");
284
+ async prepareScrollTo(layout, input, uiSnapshotProvider, views, packageName = input.config.run.packageName, resourceIdOnly = false) {
285
+ const containers = listRecorderTargets(layout, "swipe").filter((target) => !resourceIdOnly || target.locator.resourceId !== undefined);
261
286
  if (containers.length === 0) {
262
287
  await this.dependencies.prompt.notifyFailure("No scrollable element has a unique deterministic Locator");
263
288
  return undefined;
@@ -270,12 +295,16 @@ export class RecorderService {
270
295
  }
271
296
  const direction = await this.dependencies.prompt.selectSwipeDirection();
272
297
  const options = await this.dependencies.prompt.swipeOptions();
273
- const executor = new ActionExecutor(this.dependencies.adb, input.deviceSerial);
274
- const idleWaiter = new IdleWaiter(this.dependencies.androidCli, this.dependencies.clock, input.deviceSerial);
298
+ const executor = new ActionExecutor(views.adb, input.deviceSerial, () => uiSnapshotProvider.invalidate?.("beforeAction"));
299
+ const idleWaiter = new IdleWaiter(views.uiStability, this.dependencies.clock, input.deviceSerial, packageName, deviceIdentityResolver(views.adb, {
300
+ packageName,
301
+ deviceSerial: input.deviceSerial,
302
+ timeoutMs: input.config.idle.timeoutMs
303
+ }));
275
304
  let currentLayout = layout;
276
305
  let swipesUsed = 0;
277
306
  for (;;) {
278
- const targets = listLocatableTargets(currentLayout);
307
+ const targets = listLocatableTargets(currentLayout).filter((target) => !resourceIdOnly || target.locator.resourceId !== undefined);
279
308
  const decision = await this.dependencies.prompt.scrollTargetDecision(targets.map((t) => ({ id: t.element.id, label: t.label })));
280
309
  if (decision.kind === "cancel") {
281
310
  return undefined;
@@ -321,11 +350,274 @@ export class RecorderService {
321
350
  return undefined;
322
351
  }
323
352
  swipesUsed += 1;
324
- currentLayout = await this.dependencies.androidCli.layout({
325
- deviceSerial: input.deviceSerial,
353
+ currentLayout = (await uiSnapshotProvider.capture({
354
+ reason: "locate",
355
+ freshness: "sameMutationEpoch",
326
356
  ...(input.signal === undefined ? {} : { signal: input.signal }),
327
357
  timeoutMs: input.config.idle.timeoutMs
358
+ })).roots;
359
+ }
360
+ }
361
+ async recordBridgeStep(layout, input, identity, uiSnapshotProvider, views) {
362
+ try {
363
+ const scenario = await this.dependencies.prompt.selectBridgeScenario();
364
+ const description = await this.dependencies.prompt.inputBridgeDescription(scenario);
365
+ const returnTimeoutMs = await this.dependencies.prompt.inputBridgeReturnTimeoutMs();
366
+ const targets = listRecorderTargets(layout, "click");
367
+ if (targets.length === 0) {
368
+ await this.dependencies.prompt.notifyFailure("No clickable element has a unique deterministic Locator for the bridge trigger");
369
+ return { status: "skipped" };
370
+ }
371
+ const selectedId = await this.dependencies.prompt.selectTarget(targets.map((target) => ({
372
+ id: target.element.id,
373
+ label: target.label
374
+ })));
375
+ const triggerTarget = targets.find((candidate) => candidate.element.id === selectedId);
376
+ if (triggerTarget === undefined) {
377
+ await this.dependencies.prompt.notifyFailure("Selected trigger element is unavailable");
378
+ return { status: "skipped" };
379
+ }
380
+ const trigger = actionTarget(triggerTarget);
381
+ if (trigger === undefined) {
382
+ await this.dependencies.prompt.notifyFailure("Selected trigger element has no executable geometry");
383
+ return { status: "skipped" };
384
+ }
385
+ const before = normalizeActivity(input.config.run.packageName, await views.adb.currentActivity(identity));
386
+ const executor = new ActionExecutor(views.adb, input.deviceSerial, () => uiSnapshotProvider.invalidate?.("beforeAction"));
387
+ const externalSteps = this.externalStepRunner(input, uiSnapshotProvider, views, executor);
388
+ // Set once the trigger escaped: a failure before that left the app
389
+ // where it was, so the person can simply choose again.
390
+ const progress = { escaped: false };
391
+ const bridge = await new BridgeRunner({
392
+ adb: views.adb,
393
+ clock: this.dependencies.clock,
394
+ actionExecutor: executor,
395
+ externalSteps,
396
+ idleWaiter: this.idleWaiter(input, views, input.config.run.packageName),
397
+ idle: input.config.idle,
398
+ packageName: input.config.run.packageName,
399
+ deviceSerial: input.deviceSerial
400
+ }).run({
401
+ trigger: {
402
+ step: {
403
+ action: "click",
404
+ locator: triggerTarget.locator,
405
+ activity: { before, after: before }
406
+ },
407
+ target: trigger
408
+ },
409
+ escapeTimeoutMs: BRIDGE_ESCAPE_TIMEOUT_MS,
410
+ returnTimeoutMs,
411
+ // The Recorder records whichever app the trigger opened.
412
+ acceptEscape: () => {
413
+ progress.escaped = true;
414
+ return undefined;
415
+ },
416
+ external: {
417
+ kind: "drive",
418
+ drive: async (escapedPackageName) => {
419
+ await this.dependencies.prompt.notifyExternalEscape(escapedPackageName);
420
+ return this.recordExternalSteps(input, escapedPackageName, uiSnapshotProvider, views, externalSteps);
421
+ }
422
+ },
423
+ signal: input.signal
328
424
  });
425
+ if (bridge.status === "cancelled") {
426
+ return { status: "cancelled" };
427
+ }
428
+ if (bridge.status === "failed") {
429
+ if (progress.escaped) {
430
+ return { status: "failed", message: bridge.message };
431
+ }
432
+ if (bridge.code === "BRIDGE_NO_ESCAPE") {
433
+ await this.dependencies.prompt.notifyBridgeNoEscape();
434
+ }
435
+ else {
436
+ await this.dependencies.prompt.notifyFailure(bridge.message);
437
+ }
438
+ return { status: "skipped" };
439
+ }
440
+ await this.dependencies.prompt.notifyExternalReturn();
441
+ const processes = await views.adb.appProcesses(identity);
442
+ if (primaryAppPid(processes, input.config.run.packageName) === null) {
443
+ return {
444
+ status: "failed",
445
+ message: "App process crashed after the recorded Action"
446
+ };
447
+ }
448
+ const after = normalizeActivity(input.config.run.packageName, await views.adb.currentActivity(identity));
449
+ const bridgeStep = JourneyStepSchema.parse({
450
+ action: "bridge",
451
+ scenario,
452
+ description,
453
+ triggerLocator: triggerTarget.locator,
454
+ escapedPackageName: bridge.escapedPackageName,
455
+ returnTimeoutMs,
456
+ externalSteps: bridge.externalSteps ?? [],
457
+ replayMode: "auto",
458
+ activity: { before, after }
459
+ });
460
+ return { status: "recorded", step: bridgeStep };
461
+ }
462
+ catch (error) {
463
+ if (input.signal?.aborted === true) {
464
+ return { status: "cancelled" };
465
+ }
466
+ throw error;
467
+ }
468
+ }
469
+ /**
470
+ * Records External Steps one chosen step at a time. Each step (except
471
+ * `scrollTo`, which the person already scrolled while choosing it) runs
472
+ * through the same ExternalStepRunner Replay uses, so only a step that
473
+ * replays is recorded. Leaving the escaped app ends the list.
474
+ */
475
+ async recordExternalSteps(input, escapedPackageName, uiSnapshotProvider, views, runner) {
476
+ const externalSteps = [];
477
+ const externalIdentity = {
478
+ packageName: escapedPackageName,
479
+ deviceSerial: input.deviceSerial,
480
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
481
+ timeoutMs: input.config.idle.timeoutMs
482
+ };
483
+ for (;;) {
484
+ const action = await this.dependencies.prompt.selectExternalStepAction();
485
+ if (action === "finishExternal") {
486
+ break;
487
+ }
488
+ const foreground = await views.adb.foregroundComponent(externalIdentity);
489
+ if (foreground.packageName !== escapedPackageName) {
490
+ break;
491
+ }
492
+ const layout = (await uiSnapshotProvider.capture({
493
+ reason: "locate",
494
+ freshness: "sameMutationEpoch",
495
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
496
+ timeoutMs: input.config.idle.timeoutMs
497
+ })).roots;
498
+ const prepared = await this.prepareExternalStep(action, layout, input, escapedPackageName, uiSnapshotProvider, views);
499
+ if (prepared === undefined) {
500
+ continue;
501
+ }
502
+ const step = this.buildExternalStep(prepared.draft, foreground.activity);
503
+ if (step.action === "scrollTo") {
504
+ externalSteps.push(step);
505
+ continue;
506
+ }
507
+ const outcome = await runner.run([step], escapedPackageName, input.signal);
508
+ if (outcome.status === "cancelled") {
509
+ return { status: "cancelled" };
510
+ }
511
+ if (outcome.status === "failed") {
512
+ // The action ran but the app never settled: the device is no longer
513
+ // in a state the recorded steps describe.
514
+ if (outcome.code === "IDLE_TIMEOUT") {
515
+ return {
516
+ status: "failed",
517
+ code: outcome.code,
518
+ message: outcome.message
519
+ };
520
+ }
521
+ await this.dependencies.prompt.notifyFailure(outcome.message);
522
+ continue;
523
+ }
524
+ externalSteps.push(step);
525
+ const after = await views.adb.foregroundComponent(externalIdentity);
526
+ if (after.packageName !== escapedPackageName) {
527
+ break;
528
+ }
529
+ }
530
+ return { status: "driven", steps: externalSteps };
531
+ }
532
+ externalStepRunner(input, uiSnapshotProvider, views, actionExecutor) {
533
+ let viewport;
534
+ return new ExternalStepRunner({
535
+ adb: views.adb,
536
+ actionExecutor,
537
+ uiSnapshotProvider,
538
+ captureLayout: async (reason, timeoutMs, signal) => {
539
+ const snapshot = await uiSnapshotProvider.capture({
540
+ reason,
541
+ freshness: "sameMutationEpoch",
542
+ timeoutMs,
543
+ ...(signal === undefined ? {} : { signal })
544
+ });
545
+ viewport = snapshot.viewport;
546
+ return snapshot.roots;
547
+ },
548
+ createIdleWaiter: (packageName) => (this.idleWaiter(input, views, packageName)),
549
+ idle: input.config.idle,
550
+ viewport: () => viewport,
551
+ deviceSerial: input.deviceSerial
552
+ });
553
+ }
554
+ idleWaiter(input, views, packageName) {
555
+ return new IdleWaiter(views.uiStability, this.dependencies.clock, input.deviceSerial, packageName, deviceIdentityResolver(views.adb, {
556
+ packageName,
557
+ deviceSerial: input.deviceSerial,
558
+ timeoutMs: input.config.idle.timeoutMs
559
+ }));
560
+ }
561
+ async prepareExternalStep(action, layout, input, packageName, uiSnapshotProvider, views) {
562
+ if (action === "scrollTo") {
563
+ return this.prepareScrollTo(layout, input, uiSnapshotProvider, views, packageName, true);
564
+ }
565
+ if (action === "inputText") {
566
+ return {
567
+ draft: { action, text: await this.dependencies.prompt.inputText() }
568
+ };
329
569
  }
570
+ if (action === "back" || action === "wait") {
571
+ return { draft: { action } };
572
+ }
573
+ const targets = listRecorderTargets(layout, action).filter((target) => target.locator.resourceId !== undefined);
574
+ if (targets.length === 0) {
575
+ await this.dependencies.prompt.notifyFailure("No enabled element has a unique resourceId Locator in the external app");
576
+ return undefined;
577
+ }
578
+ const selectedId = await this.dependencies.prompt.selectTarget(targets.map((target) => ({
579
+ id: target.element.id,
580
+ label: target.label
581
+ })));
582
+ const target = targets.find((candidate) => candidate.element.id === selectedId);
583
+ if (target === undefined) {
584
+ await this.dependencies.prompt.notifyFailure("Selected external element is unavailable");
585
+ return undefined;
586
+ }
587
+ if (action === "swipe") {
588
+ const options = await this.dependencies.prompt.swipeOptions();
589
+ return {
590
+ target,
591
+ draft: {
592
+ action,
593
+ locator: target.locator,
594
+ direction: await this.dependencies.prompt.selectSwipeDirection(),
595
+ ...options
596
+ }
597
+ };
598
+ }
599
+ if (action === "click") {
600
+ return {
601
+ target,
602
+ draft: {
603
+ action,
604
+ locator: target.locator
605
+ }
606
+ };
607
+ }
608
+ return {
609
+ target,
610
+ draft: {
611
+ action,
612
+ locator: target.locator,
613
+ durationMs: await this.dependencies.prompt.longClickDuration()
614
+ }
615
+ };
616
+ }
617
+ buildExternalStep(draft, expectedActivity) {
618
+ return ExternalStepSchema.parse({
619
+ ...draft,
620
+ expectedActivity
621
+ });
330
622
  }
331
623
  }
@@ -5,7 +5,7 @@ function renderSummary(report) {
5
5
  `TapHound run ${report.runId}: ${report.status.toUpperCase()}`,
6
6
  `Journey: ${report.journey.name}`,
7
7
  `Package: ${report.project.packageName}`,
8
- `Device: ${report.environment.deviceSerial}`,
8
+ ...report.environment.devices.map((device) => `Device[${device.role}]: ${device.deviceSerial}`),
9
9
  "",
10
10
  "Layers:",
11
11
  ...Object.entries(report.layers).map(([layer, status]) => `- ${layer}: ${status}`)
@@ -0,0 +1,28 @@
1
+ import type { AdbPort } from "../../ports/adb.js";
2
+ import type { Clock } from "../../ports/clock.js";
3
+ export interface ColdLaunchInput {
4
+ packageName: string;
5
+ /** Fully qualified launch Activity. */
6
+ activity: string;
7
+ deviceSerial: string;
8
+ pollIntervalMs: number;
9
+ timeoutMs: number;
10
+ signal?: AbortSignal | undefined;
11
+ }
12
+ export type ColdLaunchOutcome = {
13
+ status: "ready";
14
+ pids: readonly number[];
15
+ /** Time spent waiting for the process after the launch returned. */
16
+ processWaitMs: number;
17
+ } | {
18
+ status: "failed";
19
+ stage: "reset" | "launch" | "process";
20
+ message: string;
21
+ } | {
22
+ status: "cancelled";
23
+ };
24
+ /**
25
+ * The one cold launch Replay, the Recorder, and Generation share: force-stop
26
+ * the app, start its launch Activity, and wait until its process exists.
27
+ */
28
+ export declare function coldLaunchApp(adb: AdbPort, clock: Clock, input: ColdLaunchInput): Promise<ColdLaunchOutcome>;