@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
@@ -11,8 +11,8 @@
11
11
  */
12
12
  import * as production from '../../portal/production/production-activation.js';
13
13
  import { z } from 'zod';
14
- import { leaseNotConnectedHint, leaseReaderFailedHint, leaseStillLoadingHint, } from './lease-hint.js';
15
- import { connectOrInject, moveReusedLease, notReadyReason, probeLeaseAlive, } from './lease-readiness.js';
14
+ import { keptLeaseAnswer, notLoadedCause, leaseNotConnectedHint, leaseReaderFailedHint, leaseStillLoadingHint, } from './lease-hint.js';
15
+ import { LEASE_READY_TIMEOUT_MS, awaitLeaseAnswering, connectOrInject, connectWaitUntil, moveReusedLease, notReadyReason, probeLeaseAlive, reconnectHeldLease, HeldLeaseReload, } from './lease-readiness.js';
16
16
  import { probeSdkMarker } from './gaps/sdk-marker-probe.js';
17
17
  import { observeWebDocument } from '../../portal/session/dev-server/served-document.js';
18
18
  import { diagnoseObservedWebCsp } from '@reticlehq/init';
@@ -21,14 +21,17 @@ import { hasAnyAppConnectedBefore } from '../../memory/recall/prior/connection-m
21
21
  import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../../portal/session/lease-visibility.js';
22
22
  import { reticleStateHome } from '../../command/daemon/daemon.js';
23
23
  import { claimArtifactRoot, dropArtifactRootClaim } from '../../memory/project/root-claims.js';
24
- import { LeaseNotReadyReason, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, SeedStorageSchema, originOf, } from '@reticlehq/core';
24
+ import { LeaseNotReadyReason, RETICLE_DEFAULT_PORT, SeedStorageSchema, originOf, } from '@reticlehq/core';
25
25
  import { HudVisibility, ReticleTool, SESSION_HEALTH } from '@reticlehq/core';
26
26
  import { HOST_STUB_NOTE } from './tool-kit.js';
27
27
  import { PoolAtCapacityError } from '../../portal/pool/browser-pool.js';
28
- import { asString } from '@reticlehq/core';
28
+ import { asNumber, asString } from '@reticlehq/core';
29
+ import { timeoutMsSchema } from './args/numeric-bounds.js';
29
30
  import { chromiumPreflightRefusal } from '../../command/cli/doctor/browser/chromium-hint.js';
30
31
  import { LEASE_PERMISSIONS_ARG, hintOf, notificationReadBack, parseLeasePermissions, permissionRefusal, refuseRegrant, } from './lease-permissions.js';
31
32
  import { LEASE_EMULATE_ARG, leaseEmulationOf } from '../../portal/input/viewport-tools.js';
33
+ import { LEASE_DETERMINISTIC_ARG, leaseDeterminismOf } from '../../portal/pool/determinism.js';
34
+ import { LEASE_COVERAGE_TOOL, LEASE_JS_COVERAGE_ARG } from './lease-coverage-tools.js';
32
35
  export { sessionMutationPort, sessionPerturbationPort, suiteFixturePort, suiteFixtureSeed, } from './session-ports.js';
33
36
  /**
34
37
  * Everything the daemon already knows about why a leased tab might not have dialled in.
@@ -86,38 +89,17 @@ async function leaseEvidence(deps, port, url) {
86
89
  }
87
90
  const POOL_UNAVAILABLE = 'browser pool unavailable — the lease tools need the daemon-managed pool (start Reticle via `reticle mcp`).';
88
91
  const ALREADY_HELD_LEASE = 'you already hold a lease on this origin';
92
+ const DETERMINISTIC_NOTE = 'Time stands still on this tab. Step it with reticle_clock { stepFrames: N }, then read or capture.';
89
93
  function alreadyHeldHint(sessionId, origin) {
90
94
  return (`${ALREADY_HELD_LEASE} (${origin}): ${sessionId} — reused it rather than opening a second ` +
91
95
  'tab, which would make every later call require sessionId. It is still signed in as whoever ' +
92
96
  'used it last (cookies and storage come with it): release it first for a fresh context or ' +
93
- 'another identity.');
94
- }
95
- /**
96
- * Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
97
- * adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
98
- */
99
- export function appendReticleParams(url, session, projectId, hud) {
100
- try {
101
- const u = new URL(url);
102
- u.searchParams.set(RETICLE_URL_PARAM.SESSION, session);
103
- if (projectId !== undefined && projectId.length > 0) {
104
- u.searchParams.set(RETICLE_URL_PARAM.PROJECT, projectId);
105
- }
106
- if (hud !== undefined) {
107
- u.searchParams.set(RETICLE_URL_PARAM.HUD, hud);
108
- }
109
- return u.toString();
110
- }
111
- catch {
112
- const sep = url.includes('?') ? '&' : '?';
113
- const proj = projectId !== undefined && projectId.length > 0
114
- ? `&${RETICLE_URL_PARAM.PROJECT}=${encodeURIComponent(projectId)}`
115
- : '';
116
- return `${url}${sep}${RETICLE_URL_PARAM.SESSION}=${encodeURIComponent(session)}${proj}`;
117
- }
97
+ 'another identity, or pass fresh: true to open a separate tab and keep this one.');
118
98
  }
