@reticlehq/server 3.2.0 → 3.4.0

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 (437) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +23 -22
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +41 -0
  7. package/dist/command/cli/adhoc-suite.js +159 -0
  8. package/dist/command/cli/adhoc-verdict.d.ts +58 -2
  9. package/dist/command/cli/adhoc-verdict.js +108 -13
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +30 -8
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  20. package/dist/command/cli/cli-parse-grammar.js +7 -0
  21. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  22. package/dist/command/cli/cli-parse-verify.js +86 -13
  23. package/dist/command/cli/cli-parse.d.ts +14 -1
  24. package/dist/command/cli/cli-parse.js +54 -8
  25. package/dist/command/cli/cli-update-commands.js +8 -1
  26. package/dist/command/cli/cli-verify.d.ts +80 -0
  27. package/dist/command/cli/cli-verify.js +181 -26
  28. package/dist/command/cli/cloud-cli.js +5 -2
  29. package/dist/command/cli/cloud-kit.d.ts +0 -2
  30. package/dist/command/cli/cloud-kit.js +3 -5
  31. package/dist/command/cli/cloud-login.js +21 -3
  32. package/dist/command/cli/demo/demo-page.js +6 -3
  33. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  34. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  35. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  36. package/dist/command/cli/launch/cli-launch.d.ts +9 -29
  37. package/dist/command/cli/launch/cli-launch.js +27 -49
  38. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  39. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  40. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  41. package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
  42. package/dist/command/cli/ports/resolve/cli-port.js +169 -15
  43. package/dist/command/cli/report-command.d.ts +16 -0
  44. package/dist/command/cli/report-command.js +97 -0
  45. package/dist/command/cli/setup-mcp-cli.js +13 -13
  46. package/dist/command/cli/tutorial.d.ts +2 -2
  47. package/dist/command/cli/tutorial.js +21 -7
  48. package/dist/command/cli.js +67 -10
  49. package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
  50. package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
  51. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  52. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  53. package/dist/command/daemon/daemon-resolve.js +1 -1
  54. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  55. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  56. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  57. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  58. package/dist/command/dev/stale-issue-guard.js +8 -8
  59. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  60. package/dist/command/setup/bringup/connect-lease.js +38 -0
  61. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  62. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  63. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  64. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  65. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  66. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  67. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  68. package/dist/command/setup/bringup/static-page-server.js +151 -0
  69. package/dist/command/setup/desktop-shape.d.ts +4 -0
  70. package/dist/command/setup/desktop-shape.js +18 -2
  71. package/dist/command/setup/init/init-host.js +8 -0
  72. package/dist/command/setup/init/init-port.d.ts +15 -0
  73. package/dist/command/setup/init/init-port.js +44 -0
  74. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  75. package/dist/command/setup/init/init-runtime.js +39 -2
  76. package/dist/command/setup/node-effects.d.ts +27 -14
  77. package/dist/command/setup/node-effects.js +80 -44
  78. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  79. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  80. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  81. package/dist/command/setup/probe/page-probe.js +18 -3
  82. package/dist/command/setup/run-setup.d.ts +26 -1
  83. package/dist/command/setup/run-setup.js +96 -13
  84. package/dist/command/setup/session-pick.d.ts +13 -0
  85. package/dist/command/setup/session-pick.js +20 -0
  86. package/dist/command/setup/setup-command.d.ts +2 -14
  87. package/dist/command/setup/setup-command.js +105 -31
  88. package/dist/command/setup/setup-mcp.js +3 -3
  89. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  90. package/dist/command/setup/terminal/wait-progress.js +17 -0
  91. package/dist/features/crawl/crawl-empty.js +1 -1
  92. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  93. package/dist/features/crawl/crawl-tools.js +57 -0
  94. package/dist/features/crawl/crawl.d.ts +2 -0
  95. package/dist/features/crawl/crawl.js +171 -156
  96. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  97. package/dist/features/exhaust/app-coverage.js +31 -0
  98. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  99. package/dist/features/exhaust/code-coverage.js +77 -0
  100. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  101. package/dist/features/exhaust/exhaust-run.js +82 -0
  102. package/dist/features/exhaust/explorer.d.ts +103 -0
  103. package/dist/features/exhaust/explorer.js +195 -0
  104. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  105. package/dist/features/exhaust/failure-branches.js +92 -0
  106. package/dist/features/exhaust/ledger.d.ts +202 -0
  107. package/dist/features/exhaust/ledger.js +252 -0
  108. package/dist/features/exhaust/session-fold.d.ts +31 -0
  109. package/dist/features/exhaust/session-fold.js +79 -0
  110. package/dist/features/exhaust/session-port.d.ts +36 -0
  111. package/dist/features/exhaust/session-port.js +102 -0
  112. package/dist/features/harness/drive-report.d.ts +0 -9
  113. package/dist/features/harness/drive-report.js +18 -0
  114. package/dist/features/harness/driver.js +4 -7
  115. package/dist/features/harness/fill-values.d.ts +6 -0
  116. package/dist/features/harness/fill-values.js +3 -0
  117. package/dist/features/harness/jev-driver.js +36 -22
  118. package/dist/features/harness/openai-driver.js +4 -7
  119. package/dist/features/harness/platform-config.d.ts +10 -0
  120. package/dist/features/harness/platform-config.js +13 -6
  121. package/dist/index.d.ts +10 -1
  122. package/dist/index.js +78 -104
  123. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  124. package/dist/judgement/capsule/capsule-store.js +58 -6
  125. package/dist/judgement/capsule/capsule.js +5 -0
  126. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  127. package/dist/judgement/capsule/causal-summary.js +7 -0
  128. package/dist/judgement/domain/domain-model.js +40 -9
  129. package/dist/judgement/oracles/self-instrument.js +4 -2
  130. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  131. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  132. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  133. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  134. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  135. package/dist/judgement/runs/artifact/render-report.js +4 -1
  136. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  137. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  138. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  139. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  140. package/dist/judgement/runs/context-tools.js +21 -3
  141. package/dist/judgement/runs/drive-run.js +7 -2
  142. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  143. package/dist/judgement/runs/verification-sync.js +1 -1
  144. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  145. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  146. package/dist/language/flows/change/affected.d.ts +9 -0
  147. package/dist/language/flows/change/affected.js +35 -0
  148. package/dist/language/flows/change/gate.d.ts +20 -0
  149. package/dist/language/flows/change/gate.js +12 -1
  150. package/dist/language/flows/flow-anchor.d.ts +12 -0
  151. package/dist/language/flows/flow-anchor.js +20 -0
  152. package/dist/language/flows/flow-author.d.ts +7 -0
  153. package/dist/language/flows/flow-author.js +19 -0
  154. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  155. package/dist/language/flows/flow-expect-grammar.js +92 -11
  156. package/dist/language/flows/flow-intent.js +1 -1
  157. package/dist/language/flows/flow-journey.d.ts +54 -0
  158. package/dist/language/flows/flow-journey.js +119 -0
  159. package/dist/language/flows/flow-learning.d.ts +3 -0
  160. package/dist/language/flows/flow-learning.js +39 -8
  161. package/dist/language/flows/flow-replay-run.d.ts +33 -12
  162. package/dist/language/flows/flow-replay-run.js +131 -52
  163. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  164. package/dist/language/flows/flow-replay.d.ts +27 -12
  165. package/dist/language/flows/flow-replay.js +127 -111
  166. package/dist/language/flows/flow-result.d.ts +2 -0
  167. package/dist/language/flows/flow-result.js +25 -0
  168. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  169. package/dist/language/flows/flow-step-runners.js +20 -4
  170. package/dist/language/flows/flow-success.d.ts +21 -6
  171. package/dist/language/flows/flow-success.js +50 -138
  172. package/dist/language/flows/flow-tools.js +17 -5
  173. package/dist/language/flows/flows.d.ts +18 -5
  174. package/dist/language/flows/flows.js +101 -16
  175. package/dist/language/flows/heal.d.ts +9 -1
  176. package/dist/language/flows/heal.js +24 -2
  177. package/dist/language/flows/learned-sources.d.ts +21 -0
  178. package/dist/language/flows/learned-sources.js +84 -0
  179. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  180. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  181. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  182. package/dist/language/flows/recording/replay-halt.js +3 -1
  183. package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
  184. package/dist/language/flows/recording/tape/recordings.js +55 -1
  185. package/dist/language/flows/replay.d.ts +19 -0
  186. package/dist/language/flows/replay.js +53 -13
  187. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  188. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  189. package/dist/language/flows/stores/flake-store.js +2 -3
  190. package/dist/language/flows/suite/coverage.d.ts +1 -1
  191. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  192. package/dist/language/flows/verify-change-tools.js +19 -2
  193. package/dist/launch-chromium.d.ts +80 -0
  194. package/dist/launch-chromium.js +218 -0
  195. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  196. package/dist/memory/cloud/cloud-config.js +47 -7
  197. package/dist/memory/cloud/cloud-sync.d.ts +4 -3
  198. package/dist/memory/cloud/cloud-sync.js +7 -10
  199. package/dist/memory/cloud/harness-config.d.ts +2 -0
  200. package/dist/memory/cloud/harness-offer.js +5 -6
  201. package/dist/memory/cloud/harness-switch.js +5 -6
  202. package/dist/memory/cloud/sync-cycle.d.ts +78 -1
  203. package/dist/memory/cloud/sync-cycle.js +321 -53
  204. package/dist/memory/cloud/sync-daemon.d.ts +13 -0
  205. package/dist/memory/cloud/sync-daemon.js +45 -20
  206. package/dist/memory/cloud/sync-disk.js +27 -8
  207. package/dist/memory/impact/impact-store.js +6 -0
  208. package/dist/memory/intent/inline-intent.js +8 -3
  209. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  210. package/dist/memory/intent/intent-shard-store.js +252 -140
  211. package/dist/memory/intent/intent-shard.d.ts +0 -23
  212. package/dist/memory/intent/intent-shard.js +1 -11
  213. package/dist/memory/intent/intent-store.d.ts +13 -8
  214. package/dist/memory/intent/intent-store.js +46 -90
  215. package/dist/memory/intent/intent-tools.js +34 -23
  216. package/dist/memory/journal/ambient-store.js +2 -3
  217. package/dist/memory/journal/deviation-report.js +4 -1
  218. package/dist/memory/journal/deviation-service.js +10 -2
  219. package/dist/memory/journal/drive-flow.d.ts +19 -14
  220. package/dist/memory/journal/drive-flow.js +65 -48
  221. package/dist/memory/journal/envelope-store.js +2 -3
  222. package/dist/memory/journal/journal-recorder.d.ts +21 -0
  223. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  224. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  225. package/dist/memory/journal/on-disk/retention.d.ts +14 -10
  226. package/dist/memory/journal/on-disk/retention.js +13 -12
  227. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  228. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  229. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
  230. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
  231. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  232. package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
  233. package/dist/memory/journal/route-template.d.ts +22 -0
  234. package/dist/memory/journal/route-template.js +44 -0
  235. package/dist/memory/journal/session-end.d.ts +13 -2
  236. package/dist/memory/journal/session-end.js +88 -18
  237. package/dist/memory/journal/session-journal.d.ts +8 -1
  238. package/dist/memory/journal/session-journal.js +10 -0
  239. package/dist/memory/project/baselines.d.ts +1 -1
  240. package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
  241. package/dist/memory/project/dir/reticle-dir.js +1 -0
  242. package/dist/memory/project/fs/fs-port.js +44 -5
  243. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  244. package/dist/memory/project/fs/write-atomic.js +48 -0
  245. package/dist/memory/project/project-tools.js +3 -1
  246. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  247. package/dist/memory/recall/prior/connection-memory.js +8 -0
  248. package/dist/memory/recall/registered-projects.d.ts +2 -1
  249. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  250. package/dist/portal/bridge/auth-failure-reason.js +9 -4
  251. package/dist/portal/bridge/bridge.d.ts +8 -0
  252. package/dist/portal/bridge/bridge.js +43 -2
  253. package/dist/portal/input/js-coverage.d.ts +25 -0
  254. package/dist/portal/input/js-coverage.js +30 -0
  255. package/dist/portal/input/real-input.d.ts +11 -0
  256. package/dist/portal/input/real-input.js +31 -5
  257. package/dist/portal/pool/browser-pool.d.ts +10 -0
  258. package/dist/portal/pool/browser-pool.js +10 -0
  259. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  260. package/dist/portal/pool/playwright-launcher.js +21 -13
  261. package/dist/portal/pool/pool-contract.d.ts +2 -0
  262. package/dist/portal/pool/storage-seed.d.ts +2 -1
  263. package/dist/portal/pool/zero-install.d.ts +27 -0
  264. package/dist/portal/pool/zero-install.js +54 -0
  265. package/dist/portal/realm/web-realm.js +6 -0
  266. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  267. package/dist/portal/session/dev-server/dev-command.js +30 -12
  268. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  269. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  270. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  271. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  272. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  273. package/dist/portal/session/dev-server/served-document.js +57 -0
  274. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  275. package/dist/portal/session/facts/background-traffic.js +25 -0
  276. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  277. package/dist/portal/session/facts/observed-state.js +34 -0
  278. package/dist/portal/session/live-control-tools.js +48 -3
  279. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  280. package/dist/portal/session/no-session-diagnosis.js +62 -5
  281. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  282. package/dist/portal/session/no-session-next-action.js +48 -9
  283. package/dist/portal/session/no-session-watch.d.ts +18 -0
  284. package/dist/portal/session/no-session-watch.js +88 -2
  285. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  286. package/dist/portal/session/session-health.d.ts +61 -0
  287. package/dist/portal/session/session-health.js +43 -3
  288. package/dist/portal/session/session-info.d.ts +6 -0
  289. package/dist/portal/session/session-manager.d.ts +29 -6
  290. package/dist/portal/session/session-manager.js +42 -5
  291. package/dist/portal/session/session-reaper.d.ts +28 -0
  292. package/dist/portal/session/session-reaper.js +49 -1
  293. package/dist/portal/session/session-successor.d.ts +11 -0
  294. package/dist/portal/session/session-successor.js +14 -1
  295. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  296. package/dist/portal/session/session-verdict-facts.js +15 -0
  297. package/dist/portal/session/session.d.ts +10 -0
  298. package/dist/portal/session/session.js +34 -26
  299. package/dist/surface/http-server.d.ts +2 -1
  300. package/dist/surface/http-server.js +4 -2
  301. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  302. package/dist/surface/mcp/mcp-proxy.js +5 -5
  303. package/dist/surface/mcp/mcp.js +4 -2
  304. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  305. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  306. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  307. package/dist/surface/mcp/server-instructions.js +21 -8
  308. package/dist/surface/tools/act/after-match.d.ts +17 -0
  309. package/dist/surface/tools/act/after-match.js +30 -0
  310. package/dist/surface/tools/act/already-true.d.ts +40 -0
  311. package/dist/surface/tools/act/already-true.js +36 -0
  312. package/dist/surface/tools/act/durable.d.ts +69 -0
  313. package/dist/surface/tools/act/durable.js +139 -0
  314. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
  315. package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
  316. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  317. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  318. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  319. package/dist/surface/tools/act/proved-controls.js +11 -0
  320. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  321. package/dist/surface/tools/act/sequence-grade.js +32 -1
  322. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  323. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  324. package/dist/surface/tools/act-capsule.d.ts +7 -0
  325. package/dist/surface/tools/act-capsule.js +68 -8
  326. package/dist/surface/tools/act-sequence-tool.js +25 -8
  327. package/dist/surface/tools/act-tools.js +51 -74
  328. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  329. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  330. package/dist/surface/tools/assert/assert-grade.js +26 -3
  331. package/dist/surface/tools/assert/assert-verdict.js +32 -6
  332. package/dist/surface/tools/browser-tools.js +17 -4
  333. package/dist/surface/tools/coverage-tools.js +27 -1
  334. package/dist/surface/tools/dynamic-tools.js +6 -0
  335. package/dist/surface/tools/error-recovery.js +4 -4
  336. package/dist/surface/tools/feedback-invite.js +6 -4
  337. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  338. package/dist/surface/tools/feedback-tools.js +1 -1
  339. package/dist/surface/tools/harness-explore.d.ts +3 -2
  340. package/dist/surface/tools/harness-explore.js +7 -6
  341. package/dist/surface/tools/invoke-tool.js +17 -4
  342. package/dist/surface/tools/lease-hint.d.ts +8 -0
  343. package/dist/surface/tools/lease-hint.js +10 -0
  344. package/dist/surface/tools/lease-tools.d.ts +16 -28
  345. package/dist/surface/tools/lease-tools.js +68 -11
  346. package/dist/surface/tools/lineage-tools.js +1 -1
  347. package/dist/surface/tools/live-call-text.js +4 -1
  348. package/dist/surface/tools/observe-tools.js +23 -7
  349. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  350. package/dist/surface/tools/real-input-attempt.js +1 -1
  351. package/dist/surface/tools/tool-kit.d.ts +5 -0
  352. package/dist/surface/tools/tool-kit.js +6 -1
  353. package/dist/surface/tools/tool-surface.js +17 -5
  354. package/dist/surface/tools/tools.js +68 -29
  355. package/dist/telemetry/argument-shape.d.ts +0 -2
  356. package/dist/telemetry/argument-shape.js +0 -4
  357. package/dist/telemetry/error-class.d.ts +2 -3
  358. package/dist/telemetry/error-class.js +2 -3
  359. package/dist/telemetry/feedback-local.d.ts +1 -0
  360. package/dist/telemetry/feedback-local.js +1 -3
  361. package/dist/telemetry/feedback.d.ts +1 -1
  362. package/dist/telemetry/harness-drive.d.ts +0 -2
  363. package/dist/telemetry/harness-drive.js +0 -4
  364. package/dist/telemetry/hud-metrics.d.ts +32 -0
  365. package/dist/telemetry/hud-metrics.js +95 -0
  366. package/dist/telemetry/session-metrics.d.ts +5 -0
  367. package/dist/telemetry/session-metrics.js +15 -1
  368. package/dist/wire-journal.d.ts +34 -0
  369. package/dist/wire-journal.js +104 -0
  370. package/dist/workspace-packages.d.ts +0 -2
  371. package/dist/workspace-packages.js +1 -9
  372. package/docs/README.md +5 -2
  373. package/docs/actions.mdx +3 -3
  374. package/docs/agent-cheatsheet.md +4 -4
  375. package/docs/architecture.md +6 -6
  376. package/docs/best-practices.mdx +1 -1
  377. package/docs/capabilities.mdx +32 -1
  378. package/docs/cli/cloud.mdx +21 -9
  379. package/docs/cli/doctor.mdx +4 -0
  380. package/docs/cli/feedback.mdx +2 -2
  381. package/docs/cli/gate.mdx +9 -4
  382. package/docs/cli/init.mdx +1 -0
  383. package/docs/cli/report.mdx +44 -0
  384. package/docs/cli/update.mdx +10 -0
  385. package/docs/cli.mdx +9 -0
  386. package/docs/desktop.mdx +14 -10
  387. package/docs/environment.mdx +87 -0
  388. package/docs/faq.mdx +18 -16
  389. package/docs/first-drive.md +5 -13
  390. package/docs/flows.md +32 -2
  391. package/docs/frameworks.mdx +140 -9
  392. package/docs/getting-started.md +3 -3
  393. package/docs/http-transport.md +6 -3
  394. package/docs/human-control.md +2 -2
  395. package/docs/index.mdx +16 -6
  396. package/docs/install-agentic.mdx +37 -1
  397. package/docs/install-manual.mdx +1 -1
  398. package/docs/integration-patterns.md +3 -3
  399. package/docs/multi-agent-testing.md +1 -1
  400. package/docs/packages/babel-plugin.mdx +1 -1
  401. package/docs/packages/browser.mdx +2 -2
  402. package/docs/packages/react.mdx +2 -2
  403. package/docs/packages/tauri.mdx +1 -1
  404. package/docs/packages/vite-plugin.mdx +2 -2
  405. package/docs/packages.mdx +9 -9
  406. package/docs/predicates.mdx +176 -7
  407. package/docs/quickstart.mdx +2 -2
  408. package/docs/recipes.mdx +2 -2
  409. package/docs/state-management.mdx +2 -2
  410. package/docs/system-map.md +1 -1
  411. package/docs/telemetry.md +1 -1
  412. package/docs/token-efficiency.md +3 -3
  413. package/docs/tools/act-and-wait.mdx +1 -0
  414. package/docs/tools/act-sequence.mdx +1 -1
  415. package/docs/tools/console.mdx +1 -1
  416. package/docs/tools/inspect.mdx +1 -1
  417. package/docs/tools/network.mdx +1 -1
  418. package/docs/tools/query.mdx +11 -11
  419. package/docs/tools/sessions.mdx +1 -1
  420. package/docs/tools/snapshot.mdx +1 -1
  421. package/docs/tools/state.mdx +1 -1
  422. package/docs/tools/wait-for.mdx +1 -1
  423. package/docs/usage.md +8 -7
  424. package/docs/use-cases.mdx +157 -0
  425. package/docs/vs-screenshots.mdx +3 -3
  426. package/docs/what-is-recorded.md +62 -0
  427. package/docs/what-is-reticle.mdx +17 -20
  428. package/docs/why-reticle.mdx +14 -21
  429. package/package.json +11 -10
  430. package/dist/judgement/capsule/minimize.d.ts +0 -12
  431. package/dist/judgement/capsule/minimize.js +0 -22
  432. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  433. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
  434. package/dist/judgement/phenomena/phenomena.d.ts +0 -26
  435. package/dist/judgement/phenomena/phenomena.js +0 -101
  436. package/dist/language/flows/flow-report.d.ts +0 -22
  437. package/dist/language/flows/flow-report.js +0 -106
