@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
@@ -12,21 +12,36 @@
12
12
  * tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
13
13
  * Pure: no IO, no clock.
14
14
  */
15
- import { type FlowExpect, type FlowFile } from '@reticlehq/core';
16
- import type { EvalResult, Predicate } from '@reticlehq/engine/question/predicate/predicate.js';
15
+ import { type FlowExpect, type FlowFile, type Predicate } from '@reticlehq/core';
16
+ import type { EvalResult } from '@reticlehq/engine/question/predicate/predicate.js';
17
17
  import type { FlowReplaySession, WaitForSignal } from './flow-replay-types.js';
18
18
  /** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
19
19
  export declare function dynamicTestids(flow: FlowFile): Set<string>;
20
20
  /** The synthetic step.tool a replay appends when it asserts the flow's success oracle. */
21
21
  export declare const SUCCESS_STEP_TOOL = "success";
22
22
  /** A short human label for the success end-condition, for result rows. */
23
- export declare function successLabel(success: FlowExpect): string;
24
- /** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
25
- export declare function successToPredicate(success: FlowExpect, dynamic: ReadonlySet<string>): Predicate | undefined;
23
+ export declare function successLabel(success: Predicate | undefined): string;
24
+ /**
25
+ * What a flow's success oracle actually asserts, once the file has been read and the dynamic
26
+ * (LLM-output) testids have been taken out of it.
27
+ *
28
+ * Most of what this function used to do is gone, and that is the migration working as intended: it
29
+ * compiled a flat `FlowExpect` into a `Predicate`, and a step's `expect` IS a predicate now, lifted
30
+ * by the schema when the file is read. It keeps the name and the signature because it is on
31
+ * `@reticlehq/test`'s published surface, and it still accepts the flat shape for the same reason a
32
+ * v1 file still loads — a spec written against the old spelling is not wrong, it is old.
33
+ *
34
+ * What remains is the DYNAMIC SKIP, and it is not cosmetic. A testid whose content a model writes is
35
+ * asserted for PRESENCE by the step layer and never for content; a success oracle bound to one would
36
+ * assert exactly what the flow has already declared unassertable. Dropping every such clause can
37
+ * leave nothing at all, and `undefined` is the honest answer — vacuously met, the same as declaring
38
+ * no success, because nothing here was checkable.
39
+ */
40
+ export declare function successToPredicate(success: Predicate | FlowExpect | undefined, dynamic: ReadonlySet<string>): Predicate | undefined;
26
41
  /**
27
42
  * Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
28
43
  * real waitForPredicate in production, a fake in unit tests). Passes when: no success was declared,
29
44
  * OR every success field was dynamic-skipped (vacuously met), OR the compiled predicate held within
30
45
  * the injected timeout. Never reads the wall clock.
31
46
  */
32
- export declare function assertSuccess(session: FlowReplaySession, success: FlowExpect | undefined, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since?: number): Promise<EvalResult>;
47
+ export declare function assertSuccess(session: FlowReplaySession, success: Predicate | undefined, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since?: number): Promise<EvalResult>;
@@ -12,7 +12,7 @@
12
12
  * tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
13
13
  * Pure: no IO, no clock.
14
14
  */
15
- import { AnchorKind, PredicateKind } from '@reticlehq/core';
15
+ import { AnchorKind, PredicateKind, clauseOfKind, flowExpectToPredicate, withoutClauses, } from '@reticlehq/core';
16
16
  /** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
17
17
  export function dynamicTestids(flow) {
18
18
  return new Set((flow.dynamic ?? [])
@@ -23,145 +23,59 @@ export function dynamicTestids(flow) {
23
23
  export const SUCCESS_STEP_TOOL = 'success';
24
24
  /** A short human label for the success end-condition, for result rows. */
