@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
@@ -0,0 +1,119 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { PredicateKind } from '@reticlehq/core';
3
+ /**
4
+ * One journey, however many times it was driven.
5
+ *
6
+ * Every browser session saves its own flow at teardown, so a journey driven in five sessions was
7
+ * five near-identical files. Two flows are the SAME journey when they start on the same page and
8
+ * take the same steps: same tool, action, anchor and invocation, in the same order. What was typed
9
+ * and what was claimed do not decide it — a re-drive that fills a different value or declares one
10
+ * more consequence is the same journey, better described.
11
+ */
12
+ /** The anchor minus its source location, which moves when an unrelated line is edited. */
13
+ function anchorShape(anchor) {
14
+ const { source: _source, ...rest } = anchor;
15
+ return rest;
16
+ }
17
+ function stepShape(step) {
18
+ return [
19
+ step.tool,
20
+ step.action ?? null,
21
+ anchorShape(step.anchor),
22
+ step.invoke ?? null,
23
+ (step.steps ?? []).map(stepShape),
24
+ ];
25
+ }
26
+ const FINGERPRINT_LENGTH = 12;
27
+ /** A short, stable identity for a journey's shape: its start page and its steps. */
28
+ export function journeyFingerprint(flow) {
29
+ return createHash('sha256')
30
+ .update(JSON.stringify([flow.startPath ?? null, flow.steps.map(stepShape)]))
31
+ .digest('hex')
32
+ .slice(0, FINGERPRINT_LENGTH);
33
+ }
34
+ /** `kept` wins wherever it says something; `other` fills only what `kept` left unsaid. */
35
+ function fillStep(kept, other) {
36
+ if (other === undefined)
37
+ return kept;
38
+ const merged = { ...other, ...kept };
39
+ if (kept.steps !== undefined) {
40
+ merged.steps = kept.steps.map((sub, i) => fillStep(sub, other.steps?.[i]));
41
+ }
42
+ return merged;
43
+ }
44
+ /**
45
+ * Fold two copies of one journey into one file.
46
+ *
47
+ * Gap-filling, never overwriting: whatever the kept flow already says — its name, its intent, a
48
+ * step's declared consequence, a quarantine a person wrote — survives, and the other copy only
49
+ * supplies what is missing. So a merge can add an assertion or an intent and can never remove one.
50
+ */
51
+ export function mergeJourney(kept, other) {
52
+ return {
53
+ ...other,
54
+ ...kept,
55
+ steps: kept.steps.map((step, i) => fillStep(step, other.steps[i])),
56
+ };
57
+ }
58
+ /**
59
+ * The name a new journey saves under.
60
+ *
61
+ * Its readable name when that is free or already this journey's; otherwise the same name with the
62
+ * fingerprint's head, so two different journeys that share an intent never overwrite each other.
63
+ */
64
+ export function journeyName(base, fingerprint, taken) {
65
+ const owner = taken.get(base);
66
+ return owner === undefined || owner === fingerprint ? base : `${base}-${fingerprint.slice(0, 6)}`;
67
+ }
68
+ /**
69
+ * What must hold before a journey and what it leaves behind, read from where it started and ended.
70
+ *
71
+ * Routes only: the page is the one piece of state every recorded step knows for certain, and a
72
+ * claim written from a guess about the rest would be a precondition nobody met. Written in the
73
+ * exact form `canFollow` compares by value, so A-then-B is answerable from the two files alone.
74
+ */
75
+ export function routeClaims(startPath, steps) {
76
+ const first = startPath ?? steps[0]?.page;
77
+ const last = steps.at(-1);
78
+ const end = last?.endPage ?? last?.page;
79
+ return {
80
+ ...(first === undefined ? {} : { requires: [{ kind: PredicateKind.ROUTE, pathname: first }] }),
81
+ ...(end === undefined ? {} : { ensures: [{ kind: PredicateKind.ROUTE, pathname: end }] }),
82
+ };
83
+ }
84
+ /**
85
+ * The preconditions only another flow can establish.
86
+ *
87
+ * A route claim on the page the flow starts on is not one of them: replay navigates there itself, so
88
+ * it neither opts the flow out of the reset nor gates the run. That is the claim a saved drive writes
89
+ * for the map (see routeClaims) — reading it as "starts from leftover state" would stop every
90
+ * auto-saved flow from ever being reset.
91
+ */
92
+ export function establishedState(flow) {
93
+ const start = flow.startPath;
94
+ return (flow.requires ?? []).filter((claim) => !(start !== undefined &&
95
+ PredicateKind.ROUTE === claim.kind &&
96
+ claim.pathname !== undefined &&
97
+ samePath(claim.pathname, start)));
98
+ }
99
+ /**
100
+ * Is the tab where the flow asked to start? Up to a trailing slash, and up to the query the flow
101
+ * did not ask about.
102
+ *
103
+ * `startPath` is the SPECIFICATION, so it decides what counts. A query it recorded is compared:
104
+ * `?tab=wrap` and `?tab=summary` are different pages, and a replay that starts on the wrong one
105
+ * proves nothing about the right one. A query it did NOT record is ignored: the tab carrying
106
+ * `?next=%2F` on a login page, or the identity params Reticle puts on a leased tab, are not the flow
107
+ * being elsewhere, and navigating to strip them costs a session for nothing.
108
+ *
109
+ * The asymmetry is the whole point and the reason `observed` and `expected` are named rather than
110
+ * `a` and `b`. Comparing with the query on both sides always re-navigated a query-bearing
111
+ * `startPath` (#1059, which killed the session mid-flow); comparing with it on neither side reads a
112
+ * tab on `?tab=summary` as already at `?tab=wrap`.
113
+ */
114
+ export function samePath(observed, expected) {
115
+ const trimmed = (path) => path.replace(/\/$/, '');
116
+ const withoutQuery = (path) => path.replace(/\?[^#]*/, '');
117
+ const comparable = expected.includes('?') ? observed : withoutQuery(observed);
118
+ return trimmed(comparable) === trimmed(expected);
119
+ }
@@ -1,5 +1,6 @@
1
1
  import type { FlowReplayResult } from '@reticlehq/core';
2
2
  import type { ToolDeps } from '../../surface/tools/tool-kit.js';
3
+ import { withLearnedSources } from './learned-sources.js';
3
4
  /**
4
5
  * Write back what a replay taught the flow, once the replay has finished writing its own.
5
6
  *
@@ -50,3 +51,5 @@ export declare function replayAndLearn(deps: ToolDeps,
50
51
  * caught it. `seed` and anything added later travel the same path, so the whole object goes.
51
52
  */
52
53
  args: Record<string, unknown>): Promise<FlowReplayResult>;
54
+ /** Write back the files a clean replay's anchors resolved to. Best-effort, as `persistLearning` is. */
55
+ export declare function persistSources(deps: ToolDeps, args: Record<string, unknown>, sources: Parameters<typeof withLearnedSources>[1]): Promise<void>;
@@ -1,6 +1,16 @@
1
1
  import { asString } from '@reticlehq/core';
2
2
  import { flowsForSession } from './flow-store-for-session.js';
3
3
  import { replayNamedFlow } from './flow-replay-run.js';
4
+ import { recordingSources, withLearnedSources } from './learned-sources.js';
5
+ import { ReplayStatus } from '@reticlehq/core';
6
+ function projectOf(deps, args) {
7
+ try {
8
+ return deps.sessions.resolve(asString(args['sessionId'])).projectId;
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ }
4
14
  /**
5
15
  * Write back what a replay taught the flow, once the replay has finished writing its own.
6
16
  *
@@ -26,13 +36,7 @@ export async function persistLearning(deps, args, result) {
26
36
  if (0 === name.length)
27
37
  return result;
28
38
  try {
29
- let projectId;
30
- try {
31
- projectId = deps.sessions.resolve(asString(args['sessionId'])).projectId;
32
- }
33
- catch {
34
- projectId = undefined;
35
- }
39
+ const projectId = projectOf(deps, args);
36
40
  await flowsForSession(deps, projectId).flows.recordLearned(name, learned, projectId);
37
41
  }
38
42
  catch {
@@ -72,5 +76,32 @@ export async function replayAndLearn(deps,
72
76
  * caught it. `seed` and anything added later travel the same path, so the whole object goes.
73
77
  */
74
78
  args) {
75
- return await persistLearning(deps, args, await replayNamedFlow(deps, args));
79
+ let recorded;
80
+ const result = await replayNamedFlow(deps, args, (session) => {
81
+ recorded = recordingSources(session);
82
+ return recorded.session;
83
+ });
84
+ // Only a clean run teaches a file: a drifted step may have resolved to the wrong element.
85
+ if (result.status === ReplayStatus.OK && recorded !== undefined && recorded.sources.size > 0) {
86
+ await persistSources(deps, args, recorded.sources);
87
+ }
88
+ return await persistLearning(deps, args, result);
89
+ }
90
+ /** Write back the files a clean replay's anchors resolved to. Best-effort, as `persistLearning` is. */
91
+ export async function persistSources(deps, args, sources) {
92
+ const name = asString(args['flowName']) ?? '';
93
+ if (0 === name.length)
94
+ return;
95
+ try {
96
+ const projectId = projectOf(deps, args);
97
+ const store = flowsForSession(deps, projectId).flows;
98
+ const current = await store.load(name, projectId);
99
+ // Read first so a flow that already names every file is never rewritten.
100
+ if (!current.ok || withLearnedSources(current.value, sources) === undefined)
101
+ return;
102
+ await store.recordSources(name, sources, projectId);
103
+ }
104
+ catch {
105
+ // Bookkeeping only.
106
+ }
76
107
  }
@@ -1,15 +1,14 @@
1
- import { FlowErrorCode, type CommandResult, type FlowFile, type FlowReplayResult, type Contradiction, type FlowStepResult, type ReticleEvent } from '@reticlehq/core';
1
+ import { type CommandResult, type FlowFile, type FlowReplayResult, type Contradiction, type FlowStepResult, type ReticleEvent } from '@reticlehq/core';
2
2
  import type { ArrivalClock } from '../../surface/tools/act/navigation/navigate-arrival.js';
3
3
  import type { SessionManager } from '../../portal/session/session-manager.js';
4
4
  import type { Session } from '../../portal/session/session.js';
5
- import { DocumentLostDuringReplay } from './flow-replay.js';
5
+ import { DocumentLostDuringReplay, type FlowReplaySession } from './flow-replay.js';
6
6
  import type { ToolDeps } from '../../surface/tools/tool-kit.js';
7
7
  export declare function latestRecordedFlow(events: ReticleEvent[]): {
8
8
  name: string;
9
9
  flow: import('@reticlehq/core').FlowFile;
10
10
  } | undefined;
11
- /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
12
- export declare function flowErrorMessage(code: FlowErrorCode, detail?: string): string;
11
+ export { flowErrorMessage } from './flow-result.js';
13
12
  /** The slice of a session the start-path logic reads: the live URL plus the event buffer. */
14
13
  interface StartPathSession {
15
14
  url?: string;
@@ -25,21 +24,39 @@ interface StartPathSession {
25
24
  * navigation was refused or the SDK never reconnected in the window.
26
25
  */
27
26
  export declare function startPathMismatchHint(flow: FlowFile, session: StartPathSession): string | undefined;
27
+ /** What replay learned on its way to step 0. Both fields absent = the tab was left exactly as it was. */
28
+ export interface StartPathArrival {
29
+ /** The successor session replay must continue on. Absent when nothing was navigated, or arrival timed out. */
30
+ session?: Session;
31
+ /** Set when the RESET itself is why step 1 cannot start — the reason a step-1 red is not drift. */
32
+ resetCost?: string;
33
+ }
28
34
  /**
29
- * Replay's half of the FlowFile contract: navigate to the flow's `startPath` before step 1.
35
+ * Replay's half of the FlowFile contract: put the tab on the flow's `startPath` before step 1, and
36
+ * RELOAD it if it is already there.
37
+ *
38
+ * The reload is the state contract. Replaying "add a task" twice against a tab nobody reset gives
39
+ * the second run a world the recording never saw, and its assertions then describe the first run
40
+ * rather than the app. Our own `replay-determinism.mjs` has always refreshed before every run — the
41
+ * determinism we measured was never the determinism a user got.
42
+ *
43
+ * `requires` is the declared opt-out, and its first caller. A flow that says it starts from state
44
+ * another flow established is the one flow a page load would destroy, so it is never reset — here
45
+ * or from the wrong page. Silence is not an opt-out: a flow that depends on leftovers without
46
+ * saying so is a flow whose green means nothing on a fresh machine, and CI is a fresh machine.
30
47
  *
31
48
  * A full-page load tears down the session socket, so the navigation must happen here — before any
32
49
  * step runs — and the replay continues on the session the SDK reconnects as (found via the same
33
50
  * tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
34
- * when the flow carries no startPath, the tab is already there, the current route is unobservable,
35
- * the navigation is refused, or the SDK never reconnects in the window, it returns undefined and
36
- * replay proceeds on the connected session as before — with startPathMismatchHint turning any
37
- * resulting drift into an actionable next move rather than a mystifying one.
51
+ * when the flow carries no startPath, the current route is unobservable, the navigation is refused,
52
+ * or the SDK never reconnects in the window, replay proceeds on the connected session as before —
53
+ * with startPathMismatchHint turning any resulting drift into an actionable next move rather than a
54
+ * mystifying one.
38
55
  */
39
56
  export declare function arriveAtStartPath(sessions: SessionManager, session: StartPathSession & {
40
57
  id: string;
41
58
  command(name: string, args?: Record<string, unknown>): Promise<CommandResult>;
42
- }, flow: FlowFile, timeoutMs?: number, clock?: ArrivalClock): Promise<Session | undefined>;
59
+ }, flow: FlowFile, timeoutMs?: number, clock?: ArrivalClock): Promise<StartPathArrival>;
43
60
  /**
44
61
  * Send a tab somewhere and wait for the session that comes back.
45
62
  *
@@ -57,7 +74,9 @@ export declare function navigateAndAwait(sessions: SessionManager, session: {
57
74
  /** Optional because a session's URL is, and `carryReticleIdentity` already handles its absence. */
58
75
  url?: string;
59
76
  command(name: string, args?: Record<string, unknown>): Promise<CommandResult>;
60
- }, destination: string, expectedPath: string, timeoutMs?: number, clock?: ArrivalClock): Promise<Session | undefined>;
77
+ }, destination: string, expectedPath: string, timeoutMs?: number, clock?: ArrivalClock,
78
+ /** True when the destination is the page we are already on — see `arrivedSuccessor`. */
79
+ isReload?: boolean): Promise<Session | undefined>;
61
80
  /**
62
81
  * The honest answer when the document replay was driving went away mid-run.
63
82
  *
@@ -75,7 +94,9 @@ export declare function navigateAndAwait(sessions: SessionManager, session: {
75
94
  * with. `navigateAndAwait` CAN wait, because it knows it asked for the navigation, and it does.
76
95
  */
