@reticlehq/server 3.9.0 → 3.10.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 (350) hide show
  1. package/SKILL.md +13 -12
  2. package/dist/command/cli/adhoc-suite.js +2 -0
  3. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  4. package/dist/command/cli/adhoc-verdict.js +50 -187
  5. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  6. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  7. package/dist/command/cli/cli-doctor.js +1 -1
  8. package/dist/command/cli/cli-flow-commands.js +2 -9
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  11. package/dist/command/cli/cli-parse-compare.js +75 -0
  12. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  13. package/dist/command/cli/cli-parse-grade.js +75 -0
  14. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  15. package/dist/command/cli/cli-parse-grammar.js +18 -20
  16. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  17. package/dist/command/cli/cli-parse-verify.js +63 -14
  18. package/dist/command/cli/cli-parse.d.ts +17 -15
  19. package/dist/command/cli/cli-parse.js +44 -27
  20. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  21. package/dist/command/cli/cli-update-commands.js +29 -20
  22. package/dist/command/cli/cli-usage.js +58 -29
  23. package/dist/command/cli/cli-verify.d.ts +11 -2
  24. package/dist/command/cli/cli-verify.js +27 -8
  25. package/dist/command/cli/cloud-cli.js +2 -119
  26. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  27. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  28. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  29. package/dist/command/cli/doctor-repairs.js +1 -1
  30. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  31. package/dist/command/cli/ensure-daemon.js +57 -0
  32. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  33. package/dist/command/cli/finished-run/grade-command.js +180 -0
  34. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  35. package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
  36. package/dist/command/cli/ports/port-holder.d.ts +9 -0
  37. package/dist/command/cli/ports/port-holder.js +27 -3
  38. package/dist/command/cli/status/status-lines.d.ts +1 -0
  39. package/dist/command/cli/status/status-lines.js +22 -0
  40. package/dist/command/cli/status-fields.d.ts +5 -1
  41. package/dist/command/cli/status-fields.js +7 -3
  42. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  43. package/dist/command/cli/two-builds/compare-command.js +314 -0
  44. package/dist/command/cli/verify-against.d.ts +61 -0
  45. package/dist/command/cli/verify-against.js +187 -0
  46. package/dist/command/cli/verify-no-session.d.ts +2 -0
  47. package/dist/command/cli/verify-no-session.js +13 -0
  48. package/dist/command/cli/verify-results.d.ts +35 -2
  49. package/dist/command/cli/verify-results.js +76 -10
  50. package/dist/command/cli.js +46 -41
  51. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  52. package/dist/command/daemon/daemon-resolve.js +27 -1
  53. package/dist/command/daemon/daemon.d.ts +12 -1
  54. package/dist/command/daemon/daemon.js +35 -4
  55. package/dist/command/daemon/lifetime/death-cause.js +21 -2
  56. package/dist/command/daemon/lifetime/heartbeat.js +7 -2
  57. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  58. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  59. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  60. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  61. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  62. package/dist/command/setup/bringup/static-page-server.js +43 -9
  63. package/dist/command/setup/init/init-runtime.js +6 -0
  64. package/dist/command/setup/node-effects.d.ts +3 -2
  65. package/dist/command/setup/node-effects.js +9 -3
  66. package/dist/command/setup/remaining-steps.d.ts +8 -0
  67. package/dist/command/setup/remaining-steps.js +10 -0
  68. package/dist/command/setup/run-setup.d.ts +2 -0
  69. package/dist/command/setup/run-setup.js +9 -1
  70. package/dist/command/setup/setup-command.js +6 -3
  71. package/dist/command/update/updater.js +6 -0
  72. package/dist/command/version/version-skew.js +1 -1
  73. package/dist/features/compare/action-plan.d.ts +260 -0
  74. package/dist/features/compare/action-plan.js +543 -0
  75. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  76. package/dist/features/compare/behaviour-compare.js +85 -0
  77. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  78. package/dist/features/compare/behaviour-diff.js +202 -0
  79. package/dist/features/compare/behaviour-report.d.ts +40 -0
  80. package/dist/features/compare/behaviour-report.js +128 -0
  81. package/dist/features/compare/build-readings.d.ts +106 -0
  82. package/dist/features/compare/build-readings.js +250 -0
  83. package/dist/features/compare/compare-diff.d.ts +62 -0
  84. package/dist/features/compare/compare-diff.js +177 -0
  85. package/dist/features/compare/compare-report-html.d.ts +6 -0
  86. package/dist/features/compare/compare-report-html.js +102 -0
  87. package/dist/features/compare/compare-report.d.ts +15 -0
  88. package/dist/features/compare/compare-report.js +73 -0
  89. package/dist/features/compare/compare-run.d.ts +60 -0
  90. package/dist/features/compare/compare-run.js +123 -0
  91. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  92. package/dist/features/compare/outside/effect-lines.js +103 -0
  93. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  94. package/dist/features/compare/outside/state-sources.js +164 -0
  95. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  96. package/dist/features/compare/outside/value-dictionary.js +227 -0
  97. package/dist/features/compare/visible-content.d.ts +20 -0
  98. package/dist/features/compare/visible-content.js +125 -0
  99. package/dist/features/crawl/crawl-empty.js +2 -1
  100. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  101. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  102. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  103. package/dist/features/crawl/crawl-tools.js +28 -12
  104. package/dist/features/crawl/crawl.d.ts +8 -9
  105. package/dist/features/crawl/crawl.js +61 -65
  106. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  107. package/dist/features/exhaust/app-coverage.js +3 -1
  108. package/dist/features/exhaust/code-coverage.js +8 -4
  109. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  110. package/dist/features/exhaust/edge-cases.js +310 -0
  111. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  112. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  113. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  114. package/dist/features/exhaust/edges/edge-run.js +169 -0
  115. package/dist/features/exhaust/explorer.d.ts +87 -14
  116. package/dist/features/exhaust/explorer.js +159 -25
  117. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  118. package/dist/features/exhaust/failure-branches.js +29 -11
  119. package/dist/features/exhaust/invariants.d.ts +112 -0
  120. package/dist/features/exhaust/invariants.js +188 -0
  121. package/dist/features/exhaust/ledger.d.ts +68 -0
  122. package/dist/features/exhaust/ledger.js +121 -13
  123. package/dist/features/exhaust/session-fold.d.ts +10 -0
  124. package/dist/features/exhaust/session-fold.js +46 -3
  125. package/dist/features/exhaust/session-port.d.ts +3 -1
  126. package/dist/features/exhaust/session-port.js +97 -28
  127. package/dist/features/grade/grade-answer.d.ts +99 -0
  128. package/dist/features/grade/grade-answer.js +112 -0
  129. package/dist/features/grade/grade-noise.d.ts +27 -0
  130. package/dist/features/grade/grade-noise.js +55 -0
  131. package/dist/features/grade/grade-renumber.d.ts +12 -0
  132. package/dist/features/grade/grade-renumber.js +86 -0
  133. package/dist/features/grade/grade-state.d.ts +50 -0
  134. package/dist/features/grade/grade-state.js +93 -0
  135. package/dist/features/grade/grade-suggest.d.ts +33 -0
  136. package/dist/features/grade/grade-suggest.js +98 -0
  137. package/dist/features/visual/visual-tools.js +66 -19
  138. package/dist/index.js +7 -4
  139. package/dist/judgement/capsule/causal-summary.js +9 -2
  140. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  141. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  142. package/dist/judgement/runs/reticle-runner.js +16 -2
  143. package/dist/judgement/runs/runner-port.d.ts +2 -0
  144. package/dist/judgement/runs/runner-port.js +23 -5
  145. package/dist/language/flows/affected-tools.d.ts +1 -1
  146. package/dist/language/flows/affected-tools.js +49 -30
  147. package/dist/language/flows/change/git-changed.d.ts +2 -1
  148. package/dist/language/flows/change/git-changed.js +19 -2
  149. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  150. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  151. package/dist/language/flows/flow-anchor.d.ts +60 -2
  152. package/dist/language/flows/flow-anchor.js +125 -20
  153. package/dist/language/flows/flow-classify.d.ts +20 -0
  154. package/dist/language/flows/flow-classify.js +90 -12
  155. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  156. package/dist/language/flows/flow-cross-step.js +25 -1
  157. package/dist/language/flows/flow-journey.d.ts +38 -4
  158. package/dist/language/flows/flow-journey.js +138 -8
  159. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  160. package/dist/language/flows/flow-replay-run.js +76 -14
  161. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  162. package/dist/language/flows/flow-replay.d.ts +19 -1
  163. package/dist/language/flows/flow-replay.js +169 -52
  164. package/dist/language/flows/flow-replayable.js +7 -1
  165. package/dist/language/flows/flow-start-path.d.ts +34 -5
  166. package/dist/language/flows/flow-start-path.js +202 -15
  167. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  168. package/dist/language/flows/flow-step-runners.js +184 -109
  169. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  170. package/dist/language/flows/flow-store-for-session.js +44 -0
  171. package/dist/language/flows/flow-tools.js +46 -33
  172. package/dist/language/flows/flows.js +176 -76
  173. package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
  174. package/dist/language/flows/recording/tape/recordings.js +71 -5
  175. package/dist/language/flows/replay.d.ts +19 -5
  176. package/dist/language/flows/replay.js +99 -7
  177. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  178. package/dist/language/flows/steps/page-steps.js +198 -0
  179. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  180. package/dist/language/flows/suite/sequential-run.js +96 -0
  181. package/dist/language/flows/verify-change-tools.js +26 -15
  182. package/dist/memory/cloud/background-sync.d.ts +13 -0
  183. package/dist/memory/cloud/background-sync.js +24 -3
  184. package/dist/memory/cloud/sync-daemon.js +26 -27
  185. package/dist/memory/journal/attach-journal.js +2 -1
  186. package/dist/memory/journal/drive-flow.d.ts +7 -0
  187. package/dist/memory/journal/drive-flow.js +44 -13
  188. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  189. package/dist/memory/journal/session-journal.d.ts +7 -0
  190. package/dist/memory/journal/session-journal.js +58 -10
  191. package/dist/memory/project/root-claims.d.ts +9 -0
  192. package/dist/memory/project/root-claims.js +9 -4
  193. package/dist/portal/input/download-capture.d.ts +7 -0
  194. package/dist/portal/input/download-capture.js +92 -0
  195. package/dist/portal/input/network-detail.d.ts +6 -0
  196. package/dist/portal/input/network-detail.js +12 -1
  197. package/dist/portal/input/network-mock-tools.js +49 -13
  198. package/dist/portal/input/network-mock.d.ts +8 -0
  199. package/dist/portal/input/network-mock.js +28 -9
  200. package/dist/portal/input/page-capture.d.ts +24 -0
  201. package/dist/portal/input/page-capture.js +33 -0
  202. package/dist/portal/input/real-input.d.ts +22 -20
  203. package/dist/portal/input/real-input.js +41 -31
  204. package/dist/portal/input/viewport-tools.d.ts +8 -0
  205. package/dist/portal/input/viewport-tools.js +75 -18
  206. package/dist/portal/pool/browser-pool.d.ts +11 -29
  207. package/dist/portal/pool/browser-pool.js +35 -17
  208. package/dist/portal/pool/context-permissions.d.ts +17 -3
  209. package/dist/portal/pool/context-permissions.js +36 -5
  210. package/dist/portal/pool/determinism.d.ts +52 -0
  211. package/dist/portal/pool/determinism.js +259 -0
  212. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  213. package/dist/portal/pool/lease-emulation.js +18 -0
  214. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  215. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  216. package/dist/portal/pool/lease-page-error.d.ts +44 -17
  217. package/dist/portal/pool/lease-page-error.js +147 -5
  218. package/dist/portal/pool/page-error-journal.d.ts +7 -1
  219. package/dist/portal/pool/page-error-journal.js +27 -0
  220. package/dist/portal/pool/playwright-launcher.js +51 -8
  221. package/dist/portal/pool/pool-contract.d.ts +27 -1
  222. package/dist/portal/pool/pool-lease.d.ts +36 -1
  223. package/dist/portal/pool/zero-install.d.ts +8 -0
  224. package/dist/portal/pool/zero-install.js +23 -1
  225. package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
  226. package/dist/portal/session/dev-server/dev-command.js +61 -15
  227. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  228. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  229. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  230. package/dist/portal/session/dev-server/project-ports.js +62 -0
  231. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  232. package/dist/portal/session/facts/departed-network.js +142 -0
  233. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  234. package/dist/portal/session/no-session-diagnosis.js +67 -25
  235. package/dist/portal/session/no-session-next-action.d.ts +2 -0
  236. package/dist/portal/session/no-session-next-action.js +12 -3
  237. package/dist/portal/session/no-session-watch.d.ts +6 -0
  238. package/dist/portal/session/no-session-watch.js +38 -3
  239. package/dist/portal/session/serving-directory.d.ts +10 -0
  240. package/dist/portal/session/serving-directory.js +39 -8
  241. package/dist/portal/session/session-manager.d.ts +10 -0
  242. package/dist/portal/session/session-manager.js +174 -63
  243. package/dist/start-wiring.d.ts +2 -1
  244. package/dist/start-wiring.js +2 -1
  245. package/dist/surface/mcp/mcp.js +3 -2
  246. package/dist/surface/mcp/shared-params.d.ts +2 -0
  247. package/dist/surface/mcp/shared-params.js +31 -0
  248. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  249. package/dist/surface/tools/act/act-observation.js +118 -12
  250. package/dist/surface/tools/act/act-preflight.js +1 -1
  251. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  252. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  253. package/dist/surface/tools/act/after-match.d.ts +4 -1
  254. package/dist/surface/tools/act/after-match.js +8 -1
  255. package/dist/surface/tools/act/already-true.js +1 -1
  256. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  257. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  258. package/dist/surface/tools/act/resolve-target.d.ts +15 -1
  259. package/dist/surface/tools/act/resolve-target.js +24 -1
  260. package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
  261. package/dist/surface/tools/act/sequence-grade.js +37 -26
  262. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  263. package/dist/surface/tools/act-command.d.ts +23 -0
  264. package/dist/surface/tools/act-command.js +50 -0
  265. package/dist/surface/tools/act-sequence-tool.js +78 -50
  266. package/dist/surface/tools/act-tools.d.ts +1 -13
  267. package/dist/surface/tools/act-tools.js +74 -86
  268. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  269. package/dist/surface/tools/agent/build-staleness.js +1 -1
  270. package/dist/surface/tools/assert/assert-verdict.js +16 -37
  271. package/dist/surface/tools/browser-tools.js +12 -6
  272. package/dist/surface/tools/compare-tools.d.ts +11 -0
  273. package/dist/surface/tools/compare-tools.js +94 -0
  274. package/dist/surface/tools/coverage-tools.js +1 -1
  275. package/dist/surface/tools/dynamic-tools.js +9 -2
  276. package/dist/surface/tools/error-recovery.d.ts +12 -1
  277. package/dist/surface/tools/error-recovery.js +91 -21
  278. package/dist/surface/tools/explore-tools.js +1 -1
  279. package/dist/surface/tools/invoke-tool.js +3 -2
  280. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  281. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  282. package/dist/surface/tools/lease-hint.d.ts +35 -1
  283. package/dist/surface/tools/lease-hint.js +42 -0
  284. package/dist/surface/tools/lease-readiness.d.ts +26 -0
  285. package/dist/surface/tools/lease-readiness.js +79 -0
  286. package/dist/surface/tools/lease-seed.d.ts +14 -1
  287. package/dist/surface/tools/lease-seed.js +35 -3
  288. package/dist/surface/tools/lease-tools.d.ts +3 -41
  289. package/dist/surface/tools/lease-tools.js +62 -110
  290. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  291. package/dist/surface/tools/lease-url-identity.js +97 -0
  292. package/dist/surface/tools/merge-tools.js +99 -2
  293. package/dist/surface/tools/merged-name-redirect.js +5 -1
  294. package/dist/surface/tools/observe-tools.js +15 -9
  295. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  296. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  297. package/dist/surface/tools/read-tools.js +33 -30
  298. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  299. package/dist/surface/tools/real-input-attempt.js +143 -28
  300. package/dist/surface/tools/session-ports.d.ts +14 -0
  301. package/dist/surface/tools/session-ports.js +53 -0
  302. package/dist/surface/tools/tool-kit.d.ts +6 -0
  303. package/dist/surface/tools/tool-kit.js +2 -3
  304. package/dist/surface/tools/tool-surface.js +0 -1
  305. package/dist/surface/tools/tools.js +28 -5
  306. package/dist/surface/tools/type-spelling.d.ts +13 -0
  307. package/dist/surface/tools/type-spelling.js +36 -0
  308. package/dist/telemetry/agent-client.d.ts +6 -0
  309. package/dist/telemetry/agent-client.js +10 -0
  310. package/dist/telemetry/argument-shape.js +1 -0
  311. package/dist/telemetry/tool-refused.d.ts +6 -3
  312. package/dist/telemetry/tool-refused.js +8 -3
  313. package/docs/actions.mdx +3 -3
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/capabilities.mdx +61 -1
  316. package/docs/cli/affected.mdx +9 -7
  317. package/docs/cli/cloud.mdx +3 -3
  318. package/docs/cli/compare.mdx +94 -0
  319. package/docs/cli/doctor.mdx +1 -1
  320. package/docs/cli/grade.mdx +97 -0
  321. package/docs/cli/license.mdx +9 -7
  322. package/docs/cli/restart.mdx +7 -5
  323. package/docs/cli/rollback.mdx +5 -5
  324. package/docs/cli/stop.mdx +3 -3
  325. package/docs/cli/update.mdx +2 -2
  326. package/docs/cli/verify.mdx +23 -1
  327. package/docs/cli.mdx +33 -23
  328. package/docs/deploy-checks.md +1 -1
  329. package/docs/desktop.mdx +1 -1
  330. package/docs/enterprise.md +2 -2
  331. package/docs/environment.mdx +1 -1
  332. package/docs/flows.md +11 -1
  333. package/docs/frameworks.mdx +9 -9
  334. package/docs/getting-started.md +1 -1
  335. package/docs/install-manual.mdx +1 -1
  336. package/docs/license-activation.mdx +3 -3
  337. package/docs/packages/tauri.mdx +1 -1
  338. package/docs/packages.mdx +1 -1
  339. package/docs/predicates.mdx +1 -1
  340. package/docs/quickstart.mdx +68 -49
  341. package/docs/skill-setup.md +1 -1
  342. package/docs/skill.md +14 -13
  343. package/docs/telemetry.md +2 -2
  344. package/docs/tools/navigate.mdx +1 -1
  345. package/docs/tools/verify.mdx +4 -1
  346. package/docs/troubleshooting.mdx +1 -1
  347. package/docs/usage.md +17 -14
  348. package/package.json +13 -13
  349. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  350. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -13,7 +13,7 @@ import { aliasParam } from './args/alias-args.js';
