@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
@@ -49,6 +49,10 @@ export interface NetworkDetail {
49
49
  * Omitted when the whole body fits, so the caveat means something when it appears.
50
50
  */
51
51
  requestBodyTruncated?: boolean;
52
+ /** The response body, captured only where the page cannot (a server-sent download); capped. */
53
+ responseBody?: string;
54
+ /** True when `responseBody` was cut to the cap: a body check on the rest cannot be decided. */
55
+ responseBodyTruncated?: boolean;
52
56
  /**
53
57
  * The document that ISSUED the request, which is what identifies the session it belongs to.
54
58
  *
@@ -73,6 +77,8 @@ export declare function buildNetworkDetail(raw: {
73
77
  headers: Record<string, string>;
74
78
  resourceType?: string;
75
79
  requestBody?: string;
80
+ /** The response body, when the capture could read it (a server-sent download). */
81
+ responseBody?: string;
76
82
  pageUrl?: string;
77
83
  }, policy?: RedactionPolicy): NetworkDetail;
78
84
  /** The minimal Playwright surfaces this attachment reads — kept structural so it's fake-testable. */
@@ -19,8 +19,9 @@ const DEFAULT_POLICY = { isSensitiveKey: defaultIsSensitiveKey };
19
19
  * which is key-based over a parsed object — not string-shaped.
20
20
  */
