@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,33 @@
1
+ import { VisualReason } from '@reticlehq/core';
2
+ import type { ToolDeps } from '../../surface/tools/tool-kit.js';
3
+ /**
4
+ * Ask the desktop shell to photograph its own window.
5
+ *
6
+ * Returns undefined for a plain web page (no capture helper) and for a desktop app that did not
7
+ * install `@reticlehq/electron/main`. Deliberately NOT a screen-region capture: photographing
8
+ * the glass returns whatever window is on top, which would save a picture of the user's editor as a
9
+ * visual baseline — the exact false green Reticle exists to eliminate.
10
+ */
11
+ export declare function desktopCapture(deps: ToolDeps, sessionId: string | undefined, fullPage: boolean): Promise<{
12
+ png?: Uint8Array;
13
+ reason?: VisualReason;
14
+ recommendation?: string;
15
+ }>;
16
+ /**
17
+ * Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
18
+ * become a file-read oracle for whatever a compromised renderer names.
19
+ *
20
+ * Two layouts are accepted, and the pair is the whole subtlety:
21
+ *
22
+ * - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
23
+ * so no other local user can read the screenshot or pre-place a symlink at the path we write.
24
+ * - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
25
+ * SEPARATE package on the user's own upgrade schedule: an app still on the older
26
+ * `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
27
+ * start returning no image. It grants the daemon nothing it did not already have.
28
+ *
29
+ * Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
30
+ * `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
31
+ * `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
32
+ */
33
+ export declare function isCapturePath(path: string): boolean;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The desktop shell's photograph of its own window, read from the file the shell wrote.
3
+ *
4
+ * Shared by `reticle_screenshot`, the drive frames and the step shots. The step shots use it only
5
+ * for a page SDK before 3.7, which has no `render` command and declares `capture` instead.
6
+ */
7
+ import { readFile, unlink } from 'node:fs/promises';
8
+ import { tmpdir } from 'node:os';
9
+ import { basename, dirname } from 'node:path';
10
+ import { asRecord, asString, ReticleCommand, RETICLE_CAPTURE_FILE_PREFIX, RETICLE_TAURI_INERT_MARKER, TAURI_INERT_RECOMMENDATION, VisualReason, } from '@reticlehq/core';
11
+ import { trackCaptureDirectory } from './capture-cleanup.js';
12
+ /**
13
+ * Ask the desktop shell to photograph its own window.
14
+ *
15
+ * Returns undefined for a plain web page (no capture helper) and for a desktop app that did not
16
+ * install `@reticlehq/electron/main`. Deliberately NOT a screen-region capture: photographing
17
+ * the glass returns whatever window is on top, which would save a picture of the user's editor as a
18
+ * visual baseline — the exact false green Reticle exists to eliminate.
19
+ */
20
+ export async function desktopCapture(deps, sessionId, fullPage) {
21
+ const session = deps.sessions.resolve(sessionId);
22
+ // A session that cannot take commands (no live browser behind it) simply has no pixels to give.
23
+ if (typeof session.command !== 'function') {
24
+ return {};
25
+ }
26
+ const res = await session.command(ReticleCommand.CAPTURE, { fullPage });
27
+ if (!res.ok) {
28
+ return {};
29
+ }
30
+ const r = asRecord(res.result);
31
+ if (r['ok'] !== true) {
32
+ // The shell refused a full-page capture it cannot do. That is a different answer from "capture
33
+ // broke", and the caller has to hear it — otherwise they read `fullPage` as honoured.
34
+ if (r['reason'] === VisualReason.FULL_PAGE_UNSUPPORTED) {
35
+ return { reason: VisualReason.FULL_PAGE_UNSUPPORTED };
36
+ }
37
+ // A Tauri release build with the capture compiled out: say that, never the web answer.
38
+ return true === asString(r['reason'])?.includes(RETICLE_TAURI_INERT_MARKER)
39
+ ? { reason: VisualReason.NO_PROVIDER, recommendation: TAURI_INERT_RECOMMENDATION }
40
+ : {};
41
+ }
42
+ const path = asString(r['path']);
43
+ if (path === undefined || !isCapturePath(path)) {
44
+ return {};
45
+ }
46
+ try {
47
+ const bytes = await readFile(path);
48
+ await unlink(path).catch(() => undefined);
49
+ trackCaptureDirectory(path);
50
+ return isCompletePng(bytes) ? { png: new Uint8Array(bytes) } : {};
51
+ }
52
+ catch {
53
+ return {};
54
+ }
55
+ }
56
+ /**
57
+ * Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
58
+ * become a file-read oracle for whatever a compromised renderer names.
59
+ *
60
+ * Two layouts are accepted, and the pair is the whole subtlety:
61
+ *
62
+ * - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
63
+ * so no other local user can read the screenshot or pre-place a symlink at the path we write.
64
+ * - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
65
+ * SEPARATE package on the user's own upgrade schedule: an app still on the older
66
+ * `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
67
+ * start returning no image. It grants the daemon nothing it did not already have.
68
+ *
69
+ * Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
70
+ * `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
71
+ * `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
72
+ */
73
+ export function isCapturePath(path) {
74
+ if (!basename(path).startsWith(RETICLE_CAPTURE_FILE_PREFIX)) {
75
+ return false;
76
+ }
77
+ const parent = dirname(path);
78
+ if (parent === tmpdir()) {
79
+ return true;
80
+ }
81
+ return dirname(parent) === tmpdir() && basename(parent).startsWith(RETICLE_CAPTURE_FILE_PREFIX);
82
+ }
83
+ /**
84
+ * A PNG is complete only if it ends with IEND. This check exists because a truncated image once
85
+ * sailed through as a saved baseline: the tool answered `saved: true` with a path, and the file was
86
+ * undecodable. A screenshot that cannot be read must fail loudly, never bank a corrupt baseline that
87
+ * a later visual_diff will compare against.
88
+ */
89
+ function isCompletePng(bytes) {
90
+ if (bytes.byteLength < 12) {
91
+ return false;
92
+ }
93
+ return 'IEND' === bytes.subarray(bytes.byteLength - 8, bytes.byteLength - 4).toString('ascii');
94
+ }
@@ -14,9 +14,10 @@
14
14
  * nothing at all, the next one is awaited, so the agent is told `{ error }` instead of a path to a
