@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
@@ -3,23 +3,8 @@ import { aliasParam } from './args/alias-args.js';
3
3
  import { resolveSessionWithin } from '../../portal/session/timing/resolve-within.js';
4
4
  import { WALL_CLOCK } from '../../portal/session/timing/wall-clock.js';
5
5
  import { timeoutMsSchema } from './args/numeric-bounds.js';
6
- import { captureAct } from '../../language/flows/replay.js';
7
- /**
8
- * The pathname of a session's live url, or undefined when it has none yet.
9
- *
10
- * Pathname rather than the whole url, because that is what `startPath` is compared against and what
11
- * a hash-routed app makes meaningless in the document location — see routeOfEvent.
12
- */
13
- function pathOf(url) {
14
- if (url === undefined)
15
- return undefined;
16
- try {
17
- return new URL(url).pathname;
18
- }
19
- catch {
20
- return undefined;
21
- }
22
- }
6
+ import { captureAct, captureVerdictedAct, pathOf } from '../../language/flows/replay.js';
7
+ import { durableArg, durableOutput, reloadAndRecheck, withDurability } from './act/durable.js';
23
8
  import { ActionType, ActionWarning, ReticleCommand, CaptureLoss, DEFAULT_ASSERT_TIMEOUT_MS, InputMode, Verified, VerifiedReason, PredicateKind, } from '@reticlehq/core';
24
9
  import { leanActResult, mutatedWithin } from './act/act-view.js';
25
10
  import { ReticleTool } from '@reticlehq/core';
@@ -37,12 +22,12 @@ import { intentDebt, openSessionIntents } from '../../memory/intent/open-intents
37
22
  import { dischargeInlineIntent, inlineVerdictId, linkInlineIntent, } from '../../memory/intent/inline-intent.js';
38
23
  import { declaresState } from '@reticlehq/engine/question/predicate/predicate-asks.js';
39
24
  import { isStateUnwatched } from '@reticlehq/engine/evidence/blind-spots.js';
40
- import { inFlightRequestLabels, repeatedRequestLabels, waitForInFlight, } from './act/settle-in-flight.js';
41
- import { waitForReaction } from './act/react-grace.js';
25
+ import { inFlightRequestLabels, repeatedRequestLabels } from './act/settle-in-flight.js';
26
+ import { finishAfterMatch } from './act/after-match.js';
27
+ import { noteProved } from './act/proved-controls.js';
42
28
  import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
43
29
  import { honestyForVerdict } from '@reticlehq/engine/evidence/honesty.js';
44
30
  import { declaredExpectations, declaresBodyIndependentChannel, } from '@reticlehq/engine/question/declared.js';
45
- import { readsDomState, alreadyTrueHiddenMatch as alreadyTrueHiddenMatchOf, } from '@reticlehq/engine/evidence/already-true.js';
46
31
  import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
47
32
  import { saveFailedAssertCapsule } from './act-capsule.js';
48
33
  import { blastRadius, buildDivergenceCapsule, wireCapsule } from '../../judgement/capsule/capsule.js';
