@reticlehq/server 3.1.0 → 3.3.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 (384) hide show
  1. package/SKILL.md +30 -28
  2. package/dist/command/cli/adhoc-suite.d.ts +19 -0
  3. package/dist/command/cli/adhoc-suite.js +88 -0
  4. package/dist/command/cli/adhoc-verdict.d.ts +23 -2
  5. package/dist/command/cli/adhoc-verdict.js +18 -10
  6. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  7. package/dist/command/cli/cli-doctor.js +37 -5
  8. package/dist/command/cli/cli-flow-commands.d.ts +2 -1
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  11. package/dist/command/cli/cli-parse-grammar.js +7 -0
  12. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  13. package/dist/command/cli/cli-parse-verify.js +86 -13
  14. package/dist/command/cli/cli-parse.d.ts +8 -1
  15. package/dist/command/cli/cli-parse.js +38 -7
  16. package/dist/command/cli/cli-verify.d.ts +59 -0
  17. package/dist/command/cli/cli-verify.js +131 -19
  18. package/dist/command/cli/cloud-cli.js +7 -6
  19. package/dist/command/cli/cloud-kit.d.ts +0 -2
  20. package/dist/command/cli/cloud-kit.js +3 -7
  21. package/dist/command/cli/config/config-discovery.d.ts +12 -0
  22. package/dist/command/cli/config/config-discovery.js +19 -0
  23. package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
  24. package/dist/command/cli/doctor/doctor-rows.js +12 -0
  25. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  26. package/dist/command/cli/drive/drive-command.js +1 -1
  27. package/dist/command/cli/launch/cli-launch.d.ts +3 -23
  28. package/dist/command/cli/launch/cli-launch.js +29 -70
  29. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
  30. package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
  31. package/dist/command/cli/mcp-command.js +5 -3
  32. package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
  33. package/dist/command/cli/ports/resolve/cli-port.js +50 -2
  34. package/dist/command/cli/report-command.d.ts +16 -0
  35. package/dist/command/cli/report-command.js +97 -0
  36. package/dist/command/cli/status/status-lines.d.ts +28 -0
  37. package/dist/command/cli/status/status-lines.js +117 -0
  38. package/dist/command/cli.d.ts +1 -1
  39. package/dist/command/cli.js +29 -177
  40. package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
  41. package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
  42. package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
  43. package/dist/command/daemon/binding/loopback-reach.js +63 -0
  44. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  45. package/dist/command/daemon/daemon-resilience.d.ts +13 -1
  46. package/dist/command/daemon/daemon-resilience.js +12 -1
  47. package/dist/command/daemon/daemon.d.ts +2 -11
  48. package/dist/command/daemon/daemon.js +8 -11
  49. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  50. package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
  51. package/dist/command/daemon/lifetime/heartbeat.js +10 -0
  52. package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
  53. package/dist/command/daemon/lifetime/log-cap.js +115 -0
  54. package/dist/command/setup/agent-notice-memory.d.ts +16 -0
  55. package/dist/command/setup/agent-notice-memory.js +65 -0
  56. package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
  57. package/dist/command/setup/bringup/dev-server-wait.js +16 -0
  58. package/dist/command/setup/bringup/ensure-daemon.js +1 -1
  59. package/dist/command/setup/init/init-host.js +8 -0
  60. package/dist/command/setup/init/init-runtime.js +13 -1
  61. package/dist/command/setup/node-effects.js +1 -1
  62. package/dist/command/setup/probe/page-probe.js +9 -2
  63. package/dist/command/setup/run-setup.d.ts +25 -7
  64. package/dist/command/setup/run-setup.js +51 -9
  65. package/dist/command/setup/setup-command.js +31 -3
  66. package/dist/command/setup/terminal/confirm.js +2 -1
  67. package/dist/command/update/updater.d.ts +27 -0
  68. package/dist/command/update/updater.js +35 -2
  69. package/dist/features/crawl/crawl-tools.js +3 -1
  70. package/dist/features/crawl/crawl.d.ts +2 -0
  71. package/dist/features/crawl/crawl.js +3 -1
  72. package/dist/features/harness/drive-report.d.ts +73 -0
  73. package/dist/features/harness/drive-report.js +196 -0
  74. package/dist/features/harness/driver.js +29 -8
  75. package/dist/features/harness/drivers.d.ts +19 -0
  76. package/dist/features/harness/drivers.js +19 -0
  77. package/dist/features/harness/fill-values.d.ts +67 -0
  78. package/dist/features/harness/fill-values.js +153 -0
  79. package/dist/features/harness/jev-driver.d.ts +188 -0
  80. package/dist/features/harness/jev-driver.js +661 -0
  81. package/dist/features/harness/openai-driver.d.ts +46 -0
  82. package/dist/features/harness/openai-driver.js +197 -0
  83. package/dist/features/harness/platform-config.d.ts +62 -0
  84. package/dist/features/harness/platform-config.js +97 -0
  85. package/dist/features/visual/visual-tools.js +26 -2
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.js +80 -130
  88. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  89. package/dist/judgement/capsule/capsule-store.js +58 -6
  90. package/dist/judgement/capsule/capsule.js +5 -0
  91. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  92. package/dist/judgement/capsule/causal-summary.js +7 -0
  93. package/dist/judgement/domain/domain-model.js +38 -9
  94. package/dist/judgement/domain/domain-tools.js +4 -2
  95. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  96. package/dist/judgement/phenomena/phenomena.d.ts +2 -1
  97. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  98. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  99. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  100. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  101. package/dist/judgement/runs/artifact/render-report.js +4 -1
  102. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  103. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  104. package/dist/judgement/runs/context-tools.js +21 -3
  105. package/dist/judgement/runs/drive-run.js +7 -2
  106. package/dist/judgement/runs/verification-sync.d.ts +2 -2
  107. package/dist/judgement/runs/verification-sync.js +9 -2
  108. package/dist/language/flows/affected-tools.js +6 -3
  109. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  110. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  111. package/dist/language/flows/flow-anchor.d.ts +12 -0
  112. package/dist/language/flows/flow-anchor.js +20 -0
  113. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  114. package/dist/language/flows/flow-expect-grammar.js +92 -11
  115. package/dist/language/flows/flow-intent.js +1 -1
  116. package/dist/language/flows/flow-learning.d.ts +3 -0
  117. package/dist/language/flows/flow-learning.js +39 -8
  118. package/dist/language/flows/flow-replay-run.d.ts +55 -10
  119. package/dist/language/flows/flow-replay-run.js +217 -58
  120. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  121. package/dist/language/flows/flow-replay.d.ts +29 -11
  122. package/dist/language/flows/flow-replay.js +221 -203
  123. package/dist/language/flows/flow-result.d.ts +18 -1
  124. package/dist/language/flows/flow-result.js +18 -2
  125. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  126. package/dist/language/flows/flow-step-runners.js +20 -4
  127. package/dist/language/flows/flow-store-for-session.d.ts +2 -1
  128. package/dist/language/flows/flow-success.d.ts +21 -6
  129. package/dist/language/flows/flow-success.js +50 -138
  130. package/dist/language/flows/flow-tools.js +8 -4
  131. package/dist/language/flows/flows.d.ts +15 -10
  132. package/dist/language/flows/flows.js +17 -4
  133. package/dist/language/flows/heal.d.ts +9 -1
  134. package/dist/language/flows/heal.js +24 -2
  135. package/dist/language/flows/learned-sources.d.ts +21 -0
  136. package/dist/language/flows/learned-sources.js +84 -0
  137. package/dist/language/flows/narrow-write.d.ts +4 -4
  138. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  139. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  140. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  141. package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
  142. package/dist/language/flows/replay.js +13 -9
  143. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  144. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  145. package/dist/language/flows/stores/flake-store.js +2 -3
  146. package/dist/language/flows/suite/coverage.d.ts +1 -1
  147. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  148. package/dist/language/flows/suite-selection.d.ts +15 -1
  149. package/dist/language/flows/suite-selection.js +1 -1
  150. package/dist/language/flows/verify-change-tools.js +21 -4
  151. package/dist/memory/cloud/account-state.d.ts +1 -1
  152. package/dist/memory/cloud/account-state.js +5 -4
  153. package/dist/memory/cloud/cloud-config.d.ts +9 -0
  154. package/dist/memory/cloud/cloud-config.js +9 -0
  155. package/dist/memory/cloud/cloud-sync.d.ts +7 -4
  156. package/dist/memory/cloud/cloud-sync.js +11 -6
  157. package/dist/memory/cloud/harness-config.d.ts +12 -0
  158. package/dist/memory/cloud/harness-config.js +58 -0
  159. package/dist/memory/cloud/harness-offer.d.ts +44 -0
  160. package/dist/memory/cloud/harness-offer.js +146 -0
  161. package/dist/memory/cloud/harness-switch.d.ts +11 -0
  162. package/dist/memory/cloud/harness-switch.js +53 -0
  163. package/dist/memory/cloud/sync-cycle.d.ts +12 -0
  164. package/dist/memory/cloud/sync-cycle.js +29 -4
  165. package/dist/memory/cloud/sync-daemon.d.ts +35 -2
  166. package/dist/memory/cloud/sync-daemon.js +45 -0
  167. package/dist/memory/cloud/sync-disk.js +11 -6
  168. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  169. package/dist/memory/impact/impact-recorder.js +2 -1
  170. package/dist/memory/impact/impact-store.d.ts +5 -0
  171. package/dist/memory/impact/impact-store.js +25 -0
  172. package/dist/memory/intent/inline-intent.js +8 -3
  173. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  174. package/dist/memory/intent/intent-shard-store.js +253 -141
  175. package/dist/memory/intent/intent-shard.d.ts +22 -23
  176. package/dist/memory/intent/intent-shard.js +1 -1
  177. package/dist/memory/intent/intent-store.d.ts +13 -8
  178. package/dist/memory/intent/intent-store.js +46 -90
  179. package/dist/memory/intent/intent-tools.js +34 -23
  180. package/dist/memory/journal/ambient-store.js +2 -3
  181. package/dist/memory/journal/attach-journal.d.ts +11 -0
  182. package/dist/memory/journal/attach-journal.js +1 -1
  183. package/dist/memory/journal/deviation-report.js +4 -1
  184. package/dist/memory/journal/deviation-service.js +10 -2
  185. package/dist/memory/journal/drive-flow.d.ts +2 -2
  186. package/dist/memory/journal/drive-flow.js +21 -12
  187. package/dist/memory/journal/envelope-store.js +2 -3
  188. package/dist/memory/journal/journal-query.d.ts +35 -1
  189. package/dist/memory/journal/journal-query.js +46 -0
  190. package/dist/memory/journal/journal-recorder.d.ts +71 -2
  191. package/dist/memory/journal/journal-recorder.js +45 -7
  192. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  193. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  194. package/dist/memory/journal/on-disk/retention.d.ts +91 -2
  195. package/dist/memory/journal/on-disk/retention.js +181 -11
  196. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  197. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  198. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
  199. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
  200. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  201. package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
  202. package/dist/memory/journal/route-template.d.ts +22 -0
  203. package/dist/memory/journal/route-template.js +44 -0
  204. package/dist/memory/journal/self-observation.d.ts +3 -0
  205. package/dist/memory/journal/self-observation.js +67 -0
  206. package/dist/memory/journal/session-end.d.ts +17 -4
  207. package/dist/memory/journal/session-end.js +85 -7
  208. package/dist/memory/journal/session-journal.d.ts +122 -5
  209. package/dist/memory/journal/session-journal.js +489 -32
  210. package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
  211. package/dist/memory/project/artifact-root-resolver.js +89 -0
  212. package/dist/memory/project/artifact-root.d.ts +39 -2
  213. package/dist/memory/project/artifact-root.js +58 -1
  214. package/dist/memory/project/baselines.d.ts +1 -1
  215. package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
  216. package/dist/memory/project/dir/fill-value-store.js +88 -0
  217. package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
  218. package/dist/memory/project/dir/reticle-dir.js +11 -0
  219. package/dist/memory/project/fs/fs-port.d.ts +22 -1
  220. package/dist/memory/project/fs/fs-port.js +72 -5
  221. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  222. package/dist/memory/project/fs/write-atomic.js +48 -0
  223. package/dist/memory/project/learned-routes.d.ts +16 -2
  224. package/dist/memory/project/learned-routes.js +26 -10
  225. package/dist/memory/project/project-for-root.d.ts +30 -0
  226. package/dist/memory/project/project-for-root.js +43 -0
  227. package/dist/memory/project/project-tools.js +10 -5
  228. package/dist/memory/project/session-root.d.ts +35 -6
  229. package/dist/memory/project/session-root.js +70 -8
  230. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  231. package/dist/memory/recall/prior/connection-memory.js +8 -0
  232. package/dist/memory/recall/registered-projects.d.ts +2 -1
  233. package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
  234. package/dist/portal/bridge/auth-failure-reason.js +20 -3
  235. package/dist/portal/bridge/bridge.d.ts +6 -1
  236. package/dist/portal/bridge/bridge.js +36 -2
  237. package/dist/portal/input/real-input.js +2 -1
  238. package/dist/portal/pool/browser-pool.d.ts +10 -0
  239. package/dist/portal/pool/browser-pool.js +10 -0
  240. package/dist/portal/pool/playwright-launcher.js +1 -0
  241. package/dist/portal/pool/pool-contract.d.ts +2 -0
  242. package/dist/portal/pool/storage-seed.d.ts +2 -1
  243. package/dist/portal/pool/zero-install.d.ts +27 -0
  244. package/dist/portal/pool/zero-install.js +54 -0
  245. package/dist/portal/realm/web-realm.js +6 -0
  246. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  247. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  248. package/dist/portal/session/dev-server/served-document.js +57 -0
  249. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  250. package/dist/portal/session/facts/background-traffic.js +25 -0
  251. package/dist/portal/session/facts/session-replaced.d.ts +19 -0
  252. package/dist/portal/session/facts/session-replaced.js +22 -0
  253. package/dist/portal/session/live-control-tools.js +48 -3
  254. package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
  255. package/dist/portal/session/no-session-diagnosis.js +67 -60
  256. package/dist/portal/session/no-session-next-action.js +3 -2
  257. package/dist/portal/session/no-session-watch.js +14 -10
  258. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  259. package/dist/portal/session/rebind.d.ts +18 -0
  260. package/dist/portal/session/rebind.js +38 -0
  261. package/dist/portal/session/session-health.d.ts +61 -0
  262. package/dist/portal/session/session-health.js +40 -1
  263. package/dist/portal/session/session-info.d.ts +6 -0
  264. package/dist/portal/session/session-manager.d.ts +13 -0
  265. package/dist/portal/session/session-manager.js +42 -3
  266. package/dist/portal/session/session-successor.d.ts +11 -0
  267. package/dist/portal/session/session-successor.js +14 -1
  268. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  269. package/dist/portal/session/session-verdict-facts.js +15 -0
  270. package/dist/portal/session/session.d.ts +26 -10
  271. package/dist/portal/session/session.js +48 -49
  272. package/dist/status-payload.d.ts +12 -1
  273. package/dist/status-payload.js +4 -1
  274. package/dist/surface/http-server.js +3 -5
  275. package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
  276. package/dist/surface/mcp/faults/mcp-outage.js +20 -4
  277. package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
  278. package/dist/surface/mcp/mcp-proxy.js +23 -12
  279. package/dist/surface/mcp/mcp.d.ts +10 -1
  280. package/dist/surface/mcp/mcp.js +59 -29
  281. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
  282. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
  283. package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
  284. package/dist/surface/mcp/proxy/proxy-log.js +7 -22
  285. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  286. package/dist/surface/mcp/server-instructions.js +12 -1
  287. package/dist/surface/tools/act/act-preflight.js +6 -1
  288. package/dist/surface/tools/act/already-true.d.ts +40 -0
  289. package/dist/surface/tools/act/already-true.js +36 -0
  290. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
  291. package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
  292. package/dist/surface/tools/act/sequence-grade.js +13 -0
  293. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  294. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  295. package/dist/surface/tools/act-capsule.js +49 -8
  296. package/dist/surface/tools/act-tools.js +25 -24
  297. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  298. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  299. package/dist/surface/tools/assert/assert-grade.js +14 -2
  300. package/dist/surface/tools/assert/assert-verdict.js +74 -21
  301. package/dist/surface/tools/browser-tools.js +4 -3
  302. package/dist/surface/tools/coverage-tools.js +5 -2
  303. package/dist/surface/tools/dynamic-tools.js +43 -46
  304. package/dist/surface/tools/error-recovery.d.ts +6 -0
  305. package/dist/surface/tools/error-recovery.js +15 -0
  306. package/dist/surface/tools/explore-tools.js +80 -7
  307. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  308. package/dist/surface/tools/feedback-tools.js +50 -3
  309. package/dist/surface/tools/first-sentence.d.ts +24 -0
  310. package/dist/surface/tools/first-sentence.js +0 -0
  311. package/dist/surface/tools/harness-explore.d.ts +125 -1
  312. package/dist/surface/tools/harness-explore.js +401 -15
  313. package/dist/surface/tools/harness-plan.d.ts +76 -0
  314. package/dist/surface/tools/harness-plan.js +156 -0
  315. package/dist/surface/tools/harness-toolset.js +12 -1
  316. package/dist/surface/tools/invoke-tool.js +82 -9
  317. package/dist/surface/tools/lease-hint.d.ts +8 -0
  318. package/dist/surface/tools/lease-hint.js +10 -0
  319. package/dist/surface/tools/lease-tools.d.ts +16 -28
  320. package/dist/surface/tools/lease-tools.js +53 -5
  321. package/dist/surface/tools/lineage-tools.js +1 -1
  322. package/dist/surface/tools/live-call-text.d.ts +6 -0
  323. package/dist/surface/tools/live-call-text.js +36 -2
  324. package/dist/surface/tools/observe-tools.js +69 -15
  325. package/dist/surface/tools/read/query-windowed.d.ts +1 -1
  326. package/dist/surface/tools/read/query-windowed.js +1 -1
  327. package/dist/surface/tools/read-tools.js +1 -2
  328. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  329. package/dist/surface/tools/real-input-attempt.js +1 -1
  330. package/dist/surface/tools/tool-kit.d.ts +23 -3
  331. package/dist/surface/tools/tool-kit.js +6 -1
  332. package/dist/surface/tools/tool-surface.js +17 -5
  333. package/dist/surface/tools/tools-helpers.d.ts +8 -7
  334. package/dist/surface/tools/tools-helpers.js +11 -13
  335. package/dist/surface/tools/tools.js +70 -30
  336. package/dist/telemetry/bug-found.js +32 -2
  337. package/dist/telemetry/feedback-context.js +3 -3
  338. package/dist/telemetry/feedback-local.d.ts +1 -0
  339. package/dist/telemetry/feedback-local.js +1 -3
  340. package/dist/telemetry/feedback.d.ts +1 -1
  341. package/dist/telemetry/harness-drive.d.ts +6 -0
  342. package/dist/telemetry/harness-drive.js +39 -0
  343. package/dist/telemetry/hud-metrics.d.ts +32 -0
  344. package/dist/telemetry/hud-metrics.js +95 -0
  345. package/dist/telemetry/mcp-connection.d.ts +14 -1
  346. package/dist/telemetry/mcp-connection.js +17 -2
  347. package/dist/telemetry/onboarding-firsts.d.ts +30 -1
  348. package/dist/telemetry/onboarding-firsts.js +54 -12
  349. package/dist/telemetry/session-metrics.d.ts +5 -0
  350. package/dist/telemetry/session-metrics.js +15 -1
  351. package/dist/telemetry/verification-of.js +31 -3
  352. package/dist/wire-journal.d.ts +31 -0
  353. package/dist/wire-journal.js +100 -0
  354. package/docs/README.md +1 -0
  355. package/docs/agent-cheatsheet.md +7 -3
  356. package/docs/autodrive.md +42 -2
  357. package/docs/benchmarks.md +1 -1
  358. package/docs/capabilities.mdx +1 -1
  359. package/docs/cli/cloud.mdx +11 -9
  360. package/docs/cli/doctor.mdx +1 -0
  361. package/docs/cli/init.mdx +1 -0
  362. package/docs/cli/report.mdx +44 -0
  363. package/docs/cli/status.mdx +35 -20
  364. package/docs/cli.mdx +8 -18
  365. package/docs/desktop.mdx +1 -1
  366. package/docs/faq.mdx +1 -1
  367. package/docs/flows.md +11 -0
  368. package/docs/frameworks.mdx +113 -1
  369. package/docs/install-agentic.mdx +36 -0
  370. package/docs/install-manual.mdx +1 -1
  371. package/docs/packages/tauri.mdx +1 -1
  372. package/docs/packages.mdx +1 -1
  373. package/docs/predicates.mdx +144 -4
  374. package/docs/system-map.md +6 -4
  375. package/docs/telemetry.md +2 -1
  376. package/docs/token-efficiency.md +2 -2
  377. package/docs/tools/overview.mdx +1 -1
  378. package/docs/tools/query.mdx +10 -10
  379. package/docs/troubleshooting.mdx +8 -2
  380. package/docs/usage.md +2 -2
  381. package/docs/what-is-recorded.md +62 -0
  382. package/package.json +5 -5
  383. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  384. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