13
13
  import { resolveSessionWithin } from '../../portal/session/timing/resolve-within.js';
14
14
  import { WALL_CLOCK } from '../../portal/session/timing/wall-clock.js';
15
15
  import { timeoutMsSchema } from './args/numeric-bounds.js';
16
- import { captureAct, captureVerdictedAct, pathOf, wroteSince } from '../../language/flows/replay.js';
16
+ import { captureAct, captureVerdictedAct, pageOf, wroteSince } from '../../language/flows/replay.js';
17
17
  import { durableArg, durableOutput, reloadAndRecheck, withDurability } from './act/durable.js';
18
18
  import { ActionType, ActionWarning, ReticleCommand, CaptureLoss, DEFAULT_ASSERT_TIMEOUT_MS, InputMode, Verified, VerifiedReason, PredicateKind, checkIntent, } from '@reticlehq/core';
19
19
  import { leanActResult, mutatedWithin } from './act/act-view.js';
@@ -34,19 +34,19 @@ import { declaresState } from '@reticlehq/engine/question/predicate/predicate-as
34
34
  import { isStateUnwatched } from '@reticlehq/engine/evidence/blind-spots.js';
35
35
  import { hiddenMatchNote } from '@reticlehq/engine/evidence/already-true.js';
36
36
  import { inFlightRequestLabels, repeatedRequestLabels } from './act/settle-in-flight.js';
