@reticlehq/server 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/SKILL.md +13 -12
  2. package/dist/command/cli/adhoc-suite.js +2 -0
  3. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  4. package/dist/command/cli/adhoc-verdict.js +50 -187
  5. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  6. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  7. package/dist/command/cli/cli-doctor.js +1 -1
  8. package/dist/command/cli/cli-flow-commands.js +2 -9
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  11. package/dist/command/cli/cli-parse-compare.js +75 -0
  12. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  13. package/dist/command/cli/cli-parse-grade.js +75 -0
  14. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  15. package/dist/command/cli/cli-parse-grammar.js +18 -20
  16. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  17. package/dist/command/cli/cli-parse-verify.js +63 -14
  18. package/dist/command/cli/cli-parse.d.ts +17 -15
  19. package/dist/command/cli/cli-parse.js +44 -27
  20. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  21. package/dist/command/cli/cli-update-commands.js +29 -20
  22. package/dist/command/cli/cli-usage.js +58 -29
  23. package/dist/command/cli/cli-verify.d.ts +11 -2
  24. package/dist/command/cli/cli-verify.js +27 -8
  25. package/dist/command/cli/cloud-cli.js +2 -119
  26. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  27. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  28. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  29. package/dist/command/cli/doctor-repairs.js +1 -1
  30. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  31. package/dist/command/cli/ensure-daemon.js +57 -0
  32. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  33. package/dist/command/cli/finished-run/grade-command.js +180 -0
  34. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  35. package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
  36. package/dist/command/cli/ports/port-holder.d.ts +9 -0
  37. package/dist/command/cli/ports/port-holder.js +27 -3
  38. package/dist/command/cli/status/status-lines.d.ts +1 -0
  39. package/dist/command/cli/status/status-lines.js +22 -0
  40. package/dist/command/cli/status-fields.d.ts +5 -1
  41. package/dist/command/cli/status-fields.js +7 -3
  42. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  43. package/dist/command/cli/two-builds/compare-command.js +314 -0
  44. package/dist/command/cli/verify-against.d.ts +61 -0
  45. package/dist/command/cli/verify-against.js +187 -0
  46. package/dist/command/cli/verify-no-session.d.ts +2 -0
  47. package/dist/command/cli/verify-no-session.js +13 -0
  48. package/dist/command/cli/verify-results.d.ts +35 -2
  49. package/dist/command/cli/verify-results.js +76 -10
  50. package/dist/command/cli.js +46 -41
  51. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  52. package/dist/command/daemon/daemon-resolve.js +27 -1
  53. package/dist/command/daemon/daemon.d.ts +12 -1
  54. package/dist/command/daemon/daemon.js +35 -4
  55. package/dist/command/daemon/lifetime/death-cause.js +21 -2
  56. package/dist/command/daemon/lifetime/heartbeat.js +7 -2
  57. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  58. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  59. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  60. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  61. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  62. package/dist/command/setup/bringup/static-page-server.js +43 -9
  63. package/dist/command/setup/init/init-runtime.js +6 -0
  64. package/dist/command/setup/node-effects.d.ts +3 -2
  65. package/dist/command/setup/node-effects.js +9 -3
  66. package/dist/command/setup/remaining-steps.d.ts +8 -0
  67. package/dist/command/setup/remaining-steps.js +10 -0
  68. package/dist/command/setup/run-setup.d.ts +2 -0
  69. package/dist/command/setup/run-setup.js +9 -1
  70. package/dist/command/setup/setup-command.js +6 -3
  71. package/dist/command/update/updater.js +6 -0
  72. package/dist/command/version/version-skew.js +1 -1
  73. package/dist/features/compare/action-plan.d.ts +260 -0
  74. package/dist/features/compare/action-plan.js +543 -0
  75. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  76. package/dist/features/compare/behaviour-compare.js +85 -0
  77. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  78. package/dist/features/compare/behaviour-diff.js +202 -0
  79. package/dist/features/compare/behaviour-report.d.ts +40 -0
  80. package/dist/features/compare/behaviour-report.js +128 -0
  81. package/dist/features/compare/build-readings.d.ts +106 -0
  82. package/dist/features/compare/build-readings.js +250 -0
  83. package/dist/features/compare/compare-diff.d.ts +62 -0
  84. package/dist/features/compare/compare-diff.js +177 -0
  85. package/dist/features/compare/compare-report-html.d.ts +6 -0
  86. package/dist/features/compare/compare-report-html.js +102 -0
  87. package/dist/features/compare/compare-report.d.ts +15 -0
  88. package/dist/features/compare/compare-report.js +73 -0
  89. package/dist/features/compare/compare-run.d.ts +60 -0
  90. package/dist/features/compare/compare-run.js +123 -0
  91. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  92. package/dist/features/compare/outside/effect-lines.js +103 -0
  93. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  94. package/dist/features/compare/outside/state-sources.js +164 -0
  95. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  96. package/dist/features/compare/outside/value-dictionary.js +227 -0
  97. package/dist/features/compare/visible-content.d.ts +20 -0
  98. package/dist/features/compare/visible-content.js +125 -0
  99. package/dist/features/crawl/crawl-empty.js +2 -1
  100. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  101. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  102. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  103. package/dist/features/crawl/crawl-tools.js +28 -12
  104. package/dist/features/crawl/crawl.d.ts +8 -9
  105. package/dist/features/crawl/crawl.js +61 -65
  106. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  107. package/dist/features/exhaust/app-coverage.js +3 -1
  108. package/dist/features/exhaust/code-coverage.js +8 -4
  109. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  110. package/dist/features/exhaust/edge-cases.js +310 -0
  111. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  112. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  113. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  114. package/dist/features/exhaust/edges/edge-run.js +169 -0
  115. package/dist/features/exhaust/explorer.d.ts +87 -14
  116. package/dist/features/exhaust/explorer.js +159 -25
  117. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  118. package/dist/features/exhaust/failure-branches.js +29 -11
  119. package/dist/features/exhaust/invariants.d.ts +112 -0
  120. package/dist/features/exhaust/invariants.js +188 -0
  121. package/dist/features/exhaust/ledger.d.ts +68 -0
  122. package/dist/features/exhaust/ledger.js +121 -13
  123. package/dist/features/exhaust/session-fold.d.ts +10 -0
  124. package/dist/features/exhaust/session-fold.js +46 -3
  125. package/dist/features/exhaust/session-port.d.ts +3 -1
  126. package/dist/features/exhaust/session-port.js +97 -28
  127. package/dist/features/grade/grade-answer.d.ts +99 -0
  128. package/dist/features/grade/grade-answer.js +112 -0
  129. package/dist/features/grade/grade-noise.d.ts +27 -0
  130. package/dist/features/grade/grade-noise.js +55 -0
  131. package/dist/features/grade/grade-renumber.d.ts +12 -0
  132. package/dist/features/grade/grade-renumber.js +86 -0
  133. package/dist/features/grade/grade-state.d.ts +50 -0
  134. package/dist/features/grade/grade-state.js +93 -0
  135. package/dist/features/grade/grade-suggest.d.ts +33 -0
  136. package/dist/features/grade/grade-suggest.js +98 -0
  137. package/dist/features/visual/visual-tools.js +66 -19
  138. package/dist/index.js +7 -4
  139. package/dist/judgement/capsule/causal-summary.js +9 -2
  140. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  141. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  142. package/dist/judgement/runs/reticle-runner.js +16 -2
  143. package/dist/judgement/runs/runner-port.d.ts +2 -0
  144. package/dist/judgement/runs/runner-port.js +23 -5
  145. package/dist/language/flows/affected-tools.d.ts +1 -1
  146. package/dist/language/flows/affected-tools.js +49 -30
  147. package/dist/language/flows/change/git-changed.d.ts +2 -1
  148. package/dist/language/flows/change/git-changed.js +19 -2
  149. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  150. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  151. package/dist/language/flows/flow-anchor.d.ts +60 -2
  152. package/dist/language/flows/flow-anchor.js +125 -20
  153. package/dist/language/flows/flow-classify.d.ts +20 -0
  154. package/dist/language/flows/flow-classify.js +90 -12
  155. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  156. package/dist/language/flows/flow-cross-step.js +25 -1
  157. package/dist/language/flows/flow-journey.d.ts +38 -4
  158. package/dist/language/flows/flow-journey.js +138 -8
  159. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  160. package/dist/language/flows/flow-replay-run.js +76 -14
  161. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  162. package/dist/language/flows/flow-replay.d.ts +19 -1
  163. package/dist/language/flows/flow-replay.js +169 -52
  164. package/dist/language/flows/flow-replayable.js +7 -1
  165. package/dist/language/flows/flow-start-path.d.ts +34 -5
  166. package/dist/language/flows/flow-start-path.js +202 -15
  167. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  168. package/dist/language/flows/flow-step-runners.js +184 -109
  169. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  170. package/dist/language/flows/flow-store-for-session.js +44 -0
  171. package/dist/language/flows/flow-tools.js +46 -33
  172. package/dist/language/flows/flows.js +176 -76
  173. package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
  174. package/dist/language/flows/recording/tape/recordings.js +71 -5
  175. package/dist/language/flows/replay.d.ts +19 -5
  176. package/dist/language/flows/replay.js +99 -7
  177. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  178. package/dist/language/flows/steps/page-steps.js +198 -0
  179. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  180. package/dist/language/flows/suite/sequential-run.js +96 -0
  181. package/dist/language/flows/verify-change-tools.js +26 -15
  182. package/dist/memory/cloud/background-sync.d.ts +13 -0
  183. package/dist/memory/cloud/background-sync.js +24 -3
  184. package/dist/memory/cloud/sync-daemon.js +26 -27
  185. package/dist/memory/journal/attach-journal.js +2 -1
  186. package/dist/memory/journal/drive-flow.d.ts +7 -0
  187. package/dist/memory/journal/drive-flow.js +44 -13
  188. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  189. package/dist/memory/journal/session-journal.d.ts +7 -0
  190. package/dist/memory/journal/session-journal.js +58 -10
  191. package/dist/memory/project/root-claims.d.ts +9 -0
  192. package/dist/memory/project/root-claims.js +9 -4
  193. package/dist/portal/input/download-capture.d.ts +7 -0
  194. package/dist/portal/input/download-capture.js +92 -0
  195. package/dist/portal/input/network-detail.d.ts +6 -0
  196. package/dist/portal/input/network-detail.js +12 -1
  197. package/dist/portal/input/network-mock-tools.js +49 -13
  198. package/dist/portal/input/network-mock.d.ts +8 -0
  199. package/dist/portal/input/network-mock.js +28 -9
  200. package/dist/portal/input/page-capture.d.ts +24 -0
  201. package/dist/portal/input/page-capture.js +33 -0
  202. package/dist/portal/input/real-input.d.ts +22 -20
  203. package/dist/portal/input/real-input.js +41 -31
  204. package/dist/portal/input/viewport-tools.d.ts +8 -0
  205. package/dist/portal/input/viewport-tools.js +75 -18
  206. package/dist/portal/pool/browser-pool.d.ts +11 -29
  207. package/dist/portal/pool/browser-pool.js +35 -17
  208. package/dist/portal/pool/context-permissions.d.ts +17 -3
  209. package/dist/portal/pool/context-permissions.js +36 -5
  210. package/dist/portal/pool/determinism.d.ts +52 -0
  211. package/dist/portal/pool/determinism.js +259 -0
  212. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  213. package/dist/portal/pool/lease-emulation.js +18 -0
  214. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  215. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  216. package/dist/portal/pool/lease-page-error.d.ts +44 -17
  217. package/dist/portal/pool/lease-page-error.js +147 -5
  218. package/dist/portal/pool/page-error-journal.d.ts +7 -1
  219. package/dist/portal/pool/page-error-journal.js +27 -0
  220. package/dist/portal/pool/playwright-launcher.js +51 -8
  221. package/dist/portal/pool/pool-contract.d.ts +27 -1
  222. package/dist/portal/pool/pool-lease.d.ts +36 -1
  223. package/dist/portal/pool/zero-install.d.ts +8 -0
  224. package/dist/portal/pool/zero-install.js +23 -1
  225. package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
  226. package/dist/portal/session/dev-server/dev-command.js +61 -15
  227. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  228. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  229. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  230. package/dist/portal/session/dev-server/project-ports.js +62 -0
  231. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  232. package/dist/portal/session/facts/departed-network.js +142 -0
  233. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  234. package/dist/portal/session/no-session-diagnosis.js +67 -25
  235. package/dist/portal/session/no-session-next-action.d.ts +2 -0
  236. package/dist/portal/session/no-session-next-action.js +12 -3
  237. package/dist/portal/session/no-session-watch.d.ts +6 -0
  238. package/dist/portal/session/no-session-watch.js +38 -3
  239. package/dist/portal/session/serving-directory.d.ts +10 -0
  240. package/dist/portal/session/serving-directory.js +39 -8
  241. package/dist/portal/session/session-manager.d.ts +10 -0
  242. package/dist/portal/session/session-manager.js +174 -63
  243. package/dist/start-wiring.d.ts +2 -1
  244. package/dist/start-wiring.js +2 -1
  245. package/dist/surface/mcp/mcp.js +3 -2
  246. package/dist/surface/mcp/shared-params.d.ts +2 -0
  247. package/dist/surface/mcp/shared-params.js +31 -0
  248. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  249. package/dist/surface/tools/act/act-observation.js +118 -12
  250. package/dist/surface/tools/act/act-preflight.js +1 -1
  251. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  252. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  253. package/dist/surface/tools/act/after-match.d.ts +4 -1
  254. package/dist/surface/tools/act/after-match.js +8 -1
  255. package/dist/surface/tools/act/already-true.js +1 -1
  256. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  257. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  258. package/dist/surface/tools/act/resolve-target.d.ts +15 -1
  259. package/dist/surface/tools/act/resolve-target.js +24 -1
  260. package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
  261. package/dist/surface/tools/act/sequence-grade.js +37 -26
  262. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  263. package/dist/surface/tools/act-command.d.ts +23 -0
  264. package/dist/surface/tools/act-command.js +50 -0
  265. package/dist/surface/tools/act-sequence-tool.js +78 -50
  266. package/dist/surface/tools/act-tools.d.ts +1 -13
  267. package/dist/surface/tools/act-tools.js +74 -86
  268. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  269. package/dist/surface/tools/agent/build-staleness.js +1 -1
  270. package/dist/surface/tools/assert/assert-verdict.js +16 -37
  271. package/dist/surface/tools/browser-tools.js +12 -6
  272. package/dist/surface/tools/compare-tools.d.ts +11 -0
  273. package/dist/surface/tools/compare-tools.js +94 -0
  274. package/dist/surface/tools/coverage-tools.js +1 -1
  275. package/dist/surface/tools/dynamic-tools.js +9 -2
  276. package/dist/surface/tools/error-recovery.d.ts +12 -1
  277. package/dist/surface/tools/error-recovery.js +91 -21
  278. package/dist/surface/tools/explore-tools.js +1 -1
  279. package/dist/surface/tools/invoke-tool.js +3 -2
  280. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  281. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  282. package/dist/surface/tools/lease-hint.d.ts +35 -1
  283. package/dist/surface/tools/lease-hint.js +42 -0
  284. package/dist/surface/tools/lease-readiness.d.ts +26 -0
  285. package/dist/surface/tools/lease-readiness.js +79 -0
  286. package/dist/surface/tools/lease-seed.d.ts +14 -1
  287. package/dist/surface/tools/lease-seed.js +35 -3
  288. package/dist/surface/tools/lease-tools.d.ts +3 -41
  289. package/dist/surface/tools/lease-tools.js +62 -110
  290. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  291. package/dist/surface/tools/lease-url-identity.js +97 -0
  292. package/dist/surface/tools/merge-tools.js +99 -2
  293. package/dist/surface/tools/merged-name-redirect.js +5 -1
  294. package/dist/surface/tools/observe-tools.js +15 -9
  295. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  296. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  297. package/dist/surface/tools/read-tools.js +33 -30
  298. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  299. package/dist/surface/tools/real-input-attempt.js +143 -28
  300. package/dist/surface/tools/session-ports.d.ts +14 -0
  301. package/dist/surface/tools/session-ports.js +53 -0
  302. package/dist/surface/tools/tool-kit.d.ts +6 -0
  303. package/dist/surface/tools/tool-kit.js +2 -3
  304. package/dist/surface/tools/tool-surface.js +0 -1
  305. package/dist/surface/tools/tools.js +28 -5
  306. package/dist/surface/tools/type-spelling.d.ts +13 -0
  307. package/dist/surface/tools/type-spelling.js +36 -0
  308. package/dist/telemetry/agent-client.d.ts +6 -0
  309. package/dist/telemetry/agent-client.js +10 -0
  310. package/dist/telemetry/argument-shape.js +1 -0
  311. package/dist/telemetry/tool-refused.d.ts +6 -3
  312. package/dist/telemetry/tool-refused.js +8 -3
  313. package/docs/actions.mdx +3 -3
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/capabilities.mdx +61 -1
  316. package/docs/cli/affected.mdx +9 -7
  317. package/docs/cli/cloud.mdx +3 -3
  318. package/docs/cli/compare.mdx +94 -0
  319. package/docs/cli/doctor.mdx +1 -1
  320. package/docs/cli/grade.mdx +97 -0
  321. package/docs/cli/license.mdx +9 -7
  322. package/docs/cli/restart.mdx +7 -5
  323. package/docs/cli/rollback.mdx +5 -5
  324. package/docs/cli/stop.mdx +3 -3
  325. package/docs/cli/update.mdx +2 -2
  326. package/docs/cli/verify.mdx +23 -1
  327. package/docs/cli.mdx +33 -23
  328. package/docs/deploy-checks.md +1 -1
  329. package/docs/desktop.mdx +1 -1
  330. package/docs/enterprise.md +2 -2
  331. package/docs/environment.mdx +1 -1
  332. package/docs/flows.md +11 -1
  333. package/docs/frameworks.mdx +9 -9
  334. package/docs/getting-started.md +1 -1
  335. package/docs/install-manual.mdx +1 -1
  336. package/docs/license-activation.mdx +3 -3
  337. package/docs/packages/tauri.mdx +1 -1
  338. package/docs/packages.mdx +1 -1
  339. package/docs/predicates.mdx +1 -1
  340. package/docs/quickstart.mdx +68 -49
  341. package/docs/skill-setup.md +1 -1
  342. package/docs/skill.md +14 -13
  343. package/docs/telemetry.md +2 -2
  344. package/docs/tools/navigate.mdx +1 -1
  345. package/docs/tools/verify.mdx +4 -1
  346. package/docs/troubleshooting.mdx +1 -1
  347. package/docs/usage.md +17 -14
  348. package/package.json +13 -13
  349. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  350. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -6,6 +6,7 @@ import { AttachmentHistory } from './presence/attachment-history.js';
