@reticlehq/server 3.8.2 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -22,10 +22,28 @@ export declare function within(child: string | undefined, parent: string): boole
22
22
  export declare function projectAppUrlFinder(io: Pick<FirstCallIo, 'project' | 'serving' | 'servingDirectory'>): () => Promise<string | undefined>;
23
23
  /** One lease per project for the daemon's life: a failure is reported once, never retried. */
24
24
  export declare function firstCallOpener(io: FirstCallIo): () => Promise<FirstCallOpened | undefined>;
25
+ /**
26
+ * The first-session offer, on the first tool call of each agent connection.
27
+ *
28
+ * The plugin's SessionStart hook says it when a session starts. A plugin installed in an open
29
+ * session and loaded with `/reload-plugins` fires no SessionStart, so the server says it too. Only
30
+ * the first call of a connection looks: an offer on a later call reads as noise.
31
+ */
32
+ export interface FirstSessionOfferIo {
33
+ /** The unwired web app this call is about; undefined when wired, not a web app, or no project. */
34
+ project: () => string | undefined;
35
+ /** Whether an app is attached now. */
36
+ connected: () => boolean;
37
+ /** Called once for each offer that goes out, with its project. */
38
+ offered: (dir: string) => void;
39
+ }
40
+ /** The offer for this connection's first call, or undefined. Each attach id gets one look. */
41
+ export declare function firstSessionOfferGate(io: FirstSessionOfferIo): (attachId: string) => string | undefined;
25
42
  /**
26
43
  * The daemon's two answers to an app nobody wired, built together because both ask the same
27
44
  * question first — which unwired web app is this caller in. `firstRun` names it when the agent
28
- * lists sessions; `openUnwiredApp` opens it zero-install on a page-needing call.
45
+ * lists sessions; `openUnwiredApp` opens it zero-install on a page-needing call;
46
+ * `firstSessionOffer` says the offer on the first call of each agent connection.
29
47
  */