15
15
  * file that will never exist.
16
16
  *
17
- * ponytail: the desktop shell's own window capture is not a source here. It returns a device-pixel
18
- * PNG, and the daemon ships no JPEG encoder to shrink it to the step format; an Electron renderer
19
- * draws itself fine in-page. Add it when the shells can hand back a JPEG.
17
+ * A page SDK before 3.7 has no `render`. When such a page declares the desktop shell's `capture`,
18
+ * that command is asked instead. Its answer is a device-pixel PNG, and the daemon ships no JPEG
19
+ * encoder, so that picture is kept as a `.png` and stays on this machine (the platform takes only
20
+ * JPEG). A page that declares neither command is not asked: the result says to update its SDK.
20
21
  */
21
22
  import { createHash } from 'node:crypto';
22
23
  import { join } from 'node:path';
@@ -25,7 +26,12 @@ import { runIdOfCall } from '../../judgement/runs/drive-run.js';
25
26
  import { noteLanding } from '../../judgement/runs/artifact/run-store.js';
26
27
  import { isValidRunId, stepShotDir } from '../../memory/project/dir/reticle-dir.js';
27
28
  import { UPLOAD_TEXT_MAX } from '@reticlehq/core/artifacts';
29
+ import { sdkBehindNote } from '../../command/version/version-skew.js';
30
+ import { desktopCapture } from './desktop-capture.js';
28
31
  const JPEG_EXT = '.jpg';
32
+ const PNG_EXT = '.png';
33
+ /** Why a page that declared neither `render` nor `capture` was not asked for a picture. */
34
+ const NO_PAGE_CAPTURE = 'this page declared no command that draws it (neither "render" nor "capture"), so nothing was sent';
29
35
  const TOOL_PREFIX = /^reticle_/;
30
36
  const SEQ_WIDTH = 4;
31
37
  /** A prior capture on this tab produced nothing — see the header. */
@@ -34,19 +40,58 @@ const NOTHING = 'none';
34
40
  const CDP_QUALITY = Math.round(STEP_SCREENSHOT.JPEG_QUALITY * 100);
35
41
  /** What worked last on each tab: the next result names that source. */
36
42
  const lastSource = new Map();
43
+ /**
44
+ * The file type of the last picture of each tab: the next path is named with it.
45
+ *
46
+ * ponytail: a tab that changes type between two steps gets one predicted path with the old type;
47
+ * the run record names the real file. Wait for the capture if that ever matters.
48
+ */
49
+ const lastExt = new Map();
37
50
  /** The next sequence number per run directory, read from its record the first time. */
