@reticlehq/server 3.8.2 → 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 (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -0,0 +1,314 @@
1
+ /**
2
+ * `reticle compare <reference-url> <candidate-url> [--base <url>] [--json]`.
3
+ *
4
+ * Drives two running builds of one app the same way and reports each difference in what a person
5
+ * sees, for example main against a pull request preview. The app needs nothing installed: each run
6
+ * is a tab the daemon leases, and a page with no Reticle SDK gets the daemon's own reader. The rules
7
+ * are in `@/features/compare/behaviour-compare.ts`; this file connects them to the daemon over its
8
+ * MCP transport, with the same tool calls an agent makes.
9
+ */
10
+ import { ActionType, isLocalhostSubdomain, isLoopbackHostname, ReticleTool, SnapshotMode, } from '@reticlehq/core';
11
+ import { mkdirSync, writeFileSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+ import { runCompare } from '../../../features/compare/behaviour-compare.js';
14
+ import { compareReportHtml } from '../../../features/compare/compare-report-html.js';
15
+ import { invalidComparison } from '../../../features/compare/behaviour-diff.js';
16
+ import { compareExitCode, compareReportJson, compareReportLines, } from '../../../features/compare/behaviour-report.js';
17
+ import { DriverLostError } from '../../../features/compare/build-readings.js';
18
+ import { StateDeclarationSchema, readStateSources, } from '../../../features/compare/outside/state-sources.js';
19
+ import { consoleLines, networkLines } from '../../../features/compare/outside/effect-lines.js';
20
+ import { execFile } from 'node:child_process';
21
+ import { readFile } from 'node:fs/promises';
22
+ import { promisify } from 'node:util';
23
+ import { callerArtifactRoot } from '../../../memory/project/link-directory.js';
24
+ import { defaultPairingTokenDir, readOrCreatePairingTokenSync, } from '../../../portal/bridge/pairing-token.js';
25
+ import { acquireLease, connectOverSse, endpointFor, payloadOf, refusalText, releaseLease, } from '../daemon-link/daemon-client.js';
26
+ /** How long a navigation may take before the start page counts as not loaded. */
27
+ const NAVIGATE_WAIT_MS = 15_000;
28
+ /** Room for one tool call past its own wait. */
29
+ const CALL_MS = 60_000;
30
+ /** The most network calls and console lines read for one sequence. */
31
+ const EFFECTS_LIMIT = 50;
32
+ /** How long fetching one page or script file may take. */
33
+ const FETCH_MS = 10_000;
34
+ /**
35
+ * Refusals that mean the driver lost its tab, not that the build did something. A run that sees
36
+ * one proves nothing about the build.
37
+ */
38
+ const DRIVER_LOST = /no browser session connected|no connected session|multiple sessions connected|session disconnected|names no session|no such session|session ended/i;
39
+ const MSG_NO_DAEMON = 'could not reach the daemon on port';
40
+ const MSG_LEASE_FAILED = 'could not open a browser on';
41
+ const MSG_STATE_UNREADABLE = 'could not read the state sources in';
42
+ /** How long one state source read may take. */
43
+ const STATE_READ_MS = 60_000;
44
+ /** The most output one state command may give. */
45
+ const STATE_OUTPUT_BYTES = 64 * 1024 * 1024;
46
+ const SHELL = 'sh';
47
+ const SHELL_COMMAND_FLAG = '-c';
48
+ const DOCKER = 'docker';
49
+ const DOCKER_EXEC = 'exec';
50
+ const UTF8 = 'utf8';
51
+ const asText = (value) => 'string' === typeof value ? value : undefined;
52
+ /** The pairing token the daemon asks for, or undefined when there is none. */
53
+ export function pairingToken() {
54
+ const token = readOrCreatePairingTokenSync(defaultPairingTokenDir());
55
+ return token === undefined || 0 === token.length ? undefined : token;
56
+ }
57
+ /**
58
+ * A build on loopback or a `*.localhost` name is a disposable copy, so compare presses its guarded
59
+ * controls too ("Place order", "Delete"): they are the actions most worth comparing. On any other
60
+ * host the act guard stays on, so a compare can never delete real data.
61
+ */
62
+ function isDisposableBuild(url) {
63
+ try {
64
+ const { hostname } = new URL(url);
65
+ return isLoopbackHostname(hostname) || isLocalhostSubdomain(hostname);
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /** The act tool's args: a press names its key as `text`, a fill or a select its `value`. */
72
+ function actArgs(action, value, guarded) {
73
+ if (value === undefined) {
74
+ return guarded;
75
+ }
76
+ return ActionType.PRESS === action ? { text: value, ...guarded } : { value, ...guarded };
77
+ }
78
+ /** One leased tab as the compare driver. Every call goes through `reticle_run` on that tab. */
79
+ export function leasedPage(caller, url, sessionId) {
80
+ let id = sessionId;
81
+ const guarded = isDisposableBuild(url) ? { confirmDangerous: true } : {};
82
+ const run = async (tool, args, timeoutMs = CALL_MS) => {
83
+ let result;
84
+ try {
85
+ result = await caller.call(ReticleTool.RUN, { tool, args: { ...args, sessionId: id } }, timeoutMs);
86
+ }
87
+ catch (error) {
88
+ // Name the call, so a run that lost its driver says where.
89
+ throw new DriverLostError(`${tool}: ${error instanceof Error ? error.message : String(error)}`);
90
+ }
91
+ const payload = payloadOf(result);
92
+ const refusal = refusalText(result) ?? asText(payload['error']);
93
+ if (refusal !== undefined && DRIVER_LOST.test(refusal)) {
94
+ throw new DriverLostError(`${tool}: ${refusal}`);
95
+ }
96
+ return refusal === undefined ? { payload } : { payload, refusal };
97
+ };
98
+ const navigate = async (to) => {
99
+ const { payload, refusal } = await run(ReticleTool.NAVIGATE, { url: to, timeout_ms: NAVIGATE_WAIT_MS }, NAVIGATE_WAIT_MS * 2);
100
+ id = asText(payload['sessionId']) ?? id;
101
+ return refusal === undefined && true === payload['ok'] && false !== payload['confirmed'];
102
+ };
103
+ /** Faults stay on the tab until cleared, and each fresh sequence must see the real server. */
104
+ let mocked = false;
105
+ const mockRequests = async (args) => {
106
+ const { payload, refusal } = await run(ReticleTool.NETWORK_MOCK, args);
107
+ return refusal === undefined && true === payload['applied'];
108
+ };
109
+ return {
110
+ async open() {
111
+ if (mocked) {
112
+ if (!(await mockRequests({ clear: true }))) {
113
+ return false;
114
+ }
115
+ mocked = false;
116
+ }
117
+ return navigate(url);
118
+ },
119
+ mock(rules) {
120
+ mocked = true;
121
+ return mockRequests({ mocks: rules });
122
+ },
123
+ goto(query) {
124
+ return navigate(new URL(query, url).href);
125
+ },
126
+ async read() {
127
+ const { payload } = await run(ReticleTool.SNAPSHOT, { mode: SnapshotMode.FULL, ids: true });
128
+ return asText(payload['tree']) ?? '';
129
+ },
130
+ async act(at, action, value) {
131
+ const { payload, refusal } = await run(ReticleTool.ACT, {
132
+ ...('string' === typeof at ? { ref: at } : { target: at }),
133
+ action,
134
+ args: actArgs(action, value, guarded),
135
+ });
136
+ const ok = refusal === undefined && false !== payload['dispatched'];
137
+ const since = payload['since'];
138
+ return 'number' === typeof since ? { ok, since } : { ok };
139
+ },
140
+ async effects(since) {
141
+ // A refused read is passed on as `{ error }`, so it becomes a visible line, not an empty list.
142
+ const read = async (tool, args) => {
143
+ const { payload, refusal } = await run(tool, { since, ...args, limit: EFFECTS_LIMIT });
144
+ return refusal === undefined ? payload : { error: refusal };
145
+ };
146
+ return [
147
+ ...networkLines(await read(ReticleTool.NETWORK, { bodies: true })),
148
+ ...consoleLines(await read(ReticleTool.CONSOLE, {})),
149
+ ];
150
+ },
151
+ async close() {
152
+ await releaseLease(caller, id);
153
+ },
154
+ };
155
+ }
156
+ export const MSG_REPORT_WRITTEN = 'Report:';
157
+ export const MSG_REPORT_FAILED = 'Report not written:';
158
+ function writeReportFile(path, html) {
159
+ mkdirSync(dirname(path), { recursive: true });
160
+ writeFileSync(path, html);
161
+ }
162
+ const execFileText = promisify(execFile);
163
+ /** State sources read on this machine: a shell, `docker exec`, `fetch` and the file system. */
164
+ export const hostStateIo = {
165
+ async run(command, container) {
166
+ const [file, args] = container === undefined
167
+ ? [SHELL, [SHELL_COMMAND_FLAG, command]]
168
+ : [DOCKER, [DOCKER_EXEC, container, SHELL, SHELL_COMMAND_FLAG, command]];
169
+ const { stdout } = await execFileText(file, args, {
170
+ encoding: UTF8,
171
+ timeout: STATE_READ_MS,
172
+ maxBuffer: STATE_OUTPUT_BYTES,
173
+ });
174
+ return stdout;
175
+ },
176
+ async fetchJson(url) {
177
+ const response = await fetch(url, { signal: AbortSignal.timeout(STATE_READ_MS) });
178
+ if (!response.ok) {
179
+ throw new Error(`HTTP ${String(response.status)}`);
180
+ }
181
+ return response.json();
182
+ },
183
+ readFile: (path) => readFile(path, UTF8),
184
+ };
185
+ /** The sources in the `--state` file, or why they could not be read. */
186
+ async function loadStateSources(path) {
187
+ try {
188
+ const parsed = StateDeclarationSchema.safeParse(JSON.parse(await readFile(path, UTF8)));
189
+ return parsed.success ? parsed.data.sources : { error: parsed.error.message };
190
+ }
191
+ catch (error) {
192
+ return { error: error instanceof Error ? error.message : String(error) };
193
+ }
194
+ }
195
+ /** Fetch a page or script file as a browser would. Undefined when it could not be fetched. */
196
+ async function fetchText(url) {
197
+ try {
198
+ const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_MS) });
199
+ return response.ok ? await response.text() : undefined;
200
+ }
201
+ catch {
202
+ return undefined;
203
+ }
204
+ }
205
+ /** The exit code, and the text or the one JSON line to print. */
206
+ function render(json, urls, result, timings, sink) {
207
+ const report = compareReportJson(urls, result, timings);
208
+ // A report that could not be written says so: a line in text mode, a field in the one JSON object.
209
+ let reportError;
210
+ if (sink !== undefined) {
211
+ try {
212
+ sink.write(sink.path, compareReportHtml(report));
213
+ }
214
+ catch (error) {
215
+ reportError = error instanceof Error ? error.message : String(error);
216
+ }
217
+ }
218
+ if (json) {
219
+ return {
220
+ code: compareExitCode(result),
221
+ lines: [JSON.stringify(reportError === undefined ? report : { ...report, reportError })],
222
+ };
223
+ }
224
+ const lines = compareReportLines(urls, result, timings);
225
+ if (sink !== undefined) {
226
+ lines.push(reportError === undefined
227
+ ? `${MSG_REPORT_WRITTEN} ${sink.path}`
228
+ : `${MSG_REPORT_FAILED} ${sink.path}: ${reportError}`);
229
+ }
230
+ return { code: compareExitCode(result), lines };
231
+ }
232
+ /** Compare the builds through the daemon on `port`. Returns the lines to print and the exit code. */
233
+ export async function runCompareCommand(options) {
234
+ const urls = {
235
+ reference: options.reference,
236
+ candidate: options.candidate,
237
+ ...(options.base === undefined ? {} : { base: options.base }),
238
+ };
239
+ const sink = options.report === undefined
240
+ ? undefined
241
+ : { path: options.report, write: options.writeReport ?? writeReportFile };
242
+ let readState;
243
+ if (options.state !== undefined) {
244
+ const loaded = await loadStateSources(options.state);
245
+ if ('error' in loaded) {
246
+ const reason = `${MSG_STATE_UNREADABLE} ${options.state}: ${loaded.error}`;
247
+ return render(options.json, urls, invalidComparison(reason, 0), undefined, sink);
248
+ }
249
+ readState = () => readStateSources(loaded, hostStateIo);
250
+ }
251
+ const connect = options.connect ?? connectOverSse;
252
+ let caller;
253
+ try {
254
+ caller = await connect(endpointFor(options.port, options.token));
255
+ }
256
+ catch (error) {
257
+ const why = error instanceof Error ? error.message : String(error);
258
+ const reason = `${MSG_NO_DAEMON} ${String(options.port)}: ${why}`;
259
+ return render(options.json, urls, invalidComparison(reason, 0), undefined, sink);
260
+ }
261
+ const open = async (url) => {
262
+ const lease = await acquireLease(caller, url, false, options.root);
263
+ if ('failed' in lease) {
264
+ throw new Error([`${MSG_LEASE_FAILED} ${url}`, ...lease.failed].join(' '));
265
+ }
266
+ return leasedPage(caller, url, lease.leased);
267
+ };
268
+ try {
269
+ const { result, timings } = await runCompare({
270
+ ...urls,
271
+ open,
272
+ sleep: options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
273
+ now: options.now ?? Date.now,
274
+ fetchText: options.fetchText ?? fetchText,
275
+ ...(readState === undefined ? {} : { readState }),
276
+ });
277
+ return render(options.json, urls, result, timings, sink);
278
+ }
279
+ catch (error) {
280
+ const reason = error instanceof Error ? error.message : String(error);
281
+ return render(options.json, urls, invalidComparison(reason, 0), undefined, sink);
282
+ }
283
+ finally {
284
+ await caller.close().catch(() => undefined);
285
+ }
286
+ }
287
+ /**
288
+ * The command. It needs a daemon, as an agent does: `ensureDaemon` attaches to the one on the port,
289
+ * or starts one. Nothing here stops a daemon, so an agent's MCP link stays up.
290
+ */
291
+ export async function handleCompare(parsed, ensureDaemon, out) {
292
+ const fail = (reason) => {
293
+ const sink = parsed.report === undefined ? undefined : { path: parsed.report, write: writeReportFile };
294
+ const result = render(parsed.json, parsed, invalidComparison(reason, 0), undefined, sink);
295
+ result.lines.forEach(out.line);
296
+ out.exit(result.code);
297
+ };
298
+ try {
299
+ await ensureDaemon(parsed.port);
300
+ }
301
+ catch (error) {
302
+ fail(error instanceof Error ? error.message : String(error));
303
+ return;
304
+ }
305
+ const token = pairingToken();
306
+ const root = callerArtifactRoot(process.cwd());
307
+ const result = await runCompareCommand({
308
+ ...parsed,
309
+ ...(token === undefined ? {} : { token }),
310
+ ...(root === undefined ? {} : { root }),
311
+ });
312
+ result.lines.forEach(out.line);
313
+ out.exit(result.code);
314
+ }
@@ -0,0 +1,61 @@
1
+ import { type CompareDriver } from '../../features/compare/compare-run.js';
2
+ import { type FileSystemPort } from '../../memory/project/fs/fs-port.js';
3
+ import { type ToolCaller } from './daemon-link/daemon-client.js';
4
+ /** How long to wait after each action before the page is read, so a short timer can fire. */
5
+ export declare const AGAINST_WAIT_MS = 300;
6
+ /** The explorer's action budget on the reference. */
7
+ export declare const AGAINST_MAX_ACTIONS = 150;
8
+ /** The tab one run drives. */
9
+ export interface LeasedTab {
10
+ url: string;
11
+ sessionId: string;
12
+ /** Release the lease when the run ends. */
13
+ closeLease: boolean;
14
+ confirmDangerous?: boolean;
15
+ }
16
+ /** One leased tab, as the compare driver. Every call goes through `reticle_run` on that tab. */
17
+ export declare function daemonDriver(caller: ToolCaller, tab: LeasedTab): CompareDriver;
18
+ /** The saved flows a run replays, with `--select` applied, and how many steps each has. */
19
+ export declare function flowsToReplay(fs: FileSystemPort, reticleRoot: string, select: readonly string[] | undefined, now: () => number): Promise<{
20
+ name: string;
21
+ steps: number;
22
+ }[]>;
23
+ export interface VerifyAgainstOptions {
24
+ port: number;
25
+ candidate: string;
26
+ reference: string;
27
+ flows: readonly {
28
+ name: string;
29
+ steps: number;
30
+ }[];
31
+ token?: string;
32
+ /** The caller's project `.reticle`, where the leased tabs write their runs. */
33
+ root?: string;
34
+ confirmDangerous?: boolean;
35
+ resultsJson?: string;
36
+ sleep: (ms: number) => Promise<void>;
37
+ /** Injected so a test can use its own transport. */
38
+ connect?: (endpoint: URL) => Promise<ToolCaller>;
39
+ write?: (path: string, text: string) => Promise<void>;
40
+ }
41
+ /** Compare the two builds through the daemon on `port`. Returns the lines to print and the exit code. */
42
+ export declare function runVerifyAgainst(options: VerifyAgainstOptions): Promise<{
43
+ code: number;
44
+ lines: string[];
45
+ }>;
46
+ /**
47
+ * The command. It needs a daemon, as an agent does: `ensureDaemon` attaches to the one on the port,
48
+ * or starts one. Nothing here stops a daemon, so an agent's MCP link stays up.
49
+ */
50
+ export declare function handleVerifyAgainst(parsed: {
51
+ url: string;
52
+ against: string;
53
+ port: number;
54
+ select?: string[];
55
+ resultsJson?: string;
56
+ confirmDangerous?: boolean;
57
+ }, ensureDaemon: (port: number) => Promise<void>, out: {
58
+ line: (text: string) => void;
59
+ fail: (text: string) => void;
60
+ exit: (code: number) => void;
61
+ }): Promise<void>;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * `reticle verify <candidate-url> --against <reference-url>`: did a change alter what users see?
3
+ *
4
+ * It drives both running builds with the same actions (the saved flows, and the explorer on the
5
+ * reference) and reports each difference in what a person can read and use. The rules are in
6
+ * `@/features/compare`. This file connects them to the running daemon: each run is a leased tab,
7
+ * and each read or action is a tool call on that tab, the same calls an agent makes.
8
+ */
9
+ import { writeFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { ReticleDir, ReticleTool, SnapshotMode, isConsequenceDrift, selectFlows, } from '@reticlehq/core';
12
+ import { compareBuilds } from '../../features/compare/compare-run.js';
13
+ import { compareExit, compareJson, compareLines } from '../../features/compare/compare-report.js';
14
+ import { FlowStore } from '../../language/flows/flows.js';
15
+ import { createNodeFileSystem } from '../../memory/project/fs/fs-port.js';
16
+ import { callerArtifactRoot } from '../../memory/project/link-directory.js';
17
+ import { defaultPairingTokenDir, readOrCreatePairingTokenSync, } from '../../portal/bridge/pairing-token.js';
18
+ import { acquireLease, connectOverSse, endpointFor, payloadOf, refusalText, releaseLease, } from './daemon-link/daemon-client.js';
19
+ /** How long a navigation may take before the start page counts as not loaded. */
20
+ const NAVIGATE_WAIT_MS = 15_000;
21
+ /** One replayed flow step: a step can wait for its own consequence, so give it room. */
22
+ const REPLAY_STEP_CALL_MS = 120_000;
23
+ /** How long to wait after each action before the page is read, so a short timer can fire. */
24
+ export const AGAINST_WAIT_MS = 300;
25
+ /** The explorer's action budget on the reference. */
26
+ export const AGAINST_MAX_ACTIONS = 150;
27
+ /** The label of a replayed step that names no anchor. */
28
+ const UNNAMED_STEP = 'step';
29
+ const MSG_LEASE_FAILED = 'could not open a browser on';
30
+ const MSG_NO_DAEMON = 'could not reach the daemon on port';
31
+ const asText = (value) => 'string' === typeof value ? value : undefined;
32
+ /** A step that ran: it passed, or its action ran and only its declared consequence did not hold. */
33
+ function stepDone(step) {
34
+ if (step === undefined) {
35
+ return false;
36
+ }
37
+ if (true === step['ok']) {
38
+ return true;
39
+ }
40
+ const drift = step['drift'];
41
+ const reason = asText(drift?.reasonKind);
42
+ return reason !== undefined && isConsequenceDrift(reason);
43
+ }
44
+ /** One leased tab, as the compare driver. Every call goes through `reticle_run` on that tab. */
45
+ export function daemonDriver(caller, tab) {
46
+ let sessionId = tab.sessionId;
47
+ const run = async (tool, args, timeoutMs) => {
48
+ const result = await caller.call(ReticleTool.RUN, { tool, args: { ...args, sessionId } }, timeoutMs);
49
+ return { payload: payloadOf(result), refused: refusalText(result) !== undefined };
50
+ };
51
+ const snapshot = async (mode) => (await run(ReticleTool.SNAPSHOT, { mode })).payload;
52
+ return {
53
+ async reset() {
54
+ const { payload, refused } = await run(ReticleTool.NAVIGATE, { url: tab.url, timeout_ms: NAVIGATE_WAIT_MS }, NAVIGATE_WAIT_MS * 2);
55
+ const next = asText(payload['sessionId']);
56
+ if (next !== undefined) {
57
+ sessionId = next;
58
+ }
59
+ return !refused && true === payload['ok'] && false !== payload['confirmed'];
60
+ },
61
+ async look() {
62
+ const body = await snapshot(SnapshotMode.INTERACTIVE);
63
+ const route = asText(body['status']?.route);
64
+ return { tree: asText(body['tree']) ?? '', ...(route === undefined ? {} : { route }) };
65
+ },
66
+ async read() {
67
+ return asText((await snapshot(SnapshotMode.FULL))['tree']) ?? '';
68
+ },
69
+ async act(ref, action, value) {
70
+ const { payload, refused } = await run(ReticleTool.ACT, {
71
+ ref,
72
+ action,
73
+ args: value === undefined ? {} : { value },
74
+ });
75
+ return { ok: !refused && false !== payload['dispatched'], events: [] };
76
+ },
77
+ async replayStep(flow, index) {
78
+ const { payload } = await run(ReticleTool.FLOW_REPLAY, {
79
+ flowName: flow,
80
+ to: index + 1,
81
+ ...(0 === index ? {} : { at: index }),
82
+ ...(true === tab.confirmDangerous ? { confirmDangerous: true } : {}),
83
+ }, REPLAY_STEP_CALL_MS);
84
+ const steps = Array.isArray(payload['steps']) ? payload['steps'] : [];
85
+ const step = steps.map((s) => s).find((s) => index === s['step']);
86
+ return { done: stepDone(step), label: asText(step?.['anchor']) ?? UNNAMED_STEP };
87
+ },
88
+ async close() {
89
+ if (tab.closeLease) {
90
+ await releaseLease(caller, sessionId);
91
+ }
92
+ },
93
+ };
94
+ }
95
+ /** The saved flows a run replays, with `--select` applied, and how many steps each has. */
96
+ export async function flowsToReplay(fs, reticleRoot, select, now) {
97
+ const store = new FlowStore(fs, reticleRoot, { now });
98
+ const files = [];
99
+ for (const name of await store.list()) {
100
+ const loaded = await store.load(name);
101
+ if (loaded.ok) {
102
+ files.push(loaded.value);
103
+ }
104
+ }
105
+ const chosen = selectFlows(files, select === undefined ? {} : { labels: [...select] }).run;
106
+ return chosen.map((flow) => ({ name: flow.name, steps: flow.steps.length }));
107
+ }
108
+ /** Compare the two builds through the daemon on `port`. Returns the lines to print and the exit code. */
109
+ export async function runVerifyAgainst(options) {
110
+ const connect = options.connect ?? connectOverSse;
111
+ let caller;
112
+ try {
113
+ caller = await connect(endpointFor(options.port, options.token));
114
+ }
115
+ catch (error) {
116
+ const why = error instanceof Error ? error.message : String(error);
117
+ return { code: 1, lines: [`${MSG_NO_DAEMON} ${String(options.port)}: ${why}`] };
118
+ }
119
+ const open = async (url) => {
120
+ const lease = await acquireLease(caller, url, false, options.root);
121
+ if ('failed' in lease) {
122
+ throw new Error([`${MSG_LEASE_FAILED} ${url}`, ...lease.failed].join('\n'));
123
+ }
124
+ return daemonDriver(caller, {
125
+ url,
126
+ sessionId: lease.leased,
127
+ closeLease: true,
128
+ ...(true === options.confirmDangerous ? { confirmDangerous: true } : {}),
129
+ });
130
+ };
131
+ try {
132
+ const result = await compareBuilds({
133
+ reference: options.reference,
134
+ candidate: options.candidate,
135
+ open,
136
+ flows: options.flows,
137
+ maxActions: AGAINST_MAX_ACTIONS,
138
+ wait: () => options.sleep(AGAINST_WAIT_MS),
139
+ });
140
+ const urls = { reference: options.reference, candidate: options.candidate };
141
+ if (options.resultsJson !== undefined) {
142
+ const write = options.write ?? ((path, text) => writeFile(path, text, 'utf8'));
143
+ await write(options.resultsJson, `${JSON.stringify(compareJson(urls, result), null, 2)}\n`);
144
+ }
145
+ return { code: compareExit(result), lines: compareLines(urls, result) };
146
+ }
147
+ catch (error) {
148
+ return { code: 1, lines: [error instanceof Error ? error.message : String(error)] };
149
+ }
150
+ finally {
151
+ await caller.close().catch(() => undefined);
152
+ }
153
+ }
154
+ /**
155
+ * The command. It needs a daemon, as an agent does: `ensureDaemon` attaches to the one on the port,
156
+ * or starts one. Nothing here stops a daemon, so an agent's MCP link stays up.
157
+ */
158
+ export async function handleVerifyAgainst(parsed, ensureDaemon, out) {
159
+ try {
160
+ await ensureDaemon(parsed.port);
161
+ }
162
+ catch (error) {
163
+ out.fail(error instanceof Error ? error.message : String(error));
164
+ out.exit(1);
165
+ return;
166
+ }
167
+ const cwd = process.cwd();
168
+ const root = callerArtifactRoot(cwd);
169
+ const token = readOrCreatePairingTokenSync(defaultPairingTokenDir());
170
+ // The same `.reticle` the leased tabs replay from, so the list and the replay agree.
171
+ const flows = await flowsToReplay(createNodeFileSystem(), root ?? join(cwd, ReticleDir.ROOT), parsed.select, Date.now);
172
+ const result = await runVerifyAgainst({
173
+ port: parsed.port,
174
+ candidate: parsed.url,
175
+ reference: parsed.against,
176
+ flows,
177
+ ...(token === undefined || 0 === token.length ? {} : { token }),
178
+ ...(root === undefined ? {} : { root }),
179
+ ...(true === parsed.confirmDangerous ? { confirmDangerous: true } : {}),
180
+ ...(parsed.resultsJson === undefined ? {} : { resultsJson: parsed.resultsJson }),
181
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
182
+ });
183
+ for (const line of result.lines) {
184
+ out.line(line);
185
+ }
186
+ out.exit(result.code);
187
+ }
@@ -0,0 +1,2 @@
1
+ /** Why no session connected. A short timeout also says that the unit is milliseconds. */
2
+ export declare function noSessionMessage(timeoutMs: number): string;
@@ -0,0 +1,13 @@
1
+ const MSG_NO_SESSION = 'No app connected: Reticle drove the URL but no @reticlehq/browser session dialed back.\n' +
2
+ ' Make sure the SDK is in the build and reticle.connect() runs on the preview page' +
3
+ ' (for a non-localhost preview: allowNonLocalhost + a pairing token).';
4
+ /** A `--timeout` under this is likely a count of seconds, so the message names the unit. */
5
+ const SHORT_TIMEOUT_MS = 1000;
6
+ /** Why no session connected. A short timeout also says that the unit is milliseconds. */
7
+ export function noSessionMessage(timeoutMs) {
8
+ if (timeoutMs >= SHORT_TIMEOUT_MS) {
9
+ return MSG_NO_SESSION;
10
+ }
11
+ return (`${MSG_NO_SESSION}\n The wait was ${String(timeoutMs)} ms: the timeout is in milliseconds. ` +
12
+ 'Use --timeout 60000 to wait 60 seconds.');
13
+ }
@@ -7,7 +7,31 @@
7
7
  */
8
8
  import { Verified, type ReticleVerificationRun } from '@reticlehq/core';
9
9
  type JourneyVerdict = typeof Verified.YES | typeof Verified.NO | typeof Verified.UNKNOWN;
10
+ /**
11
+ * How many steps passed, failed, or were never judged. `unknown` is a step the replay never
12
+ * reached or could not decide; it stays in the total, so an undecided step never raises a score.
13
+ */
14
+ export interface StepCounts {
15
+ passed: number;
16
+ failed: number;
17
+ unknown: number;
18
+ /**
19
+ * Verdicts reached AFTER the journey's first failed step (`--sweep` goes on past it). They are not
20
+ * in `passed` or `failed`, and they count as `unknown` in the score: a step is paid only when every
21
+ * step before it passed. Absent when no step ran after a failure.
22
+ */
23
+ swept?: {
24
+ passed: number;
25
+ failed: number;
26
+ };
27
+ }
28
+ /**
29
+ * The shape of the `--results-json` file. 1 had only `verdict` and `results`; 2 adds `steps` per
30
+ * journey and the gated `score`. A consumer checks this before it reads a field that 1 did not have.
31
+ */
32
+ export declare const RESULTS_SCHEMA_VERSION = 2;
10
33
  export interface VerifyResults {
34
+ schemaVersion: number;
11
35
  verdict: JourneyVerdict;
12
36
  /** `runId` is the run the journey came from, set only when that run reached the platform. */
13
37
  results: {
@@ -15,8 +39,17 @@ export interface VerifyResults {
15
39
  verdict: JourneyVerdict;
16
40
  reason?: string;
17
41
  runId?: string;
42
+ steps: StepCounts;
18
43
  }[];
44
+ /** All journeys together. `value` is passed / total, absent when there is no step at all. */
45
+ score: StepCounts & {
46
+ total: number;
47
+ value?: number;
48
+ };
19
49
  }
20
- /** `pushed`: the run reached the platform, so its id resolves there and the results name it. */
21
- export declare function verifyResults(run: ReticleVerificationRun, pushed?: boolean): VerifyResults;
50
+ /**
51
+ * `pushed`: the run reached the platform, so its id resolves there and the results name it.
52
+ * `planned`: each flow file's step count, by journey name (see `stepCounts`).
53
+ */
54
+ export declare function verifyResults(run: ReticleVerificationRun, pushed?: boolean, planned?: ReadonlyMap<string, number>): VerifyResults;
22
55
  export {};