@reticlehq/server 3.6.0 → 3.7.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 (614) hide show
  1. package/SKILL.md +122 -123
  2. package/dist/command/cli/adhoc-suite.d.ts +14 -0
  3. package/dist/command/cli/adhoc-suite.js +42 -7
  4. package/dist/command/cli/adhoc-verdict.d.ts +18 -2
  5. package/dist/command/cli/adhoc-verdict.js +70 -6
  6. package/dist/command/cli/answers/gate-exit.d.ts +23 -0
  7. package/dist/command/cli/answers/gate-exit.js +45 -0
  8. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  9. package/dist/command/cli/answers/gate-hook-message.js +3 -1
  10. package/dist/command/cli/auth/device-flow.d.ts +13 -2
  11. package/dist/command/cli/auth/device-flow.js +17 -0
  12. package/dist/command/cli/cli-doctor.js +14 -0
  13. package/dist/command/cli/cli-flow-commands.d.ts +0 -11
  14. package/dist/command/cli/cli-flow-commands.js +28 -105
  15. package/dist/command/cli/cli-kill.js +3 -2
  16. package/dist/command/cli/cli-parse-grammar.d.ts +24 -0
  17. package/dist/command/cli/cli-parse-grammar.js +75 -0
  18. package/dist/command/cli/cli-parse-verify.d.ts +2 -0
  19. package/dist/command/cli/cli-parse-verify.js +18 -0
  20. package/dist/command/cli/cli-parse.d.ts +19 -28
  21. package/dist/command/cli/cli-parse.js +82 -182
  22. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  23. package/dist/command/cli/cli-update-commands.js +61 -9
  24. package/dist/command/cli/cli-usage.d.ts +33 -0
  25. package/dist/command/cli/cli-usage.js +287 -0
  26. package/dist/command/cli/cli-verify.d.ts +34 -4
  27. package/dist/command/cli/cli-verify.js +81 -12
  28. package/dist/command/cli/cloud-cli.d.ts +13 -0
  29. package/dist/command/cli/cloud-cli.js +145 -333
  30. package/dist/command/cli/cloud-kit.d.ts +12 -0
  31. package/dist/command/cli/cloud-kit.js +41 -2
  32. package/dist/command/cli/cloud-link.d.ts +57 -0
  33. package/dist/command/cli/cloud-link.js +308 -0
  34. package/dist/command/cli/cloud-login.d.ts +15 -1
  35. package/dist/command/cli/cloud-login.js +67 -13
  36. package/dist/command/cli/context-command.d.ts +1 -0
  37. package/dist/command/cli/context-command.js +33 -0
  38. package/dist/command/cli/daemon-start-options.d.ts +3 -0
  39. package/dist/command/cli/daemon-start-options.js +24 -3
  40. package/dist/command/cli/doctor/doctor-coverage-line.d.ts +2 -0
  41. package/dist/command/cli/doctor/doctor-coverage-line.js +47 -0
  42. package/dist/command/cli/doctor/doctor-project-line.d.ts +0 -12
  43. package/dist/command/cli/doctor/doctor-project-line.js +5 -4
  44. package/dist/command/cli/doctor/doctor-rows.d.ts +6 -0
  45. package/dist/command/cli/doctor/doctor-rows.js +6 -0
  46. package/dist/command/cli/doctor/doctor-signing-line.d.ts +38 -0
  47. package/dist/command/cli/doctor/doctor-signing-line.js +114 -0
  48. package/dist/command/cli/known-regression.d.ts +13 -0
  49. package/dist/command/cli/known-regression.js +34 -0
  50. package/dist/command/cli/launch/sdk-version-match.d.ts +12 -0
  51. package/dist/command/cli/launch/sdk-version-match.js +25 -0
  52. package/dist/command/cli/lifecycle/daemon-lifecycle.js +11 -2
  53. package/dist/command/cli/mcp-command.d.ts +6 -0
  54. package/dist/command/cli/mcp-command.js +13 -5
  55. package/dist/command/cli/ports/port-holder.js +1 -1
  56. package/dist/command/cli/project-name.d.ts +29 -1
  57. package/dist/command/cli/project-name.js +107 -2
  58. package/dist/command/cli/setup-mcp-cli.d.ts +17 -1
  59. package/dist/command/cli/setup-mcp-cli.js +29 -8
  60. package/dist/command/cli/status/status-lines.d.ts +1 -0
  61. package/dist/command/cli/status/status-lines.js +47 -2
  62. package/dist/command/cli/status/workspace-links.d.ts +10 -0
  63. package/dist/command/cli/status/workspace-links.js +29 -0
  64. package/dist/command/cli/try-command.d.ts +96 -0
  65. package/dist/command/cli/try-command.js +362 -0
  66. package/dist/command/cli/tutorial.d.ts +0 -100
  67. package/dist/command/cli/tutorial.js +0 -178
  68. package/dist/command/cli/verify-results.d.ts +19 -0
  69. package/dist/command/cli/verify-results.js +29 -0
  70. package/dist/command/cli-onboarding.d.ts +0 -11
  71. package/dist/command/cli-onboarding.js +0 -15
  72. package/dist/command/cli.d.ts +14 -0
  73. package/dist/command/cli.js +103 -83
  74. package/dist/command/daemon/daemon-resilience.js +4 -4
  75. package/dist/command/daemon/daemon-resolve.d.ts +9 -1
  76. package/dist/command/daemon/daemon-resolve.js +49 -10
  77. package/dist/command/setup/bringup/bridge-port.d.ts +3 -1
  78. package/dist/command/setup/bringup/bridge-port.js +13 -3
  79. package/dist/command/setup/bringup/dev-server-log-cap.js +2 -0
  80. package/dist/command/setup/bringup/ensure-daemon.js +9 -2
  81. package/dist/command/setup/init/agent-restart.d.ts +23 -0
  82. package/dist/command/setup/init/agent-restart.js +28 -0
  83. package/dist/command/setup/init/init-port.d.ts +10 -1
  84. package/dist/command/setup/init/init-port.js +22 -2
  85. package/dist/command/setup/init/init-runtime.d.ts +12 -2
  86. package/dist/command/setup/init/init-runtime.js +96 -37
  87. package/dist/command/setup/node-effects.d.ts +2 -0
  88. package/dist/command/setup/node-effects.js +36 -8
  89. package/dist/command/setup/probe/existing-app.d.ts +12 -0
  90. package/dist/command/setup/probe/existing-app.js +14 -0
  91. package/dist/command/setup/remaining-steps.js +1 -1
  92. package/dist/command/setup/run-setup.d.ts +29 -9
  93. package/dist/command/setup/run-setup.js +34 -28
  94. package/dist/command/setup/setup-command.d.ts +23 -0
  95. package/dist/command/setup/setup-command.js +57 -4
  96. package/dist/command/setup/setup-mcp.js +25 -2
  97. package/dist/command/setup/tauri-signing.d.ts +19 -0
  98. package/dist/command/setup/tauri-signing.js +79 -0
  99. package/dist/command/setup/terminal/listeners.d.ts +10 -1
  100. package/dist/command/setup/terminal/listeners.js +37 -6
  101. package/dist/command/update/update-nudge.d.ts +9 -0
  102. package/dist/command/update/update-nudge.js +1 -1
  103. package/dist/command/update/updater.js +11 -15
  104. package/dist/command/version/version-nudge.js +3 -0
  105. package/dist/command/version/version-skew.d.ts +20 -1
  106. package/dist/command/version/version-skew.js +38 -2
  107. package/dist/features/crawl/crawl.js +14 -1
  108. package/dist/features/exhaust/ledger.d.ts +14 -14
  109. package/dist/features/harness/drive-report.d.ts +39 -5
  110. package/dist/features/harness/drive-report.js +271 -54
  111. package/dist/features/harness/drive-runs.d.ts +120 -0
  112. package/dist/features/harness/drive-runs.js +289 -0
  113. package/dist/features/harness/drivers.d.ts +1 -1
  114. package/dist/features/harness/drivers.js +1 -1
  115. package/dist/features/harness/explore/console-pages.d.ts +15 -0
  116. package/dist/features/harness/explore/console-pages.js +146 -0
  117. package/dist/features/harness/explore/disparity.d.ts +19 -0
  118. package/dist/features/harness/explore/disparity.js +71 -0
  119. package/dist/features/harness/explore/fake-app.d.ts +16 -0
  120. package/dist/features/harness/explore/fake-app.js +49 -0
  121. package/dist/features/harness/explore/goal-search.d.ts +26 -0
  122. package/dist/features/harness/explore/goal-search.js +144 -0
  123. package/dist/features/harness/explore/page-tree.d.ts +49 -0
  124. package/dist/features/harness/explore/page-tree.js +148 -0
  125. package/dist/features/harness/explore/site-map.d.ts +34 -0
  126. package/dist/features/harness/explore/site-map.js +150 -0
  127. package/dist/features/harness/goals.d.ts +68 -7
  128. package/dist/features/harness/goals.js +289 -36
  129. package/dist/features/harness/harness.d.ts +30 -1
  130. package/dist/features/harness/harness.js +141 -7
  131. package/dist/features/harness/platform/local-apps.d.ts +36 -3
  132. package/dist/features/harness/platform/local-apps.js +81 -5
  133. package/dist/features/harness/platform/persona-credentials.d.ts +20 -0
  134. package/dist/features/harness/platform/persona-credentials.js +69 -0
  135. package/dist/features/harness/platform/platform-drives.d.ts +61 -0
  136. package/dist/features/harness/platform/platform-drives.js +114 -0
  137. package/dist/features/harness/platform/refusal.d.ts +52 -0
  138. package/dist/features/harness/platform/refusal.js +71 -0
  139. package/dist/features/harness/platform/remote-drive.d.ts +8 -2
  140. package/dist/features/harness/platform/remote-drive.js +22 -9
  141. package/dist/features/harness/platform/script.d.ts +57 -11
  142. package/dist/features/harness/platform/script.js +144 -12
  143. package/dist/features/harness/platform/server-driver.d.ts +17 -4
  144. package/dist/features/harness/platform/server-driver.js +29 -26
  145. package/dist/features/harness/platform-config.d.ts +38 -5
  146. package/dist/features/harness/platform-config.js +93 -8
  147. package/dist/features/harness/route-guess.d.ts +13 -0
  148. package/dist/features/harness/route-guess.js +49 -0
  149. package/dist/features/harness/script/script-run.d.ts +30 -1
  150. package/dist/features/harness/script/script-run.js +355 -48
  151. package/dist/features/license/license.d.ts +2 -2
  152. package/dist/features/visual/step-shots.d.ts +31 -0
  153. package/dist/features/visual/step-shots.js +231 -0
  154. package/dist/features/visual/visual-tools.js +22 -14
  155. package/dist/hooks/client-directory.d.ts +18 -0
  156. package/dist/hooks/client-directory.js +57 -0
  157. package/dist/hooks/coding-agents.d.ts +34 -0
  158. package/dist/hooks/coding-agents.js +117 -0
  159. package/dist/hooks/driven-by.d.ts +21 -2
  160. package/dist/hooks/driven-by.js +42 -2
  161. package/dist/hooks/hook-bus.d.ts +4 -0
  162. package/dist/hooks/hook-bus.js +18 -0
  163. package/dist/index.d.ts +1 -0
  164. package/dist/index.js +26 -183
  165. package/dist/judgement/capsule/capsule-store.d.ts +2 -2
  166. package/dist/judgement/capsule/causal-summary.d.ts +15 -1
  167. package/dist/judgement/capsule/causal-summary.js +33 -0
  168. package/dist/judgement/runs/artifact/build-verification-run.d.ts +1 -1
  169. package/dist/judgement/runs/artifact/build-verification-run.js +20 -4
  170. package/dist/judgement/runs/artifact/gap-summary.d.ts +0 -18
  171. package/dist/judgement/runs/artifact/gap-summary.js +12 -3
  172. package/dist/judgement/runs/artifact/replay-mapping.js +25 -9
  173. package/dist/judgement/runs/artifact/run-store.d.ts +13 -1
  174. package/dist/judgement/runs/artifact/run-store.js +175 -8
  175. package/dist/judgement/runs/context-tools.js +59 -1
  176. package/dist/judgement/runs/default-run-id.d.ts +3 -0
  177. package/dist/judgement/runs/default-run-id.js +16 -0
  178. package/dist/judgement/runs/drive-run.d.ts +53 -5
  179. package/dist/judgement/runs/drive-run.js +234 -20
  180. package/dist/judgement/runs/resume.d.ts +44 -0
  181. package/dist/judgement/runs/resume.js +137 -0
  182. package/dist/judgement/runs/reticle-runner.js +5 -2
  183. package/dist/judgement/runs/runner-port.d.ts +5 -1
  184. package/dist/judgement/runs/runner-port.js +8 -2
  185. package/dist/judgement/runs/verification-sync.d.ts +2 -0
  186. package/dist/judgement/runs/verification-sync.js +36 -15
  187. package/dist/language/flows/decision.js +2 -1
  188. package/dist/language/flows/fields/flow-secret-field.d.ts +12 -0
  189. package/dist/language/flows/fields/flow-secret-field.js +25 -2
  190. package/dist/language/flows/flow-anchor.d.ts +7 -2
  191. package/dist/language/flows/flow-anchor.js +10 -3
  192. package/dist/language/flows/flow-intent.d.ts +1 -0
  193. package/dist/language/flows/flow-journey.d.ts +6 -0
  194. package/dist/language/flows/flow-journey.js +20 -0
  195. package/dist/language/flows/flow-learning.d.ts +4 -0
  196. package/dist/language/flows/flow-learning.js +37 -3
  197. package/dist/language/flows/flow-replay-run.js +16 -18
  198. package/dist/language/flows/flow-replay-types.d.ts +1 -1
  199. package/dist/language/flows/flow-replay.d.ts +18 -1
  200. package/dist/language/flows/flow-replay.js +22 -7
  201. package/dist/language/flows/flow-result.d.ts +22 -1
  202. package/dist/language/flows/flow-result.js +47 -2
  203. package/dist/language/flows/flow-step-runners.d.ts +2 -0
  204. package/dist/language/flows/flow-step-runners.js +12 -6
  205. package/dist/language/flows/flow-store-for-session.js +5 -1
  206. package/dist/language/flows/flow-tools.d.ts +10 -0
  207. package/dist/language/flows/flow-tools.js +61 -62
  208. package/dist/language/flows/flows.d.ts +16 -1
  209. package/dist/language/flows/flows.js +69 -10
  210. package/dist/language/flows/narrow-write.d.ts +7 -0
  211. package/dist/language/flows/narrow-write.js +36 -1
  212. package/dist/language/flows/recording/recording-backtrack.js +14 -1
  213. package/dist/language/flows/recording/tape/recordings.d.ts +29 -1
  214. package/dist/language/flows/recording/tape/recordings.js +60 -10
  215. package/dist/language/flows/replay.js +29 -6
  216. package/dist/language/flows/single-replay.d.ts +7 -0
  217. package/dist/language/flows/single-replay.js +26 -0
  218. package/dist/language/flows/verify-change-tools.d.ts +1 -0
  219. package/dist/language/flows/verify-change-tools.js +200 -193
  220. package/dist/memory/cloud/cloud-config.d.ts +10 -1
  221. package/dist/memory/cloud/cloud-config.js +19 -5
  222. package/dist/memory/cloud/cloud-sync.js +3 -3
  223. package/dist/memory/cloud/harness-config.d.ts +49 -0
  224. package/dist/memory/cloud/harness-config.js +16 -20
  225. package/dist/memory/cloud/memory-scope.d.ts +17 -0
  226. package/dist/memory/cloud/memory-scope.js +22 -0
  227. package/dist/memory/cloud/sync-cycle.d.ts +32 -4
  228. package/dist/memory/cloud/sync-cycle.js +113 -12
  229. package/dist/memory/cloud/sync-daemon.d.ts +11 -0
  230. package/dist/memory/cloud/sync-daemon.js +114 -4
  231. package/dist/memory/cloud/sync-disk.js +26 -2
  232. package/dist/memory/cloud/sync-screenshots.d.ts +32 -0
  233. package/dist/memory/cloud/sync-screenshots.js +105 -0
  234. package/dist/memory/cloud/unsynced-roots.d.ts +47 -0
  235. package/dist/memory/cloud/unsynced-roots.js +166 -0
  236. package/dist/memory/impact/account-watch.d.ts +16 -1
  237. package/dist/memory/impact/account-watch.js +26 -1
  238. package/dist/memory/impact/impact-store.d.ts +1 -4
  239. package/dist/memory/impact/impact-store.js +32 -42
  240. package/dist/memory/intent/inline-intent.d.ts +1 -0
  241. package/dist/memory/intent/intent-shard-store.js +16 -4
  242. package/dist/memory/intent/intent-shard.d.ts +12 -0
  243. package/dist/memory/intent/intent-store.d.ts +1 -0
  244. package/dist/memory/intent/intent-tools.js +42 -2
  245. package/dist/memory/intent/open-intents.d.ts +6 -0
  246. package/dist/memory/intent/open-intents.js +10 -1
  247. package/dist/memory/intent/settled-words.d.ts +24 -0
  248. package/dist/memory/intent/settled-words.js +130 -0
  249. package/dist/memory/journal/drive-flow.d.ts +16 -0
  250. package/dist/memory/journal/drive-flow.js +34 -2
  251. package/dist/memory/journal/drive-run-flush.d.ts +2 -0
  252. package/dist/memory/journal/drive-run-flush.js +20 -5
  253. package/dist/memory/journal/journal-recorder.js +46 -3
  254. package/dist/memory/journal/on-disk/retention.d.ts +5 -0
  255. package/dist/memory/journal/on-disk/retention.js +27 -2
  256. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  257. package/dist/memory/journal/session-end.d.ts +15 -1
  258. package/dist/memory/journal/session-end.js +38 -7
  259. package/dist/memory/project/artifact-root-resolver.js +33 -5
  260. package/dist/memory/project/artifact-root.d.ts +4 -3
  261. package/dist/memory/project/artifact-root.js +2 -2
  262. package/dist/memory/project/dir/reticle-dir.d.ts +14 -0
  263. package/dist/memory/project/dir/reticle-dir.js +23 -2
  264. package/dist/memory/project/link-directory.d.ts +15 -0
  265. package/dist/memory/project/link-directory.js +44 -0
  266. package/dist/memory/project/root-claims.d.ts +6 -0
  267. package/dist/memory/project/root-claims.js +39 -0
  268. package/dist/memory/project/session-root.d.ts +2 -0
  269. package/dist/memory/project/session-root.js +9 -1
  270. package/dist/memory/project/sync-status.d.ts +46 -0
  271. package/dist/memory/project/sync-status.js +135 -1
  272. package/dist/memory/recall/memory-tools.js +46 -3
  273. package/dist/memory/recall/platform-memory.d.ts +34 -0
  274. package/dist/memory/recall/platform-memory.js +139 -0
  275. package/dist/memory/recall/project-memory.js +36 -8
  276. package/dist/portal/bridge/bridge.d.ts +9 -5
  277. package/dist/portal/bridge/bridge.js +66 -23
  278. package/dist/portal/bridge/harness-request.d.ts +23 -0
  279. package/dist/portal/bridge/harness-request.js +27 -0
  280. package/dist/portal/bridge/wrong-path.d.ts +17 -0
  281. package/dist/portal/bridge/wrong-path.js +35 -0
  282. package/dist/portal/input/network-mock-tools.js +11 -2
  283. package/dist/portal/input/real-input.d.ts +2 -0
  284. package/dist/portal/input/real-input.js +13 -10
  285. package/dist/portal/pool/browser-pool.d.ts +48 -0
  286. package/dist/portal/pool/browser-pool.js +120 -2
  287. package/dist/portal/pool/context-permissions.d.ts +33 -0
  288. package/dist/portal/pool/context-permissions.js +33 -0
  289. package/dist/portal/pool/lease-marker-stamp.d.ts +23 -0
  290. package/dist/portal/pool/lease-marker-stamp.js +49 -0
  291. package/dist/portal/pool/playwright-launcher.d.ts +27 -5
  292. package/dist/portal/pool/playwright-launcher.js +59 -14
  293. package/dist/portal/pool/pool-contract.d.ts +21 -1
  294. package/dist/portal/session/agent-link.d.ts +8 -0
  295. package/dist/portal/session/agent-link.js +17 -0
  296. package/dist/portal/session/command-payload.js +6 -1
  297. package/dist/portal/session/daemon-answers.d.ts +3 -3
  298. package/dist/portal/session/daemon-answers.js +4 -2
  299. package/dist/portal/session/dev-server/dev-command.d.ts +9 -0
  300. package/dist/portal/session/dev-server/dev-command.js +27 -1
  301. package/dist/portal/session/facts/observed-state.d.ts +16 -18
  302. package/dist/portal/session/facts/observed-state.js +46 -3
  303. package/dist/portal/session/human/live-control.d.ts +9 -2
  304. package/dist/portal/session/human/live-control.js +12 -3
  305. package/dist/portal/session/live-control-tools.js +82 -13
  306. package/dist/portal/session/narration-args.d.ts +5 -0
  307. package/dist/portal/session/narration-args.js +17 -0
  308. package/dist/portal/session/no-session-diagnosis.d.ts +1 -1
  309. package/dist/portal/session/no-session-diagnosis.js +31 -9
  310. package/dist/portal/session/no-session-next-action.d.ts +20 -0
  311. package/dist/portal/session/no-session-next-action.js +134 -61
  312. package/dist/portal/session/no-session-watch.js +179 -22
  313. package/dist/portal/session/presence/session-lease.js +1 -1
  314. package/dist/portal/session/recorded-gaps.d.ts +97 -0
  315. package/dist/portal/session/recorded-gaps.js +251 -0
  316. package/dist/portal/session/session-health.d.ts +11 -0
  317. package/dist/portal/session/session-health.js +3 -1
  318. package/dist/portal/session/session-info.js +2 -1
  319. package/dist/portal/session/session-manager.d.ts +18 -0
  320. package/dist/portal/session/session-manager.js +54 -3
  321. package/dist/portal/session/session-successor.d.ts +8 -0
  322. package/dist/portal/session/session-successor.js +1 -1
  323. package/dist/portal/session/session.d.ts +15 -28
  324. package/dist/portal/session/session.js +60 -61
  325. package/dist/portal/session/timing/command-timeout.js +25 -1
  326. package/dist/portal/session/timing/wall-clock.d.ts +1 -1
  327. package/dist/portal/session/timing/wall-clock.js +1 -1
  328. package/dist/portal/session/zero-install-first-call.d.ts +31 -0
  329. package/dist/portal/session/zero-install-first-call.js +88 -0
  330. package/dist/start-options.d.ts +7 -1
  331. package/dist/start-wiring.d.ts +63 -0
  332. package/dist/start-wiring.js +169 -0
  333. package/dist/status-payload.d.ts +13 -1
  334. package/dist/status-payload.js +13 -1
  335. package/dist/surface/http-server.js +7 -1
  336. package/dist/surface/mcp/attach-memory.d.ts +1 -0
  337. package/dist/surface/mcp/attach-memory.js +12 -1
  338. package/dist/surface/mcp/mcp-proxy.d.ts +4 -0
  339. package/dist/surface/mcp/mcp-proxy.js +29 -8
  340. package/dist/surface/mcp/mcp.d.ts +23 -0
  341. package/dist/surface/mcp/mcp.js +34 -4
  342. package/dist/surface/mcp/proxy/drive-calls.d.ts +10 -0
  343. package/dist/surface/mcp/proxy/drive-calls.js +27 -0
  344. package/dist/surface/mcp/proxy/mcp-post-transport.js +5 -0
  345. package/dist/surface/mcp/server-instructions.js +22 -14
  346. package/dist/surface/mcp/shared-params.js +1 -1
  347. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  348. package/dist/surface/mcp/surface-vocabulary.js +1 -0
  349. package/dist/surface/tools/account-tool.d.ts +22 -0
  350. package/dist/surface/tools/account-tool.js +157 -0
  351. package/dist/surface/tools/act/act-danger.d.ts +11 -0
  352. package/dist/surface/tools/act/act-danger.js +38 -1
  353. package/dist/surface/tools/act/act-observation.d.ts +33 -0
  354. package/dist/surface/tools/act/act-observation.js +57 -0
  355. package/dist/surface/tools/act/act-preflight.js +25 -2
  356. package/dist/surface/tools/act/act-target.js +8 -3
  357. package/dist/surface/tools/act/resolve-target.d.ts +1 -1
  358. package/dist/surface/tools/act/resolve-target.js +18 -3
  359. package/dist/surface/tools/act/screen-request.d.ts +34 -0
  360. package/dist/surface/tools/act/screen-request.js +113 -0
  361. package/dist/surface/tools/act/sequence-grade.d.ts +8 -0
  362. package/dist/surface/tools/act/sequence-grade.js +13 -2
  363. package/dist/surface/tools/act-sequence-tool.js +136 -27
  364. package/dist/surface/tools/act-tools.d.ts +0 -9
  365. package/dist/surface/tools/act-tools.js +48 -34
  366. package/dist/surface/tools/agent/app-notes-learning.d.ts +7 -0
  367. package/dist/surface/tools/agent/app-notes-learning.js +80 -0
  368. package/dist/surface/tools/agent/app-notes.d.ts +4 -0
  369. package/dist/surface/tools/agent/app-notes.js +48 -0
  370. package/dist/surface/tools/agent/build-staleness.d.ts +7 -0
  371. package/dist/surface/tools/agent/build-staleness.js +62 -0
  372. package/dist/surface/tools/agent/compact-envelope.d.ts +80 -0
  373. package/dist/surface/tools/agent/compact-envelope.js +374 -0
  374. package/dist/surface/tools/agent/for-the-agent.d.ts +12 -0
  375. package/dist/surface/tools/agent/for-the-agent.js +44 -0
  376. package/dist/surface/tools/agent/notices.d.ts +50 -0
  377. package/dist/surface/tools/agent/notices.js +152 -0
  378. package/dist/surface/tools/agent/reteach.d.ts +11 -0
  379. package/dist/surface/tools/agent/reteach.js +50 -0
  380. package/dist/surface/tools/args/alias-args.d.ts +5 -0
  381. package/dist/surface/tools/args/alias-args.js +10 -0
  382. package/dist/surface/tools/args/confirm-dangerous-arg.d.ts +6 -0
  383. package/dist/surface/tools/args/confirm-dangerous-arg.js +6 -0
  384. package/dist/surface/tools/assert/assert-source.d.ts +15 -0
  385. package/dist/surface/tools/assert/assert-source.js +40 -0
  386. package/dist/surface/tools/assert/assert-verdict.js +25 -15
  387. package/dist/surface/tools/browser-tools.js +43 -1
  388. package/dist/surface/tools/chat-drives.d.ts +75 -0
  389. package/dist/surface/tools/chat-drives.js +265 -21
  390. package/dist/surface/tools/coverage-tools.d.ts +2 -10
  391. package/dist/surface/tools/coverage-tools.js +2 -53
  392. package/dist/surface/tools/dynamic-tools.js +29 -1
  393. package/dist/surface/tools/empty-session-list.d.ts +10 -0
  394. package/dist/surface/tools/empty-session-list.js +45 -5
  395. package/dist/surface/tools/error-recovery.d.ts +8 -0
  396. package/dist/surface/tools/error-recovery.js +69 -5
  397. package/dist/surface/tools/explore-tools.d.ts +23 -2
  398. package/dist/surface/tools/explore-tools.js +377 -74
  399. package/dist/surface/tools/feedback-tools.d.ts +2 -3
  400. package/dist/surface/tools/feedback-tools.js +4 -3
  401. package/dist/surface/tools/gap-novelty.d.ts +3 -1
  402. package/dist/surface/tools/gap-novelty.js +8 -1
  403. package/dist/surface/tools/harness-explore.d.ts +52 -73
  404. package/dist/surface/tools/harness-explore.js +177 -146
  405. package/dist/surface/tools/harness-gate.d.ts +88 -0
  406. package/dist/surface/tools/harness-gate.js +235 -0
  407. package/dist/surface/tools/harness-hunt.d.ts +43 -0
  408. package/dist/surface/tools/harness-hunt.js +215 -0
  409. package/dist/surface/tools/harness-plan.d.ts +5 -0
  410. package/dist/surface/tools/harness-plan.js +7 -0
  411. package/dist/surface/tools/harness-script.d.ts +22 -0
  412. package/dist/surface/tools/harness-script.js +256 -38
  413. package/dist/surface/tools/harness-tab-guard.d.ts +5 -0
  414. package/dist/surface/tools/harness-tab-guard.js +27 -0
  415. package/dist/surface/tools/harness-toolset.d.ts +8 -0
  416. package/dist/surface/tools/harness-toolset.js +27 -4
  417. package/dist/surface/tools/human-notes.d.ts +16 -0
  418. package/dist/surface/tools/human-notes.js +27 -0
  419. package/dist/surface/tools/invoke-tool.d.ts +5 -2
  420. package/dist/surface/tools/invoke-tool.js +220 -93
  421. package/dist/surface/tools/lease-hint.js +26 -5
  422. package/dist/surface/tools/lease-permissions.d.ts +33 -0
  423. package/dist/surface/tools/lease-permissions.js +73 -0
  424. package/dist/surface/tools/lease-readiness.d.ts +38 -0
  425. package/dist/surface/tools/lease-readiness.js +57 -0
  426. package/dist/surface/tools/lease-seed.d.ts +20 -0
  427. package/dist/surface/tools/lease-seed.js +45 -1
  428. package/dist/surface/tools/lease-tools.d.ts +10 -1
  429. package/dist/surface/tools/lease-tools.js +119 -69
  430. package/dist/surface/tools/live-call-text.js +2 -2
  431. package/dist/surface/tools/merge-tools.js +42 -2
  432. package/dist/surface/tools/merged-name-redirect.js +4 -0
  433. package/dist/surface/tools/next-step.d.ts +11 -1
  434. package/dist/surface/tools/next-step.js +73 -18
  435. package/dist/surface/tools/observe-tools.js +43 -5
  436. package/dist/surface/tools/platform-moment.d.ts +60 -0
  437. package/dist/surface/tools/platform-moment.js +132 -4
  438. package/dist/surface/tools/read-tools.js +65 -14
  439. package/dist/surface/tools/reticle-result.d.ts +120 -0
  440. package/dist/surface/tools/reticle-result.js +364 -0
  441. package/dist/surface/tools/tool-kit.d.ts +68 -2
  442. package/dist/surface/tools/tool-kit.js +73 -1
  443. package/dist/surface/tools/tools.js +25 -13
  444. package/dist/telemetry/account-envelope.d.ts +7 -0
  445. package/dist/telemetry/account-envelope.js +61 -0
  446. package/dist/telemetry/agent-client.d.ts +15 -0
  447. package/dist/telemetry/agent-client.js +57 -0
  448. package/dist/telemetry/app-instrumented.js +9 -2
  449. package/dist/telemetry/argument-shape.d.ts +7 -0
  450. package/dist/telemetry/argument-shape.js +86 -1
  451. package/dist/telemetry/automation-hint.d.ts +2 -3
  452. package/dist/telemetry/automation-hint.js +33 -8
  453. package/dist/telemetry/cli-telemetry.js +7 -2
  454. package/dist/telemetry/daemon-telemetry.js +3 -0
  455. package/dist/telemetry/error-fingerprint.d.ts +10 -1
  456. package/dist/telemetry/error-fingerprint.js +61 -5
  457. package/dist/telemetry/feedback-cli.d.ts +0 -13
  458. package/dist/telemetry/feedback-cli.js +0 -77
  459. package/dist/telemetry/feedback.d.ts +13 -0
  460. package/dist/telemetry/feedback.js +183 -17
  461. package/dist/telemetry/harness-run.d.ts +18 -0
  462. package/dist/telemetry/harness-run.js +80 -0
  463. package/dist/telemetry/hud-events.d.ts +16 -0
  464. package/dist/telemetry/hud-events.js +27 -0
  465. package/dist/telemetry/hud-metrics.d.ts +27 -6
  466. package/dist/telemetry/hud-metrics.js +99 -25
  467. package/dist/telemetry/init-telemetry.js +2 -2
  468. package/dist/telemetry/install-source.d.ts +13 -25
  469. package/dist/telemetry/install-source.js +44 -3
  470. package/dist/telemetry/mcp-connection.d.ts +0 -14
  471. package/dist/telemetry/mcp-connection.js +5 -1
  472. package/dist/telemetry/onboarding-firsts.d.ts +32 -0
  473. package/dist/telemetry/onboarding-firsts.js +81 -6
  474. package/dist/telemetry/platform-linked.d.ts +5 -0
  475. package/dist/telemetry/platform-linked.js +32 -0
  476. package/dist/telemetry/project-profile.js +5 -3
  477. package/dist/telemetry/proxy-telemetry.js +4 -1
  478. package/dist/telemetry/session-metrics.d.ts +15 -4
  479. package/dist/telemetry/session-metrics.js +88 -52
  480. package/dist/telemetry/telemetry.d.ts +26 -2
  481. package/dist/telemetry/telemetry.js +102 -21
  482. package/dist/telemetry/verification-of.d.ts +3 -1
  483. package/dist/telemetry/verification-of.js +12 -4
  484. package/dist/wire-journal.d.ts +2 -2
  485. package/dist/wire-journal.js +16 -5
  486. package/docs/README.md +2 -2
  487. package/docs/actions.mdx +6 -3
  488. package/docs/adapters.md +1 -1
  489. package/docs/agent-cheatsheet.md +24 -27
  490. package/docs/architecture.md +5 -5
  491. package/docs/autodrive.md +13 -12
  492. package/docs/benchmarks.md +5 -5
  493. package/docs/best-practices.mdx +27 -9
  494. package/docs/capabilities.mdx +14 -16
  495. package/docs/cli/affected.mdx +1 -1
  496. package/docs/cli/cloud.mdx +54 -75
  497. package/docs/cli/doctor.mdx +8 -3
  498. package/docs/cli/drive.mdx +12 -12
  499. package/docs/cli/feedback.mdx +3 -3
  500. package/docs/cli/gate.mdx +7 -2
  501. package/docs/cli/help.mdx +50 -55
  502. package/docs/cli/init.mdx +32 -15
  503. package/docs/cli/license.mdx +12 -8
  504. package/docs/cli/mcp.mdx +4 -4
  505. package/docs/cli/open.mdx +3 -3
  506. package/docs/cli/report.mdx +4 -2
  507. package/docs/cli/restart.mdx +2 -2
  508. package/docs/cli/rollback.mdx +14 -8
  509. package/docs/cli/serve.mdx +4 -4
  510. package/docs/cli/setup.mdx +23 -0
  511. package/docs/cli/status.mdx +13 -4
  512. package/docs/cli/stop.mdx +67 -8
  513. package/docs/cli/try.mdx +62 -0
  514. package/docs/cli/update.mdx +4 -3
  515. package/docs/cli/verify.mdx +17 -6
  516. package/docs/cli/version.mdx +3 -2
  517. package/docs/cli.mdx +110 -41
  518. package/docs/debugging.md +3 -3
  519. package/docs/deploy-checks.md +1 -1
  520. package/docs/desktop.mdx +5 -3
  521. package/docs/enterprise.md +11 -10
  522. package/docs/environment.mdx +5 -2
  523. package/docs/faq.mdx +3 -3
  524. package/docs/first-drive.md +5 -4
  525. package/docs/flows.md +35 -27
  526. package/docs/for-agents.md +4 -5
  527. package/docs/frameworks.mdx +32 -21
  528. package/docs/getting-started.md +66 -64
  529. package/docs/human-control.md +15 -14
  530. package/docs/index.mdx +15 -43
  531. package/docs/install-agentic.mdx +22 -37
  532. package/docs/install-manual.mdx +22 -12
  533. package/docs/instrumentation.mdx +5 -3
  534. package/docs/integration-patterns.md +7 -7
  535. package/docs/license-activation.mdx +1 -1
  536. package/docs/multi-agent-testing.md +24 -6
  537. package/docs/packages/babel-plugin.mdx +4 -2
  538. package/docs/packages/browser.mdx +16 -10
  539. package/docs/packages/core.mdx +12 -5
  540. package/docs/packages/electron.mdx +3 -2
  541. package/docs/packages/engine.mdx +1 -1
  542. package/docs/packages/eslint-plugin.mdx +18 -12
  543. package/docs/packages/next.mdx +8 -7
  544. package/docs/packages/open-verification.mdx +1 -1
  545. package/docs/packages/server.mdx +5 -5
  546. package/docs/packages/tauri.mdx +8 -8
  547. package/docs/packages/test.mdx +4 -10
  548. package/docs/packages/vite-plugin.mdx +11 -4
  549. package/docs/packages.mdx +11 -15
  550. package/docs/platform-integration.md +6 -5
  551. package/docs/predicates.mdx +40 -24
  552. package/docs/quickstart.mdx +58 -29
  553. package/docs/required-checks.md +8 -7
  554. package/docs/reticle-manual.md +15 -5
  555. package/docs/skill-file.mdx +17 -79
  556. package/docs/skill-setup.md +9 -9
  557. package/docs/skill.md +200 -0
  558. package/docs/system-map.md +7 -7
  559. package/docs/telemetry.md +22 -24
  560. package/docs/testing.md +4 -4
  561. package/docs/token-efficiency.md +12 -12
  562. package/docs/tools/act-and-wait.mdx +9 -2
  563. package/docs/tools/act-sequence.mdx +6 -7
  564. package/docs/tools/act.mdx +2 -2
  565. package/docs/tools/assert.mdx +18 -17
  566. package/docs/tools/console.mdx +9 -9
  567. package/docs/tools/inspect.mdx +16 -7
  568. package/docs/tools/look.mdx +24 -0
  569. package/docs/tools/navigate.mdx +5 -3
  570. package/docs/tools/network.mdx +2 -2
  571. package/docs/tools/observe.mdx +6 -6
  572. package/docs/tools/overview.mdx +34 -33
  573. package/docs/tools/query.mdx +5 -5
  574. package/docs/tools/session-and-feedback.mdx +13 -4
  575. package/docs/tools/sessions.mdx +8 -8
  576. package/docs/tools/snapshot.mdx +6 -5
  577. package/docs/tools/state.mdx +9 -9
  578. package/docs/tools/tools-and-run.mdx +26 -24
  579. package/docs/tools/verify.mdx +28 -0
  580. package/docs/tools/wait-for.mdx +8 -4
  581. package/docs/troubleshooting.mdx +21 -6
  582. package/docs/usage.md +51 -45
  583. package/docs/use-cases.mdx +3 -3
  584. package/docs/vs-chrome-devtools-mcp.mdx +9 -6
  585. package/docs/vs-playwright-mcp.mdx +3 -1
  586. package/docs/what-is-recorded.md +7 -5
  587. package/docs/what-is-reticle.mdx +2 -2
  588. package/docs/why-reticle.mdx +1 -1
  589. package/package.json +6 -5
  590. package/dist/command/cli/demo/demo-page.d.ts +0 -78
  591. package/dist/command/cli/demo/demo-page.js +0 -249
  592. package/dist/command/cli/demo/demo-run.d.ts +0 -38
  593. package/dist/command/cli/demo/demo-run.js +0 -138
  594. package/dist/judgement/hunt/hunt-report.d.ts +0 -49
  595. package/dist/judgement/hunt/hunt-report.js +0 -84
  596. package/dist/language/flows/buddy-status.d.ts +0 -25
  597. package/dist/language/flows/buddy-status.js +0 -32
  598. package/dist/language/flows/change/watch-batcher.d.ts +0 -18
  599. package/dist/language/flows/change/watch-batcher.js +0 -19
  600. package/dist/memory/cloud/harness-offer.d.ts +0 -44
  601. package/dist/memory/cloud/harness-offer.js +0 -145
  602. package/dist/surface/tools/feature-capture.d.ts +0 -151
  603. package/dist/surface/tools/feature-capture.js +0 -235
  604. package/dist/surface/tools/tool-hit-rate.d.ts +0 -87
  605. package/dist/surface/tools/tool-hit-rate.js +0 -90
  606. package/dist/telemetry/harness-drive.d.ts +0 -4
  607. package/dist/telemetry/harness-drive.js +0 -35
  608. package/dist/telemetry/identify.d.ts +0 -22
  609. package/dist/telemetry/identify.js +0 -73
  610. package/docs/cli/capsules.mdx +0 -70
  611. package/docs/cli/hunt.mdx +0 -73
  612. package/docs/cli/identify.mdx +0 -76
  613. package/docs/cli/kill.mdx +0 -91
  614. package/docs/cli/watch.mdx +0 -62
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The `permissions` argument of `reticle_lease { action: "acquire" }` (#1370).
3
+ *
4
+ * Granted before the first navigation of a fresh lease. On an origin that already has a live lease,
5
+ * acquire hands back THAT lease rather than opening a second tab, and it may be another agent's tab
6
+ * mid-flow — nothing at this layer knows whose it is. So its grants never change there: the same list
7
+ * is a plain reuse, a different one is refused with the way out (release, then acquire again).
8
+ */
9
+ import { z } from 'zod';
10
+ import { ReticleTool } from '@reticlehq/core';
11
+ import { LeasePermissionError, NOTIFICATIONS_PERMISSION, } from '../../portal/pool/context-permissions.js';
12
+ export const LEASE_PERMISSIONS_ARG = z
13
+ .array(z.string().min(1))
14
+ .optional()
15
+ .describe('Browser permissions to grant on the app origin before the first navigation, e.g. ["geolocation", "clipboard-read"]. A fresh lease starts with none granted. An origin that already has a lease keeps its grants: a different list there is refused, so release it and acquire again to change them. Names are Playwright\'s, and one it does not know is refused.');
16
+ /** The family member these refusals come from, named the way `open-note.ts` names it. */
17
+ const LeaseAction = { ACQUIRE: 'acquire' };
18
+ /** How this tool's refusals name the call: built from `ReticleTool.LEASE`, not spelled out. */
19
+ const ACQUIRE = `${ReticleTool.LEASE}{action:"${LeaseAction.ACQUIRE}"}`;
20
+ const INVALID_PERMISSIONS = `${ACQUIRE} permissions must be a list of permission names, e.g. ["geolocation"]`;
21
+ /** The one `Notification.permission` value that means the page can see a notifications grant. */
22
+ const GRANTED = 'granted';
23
+ /** The validated list, undefined when the caller passed none, or a refusal naming the right shape. */
24
+ export function parseLeasePermissions(raw) {
25
+ if (raw === undefined)
26
+ return undefined;
27
+ const parsed = LEASE_PERMISSIONS_ARG.safeParse(raw);
28
+ if (!parsed.success)
29
+ throw new Error(INVALID_PERMISSIONS);
30
+ return parsed.data;
31
+ }
32
+ /** A permission failure, reworded for the agent; undefined for every other error. */
33
+ export function permissionRefusal(err) {
34
+ return err instanceof LeasePermissionError ? new Error(`${ACQUIRE} ${err.message}`) : undefined;
35
+ }
36
+ /**
37
+ * What to tell the agent when the page cannot see a notifications grant, else undefined.
38
+ *
39
+ * Undefined too when the page could not be asked: the grant was made, and a reading nobody took is
40
+ * not evidence that it failed.
41
+ */
42
+ export async function notificationReadBack(pool, sessionId, permissions) {
43
+ if (true !== permissions?.includes(NOTIFICATIONS_PERMISSION))
44
+ return undefined;
45
+ const reading = await pool.notificationPermission(sessionId);
46
+ if (reading === undefined || GRANTED === reading)
47
+ return undefined;
48
+ return (`notifications was granted, but this page's Notification.permission still reads "${reading}": ` +
49
+ 'the pooled headless browser does not support notifications, so UI gated on ' +
50
+ 'Notification.permission will not see the grant. navigator.permissions.query({ name: "notifications" }) does report it.');
51
+ }
52
+ const sameSet = (a, b) => a.length === b.length &&
53
+ a.every((name) => b.includes(name)) &&
54
+ b.every((name) => a.includes(name));
55
+ /**
56
+ * Refuse to reuse a held lease under a different set of grants. Nothing to refuse when the caller
57
+ * named no list, or the same one in any order.
58
+ */
59
+ export function refuseRegrant(pool, sessionId, permissions) {
60
+ if (permissions === undefined)
61
+ return;
62
+ const held = pool.permissionsOf(sessionId);
63
+ if (held === undefined || sameSet(permissions, held))
64
+ return;
65
+ throw new Error(`${ACQUIRE} this origin's lease ${sessionId} already holds [${held.join(', ')}], and it may be ` +
66
+ "another agent's live tab, so its grants are not changed in place. If it is yours, release it " +
67
+ 'and acquire again with the new list.');
68
+ }
69
+ /** `{ hint }` joining whichever parts are present, or nothing at all. */
70
+ export function hintOf(...parts) {
71
+ const present = parts.filter((part) => part !== undefined);
72
+ return 0 === present.length ? {} : { hint: present.join(' ') };
73
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Is a leased tab usable right now? The checks `reticle_lease` runs before it may say `ready: true`.
3
+ * Presence in the sessions map is not enough: the tab has to answer, and it has to be visible.
4
+ */
5
+ /** The narrow slice of a session the probe needs. Anything that quacks like this works. */
6
+ interface ProbeableSession {
7
+ command?: (name: string, args: Record<string, unknown>, timeoutMs: number) => Promise<unknown>;
8
+ }
9
+ /**
10
+ * Did the SDK report its tab hidden? A hidden tab answers commands while its timers and rAF are
11
+ * throttled, so a lease on it is `ready` by every other test and verifies nothing (#1351).
12
+ *
13
+ * Known gap: this reads the last PAGE_HEALTH, which lands just after HELLO — a check in that gap sees the
14
+ * default `false`. The pool foregrounding the page is what covers the gap; this catches the rest.
15
+ */
16
+ export declare function tabHidden(session: unknown): boolean;
17
+ /**
18
+ * Does this tab still answer?
19
+ *
20
+ * PRESENCE IS NOT LIVENESS. The sessions map still holds a tab that is attached, streaming events,
21
+ * and answering nothing, so `ready` read off a row in that map hands back a lease that `snapshot`,
22
+ * `state` and `console` then reject (#692). It has to mean the tab replied.
23
+ *
24
+ * ANY reply counts, including one that reports the command failed. The question is whether the SDK
25
+ * answers at all, not what it says — so an SDK too old to know the command replies
26
+ * `unknown command '…'`, and that is proof. Only the absence of a reply is evidence of absence:
27
+ * `PendingCommands.track` REJECTS on timeout and on a dropped socket, and resolves on every real
28
+ * answer, so the two cases are already separated for us.
29
+ *
30
+ * `CAPABILITIES` is the probe because it takes no arguments and returns a small fixed list. No new
31
+ * wire command is introduced: this rides an existing round trip.
32
+ *
33
+ * Fails OPEN. A registry entry with no `command` is a shape this code did not put there, and
34
+ * turning a lease that works into a refusal over a probe that could not run would be a worse
35
+ * failure than the one being fixed.
36
+ */
37
+ export declare function probeLeaseAlive(session: ProbeableSession | undefined, timeoutMs?: number): Promise<boolean>;
38
+ export {};
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Is a leased tab usable right now? The checks `reticle_lease` runs before it may say `ready: true`.
3
+ * Presence in the sessions map is not enough: the tab has to answer, and it has to be visible.
4
+ */
5
+ import { ReticleCommand } from '@reticlehq/core';
6
+ /**
7
+ * How long a liveness probe waits for the tab to say anything at all.
8
+ *
9
+ * Short on purpose. This is not "finish the work", it is "are you there" — a page executing
10
+ * JavaScript answers a no-argument command in single-digit milliseconds, and a wedged one is not
11
+ * going to answer in two seconds either.
12
+ */
13
+ const LEASE_PROBE_TIMEOUT_MS = 1_500;
14
+ /**
15
+ * Did the SDK report its tab hidden? A hidden tab answers commands while its timers and rAF are
16
+ * throttled, so a lease on it is `ready` by every other test and verifies nothing (#1351).
17
+ *
18
+ * Known gap: this reads the last PAGE_HEALTH, which lands just after HELLO — a check in that gap sees the
19
+ * default `false`. The pool foregrounding the page is what covers the gap; this catches the rest.
20
+ */
21
+ export function tabHidden(session) {
22
+ return (null !== session &&
23
+ 'object' === typeof session &&
24
+ true === session.info?.().hidden);
25
+ }
26
+ /**
27
+ * Does this tab still answer?
28
+ *
29
+ * PRESENCE IS NOT LIVENESS. The sessions map still holds a tab that is attached, streaming events,
30
+ * and answering nothing, so `ready` read off a row in that map hands back a lease that `snapshot`,
31
+ * `state` and `console` then reject (#692). It has to mean the tab replied.
32
+ *
33
+ * ANY reply counts, including one that reports the command failed. The question is whether the SDK
34
+ * answers at all, not what it says — so an SDK too old to know the command replies
35
+ * `unknown command '…'`, and that is proof. Only the absence of a reply is evidence of absence:
36
+ * `PendingCommands.track` REJECTS on timeout and on a dropped socket, and resolves on every real
37
+ * answer, so the two cases are already separated for us.
38
+ *
39
+ * `CAPABILITIES` is the probe because it takes no arguments and returns a small fixed list. No new
40
+ * wire command is introduced: this rides an existing round trip.
41
+ *
42
+ * Fails OPEN. A registry entry with no `command` is a shape this code did not put there, and
43
+ * turning a lease that works into a refusal over a probe that could not run would be a worse
44
+ * failure than the one being fixed.
45
+ */
46
+ export async function probeLeaseAlive(session, timeoutMs = LEASE_PROBE_TIMEOUT_MS) {
47
+ const send = session?.command;
48
+ if (send === undefined)
49
+ return true;
50
+ try {
51
+ await send.call(session, ReticleCommand.CAPABILITIES, {}, timeoutMs);
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
@@ -15,7 +15,27 @@ export declare function looksLikeStorageStateExport(seed: unknown): boolean;
15
15
  * with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
16
16
  * of an internals-leaking wall of text.
17
17
  */
18
+ /** What {@link cleanNavError} answers for a navigation that ran out of time. */
19
+ export declare const NAV_TIMED_OUT = "navigation timed out";
18
20
  export declare function cleanNavError(err: unknown, seed?: SeedStorage): string;
21
+ /** What a lease whose navigation hit a certificate error tells the caller. */
22
+ export declare const NAV_CERT_REFUSED: string;
23
+ /** The question a failed lease navigation asks, when nothing says the app is up. */
24
+ export declare const NAV_FAILED_QUESTION = "is the app running there?";
25
+ /** What a lease navigation that ran out of time tells the caller to do. */
26
+ export declare const NAV_TIMEOUT_ADVICE: string;
27
+ /**
28
+ * The sentence for a lease whose first navigation failed.
29
+ *
30
+ * A timeout and a refusal are different stories and get different sentences. A refusal
31
+ * (`net::ERR_CONNECTION_REFUSED` and the like) means nothing is serving there, so asking whether the
32
+ * app is running is the right question. A timeout proves less — only that the page was not ready in
33
+ * time — and its most common cause is a dev server compiling a route for the first time, which
34
+ * routinely outlasts the navigation budget. Asking "is the app running?" there sends the agent
35
+ * hunting for a dev server that is up instead of retrying, so the timeout sentence names both
36
+ * cases and leads with the retry.
37
+ */
38
+ export declare function navFailureMessage(url: string, err: unknown, seed?: SeedStorage): string;
19
39
  /**
20
40
  * Determine if a required seedStorage application precondition failed.
21
41
  *
@@ -65,6 +65,8 @@ export function looksLikeStorageStateExport(seed) {
65
65
  * with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
66
66
  * of an internals-leaking wall of text.
67
67
  */
68
+ /** What {@link cleanNavError} answers for a navigation that ran out of time. */
69
+ export const NAV_TIMED_OUT = 'navigation timed out';
68
70
  export function cleanNavError(err, seed) {
69
71
  const rawMsg = err instanceof Error ? err.message : String(err);
70
72
  const msg = scrubSeedFromError(rawMsg, seed);
@@ -76,13 +78,55 @@ export function cleanNavError(err, seed) {
76
78
  if (netCode !== null)
77
79
  return netCode[0];
78
80
  if (/timeout/i.test(firstLine))
79
- return 'navigation timed out';
81
+ return NAV_TIMED_OUT;
80
82
  return firstLine
81
83
  .replace(/^page\.goto:\s*/, '')
82
84
  .replace(/\s+at\s+https?:\/\/\S+.*$/, '')
83
85
  .trim()
84
86
  .slice(0, 100);
85
87
  }
88
+ /** Chromium's certificate failures: `net::ERR_CERT_AUTHORITY_INVALID`, `ERR_CERT_DATE_INVALID`, … */
89
+ const CERT_ERROR = /^net::ERR_CERT_/;
90
+ /** What a lease whose navigation hit a certificate error tells the caller. */
91
+ export const NAV_CERT_REFUSED = 'the browser refused its TLS certificate, which a self-signed or mkcert dev certificate gets. ' +
92
+ 'If it is your dev server, acquire again with ignoreHTTPSErrors: true to accept it for this lease';
93
+ /** The question a failed lease navigation asks, when nothing says the app is up. */
94
+ export const NAV_FAILED_QUESTION = 'is the app running there?';
95
+ /** What a lease navigation that ran out of time tells the caller to do. */
96
+ export const NAV_TIMEOUT_ADVICE = 'If the app is running, a dev server compiling a route for the first time can take longer than ' +
97
+ 'that: retry the acquire. If it is not running, start it first.';
98
+ /** Lead of the timeout sentence, before the budget ("within 30 s", or "in time" when unknown). */
99
+ const NAV_TIMEOUT_LEAD = 'the page did not finish loading';
100
+ const NAV_TIMEOUT_UNKNOWN_BUDGET = 'in time';
101
+ /** Playwright's own wording for a navigation that ran out of time, with the budget it used. */
102
+ const PLAYWRIGHT_TIMEOUT = /Timeout (\d+)ms exceeded/;
103
+ /**
104
+ * The sentence for a lease whose first navigation failed.
105
+ *
106
+ * A timeout and a refusal are different stories and get different sentences. A refusal
107
+ * (`net::ERR_CONNECTION_REFUSED` and the like) means nothing is serving there, so asking whether the
108
+ * app is running is the right question. A timeout proves less — only that the page was not ready in
109
+ * time — and its most common cause is a dev server compiling a route for the first time, which
110
+ * routinely outlasts the navigation budget. Asking "is the app running?" there sends the agent
111
+ * hunting for a dev server that is up instead of retrying, so the timeout sentence names both
112
+ * cases and leads with the retry.
113
+ */
114
+ export function navFailureMessage(url, err, seed) {
115
+ const reason = cleanNavError(err, seed);
116
+ // A certificate the browser refused is not an app that is down: the server answered, with a cert
117
+ // no CA here vouches for (a self-signed or mkcert dev certificate). Asking "is the app running?"
118
+ // sent the caller to restart a dev server that was fine (#1255).
119
+ if (CERT_ERROR.test(reason))
120
+ return `could not open ${url} — ${NAV_CERT_REFUSED} (${reason})`;
121
+ if (NAV_TIMED_OUT !== reason)
122
+ return `could not open ${url} — ${NAV_FAILED_QUESTION} (${reason})`;
123
+ const rawMsg = err instanceof Error ? err.message : String(err);
124
+ const budgetMs = PLAYWRIGHT_TIMEOUT.exec(rawMsg)?.[1];
125
+ const within = budgetMs === undefined
126
+ ? NAV_TIMEOUT_UNKNOWN_BUDGET
127
+ : `within ${String(Number(budgetMs) / 1000)} s`;
128
+ return `could not open ${url} — ${NAV_TIMEOUT_LEAD} ${within} (${reason}). ${NAV_TIMEOUT_ADVICE}`;
129
+ }
86
130
  /**
87
131
  * Determine if a required seedStorage application precondition failed.
88
132
  *
@@ -15,7 +15,8 @@ import { type NetworkMutationPort } from '../../portal/input/network-mutation.js
15
15
  import type { Perturbation } from '@reticlehq/core';
16
16
  import { type SeedStorage } from '@reticlehq/core';
17
17
  import { HudVisibility } from '@reticlehq/core';
18
- import type { ToolDef } from './tool-kit.js';
18
+ import { type ToolDef, type ToolDeps } from './tool-kit.js';
19
+ import type { BrowserPool } from '../../portal/pool/browser-pool.js';
19
20
  /**
20
21
  * Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
21
22
  * adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
@@ -112,6 +113,14 @@ export declare function connectOrInject(lease: {
112
113
  }>;
113
114
  export declare function hasOriginLock(origin: string): boolean;
114
115
  export declare const LEASE_ACQUIRE_TOOL: ToolDef;
116
+ /**
117
+ * Release one lease and everything that hangs off it. Returns how many leases are still held.
118
+ *
119
+ * Shared with `reticle_session { action: "end" }`: an ended session that kept its lease stayed
120
+ * connected until the reaper took it, and every id-less call from the next agent was refused as
121
+ * ambiguous in the meantime.
122
+ */
123
+ export declare function releaseLease(deps: ToolDeps, pool: BrowserPool, sessionId: string): Promise<number>;
115
124
  export declare const LEASE_TOOLS: ToolDef[];
116
125
  /**
117
126
  * The state a suite should boot its flows from: whatever the agent is already sitting in.
@@ -13,6 +13,7 @@ import { fixturePortFor, seedFromStorageState, } from '../../portal/input/storag
13
13
  import { mutationPortFor } from '../../portal/input/network-mutation.js';
14
14
  import { z } from 'zod';
15
15
  import { leaseNotConnectedHint } from './lease-hint.js';
16
+ import { probeLeaseAlive, tabHidden } from './lease-readiness.js';
16
17
  import { probeSdkMarker } from './gaps/sdk-marker-probe.js';
17
18
  import { observeWebDocument } from '../../portal/session/dev-server/served-document.js';
18
19
  import { diagnoseObservedWebCsp } from '@reticlehq/init';
@@ -20,10 +21,13 @@ import { readProjectFramework, readProjectId } from '../../command/cli/ports/res
20
21
  import { hasAnyAppConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
21
22
  import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../../portal/session/lease-visibility.js';
22
23
  import { reticleStateHome } from '../../command/daemon/daemon.js';
23
- import { LeaseNotReadyReason, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, ReticleCommand, SeedStorageSchema, } from '@reticlehq/core';
24
+ import { claimArtifactRoot, dropArtifactRootClaim } from '../../memory/project/root-claims.js';
25
+ import { LeaseNotReadyReason, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, SeedStorageSchema, } from '@reticlehq/core';
24
26
  import { HudVisibility, ReticleTool, SESSION_HEALTH } from '@reticlehq/core';
27
+ import { HOST_STUB_NOTE } from './tool-kit.js';
25
28
  import { asString } from '@reticlehq/core';
26
29
  import { chromiumPreflightRefusal } from '../../command/cli/doctor/browser/chromium-hint.js';
30
+ import { LEASE_PERMISSIONS_ARG, hintOf, notificationReadBack, parseLeasePermissions, permissionRefusal, refuseRegrant, } from './lease-permissions.js';
27
31
  /**
28
32
  * Everything the daemon already knows about why a leased tab might not have dialled in.
29
33
  *
@@ -89,7 +93,9 @@ function originOf(url) {
89
93
  const ALREADY_HELD_LEASE = 'you already hold a lease on this origin';
90
94
  function alreadyHeldHint(sessionId, origin) {
91
95
  return (`${ALREADY_HELD_LEASE} (${origin}): ${sessionId} — reused it rather than opening a second ` +
92
- 'tab, which would make every later call require sessionId. Release first if you want a fresh one.');
96
+ 'tab, which would make every later call require sessionId. It is still signed in as whoever ' +
97
+ 'used it last (cookies and storage come with it): release it first for a fresh context or ' +
98
+ 'another identity.');
93
99
  }
94
100
  /**
95
101
  * Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
@@ -115,7 +121,7 @@ export function appendReticleParams(url, session, projectId, hud) {
115
121
  }
116
122
  }
117
123
  export { cleanNavError, evaluateSeedPrecondition, scrubSeedFromError } from './lease-seed.js';
118
- import { cleanNavError, evaluateSeedPrecondition, looksLikeStorageStateExport, scrubSeedFromError, } from './lease-seed.js';
124
+ import { evaluateSeedPrecondition, looksLikeStorageStateExport, navFailureMessage, scrubSeedFromError, } from './lease-seed.js';
119
125
  /** A fresh, collision-resistant lease id. Uses crypto at the I/O boundary (not pure logic). */
120
126
  function newLeaseId() {
121
127
  const uuid = 'function' === typeof globalThis.crypto?.randomUUID
@@ -186,46 +192,6 @@ function sessionParamOf(url) {
186
192
  }
187
193
  const LEASE_READY_ATTEMPTS = 100;
188
194
  const LEASE_READY_POLL_MS = 100;
189
- /**
190
- * How long a liveness probe waits for the tab to say anything at all.
191
- *
192
- * Short on purpose. This is not "finish the work", it is "are you there" — a page executing
193
- * JavaScript answers a no-argument command in single-digit milliseconds, and a wedged one is not
194
- * going to answer in two seconds either.
195
- */
196
- const LEASE_PROBE_TIMEOUT_MS = 1_500;
197
- /**
198
- * Does this tab still answer?
199
- *
200
- * PRESENCE IS NOT LIVENESS. The sessions map still holds a tab that is attached, streaming events,
201
- * and answering nothing, so `ready` read off a row in that map hands back a lease that `snapshot`,
202
- * `state` and `console` then reject (#692). It has to mean the tab replied.
203
- *
204
- * ANY reply counts, including one that reports the command failed. The question is whether the SDK
205
- * answers at all, not what it says — so an SDK too old to know the command replies
206
- * `unknown command '…'`, and that is proof. Only the absence of a reply is evidence of absence:
207
- * `PendingCommands.track` REJECTS on timeout and on a dropped socket, and resolves on every real
208
- * answer, so the two cases are already separated for us.
209
- *
210
- * `CAPABILITIES` is the probe because it takes no arguments and returns a small fixed list. No new
211
- * wire command is introduced: this rides an existing round trip.
212
- *
213
- * Fails OPEN. A registry entry with no `command` is a shape this code did not put there, and
214
- * turning a lease that works into a refusal over a probe that could not run would be a worse
215
- * failure than the one being fixed.
216
- */
217
- async function probeLeaseAlive(session, timeoutMs = LEASE_PROBE_TIMEOUT_MS) {
218
- const send = session?.command;
219
- if (send === undefined)
220
- return true;
221
- try {
222
- await send.call(session, ReticleCommand.CAPABILITIES, {}, timeoutMs);
223
- return true;
224
- }
225
- catch {
226
- return false;
227
- }
228
- }
229
195
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
230
196
  /**
231
197
  * Poll until the leased tab's SDK has actually registered on the bridge, so the sessionId we return
@@ -314,7 +280,7 @@ export function hasOriginLock(origin) {
314
280
  }
315
281
  export const LEASE_ACQUIRE_TOOL = {
316
282
  name: ReticleTool.LEASE_ACQUIRE,
317
- description: 'Lease a fresh isolated headless browser context from the shared pool 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 at capacity this waits for a free slot. 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.',
283
+ 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 at capacity this waits for a free slot. 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.',
318
284
  inputSchema: {
319
285
  url: z
320
286
  .string()
@@ -324,29 +290,50 @@ export const LEASE_ACQUIRE_TOOL = {
324
290
  .optional()
325
291
  .describe('Stable project id to stamp on the leased tab so the agent can scope to it.'),
326
292
  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).'),
293
+ initScript: z
294
+ .string()
295
+ .optional()
296
+ .describe("JavaScript installed in the tab BEFORE any of the page's own scripts run. Use it for TEST DOUBLES of the host bindings a desktop shell injects — Wails `window.go.*`, Tauri `window.runtime`/`__TAURI_INTERNALS__`, an Electron preload object — so a view that calls them on mount is driven against the stub instead of throwing. It stays on for this lease only, and it is disposed with the lease. Every verdict on this lease carries a note that a stub was installed, so a green is never read as one against the real shell."),
327
297
  headed: z
328
298
  .boolean()
329
299
  .optional()
330
300
  .describe('Open it in a browser window somebody can watch, instead of headless.'),
301
+ ignoreHTTPSErrors: z
302
+ .boolean()
303
+ .optional()
304
+ .describe('Accept a self-signed or mkcert certificate on an https dev server, for this lease only. Off by default.'),
331
305
  hud: z
332
306
  .enum([HudVisibility.SHOWN, HudVisibility.HIDDEN, HudVisibility.REMOVED])
333
307
  .optional()
334
308
  .describe('How the HUD starts on the page: shown (default), hidden, or removed.'),
309
+ permissions: LEASE_PERMISSIONS_ARG,
310
+ root: z
311
+ .string()
312
+ .optional()
313
+ .describe("Absolute path of the .reticle folder this lease's runs belong in — the caller's project. Without it, the page's project id decides, and a page with none lands in ~/.reticle/unmatched, which nothing syncs."),
335
314
  },
336
315
  outputSchema: {
337
316
  sessionId: z.string(),
338
317
  url: z.string(),
339
318
  ready: z
340
319
  .boolean()
341
- .describe('Whether the leased tab is connected AND answering. On a reused lease this is probed, not assumed — a row in the session map is presence, not liveness. False ⇒ read notReadyReason.'),
320
+ .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.'),
342
321
  zeroInstall: z
343
322
  .boolean()
344
323
  .optional()
345
324
  .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.'),
325
+ hostStub: z
326
+ .string()
327
+ .optional()
328
+ .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.'),
346
329
  notReadyReason: z
347
- .enum([LeaseNotReadyReason.SDK_NEVER_DIALLED, LeaseNotReadyReason.SDK_STOPPED_ANSWERING])
330
+ .enum([
331
+ LeaseNotReadyReason.SDK_NEVER_DIALLED,
332
+ LeaseNotReadyReason.SDK_STOPPED_ANSWERING,
333
+ LeaseNotReadyReason.TAB_HIDDEN,
334
+ ])
348
335
  .optional()
349
- .describe('Present only when ready is false. sdk_never_dialled ⇒ nothing connected within the wait, so check the install (the app may not embed @reticlehq/core). sdk_stopped_answering ⇒ an SDK did connect and has stopped replying, so the tab is wedged and needs recovering, not reinstalling.'),
336
+ .describe('Present only when ready is false. sdk_never_dialled ⇒ nothing connected within the wait, so check the install (the app may not embed @reticlehq/core). sdk_stopped_answering ⇒ an SDK did connect and has stopped replying, so the tab is wedged and needs recovering, not reinstalling. tab_hidden ⇒ the SDK answers but its tab is hidden, so timers and animations are throttled and nothing on it verifies; bring the tab to the front, or release and re-acquire.'),
350
337
  expiresInMs: z
351
338
  .number()
352
339
  .describe('Milliseconds until this lease expires if untouched. Each tool call that targets this session resets the clock. Plan your work to finish or re-acquire before this runs out.'),
@@ -386,8 +373,13 @@ export const LEASE_ACQUIRE_TOOL = {
386
373
  throw new Error(refusal);
387
374
  }
388
375
  const projectId = asString(args['projectId']);
376
+ const root = asString(args['root']);
389
377
  const headed = true === args['headed'];
390
378
  const hud = Object.values(HudVisibility).find((v) => v === args['hud']);
379
+ const permissions = parseLeasePermissions(args['permissions']);
380
+ // The stub of the shell bindings, when the caller supplied one. Installed by the pool before
381
+ // the first navigation, so it is already defined when the app's first module runs.
382
+ const initScript = asString(args['initScript']);
391
383
  const seedStorageArg = args['seedStorage'];
392
384
  let validatedSeed;
393
385
  if (seedStorageArg !== undefined) {
@@ -423,10 +415,31 @@ export const LEASE_ACQUIRE_TOOL = {
423
415
  try {
424
416
  // Only a lease THIS caller took: another agent's tab is never reused or released (#1226).
425
417
  // A window asked for is never answered with the headless tab on that origin, nor the reverse.
418
+ // A held lease that already runs the SAME stub is exactly what the caller asked for, so it is
419
+ // left alone and reused below — releasing it would throw away the tab, and everything loaded
420
+ // in it, for nothing. A lease with no stub, or with a different one, is released FIRST instead:
421
+ // an init script has to exist before the page's own scripts run, which a loaded page cannot
422
+ // do, and the resolve below then finds nothing and mints a fresh context with the stub. A
423
+ // lease of the other kind is left alone, exactly as it is when a window was asked for.
424
+ const heldForStub = origin === undefined ? undefined : pool.leaseIdOnOrigin(origin, deps.attachId);
425
+ if (initScript !== undefined &&
426
+ heldForStub !== undefined &&
427
+ pool.isHeaded(heldForStub) === headed &&
428
+ pool.hostStubScript?.(heldForStub) !== initScript) {
429
+ await pool.release(heldForStub);
430
+ }
426
431
  const held = origin === undefined ? undefined : pool.leaseIdOnOrigin(origin, deps.attachId);
427
432
  const existing = held !== undefined && pool.isHeaded(held) === headed ? held : undefined;
428
- if (validatedSeed !== undefined && existing !== undefined) {
429
- // Seeding asked for: release its own context so the new lease starts with the given state.
433
+ // A context's certificate setting is fixed when it is made, so an explicit request for the
434
+ // other one cannot be answered by the held lease: handing it back would keep checks off after
435
+ // `false`, or keep a local certificate refused after `true` (#1255). Omitted keeps the lease.
436
+ const certAsked = args['ignoreHTTPSErrors'];
437
+ const certMismatch = existing !== undefined &&
438
+ 'boolean' === typeof certAsked &&
439
+ pool.ignoresHTTPSErrors(existing) !== certAsked;
440
+ if ((validatedSeed !== undefined || certMismatch) && existing !== undefined) {
441
+ // Seeding or a different certificate setting asked for: release its own context so the new
442
+ // lease starts with what was asked.
430
443
  await pool.release(existing);
431
444
  }
432
445
  else if (existing !== undefined && origin !== undefined) {
@@ -443,23 +456,33 @@ export const LEASE_ACQUIRE_TOOL = {
443
456
  // A no-op when the id resolved to itself; load-bearing when it did not, because every later
444
457
  // touch and release arrives under the id being handed back here.
445
458
  pool.alias(resolved, existing);
459
+ refuseRegrant(pool, existing, permissions);
446
460
  pool.touch(existing);
447
461
  // Probed only HERE, never on the mint path below. There, the readiness wait resolved
448
462
  // moments ago and IS the liveness evidence; on reuse the last evidence may be minutes
449
463
  // old, or there may be none at all — `unresponsive` is set by past commands failing, and
450
464
  // its own contract says absence means "answering, OR NOT ASKED YET". So the happy path of
451
465
  // a first acquire pays nothing for this.
452
- const alive = await probeLeaseAlive(deps.sessions.get(resolved));
466
+ const reusedSession = deps.sessions.get(resolved);
467
+ const alive = await probeLeaseAlive(reusedSession);
468
+ const reuseReason = !alive
469
+ ? LeaseNotReadyReason.SDK_STOPPED_ANSWERING
470
+ : tabHidden(reusedSession)
471
+ ? LeaseNotReadyReason.TAB_HIDDEN
472
+ : undefined;
453
473
  return {
454
474
  sessionId: resolved,
455
475
  url,
456
- ready: alive,
457
- ...(alive ? {} : { notReadyReason: LeaseNotReadyReason.SDK_STOPPED_ANSWERING }),
476
+ ready: reuseReason === undefined,
477
+ ...(reuseReason === undefined ? {} : { notReadyReason: reuseReason }),
458
478
  reused: true,
459
479
  expiresInMs: pool.leaseTtlMs(),
460
480
  leased: pool.activeCount(),
461
481
  queued: pool.queuedCount(),
462
482
  hint: alreadyHeldHint(resolved, origin),
483
+ // Asked of the pool, not remembered here: the stub was installed when THIS lease was
484
+ // minted, and the caller reusing it now may be seeing it for the first time.
485
+ ...(true === pool.stubInstalled?.(existing) ? { hostStub: HOST_STUB_NOTE } : {}),
463
486
  ...(alreadyOpen === undefined
464
487
  ? {}
465
488
  : { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
@@ -471,6 +494,9 @@ export const LEASE_ACQUIRE_TOOL = {
471
494
  await pool.release(existing);
472
495
  }
473
496
  const sessionId = newLeaseId();
497
+ // Before the page can dial in: the session is stamped with its root the moment it registers.
498
+ if (root !== undefined)
499
+ claimArtifactRoot(sessionId, root);
474
500
  const navUrl = appendReticleParams(url, sessionId, projectId, hud);
475
501
  let lease;
476
502
  try {
@@ -479,15 +505,21 @@ export const LEASE_ACQUIRE_TOOL = {
479
505
  ...(headed ? { headed } : {}),
480
506
  ...(deps.attachId === undefined ? {} : { owner: deps.attachId }),
481
507
  ...(validatedSeed !== undefined ? { seedStorage: validatedSeed } : {}),
508
+ ...(initScript !== undefined ? { initScript } : {}),
509
+ ...(permissions !== undefined ? { permissions } : {}),
510
+ ...(true === args['ignoreHTTPSErrors'] ? { ignoreHTTPSErrors: true } : {}),
482
511
  });
483
512
  }
484
513
  catch (err) {
514
+ const refusal = permissionRefusal(err);
515
+ if (refusal !== undefined)
516
+ throw refusal;
485
517
  if (err instanceof Error && err.message.startsWith('Storage seeding failed')) {
486
518
  throw new Error(scrubSeedFromError(err.message, validatedSeed));
487
519
  }
488
520
  // A raw page.goto failure is noisy and leaks the internal URL params — surface a clean,
489
- // actionable message instead.
490
- throw new Error(`could not open ${url} — is the app running there? (${cleanNavError(err, validatedSeed)})`);
521
+ // actionable message instead, one that tells a slow first compile from a missing app.
522
+ throw new Error(navFailureMessage(url, err, validatedSeed));
491
523
  }
492
524
  // Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
493
525
  // Resolved rather than assumed: an app that names its own session registers under that name,
@@ -515,15 +547,21 @@ export const LEASE_ACQUIRE_TOOL = {
515
547
  tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
516
548
  // ready means the SDK dialled in — not that contracts match. Carry the skew warning on acquire
517
549
  // so the agent does not learn it only after a CDP tool invents a closed page (#688).
550
+ const mintReason = !ready
551
+ ? LeaseNotReadyReason.SDK_NEVER_DIALLED
552
+ : tabHidden(session)
553
+ ? LeaseNotReadyReason.TAB_HIDDEN
554
+ : undefined;
518
555
  const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
519
556
  return {
520
557
  sessionId: registeredId ?? lease.sessionId,
521
558
  url,
522
- ready,
559
+ ready: mintReason === undefined,
523
560
  // The other half of the pair. `ready: false` carried two opposite situations under one
524
561
  // word: no SDK ever dialled in (look at the install) versus one dialled in and stopped
525
- // answering (recover the tab). They want different next actions, so they get names.
526
- ...(ready ? {} : { notReadyReason: LeaseNotReadyReason.SDK_NEVER_DIALLED }),
562
+ // answering (recover the tab). They want different next actions, so they get names — and so
563
+ // does a third: dialled in and answering, but hidden, so nothing on it verifies (#1351).
564
+ ...(mintReason === undefined ? {} : { notReadyReason: mintReason }),
527
565
  ...(zeroInstall ? { zeroInstall: true } : {}),
528
566
  expiresInMs: pool.leaseTtlMs(),
529
567
  leased: pool.activeCount(),
@@ -537,9 +575,10 @@ export const LEASE_ACQUIRE_TOOL = {
537
575
  },
538
576
  }),
539
577
  ...(versionSkew === undefined ? {} : { versionSkew }),
540
- ...(ready
541
- ? {}
542
- : { hint: await notConnectedHint(deps, url, pool.dialFailureUrl?.(lease.sessionId)) }),
578
+ ...(initScript === undefined ? {} : { hostStub: HOST_STUB_NOTE }),
579
+ ...hintOf(ready
580
+ ? undefined
581
+ : await notConnectedHint(deps, url, pool.dialFailureUrl?.(lease.sessionId)), ready ? await notificationReadBack(pool, lease.sessionId, permissions) : undefined),
543
582
  };
544
583
  }
545
584
  finally {
@@ -579,17 +618,28 @@ const LEASE_RELEASE_TOOL = {
579
618
  if (sessionId === undefined || 0 === sessionId.length) {
580
619
  throw new Error('reticle_lease{action:"release"} requires a sessionId');
581
620
  }
582
- // Read the project BEFORE releasing: afterwards the session is gone and there is nothing to
583
- // ask which project it belonged to.
584
- const projectId = deps.sessions.get(sessionId)?.projectId;
585
- await pool.release(sessionId);
586
- // Only once the LAST lease is gone. Announcing "live again" while another lease still drives
587
- // would be a lie, and a HUD that says the wrong thing is worse than one that says nothing.
588
- if (0 === pool.activeCount())
589
- tellWatchers(deps, projectId, AGENT_DRIVING_HERE_AGAIN);
590
- return { released: true, leased: pool.activeCount() };
621
+ return { released: true, leased: await releaseLease(deps, pool, sessionId) };
591
622
  },
592
623
  };
624
+ /**
625
+ * Release one lease and everything that hangs off it. Returns how many leases are still held.
626
+ *
627
+ * Shared with `reticle_session { action: "end" }`: an ended session that kept its lease stayed
628
+ * connected until the reaper took it, and every id-less call from the next agent was refused as
629
+ * ambiguous in the meantime.
630
+ */
631
+ export async function releaseLease(deps, pool, sessionId) {
632
+ // Read the project BEFORE releasing: afterwards the session is gone and there is nothing to
633
+ // ask which project it belonged to.
634
+ const projectId = deps.sessions.get(sessionId)?.projectId;
635
+ await pool.release(sessionId);
636
+ dropArtifactRootClaim(sessionId);
637
+ // Only once the LAST lease is gone. Announcing "live again" while another lease still drives
638
+ // would be a lie, and a HUD that says the wrong thing is worse than one that says nothing.
639
+ if (0 === pool.activeCount())
640
+ tellWatchers(deps, projectId, AGENT_DRIVING_HERE_AGAIN);
641
+ return pool.activeCount();
642
+ }
593
643
  /**
594
644
  * What to say when a lease was taken while a real tab was already open.
595
645
  *