@@ -1,17 +1,44 @@
1
- import { mayResumeByReplayingPrefix, StepEffect } from '@reticlehq/core';
2
- import { Surface, formatStepAddress } from 'open-verification';
1
+ import { formatStepAddress } from 'open-verification';
2
+ import { assertsState, expectLabel, expectedElementTestid, withoutClauses } from '@reticlehq/core';
3
3
  import { span } from '../../trace.js';
4
- import { anchorLabel, expectElementDrift, resolveTestid, testidDrift } from './flow-anchor.js';
4
+ import { anchorLabel, expectElementDrift, resolveTestid, testidDrift, ambiguousAnchorDrift, } from './flow-anchor.js';
5
5
  export { anchorLabel, componentLabel, componentQueryArgs, editDistance, expectElementDrift, nearestIsAmbiguous, nearestTestid, resolveQuery, testidDrift, } from './flow-anchor.js';
6
6
  import { routeOfEvent } from '@reticlehq/engine/question/predicate/predicate-route.js';
7
7
  import { stepEffect } from '@reticlehq/engine/evidence/step-effect.js';
8
- import { AnchorKind, DriftReason, EventType, FlowStepTool, ReticleCommand, PredicateKind, } from '@reticlehq/core';
8
+ import { AnchorKind, DriftReason, EventType, FlowStepTool, PredicateKind, } from '@reticlehq/core';
9
9
  import { asString, isConsequenceDrift } from '@reticlehq/core';
