@reticlehq/server 3.7.0 → 3.8.1

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 (578) hide show
  1. package/SKILL.md +59 -68
  2. package/bin/reticle.js +9 -2
  3. package/dist/command/cli/adhoc-suite.js +1 -1
  4. package/dist/command/cli/adhoc-verdict.d.ts +12 -0
  5. package/dist/command/cli/adhoc-verdict.js +62 -4
  6. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  7. package/dist/command/cli/answers/gate-hook-message.js +5 -1
  8. package/dist/command/cli/auth/cloud-session.d.ts +11 -19
  9. package/dist/command/cli/auth/cloud-session.js +23 -0
  10. package/dist/command/cli/auth/device-flow.d.ts +3 -37
  11. package/dist/command/cli/cli-doctor.js +66 -21
  12. package/dist/command/cli/cli-flow-commands.js +53 -11
  13. package/dist/command/cli/cli-kill.d.ts +8 -0
  14. package/dist/command/cli/cli-kill.js +43 -3
  15. package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
  16. package/dist/command/cli/cli-parse-feedback.js +13 -1
  17. package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
  18. package/dist/command/cli/cli-parse-grammar.js +1 -6
  19. package/dist/command/cli/cli-parse-serve.d.ts +24 -0
  20. package/dist/command/cli/cli-parse-serve.js +124 -0
  21. package/dist/command/cli/cli-parse.d.ts +12 -11
  22. package/dist/command/cli/cli-parse.js +100 -123
  23. package/dist/command/cli/cli-usage.d.ts +0 -11
  24. package/dist/command/cli/cli-usage.js +43 -16
  25. package/dist/command/cli/cli-verify.d.ts +1 -1
  26. package/dist/command/cli/cli-verify.js +29 -14
  27. package/dist/command/cli/cloud-cli.js +27 -9
  28. package/dist/command/cli/cloud-kit.js +3 -3
  29. package/dist/command/cli/cloud-link.d.ts +1 -20
  30. package/dist/command/cli/cloud-link.js +2 -0
  31. package/dist/command/cli/cloud-login.d.ts +6 -2
  32. package/dist/command/cli/cloud-login.js +13 -41
  33. package/dist/command/cli/config/checkout.d.ts +10 -0
  34. package/dist/command/cli/config/checkout.js +78 -0
  35. package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
  36. package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
  37. package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
  38. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  39. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  40. package/dist/command/cli/drive/drive-attach.d.ts +5 -0
  41. package/dist/command/cli/drive/drive-attach.js +21 -1
  42. package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
  43. package/dist/command/cli/hook/codex-transcript.js +224 -0
  44. package/dist/command/cli/hook/episode-structure.d.ts +24 -0
  45. package/dist/command/cli/hook/episode-structure.js +138 -0
  46. package/dist/command/cli/hook/hook-command.d.ts +9 -0
  47. package/dist/command/cli/hook/hook-command.js +211 -0
  48. package/dist/command/cli/hook/hook-fast.d.ts +31 -0
  49. package/dist/command/cli/hook/hook-fast.js +136 -0
  50. package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
  51. package/dist/command/cli/hook/prompt-hook.js +199 -0
  52. package/dist/command/cli/hook/tool-hook.d.ts +21 -0
  53. package/dist/command/cli/hook/tool-hook.js +259 -0
  54. package/dist/command/cli/launch/cli-launch.d.ts +2 -0
  55. package/dist/command/cli/launch/cli-launch.js +22 -2
  56. package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
  57. package/dist/command/cli/mcp-command.d.ts +9 -0
  58. package/dist/command/cli/mcp-command.js +127 -41
  59. package/dist/command/cli/open-navigate.d.ts +43 -0
  60. package/dist/command/cli/open-navigate.js +85 -0
  61. package/dist/command/cli/ports/port-holder.d.ts +16 -4
  62. package/dist/command/cli/ports/port-holder.js +58 -12
  63. package/dist/command/cli/ports/resolve/cli-port.js +18 -2
  64. package/dist/command/cli/project-name.d.ts +1 -7
  65. package/dist/command/cli/project-name.js +1 -0
  66. package/dist/command/cli/report-command.d.ts +8 -2
  67. package/dist/command/cli/report-command.js +95 -18
  68. package/dist/command/cli/status-fields.d.ts +9 -2
  69. package/dist/command/cli/status-fields.js +148 -4
  70. package/dist/command/cli/try-command.d.ts +3 -4
  71. package/dist/command/cli/try-command.js +6 -11
  72. package/dist/command/cli/verify-results.d.ts +4 -1
  73. package/dist/command/cli/verify-results.js +3 -1
  74. package/dist/command/cli.d.ts +1 -7
  75. package/dist/command/cli.js +79 -160
  76. package/dist/command/daemon/daemon-resilience.d.ts +6 -1
  77. package/dist/command/daemon/daemon-resilience.js +31 -18
  78. package/dist/command/daemon/daemon.d.ts +11 -0
  79. package/dist/command/daemon/daemon.js +70 -2
  80. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
  81. package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
  82. package/dist/command/daemon/wake-decision.js +4 -2
  83. package/dist/command/daemon/win32-escape.d.ts +32 -0
  84. package/dist/command/daemon/win32-escape.js +119 -0
  85. package/dist/command/setup/agent-configs.d.ts +9 -0
  86. package/dist/command/setup/agent-configs.js +9 -0
  87. package/dist/command/setup/auto-approve.d.ts +10 -3
  88. package/dist/command/setup/auto-approve.js +51 -3
  89. package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
  90. package/dist/command/setup/bringup/dev-server-wait.js +5 -1
  91. package/dist/command/setup/bringup/ensure-daemon.js +4 -7
  92. package/dist/command/setup/init/init-host.js +6 -0
  93. package/dist/command/setup/init/init-port.js +5 -4
  94. package/dist/command/setup/init/init-runtime.js +7 -1
  95. package/dist/command/setup/node-effects.d.ts +8 -0
  96. package/dist/command/setup/node-effects.js +36 -4
  97. package/dist/command/setup/probe/dev-port.d.ts +26 -0
  98. package/dist/command/setup/probe/dev-port.js +117 -0
  99. package/dist/command/setup/probe/page-probe.d.ts +1 -1
  100. package/dist/command/setup/probe/page-probe.js +11 -3
  101. package/dist/command/setup/remaining-steps.d.ts +0 -7
  102. package/dist/command/setup/remaining-steps.js +3 -2
  103. package/dist/command/setup/run-setup.d.ts +11 -0
  104. package/dist/command/setup/run-setup.js +33 -5
  105. package/dist/command/setup/setup-command.d.ts +2 -0
  106. package/dist/command/setup/setup-command.js +22 -4
  107. package/dist/command/update/updater.d.ts +1 -6
  108. package/dist/command/update/updater.js +4 -5
  109. package/dist/command/update/version-seen.d.ts +24 -0
  110. package/dist/command/update/version-seen.js +65 -0
  111. package/dist/command/version/peer-announce.d.ts +9 -1
  112. package/dist/command/version/peer-announce.js +25 -2
  113. package/dist/command/version/sdk-fix.d.ts +5 -6
  114. package/dist/command/version/sdk-fix.js +17 -32
  115. package/dist/command/version/version-skew.d.ts +16 -1
  116. package/dist/command/version/version-skew.js +84 -10
  117. package/dist/features/crawl/crawl-tools.js +2 -2
  118. package/dist/features/crawl/crawl.js +2 -0
  119. package/dist/features/exhaust/ledger.d.ts +15 -96
  120. package/dist/features/exhaust/ledger.js +3 -3
  121. package/dist/features/harness/drive-runs.d.ts +28 -4
  122. package/dist/features/harness/drive-runs.js +99 -7
  123. package/dist/features/harness/drivers.d.ts +1 -1
  124. package/dist/features/harness/drivers.js +1 -1
  125. package/dist/features/harness/harness.d.ts +5 -0
  126. package/dist/features/harness/harness.js +24 -12
  127. package/dist/features/harness/platform/platform-drives.d.ts +7 -1
  128. package/dist/features/harness/platform/platform-drives.js +12 -7
  129. package/dist/features/harness/platform/refusal.d.ts +1 -1
  130. package/dist/features/harness/platform/refusal.js +4 -4
  131. package/dist/features/harness/platform/remote-drive.js +70 -28
  132. package/dist/features/harness/platform/server-driver.js +22 -1
  133. package/dist/features/harness/platform-config.d.ts +1 -10
  134. package/dist/features/harness/platform-config.js +0 -20
  135. package/dist/features/license/license.d.ts +2 -14
  136. package/dist/features/visual/step-shots.d.ts +33 -1
  137. package/dist/features/visual/step-shots.js +155 -1
  138. package/dist/features/visual/visual-tools.js +12 -4
  139. package/dist/hooks/coding-agents.d.ts +6 -0
  140. package/dist/hooks/coding-agents.js +24 -3
  141. package/dist/hooks/episode-stamp.d.ts +32 -0
  142. package/dist/hooks/episode-stamp.js +61 -0
  143. package/dist/hooks/hook-commands.js +19 -9
  144. package/dist/index.d.ts +5 -1
  145. package/dist/index.js +66 -24
  146. package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
  147. package/dist/judgement/app-graph/journal-steps.js +92 -0
  148. package/dist/judgement/capsule/capsule-store.d.ts +2 -39
  149. package/dist/judgement/domain/domain-model.js +1 -10
  150. package/dist/judgement/domain/domain-tools.js +4 -2
  151. package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
  152. package/dist/judgement/runs/artifact/run-store.js +153 -28
  153. package/dist/judgement/runs/default-run-id.d.ts +3 -1
  154. package/dist/judgement/runs/default-run-id.js +15 -3
  155. package/dist/judgement/runs/drive-run.d.ts +3 -3
  156. package/dist/judgement/runs/drive-run.js +11 -7
  157. package/dist/judgement/runs/run-ledger.d.ts +21 -0
  158. package/dist/judgement/runs/run-ledger.js +143 -0
  159. package/dist/judgement/runs/runner-port.d.ts +1 -1
  160. package/dist/judgement/runs/runner-port.js +8 -2
  161. package/dist/judgement/runs/verification-sync.d.ts +4 -3
  162. package/dist/judgement/runs/verification-sync.js +27 -5
  163. package/dist/language/flows/affected-tools.js +2 -1
  164. package/dist/language/flows/annotate-notes/annotate.js +8 -3
  165. package/dist/language/flows/annotate-tools.js +1 -1
  166. package/dist/language/flows/change/affected.d.ts +8 -0
  167. package/dist/language/flows/change/affected.js +20 -7
  168. package/dist/language/flows/decision.js +19 -4
  169. package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
  170. package/dist/language/flows/fields/flow-secret-field.js +67 -5
  171. package/dist/language/flows/flow-anchor.d.ts +5 -0
  172. package/dist/language/flows/flow-anchor.js +4 -0
  173. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  174. package/dist/language/flows/flow-cross-step.js +9 -0
  175. package/dist/language/flows/flow-expect-grammar.js +15 -10
  176. package/dist/language/flows/flow-intent.js +6 -1
  177. package/dist/language/flows/flow-journey.d.ts +8 -0
  178. package/dist/language/flows/flow-journey.js +13 -0
  179. package/dist/language/flows/flow-lease.d.ts +27 -0
  180. package/dist/language/flows/flow-lease.js +59 -0
  181. package/dist/language/flows/flow-replay-run.d.ts +1 -87
  182. package/dist/language/flows/flow-replay-run.js +111 -310
  183. package/dist/language/flows/flow-replay.js +21 -19
  184. package/dist/language/flows/flow-replayable.d.ts +38 -0
  185. package/dist/language/flows/flow-replayable.js +179 -0
  186. package/dist/language/flows/flow-start-path.d.ts +108 -0
  187. package/dist/language/flows/flow-start-path.js +313 -0
  188. package/dist/language/flows/flow-step-runners.d.ts +23 -2
  189. package/dist/language/flows/flow-step-runners.js +191 -32
  190. package/dist/language/flows/flow-store-for-session.d.ts +8 -1
  191. package/dist/language/flows/flow-store-for-session.js +6 -0
  192. package/dist/language/flows/flow-tools.d.ts +2 -23
  193. package/dist/language/flows/flow-tools.js +63 -113
  194. package/dist/language/flows/flows.d.ts +4 -6
  195. package/dist/language/flows/flows.js +90 -15
  196. package/dist/language/flows/heal-run.d.ts +10 -4
  197. package/dist/language/flows/heal-run.js +26 -9
  198. package/dist/language/flows/recording/flake.d.ts +3 -27
  199. package/dist/language/flows/recording/flake.js +1 -1
  200. package/dist/language/flows/recording/recording-backtrack.js +1 -4
  201. package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
  202. package/dist/language/flows/recording/tape/recordings.js +9 -3
  203. package/dist/language/flows/replay.d.ts +25 -5
  204. package/dist/language/flows/replay.js +83 -16
  205. package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
  206. package/dist/language/flows/suite/coverage.d.ts +9 -0
  207. package/dist/language/flows/suite/coverage.js +12 -1
  208. package/dist/language/flows/suite-selection.d.ts +25 -0
  209. package/dist/language/flows/suite-selection.js +41 -9
  210. package/dist/machine/git-tracks.d.ts +6 -0
  211. package/dist/machine/git-tracks.js +36 -0
  212. package/dist/memory/cloud/background-sync.d.ts +25 -0
  213. package/dist/memory/cloud/background-sync.js +79 -0
  214. package/dist/memory/cloud/cloud-config.d.ts +12 -1
  215. package/dist/memory/cloud/cloud-config.js +39 -13
  216. package/dist/memory/cloud/cloud-sync.d.ts +3 -24
  217. package/dist/memory/cloud/cloud-sync.js +5 -2
  218. package/dist/memory/cloud/harness-config.d.ts +0 -7
  219. package/dist/memory/cloud/memory-scope.d.ts +19 -0
  220. package/dist/memory/cloud/memory-scope.js +48 -0
  221. package/dist/memory/cloud/sync-answer.d.ts +43 -0
  222. package/dist/memory/cloud/sync-answer.js +42 -0
  223. package/dist/memory/cloud/sync-cycle.d.ts +40 -10
  224. package/dist/memory/cloud/sync-cycle.js +97 -144
  225. package/dist/memory/cloud/sync-daemon.d.ts +50 -3
  226. package/dist/memory/cloud/sync-daemon.js +473 -83
  227. package/dist/memory/cloud/sync-describe.d.ts +28 -0
  228. package/dist/memory/cloud/sync-describe.js +92 -0
  229. package/dist/memory/cloud/sync-disk.d.ts +9 -1
  230. package/dist/memory/cloud/sync-disk.js +137 -33
  231. package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
  232. package/dist/memory/cloud/sync-flow-versions.js +42 -0
  233. package/dist/memory/impact/impact-store.js +32 -15
  234. package/dist/memory/intent/intent-shard.d.ts +41 -266
  235. package/dist/memory/intent/intent-tools.js +20 -61
  236. package/dist/memory/intent/record-request.d.ts +27 -0
  237. package/dist/memory/intent/record-request.js +88 -0
  238. package/dist/memory/intent/settled-words.js +9 -1
  239. package/dist/memory/journal/drive-flow.d.ts +3 -0
  240. package/dist/memory/journal/drive-flow.js +11 -41
  241. package/dist/memory/journal/envelope-store.js +1 -1
  242. package/dist/memory/journal/envelope.d.ts +6 -154
  243. package/dist/memory/journal/episode-log.d.ts +107 -0
  244. package/dist/memory/journal/episode-log.js +302 -0
  245. package/dist/memory/journal/journal-recorder.d.ts +7 -0
  246. package/dist/memory/journal/journal-recorder.js +22 -9
  247. package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
  248. package/dist/memory/journal/on-disk/ambient-file.js +1 -1
  249. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
  250. package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
  251. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
  252. package/dist/memory/journal/self-observation.js +3 -1
  253. package/dist/memory/journal/session-end.js +23 -9
  254. package/dist/memory/journal/session-journal.js +39 -20
  255. package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
  256. package/dist/memory/project/artifact-root-resolver.js +68 -23
  257. package/dist/memory/project/artifact-root.d.ts +17 -3
  258. package/dist/memory/project/artifact-root.js +47 -9
  259. package/dist/memory/project/fs/fs-port.d.ts +5 -0
  260. package/dist/memory/project/fs/fs-port.js +13 -0
  261. package/dist/memory/project/learned-routes.d.ts +3 -4
  262. package/dist/memory/project/learned-routes.js +3 -11
  263. package/dist/memory/project/link-directory.js +5 -2
  264. package/dist/memory/project/root-claims.d.ts +3 -1
  265. package/dist/memory/project/root-claims.js +41 -7
  266. package/dist/memory/project/session-root.d.ts +1 -1
  267. package/dist/memory/project/session-root.js +1 -15
  268. package/dist/memory/project/sync-status.js +5 -6
  269. package/dist/memory/recall/memory-tools.js +2 -2
  270. package/dist/memory/recall/platform-memory.d.ts +3 -1
  271. package/dist/memory/recall/platform-memory.js +9 -1
  272. package/dist/memory/recall/project-memory.js +15 -19
  273. package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
  274. package/dist/portal/bridge/auth-failure-reason.js +19 -0
  275. package/dist/portal/bridge/bridge.d.ts +19 -4
  276. package/dist/portal/bridge/bridge.js +150 -177
  277. package/dist/portal/bridge/harness-request.d.ts +15 -0
  278. package/dist/portal/bridge/harness-request.js +19 -0
  279. package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
  280. package/dist/portal/bridge/origin/origin-follow.js +110 -0
  281. package/dist/portal/bridge/pairing-token.d.ts +6 -0
  282. package/dist/portal/bridge/pairing-token.js +47 -16
  283. package/dist/portal/bridge/protocol-skew.d.ts +27 -2
  284. package/dist/portal/bridge/protocol-skew.js +66 -2
  285. package/dist/portal/bridge/session-nonces.d.ts +46 -0
  286. package/dist/portal/bridge/session-nonces.js +72 -0
  287. package/dist/portal/input/viewport-tools.d.ts +31 -0
  288. package/dist/portal/input/viewport-tools.js +106 -15
  289. package/dist/portal/pool/bounded-close.d.ts +23 -0
  290. package/dist/portal/pool/bounded-close.js +38 -0
  291. package/dist/portal/pool/browser-pool.d.ts +44 -39
  292. package/dist/portal/pool/browser-pool.js +247 -95
  293. package/dist/portal/pool/context-permissions.d.ts +9 -1
  294. package/dist/portal/pool/context-permissions.js +26 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +37 -0
  296. package/dist/portal/pool/lease-emulation.js +29 -0
  297. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  298. package/dist/portal/pool/playwright-launcher.js +74 -7
  299. package/dist/portal/pool/pool-contract.d.ts +23 -1
  300. package/dist/portal/pool/pool-lease.d.ts +128 -0
  301. package/dist/portal/pool/pool-lease.js +49 -0
  302. package/dist/portal/pool/zero-install.d.ts +8 -1
  303. package/dist/portal/pool/zero-install.js +24 -2
  304. package/dist/portal/production/production-activation.d.ts +136 -0
  305. package/dist/portal/production/production-activation.js +173 -0
  306. package/dist/portal/production/production-drive.d.ts +52 -0
  307. package/dist/portal/production/production-drive.js +301 -0
  308. package/dist/portal/production/production-hello.d.ts +23 -0
  309. package/dist/portal/production/production-hello.js +23 -0
  310. package/dist/portal/realm/web-realm.js +42 -15
  311. package/dist/portal/session/control-envelope.d.ts +1 -1
  312. package/dist/portal/session/dev-server/served-document.d.ts +0 -5
  313. package/dist/portal/session/dev-server/served-document.js +6 -8
  314. package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
  315. package/dist/portal/session/first-run-wiring.d.ts +3 -36
  316. package/dist/portal/session/first-run-wiring.js +10 -103
  317. package/dist/portal/session/live-control-tools.js +61 -19
  318. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  319. package/dist/portal/session/no-session-next-action.js +40 -15
  320. package/dist/portal/session/no-session-watch.d.ts +7 -0
  321. package/dist/portal/session/no-session-watch.js +67 -23
  322. package/dist/portal/session/output-budget.d.ts +10 -0
  323. package/dist/portal/session/output-budget.js +16 -0
  324. package/dist/portal/session/presence/session-lease.d.ts +2 -0
  325. package/dist/portal/session/presence/session-lease.js +8 -0
  326. package/dist/portal/session/recorded-gaps.d.ts +15 -11
  327. package/dist/portal/session/recorded-gaps.js +39 -18
  328. package/dist/portal/session/serving-directory.d.ts +24 -2
  329. package/dist/portal/session/serving-directory.js +93 -5
  330. package/dist/portal/session/session-info.d.ts +26 -0
  331. package/dist/portal/session/session-info.js +20 -0
  332. package/dist/portal/session/session-manager.d.ts +35 -3
  333. package/dist/portal/session/session-manager.js +69 -14
  334. package/dist/portal/session/session-successor.js +1 -9
  335. package/dist/portal/session/session-tools.js +22 -4
  336. package/dist/portal/session/session.d.ts +10 -10
  337. package/dist/portal/session/session.js +37 -29
  338. package/dist/portal/session/zero-install-first-call.d.ts +7 -2
  339. package/dist/portal/session/zero-install-first-call.js +43 -20
  340. package/dist/start-wiring.d.ts +2 -2
  341. package/dist/start-wiring.js +17 -3
  342. package/dist/status-payload.d.ts +11 -1
  343. package/dist/status-payload.js +7 -1
  344. package/dist/surface/agent-presence-grace.d.ts +11 -0
  345. package/dist/surface/agent-presence-grace.js +34 -0
  346. package/dist/surface/http-server.d.ts +12 -1
  347. package/dist/surface/http-server.js +64 -15
  348. package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
  349. package/dist/surface/mcp/faults/mcp-outage.js +19 -1
  350. package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
  351. package/dist/surface/mcp/mcp-proxy.js +194 -267
  352. package/dist/surface/mcp/mcp.d.ts +20 -3
  353. package/dist/surface/mcp/mcp.js +88 -25
  354. package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
  355. package/dist/surface/mcp/proxy/drive-calls.js +114 -5
  356. package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
  357. package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
  358. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
  359. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
  360. package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
  361. package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
  362. package/dist/surface/mcp/server-instructions.js +35 -16
  363. package/dist/surface/mcp/shared-params.d.ts +8 -0
  364. package/dist/surface/mcp/shared-params.js +15 -1
  365. package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
  366. package/dist/surface/mcp/sse-frame-parser.js +18 -0
  367. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  368. package/dist/surface/mcp/surface-vocabulary.js +13 -0
  369. package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
  370. package/dist/surface/mcp/tool-catalog-cache.js +14 -4
  371. package/dist/surface/tools/account-tool.d.ts +4 -3
  372. package/dist/surface/tools/account-tool.js +20 -31
  373. package/dist/surface/tools/act/act-danger.d.ts +2 -0
  374. package/dist/surface/tools/act/act-danger.js +41 -14
  375. package/dist/surface/tools/act/act-observation.d.ts +11 -0
  376. package/dist/surface/tools/act/act-observation.js +33 -0
  377. package/dist/surface/tools/act/act-preflight.js +10 -3
  378. package/dist/surface/tools/act/act-sequence-retry.js +2 -1
  379. package/dist/surface/tools/act/act-target.js +19 -5
  380. package/dist/surface/tools/act/durable.js +1 -1
  381. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
  382. package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
  383. package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
  384. package/dist/surface/tools/act/unsettled-find.js +71 -0
  385. package/dist/surface/tools/act-sequence-tool.js +53 -24
  386. package/dist/surface/tools/act-tools.d.ts +2 -3
  387. package/dist/surface/tools/act-tools.js +49 -47
  388. package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
  389. package/dist/surface/tools/agent/for-the-agent.js +41 -3
  390. package/dist/surface/tools/assert/assert-grade.js +8 -3
  391. package/dist/surface/tools/assert/assert-verdict.js +4 -2
  392. package/dist/surface/tools/browser-tools.js +7 -4
  393. package/dist/surface/tools/chat-drives.js +50 -25
  394. package/dist/surface/tools/coverage-tools.js +1 -1
  395. package/dist/surface/tools/dynamic-tools.d.ts +8 -0
  396. package/dist/surface/tools/dynamic-tools.js +89 -34
  397. package/dist/surface/tools/empty-session-list.d.ts +18 -25
  398. package/dist/surface/tools/empty-session-list.js +67 -38
  399. package/dist/surface/tools/error-recovery.d.ts +17 -7
  400. package/dist/surface/tools/error-recovery.js +53 -20
  401. package/dist/surface/tools/explore-tools.d.ts +10 -0
  402. package/dist/surface/tools/explore-tools.js +72 -19
  403. package/dist/surface/tools/feedback-tools.js +23 -2
  404. package/dist/surface/tools/gap-novelty.d.ts +6 -0
  405. package/dist/surface/tools/gap-novelty.js +9 -0
  406. package/dist/surface/tools/gaps/capability-absences.js +4 -3
  407. package/dist/surface/tools/harness-explore.d.ts +1 -1
  408. package/dist/surface/tools/harness-explore.js +6 -4
  409. package/dist/surface/tools/harness-gate.d.ts +43 -21
  410. package/dist/surface/tools/harness-gate.js +55 -81
  411. package/dist/surface/tools/harness-script.d.ts +2 -0
  412. package/dist/surface/tools/harness-script.js +11 -6
  413. package/dist/surface/tools/harness-toolset.js +3 -4
  414. package/dist/surface/tools/invoke-tool.d.ts +0 -8
  415. package/dist/surface/tools/invoke-tool.js +164 -178
  416. package/dist/surface/tools/lease-hint.d.ts +9 -0
  417. package/dist/surface/tools/lease-hint.js +22 -3
  418. package/dist/surface/tools/lease-permissions.d.ts +1 -1
  419. package/dist/surface/tools/lease-readiness.d.ts +55 -0
  420. package/dist/surface/tools/lease-readiness.js +150 -2
  421. package/dist/surface/tools/lease-tools.d.ts +3 -75
  422. package/dist/surface/tools/lease-tools.js +124 -188
  423. package/dist/surface/tools/map-tools.d.ts +20 -0
  424. package/dist/surface/tools/map-tools.js +145 -0
  425. package/dist/surface/tools/merge-tools.js +11 -7
  426. package/dist/surface/tools/next-step.js +6 -3
  427. package/dist/surface/tools/observe-tools.js +46 -35
  428. package/dist/surface/tools/platform-moment.d.ts +3 -12
  429. package/dist/surface/tools/platform-moment.js +12 -24
  430. package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
  431. package/dist/surface/tools/read/complete-snapshot.js +13 -2
  432. package/dist/surface/tools/read/query-paginate.js +6 -0
  433. package/dist/surface/tools/read/query-shape.d.ts +9 -0
  434. package/dist/surface/tools/read/query-shape.js +15 -0
  435. package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
  436. package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
  437. package/dist/surface/tools/read-tools.js +5 -5
  438. package/dist/surface/tools/real-input-attempt.d.ts +11 -0
  439. package/dist/surface/tools/real-input-attempt.js +206 -38
  440. package/dist/surface/tools/reconcile-tools.js +21 -8
  441. package/dist/surface/tools/reticle-result.d.ts +3 -0
  442. package/dist/surface/tools/reticle-result.js +8 -1
  443. package/dist/surface/tools/run-report.d.ts +22 -0
  444. package/dist/surface/tools/run-report.js +141 -0
  445. package/dist/surface/tools/session-ports.d.ts +57 -0
  446. package/dist/surface/tools/session-ports.js +102 -0
  447. package/dist/surface/tools/tool-kit.d.ts +58 -4
  448. package/dist/surface/tools/tool-kit.js +140 -6
  449. package/dist/surface/tools/tool-surface.d.ts +2 -0
  450. package/dist/surface/tools/tool-surface.js +45 -0
  451. package/dist/surface/tools/tools.js +49 -87
  452. package/dist/telemetry/automation-hint.d.ts +5 -0
  453. package/dist/telemetry/automation-hint.js +7 -0
  454. package/dist/telemetry/cli-telemetry.d.ts +22 -0
  455. package/dist/telemetry/cli-telemetry.js +80 -11
  456. package/dist/telemetry/daemon-telemetry.d.ts +3 -1
  457. package/dist/telemetry/daemon-telemetry.js +9 -2
  458. package/dist/telemetry/feedback-cli.d.ts +1 -1
  459. package/dist/telemetry/feedback-cli.js +8 -3
  460. package/dist/telemetry/feedback.d.ts +1 -1
  461. package/dist/telemetry/feedback.js +51 -4
  462. package/dist/telemetry/harness-run.d.ts +3 -3
  463. package/dist/telemetry/harness-run.js +18 -7
  464. package/dist/telemetry/hud-events.d.ts +1 -1
  465. package/dist/telemetry/hud-metrics.d.ts +6 -3
  466. package/dist/telemetry/hud-metrics.js +20 -8
  467. package/dist/telemetry/install-source.js +1 -1
  468. package/dist/telemetry/mcp-connection.d.ts +1 -1
  469. package/dist/telemetry/mcp-connection.js +21 -2
  470. package/dist/telemetry/onboarding-firsts.d.ts +6 -1
  471. package/dist/telemetry/onboarding-firsts.js +41 -1
  472. package/dist/telemetry/proxy-telemetry.d.ts +42 -0
  473. package/dist/telemetry/proxy-telemetry.js +70 -1
  474. package/dist/telemetry/session-metrics.d.ts +1 -3
  475. package/dist/telemetry/session-metrics.js +16 -12
  476. package/dist/telemetry/telemetry.d.ts +9 -1
  477. package/dist/telemetry/telemetry.js +16 -1
  478. package/dist/telemetry/verification-of.d.ts +3 -1
  479. package/dist/telemetry/verification-of.js +7 -1
  480. package/dist/wire-journal.js +12 -2
  481. package/docs/README.md +4 -2
  482. package/docs/actions.mdx +7 -6
  483. package/docs/adapters.md +2 -1
  484. package/docs/agent-cheatsheet.md +19 -5
  485. package/docs/architecture.md +13 -10
  486. package/docs/autodrive.md +6 -5
  487. package/docs/benchmarks.md +2 -2
  488. package/docs/capabilities.mdx +1 -1
  489. package/docs/cli/affected.mdx +1 -1
  490. package/docs/cli/cloud.mdx +6 -6
  491. package/docs/cli/doctor.mdx +3 -1
  492. package/docs/cli/feedback.mdx +11 -7
  493. package/docs/cli/gate.mdx +1 -1
  494. package/docs/cli/help.mdx +1 -1
  495. package/docs/cli/init.mdx +20 -16
  496. package/docs/cli/mcp.mdx +5 -2
  497. package/docs/cli/open.mdx +5 -2
  498. package/docs/cli/report.mdx +1 -1
  499. package/docs/cli/restart.mdx +1 -1
  500. package/docs/cli/rollback.mdx +4 -4
  501. package/docs/cli/serve.mdx +7 -5
  502. package/docs/cli/setup.mdx +1 -1
  503. package/docs/cli/status.mdx +6 -2
  504. package/docs/cli/stop.mdx +3 -3
  505. package/docs/cli/telemetry.mdx +5 -1
  506. package/docs/cli/try.mdx +8 -8
  507. package/docs/cli/update.mdx +8 -5
  508. package/docs/cli/verify.mdx +12 -5
  509. package/docs/cli/version.mdx +1 -1
  510. package/docs/cli.mdx +10 -6
  511. package/docs/debugging.md +1 -1
  512. package/docs/deploy-checks.md +8 -6
  513. package/docs/desktop.mdx +14 -18
  514. package/docs/enterprise.md +6 -6
  515. package/docs/environment.mdx +5 -3
  516. package/docs/faq.mdx +9 -6
  517. package/docs/first-drive.md +19 -9
  518. package/docs/flows.md +3 -4
  519. package/docs/for-agents.md +1 -1
  520. package/docs/frameworks.mdx +34 -11
  521. package/docs/getting-started.md +46 -29
  522. package/docs/http-transport.md +2 -2
  523. package/docs/human-control.md +8 -8
  524. package/docs/index.mdx +2 -2
  525. package/docs/install-agentic.mdx +18 -19
  526. package/docs/install-manual.mdx +8 -4
  527. package/docs/integration-patterns.md +5 -5
  528. package/docs/multi-agent-testing.md +7 -5
  529. package/docs/packages/browser.mdx +34 -2
  530. package/docs/packages/core.mdx +2 -1
  531. package/docs/packages/electron.mdx +15 -3
  532. package/docs/packages/eslint-plugin.mdx +1 -1
  533. package/docs/packages/next.mdx +17 -5
  534. package/docs/packages/react.mdx +8 -1
  535. package/docs/packages/server.mdx +3 -3
  536. package/docs/packages/tauri.mdx +3 -3
  537. package/docs/packages/vite-plugin.mdx +1 -1
  538. package/docs/packages.mdx +19 -6
  539. package/docs/platform-integration.md +12 -4
  540. package/docs/predicates.mdx +18 -7
  541. package/docs/production-mode.md +108 -0
  542. package/docs/prompt-capture.md +99 -0
  543. package/docs/quickstart.mdx +14 -12
  544. package/docs/recipes.mdx +9 -8
  545. package/docs/required-checks.md +3 -2
  546. package/docs/skill-file.mdx +4 -3
  547. package/docs/skill-setup.md +2 -2
  548. package/docs/skill.md +62 -70
  549. package/docs/state-files.md +1 -0
  550. package/docs/state-management.mdx +11 -9
  551. package/docs/system-map.md +14 -13
  552. package/docs/telemetry.md +32 -24
  553. package/docs/testing.md +9 -5
  554. package/docs/tools/act-and-wait.mdx +17 -13
  555. package/docs/tools/act-sequence.mdx +40 -66
  556. package/docs/tools/act.mdx +7 -5
  557. package/docs/tools/assert.mdx +1 -1
  558. package/docs/tools/look.mdx +1 -1
  559. package/docs/tools/navigate.mdx +1 -1
  560. package/docs/tools/network.mdx +13 -11
  561. package/docs/tools/overview.mdx +3 -3
  562. package/docs/tools/query.mdx +3 -3
  563. package/docs/tools/session-and-feedback.mdx +9 -6
  564. package/docs/tools/sessions.mdx +7 -0
  565. package/docs/tools/snapshot.mdx +9 -7
  566. package/docs/tools/tools-and-run.mdx +11 -10
  567. package/docs/tools/verify.mdx +1 -1
  568. package/docs/troubleshooting.mdx +21 -9
  569. package/docs/usage.md +58 -43
  570. package/docs/use-cases.mdx +2 -2
  571. package/docs/vs-chrome-devtools-mcp.mdx +5 -5
  572. package/docs/vs-playwright-mcp.mdx +5 -4
  573. package/docs/what-is-recorded.md +12 -7
  574. package/docs/what-is-reticle.mdx +2 -2
  575. package/docs/why-reticle.mdx +1 -1
  576. package/package.json +9 -12
  577. package/dist/command/dev/stale-issue-guard.d.ts +0 -34
  578. package/dist/command/dev/stale-issue-guard.js +0 -69
