@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,198 @@
1
+ /**
2
+ * Steps that reach past the page the journey is on: one opens another page (`navigate`), one sends a
3
+ * request to the app (`request`). Neither acts on an element, and both depend on a caller that owns
4
+ * what the replay cannot reach by itself: the sessions, the origin, the cookies.
5
+ */
6
+ import { FlowStepTool, } from '@reticlehq/core';
7
+ /*
8
+ * A step that opens a page in the middle of a journey. The replay goes on with the page that loaded,
9
+ * and the step's `expect` is judged there (see `OpenPage`).
10
+ */
11
+ /** The step's result before its `expect` is judged, and the session to go on with. */
12
+ export async function openStepPage(step, index, open) {
13
+ const url = step.navigate ?? '';
14
+ const base = { step: index, tool: FlowStepTool.NAVIGATE, anchor: url };
15
+ // Never skipped: a journey that reports green without the page it was meant to check is a false
16
+ // green arriving through the step meant to make the check stronger.
17
+ if (open === undefined) {
18
+ return {
19
+ result: {
20
+ ...base,
21
+ ok: false,
22
+ error: `this replay cannot open a page, so the step that opens ${url} did not run`,
23
+ },
24
+ };
25
+ }
26
+ const opened = await open(url);
27
+ if ('error' in opened) {
28
+ return { result: { ...base, ok: false, error: opened.error } };
29
+ }
30
+ return { result: { ...base, ok: true }, session: opened.session };
31
+ }
32
+ /** Why an answer does not match the step's `response`, or undefined when it does. */
33
+ function responseMismatch(response, answer) {
34
+ const statuses = response.status === undefined ? [] : [response.status].flat();
35
+ if (statuses.length > 0 && !statuses.includes(answer.status)) {
36
+ return `the answer was ${String(answer.status)}, expected ${statuses.join(' or ')}`;
37
+ }
38
+ if (response.contains !== undefined && !answer.body.includes(response.contains)) {
39
+ return `the answer (${String(answer.status)}) does not contain "${response.contains}"`;
40
+ }
41
+ if (response.absent !== undefined && answer.body.includes(response.absent)) {
42
+ return `the answer (${String(answer.status)}) contains "${response.absent}", which it must not`;
43
+ }
44
+ return undefined;
45
+ }
46
+ /*
47
+ * A step that sends one request and judges the answer. It never skips: a rule the flow says it
48
+ * checks and did not is a false green.
49
+ */
50
+ /** Methods that only read. Any other method changes data and needs the run's confirmation. */
51
+ const READ_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
52
+ export async function runRequestStep(step, index, send, judged,
53
+ /**
54
+ * The run's own yes to steps that change data, never the flow file's: flow files are shared, and a
55
+ * saved `DELETE` would otherwise run with the user's cookies on any app the flow is pointed at.
56
+ */
57
+ confirmDangerous) {
58
+ const request = step.request;
59
+ const label = request === undefined ? '' : `${request.method} ${request.url}`;
60
+ const base = { step: index, tool: FlowStepTool.REQUEST, anchor: label };
61
+ if (request === undefined || send === undefined) {
62
+ return {
63
+ ...base,
64
+ ok: false,
65
+ error: `this replay cannot send a request, so ${label} did not run`,
66
+ };
67
+ }
68
+ if (!READ_METHODS.has(request.method.toUpperCase()) && !confirmDangerous) {
69
+ return {
70
+ ...base,
71
+ ok: false,
72
+ error: `potentially destructive request blocked: ${label} changes data, so it was not sent. ` +
73
+ 'Confirm it for this run: reticle_verify{action:"flows", confirmDangerous: true}, or ' +
74
+ '`reticle verify <url> --confirm-dangerous`. It cannot be confirmed in the flow file.',
75
+ };
76
+ }
77
+ const answer = await send(request);
78
+ if ('error' in answer) {
79
+ return { ...base, ok: false, error: answer.error };
80
+ }
81
+ const mismatch = !judged || step.response === undefined ? undefined : responseMismatch(step.response, answer);
82
+ return mismatch === undefined ? { ...base, ok: true } : { ...base, ok: false, error: mismatch };
83
+ }
84
+ /** True for a step that reaches past the page: `navigate` or `request`. */
85
+ export function isPageStep(step) {
86
+ return step.navigate !== undefined || step.request !== undefined;
87
+ }
88
+ /**
89
+ * Run a `navigate` or `request` step. `judge` evaluates an `expect` on a page; it comes from the
90
+ * replay so this file does not import the replay back. `checked` is false for a setup step, whose
91
+ * consequence is not checked. A navigate step returns the session to go on with.
92
+ */
93
+ export async function runPageStep(step, index, ctx) {
94
+ if (step.request !== undefined) {
95
+ return {
96
+ result: await runRequestStep(step, index, ctx.send, ctx.checked, ctx.confirmDangerous),
97
+ };
98
+ }
99
+ const opened = await openStepPage(step, index, ctx.open);
100
+ if (opened.session === undefined) {
101
+ return opened;
102
+ }
103
+ const result = opened.result;
104
+ const page = ctx.route(opened.session);
105
+ if (page !== undefined) {
106
+ result.page = page;
107
+ }
108
+ const drift = step.expect === undefined || !ctx.checked
109
+ ? undefined
110
+ : await ctx.judge(opened.session, step.expect);
111
+ if (drift !== undefined) {
112
+ result.ok = false;
113
+ result.drift = drift;
114
+ }
115
+ return { result, session: opened.session };
116
+ }
117
+ /** How long a `request` step waits for the answer. */
118
+ const REQUEST_TIMEOUT_MS = 15_000;
119
+ /** The `Cookie` header the page would send to `host`, from the driven browser's storage state. */
120
+ function cookieHeader(state, host) {
121
+ const cookies = state?.cookies;
122
+ if (!Array.isArray(cookies)) {
123
+ return '';
124
+ }
125
+ return cookies
126
+ .filter((c) => {
127
+ const domain = c.domain;
128
+ if (typeof domain !== 'string') {
129
+ return false;
130
+ }
131
+ const bare = domain.replace(/^\./, '');
132
+ return host === bare || host.endsWith(`.${bare}`);
133
+ })
134
+ .map((c) => `${c.name}=${c.value}`)
135
+ .join('; ');
136
+ }
137
+ /**
138
+ * The sender a `request` step uses: same origin as the page only, the page's cookies unless the step
139
+ * is anonymous, no redirects followed (a 302 is the answer to judge). `pageUrl` is read at send time,
140
+ * because a `navigate` step changes the page.
141
+ */
142
+ export function requestSender(pageUrl, provider,
143
+ /** The `Cookie` header of the account a step names in `as`, or undefined when not supplied. */
144
+ accountCookie = () => undefined) {
145
+ return async (request) => {
146
+ const url = pageUrl();
147
+ if (url === undefined) {
148
+ return { error: 'the page has no known address, so the request has nowhere to go' };
149
+ }
150
+ const origin = new URL(url).origin;
151
+ const target = new URL(request.url, origin);
152
+ if (target.origin !== origin) {
153
+ return {
154
+ error: `${request.url} is not on the app's origin ${origin}; a flow sends requests only to its app`,
155
+ };
156
+ }
157
+ const headers = {};
158
+ if (request.body !== undefined) {
159
+ headers['content-type'] = request.contentType ?? 'application/json';
160
+ }
161
+ if (request.as !== undefined) {
162
+ const cookie = accountCookie(request.as);
163
+ if (cookie === undefined || '' === cookie) {
164
+ return {
165
+ error: `the session of the account "${request.as}" was not supplied, so the request was not ` +
166
+ 'sent. Supply its Cookie header as a secret for this run.',
167
+ };
168
+ }
169
+ headers['cookie'] = cookie;
170
+ }
171
+ else if (true !== request.anonymous) {
172
+ const state = await provider?.storageState?.(url);
173
+ if (state === undefined) {
174
+ return {
175
+ error: 'the page cookies cannot be read here (this is not a driven browser), so the request ' +
176
+ 'cannot go as the signed-in user. Mark it `anonymous`, or replay with `reticle verify`.',
177
+ };
178
+ }
179
+ const cookie = cookieHeader(state, target.hostname);
180
+ if (cookie !== '') {
181
+ headers['cookie'] = cookie;
182
+ }
183
+ }
184
+ try {
185
+ const answer = await fetch(target, {
186
+ method: request.method,
187
+ headers,
188
+ ...(request.body === undefined ? {} : { body: request.body }),
189
+ redirect: 'manual',
190
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
191
+ });
192
+ return { status: answer.status, body: await answer.text() };
193
+ }
194
+ catch (error) {
195
+ return { error: `the request ${request.method} ${target.pathname} failed: ${String(error)}` };
196
+ }
197
+ };
198
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The sequential half of `reticle_verify { action: "flows" | "change" }`.
3
+ *
4
+ * Flows replay one after the other against one live tab, with no limit on the total time. When the
5
+ * browser session was lost part-way through, every remaining flow waited for a page that never came,
6
+ * and the MCP client gave up (about 300 s) with no result at all - including for the flows that had
7
+ * already run. So the loop now stops at two points, and says so:
8
+ *
9
+ * - the total time budget is spent: no further flow is started;
10
+ * - the session is gone after a flow: the rest cannot run, so they are not attempted.
11
+ *
12
+ * Kept free of IO (the clock, the liveness probe and the replay are injected) so both stops are
13
+ * tested with a fake clock.
14
+ */
15
+ import { z } from 'zod';
16
+ import { SuiteStopReason, SuiteVerdictStatus } from '@reticlehq/core';
17
+ /** Below the roughly 300 s at which common MCP clients stop waiting, so a result can still be returned. */
18
+ export declare const DEFAULT_SUITE_BUDGET_MS = 240000;
19
+ /** An hour: more than that is not a budget, and a client will have given up long before. */
20
+ export declare const MAX_SUITE_BUDGET_MS = 3600000;
21
+ export interface SuiteStop {
22
+ reason: SuiteStopReason;
23
+ /** For SESSION_LOST: the flow that was running when the session went. */
24
+ flow?: string;
25
+ budgetMs?: number;
26
+ nextAction: string;
27
+ }
28
+ export interface SequentialRun<R> {
29
+ results: R[];
30
+ /** Selected flows that were never started, in order. */
31
+ notRun: string[];
32
+ stopped?: SuiteStop;
33
+ }
34
+ export interface SequentialRunOptions<R> {
35
+ now: () => number;
36
+ budgetMs: number;
37
+ /** True while the session the flows drive is still there. Probed after each flow. */
38
+ sessionAlive: () => boolean;
39
+ run: (name: string) => Promise<R>;
40
+ }
41
+ export declare function runSequentially<R>(names: readonly string[], options: SequentialRunOptions<R>): Promise<SequentialRun<R>>;
42
+ /**
43
+ * Mark a verdict `partial` when the suite stopped early. The ran flows keep their counts and
44
+ * failures; the summary leads with what did not run, because a green count over half a suite must
45
+ * not read as a pass.
46
+ */
47
+ export declare function withStop<V extends {
48
+ status: SuiteVerdictStatus;
49
+ summary: string;
50
+ }>(verdict: V, notRun: readonly string[], stopped: SuiteStop | undefined): V & {
51
+ notRun?: string[];
52
+ stopped?: SuiteStop;
53
+ };
54
+ /** The `budgetMs` input of the suite tools. */
55
+ export declare const BUDGET_MS_ARG: z.ZodOptional<z.ZodNumber>;
56
+ /** The two output fields a "partial" suite adds. */
57
+ export declare const PARTIAL_OUTPUT: {
58
+ notRun: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
+ stopped: z.ZodOptional<z.ZodObject<{
60
+ reason: z.ZodEnum<{
61
+ time_budget: "time_budget";
62
+ session_lost: "session_lost";
63
+ }>;
64
+ flow: z.ZodOptional<z.ZodString>;
65
+ budgetMs: z.ZodOptional<z.ZodNumber>;
66
+ nextAction: z.ZodString;
67
+ }, z.core.$strip>>;
68
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The sequential half of `reticle_verify { action: "flows" | "change" }`.
3
+ *
4
+ * Flows replay one after the other against one live tab, with no limit on the total time. When the
5
+ * browser session was lost part-way through, every remaining flow waited for a page that never came,
6
+ * and the MCP client gave up (about 300 s) with no result at all - including for the flows that had
7
+ * already run. So the loop now stops at two points, and says so:
8
+ *
9
+ * - the total time budget is spent: no further flow is started;
10
+ * - the session is gone after a flow: the rest cannot run, so they are not attempted.
11
+ *
12
+ * Kept free of IO (the clock, the liveness probe and the replay are injected) so both stops are
13
+ * tested with a fake clock.
14
+ */
15
+ import { z } from 'zod';
16
+ import { SuiteStopReason, SuiteVerdictStatus } from '@reticlehq/core';
17
+ /** Below the roughly 300 s at which common MCP clients stop waiting, so a result can still be returned. */
18
+ export const DEFAULT_SUITE_BUDGET_MS = 240_000;
19
+ /** An hour: more than that is not a budget, and a client will have given up long before. */
20
+ export const MAX_SUITE_BUDGET_MS = 3_600_000;
21
+ export async function runSequentially(names, options) {
22
+ const deadline = options.now() + options.budgetMs;
23
+ const results = [];
24
+ for (const [index, name] of names.entries()) {
25
+ // The first flow always starts: a budget that stops a suite before anything ran proves nothing.
26
+ if (index > 0 && options.now() >= deadline) {
27
+ return {
28
+ results,
29
+ notRun: names.slice(index),
30
+ stopped: {
31
+ reason: SuiteStopReason.TIME_BUDGET,
32
+ budgetMs: options.budgetMs,
33
+ nextAction: 'run the flows listed in notRun in a new call (pass `names`), or raise `budgetMs` if your client waits longer',
34
+ },
35
+ };
36
+ }
37
+ results.push(await options.run(name));
38
+ if (index < names.length - 1 && !options.sessionAlive()) {
39
+ return {
40
+ results,
41
+ notRun: names.slice(index + 1),
42
+ stopped: {
43
+ reason: SuiteStopReason.SESSION_LOST,
44
+ flow: name,
45
+ nextAction: 'reconnect the app (open it in a tab, or call reticle_sessions to find the live one), then run the flows listed in notRun with `names`',
46
+ },
47
+ };
48
+ }
49
+ }
50
+ return { results, notRun: [] };
51
+ }
52
+ /**
53
+ * Mark a verdict `partial` when the suite stopped early. The ran flows keep their counts and
54
+ * failures; the summary leads with what did not run, because a green count over half a suite must
55
+ * not read as a pass.
56
+ */
57
+ export function withStop(verdict, notRun, stopped) {
58
+ if (stopped === undefined) {
59
+ return verdict;
60
+ }
61
+ const why = stopped.reason === SuiteStopReason.TIME_BUDGET
62
+ ? `the ${String(Math.round((stopped.budgetMs ?? 0) / 1000))} s total budget ran out`
63
+ : `the browser session was lost while "${stopped.flow ?? ''}" was running`;
64
+ return {
65
+ ...verdict,
66
+ status: SuiteVerdictStatus.PARTIAL,
67
+ summary: `PARTIAL - ${why}; ${String(notRun.length)} flow(s) did not run (${notRun.join(', ')}). Of those that ran: ${verdict.summary}`,
68
+ notRun: [...notRun],
69
+ stopped,
70
+ };
71
+ }
72
+ /** The `budgetMs` input of the suite tools. */
73
+ export const BUDGET_MS_ARG = z
74
+ .number()
75
+ .finite()
76
+ .int()
77
+ .positive()
78
+ .max(MAX_SUITE_BUDGET_MS)
79
+ .optional()
80
+ .describe(`Total time budget for the sequential run, in milliseconds (default ${String(DEFAULT_SUITE_BUDGET_MS)}, below the ~300 s at which common MCP clients stop waiting). When it is spent no further flow is started: the call returns status "partial" with the finished results and the flows in notRun.`);
81
+ /** The two output fields a "partial" suite adds. */
82
+ export const PARTIAL_OUTPUT = {
83
+ notRun: z
84
+ .array(z.string())
85
+ .optional()
86
+ .describe('Present on a "partial" suite: the selected flows that were never started because the time budget ran out or the session was lost.'),
87
+ stopped: z
88
+ .object({
89
+ reason: z.enum([SuiteStopReason.TIME_BUDGET, SuiteStopReason.SESSION_LOST]),
90
+ flow: z.string().optional(),
91
+ budgetMs: z.number().optional(),
92
+ nextAction: z.string(),
93
+ })
94
+ .optional()
95
+ .describe('Present on a "partial" suite: why it ended early (time_budget | session_lost), the flow that lost the session, and one next action.'),
96
+ };
@@ -1,19 +1,21 @@
1
1
  import { z } from 'zod';
2
- import { sessionRoot, sessionProjectId } from '../../memory/project/session-root.js';
2
+ import { sessionRoot } from '../../memory/project/session-root.js';
3
3
  import { verdictForSuite } from '../../judgement/outcome/verify-change-verdict.js';
4
4
  import { attributedFailures } from '../../judgement/outcome/attributed-failure.js';
5
5
  import { Verified, isAdvisory } from '@reticlehq/core';
6
6
  import { ReticleTool } from '@reticlehq/core';
7
7
  import { asNumber, asRecord, asString } from '@reticlehq/core';
8
8
  import { workerCountSchema } from '../../surface/tools/args/numeric-bounds.js';
9
- import { loadNamedFlows, resolveChangedFiles } from '../../command/cli/cli-flow-commands.js';
10
- import { projectDirFor } from '../../memory/project/session-root.js';
9
+ import { resolveChangedFiles } from '../../command/cli/cli-flow-commands.js';
10
+ import { namedFlowsForSession } from './flow-store-for-session.js';
11
11
  import { affectedSavedFlows } from './change/flow-sources.js';
12
12
  import { FLOW_TOOLS } from './flow-tools.js';
13
13
  import { RESULT_FIELD, changeResult } from '../../surface/tools/reticle-result.js';
14
14
  import { findContradictions } from '@reticlehq/engine/disagreement/contradictions.js';
15
+ import { isOneActionOnly } from './flow-cross-step.js';
15
16
  import { parseControls } from '../../surface/tools/coverage-tools.js';
16
17
  import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
18
+ import { BUDGET_MS_ARG } from './suite/sequential-run.js';
17
19
  const VERIFY_CHANGE = {
18
20
  name: ReticleTool.VERIFY_CHANGE,
19
21
  description: 'Did my change break anything? Give it the files you edited (or `since` for a git ref) and it works out which saved flows cover them, replays exactly those, and answers with one `verified` (yes|no|unknown) plus `because`. `unknown` when NO saved flow covers the change — that is the honest answer, never a green: nothing ran, so nothing was proved. Returns { verified, because, changedFiles, flowsRun, suite, unknownProvenance }.',
@@ -30,6 +32,11 @@ const VERIFY_CHANGE = {
30
32
  parallel: workerCountSchema
31
33
  .optional()
32
34
  .describe('Replay this many affected flows at once (needs the browser pool).'),
35
+ ignoreHTTPSErrors: z
36
+ .boolean()
37
+ .optional()
38
+ .describe('Accept a self-signed or local-CA certificate for parallel flow leases. Omit to inherit the source lease setting; otherwise certificate checks stay on.'),
39
+ budgetMs: BUDGET_MS_ARG,
33
40
  sessionId: z
34
41
  .string()
35
42
  .optional()
@@ -68,16 +75,14 @@ const VERIFY_CHANGE = {
68
75
  // used to say it was: `deps.reticleRoot` is the daemon's `.reticle` SUBDIRECTORY, and the
69
76
  // daemon's cwd is wherever the MCP host started it — `/` or `$HOME` for a globally-registered
70
77
  // server. Both answer about the wrong repository while reporting a clean "nothing changed",
71
- // which reads as a pass. `projectDirFor` is the directory holding the resolved root.
72
- const changed = await resolveChangedFiles(files, since, projectDirFor(deps, asString(args['sessionId'])));
78
+ // which reads as a pass. `projectDir` is the directory holding the resolved root.
79
+ //
80
+ // The flows come from the store `flows` replays from, and flow_save writes to. A read that
81
+ // resolved differently would report "no flows covered this change" over flows that exist,
82
+ // which reads as a clean result rather than an error (#1996).
83
+ const { flows, projectDir } = await namedFlowsForSession(deps, asString(args['sessionId']));
84
+ const changed = await resolveChangedFiles(files, since, projectDir);
73
85
  const changedFiles = changed.files;
74
- // Same root flow_save wrote to. A read that resolved differently would report "no flows
75
- // covered this change" over flows that exist, which reads as a clean result rather than
76
- // an error, so the disagreement would be invisible.
77
- // Both halves of the address from one place. The root was already resolved from the session;
78
- // the id used to come from the daemon's cwd, so they named different projects.
79
- const sid = asString(args['sessionId']);
80
- const flows = await loadNamedFlows(deps.fs, sessionRoot(deps, sid), sessionProjectId(deps, sid));
81
86
  const { affected, unknownProvenance } = affectedSavedFlows(flows, changedFiles);
82
87
  if (0 === changedFiles.length) {
83
88
  return {
@@ -126,11 +131,16 @@ const VERIFY_CHANGE = {
126
131
  // flows produced, and never events from whatever the agent did before calling this.
127
132
  const cursor = cursorBeforeReplay(deps, args);
128
133
  const verify = FLOW_TOOLS.find((tool) => tool.name === ReticleTool.FLOW_VERIFY);
129
- if (verify === undefined)
134
+ if (verify === undefined) {
130
135
  throw new Error('the whole-suite replay tool is not registered');
136
+ }
131
137
  const suite = asRecord(await verify.handler(deps, {
132
138
  names: affected,
133
139
  ...(asNumber(args['parallel']) === undefined ? {} : { parallel: args['parallel'] }),
140
+ ...(asNumber(args['budgetMs']) === undefined ? {} : { budgetMs: args['budgetMs'] }),
141
+ ...('boolean' === typeof args['ignoreHTTPSErrors']
142
+ ? { ignoreHTTPSErrors: args['ignoreHTTPSErrors'] }
143
+ : {}),
134
144
  ...(asString(args['sessionId']) === undefined ? {} : { sessionId: args['sessionId'] }),
135
145
  }));
136
146
  const failed = suite.failed ?? 0;
@@ -254,9 +264,10 @@ async function inspectAfterReplay(deps, args, cursor) {
254
264
  appOrigin: session.url,
255
265
  background: session.background,
256
266
  namedNetUrls: [],
257
- // The replay is the action, and the cursor is where it started.
267
+ // The cursor is where the replay started. The replay is many user actions, not one, so a
268
+ // finding that only means something inside one action's window is left to the steps (#2013).
258
269
  actionSince: cursor,
259
- });
270
+ }).filter((found) => !isOneActionOnly(found));
260
271
  const snapshot = await session.command(ReticleCommand.SNAPSHOT, {
261
272
  mode: SnapshotMode.INTERACTIVE,
262
273
  });
@@ -87,6 +87,7 @@ interface ProbeMachine {
87
87
  env: NodeJS.ProcessEnv;
88
88
  exists: (path: string) => boolean;
89
89
  pathProblem: (path: string) => ChromiumPathProblem | undefined;
90
+ platform?: NodeJS.Platform;
90
91
  }
91
92
  /**
92
93
  * The lease preflight's probe: "can a launch succeed?", which now includes the Chrome/Edge fallback
@@ -48,11 +48,15 @@ const CHANNEL_PATHS = {
48
48
  },
49
49
  };
50
50
  const WINDOWS_INSTALL_ROOTS = ['LOCALAPPDATA', 'PROGRAMFILES', 'PROGRAMFILES(X86)'];
51
+ function darwinChannelCandidates(appPath, env) {
52
+ const home = env['HOME']?.replace(/\/+$/, '');
53
+ return undefined === home || 0 === home.length ? [appPath] : [appPath, `${home}${appPath}`];
54
+ }
51
55
  /** Candidate executable paths for a channel on this platform; empty where it is not installable. */
52
56
  export function channelExecutableCandidates(channel, platform, env) {
53
57
  const paths = CHANNEL_PATHS[channel];
54
58
  if ('darwin' === platform)
55
- return [paths.darwin];
59
+ return darwinChannelCandidates(paths.darwin, env);
56
60
  if ('linux' === platform)
57
61
  return [paths.linux];
58
62
  if ('win32' === platform) {
@@ -63,9 +67,21 @@ export function channelExecutableCandidates(channel, platform, env) {
63
67
  }
64
68
  return [];
65
69
  }
70
+ function findInstalledBrowser(deps) {
71
+ for (const channel of CHANNEL_ORDER) {
72
+ const found = channelExecutableCandidates(channel, deps.platform, deps.env).find((p) => deps.exists(p));
73
+ if (undefined !== found) {
74
+ // Playwright resolves a channel from its own registry (/Applications on macOS). A browser in
75
+ // ~/Applications is not in that registry, so launch it by executablePath instead.
76
+ const outsideRegistry = 'darwin' === deps.platform && found !== CHANNEL_PATHS[channel].darwin;
77
+ return outsideRegistry ? { channel, executablePath: found } : { channel };
78
+ }
79
+ }
80
+ return undefined;
81
+ }
66
82
  /** The first installed Chrome/Edge on this machine, or undefined. Looks only; installs nothing. */
67
83
  export function findInstalledChannel(deps) {
68
- return CHANNEL_ORDER.find((channel) => channelExecutableCandidates(channel, deps.platform, deps.env).some((p) => deps.exists(p)));
84
+ return findInstalledBrowser(deps)?.channel;
69
85
  }
70
86
  const usingChannelLine = (channel) => `[reticle] Playwright's Chromium is not installed; using the installed ${CHANNEL_LABEL[channel]} instead.\n`;
71
87
  const installingLine = (command) => `[reticle] No Chromium, Chrome or Edge found; installing Playwright's Chromium (${command}) — a one-time download…\n`;
@@ -98,10 +114,12 @@ export async function resolveChromiumTarget(deps) {
98
114
  }
99
115
  if (deps.exists(bundled))
100
116
  return { found: true };
101
- const channel = findInstalledChannel(deps);
102
- if (channel !== undefined) {
103
- deps.announce(usingChannelLine(channel));
104
- return { found: true, channel };
117
+ const installed = findInstalledBrowser(deps);
118
+ if (undefined !== installed) {
119
+ deps.announce(usingChannelLine(installed.channel));
120
+ return undefined === installed.executablePath
121
+ ? { found: true, channel: installed.channel }
122
+ : { found: true, executablePath: installed.executablePath };
105
123
  }
106
124
  deps.announce(installingLine(chromiumInstallCommand(bundledPlaywrightVersion())));
107
125
  const ok = await deps.install();
@@ -207,6 +225,7 @@ const THIS_MACHINE = {
207
225
  env: process.env,
208
226
  exists: existsSync,
209
227
  pathProblem: (path) => chromiumPathProblem(path, process.platform),
228
+ platform: process.platform,
210
229
  };
211
230
  /**
212
231
  * The lease preflight's probe: "can a launch succeed?", which now includes the Chrome/Edge fallback
@@ -217,16 +236,29 @@ export async function probeLaunchableChromium(machine = THIS_MACHINE) {
217
236
  const configured = configuredChromiumProbe(machine.env, machine.pathProblem);
218
237
  if (configured !== undefined)
219
238
  return configured;
239
+ const platform = machine.platform ?? process.platform;
220
240
  try {
221
241
  const { chromium } = await import('playwright');
222
- const target = await resolveChromiumTarget(defaultDeps(chromium));
242
+ const target = await resolveChromiumTarget({
243
+ ...defaultDeps(chromium),
244
+ exists: machine.exists,
245
+ pathProblem: machine.pathProblem,
246
+ platform,
247
+ env: machine.env,
248
+ ...(machine === THIS_MACHINE
249
+ ? {}
250
+ : {
251
+ install: () => Promise.resolve(false),
252
+ announce: () => { },
253
+ }),
254
+ });
223
255
  if (target.found)
224
256
  return { exists: true };
225
257
  }
226
258
  catch {
227
- // No playwright at all — probeChromium below says so, with where it looked.
259
+ // No playwright at all — probeChromiumWithFallback below says so, with where it looked.
228
260
  }
229
- return probeChromium();
261
+ return probeChromiumWithFallback(machine);
230
262
  }
231
263
  /**
232
264
  * `doctor`'s probe: the plain Chromium probe, plus the installed browser that stands in when the
@@ -236,13 +268,18 @@ export async function probeChromiumWithFallback(machine = THIS_MACHINE) {
236
268
  const configured = configuredChromiumProbe(machine.env, machine.pathProblem);
237
269
  if (configured !== undefined)
238
270
  return configured;
239
- const probe = await probeChromium();
271
+ const probe = await probeChromium(machine.exists);
240
272
  if (probe.exists)
241
273
  return probe;
274
+ const platform = machine.platform ?? process.platform;
242
275
  const channel = findInstalledChannel({
243
276
  exists: machine.exists,
244
- platform: process.platform,
277
+ platform,
245
278
  env: machine.env,
246
279
  });
247
- return channel === undefined ? probe : { ...probe, fallback: CHANNEL_LABEL[channel] };
280
+ if (undefined !== channel) {
281
+ return { ...probe, fallback: CHANNEL_LABEL[channel] };
282
+ }
283
+ const fallbackPaths = CHANNEL_ORDER.flatMap((ch) => channelExecutableCandidates(ch, platform, machine.env));
284
+ return 0 === fallbackPaths.length ? probe : { ...probe, fallbackPaths };
248
285
  }
@@ -15,6 +15,19 @@ export interface BackgroundSyncGate {
15
15
  /** Who is sending, when it is ON: a new value after a new sign-in or a new key. */
16
16
  credential?: string;
17
17
  }
18
+ /**
19
+ * The switch a person set to stop sending without being asked, or undefined when nobody set it.
20
+ *
21
+ * Every send that nobody asked for reads it, the daemon's and the one `start()` runs on close. The
22
+ * close cycle read nothing, so a person who switched it off still sent this project and every
23
+ * other linked one when `reticle drive` or a self-booting `verify` closed.
24
+ */
25
+ export declare function backgroundSyncOptOut(reticleRoot: string, env: NodeJS.ProcessEnv): BackgroundSyncGate | undefined;
26
+ /**
27
+ * The gate of the one cycle `start()` sends on close, with nobody signed in. CI's key in the
28
+ * environment still sends there, as the docs promise. A switch that a person set still stops it.
29
+ */
30
+ export declare function closeCycleGate(reticleRoot: string, env: NodeJS.ProcessEnv): BackgroundSyncGate;
18
31
  /** May the daemon send this root's work now? Reads the disk, never the network. */
19
32
  export declare function backgroundSyncGate(input: {
20
33
  reticleRoot: string;
@@ -29,9 +29,14 @@ export const BackgroundSyncState = {
29
29
  };
30
30
  const OFF_VALUES = new Set(['0', 'false', 'off', 'no']);
31
31
  const BACKGROUND_SYNC_SETTING = 'backgroundSync';
32
- /** May the daemon send this root's work now? Reads the disk, never the network. */
33
- export function backgroundSyncGate(input) {
34
- const { reticleRoot, cloud, env, signedIn } = input;
32
+ /**
33
+ * The switch a person set to stop sending without being asked, or undefined when nobody set it.
34
+ *
35
+ * Every send that nobody asked for reads it, the daemon's and the one `start()` runs on close. The
36
+ * close cycle read nothing, so a person who switched it off still sent this project and every
37
+ * other linked one when `reticle drive` or a self-booting `verify` closed.
38
+ */
39
+ export function backgroundSyncOptOut(reticleRoot, env) {
35
40
  if (OFF_VALUES.has((env[ReticleEnv.BACKGROUND_SYNC] ?? '').toLowerCase())) {
36
41
  return {
37
42
  state: BackgroundSyncState.OPTED_OUT,
@@ -44,6 +49,22 @@ export function backgroundSyncGate(input) {
44
49
  why: `off in this project ("${BACKGROUND_SYNC_SETTING}": false in .reticle.json)`,
45
50
  };
46
51
  }
52
+ return undefined;
53
+ }
54
+ /**
55
+ * The gate of the one cycle `start()` sends on close, with nobody signed in. CI's key in the
56
+ * environment still sends there, as the docs promise. A switch that a person set still stops it.
57
+ */
58
+ export function closeCycleGate(reticleRoot, env) {
59
+ return (backgroundSyncOptOut(reticleRoot, env) ?? { state: BackgroundSyncState.ON, why: 'on close' });
60
+ }
61
+ /** May the daemon send this root's work now? Reads the disk, never the network. */
62
+ export function backgroundSyncGate(input) {
63
+ const { reticleRoot, cloud, env, signedIn } = input;
64
+ const optedOut = backgroundSyncOptOut(reticleRoot, env);
65
+ if (optedOut !== undefined) {
66
+ return optedOut;
67
+ }
47
68
  // `link` is set only from the project's own link file; an environment key leaves it unset.
48
69
  if (cloud.link === undefined) {
49
70
  return {