10
- import { replayActionArgs, ambiguousTestidNote } from './replay.js';
11
10
  import { anchorFieldName } from './fields/flow-secret-field.js';
12
11
  import { degradedStepResult, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
13
- import { successToPredicate } from './flow-success.js';
14
- import { ReticleTool } from '@reticlehq/core';
12
+ import { inFlightRequestLabels } from '../../surface/tools/act/settle-in-flight.js';
13
+ import { namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
14
+ /** What a still-open named request means, in the drift's own words. */
15
+ const IN_FLIGHT_AT_BUDGET_END = 'the request this step declared had not come back when the budget ended — it is still in flight, ' +
16
+ 'so nothing here says the app failed. Raise the step timeout, or look at the endpoint';
17
+ import { isDocumentGoneError, SESSION_DISCONNECTED_REASON, } from '../../portal/session/facts/session-replaced.js';
18
+ import { actOnResolvedRef } from './flow-step-runners.js';
19
+ /**
20
+ * The document this replay was driving went away mid-run.
21
+ *
22
+ * Almost always because the replay itself navigated it — a `startPath` goto, or a step that clicks a
23
+ * link. The steps already in hand are real answers from the document that WAS there, so they travel
24
+ * with the error; the step that was in flight has no answer at all and is deliberately absent.
25
+ * Synthesising one would be a verdict about a page nobody observed, which is the one thing worse
26
+ * than returning no verdict.
27
+ *
28
+ * Typed rather than a message, because the caller has to tell it from a step that genuinely failed:
29
+ * one says the app is wrong, the other says we stopped watching.
30
+ */
31
+ export class DocumentLostDuringReplay extends Error {
32
+ steps;
33
+ atStep;
34
+ constructor(steps, atStep, lostTo) {
35
+ super(`the document was replaced while step ${String(atStep)} was running`, {
36
+ cause: lostTo,
37
+ });
38
+ this.steps = steps;
39
+ this.atStep = atStep;
40
+ }
41
+ }
15
42
  const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
16
43
  /**
17
44
  * The route (pathname) currently in effect — the page a step runs on. Reads the latest ROUTE_CHANGE
@@ -76,7 +103,7 @@ function summarizeConsequence(events) {
76
103
  return parts.length > 0 ? parts.join('; ') : undefined;
77
104
  }
78
105
  /** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
79
- async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
106
+ export async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
80
107
  const { refs, hint } = await resolveTestid(session, value, sleep);
81
108
  if (0 === refs.length) {
82
109
  return {
@@ -87,35 +114,50 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
87
114
  drift: testidDrift(value, hint),
88
115
  };
89
116
  }
90
- const ref = refs[0] ?? '';
91
- const note = refs.length > 1 ? ambiguousTestidNote(value) : undefined;
92
- session.beginAction?.(ReticleTool.FLOW_REPLAY, { ref, action: step.action ?? '' });
93
- let act;
94
- try {
95
- act = await session.command(ReticleCommand.ACT, {
96
- ref,
97
- action: step.action ?? '',
98
- // The field this step types into — from the anchor, so a redacted fill can be supplied from
99
- // RETICLE_SECRET_<FIELD> without the flow carrying the secret. The testid runner used to pass
100
- // the testid string here and the other two runners passed nothing, so a role-anchored login
101
- // typed the literal placeholder.
102
- args: replayActionArgs(step.args, confirmDangerous, anchorFieldName(step.anchor)),
103
- });
104
- }
105
- finally {
106
- session.finishAction?.();
117
+ /*
118
+ * More than one match is DRIFT, not a note on a passing step.
119
+ *
120
+ * This used to take refs[0], act, and return ok:true with `ambiguousTestidNote`. The verdict
121
+ * reads `drift` and `ok` and never reads a note, so "we guessed which element you meant" was
122
+ * indistinguishable from "it did what it did before" -- the one claim a replay makes.
123
+ *
124
+ * The action is NOT dispatched. Acting and then reporting drift would leave the app changed by
125
+ * a click nobody can attribute, which is worse than the ambiguity it reports.
126
+ */
127
+ if (refs.length > 1) {
128
+ return {
129
+ step: index,
130
+ tool: step.tool,
131
+ anchor: value,
132
+ ok: false,
133
+ drift: ambiguousAnchorDrift(value, refs.length),
134
+ };
107
135
  }
108
- const result = { step: index, tool: step.tool, anchor: value, ok: act.ok };
109
- if (!act.ok) {
110
- result.error = act.error ?? 'command failed';
111
- if (note !== undefined)
112
- result.note = note;
136
+ const ref = refs[0] ?? '';
137
+ /*
138
+ * The same dispatch its siblings use, including the one re-resolve on a stale ref (2.3).
139
+ *
140
+ * This runner had its own inline copy and no retry, so a flow died on a re-render purely because
141
+ * the step was anchored by testid rather than by role — the locator deciding how sturdy the replay
142
+ * is, which is exactly backwards. testid is also the anchor `reticle init` steers people towards,
143
+ * so the kind most likely to appear in a real flow was the kind without the cure.
144
+ *
145
+ * The re-resolve keeps the ambiguity rule above: more than one match is drift, never a guess, so
146
+ * it hands back a ref only when the locator still names exactly one element.
147
+ */
148
+ const result = await actOnResolvedRef(session, step, index, value, ref, confirmDangerous,
149
+ // The field this step types into — from the anchor, so a redacted fill can be supplied from
150
+ // RETICLE_SECRET_<FIELD> without the flow carrying the secret.
151
+ anchorFieldName(step.anchor), async () => {
152
+ const again = await resolveTestid(session, value, sleep);
153
+ return 1 === again.refs.length ? again.refs[0] : undefined;
154
+ });
155
+ if (!result.ok)
113
156
  return result;
114
- }
115
157
  // assert the step's expect.element testid is present AFTER the action —
116
158
  // unless that testid was marked DYNAMIC (the LLM-output case), in which case its presence/content
117
159
  // is NOT asserted (only the action ran). The skip is scoped strictly to the dynamic set.
118
- const expectTestid = step.expect?.element?.testid;
160
+ const expectTestid = expectedElementTestid(step.expect);
119
161
  if (expectTestid !== undefined && !dynamic.has(expectTestid)) {
120
162
  const expectRefs = await resolveTestid(session, expectTestid, sleep);
121
163
  if (0 === expectRefs.refs.length) {
@@ -131,8 +173,6 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
131
173
  };
132
174
  }
133
175
  }
134
- if (note !== undefined)
135
- result.note = note;
136
176
  return result;
137
177
  }
