@reticlehq/server 2.12.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/SKILL.md +67 -109
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -1
  5. package/dist/bridge/bridge.js +247 -164
  6. package/dist/bridge/pairing-token.d.ts +5 -0
  7. package/dist/bridge/pairing-token.js +28 -0
  8. package/dist/bridge/protocol-skew.d.ts +37 -0
  9. package/dist/bridge/protocol-skew.js +45 -0
  10. package/dist/capsule/capsule-store.d.ts +6 -6
  11. package/dist/capsule/causal-summary.js +7 -2
  12. package/dist/cli/cli-doctor.js +31 -1
  13. package/dist/cli/cli-launch.d.ts +11 -1
  14. package/dist/cli/cli-launch.js +59 -10
  15. package/dist/cli/cli-parse.d.ts +15 -1
  16. package/dist/cli/cli-parse.js +212 -8
  17. package/dist/cli/cli-verify.d.ts +1 -0
  18. package/dist/cli/cli-verify.js +3 -2
  19. package/dist/cli/cloud-cli.js +428 -259
  20. package/dist/cli/cloud-keystore.d.ts +37 -0
  21. package/dist/cli/cloud-keystore.js +97 -0
  22. package/dist/cli/cloud-kit.d.ts +37 -0
  23. package/dist/cli/cloud-kit.js +121 -0
  24. package/dist/cli/cloud-login.d.ts +17 -0
  25. package/dist/cli/cloud-login.js +183 -0
  26. package/dist/cli/cloud-session.d.ts +48 -0
  27. package/dist/cli/cloud-session.js +53 -0
  28. package/dist/cli/config-discovery.js +1 -0
  29. package/dist/cli/daemon-start-options.d.ts +32 -0
  30. package/dist/cli/daemon-start-options.js +38 -0
  31. package/dist/cli/device-flow.d.ts +76 -0
  32. package/dist/cli/device-flow.js +56 -0
  33. package/dist/cli/doctor-rows.d.ts +9 -0
  34. package/dist/cli/doctor-rows.js +9 -0
  35. package/dist/cli/drive-attach.d.ts +13 -0
  36. package/dist/cli/drive-attach.js +18 -0
  37. package/dist/cli/drive-command.js +7 -3
  38. package/dist/cli/mcp-command.js +51 -20
  39. package/dist/cli/project-name.d.ts +30 -0
  40. package/dist/cli/project-name.js +44 -0
  41. package/dist/cli/status-next-action.d.ts +14 -0
  42. package/dist/cli/status-next-action.js +4 -4
  43. package/dist/cli.d.ts +1 -0
  44. package/dist/cli.js +134 -80
  45. package/dist/cloud/cloud-config.d.ts +22 -1
  46. package/dist/cloud/cloud-config.js +64 -5
  47. package/dist/cloud/cloud-sync.d.ts +5 -5
  48. package/dist/cloud/cloud-sync.js +33 -2
  49. package/dist/cloud/sync-cycle.d.ts +110 -0
  50. package/dist/cloud/sync-cycle.js +270 -0
  51. package/dist/cloud/sync-daemon.d.ts +69 -0
  52. package/dist/cloud/sync-daemon.js +260 -0
  53. package/dist/cloud/sync-disk.d.ts +9 -0
  54. package/dist/cloud/sync-disk.js +191 -0
  55. package/dist/cloud/sync-hash.d.ts +2 -0
  56. package/dist/cloud/sync-hash.js +35 -0
  57. package/dist/crawl/crawl-tools.js +5 -3
  58. package/dist/crawl/crawl.d.ts +3 -0
  59. package/dist/crawl/crawl.js +5 -0
  60. package/dist/daemon/daemon-resolve.d.ts +43 -0
  61. package/dist/daemon/daemon-resolve.js +69 -0
  62. package/dist/daemon/dev-servers.d.ts +15 -0
  63. package/dist/daemon/dev-servers.js +68 -0
  64. package/dist/daemon/wake-decision.d.ts +32 -0
  65. package/dist/daemon/wake-decision.js +38 -0
  66. package/dist/domain/domain-tools.js +2 -2
  67. package/dist/events/contradictions.d.ts +18 -0
  68. package/dist/events/contradictions.js +134 -21
  69. package/dist/events/declared.d.ts +7 -4
  70. package/dist/events/declared.js +15 -6
  71. package/dist/events/echo-mismatch.js +64 -14
  72. package/dist/events/event-filters.d.ts +8 -0
  73. package/dist/events/event-filters.js +19 -2
  74. package/dist/events/predicate-eval.js +53 -5
  75. package/dist/events/predicate-precheck.d.ts +8 -0
  76. package/dist/events/predicate-precheck.js +41 -0
  77. package/dist/events/predicate-route.d.ts +14 -0
  78. package/dist/events/predicate-route.js +36 -8
  79. package/dist/events/predicate.d.ts +5 -0
  80. package/dist/events/predicate.js +37 -5
  81. package/dist/events/split-text-miss.d.ts +30 -0
  82. package/dist/events/split-text-miss.js +44 -0
  83. package/dist/flows/flake.d.ts +6 -6
  84. package/dist/flows/flow-memory-consult.d.ts +55 -0
  85. package/dist/flows/flow-memory-consult.js +38 -0
  86. package/dist/flows/flow-replay-run.d.ts +18 -7
  87. package/dist/flows/flow-replay-run.js +239 -22
  88. package/dist/flows/flow-replay.js +11 -2
  89. package/dist/flows/flow-step-runners.d.ts +8 -0
  90. package/dist/flows/flow-step-runners.js +8 -2
  91. package/dist/flows/flow-store-for-session.d.ts +31 -0
  92. package/dist/flows/flow-store-for-session.js +33 -0
  93. package/dist/flows/flow-tools.js +58 -32
  94. package/dist/flows/flows.d.ts +8 -0
  95. package/dist/flows/flows.js +468 -2
  96. package/dist/flows/heal-run.js +5 -2
  97. package/dist/flows/predicate-to-expect.d.ts +17 -12
  98. package/dist/flows/predicate-to-expect.js +30 -12
  99. package/dist/flows/recordings.d.ts +13 -1
  100. package/dist/flows/recordings.js +17 -2
  101. package/dist/flows/replay.d.ts +22 -2
  102. package/dist/flows/replay.js +28 -2
  103. package/dist/flows/verify-change-tools.js +5 -2
  104. package/dist/honesty/blind-spots.d.ts +55 -2
  105. package/dist/honesty/blind-spots.js +63 -3
  106. package/dist/honesty/honesty.d.ts +24 -0
  107. package/dist/honesty/honesty.js +25 -1
  108. package/dist/honesty/instrumentation-gaps.d.ts +9 -0
  109. package/dist/honesty/instrumentation-gaps.js +29 -2
  110. package/dist/honesty/verified.d.ts +2 -1
  111. package/dist/honesty/verified.js +16 -1
  112. package/dist/http-server.js +12 -0
  113. package/dist/impact/impact-recorder.d.ts +31 -8
  114. package/dist/impact/impact-recorder.js +104 -21
  115. package/dist/impact/impact-store.d.ts +15 -1
  116. package/dist/impact/impact-store.js +43 -2
  117. package/dist/index.js +113 -7
  118. package/dist/init/agent-rules.d.ts +13 -6
  119. package/dist/init/agent-rules.js +23 -12
  120. package/dist/init/astro-patch.d.ts +16 -0
  121. package/dist/init/astro-patch.js +28 -0
  122. package/dist/init/capabilities.d.ts +4 -1
  123. package/dist/init/capabilities.js +22 -2
  124. package/dist/init/confirm.d.ts +31 -7
  125. package/dist/init/confirm.js +48 -8
  126. package/dist/init/cra.d.ts +20 -4
  127. package/dist/init/cra.js +57 -17
  128. package/dist/init/csp-check.js +17 -2
  129. package/dist/init/csp-doctor.d.ts +8 -1
  130. package/dist/init/csp-doctor.js +8 -3
  131. package/dist/init/dev-script.d.ts +3 -1
  132. package/dist/init/dev-script.js +8 -5
  133. package/dist/init/format-generated.d.ts +18 -0
  134. package/dist/init/format-generated.js +39 -0
  135. package/dist/init/install-hint.d.ts +9 -0
  136. package/dist/init/install-hint.js +29 -0
  137. package/dist/init/mcp-clients.d.ts +7 -0
  138. package/dist/init/mcp-clients.js +7 -0
  139. package/dist/init/mcp.d.ts +1 -1
  140. package/dist/init/mcp.js +1 -1
  141. package/dist/init/node-io.js +11 -1
  142. package/dist/init/plan-framework.js +81 -45
  143. package/dist/init/plan.d.ts +20 -7
  144. package/dist/init/plan.js +20 -45
  145. package/dist/init/preflight.d.ts +28 -0
  146. package/dist/init/preflight.js +38 -0
  147. package/dist/init/run.d.ts +32 -9
  148. package/dist/init/run.js +141 -119
  149. package/dist/init/slash-command.d.ts +1 -1
  150. package/dist/init/snippets.js +41 -25
  151. package/dist/init/vite-config.d.ts +1 -1
  152. package/dist/init/vite-config.js +39 -25
  153. package/dist/init/workspace-apps.d.ts +17 -10
  154. package/dist/init/workspace-apps.js +110 -0
  155. package/dist/input/network-mock-tools.js +3 -6
  156. package/dist/input/real-input.js +25 -5
  157. package/dist/input/scroll-tools.js +4 -6
  158. package/dist/input/viewport-tools.js +5 -4
  159. package/dist/intent/inline-intent.d.ts +10 -19
  160. package/dist/intent/inline-intent.js +91 -1
  161. package/dist/intent/intent-shard-store.d.ts +72 -0
  162. package/dist/intent/intent-shard-store.js +203 -0
  163. package/dist/intent/intent-shard.d.ts +411 -0
  164. package/dist/intent/intent-shard.js +176 -0
  165. package/dist/intent/intent-store.d.ts +12 -0
  166. package/dist/intent/intent-store.js +21 -0
  167. package/dist/intent/intent-subject.d.ts +45 -0
  168. package/dist/intent/intent-subject.js +177 -0
  169. package/dist/intent/intent-tools.js +65 -2
  170. package/dist/intent/open-intents.d.ts +21 -0
  171. package/dist/intent/open-intents.js +15 -0
  172. package/dist/license/license-env.d.ts +29 -0
  173. package/dist/license/license-env.js +148 -0
  174. package/dist/loopback-agent.d.ts +31 -0
  175. package/dist/loopback-agent.js +57 -0
  176. package/dist/mcp/attach-memory.d.ts +18 -2
  177. package/dist/mcp/attach-memory.js +50 -7
  178. package/dist/mcp/client-identity.d.ts +31 -0
  179. package/dist/mcp/client-identity.js +51 -0
  180. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  181. package/dist/mcp/mcp-post-transport.js +109 -0
  182. package/dist/mcp/mcp-proxy.d.ts +2 -1
  183. package/dist/mcp/mcp-proxy.js +52 -62
  184. package/dist/mcp/mcp.js +7 -1
  185. package/dist/mcp/proxy-backoff.d.ts +11 -0
  186. package/dist/mcp/proxy-backoff.js +13 -0
  187. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  188. package/dist/mcp/proxy-daemon-probe.js +7 -1
  189. package/dist/mcp/proxy-handshake.d.ts +23 -0
  190. package/dist/mcp/proxy-handshake.js +31 -0
  191. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  192. package/dist/mcp/tool-catalog-cache.js +12 -0
  193. package/dist/memory/memory-tools.d.ts +2 -0
  194. package/dist/memory/memory-tools.js +82 -0
  195. package/dist/memory/project-memory.d.ts +34 -0
  196. package/dist/memory/project-memory.js +96 -0
  197. package/dist/oracles/propose-consequences.js +12 -3
  198. package/dist/pool/browser-pool.d.ts +63 -4
  199. package/dist/pool/browser-pool.js +113 -4
  200. package/dist/pool/playwright-launcher.js +9 -0
  201. package/dist/project/project-tools.js +3 -3
  202. package/dist/project/reticle-dir.d.ts +15 -2
  203. package/dist/project/reticle-dir.js +21 -4
  204. package/dist/runs/run-store.d.ts +10 -0
  205. package/dist/runs/run-store.js +10 -0
  206. package/dist/runs/verification-sync.d.ts +0 -11
  207. package/dist/runs/verification-sync.js +22 -1
  208. package/dist/runs/verify-server.d.ts +8 -1
  209. package/dist/runs/verify-server.js +14 -2
  210. package/dist/session/command-timeout.d.ts +5 -2
  211. package/dist/session/command-timeout.js +5 -4
  212. package/dist/session/lease-visibility.d.ts +67 -0
  213. package/dist/session/lease-visibility.js +76 -0
  214. package/dist/session/no-session-diagnosis.d.ts +23 -0
  215. package/dist/session/no-session-diagnosis.js +58 -17
  216. package/dist/session/no-session-next-action.d.ts +7 -0
  217. package/dist/session/no-session-next-action.js +42 -3
  218. package/dist/session/no-session-watch.d.ts +10 -6
  219. package/dist/session/no-session-watch.js +51 -19
  220. package/dist/session/pending-commands.d.ts +11 -1
  221. package/dist/session/pending-commands.js +12 -2
  222. package/dist/session/resolve-within.d.ts +34 -0
  223. package/dist/session/resolve-within.js +53 -0
  224. package/dist/session/session-info.d.ts +18 -0
  225. package/dist/session/session-info.js +11 -0
  226. package/dist/session/session-manager.d.ts +14 -0
  227. package/dist/session/session-manager.js +61 -3
  228. package/dist/session/session-mirror.d.ts +16 -0
  229. package/dist/session/session-mirror.js +22 -0
  230. package/dist/session/session-recommendation.d.ts +19 -5
  231. package/dist/session/session-recommendation.js +25 -9
  232. package/dist/session/session-successor.js +36 -1
  233. package/dist/session/session-tools.js +2 -2
  234. package/dist/session/session.d.ts +22 -2
  235. package/dist/session/session.js +87 -7
  236. package/dist/session/wall-clock.d.ts +12 -0
  237. package/dist/session/wall-clock.js +14 -0
  238. package/dist/setup/agent-configs.d.ts +96 -0
  239. package/dist/setup/agent-configs.js +333 -0
  240. package/dist/setup/agent-io.d.ts +9 -0
  241. package/dist/setup/agent-io.js +16 -0
  242. package/dist/setup/agent-writer.d.ts +51 -0
  243. package/dist/setup/agent-writer.js +115 -0
  244. package/dist/setup/approval-migration.d.ts +47 -0
  245. package/dist/setup/approval-migration.js +68 -0
  246. package/dist/setup/auto-approve.d.ts +85 -0
  247. package/dist/setup/auto-approve.js +150 -0
  248. package/dist/setup/bridge-port.d.ts +15 -0
  249. package/dist/setup/bridge-port.js +24 -0
  250. package/dist/setup/desktop-shape.d.ts +57 -0
  251. package/dist/setup/desktop-shape.js +63 -0
  252. package/dist/setup/dev-server-wait.d.ts +68 -0
  253. package/dist/setup/dev-server-wait.js +67 -0
  254. package/dist/setup/drive-agent.d.ts +71 -0
  255. package/dist/setup/drive-agent.js +171 -0
  256. package/dist/setup/drive-plan.d.ts +68 -0
  257. package/dist/setup/drive-plan.js +130 -0
  258. package/dist/setup/ensure-daemon.d.ts +29 -0
  259. package/dist/setup/ensure-daemon.js +58 -0
  260. package/dist/setup/init-runtime.d.ts +47 -0
  261. package/dist/setup/init-runtime.js +160 -0
  262. package/dist/setup/interrupt.d.ts +25 -0
  263. package/dist/setup/interrupt.js +40 -0
  264. package/dist/setup/license-key.d.ts +39 -0
  265. package/dist/setup/license-key.js +72 -0
  266. package/dist/setup/listeners.d.ts +37 -0
  267. package/dist/setup/listeners.js +85 -0
  268. package/dist/setup/node-effects.d.ts +60 -0
  269. package/dist/setup/node-effects.js +210 -0
  270. package/dist/setup/page-probe.d.ts +39 -0
  271. package/dist/setup/page-probe.js +49 -0
  272. package/dist/setup/relaunch.d.ts +36 -0
  273. package/dist/setup/relaunch.js +57 -0
  274. package/dist/setup/remaining-steps.d.ts +19 -0
  275. package/dist/setup/remaining-steps.js +31 -0
  276. package/dist/setup/run-setup.d.ts +99 -0
  277. package/dist/setup/run-setup.js +203 -0
  278. package/dist/setup/session-pick.d.ts +47 -0
  279. package/dist/setup/session-pick.js +51 -0
  280. package/dist/setup/setup-command.d.ts +45 -0
  281. package/dist/setup/setup-command.js +199 -0
  282. package/dist/setup/setup-options.d.ts +74 -0
  283. package/dist/setup/setup-options.js +67 -0
  284. package/dist/setup/transcripts.d.ts +16 -0
  285. package/dist/setup/transcripts.js +43 -0
  286. package/dist/status-payload.d.ts +14 -1
  287. package/dist/status-payload.js +23 -1
  288. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  289. package/dist/telemetry/daemon-telemetry.js +52 -5
  290. package/dist/telemetry/error-class.js +1 -1
  291. package/dist/telemetry/feedback-context.d.ts +9 -1
  292. package/dist/telemetry/feedback-context.js +60 -29
  293. package/dist/telemetry/feedback-local.d.ts +15 -0
  294. package/dist/telemetry/feedback-local.js +56 -0
  295. package/dist/telemetry/feedback.d.ts +11 -0
  296. package/dist/telemetry/feedback.js +19 -6
  297. package/dist/telemetry/init-telemetry.d.ts +2 -0
  298. package/dist/telemetry/init-telemetry.js +2 -0
  299. package/dist/telemetry/install-source.d.ts +14 -0
  300. package/dist/telemetry/install-source.js +30 -0
  301. package/dist/telemetry/license-activation.d.ts +7 -1
  302. package/dist/telemetry/license-activation.js +9 -5
  303. package/dist/telemetry/mcp-connection.js +7 -0
  304. package/dist/telemetry/project-profile.d.ts +12 -1
  305. package/dist/telemetry/project-profile.js +11 -6
  306. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  307. package/dist/telemetry/proxy-telemetry.js +21 -0
  308. package/dist/telemetry/session-metrics.d.ts +12 -0
  309. package/dist/telemetry/session-metrics.js +35 -1
  310. package/dist/telemetry/telemetry.js +17 -2
  311. package/dist/telemetry/tool-refused.d.ts +3 -1
  312. package/dist/telemetry/tool-refused.js +34 -2
  313. package/dist/tools/act-capsule.d.ts +10 -0
  314. package/dist/tools/act-capsule.js +8 -2
  315. package/dist/tools/act-danger.d.ts +2 -1
  316. package/dist/tools/act-danger.js +11 -3
  317. package/dist/tools/act-observation.d.ts +41 -0
  318. package/dist/tools/act-observation.js +36 -0
  319. package/dist/tools/act-preflight.d.ts +20 -0
  320. package/dist/tools/act-preflight.js +98 -0
  321. package/dist/tools/act-sequence-retry.d.ts +45 -0
  322. package/dist/tools/act-sequence-retry.js +58 -0
  323. package/dist/tools/act-sequence-tool.d.ts +13 -0
  324. package/dist/tools/act-sequence-tool.js +135 -0
  325. package/dist/tools/act-target.d.ts +14 -0
  326. package/dist/tools/act-target.js +47 -0
  327. package/dist/tools/act-tools.d.ts +21 -1
  328. package/dist/tools/act-tools.js +100 -213
  329. package/dist/tools/assert-verdict.js +22 -16
  330. package/dist/tools/browser-tools.js +6 -2
  331. package/dist/tools/dynamic-tools.js +24 -5
  332. package/dist/tools/error-recovery.d.ts +1 -0
  333. package/dist/tools/error-recovery.js +8 -0
  334. package/dist/tools/invoke-tool.js +59 -9
  335. package/dist/tools/lease-tools.d.ts +30 -4
  336. package/dist/tools/lease-tools.js +169 -5
  337. package/dist/tools/merged-name-redirect.d.ts +13 -0
  338. package/dist/tools/merged-name-redirect.js +22 -0
  339. package/dist/tools/numeric-bounds.d.ts +100 -0
  340. package/dist/tools/numeric-bounds.js +125 -0
  341. package/dist/tools/observe-tools.js +47 -40
  342. package/dist/tools/read-tools.js +28 -7
  343. package/dist/tools/real-input-attempt.d.ts +10 -0
  344. package/dist/tools/real-input-attempt.js +59 -1
  345. package/dist/tools/reconcile-tools.js +4 -4
  346. package/dist/tools/tool-kit.d.ts +12 -0
  347. package/dist/tools/tool-kit.js +6 -1
  348. package/dist/tools/tool-names.d.ts +2 -0
  349. package/dist/tools/tool-names.js +2 -0
  350. package/dist/tools/tools-helpers.d.ts +19 -0
  351. package/dist/tools/tools-helpers.js +46 -0
  352. package/dist/tools/tools.d.ts +2 -0
  353. package/dist/tools/tools.js +56 -8
  354. package/dist/update/reticle-deps.d.ts +9 -0
  355. package/dist/update/reticle-deps.js +27 -0
  356. package/dist/update/sdk-sync.d.ts +1 -2
  357. package/dist/update/sdk-sync.js +1 -21
  358. package/dist/update/update-nudge.d.ts +18 -0
  359. package/dist/update/update-nudge.js +20 -0
  360. package/dist/version/peer-announce.js +4 -2
  361. package/dist/version/sdk-fix.d.ts +47 -0
  362. package/dist/version/sdk-fix.js +127 -0
  363. package/dist/version/version-nudge.d.ts +14 -0
  364. package/dist/version/version-nudge.js +21 -0
  365. package/dist/version/version-skew.d.ts +38 -3
  366. package/dist/version/version-skew.js +83 -7
  367. package/dist/visual/visual-store.d.ts +6 -6
  368. package/dist/visual/visual-store.js +18 -16
  369. package/dist/visual/visual-tools.js +57 -12
  370. package/docs/cli/doctor.mdx +1 -0
  371. package/docs/cli/serve.mdx +4 -2
  372. package/docs/cli.mdx +7 -3
  373. package/docs/desktop.mdx +1 -1
  374. package/docs/docs.json +1 -1
  375. package/docs/getting-started.md +19 -3
  376. package/docs/index.mdx +9 -6
  377. package/docs/install-agentic.mdx +38 -12
  378. package/docs/install-manual.mdx +1 -1
  379. package/docs/packages.mdx +1 -1
  380. package/docs/skill-file.mdx +3 -3
  381. package/docs/telemetry-contract.md +108 -4
  382. package/docs/tools-act-sequence.mdx +16 -0
  383. package/docs/usage.md +1 -1
  384. package/package.json +4 -3
  385. package/dist/init/cursor.d.ts +0 -24
  386. package/dist/init/cursor.js +0 -80