6
6
  import { claimedSessionOf, openedBeforeDeparture, pickDocumentSuccessor, } from './session-successor.js';
7
7
  import { SESSION_DISCONNECTED_REASON, SESSION_PARKED_REASON } from './facts/session-replaced.js';
8
8
  import { isErrorDocument } from './session-info.js';
9
+ import { carryOnNewDocument, departedNetworkOf, } from './facts/departed-network.js';
9
10
  /** Core's `originOf`, re-exported for this module's existing importers. */
10
11
  export { originOf };
11
12
  /**
@@ -37,18 +38,10 @@ function scopeSessions(sessions, scope) {
37
38
  * target it: both facts are already in hand here.
38
39
  */
39
40
  export function scopeMissError(connected, scope) {
40
- const who = scope?.projectId !== undefined
41
- ? `project '${scope.projectId}'`
42
- : scope?.url !== undefined
43
- ? `your app at ${scope.url}`
44
- : 'the active project';
41
+ const who = scopeName(scope);
45
42
  const anyUntagged = connected.some((s) => s.projectId === undefined);
46
43
  const anyTagged = connected.some((s) => s.projectId !== undefined);
47
- const how = anyUntagged && !anyTagged
48
- ? 'with no projectId'
49
- : anyUntagged && anyTagged
50
- ? 'under a different project or with no projectId'
51
- : 'under a different project';
44
+ const how = whereConnected(anyUntagged, anyTagged);
52
45
  const listed = connected
53
46
  .map((s) => s.projectId !== undefined
54
47
  ? `'${s.projectId}' (${s.url}, sessionId '${s.id}')`
@@ -62,6 +55,48 @@ export function scopeMissError(connected, scope) {
62
55
  `${listed}. The daemon scopes to the .reticle.json of the directory it was started in, so ` +
63
56
  `this is a scope mismatch, not a dead app. ${advice}`);
64
57
  }
58
+ /** What the scope names, in the words of the refusal. */
59
+ function scopeName(scope) {
60
+ if (scope?.projectId !== undefined) {
61
+ return `project '${scope.projectId}'`;
62
+ }
63
+ if (scope?.url !== undefined) {
64
+ return `your app at ${scope.url}`;
65
+ }
66
+ return 'the active project';
67
+ }
68
+ /** Where the connected sessions are, given which of them carry a projectId. */
69
+ function whereConnected(anyUntagged, anyTagged) {
70
+ if (!anyUntagged) {
71
+ return 'under a different project';
72
+ }
73
+ if (!anyTagged) {
74
+ return 'with no projectId';
75
+ }
76
+ return 'under a different project or with no projectId';
77
+ }
78
+ /**
79
+ * A session's rank for auto-selection: lower is better.
80
+ *
81
+ * 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat),
82
+ * 2 = an error document, 3 = attached but not answering commands. A wedged tab keeps streaming
83
+ * events, so recency RATES IT HIGHEST — it was beating a healthy sibling on the one signal scoring
84
+ * looked at, and every call auto-targeted the one tab guaranteed to time out. An error document (a
85
+ * dev server's 404 that still mounted the SDK) ranks below any working page, hidden or not: it is
86
+ * never the app the agent was asked about.
87
+ */
88
+ function rankOf(session) {
89
+ if (session.unresponsive()) {
90
+ return 3;
91
+ }
92
+ if (isErrorDocument(session.page)) {
93
+ return 2;
94
+ }
95
+ if (session.throttled()) {
96
+ return 1;
97
+ }
98
+ return 0;
99
+ }
65
100
  /**
66
101
  * Owns the set of connected browser sessions and the smart auto-selection that resolves which one a
67
102
  * tool targets when the agent omits an explicit sessionId. Extracted from session.ts so each file
@@ -96,6 +131,11 @@ export class SessionManager {
96
131
  #tombstones = new Map();
97
132
  /** The tabs already open when each tombstoned session departed: siblings, never its successor. */
98
133
  #siblingsAtDeparture = new Map();
134
+ /**
135
+ * The network record each departed document made after the agent's last act, kept until the same
136
+ * tab's next document connects (#1944). Bounded with the tombstones. See departed-network.ts.
137
+ */
138
+ #departedNetwork = new Map();
99
139
  /**
100
140
  * The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
101
141
  * without passing its own scope, this is applied — so auto-selection is project-scoped by default
@@ -145,10 +185,24 @@ export class SessionManager {
145
185
  // that silently fails to register is a leak nothing would report.
146
186
  declareDrivenRedactionKeys(session.id, session.redactKeys);
147
187
  this.#attachment.attached(session.id);
188
+ this.#carryDepartedNetwork(session, previous);
148
189
  this.#tombstones.delete(session.id);
149
190
  this.#siblingsAtDeparture.delete(session.id);
150
191
  return previous;
151
192
  }
193
+ /**
194
+ * Give a new document the requests its tab's previous document made after the last act (#1944).
195
+ * `previous` is that document when its socket has not closed yet; else the record kept at removal.
196
+ */
197
+ #carryDepartedNetwork(session, previous) {
198
+ const departed = previous !== undefined && previous !== session
199
+ ? departedNetworkOf(previous)
200
+ : this.#departedNetwork.get(session.id);
201
+ this.#departedNetwork.delete(session.id);
202
+ if (departed !== undefined) {
203
+ carryOnNewDocument(session, departed);
204
+ }
205
+ }
152
206
  /**
153
207
  * The other tabs of the same app as `driven`.
154
208
  *
@@ -173,6 +227,7 @@ export class SessionManager {
173
227
  session.disconnect(SESSION_PARKED_REASON);
174
228
  this.remove(session);
175
229
  this.#tombstones.delete(session.id);
230
+ this.#departedNetwork.delete(session.id);
176
231
  }
177
232
  isParked(sessionId) {
178
233
  return this.#parked.has(sessionId);
@@ -188,6 +243,13 @@ export class SessionManager {
188
243
  // the reconnect is exactly where that matters.
189
244
  this.#attachment.detached(session.id);
190
245
  this.#rememberTombstone(session);
246
+ // Kept only beside a tombstone, so the tombstone cap bounds it too.
247
+ const departedNetwork = this.#tombstones.has(session.id)
248
+ ? departedNetworkOf(session)
249
+ : undefined;
250
+ if (departedNetwork !== undefined) {
251
+ this.#departedNetwork.set(session.id, departedNetwork);
252
+ }
191
253
  return this.#sessions.delete(session.id);
192
254
  }
193
255
  #rememberTombstone(session) {
@@ -216,6 +278,7 @@ export class SessionManager {
216
278
  }
217
279
  this.#tombstones.delete(oldest);
218
280
  this.#siblingsAtDeparture.delete(oldest);
281
+ this.#departedNetwork.delete(oldest);
219
282
  }
220
283
  }
221
284
  #successorOf(sessionId) {
@@ -239,6 +302,10 @@ export class SessionManager {
239
302
  get(sessionId) {
240
303
  return this.#sessions.get(sessionId);
241
304
  }
305
+ /** The id the same tab connected as after this session's page went away, if one has. */
306
+ successorId(sessionId) {
307
+ return this.#successorOf(sessionId)?.id;
308
+ }
242
309
  /**
243
310
  * Did this id belong to a session that has since disconnected?
244
311
  *
@@ -515,33 +582,11 @@ export class SessionManager {
515
582
  return (`no connected session with id '${sessionId}'.${ended} Your app's tabs: ${named}. ` +
516
583
  'Retry with one of those, or omit sessionId entirely and let Reticle scope to your project.');
517
584
  }
518
- resolve(sessionId, scope) {
519
- if (sessionId !== undefined) {
520
- const found = this.#sessions.get(sessionId) ?? this.#successorOf(sessionId);
521
- if (found === undefined) {
522
- throw new Error(this.#unknownSessionError(sessionId));
523
- }
524
- found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
525
- return found;
526
- }
527
- if (0 === this.#sessions.size) {
528
- const closure = this.lastClosure();
529
- // A diagnosis beats a checklist: it names which of the three causes this actually is.
530
- const hint = this.#noSessionHint?.();
531
- // BOTH, when there are both — the two answer different questions and the pair is strictly
532
- // more than either. This used to be an either/or, and a recorded refusal DISCARDED the whole
533
- // diagnosis: the one case where the daemon has hard evidence about why nothing is connected
534
- // was also the one case where it threw away everything else it had learned, including the
535
- // port scan and the next action. The refusal leads, because it is the fact.
536
- const refusal = closure === undefined
537
- ? ''
538
- : ` NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`;
539
- // Hand the branch code to the refusal that is about to be reported for this throw. The
540
- // diagnosis is computed HERE and the refusal is classified from the message downstream, so
541
- // without this every no-session refusal lands in one undifferentiated bucket (#615).
542
- notePendingNoSessionReason(this.#noSessionReason?.());
543
- throw new Error(`${hint ?? NO_SESSION_CONNECTED_ERROR}${refusal}${NO_SESSION_NAVIGATE_FIX}`);
544
- }
585
+ /**
586
+ * The sessions an id-less call may choose from: in the project scope, then in the caller's own
587
+ * checkout. Throws when those scopes leave nothing, or leave tabs of more than one project.
588
+ */
589
+ #candidates(scope) {
545
590
  // Scope to the agent's active project FIRST, so a stray tab from another app/origin (e.g. a