@@ -1,5 +1,4 @@
1
- import { Surface } from 'open-verification';
2
- export { anchorLabel, componentLabel, componentQueryArgs, editDistance, expectElementDrift, nearestIsAmbiguous, nearestTestid, resolveQuery, testidDrift, } from './flow-anchor.js';
1
+ export { nearestIsAmbiguous, nearestTestid, resolveQuery } from './flow-anchor.js';
3
2
  import type { FlowReplaySession, WaitForSignal, Sleep } from './flow-replay-types.js';
4
3
  export type { FlowReplaySession, WaitForSignal, Sleep } from './flow-replay-types.js';
5
4
  import { type Drift, type FlowFile, type FlowStep, type FlowStepResult } from '@reticlehq/core';
@@ -20,6 +19,8 @@ export declare class DocumentLostDuringReplay extends Error {
20
19
  readonly atStep: number;
21
20
  constructor(steps: FlowStepResult[], atStep: number, lostTo?: unknown);
22
21
  }
22
+ /** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
23
+ export declare function runTestidStep(session: FlowReplaySession, step: FlowStep, index: number, value: string, dynamic: ReadonlySet<string>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
23
24
  /**
24
25
  * After a step's anchor resolves and its action runs, its `expect` is EVALUATED — every kind of it.
25
26
  *
@@ -41,16 +42,26 @@ export declare class DocumentLostDuringReplay extends Error {
41
42
  * quietly stop agreeing, and the disagreement would land as a flow that replays green against a
42
43
  * state it was never meant to run in.
43
44
  */