99
+ export { appendReticleParams, carryReticleIdentity, resolveLeasedSessionId, } from './lease-url-identity.js';
100
+ import { appendReticleParams, carryReticleIdentity, resolveLeasedSessionId, } from './lease-url-identity.js';
119
101
  export { cleanNavError, evaluateSeedPrecondition, scrubSeedFromError } from './lease-seed.js';
120
- import { evaluateSeedPrecondition, looksLikeStorageStateExport, navFailureMessage, scrubSeedFromError, } from './lease-seed.js';
102
+ import { evaluateSeedPrecondition, looksLikeStorageStateExport, navFailureMessage, scrubSeedFromError, serverAcceptedAfterTimeout, } from './lease-seed.js';
121
103
  /** A fresh, collision-resistant lease id. Uses crypto at the I/O boundary (not pure logic). */
122
104
  function newLeaseId() {
123
105
  const uuid = 'function' === typeof globalThis.crypto?.randomUUID
@@ -125,84 +107,19 @@ function newLeaseId() {
125
107
  : `${String(Date.now())}-${String(performance.now())}`;
126
108
  return `lease-${uuid}`;
127
109
  }
128
- /**
129
- * The session a lease's tab actually registered as, or undefined while nothing has.
130
- *
131
- * Normally that is the lease id: `appendReticleParams` stamps `__reticle_session` on the URL and the
132
- * SDK adopts it. An app that passes an explicit `session` to `connect()` keeps its own name instead,
133
- * which is legitimate — a single-app fixture does it deliberately so a battery can address it by a
134
- * known id. Matching on the lease id alone could never see those tabs, so the lease reported
135
- * `ready: false` and a hint blaming a port mismatch while the session was connected and driveable.
136
- *
137
- * The URL marker is the evidence, and it is what makes this safe: two concurrent leases on one origin
138
- * carry different markers, so neither can adopt the other's tab. Parsed rather than substring-matched,
139
- * so `lease-abc` cannot claim `lease-abcdef`.
140
- */
141
- export function resolveLeasedSessionId(sessions, leaseId) {
142
- if (sessions.get(leaseId) !== undefined) {
143
- return leaseId;
144
- }
145
- return sessions.all().find((s) => sessionParamOf(s.url) === leaseId)?.id;
146
- }
147
- /**
148
- * Carry a claimed identity across a navigation.
149
- *
150
- * A LEASED tab is addressed by `__reticle_session` in its URL: that param is how the pool finds the
151
- * context it opened, and — since succession was tightened — it is also the ONLY thing that lets the
152
- * reconnecting document inherit the lease. `reticle_navigate` sent the caller's raw URL, so the very
153
- * first navigation stripped the marker, the tab re-announced as an ordinary anonymous session, and
154
- * the lease was orphaned: "no sessions are connected at all", with a browser window still open on
155
- * screen. A leased browser that cannot survive being navigated is not a usable one.
156
- *
157
- * A tab that claims nothing is left exactly as it was — this must never bolt Reticle's params onto
158
- * a human's own URL.
159
- */
160
- export function carryReticleIdentity(fromUrl, toUrl) {
161
- const session = sessionParamOf(fromUrl);
162
- if (session === undefined) {
163
- return toUrl;
164
- }
165
- // Already addressed (a caller passing the stamped URL back) — appending would be a no-op at best.
166
- if (sessionParamOf(toUrl) !== undefined) {
167
- return toUrl;
168
- }
169
- const projectId = projectParamOf(fromUrl);
170
- return appendReticleParams(toUrl, session, projectId);
171
- }
172
- function projectParamOf(url) {
173
- if (url === undefined) {
174
- return undefined;
175
- }
176
- try {
177
- return new URL(url).searchParams.get(RETICLE_URL_PARAM.PROJECT) ?? undefined;
178
- }
179
- catch {
180
- return undefined;
181
- }
182
- }
183
- function sessionParamOf(url) {
184
- if (url === undefined) {
185
- return undefined;
186
- }
187
- try {
188
- return new URL(url).searchParams.get(RETICLE_URL_PARAM.SESSION) ?? undefined;
189
- }
190
- catch {
191
- return undefined;
192
- }
193
- }
194
110
  /**
195
111
  * Acquire a leased, SDK-connected context for a caller that is not the lease TOOL (the parallel suite).
196
112
  * Returns the session id plus its release; the caller must release in a finally so a crashed flow never
197
113
  * leaks a pool slot and starves the rest of the suite.
198
114
  */
199
- export async function acquireLeasedSession(pool, sessions, url, projectId, seedStorage) {
115
+ export async function acquireLeasedSession(pool, sessions, url, projectId, seedStorage, ignoreHTTPSErrors) {
200
116
  const sessionId = newLeaseId();
201
117
  let lease;
202
118
  try {
203
119
  lease = await pool.acquire(appendReticleParams(url, sessionId, projectId), {
204
120
  sessionId,
205
121
  ...(seedStorage !== undefined ? { seedStorage } : {}),
122
+ ...(true === ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
206
123
  });
207
124
  }
208
125
  catch (err) {
@@ -248,7 +165,7 @@ export function hasOriginLock(origin) {
248
165
  }
249
166
  export const LEASE_ACQUIRE_TOOL = {
250
167
  name: ReticleTool.LEASE_ACQUIRE,
251
- description: 'Lease a fresh isolated browser context from the shared pool (shown or hidden as the daemon was started; `headed: true` forces a window) and navigate it to the app URL (the app must already be running; one with no Reticle SDK gets one supplied, reported as zeroInstall). If this origin is already leased and still connected, this returns THAT session rather than minting a second tab — a second acquire on the same origin poisons default session resolution. Returns the sessionId the leased tab registers — pass it to other tools. The pool keeps all leases in ONE browser and caps concurrency; if every slot is taken this is refused at once, naming the cap, rather than queued. Release with reticle_lease{action:"release"} when the flow is done. PREFER AN ALREADY-OPEN TAB: if reticle_sessions lists a non-leased session for this app, drive THAT instead — a lease is invisible to the person watching the app, whose HUD lives in their own tab, and a tab flagged hidden/throttled is often still driveable. Lease for isolation you actually need (a second identity, a clean context, parallel flows) or when driving the open tab has failed — this call answers with `preferExisting` when a live tab was available.',
168
+ description: 'Lease a fresh isolated browser context from the shared pool (shown or hidden as the daemon was started; `headed: true` forces a window) and navigate it to the app URL (the app must already be running; one with no Reticle SDK gets one supplied, reported as zeroInstall). If this origin is already leased, this returns THAT session rather than minting a second tab (a tab that has not connected is loaded again in its own context, so its cookies and sign-in are kept) — a second acquire on the same origin poisons default session resolution. Returns the sessionId the leased tab registers — pass it to other tools. The pool keeps all leases in ONE browser and caps concurrency; if every slot is taken this is refused at once, naming the cap, rather than queued. Release with reticle_lease{action:"release"} when the flow is done. PREFER AN ALREADY-OPEN TAB: if reticle_sessions lists a non-leased session for this app, drive THAT instead — a lease is invisible to the person watching the app, whose HUD lives in their own tab, and a tab flagged hidden/throttled is often still driveable. Lease for isolation you actually need (a second identity, a clean context, parallel flows) or when driving the open tab has failed — this call answers with `preferExisting` when a live tab was available.',
252
169
  inputSchema: {
253
170
  url: z
254
171
  .string()
@@ -259,6 +176,10 @@ export const LEASE_ACQUIRE_TOOL = {
259
176
  .optional()
260
177
  .describe('Stable project id to stamp on the leased tab so the agent can scope to it.'),
261
178
  seedStorage: SeedStorageSchema.optional().describe('Initial storage state (local, session and cookies) to seed before the first navigation (e.g. `seedStorage: { local: { token: "..." } }` to start already authenticated).'),
179
+ fresh: z
180
+ .boolean()
181
+ .optional()
182
+ .describe('Open a separate tab even when this connection already holds a lease on the origin, and keep that one. For parallel work that shares one MCP connection (sub-agents). Pass the returned sessionId to every later call.'),
262
183
  initScript: z
263
184
  .string()
264
185
  .optional()
@@ -277,26 +198,39 @@ export const LEASE_ACQUIRE_TOOL = {
277
198
  .describe('How the HUD starts on the page: shown (default), hidden, or removed.'),
278
199
  permissions: LEASE_PERMISSIONS_ARG,
279
200
  emulate: LEASE_EMULATE_ARG,
201
+ deterministic: LEASE_DETERMINISTIC_ARG,
202
+ jsCoverage: LEASE_JS_COVERAGE_ARG,
280
203
  production: production.PRODUCTION_LEASE_ARG,
204
+ timeout_ms: timeoutMsSchema
205
+ .optional()
206
+ .describe(`How long to wait for the leased tab to connect and answer before answering ready:false with notReadyReason. Default ${String(LEASE_READY_TIMEOUT_MS)}. 0 looks once without waiting.`),
281
207
  root: z
282
208
  .string()
283
209
  .optional()
284
- .describe("Absolute path of the .reticle folder this lease's runs belong in — the caller's project. The project folder itself is accepted when it contains a .reticle. Without it, the page's project id decides, and a page with none lands in ~/.reticle/unmatched, which nothing syncs."),
210
+ .describe("Absolute path of the .reticle folder this lease's runs belong in — the caller's project. An existing project folder is accepted even when it has no .reticle yet (the first run writes it). Omitted, the page's project id decides, and a page with none lands in ~/.reticle/unmatched, which nothing syncs."),
285
211
  },
286
212
  outputSchema: {
287
213
  sessionId: z.string(),
288
214
  url: z.string(),
289
215
  ready: z
290
216
  .boolean()
291
- .describe('Whether the leased tab is connected AND answering. It is NOT a claim that the app has hydrated, so an act can miss: make the first act reticle_act_and_wait with an `until`, or reticle_assert { action: "wait" }. Empty reads may mean not hydrated. False ⇒ read notReadyReason.'),
217
+ .describe('Whether the leased tab is connected AND answered a probe under sessionId, so the next call finds it. acquire waits for this, up to timeout_ms. It is NOT a claim that the app has hydrated, so an act can miss: make the first act reticle_act_and_wait with an `until`, or reticle_assert { action: "wait" }. Empty reads may mean not hydrated. False ⇒ read notReadyReason.'),
292
218
  zeroInstall: z
293
219
  .boolean()
294
220
  .optional()
295
221
  .describe('Present when this app ships no Reticle SDK and the lease supplied one. Verdicts work on the DOM, network, console and routes; there is no framework adapter, so no component state and no source file:line. Install with `npx @reticlehq/server init` for those.'),
222
+ deterministic: z
223
+ .string()
224
+ .optional()
225
+ .describe('Present when this tab runs a seeded random source and a stepped clock. Its time moves only on reticle_clock { stepFrames }, so a wait for a timer or an animation never ends until you step.'),
296
226
  hostStub: z
297
227
  .string()
298
228
  .optional()
299
229
  .describe('Present when this leased tab runs an init script stubbing the bindings a desktop shell injects. Verdicts on this lease are about the app with that stub, not about the real shell.'),
230
+ cspWidenedFor: z
231
+ .string()
232
+ .optional()
233
+ .describe("Present when the lease added this origin (the daemon's WebSocket) to the page CSP connect-src, so the zero-install reader can connect. The rest of the page CSP applies as the server sent it."),
300
234
  notReadyReason: z
301
235
  .enum([
302
236
  LeaseNotReadyReason.SDK_NEVER_DIALLED,
@@ -359,7 +293,11 @@ export const LEASE_ACQUIRE_TOOL = {
359
293
  const headed = true === args['headed'];
360
294
  const hud = Object.values(HudVisibility).find((v) => v === args['hud']);
361
295
  const permissions = production.leasePermissions(parseLeasePermissions(args['permissions']), args['production']);
362
- const emulate = leaseEmulationOf(args['emulate']);
296
+ // Determinism is set on the context, like the emulation, so it travels with it.
297
+ const deterministic = leaseDeterminismOf(args['deterministic']);
298
+ const emulated = leaseEmulationOf(args['emulate']);
299
+ const emulate = deterministic === undefined ? emulated : { ...(emulated ?? {}), deterministic };
300
+ const jsCoverage = true === args['jsCoverage'];
363
301
  // The stub of the shell bindings, when the caller supplied one. Installed by the pool before
364
302
  // the first navigation, so it is already defined when the app's first module runs.
365
303
  const initScript = asString(args['initScript']);
@@ -408,14 +346,17 @@ export const LEASE_ACQUIRE_TOOL = {
408
346
  // an init script has to exist before the page's own scripts run, which a loaded page cannot
409
347
  // do, and the resolve below then finds nothing and mints a fresh context with the stub. A
410
348
  // lease of the other kind is left alone, exactly as it is when a window was asked for.
411
- const heldForStub = origin === undefined ? undefined : pool.leaseIdOnOrigin(origin, deps.attachId);
349
+ // `fresh` asks for a separate tab: the held one is neither reused nor released (#1932).
350
+ const fresh = true === args['fresh'];
351
+ const heldOn = (o) => fresh || o === undefined ? undefined : pool.leaseIdOnOrigin(o, deps.attachId);
352
+ const heldForStub = heldOn(origin);
412
353
  if (initScript !== undefined &&
413
354
  heldForStub !== undefined &&
414
355
  pool.isHeaded(heldForStub) === headed &&
415
356
  pool.hostStubScript?.(heldForStub) !== initScript) {
416
357
  await pool.release(heldForStub);
417
358
  }
418
- const held = origin === undefined ? undefined : pool.leaseIdOnOrigin(origin, deps.attachId);
359
+ const held = heldOn(origin);
419
360
  const existing = held !== undefined && pool.isHeaded(held) === headed ? held : undefined;
420
361
  // A context's certificate setting is fixed when it is made, so an explicit request for the
421
362
  // other one cannot be answered by the held lease: handing it back would keep checks off after
@@ -428,6 +369,7 @@ export const LEASE_ACQUIRE_TOOL = {
428
369
  if ((validatedSeed !== undefined ||
429
370
  certMismatch ||
430
371
  emulate !== undefined ||
372
+ jsCoverage ||
431
373
  activated !== undefined) &&
432
374
  existing !== undefined) {
433
375
  // Seeding or a different certificate setting asked for: release its own context so the new
@@ -442,6 +384,9 @@ export const LEASE_ACQUIRE_TOOL = {
442
384
  // context — the tab-poisoning this branch exists to prevent, reached through the branch
443
385
  // itself. It bites when the first acquire returned `ready: false`, since the mint path only
444
386
  // aliases once its wait has resolved, so nothing recorded the app's own name (#692).
387
+ // A tab that never registered is loaded again in its OWN context, and kept while it is open:
388
+ // releasing it and minting threw away the cookies, so a signed-in tab came back signed out (#1924).
389
+ const reload = await reconnectHeldLease((ms) => pool.gotoLease(existing, carryReticleIdentity(pool.leaseUrl(existing), url), ms), async () => (await pool.viewportOfLease(existing)) !== undefined, () => pool.leaseLoading(existing), () => resolveLeasedSessionId(deps.sessions, existing) !== undefined, deps.now() + (asNumber(args['timeout_ms']) ?? LEASE_READY_TIMEOUT_MS), deps.now);
445
390
  const resolved = resolveLeasedSessionId(deps.sessions, existing);
446
391
  if (resolved !== undefined) {
447
392
  // The other name this lease answers to, told to the pool exactly as the mint path tells it.
@@ -475,14 +420,20 @@ export const LEASE_ACQUIRE_TOOL = {
475
420
  // Asked of the pool, not remembered here: the stub was installed when THIS lease was
476
421
  // minted, and the caller reusing it now may be seeing it for the first time.
477
422
  ...(true === pool.stubInstalled?.(existing) ? { hostStub: HOST_STUB_NOTE } : {}),
423
+ ...cspWidenedOf(pool, existing),
478
424
  ...(alreadyOpen === undefined
479
425
  ? {}
480
426
  : { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
481
427
  };
482
428
  }
483
- // The lease is still held but the tab has gone — a reload dropped the session, and no session
484
- // anywhere is driving it. Free the slot and mint, rather than handing back a dead id or
485
- // leaving both contexts occupied.
429
+ if (reload === HeldLeaseReload.RELOADED || reload === HeldLeaseReload.NOT_LOADED) {
430
+ const cause = reload === HeldLeaseReload.RELOADED
431
+ ? await notConnectedHint(deps, url, pool.dialFailureUrl?.(existing))
432
+ : await notLoadedCause(url, deps.portAccepts);
433
+ return keptLeaseAnswer({ sessionId: existing, origin, url, reload }, pool, cause);
434
+ }
435
+ // The tab is gone: its page or its context was closed. Free the slot and mint, rather than
436
+ // handing back a dead id or leaving both contexts occupied.
486
437
  await pool.release(existing);
487
438
  }
488
439
  const sessionId = newLeaseId();
@@ -505,6 +456,7 @@ export const LEASE_ACQUIRE_TOOL = {
505
456
  ...(permissions !== undefined ? { permissions } : {}),
506
457
  ...(true === args['ignoreHTTPSErrors'] ? { ignoreHTTPSErrors: true } : {}),
507
458
  ...(emulate === undefined ? {} : { emulate }),
459
+ ...(jsCoverage ? { jsCoverage } : {}),
508
460
  });
509
461
  }
510
462
  catch (err) {
@@ -519,19 +471,27 @@ export const LEASE_ACQUIRE_TOOL = {
519
471
  throw new Error(scrubSeedFromError(err.message, validatedSeed));
520
472
  }
521
473
  // A raw page.goto failure is noisy and leaks the internal URL params — surface a clean,
522
- // actionable message instead, one that tells a slow first compile from a missing app.
523
- throw new Error(navFailureMessage(url, err, validatedSeed));
474
+ // actionable message instead, one that tells a slow first compile from a missing app. A
475
+ // timeout on a port that accepted the connection is a server still compiling (#2005).
476
+ const accepted = await serverAcceptedAfterTimeout(url, err, validatedSeed, deps.portAccepts);
477
+ throw new Error(navFailureMessage(url, err, validatedSeed, accepted));
524
478
  }
525
479
  // Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
526
480
  // Resolved rather than assumed: an app that names its own session registers under that name,
527
481
  // and the id we hand back has to be the one the agent can actually drive.
528
482
  let registeredId;
529
- const waitStartedAt = Date.now();
530
- const waited = await production.guardedForProduction(pool, lease, activated);
531
- const { ready, zeroInstall, stillLoading, readerFailure } = await connectOrInject(waited, () => {
483
+ const isRegistered = () => {
532
484
  registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
533
485
  return registeredId !== undefined;
534
- });
486
+ };
487
+ const waitStartedAt = deps.now();
488
+ const deadline = waitStartedAt + (asNumber(args['timeout_ms']) ?? LEASE_READY_TIMEOUT_MS);
489
+ const waited = await production.guardedForProduction(pool, lease, activated);
490
+ const { ready: connected, zeroInstall, stillLoading, readerFailure, } = await connectOrInject(waited, isRegistered, connectWaitUntil(deadline, deps.now));
491
+ // Connected is a row in the sessions map. Ready is a reply from it, so the next call works.
492
+ const answerReason = connected
493
+ ? await awaitLeaseAnswering(() => (isRegistered() ? deps.sessions.get(registeredId ?? lease.sessionId) : undefined), deadline, deps.now)
494
+ : undefined;
535
495
  // Tell the pool the other name this lease answers to. Every later touch and release arrives
536
496
  // under the id we are about to hand back, and the pool is keyed by the id it navigated with;
537
497
  // without this the touches miss, the lease ages out despite continuous activity, and the
@@ -541,6 +501,7 @@ export const LEASE_ACQUIRE_TOOL = {
541
501
  }
542
502
  const effectiveSessionId = registeredId ?? lease.sessionId;
543
503
  const session = deps.sessions.get(effectiveSessionId);
504
+ const ready = connected && answerReason === undefined;
544
505
  if (validatedSeed !== undefined && session !== undefined) {
545
506
  const failureReason = evaluateSeedPrecondition(url, session.url, validatedSeed, lease.navStatus);
546
507
  if (failureReason !== undefined) {
@@ -552,18 +513,17 @@ export const LEASE_ACQUIRE_TOOL = {
552
513
  if (readerFailure === undefined) {
553
514
  tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
554
515
  }
555
- // ready means the SDK dialled in — not that contracts match. Carry the skew warning on acquire
516
+ // ready means the SDK answers — not that contracts match. Carry the skew warning on acquire
556
517
  // so the agent does not learn it only after a CDP tool invents a closed page (#688).
557
- const mintReason = readerFailure?.reason ??
558
- notReadyReason(ready, session, LeaseNotReadyReason.SDK_NEVER_DIALLED);
518
+ const mintReason = readerFailure?.reason ?? (connected ? answerReason : LeaseNotReadyReason.SDK_NEVER_DIALLED);
559
519
  const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
560
520
  let notReadyHint;
561
- if (!ready) {
521
+ if (!connected) {
562
522
  if (readerFailure !== undefined) {
563
523
  notReadyHint = leaseReaderFailedHint(url, readerFailure);
564
524
  }
565
525
  else if (true === stillLoading) {
566
- notReadyHint = leaseStillLoadingHint(url, Date.now() - waitStartedAt);
526
+ notReadyHint = leaseStillLoadingHint(url, deps.now() - waitStartedAt);
567
527
  }
568
528
  else if (activated !== undefined) {
569
529
  notReadyHint = production.notActivatedHint(url);
@@ -596,6 +556,8 @@ export const LEASE_ACQUIRE_TOOL = {
596
556
  ...(versionSkew === undefined ? {} : { versionSkew }),
597
557
  ...production.summary(activated),
598
558
  ...(initScript === undefined ? {} : { hostStub: HOST_STUB_NOTE }),
559
+ ...(deterministic === undefined ? {} : { deterministic: DETERMINISTIC_NOTE }),
560
+ ...cspWidenedOf(pool, lease.sessionId),
599
561
  ...hintOf(notReadyHint, ready ? await notificationReadBack(pool, lease.sessionId, permissions) : undefined),
600
562
  };
601
563
  }
@@ -607,6 +569,11 @@ export const LEASE_ACQUIRE_TOOL = {
607
569
  }
608
570
  },
609
571
  };
572
+ /** `cspWidenedFor` when the lease changed its page CSP: a change to the page is declared (#2016). */
573
+ function cspWidenedOf(pool, leaseId) {
574
+ const origin = pool.cspWidenedFor?.(leaseId);
575
+ return origin === undefined ? {} : { cspWidenedFor: origin };
576
+ }
610
577
  /** The whole not-connected diagnosis, gathered and worded. Split out so the acquire path stays flat. */
611
578
  async function notConnectedHint(deps, url, dialledUrl) {
612
579
  const bridgePort = deps.bridgePort ?? RETICLE_DEFAULT_PORT;
@@ -618,11 +585,12 @@ async function notConnectedHint(deps, url, dialledUrl) {
618
585
  }
619
586
  const LEASE_RELEASE_TOOL = {
620
587
  name: ReticleTool.LEASE_RELEASE,
621
- description: 'Release a leased browser context by sessionId, closing it and freeing the pool slot for a queued acquire. Call this when a flow finishes so the pool stays within its concurrency cap.',
588
+ description: 'Release a leased browser context by sessionId, closing it and freeing the pool slot for a queued acquire. Call this when a flow finishes so the pool stays within its concurrency cap. With no sessionId, it releases the one lease this agent holds.',
622
589
  inputSchema: {
623
590
  sessionId: z
624
591
  .string()
625
- .describe('The leased sessionId returned by reticle_lease{action:"acquire"}.'),
592
+ .optional()
593
+ .describe('The leased sessionId returned by reticle_lease{action:"acquire"}. Omit it when this agent holds one lease.'),
626
594
  },
627
595
  outputSchema: {
628
596
  released: z.boolean(),
@@ -633,10 +601,7 @@ const LEASE_RELEASE_TOOL = {
633
601
  if (pool === undefined) {
634
602
  throw new Error(POOL_UNAVAILABLE);
635
603
  }
636
- const sessionId = asString(args['sessionId']);
637
- if (sessionId === undefined || 0 === sessionId.length) {
638
- throw new Error('reticle_lease{action:"release"} requires a sessionId');
639
- }
604
+ const sessionId = asString(args['sessionId']) ?? ownOnlyLease(deps, pool);
640
605
  // An id that holds no lease (a typo, or one that expired) answered `released: true`.
641
606
  if (pool.leaseUrl(sessionId) === undefined) {
642
607
  const held = pool.leasedSessionIds();
@@ -646,6 +611,21 @@ const LEASE_RELEASE_TOOL = {
646
611
  return { released: true, leased: await releaseLease(deps, pool, sessionId) };
647
612
  },
648
613
  };
614
+ /**
615
+ * The one lease this caller holds, for a release with no sessionId. Another agent's lease is never
616
+ * this caller's to release (#1226), so none or several is a refusal that names what is held.
617
+ */
618
+ function ownOnlyLease(deps, pool) {
619
+ const own = pool.leasedSessionIds().filter((id) => pool.heldBy(id, deps.attachId));
620
+ const [only] = own;
621
+ if (only !== undefined && 1 === own.length) {
622
+ return only;
623
+ }
624
+ const held = 0 === own.length
625
+ ? 'this agent holds no lease'
626
+ : `this agent holds ${String(own.length)} leases: ${own.join(', ')}`;
627
+ throw new Error(`reticle_lease{action:"release"} needs a sessionId: ${held}.`);
628
+ }
649
629
  /**
650
630
  * Release one lease and everything that hangs off it. Returns how many leases are still held.
651
631
  *
@@ -736,4 +716,4 @@ function tellWatchers(deps, projectId, text) {
736
716
  // A HUD that missed one line is a smaller problem than a lease reported as broken.
737
717
  }
738
718
  }
739
- export const LEASE_TOOLS = [LEASE_ACQUIRE_TOOL, LEASE_RELEASE_TOOL];
719
+ export const LEASE_TOOLS = [LEASE_ACQUIRE_TOOL, LEASE_RELEASE_TOOL, LEASE_COVERAGE_TOOL];
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Reticle's identity params on a lease URL: stamp them, read them back, carry them across a
3
+ * navigation, and match a registered session to the lease that stamped it.
4
+ *
5
+ * Split out of lease-tools.ts at the 1000-line cap. Pure: no I/O.
6
+ */
7
+ import { type HudVisibility } from '@reticlehq/core';
8
+ /**
9
+ * Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
10
+ * adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
11
+ */
12
+ export declare function appendReticleParams(url: string, session: string, projectId?: string, hud?: HudVisibility): string;
13
+ /**
14
+ * The session a lease's tab actually registered as, or undefined while nothing has.
15
+ *
16
+ * Normally that is the lease id: `appendReticleParams` stamps `__reticle_session` on the URL and the
17
+ * SDK adopts it. An app that passes an explicit `session` to `connect()` keeps its own name instead,
18
+ * which is legitimate — a single-app fixture does it deliberately so a battery can address it by a
19
+ * known id. Matching on the lease id alone could never see those tabs, so the lease reported
20
+ * `ready: false` and a hint blaming a port mismatch while the session was connected and driveable.
21
+ *
22
+ * The URL marker is the evidence, and it is what makes this safe: two concurrent leases on one origin
23
+ * carry different markers, so neither can adopt the other's tab. Parsed rather than substring-matched,
24
+ * so `lease-abc` cannot claim `lease-abcdef`.
25
+ */
26
+ export declare function resolveLeasedSessionId(sessions: {
27
+ get: (id: string) => unknown;
28
+ all: () => {
29
+ id: string;
30
+ url?: string;
31
+ }[];
32
+ }, leaseId: string): string | undefined;
33
+ /**
34
+ * Carry a claimed identity across a navigation.
35
+ *
36
+ * A LEASED tab is addressed by `__reticle_session` in its URL: that param is how the pool finds the
37
+ * context it opened, and — since succession was tightened — it is also the ONLY thing that lets the
38
+ * reconnecting document inherit the lease. `reticle_navigate` sent the caller's raw URL, so the very
39
+ * first navigation stripped the marker, the tab re-announced as an ordinary anonymous session, and
40
+ * the lease was orphaned: "no sessions are connected at all", with a browser window still open on
41
+ * screen. A leased browser that cannot survive being navigated is not a usable one.
42
+ *
43
+ * A tab that claims nothing is left exactly as it was — this must never bolt Reticle's params onto
44
+ * a human's own URL.
45
+ */
46
+ export declare function carryReticleIdentity(fromUrl: string | undefined, toUrl: string): string;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Reticle's identity params on a lease URL: stamp them, read them back, carry them across a
3
+ * navigation, and match a registered session to the lease that stamped it.
4
+ *
5
+ * Split out of lease-tools.ts at the 1000-line cap. Pure: no I/O.
6
+ */
7
+ import { RETICLE_URL_PARAM } from '@reticlehq/core';
8
+ /**
9
+ * Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
10
+ * adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
11
+ */
12
+ export function appendReticleParams(url, session, projectId, hud) {
13
+ try {
14
+ const u = new URL(url);
15
+ u.searchParams.set(RETICLE_URL_PARAM.SESSION, session);
16
+ if (projectId !== undefined && projectId.length > 0) {
17
+ u.searchParams.set(RETICLE_URL_PARAM.PROJECT, projectId);
18
+ }
19
+ if (hud !== undefined) {
20
+ u.searchParams.set(RETICLE_URL_PARAM.HUD, hud);
21
+ }
22
+ return u.toString();
23
+ }
24
+ catch {
25
+ const sep = url.includes('?') ? '&' : '?';
26
+ const proj = projectId !== undefined && projectId.length > 0
27
+ ? `&${RETICLE_URL_PARAM.PROJECT}=${encodeURIComponent(projectId)}`
28
+ : '';
29
+ return `${url}${sep}${RETICLE_URL_PARAM.SESSION}=${encodeURIComponent(session)}${proj}`;
30
+ }
31
+ }
32
+ /**
33
+ * The session a lease's tab actually registered as, or undefined while nothing has.
34
+ *
35
+ * Normally that is the lease id: `appendReticleParams` stamps `__reticle_session` on the URL and the
36
+ * SDK adopts it. An app that passes an explicit `session` to `connect()` keeps its own name instead,
37
+ * which is legitimate — a single-app fixture does it deliberately so a battery can address it by a
38
+ * known id. Matching on the lease id alone could never see those tabs, so the lease reported
39
+ * `ready: false` and a hint blaming a port mismatch while the session was connected and driveable.
40
+ *
41
+ * The URL marker is the evidence, and it is what makes this safe: two concurrent leases on one origin
42
+ * carry different markers, so neither can adopt the other's tab. Parsed rather than substring-matched,
43
+ * so `lease-abc` cannot claim `lease-abcdef`.
44
+ */
45
+ export function resolveLeasedSessionId(sessions, leaseId) {
46
+ if (sessions.get(leaseId) !== undefined) {
47
+ return leaseId;
48
+ }
49
+ return sessions.all().find((s) => sessionParamOf(s.url) === leaseId)?.id;
50
+ }
51
+ /**
52
+ * Carry a claimed identity across a navigation.
53
+ *
54
+ * A LEASED tab is addressed by `__reticle_session` in its URL: that param is how the pool finds the
55
+ * context it opened, and — since succession was tightened — it is also the ONLY thing that lets the
56
+ * reconnecting document inherit the lease. `reticle_navigate` sent the caller's raw URL, so the very
57
+ * first navigation stripped the marker, the tab re-announced as an ordinary anonymous session, and
58
+ * the lease was orphaned: "no sessions are connected at all", with a browser window still open on
59
+ * screen. A leased browser that cannot survive being navigated is not a usable one.
60
+ *
61
+ * A tab that claims nothing is left exactly as it was — this must never bolt Reticle's params onto
62
+ * a human's own URL.
63
+ */
64
+ export function carryReticleIdentity(fromUrl, toUrl) {
65
+ const session = sessionParamOf(fromUrl);
66
+ if (session === undefined) {
67
+ return toUrl;
68
+ }
69
+ // Already addressed (a caller passing the stamped URL back) — appending would be a no-op at best.
70
+ if (sessionParamOf(toUrl) !== undefined) {
71
+ return toUrl;
72
+ }
73
+ const projectId = projectParamOf(fromUrl);
74
+ return appendReticleParams(toUrl, session, projectId);
75
+ }
76
+ function projectParamOf(url) {
77
+ if (url === undefined) {
78
+ return undefined;
79
+ }
80
+ try {
81
+ return new URL(url).searchParams.get(RETICLE_URL_PARAM.PROJECT) ?? undefined;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ function sessionParamOf(url) {
88
+ if (url === undefined) {
89
+ return undefined;
90
+ }
91
+ try {
92
+ return new URL(url).searchParams.get(RETICLE_URL_PARAM.SESSION) ?? undefined;
93
+ }
94
+ catch {
95
+ return undefined;
96
+ }
97
+ }