@@ -52,7 +37,8 @@ import { absenceBlindSpotNote, buildCoverageStatement, blindSpotsFromState, tran
52
37
  import { acceptedWriteLabels } from '@reticlehq/engine/evidence/accepted-write.js';
53
38
  import { unreadWriteLabels } from '@reticlehq/engine/evidence/unread-outcome.js';
54
39
  import { evaluatePredicate, waitForPredicate, provenExpectedLinks, PredicateSchema, } from '@reticlehq/engine/question/predicate/predicate.js';
55
- import { healthEnvelope, refuseIfThrottled } from '../../portal/session/session-health.js';
40
+ import { sessionVerdictFacts } from '../../portal/session/session-verdict-facts.js';
41
+ import { currentOf, healthEnvelope, refuseIfThrottled } from '../../portal/session/session-health.js';
56
42
  import { pausedShortCircuit, pausedOutputShape, withControl, PAUSED_NO_VERDICT, } from '../../portal/session/control-envelope.js';
57
43
  import { asNumber, asRecord, asString } from '@reticlehq/core';
58
44
  import { sourceOf } from './tools-helpers.js';
@@ -64,6 +50,7 @@ import { asActionType, gradeOf } from './act/act-helpers.js';
64
50
  import { resolveActTarget } from './act/act-target.js';
65
51
  import { tryRealInput, rewriteUploadArgs, HOVER_NEEDS_POINTER_MSG } from './real-input-attempt.js';
66
52
  import { gradeOfPredicate } from './assert/assert-grade.js';
53
+ import { readAlreadyTrue } from './act/already-true.js';
67
54
  /**
68
55
  * Narrow the wire's `action` to a real ActionType, or undefined.
69
56
  *
@@ -253,6 +240,7 @@ export const ACT_TOOLS = [
253
240
  });
254
241
  }
255
242
  finally {
243
+ deps.recordings.markEnded(pathOf(session.url));
256
244
  // Close the window on every exit (settle or throw), recording the action + settle outcome.
257
245
  session.finishAction(undefined, settledOutcome, true === settledOutcome ? session.elapsed() - since : undefined);
258
246
  }
@@ -285,7 +273,7 @@ export const ACT_TOOLS = [
285
273
  .record(z.unknown())
286
274
  .optional()
287
275
  .describe('Action-specific arguments: { value } for fill/select, { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { confirmDangerous: true } for a potentially destructive control. For upload: { path } is a path on disk (absolute or relative to project root; daemon reads real bytes) or { name, content?, type? } for inline bytes.'),
288
- predicate: PredicateSchema.optional().describe('Alias for `until` (the name reticle_assert / reticle_wait_for use).'),
276
+ predicate: PredicateSchema.optional().describe('Alias for `until`.'),
289
277
  until: PredicateSchema.optional().describe('Predicate to wait for after the action completes (same shape as reticle_assert). OMIT to wait for the page to SETTLE — network + DOM idle — the deterministic default instead of a sleep. To assert a consequence AND settle, allOf them: { kind: "allOf", predicates: [<your predicate>, { kind: "settled" }] }.'),
290
278
  timeout_ms: timeoutMsSchema
291
279
  .optional()
@@ -295,9 +283,11 @@ export const ACT_TOOLS = [
295
283
  .optional()
296
284
  .describe('Throw if the tab is throttled. Default: false.'),
297
285
  intent: intentArg,
286
+ durable: durableArg,
298
287
  ...sessionIdShape,
299
288
  },
300
289
  outputSchema: {
290
+ durable: durableOutput,
301
291
  effect: z
302
292
  .unknown()
303
293
  .describe('The reticle_act result (dispatched, settled, inputMode, etc.).'),
@@ -428,22 +418,13 @@ export const ACT_TOOLS = [
428
418
  // A verdict that lives only in the response lives only in the agent's context window, which is
429
419
  // exactly the copy a compaction destroys — see runs/run-context.ts.
430
420
  let verdictEffect;
431
- // Was the declared consequence ALREADY TRUE? Only asked for predicates that read live DOM
432
- // state — event-based ones are floored at this act's cursor and cannot be satisfied by the
433
- // past, so they need no pre-check and pay nothing. One extra query, on the path where a green
434
- // is otherwise unfalsifiable. See engine/src/evidence/already-true.ts.
435
- const alreadyTruePrecheck = until !== undefined && readsDomState(until)
436
- ? await evaluatePredicate(session, until, since, false)
437
- : undefined;
438
- const alreadyTrue = alreadyTruePrecheck?.pass ?? false;
421
+ // Was the consequence ALREADY TRUE, and what did that reading say? See act/already-true.ts.
422
+ const { alreadyTrue, alreadyTrueHiddenMatch, alreadyTrueEvidence, baselines } = await readAlreadyTrue(session, until, since);
439
423
  // Same as the ACT handler: the route this step RAN on, before the action can move the app.
440
424
  const routeBeforeWait = pathOf(session.url);
441
- // #889: the pre-check evidence is already in hand — cheap to also ask whether that match was
442
- // against something hidden, so the already_true message can name it instead of leaving the
443
- // agent to re-derive "was this actually showing?" from nothing.
444
- const alreadyTrueHiddenMatch = alreadyTrue && until !== undefined
445
- ? alreadyTrueHiddenMatchOf(until, alreadyTruePrecheck?.evidence)
446
- : false;
425
+ // Recorded in `finally`, once the verdict is known — see captureVerdictedAct.
426
+ let dispatched;
427
+ let recordedVerdict;
447
428
  try {
448
429
  // actCommand is the single interception point for upload+path rewrite.
449
430
  //
@@ -461,7 +442,7 @@ export const ACT_TOOLS = [
461
442
  if (actResult !== null) {
462
443
  if (!actResult.ok)
463
444
  throw new Error(actResult.error ?? 'act failed');
464
- captureAct(deps.recordings, args, actResult.result, routeBeforeWait);
445
+ dispatched = { result: actResult.result };
465
446
  // Dispatched — now this act owns the cursor and the effect. Marking its OWN measurement
466
447
  // also stops the spread below from inheriting an earlier reticle_act's action and
467
448
  // mutation count.
@@ -472,8 +453,8 @@ export const ACT_TOOLS = [
472
453
  let verdict = null === actResult
473
454
  ? { pass: false, observationLost: true }
474
455
  : timeout > 0
475
- ? await waitForPredicate(session, until, timeout, since)
476
- : await evaluatePredicate(session, until, since);
456
+ ? await waitForPredicate(session, until, timeout, since, baselines)
457
+ : await evaluatePredicate(session, until, since, true, baselines);
477
458
  // The SDK may have gone away mid-act — see act-observation.ts.
478
459
  const followed = await followLostObservation({
479
460
  sessions: deps.sessions,
@@ -481,40 +462,18 @@ export const ACT_TOOLS = [
481
462
  verdict,
482
463
  timeout,
483
464
  predicateStarted,
484
- reevaluate: (next, budget) => waitForPredicate(next, until, budget, 0),
465
+ reevaluate: (next, budget) => waitForPredicate(next, until, budget, 0, baselines),
485
466
  });
486
467
  if (followed.followed)
487
468
  since = 0;
488
469
  session = followed.session;
489
470
  verdict = followed.verdict;
490
- // The predicate resolves the INSTANT it holds, which on an optimistically-navigating app is
491
- // while the write is still in flight — so the verdict was taken over a window the app had not
492
- // finished, and came back `unknown / unsettled` asking the CALLER to re-check. A login that
493
- // genuinely succeeded returned at 492ms of an 8000ms budget with every corroborating channel
494
- // agreeing. Spend the rest of the budget the caller already granted.
495
- //
496
- // This decides nothing: it waits for the answer instead of guessing at it. A response that
497
- // arrives as a FAILURE still contradicts — and now arrives INSIDE the window, where the
498
- // detector can see it at all — while a request still open when the budget genuinely runs out
499
- // reports unsettled exactly as before, an honest limit rather than an early exit. Costs
500
- // nothing on the common path, where no request is in flight.
471
+ // After a match, let the app finish before the window closes — see act/after-match.ts.
501
472
  if (verdict.pass && timeout > 0) {
502
- const sleep = {
503
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
504
- };
505
- const spent = () => timeout - (session.elapsed() - predicateStarted);
506
- await waitForInFlight(session, since, spent(), sleep);
507
- // Waiting for the response moved the hazard rather than removing it: the response now lands
508
- // inside the window BY DESIGN, and the app's re-render happens a task or two later. Close
509
- // the window in that gap and every channel agrees the app took a successful write and did
510
- // nothing — `response-ignored`, i.e. `verified:"no"` on a correct app, produced entirely by
511
- // where we stopped looking. A false accusation is the more damaging direction of error for
512
- // a verification tool: it sends someone to fix code that is not broken.
513
- //
514
- // So the response is not the end of the window; the app's REACTION to it is. Paid only in
515
- // the shape that would otherwise be accused — a successful mutating write with nothing
516
- // moved after it — and short enough that a genuinely dropped response is still reported.
517
- await waitForReaction(session, since, spent(), sleep);
473
+ const left = () => timeout - (session.elapsed() - predicateStarted);
474
+ const reverted = await finishAfterMatch(session, until, since, baselines, left);
475
+ if (reverted !== undefined)
476
+ verdict = { pass: false, failureReason: reverted, observed: reverted };
518
477
  }
519
478
  const r = asRecord(actResult?.result);
520
479
  if ('boolean' === typeof r['settled'])
@@ -654,17 +613,17 @@ export const ACT_TOOLS = [
654
613
  currentDocumentId: session.currentDocumentId,
655
614
  currentEditEpoch: session.currentEditEpoch,
656
615
  appOrigin: session.url,
616
+ background: session.background,
657
617
  });
658
618
  // The single field an agent reads. Everything below it is the evidence it was derived from;
659
619
  // this is the only one that has to be interpreted, and now it interprets itself.
660
620
  const outcomePending = acceptedWriteLabels(windowEvents);
661
621
  const outcomeUnread = unreadWriteLabels(windowEvents);
662
- const stillInFlight = inFlightRequestLabels(windowEvents);
663
- const decision = decideVerified({
622
+ const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
623
+ const { decision, durable } = await withDurability(args['durable'], until, decideVerified({
664
624
  pass: verdict.pass,
665
- // So the unread-body remedy can check it applies to THIS page. Threaded rather than
666
- // looked up inside decideVerified, which is pure and has no session.
667
- ...(session.sdkVersion === undefined ? {} : { sdkVersion: session.sdkVersion }),
625
+ // Threaded rather than looked up: decideVerified is pure and has no session.
626
+ ...sessionVerdictFacts(session),
668
627
  // The caller NAMED the consequence rather than defaulting to "wait for idle". A
669
628
  // declaration made before the action is what this tool sells, and idle-settlement was
670
629
  // overriding it — see `declaredConsequence`. An explicit `{ kind: "settled" }` is not a
@@ -701,7 +660,9 @@ export const ACT_TOOLS = [
701
660
  repeated: repeatedRequestLabels(windowEvents),
702
661
  },
703
662
  ...(namedNetIsInFlight(until, stillInFlight) ? { namedRequestInFlight: true } : {}),
704
- });
663
+ }), (part) => reloadAndRecheck(deps, session, part, timeout));
664
+ recordedVerdict = String(decision.verified);
665
+ noteProved(session, recordedVerdict, actResult?.result);
705
666
  // Computed once: the verdict block reports it, and the instrumentation gaps are a second
706
667
  // reading of the same evidence rather than a new observation.
707
668
  const actionSummary = causalSummary(windowEvents, {
@@ -770,6 +731,16 @@ export const ACT_TOOLS = [
770
731
  // which is why the two are recorded apart rather than assumed alike.
771
732
  declaredBeforeActing: true,
772
733
  grade: gradeOfPredicate(until),
734
+ // What the declared consequence READ. The run fold has only the journal, so a kind absent
735
+ // here was being invented there as `element`. Same field, same reason, on the assert path.
736
+ kind: until.kind,
737
+ // The deciding clause, kept — and it was NOT, on this path, the commonest one. `unknown`
738
+ // alone cannot tell an outcome that has not arrived from a capture that could not be
739
+ // read, so a fold over the journal saw a verdict it could not attribute, and the
740
+ // attribution is the only part of an `unknown` a reader can act on. The comment above
741
+ // says "same field, same reason, on the assert path" and was half true. Found by the
742
+ // session gap summary, whose `undecidedBy` was empty for every unknown this tool made.
743
+ reason: decision.verifiedReason,
773
744
  };
774
745
  // Recorded on the session, so a later "am I done?" can answer with what is STILL missing
775
746
  // rather than with everything that was ever missing. An empty list closes a gap, which is
@@ -777,6 +748,8 @@ export const ACT_TOOLS = [
777
748
  noteSessionGaps(session, gaps);
778
749
  return withControl(session, {
779
750
  ...decision,
751
+ // WHAT was already true, not only that something was (4.1) — see act/already-true.ts.
752
+ ...(alreadyTrueEvidence === undefined ? {} : { alreadyTrueEvidence }),
780
753
  // An unobserved act has no effect to report, and inventing an empty one would read as
781
754
  // "the page did nothing" — a claim about the app, from a call that never saw it.
782
755
  ...(null === actResult ? {} : { effect: leanActResult(actResult.result) }),
@@ -817,16 +790,20 @@ export const ACT_TOOLS = [
817
790
  // agent cannot miss it by not asking. Omitted entirely when clean, so a healthy action
818
791
  // pays nothing.
819
792
  ...(contradictions.length > 0 ? { contradictions } : {}),
793
+ ...(durable === undefined ? {} : { durable }),
820
794
  honesty: honestyForVerdict(String(decision.verified), honesty),
821
795
  // Without its summary: it is byte-identical to `summary` two fields up. See wireCapsule.
822
796
  ...(capsule === undefined ? {} : { capsule: wireCapsule(capsule) }),
823
797
  ...(0 === greenRadius.length ? {} : { blastRadius: greenRadius }),
824
798
  since,
825
799
  ...(session.id === actedSessionId ? {} : { sessionId: session.id }),
826
- ...healthEnvelope(session),
800
+ ...healthEnvelope(currentOf(deps.sessions, session)),
827
801
  });
828
802
  }
829
803
  finally {
804
+ if (dispatched !== undefined)
805
+ captureVerdictedAct(deps.recordings, args, dispatched.result, routeBeforeWait, recordedVerdict);
806
+ deps.recordings.markEnded(pathOf(currentOf(deps.sessions, session).url));
830
807
  acted.finishAction(verdictEffect, settledOutcome, true === settledOutcome ? acted.elapsed() - actedSince : undefined);
831
808
  }
832
809
  },
@@ -31,8 +31,6 @@ export declare const MAX_BLOCKING_WAIT_MS: number;
31
31
  * in the name of bounding one that never worked.
32
32
  */
33
33
  export declare const MAX_STATE_DEPTH = 256;
34
- /** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
35
- export declare const MAX_SCROLLS = 200;
36
34
  /** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
37
35
  export declare const MIN_VIEWPORT_PX = 64;
38
36
  export declare const MAX_VIEWPORT_PX = 10000;
@@ -45,6 +43,21 @@ export declare const cursorSchema: z.ZodNumber;
45
43
  * A wait budget in ms. 0 means evaluate now (documented on assert). Negative cannot be honoured.
46
44
  */
47
45
  export declare const timeoutMsSchema: z.ZodNumber;
46
+ /**
47
+ * `reticle_wait_for`'s budget, which may exceed one call: it waits at most MAX_BLOCKING_WAIT_MS per
48
+ * call and hands back `resume_ms` for the rest (#601, from #635).
49
+ */
50
+ export declare const waitForTimeoutMsSchema: z.ZodNumber;
51
+ /**
52
+ * What is left of a wait that stopped at the per-call limit, or undefined when there is nothing to
53
+ * resume: the predicate held, the whole budget fit in this call, or the wait ended for a reason
54
+ * waiting longer cannot change (the page went away, or the read could not be made).
55
+ */
56
+ export declare function resumeAfter(requestedMs: number, perCallMs: number, verdict: {
57
+ pass: boolean;
58
+ observationLost?: boolean;
59
+ inconclusive?: string;
60
+ }): number | undefined;
48
61
  /**
49
62
  * A count / cap. 0 means "return none", which is a real request. Negative is not.
50
63
  * Capped at the ring-buffer size so a `limit: 1e9` cannot pretend the buffer is larger than it is.
@@ -38,8 +38,8 @@ export const MAX_TIMEOUT_MS = 120_000;
38
38
  *
39
39
  * This does not make long waits possible, and is not meant to — it makes the ADVERTISED bound one
40
40
  * that can actually be honoured. A caller that genuinely needs to outlast this polls: several short
41
- * waits, each of which returns a verdict. See #601 for the bounded-wait cursor that would let one
42
- * call do it properly.
41
+ * waits, each of which returns a verdict. `reticle_wait_for` does that for the caller: see
42
+ * `waitForTimeoutMsSchema` and `resumeAfter`.
43
43
  */
44
44
  const MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
45
45
  export const MAX_BLOCKING_WAIT_MS = MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS - 5_000;
@@ -54,7 +54,7 @@ export const MAX_STATE_DEPTH = 256;
54
54
  /** A crawl that clicks forever is not a bound. */
55
55
  const MAX_CRAWL_STEPS = 500;
56
56
  /** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
57
- export const MAX_SCROLLS = 200;
57
+ const MAX_SCROLLS = 200;
58
58
  /** Fake-clock jump. One day of timers is a test; a billion ms is not. */
59
59
  const MAX_ADVANCE_MS = 86_400_000;
60
60
  /** Presenter idle window. One hour is a slow app; larger values never fire. */
@@ -71,6 +71,33 @@ export const cursorSchema = z.number().finite().int().nonnegative();
71
71
  * A wait budget in ms. 0 means evaluate now (documented on assert). Negative cannot be honoured.
72
72
  */
73
73
  export const timeoutMsSchema = z.number().finite().int().nonnegative().max(MAX_BLOCKING_WAIT_MS);
74
+ /**
75
+ * The longest `reticle_wait_for` accepts, across as many calls as it takes. Ten minutes is a slow
76
+ * build or a queued job; an hour is a hang somebody forgot about.
77
+ */
78
+ const MAX_RESUMABLE_WAIT_MS = 600_000;
79
+ /**
80
+ * `reticle_wait_for`'s budget, which may exceed one call: it waits at most MAX_BLOCKING_WAIT_MS per
81
+ * call and hands back `resume_ms` for the rest (#601, from #635).
82
+ */
83
+ export const waitForTimeoutMsSchema = z
84
+ .number()
85
+ .finite()
86
+ .int()
87
+ .nonnegative()
88
+ .max(MAX_RESUMABLE_WAIT_MS);
89
+ /**
90
+ * What is left of a wait that stopped at the per-call limit, or undefined when there is nothing to
91
+ * resume: the predicate held, the whole budget fit in this call, or the wait ended for a reason
92
+ * waiting longer cannot change (the page went away, or the read could not be made).
93
+ */
94
+ export function resumeAfter(requestedMs, perCallMs, verdict) {
95
+ if (verdict.pass || true === verdict.observationLost || verdict.inconclusive !== undefined) {
96
+ return undefined;
97
+ }
98
+ const left = requestedMs - perCallMs;
99
+ return left > 0 ? left : undefined;
100
+ }
74
101
  /**
75
102
  * A count / cap. 0 means "return none", which is a real request. Negative is not.
76
103
  * Capped at the ring-buffer size so a `limit: 1e9` cannot pretend the buffer is larger than it is.
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Pure: no IO, no clock.
12
12
  */
13
- import { isConsequenceKind, isPresenceKind, PredicateKind } from '@reticlehq/core';
13
+ import { compareSourceClauses, isConsequenceKind, isPresenceKind, PredicateKind, } from '@reticlehq/core';
14
14
  import { HonestyGrade, strongerGrade, weakerGrade } from '@reticlehq/engine/evidence/honesty.js';
15
15
  export const PRESENCE_ONLY_ADVICE = 'This predicate only checks element/text presence, not an observable consequence. A locator healed to the wrong element (or a stale render) can satisfy it while the feature is broken. Prefer a { signal } or { net } assertion (or allOf it with one) so green means the feature actually worked.';
16
16
  function walk(predicate, negated = false) {
@@ -27,6 +27,14 @@ function walk(predicate, negated = false) {
27
27
  // never matched anything in the first place. Negation makes a presence check weaker, not stronger.
28
28
  if (PredicateKind.NOT === predicate.kind)
29
29
  return walk(predicate.predicate, true);
30
+ // A comparison grades as what its sides read: with a net/signal/state side it relates the page to
31
+ // the app's own record, which a wrong locator cannot fake; text against text is two readings of
32
+ // the same DOM. Its text side is always a content check, so it never counts as bare presence
33
+ // unless negated.
34
+ if (PredicateKind.COMPARE === predicate.kind) {
35
+ const consequence = compareSourceClauses(predicate).some((c) => isConsequenceKind(c.kind));
36
+ return { consequence, presence: !consequence && negated };
37
+ }
30
38
  // Leaf: classify against the single source of truth in core. Kinds that are neither consequence
31
39
  // nor presence (route/console/settled/animation) correctly return false for both.
32
40
  return {
@@ -109,17 +117,32 @@ export function gradeOfPredicate(predicate) {
109
117
  // verdict that proved only presence report `signal`, and a `minGrade: net` gate would trust it.
110
118
  case PredicateKind.ANY_OF:
111
119
  return combine(predicate.predicates, weakerGrade);
120
+ // Both sides had to be read and agree, so it proves as much as its stronger side.
121
+ case PredicateKind.COMPARE:
122
+ return combine(compareSourceClauses(predicate), strongerGrade);
112
123
  // Everything else, `not` included: an absence claim is satisfied trivially by a locator that
113
124
  // never matched anything, which is the argument `walk` already makes about negation.
114
125
  default:
115
126
  return HonestyGrade.PRESENCE;
116
127
  }
117
128
  }
118
- /** Fold a combinator's branches on the ladder. No branches proves nothing, so it stays at the floor. */
129
+ /**
130
+ * Fold a combinator's branches on the ladder. No branches proves nothing, so it stays at the floor.
131
+ *
132
+ * The floor is `NONE`, not `PRESENCE`. This sentence has been here since the function was written
133
+ * and the code returned one rung above it, which is the entire defect: `VACUOUS_GRADE`
134
+ * (`engine/src/evidence/verified.ts`) fires on `NONE`, so an empty combinator graded `PRESENCE`
135
+ * walked past the one guard whose job is refusing a green that rests on nothing — and reported
136
+ * `verified: "yes"` for a drive that asserted nothing at all.
137
+ *
138
+ * The schema refuses an empty combinator now, so this is the second defence rather than the only
139
+ * one. It is kept because the two stop different things: the schema stops it ENTERING through the
140
+ * documented door, and this stops it COUNTING if it is ever constructed rather than parsed.
141
+ */
119
142
  function combine(branches, pick) {
120
143
  const grades = branches.map(gradeOfPredicate);
121
144
  const first = grades[0];
122
145
  if (first === undefined)
123
- return HonestyGrade.PRESENCE;
146
+ return HonestyGrade.NONE;
124
147
  return grades.reduce(pick, first);
125
148
  }
@@ -1,4 +1,5 @@
1
- import { CaptureLoss, channelsRead, PredicateKind } from '@reticlehq/core';
1
+ import { CaptureLoss, channelsReadBy, PredicateKind } from '@reticlehq/core';
2
+ import { sessionVerdictFacts } from '../../../portal/session/session-verdict-facts.js';
2
3
  import { gapsForAction } from '@reticlehq/engine/evidence/instrumentation-gaps.js';
3
4
  import { noteSessionGaps } from '@reticlehq/engine/evidence/gap-ledger.js';
4
5
  import { declaresState } from '@reticlehq/engine/question/predicate/predicate-asks.js';
@@ -15,6 +16,7 @@ import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/eviden
15
16
  import { inFlightRequestLabels, repeatedRequestLabels, } from '../act/settle-in-flight.js';
16
17
  import { gradeOfPredicate } from './assert-grade.js';
17
18
  import { assertSource } from './assert-source.js';
19
+ import { VerdictAttribution, verdictAttributionOf } from '@reticlehq/core';
18
20
  /**
19
21
  * The honesty verdict for a plain `reticle_assert`.
20
22
  *
@@ -85,6 +87,7 @@ changeUndeclared) {
85
87
  currentDocumentId: session.currentDocumentId,
86
88
  currentEditEpoch: session.currentEditEpoch,
87
89
  appOrigin: session.url,
90
+ background: session.background,
88
91
  expectedFailures: declared.netFailures,
89
92
  namedNetUrls: declared.netUrls,
90
93
  renderProved: pass && declared.rendersContent,
@@ -114,7 +117,7 @@ changeUndeclared) {
114
117
  const impeachingNotes = [impeaching.note, gap, ...crashedRuleNotes()].filter((n) => n !== undefined);
115
118
  const outcomePending = acceptedWriteLabels(windowEvents);
116
119
  const outcomeUnread = unreadWriteLabels(windowEvents);
117
- const stillInFlight = inFlightRequestLabels(windowEvents);
120
+ const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
118
121
  const effectiveInconclusive = inconclusive ?? (!pass ? session.preconditionFailure?.() : undefined);
119
122
  /**
120
123
  * Does a green here rest on the window having been COMPLETE?
@@ -145,11 +148,10 @@ changeUndeclared) {
145
148
  // first clause, and one this implementation could not run until the page started declaring.
146
149
  // Both halves are conditional on purpose: an SDK too old to declare sends nothing, and
147
150
  // treating that silence as an empty set would refuse every claim from every older page.
148
- channelsRead: channelsRead(predicate.kind),
151
+ channelsRead: channelsReadBy(predicate),
149
152
  ...(session.channels === undefined ? {} : { channelsObservable: session.channels }),
150
- // So the unread-body remedy can check it applies to THIS page. Threaded rather than
151
- // looked up inside decideVerified, which is pure and has no session.
152
- ...(session.sdkVersion === undefined ? {} : { sdkVersion: session.sdkVersion }),
153
+ // Threaded rather than looked up: decideVerified is pure and has no session.
154
+ ...sessionVerdictFacts(session),
153
155
  // Same rule as the act path: the caller named a consequence, so a settlement-only finding must
154
156
  // not override it. A fix that lived on one half of the verdict surface would leave the other
155
157
  // half broken, and this is the tool agents call most.
@@ -217,6 +219,8 @@ changeUndeclared) {
217
219
  pass,
218
220
  lastActSource: session.lastAct.source(),
219
221
  });
222
+ // Who can act on this verdict, derived from the clause that decided it — see `attributedTo` below.
223
+ const attributedTo = verdictAttributionOf(decision.verifiedReason);
220
224
  return {
221
225
  decision: decision,
222
226
  contradictions,
@@ -226,6 +230,9 @@ changeUndeclared) {
226
230
  claim: describeWaitTarget(predicate),
227
231
  verified: decision.verified,
228
232
  ...(source === undefined ? {} : { source }),
233
+ // What answering this claim READ. Known here and nowhere later: a run folded from the journal
234
+ // has only what the journal kept, and without this it stamped `element` on every check.
235
+ kind: predicate.kind,
229
236
  // The three facts that make a verdict something other than a pass/fail line. All three were
230
237
  // known right here and none of them survived into the durable record, so nothing downstream
231
238
  // could report them however well this moment understood them.
@@ -240,6 +247,25 @@ changeUndeclared) {
240
247
  // arrived from a capture that could not be read, and only the first is worth asking about
241
248
  // again -- which is what makes a later answer a CORRECTION rather than a second opinion.
242
249
  reason: decision.verifiedReason,
250
+ /*
251
+ * WHOSE problem it is, in four values, finishing the sentence above.
252
+ *
253
+ * The reason names the deciding clause; this names who can act on it, which for an `unknown`
254
+ * is the only question the reader has. `environment` resolves by waiting, `could-not-see` by
255
+ * looking again with better coverage, `harness` by fixing Reticle or the call, and `code` is
256
+ * the only one that means go and change the app. Omitted on a proof, which has no owner.
257
+ *
258
+ * Derived here rather than inside the verdict: it is a pure function of a reason the engine
259
+ * already returns, and `core-coupling-only-shrinks` caps what the engine may borrow from core.
260
+ *
261
+ * Named `attributedTo`, not `attribution`, because two other things already carry that word:
262
+ * `honesty.attribution` a few lines up ("what the grade is attributed to" — the window), and
263
+ * `BugAttribution` in telemetry ("we found a defect, whose defect is it?"). Three meanings on
264
+ * one noun is how a reader ends up acting on the wrong one.
265
+ */
266
+ ...(attributedTo === undefined || VerdictAttribution.NONE === attributedTo
267
+ ? {}
268
+ : { attributedTo }),
243
269
  grade: gradeOfPredicate(predicate),
244
270
  couldNotSee: spots.map((spot) => spot.kind),
245
271
  },
@@ -1,7 +1,7 @@
1
1
  import { carryReticleIdentity } from './lease-tools.js';
2
2
  import { z } from 'zod';
3
3
  import { navigateResult } from './act/navigation/navigate-result.js';
4
- import { awaitArrival, idsAtTarget, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
4
+ import { absoluteTarget, awaitArrival, idsAtTarget, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
5
5
  import { reloadResult } from './act/navigation/reload-result.js';
6
6
  import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS, } from '../../portal/session/session-reconnect.js';
7
7
  import { ReticleCommand } from '@reticlehq/core';
@@ -13,7 +13,7 @@ export const BROWSER_TOOLS = [
13
13
  {
14
14
  name: ReticleTool.NAVIGATE,
15
15
  example: { url: '/settings' },
16
- description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the page itself cannot report on it. The daemon then waits up to `timeout_ms` (default 5000) for the SDK to reconnect: `confirmed:true` with a new `sessionId` means the page arrived and you can act immediately. `confirmed:false` means it did not arrive within that window (reported as `waitedMs`) — the page may be slow, uninstrumented, or not there. A slow SPA can take 30-60s to reattach: raise `timeout_ms` rather than polling reticle_sessions.',
16
+ description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the old page cannot report on it. The daemon then waits up to `timeout_ms` (default 5000) for the SDK to reconnect. `confirmed:true` with a `sessionId` means the requested page arrived and you can act immediately. `confirmed:false` with `landedOn` means the navigation arrived but the app landed at another URL, such as an authentication or route-guard redirect; use `landedOn` to understand where the browser actually ended up. `confirmed:false` without `landedOn` means the target was not observed within the wait window; a slow app may still be on its way, so increase `timeout_ms` when appropriate.',
17
17
  inputSchema: {
18
18
  url: z.string().optional().describe('The URL to navigate to. Omit when using reload.'),
19
19
  reload: z
@@ -38,6 +38,10 @@ export const BROWSER_TOOLS = [
38
38
  confirmed: z.boolean().optional(),
39
39
  /** The session the SDK reconnected as, when arrival was confirmed — it is a NEW id. */
40
40
  sessionId: z.string().optional(),
41
+ /**
42
+ * The URL where the navigated document landed when it differs from the requested target.
43
+ */
44
+ landedOn: z.string().optional(),
41
45
  /**
42
46
  * On confirmed:false — the budget (ms) that expired without the page coming back. Says which
43
47
  * of "Reticle stopped waiting" and "the page never came back" you are looking at.
@@ -58,6 +62,7 @@ export const BROWSER_TOOLS = [
58
62
  // back past this point. Reported from the field as an assertion whose clauses all passed
59
63
  // coming back `contradicted` by hundreds of 500s against resources that were already gone.
60
64
  before.lastAct.markNavigated(before.elapsed());
65
+ deps.recordings.markNavigated();
61
66
  await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
62
67
  hard: true === args['hard'],
63
68
  });
@@ -89,12 +94,14 @@ export const BROWSER_TOOLS = [
89
94
  // Sampled BEFORE dispatch, because that is the only moment it can be known. A session already
90
95
  // sitting on the target is not evidence that THIS navigation arrived — and the arrival scan,
91
96
  // which reads whatever is at the target afterwards, cannot tell the two apart on its own.
92
- const priorIds = idsAtTarget(deps.sessions, url);
97
+ const target = absoluteTarget(url, session.url);
98
+ const priorIds = idsAtTarget(deps.sessions, target);
93
99
  session.beginAction(ReticleTool.NAVIGATE, { url });
94
100
  // Same floor as the reload path above, for the same reason: going to a new URL replaces the
95
101
  // document just as thoroughly. `beginAction` attributes events to this action; it does not move
96
102
  // the window a later assert judges over, and those are two different jobs.
97
103
  session.lastAct.markNavigated(session.elapsed());
104
+ deps.recordings.markNavigated();
98
105
  try {
99
106
  const result = (await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.NAVIGATE, { url }));
100
107
  // `ok` is the browser accepting the instruction, not the page arriving — see navigate-result.
@@ -103,7 +110,13 @@ export const BROWSER_TOOLS = [
103
110
  // reticle_sessions itself. A refusal skips the wait: there is nothing to wait FOR.
104
111
  const timeoutMs = asNumber(args['timeout_ms']) ?? ARRIVAL_TIMEOUT_MS;
105
112
  const arrival = true === result.ok
106
- ? await awaitArrival(deps.sessions, url, { navigatedId: session.id, priorIds }, timeoutMs)
113
+ ? await awaitArrival(deps.sessions, target, {
114
+ // Keep the original Session object and URL so arrival detection can distinguish
115
+ // an in-progress navigation from a document that arrived somewhere else.
116
+ navigatedSession: session,
117
+ navigatedFrom: session.url,
118
+ priorIds,
119
+ }, timeoutMs)
107
120
  : null;
108
121
  return navigateResult(result, arrival, timeoutMs);
109
122
  }
@@ -10,6 +10,7 @@ import { ReticleTool } from '@reticlehq/core';
10
10
  import { asString } from '@reticlehq/core';
11
11
  import { exercisedCount } from './gaps/coverage-identity.js';
12
12
  import { commandOrThrow, sessionIdShape } from './tool-kit.js';
13
+ import { foldAppCoverage } from '../../features/exhaust/app-coverage.js';
13
14
  /**
14
15
  * `reticle_coverage` — which interactive controls this session has driven, and which it has not.
15
16
  *
@@ -69,8 +70,17 @@ export function buildCoverageTools(toolNames) {
69
70
  name: ReticleTool.COVERAGE,
70
71
  description: 'Which interactive controls you have driven this session, and which you have NOT. Returns { total, exercised, untouched:[{ref,label}], alsoDroveGone? } over the controls currently on the page. Use it to decide whether verification is finished: an untouched list that still holds the controls your change affects means you are not done. This is about what you did not TOUCH — distinct from the `coverage` field on an action result, which reports what the layer could not SEE.',
71
72
  example: {},
72
- inputSchema: { ...sessionIdShape },
73
+ inputSchema: {
74
+ app: z
75
+ .boolean()
76
+ .optional()
77
+ .describe('Also fold into the app-wide ledger; returns `app.levels`.'),
78
+ ...sessionIdShape,
79
+ },
73
80
  outputSchema: {
81
+ // The app-wide ledger — see features/exhaust/ledger.ts. Loose: run-only, and the levels
82
+ // carry their own shape.
83
+ app: z.record(z.unknown()).optional(),
74
84
  total: z.number(),
75
85
  exercised: z.number(),
76
86
  untouched: z.array(z.object({ ref: z.string(), label: z.string() })),
@@ -171,6 +181,22 @@ export function buildCoverageTools(toolNames) {
171
181
  ...(regressed === undefined ? {} : { observabilityRegressed: regressed }),
172
182
  featureUse,
173
183
  toolHitRate,
184
+ ...(true === args['app']
185
+ ? {
186
+ app: await foldAppCoverage({
187
+ fs: deps.fs,
188
+ reticleRoot: sessionRoot(deps, sessionId),
189
+ seen: parseControls(tree).map((c) => c.label),
190
+ session,
191
+ now: deps.now,
192
+ ...(deps.realInput?.takeCodeCoverage === undefined
193
+ ? {}
194
+ : {
195
+ takeCode: (url) => deps.realInput?.takeCodeCoverage?.(url) ?? Promise.resolve(undefined),
196
+ }),
197
+ }),
198
+ }
199
+ : {}),
174
200
  };
175
201
  },
176
202
  },