38
51
  const nextSeq = new Map();
52
+ /** The PNG signature's first four bytes, and where the IHDR chunk keeps the width and height. */
53
+ const PNG_MAGIC = [0x89, 0x50, 0x4e, 0x47];
54
+ const PNG_WIDTH_AT = 16;
55
+ const PNG_HEIGHT_AT = 20;
56
+ /** Width and height from a PNG's header, or undefined when the bytes are not a PNG. */
57
+ function pngSize(bytes) {
58
+ if (bytes.length < PNG_HEIGHT_AT + 4 || PNG_MAGIC.some((b, i) => b !== bytes[i])) {
59
+ return undefined;
60
+ }
61
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
62
+ const width = view.getUint32(PNG_WIDTH_AT);
63
+ const height = view.getUint32(PNG_HEIGHT_AT);
64
+ return width > 0 && height > 0 ? { width, height } : undefined;
65
+ }
66
+ /**
67
+ * The command that asks this page for its own picture, or undefined when it declared none.
68
+ *
69
+ * `render` came in SDK 3.7. An older SDK declares only the desktop shell's `capture`. A page that
70
+ * declared nothing is too old to say, so it is asked for `render` as before.
71
+ */
72
+ function pageCaptureCommand(session) {
73
+ const declared = session.commands;
74
+ if (declared === undefined || declared.includes(ReticleCommand.RENDER)) {
75
+ return ReticleCommand.RENDER;
76
+ }
77
+ if (declared.includes(ReticleCommand.CAPTURE)) {
78
+ return ReticleCommand.CAPTURE;
79
+ }
80
+ return undefined;
81
+ }
39
82
  /**
40
83
  * Width and height from a JPEG's frame header, or undefined when the bytes are not a JPEG.
41
84
  * Read rather than trusted: the run record and the upload both state the size.
42
85
  */