546
591
  // leftover dashboard on a different port) is structurally unselectable — it never enters the
547
592
  // candidate set, no matter how recently it was heard from. This is the anti-cross-talk guard.
@@ -564,28 +609,20 @@ export class SessionManager {
564
609
  // exists to be. Naming the tab and its sessionId costs the agent one extra argument.
565
610
  throw new Error(scopeMissError(connected, effectiveScope));
566
611
  }
567
- if (1 === all.length) {
568
- const [only] = all;
569
- if (only === undefined) {
570
- throw new Error('session lookup failed');
571
- }
572
- only.markAgentActivity();
573
- return only;
574
- }
575
612
  // Two projects and nothing to choose between them: refuse, BEFORE any scoring.
576
613
  //
577
614
  // Reported from the field: a call from `apps/next-app-router` was answered by a `rowy-*`
578
615
  // session, and `act_and_wait` spent 16.9s returning `verified:'no'` about an app never under
579
616
  // test — a confident false verdict, with a source pointer, about code the agent never touched.
580
617
  //
581
- // The recency rule below cannot prevent it. It asks "is one of these clearly fresher", which is
582
- // the right question for two tabs of ONE app and the wrong question for two apps: the fresher
583
- // tab of the wrong project is still the wrong project. The reported case had a clear winner,
584
- // so the ambiguity check passed and the wrong app was selected.
618
+ // The recency rule in resolve() cannot prevent it. It asks "is one of these clearly fresher",
619
+ // which is the right question for two tabs of ONE app and the wrong question for two apps: the
620
+ // fresher tab of the wrong project is still the wrong project. The reported case had a clear
621
+ // winner, so the ambiguity check passed and the wrong app was selected.
585
622
  //