@@ -23,6 +23,20 @@ interface VersionSkewNudge {
23
23
  export declare function noteVersionSkew(pair: SkewPair, action: string): void;
24
24
  /** The next undelivered skew, once. */
25
25
  export declare function takeVersionSkew(): VersionSkewNudge | undefined;
26
+ /**
27
+ * Splice pending skew onto a tool-error payload.
28
+ *
29
+ * Successful calls already carry `version_skew` via `runTool`. Thrown errors never reached that
30
+ * splice — they became a Playwright string plus `FEEDBACK_ASK`, so a caller debugs a CDP timeout
31
+ * instead of the mismatched pair that caused it (#618). When the error was unrecognized, the
32
+ * feedback ask is replaced: the skew IS the next move, and inviting a bug report about it is
33
+ * backwards. A recognized recovery is kept; the envelope still rides along.
34
+ */
35
+ export declare function takeVersionSkewOnto(payload: {
36
+ error: string;
37
+ recovery?: string;
38
+ feedback?: string;
39
+ }): Record<string, unknown>;
26
40
  /** Tests only — drop the module state so each case starts clean. */
27
41
  export declare function resetVersionSkew(): void;
28
42
  export {};
@@ -11,6 +11,7 @@
11
11
  * result, then goes quiet until the skew CHANGES. Told, not nagged: a banner on every call is one an
12
12
  * agent learns to skip, and it would cost those tokens on every call for the rest of the session.
13
13
  */
14
+ import { EnvelopeKey } from '../tools/tool-kit.js';
14
15
  /** Pending per pair — the SDK and the daemon are different upgrades and each is worth saying once. */
15
16
  const pending = new Map();
16
17
  /** The last message delivered for each pair, so the same skew is never repeated. */
@@ -33,6 +34,26 @@ export function takeVersionSkew() {
33
34
  }
34
35
  return undefined;
35
36
  }