43
86
  export function jpegSize(bytes) {
44
- if (bytes.length < 4 || 0xff !== bytes[0] || 0xd8 !== bytes[1])
87
+ if (bytes.length < 4 || 0xff !== bytes[0] || 0xd8 !== bytes[1]) {
45
88
  return undefined;
89
+ }
46
90
  let i = 2;
47
91
  while (i + 9 < bytes.length) {
48
- if (0xff !== bytes[i])
92
+ if (0xff !== bytes[i]) {
49
93
  return undefined;
94
+ }
50
95
  const marker = bytes[i + 1] ?? 0;
51
96
  const length = ((bytes[i + 2] ?? 0) << 8) | (bytes[i + 3] ?? 0);
52
97
  // The start-of-frame markers carry the size; 0xC4, 0xC8 and 0xCC share the range and are not frames.
@@ -63,22 +108,32 @@ export function jpegSize(bytes) {
63
108
  async function browserPixels(deps, session) {
64
109
  const opts = { jpegQuality: CDP_QUALITY, cssScale: true };
65
110
  const leased = await deps.pool?.screenshotLease(session.id, opts);
66
- if (leased !== undefined)
111
+ if (leased !== undefined) {
67
112
  return leased;
113
+ }
114
+ // A lease capture that failed still has a reason. Leaving it on the floor made the only text
115
+ // the page's "I cannot draw", which says nothing about the browser that was asked first.
116
+ const failed = deps.pool?.leaseCaptureError(session.id);
117
+ if (failed !== undefined) {
118
+ throw new Error(failed);
119
+ }
68
120
  // Only a provider driving THIS tab: a launched provider photographs the one page it owns whatever
69
121
  // url it is handed, which is how a screenshot of one tab was once filed under another (#1407).
70
- if (true !== (await deps.realInput?.isAvailableFor(session.url)))
122
+ if (true !== (await deps.realInput?.isAvailableFor(session.url))) {
71
123
  return undefined;
124
+ }
72
125
  return deps.realInput?.screenshot?.(session.url, opts);
73
126
  }
74
127
  /** The page drawn by the SDK. The answer is base64 in chunks — see `ReticleCommand.RENDER`. */
75
128
  async function renderedPixels(session) {
76
129
  const res = await session.command(ReticleCommand.RENDER, {});
77
- if (!res.ok)
130
+ if (!res.ok) {
78
131
  return res.error ?? 'the page did not answer';
132
+ }
79
133
  const r = asRecord(res.result);
80
- if (true !== r['ok'])
134
+ if (true !== r['ok']) {
81
135
  return 'string' === typeof r['reason'] ? r['reason'] : 'render failed';
136
+ }
82
137
  const chunks = Array.isArray(r['chunks']) ? r['chunks'] : [];
83
138
  return new Uint8Array(Buffer.from(chunks.filter((c) => 'string' === typeof c).join(''), 'base64'));
84
139
  }
@@ -88,20 +143,47 @@ async function capture(deps, session) {
88
143
  try {
89
144
  const real = await browserPixels(deps, session);
90
145
  const size = real === undefined ? undefined : jpegSize(real);
91
- if (real !== undefined && size !== undefined)
92
- return { bytes: real, source: ScreenshotSource.BROWSER, ...size };
146
+ if (real !== undefined && size !== undefined) {
147
+ return { bytes: real, source: ScreenshotSource.BROWSER, ext: JPEG_EXT, ...size };
148
+ }
93
149
  }
94
150
  catch (error) {
95
151
  reasons.push(error instanceof Error ? error.message : String(error));
96
152
  }
153
+ const command = pageCaptureCommand(session);
154
+ if (command === undefined) {
155
+ // Nothing is sent: the page said it has no command for this.
156
+ reasons.push(NO_PAGE_CAPTURE);
157
+ }
158
+ if (command !== ReticleCommand.RENDER) {
159
+ if (command === ReticleCommand.CAPTURE) {
160
+ const png = (await desktopCapture(deps, session.id, false).catch(() => undefined))?.png;
161
+ const size = png === undefined ? undefined : pngSize(png);
162
+ if (png !== undefined && size !== undefined) {
163
+ return { bytes: png, source: ScreenshotSource.BROWSER, ext: PNG_EXT, ...size };
164
+ }
165
+ reasons.push('the desktop shell did not capture its window');
166
+ }
167
+ const behind = sdkBehindNote(session.sdkVersion);
168
+ if (behind !== undefined) {
169
+ reasons.push(behind);
170
+ }
171
+ return `no screenshot: ${reasons.join('; ')}`;
172
+ }
97
173
  try {
98
174
  const drawn = await renderedPixels(session);
99
- if ('string' === typeof drawn)
175
+ if ('string' === typeof drawn) {
100
176
  reasons.push(`the page could not draw itself: ${drawn}`);
177
+ const behind = sdkBehindNote(session.sdkVersion);
178
+ if (behind !== undefined) {
179
+ reasons.push(behind);
180
+ }
181
+ }
101
182
  else {
102
183
  const size = jpegSize(drawn);
103
- if (size !== undefined)
104
- return { bytes: drawn, source: ScreenshotSource.RENDERED, ...size };
184
+ if (size !== undefined) {
185
+ return { bytes: drawn, source: ScreenshotSource.RENDERED, ext: JPEG_EXT, ...size };
186
+ }
105
187
  reasons.push('the page drew something that is not a JPEG');
106
188
  }
107
189
  }
@@ -113,12 +195,15 @@ async function capture(deps, session) {
113
195
  /** Which source this tab's next result names, or undefined when the last capture got nothing. */
114
196
  async function predict(deps, session) {
115
197
  const last = lastSource.get(session.id);
116
- if (NOTHING === last)
198
+ if (NOTHING === last) {
117
199
  return undefined;
118
- if (last !== undefined)
200
+ }
201
+ if (last !== undefined) {
119
202
  return last;
120
- if (true === deps.pool?.leasedSessionIds().includes(session.id))
203
+ }
204
+ if (true === deps.pool?.leasedSessionIds().includes(session.id)) {
121
205
  return ScreenshotSource.BROWSER;
206
+ }
122
207
  const driven = deps.realInput?.screenshot !== undefined &&
123
208
  true === (await deps.realInput.isAvailableFor(session.url));
124
209
  return driven ? ScreenshotSource.BROWSER : ScreenshotSource.RENDERED;
@@ -168,8 +253,9 @@ async function shoot(deps, session, tool, { step, verdict }) {
168
253
  // Read before anything is awaited: the action this call's verdict was recorded on.
169
254
  const actionId = true === verdict ? session.lastActionId : undefined;
170
255
  const runId = runIdOfCall(session.id);
171
- if (!isValidRunId(runId))
256
+ if (!isValidRunId(runId)) {
172
257
  return { error: `no screenshot: unusable run id ${runId}` };
258
+ }
173
259
  const dir = stepShotDir(root, runId);
174
260
  void filmTab(deps, session, dir);
175
261
  const seq = await allocate(deps.fs, dir);
@@ -185,14 +271,17 @@ async function shoot(deps, session, tool, { step, verdict }) {
185
271
  // Started before anything else is awaited: the picture is of THIS moment.
186
272
  const taken = capture(deps, session).then((pixels) => fresh && 'string' === typeof pixels ? capture(deps, session) : pixels);
187
273
  const predicted = fresh ? undefined : await predict(deps, session);
188
- const file = `${String(seq).padStart(SEQ_WIDTH, '0')}-${tool.replace(TOOL_PREFIX, '')}${JPEG_EXT}`;
189
- const path = join(dir, file);
274
+ const name = (ext) => `${String(seq).padStart(SEQ_WIDTH, '0')}-${tool.replace(TOOL_PREFIX, '')}${ext}`;
275
+ const predictedPath = join(dir, name(lastExt.get(session.id) ?? JPEG_EXT));
190
276
  const land = taken.then(async (pixels) => {
191
277
  if ('string' === typeof pixels) {
192
278
  lastSource.set(session.id, NOTHING);
193
279
  return;
194
280
  }
195
281
  lastSource.set(session.id, pixels.source);
282
+ lastExt.set(session.id, pixels.ext);
283
+ const file = name(pixels.ext);
284
+ const path = join(dir, file);
196
285
  try {
197
286
  await deps.fs.mkdir(dir);
198
287
  await deps.fs.writeFileBytes(path, pixels.bytes);
@@ -215,12 +304,15 @@ async function shoot(deps, session, tool, { step, verdict }) {
215
304
  }
216
305
  });
217
306
  noteLanding(dir, land);
218
- if (predicted !== undefined)
219
- return { path, source: predicted, pending: true };
307
+ if (predicted !== undefined) {
308
+ return { path: predictedPath, source: predicted, pending: true };
309
+ }
220
310
  // Nothing worked last time: wait, and tell the truth either way.
221
311
  const pixels = await taken;
222
312
  await land;
223
- return 'string' === typeof pixels ? { error: pixels } : { path, source: pixels.source };
313
+ return 'string' === typeof pixels
314
+ ? { error: pixels }
315
+ : { path: join(dir, name(pixels.ext)), source: pixels.source };
224
316
  }
225
317
  /** The replay hook that gives every reported flow step its screenshot, filed as `<flow>#<step>`. */
226
318
  export function replayStepShooter(deps, sessionId) {
@@ -267,8 +359,9 @@ const films = new Map();
267
359
  const framesIn = new Map();
268
360
  /** Is this a tab Reticle drives, with a real browser behind it? */
269
361
  async function driven(deps, session) {
270
- if (true === deps.pool?.leasedSessionIds().includes(session.id))
362
+ if (true === deps.pool?.leasedSessionIds().includes(session.id)) {
271
363
  return true;
364
+ }
272
365
  return (deps.realInput?.screenshot !== undefined &&
273
366
  true === (await deps.realInput.isAvailableFor(session.url)));
274
367
  }
@@ -281,10 +374,12 @@ async function filmTab(deps, session, dir) {
281
374
  film.still = 1;
282
375
  return;
283
376
  }
284
- if (!(await driven(deps, session).catch(() => false)))
377
+ if (!(await driven(deps, session).catch(() => false))) {
285
378
  return;
286
- if (films.has(session.id))
379
+ }
380
+ if (films.has(session.id)) {
287
381
  return;
382
+ }
288
383
  const timer = setInterval(() => {
289
384
  void filmNow(session.id);
290
385
  }, FRAME.EVERY_MS);
@@ -295,16 +390,18 @@ async function filmTab(deps, session, dir) {
295
390
  /** Stop filming a tab. Safe to call for a tab that is not filmed. */
296
391
  export function stopFilming(sessionId) {
297
392
  const film = films.get(sessionId);
298
- if (film === undefined)
393
+ if (film === undefined) {
299
394
  return;
395
+ }
300
396
  clearInterval(film.timer);
301
397
  films.delete(sessionId);
302
398
  }
303
399
  /** One tick of a tab's film: one frame, when the budget, the gap and the page allow. Never throws. */
304
400
  export async function filmNow(sessionId) {
305
401
  const film = films.get(sessionId);
306
- if (film === undefined || film.busy !== undefined)
402
+ if (film === undefined || film.busy !== undefined) {
307
403
  return;
404
+ }
308
405
  film.busy = frame(sessionId, film).finally(() => {
309
406
  film.busy = undefined;
310
407
  });
@@ -315,8 +412,9 @@ async function frame(sessionId, film) {
315
412
  try {
316
413
  session = film.deps.sessions.resolve(sessionId);
317
414
  // An ended tab has nothing more to show.
318
- if (session.isEnded())
415
+ if (session.isEnded()) {
319
416
  session = undefined;
417
+ }
320
418
  }
321
419
  catch {
322
420
  session = undefined;
@@ -327,18 +425,21 @@ async function frame(sessionId, film) {
327
425
  }
328
426
  const dir = film.dir;
329
427
  const kept = framesIn.get(dir) ?? 0;
330
- if (kept >= FRAME.MAX_PER_RUN)
428
+ if (kept >= FRAME.MAX_PER_RUN) {
331
429
  return;
430
+ }
332
431
  film.idle += 1;
333
432
  const gap = 2 ** Math.floor(kept / (FRAME.MAX_PER_RUN / FRAME_STAGES));
334
- if (film.idle < Math.max(gap, film.still))
433
+ if (film.idle < Math.max(gap, film.still)) {
335
434
  return;
435
+ }
336
436
  film.idle = 0;
337
437
  try {
338
438
  const bytes = await framePixels(film.deps, session);
339
439
  const size = bytes === undefined ? undefined : jpegSize(bytes);
340
- if (bytes === undefined || size === undefined)
440
+ if (bytes === undefined || size === undefined) {
341
441
  return;
442
+ }
342
443
  const sha256 = createHash('sha256').update(bytes).digest('hex');
343
444
  if (film.recent.includes(sha256)) {
344
445
  film.still = Math.min(film.still * 2, MAX_STILL_TICKS);
@@ -348,8 +449,9 @@ async function frame(sessionId, film) {
348
449
  film.still = 1;
349
450
  const seq = await allocate(film.deps.fs, dir);
350
451
  // The step shots' own limit is the platform's: a frame never takes the last of it.
351
- if (seq > STEP_SCREENSHOT.MAX_PER_RUN)
452
+ if (seq > STEP_SCREENSHOT.MAX_PER_RUN) {
352
453
  return;
454
+ }
353
455
  framesIn.set(dir, kept + 1);
354
456
  const file = `${String(seq).padStart(SEQ_WIDTH, '0')}-${FRAME.TOOL}${JPEG_EXT}`;
355
457
  const record = {
@@ -377,9 +479,11 @@ async function frame(sessionId, film) {
377
479
  async function framePixels(deps, session) {
378
480
  const opts = { jpegQuality: FRAME.JPEG_QUALITY, cssScale: true };
379
481
  const leased = await deps.pool?.screenshotLease(session.id, opts);
380
- if (leased !== undefined)
482
+ if (leased !== undefined) {
381
483
  return leased;
382
- if (true !== (await deps.realInput?.isAvailableFor(session.url)))
484
+ }
485
+ if (true !== (await deps.realInput?.isAvailableFor(session.url))) {
383
486
  return undefined;
487
+ }
384
488
  return deps.realInput?.screenshot?.(session.url, opts);
385
489
  }
@@ -8,24 +8,6 @@ import type { ToolDef, ToolDeps } from '../../surface/tools/tool-kit.js';
8
8
  * gone mid-drive must not break the drive.
9
9
  */
10
10
  export declare function driveFrame(deps: ToolDeps, sessionId: string, jpegQuality: number): Promise<Uint8Array | undefined>;
11
- /**
12
- * Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
13
- * become a file-read oracle for whatever a compromised renderer names.
14
- *
15
- * Two layouts are accepted, and the pair is the whole subtlety:
16
- *
17
- * - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
18
- * so no other local user can read the screenshot or pre-place a symlink at the path we write.
19
- * - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
20
- * SEPARATE package on the user's own upgrade schedule: an app still on the older
21
- * `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
22
- * start returning no image. It grants the daemon nothing it did not already have.
23
- *
24
- * Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
25
- * `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
26
- * `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
27
- */
28
- export declare function isCapturePath(path: string): boolean;
29
11
  /**
30
12
  * The opt-in pixel layer. Both tools require a DRIVEN browser (reticle drive /
31
13
  * RETICLE_CDP_URL) — the always-on SDK ships no screenshotter — and return a structured NO_PROVIDER