30
48
  export declare function agentWiring(sessions: {
31
49
  list(): readonly unknown[];
@@ -33,4 +51,5 @@ export declare function agentWiring(sessions: {
33
51
  firstRun: FirstRunWiring;
34
52
  openUnwiredApp: () => Promise<FirstCallOpened | undefined>;
35
53
  projectAppUrl: () => Promise<string | undefined>;
54
+ firstSessionOffer: (attachId: string) => string | undefined;
36
55
  };
@@ -21,8 +21,11 @@ import { DEV_SERVER_PORTS } from '../../command/cli/ports/resolve/cli-port.js';
21
21
  import { callingClientDirectory } from '../../hooks/client-directory.js';
22
22
  import { detectDevCommandInProject, initCommandIn } from './dev-server/dev-command.js';
23
23
  import { probeDevServerStates } from './dev-server/dev-server-probe.js';
24
+ import { NextStepOffered } from '@reticlehq/core';
25
+ import { readProjectId } from '../../command/cli/ports/resolve/cli-port.js';
26
+ import { noteNextStepOffered } from '../../telemetry/onboarding-firsts.js';
24
27
  import { firstRunWiring } from './first-run-wiring.js';
25
- import { wiringConsent } from './no-session-next-action.js';
28
+ import { firstSessionOffer, wiringConsent } from './no-session-next-action.js';
26
29
  import { servingDirectoryOf } from './serving-directory.js';
27
30
  const LOCALHOST = 'http://localhost';
28
31
  /** The path with symlinks resolved when it exists (lsof reports `/private/tmp`, callers `/tmp`). */
@@ -87,10 +90,32 @@ export function firstCallOpener(io) {
87
90
  return { url, notice: openedNotice(url, initCommandIn(dir)) };
88
91
  };
89
92
  }
93
+ /** The offer for this connection's first call, or undefined. Each attach id gets one look. */
94
+ export function firstSessionOfferGate(io) {
95
+ // ponytail: one short id per MCP connection for the daemon's life; bound it if a daemon ever
96
+ // serves connections by the million.
97
+ const looked = new Set();
98
+ return (attachId) => {
99
+ if (looked.has(attachId)) {
100
+ return undefined;
101
+ }
102
+ looked.add(attachId);
103
+ if (io.connected()) {
104
+ return undefined;
105
+ }
106
+ const dir = io.project();
107
+ if (dir === undefined) {
108
+ return undefined;
109
+ }
110
+ io.offered(dir);
111
+ return firstSessionOffer(initCommandIn(dir));
112
+ };
113
+ }
90
114
  /**
91
115
  * The daemon's two answers to an app nobody wired, built together because both ask the same
92
116
  * question first — which unwired web app is this caller in. `firstRun` names it when the agent
93
- * lists sessions; `openUnwiredApp` opens it zero-install on a page-needing call.
117
+ * lists sessions; `openUnwiredApp` opens it zero-install on a page-needing call;
118
+ * `firstSessionOffer` says the offer on the first call of each agent connection.
94
119
  */
95
120
  export function agentWiring(sessions, lease) {
96
121
  const firstRun = firstRunWiring();
@@ -107,5 +132,12 @@ export function agentWiring(sessions, lease) {
107
132
  firstRun,
108
133
  openUnwiredApp: firstCallOpener(io),
109
134
  projectAppUrl: projectAppUrlFinder({ ...io, project: callerFolder }),
135
+ firstSessionOffer: firstSessionOfferGate({
136
+ ...io,
137
+ // The offer leads with the lease, so it is counted as a lease offer.
138
+ offered: (dir) => {
139
+ noteNextStepOffered(NextStepOffered.LEASE, () => readProjectId(dir) !== undefined);
140
+ },
141
+ }),
110
142
  };
111
143
  }
@@ -3,14 +3,40 @@ import type { StartOptions } from './start-options.js';
3
3
  import type { Bridge } from './portal/bridge/bridge.js';
4
4
  import type { RealInputProvider } from './portal/input/real-input.js';
5
5
  import { BrowserPool } from './portal/pool/browser-pool.js';
6
+ import { type PageErrorJournal } from './portal/pool/page-error-journal.js';
7
+ import type { PooledPageError } from './portal/pool/pool-contract.js';
8
+ import type { BrowserPoolOptions } from './portal/pool/pool-lease.js';
6
9
  import { type InjectedConnect } from './portal/pool/zero-install.js';
10
+ import type { NonceMinter } from './portal/production/production-activation.js';
7
11
  /**
8
12
  * Build the shared browser pool (one headless Chromium, N capped isolated leased contexts). Lazy —
9
13
  * no Chromium launches until the first lease — so creating it is free even when never used.
10
14
  */
11
15
  /** Where the zero-install reader connects: this daemon's own bridge, with its pairing token. */
12
16
  export declare function readerConnect(port: number, token: string | undefined): InjectedConnect;
13
- export declare function createBrowserPool(headless: boolean, reader: InjectedConnect): BrowserPool;
17
+ /**
18
+ * The zero-install script for pages that connect to `reader`, built on first use and kept: the
19
+ * page that needs it is one whose app never connected. Undefined when this install has no reader.
20
+ *
21
+ * A page that is not on loopback gets a nonce minted for its origin in place of the pairing token.
22
+ * The bridge admits a WebSocket from an origin only while a nonce names it, so the reader on a
23
+ * deployed page was refused with a 403 (#2016). The nonce also keeps the machine's pairing token
24
+ * out of a page that somebody else serves.
25
+ *
26
+ * ponytail: the nonce lives for one drive (SESSION_NONCE_TTL_MS). A lease reloaded after that
27
+ * dials with a dead nonce; mint per navigation if that is ever seen.
28
+ */
29
+ export declare function zeroInstallLoader(reader: InjectedConnect, nonces?: Pick<NonceMinter, 'mint'>): (url?: string) => string | undefined;
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;
31
+ /**
32
+ * Journal the uncaught page errors the pool hears into the session that connects for that lease.
33
+ *
34
+ * The pool sees every throw its pages make from before the first navigation; the SDK only exists
35
+ * afterwards. Without this wiring an import-time crash — a blank page with no error text anywhere
36
+ * (#1966) — was invisible to the agent driving it. Returns the journal so the pool's sink can be
37
+ * handed over; the bridge hook is registered here.
38
+ */
39
+ export declare function attachPageErrorJournal(bridge: Bridge): PageErrorJournal;
14
40
  /**
15
41
  * Route CDP-authoritative network detail onto the driven session's journal.
16
42
  *
@@ -26,7 +52,9 @@ export declare function makeNetworkDetailRouter(bridge: Bridge, driveUrl: string
26
52
  * so a failed launch never leaks a WS port. A past divergence between the two paths let daemon mode
27
53
  * run with a different setup — this removes that risk.
28
54
  */
29
- export declare function resolveRealInput(options: StartOptions, onNavigateError: () => Promise<void>, onNetworkDetail?: (detail: NetworkDetail) => void): Promise<{
55
+ export declare function resolveRealInput(options: StartOptions, onNavigateError: () => Promise<void>, onNetworkDetail?: (detail: NetworkDetail) => void,
56
+ /** Where a driven page with no SDK connects the zero-install reader. Absent: it gets none. */
57
+ reader?: InjectedConnect): Promise<{
30
58
  realInput?: RealInputProvider;
31
59
  owned?: {
32
60
  dispose: () => Promise<void>;
@@ -23,7 +23,10 @@ import { SERVER_VERSION } from './command/version/identity/server-version.js';
23
23
  import { CdpRealInputProvider, LaunchedRealInputProvider } from './portal/input/real-input.js';
24
24
  import { BrowserPool } from './portal/pool/browser-pool.js';
25
25
  import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
26
+ import { makePageErrorJournal } from './portal/pool/page-error-journal.js';
27
+ import { resolveLeasedSessionId } from './surface/tools/lease-tools.js';
26
28
  import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
29
+ import { offLoopbackOrigin } from './portal/pool/context-permissions.js';
27
30
  /**
28
31
  * Build the shared browser pool (one headless Chromium, N capped isolated leased contexts). Lazy —
29
32
  * no Chromium launches until the first lease — so creating it is free even when never used.
@@ -32,26 +35,85 @@ import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './
32
35
  export function readerConnect(port, token) {
33
36
  return { url: bridgeWsUrl(port), ...(token === undefined ? {} : { token }) };
34
37
  }
35
- export function createBrowserPool(headless, reader) {
36
- const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
37
- const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
38
- ? globalThis.crypto.randomUUID()
39
- : String(Date.now())}`;
40
- // Built on first use and kept: the page that needs it is one whose app never connected.
38
+ /**
39
+ * The zero-install script for pages that connect to `reader`, built on first use and kept: the
40
+ * page that needs it is one whose app never connected. Undefined when this install has no reader.
41
+ *
42
+ * A page that is not on loopback gets a nonce minted for its origin in place of the pairing token.
43
+ * The bridge admits a WebSocket from an origin only while a nonce names it, so the reader on a
44
+ * deployed page was refused with a 403 (#2016). The nonce also keeps the machine's pairing token
45
+ * out of a page that somebody else serves.
46
+ *
47
+ * ponytail: the nonce lives for one drive (SESSION_NONCE_TTL_MS). A lease reloaded after that
48
+ * dials with a dead nonce; mint per navigation if that is ever seen.
49
+ */
50
+ export function zeroInstallLoader(reader, nonces) {
41
51
  let script;
42
- const zeroInstallScript = () => {
52
+ return (url) => {
53
+ const origin = offLoopbackOrigin(url);
54
+ if (origin !== undefined && nonces !== undefined) {
55
+ const bundle = readReaderBundle();
56
+ if (bundle === undefined) {
57
+ return undefined;
58
+ }
59
+ return zeroInstallScriptFor(bundle, {
60
+ url: reader.url,
61
+ token: nonces.mint({ origin }).nonce,
62
+ });
63
+ }
43
64
  if (script === undefined) {
44
65
  const bundle = readReaderBundle();
45
66
  script = bundle === undefined ? null : zeroInstallScriptFor(bundle, reader);
46
67
  }
47
68
  return script ?? undefined;
48
69
  };
70
+ }
71
+ export function createBrowserPool(headless, reader, onPageError, onLeaseClosed, nonces, onDialog) {
72
+ const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
73
+ const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
74
+ ? globalThis.crypto.randomUUID()
75
+ : String(Date.now())}`;
49
76
  return new BrowserPool(playwrightLauncher({ headless }), {
50
77
  maxContexts,
51
78
  genSessionId,
52
- zeroInstallScript,
79
+ zeroInstallScript: zeroInstallLoader(reader, nonces),
80
+ // The reader dials this origin; a lease off loopback adds it to the page CSP (#2016).
81
+ daemonOrigin: new URL(reader.url).origin,
53
82
  launchHeaded: playwrightLauncher({ headless: false }),
83
+ ...(onPageError === undefined ? {} : { onPageError }),
84
+ ...(onLeaseClosed === undefined ? {} : { onLeaseClosed }),
85
+ ...(onDialog === undefined ? {} : { onDialog }),
86
+ });
87
+ }
88
+ /**
89
+ * Journal the uncaught page errors the pool hears into the session that connects for that lease.
90
+ *
91
+ * The pool sees every throw its pages make from before the first navigation; the SDK only exists
92
+ * afterwards. Without this wiring an import-time crash — a blank page with no error text anywhere
93
+ * (#1966) — was invisible to the agent driving it. Returns the journal so the pool's sink can be
94
+ * handed over; the bridge hook is registered here.
95
+ */
96
+ export function attachPageErrorJournal(bridge) {
97
+ const journal = makePageErrorJournal({
98
+ resolveSession: (leaseId) => {
99
+ // Same resolution the lease tool uses: the lease id, or the id of a session whose URL
100
+ // carries the lease marker — an app that registered under its own name still reaches its lease.
101
+ const id = resolveLeasedSessionId(bridge.sessions, leaseId);
102
+ if (id === undefined) {
103
+ return undefined;
104
+ }
105
+ const session = bridge.sessions.get(id);
106
+ return session === undefined ? undefined : session;
107
+ },
108
+ });
109
+ bridge.attachSessionCreate((session) => journal.onSessionCreate(session));
110
+ // The daemon runs for days; both of the journal's maps are keyed by things that end, so both
111
+ // endings are hooked. The bridge's end hook is additive — the durable journal's teardown runs too.
112
+ bridge.attachSessionEnd((session) => {
113
+ journal.onSessionEnd(session.id);
114
+ return Promise.resolve();
54
115
  });
116
+ return journal;
55
117
  }
56
118
  /**
57
119
  * Route CDP-authoritative network detail onto the driven session's journal.
@@ -98,7 +160,9 @@ export function makeNetworkDetailRouter(bridge, driveUrl) {
98
160
  * so a failed launch never leaks a WS port. A past divergence between the two paths let daemon mode
99
161
  * run with a different setup — this removes that risk.
100
162
  */
101
- export async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
163
+ export async function resolveRealInput(options, onNavigateError, onNetworkDetail,
164
+ /** Where a driven page with no SDK connects the zero-install reader. Absent: it gets none. */
165
+ reader) {
102
166
  const driveUrl = options.driveUrl;
103
167
  if (driveUrl !== undefined && driveUrl.length > 0) {
104
168
  const headless = options.headless ?? true;
@@ -114,6 +178,7 @@ export async function resolveRealInput(options, onNavigateError, onNetworkDetail
114
178
  ...(injectConnect !== undefined ? { injectConnect } : {}),
115
179
  ...(storageState !== undefined ? { storageState } : {}),
116
180
  ...(onNetworkDetail !== undefined ? { onNetworkDetail } : {}),
181
+ ...(reader !== undefined ? { zeroInstallScript: zeroInstallLoader(reader) } : {}),
117
182
  }));
118
183
  const launched = factory({ driveUrl, headless });
119
184
  try {
@@ -5,6 +5,8 @@ import { localInitializeResponse, degradedInstructions, isHandshakeLine, drainLi
5
5
  export { HandshakeReplay, RECONNECT_INITIALIZE_ID } from './proxy/proxy-handshake.js';
6
6
  import { isToolCallRequest, isToolsListRequest, ToolCatalogCache } from './tool-catalog-cache.js';
7
7
  import { rememberEnumerated, rememberProxyStarted, rememberToolCalled } from './attach-memory.js';
8
+ import { initializeClientName, rememberAgentConnected } from './peer/agents-connected.js';
9
+ import { agentClientOf } from '../../hooks/coding-agents.js';
8
10
  import { toolsChangedNotification } from './proxy/proxy-handshake.js';
9
11
  import { LOOPBACK_HOST, MCP_SSE_PATH, MCP_SHUTDOWN_EVENT, ReticleEnv, CONTRACT_FINGERPRINT, ProxyExitReason, PROXY_ID_PARAM, } from '@reticlehq/core';
10
12
  import { SseFrameParser, buildSessionUrl } from './sse-frame-parser.js';
@@ -17,7 +19,8 @@ import { hasAnyProjectConnectedBefore } from '../../memory/recall/prior/connecti
17
19
  import { projectIdsAt } from '../../command/cli/ports/resolve/cli-port.js';
18
20
  import { PEER_VERSION_PARAM, PEER_CONTRACT_PARAM, agentSessionQuery, } from '../../command/version/peer-announce.js';
19
21
  import { onStreamDrop, onClientRequest, onReconnectBudgetSpent, onQueueExpired, OnDrop, OnRequest, } from './proxy/proxy-lifecycle.js';
20
- import { describePresence, probePresence } from '../../command/daemon/binding/port-presence.js';
22
+ import { probePresence } from '../../command/daemon/binding/port-presence.js';
23
+ import { describePortState } from '../../command/daemon/port-state.js';
21
24
  import { PROXY_ID, flushProxyExit, noteProxyDrop, noteProxyToolCall, } from '../../telemetry/proxy-telemetry.js';
22
25
  export { flushProxyExit, reportWakeRefused } from '../../telemetry/proxy-telemetry.js';
23
26
  import { detectStack } from '../../telemetry/feedback-context.js';
@@ -513,7 +516,7 @@ boot) {
513
516
  // The sentence `doctor` has always been able to say, in the log the agent's own process
514
517
  // writes. A FOREIGN holder used to be indistinguishable from a free port here, and the
515
518
  // proxy spun against it instead of saying which one it was.
516
- const sentence = describePresence(presence, port);
519
+ const sentence = describePortState(presence, port);
517
520
  proxyLog('reticle_mcp_proxy_dormant', { port, presence, note: sentence });
518
521
  // Nothing is coming that could answer the client's handshake through this port, and the
519
522
  // proxy now knows that rather than merely suspecting it. Waiting out LOCAL_HANDSHAKE_MS
@@ -792,6 +795,11 @@ boot) {
792
795
  if (isToolsListRequest(trimmed)) {
793
796
  rememberEnumerated(reticleStateHome(), port);
794
797
  }
798
+ // WHICH agent connected, so the installer and `status` can say "Connected: Cursor ✓".
799
+ const client = agentClientOf(initializeClientName(trimmed));
800
+ if (client !== undefined) {
801
+ rememberAgentConnected(reticleStateHome(), client);
802
+ }
795
803
  // The hop every other check misses. Recorded on the REQUEST rather than a reply, because
796
804
  // the question is whether anything ever left the agent — a call that leaves and gets no
797
805
  // answer is a different fault from one that was never made, and only this side sees it.
@@ -7,11 +7,12 @@ 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';
14
14
  import { buildErrorPayload, codedRefusalOf } from '../tools/error-recovery.js';
15
+ import { withRepeatHint } from '../tools/repeat-hint.js';
15
16
  import { connectionSkew, takeVersionSkewOnto } from '../../command/version/version-nudge.js';
16
17
  import { resultIsError } from './faults/mcp-is-error.js';
17
18
  import { consumerVerdictRefusal, reservedVerdictKeysIn } from './consumer-verdict-guard.js';
@@ -131,7 +132,7 @@ const PREDICATE_FIELD_GRAMMAR_HINT = ' Bug-catching options: net.count (exact re
131
132
  'compare (page text vs server answer, no expected value: { left:{from:"text",scope}, ' +
132
133
  'right:{from:"net",urlContains,path}, as:"number" }), ' +
133
134
  'console.absent:true (action completed with a CLEAN ' +
134
- '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 ' +
135
136
  'grammar of a kind.';
136
137
  const COMPACT_PREDICATE_DESCRIPTION = `${PREDICATE_KINDS}${PREDICATE_FIELD_GRAMMAR_HINT}`;
137
138
  /**
@@ -149,8 +150,9 @@ function leanZodShape(shape, advertised, predicateAnchor) {
149
150
  let hintSpent = predicateAnchor !== undefined;
150
151
  for (const [key, schema] of Object.entries(shape)) {
151
152
  if (isPredicateParam(schema)) {
152
- // Every tool keeps the kinds; only the navigation hint is anchored.
153
- const compact = z.record(z.string(), z.unknown());
153
+ // Every tool keeps the kinds; only the navigation hint is anchored. A bare string is visible
154
+ // text (core's `PredicateSchema`), so the SDK must let it through to that parse.
155
+ const compact = z.union([z.string(), z.record(z.string(), z.unknown())]);
154
156
  out[key] = (schema.isOptional() ? compact.optional() : compact).describe(hintSpent ? PREDICATE_KINDS : COMPACT_PREDICATE_DESCRIPTION);
155
157
  hintSpent = true;
156
158
  continue;
@@ -365,6 +367,7 @@ export function installFriendlyArgErrors(server, examples, allowedKeys = new Map
365
367
  const declared = [...(allowedKeys.get(toolName) ?? [])].join(', ');
366
368
  throw new McpError(ErrorCode.InvalidParams, `Unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')}. ` +
367
369
  `They were NOT applied — a result computed without them would look like an answer. ` +
370
+ refusedKeyRedirect(toolName, unknown) +
368
371
  (0 === declared.length ? '' : `${toolName} declares: ${declared}. `) +
369
372
  (example === undefined
370
373
  ? `Call reticle_tools { names: ["${toolName}"] } for its parameters.`
@@ -769,7 +772,7 @@ tools = tableForSurface(profile)) {
769
772
  content: [
770
773
  {
771
774
  type: 'text',
772
- text: JSON.stringify(liveCallValues(takeVersionSkewOnto(buildErrorPayload(message, codedRefusalOf(error)), deps.peerSkew), advertisedNames)),
775
+ text: JSON.stringify(liveCallValues(takeVersionSkewOnto(withRepeatHint(buildErrorPayload(message, codedRefusalOf(error)), error), deps.peerSkew), advertisedNames)),
773
776
  },
774
777
  ],
775
778
  };
@@ -0,0 +1,6 @@
1
+ /** The `AgentClient` values of every agent that has connected. Empty on a missing or broken file. */
2
+ export declare function connectedAgentClients(stateDir: string): ReadonlySet<string>;
3
+ /** Record that `client` (an `AgentClient` value) connected. Written once per client. */
4
+ export declare function rememberAgentConnected(stateDir: string, client: string): void;
5
+ /** `clientInfo.name` of an MCP `initialize` request line, or undefined for any other line. */
6
+ export declare function initializeClientName(line: string): string | undefined;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Which coding agents have connected to Reticle on this machine, by name.
3
+ *
4
+ * `attach-memory.ts` records that SOME client attached on a port. It cannot say which one, so with
5
+ * two agents a healthy one hides a broken one. This record keeps the client that the MCP
6
+ * `initialize` handshake names, as its closed `AgentClient` value. It is machine wide, because the
7
+ * installer runs before there is a project and therefore before there is a port.
8
+ *
9
+ * The proxy writes it when the request arrives. It records a fact that happened, never an inference
10
+ * from a config file. Best-effort in both directions: a hint never breaks the client path.
11
+ */
12
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ const RECORD_FILE = 'agents-connected.json';
15
+ const INITIALIZE_METHOD = 'initialize';
16
+ /** The `AgentClient` values of every agent that has connected. Empty on a missing or broken file. */
17
+ export function connectedAgentClients(stateDir) {
18
+ try {
19
+ const parsed = JSON.parse(readFileSync(join(stateDir, RECORD_FILE), 'utf8'));
20
+ return new Set(Array.isArray(parsed) ? parsed.filter((v) => 'string' === typeof v) : []);
21
+ }
22
+ catch {
23
+ return new Set();
24
+ }
25
+ }
26
+ /** Record that `client` (an `AgentClient` value) connected. Written once per client. */
27
+ export function rememberAgentConnected(stateDir, client) {
28
+ const known = connectedAgentClients(stateDir);
29
+ if (known.has(client)) {
30
+ return;
31
+ }
32
+ try {
33
+ mkdirSync(stateDir, { recursive: true });
34
+ writeFileSync(join(stateDir, RECORD_FILE), JSON.stringify([...known, client]), 'utf8');
35
+ }
36
+ catch {
37
+ // An unwritable state home is reported elsewhere; it must not break the client path.
38
+ }
39
+ }
40
+ /** `clientInfo.name` of an MCP `initialize` request line, or undefined for any other line. */
41
+ export function initializeClientName(line) {
42
+ try {
43
+ const msg = JSON.parse(line);
44
+ if ('object' !== typeof msg || null === msg) {
45
+ return undefined;
46
+ }
47
+ const { method, params } = msg;
48
+ if (INITIALIZE_METHOD !== method || 'object' !== typeof params || null === params) {
49
+ return undefined;
50
+ }
51
+ const info = params.clientInfo;
52
+ const name = 'object' === typeof info && null !== info ? info.name : undefined;
53
+ return 'string' === typeof name && 0 < name.length ? name : undefined;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
@@ -1,6 +1,7 @@
1
1
  import * as net from 'node:net';
2
2
  import { LOOPBACK_HOST } from '@reticlehq/core';
3
- import { describePresence, presenceIsUsable, probePresence, PortPresence, } from '../../../command/daemon/binding/port-presence.js';
3
+ import { presenceIsUsable, probePresence, PortPresence, } from '../../../command/daemon/binding/port-presence.js';
4
+ import { describePortState } from '../../../command/daemon/port-state.js';
4
5
  import { fetchStatus } from '../../../command/daemon/binding/daemon-status-probe.js';
5
6
  import { isAlive, readPid } from '../../../command/daemon/daemon.js';
6
7
  const DEFAULT_DAEMON_READY_TIMEOUT_MS = 10_000;
@@ -21,6 +22,16 @@ function delay(ms) {
21
22
  export function daemonPollDelayMs(attempt) {
22
23
  return Math.min(DAEMON_POLL_INTERVAL_MS * attempt, DAEMON_POLL_MAX_INTERVAL_MS);
23
24
  }
25
+ const CONNECTION_RESET = 'ECONNRESET';
26
+ /**
27
+ * A loopback connect to a port with no listener is refused at once. A timeout means a listener
28
+ * whose queue is full because it accepts nothing: a daemon stuck in its exit (#1937). Read as
29
+ * "free", that made `status` say the port was free while a process held it.
30
+ *
31
+ * Not on Windows: it retries a refused loopback connect for about two seconds, so there a timeout
32
+ * is also what a free port gives.
33
+ */
34
+ const TIMEOUT_MEANS_HELD = 'win32' !== process.platform;
24
35
  /**
25
36
  * Returns true if something is already listening on the reticle port.
26
37
  * Uses a plain TCP probe so we don't create a side-effectful SSE session
@@ -38,10 +49,11 @@ export function probeDaemon(port, host = LOOPBACK_HOST) {
38
49
  socket.destroy();
39
50
  resolve(true);
40
51
  });
41
- socket.on('error', () => resolve(false));
52
+ // A reset is an answer from a listener, not "nothing listens".
53
+ socket.on('error', (err) => resolve(CONNECTION_RESET === err.code));
42
54
  socket.on('timeout', () => {
43
55
  socket.destroy();
44
- resolve(false);
56
+ resolve(TIMEOUT_MEANS_HELD);
45
57
  });
46
58
  socket.connect(port, host);
47
59
  });
@@ -70,7 +82,7 @@ export async function waitForDaemon(port, probes = { tcpOpen: probeDaemon, statu
70
82
  return;
71
83
  if (false === probes.alive?.()) {
72
84
  throw new Error(`the reticle daemon on port ${String(port)} exited before it was ready — ` +
73
- describePresence(presence, port));
85
+ describePortState(presence, port));
74
86
  }
75
87
  attempt++;
76
88
  await delay(daemonPollDelayMs(attempt));
@@ -79,7 +91,7 @@ export async function waitForDaemon(port, probes = { tcpOpen: probeDaemon, statu
79
91
  // means something is squatting the port. Reporting only the timeout sent readers hunting the
80
92
  // wrong one.
81
93
  throw new Error(`reticle daemon did not become ready on port ${String(port)} within ` +
82
- `${String(DAEMON_READY_TIMEOUT_MS)}ms — ${describePresence(presence, port)}`);
94
+ `${String(DAEMON_READY_TIMEOUT_MS)}ms — ${describePortState(presence, port)}`);
83
95
  }
84
96
  /** True while the process in the daemon pid file for `port` runs. */
85
97
  export function daemonProcessAlive(port) {
@@ -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.