138
178
  /**
@@ -156,44 +196,63 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
156
196
  * quietly stop agreeing, and the disagreement would land as a flow that replays green against a
157
197
  * state it was never meant to run in.
158
198
  */
159
- export async function assertStepExpect(session, expect, dynamic, waitForSignal, timeoutMs, since) {
199
+ export async function assertStepExpect(session, expect, waitForSignal, timeoutMs, since) {
160
200
  // A testid is already asserted against the live DOM by the step runner. A role/name locator is
161
201
  // not that path — stripping every element made a recorded `until` by button name a no-op, so a
162
202
  // flow that proved the control at capture time could not go red when it was gone.
163
- const consequences = { ...expect };
164
- if (undefined !== consequences.element?.testid) {
165
- delete consequences.element;
166
- }
167
- const predicate = successToPredicate(consequences, dynamic);
203
+ /*
204
+ * Two clauses replay must not wait on, both for reasons that predate the tree.
205
+ *
206
+ * A testid element was already asserted against the live DOM by the step runner, so waiting on it
207
+ * again is a second read of a question already answered. A DYNAMIC testid is one whose content a
208
+ * model writes, and its presence is deliberately never asserted.
209
+ *
210
+ * A role/name locator is NEITHER, and stripping every element made a recorded `until` by button
211
+ * name a no-op — a flow that proved the control at capture time could not go red when it was gone.
212
+ *
213
+ * The DYNAMIC set used to be a parameter here and was dead the whole time: the testid was deleted
214
+ * from the expectation on the line above, and the only thing that read the set asked for the
215
+ * testid that had just been removed. One production caller was already passing an empty set. The
216
+ * rule is real and still applies to the flow's SUCCESS oracle, which is where it lives.
217
+ */
218
+ const predicate = withoutClauses(expect,
219
+ // Only a PRESENT testid is the runner's to check; an absence stays here or nobody checks it.
220
+ (clause) => PredicateKind.ELEMENT === clause.kind &&
221
+ clause.query.testid !== undefined &&
222
+ true !== clause.absent);
168
223
  if (predicate === undefined)
169
224
  return undefined;
170
225
  const verdict = await waitForSignal(session, predicate, timeoutMs, since);
171
226
  if (verdict.pass)
172
227
  return undefined;
228
+ // The wait ended because the page stopped being observable, not because the consequence failed:
229
+ // the same fact as a command the dying socket rejected, so it takes the same path (the replay loop
230
+ // reports a lost document). As a drift, a socket that blinked for 26ms read as the app's defect.
231
+ if (true === verdict.observationLost)
232
+ throw new Error(SESSION_DISCONNECTED_REASON);
233
+ // A request the step NAMED that had not come back yet is a blind spot, not an assertion the app
234
+ // failed. The live verdict path already draws this line (namedNetIsInFlight); replay read the
235
+ // same window and never asked, so a slow endpoint was reported as a consequence that never fired.
236
+ // An unrelated open request does not pardon a named URL that never started — matching decides it.
237
+ const openRequests = assertsState(expect)
238
+ ? []
239
+ : inFlightRequestLabels(session.eventsSince(since), session.url, session.background);
240
+ const namedInFlight = namedNetIsInFlight(predicate, openRequests);
173
241
  return {
174
242
  // The store case keeps its own kind because heal and the run report branch on it; everything
175
243
  // else is a consequence that did not hold, and the reason carries observed-vs-expected.
176
- reasonKind: expect.state !== undefined ? DriftReason.STATE_MISMATCH : DriftReason.SIGNAL_NOT_OBSERVED,
177
- reason: verdict.failureReason ?? "the step's declared consequence did not hold",
244
+ reasonKind: assertsState(expect)
245
+ ? DriftReason.STATE_MISMATCH
246
+ : namedInFlight
247
+ ? DriftReason.NET_STILL_IN_FLIGHT
248
+ : DriftReason.SIGNAL_NOT_OBSERVED,
249
+ reason: namedInFlight
250
+ ? `${IN_FLIGHT_AT_BUDGET_END} (${openRequests.join(', ')})`
251
+ : (verdict.failureReason ?? "the step's declared consequence did not hold"),
178
252
  anchor: expectLabel(expect),
179
253
  nearest: null,
180
254
  };
181
255
  }
182
- /** Name the thing that was asserted, for the drift's `anchor` column. */
183
- function expectLabel(expect) {
184
- if (expect.signal !== undefined)
185
- return `signal:${expect.signal}`;
186
- if (expect.net !== undefined)
187
- return `net:${expect.net.urlContains ?? expect.net.method ?? '*'}`;
188
- if (expect.state !== undefined)
189
- return `state:${expect.state.path}`;
190
- if (expect.console !== undefined)
191
- return `console:${expect.console.level ?? '*'}`;
192
- if (undefined !== expect.element) {
193
- return expect.element.testid ?? expect.element.name ?? expect.element.role ?? 'element';
194
- }
195
- return 'expect';
196
- }
197
256
  /** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
198
257
  async function runSignalStep(session, step, index, name, waitForSignal, signalTimeoutMs, since) {
199
258
  // Scope to THIS replay's floor, not the whole buffer.
@@ -221,40 +280,6 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
221
280
  },
222
281
  };
223
282
  }
224
- /**
225
- * Where to start REPORTING — and whether re-driving the steps before it is allowed at all.
226
- *
227
- * "Resume is nearly free, just re-run the prefix" is true of a browser and false on a subject that
228
- * COMMITS. Re-driving a prefix on a service re-sends every request before step N; on a device it
229
- * moves an arm again. Neither is a convenience, and a protocol that did it silently would be
230
- * defective rather than helpful.
231
- *
232
- * So the surface's declared profile decides, through the specification's own `resumeStrategy` rather
233
- * than a local reading of `replayPrefix`. A refusal resumes from 0 — the whole journey is reported,
234
- * nothing is skipped and nothing is silently repeated beyond what a plain replay already does.
235
- */
236
- function resumableFrom(options, steps) {
237
- const asked = Math.max(0, options.from ?? 0);
238
- if (0 === asked)
239
- return 0;
240
- if (!mayResumeByReplayingPrefix(options.surface ?? Surface.WEB))
241
- return 0;
242
- /*
243
- * The surface answers for the SUBJECT; a step answers for itself.
244
- *
245
- * `web` is the permissive profile and it is right about a browser in general and wrong about the
246
- * one click that charges a card. The surface check cannot see that, because the distinction is
247
- * not a property of the realm — it is a property of the step. A flow that declares a prefix step
248
- * as `commits` is saying: re-running me is not free, whatever the surface thinks.
249
- *
250
- * Refusing means resuming from 0, which is the same fail-safe the surface refusal already uses:
251
- * the whole journey is reported and nothing is silently repeated beyond what a plain replay does.
252
- * Absent effect is UNKNOWN and stays permissive — every flow recorded before this shipped has no
253
- * effect on any step, and assuming the worst there would refuse every resume in existence.
254
- */
255
- const commitsInPrefix = steps.slice(0, asked).some((step) => StepEffect.COMMITS === step.effect);
256
- return commitsInPrefix ? 0 : asked;
257
- }
258
283
  /**
259
284
  * Run an `invoke` step: load the named flow and replay it, or fail saying why.
260
285
  *
@@ -288,10 +313,7 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
288
313
  error: `cannot replay "${name}": it was not found, so this journey would report green having never run it`,
289
314
  };
290
315
  }
291
- // `from` is deliberately dropped rather than forwarded: it is a REPORTING offset into the
292
- // caller's own step list, and applying it inside a sub-journey would silently hide that
293
- // journey's first steps for a reason that has nothing to do with it.
294
- const { from: _ignored, ...carried } = options;
316
+ const carried = options;
295
317
  const nested = await replayFlow(session, sub, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, {
296
318
  ...carried,
297
319
  via: [{ flow: flow.name, step: index }, ...via],
@@ -309,16 +331,6 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
309
331
  }
310
332
  export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs, confirmDangerous = false, sleep = realSleep, options = {}) {
311
333
  const results = [];
312
- /*
313
- * Where to start REPORTING. Everything before it still runs.
314
- *
315
- * Resuming is re-driving the prefix, not restoring state: there is no way to put an app back
316
- * where it was without driving it there, and at a measured ~27ms a step there is no reason to
317
- * try. So the prefix executes silently and the caller sees the journey from the point it asked
318
- * about -- which is what makes "fix the break, resume, find the next one" a loop rather than a
319
- * full re-read each time.
320
- */
321
- const from = resumableFrom(options, flow.steps);
322
334
  // testids whose region is LLM-dynamic — their expect-presence is NOT asserted.
323
335
  const dynamic = new Set((flow.dynamic ?? [])
324
336
  .filter((a) => a.kind === AnchorKind.TESTID)
@@ -332,108 +344,114 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
332
344
  // wait for the slowest step would trade a false red for a slow suite. See FlowStep.timeoutMs.
333
345
  const waitFor = (step) => step.timeoutMs ?? flow.signalTimeoutMs ?? signalTimeoutMs;
334
346
  let index = 0;
335
- for (const step of flow.steps) {
336
- if (step.invoke !== undefined) {
337
- results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options));
338
- const last = results[results.length - 1];
339
- if (last !== undefined && false === last.ok)
340
- break;
341
- index += 1;
342
- continue;
343
- }
344
- const label = anchorLabel(step.anchor);
345
- // The page this step runs on (the journey's "which page") — captured before the action.
346
- const page = currentRoute(session);
347
- // Event-time floor so the consequence reflects only THIS step's aftermath, not prior steps'.
348
- const cursorBefore = session.elapsed();
349
- const subSteps = step.steps;
350
- // Traced per step, so a slow replay says WHICH step and which anchor kind spent the time. The
351
- // per-step `durationMs` below is what the agent gets back; this is what a developer profiling
352
- // the replay engine gets, nested under the tool call with the browser round-trips beneath it.
353
- const result = await span('flow.step', { index, anchor: step.anchor.kind, label }, async () => {
354
- if (isDegradedAnchor(step.anchor)) {
355
- // Never QUERY the sentinel — it marks "no anchor was determined", not an element to find.
356
- return degradedStepResult(step, index, label);
357
- }
358
- if (subSteps !== undefined && subSteps.length > 0) {
359
- return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep, dynamic);
360
- }
361
- if (step.anchor.kind === AnchorKind.SIGNAL) {
362
- return runSignalStep(session, step, index, label, waitForSignal, waitFor(step), replayFloor);
363
- }
364
- if (step.anchor.kind === AnchorKind.COMPONENT) {
365
- return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
347
+ try {
348
+ for (const step of flow.steps) {
349
+ if (step.invoke !== undefined) {
350
+ results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options));
351
+ const last = results[results.length - 1];
352
+ if (last !== undefined && false === last.ok)
353
+ break;
354
+ index += 1;
355
+ continue;
366
356
  }
367
- if (step.anchor.kind === AnchorKind.ROLE && step.anchor.name !== undefined) {
368
- // A NAMED role anchor addresses one element. The nameless one is the degraded placeholder
369
- // and keeps its old path, where it fails legibly rather than querying a role as a testid.
370
- return runRoleStep(session, step, index, step.anchor, confirmDangerous, sleep);
357
+ const label = anchorLabel(step.anchor);
358
+ // The page this step runs on (the journey's "which page") — captured before the action.
359
+ const page = currentRoute(session);
360
+ // Event-time floor so the consequence reflects only THIS step's aftermath, not prior steps'.
361
+ const cursorBefore = session.elapsed();
362
+ const subSteps = step.steps;
363
+ // Traced per step, so a slow replay says WHICH step and which anchor kind spent the time. The
364
+ // per-step `durationMs` below is what the agent gets back; this is what a developer profiling
365
+ // the replay engine gets, nested under the tool call with the browser round-trips beneath it.
366
+ const result = await span('flow.step', { index, anchor: step.anchor.kind, label }, async () => {
367
+ if (isDegradedAnchor(step.anchor)) {
368
+ // Never QUERY the sentinel — it marks "no anchor was determined", not an element to find.
369
+ return degradedStepResult(step, index, label);
370
+ }
371
+ if (subSteps !== undefined && subSteps.length > 0) {
372
+ return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep, dynamic);
373
+ }
374
+ if (step.anchor.kind === AnchorKind.SIGNAL) {
375
+ return runSignalStep(session, step, index, label, waitForSignal, waitFor(step), replayFloor);
376
+ }
377
+ if (step.anchor.kind === AnchorKind.COMPONENT) {
378
+ return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
379
+ }
380
+ if (step.anchor.kind === AnchorKind.ROLE && step.anchor.name !== undefined) {
381
+ // A NAMED role anchor addresses one element. The nameless one is the degraded placeholder
382
+ // and keeps its old path, where it fails legibly rather than querying a role as a testid.
383
+ return runRoleStep(session, step, index, step.anchor, confirmDangerous, sleep);
384
+ }
385
+ return runTestidStep(session, step, index, label, dynamic, confirmDangerous, sleep);
386
+ });
387
+ // Once the anchor resolved and the action ran, the step's own expect is evaluated — signal, net,
388
+ // console and store truth alike — deterministically, in the same cheap replay loop, with no LLM.
389
+ /*
390
+ * The step's own expect, and then every sub-step's.
391
+ *
392
+ * `classifyFlowAssertions` already walks act_sequence sub-steps and counts an expect on either
393
+ * level, and states the invariant plainly: this and what replay enforces must move together, or
394
+ * "the difference is a false green or a lost verification". Enforcing only the top level was
395
+ * that difference -- a sequence whose sub-step declared a consequence was GRADED asserted and
396
+ * checked by nothing, so it could not go red while wearing the grade that says it could.
397
+ *
398
+ * WEAKER THAN THE LIVE PATH, and deliberately so rather than silently. A recorded sequence is
399
+ * dispatched as ONE batched command, so there are no per-sub-step cursors to open a window with
400
+ * -- every sub-step expect is evaluated against the window the whole sequence opened, which
401
+ * means sub-step three's claim can be satisfied by sub-step one's consequence. The live
402
+ * `act_sequence` path takes a cursor before each dispatch and does not have this. Narrowing it
403
+ * here means dispatching sub-steps individually on replay, which is a behaviour change to the
404
+ * replay path and belongs in its own commit.
405
+ */
406
+ const declared = [step.expect, ...(step.steps ?? []).map((sub) => sub.expect)].filter((expectation) => expectation !== undefined);
407
+ for (const expectation of declared) {
408
+ if (!result.ok || result.drift !== undefined)
409
+ break;
410
+ const expectDrift = await assertStepExpect(session, expectation, waitForSignal, waitFor(step), cursorBefore);
411
+ if (expectDrift !== undefined) {
412
+ result.ok = false;
413
+ result.drift = expectDrift;
414
+ }
371
415
  }
372
- return runTestidStep(session, step, index, label, dynamic, confirmDangerous, sleep);
373
- });
374
- // Once the anchor resolved and the action ran, the step's own expect is evaluated — signal, net,
375
- // console and store truth alike — deterministically, in the same cheap replay loop, with no LLM.
376
- /*
377
- * The step's own expect, and then every sub-step's.
378
- *
379
- * `classifyFlowAssertions` already walks act_sequence sub-steps and counts an expect on either
380
- * level, and states the invariant plainly: this and what replay enforces must move together, or
381
- * "the difference is a false green or a lost verification". Enforcing only the top level was
382
- * that difference -- a sequence whose sub-step declared a consequence was GRADED asserted and
383
- * checked by nothing, so it could not go red while wearing the grade that says it could.
384
- *
385
- * WEAKER THAN THE LIVE PATH, and deliberately so rather than silently. A recorded sequence is
386
- * dispatched as ONE batched command, so there are no per-sub-step cursors to open a window with
387
- * -- every sub-step expect is evaluated against the window the whole sequence opened, which
388
- * means sub-step three's claim can be satisfied by sub-step one's consequence. The live
389
- * `act_sequence` path takes a cursor before each dispatch and does not have this. Narrowing it
390
- * here means dispatching sub-steps individually on replay, which is a behaviour change to the
391
- * replay path and belongs in its own commit.
392
- */
393
- const declared = [step.expect, ...(step.steps ?? []).map((sub) => sub.expect)].filter((expectation) => expectation !== undefined);
394
- for (const expectation of declared) {
395
- if (!result.ok || result.drift !== undefined)
416
+ if (page !== undefined)
417
+ result.page = page;
418
+ const windowEvents = session.eventsSince(cursorBefore).filter((e) => e.t >= cursorBefore);
419
+ const consequence = summarizeConsequence(windowEvents);
420
+ if (consequence !== undefined)
421
+ result.consequence = consequence;
422
+ // Per-step wall time is NOT shipped: it is `window.until - window.since`, computed from two numbers
423
+ // the next line already puts in the same object, under the same emission condition. A step used to
424
+ // carry the subtraction AND both operands, on every step of every replay.
425
+ const cursorAfter = session.elapsed();
426
+ // ONE builder, shared with the live act path, so a driven step and a replayed one describe what
427
+ // happened in identical words rather than in two vocabularies that agree by coincidence.
428
+ Object.assign(result, stepEffect(windowEvents, { since: cursorBefore, until: cursorAfter }));
429
+ /*
430
+ * A prefix step is setup and is not reported -- UNLESS it failed.
431
+ *
432
+ * A failure there means the resume never reached the step it was asked to resume from, and the
433
+ * run did not start where the caller will read it as having started. Swallowing it would turn
434
+ * "I could not get there" into "I got there and it was fine".
435
+ */
436
+ // `tool` is dropped HERE rather than at the ten places that set it, so a new step runner cannot
437
+ // forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
438
+ if (FlowStepTool.ACT === result.tool)
439
+ delete result.tool;
440
+ results.push(result);
441
+ // Under `sweep`, a failure whose action still RAN does not stop the run — the page is where the
442
+ // step left it, so the next step is as meaningful as it was going to be. Anything else halts.
443
+ const sweepPast = true === options.sweep &&
444
+ result.drift !== undefined &&
445
+ isConsequenceDrift(result.drift.reasonKind);
446
+ if (!sweepPast && (result.drift !== undefined || !result.ok))
396
447
  break;
397
- const expectDrift = await assertStepExpect(session, expectation, dynamic, waitForSignal, waitFor(step), cursorBefore);
398
- if (expectDrift !== undefined) {
399
- result.ok = false;
400
- result.drift = expectDrift;
401
- }
448
+ index += 1;
402
449
  }
403
- if (page !== undefined)
404
- result.page = page;
405
- const windowEvents = session.eventsSince(cursorBefore).filter((e) => e.t >= cursorBefore);
406
- const consequence = summarizeConsequence(windowEvents);
407
- if (consequence !== undefined)
408
- result.consequence = consequence;
409
- // Per-step wall time is NOT shipped: it is `window.until - window.since`, computed from two numbers
410
- // the next line already puts in the same object, under the same emission condition. A step used to
411
- // carry the subtraction AND both operands, on every step of every replay.
412
- const cursorAfter = session.elapsed();
413
- // ONE builder, shared with the live act path, so a driven step and a replayed one describe what
414
- // happened in identical words rather than in two vocabularies that agree by coincidence.
415
- Object.assign(result, stepEffect(windowEvents, { since: cursorBefore, until: cursorAfter }));
416
- /*
417
- * A prefix step is setup and is not reported -- UNLESS it failed.
418
- *
419
- * A failure there means the resume never reached the step it was asked to resume from, and the
420
- * run did not start where the caller will read it as having started. Swallowing it would turn
421
- * "I could not get there" into "I got there and it was fine".
422
- */
423
- // `tool` is dropped HERE rather than at the ten places that set it, so a new step runner cannot
424
- // forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
425
- if (FlowStepTool.ACT === result.tool)
426
- delete result.tool;
427
- if (index >= from || !result.ok || result.drift !== undefined)
428
- results.push(result);
429
- // Under `sweep`, a failure whose action still RAN does not stop the run — the page is where the
430
- // step left it, so the next step is as meaningful as it was going to be. Anything else halts.
431
- const sweepPast = true === options.sweep &&
432
- result.drift !== undefined &&
433
- isConsequenceDrift(result.drift.reasonKind);
434
- if (!sweepPast && (result.drift !== undefined || !result.ok))
435
- break;
436
- index += 1;
450
+ }
451
+ catch (error) {
452
+ if (!isDocumentGoneError(error))
453
+ throw error;
454
+ throw new DocumentLostDuringReplay(results, index, error);
437
455
  }