37
- import { awaitShownMatch, finishAfterMatch } from './act/after-match.js';
37
+ import { awaitShownMatch, evaluateAfterAct, finishAfterMatch } from './act/after-match.js';
38
38
  import { noteProved } from './act/proved-controls.js';
39
- import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
39
+ import { decideVerified, withDeclinedDialog } from '@reticlehq/engine/evidence/verified.js';
40
40
  import { buildHonestyBlock, honestyForVerdict } from '@reticlehq/engine/evidence/honesty.js';
41
- import { declaredExpectations, declaresBodyIndependentChannel, } from '@reticlehq/engine/question/declared.js';
41
+ import { declaredExpectations, declaresBodyIndependentChannel, namedReactions, provedConsequences, } from '@reticlehq/engine/question/declared.js';
42
42
  import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
43
43
  import { saveFailedAssertCapsule } from './act-capsule.js';
44
44
  import { blastRadius, buildDivergenceCapsule, wireCapsule } from '../../judgement/capsule/capsule.js';
45
45
  import { predicateToExpectedLinks } from '@reticlehq/engine/question/predicate/predicate-to-links.js';
46
- import { absenceBlindSpotNote, buildCoverageStatement, blindSpotsFromState, transportGapNote, Coverage, impeachesCapture, } from '@reticlehq/engine/evidence/blind-spots.js';
46
+ import { absenceBlindSpotNote, buildCoverageStatement, canvasPaintSpots, blindSpotsFromState, failingClauseReadsLostChannel, restsOnCompleteWindow, transportGapNote, Coverage, impeachesCapture, } from '@reticlehq/engine/evidence/blind-spots.js';
47
47
  import { acceptedWriteLabels } from '@reticlehq/engine/evidence/accepted-write.js';
48
48
  import { unreadWriteLabels, unreadWriteReason } from '@reticlehq/engine/evidence/unread-outcome.js';
49
- import { evaluatePredicate, waitForPredicate, provenExpectedLinks, PredicateSchema, } from '@reticlehq/engine/question/predicate/predicate.js';
49
+ import { waitForPredicate, provenExpectedLinks, PredicateSchema, } from '@reticlehq/engine/question/predicate/predicate.js';
50
50
  import { sessionVerdictFacts } from '../../portal/session/session-verdict-facts.js';
51
51
  import { currentOf, healthEnvelope, refuseIfThrottled } from '../../portal/session/session-health.js';
52
52
  import { pausedShortCircuit, pausedOutputShape, withControl, PAUSED_NO_VERDICT, } from '../../portal/session/control-envelope.js';
@@ -54,14 +54,15 @@ import { asNumber, asRecord, asString } from '@reticlehq/core';
54
54
  import { sourceOf } from './tools-helpers.js';
55
55
  import { confirmedByDeclaration } from './act/act-danger.js';
56
56
  import { dispatchAct, preflightAct } from './act/act-preflight.js';
57
- import { actTargetNote, checkNet, followLostObservation } from './act/act-observation.js';
57
+ import { actInconclusive, checkNet, followLostObservation, NAVIGATION_FOLLOW_GRACE_MS, } from './act/act-observation.js';
58
58
  import { intentArg, sessionIdShape } from './tool-kit.js';
59
+ import { actCommand } from './act-command.js';
60
+ import { tryRealInput } from './real-input-attempt.js';
59
61
  import { verdictIntentGap } from '@reticlehq/engine/evidence/verdict-intent-gap.js';
60
62
  import { asActionType, gradeOf } from './act/act-helpers.js';
61
63
  import { resolveActTarget } from './act/act-target.js';
64
+ import { hiddenTargetField } from './act/resolve-target.js';
62
65
  import { screenRequestFindings, withAdvisories } from './act/screen-request.js';
63
- import { tryRealInput, rewriteUploadArgs, HOVER_NEEDS_POINTER_MSG } from './real-input-attempt.js';
64
- import { sessionRoot } from '../../memory/project/session-root.js';
65
66
  import { gradeOfPredicate } from './assert/assert-grade.js';
66
67
  import { readAlreadyTrue } from './act/already-true.js';
67
68
  import { engineBlindSpotsField, noteSourceSeen } from '../../portal/session/recorded-gaps.js';