25
25
  export function successLabel(success) {
26
- if (success.signal !== undefined)
27
- return success.signal;
28
- if (success.net !== undefined)
29
- return success.net.urlContains ?? success.net.method ?? 'net';
30
- if (success.console !== undefined) {
31
- return `console:${true === success.console.absent ? 'clean' : (success.console.level ?? 'error')}`;
26
+ /*
27
+ * NOT `expectLabel`, and the difference is the reader.
28
+ *
29
+ * That one names a clause for a drift row, where `signal:` tells you which channel failed. This
30
+ * one is `mustHold` in the domain model an agent reads BEFORE testing — a list of what each flow
31
+ * guarantees — and there the prefix is noise on the commonest case. A signal's own name is the
32
+ * sentence: "order:placed" is what must hold.
33
+ */
34
+ const signal = clauseOfKind(success, PredicateKind.SIGNAL);
35
+ if (signal?.name !== undefined)
36
+ return signal.name;
37
+ const net = clauseOfKind(success, PredicateKind.NET);
38
+ if (net !== undefined)
39
+ return net.urlContains ?? net.method ?? 'net';
40
+ const console_ = clauseOfKind(success, PredicateKind.CONSOLE);
41
+ if (console_ !== undefined) {
42
+ return `console:${true === console_.absent ? 'clean' : (console_.level ?? 'error')}`;
32
43
  }
33
- if (success.state !== undefined)
34
- return `state:${success.state.path}`;
35
- if (success.text !== undefined)
36
- return `text:${success.text.contains}`;
37
- return success.element?.testid ?? success.element?.name ?? success.element?.role ?? 'success';
44
+ const state = clauseOfKind(success, PredicateKind.STATE);
45
+ if (state !== undefined)
46
+ return `state:${state.path}`;
47
+ const text = clauseOfKind(success, PredicateKind.TEXT);
48
+ if (text?.contains !== undefined)
49
+ return `text:${text.contains}`;
50
+ const element = clauseOfKind(success, PredicateKind.ELEMENT);
51
+ const q = element?.query;
52
+ return q?.testid ?? q?.name ?? q?.role ?? 'success';
38
53
  }
39
- /** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
54
+ /**
55
+ * What a flow's success oracle actually asserts, once the file has been read and the dynamic
56
+ * (LLM-output) testids have been taken out of it.
57
+ *
58
+ * Most of what this function used to do is gone, and that is the migration working as intended: it
59
+ * compiled a flat `FlowExpect` into a `Predicate`, and a step's `expect` IS a predicate now, lifted
60
+ * by the schema when the file is read. It keeps the name and the signature because it is on
61
+ * `@reticlehq/test`'s published surface, and it still accepts the flat shape for the same reason a
62
+ * v1 file still loads — a spec written against the old spelling is not wrong, it is old.
63
+ *
64
+ * What remains is the DYNAMIC SKIP, and it is not cosmetic. A testid whose content a model writes is
65
+ * asserted for PRESENCE by the step layer and never for content; a success oracle bound to one would
66
+ * assert exactly what the flow has already declared unassertable. Dropping every such clause can
67
+ * leave nothing at all, and `undefined` is the honest answer — vacuously met, the same as declaring
68
+ * no success, because nothing here was checkable.
69
+ */
40
70
  export function successToPredicate(success, dynamic) {
41
- const parts = [];
42
- if (success.signal !== undefined) {
43
- const signal = {
44
- kind: PredicateKind.SIGNAL,
45
- name: success.signal,
46
- };
47
- if (success.signalData !== undefined)
48
- signal.dataMatches = success.signalData;
49
- if (success.signalCount !== undefined) {
50
- signal.count = success.signalCount;
51
- // Same post-settle reasoning as net.count: the success waiter is wait-until-true, so an exact
52
- // count is transiently satisfied the instant the FIRST matching signal fires — before a
53
- // double-fire's duplicate arrives. Gating on `settled` forces the count to be read only after
54
- // the app goes quiet, by which point the duplicate IS counted and the over-count fails.
55
- parts.push({ kind: PredicateKind.SETTLED });
56
- }
57
- parts.push(signal);
58
- }
59
- if (success.route !== undefined) {
60
- // The mirror of `predicateToExpect`'s ROUTE case. Replay evaluates predicates, so carrying the
61
- // route back here is the whole of what makes a recorded navigation able to fail.
62
- const route = {
63
- kind: PredicateKind.ROUTE,
64
- };
65
- if (success.route.pathname !== undefined)
66
- route.pathname = success.route.pathname;
67
- if (success.route.contains !== undefined)
68
- route.contains = success.route.contains;
69
- return route;
70
- }
71
- if (success.net !== undefined) {
72
- const net = { kind: PredicateKind.NET };
73
- if (success.net.method !== undefined)
74
- net.method = success.net.method;
75
- if (success.net.urlContains !== undefined)
76
- net.urlContains = success.net.urlContains;
77
- if (success.net.status !== undefined)
78
- net.status = success.net.status;
79
- if (success.net.count !== undefined) {
80
- net.count = success.net.count;
81
- // A cardinality assertion is inherently POST-SETTLE. The success waiter is wait-until-true, so an
82
- // exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
83
- // before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
84
- // only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
85
- parts.push({ kind: PredicateKind.SETTLED });
86
- }
87
- parts.push(net);
88
- }
89
- if (success.console !== undefined) {
90
- const con = {
91
- kind: PredicateKind.CONSOLE,
92
- };
93
- if (success.console.level !== undefined)
94
- con.level = success.console.level;
95
- if (success.console.absent !== undefined) {
96
- con.absent = success.console.absent;
97
- // An `absent` assertion is post-settle, same as net.count: a wait-until-true waiter sees "no
98
- // error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
99
- // the console is read only after the page quiets, by which point any error is in the buffer.
100
- if (success.console.absent)
101
- parts.push({ kind: PredicateKind.SETTLED });
102
- }
103
- parts.push(con);
104
- }
105
- const state = success.state;
106
- if (state !== undefined) {
107
- const part = {
108
- kind: PredicateKind.STATE,
109
- path: state.path,
110
- };
111
- if (state.store !== undefined)
112
- part.store = state.store;
113
- if (state.equals !== undefined)
114
- part.equals = state.equals;
115
- // `hold` = an INVARIANT ("this state must still hold after the action settles"), vs the default
116
- // wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
117
- // value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
118
- // on `settled` so the read happens after the page quiets, by which point the leak has landed.
119
- if (true === state.hold)
120
- parts.push({ kind: PredicateKind.SETTLED });
121
- parts.push(part);
122
- }
123
- const element = success.element;
124
- if (element !== undefined) {
125
- const testid = element.testid;
126
- // A dynamic-marked testid is NOT asserted as a success condition (presence-only).
127
- if (testid === undefined || !dynamic.has(testid)) {
128
- const query = {};
129
- if (testid !== undefined)
130
- query['testid'] = testid;
131
- if (element.role !== undefined)
132
- query['role'] = element.role;
133
- if (element.name !== undefined)
134
- query['name'] = element.name;
135
- if (Object.keys(query).length > 0)
136
- parts.push({ kind: PredicateKind.ELEMENT, query });
137
- }
138
- }
139
- const text = success.text;
140
- if (text !== undefined) {
141
- const part = {
142
- kind: PredicateKind.TEXT,
143
- contains: text.contains,
144
- };
145
- if (text.scope !== undefined)
146
- part.scope = text.scope;
147
- if (text.visible !== undefined)
148
- part.visible = text.visible;
149
- if (true === text.absent) {
150
- part.absent = true;
151
- // Same post-settle reasoning as `console.absent` and `state.hold`: a wait-until-true waiter
152
- // reads "not there yet" on the first poll and passes BEFORE the text it is meant to see
153
- // disappear has even been rendered. Gate on `settled` so the read happens after the page
154
- // quiets, by which point a text that was going to appear has.
155
- parts.push({ kind: PredicateKind.SETTLED });
156
- }
157
- parts.push(part);
158
- }
159
- const [first] = parts;
160
- if (0 === parts.length)
71
+ if (success === undefined)
161
72
  return undefined;
162
- if (1 === parts.length && first !== undefined)
163
- return first;
164
- return { kind: PredicateKind.ALL_OF, predicates: parts };
73
+ const predicate = 'kind' in success ? success : flowExpectToPredicate(success);
74
+ if (predicate === undefined)
75
+ return undefined;
76
+ return withoutClauses(predicate, (clause) => PredicateKind.ELEMENT === clause.kind &&
77
+ clause.query.testid !== undefined &&
78
+ dynamic.has(clause.query.testid));
165
79
  }
166
80
  /**
167
81
  * Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
@@ -170,8 +84,6 @@ export function successToPredicate(success, dynamic) {
170
84
  * the injected timeout. Never reads the wall clock.
171
85
  */
172
86
  export async function assertSuccess(session, success, dynamic, waitForSignal, timeoutMs, since = 0) {
173
- if (success === undefined)
174
- return { pass: true };
175
87
  const predicate = successToPredicate(success, dynamic);
176
88
  if (predicate === undefined)
177
89
  return { pass: true };
@@ -1,3 +1,4 @@
1
+ import { flowAuthor } from './flow-author.js';
1
2
  import { FLOW_MUTATE_TOOL } from './flow-mutate-tools.js';
2
3
  import { seedSchema } from '../../surface/tools/args/numeric-bounds.js';
3
4
  import { z } from 'zod';
@@ -217,7 +218,12 @@ export const FLOW_TOOLS = [
217
218
  if (emptyRefusal !== undefined)
218
219
  return Promise.resolve(emptyRefusal);
219
220
  const { flows, root } = flowsForSession(deps, projectId);
220
- const toSave = saveAs === undefined ? program : { ...program, name: saveAs };
221
+ const author = flowAuthor();
222
+ const toSave = {
223
+ ...program,
224
+ ...(saveAs === undefined ? {} : { name: saveAs }),
225
+ ...(author === undefined ? {} : { author }),
226
+ };
221
227
  return flows.save(toSave, annotations, projectId).then(async (res) => {
222
228
  if (!res.ok)
223
229
  return { error: flowErrorMessage(res.code, res.detail), code: res.code };
@@ -382,6 +388,10 @@ export const FLOW_TOOLS = [
382
388
  .boolean()
383
389
  .optional()
384
390
  .describe('Set true to allow destructive controls during this replay only.'),
391
+ from: z
392
+ .union([z.number().int().nonnegative(), z.string().min(1)])
393
+ .optional()
394
+ .describe('Resume at this step (index or step id). Earlier steps re-run as unchecked, unreported setup; a setup step marked effect:"commits" refuses the resume.'),
385
395
  sweep: z
386
396
  .boolean()
387
397
  .optional()
@@ -636,9 +646,7 @@ export const FLOW_TOOLS = [
636
646
  return flow === undefined ? { replay } : { replay, flow };
637
647
  }));
638
648
  const timed = outcomes.map((o, i) => ({
639
- replay: o.ok && o.value !== undefined
640
- ? o.value.replay
641
- : leaseFailureReplay(requested[i] ?? '', o.error),
649
+ ...(parallelRuns[i] ?? { replay: leaseFailureReplay(requested[i] ?? '', o.error) }),
642
650
  durationMs: o.ok && o.value !== undefined ? o.value.durationMs : 0,
643
651
  }));
644
652
  const flaky = await recordSuiteFlakes(deps.fs, rootForProjectId(deps, projectId), parallelRuns);
@@ -671,7 +679,11 @@ export const FLOW_TOOLS = [
671
679
  .catch(() => null);
672
680
  const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
673
681
  runs.push(flow === undefined ? { replay } : { replay, flow });
674
- timed.push({ replay, durationMs: deps.now() - start });
682
+ timed.push({
683
+ replay,
684
+ durationMs: deps.now() - start,
685
+ ...(flow === undefined ? {} : { flow }),
686
+ });
675
687
  }
676
688
  // The flake ledger the CLI gate already keeps — see FlakeStore. It was only ever written by
677
689
  // `reticle flow` on the command line, so an AGENT running this tool a hundred times learned
@@ -1,8 +1,8 @@
1
- import { type ProjectId } from '@reticlehq/core';
2
- export { REDACTED_FILL } from './fields/flow-secret-field.js';
1
+ import { type Predicate, type ProjectId } from '@reticlehq/core';
2
+ export { REDACTED_FILL, secretEnvKey } from './fields/flow-secret-field.js';
3
3
  import { type FlowResult } from './flow-result.js';
4
4
  export type { FlowResult } from './flow-result.js';
5
- import type { FlowAnchor, FlowExpect, FlowFile, FlowStep, HealChange, InstrumentationGap } from '@reticlehq/core';
5
+ import type { FlowAnchor, FlowFile, FlowStep, HealChange, InstrumentationGap } from '@reticlehq/core';
6
6
  import type { CompiledProgram, RecordedStep } from './recording/tape/recordings.js';
7
7
  import type { FileSystemPort } from '../../memory/project/fs/fs-port.js';
8
8
  import type { Clock } from '../../machine/clock.js';
@@ -49,9 +49,9 @@ interface SaveSummary {
49
49
  * the same bytes as before.
50
50
  */
51
51
  export interface FlowAnnotations {
52
- stepExpect: Map<number, FlowExpect>;
52
+ stepExpect: Map<number, Predicate>;
53
53
  dynamic: string[];
54
- success?: FlowExpect;
54
+ success?: Predicate;
55
55
  /** The flow's declared business goal (intent annotation). */
56
56
  intent?: string;
57
57
  }
@@ -65,6 +65,15 @@ export declare class FlowStore {
65
65
  * the flow before writing. Omitting `annotations` reproduces the same bytes.
66
66
  */
67
67
  save(program: CompiledProgram, annotations?: FlowAnnotations, projectId?: ProjectId): Promise<FlowResult<SaveSummary>>;
68
+ /**
69
+ * Save a flow the session drove, folded into any saved copy of the same journey.
70
+ *
71
+ * Every session saves its own drive, so one journey arrived as a near-copy per session. Flows
72
+ * under `prefix` (the auto-saved ones — a hand-named flow is never merged or removed) that start
73
+ * on the same page and take the same steps become ONE file: the oldest keeps its name, the others
74
+ * fill its gaps and are removed. The same scan collapses copies saved before this existed.
75
+ */
76
+ saveJourney(program: CompiledProgram, projectId: ProjectId | undefined, prefix: string): Promise<FlowResult<SaveSummary>>;
68
77
  /**
69
78
  * Persist an already-anchored FlowFile captured in-page (no recompilation). The
70
79
  * browser resolved every semantic anchor at capture time; here we only validate the name +
@@ -75,6 +84,10 @@ export declare class FlowStore {
75
84
  recordLearned(name: string, learned: NonNullable<FlowFile['learned']>, projectId?: ProjectId): Promise<FlowResult<{
76
85
  name: string;
77
86
  }>>;
87
+ /** Give sourceless steps the files a clean replay resolved them to, merged onto the file as it is now. */
88
+ recordSources(name: string, sources: ReadonlyMap<string, NonNullable<FlowStep['source']>>, projectId?: ProjectId): Promise<FlowResult<{
89
+ name: string;
90
+ }>>;
78
91
  /**
79
92
  * Apply confident testid rebinds to an on-disk flow (the `reticle_flow_heal` apply path),
80
93
  * rewriting ONLY the named steps' anchors and preserving every other field. Loading, the name
@@ -1,13 +1,15 @@
1
- import { asFlowName, asProjectId } from '@reticlehq/core';
1
+ import { asFlowName, asProjectId, } from '@reticlehq/core';
2
2
  import { REDACTED_FILL } from './fields/flow-secret-field.js';
3
- export { REDACTED_FILL } from './fields/flow-secret-field.js';
3
+ export { REDACTED_FILL, secretEnvKey } from './fields/flow-secret-field.js';
4
4
  import { safeProjectId } from './flow-result.js';
5
5
  import { changeInPlace } from './narrow-write.js';
6
- import { AnchorKind, DEGRADED_ANCHOR_ROLE, FLOW_FILE_VERSION, FlowErrorCode, FlowFileSchema, FlowStepTool, QueryBy, defaultIsSensitiveKey, } from '@reticlehq/core';
6
+ import { AnchorKind, DEGRADED_ANCHOR_ROLE, FLOW_FILE_VERSION, flowFileVersionFor, FlowErrorCode, FlowFileSchema, FlowStepTool, QueryBy, defaultIsSensitiveKey, } from '@reticlehq/core';
7
7
  import { ReticleTool } from '@reticlehq/core';
8
8
  import { asRecord, asString } from '@reticlehq/core';
9
9
  import { applyHealChanges } from './heal.js';
10
+ import { withLearnedSources } from './learned-sources.js';
10
11
  import { flowIntentGap, linkFlowIntent } from './flow-intent.js';
12
+ import { journeyFingerprint, journeyName, mergeJourney, routeClaims } from './flow-journey.js';
11
13
  import { IntentStore } from '../../memory/intent/intent-store.js';
12
14
  import { flowDir, flowParentDir, flowPath, reticleDirPaths, isValidFlowName, } from '../../memory/project/dir/reticle-dir.js';
13
15
  import { describeFlowZodFailure, parseFlowFileText } from './flow-expect-grammar.js';
@@ -163,6 +165,14 @@ function buildStep(tool, anchor, action, args, degraded) {
163
165
  * NEVER silently dropped. ACT_SEQUENCE recurses over its sub-steps.
164
166
  */
165
167
  export function recordedStepToFlowStep(step) {
168
+ const out = anchoredStep(step);
169
+ if (step.page !== undefined)
170
+ out.page = step.page;
171
+ if (step.endPage !== undefined && step.endPage !== step.page)
172
+ out.endPage = step.endPage;
173
+ return out;
174
+ }
175
+ function anchoredStep(step) {
166
176
  if (step.invoke !== undefined) {
167
177
  // An invocation drives nothing: no action, no args, and an anchor only because every step
168
178
  // carries one. Falling through to the action path below would give it an `action` and a
@@ -236,7 +246,8 @@ export class FlowStore {
236
246
  * of them produces byte-identical on-disk content (locked by the byte-stability tests).
237
247
  */
238
248
  #serialize(flow) {
239
- return `${JSON.stringify(flow, null, JSON_INDENT)}\n`;
249
+ const stamped = { ...flow, version: flowFileVersionFor(flow) };
250
+ return `${JSON.stringify(stamped, null, JSON_INDENT)}\n`;
240
251
  }
241
252
  /**
242
253
  * Register the flow's business goal in the intent ledger and stamp the row's id onto the flow.
@@ -266,16 +277,8 @@ export class FlowStore {
266
277
  ...(gap === undefined ? {} : { intentGap: gap }),
267
278
  };
268
279
  }
269
- /**
270
- * Convert a CompiledProgram (testid-normalized) into an anchored, on-disk flow + write it.
271
- * Optionally fold structured annotations (per-step expect, dynamic[], success) onto
272
- * the flow before writing. Omitting `annotations` reproduces the same bytes.
273
- */
274
- async save(program, annotations, projectId) {
275
- if (!isValidFlowName(program.name)) {
276
- return { ok: false, code: FlowErrorCode.INVALID_NAME };
277
- }
278
- const pid = safeProjectId(projectId);
280
+ /** The on-disk flow a compiled recording becomes, before anything is written. */
281
+ #build(program, annotations, pid) {
279
282
  const steps = program.steps.map(recordedStepToFlowStep);
280
283
  const base = {
281
284
  version: FLOW_FILE_VERSION,
@@ -294,11 +297,85 @@ export class FlowStore {
294
297
  */
295
298
  ...(program.startPath === undefined ? {} : { startPath: program.startPath }),
296
299
  };
297
- const flow = await this.#linkIntent(withAnnotations(base, annotations));
300
+ const intent = program.steps.find((step) => step.intent !== undefined)?.intent;
301
+ const annotated = withAnnotations(base, annotations);
302
+ return {
303
+ ...annotated,
304
+ // The action's own intent, when nobody annotated the flow — see RecordedStep.intent.
305
+ ...(annotated.intent === undefined && intent !== undefined ? { intent } : {}),
306
+ ...routeClaims(annotated.startPath, annotated.steps),
307
+ ...(program.author === undefined ? {} : { author: program.author }),
308
+ };
309
+ }
310
+ /**
311
+ * Convert a CompiledProgram (testid-normalized) into an anchored, on-disk flow + write it.
312
+ * Optionally fold structured annotations (per-step expect, dynamic[], success) onto
313
+ * the flow before writing. Omitting `annotations` reproduces the same bytes.
314
+ */
315
+ async save(program, annotations, projectId) {
316
+ if (!isValidFlowName(program.name)) {
317
+ return { ok: false, code: FlowErrorCode.INVALID_NAME };
318
+ }
319
+ const pid = safeProjectId(projectId);
320
+ const base = this.#build(program, annotations, pid);
321
+ const flow = await this.#linkIntent(base);
298
322
  await this.#fs.mkdir(flowParentDir(this.#root, program.name, pid));
299
323
  await this.#fs.writeFile(flowPath(this.#root, program.name, pid), this.#serialize(flow));
300
324
  return { ok: true, value: this.#summary(flow) };
301
325
  }
326
+ /**
327
+ * Save a flow the session drove, folded into any saved copy of the same journey.
328
+ *
329
+ * Every session saves its own drive, so one journey arrived as a near-copy per session. Flows
330
+ * under `prefix` (the auto-saved ones — a hand-named flow is never merged or removed) that start
331
+ * on the same page and take the same steps become ONE file: the oldest keeps its name, the others
332
+ * fill its gaps and are removed. The same scan collapses copies saved before this existed.
333
+ */
334
+ async saveJourney(program, projectId, prefix) {
335
+ if (!isValidFlowName(program.name))
336
+ return { ok: false, code: FlowErrorCode.INVALID_NAME };
337
+ const pid = safeProjectId(projectId);
338
+ const fresh = this.#build(program, undefined, pid);
339
+ const saved = [];
340
+ for (const name of (await this.list(pid)).filter((n) => n.startsWith(prefix))) {
341
+ const loaded = await this.load(name, pid);
342
+ if (loaded.ok)
343
+ saved.push(loaded.value);
344
+ }
345
+ const byPrint = new Map();
346
+ for (const flow of saved) {
347
+ const print = journeyFingerprint(flow);
348
+ byPrint.set(print, [...(byPrint.get(print) ?? []), flow]);
349
+ }
350
+ const print = journeyFingerprint(fresh);
351
+ const taken = new Map(saved.map((flow) => [flow.name, journeyFingerprint(flow)]));
352
+ const named = { ...fresh, name: journeyName(fresh.name, print, taken) };
353
+ byPrint.set(print, [...(byPrint.get(print) ?? []), named]);
354
+ let result = named;
355
+ for (const [groupPrint, group] of byPrint) {
356
+ if (group.length < 2 && groupPrint !== print)
357
+ continue;
358
+ const [kept, ...rest] = [...group].sort((a, b) => a.createdAt - b.createdAt);
359
+ if (kept === undefined)
360
+ continue;
361
+ const merged = await this.#linkIntent(rest.reduce(mergeJourney, kept));
362
+ await this.#writeInPlace(merged, pid);
363
+ for (const dropped of rest) {
364
+ if (dropped.name !== merged.name && dropped !== named)
365
+ await this.remove(dropped.name, pid);
366
+ }
367
+ if (groupPrint === print)
368
+ result = merged;
369
+ }
370
+ return { ok: true, value: this.#summary(result) };
371
+ }
372
+ /** Write a flow where it already lives, or into its project's directory when it is new. */
373
+ async #writeInPlace(flow, pid) {
374
+ const name = asFlowName(flow.name);
375
+ const path = (await this.#resolveReadPath(name, pid)) ?? flowPath(this.#root, name, pid);
376
+ await this.#fs.mkdir(flowParentDir(this.#root, name, pid));
377
+ await this.#fs.writeFile(path, this.#serialize(flow));
378
+ }
302
379
  /**
303
380
  * Persist an already-anchored FlowFile captured in-page (no recompilation). The
304
381
  * browser resolved every semantic anchor at capture time; here we only validate the name +
@@ -351,6 +428,13 @@ export class FlowStore {
351
428
  value: { name },
352
429
  }));
353
430
  }
431
+ /** Give sourceless steps the files a clean replay resolved them to, merged onto the file as it is now. */
432
+ async recordSources(name, sources, projectId) {
433
+ return await this.#changeInPlace(name, projectId, (flow) => ({
434
+ next: withLearnedSources(flow, sources) ?? flow,
435
+ value: { name },
436
+ }));
437
+ }
354
438
  /**
355
439
  * Apply confident testid rebinds to an on-disk flow (the `reticle_flow_heal` apply path),
356
440
  * rewriting ONLY the named steps' anchors and preserving every other field. Loading, the name
@@ -362,7 +446,8 @@ export class FlowStore {
362
446
  */
363
447
  async heal(name, changes, projectId) {
364
448
  return await this.#changeInPlace(name, projectId, (flow) => {
365
- const { flow: next, applied } = applyHealChanges(flow, changes);
449
+ // The store's injected clock, so a written flow records when the rebind actually happened.
450
+ const { flow: next, applied } = applyHealChanges(flow, changes, () => this.#clock.now());
366
451
  return { next, value: { name, changed: applied } };
367
452
  });
368
453
  }
@@ -25,7 +25,15 @@ export declare function confidenceFor(from: string, to: string): number;
25
25
  * skipped (idempotent / defensive), never throwing. Shared by the on-disk writer (FlowStore.heal)
26
26
  * and the in-memory pre-write verification in reticle_flow_heal, so both rewrite identically.
27
27
  */
28
- export declare function applyHealChanges(flow: FlowFile, changes: HealChange[]): {
28
+ export declare function applyHealChanges(flow: FlowFile, changes: HealChange[],
29
+ /**
30
+ * When the rebind happened. Injected rather than read here, because this function is pure and a
31
+ * clock inside it would make the flow it returns depend on when a test ran.
32
+ *
33
+ * Optional so every existing caller keeps working; without it the step still records WHICH anchor
34
+ * was replaced, which is the half a reader actually acts on.
35
+ */
36
+ now?: () => number): {
29
37
  flow: FlowFile;
30
38
  applied: HealChange[];
31
39
  };
@@ -38,7 +38,15 @@ export function confidenceFor(from, to) {
38
38
  * skipped (idempotent / defensive), never throwing. Shared by the on-disk writer (FlowStore.heal)
39
39
  * and the in-memory pre-write verification in reticle_flow_heal, so both rewrite identically.
40
40
  */
41
- export function applyHealChanges(flow, changes) {
41
+ export function applyHealChanges(flow, changes,
42
+ /**
43
+ * When the rebind happened. Injected rather than read here, because this function is pure and a
44
+ * clock inside it would make the flow it returns depend on when a test ran.
45
+ *
46
+ * Optional so every existing caller keeps working; without it the step still records WHICH anchor
47
+ * was replaced, which is the half a reader actually acts on.
48
+ */
49
+ now) {
42
50
  const byStep = new Map();
43
51
  for (const change of changes)
44
52
  byStep.set(change.step, change);
@@ -51,7 +59,21 @@ export function applyHealChanges(flow, changes) {
51
59
  return step;
52
60
  }
53
61
  applied.push(change);
54
- return { ...step, anchor: { kind: AnchorKind.TESTID, value: change.to } };
62
+ return {
63
+ ...step,
64
+ anchor: { kind: AnchorKind.TESTID, value: change.to },
65
+ /*
66
+ * `from` is the ORIGINAL recorded anchor, kept across later heals.
67
+ *
68
+ * A step healed twice has an intermediate name nobody ever chose, so overwriting with the
69
+ * newest would throw away the only anchor a reader wants — the one that was recorded by
70
+ * driving. The timestamp is the LATEST heal, because that is when this anchor arrived.
71
+ */
72
+ healed: {
73
+ from: step.healed?.from ?? change.from,
74
+ at: now?.() ?? step.healed?.at ?? 0,
75
+ },
76
+ };
55
77
  });
56
78
  return { flow: { ...flow, steps }, applied };
57
79
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Learn a flow step's source file from the element its anchor resolved to on a replay.
3
+ *
4
+ * A step recorded before the build stamped the app names no file, so a scoped re-verify cannot say
5
+ * which edits it covers. Every replay already asks the page for each anchor's element, and that
6
+ * element carries its stamp now: this records the answer at the one place every resolution passes
7
+ * through (the session's QUERY), rather than threading it through each step runner.
8
+ */
9
+ import { type FlowFile, type FlowStep } from '@reticlehq/core';
10
+ import type { FlowReplaySession } from './flow-replay-types.js';
11
+ type StepSource = NonNullable<FlowStep['source']>;
12
+ /** `src/Pay.tsx:12:4` or `src/Pay.tsx:12` → `{file, line}`; anything else is not a location. */
13
+ export declare function parseCompactSource(value: string): StepSource | undefined;
14
+ /** Wrap a replay session so every anchor QUERY that resolves to one stamped element is remembered. */
15
+ export declare function recordingSources(inner: FlowReplaySession): {
16
+ session: FlowReplaySession;
17
+ sources: ReadonlyMap<string, StepSource>;
18
+ };
19
+ /** The flow with every sourceless step given the file its anchor resolved to; undefined if none was. */
20
+ export declare function withLearnedSources(flow: FlowFile, sources: ReadonlyMap<string, StepSource>): FlowFile | undefined;
21
+ export {};