77
96
  export declare function lostDocumentResult(name: string, lost: DocumentLostDuringReplay): FlowReplayResult;
78
- export declare function replayNamedFlow(deps: ToolDeps, args: Record<string, unknown>): Promise<FlowReplayResult>;
97
+ export declare function replayNamedFlow(deps: ToolDeps, args: Record<string, unknown>,
98
+ /** Wraps the session the steps run on, so a caller can watch what they resolved. */
99
+ observe?: (session: FlowReplaySession) => FlowReplaySession): Promise<FlowReplayResult>;
79
100
  export declare function crossStepOnly(whole: readonly Contradiction[], steps: readonly FlowStepResult[]): Contradiction[];
80
101
  /**
81
102
  * The connecting session's project, or undefined when no browser is attached. Flow tools use it to
@@ -2,16 +2,17 @@ import { resolveFlowUploads } from './fields/flow-upload-resolve.js';
2
2
  import { learnFromRun } from '@reticlehq/engine/evidence/learned-guards.js';
3
3
  import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, ReticleCommand, RunKind, RunStatus, } from '@reticlehq/core';
4
4
  import { haltedFrom } from './recording/replay-halt.js';
5
+ import { establishedState, samePath } from './flow-journey.js';
5
6
  import { asRecord, asString } from '@reticlehq/core';
6
7
  import { routeOfEvent, routeOfUrl } from '@reticlehq/engine/question/predicate/predicate-route.js';
7
8
  import { carryReticleIdentity } from '../../surface/tools/lease-tools.js';
8
9
  import { replayFlow } from './flow-replay.js';
9
- import { anchorQueryArgs } from './flow-step-runners.js';
10
+ import { anchorPrecondition, anchorQueryArgs } from './flow-step-runners.js';
10
11
  import { queryRefs } from './replay.js';
11
12
  import { assertSuccess, dynamicTestids, successLabel, SUCCESS_STEP_TOOL } from './flow-success.js';
12
13
  import { buildDecision, unverifiableReason } from './decision.js';
13
14
  import { unsuppliedSecrets } from './fields/flow-secret-field.js';
14
- import { assertStepExpect, DocumentLostDuringReplay, } from './flow-replay.js';
15
+ import { assertStepExpect, DocumentLostDuringReplay, resumeArg, unresolvedResume, } from './flow-replay.js';
15
16
  import { isDocumentGoneError } from '../../portal/session/facts/session-replaced.js';
16
17
  import { classifyFlowAssertions, flattenSteps } from './flow-classify.js';
17
18
  import { dischargeFlowIntent, flowIntentStatement, flowReplayVerdictId } from './flow-intent.js';
@@ -31,7 +32,6 @@ import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
31
32
  import { log } from '../../log.js';
32
33
  import { flowsForSession } from './flow-store-for-session.js';
33
34
  import { projectForRoot } from '../../memory/project/project-for-root.js';
34
- import { FlowParseNote } from './flow-expect-grammar.js';
35
35
  export function latestRecordedFlow(events) {
36
36
  for (let i = events.length - 1; i >= 0; i--) {
37
37
  const event = events[i];
@@ -43,21 +43,8 @@ export function latestRecordedFlow(events) {
43
43
  }
44
44
  return undefined;
45
45
  }
46
- /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
47
- export function flowErrorMessage(code, detail) {
48
- if (FlowErrorCode.PARSE_FAILED === code && undefined !== detail)
49
- return detail;
50
- switch (code) {
51
- case FlowErrorCode.INVALID_NAME:
52
- return 'invalid flow name — use a single safe segment (letters/digits/-/_), no path separators';
53
- case FlowErrorCode.NOT_FOUND:
54
- return 'no such flow on disk — run reticle_flow{action:"list"} to see saved flows';
55
- case FlowErrorCode.PARSE_FAILED:
56
- return FlowParseNote.MALFORMED;
57
- case FlowErrorCode.NO_RECORDING:
58
- return 'no compiled recording by that name — record one (reticle_record{action:"start"|"stop"}) first';
59
- }
60
- }
46
+ export { flowErrorMessage } from './flow-result.js';
47
+ import { flowErrorMessage } from './flow-result.js';
61
48
  /** Map the wire ReplayStatus onto the persisted RunStatus (ok→pass). */