438
456
  return results;
439
457
  }
@@ -1,4 +1,5 @@
1
1
  import { FlowErrorCode } from '@reticlehq/core';
2
+ import { type ProjectId } from '@reticlehq/core';
2
3
  /** Discriminated result so callers never branch on free strings. */
3
4
  export type FlowResult<T> = {
4
5
  ok: true;
@@ -14,5 +15,21 @@ export type FlowResult<T> = {
14
15
  * An unsafe or absent value collapses to `undefined` — the flat, global store — so a malformed id
15
16
  * can never escape `.reticle/flows/`. The store defends the disk boundary itself rather than
16
17
  * trusting whatever arrived in a session's HELLO.
18
+ *
19
+ * Held to one SEGMENT, not to a flow name. A project id becomes a DIRECTORY; a flow name is an
20
+ * ADDRESS, and since composites began naming sub-journeys as `onboarding/signup` its pattern has
21
+ * allowed separators. Validating a directory name with the address guard let a page choose how deep
22
+ * under `.reticle/` Reticle wrote — not an escape, since neither pattern admits `..` or a leading
23
+ * separator, but the page naming directories all the same.
24
+ *
25
+ * A FILTER, not a mint. It answers "is this id safe on disk", never "is this id a project" — a
26
+ * session id passes `SAFE_SEGMENT_PATTERN` just as cleanly, so minting the brand here would launder
27
+ * the exact confusion `ProjectId` exists to catch. The brand arrives already attached, from HELLO
28
+ * or from `.reticle.json`; see `ProjectId` in core.
29
+ *
30
+ * The narrowing said it out loud and nobody was listening: `isValidFlowName` narrows its argument to
31
+ * `FlowName`, for a value that is a project. The sibling guards for session and run ids have used
32
+ * the segment pattern since that widening, and `id-guards-are-not-flow-names.test.ts` exists to
33
+ * hold the distinction — this is the fourth id joined into a path, and the one that file missed.
17
34
  */
18
- export declare const safeProjectId: (projectId?: string) => string | undefined;
35
+ export declare const safeProjectId: (projectId?: ProjectId) => ProjectId | undefined;
@@ -1,10 +1,26 @@
1
1
  import { FlowErrorCode } from '@reticlehq/core';
2
- import { isValidFlowName } from '../../memory/project/dir/reticle-dir.js';
2
+ import { SAFE_SEGMENT_PATTERN } from '@reticlehq/core';
3
3
  /**
4
4
  * A project id that is safe to put in a path, or nothing.
5
5
  *
6
6
  * An unsafe or absent value collapses to `undefined` — the flat, global store — so a malformed id
7
7
  * can never escape `.reticle/flows/`. The store defends the disk boundary itself rather than
8
8
  * trusting whatever arrived in a session's HELLO.
9
+ *
10
+ * Held to one SEGMENT, not to a flow name. A project id becomes a DIRECTORY; a flow name is an
11
+ * ADDRESS, and since composites began naming sub-journeys as `onboarding/signup` its pattern has
12
+ * allowed separators. Validating a directory name with the address guard let a page choose how deep
13
+ * under `.reticle/` Reticle wrote — not an escape, since neither pattern admits `..` or a leading
14
+ * separator, but the page naming directories all the same.
15
+ *
16
+ * A FILTER, not a mint. It answers "is this id safe on disk", never "is this id a project" — a
17
+ * session id passes `SAFE_SEGMENT_PATTERN` just as cleanly, so minting the brand here would launder
18
+ * the exact confusion `ProjectId` exists to catch. The brand arrives already attached, from HELLO
19
+ * or from `.reticle.json`; see `ProjectId` in core.
20
+ *
21
+ * The narrowing said it out loud and nobody was listening: `isValidFlowName` narrows its argument to
22
+ * `FlowName`, for a value that is a project. The sibling guards for session and run ids have used
23
+ * the segment pattern since that widening, and `id-guards-are-not-flow-names.test.ts` exists to
24
+ * hold the distinction — this is the fourth id joined into a path, and the one that file missed.
9
25
  */
10
- export const safeProjectId = (projectId) => projectId !== undefined && isValidFlowName(projectId) ? projectId : undefined;
26
+ export const safeProjectId = (projectId) => projectId !== undefined && SAFE_SEGMENT_PATTERN.test(projectId) ? projectId : undefined;
@@ -1,17 +1,26 @@
1
- /**
2
- * How one anchored step is turned into a live action.
3
- *
4
- * Split out of flow-replay.ts when adding the role+name runner pushed that file past the size cap.
5
- * These three share one shape — resolve an anchor to a ref, then dispatch — and the sharing is the
6
- * point: the action window must open and close identically no matter which anchor found the element,
7
- * or a step's events land in the wrong window depending on how it was addressed.
8
- */
9
- import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult } from '@reticlehq/core';
1
+ import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult, type Predicate } from '@reticlehq/core';
10
2
  import type { FlowReplaySession, Sleep } from './flow-replay-types.js';