586
623
  // This sits ahead of scoring so freshness cannot rescue it. It only fires when nothing scoped
587
- // the call — an explicit `sessionId` returns far above, and an explicit or default scope has
588
- // already narrowed `all` to one project by here.
624
+ // the call — an explicit `sessionId` never reaches here, and an explicit or default scope has
625
+ // already narrowed `all` to one project by here. One session is one project, so it never fires.
589
626
  const projects = new Set(all.map((s) => s.projectId).filter((p) => p !== undefined));
590
627
  if (projects.size > 1) {
591
628
  const listed = all
@@ -598,18 +635,46 @@ export class SessionManager {
598
635
  'never touched — the daemon scopes to the directory it was started in, so running it above ' +
599
636
  'several apps leaves it unable to tell them apart.');
600
637
  }
601
- // Multiple sessions: score each (lower = better candidate for auto-selection).
602
- // 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat),
603
- // 2 = attached but not answering commands. A wedged tab keeps streaming events, so recency
604
- // RATES IT HIGHEST — it was beating a healthy sibling on the one signal scoring looked at, and
605
- // every call auto-targeted the one tab guaranteed to time out.
606
- const scored = all.map((s) => ({
607
- s,
608
- // An error document (a dev server's 404 that still mounted the SDK) ranks below any working
609
- // page, hidden or not: it is never the app the agent was asked about.
610
- score: s.unresponsive() ? 3 : isErrorDocument(s.page) ? 2 : s.throttled() ? 1 : 0,
611
- ms: s.lastSeenMs(),
612
- }));
638
+ return all;
639
+ }
640
+ resolve(sessionId, scope) {
641
+ if (sessionId !== undefined) {
642
+ const found = this.#sessions.get(sessionId) ?? this.#successorOf(sessionId);
643
+ if (found === undefined) {
644
+ throw new Error(this.#unknownSessionError(sessionId));
645
+ }
646
+ found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
647
+ return found;
648
+ }
649
+ if (0 === this.#sessions.size) {
650
+ const closure = this.lastClosure();
651
+ // A diagnosis beats a checklist: it names which of the three causes this actually is.
652
+ const hint = this.#noSessionHint?.();
653
+ // BOTH, when there are both — the two answer different questions and the pair is strictly
654
+ // more than either. This used to be an either/or, and a recorded refusal DISCARDED the whole
655
+ // diagnosis: the one case where the daemon has hard evidence about why nothing is connected
656
+ // was also the one case where it threw away everything else it had learned, including the
657
+ // port scan and the next action. The refusal leads, because it is the fact.
658
+ const refusal = closure === undefined
659
+ ? ''
660
+ : ` NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`;
661
+ // Hand the branch code to the refusal that is about to be reported for this throw. The
662
+ // diagnosis is computed HERE and the refusal is classified from the message downstream, so
663
+ // without this every no-session refusal lands in one undifferentiated bucket (#615).
664
+ notePendingNoSessionReason(this.#noSessionReason?.());
665
+ throw new Error(`${hint ?? NO_SESSION_CONNECTED_ERROR}${refusal}${NO_SESSION_NAVIGATE_FIX}`);
666
+ }
667
+ const all = this.#candidates(scope);
668
+ if (1 === all.length) {
669
+ const [only] = all;
670
+ if (only === undefined) {
671
+ throw new Error('session lookup failed');
672
+ }
673
+ only.markAgentActivity();
674
+ return only;
675
+ }
676
+ // Multiple sessions: score each (lower = better candidate for auto-selection). See rankOf().
677
+ const scored = all.map((s) => ({ s, score: rankOf(s), ms: s.lastSeenMs() }));
613
678
  const bestScore = Math.min(...scored.map((x) => x.score));
614
679
  const candidates = scored.filter((x) => x.score === bestScore);
615
680
  // Sort candidates by recency (ascending lastSeenMs = most recently active first).
@@ -651,6 +716,44 @@ export class SessionManager {
651
716
  best.s.markAgentActivity();
652
717
  return best.s;
653
718
  }
719
+ /**
720
+ * Resolve the session for a call that only some sessions can serve.
721
+ *
722
+ * With no sessionId, resolve() chooses by health and recency alone. A lease that could resize the
723
+ * page lost to a focused tab that could not, and the call was refused (#1959). Here, when exactly
724
+ * one candidate can serve the call, that candidate is chosen.
725
+ */
726
+ async resolveServing(sessionId, canServe) {
727
+ // A named session, or none connected: resolve() already gives the answer and its refusals.
728
+ if (sessionId !== undefined || 0 === this.#sessions.size) {
729
+ return this.resolve(sessionId);
730
+ }
731
+ // The same candidates resolve() chooses from, so the project and checkout scopes still apply.
732
+ // canServe can ask a browser, so each answer is awaited in turn.
733
+ const serving = [];
734
+ for (const candidate of this.#candidates()) {
735
+ if (await canServe(candidate)) {
736
+ serving.push(candidate);
737
+ }
738
+ }
739
+ // Exactly one can serve the call: there is nothing to guess.
740
+ const [onlyServing, secondServing] = serving;
741
+ if (onlyServing !== undefined && secondServing === undefined) {
742
+ onlyServing.markAgentActivity();
743
+ return onlyServing;
744
+ }
745
+ // None can serve: return the usual choice, so the tool gives its own refusal and its advice.
746
+ // Several can serve, and the usual choice cannot: choosing among them is a guess, so refuse
747
+ // and name them. canServe admits only the caller's own sessions, so no other agent's lease is
748
+ // named here.
749
+ const chosen = this.resolve();
750
+ if (0 < serving.length && !serving.includes(chosen)) {
751
+ const ids = serving.map((s) => `'${s.id}'`).join(', ');
752
+ throw new Error(`no sessionId was given, and ${String(serving.length)} sessions can serve this call: ` +
753
+ `${ids}. Pass sessionId to choose one.`);
754
+ }
755
+ return chosen;
756
+ }
654
757
  }
655
758
  /**
656
759
  * Which of a project's tabs belong to the agent's own git checkout.
@@ -679,6 +782,14 @@ export function scopeToCallerCheckout(sessions, caller, checkoutOfTab) {
679
782
  if (0 === siblings.length) {
680
783
  return { sessions: [] };
681
784
  }
785
+ // The caller is the folder the MCP proxy started in, not always where the agent works. Claude Code
786
+ // starts the proxy in the main checkout and then moves the agent into a linked worktree (#1916).
787
+ // So from the main checkout, one linked worktree that serves every tab is the agent's own: drive it.
788
+ // The tab names its `worktree`. Tabs of two worktrees, or a proxy in a linked worktree, stay refused.
789
+ const roots = new Set(siblings.map((p) => p.checkout?.root));
790
+ if (!caller.linked && 1 === roots.size && true === siblings[0]?.checkout?.linked) {
791
+ return { sessions: siblings.map((p) => p.session) };
792
+ }
682
793
  const listed = siblings
683
794
  .map((p) => `${describeCheckout(p.checkout)} (sessionId '${p.session.id}', ${p.session.url})`)
684
795
  .join(', ');
@@ -5,6 +5,7 @@ import type { RealInputProvider } from './portal/input/real-input.js';
5
5
  import { BrowserPool } from './portal/pool/browser-pool.js';
6
6
  import { type PageErrorJournal } from './portal/pool/page-error-journal.js';
7
7
  import type { PooledPageError } from './portal/pool/pool-contract.js';
8
+ import type { BrowserPoolOptions } from './portal/pool/pool-lease.js';
8
9
  import { type InjectedConnect } from './portal/pool/zero-install.js';
9
10
  import type { NonceMinter } from './portal/production/production-activation.js';
10
11
  /**
@@ -26,7 +27,7 @@ export declare function readerConnect(port: number, token: string | undefined):
26
27
  * dials with a dead nonce; mint per navigation if that is ever seen.
27
28
  */
28
29
  export declare function zeroInstallLoader(reader: InjectedConnect, nonces?: Pick<NonceMinter, 'mint'>): (url?: string) => string | undefined;
29
- export declare function createBrowserPool(headless: boolean, reader: InjectedConnect, onPageError?: (leaseId: string, error: PooledPageError) => void, onLeaseClosed?: (leaseId: string) => void, nonces?: Pick<NonceMinter, 'mint'>): BrowserPool;
30
+ export declare function createBrowserPool(headless: boolean, reader: InjectedConnect, onPageError?: (leaseId: string, error: PooledPageError) => void, onLeaseClosed?: (leaseId: string) => void, nonces?: Pick<NonceMinter, 'mint'>, onDialog?: BrowserPoolOptions['onDialog']): BrowserPool;
30
31
  /**
31
32
  * Journal the uncaught page errors the pool hears into the session that connects for that lease.
32
33
  *
@@ -68,7 +68,7 @@ export function zeroInstallLoader(reader, nonces) {
68
68
  return script ?? undefined;
69
69
  };
70
70
  }
71
- export function createBrowserPool(headless, reader, onPageError, onLeaseClosed, nonces) {
71
+ export function createBrowserPool(headless, reader, onPageError, onLeaseClosed, nonces, onDialog) {
72
72
  const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
73
73
  const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
74
74
  ? globalThis.crypto.randomUUID()
@@ -82,6 +82,7 @@ export function createBrowserPool(headless, reader, onPageError, onLeaseClosed,
82
82
  launchHeaded: playwrightLauncher({ headless: false }),
83
83
  ...(onPageError === undefined ? {} : { onPageError }),
84
84
  ...(onLeaseClosed === undefined ? {} : { onLeaseClosed }),
85
+ ...(onDialog === undefined ? {} : { onDialog }),
85
86
  });
86
87
  }
87
88
  /**
@@ -7,7 +7,7 @@ import { isToonable, resultToToon, MCP_SERVER_NAME } from '@reticlehq/core';
7
7
  import { tableForSurface } from '../tools/tools.js';
8
8
  import { filterTools, describeToolSurface, TOOL_SURFACE, toolAnnotations, } from '../tools/tool-surface.js';
9
9
  import { ReticleTool } from '@reticlehq/core';
10
- import { sharedParamShort } from './shared-params.js';
10
+ import { refusedKeyRedirect, sharedParamShort } from './shared-params.js';
11
11
  import { buildDynamicTools, descriptionOf } from '../tools/dynamic-tools.js';
12
12
  import { runTool } from '../tools/invoke-tool.js';
13
13
  import { sessionEnvelopeShape, newSnapshotCache } from '../tools/tool-kit.js';
@@ -132,7 +132,7 @@ const PREDICATE_FIELD_GRAMMAR_HINT = ' Bug-catching options: net.count (exact re
132
132
  'compare (page text vs server answer, no expected value: { left:{from:"text",scope}, ' +
133
133
  'right:{from:"net",urlContains,path}, as:"number" }), ' +
134
134
  'console.absent:true (action completed with a CLEAN ' +
135
- 'console), absent:true on element/text (it should be gone). Call reticle_tools for the full field ' +
135
+ 'console), absent:true on element/text (gone from the DOM; add visible:true for "not shown"). Call reticle_tools for the full field ' +
136
136
  'grammar of a kind.';
137
137
  const COMPACT_PREDICATE_DESCRIPTION = `${PREDICATE_KINDS}${PREDICATE_FIELD_GRAMMAR_HINT}`;
138
138
  /**
@@ -367,6 +367,7 @@ export function installFriendlyArgErrors(server, examples, allowedKeys = new Map
367
367
  const declared = [...(allowedKeys.get(toolName) ?? [])].join(', ');
368
368
  throw new McpError(ErrorCode.InvalidParams, `Unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')}. ` +
369
369
  `They were NOT applied — a result computed without them would look like an answer. ` +
370
+ refusedKeyRedirect(toolName, unknown) +
370
371
  (0 === declared.length ? '' : `${toolName} declares: ${declared}. `) +
371
372
  (example === undefined
372
373
  ? `Call reticle_tools { names: ["${toolName}"] } for its parameters.`
@@ -33,3 +33,5 @@ export declare const SHARED_PARAM_GUIDANCE: string;
33
33
  export declare function sharedParamShort(name: string, schema: z.ZodTypeAny): string | undefined;
34
34
  /** Is this a parameter whose guidance now lives in the instructions block? */
35
35
  export declare function isSharedParam(name: string): boolean;
36
+ /** The sentences that name the tool for each refused key, or an empty string. */
37
+ export declare function refusedKeyRedirect(toolName: string, refused: readonly string[]): string;
@@ -1,3 +1,4 @@
1
+ import { ReticleTool } from '@reticlehq/core';
1
2
  /**
2
3
  * Parameters that mean the same thing on every tool, documented ONCE instead of every turn.
3
4
  *
@@ -48,3 +49,33 @@ export function sharedParamShort(name, schema) {
48
49
  export function isSharedParam(name) {
49
50
  return Object.hasOwn(SHARED_PARAM_SHORT, name);
50
51
  }
52
+ /**
53
+ * Where to go when a tool refuses a parameter that another tool takes (#1944).
54
+ *
55
+ * An agent proved a click with `reticle_act_and_wait`, saw a request missing from the act's window,
56
+ * and asked for a wider window with `since`. The refusal listed what the tool declares and stopped
57
+ * there, so the agent did not learn that `reticle_assert` reads any window it is given. The refusal
58
+ * now names that tool.
59
+ *
60
+ * A lookup table, keyed by tool and then by the refused parameter. Add a row only where another
61
+ * tool really does what the refused parameter asked for.
62
+ */
63
+ const REDIRECTS = {
64
+ [ReticleTool.ACT_AND_WAIT]: {
65
+ since: `${ReticleTool.ACT_AND_WAIT} always reads the window that its own action opens. To check a ` +
66
+ `window that starts earlier, call ${ReticleTool.ASSERT} { predicate, since } with the cursor ` +
67
+ 'from an earlier result.',
68
+ },
69
+ };
70
+ /** The sentences that name the tool for each refused key, or an empty string. */
71
+ export function refusedKeyRedirect(toolName, refused) {
72
+ const forTool = REDIRECTS[toolName];
73
+ if (forTool === undefined) {
74
+ return '';
75
+ }
76
+ return refused
77
+ .map((key) => (Object.hasOwn(forTool, key) ? forTool[key] : undefined))
78
+ .filter((line) => line !== undefined)
79
+ .map((line) => `${line} `)
80
+ .join('');
81
+ }
@@ -10,10 +10,12 @@
10
10
  * used from the two points where observation can be lost: a write rejected at dispatch, and a wait
11
11
  * cut off mid-predicate.
12
12
  */
13
- import { type ReticleEvent } from '@reticlehq/core';
13
+ import { type Drift, type ReticleEvent } from '@reticlehq/core';
14
14
  import type { Session } from '../../../portal/session/session.js';
15
- import { type SuccessorRegistry } from '../../../portal/session/session-successor.js';
15
+ import { type SuccessorClock, type SuccessorRegistry } from '../../../portal/session/session-successor.js';
16
16
  import type { EvalResult } from '@reticlehq/engine/question/predicate/predicate-eval.js';
17
+ /** How long an act whose page navigated away waits for the lease's successor to connect. */
18
+ export declare const NAVIGATION_FOLLOW_GRACE_MS = 8000;
17
19
  interface FollowSuccessor {
18
20
  sessions: SuccessorRegistry;
19
21
  session: Session;
@@ -24,12 +26,27 @@ interface FollowSuccessor {
24
26
  predicateStarted: number;
25
27
  /** Re-ask the declared consequence on `next`, with whatever budget is left. */
26
28
  reevaluate: (next: Session, budgetMs: number) => Promise<EvalResult>;
29
+ /**
30
+ * Grace for the OTHER navigation shape (#2009): a FAILURE graded while the action's own form
31
+ * POST / link navigation was still in flight. The wait spent its budget on the document BEFORE
32
+ * it left, so nothing was "lost" and no successor was looked for — and the verdict then reads
33
+ * "still on /login" for a sign-in that worked. When set, a failed verdict waits this long for
34
+ * the document's successor and re-asks there; when it never connects, the verdict says the page
35
+ * navigated instead of leaving the failure standing. Undefined keeps the old behaviour.
36
+ */
37
+ failedFollowGraceMs?: number;
38
+ /** The act's own event cursor: the window the departure evidence is read from. */
39
+ since?: number;
40
+ /** Injected so tests do not sleep; defaults to the real clock. */
41
+ clock?: SuccessorClock;
27
42
  }
28
43
  interface FollowedOutcome {
29
44
  session: Session;
30
45
  verdict: EvalResult;
31
46
  /** True when the wait moved to a new document, so the caller must reset its event cursor. */
32
47
  followed: boolean;
48
+ /** The re-ask ran on the caller's grace — a budget extension declared in the result (#2009). */
49
+ budgetExtendedMs?: number;
33
50
  }
34
51
  /**
35
52
  * Follow the unique same-origin successor and re-ask there, or leave the verdict untouched.
@@ -56,8 +73,29 @@ export declare function disabledTargetNote(pass: boolean, actResult: unknown): s
56
73
  * failure, whichever way the consequence went.
57
74
  */
58
75
  export declare function unreachableTargetNote(actResult: unknown): string | undefined;
76
+ /**
77
+ * The drift for the first sub-step of an `ACT_SEQUENCE` result whose target no person could have
78
+ * clicked, or undefined. The page reports one effect for each sub-step it ran, in order. Synthetic
79
+ * dispatch reaches an unreachable target all the same, so a sequence is read sub-step by sub-step, as
80
+ * a single act is. `labelOf` names the sub-step, by its index in this result.
81
+ */
82
+ export declare function unreachableSubStepDrift(sequenceResult: unknown, labelOf: (at: number) => string): Drift | undefined;
59
83
  /** Why the acted target leaves the verdict undecided: no person could reach it, or it was disabled. */
60
84
  export declare function actTargetNote(pass: boolean, actResult: unknown): string | undefined;
85
+ /**
86
+ * The cause and the one fix for a read that came back `unknown`, in place of a bare fact (#1990).
87
+ * A page whose SDK is behind this daemon may not serve `match` at all, so the fix is the same
88
+ * SDK-behind sentence the listing shows. On a current page the read ran out of the assertion's own
89
+ * budget, and the fix is more of it. Any reason that is not a command timeout is returned as it came.
90
+ */
91
+ export declare function diagnoseTimeout(reason: string | undefined, session: {
92
+ readonly sdkVersion?: string | undefined;
93
+ }): string | undefined;
94
+ /** Why an act's verdict is `unknown`: a read that timed out, else an unreachable or disabled target. */
95
+ export declare const actInconclusive: (verdict: {
96
+ pass: boolean;
97
+ inconclusive?: string | undefined;
98
+ }, actResult: unknown, session: Session) => string | undefined;
61
99
  /**
62
100
  * The requests of a failed check's window, as its record carries them: method, url, status and the
63
101
  * start of the response body (redacted in the page before it was ever sent). Dev tooling is left out.