@@ -9,28 +9,16 @@
9
9
  * The browser is injected via a `Launcher` so all lifecycle logic is testable without real Chromium;
10
10
  * the thin Playwright adapter that satisfies these interfaces lives separately.
11
11
  */
12
- import { unreachableUrlIn } from '@reticlehq/core';
12
+ import { LEASE_CLOSE_TIMEOUT_MS, unreachableUrlIn } from '@reticlehq/core';
13
+ import { log } from '../../log.js';
14
+ import { closeLeaseTab, LEASE_CLOSE_TIMED_OUT, settledWithin } from './bounded-close.js';
13
15
  import { seedStorageInto, targetOriginOf } from './storage-seed.js';
14
16
  import { installLeaseMarker, leaseMarkerOf } from './lease-marker-stamp.js';
15
- import { grantLeasePermissions, NOTIFICATION_PERMISSION_READ, NOTIFICATION_READ_TIMEOUT_MS, } from './context-permissions.js';
16
- /** Default lease time-to-live: a lease untouched for this long is presumed orphaned and reclaimed. */
17
- export const DEFAULT_LEASE_TTL_MS = 5 * 60_000;
18
- /** Default cap on how long a lease's initial navigation may take before it fails (frees the slot). */
19
- const DEFAULT_NAV_TIMEOUT_MS = 30_000;
20
- /**
21
- * How many reaped lease ids to remember. Only the most recent departure is ever asked about, so
22
- * this is slack for a burst sweep, not a history.
23
- */
24
- const MAX_REMEMBERED_REAPED_LEASES = 64;
25
- /** What a headed lease is refused with where no window can be opened. */
26
- export const NO_HEADED_BROWSER = 'no headed browser can be opened here';
27
- /**
28
- * What an acquire that asked for an init script is refused with when the page cannot run one.
29
- *
30
- * A refusal rather than a lease: the caller asked for a stub of the bindings a desktop shell
31
- * injects, and a tab opened without it would throw in the app's view and read as a broken app.
32
- */
33
- const INIT_SCRIPT_UNSUPPORTED = 'Init script failed: this page does not support init scripts, so the requested stub would not be there';
17
+ import { grantLeasePermissions, readNotificationPermission } from './context-permissions.js';
18
+ import { readViewport } from './lease-emulation.js';
19
+ import { NotHtmlDocumentError, READER_NOT_HTML } from './zero-install.js';
20
+ import { DEFAULT_LEASE_TTL_MS, DEFAULT_NAV_TIMEOUT_MS, INIT_SCRIPT_UNSUPPORTED, MAX_REMEMBERED_REAPED_LEASES, NO_HEADED_BROWSER, ORPHANED_LEASE_GRACE_MS, pageLoading, PoolAtCapacityError, } from './pool-lease.js';
21
+ export { DEFAULT_LEASE_TTL_MS, NO_HEADED_BROWSER, ORPHANED_LEASE_GRACE_MS, PoolAtCapacityError, } from './pool-lease.js';
34
22
  /**
35
23
  * Owns a headless browser (and, when asked, a headed one) and leases isolated contexts out of them,
36
24
  * capped at `maxContexts` across both.
@@ -74,8 +62,9 @@ export class BrowserPool {
74
62
  /** FIFO of acquires waiting for a slot; each resolves when one frees. */