@@ -71,45 +72,11 @@ import { engineBlindSpotsField, noteSourceSeen } from '../../portal/session/reco
71
72
  */
72
73
  const ACTION_TYPE_VALUES = Object.values(ActionType);
73
74
  const actionTypeEnum = z.enum(ACTION_TYPE_VALUES);
74
- /**
75
- * Single dispatch point for every ACT and ACT_SEQUENCE command.
76
- *
77
- * reticle_act, act_and_wait and act_sequence rewrite uploads here, once. The path resolves against
78
- * the session's project, not the daemon's folder, so a worktree uploads its own fixture (#1769).
79
- *
80
- * captureAct is called AFTER this, so the flow-recording captures the pre-rewrite args (the path
81
- * the agent actually wrote) rather than the base64 blob — replay would otherwise send 750 KiB of
82
- * base64 to the browser as if it were an inline content call.
83
- */
84
- export async function actCommand(deps, session, actArgs, timeoutMs) {
85
- const rewritten = await rewriteUploadArgs({ fs: deps.fs, reticleRoot: sessionRoot(deps, session.id) }, 'string' === typeof actArgs['action'] ? actArgs['action'] : '', asRecord(actArgs['args']));
86
- // Sequence and act_and_wait dispatch through here without the ACT handler's tryRealInput.
87
- // A synthetic hover reports dispatched/settled while CSS :hover never applies — drive a real
88
- // pointer when one is available, otherwise refuse rather than lie. A press on a lease takes its
89
- // real keyboard, as reticle_act does: a synthetic Tab moves no focus.
90
- const hover = ActionType.HOVER === actArgs['action'];
91
- if (hover || (ActionType.PRESS === actArgs['action'] && deps.realInput === undefined)) {
92
- const ref = asString(actArgs['ref']);
93
- if (hover && ref === undefined)
94
- throw new Error(HOVER_NEEDS_POINTER_MSG);
95
- const action = hover ? ActionType.HOVER : ActionType.PRESS;
96
- const real = await tryRealInput(deps, session, ref ?? '', action, actArgs);
97
- if (hover && real.result === undefined)
98
- throw new Error(HOVER_NEEDS_POINTER_MSG);
99
- if (real.result !== undefined) {
100
- const result = { dispatched: true, settled: real.settled, ...asRecord(real.result) };
101
- return { kind: 'command_result', id: action, ok: true, result };
102
- }
103
- }
104
- const bridgeArgs = { ...actArgs, args: rewritten };
105
- // What the acted element shows (test id, source) is noted in `Session.command`, Harness acts too.
106
- return session.command(ReticleCommand.ACT, bridgeArgs, timeoutMs);
107
- }
108
75
  export const ACT_TOOLS = [
109
76
  {
110
77
  name: ReticleTool.ACT,
111
78
  example: { ref: 'e42', action: 'fill', args: { value: 'hello' } },
112
- description: 'Act WITHOUT checking the result — if the action is supposed to cause something, use reticle_act_and_wait { until } instead and get the verdict in the same call. This tool returns immediately with a `since` cursor; observe the reaction yourself with reticle_observe. Use it for actions with no observable consequence to assert (focus, hover, scrollIntoView, a fill you are about to submit). One action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. Fields at their uninformative default are OMITTED so a clean action collapses to its consequence: an absent dispatched/targetMatched/visible/enabled means true, an absent occluded/scrolledIntoView/valueChanged/defaultPrevented means false, an absent focusMoved/occludedBy means null. occluded=true means the click point is covered by another element (a real user could not click it) — synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. effect.alreadyAtValue=true (check/uncheck only) means the box already read as the requested state, so NOTHING was dispatched and the app was never told — the DOM property is not evidence the application holds that value, so assert the app\'s own state (a signal, a request, a derived control), not the box. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured — pass args.native:true to force a trusted native click (file pickers, clipboard). args.holdMs keeps the pointer DOWN for that long between mousedown and mouseup — the only way to drive a hold-to-confirm control (hold-to-delete, hold-to-record, long-press); effect.heldMs reports the hold actually achieved, which a throttled background tab can stretch. args.confirmDangerous is a permission gate and NOT a duration: it allows a destructive control, it does not hold one. inputModeReason explains any real→synthetic choice so it is never silent. On a leased tab a press uses the real keyboard: Tab moves focus, and a press with a ref focuses that element first, so Enter or Space presses it as a person would. Full model (real-input, throttled tabs, `reticle drive`): node_modules/@reticlehq/server/docs/usage.md §18.',
79
+ description: 'Act WITHOUT checking the result — if the action is supposed to cause something, use reticle_act_and_wait { until } instead and get the verdict in the same call. This tool returns immediately with a `since` cursor; observe the reaction yourself with reticle_observe. Use it for actions with no observable consequence to assert (focus, hover, scrollIntoView, a fill you are about to submit). One action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView|insertText. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. Fields at their uninformative default are OMITTED so a clean action collapses to its consequence: an absent dispatched/targetMatched/visible/enabled means true, an absent occluded/scrolledIntoView/valueChanged/defaultPrevented means false, an absent focusMoved/occludedBy means null. occluded=true means the click point is covered by another element (a real user could not click it) — synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. effect.alreadyAtValue=true (check/uncheck only) means the box already read as the requested state, so NOTHING was dispatched and the app was never told — the DOM property is not evidence the application holds that value, so assert the app\'s own state (a signal, a request, a derived control), not the box. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured — pass args.native:true to force a trusted native click (file pickers, clipboard). args.holdMs keeps the pointer DOWN for that long between mousedown and mouseup — the only way to drive a hold-to-confirm control (hold-to-delete, hold-to-record, long-press); effect.heldMs reports the hold actually achieved, which a throttled background tab can stretch. args.confirmDangerous is a permission gate and NOT a duration: it allows a destructive control, it does not hold one. inputModeReason explains any real→synthetic choice so it is never silent. For insertText, defaultPrevented=true with valueChanged=true is the success path on an editor that owns its input: it cancelled beforeinput and applied the text, so it is NOT a refusal. insertText refuses with an error when the surface is not contenteditable — an EditContext editor (Monaco, CodeMirror 6) is refused too, because a scripted update fires no textupdate for its model — or when the native command exists and fails. On a leased tab a press uses the real keyboard: Tab moves focus, and a press with a ref focuses that element first, so Enter or Space presses it as a person would. Full model (real-input, throttled tabs, `reticle drive`): node_modules/@reticlehq/server/docs/usage.md §18.',
113
80
  inputSchema: {
114
81
  ref: z
115
82
  .string()
@@ -123,7 +90,7 @@ export const ACT_TOOLS = [
123
90
  args: z
124
91
  .record(z.string(), z.unknown())
125
92
  .optional()
126
- .describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt — a Cmd+K) or a click/dblclick (Shift+click range select), { button: "right" | "middle" } for a click (right sends contextmenu), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { native: true } to force a trusted native click, { holdMs: N } to keep the pointer or the key DOWN for N ms (hold-to-confirm controls; effect.heldMs reports what was achieved), { confirmDangerous: true } to allow a potentially destructive control — a permission gate, NOT a duration. For press: { keys: ["Control","k"] } holds several keys down TOGETHER (pressed in order, released in reverse) and { code } names the physical key — both stay on the in-page dispatcher, the only path that can express them. For upload: { path } is a path on disk (absolute or relative to the project root; the daemon reads the file and delivers real bytes to the file picker — this is the way to verify document-ingestion flows); or { name, content?, type? } to supply inline bytes directly.'),
93
+ .describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press/insertText (the key NAME, e.g. Escape or Tab, for press/type — or the text to insert for insertText), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt — a Cmd+K) or a click/dblclick (Shift+click range select), { button: "right" | "middle" } for a click (right sends contextmenu), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { at: { x, y } } for click/dblclick/hover/tap/drag to act at a point of the element as fractions 0..1 of its box (a canvas has no child elements), { to: { x, y } } for drag to end at a point of the drop target (or of the source), { native: true } to force a trusted native click, { holdMs: N } to keep the pointer or the key DOWN for N ms (hold-to-confirm controls; effect.heldMs reports what was achieved), { confirmDangerous: true } to allow a potentially destructive control — a permission gate, NOT a duration. For press: { keys: ["Control","k"] } holds several keys down TOGETHER (pressed in order, released in reverse) and { code } names the physical key — both stay on the in-page dispatcher, the only path that can express them. For upload: { path } is a path on disk (absolute or relative to the project root; the daemon reads the file and delivers real bytes to the file picker — this is the way to verify document-ingestion flows); or { name, content?, type? } to supply inline bytes directly.'),
127
94
  refuseWhenThrottled: z
128
95
  .boolean()
129
96
  .optional()
@@ -160,8 +127,9 @@ export const ACT_TOOLS = [
160
127
  const session = deps.sessions.resolve(asString(args['sessionId']));
161
128
  // Live-control: refuse to drive the page while the human has paused us (before any work).
162
129
  const paused = pausedShortCircuit(session);
163
- if (paused !== undefined)
130
+ if (paused !== undefined) {
164
131
  return paused;
132
+ }
165
133
  refuseIfThrottled(session, args['refuseWhenThrottled']);
166
134
  // Where this step RUNS, read before the action — not after.
167
135
  //
@@ -169,12 +137,13 @@ export const ACT_TOOLS = [
169
137
  // ambient tape cuts journeys on this value, and stamping the destination put the sign-in click
170
138
  // at the head of the page it navigated TO: 4 of 11 auto-captured flows began with
171
139
  // `login-submit` on `/deployments`, which is the exact flow the segmentation exists to prevent.
172
- const routeBefore = pathOf(session.url);
140
+ const routeBefore = pageOf(session.url);
173
141
  // Resolve `target` to a ref BEFORE the action window opens, so the lookup is not attributed to
174
142
  // the act and cannot be mistaken for something the action caused.
175
143
  const targetRef = await resolveActTarget(session, args);
176
- if ('error' === targetRef.kind)
144
+ if ('error' === targetRef.kind) {
177
145
  throw new Error(targetRef.message);
146
+ }
178
147
  const since = session.elapsed();
179
148
  // The act cursor + effect are marked only once the action has actually DISPATCHED (below, on
180
149
  // each success path) — a refused act must leave nothing behind for the next observe to judge.
@@ -204,23 +173,23 @@ export const ACT_TOOLS = [
204
173
  settled: real.settled,
205
174
  settleReason: null,
206
175
  result: leanActResult(real.result),
176
+ ...hiddenTargetField(targetRef),
207
177
  ...healthEnvelope(session),
208
178
  });
209
179
  }
210
180
  // actCommand is the single interception point: it rewrites upload+path args to real bytes
211
181
  // before any ACT command crosses the bridge, covering this call site and all others.
212
- const result = await actCommand(deps, session, {
213
- ref: targetRef.ref,
214
- action: args['action'],
215
- args: args['args'] ?? {},
216
- });
217
- if (!result.ok)
182
+ const plain = { ref: targetRef.ref, action: args['action'], args: args['args'] ?? {} };
183
+ const result = await actCommand(deps, session, ReticleCommand.ACT, plain, undefined, true);
184
+ if (!result.ok) {
218
185
  throw new Error(result.error ?? 'act failed');
186
+ }
219
187
  captureAct(deps.recordings, args, result.result, routeBefore, session.id);
220
188
  // lift dispatch/settle status to the envelope (a settle timeout is NOT a failure).
221
189
  const r = asRecord(result.result);
222
- if ('boolean' === typeof r['settled'])
190
+ if ('boolean' === typeof r['settled']) {
223
191
  settledOutcome = r['settled'];
192
+ }
224
193
  // Keep what only this call measured, so the observe that judges this window can ask whether
225
194
  // anything happened INSIDE the target — the one fact that separates a dead control from a
226
195
  // page that was merely busy with something else.
@@ -234,12 +203,13 @@ export const ACT_TOOLS = [
234
203
  settled: r['settled'] ?? null,
235
204
  settleReason: r['settleReason'] ?? null,
236
205
  result: leanActResult(result.result),
206
+ ...hiddenTargetField(targetRef),
237
207
  ...(true === real.fellBack ? { warning: ActionWarning.REAL_INPUT_FELL_BACK } : {}),
238
208
  ...healthEnvelope(session),
239
209
  });
240
210
  }