62
49
  function replayToRunStatus(status) {
63
50
  switch (status) {
@@ -106,20 +93,25 @@ recordRoot) {
106
93
  function currentPathOf(session) {
107
94
  const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
108
95
  const last = routes.at(-1);
109
- // pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE. Reading
110
- // the pathname alone made both sides `/` on a hash router — always "same path", so the hint never
111
- // fired however far the tab had drifted, on the router desktop renderers use by default.
96
+ // pathname + search + hash, because `startPath` is compared against this and must stay NAVIGABLE.
97
+ //
98
+ // Reading the pathname alone made both sides `/` on a hash router — always "same path", so the
99
+ // hint never fired however far the tab had drifted, on the router desktop renderers use by
100
+ // default. Leaving the SEARCH out was the mirror defect: `startPath` keeps its query, so a flow
101
+ // starting at `/admin/events/7?tab=wrap` never matched a tab sitting on exactly that, re-navigated
102
+ // on every replay, and the re-navigation killed the session mid-flow.
103
+ //
104
+ // Comparing without the query instead would have been worse in the direction that matters. The
105
+ // query usually decides what the page renders, so a tab on `?tab=summary` would read as "already
106
+ // at `?tab=wrap`" and the replay would start on the wrong page with nothing saying so. Both sides
107
+ // carry it, which keeps a real difference visible and stops the false one.
112
108
  const observed = last === undefined ? undefined : routeOfEvent(last);
113
109
  if (observed !== undefined)
114
- return `${observed.docPath}${observed.hash}`;
110
+ return `${observed.docPath}${observed.search}${observed.hash}`;
115
111
  if (session.url === undefined)
116
112
  return undefined;
117
113
  const fromUrl = routeOfUrl(session.url);
118
- return fromUrl === undefined ? undefined : `${fromUrl.docPath}${fromUrl.hash}`;
119
- }
120
- /** Pathname equality up to a trailing slash — a router normalising one must not read as "elsewhere". */
121
- function samePath(a, b) {
122
- return a.replace(/\/$/, '') === b.replace(/\/$/, '');
114
+ return fromUrl === undefined ? undefined : `${fromUrl.docPath}${fromUrl.search}${fromUrl.hash}`;
123
115
  }
124
116
  /**
125
117
  * Ask the project what it already knows about this flow.
@@ -201,9 +193,26 @@ const REAL_ARRIVAL_CLOCK = {
201
193
  * `resolve` throws). Keyed to the navigated tab's own identity on purpose: matching "any session at
202
194
  * the target page" would happily hand replay an unrelated tab that was already sitting there.
203
195
  */
204
- function arrivedSuccessor(sessions, oldId, target) {
196
+ function arrivedSuccessor(sessions, oldId, target,
197
+ /** The handle that issued the navigation, when only a genuinely new one counts as arrival. */
198
+ mustReplace) {
205
199
  try {
206
200
  const candidate = sessions.resolve(oldId);
201
+ /*
202
+ * A RELOAD lands on the path it left, so the path cannot tell the successor from the tab that
203
+ * is still tearing down — `resolve(oldId)` answers with the dying one while it is registered,
204
+ * which looks like instant arrival and hands replay a socket that will never answer again.
205
+ *
206
+ * The discriminator is the OBJECT, not the id. A leased tab carries its identity across a page
207
+ * load (that is what the identity params are for), so it reconnects under the SAME id — waiting
208
+ * for a new id there waits forever, and the first lease this was tried on timed out at step 1
209
+ * on a tab that was perfectly healthy. `SessionManager.add` replaces the instance on every
210
+ * reconnect, so a different instance is exactly "the socket came back".
211
+ *
212
+ * A navigation to a different route has the path as its discriminator and does not need this.
213
+ */
214
+ if (mustReplace !== undefined && candidate === mustReplace)
215
+ return undefined;
207
216
  const path = currentPathOf(candidate);
208
217
  return path !== undefined && samePath(path, target) ? candidate : undefined;
209
218
  }
@@ -242,36 +251,83 @@ async function firstStepResolvesHere(session, flow) {
242
251
  }
243
252
  }
244
253
  /**
245
- * Replay's half of the FlowFile contract: navigate to the flow's `startPath` before step 1.
254
+ * Replay's half of the FlowFile contract: put the tab on the flow's `startPath` before step 1, and
255
+ * RELOAD it if it is already there.
256
+ *
257
+ * The reload is the state contract. Replaying "add a task" twice against a tab nobody reset gives
258
+ * the second run a world the recording never saw, and its assertions then describe the first run
259
+ * rather than the app. Our own `replay-determinism.mjs` has always refreshed before every run — the
260
+ * determinism we measured was never the determinism a user got.
261
+ *
262
+ * `requires` is the declared opt-out, and its first caller. A flow that says it starts from state
263
+ * another flow established is the one flow a page load would destroy, so it is never reset — here
264
+ * or from the wrong page. Silence is not an opt-out: a flow that depends on leftovers without
265
+ * saying so is a flow whose green means nothing on a fresh machine, and CI is a fresh machine.
246
266
  *
247
267
  * A full-page load tears down the session socket, so the navigation must happen here — before any
248
268
  * step runs — and the replay continues on the session the SDK reconnects as (found via the same
249
269
  * tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
250
- * when the flow carries no startPath, the tab is already there, the current route is unobservable,
251
- * the navigation is refused, or the SDK never reconnects in the window, it returns undefined and
252
- * replay proceeds on the connected session as before — with startPathMismatchHint turning any
253
- * resulting drift into an actionable next move rather than a mystifying one.
270
+ * when the flow carries no startPath, the current route is unobservable, the navigation is refused,
271
+ * or the SDK never reconnects in the window, replay proceeds on the connected session as before —
272
+ * with startPathMismatchHint turning any resulting drift into an actionable next move rather than a
273
+ * mystifying one.
254
274
  */
255
275
  export async function arriveAtStartPath(sessions, session, flow, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK) {
256
276
  const target = flow.startPath;
257
277
  if (target === undefined || 0 === target.length)
258
- return undefined;
278
+ return {};
259
279
  const current = currentPathOf(session);
260
- if (current === undefined || samePath(current, target))
261
- return undefined;
262
- // The route differs — but that only matters if it stops the flow starting. See above.
263
- if (await firstStepResolvesHere(session, flow))
264
- return undefined;
280
+ if (current === undefined)
281
+ return {};
282
+ // The declared opt-out, both directions. See above.
283
+ if (0 < establishedState(flow).length)
284
+ return {};
285
+ const here = samePath(current, target);
286
+ // A route mismatch that step 1 can start from anyway is not worth a page load: navigating away
287
+ // from a persistent anchor could only hurt, and did. Arriving is the goal; resetting a tab that is
288
+ // ALREADY here is a different decision, taken above, and this must not override it.
289
+ if (!here && (await firstStepResolvesHere(session, flow)))
290
+ return {};
265
291
  let destination;
266
292
  try {
267
293
  // startPath is a pathname (a host belongs to the machine, not the journey) — resolve it
268
- // against the tab's own URL to get something the browser can be sent to.
269
- destination = new URL(target, session.url).toString();
294
+ // against the tab's own URL to get something the browser can be sent to. Already HERE, the
295
+ // reset reloads the page the tab is on: `samePath` ignored a query the flow did not record, and
296
+ // navigating to `target` would strip it, so the replay would test a different page than the one
297
+ // it just agreed it was on.
298
+ destination = new URL(here ? current : target, session.url).toString();
270
299
  }
271
300
  catch {
272
- return undefined; // no usable base URL — nowhere to navigate from
301
+ return {}; // no usable base URL — nowhere to navigate from
273
302
  }
274
- return navigateAndAwait(sessions, session, destination, target, timeoutMs, clock);
303
+ // Measured either side of the reset, and only when the reset is a reload: reachable before and
304
+ // gone after is the reload's doing, not the flow file's. Without this, an app holding its session
305
+ // in memory comes back signed out and step 1 blames a component that is completely fine.
306
+ const resolvedBefore = here && (await firstStepResolvesHere(session, flow));
307
+ const arrived = await navigateAndAwait(sessions, session, destination, target, timeoutMs, clock, here);
308
+ if (arrived === undefined)
309
+ return {};
310
+ if (!resolvedBefore || (await firstStepResolvesHere(arrived, flow)))
311
+ return { session: arrived };
312
+ return {
313
+ session: arrived,
314
+ resetCost: resetCostHint(flow, target),
315
+ };
316
+ }
317
+ /**
318
+ * Why step 1 failed after the reset, and the one line that opts the flow out.
319
+ *
320
+ * No tool writes `requires`, so naming the field was advice nobody could act on without guessing its
321
+ * shape. The first anchor IS the precondition: the element step 1 needs, present before it runs.
322
+ */
323
+ function resetCostHint(flow, target) {
324
+ const first = flow.steps?.[0];
325
+ const needs = first === undefined ? undefined : anchorPrecondition(first.anchor);
326
+ const optOut = needs === undefined
327
+ ? 'Declare what it needs (`requires`) in the flow file to opt out of the reset'
328
+ : `Add \`"requires":${JSON.stringify([needs])}\` to the flow file to opt out of the reset`;
329
+ return (`replay reloaded ${target} before step 1 and the first anchor did not come back — this flow ` +
330
+ `starts from state a page load discards. ${optOut}, or record it from a cold page`);
275
331
  }
276
332
  /**
277
333
  * Send a tab somewhere and wait for the session that comes back.
@@ -285,7 +341,9 @@ export async function arriveAtStartPath(sessions, session, flow, timeoutMs = STA
285
341
  * to move and this decides how, which is also why the deliberate short-circuits live up there and
286
342
  * not in here: a caller that has already decided it must move should not have to argue with them.
287
343
  */
288
- export async function navigateAndAwait(sessions, session, destination, expectedPath, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK) {
344
+ export async function navigateAndAwait(sessions, session, destination, expectedPath, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK,
345
+ /** True when the destination is the page we are already on — see `arrivedSuccessor`. */
346
+ isReload = false) {
289
347
  // A leased tab is addressed by URL params, so navigating without them would strand the lease.
290
348
  const url = carryReticleIdentity(session.url, destination);
291
349
  try {
@@ -303,7 +361,7 @@ export async function navigateAndAwait(sessions, session, destination, expectedP
303
361
  }
304
362
  const deadline = clock.now() + timeoutMs;
305
363
  for (;;) {
306
- const arrived = arrivedSuccessor(sessions, session.id, expectedPath);
364
+ const arrived = arrivedSuccessor(sessions, session.id, expectedPath, isReload ? session : undefined);
307
365
  if (arrived !== undefined)
308
366
  return arrived;
309
367
  if (clock.now() >= deadline)
@@ -358,10 +416,18 @@ async function loadInvokedFlows(deps, flow, projectId) {
358
416
  * a consequence of the first being absent.
359
417
  */
360
418
  async function firstUnmetPrecondition(session, flow, since) {
361
- for (const claim of flow.requires ?? []) {
419
+ for (const claim of establishedState(flow)) {
362
420
  // Zero budget: a precondition is a claim about the state you are starting FROM. Waiting for one
363
421
  // turns "was it true" into "did it become true", which is a different and much weaker question.
364
- const drift = await assertStepExpect(session, claim, new Set(), waitForPredicate, 0, since);
422
+ let drift;
423
+ try {
424
+ drift = await assertStepExpect(session, claim, waitForPredicate, 0, since);
425
+ }
426
+ catch (error) {
427
+ if (!isDocumentGoneError(error))
428
+ throw error;
429
+ return `the page went away while this flow's preconditions were being checked, so nothing ran and nothing was proved. Replay again once the page is back.`;
430
+ }
365
431
  if (drift !== undefined) {
366
432
  return `a precondition of this flow does not hold (${JSON.stringify(claim)}), so nothing ran and nothing was proved. Run the flow that establishes it first, or drive that state yourself.`;
367
433
  }
@@ -390,7 +456,8 @@ export function lostDocumentResult(name, lost) {
390
456
  status: ReplayStatus.OK,
391
457
  steps: lost.steps,
392
458
  unverifiable: {
393
- reason: `the page loaded a new document while step ${String(lost.atStep)} was running, so the run ` +
459
+ reason: `the page's connection to Reticle was replaced while step ${String(lost.atStep)} was running ` +
460
+ `(a new document loaded, or its socket reconnected), so the run ` +
394
461
  `could not be graded — the steps before it are reported and nothing after it was observed. ` +
395
462
  `Nothing here says the app failed. If that navigation is part of the journey, record the ` +
396
463
  `step's consequence with \`expect\` and replay again; if it was not, the flow started ` +
@@ -398,7 +465,9 @@ export function lostDocumentResult(name, lost) {
398
465
  },
399
466
  };
400
467
  }
401
- export async function replayNamedFlow(deps, args) {
468
+ export async function replayNamedFlow(deps, args,
469
+ /** Wraps the session the steps run on, so a caller can watch what they resolved. */
470
+ observe = (s) => s) {
402
471
  const startedAt = deps.now();
403
472
  const name = asString(args['flowName']) ?? '';
404
473
  // Resolve within the connecting app's scope so a shared daemon replays THIS project's flow, not a
@@ -438,8 +507,11 @@ export async function replayNamedFlow(deps, args) {
438
507
  // The FlowFile contract: replay navigates to the flow's startPath before step 1, and the steps run
439
508
  // on the session the SDK reconnects as. When arrival can't be confirmed, replay proceeds on the
440
509
  // connected session — and the hint below turns the wrong-page drift into an actionable next move.
441
- const session = (await arriveAtStartPath(deps.sessions, connected, loaded.value)) ?? connected;
442
- const startPathHint = startPathMismatchHint(loaded.value, session);
510
+ const arrival = await arriveAtStartPath(deps.sessions, connected, loaded.value);
511
+ const session = arrival.session ?? connected;
512
+ // The reset's own cost outranks the wrong-page hint: if the reload is why step 1 cannot start,
513
+ // "navigate there and replay" is advice that would do the same thing again.
514
+ const startPathHint = arrival.resetCost ?? startPathMismatchHint(loaded.value, session);
443
515
  // Floor the success oracle at the start of THIS replay so a stale signal from a prior run
444
516
  // in the same session can't fake a pass.
445
517
  const replayFloor = session.elapsed();
@@ -495,6 +567,12 @@ export async function replayNamedFlow(deps, args) {
495
567
  },
496
568
  };
497
569
  }
570
+ const from = resumeArg(args['from']);
571
+ const badResume = unresolvedResume(from, replayable.steps);
572
+ if (badResume !== undefined) {
573
+ const error = { code: FlowErrorCode.STEP_NOT_FOUND, message: badResume };
574
+ return { name: replayable.name, status: ReplayStatus.ERROR, steps: [], error };
575
+ }
498
576
  const unmet = await firstUnmetPrecondition(session, replayable, replayFloor);
499
577
  if (unmet !== undefined) {
500
578
  return {
@@ -506,7 +584,7 @@ export async function replayNamedFlow(deps, args) {
506
584
  }
507
585
  let steps;
508
586
  try {
509
- steps = await replayFlow(session, replayable, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous'], undefined, {
587
+ steps = await replayFlow(observe(session), replayable, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous'], undefined, {
510
588
  // How an `invoke` step finds the flow it runs. Scoped to the same project as the flow being
511
589
  // replayed, so a composite cannot reach into another app's store for a same-named sub-journey.
512
590
  resolveFlow: async (invoked) => {
@@ -516,6 +594,7 @@ export async function replayNamedFlow(deps, args) {
516
594
  // Bug-sweep mode: keep going past a step whose action ran and whose consequence merely did
517
595
  // not hold, so one flow reports one verdict per step instead of stopping at the first defect.
518
596
  sweep: true === args['sweep'],
597
+ ...(from === undefined ? {} : { from }),
519
598
  });
520
599
  }
521
600
  catch (error) {
@@ -26,6 +26,15 @@ export interface FlowReplaySession {
26
26
  onEvent(listener: (event: ReticleEvent) => void): () => void;
27
27
  /** Buffer clock (ms since connect) — required by the predicate engine's `settled` check. */
28
28
  elapsed(): number;
29
+ /**
30
+ * Where the app under test lives, so a third-party beacon is not mistaken for the app still
31
+ * working. Optional for the same reason `beginAction` is — a minimal test double should not have
32
+ * to invent an origin — and it FAILS OPEN: with no origin nothing is foreign, so an omitted value
33
+ * suppresses nothing rather than silently widening what a replay ignores.
34
+ */
35
+ url?: string;
36
+ /** Same-origin endpoints the project declared as background — see `Session.background`. */
37
+ background?: readonly string[];
29
38
  }
30
39
  /**
31
40
  * The injected predicate-waiter (the real waitForPredicate) — reused, never reimplemented.