11
3
  export declare function runRoleStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
12
4
  kind: typeof AnchorKind.ROLE;
13
5
  }>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
14
- export declare function replayDestructiveActionHint(rawError: string): string;
6
+ /**
7
+ * Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
8
+ * including the action window, whose open/close must not depend on which anchor found the element.
9
+ */
10
+ export declare function actOnResolvedRef(session: FlowReplaySession, step: FlowStep, index: number, label: string, ref: string, confirmDangerous: boolean,
11
+ /**
12
+ * The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
13
+ * redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
14
+ * and looked up at replay under no name at all, so the flow types the placeholder into the form.
15
+ */
16
+ field?: string,
17
+ /**
18
+ * Resolve the anchor again. Supplied by the caller because only it knows how this step's anchor is
19
+ * found, and the retry below is worthless without it: re-dispatching the SAME dead ref fails
20
+ * identically. What changed between the two attempts is the DOM, so the locator has to be run
21
+ * against it again.
22
+ */
23
+ reresolve?: () => Promise<string | undefined>): Promise<FlowStepResult>;
15
24
  /** Run one component-anchored step: re-resolve via QUERY by:'component', ACT on the live ref, else drift. */
16
25
  export declare function runComponentStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
17
26
  kind: typeof AnchorKind.COMPONENT;
@@ -39,6 +48,13 @@ export declare function degradedStepResult(step: FlowStep, index: number, label:
39
48
  * the step runner's own resolution asking the same thing of the same anchor.
40
49
  */
41
50
  export declare function anchorQueryArgs(anchor: FlowAnchor): Record<string, unknown> | null;
51
+ /**
52
+ * The anchor as a PRECONDITION a flow file can store: "this element is on the page".
53
+ *
54
+ * What `requires` means for a flow that starts from state a page load discards, in the shape the
55
+ * flow's own `requires` holds. No tool writes `requires`, so the reset's hint hands over this line.
56
+ */
57
+ export declare function anchorPrecondition(anchor: FlowAnchor): Predicate | undefined;
42
58
  /**
43
59
  * Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
44
60
  * ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one