75
63
  #waiters = [];
76
64
  constructor(launch, opts) {
77
- if (opts.maxContexts < 1)
65
+ if (opts.maxContexts < 1) {
78
66
  throw new Error('maxContexts must be >= 1');
67
+ }
79
68
  this.#headless = { launch, headed: false };
80
69
  this.#headed = { launch: opts.launchHeaded, headed: true };
81
70
  this.#max = opts.maxContexts;
@@ -116,11 +105,13 @@ export class BrowserPool {
116
105
  * Prefers the alias the agent was given, when the app registered under its own name.
117
106
  */
118
107
  leaseIdOnOrigin(origin, owner) {
119
- if (owner === undefined)
108
+ if (owner === undefined) {
120
109
  return undefined;
110
+ }
121
111
  for (const [leaseId, lease] of this.#active) {
122
- if (lease.owner !== owner)
112
+ if (lease.owner !== owner) {
123
113
  continue;
114
+ }
124
115
  let leaseOrigin;
125
116
  try {
126
117
  leaseOrigin = new URL(lease.url).origin;
@@ -128,11 +119,13 @@ export class BrowserPool {
128
119
  catch {
129
120
  continue;
130
121
  }
131
- if (leaseOrigin !== origin)
122
+ if (leaseOrigin !== origin) {
132
123
  continue;
124
+ }
133
125
  for (const [alias, id] of this.#aliases) {
134
- if (id === leaseId)
126
+ if (id === leaseId) {
135
127
  return alias;
128
+ }
136
129
  }
137
130
  return leaseId;
138
131
  }
@@ -161,10 +154,12 @@ export class BrowserPool {
161
154
  * that would have read a computed value.
162
155
  */
163
156
  alias(registeredId, leaseId) {
164
- if (registeredId === leaseId)
157
+ if (registeredId === leaseId) {
165
158
  return;
166
- if (!this.#active.has(leaseId))
159
+ }
160
+ if (!this.#active.has(leaseId)) {
167
161
  return;
162
+ }
168
163
  this.#aliases.set(registeredId, leaseId);
169
164
  }
170
165
  /**
@@ -202,8 +197,35 @@ export class BrowserPool {
202
197
  /** Mark a lease as still in use (called on agent activity) so the reaper doesn't reclaim it. */
203
198
  touch(sessionId) {
204
199
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
205
- if (lease !== undefined)
206
- lease.touchedAt = this.#now();
200
+ if (lease === undefined) {
201
+ return;
202
+ }
203
+ lease.touchedAt = this.#now();
204
+ delete lease.detachedAt;
205
+ }
206
+ /**
207
+ * The MCP connection `owner` closed. Its leases go after {@link ORPHANED_LEASE_GRACE_MS} unless it
208
+ * touches them again: a reconnect is a new owner, so nothing else would ever hand them back, and
209
+ * each one held a browser window open for the full TTL.
210
+ */
211
+ detachOwner(owner) {
212
+ const now = this.#now();
213
+ for (const lease of this.#active.values()) {
214
+ if (lease.owner === owner) {
215
+ lease.detachedAt = now;
216
+ }
217
+ }
218
+ }
219
+ /**
220
+ * The MCP connection `owner` came back: a proxy that reconnects keeps its id. Its leases stop
221
+ * their grace, because the owner they wait for is here again.
222
+ */
223
+ reattachOwner(owner) {
224
+ for (const lease of this.#active.values()) {
225
+ if (lease.owner === owner) {
226
+ delete lease.detachedAt;
227
+ }
228
+ }
207
229
  }
208
230
  /**
209
231
  * Reclaim leases untouched for longer than the TTL — a hung/crashed agent never holds a slot
@@ -221,8 +243,9 @@ export class BrowserPool {
221
243
  */
222
244
  async screenshotLease(sessionId, opts = {}) {
223
245
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
224
- if (lease === undefined || lease.page.screenshot === undefined)
246
+ if (lease === undefined || lease.page.screenshot === undefined) {
225
247
  return undefined;
248
+ }
226
249
  this.touch(sessionId);
227
250
  try {
228
251
  return await lease.page.screenshot(opts);
@@ -244,8 +267,9 @@ export class BrowserPool {
244
267
  */
245
268
  async gotoLease(sessionId, url, timeoutMs) {
246
269
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
247
- if (lease === undefined)
270
+ if (lease === undefined) {
248
271
  return false;
272
+ }
249
273
  this.touch(sessionId);
250
274
  try {
251
275
  await lease.page.goto(url, timeoutMs === undefined ? {} : { timeoutMs });
@@ -264,8 +288,9 @@ export class BrowserPool {
264
288
  */
265
289
  async hoverLease(sessionId, x, y) {
266
290
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
267
- if (lease === undefined || lease.page.hover === undefined)
291
+ if (lease === undefined || lease.page.hover === undefined) {
268
292
  return false;
293
+ }
269
294
  this.touch(sessionId);
270
295
  try {
271
296
  await lease.page.hover(x, y);
@@ -275,6 +300,46 @@ export class BrowserPool {
275
300
  return false;
276
301
  }
277
302
  }
303
+ /** Set a live lease's media features, or false when this is not a lease or its page cannot. */
304
+ async emulateLease(sessionId, media) {
305
+ const lease = this.#active.get(this.#leaseIdOf(sessionId));
306
+ if (lease?.page.emulateMedia === undefined) {
307
+ return false;
308
+ }
309
+ this.touch(sessionId);
310
+ try {
311
+ await lease.page.emulateMedia(media);
312
+ return true;
313
+ }
314
+ catch {
315
+ return false;
316
+ }
317
+ }
318
+ /** Whether this lease's page is still loading. False for a non-lease, or a page that cannot say. */
319
+ async leaseLoading(sessionId) {
320
+ const lease = this.#active.get(this.#leaseIdOf(sessionId));
321
+ if (lease === undefined) {
322
+ return false;
323
+ }
324
+ return pageLoading(lease.page).catch(() => false);
325
+ }
326
+ /** Whether this session is a lease whose page has a real keyboard. */
327
+ hasKeyboard(sessionId) {
328
+ return this.#active.get(this.#leaseIdOf(sessionId))?.page.press !== undefined;
329
+ }
330
+ /**
331
+ * Press a key with the leased page's real keyboard, or undefined when this session is not a lease
332
+ * or its page has no keyboard. A throw is the caller's to read: a key that went down and did not
333
+ * come up must not be answered with a second, synthetic press.
334
+ */
335
+ async pressLease(sessionId, args) {
336
+ const lease = this.#active.get(this.#leaseIdOf(sessionId));
337
+ if (lease?.page.press === undefined) {
338
+ return undefined;
339
+ }
340
+ this.touch(sessionId);
341
+ return lease.page.press(args);
342
+ }
278
343
  /**
279
344
  * Install network mocks on a leased page, or false when this session is not a lease or cannot
280
345
  * intercept. Same optional-capability contract as screenshotLease: absence must read as "could not
@@ -291,8 +356,9 @@ export class BrowserPool {
291
356
  */
292
357
  async setViewportLease(sessionId, size) {
293
358
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
294
- if (lease === undefined || lease.page.setViewport === undefined)
359
+ if (lease === undefined || lease.page.setViewport === undefined) {
295
360
  return false;
361
+ }
296
362
  this.touch(sessionId);
297
363
  try {
298
364
  await lease.page.setViewport(size);
@@ -302,19 +368,40 @@ export class BrowserPool {
302
368
  return false;
303
369
  }
304
370
  }
371
+ /** The size a leased page reports for itself, or undefined when it cannot be read. */
372
+ async viewportOfLease(sessionId) {
373
+ const lease = this.#active.get(this.#leaseIdOf(sessionId));
374
+ return lease === undefined ? undefined : readViewport(lease.page);
375
+ }
305
376
  async setMocksLease(sessionId, rules) {
306
377
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
307
- if (lease === undefined || lease.page.installMocks === undefined)
378
+ if (lease === undefined || lease.page.installMocks === undefined) {
308
379
  return false;
380
+ }
309
381
  this.touch(sessionId);
310
382
  try {
311
383
  await lease.page.installMocks(rules);
384
+ // Mocks registered later are asked first, and one that lets a request through would skip
385
+ // the production guard. Re-asserting the guard puts it back in front.
386
+ if (lease.guard !== undefined) {
387
+ await lease.page.guardRequests?.(lease.guard);
388
+ }
312
389
  return true;
313
390
  }
314
391
  catch {
315
392
  return false;
316
393
  }
317
394
  }
395
+ /** Guard a leased page's requests, or false when this is not a lease or its page cannot. */
396
+ async guardLease(sessionId, decide) {
397
+ const lease = this.#active.get(this.#leaseIdOf(sessionId));
398
+ if (lease === undefined || lease.page.guardRequests === undefined) {
399
+ return false;
400
+ }
401
+ await lease.page.guardRequests(decide);
402
+ lease.guard = decide;
403
+ return true;
404
+ }
318
405
  /** What a lease was granted when it opened, or undefined for a session that is not a lease. */
319
406
  permissionsOf(sessionId) {
320
407
  return this.#active.get(this.#leaseIdOf(sessionId))?.permissions;
@@ -344,35 +431,23 @@ export class BrowserPool {
344
431
  */
345
432
  async notificationPermission(sessionId) {
346
433
  const lease = this.#active.get(this.#leaseIdOf(sessionId));
347
- if (lease === undefined || lease.page.evaluate === undefined)
348
- return undefined;
349
- let timer;
350
- const gaveUp = new Promise((resolve) => {
351
- timer = setTimeout(() => resolve(undefined), NOTIFICATION_READ_TIMEOUT_MS);
352
- });
353
- try {
354
- const value = await Promise.race([lease.page.evaluate(NOTIFICATION_PERMISSION_READ), gaveUp]);
355
- return 'string' === typeof value ? value : undefined;
356
- }
357
- catch {
358
- return undefined;
359
- }
360
- finally {
361
- clearTimeout(timer);
362
- }
434
+ return lease === undefined ? undefined : readNotificationPermission(lease.page);
363
435
  }
364
436
  async sweepExpired() {
365
437
  const now = this.#now();
366
438
  const expired = [...this.#active.entries()]
367
- .filter(([, lease]) => now - lease.touchedAt > this.#ttl)
439
+ .filter(([, lease]) => now - lease.touchedAt > this.#ttl ||
440
+ (lease.detachedAt !== undefined && now - lease.detachedAt >= ORPHANED_LEASE_GRACE_MS))
368
441
  .map(([sessionId]) => sessionId);
369
- for (const sessionId of expired)
442
+ for (const sessionId of expired) {
370
443
  await this.#release(sessionId);
444
+ }
371
445
  this.#reapedLeases += expired.length;
372
446
  for (const sessionId of expired) {
373
447
  this.#reapedLeaseIds.push(sessionId);
374
- if (this.#reapedLeaseIds.length > MAX_REMEMBERED_REAPED_LEASES)
448
+ if (this.#reapedLeaseIds.length > MAX_REMEMBERED_REAPED_LEASES) {
375
449
  this.#reapedLeaseIds.shift();
450
+ }
376
451
  }
377
452
  return expired;
378
453
  }
@@ -404,27 +479,43 @@ export class BrowserPool {
404
479
  }
405
480
  /**
406
481
  * Lease an isolated context, navigate it to `url`, and return a handle. If the pool is at capacity,
407
- * waits FIFO until a slot frees (or until `signal` aborts, if provided).
482
+ * waits FIFO until a slot frees (or until `signal` aborts, if provided), unless `whenFull` is
483
+ * `'refuse'`: then it throws {@link PoolAtCapacityError} at once and claims nothing.
408
484
  */
409
485
  async acquire(url, opts = {}) {
410
- if (this.#closed)
486
+ if (this.#closed) {
411
487
  throw new Error('browser pool is shut down');
488
+ }
412
489
  const slot = true === opts.headed ? this.#headed : this.#headless;
413
490
  // Refused before a slot is claimed, so a refusal never holds one.
414
- if (slot.launch === undefined)
491
+ if (slot.launch === undefined) {
415
492
  throw new Error(NO_HEADED_BROWSER);
493
+ }
416
494
  // #waitForSlot claims the slot synchronously (bumps #occupied) before returning, so the cap holds
417
- // even when many acquires race through the gate in the same tick.
418
- await this.#waitForSlot(opts.signal);
495
+ // even when many acquires race through the gate in the same tick. A refusing acquire claims the
496
+ // same way, or claims nothing and says so.
497
+ if ('refuse' === opts.whenFull) {
498
+ if (!this.#tryClaim()) {
499
+ throw new PoolAtCapacityError(this.#max);
500
+ }
501
+ }
502
+ else {
503
+ await this.#waitForSlot(opts.signal);
504
+ }
419
505
  const sessionId = opts.sessionId ?? this.#genId();
420
506
  let context;
421
507
  let pending;
422
508
  let pendingDialogMessage;
423
509
  try {
424
510
  const browser = await this.#ensureBrowser(slot);
425
- context = await browser.newContext(true === opts.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : undefined);
426
- if (opts.permissions !== undefined)
511
+ const contextOpts = {
512
+ ...(true === opts.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
513
+ ...opts.emulate,
514
+ };
515
+ context = await browser.newContext(0 < Object.keys(contextOpts).length ? contextOpts : undefined);
516
+ if (opts.permissions !== undefined) {
427
517
  await grantLeasePermissions(context, opts.permissions, url);
518
+ }
428
519
  const page = await context.newPage();
429
520
  // A caller-supplied init script is a stub of the bindings a desktop shell injects, and it has
430
521
  // to exist before the app's first module runs. Installed here — before goto — so the very
@@ -447,23 +538,28 @@ export class BrowserPool {
447
538
  // retry backoff stretches out.
448
539
  page.onConsole?.((text) => {
449
540
  const dialled = unreachableUrlIn(text);
450
- if (dialled === undefined)
541
+ if (dialled === undefined) {
451
542
  return;
543
+ }
452
544
  const active = this.#active.get(sessionId);
453
- if (active !== undefined)
545
+ if (active !== undefined) {
454
546
  active.dialFailureUrl = dialled;
455
- else
456
- pending = dialled; // logged during goto, before the lease is registered below
547
+ }
548
+ else {
549
+ pending = dialled;
550
+ } // logged during goto, before the lease is registered below
457
551
  });
458
552
  // Same "listen before goto" reasoning as onConsole above: a dialog opened during the initial
459
553
  // navigation (an app that confirms something in an onload handler) must still be dismissed, not
460
554
  // just ones opened after the lease is registered.
461
555
  page.onDialog?.((dialog) => {
462
556
  const active = this.#active.get(sessionId);
463
- if (active !== undefined)
557
+ if (active !== undefined) {
464
558
  active.lastDialogMessage = dialog.message;
465
- else
559
+ }
560
+ else {
466
561
  pendingDialogMessage = dialog.message;
562
+ }
467
563
  void dialog.dismiss();
468
564
  });
469
565
  const marker = leaseMarkerOf(url);
@@ -491,8 +587,9 @@ export class BrowserPool {
491
587
  // #occupied. Registering the lease now would resurrect a dead entry against a crashed browser with
492
588
  // the slot count out of sync (drifting below #active.size, eventually exceeding the cap). If we're
493
589
  // no longer the live browser, bail — the catch below closes the context and returns the slot.
494
- if (slot.browser !== browser)
590
+ if (slot.browser !== browser) {
495
591
  throw new Error('browser crashed during navigation');
592
+ }
496
593
  // Foreground it: a page opened beside other tabs can start hidden (#1351). Best-effort — a tab
497
594
  // that stays hidden is reported by name at the lease, which failing here would lose.
498
595
  await page.bringToFront?.().catch(() => undefined);
@@ -502,8 +599,9 @@ export class BrowserPool {
502
599
  if ('function' === typeof s) {
503
600
  try {
504
601
  const code = s.call(navRes);
505
- if ('number' === typeof code)
602
+ if ('number' === typeof code) {
506
603
  navStatus = code;
604
+ }
507
605
  }
508
606
  catch {
509
607
  // ignore
@@ -535,19 +633,24 @@ export class BrowserPool {
535
633
  release: () => this.#release(sessionId),
536
634
  injectReader: async () => {
537
635
  const script = this.#zeroInstall?.();
538
- if (script === undefined || page.evaluate === undefined)
636
+ if (script === undefined || page.evaluate === undefined) {
539
637
  return false;
638
+ }
540
639
  await page.addInitScript?.(script);
541
- await page.evaluate(script);
640
+ if (READER_NOT_HTML === (await page.evaluate(script))) {
641
+ throw new NotHtmlDocumentError();
642
+ }
542
643
  return true;
543
644
  },
645
+ isLoading: () => pageLoading(page),
544
646
  };
545
647
  }
546
648
  catch (err) {
547
649
  // Setup failed after we claimed the slot — give it back so a queued acquire isn't stuck, and
548
650
  // close the half-open context.
549
- if (context !== undefined)
550
- await context.close().catch(() => undefined);
651
+ if (context !== undefined) {
652
+ await settledWithin(context.close(), LEASE_CLOSE_TIMEOUT_MS);
653
+ }
551
654
  this.#releaseSlot();
552
655
  throw err;
553
656
  }
@@ -586,28 +689,65 @@ export class BrowserPool {
586
689
  }
587
690
  const browser = slot.browser;
588
691
  slot.browser = undefined;
589
- if (browser !== undefined)
692
+ if (browser !== undefined) {
590
693
  await browser.close().catch(() => undefined);
694
+ }
591
695
  }
592
- for (const waiter of this.#waiters.splice(0))
696
+ for (const waiter of this.#waiters.splice(0)) {
593
697
  waiter();
698
+ }
594
699
  }
595
700
  async #release(sessionId) {
596
701
  const lease = this.#active.get(sessionId);
597
- if (lease === undefined)
598
- return; // already released or lost to a crash
702
+ if (lease === undefined) {
703
+ return;
704
+ } // already released or lost to a crash
599
705
  this.#active.delete(sessionId);
600
706
  // Drop any alias pointing here, or the map grows for the life of the daemon and a later lease
601
707
  // reusing that registered name would resolve to a context that is already closed.
602
708
  for (const [registered, leaseId] of this.#aliases) {
603
- if (leaseId === sessionId)
709
+ if (leaseId === sessionId) {
604
710
  this.#aliases.delete(registered);
711
+ }
605
712
  }
606
- // Disposed with the lease, which is what "for that lease only" means. Best-effort: a context
607
- // that is already gone (a crash) makes the disposal moot, not a reason to fail the release.
608
- await lease.hostStub?.dispose().catch(() => undefined);
609
- await lease.context.close().catch(() => undefined);
713
+ // Bounded: a close that never returned held the slot and kept the tab open after the agent left.
714
+ const closed = await closeLeaseTab(lease, LEASE_CLOSE_TIMEOUT_MS);
610
715
  this.#releaseSlot();
716
+ if (!closed) {
717
+ await this.#dropWedgedBrowser(sessionId, lease.headed);
718
+ }
719
+ await this.#closeIfIdle();
720
+ }
721
+ /** Close the browser of a tab that did not close in time, unless another lease still uses it. */
722
+ async #dropWedgedBrowser(sessionId, headed) {
723
+ const slot = headed ? this.#headed : this.#headless;
724
+ const shared = [...this.#active.values()].some((other) => other.headed === headed);
725
+ const browser = slot.browser;
726
+ const drop = !shared && browser !== undefined && slot.launching === undefined;
727
+ log(LEASE_CLOSE_TIMED_OUT, { lease: sessionId, browserClosed: drop });
728
+ if (!drop) {
729
+ return;
730
+ }
731
+ slot.browser = undefined;
732
+ await settledWithin(browser.close(), LEASE_CLOSE_TIMEOUT_MS);
733
+ }
734
+ /**
735
+ * Close the browsers once nothing holds or waits for a lease. An idle Chromium kept its memory
736
+ * until the daemon itself went idle. Every acquire claims a slot before it touches a browser, so
737
+ * nothing occupied means no acquire is between launching a browser and using it.
738
+ */
739
+ async #closeIfIdle() {
740
+ if (this.#active.size > 0 || this.#occupied > 0 || this.#waiters.length > 0) {
741
+ return;
742
+ }
743
+ for (const slot of [this.#headless, this.#headed]) {
744
+ const browser = slot.browser;
745
+ if (browser === undefined || slot.launching !== undefined) {
746
+ continue;
747
+ }
748
+ slot.browser = undefined;
749
+ await settledWithin(browser.close(), LEASE_CLOSE_TIMEOUT_MS);
750
+ }
611
751
  }
612
752
  /** Synchronously claim a slot if under the cap. Returns true on success (caller proceeds). */
613
753
  #tryClaim() {
@@ -618,21 +758,24 @@ export class BrowserPool {
618
758
  return false;
619
759
  }
620
760
  #waitForSlot(signal) {
621
- if (this.#tryClaim())
761
+ if (this.#tryClaim()) {
622
762
  return Promise.resolve();
763
+ }
623
764
  return new Promise((resolve, reject) => {
624
765
  const onFree = () => {
625
766
  // The pool shut down while we waited → reject rather than relaunch a browser.
626
767
  if (this.#closed) {
627
- if (signal !== undefined)
768
+ if (signal !== undefined) {
628
769
  signal.removeEventListener('abort', onAbort);
770
+ }
629
771
  reject(new Error('browser pool is shut down'));
630
772
  return;
631
773
  }
632
774
  // Claim the freed slot; if another woken waiter beat us to it, re-queue.
633
775
  if (this.#tryClaim()) {
634
- if (signal !== undefined)
776
+ if (signal !== undefined) {
635
777
  signal.removeEventListener('abort', onAbort);
778
+ }
636
779
  resolve();
637
780
  }
638
781
  else {
@@ -641,8 +784,9 @@ export class BrowserPool {
641
784
  };
642
785
  const onAbort = () => {
643
786
  const i = this.#waiters.indexOf(onFree);
644
- if (i >= 0)
787
+ if (i >= 0) {
645
788
  this.#waiters.splice(i, 1);
789
+ }
646
790
  reject(new Error('acquire aborted'));
647
791
  };
648
792
  if (signal !== undefined) {
@@ -657,20 +801,25 @@ export class BrowserPool {
657
801
  }
658
802
  /** Give back one occupied slot and hand it to the next waiter (which re-claims it synchronously). */
659
803
  #releaseSlot() {
660
- if (this.#occupied > 0)
804
+ if (this.#occupied > 0) {
661
805
  this.#occupied -= 1;
806
+ }
662
807
  const next = this.#waiters.shift();
663
- if (next !== undefined)
808
+ if (next !== undefined) {
664
809
  next();
810
+ }
665
811
  }
666
812
  async #ensureBrowser(slot) {
667
- if (this.#closed)
813
+ if (this.#closed) {
668
814
  throw new Error('browser pool is shut down');
669
- if (slot.browser !== undefined && slot.browser.isConnected())
815
+ }
816
+ if (slot.browser !== undefined && slot.browser.isConnected()) {
670
817
  return slot.browser;
818
+ }
671
819
  const launch = slot.launch;
672
- if (launch === undefined)
820
+ if (launch === undefined) {
673
821
  throw new Error(NO_HEADED_BROWSER);
822
+ }
674
823
  // De-dupe concurrent launches: the first acquire to find no browser starts one; the rest await it.
675
824
  if (slot.launching === undefined) {
676
825
  // The in-flight promise MUST be cleared on failure as well as success. It used to be cleared only
@@ -695,21 +844,24 @@ export class BrowserPool {
695
844
  * acquire of that kind relaunches it. The other browser's leases live on.
696
845
  */
697
846
  #onCrash(slot, crashed) {
698
- if (slot.browser !== crashed)
699
- return; // a stale handler from a prior browser
847
+ if (slot.browser !== crashed) {
848
+ return;
849
+ } // a stale handler from a prior browser
700
850
  slot.browser = undefined;
701
851
  let freed = 0;
702
852
  for (const [id, lease] of this.#active) {
703
- if (lease.headed !== slot.headed)
853
+ if (lease.headed !== slot.headed) {
704
854
  continue;
855
+ }
705
856
  this.#active.delete(id);
706
857
  freed += 1;
707
858
  }
708
859
  // A lease mid-acquire on the dead browser holds a slot too; it frees it in its own catch. When
709
860
  // every lease belonged to this browser, nothing else can be holding one.
710
861
  this.#occupied = 0 === this.#active.size ? 0 : Math.max(0, this.#occupied - freed);
711
- for (const waiter of this.#waiters.splice(0))
712
- waiter(); // let them re-claim + relaunch
862
+ for (const waiter of this.#waiters.splice(0)) {
863
+ waiter();
864
+ } // let them re-claim + relaunch
713
865
  }
714
866
  /** Whether this lease's context skips certificate checks. A context cannot change it later. */
715
867
  ignoresHTTPSErrors(sessionId) {
@@ -15,7 +15,7 @@
15
15
  * `denied`. A page that gates its UI on the second never sees the grant, so a lease that reported
16
16
  * "granted" there would be reporting something the page cannot see.
17
17
  */
18
- import type { PooledContext } from './pool-contract.js';
18
+ import type { PooledContext, PooledPage } from './pool-contract.js';
19
19
  /** The permission whose grant the headless shell does not pass on to `Notification.permission`. */
20
20
  export declare const NOTIFICATIONS_PERMISSION = "notifications";
21
21
  /** Evaluated in the leased page. A string, because the pool's page surface evaluates scripts. */
@@ -31,3 +31,11 @@ export declare class LeasePermissionError extends Error {
31
31
  }
32
32
  /** Grant `permissions` on the origin of `url`. Nothing to grant is a no-op, not a refusal. */
33
33
  export declare function grantLeasePermissions(context: PooledContext, permissions: readonly string[], url: string): Promise<void>;
34
+ /**
35
+ * What the page's `Notification.permission` says, or undefined when it cannot be asked.
36
+ *
37
+ * Read back rather than inferred from the grant, because the headless shell answers `denied`
38
+ * after a notifications grant. Bounded, so a page that never answers reads as "could not ask"
39
+ * instead of holding the caller.
40
+ */
41
+ export declare function readNotificationPermission(page: PooledPage): Promise<string | undefined>;
@@ -31,3 +31,29 @@ export async function grantLeasePermissions(context, permissions, url) {
31
31
  throw new LeasePermissionError(`could not grant ${permissions.join(', ')}: ${reasonOf(err)}`);
32
32
  }
33
33
  }
34
+ /**
35
+ * What the page's `Notification.permission` says, or undefined when it cannot be asked.
36
+ *
37
+ * Read back rather than inferred from the grant, because the headless shell answers `denied`
38
+ * after a notifications grant. Bounded, so a page that never answers reads as "could not ask"
39
+ * instead of holding the caller.
40
+ */
41
+ export async function readNotificationPermission(page) {
42
+ if (page.evaluate === undefined) {
43
+ return undefined;
44
+ }
45
+ let timer;
46
+ const gaveUp = new Promise((resolve) => {
47
+ timer = setTimeout(() => resolve(undefined), NOTIFICATION_READ_TIMEOUT_MS);
48
+ });
49
+ try {
50
+ const value = await Promise.race([page.evaluate(NOTIFICATION_PERMISSION_READ), gaveUp]);
51
+ return 'string' === typeof value ? value : undefined;
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ finally {
57
+ clearTimeout(timer);
58
+ }
59
+ }