21
21
  function redactByKey(value, policy) {
22
- if (Array.isArray(value))
22
+ if (Array.isArray(value)) {
23
23
  return value.map((v) => redactByKey(v, policy));
24
+ }
24
25
  if (value !== null && 'object' === typeof value) {
25
26
  const out = {};
26
27
  for (const [k, v] of Object.entries(value)) {
@@ -86,6 +87,10 @@ function projectHeaders(headers, policy) {
86
87
  * about the function's inputs.
87
88
  */
88
89
  export function buildNetworkDetail(raw, policy = DEFAULT_POLICY) {
90
+ // Capped and redacted exactly like the request body, and the cut is declared the same way.
91
+ const responseBody = raw.responseBody === undefined || 0 === raw.responseBody.length
92
+ ? undefined
93
+ : projectWireBody(raw.responseBody, policy);
89
94
  const wireBody = raw.requestBody === undefined || 0 === raw.requestBody.length
90
95
  ? undefined
91
96
  : projectWireBody(raw.requestBody, policy);
@@ -101,6 +106,12 @@ export function buildNetworkDetail(raw, policy = DEFAULT_POLICY) {
101
106
  requestBody: wireBody.body,
102
107
  ...(wireBody.truncated ? { requestBodyTruncated: true } : {}),
103
108
  }),
109
+ ...(responseBody === undefined
110
+ ? {}
111
+ : {
112
+ responseBody: responseBody.body,
113
+ ...(responseBody.truncated ? { responseBodyTruncated: true } : {}),
114
+ }),
104
115
  ...(raw.pageUrl === undefined || 0 === raw.pageUrl.length ? {} : { pageUrl: raw.pageUrl }),
105
116
  };
106
117
  }
@@ -8,6 +8,17 @@ function mockProvider(deps) {
8
8
  const p = deps.realInput;
9
9
  return p !== undefined && 'function' === typeof p.setMocks ? p : undefined;
10
10
  }
11
+ /**
12
+ * Whether this call can mock the session: a lease of this caller, or a page the driven browser has.
13
+ * Having a driven browser is not enough: it mocks its own page, whatever session was named.
14
+ */
15
+ async function canMock(deps, session) {
16
+ if (true === deps.pool?.heldBy(session.id, deps.attachId)) {
17
+ return true;
18
+ }
19
+ const driven = mockProvider(deps);
20
+ return driven !== undefined && driven.isAvailableFor(session.url);
21
+ }
11
22
  const ruleShape = z.object({
12
23
  urlContains: z
13
24
  .string()
@@ -22,6 +33,11 @@ const ruleShape = z.object({
22
33
  .boolean()
23
34
  .optional()
24
35
  .describe('Simulate a network failure (offline) instead of a response.'),
36
+ forwardTo: z
37
+ .string()
38
+ .url()
39
+ .optional()
40
+ .describe('Send the matching request on to this origin, e.g. "http://127.0.0.1:15500"; the path and query stay and the real server answers.'),
25
41
  });
26
42
  /**
27
43
  * Narrow the validated tool args into MockRule[], omitting undefined keys (exactOptionalPropertyTypes).
@@ -41,8 +57,9 @@ const ruleShape = z.object({
41
57
  * to parse.
42
58
  */
43
59
  export function toRules(value) {
44
- if (value === undefined)
60
+ if (value === undefined) {
45
61
  return [];
62
+ }
46
63
  const parsed = z.array(ruleShape).safeParse(value);
47
64
  if (!parsed.success) {
48
65
  const problem = parsed.error.issues
@@ -50,23 +67,32 @@ export function toRules(value) {
50
67
  .join('; ');
51
68
  throw new Error(`reticle_network_mock could not read \`mocks\` (${problem}). Nothing was applied and mocking ` +
52
69
  'was left unchanged — an unreadable rule is NOT treated as a request to clear. A rule is ' +
53
- '{ urlContains, method?, status?, body?, contentType?, delayMs?, abort? }; pass `clear: true` ' +
70
+ '{ urlContains, method?, status?, body?, contentType?, delayMs?, abort?, forwardTo? }; pass `clear: true` ' +
54
71
  'or an empty array if you meant to turn mocking off.');
55
72
  }
56
73
  return parsed.data.map((r) => {
57
74
  const rule = { urlContains: r.urlContains };
58
- if (r.method !== undefined)
75
+ if (r.method !== undefined) {
59
76
  rule.method = r.method;
60
- if (r.status !== undefined)
77
+ }
78
+ if (r.status !== undefined) {
61
79
  rule.status = r.status;
62
- if (r.body !== undefined)
80
+ }
81
+ if (r.body !== undefined) {
63
82
  rule.body = r.body;
64
- if (r.contentType !== undefined)
83
+ }
84
+ if (r.contentType !== undefined) {
65
85
  rule.contentType = r.contentType;
66
- if (r.delayMs !== undefined)
86
+ }
87
+ if (r.delayMs !== undefined) {
67
88
  rule.delayMs = r.delayMs;
68
- if (r.abort !== undefined)
89
+ }
90
+ if (r.abort !== undefined) {
69
91
  rule.abort = r.abort;
92
+ }
93
+ if (r.forwardTo !== undefined) {
94
+ rule.forwardTo = r.forwardTo;
95
+ }
70
96
  return rule;
71
97
  });
72
98
  }
@@ -95,7 +121,7 @@ export const NETWORK_MOCK_TOOLS = [
95
121
  mocks: z
96
122
  .array(ruleShape)
97
123
  .optional()
98
- .describe('Interception rules, first match wins: { urlContains, method?, status?, body?, contentType?, delayMs?, abort? }. Omit or pass [] to clear.'),
124
+ .describe('Interception rules, first match wins: { urlContains, method?, status?, body?, contentType?, delayMs?, abort?, forwardTo? }. Omit or pass [] to clear.'),
99
125
  clear: z.boolean().optional().describe('Clear all active mocks (same as mocks: []).'),
100
126
  ...sessionIdShape,
101
127
  },
@@ -107,20 +133,30 @@ export const NETWORK_MOCK_TOOLS = [
107
133
  recommendation: z.string().optional(),
108
134
  },
109
135
  handler: async (deps, args) => {
110
- const session = deps.sessions.resolve(asString(args['sessionId']));
136
+ const session = await deps.sessions.resolveServing(asString(args['sessionId']), (s) => canMock(deps, s));
111
137
  const rules = true === args['clear'] ? [] : toRules(args['mocks']);
112
- const provider = mockProvider(deps);
138
+ // A lease is mocked as the lease, and the driven browser mocks only a page it has. The driven
139
+ // provider mocks its own page whatever session was named: taken first, it mocked another tab,
140
+ // answered `applied: true`, and the lease's real write reached the backend.
141
+ const driven = mockProvider(deps);
142
+ const provider = driven !== undefined &&
143
+ deps.pool?.leaseUrl(session.id) === undefined &&
144
+ (await driven.isAvailableFor(session.url))
145
+ ? driven
146
+ : undefined;
113
147
  if (provider !== undefined) {
114
148
  const applied = await provider.setMocks(session.url, rules);
115
- if (applied)
149
+ if (applied) {
116
150
  return mocksApplied(deps, session.id, rules.length);
151
+ }
117
152
  }
118
153
  // A lease is a Playwright-owned page — CDP intercept was always there, this tool just had no
119
154
  // route to it. Tried after the driven provider: when both exist, drive is the page the caller
120
155
  // means, and a lease is the fallback rather than a competitor.
121
156
  const leased = await deps.pool?.setMocksLease(session.id, rules);
122
- if (true === leased)
157
+ if (true === leased) {
123
158
  return mocksApplied(deps, session.id, rules.length);
159
+ }
124
160
  return {
125
161
  applied: false,
126
162
  count: 0,
@@ -26,6 +26,12 @@ export interface MockRule {
26
26
  delayMs?: number;
27
27
  /** Simulate a network failure (offline / connection refused) instead of a response. */
28
28
  abort?: boolean;
29
+ /**
30
+ * Send the request on to this origin (protocol, host and port; the path and query stay). An app that
31
+ * calls a fixed address (`http://localhost:5000`) can then reach its API on any port, for example when
32
+ * the host already uses that port. The real server answers.
33
+ */
34
+ forwardTo?: string;
29
35
  }
30
36
  /** What to do with a request after matching: fulfill a canned response, abort it, or let it through. */
31
37
  interface MockOutcome {
@@ -34,6 +40,8 @@ interface MockOutcome {
34
40
  body?: string;
35
41
  contentType?: string;
36
42
  delayMs?: number;
43
+ /** For `continue`: the url the request goes on with (a `forwardTo` rule). */
44
+ url?: string;
37
45
  }
38
46
  /**
39
47
  * Decide the outcome for a request from the active rules. Pure: no IO, no clock. The first rule
@@ -6,12 +6,26 @@ const DEFAULT_CONTENT_TYPE = 'application/json';
6
6
  */
7
7
  export function matchMock(rules, req) {
8
8
  for (const rule of rules) {
9
- if (!req.url.includes(rule.urlContains))
9
+ if (!req.url.includes(rule.urlContains)) {
10
10
  continue;
11
- if (rule.method !== undefined && rule.method.toUpperCase() !== req.method.toUpperCase())
11
+ }
12
+ if (rule.method !== undefined && rule.method.toUpperCase() !== req.method.toUpperCase()) {
12
13
  continue;
13
- if (true === rule.abort)
14
+ }
15
+ if (true === rule.abort) {
14
16
  return { kind: 'abort' };
17
+ }
18
+ if (rule.forwardTo !== undefined) {
19
+ const to = new URL(rule.forwardTo);
20
+ const next = new URL(req.url);
21
+ next.protocol = to.protocol;
22
+ next.host = to.host;
23
+ const outcome = { kind: 'continue', url: next.toString() };
24
+ if (rule.delayMs !== undefined) {
25
+ outcome.delayMs = rule.delayMs;
26
+ }
27
+ return outcome;
28
+ }
15
29
  /*
16
30
  * A rule that says only WHEN says nothing about WHAT.
17
31
  *
@@ -35,10 +49,12 @@ export function matchMock(rules, req) {
35
49
  status: rule.status ?? DEFAULT_STATUS,
36
50
  contentType: rule.contentType ?? DEFAULT_CONTENT_TYPE,
37
51
  };
38
- if (rule.body !== undefined)
52
+ if (rule.body !== undefined) {
39
53
  outcome.body = rule.body;
40
- if (rule.delayMs !== undefined)
54
+ }
55
+ if (rule.delayMs !== undefined) {
41
56
  outcome.delayMs = rule.delayMs;
57
+ }
42
58
  return outcome;
43
59
  }
44
60
  return { kind: 'continue' };
@@ -47,18 +63,21 @@ export function matchMock(rules, req) {
47
63
  export async function applyOutcome(route, outcome, sleep) {
48
64
  if ('continue' === outcome.kind) {
49
65
  // Sleep FIRST, then let it through: the delay is the whole perturbation, and the response that
50
- // comes back is the server's own.
51
- if (outcome.delayMs !== undefined && outcome.delayMs > 0)
66
+ // comes back is the server's own. Fallback, not continue: a lease's context route (the CSP
67
+ // widening, #2016) must still see the request.
68
+ if (outcome.delayMs !== undefined && outcome.delayMs > 0) {
52
69
  await sleep(outcome.delayMs);
53
- await route.continue();
70
+ }
71
+ await (outcome.url === undefined ? route.fallback() : route.fallback({ url: outcome.url }));
54
72
  return;
55
73
  }
56
74
  if ('abort' === outcome.kind) {
57
75
  await route.abort('failed');
58
76
  return;
59
77
  }
60
- if (outcome.delayMs !== undefined && outcome.delayMs > 0)
78
+ if (outcome.delayMs !== undefined && outcome.delayMs > 0) {
61
79
  await sleep(outcome.delayMs);
80
+ }
62
81
  await route.fulfill({
63
82
  status: outcome.status ?? DEFAULT_STATUS,
64
83
  contentType: outcome.contentType ?? DEFAULT_CONTENT_TYPE,
@@ -73,8 +92,9 @@ const realSleep = (ms) => new Promise((res) => setTimeout(res, ms));
73
92
  */
74
93
  export async function installNetworkMocks(page, rules, sleep = realSleep) {
75
94
  await page.unroute('**/*').catch(() => undefined);
76
- if (0 === rules.length)
95
+ if (0 === rules.length) {
77
96
  return;
97
+ }
78
98
  await page.route('**/*', (route) => {
79
99
  const req = route.request();
80
100
  const outcome = matchMock(rules, { url: req.url(), method: req.method() });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A screenshot of a Playwright page, shared by the CDP and launched providers so the capture path
3
+ * lives in one place.
4
+ */
5
+ import type { Page } from 'playwright';
6
+ import type { ElementBox } from './real-input.js';
7
+ /** Options for a page screenshot — full-page scroll capture and/or a clip box. */
8
+ export interface ScreenshotOpts {
9
+ fullPage?: boolean;
10
+ /** Restrict the capture to one element/region (viewport CSS px). */
11
+ clip?: ElementBox;
12
+ /**
13
+ * A live picture, as a JPEG at this quality: what a person is watching, taken as it is. The
14
+ * baseline tweaks (hiding the HUD, stopping animations) would make their window flicker each second.
15
+ */
16
+ jpegQuality?: number;
17
+ /** One image pixel per CSS pixel, so a high-density display does not double a step screenshot. */
18
+ cssScale?: boolean;
19
+ }
20
+ /**
21
+ * Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
22
+ * screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
23
+ */
24
+ export declare function capturePage(page: Page, opts: ScreenshotOpts): Promise<Uint8Array>;
@@ -0,0 +1,33 @@
1
+ import { SCREENSHOT_DETERMINISM, captureWithChromeHidden, } from '../pool/playwright-launcher.js';
2
+ /**
3
+ * Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
4
+ * screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
5
+ */
6
+ export async function capturePage(page, opts) {
7
+ // A step capture injects nothing into the page, so Reticle's own hosts are hidden another way.
8
+ if (true === opts.cssScale) {
9
+ return captureWithChromeHidden({ evaluate: (script) => page.evaluate(script) }, () => shoot(page, opts));
10
+ }
11
+ return shoot(page, opts);
12
+ }
13
+ /** Playwright screenshot options for one capture: a JPEG, a clip, the full page, or the viewport. */
14
+ function screenshotOptions(opts) {
15
+ if (opts.jpegQuality !== undefined) {
16
+ return {
17
+ type: 'jpeg',
18
+ quality: opts.jpegQuality,
19
+ ...(true === opts.cssScale ? { scale: 'css' } : {}),
20
+ };
21
+ }
22
+ if (opts.clip !== undefined) {
23
+ return { ...SCREENSHOT_DETERMINISM, clip: opts.clip };
24
+ }
25
+ if (true === opts.fullPage) {
26
+ return { ...SCREENSHOT_DETERMINISM, fullPage: true };
27
+ }
28
+ return { ...SCREENSHOT_DETERMINISM };
29
+ }
30
+ async function shoot(page, opts) {
31
+ const buf = await page.screenshot(screenshotOptions(opts));
32
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
33
+ }
@@ -12,7 +12,7 @@
12
12
  import { type ScriptCoverage } from './js-coverage.js';
13
13
  import type { Browser, Page } from 'playwright';
14
14
  import { DriveError } from './drive-error.js';
15
- import { ActionType, InputMode } from '@reticlehq/core';
15
+ import { ActionType, InputMode, type BoxFraction } from '@reticlehq/core';
16
16
  import { type MockRule } from './network-mock.js';
17
17
  import { type NetworkDetail } from './network-detail.js';
18
18
  /** Viewport CSS-px box as returned by the INSPECT command (getBoundingClientRect). */
@@ -49,8 +49,12 @@ export type RealInputArgs = {
49
49
  * for exactly as long as one driven in the page.
50
50
  */
51
51
  holdMs?: number;
52
- /** For drag: the resolved box of the drop-target ref (toRef). */
52
+ /** For drag: the resolved box of the drop-target ref (toRef), or the source box for `to` alone. */
53
53
  toBox?: ElementBox;
54
+ /** For a pointer action: the point inside the box, as fractions of it. Absent means the centre. */
55
+ at?: BoxFraction;
56
+ /** For drag: the end point inside `toBox`, as fractions of it. Absent means its centre. */
57
+ to?: BoxFraction;
54
58
  steps?: number;
55
59
  };
56
60
  /** What a provider reports for one driven gesture. Exported so a fake can build it by name. */
@@ -80,19 +84,8 @@ export interface RealInputResult {
80
84
  */
81
85
  heldMs?: number;
82
86
  }
83
- /** Options for a page screenshot — full-page scroll capture and/or a clip box. */
84
- export interface ScreenshotOpts {
85
- fullPage?: boolean;
86
- /** Restrict the capture to one element/region (viewport CSS px). */
87
- clip?: ElementBox;
88
- /**
89
- * A live picture, as a JPEG at this quality: what a person is watching, taken as it is. The
90
- * baseline tweaks (hiding the HUD, stopping animations) would make their window flicker each second.
91
- */
92
- jpegQuality?: number;
93
- /** One image pixel per CSS pixel, so a high-density display does not double a step screenshot. */
94
- cssScale?: boolean;
95
- }
87
+ export { capturePage, type ScreenshotOpts } from './page-capture.js';
88
+ import { type ScreenshotOpts } from './page-capture.js';
96
89
  /** The capability surface reticle_act depends on. A FAKE implementing this is injected in tests. */
97
90
  export interface RealInputProvider {
98
91
  /** Whether a Playwright Page currently matches this SDK session URL. */
@@ -120,6 +113,14 @@ export interface RealInputProvider {
120
113
  */
121
114
  storageState?(sessionUrl: string): Promise<unknown>;
122
115
  applyStorageState?(sessionUrl: string, state: unknown): Promise<boolean>;
116
+ /**
117
+ * Close every page of the driven context except the one this provider drives, and say how many.
118
+ *
119
+ * A flow that opens another tab (a modifier-click on a link) leaves it behind; its page connects
120
+ * as a second session, and everything after that asks for "the" session and is refused. Optional:
121
+ * only a provider that owns its context can know which pages are its own.
122
+ */
123
+ closeOtherPages?(): Promise<number>;
123
124
  /**
124
125
  * Install (or replace, or with [] clear) network-mock rules on the correlated page — stub a 500,
125
126
  * force offline, delay a response — for deterministic error/edge-state testing. Returns true when
@@ -162,6 +163,11 @@ export declare function boxCenter(box: ElementBox): {
162
163
  cx: number;
163
164
  cy: number;
164
165
  };
166
+ /** The point `at` names inside a box, or its centre when `at` is absent. */
167
+ export declare function boxPoint(box: ElementBox, at: BoxFraction | undefined): {
168
+ cx: number;
169
+ cy: number;
170
+ };
165
171
  /**
166
172
  * Which actions are driven by a native pointer. `press` is excluded on purpose — a key has no
167
173
  * pointer, and widening this would change what every existing caller of it means.
@@ -182,10 +188,15 @@ export declare function performGesture(page: Page, action: ActionType, box: Elem
182
188
  /** Injected so a test can assert the achieved hold deterministically; defaults to the real clock. */
183
189
  now?: () => number): Promise<RealInputResult>;
184
190
  /**
185
- * Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
186
- * screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
191
+ * Run a real pointer gesture with Reticle's own UI out of the hit test, and put it back after.
192
+ *
193
+ * A real pointer goes to the topmost element under it. When the HUD sat over the app's control, a
194
+ * driven click went to the HUD and the verdict said the app did nothing; a drag ended short (#992).
195
+ * The restore runs whatever the gesture did, so the HUD does not stay unclickable for a person.
187
196
  */
188
- export declare function capturePage(page: Page, opts: ScreenshotOpts): Promise<Uint8Array>;
197
+ export declare function withChromePassThrough<T>(page: {
198
+ evaluate: (script: string) => Promise<unknown>;
199
+ }, gesture: () => Promise<T>): Promise<T>;
189
200
  interface CdpProviderOptions {
190
201
  cdpUrl: string;
191
202
  /** Injected so the settle delay is deterministic in tests; defaults to a real Node timer. */
@@ -262,6 +273,11 @@ export interface LaunchedProviderOptions {
262
273
  * inside-app view never loses fidelity to the outside-in view. Omitted → no network detail captured.
263
274
  */
264
275
  onNetworkDetail?: (detail: NetworkDetail) => void;
276
+ /**
277
+ * Reticle's own reader for a page with no SDK, as the lease pool gives one (see zero-install.ts).
278
+ * Undefined, or a loader that answers undefined, leaves such a page with no session.
279
+ */
280
+ zeroInstallScript?: () => string | undefined;
265
281
  }
266
282
  /** The only place the dynamic value import of Playwright lives for the launched (drive) path. */
267
283
  export declare const launchedChromium: LaunchFn;
@@ -282,6 +298,7 @@ export declare class LaunchedRealInputProvider implements OwnedRealInputProvider
282
298
  * driven session had no fixture port at all, and every suite seeded nothing while reading as if it
283
299
  * seeded everything. Same two methods, same contract, one page instead of a page lookup.
284
300
  */
301
+ closeOtherPages(): Promise<number>;
285
302
  storageState(_sessionUrl: string): Promise<unknown>;
286
303
  /**
287
304
  * Put a captured state back: cookies through the context, per-origin storage through the page.
@@ -14,20 +14,34 @@ import { DriveError } from './drive-error.js';
14
14
  import { pressViaKeyboard } from './real-input-press.js';
15
15
  import { stampedDriveUrl } from './drive-url-stamp.js';
16
16
  import { launchChromium } from '../../launch-chromium.js';
17
- import { chromiumLaunchHint, gotoOptions, SCREENSHOT_DETERMINISM, captureWithChromeHidden, } from '../pool/playwright-launcher.js';
17
+ import { pageViewportOptions } from '../../chromium-launch-options.js';
18
+ import { chromiumLaunchHint, gotoOptions } from '../pool/playwright-launcher.js';
18
19
  import { BrowserLaunchKind } from '@reticlehq/core/telemetry';
19
20
  import { getSessionMetrics } from '../../telemetry/session-metrics.js';
20
21
  import { classifyConnectFailure } from '../../telemetry/connect-failure.js';
21
- import { ActionType, DriveErrorCode, DRIVE_PLAYWRIGHT_MISSING_MSG, ClickButton, clickButtonFromArgs, InputMode, pressModifiersFromArgs, } from '@reticlehq/core';
22
+ import { ActionType, DriveErrorCode, DRIVE_PLAYWRIGHT_MISSING_MSG, ClickButton, clickButtonFromArgs, InputMode, pointInBox, pressModifiersFromArgs, RETICLE_CHROME_PASS_THROUGH_SCRIPT, RETICLE_CHROME_RESTORE_POINTER_SCRIPT, } from '@reticlehq/core';
22
23
  import { installNetworkMocks } from './network-mock.js';
23
24
  import { attachNetworkDetail } from './network-detail.js';
25
+ import { attachDownloadCapture } from './download-capture.js';
24
26
  import { injectedConnectArgs } from '../pool/zero-install.js';
27
+ import { EARLY_CAPTURE_ARG, queueEarlyConsole } from '../pool/lease-page-error.js';
28
+ // The screenshot capture lives in page-capture.ts; re-exported for existing importers.
29
+ export { capturePage } from './page-capture.js';
30
+ import { capturePage } from './page-capture.js';
25
31
  export { DriveError };
26
32
  export { unspellablePressReason } from './real-input-press.js';
27
33
  /** Center of a viewport box in CSS px. Pure — unit-tested directly. */
28
34
  export function boxCenter(box) {
29
35
  return { cx: box.x + box.width / 2, cy: box.y + box.height / 2 };
30
36
  }
37
+ /** The point `at` names inside a box, or its centre when `at` is absent. */
38
+ export function boxPoint(box, at) {
39
+ if (at === undefined) {
40
+ return boxCenter(box);
41
+ }
42
+ const p = pointInBox(box, at);
43
+ return { cx: p.x, cy: p.y };
44
+ }
31
45
  /**
32
46
  * Which actions are driven by a native pointer. `press` is excluded on purpose — a key has no
33
47
  * pointer, and widening this would change what every existing caller of it means.
@@ -85,7 +99,28 @@ now = Date.now) {
85
99
  ...(heldMs > 0 ? { heldMs } : {}),
86
100
  };
87
101
  }
88
- const center = boxCenter(box);
102
+ return withChromePassThrough(page, () => pointerGesture(page, action, box, args, sleep));
103
+ }
104
+ /**
105
+ * Run a real pointer gesture with Reticle's own UI out of the hit test, and put it back after.
106
+ *
107
+ * A real pointer goes to the topmost element under it. When the HUD sat over the app's control, a
108
+ * driven click went to the HUD and the verdict said the app did nothing; a drag ended short (#992).
109
+ * The restore runs whatever the gesture did, so the HUD does not stay unclickable for a person.
110
+ */
111
+ export async function withChromePassThrough(page, gesture) {
112
+ await page.evaluate(RETICLE_CHROME_PASS_THROUGH_SCRIPT);
113
+ try {
114
+ return await gesture();
115
+ }
116
+ finally {
117
+ // A click that navigated takes the old page and its held values with it.
118
+ await page.evaluate(RETICLE_CHROME_RESTORE_POINTER_SCRIPT).catch(() => undefined);
119
+ }
120
+ }
121
+ /** The pointer half of `performGesture`: every action that lands at a point. */
122
+ async function pointerGesture(page, action, box, args, sleep) {
123
+ const center = boxPoint(box, args.at);
89
124
  const { cx, cy } = center;
90
125
  const real = (performed) => ({
91
126
  performed,
@@ -125,7 +160,7 @@ now = Date.now) {
125
160
  if (args.toBox === undefined) {
126
161
  return real(false);
127
162
  }
128
- const dst = boxCenter(args.toBox);
163
+ const dst = boxPoint(args.toBox, args.to);
129
164
  const steps = args.steps ?? DEFAULT_DRAG_STEPS;
130
165
  await page.mouse.move(cx, cy);
131
166
  await page.mouse.down();
@@ -144,31 +179,6 @@ now = Date.now) {
144
179
  }
145
180
  return real(false);
146
181
  }
147
- /**
148
- * Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
149
- * screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
150
- */
151
- export async function capturePage(page, opts) {
152
- // A step capture injects nothing into the page, so Reticle's own hosts are hidden another way.
153
- if (true === opts.cssScale) {
154
- return captureWithChromeHidden({ evaluate: (script) => page.evaluate(script) }, () => shoot(page, opts));
155
- }
156
- return shoot(page, opts);
157
- }
158
- async function shoot(page, opts) {
159
- const buf = await page.screenshot(opts.jpegQuality !== undefined
160
- ? {
161
- type: 'jpeg',
162
- quality: opts.jpegQuality,
163
- ...(true === opts.cssScale ? { scale: 'css' } : {}),
164
- }
165
- : opts.clip !== undefined
166
- ? { ...SCREENSHOT_DETERMINISM, clip: opts.clip }
167
- : true === opts.fullPage
168
- ? { ...SCREENSHOT_DETERMINISM, fullPage: true }
169
- : { ...SCREENSHOT_DETERMINISM });
170
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
171
- }
172
182
  const nodeSleep = (ms) => new Promise((resolve) => {
173
183
  setTimeout(resolve, ms);
174
184
  });
@@ -352,6 +362,8 @@ export class CdpRealInputProvider {
352
362
  }
353
363
  }
354
364
  const INJECT_CONNECT_WAIT_MS = 8_000;
365
+ /** True on a page whose Reticle SDK has loaded. */
366
+ const SDK_PRESENT = '!!globalThis.__reticleInstance';
355
367
  /** The only place the dynamic value import of Playwright lives for the launched (drive) path. */
356
368
  export const launchedChromium = async (headless) => {
357
369
  let mod;
@@ -386,6 +398,7 @@ export class LaunchedRealInputProvider {
386
398
  #injectConnect;
387
399
  #storageState;
388
400
  #onNetworkDetail;
401
+ #zeroInstallScript;
389
402
  #browser;
390
403
  #page;
391
404
  /** Pages collecting V8 coverage — see js-coverage.ts. */
@@ -399,14 +412,28 @@ export class LaunchedRealInputProvider {
399
412
  this.#injectConnect = options.injectConnect;
400
413
  this.#storageState = options.storageState;
401
414
  this.#onNetworkDetail = options.onNetworkDetail;
415
+ this.#zeroInstallScript = options.zeroInstallScript;
402
416
  }
403
417
  async navigate() {
404
418
  this.#browser = await this.#launch(this.#headless);
405
- const page = await this.#browser.newPage(this.#storageState !== undefined ? { storageState: this.#storageState } : undefined);
419
+ const page = await this.#browser.newPage({
420
+ ...pageViewportOptions(this.#headless),
421
+ ...(this.#storageState !== undefined ? { storageState: this.#storageState } : {}),
422
+ });
406
423
  this.#page = page;
424
+ // Before goto: a console error the page logs while it loads comes before its SDK connects.
425
+ await page.addInitScript(queueEarlyConsole, EARLY_CAPTURE_ARG);
407
426
  // Capture CDP-authoritative response detail into the driven session's journal (best-effort).
408
- if (this.#onNetworkDetail !== undefined) {
409
- attachNetworkDetail(page, this.#onNetworkDetail);
427
+ const sink = this.#onNetworkDetail;
428
+ if (sink !== undefined) {
429
+ attachNetworkDetail(page, sink);
430
+ // A server-sent download never reaches the page's network events; CDP request interception
431
+ // does see it (download-capture.ts). Attached before the first navigation, best-effort.
432
+ await page
433
+ .context()
434
+ .newCDPSession(page)
435
+ .then((cdp) => attachDownloadCapture(cdp, sink, () => page.url()))
436
+ .catch(() => undefined);
410
437
  }
411
438
  try {
412
439
  // Same navigation rule as the pool, and for the same measured reason: Playwright's default
@@ -420,8 +447,35 @@ export class LaunchedRealInputProvider {
420
447
  catch (e) {
421
448
  throw new DriveError(DriveErrorCode.NAVIGATE_FAILED, e instanceof Error ? e.message : String(e));
422
449
  }
450
+ await this.#supplyReader(page);
423
451
  await this.#tryInjectConnect(page);
424
452
  }
453
+ /**
454
+ * Give a page with no Reticle SDK the zero-install reader, so `verify` on an app with no `init`
455
+ * still gets a session. The wait for the page's own SDK comes first, as on a leased tab, and the
456
+ * script checks the singleton, so a page with an SDK never gets a second one.
457
+ */
458
+ async #supplyReader(page) {
459
+ const script = this.#zeroInstallScript?.();
460
+ if (script === undefined) {
461
+ return;
462
+ }
463
+ try {
464
+ await page.waitForFunction(SDK_PRESENT, { timeout: INJECT_CONNECT_WAIT_MS });
465
+ return;
466
+ }
467
+ catch {
468
+ // No SDK on the page: supply the reader below.
469
+ }
470
+ try {
471
+ // Also on every later document, so a full navigation keeps the session.
472
+ await page.addInitScript(script);
473
+ await page.evaluate(script);
474
+ }
475
+ catch {
476
+ // A page that refuses the reader has no session. verify's no-session guard reports it.
477
+ }
478
+ }
425
479
  /**
426
480
  * Wait for the page's Reticle singleton to exist, then re-invoke connect with our token + loopback
427
481
  * URL so a hosted (non-localhost) preview pairs to our bridge without the app being reconfigured.
@@ -433,7 +487,7 @@ export class LaunchedRealInputProvider {
433
487
  return;
434
488
  }
435
489
  try {
436
- await page.waitForFunction('!!globalThis.__reticleInstance', {
490
+ await page.waitForFunction(SDK_PRESENT, {
437
491
  timeout: INJECT_CONNECT_WAIT_MS,
438
492
  });
439
493
  const arg = JSON.stringify(injectedConnectArgs(opts));
@@ -482,6 +536,18 @@ export class LaunchedRealInputProvider {
482
536
  * driven session had no fixture port at all, and every suite seeded nothing while reading as if it
483
537
  * seeded everything. Same two methods, same contract, one page instead of a page lookup.
484
538
  */
539
+ async closeOtherPages() {
540
+ const page = this.#livePage();
541
+ if (page === undefined) {
542
+ return 0;
543
+ }
544
+ const others = page
545
+ .context()
546
+ .pages()
547
+ .filter((other) => other !== page && !other.isClosed());
548
+ await Promise.all(others.map((other) => other.close()));
549
+ return others.length;
550
+ }
485
551
  async storageState(_sessionUrl) {
486
552
  const page = this.#livePage();
487
553
  if (page === undefined) {