44
- export declare function assertStepExpect(session: FlowReplaySession, expect: NonNullable<FlowStep['expect']>, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since: number): Promise<Drift | undefined>;
45
+ export declare function assertStepExpect(session: FlowReplaySession, expect: NonNullable<FlowStep['expect']>, waitForSignal: WaitForSignal, timeoutMs: number, since: number): Promise<Drift | undefined>;
45
46
  /**
46
47
  * Replay a loaded flow by RE-RESOLVING every step's semantic anchor against the live DOM — never
47
48
  * a stale ref. A testid anchor is re-found by reticle_query; a signal anchor waits on a predicate.
48
49
  * On the first anchor MISS the step carries legible drift and replay STOPS, returning the partial
49
50
  * results. This is the "whose fault is it" contract, not a blind "command failed".
50
51
  */
51
- /** Where a replay starts reporting. Steps before it are re-driven, silently, as setup. */
52
+ /**
53
+ * How a replay is loaded, where it starts reporting, and how far it goes.
54
+ */
52
55
  export interface ReplayFromOptions {
53
- from?: number;
56
+ /**
57
+ * Resume at this step: an index, or a step's `id` (which survives edits that shift indices).
58
+ *
59
+ * There is no state to restore, so the steps before it are re-driven — quickly, as setup: their
60
+ * actions run, their declared consequences are not checked and their results are not reported.
61
+ * The one exception is a setup step that FAILS: the resume never reached the step it was asked
62
+ * for, and swallowing that would report the run as starting where it did not.
63
+ */
64
+ from?: number | string;
54
65
  /**
55
66
  * How to load a flow this one INVOKES. Absent means invocations cannot be followed.
56
67
  *
@@ -67,13 +78,6 @@ export interface ReplayFromOptions {
67
78
  flow: string;
68
79
  step: number;
69
80
  }[];
70
- /**
71
- * Which kind of subject this is. Decides whether re-driving the prefix is allowed at all.
72
- *
73
- * Defaults to `web`, which is the permissive answer — correct for the only surface that resumes
74
- * today, and the reason a caller on a committing surface must say so rather than rely on silence.
75
- */
76
- surface?: Surface;
77
81
  /**
78
82
  * Keep going past a step whose ACTION ran and whose declared consequence merely did not hold.
79
83
  *
@@ -88,4 +92,15 @@ export interface ReplayFromOptions {
88
92
  */
89
93
  sweep?: boolean;
90
94
  }