241
211
  finally {
242
- deps.recordings.markEnded(pathOf(session.url), session.id);
212
+ deps.recordings.markEnded(pageOf(session.url), session.id);
243
213
  // Close the window on every exit (settle or throw), recording the action + settle outcome.
244
214
  session.finishAction(undefined, settledOutcome, true === settledOutcome ? session.elapsed() - since : undefined);
245
215
  }
@@ -271,7 +241,7 @@ export const ACT_TOOLS = [
271
241
  args: z
272
242
  .record(z.string(), z.unknown())
273
243
  .optional()
274
- .describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut or a click (Meta/Control/Shift/Alt), { button: "right" | "middle" } for a click, { 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.'),
244
+ .describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press/insertText (the key NAME, e.g. Escape or Tab, for press/type — or the text to insert for insertText), { modifiers: ["Meta","Shift"] } for a press shortcut or a click (Meta/Control/Shift/Alt), { button: "right" | "middle" } for a click, { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { at: { x, y } } for click/dblclick/hover/tap/drag to act at a point of the element as fractions 0..1 of its box (a canvas has no child elements), { to: { x, y } } for drag to end at a point of the drop target (or of the source), { 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.'),
275
245
  predicate: PredicateSchema.optional().describe('Alias for `until`.'),
276
246
  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" }] }.'),
277
247
  timeout_ms: timeoutMsSchema
@@ -287,6 +257,10 @@ export const ACT_TOOLS = [
287
257
  },
288
258
  outputSchema: {
289
259
  durable: durableOutput,
260
+ budgetExtendedMs: z
261
+ .number()
262
+ .optional()
263
+ .describe("The re-ask on the successor ran on the caller's grace: a budget extension ON PURPOSE, taken only when the departure evidence showed the page navigating. Absent for every verdict that needed no follow."),
290
264
  effect: z
291
265
  .unknown()
292
266
  .optional()
@@ -401,8 +375,9 @@ export const ACT_TOOLS = [
401
375
  // BEFORE the action. A body clause this session cannot answer would fail whatever the app
402
376
  // did, and on a drive that mutates state the action is not always repeatable. See #801(C).
403
377
  const bodyRefusal = bodyClauseRefusal(until, session);
404
- if (bodyRefusal !== undefined)
378
+ if (bodyRefusal !== undefined) {
405
379
  throw new Error(bodyRefusal);
380
+ }
406
381
  const timeout = asNumber(args['timeout_ms']) ?? DEFAULT_ASSERT_TIMEOUT_MS;
407
382
  // An intent declared here lands in the ledger BEFORE the verdict is drawn, which is what makes
408
383
  // the undeclared-change gap silent on THIS verdict rather than the next one: it reads the
@@ -415,8 +390,9 @@ export const ACT_TOOLS = [
415
390
  // Resolve `target` to a ref BEFORE the action window opens, so the lookup is not attributed to
416
391
  // the act and cannot be mistaken for something the action caused.
417
392
  const resolved = await resolveActTarget(session, args);
418
- if ('error' === resolved.kind)
393
+ if ('error' === resolved.kind) {
419
394
  throw new Error(resolved.message);
395
+ }
420
396
  let since = session.elapsed();
421
397
  const actedSince = since;
422
398
  // The cursor + effect are marked after the act dispatches (below) — a refused act leaves none.
@@ -431,7 +407,7 @@ export const ACT_TOOLS = [
431
407
  // Was the consequence ALREADY TRUE, and what did that reading say? See act/already-true.ts.
432
408
  const { alreadyTrue, alreadyTrueHiddenMatch, alreadyTrueEvidence, baselines } = await readAlreadyTrue(session, until, since);
433
409
  // Same as the ACT handler: the route this step RAN on, before the action can move the app.
434
- const routeBeforeWait = pathOf(session.url);
410
+ const routeBeforeWait = pageOf(session.url);
435
411
  // Recorded in `finally`, once the verdict is known — see captureVerdictedAct. `held`: the
436
412
  // action made the declared claim hold.
437
413
  let dispatched;
@@ -445,14 +421,15 @@ export const ACT_TOOLS = [
445
421
  // returns null instead (see act-preflight.ts for why a write is never re-sent), and the
446
422
  // successor block below then asks whether the consequence held on the NEW document — a
447
423
  // green if it did, an honest observation_lost if it did not.
448
- const actResult = await dispatchAct(() => actCommand(deps, session, {
424
+ const actResult = await dispatchAct(() => actCommand(deps, session, ReticleCommand.ACT, {
449
425
  ref: resolved.ref,
450
426
  action: args['action'],
451
427
  args: confirmedByDeclaration(asRecord(args['args']), args['target'], untilNamed),
452
428
  }));
453
429
  if (actResult !== null) {
454
- if (!actResult.ok)
430
+ if (!actResult.ok) {
455
431
  throw new Error(actResult.error ?? 'act failed');
432
+ }
456
433
  dispatched = { result: actResult.result };
457
434
  // Dispatched — now this act owns the cursor and the effect. Marking its OWN measurement
458
435
  // also stops the spread below from inheriting an earlier reticle_act's action and
@@ -463,9 +440,7 @@ export const ACT_TOOLS = [
463
440
  const predicateStarted = session.elapsed();
464
441
  let verdict = null === actResult
465
442
  ? { pass: false, observationLost: true }
466
- : timeout > 0
467
- ? await waitForPredicate(session, until, timeout, since, baselines)
468
- : await evaluatePredicate(session, until, since, true, baselines);
443
+ : await evaluateAfterAct(session, until, timeout, since, baselines);
469
444
  // The SDK may have gone away mid-act — see act-observation.ts.
470
445
  const followed = await followLostObservation({
471
446
  sessions: deps.sessions,
@@ -473,23 +448,28 @@ export const ACT_TOOLS = [
473
448
  verdict,
474
449
  timeout,
475
450
  predicateStarted,
451
+ since,
452
+ failedFollowGraceMs: NAVIGATION_FOLLOW_GRACE_MS,
476
453
  reevaluate: (next, budget) => waitForPredicate(next, until, budget, 0, baselines),
477
454
  });
478
- if (followed.followed)
455
+ if (followed.followed) {
479
456
  since = 0;
457
+ }
480
458
  session = followed.session;
481
459
  verdict = followed.verdict;
482
460
  // After a match, let the app finish before the window closes — see act/after-match.ts.
483
461
  if (verdict.pass && timeout > 0) {
484
462
  const left = () => timeout - (session.elapsed() - predicateStarted);
485
463
  const reverted = await finishAfterMatch(session, until, since, baselines, left);
486
- if (reverted !== undefined)
464
+ if (reverted !== undefined) {
487
465
  verdict = { pass: false, failureReason: reverted, observed: reverted };
466
+ }
488
467
  verdict = await awaitShownMatch(session, until, since, baselines, verdict, left);
489
468
  }
490
469
  const r = asRecord(actResult?.result);
491
- if ('boolean' === typeof r['settled'])
470
+ if ('boolean' === typeof r['settled']) {
492
471
  settledOutcome = r['settled'];
472
+ }
493
473
  // Where the acted element is written, captured at act time even if the action unmounted it.
494
474
  const actedSource = sourceOf(r['source']);
495
475
  // Remembered so a LATER assertion can name a file with no element to point at, and so the
@@ -507,6 +487,9 @@ export const ACT_TOOLS = [
507
487
  // Read before the capsule, which needs it: a window the buffer trimmed cannot state an
508
488
  // absolute about what did NOT happen. See CausalSummary.truncated.
509
489
  const bufferLost = session.lostSince(since);
490
+ const impeachForLoss = bufferLost &&
491
+ (restsOnCompleteWindow(until) ||
492
+ (false === verdict.pass && failingClauseReadsLostChannel(until, windowEvents)));
510
493
  const capsule = verdict.pass
511
494
  ? undefined
512
495
  : buildDivergenceCapsule(links, windowEvents, bufferLost);
@@ -528,11 +511,13 @@ export const ACT_TOOLS = [
528
511
  // stronger than this block. Grade from the strongest asserted consequence; integrity from evictions.
529
512
  // Coverage: cross-origin frames / other blind spots the SDK reported during this window mean the
530
513
  // verdict didn't see everything — say so, never imply full coverage.
531
- const spots = blindSpotsFromState(session.blindSpots(), session.runtime);
514
+ const prior = since > 0 ? (await session.queryEvents({ since: 0 })).filter((e) => e.t < since) : [];
515
+ const canvas = canvasPaintSpots(windowEvents, { actionSince: since, prior });
516
+ const spots = [...blindSpotsFromState(session.blindSpots(), session.runtime), ...canvas];
532
517
  // The window's own counts, so sampling before it is not described as this window's (#1414).
533
518
  const windowSpots = blindSpotsFromState(session.blindSpots(since), session.runtime);
534
519
  const coverage = buildCoverageStatement(spots, windowSpots);
535
- const absenceBlindSpot = absenceBlindSpotNote(until, spots);
520
+ const absenceBlindSpot = absenceBlindSpotNote(until, spots, windowEvents);
536
521
  // A green resting on hidden matches says the node exists, not that it shows (#1408).
537
522
  const hiddenMatch = hiddenMatchNote(until, verdict.evidence);
538
523
  // Nothing subscribed ⇒ the state channel is dark, and the summary must say so rather than
@@ -567,13 +552,7 @@ export const ACT_TOOLS = [
567
552
  const honesty = buildHonestyBlock({
568
553
  grade,
569
554
  attribution: 'window',
570
- // Did the buffer lose scarce evidence FROM THIS WINDOW — not "did it evict anything while
571
- // the action ran", which was the previous rule and which is true on essentially every live
572
- // page. Age eviction retires everything past 60s on every push, and the churn floor is
573
- // sacrificed on purpose; neither costs this verdict a single event it needed. In the
574
- // field this made `unclean_capture` the dominant cause of `unknown`, reported for
575
- // evictions that happened outside the window they impeached.
576
- truncated: bufferLost,
555
+ truncated: impeachForLoss,
577
556
  coveragePartial: Coverage.PARTIAL === coverage.coverage,
578
557
  ...(coverage.note === undefined ? {} : { coverageNote: coverage.note }),
579
558
  // Settlement no longer vetoes a declared consequence that held, so the fact is carried
@@ -609,8 +588,8 @@ export const ACT_TOOLS = [
609
588
  // flagship path, and nothing said so: the finding was simply never produced.
610
589
  //
611
590
  // Learning material only. Attribution is unchanged — findings still come exclusively from
612
- // `windowEvents`, exactly as on the assert path.
613
- const prior = since > 0 ? (await session.queryEvents({ since: 0 })).filter((e) => e.t < since) : [];
591
+ // `windowEvents`, exactly as on the assert path. It is read above, before coverage, because the
592
+ // canvas-paint spot (#1525) learns the last canvas digest from it too.
614
593
  //
615
594
  // `actionSince` is the window's own floor here, which is the tightest attribution there is —
616
595
  // and it is what lets `duplicate-request` claim "one user action was performed" and mean it.
@@ -625,9 +604,10 @@ export const ACT_TOOLS = [
625
604
  namedNetUrls: declared.netUrls,
626
605
  repeatableNetUrls: declared.repeatableNetUrls,
627
606
  // A consequence that was already true before the action proves nothing about it, so it is
628
- // not evidence the destination rendered either — `alreadyTrue` decides that, once.
629
- renderProved: verdict.pass && !alreadyTrue && declared.rendersContent,
607
+ // not evidence the destination rendered or the route moved — `alreadyTrue` decides that, once.
608
+ ...provedConsequences(declared, verdict.pass && !alreadyTrue),
630
609
  ...session.lastAct.effect(),
610
+ ...namedReactions(declared),
631
611
  currentDocumentId: session.currentDocumentId,
632
612
  currentEditEpoch: session.currentEditEpoch,
633
613
  appOrigin: session.url,
@@ -645,8 +625,8 @@ export const ACT_TOOLS = [
645
625
  });
646
626
  const outcomeUnread = unreadWriteLabels(windowEvents);
647
627
  const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
648
- // A click on a disabled control cannot cause anything, nor one no person could make.
649
- const inconclusive = verdict.inconclusive ?? actTargetNote(verdict.pass, actResult?.result);
628
+ // A timed-out read says why; a disabled or unreachable target cannot cause anything.
629
+ const inconclusive = actInconclusive(verdict, actResult?.result, session);
650
630
  const { decision, durable } = await withDurability(args['durable'], until, decideVerified({
651
631
  pass: verdict.pass,
652
632
  // Threaded rather than looked up: decideVerified is pure and has no session.
@@ -686,10 +666,11 @@ export const ACT_TOOLS = [
686
666
  unsettled: {
687
667
  waitedFor: describeWaitTarget(until),
688
668
  stillInFlight,
689
- // The retry loop that leaves nothing outstanding — see repeatedRequestLabels.
690
669
  repeated: repeatedRequestLabels(windowEvents),
691
670
  },
692
671
  ...(namedNetIsInFlight(until, stillInFlight) ? { namedRequestInFlight: true } : {}),
672
+ ...(impeachForLoss && false === verdict.pass ? { bufferLostEvidence: true } : {}),
673
+ ...withDeclinedDialog(windowEvents),
693
674
  }), (part) => reloadAndRecheck(deps, session, part, timeout));
694
675
  noteProved(session, String(decision.verified), actResult?.result);
695
676
  // Computed once: the verdict block reports it, and the instrumentation gaps are a second
@@ -785,6 +766,7 @@ export const ACT_TOOLS = [
785
766
  // An unobserved act has no effect to report, and inventing an empty one would read as
786
767
  // "the page did nothing" — a claim about the app, from a call that never saw it.
787
768
  ...(null === actResult ? {} : { effect: leanActResult(actResult.result) }),
769
+ ...hiddenTargetField(resolved),
788
770
  verdict,
789
771
  // Promoted out of `effect` on red only. On green nobody needs it and it is noise; on red it
790
772
  // is the first thing the agent wants, and burying a file:line inside the effect block is
@@ -827,15 +809,21 @@ export const ACT_TOOLS = [
827
809
  // Without its summary: it is byte-identical to `summary` two fields up. See wireCapsule.
828
810
  ...(capsule === undefined ? {} : { capsule: wireCapsule(capsule) }),
829
811
  ...(0 === greenRadius.length ? {} : { blastRadius: greenRadius }),
812
+ // The re-ask on the successor ran on the caller's grace — a budget extension on purpose,
813
+ // declared so the agent can see the extra time it was given (#2009).
814
+ ...(followed.budgetExtendedMs === undefined
815
+ ? {}
816
+ : { budgetExtendedMs: followed.budgetExtendedMs }),
830
817
  since,
831
818
  ...(session.id === actedSessionId ? {} : { sessionId: session.id }),
832
819
  ...healthEnvelope(currentOf(deps.sessions, session)),
833
820
  });
834
821
  }
835
822
  finally {
836
- if (dispatched !== undefined)
823
+ if (dispatched !== undefined) {
837
824
  captureVerdictedAct(deps.recordings, args, dispatched.result, routeBeforeWait, held, session.id, wroteSince(acted, actedSince));
838
- deps.recordings.markEnded(pathOf(currentOf(deps.sessions, session).url), session.id);
825
+ }
826
+ deps.recordings.markEnded(pageOf(currentOf(deps.sessions, session).url), session.id);
839
827
  acted.finishAction(verdictEffect, settledOutcome, true === settledOutcome ? acted.elapsed() - actedSince : undefined);
840
828
  }
841
829
  },
@@ -1,4 +1,4 @@
1
- export declare const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server restart`) and retry before trusting a fix to Reticle itself.";
1
+ export declare const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server serve --restart`) and retry before trusting a fix to Reticle itself.";
2
2
  /** Newest `.js` mtime under `dir`, or 0 when it cannot be read. */
3
3
  export declare function newestJsMtime(dir: string): number;
4
4
  /** A watcher over one directory, started now. Exported for tests; the daemon uses the default. */
@@ -14,7 +14,7 @@ import { dirname, join, resolve } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  /** How often the walk may run. A few hundred stats, so cheap, but not on every call. */
16
16
  const CHECK_EVERY_MS = 30_000;
17
- export const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server restart`) and retry before trusting a fix to Reticle itself.";
17
+ export const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server serve --restart`) and retry before trusting a fix to Reticle itself.";
18
18
  /** `dist/` of this package: this file sits at dist/surface/tools/agent/. */
19
19
  const DIST_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
20
20
  /** Newest `.js` mtime under `dir`, or 0 when it cannot be read. */
@@ -8,40 +8,19 @@ import { hiddenMatchNote } from '@reticlehq/engine/evidence/already-true.js';
8
8
  import { provenExpectedLinks, } from '@reticlehq/engine/question/predicate/predicate.js';
9
9
  import { findContradictions, } from '@reticlehq/engine/disagreement/contradictions.js';
10
10
  import { crashedRuleNotes } from '@reticlehq/engine/disagreement/contradiction-folds.js';
11
- import { declaredExpectations, declaresBodyIndependentChannel, } from '@reticlehq/engine/question/declared.js';
12
- import { absenceBlindSpotNote, blindSpotsFromState, buildCoverageStatement, restsOnCompleteWindow, Coverage, impeachesCapture, transportGapNote, } from '@reticlehq/engine/evidence/blind-spots.js';
11
+ import { declaredExpectations, declaresBodyIndependentChannel, provedConsequences, } from '@reticlehq/engine/question/declared.js';
12
+ import { absenceBlindSpotNote, blindSpotsFromState, buildCoverageStatement, failingClauseReadsLostChannel, restsOnCompleteWindow, Coverage, impeachesCapture, transportGapNote, } from '@reticlehq/engine/evidence/blind-spots.js';
13
13
  import { buildHonestyBlock } from '@reticlehq/engine/evidence/honesty.js';
14
14
  import { acceptedWriteLabels, hasAcceptedWrite, } from '@reticlehq/engine/evidence/accepted-write.js';
15
15
  import { unreadWriteLabels, unreadWriteReason } from '@reticlehq/engine/evidence/unread-outcome.js';
16
- import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
16
+ import { decideVerified, withDeclinedDialog } from '@reticlehq/engine/evidence/verified.js';
17
17
  import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
18
18
  import { inFlightRequestLabels, repeatedRequestLabels, } from '../act/settle-in-flight.js';
19
19
  import { gradeOfPredicate } from './assert-grade.js';
20
20
  import { assertSource, stampedSourceIn } from './assert-source.js';
21
21
  import { VerdictAttribution, verdictAttributionOf } from '@reticlehq/core';
22
22
  import { noteSourceSeen } from '../../../portal/session/recorded-gaps.js';
23
- import { sdkBehindNote } from '../../../portal/session/session-info.js';
24
- // Unanchored: the engine prefixes the reason with "the page did not answer:".
25
- const COMMAND_TIMEOUT = /command '[^']*' timed out after (\d+)ms/;
26
- /**
27
- * The cause and the one fix for a read that came back `unknown`, in place of a bare fact (#1990).
28
- * A page whose SDK is behind this daemon may not serve `match` at all, so the fix is the same
29
- * SDK-behind sentence the listing shows. On a current page the read ran out of the assertion's own
30
- * budget, and the fix is more of it.
31
- */
32
- function diagnoseTimeout(reason, session) {
33
- const behind = sdkBehindNote(session);
34
- if (behind !== undefined) {
35
- return `${reason} ${behind}`;
36
- }
37
- const granted = COMMAND_TIMEOUT.exec(reason)?.[1];
38
- if (granted === undefined) {
39
- return reason;
40
- }
41
- return (`${reason} This read was bounded by what the assertion's budget had left ` +
42
- `(${granted}ms): the budget ran out while the page was still working. ` +
43
- 'Give the assertion more time with `timeout_ms`.');
44
- }
23
+ import { diagnoseTimeout } from '../act/act-observation.js';
45
24
  /**
46
25
  * The honesty verdict for a plain `reticle_assert`.
47
26
  *
@@ -84,7 +63,6 @@ changeUndeclared) {
84
63
  // The window's own counts, so sampling before it is not described as this window's (#1414).
85
64
  const windowSpots = blindSpotsFromState(session.blindSpots(since), session.runtime);
86
65
  const statement = buildCoverageStatement(spots, windowSpots);
87
- const absenceBlindSpot = absenceBlindSpotNote(predicate, spots);
88
66
  // A green resting on hidden matches says the node exists, not that it shows (#1408).
89
67
  const hiddenMatch = hiddenMatchNote(predicate, evidence);
90
68
  // Omitted entirely when coverage is full, so an intact page pays nothing and the field's PRESENCE
@@ -96,6 +74,8 @@ changeUndeclared) {
96
74
  }
97
75
  : {};
98
76
  const windowEvents = await session.queryEvents({ since });
77
+ // The window lets a target the page was seen to remove escape the virtualized blind spot.
78
+ const absenceBlindSpot = absenceBlindSpotNote(predicate, spots, windowEvents);
99
79
  // Everything before the window, passed as LEARNING material only. A scale error disagrees with a
100
80
  // value the API stated earlier in the session, which an action-scoped window can never contain —
101
81
  // measured live, `observe` over a wide window reported `unit-mismatch` while `assert` on the same
@@ -120,7 +100,7 @@ changeUndeclared) {
120
100
  expectedFailures: declared.netFailures,
121
101
  namedNetUrls: declared.netUrls,
122
102
  repeatableNetUrls: declared.repeatableNetUrls,
123
- renderProved: pass && declared.rendersContent,
103
+ ...provedConsequences(declared, pass),
124
104
  ...(actCursor !== undefined && actCursor >= since ? { actionSince: actCursor } : {}),
125
105
  });
126
106
  // Only a spot that IMPEACHES the capture downgrades a general verdict. Structural boundaries are
@@ -162,10 +142,7 @@ changeUndeclared) {
162
142
  const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
163
143
  const rawInconclusive = inconclusive ?? (!pass ? session.preconditionFailure?.() : undefined);
164
144
  // A timed-out page read is the one `unknown` whose cause and fix are knowable here (#1990).
165
- let effectiveInconclusive = rawInconclusive;
166
- if (rawInconclusive !== undefined && COMMAND_TIMEOUT.test(rawInconclusive)) {
167
- effectiveInconclusive = diagnoseTimeout(rawInconclusive, session);
168
- }
145
+ const effectiveInconclusive = diagnoseTimeout(rawInconclusive, session);
169
146
  /**
170
147
  * Does a green here rest on the window having been COMPLETE?
171
148
  *
@@ -180,13 +157,13 @@ changeUndeclared) {
180
157
  /**
181
158
  * The buffer evicted scarce evidence from this window.
182
159
  *
183
- * assert never consulted the buffer at all, on the reasoning that it "observes an already-open
184
- * window" — but eviction happens on push regardless of who opened the window, so
185
- * `{ console, absent: true }` returned `yes` over a window whose evidence was gone, which is
186
- * absence of evidence read as evidence of absence. The act path needs no `restsOnComplete` guard:
187
- * its cursor is the action's own.
160
+ * Two cases. (1) An ABSENCE claim needs a complete window; things dropped are the disproof it
161
+ * cannot see. (2) A FAILING positive `allOf` whose NET/SIGNAL/LOG child has no evidence left in
162
+ * the window — the child might have failed because the buffer dropped the request, not because
163
+ * the app never made it (#1231). Only the second reads the events; the first is structural.
188
164
  */
189
- const bufferLost = session.lostSince(since) && restsOnComplete;
165
+ const bufferLost = session.lostSince(since) &&
166
+ (restsOnComplete || (false === pass && failingClauseReadsLostChannel(predicate, windowEvents)));
190
167
  /** The durable ledger refused writes, on a query path that read from it. Same rule, other store. */
191
168
  const ledgerLost = ledgerClosed && restsOnComplete;
192
169
  const decision = decideVerified({
@@ -209,6 +186,8 @@ changeUndeclared) {
209
186
  ...(absenceBlindSpot === undefined ? {} : { absenceBlindSpot }),
210
187
  ...(hiddenMatch === undefined ? {} : { hiddenMatch }),
211
188
  ...(namedNetIsInFlight(predicate, stillInFlight) ? { namedRequestInFlight: true } : {}),
189
+ ...(bufferLost && false === pass ? { bufferLostEvidence: true } : {}),
190
+ ...withDeclinedDialog(windowEvents),
212
191
  honesty: buildHonestyBlock({
213
192
  grade: gradeOfPredicate(predicate),
214
193
  attribution: 'window',
@@ -2,7 +2,7 @@ import { carryReticleIdentity, LEASE_ACQUIRE_TOOL } from './lease-tools.js';
2
2
  import { z } from 'zod';
3
3
  import { navigateResult } from './act/navigation/navigate-result.js';
4
4
  import { absoluteTarget, awaitArrival, idsOpenAtDispatch, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
5
- import { reloadResult } from './act/navigation/reload-result.js';
5
+ import { HardReload, hardReloadOf, reloadResult } from './act/navigation/reload-result.js';
6
6
  import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS, } from '../../portal/session/session-reconnect.js';
7
7
  import { NO_SESSION_CONNECTED_ERROR, ReticleCommand, SESSION_STARTED_NOTE, SessionStartedVia, relativeUrlNoSession, } from '@reticlehq/core';
8
8
  import { ReticleTool } from '@reticlehq/core';
@@ -106,7 +106,7 @@ export const BROWSER_TOOLS = [
106
106
  hard: z
107
107
  .boolean()
108
108
  .optional()
109
- .describe('With reload:true, bypass the browser cache (Cmd+Shift+R). Default: false.'),
109
+ .describe('With reload:true, bypass the browser cache (Cmd+Shift+R) on a leased tab. On a tab Reticle does not drive, only the document is fetched again; scripts and modules can still come from the cache. Default: false.'),
110
110
  timeout_ms: timeoutMsSchema
111
111
  .optional()
112
112
  .describe('How long to wait for the page to come back (the SDK reconnecting at the new URL, or under the same id on a reload) before answering confirmed:false. Default 5000. 0 looks once without waiting. An SPA reattaching under HMR has been measured at 30-60s — say so here instead of polling reticle_sessions.'),
@@ -133,6 +133,8 @@ export const BROWSER_TOOLS = [
133
133
  /** Present only when no session was connected and this call opened one on the URL. */
134
134
  sessionStarted: SessionStartedSchema.optional(),
135
135
  note: z.string().optional(),
136
+ /** On a `hard` reload: whether the browser ignored its cache, or only the document was fetched again. */
137
+ hardReload: z.nativeEnum(HardReload).optional(),
136
138
  },
137
139
  handler: async (deps, args) => {
138
140
  // reload:true is the absorbed reticle_refresh — same command, one fewer advertised tool.
@@ -148,9 +150,13 @@ export const BROWSER_TOOLS = [
148
150
  // coming back `contradicted` by hundreds of 500s against resources that were already gone.
149
151
  before.lastAct.markNavigated(before.elapsed());
150
152
  deps.recordings.markNavigated();
151
- await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
152
- hard: true === args['hard'],
153
- });
153
+ const hard = true === args['hard'];
154
+ // A leased tab reloads through the browser with the cache ignored. The page itself can only
155
+ // re-fetch its document: scripts and modules still come from the cache (#1785).
156
+ const throughBrowser = hard && true === (await deps.pool?.hardReloadLease(before.id));
157
+ if (!throughBrowser) {
158
+ await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, { hard });
159
+ }
154
160
  // WAIT for the page to come back, rather than telling the agent to. The id survives the
155
161
  // reload, but the seconds between dispatch and the new HELLO are seconds in which every call
156
162
  // lands in the old, disconnected session — measured as reticle_run failing 5 of 5 on a page
@@ -165,7 +171,7 @@ export const BROWSER_TOOLS = [
165
171
  // Not a bare `{ ok: true }`. The URL branch below already discloses that `ok` means
166
172
  // DISPATCHED — the reload branch had identical semantics and said nothing, on the path most
167
173
  // likely to need it. See reload-result.
168
- return reloadResult(back, timeoutMs);
174
+ return reloadResult(back, timeoutMs, hardReloadOf(hard, throughBrowser));
169
175
  }
170
176
  const requested = asString(args['url']);
171
177
  if (requested === undefined || 0 === requested.length) {