37
+ /**
38
+ * Splice pending skew onto a tool-error payload.
39
+ *
40
+ * Successful calls already carry `version_skew` via `runTool`. Thrown errors never reached that
41
+ * splice — they became a Playwright string plus `FEEDBACK_ASK`, so a caller debugs a CDP timeout
42
+ * instead of the mismatched pair that caused it (#618). When the error was unrecognized, the
43
+ * feedback ask is replaced: the skew IS the next move, and inviting a bug report about it is
44
+ * backwards. A recognized recovery is kept; the envelope still rides along.
45
+ */
46
+ export function takeVersionSkewOnto(payload) {
47
+ const skew = takeVersionSkew();
48
+ if (skew === undefined)
49
+ return payload;
50
+ const { feedback, ...rest } = payload;
51
+ return {
52
+ ...rest,
53
+ ...(feedback !== undefined && rest.recovery === undefined ? { recovery: skew.action } : {}),
54
+ [EnvelopeKey.VERSION_SKEW]: skew,
55
+ };
56
+ }
36
57
  /** Tests only — drop the module state so each case starts clean. */
37
58
  export function resetVersionSkew() {
38
59
  pending.clear();
@@ -59,8 +59,43 @@ interface SelfIdentity {
59
59
  * inventing a warning would be as dishonest as staying silent about a real one.
60
60
  */
61
61
  export declare function describeSkew(peer: PeerIdentity, self: SelfIdentity): string | undefined;
62
- /** The fix line for a page whose SDK disagrees with this daemon. */
62
+ /**
63
+ * The fix line for a page whose SDK disagrees with this daemon, with no project to read.
64
+ *
65
+ * Names the framework-neutral sensor and plain npm, because those are the answers that are never
66
+ * actively wrong. When a project directory IS available, `resolveSdkFix` / `sdkFixForDirectory`
67
+ * in sdk-fix.ts read the real packages and package manager — this used to hardcode
68
+ * `@reticlehq/react` and told Vue projects to install a React package (#618).
69
+ */
63
70
  export declare function sdkFix(daemonVersion: string): string;
64
- /** The fix line for another Reticle process that disagrees with the daemon it attached to. */
65
- export declare const DAEMON_FIX: string;
71
+ /**
72
+ * The fix line for another Reticle process that disagrees with the daemon it attached to.
73
+ *
74
+ * Branches on DIRECTION, which the old unconditional `reticle stop` did not (#618). Restarting the
75
+ * daemon converges the pair only when the daemon is the OLDER half. When the daemon is newer — the
76
+ * common case, because the MCP registration is deliberately unpinned and so resolves fresh while a
77
+ * long-lived daemon does not — `reticle stop` starts another newer daemon and leaves the same two
78
+ * contract hashes with the roles unchanged. Nothing the agent can do converges that, so say so and
79
+ * ask for the one thing that does, exactly as the SDK branch already does when the page is stale.
80
+ *
81
+ * Both versions are named by ROLE, not by which side is calling: this is read from both ends — a
82
+ * CLI judging the daemon it attached to, and the daemon judging an agent that announced itself —
83
+ * and the advice depends on which piece is behind, not on who noticed.
84
+ */
85
+ export declare function daemonFix(daemonVersion: string | undefined, agentVersion: string | undefined): string;
86
+ /**
87
+ * Playwright wording when a CDP call cannot bind to the page.
88
+ *
89
+ * Under SDK/daemon version skew this text is a LIE: the page is still connected (DOM tools work),
90
+ * and the agent spends turns re-acquiring leases and asking a human to sign into a fresh profile
91
+ * (#688). Matched only so we can replace it with the skew sentence already on the session.
92
+ */
93
+ export declare const PLAYWRIGHT_CLOSED_ERROR: RegExp;
94
+ /** True when `error` is Playwright's closed-target class — not proof the tab is gone. */
95
+ export declare function isPlaywrightClosedError(error: unknown): boolean;
96
+ /**
97
+ * Replace a closed-target throw with the session's skew sentence when we already know the pair is
98
+ * mismatched. Undefined when there is no skew to name, or the throw is something else.
99
+ */
100
+ export declare function rewriteClosedAsSkew(error: unknown, skew: string | undefined): Error | undefined;
66
101
  export {};
@@ -23,6 +23,7 @@
23
23
  * the wire changes and stays put when internals do. Versions still ride along, because they are what
24
24
  * a human acts on.
25
25
  */
26
+ import { resolveSdkFix } from './sdk-fix.js';
26
27
  /** Which pair disagreed. Named so the nudge can report each independently. */
27
28
  export const SkewPair = {
28
29
  /** The SDK in the page vs this daemon. */
@@ -60,12 +61,87 @@ export function describeSkew(peer, self) {
60
61
  `(${peer.contract} vs ${self.contract}) — so this is a real mismatch, not a harmless patch ` +
61
62
  `difference. Tools will behave in ways neither side reports. ${peer.fix}`);
62
63
  }
63
- /** The fix line for a page whose SDK disagrees with this daemon. */
64
+ /**
65
+ * The fix line for a page whose SDK disagrees with this daemon, with no project to read.
66
+ *
67
+ * Names the framework-neutral sensor and plain npm, because those are the answers that are never
68
+ * actively wrong. When a project directory IS available, `resolveSdkFix` / `sdkFixForDirectory`
69
+ * in sdk-fix.ts read the real packages and package manager — this used to hardcode
70
+ * `@reticlehq/react` and told Vue projects to install a React package (#618).
71
+ */
64
72
  export function sdkFix(daemonVersion) {
65
- return (`Tell the human to install the matching SDK (\`npm i -D @reticlehq/react@${daemonVersion}\`) ` +
66
- 'or run `reticle update`, then restart their dev server so the page reloads with it.');
73
+ return resolveSdkFix(daemonVersion);
74
+ }
75
+ /**
76
+ * Compare two of our own version strings. Numeric dotted compare, prerelease tags ignored: both
77
+ * sides are Reticle package versions, so this never sees a range or build metadata. Undefined when
78
+ * either side does not parse, so the caller can decline to guess a direction.
79
+ */
80
+ function compareVersions(a, b) {
81
+ const parts = (v) => {
82
+ const head = v.split('-')[0];
83
+ if (head === undefined)
84
+ return undefined;
85
+ const nums = head.split('.').map((n) => parseInt(n, 10));
86
+ return nums.some(isNaN) ? undefined : nums;
87
+ };
88
+ if (a === undefined)
89
+ return undefined;
90
+ const left = parts(a);
91
+ const right = parts(b);
92
+ if (left === undefined || right === undefined)
93
+ return undefined;
94
+ for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
95
+ const diff = (left[i] ?? 0) - (right[i] ?? 0);
96
+ if (0 !== diff)
97
+ return diff;
98
+ }
99
+ return 0;
100
+ }
101
+ /**
102
+ * The fix line for another Reticle process that disagrees with the daemon it attached to.
103
+ *
104
+ * Branches on DIRECTION, which the old unconditional `reticle stop` did not (#618). Restarting the
105
+ * daemon converges the pair only when the daemon is the OLDER half. When the daemon is newer — the
106
+ * common case, because the MCP registration is deliberately unpinned and so resolves fresh while a
107
+ * long-lived daemon does not — `reticle stop` starts another newer daemon and leaves the same two
108
+ * contract hashes with the roles unchanged. Nothing the agent can do converges that, so say so and
109
+ * ask for the one thing that does, exactly as the SDK branch already does when the page is stale.
110
+ *
111
+ * Both versions are named by ROLE, not by which side is calling: this is read from both ends — a
112
+ * CLI judging the daemon it attached to, and the daemon judging an agent that announced itself —
113
+ * and the advice depends on which piece is behind, not on who noticed.
114
+ */
115
+ export function daemonFix(daemonVersion, agentVersion) {
116
+ const order = agentVersion === undefined ? undefined : compareVersions(daemonVersion, agentVersion);
117
+ if (order !== undefined && order > 0) {
118
+ return ('The daemon is NEWER than this process, so `reticle stop` will not converge them — it would ' +
119
+ 'replace it with another newer daemon and leave the same mismatch. Tell the human to restart ' +
120
+ 'the agent so it spawns a current MCP server (or update the package it spawns), then retry.');
121
+ }
122
+ return ('A daemon outlives the agents attached to it, so it keeps serving its OWN code until it restarts ' +
123
+ '— anything fixed in the newer package is simply absent. Run `reticle stop` and retry to replace ' +
124
+ 'it (other agents on that daemon will need to reconnect).');
125
+ }
126
+ /**
127
+ * Playwright wording when a CDP call cannot bind to the page.
128
+ *
129
+ * Under SDK/daemon version skew this text is a LIE: the page is still connected (DOM tools work),
130
+ * and the agent spends turns re-acquiring leases and asking a human to sign into a fresh profile
131
+ * (#688). Matched only so we can replace it with the skew sentence already on the session.
132
+ */
133
+ export const PLAYWRIGHT_CLOSED_ERROR = /target (?:page|context|browser) has been closed|browser has been closed|browser has disconnected/i;
134
+ /** True when `error` is Playwright's closed-target class — not proof the tab is gone. */
135
+ export function isPlaywrightClosedError(error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ return PLAYWRIGHT_CLOSED_ERROR.test(message);
138
+ }
139
+ /**
140
+ * Replace a closed-target throw with the session's skew sentence when we already know the pair is
141
+ * mismatched. Undefined when there is no skew to name, or the throw is something else.
142
+ */
143
+ export function rewriteClosedAsSkew(error, skew) {
144
+ if (skew === undefined || !isPlaywrightClosedError(error))
145
+ return undefined;
146
+ return new Error(skew);
67
147
  }
68
- /** The fix line for another Reticle process that disagrees with the daemon it attached to. */
69
- export const DAEMON_FIX = 'A daemon outlives the agents attached to it, so it keeps serving its OWN code until it restarts ' +
70
- '— anything fixed in the newer package is simply absent. Run `reticle stop` and retry to replace ' +
71
- 'it (other agents on that daemon will need to reconnect).';
@@ -8,15 +8,15 @@ export declare class VisualStore {
8
8
  constructor(fs: FileSystemPort, root: string);
9
9
  /** The absolute baseline path for `name` (for echoing back to the agent). Guards the name so a
10
10
  * crafted value can never echo a path outside .reticle/visual/ to a future caller that does IO. */
11
- baselinePath(name: string): string;
11
+ baselinePath(name: string, runtime?: string): string;
12
12
  /** The absolute overlay-diff path for `name`. Same name guard as baselinePath. */
13
- diffPath(name: string): string;
13
+ diffPath(name: string, runtime?: string): string;
14
14
  /** True iff a valid name and a baseline PNG exists on disk. */
15
- hasBaseline(name: string): Promise<boolean>;
15
+ hasBaseline(name: string, runtime?: string): Promise<boolean>;
16
16
  /** Write a PNG baseline (auto-creating .reticle/visual/). Returns its path; throws on a bad name. */
17
- saveBaseline(name: string, png: Uint8Array): Promise<string>;
17
+ saveBaseline(name: string, png: Uint8Array, runtime?: string): Promise<string>;
18
18
  /** Read a PNG baseline, or undefined if absent / invalid name (never throws on missing). */
19
- readBaseline(name: string): Promise<Uint8Array | undefined>;
19
+ readBaseline(name: string, runtime?: string): Promise<Uint8Array | undefined>;
20
20
  /** Write the overlay diff PNG for `name` (auto-creating .reticle/visual/). Returns its path. */
21
- saveDiff(name: string, png: Uint8Array): Promise<string>;
21
+ saveDiff(name: string, png: Uint8Array, runtime?: string): Promise<string>;
22
22
  }
@@ -1,4 +1,4 @@
1
- import { reticleDirPaths, isValidFlowName, visualDiffPath, visualPath, } from '../project/reticle-dir.js';
1
+ import { isValidFlowName, visualDiffPath, visualDir, visualPath } from '../project/reticle-dir.js';
2
2
  /**
3
3
  * On-disk PNG baselines + diffs under .reticle/visual/. Binary sibling of FlowStore — same
4
4
  * name guard (rejects path traversal before any join) and injected FileSystemPort, but bytes not text.
@@ -12,39 +12,40 @@ export class VisualStore {
12
12
  }
13
13
  /** The absolute baseline path for `name` (for echoing back to the agent). Guards the name so a
14
14
  * crafted value can never echo a path outside .reticle/visual/ to a future caller that does IO. */
15
- baselinePath(name) {
15
+ baselinePath(name, runtime) {
16
16
  if (!isValidFlowName(name))
17
17
  throw new Error(`invalid visual baseline name: ${name}`);
18
- return visualPath(this.#root, name);
18
+ return visualPath(this.#root, name, runtime);
19
19
  }
20
20
  /** The absolute overlay-diff path for `name`. Same name guard as baselinePath. */
21
- diffPath(name) {
21
+ diffPath(name, runtime) {
22
22
  if (!isValidFlowName(name))
23
23
  throw new Error(`invalid visual diff name: ${name}`);
24
- return visualDiffPath(this.#root, name);
24
+ return visualDiffPath(this.#root, name, runtime);
25
25
  }
26
26
  /** True iff a valid name and a baseline PNG exists on disk. */
27
- async hasBaseline(name) {
27
+ async hasBaseline(name, runtime) {
28
28
  if (!isValidFlowName(name))
29
29
  return false;
30
- return this.#fs.exists(visualPath(this.#root, name));
30
+ return this.#fs.exists(visualPath(this.#root, name, runtime));
31
31
  }
32
32
  /** Write a PNG baseline (auto-creating .reticle/visual/). Returns its path; throws on a bad name. */
33
- async saveBaseline(name, png) {
33
+ async saveBaseline(name, png, runtime) {
34
34
  if (!isValidFlowName(name))
35
35
  throw new Error(`invalid visual baseline name: ${name}`);
36
- await this.#fs.mkdir(reticleDirPaths(this.#root).visual);
37
- const path = visualPath(this.#root, name);
36
+ // The runtime's own directory, not the shared one — see visualDir.
37
+ await this.#fs.mkdir(visualDir(this.#root, runtime));
38
+ const path = visualPath(this.#root, name, runtime);
38
39
  await this.#fs.writeFileBytes(path, png);
39
40
  return path;
40
41
  }
41
42
  /** Read a PNG baseline, or undefined if absent / invalid name (never throws on missing). */
42
- async readBaseline(name) {
43
- if (!isValidFlowName(name) || !(await this.#fs.exists(visualPath(this.#root, name)))) {
43
+ async readBaseline(name, runtime) {
44
+ if (!isValidFlowName(name) || !(await this.#fs.exists(visualPath(this.#root, name, runtime)))) {
44
45
  return undefined;
45
46
  }
46
47
  try {
47
- return await this.#fs.readFileBytes(visualPath(this.#root, name));
48
+ return await this.#fs.readFileBytes(visualPath(this.#root, name, runtime));
48
49
  }
49
50
  catch (error) {
50
51
  if (this.#fs.isNotFound(error))
@@ -53,11 +54,12 @@ export class VisualStore {
53
54
  }
54
55
  }
55
56
  /** Write the overlay diff PNG for `name` (auto-creating .reticle/visual/). Returns its path. */
56
- async saveDiff(name, png) {
57
+ async saveDiff(name, png, runtime) {
57
58
  if (!isValidFlowName(name))
58
59
  throw new Error(`invalid visual baseline name: ${name}`);
59
- await this.#fs.mkdir(reticleDirPaths(this.#root).visual);
60
- const path = visualDiffPath(this.#root, name);
60
+ // Beside its baseline, or a diff lands in a directory the baseline is not in.
61
+ await this.#fs.mkdir(visualDir(this.#root, runtime));
62
+ const path = visualDiffPath(this.#root, name, runtime);
61
63
  await this.#fs.writeFileBytes(path, png);
62
64
  return path;
63
65
  }
@@ -1,7 +1,8 @@
1
1
  import { z } from 'zod';
2
- import { ReticleCommand, RETICLE_CAPTURE_FILE_PREFIX, VISUAL_NO_PROVIDER_RECOMMENDATION, VisualReason, } from '@reticlehq/core';
2
+ import { AppRuntime, ReticleCommand, RETICLE_CAPTURE_FILE_PREFIX, VISUAL_NO_PROVIDER_RECOMMENDATION, VisualReason, } from '@reticlehq/core';
3
3
  import { ReticleTool } from '../tools/tool-names.js';
4
4
  import { sessionIdShape } from '../tools/tool-kit.js';
5
+ import { ratioSchema } from '../tools/numeric-bounds.js';
5
6
  import { asNumber, asRecord, asString } from '../tools/tools-helpers.js';
6
7
  import { diffPng } from './visual-diff.js';
7
8
  import { VisualStore } from './visual-store.js';
@@ -148,19 +149,51 @@ function isCompletePng(bytes) {
148
149
  * `full-page-unsupported` — a viewport image returned as though it were the whole scroll height is a
149
150
  * baseline that says nothing about the content below the fold, and a later diff would call it green.
150
151
  */
152
+ /**
153
+ * Which runtime this session is, for scoping a visual artifact.
154
+ *
155
+ * Undefined when the session cannot be resolved or its SDK never reported one — and undefined means
156
+ * the flat legacy path, so a baseline captured before any of this keeps matching.
157
+ */
158
+ function runtimeOf(deps, sessionId) {
159
+ try {
160
+ return deps.sessions.resolve(sessionId).runtime;
161
+ }
162
+ catch {
163
+ return undefined;
164
+ }
165
+ }
151
166
  async function capture(deps, sessionId, args) {
152
167
  const provider = screenshotProvider(deps);
153
168
  if (provider !== undefined) {
154
169
  const session = deps.sessions.resolve(sessionId);
155
170
  const png = await provider.screenshot(session.url, await buildOpts(deps, sessionId, args));
171
+ // A driven browser renders the session's URL in a BROWSER — so the pixels are web even when the
172
+ // session named is a desktop window. Scoping those under the desktop runtime would corrupt that
173
+ // runtime's baseline with a picture of a different renderer.
156
174
  if (png !== undefined)
157
- return { png };
175
+ return { png, runtime: AppRuntime.WEB };
158
176
  }
177
+ // The window's own backing store, via the Electron/Tauri adapter — the only route whose pixels
178
+ // really are the desktop app.
159
179
  const desktop = await desktopCapture(deps, sessionId, true === args['fullPage']);
160
180
  if (desktop.png !== undefined)
161
- return { png: desktop.png };
181
+ return { png: desktop.png, runtime: runtimeOf(deps, sessionId) };
162
182
  if (desktop.reason !== undefined)
163
183
  return { reason: desktop.reason };
184
+ /*
185
+ * A LEASED page is a real browser page, so the pixels were always there — the visual tools simply
186
+ * had no route to them. Without this, `reticle_screenshot` answered "no provider" for every context
187
+ * an agent can actually acquire, which made visual regression impossible on exactly the isolated
188
+ * contexts the pool exists to hand out. Tried last: a CDP provider, when there is one, is driving
189
+ * the page the caller means, and a lease is the fallback rather than a competitor.
190
+ */
191
+ const leased = sessionId === undefined
192
+ ? undefined
193
+ : await deps.pool?.screenshotLease(sessionId, { fullPage: true === args['fullPage'] });
194
+ // A leased page is a real browser page, whatever the session is.
195
+ if (leased !== undefined)
196
+ return { png: leased, runtime: AppRuntime.WEB };
164
197
  return {
165
198
  reason: provider === undefined ? VisualReason.NO_PROVIDER : VisualReason.CAPTURE_FAILED,
166
199
  };
@@ -215,13 +248,16 @@ export const VISUAL_TOOLS = [
215
248
  },
216
249
  handler: async (deps, args) => {
217
250
  const sessionId = asString(args['sessionId']);
218
- const { png, reason } = await capture(deps, sessionId, args);
251
+ const { png, reason, runtime } = await capture(deps, sessionId, args);
219
252
  if (png === undefined) {
220
253
  return reason === VisualReason.NO_PROVIDER ? noProvider : { ok: false, reason };
221
254
  }
222
255
  const name = asString(args['name']) ?? 'default';
223
256
  const store = new VisualStore(deps.fs, deps.reticleRoot);
224
- const path = await store.saveBaseline(name, png);
257
+ // Scoped to the runtime that produced it: an Electron window, a Tauri webview and a browser
258
+ // tab do not render the same url the same way, and one shared baseline makes every
259
+ // cross-runtime diff wrong. See visualDir.
260
+ const path = await store.saveBaseline(name, png, runtime);
225
261
  return { ok: true, saved: true, name, path, bytes: png.length };
226
262
  },
227
263
  },
@@ -237,8 +273,10 @@ export const VISUAL_TOOLS = [
237
273
  ref: z.string().optional(),
238
274
  clip: rectShape.optional(),
239
275
  masks: z.array(rectShape).optional(),
240
- maxRatio: z.number().optional(),
241
- threshold: z.number().optional().describe('Pixel difference threshold (0–1). Default: 0.01.'),
276
+ maxRatio: ratioSchema.optional(),
277
+ threshold: ratioSchema
278
+ .optional()
279
+ .describe('Pixel difference threshold (0–1). Default: 0.01.'),
242
280
  ...sessionIdShape,
243
281
  },
244
282
  outputSchema: {
@@ -255,14 +293,21 @@ export const VISUAL_TOOLS = [
255
293
  handler: async (deps, args) => {
256
294
  const baseline = asString(args['baseline']) ?? '';
257
295
  const store = new VisualStore(deps.fs, deps.reticleRoot);
258
- const baselineBytes = await store.readBaseline(baseline);
259
- if (baselineBytes === undefined)
260
- return { ok: false, reason: VisualReason.BASELINE_MISSING };
296
+ // Capture FIRST, then fetch the baseline for the runtime that produced these pixels. Reading it
297
+ // by the session's runtime looked equivalent and is not: the route decides the renderer, so a
298
+ // desktop session captured through a driven browser must be compared against the WEB baseline,
299
+ // not the desktop one. Comparing across renderers is a confident wrong diff in one direction
300
+ // and a silently overwritten baseline in the other.
261
301
  const sessionId = asString(args['sessionId']);
262
- const { png: current, reason } = await capture(deps, sessionId, args);
302
+ const { png: current, reason, runtime } = await capture(deps, sessionId, args);
263
303
  if (current === undefined) {
264
304
  return reason === VisualReason.NO_PROVIDER ? noProvider : { ok: false, reason };
265
305
  }
306
+ // A baseline from another runtime is not missing by accident — it is a different rendering, and
307
+ // "no baseline for this one" is the honest answer.
308
+ const baselineBytes = await store.readBaseline(baseline, runtime);
309
+ if (baselineBytes === undefined)
310
+ return { ok: false, reason: VisualReason.BASELINE_MISSING };
266
311
  const masks = rectsFrom(args['masks']);
267
312
  const threshold = asNumber(args['threshold']);
268
313
  const maxRatio = asNumber(args['maxRatio']);
@@ -276,7 +321,7 @@ export const VISUAL_TOOLS = [
276
321
  if (diffBytes === undefined) {
277
322
  return { ok: false, ...verdict, reason: VisualReason.DIMENSION_MISMATCH };
278
323
  }
279
- const diffPath = await store.saveDiff(baseline, diffBytes);
324
+ const diffPath = await store.saveDiff(baseline, diffBytes, runtime);
280
325
  return { ok: true, ...verdict, diffPath };
281
326
  },
282
327
  },
@@ -51,6 +51,7 @@ When nothing has connected, the `sessions` line fails and the daemon's own diagn
51
51
  | `chromium` | Whether the exact Chromium revision the bundled Playwright wants is on disk. The most common silent failure |
52
52
  | `daemon` | Whether the port holds a Reticle daemon, a foreign process (named where possible), or nothing. Adds a `version` line when the daemon's version or contract fingerprint disagrees with the CLI's |
53
53
  | `sessions` | Whether any page has actually connected. Zero is a FAILING check rather than a neutral note, because it is the state the setup most often stalls in: wired correctly, daemon up, nothing dialled in. When it is zero the daemon's own no-session diagnosis prints below the checklist |
54
+ | `agent link` | Whether an MCP client has listed Reticle's tools on this port **and actually called one**. Every other row here checks a component; this one checks the hop between your agent and the daemon. It is possible for the SDK to be injected, a session to be live, and your client to show every tool enabled, while no request has ever crossed from the agent. That state passes every other check and is the one people lose hours to |
54
55
  | `bridge port` | The port your app must dial, spelled out because mixing it up with the dev server port is the most common setup failure |
55
56
  | `port check` | Only when your project's configured port disagrees with the one the daemon is on. Prints the mismatch rather than leaving you to spot it across two lines |
56
57
  | `sibling` | Only when a well-known Reticle port other than this daemon's has a listener. An observation, not a diagnosis: that listener may or may not be related |
@@ -21,8 +21,10 @@ Most people never type this. Your agent runs [`reticle mcp`](/cli/mcp), which st
21
21
  | `--headless` | boolean | `true` for `serve` | Hide the browser |
22
22
  | `--headed` | boolean | `false` | Show it. `serve` owns the pooled browser behind leases, which is batch work, so it hides by default |
23
23
  | `--http` | boolean | `false` | Also expose the HTTP verify endpoint |
24
- | `--http-port N` | number | unset | Port for that endpoint |
25
- | `--http-token T` | string | unset | Bearer token the endpoint requires |
24
+ | `--http-port N` | number | unset (`7331`) | Port for that endpoint. Requires `--http`. |
25
+ | `--http-token T` | string | unset | Bearer token the endpoint requires. Requires `--http`. |
26
+
27
+ The HTTP flags are honoured or refused, never silently dropped: against an already-running daemon that does not serve the requested verify port, `serve --http` exits 1 and names both ports. A running daemon keeps the flags it was started with, so `reticle stop`, then serve again. A verify port that cannot be bound fails the daemon start with a `reticle_daemon_start_failed` line naming that port.
26
28
 
27
29
  ## What it prints
28
30
 
package/docs/cli.mdx CHANGED
@@ -4,7 +4,7 @@ description: Every reticle command. What it does, what it prints, and what it ex
4
4
  icon: terminal
5
5
  ---
6
6
 
7
- The `reticle` CLI wires Reticle into a project (`init`), runs and diagnoses the daemon your app connects to (`serve`, `status`, `doctor`, `kill`, `restart`), and gates a build on verification in CI (`verify`, `affected`, `gate`). This page is the index; each command has its own page with real captured output and measured exit codes.
7
+ The `reticle` CLI installs Reticle into a project and proves the install works (`init`), runs and diagnoses the daemon your app connects to (`serve`, `status`, `doctor`, `kill`, `restart`), and gates a build on verification in CI (`verify`, `affected`, `gate`). This page is the index; each command has its own page with real captured output and measured exit codes.
8
8
 
9
9
  The CLI is mostly for you, not your agent. Your agent talks MCP; you use these to set things up, work out why nothing connected, and wire verification into CI.
10
10
 
@@ -32,12 +32,16 @@ npx @reticlehq/server <command>
32
32
 
33
33
  ### `reticle init`
34
34
 
35
- Wires Reticle into the project in the current directory.
35
+ Wires Reticle into the project in the current directory, then boots the app, proves a session connected and drives one flow to a verdict. It exits non-zero if no verdict was produced, and prints what is left to do.
36
36
 
37
37
  ```
38
- reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
38
+ reticle init [--flow "<what to drive>"] [--env KEY=VALUE]... [--app <dir>] [--license <key>]
39
+ [--json] [--files-only] [--no-open] [--no-drive] [--dry-run]
40
+ [--port N] [--no-mcp] [--no-install] [--timeout S] [--drive-model M]
39
41
  ```
40
42
 
43
+ `--flow`, `--env` and `--app` are the answers the command cannot work out for itself: which journey proves what you care about, what the app needs to reach a usable state, and which app in a monorepo. `--files-only` is what `init` did before it learned to boot the app: it still registers the MCP server and pre-approves Reticle's tools, so it doubles as the upgrade path for an existing install.
44
+
41
45
  <Card title="Full walkthrough with real output" icon="wand-magic-sparkles" href="/install-agentic">
42
46
  What it writes, and how to read its four status marks.
43
47
  </Card>
package/docs/desktop.mdx CHANGED
@@ -214,7 +214,7 @@ Alternatively, since an Electron renderer _is_ Chromium, `--remote-debugging-por
214
214
 
215
215
  ```toml
216
216
  [dependencies]
217
- reticle-tauri = "2.12"
217
+ reticle-tauri = "2.13"
218
218
  ```
219
219
 
220
220
  ```rust
package/docs/docs.json CHANGED
@@ -32,7 +32,7 @@
32
32
  "navigation": {
33
33
  "versions": [
34
34
  {
35
- "version": "2.12.0",
35
+ "version": "2.13.1",
36
36
  "default": true,
37
37
  "tabs": [
38
38
  {
@@ -4,7 +4,7 @@ description: 'Zero to your agent verifying your real app, step by step, with wor
4
4
  icon: rocket
5
5
  ---
6
6
 
7
- **To get started with Reticle: run `npx @reticlehq/server init` in your project root, restart your dev server, restart your coding agent, then open your app in a browser and ask the agent to drive a flow.** Reticle is a verification layer that embeds a dev-only SDK in your running web app so an AI coding agent can prove a change works instead of guessing. It needs Node 20 or newer, an app you run locally, and an agent that speaks MCP.
7
+ **To get started with Reticle: run `npx @reticlehq/server init --flow "<the journey worth proving>"` in your project root.** That one command wires your project, starts your dev server, opens the app, waits for it to connect, and drives that flow to a verdict. You do not restart anything and you do not drive it yourself. Reticle is a verification layer that embeds a dev-only SDK in your running web app so an AI coding agent can prove a change works instead of guessing. It needs Node 20 or newer, an app you run locally, and an agent that speaks MCP.
8
8
 
9
9
  > **Looking for the fast path?** [Quickstart](/quickstart) gets you to a real verdict in five minutes, and every response on it was captured live. [Agentic install](/install-agentic) and [Manual install](/install-manual) cover setup in detail, per agent and per framework.
10
10
  >
@@ -76,7 +76,19 @@ It detects your framework, package manager, and React version, then:
76
76
 
77
77
  The bridge + MCP server is a single process that serves all your projects, so it's registered at **user scope**, not in a per-project `.mcp.json`. Only the SDK (the `reticle()` plugin / connect call) is added per project.
78
78
 
79
- Re-running is safe (already-registered/already-patched steps are skipped). Preview without writing via `npx @reticlehq/server init --dry-run`. Flags: `--dry-run`, `--port N`, `--no-mcp`, `--no-install`, `--app <dir>`.
79
+ Re-running is safe: already-registered and already-patched steps are skipped, and on a wired project it goes straight to proving the app still works. Preview without writing via `npx @reticlehq/server init --dry-run`.
80
+
81
+ `init` does not stop at writing files. It starts your dev server (restarting one whose bundle predates the config edit), opens the app, waits for a session to connect from inside it, and drives one flow to a verdict, which it saves so later checks are a single call with no model involved. It exits non-zero if no verdict was produced and prints what is left to do.
82
+
83
+ Three flags carry what the command cannot work out for itself:
84
+
85
+ | Flag | What only you know |
86
+ | --- | --- |
87
+ | `--flow "<what>"` | Which journey proves the thing you care about. It can list the buttons on your page; it cannot know that checkout matters and the theme toggle does not. |
88
+ | `--env KEY=VALUE` | What your app needs to reach a usable state: the key from `.env.example`, the mock backend, the variable that skips an auth wall. Repeatable. |
89
+ | `--app <dir>` | Which app in a monorepo. It finds the servable ones; only you know which you are working in. |
90
+
91
+ The rest are dials: `--license <key>` (writes it to `.env` and keeps `.env` out of git), `--json` (one object for an agent to read), `--files-only` (write, register, pre-approve, and stop, which is what `init` did before it learned to boot the app, and what an existing install re-runs to pick up new wiring), `--no-open`, `--no-drive`, `--dry-run`, `--port N`, `--no-mcp`, `--no-install`.
80
92
 
81
93
  Then restart your dev server and skip to [Step 4](#step-4-run-it-and-verify-the-connection). The manual steps below explain what `init` sets up, if you prefer to wire it yourself.
82
94
 
@@ -310,6 +322,8 @@ export default defineConfig({
310
322
 
311
323
  ## Step 4: Run it and verify the connection
312
324
 
325
+ > `reticle init` does this for you: it starts the dev server, opens the app and waits for the session. Read on only if you are wiring by hand, or if it told you it could not.
326
+
313
327
  1. Start your app's dev server as usual (`npm run dev`).
314
328
  2. Open it in the browser (the SDK connects when the page loads).
315
329
  3. In your agent, ask it to confirm the connection:
@@ -328,6 +342,8 @@ If the list is empty, see [Troubleshooting](#troubleshooting).
328
342
 
329
343
  ## Step 5: Your first verification
330
344
 
345
+ > `reticle init --flow "<what>"` drives this for you and saves the flow. This section is what it does, for when you want to do it yourself.
346
+
331
347
  Now just talk to your agent in plain language. For example:
332
348
 
333
349
  > "Add a 'Refresh' button to the header that re-fetches the dashboard data, then use Reticle to verify clicking it fires `GET /api/dashboard` and shows no console errors."
@@ -490,7 +506,7 @@ pnpm add @reticlehq/next
490
506
 
491
507
  ### What exactly does `reticle init` change in my project?
492
508
 
493
- Four things, and none of them are mysterious: a `.reticle.json` project config, your build config (the `reticle()` Vite plugin, or `withReticle` in `next.config`), a dev-only capabilities file at `src/reticle-dev.ts` (or `app/reticle-dev.tsx` on Next.js), and your agent's rule files. It also registers the MCP server globally, which is a once-per-machine step rather than a per-project one. Run `npx @reticlehq/server init --dry-run` first to see the exact plan before anything is written.
509
+ Four files, and none of them are mysterious: a `.reticle.json` project config, your build config (the `reticle()` Vite plugin, or `withReticle` in `next.config`), a dev-only capabilities file at `src/reticle-dev.ts` (or `app/reticle-dev.tsx` on Next.js), and your agent's rule files. It then starts your dev server, opens the app and drives one flow, none of which changes your source: the dev server is left running for you afterwards, and the only file the drive may edit is the capabilities file, and only when your app registers none. It also registers the MCP server globally, which is a once-per-machine step rather than a per-project one. Run `npx @reticlehq/server init --dry-run` first to see the exact plan before anything is written.
494
510
 
495
511
  ### Do I have to re-register the MCP server for every project?
496
512
 
package/docs/index.mdx CHANGED
@@ -14,7 +14,7 @@ It answers the only question that matters: did the change actually work? On Reac
14
14
 
15
15
  ## Install it: paste this into your coding agent
16
16
 
17
- You do not install Reticle by hand. Copy the block below into Claude Code, Cursor, Copilot, Codex, Windsurf, OpenCode, or any MCP agent, and let the agent wire its own workspace. It ends with the only definition of "installed" that counts: one real flow driven, one real verdict returned.
17
+ You do not install Reticle by hand, and neither does your agent: it runs one command that does the whole thing. Copy the block below into Claude Code, Cursor, Copilot, Codex, Windsurf, OpenCode, or any MCP agent, and let it wire its own workspace. It ends with the only definition of "installed" that counts: one real flow driven, one real verdict returned.
18
18
 
19
19
  ```text
20
20
  Reticle is the proof layer for AI coding agents. It runs a dev-only SDK inside my
@@ -30,12 +30,15 @@ silent 500.
30
30
 
31
31
  Install it now:
32
32
 
33
- RETICLE_INSTALL_SOURCE=docs_site npx @reticlehq/server@latest init
33
+ RETICLE_INSTALL_SOURCE=docs_site npx @reticlehq/server@latest init \
34
+ --flow "<pick the journey that matters in my app and name it here>"
34
35
 
35
- Then follow https://docs.reticle.sh/install-agentic.md and, if anything needs a
36
- manual edit, https://docs.reticle.sh/install-manual.md. Do not stop until you have
37
- driven one real flow in my app and produced a verdict. That, not a config file, is
38
- what "installed" means.
36
+ That one command wires the project, starts the dev server, opens the app, waits
37
+ for it to connect and drives that flow to a verdict. Do not walk it by hand: read
38
+ what it prints, do anything it says is outstanding, and re-run it, which is safe.
39
+ It exits non-zero until a verdict exists, and that, not a config file, is what
40
+ "installed" means. Only if it could not run at all, follow
41
+ https://docs.reticle.sh/install-manual.md.
39
42
 
40
43
  Afterwards, use it whenever you change any user-facing behaviour.
41
44
  ```