95
+ /**
96
+ * The index a replay resumes at, or `undefined` when `from` names no step of this flow.
97
+ *
98
+ * A step declared `commits` in the prefix refuses the resume (0 = replay everything): re-driving it
99
+ * is not setup, it is a second charge, a second email, a second record.
100
+ */
101
+ export declare function resumeIndex(from: number | string | undefined, steps: readonly FlowStep[]): number | undefined;
102
+ /** The `from` argument, narrowed: a step index or a step id. */
103
+ export declare function resumeArg(raw: unknown): number | string | undefined;
104
+ /** Why `from` cannot be resumed at, or undefined when it can (or was not given). */
105
+ export declare function unresolvedResume(from: number | string | undefined, steps: readonly FlowStep[]): string | undefined;
91
106
  export declare function replayFlow(session: FlowReplaySession, flow: FlowFile, waitForSignal: WaitForSignal, signalTimeoutMs: number, confirmDangerous?: boolean, sleep?: Sleep, options?: ReplayFromOptions): Promise<FlowStepResult[]>;
@@ -1,23 +1,21 @@
1
- import { mayResumeByReplayingPrefix, StepEffect } from '@reticlehq/core';
2
- import { Surface, formatStepAddress } from 'open-verification';
1
+ import { formatStepAddress } from 'open-verification';
2
+ import { assertsState, expectLabel, expectedElementTestid, withoutClauses } from '@reticlehq/core';
3
3
  import { span } from '../../trace.js';
4
- import { anchorLabel, expectElementDrift, resolveTestid, testidDrift } from './flow-anchor.js';
5
- export { anchorLabel, componentLabel, componentQueryArgs, editDistance, expectElementDrift, nearestIsAmbiguous, nearestTestid, resolveQuery, testidDrift, } from './flow-anchor.js';
6
- import { routeOfEvent } from '@reticlehq/engine/question/predicate/predicate-route.js';
4
+ import { anchorLabel, expectElementDrift, resolveTestid, testidDrift, ambiguousAnchorDrift, } from './flow-anchor.js';
5
+ export { nearestIsAmbiguous, nearestTestid, resolveQuery } from './flow-anchor.js';
6
+ import { routeOfEvent, routeOfUrl } from '@reticlehq/engine/question/predicate/predicate-route.js';
7
7
  import { stepEffect } from '@reticlehq/engine/evidence/step-effect.js';
8
- import { AnchorKind, DriftReason, EventType, FlowStepTool, ReticleCommand, PredicateKind, } from '@reticlehq/core';
8
+ import { AnchorKind, DriftReason, EventType, FlowStepTool, StepEffect, PredicateKind, } from '@reticlehq/core';
9
9
  import { asString, isConsequenceDrift } from '@reticlehq/core';
10
- import { replayActionArgs, ambiguousTestidNote } from './replay.js';
11
10
  import { anchorFieldName } from './fields/flow-secret-field.js';
12
11
  import { degradedStepResult, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
13
- import { successToPredicate } from './flow-success.js';
14
12
  import { inFlightRequestLabels } from '../../surface/tools/act/settle-in-flight.js';
15
13
  import { namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
16
14
  /** What a still-open named request means, in the drift's own words. */
17
15
  const IN_FLIGHT_AT_BUDGET_END = 'the request this step declared had not come back when the budget ended — it is still in flight, ' +
18
16
  'so nothing here says the app failed. Raise the step timeout, or look at the endpoint';
19
- import { ReticleTool } from '@reticlehq/core';
20
- import { isDocumentGoneError } from '../../portal/session/facts/session-replaced.js';
17
+ import { isDocumentGoneError, SESSION_DISCONNECTED_REASON, } from '../../portal/session/facts/session-replaced.js';
18
+ import { actOnResolvedRef } from './flow-step-runners.js';
21
19
  /**
22
20
  * The document this replay was driving went away mid-run.
23
21
  *
@@ -50,8 +48,11 @@ const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
50
48
  function currentRoute(session) {
51
49
  const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
52
50
  const last = routes.at(-1);
53
- if (last === undefined)
54
- return undefined;
51
+ // A page that loaded and never navigated has no route event; its URL is still where it is.
52
+ if (last === undefined) {
53
+ const loaded = session.url === undefined ? undefined : routeOfUrl(session.url);
54
+ return loaded !== undefined && loaded.routePath.length > 0 ? loaded.routePath : undefined;
55
+ }
55
56
  // The ROUTER's path. This field answers "which page did this step run on", and the document
56
57
  // pathname is `/` on every page of a hash-routed app — so a whole desktop replay reported `/` for
57
58
  // every step. Sixth place the same reading was wrong; see routeOfEvent.
@@ -105,7 +106,7 @@ function summarizeConsequence(events) {
105
106
  return parts.length > 0 ? parts.join('; ') : undefined;
106
107
  }
107
108
  /** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
108
- async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
109
+ export async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
109
110
  const { refs, hint } = await resolveTestid(session, value, sleep);
110
111
  if (0 === refs.length) {
111
112
  return {
@@ -116,35 +117,50 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
116
117
  drift: testidDrift(value, hint),
117
118
  };
118
119
  }
119
- const ref = refs[0] ?? '';
120
- const note = refs.length > 1 ? ambiguousTestidNote(value) : undefined;
121
- session.beginAction?.(ReticleTool.FLOW_REPLAY, { ref, action: step.action ?? '' });
122
- let act;
123
- try {
124
- act = await session.command(ReticleCommand.ACT, {
125
- ref,
126
- action: step.action ?? '',
127
- // The field this step types into — from the anchor, so a redacted fill can be supplied from
128
- // RETICLE_SECRET_<FIELD> without the flow carrying the secret. The testid runner used to pass
129
- // the testid string here and the other two runners passed nothing, so a role-anchored login
130
- // typed the literal placeholder.
131
- args: replayActionArgs(step.args, confirmDangerous, anchorFieldName(step.anchor)),
132
- });
133
- }
134
- finally {
135
- session.finishAction?.();
120
+ /*
121
+ * More than one match is DRIFT, not a note on a passing step.
122
+ *
123
+ * This used to take refs[0], act, and return ok:true with `ambiguousTestidNote`. The verdict
124
+ * reads `drift` and `ok` and never reads a note, so "we guessed which element you meant" was
125
+ * indistinguishable from "it did what it did before" -- the one claim a replay makes.
126
+ *
127
+ * The action is NOT dispatched. Acting and then reporting drift would leave the app changed by
128
+ * a click nobody can attribute, which is worse than the ambiguity it reports.
129
+ */
130
+ if (refs.length > 1) {
131
+ return {
132
+ step: index,
133
+ tool: step.tool,
134
+ anchor: value,
135
+ ok: false,
136
+ drift: ambiguousAnchorDrift(value, refs.length),
137
+ };
136
138
  }
137
- const result = { step: index, tool: step.tool, anchor: value, ok: act.ok };
138
- if (!act.ok) {
139
- result.error = act.error ?? 'command failed';
140
- if (note !== undefined)
141
- result.note = note;
139
+ const ref = refs[0] ?? '';
140
+ /*
141
+ * The same dispatch its siblings use, including the one re-resolve on a stale ref (2.3).
142
+ *
143
+ * This runner had its own inline copy and no retry, so a flow died on a re-render purely because
144
+ * the step was anchored by testid rather than by role — the locator deciding how sturdy the replay
145
+ * is, which is exactly backwards. testid is also the anchor `reticle init` steers people towards,
146
+ * so the kind most likely to appear in a real flow was the kind without the cure.
147
+ *
148
+ * The re-resolve keeps the ambiguity rule above: more than one match is drift, never a guess, so
149
+ * it hands back a ref only when the locator still names exactly one element.
150
+ */
151
+ const result = await actOnResolvedRef(session, step, index, value, ref, confirmDangerous,
152
+ // The field this step types into — from the anchor, so a redacted fill can be supplied from
153
+ // RETICLE_SECRET_<FIELD> without the flow carrying the secret.
154
+ anchorFieldName(step.anchor), async () => {
155
+ const again = await resolveTestid(session, value, sleep);
156
+ return 1 === again.refs.length ? again.refs[0] : undefined;
157
+ });
158
+ if (!result.ok)
142
159
  return result;
143
- }
144
160
  // assert the step's expect.element testid is present AFTER the action —
145
161
  // unless that testid was marked DYNAMIC (the LLM-output case), in which case its presence/content
146
162
  // is NOT asserted (only the action ran). The skip is scoped strictly to the dynamic set.
147
- const expectTestid = step.expect?.element?.testid;
163
+ const expectTestid = expectedElementTestid(step.expect);
148
164
  if (expectTestid !== undefined && !dynamic.has(expectTestid)) {
149
165
  const expectRefs = await resolveTestid(session, expectTestid, sleep);
150
166
  if (0 === expectRefs.refs.length) {
@@ -160,8 +176,6 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
160
176
  };
161
177
  }
162
178
  }
163
- if (note !== undefined)
164
- result.note = note;
165
179
  return result;
166
180
  }
167
181
  /**
@@ -185,30 +199,52 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
185
199
  * quietly stop agreeing, and the disagreement would land as a flow that replays green against a
186
200
  * state it was never meant to run in.
187
201
  */
188
- export async function assertStepExpect(session, expect, dynamic, waitForSignal, timeoutMs, since) {
202
+ export async function assertStepExpect(session, expect, waitForSignal, timeoutMs, since) {
189
203
  // A testid is already asserted against the live DOM by the step runner. A role/name locator is
190
204
  // not that path — stripping every element made a recorded `until` by button name a no-op, so a
191
205
  // flow that proved the control at capture time could not go red when it was gone.
192
- const consequences = { ...expect };
193
- if (undefined !== consequences.element?.testid) {
194
- delete consequences.element;
195
- }
196
- const predicate = successToPredicate(consequences, dynamic);
206
+ /*
207
+ * Two clauses replay must not wait on, both for reasons that predate the tree.
208
+ *
209
+ * A testid element was already asserted against the live DOM by the step runner, so waiting on it
210
+ * again is a second read of a question already answered. A DYNAMIC testid is one whose content a
211
+ * model writes, and its presence is deliberately never asserted.
212
+ *
213
+ * A role/name locator is NEITHER, and stripping every element made a recorded `until` by button
214
+ * name a no-op — a flow that proved the control at capture time could not go red when it was gone.
215
+ *
216
+ * The DYNAMIC set used to be a parameter here and was dead the whole time: the testid was deleted
217
+ * from the expectation on the line above, and the only thing that read the set asked for the
218
+ * testid that had just been removed. One production caller was already passing an empty set. The
219
+ * rule is real and still applies to the flow's SUCCESS oracle, which is where it lives.
220
+ */
221
+ const predicate = withoutClauses(expect,
222
+ // Only a PRESENT testid is the runner's to check; an absence stays here or nobody checks it.
223
+ (clause) => PredicateKind.ELEMENT === clause.kind &&
224
+ clause.query.testid !== undefined &&
225
+ true !== clause.absent);
197
226
  if (predicate === undefined)
198
227
  return undefined;
199
228
  const verdict = await waitForSignal(session, predicate, timeoutMs, since);
200
229
  if (verdict.pass)
201
230
  return undefined;
231
+ // The wait ended because the page stopped being observable, not because the consequence failed:
232
+ // the same fact as a command the dying socket rejected, so it takes the same path (the replay loop
233
+ // reports a lost document). As a drift, a socket that blinked for 26ms read as the app's defect.
234
+ if (true === verdict.observationLost)
235
+ throw new Error(SESSION_DISCONNECTED_REASON);
202
236
  // A request the step NAMED that had not come back yet is a blind spot, not an assertion the app
203
237
  // failed. The live verdict path already draws this line (namedNetIsInFlight); replay read the
204
238
  // same window and never asked, so a slow endpoint was reported as a consequence that never fired.
205
239
  // An unrelated open request does not pardon a named URL that never started — matching decides it.
206
- const openRequests = expect.state === undefined ? inFlightRequestLabels(session.eventsSince(since)) : [];
240
+ const openRequests = assertsState(expect)
241
+ ? []
242
+ : inFlightRequestLabels(session.eventsSince(since), session.url, session.background);
207
243
  const namedInFlight = namedNetIsInFlight(predicate, openRequests);
208
244
  return {
209
245
  // The store case keeps its own kind because heal and the run report branch on it; everything
210
246
  // else is a consequence that did not hold, and the reason carries observed-vs-expected.
211
- reasonKind: expect.state !== undefined
247
+ reasonKind: assertsState(expect)
212
248
  ? DriftReason.STATE_MISMATCH
213
249
  : namedInFlight
214
250
  ? DriftReason.NET_STILL_IN_FLIGHT
@@ -220,21 +256,6 @@ export async function assertStepExpect(session, expect, dynamic, waitForSignal,
220
256
  nearest: null,
221
257
  };
222
258
  }
223
- /** Name the thing that was asserted, for the drift's `anchor` column. */
224
- function expectLabel(expect) {
225
- if (expect.signal !== undefined)
226
- return `signal:${expect.signal}`;
227
- if (expect.net !== undefined)
228
- return `net:${expect.net.urlContains ?? expect.net.method ?? '*'}`;
229
- if (expect.state !== undefined)
230
- return `state:${expect.state.path}`;
231
- if (expect.console !== undefined)
232
- return `console:${expect.console.level ?? '*'}`;
233
- if (undefined !== expect.element) {
234
- return expect.element.testid ?? expect.element.name ?? expect.element.role ?? 'element';
235
- }
236
- return 'expect';
237
- }
238
259
  /** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
239
260
  async function runSignalStep(session, step, index, name, waitForSignal, signalTimeoutMs, since) {
240
261
  // Scope to THIS replay's floor, not the whole buffer.
@@ -263,38 +284,28 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
263
284
  };
264
285
  }
265
286
  /**
266
- * Where to start REPORTING — and whether re-driving the steps before it is allowed at all.
267
- *
268
- * "Resume is nearly free, just re-run the prefix" is true of a browser and false on a subject that
269
- * COMMITS. Re-driving a prefix on a service re-sends every request before step N; on a device it
270
- * moves an arm again. Neither is a convenience, and a protocol that did it silently would be
271
- * defective rather than helpful.
287
+ * The index a replay resumes at, or `undefined` when `from` names no step of this flow.
272
288
  *
273
- * So the surface's declared profile decides, through the specification's own `resumeStrategy` rather
274
- * than a local reading of `replayPrefix`. A refusal resumes from 0 — the whole journey is reported,
275
- * nothing is skipped and nothing is silently repeated beyond what a plain replay already does.
289
+ * A step declared `commits` in the prefix refuses the resume (0 = replay everything): re-driving it
290
+ * is not setup, it is a second charge, a second email, a second record.
276
291
  */
277
- function resumableFrom(options, steps) {
278
- const asked = Math.max(0, options.from ?? 0);
279
- if (0 === asked)
280
- return 0;
281
- if (!mayResumeByReplayingPrefix(options.surface ?? Surface.WEB))
292
+ export function resumeIndex(from, steps) {
293
+ if (from === undefined)
282
294
  return 0;
283
- /*
284
- * The surface answers for the SUBJECT; a step answers for itself.
285
- *
286
- * `web` is the permissive profile and it is right about a browser in general and wrong about the
287
- * one click that charges a card. The surface check cannot see that, because the distinction is
288
- * not a property of the realm — it is a property of the step. A flow that declares a prefix step
289
- * as `commits` is saying: re-running me is not free, whatever the surface thinks.
290
- *
291
- * Refusing means resuming from 0, which is the same fail-safe the surface refusal already uses:
292
- * the whole journey is reported and nothing is silently repeated beyond what a plain replay does.
293
- * Absent effect is UNKNOWN and stays permissive — every flow recorded before this shipped has no
294
- * effect on any step, and assuming the worst there would refuse every resume in existence.
295
- */
296
- const commitsInPrefix = steps.slice(0, asked).some((step) => StepEffect.COMMITS === step.effect);
297
- return commitsInPrefix ? 0 : asked;
295
+ const at = 'number' === typeof from ? from : steps.findIndex((step) => step.id === from);
296
+ if (at < 0 || at >= steps.length || !Number.isInteger(at))
297
+ return undefined;
298
+ return steps.slice(0, at).some((step) => StepEffect.COMMITS === step.effect) ? 0 : at;
299
+ }
300
+ /** The `from` argument, narrowed: a step index or a step id. */
301
+ export function resumeArg(raw) {
302
+ return 'number' === typeof raw || ('string' === typeof raw && raw.length > 0) ? raw : undefined;
303
+ }
304
+ /** Why `from` cannot be resumed at, or undefined when it can (or was not given). */
305
+ export function unresolvedResume(from, steps) {
306
+ if (resumeIndex(from, steps) !== undefined)
307
+ return undefined;
308
+ return `cannot resume at ${JSON.stringify(from)}: this flow has ${String(steps.length)} step(s) and no step by that id`;
298
309
  }
299
310
  /**
300
311
  * Run an `invoke` step: load the named flow and replay it, or fail saying why.
@@ -307,7 +318,7 @@ function resumableFrom(options, steps) {
307
318
  * own, and the sub-journey's outcome is summarised on the invocation, so a reader sees the shape
308
319
  * they recorded rather than a flattened list that has lost every boundary.
309
320
  */
310
- async function runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options) {
321
+ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options, setup = false) {
311
322
  const name = step.invoke ?? '';
312
323
  const via = options.via ?? [];
313
324
  const here = { flow: flow.name, step: index, via };
@@ -329,10 +340,10 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
329
340
  error: `cannot replay "${name}": it was not found, so this journey would report green having never run it`,
330
341
  };
331
342
  }
332
- // `from` is deliberately dropped rather than forwarded: it is a REPORTING offset into the
333
- // caller's own step list, and applying it inside a sub-journey would silently hide that
334
- // journey's first steps for a reason that has nothing to do with it.
335
- const { from: _ignored, ...carried } = options;
343
+ // `from` is an offset into the CALLER's steps. A sub-journey invoked from the setup prefix runs
344
+ // whole and unchecked (from = its length); one invoked at or after the resume point runs normally.
345
+ const { from: _outer, ...rest } = options;
346
+ const carried = setup ? { ...rest, from: sub.steps.length } : rest;
336
347
  const nested = await replayFlow(session, sub, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, {
337
348
  ...carried,
338
349
  via: [{ flow: flow.name, step: index }, ...via],
@@ -350,16 +361,9 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
350
361
  }
351
362
  export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs, confirmDangerous = false, sleep = realSleep, options = {}) {
352
363
  const results = [];
353
- /*
354
- * Where to start REPORTING. Everything before it still runs.
355
- *
356
- * Resuming is re-driving the prefix, not restoring state: there is no way to put an app back
357
- * where it was without driving it there, and at a measured ~27ms a step there is no reason to
358
- * try. So the prefix executes silently and the caller sees the journey from the point it asked
359
- * about -- which is what makes "fix the break, resume, find the next one" a loop rather than a
360
- * full re-read each time.
361
- */
362
- const from = resumableFrom(options, flow.steps);
364
+ // Steps before this index are setup. `from` was validated by the caller; an unresolvable one
365
+ // resumes nowhere rather than everywhere, so it cannot silently widen into a full replay.
366
+ const from = resumeIndex(options.from, flow.steps) ?? flow.steps.length;
363
367
  // testids whose region is LLM-dynamic — their expect-presence is NOT asserted.
364
368
  const dynamic = new Set((flow.dynamic ?? [])
365
369
  .filter((a) => a.kind === AnchorKind.TESTID)
@@ -376,8 +380,10 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
376
380
  try {
377
381
  for (const step of flow.steps) {
378
382
  if (step.invoke !== undefined) {
379
- results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options));
383
+ results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options, index < from));
380
384
  const last = results[results.length - 1];
385
+ if (last !== undefined && index < from && last.ok)
386
+ results.pop();
381
387
  if (last !== undefined && false === last.ok)
382
388
  break;
383
389
  index += 1;
@@ -433,10 +439,12 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
433
439
  * replay path and belongs in its own commit.
434
440
  */
435
441
  const declared = [step.expect, ...(step.steps ?? []).map((sub) => sub.expect)].filter((expectation) => expectation !== undefined);
436
- for (const expectation of declared) {
442
+ // A setup step's consequence is not checked: it is re-driven to get somewhere, not to prove it.
443
+ const checked = index < from ? [] : declared;
444
+ for (const expectation of checked) {
437
445
  if (!result.ok || result.drift !== undefined)
438
446
  break;
439
- const expectDrift = await assertStepExpect(session, expectation, dynamic, waitForSignal, waitFor(step), cursorBefore);
447
+ const expectDrift = await assertStepExpect(session, expectation, waitForSignal, waitFor(step), cursorBefore);
440
448
  if (expectDrift !== undefined) {
441
449
  result.ok = false;
442
450
  result.drift = expectDrift;
@@ -444,6 +452,9 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
444
452
  }
445
453
  if (page !== undefined)
446
454
  result.page = page;
455
+ const endPage = currentRoute(session);
456
+ if (endPage !== undefined && endPage !== page)
457
+ result.endPage = endPage;
447
458
  const windowEvents = session.eventsSince(cursorBefore).filter((e) => e.t >= cursorBefore);
448
459
  const consequence = summarizeConsequence(windowEvents);
449
460
  if (consequence !== undefined)
@@ -466,8 +477,13 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
466
477
  // forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
467
478
  if (FlowStepTool.ACT === result.tool)
468
479
  delete result.tool;
469
- if (index >= from || !result.ok || result.drift !== undefined)
470
- results.push(result);
480
+ if (index < from && result.ok && result.drift === undefined) {
481
+ index += 1;
482
+ continue;
483
+ }
484
+ if (index < from)
485
+ result.note = `setup step for resuming at step ${String(from)} failed`;
486
+ results.push(result);
471
487
  // Under `sweep`, a failure whose action still RAN does not stop the run — the page is where the
472
488
  // step left it, so the next step is as meaningful as it was going to be. Anything else halts.
473
489
  const sweepPast = true === options.sweep &&
@@ -33,3 +33,5 @@ export type FlowResult<T> = {
33
33
  * hold the distinction — this is the fourth id joined into a path, and the one that file missed.
34
34
  */
35
35
  export declare const safeProjectId: (projectId?: ProjectId) => ProjectId | undefined;
36
+ /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
37
+ export declare function flowErrorMessage(code: FlowErrorCode, detail?: string): string;
@@ -1,5 +1,6 @@
1
1
  import { FlowErrorCode } from '@reticlehq/core';
2
2
  import { SAFE_SEGMENT_PATTERN } from '@reticlehq/core';
3
+ import { FlowParseNote } from './flow-expect-grammar.js';
3
4
  /**
4
5
  * A project id that is safe to put in a path, or nothing.
5
6
  *
@@ -24,3 +25,27 @@ import { SAFE_SEGMENT_PATTERN } from '@reticlehq/core';
24
25
  * hold the distinction — this is the fourth id joined into a path, and the one that file missed.
25
26
  */
26
27
  export const safeProjectId = (projectId) => projectId !== undefined && SAFE_SEGMENT_PATTERN.test(projectId) ? projectId : undefined;
28
+ /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
29
+ export function flowErrorMessage(code, detail) {
30
+ if (FlowErrorCode.PARSE_FAILED === code && undefined !== detail)
31
+ return detail;
32
+ // The detail names both versions and the remedy, so it beats anything generic this could say.
33
+ if (FlowErrorCode.WRONG_VERSION === code && undefined !== detail)
34
+ return detail;
35
+ switch (code) {
36
+ case FlowErrorCode.INVALID_NAME:
37
+ return 'invalid flow name — use a single safe segment (letters/digits/-/_), no path separators';
38
+ case FlowErrorCode.NOT_FOUND:
39
+ return 'no such flow on disk — run reticle_flow{action:"list"} to see saved flows';
40
+ case FlowErrorCode.PARSE_FAILED:
41
+ return FlowParseNote.MALFORMED;
42
+ case FlowErrorCode.NO_RECORDING:
43
+ return 'no compiled recording by that name — record one (reticle_record{action:"start"|"stop"}) first';
44
+ // Never "regenerate it": the file is intact and the reader is the wrong one. Telling somebody
45
+ // to rewrite an undamaged flow is the failure this code was split out of PARSE_FAILED to stop.
46
+ case FlowErrorCode.WRONG_VERSION:
47
+ return 'this flow file was written in a different flow-file format — the file is not damaged, this Reticle cannot read that version. Upgrade or downgrade Reticle rather than editing the flow';
48
+ case FlowErrorCode.STEP_NOT_FOUND:
49
+ return (detail ?? 'the step to resume at is not in this flow — pass a 0-based index or a step id');
50
+ }
51
+ }
@@ -1,17 +1,26 @@
1
- /**
2
- * How one anchored step is turned into a live action.
3
- *
4
- * Split out of flow-replay.ts when adding the role+name runner pushed that file past the size cap.
5
- * These three share one shape — resolve an anchor to a ref, then dispatch — and the sharing is the
6
- * point: the action window must open and close identically no matter which anchor found the element,
7
- * or a step's events land in the wrong window depending on how it was addressed.
8
- */
9
- import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult } from '@reticlehq/core';
1
+ import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult, type Predicate } from '@reticlehq/core';
10
2
  import type { FlowReplaySession, Sleep } from './flow-replay-types.js';
11
3
  export declare function runRoleStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
12
4
  kind: typeof AnchorKind.ROLE;
13
5
  }>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
14
- export declare function replayDestructiveActionHint(rawError: string): string;
6
+ /**
7
+ * Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
8
+ * including the action window, whose open/close must not depend on which anchor found the element.
9
+ */
10
+ export declare function actOnResolvedRef(session: FlowReplaySession, step: FlowStep, index: number, label: string, ref: string, confirmDangerous: boolean,
11
+ /**
12
+ * The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
13
+ * redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
14
+ * and looked up at replay under no name at all, so the flow types the placeholder into the form.
15
+ */
16
+ field?: string,
17
+ /**
18
+ * Resolve the anchor again. Supplied by the caller because only it knows how this step's anchor is
19
+ * found, and the retry below is worthless without it: re-dispatching the SAME dead ref fails
20
+ * identically. What changed between the two attempts is the DOM, so the locator has to be run
21
+ * against it again.
22
+ */
23
+ reresolve?: () => Promise<string | undefined>): Promise<FlowStepResult>;
15
24
  /** Run one component-anchored step: re-resolve via QUERY by:'component', ACT on the live ref, else drift. */
16
25
  export declare function runComponentStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
17
26
  kind: typeof AnchorKind.COMPONENT;
@@ -39,6 +48,13 @@ export declare function degradedStepResult(step: FlowStep, index: number, label:
39
48
  * the step runner's own resolution asking the same thing of the same anchor.
40
49
  */
41
50
  export declare function anchorQueryArgs(anchor: FlowAnchor): Record<string, unknown> | null;
51
+ /**
52
+ * The anchor as a PRECONDITION a flow file can store: "this element is on the page".
53
+ *
54
+ * What `requires` means for a flow that starts from state a page load discards, in the shape the
55
+ * flow's own `requires` holds. No tool writes `requires`, so the reset's hint hands over this line.
56
+ */
57
+ export declare function anchorPrecondition(anchor: FlowAnchor): Predicate | undefined;
42
58
  /**
43
59
  * Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
44
60
  * ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
@@ -6,7 +6,8 @@
6
6
  * point: the action window must open and close identically no matter which anchor found the element,
7
7
  * or a step's events land in the wrong window depending on how it was addressed.
8
8
  */
9
- import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
9
+ import { expectedElementTestid } from '@reticlehq/core';
10
+ import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, PredicateKind, QueryBy, ReticleCommand, } from '@reticlehq/core';
10
11
  import { ReticleTool } from '@reticlehq/core';
11
12
  import { replayActionArgs } from './replay.js';
12
13
  import { isStaleRefError } from '../../surface/tools/act/act-sequence-retry.js';
@@ -102,7 +103,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
102
103
  * to recognise the raw refusal still finds it, plus the fix for this specific caller.
103
104
  */
104
105
  const DESTRUCTIVE_ACTION_PATTERN = /potentially destructive (?:\w+ )*(?:action|tool) blocked/i;
105
- export function replayDestructiveActionHint(rawError) {
106
+ function replayDestructiveActionHint(rawError) {
106
107
  if (!DESTRUCTIVE_ACTION_PATTERN.test(rawError))
107
108
  return rawError;
108
109
  return (`${rawError} — this is a flow REPLAY: a step's own args.confirmDangerous is stripped before ` +
@@ -118,7 +119,7 @@ export function replayDestructiveActionHint(rawError) {
118
119
  * Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
119
120
  * including the action window, whose open/close must not depend on which anchor found the element.
120
121
  */
121
- async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
122
+ export async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
122
123
  /**
123
124
  * The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
124
125
  * redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
@@ -252,6 +253,21 @@ export function anchorQueryArgs(anchor) {
252
253
  return roleQueryArgs(anchor);
253
254
  return null;
254
255
  }
256
+ /**
257
+ * The anchor as a PRECONDITION a flow file can store: "this element is on the page".
258
+ *
259
+ * What `requires` means for a flow that starts from state a page load discards, in the shape the
260
+ * flow's own `requires` holds. No tool writes `requires`, so the reset's hint hands over this line.
261
+ */
262
+ export function anchorPrecondition(anchor) {
263
+ if (anchor.kind === AnchorKind.TESTID) {
264
+ return { kind: PredicateKind.ELEMENT, query: { testid: anchor.value } };
265
+ }
266
+ if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor)) {
267
+ return { kind: PredicateKind.ELEMENT, query: { role: anchor.role, name: anchor.name } };
268
+ }
269
+ return undefined;
270
+ }
255
271
  /**
256
272
  * Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
257
273
  * ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
@@ -321,7 +337,7 @@ dynamic = new Set()) {
321
337
  };
322
338
  /** The sub-step's own expectation, unless its testid is deliberately unasserted. */
323
339
  const declaredBy = (sub) => {
324
- const testid = sub.expect?.element?.testid;
340
+ const testid = expectedElementTestid(sub.expect);
325
341
  return testid === undefined || dynamic.has(testid) ? undefined : testid;
326
342
  };
327
343
  /**