@reticlehq/server 3.4.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +16 -8
  3. package/dist/chromium-launch-options.d.ts +3 -1
  4. package/dist/chromium-launch-options.js +2 -1
  5. package/dist/command/cli/adhoc-verdict.d.ts +1 -0
  6. package/dist/command/cli/adhoc-verdict.js +6 -3
  7. package/dist/command/cli/answers/open-note.d.ts +2 -0
  8. package/dist/command/cli/answers/open-note.js +22 -0
  9. package/dist/command/cli/auth/cloud-session.d.ts +4 -4
  10. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  11. package/dist/command/cli/cli-flow-commands.js +4 -8
  12. package/dist/command/cli/cli-parse.d.ts +1 -1
  13. package/dist/command/cli/cli-parse.js +13 -11
  14. package/dist/command/cli/cli-verify.d.ts +1 -1
  15. package/dist/command/cli/cli-verify.js +35 -25
  16. package/dist/command/cli/cloud-cli.js +110 -12
  17. package/dist/command/cli/cloud-login.d.ts +9 -1
  18. package/dist/command/cli/cloud-login.js +43 -8
  19. package/dist/command/cli/demo/demo-page.js +11 -3
  20. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  21. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  22. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  23. package/dist/command/cli/drive/drive-attach.js +71 -4
  24. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  25. package/dist/command/cli/drive/drive-command.js +13 -7
  26. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  27. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  28. package/dist/command/cli/ports/port-holder.js +24 -0
  29. package/dist/command/cli/setup-mcp-cli.js +15 -5
  30. package/dist/command/cli/tutorial.d.ts +2 -2
  31. package/dist/command/cli/tutorial.js +6 -9
  32. package/dist/command/cli.d.ts +6 -0
  33. package/dist/command/cli.js +45 -23
  34. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  35. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  36. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  37. package/dist/command/daemon/daemon-resolve.js +33 -2
  38. package/dist/command/setup/agent-writer.d.ts +1 -1
  39. package/dist/command/setup/agent-writer.js +3 -2
  40. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  41. package/dist/command/setup/bringup/connect-lease.js +1 -0
  42. package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
  43. package/dist/command/setup/bringup/dev-server-wait.js +20 -1
  44. package/dist/command/setup/init/init-port.d.ts +4 -0
  45. package/dist/command/setup/init/init-port.js +10 -0
  46. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  47. package/dist/command/setup/init/init-runtime.js +71 -9
  48. package/dist/command/setup/node-effects.js +8 -2
  49. package/dist/command/setup/remaining-steps.js +2 -1
  50. package/dist/command/setup/run-setup.d.ts +7 -0
  51. package/dist/command/setup/run-setup.js +38 -25
  52. package/dist/command/setup/setup-command.d.ts +2 -1
  53. package/dist/command/setup/setup-command.js +2 -0
  54. package/dist/command/setup/setup-mcp.d.ts +2 -0
  55. package/dist/command/setup/setup-mcp.js +14 -5
  56. package/dist/command/version/peer-announce.d.ts +6 -2
  57. package/dist/command/version/peer-announce.js +8 -6
  58. package/dist/command/version/version-nudge.d.ts +17 -3
  59. package/dist/command/version/version-nudge.js +25 -4
  60. package/dist/command/version/version-skew.js +11 -2
  61. package/dist/features/crawl/crawl-tools.js +4 -0
  62. package/dist/features/crawl/crawl.d.ts +6 -0
  63. package/dist/features/crawl/crawl.js +9 -1
  64. package/dist/features/exhaust/ledger.d.ts +7 -0
  65. package/dist/features/exhaust/ledger.js +35 -9
  66. package/dist/features/harness/app-findings.d.ts +2 -0
  67. package/dist/features/harness/app-findings.js +52 -0
  68. package/dist/features/harness/drive-report.d.ts +27 -1
  69. package/dist/features/harness/drive-report.js +162 -11
  70. package/dist/features/harness/drivers.d.ts +10 -14
  71. package/dist/features/harness/drivers.js +10 -14
  72. package/dist/features/harness/fill-values.d.ts +3 -69
  73. package/dist/features/harness/fill-values.js +3 -133
  74. package/dist/features/harness/goals.d.ts +21 -0
  75. package/dist/features/harness/goals.js +77 -0
  76. package/dist/features/harness/harness.d.ts +21 -0
  77. package/dist/features/harness/harness.js +69 -2
  78. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  79. package/dist/features/harness/platform/drive-target.js +81 -0
  80. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  81. package/dist/features/harness/platform/local-apps.js +204 -0
  82. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  83. package/dist/features/harness/platform/remote-drive.js +365 -0
  84. package/dist/features/harness/platform/script.d.ts +48 -0
  85. package/dist/features/harness/platform/script.js +128 -0
  86. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  87. package/dist/features/harness/platform/server-driver.js +179 -0
  88. package/dist/features/harness/platform-config.d.ts +7 -0
  89. package/dist/features/harness/platform-config.js +14 -2
  90. package/dist/features/harness/script/app-controls.d.ts +5 -0
  91. package/dist/features/harness/script/app-controls.js +40 -0
  92. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  93. package/dist/features/harness/script/lane-ids.js +16 -0
  94. package/dist/features/harness/script/script-run.d.ts +41 -0
  95. package/dist/features/harness/script/script-run.js +299 -0
  96. package/dist/features/license/license.d.ts +2 -2
  97. package/dist/features/visual/visual-tools.d.ts +10 -1
  98. package/dist/features/visual/visual-tools.js +40 -3
  99. package/dist/hooks/driven-by.d.ts +4 -0
  100. package/dist/hooks/driven-by.js +16 -0
  101. package/dist/hooks/hook-commands.js +15 -4
  102. package/dist/hooks/tool-log.d.ts +9 -0
  103. package/dist/hooks/tool-log.js +29 -0
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.js +91 -89
  106. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  107. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  108. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  109. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  110. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  111. package/dist/judgement/runs/artifact/run-store.js +26 -1
  112. package/dist/judgement/runs/drive-run.d.ts +22 -0
  113. package/dist/judgement/runs/drive-run.js +99 -1
  114. package/dist/judgement/runs/repair-prompt.js +1 -1
  115. package/dist/judgement/runs/reticle-runner.js +8 -1
  116. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  117. package/dist/judgement/runs/verification-sync.js +4 -3
  118. package/dist/language/flows/change/gate.d.ts +10 -0
  119. package/dist/language/flows/change/gate.js +22 -0
  120. package/dist/language/flows/decision.js +34 -0
  121. package/dist/language/flows/flow-anchor.d.ts +54 -8
  122. package/dist/language/flows/flow-anchor.js +88 -3
  123. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  124. package/dist/language/flows/flow-cross-step.js +21 -0
  125. package/dist/language/flows/flow-learning.js +8 -11
  126. package/dist/language/flows/flow-mutate-tools.js +5 -3
  127. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  128. package/dist/language/flows/flow-replay-run.js +101 -94
  129. package/dist/language/flows/flow-replay.d.ts +23 -1
  130. package/dist/language/flows/flow-replay.js +92 -19
  131. package/dist/language/flows/flow-scope.js +5 -1
  132. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  133. package/dist/language/flows/flow-step-runners.js +236 -42
  134. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  135. package/dist/language/flows/flow-store-for-session.js +2 -2
  136. package/dist/language/flows/flow-tools.js +58 -34
  137. package/dist/language/flows/flows.js +5 -2
  138. package/dist/language/flows/heal-run.js +6 -4
  139. package/dist/language/flows/recording/flake.d.ts +2 -2
  140. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  141. package/dist/language/flows/recording/tape/recordings.js +40 -8
  142. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  143. package/dist/language/flows/replay-from-hud.js +51 -0
  144. package/dist/language/flows/replay.d.ts +9 -5
  145. package/dist/language/flows/replay.js +27 -9
  146. package/dist/language/flows/suite/suite-flakes.js +2 -0
  147. package/dist/language/flows/suite-selection.d.ts +2 -15
  148. package/dist/language/flows/suite-selection.js +4 -3
  149. package/dist/language/flows/verify-change-tools.js +8 -4
  150. package/dist/launch-chromium.d.ts +27 -5
  151. package/dist/launch-chromium.js +38 -8
  152. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  153. package/dist/memory/cloud/cloud-config.js +15 -0
  154. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  155. package/dist/memory/cloud/cloud-sync.js +12 -1
  156. package/dist/memory/cloud/harness-config.d.ts +26 -1
  157. package/dist/memory/cloud/harness-config.js +99 -4
  158. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  159. package/dist/memory/cloud/hud-notices-source.js +128 -0
  160. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  161. package/dist/memory/cloud/memory-scope.js +205 -0
  162. package/dist/memory/cloud/sync-cycle.d.ts +21 -3
  163. package/dist/memory/cloud/sync-cycle.js +45 -5
  164. package/dist/memory/cloud/sync-daemon.js +7 -5
  165. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  166. package/dist/memory/cloud/sync-disk.js +5 -8
  167. package/dist/memory/impact/account-watch.d.ts +19 -0
  168. package/dist/memory/impact/account-watch.js +34 -0
  169. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  170. package/dist/memory/impact/impact-recorder.js +12 -2
  171. package/dist/memory/impact/impact-store.d.ts +16 -0
  172. package/dist/memory/impact/impact-store.js +142 -26
  173. package/dist/memory/intent/intent-shard.d.ts +2 -2
  174. package/dist/memory/intent/intent-tools.js +88 -5
  175. package/dist/memory/journal/envelope.d.ts +16 -16
  176. package/dist/memory/journal/journal-query.d.ts +14 -0
  177. package/dist/memory/journal/journal-query.js +15 -0
  178. package/dist/memory/journal/journal-recorder.js +7 -1
  179. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  180. package/dist/memory/journal/on-disk/retention.js +5 -0
  181. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  182. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  183. package/dist/memory/journal/session-end.js +6 -4
  184. package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
  185. package/dist/memory/project/artifact-root-resolver.js +134 -26
  186. package/dist/memory/project/artifact-root.d.ts +42 -5
  187. package/dist/memory/project/artifact-root.js +87 -8
  188. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  189. package/dist/memory/project/dir/reticle-dir.js +3 -1
  190. package/dist/memory/project/project-tools.js +22 -3
  191. package/dist/memory/project/session-root.d.ts +24 -1
  192. package/dist/memory/project/session-root.js +41 -5
  193. package/dist/memory/project/sync-status.d.ts +39 -0
  194. package/dist/memory/project/sync-status.js +90 -0
  195. package/dist/memory/recall/memory-tools.js +2 -0
  196. package/dist/memory/recall/project-memory.d.ts +19 -0
  197. package/dist/memory/recall/project-memory.js +45 -6
  198. package/dist/portal/bridge/bridge.d.ts +7 -4
  199. package/dist/portal/bridge/bridge.js +25 -3
  200. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  201. package/dist/portal/bridge/pairing-token.js +17 -0
  202. package/dist/portal/input/real-input.d.ts +81 -10
  203. package/dist/portal/input/real-input.js +207 -22
  204. package/dist/portal/input/viewport-tools.js +19 -9
  205. package/dist/portal/pool/browser-pool.d.ts +22 -9
  206. package/dist/portal/pool/browser-pool.js +70 -35
  207. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  208. package/dist/portal/pool/playwright-launcher.js +17 -1
  209. package/dist/portal/pool/pool-contract.d.ts +7 -4
  210. package/dist/portal/session/command-payload.d.ts +8 -0
  211. package/dist/portal/session/command-payload.js +19 -0
  212. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  213. package/dist/portal/session/dev-server/dev-command.js +61 -12
  214. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  215. package/dist/portal/session/facts/departure-note.js +47 -0
  216. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  217. package/dist/portal/session/facts/session-replaced.js +5 -0
  218. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  219. package/dist/portal/session/first-run-wiring.js +132 -0
  220. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  221. package/dist/portal/session/human/notes-ledger.js +55 -0
  222. package/dist/portal/session/human/review-store.d.ts +12 -0
  223. package/dist/portal/session/human/review-store.js +18 -10
  224. package/dist/portal/session/live-control-tools.d.ts +0 -13
  225. package/dist/portal/session/live-control-tools.js +28 -3
  226. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  227. package/dist/portal/session/no-session-diagnosis.js +15 -0
  228. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  229. package/dist/portal/session/no-session-next-action.js +31 -5
  230. package/dist/portal/session/no-session-watch.d.ts +14 -1
  231. package/dist/portal/session/no-session-watch.js +63 -10
  232. package/dist/portal/session/presence/session-mirror.js +3 -0
  233. package/dist/portal/session/serving-directory.d.ts +27 -0
  234. package/dist/portal/session/serving-directory.js +45 -0
  235. package/dist/portal/session/session-health.d.ts +7 -0
  236. package/dist/portal/session/session-health.js +6 -0
  237. package/dist/portal/session/session-info.d.ts +8 -1
  238. package/dist/portal/session/session-info.js +6 -0
  239. package/dist/portal/session/session-manager.d.ts +21 -0
  240. package/dist/portal/session/session-manager.js +44 -8
  241. package/dist/portal/session/session-reaper.js +4 -1
  242. package/dist/portal/session/session-successor.d.ts +5 -0
  243. package/dist/portal/session/session-tools.js +26 -3
  244. package/dist/portal/session/session.d.ts +12 -10
  245. package/dist/portal/session/session.js +30 -31
  246. package/dist/start-options.d.ts +6 -0
  247. package/dist/status-payload.d.ts +10 -0
  248. package/dist/status-payload.js +23 -0
  249. package/dist/surface/http-server.d.ts +1 -1
  250. package/dist/surface/http-server.js +15 -4
  251. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  252. package/dist/surface/mcp/mcp-proxy.js +36 -5
  253. package/dist/surface/mcp/mcp.d.ts +11 -6
  254. package/dist/surface/mcp/mcp.js +28 -14
  255. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  256. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  257. package/dist/surface/mcp/server-instructions.js +14 -4
  258. package/dist/surface/mcp/shared-params.js +2 -2
  259. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  260. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  261. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  262. package/dist/surface/tools/act-sequence-tool.js +2 -2
  263. package/dist/surface/tools/act-tools.js +25 -9
  264. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  265. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  266. package/dist/surface/tools/browser-tools.js +8 -1
  267. package/dist/surface/tools/chat-drives.d.ts +26 -0
  268. package/dist/surface/tools/chat-drives.js +165 -0
  269. package/dist/surface/tools/dynamic-tools.js +29 -2
  270. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  271. package/dist/surface/tools/empty-session-list.js +80 -0
  272. package/dist/surface/tools/error-recovery.d.ts +6 -0
  273. package/dist/surface/tools/error-recovery.js +38 -6
  274. package/dist/surface/tools/explore-tools.d.ts +10 -1
  275. package/dist/surface/tools/explore-tools.js +82 -13
  276. package/dist/surface/tools/feedback-tools.js +14 -0
  277. package/dist/surface/tools/harness-explore.d.ts +96 -14
  278. package/dist/surface/tools/harness-explore.js +227 -202
  279. package/dist/surface/tools/harness-plan.d.ts +12 -0
  280. package/dist/surface/tools/harness-plan.js +31 -0
  281. package/dist/surface/tools/harness-script.d.ts +8 -0
  282. package/dist/surface/tools/harness-script.js +342 -0
  283. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  284. package/dist/surface/tools/harness-toolset.js +77 -6
  285. package/dist/surface/tools/invoke-tool.js +64 -9
  286. package/dist/surface/tools/lease-seed.d.ts +29 -0
  287. package/dist/surface/tools/lease-seed.js +123 -0
  288. package/dist/surface/tools/lease-tools.d.ts +3 -23
  289. package/dist/surface/tools/lease-tools.js +30 -131
  290. package/dist/surface/tools/live-call-text.js +2 -2
  291. package/dist/surface/tools/next-step.d.ts +19 -0
  292. package/dist/surface/tools/next-step.js +159 -0
  293. package/dist/surface/tools/observe-tools.js +2 -4
  294. package/dist/surface/tools/platform-moment.d.ts +12 -0
  295. package/dist/surface/tools/platform-moment.js +168 -0
  296. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  297. package/dist/surface/tools/query-hint-schema.js +10 -0
  298. package/dist/surface/tools/read-tools.js +8 -2
  299. package/dist/surface/tools/real-input-attempt.js +96 -13
  300. package/dist/surface/tools/tool-kit.d.ts +25 -1
  301. package/dist/surface/tools/tool-kit.js +4 -6
  302. package/dist/surface/tools/tools.js +20 -48
  303. package/dist/telemetry/argument-shape.js +1 -1
  304. package/dist/telemetry/hud-metrics.d.ts +2 -0
  305. package/dist/telemetry/hud-metrics.js +17 -0
  306. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  307. package/dist/telemetry/onboarding-firsts.js +2 -2
  308. package/dist/telemetry/telemetry.d.ts +12 -0
  309. package/dist/telemetry/telemetry.js +43 -14
  310. package/dist/telemetry/verification-of.js +1 -0
  311. package/dist/wire-journal.js +4 -1
  312. package/docs/README.md +1 -1
  313. package/docs/actions.mdx +1 -2
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/autodrive.md +29 -25
  316. package/docs/cli/cloud.mdx +6 -4
  317. package/docs/cli/doctor.mdx +6 -0
  318. package/docs/cli/drive.mdx +8 -2
  319. package/docs/cli/init.mdx +11 -13
  320. package/docs/cli/status.mdx +5 -1
  321. package/docs/cli/version.mdx +3 -3
  322. package/docs/cli.mdx +14 -8
  323. package/docs/desktop.mdx +5 -1
  324. package/docs/environment.mdx +3 -7
  325. package/docs/getting-started.md +14 -2
  326. package/docs/human-control.md +1 -2
  327. package/docs/index.mdx +4 -3
  328. package/docs/install-agentic.mdx +1 -1
  329. package/docs/install-manual.mdx +2 -2
  330. package/docs/packages/next.mdx +3 -0
  331. package/docs/packages/tauri.mdx +2 -2
  332. package/docs/packages/vite-plugin.mdx +24 -1
  333. package/docs/packages.mdx +1 -1
  334. package/docs/quickstart.mdx +23 -11
  335. package/docs/telemetry.md +1 -1
  336. package/docs/tools/act.mdx +1 -1
  337. package/docs/tools/session-and-feedback.mdx +9 -9
  338. package/docs/usage.md +5 -3
  339. package/docs/what-is-recorded.md +13 -3
  340. package/docs/what-is-reticle.mdx +3 -1
  341. package/package.json +8 -12
  342. package/dist/features/harness/driver.d.ts +0 -50
  343. package/dist/features/harness/driver.js +0 -236
  344. package/dist/features/harness/jev-driver.d.ts +0 -188
  345. package/dist/features/harness/jev-driver.js +0 -678
  346. package/dist/features/harness/openai-driver.d.ts +0 -46
  347. package/dist/features/harness/openai-driver.js +0 -194
  348. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  349. package/dist/memory/project/dir/fill-value-store.js +0 -88
  350. package/docs/local-registry.md +0 -143
@@ -9,12 +9,16 @@ import { HudPanel, HudView } from '@reticlehq/core';
9
9
  */
10
10
  /** How many steps of the journey one window keeps. A journey longer than this is cut, and says so. */
11
11
  export const HUD_JOURNEY_MAX = 60;
12
+ /** Distinct slide ids counted per window; past this a broken notices file is not a dashboard. */
13
+ const MAX_SLIDE_IDS = 20;
14
+ const SLIDE_ID = /^[a-z0-9-]{1,56}$/;
12
15
  export class HudMetrics {
13
16
  #now;
14
17
  #controls = new Map();
15
18
  #viewMs = new Map();
16
19
  #panelMs = new Map();
17
20
  #journey = [];
21
+ #slides = new Map();
18
22
  #cut = false;
19
23
  /** Did anything happen this window. A HUD merely sitting open is not worth a flush of its own. */
20
24
  #changed = false;
@@ -27,6 +31,8 @@ export class HudMetrics {
27
31
  // A control the HUD never rendered is dropped here, the one place HUD use is counted, so free
28
32
  // text cannot ride in on the id.
29
33
  const control = use.control !== undefined && isHudControl(use.control) ? use.control : undefined;
34
+ if (use.slide !== undefined)
35
+ this.#countSlide(use.slide);
30
36
  if (control === undefined && use.view === undefined && use.panel === undefined)
31
37
  return;
32
38
  this.#changed = true;
@@ -47,6 +53,15 @@ export class HudMetrics {
47
53
  this.#step(`panel:${use.panel}`);
48
54
  }
49
55
  }
56
+ #countSlide(slide) {
57
+ // A slide id is Reticle's own (`notice-<id>` for the remote ones): lowercase words and dashes.
58
+ if (!SLIDE_ID.test(slide))
59
+ return;
60
+ if (!this.#slides.has(slide) && this.#slides.size >= MAX_SLIDE_IDS)
61
+ return;
62
+ this.#slides.set(slide, (this.#slides.get(slide) ?? 0) + 1);
63
+ this.#changed = true;
64
+ }
50
65
  get empty() {
51
66
  return !this.#changed;
52
67
  }
@@ -60,12 +75,14 @@ export class HudMetrics {
60
75
  ...(panelMs.size > 0 ? { hudPanelMs: Object.fromEntries(panelMs) } : {}),
61
76
  ...(this.#journey.length > 0 ? { hudJourney: [...this.#journey] } : {}),
62
77
  ...(this.#cut ? { hudJourneyCut: true } : {}),
78
+ ...(this.#slides.size > 0 ? { hudSlides: Object.fromEntries(this.#slides) } : {}),
63
79
  };
64
80
  }
65
81
  /** Start the next window. The current view and panel carry over, timed from now. */
66
82
  reset() {
67
83
  const at = this.#now();
68
84
  this.#controls.clear();
85
+ this.#slides.clear();
69
86
  this.#viewMs.clear();
70
87
  this.#panelMs.clear();
71
88
  this.#journey.length = 0;
@@ -1,9 +1,9 @@
1
- /** A tool call the daemon just ran. Called from the dispatch chokepoint, which sees every one. */
1
+ /** A successful page read. The dispatcher calls this after excluding refusals and pauses. */
2
2
  export declare function noteOnboardingFirst(toolName: string): void;
3
3
  /**
4
4
  * The first ACTION of this run, and the fact that this install has been driven at all.
5
5
  *
6
- * Called from the dispatch chokepoint's own action branch rather than matched on a tool name here.
6
+ * Called after the dispatcher observes a dispatched action, rather than on an attempted call.
7
7
  * The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
8
8
  * one call as two different firsts — and the worry was backwards: this server's instructions tell
9
9
  * every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
@@ -15,7 +15,7 @@ import { reportOnboardingStep } from './onboarding-funnel.js';
15
15
  * drives in another has still only crossed each of these once.
16
16
  */
17
17
  const seen = { look: false, act: false, verdict: false, driven: false };
18
- /** A tool call the daemon just ran. Called from the dispatch chokepoint, which sees every one. */
18
+ /** A successful page read. The dispatcher calls this after excluding refusals and pauses. */
19
19
  export function noteOnboardingFirst(toolName) {
20
20
  if (!seen.look && LOOK_TOOLS.has(toolName)) {
21
21
  seen.look = true;
@@ -29,7 +29,7 @@ export function noteOnboardingFirst(toolName) {
29
29
  /**
30
30
  * The first ACTION of this run, and the fact that this install has been driven at all.
31
31
  *
32
- * Called from the dispatch chokepoint's own action branch rather than matched on a tool name here.
32
+ * Called after the dispatcher observes a dispatched action, rather than on an attempted call.
33
33
  * The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
34
34
  * one call as two different firsts — and the worry was backwards: this server's instructions tell
35
35
  * every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
@@ -10,6 +10,18 @@ import { TelemetryEventKind, ProjectIdSource, type Crash, type Feedback, type Id
10
10
  * analytics side learns a customer list only when we deliberately upload one.
11
11
  */
12
12
  export declare const POSTHOG_GROUP_ORGANIZATION = "organization";
13
+ /**
14
+ * Read (or mint-and-persist) the anonymous machine id. `firstRun` is true the run that created it.
15
+ *
16
+ * Read first and create exclusively (`wx`), rather than asking `existsSync` and then writing: two
17
+ * processes starting together used to both see no file, both mint, and the second write replaced
18
+ * the first id, so one machine reported as two first runs. The loser of the exclusive create now
19
+ * reads the winner's id instead.
20
+ */
21
+ export declare const resolveIdentity: (file?: string) => {
22
+ anonymousId: string;
23
+ firstRun: boolean;
24
+ };
13
25
  /**
14
26
  * One-way project fingerprint: distinct-count of this = "projects Reticle is used in", reveals nothing.
15
27
  *
@@ -19,6 +19,7 @@ import { appendFileSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmS
19
19
  import { homedir, platform } from 'node:os';
20
20
  import { dirname, join } from 'node:path';
21
21
  import { TelemetryEventKind, TELEMETRY_EVENT_VERSION, ProjectIdSource, isSessionScoped, } from '@reticlehq/core/telemetry';
22
+ import { ReticleEnv } from '@reticlehq/core';
22
23
  import { SERVER_VERSION } from '../command/version/identity/server-version.js';
23
24
  const RETICLE_DIR = join(homedir(), '.reticle');
24
25
  const ID_FILE = join(RETICLE_DIR, 'telemetry-id');
@@ -100,8 +101,8 @@ import { licenseFacts } from './license-activation.js';
100
101
  import { gitFacts } from './git-facts.js';
101
102
  import { currentAutomationHint } from './automation-hint.js';
102
103
  const Env = {
103
- DISABLE: 'RETICLE_TELEMETRY', // "0" / "false" / "off" → disabled
104
- DO_NOT_TRACK: 'DO_NOT_TRACK', // the cross-tool opt-out convention (any truthy value)
104
+ DISABLE: ReticleEnv.TELEMETRY, // "0" / "false" / "off" → disabled
105
+ DO_NOT_TRACK: ReticleEnv.DO_NOT_TRACK, // the cross-tool opt-out convention (any truthy value)
105
106
  URL: 'RETICLE_TELEMETRY_URL', // override the PostHog host (EU cloud / self-hosted)
106
107
  FILE: 'RETICLE_TELEMETRY_FILE', // record to a local JSONL file and send NOTHING — see the sink note
107
108
  KEY: 'RETICLE_TELEMETRY_KEY', // override the PostHog project key
@@ -140,17 +141,45 @@ const isDisabled = (env, cwd = process.cwd()) => {
140
141
  * file, and a test run. This lifts one implicit guard for one user-initiated event kind.
141
142
  */
142
143
  const onlyBlockedBySourceCheckout = (env, cwd) => isDisabled(env, cwd) && !isDisabled(env, '/');
143
- /** Read (or mint-and-persist) the anonymous machine id. `firstRun` is true the run that created it. */
144
- const resolveIdentity = () => {
144
+ /** Node's code for "the file is already there" — what an exclusive create answers when it lost. */
145
+ const EEXIST = 'EEXIST';
146
+ const isAlreadyThere = (error) => 'object' === typeof error && null !== error && EEXIST === error.code;
147
+ const readIdFile = (file) => {
145
148
  try {
146
- if (existsSync(ID_FILE)) {
147
- const id = readFileSync(ID_FILE, 'utf8').trim();
148
- if (id.length > 0)
149
- return { anonymousId: id, firstRun: false };
150
- }
149
+ const id = readFileSync(file, 'utf8').trim();
150
+ return id.length > 0 ? id : undefined;
151
+ }
152
+ catch {
153
+ return undefined;
154
+ }
155
+ };
156
+ /**
157
+ * Read (or mint-and-persist) the anonymous machine id. `firstRun` is true the run that created it.
158
+ *
159
+ * Read first and create exclusively (`wx`), rather than asking `existsSync` and then writing: two
160
+ * processes starting together used to both see no file, both mint, and the second write replaced
161
+ * the first id, so one machine reported as two first runs. The loser of the exclusive create now
162
+ * reads the winner's id instead.
163
+ */
164
+ export const resolveIdentity = (file = ID_FILE) => {
165
+ try {
166
+ const existing = readIdFile(file);
167
+ if (existing !== undefined)
168
+ return { anonymousId: existing, firstRun: false };
151
169
  const id = randomUUID();
152
- mkdirSync(RETICLE_DIR, { recursive: true });
153
- writeFileSync(ID_FILE, id, 'utf8');
170
+ mkdirSync(dirname(file), { recursive: true });
171
+ try {
172
+ writeFileSync(file, id, { encoding: 'utf8', flag: 'wx' });
173
+ }
174
+ catch (error) {
175
+ if (!isAlreadyThere(error))
176
+ throw error;
177
+ const raced = readIdFile(file);
178
+ if (raced !== undefined)
179
+ return { anonymousId: raced, firstRun: false };
180
+ // An empty file left by a crash: replace it, as the old code did.
181
+ writeFileSync(file, id, 'utf8');
182
+ }
154
183
  return { anonymousId: id, firstRun: true };
155
184
  }
156
185
  catch {
@@ -209,10 +238,10 @@ const packageRoot = (cwd) => {
209
238
  /** Print the opt-out notice exactly once per machine (honest disclosure, the OSS-trust bar). */
210
239
  const showNoticeOnce = () => {
211
240
  try {
212
- if (existsSync(NOTICE_FILE))
213
- return;
214
241
  mkdirSync(RETICLE_DIR, { recursive: true });
215
- writeFileSync(NOTICE_FILE, '1', 'utf8');
242
+ // Exclusive create IS the "once": it throws when the file is already there, so the check and
243
+ // the write cannot be split by a second process printing the notice too.
244
+ writeFileSync(NOTICE_FILE, '1', { encoding: 'utf8', flag: 'wx' });
216
245
  process.stderr.write('reticle: anonymous usage telemetry helps improve reticle — no code, no PII, ' +
217
246
  `and your app's data never leaves your machine (${POLICY_URL}). ` +
218
247
  'Opt out any time: reticle telemetry disable\n');
@@ -70,6 +70,7 @@ const REPLAY_VERDICTS = new Map([
70
70
  [ReplayStatus.OK, { verified: Verified.YES, passed: true }],
71
71
  [ReplayStatus.DRIFT, { verified: Verified.NO, passed: false }],
72
72
  [ReplayStatus.ERROR, { verified: Verified.UNKNOWN, passed: false }],
73
+ [ReplayStatus.UNVERIFIABLE, { verified: Verified.UNKNOWN, passed: false }],
73
74
  ]);
74
75
  function replayVerdict(toolName, status) {
75
76
  if (ReticleTool.FLOW_REPLAY !== toolName || 'string' !== typeof status)
@@ -8,6 +8,7 @@
8
8
  * the 1000-line backstop is what asked for the split.
9
9
  */
10
10
  import { artifactRootResolver } from './memory/project/artifact-root-resolver.js';
11
+ import { servingDirectoryOf } from './portal/session/serving-directory.js';
11
12
  import { originOf } from './portal/session/session-manager.js';
12
13
  import { Bridge } from './portal/bridge/bridge.js';
13
14
  import { FlowStore } from './language/flows/flows.js';
@@ -35,7 +36,9 @@ export function attachJournal(bridge, deps) {
35
36
  const ambientStore = new AmbientStore(deps.fs, deps.reticleRoot);
36
37
  // Built once, not per session: the resolver walks config discovery and the user-level registry,
37
38
  // and neither changes between two tabs connecting a second apart.
38
- const resolveArtifactRoot = artifactRootResolver(deps.reticleRoot);
39
+ const resolveArtifactRoot = artifactRootResolver(deps.reticleRoot, {
40
+ servingDirectory: servingDirectoryOf,
41
+ });
39
42
  bridge.attachSessionCreate((session) => {
40
43
  // Stamp the project's own `.reticle` before ANY counter fires for this session. Without it every
41
44
  // verdict is recorded against wherever the daemon was started, which is how one app's evidence
package/docs/README.md CHANGED
@@ -76,7 +76,7 @@ Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `d
76
76
  | [vs-screenshots.mdx](vs-screenshots.mdx) | why a better vision model does not fix a non-visual bug |
77
77
  | [what-is-recorded.md](what-is-recorded.md) | what Reticle keeps, what is redacted, and what it takes for any of it to leave your machine |
78
78
  | [telemetry.md](telemetry.md) | what is collected, and how to turn it off |
79
- | [local-registry.md](local-registry.md) | installing an unpublished build |
79
+ | [local-registry.md](https://github.com/reticlehq/reticle/blob/main/docs/local-registry.md) | installing an unpublished build |
80
80
  | [license-activation.mdx](license-activation.mdx) | you have a key: where to set it, how to confirm it, what to do when it fails |
81
81
  | [enterprise.md](enterprise.md) | the licensed surface |
82
82
 
package/docs/actions.mdx CHANGED
@@ -5,7 +5,7 @@ description: Every action reticle_act and reticle_act_and_wait can perform, with
5
5
  icon: hand-pointer
6
6
  ---
7
7
 
8
- Every action Reticle can perform takes the same shape: a `ref` from [`reticle_look { action: "page" }`](/tools/snapshot) or [`reticle_look { action: "find" }`](/tools/query), an `action` name, and an optional `args` object. The same seventeen actions are available on [`reticle_act`](/tools/act), which proves nothing, and on [`reticle_act_and_wait`](/tools/act-and-wait), which returns a verdict.
8
+ Every action Reticle can perform takes the same shape: a `ref` from [`reticle_look { action: "page" }`](/tools/snapshot) or [`reticle_look { action: "find" }`](/tools/query), an `action` name, and an optional `args` object. The same actions are available on [`reticle_act`](/tools/act), which proves nothing, and on [`reticle_act_and_wait`](/tools/act-and-wait), which returns a verdict.
9
9
 
10
10
  ```json
11
11
  { "ref": "e104", "action": "fill", "args": { "value": "reticle" } }
@@ -26,7 +26,6 @@ Every action Reticle can perform takes the same shape: a `ref` from [`reticle_lo
26
26
  | `scrollIntoView` | none | |
27
27
  | `upload` | `{ content?, name?, type?, path? }` | Sets a file on `<input type="file">`. Inline bytes default to `"reticle test file"` / `"file.txt"` / `"text/plain"`. `path` reads a file from disk, scoped to the project root plus any opt-in directories listed in `.reticle.json` as `"uploadRoots": ["~/Downloads", "/Volumes/fixtures"]`. A refusal names every allowed root. |
28
28
  | `drag` | `{ toRef }` | Pointer-based drag (dnd-kit, react-beautiful-dnd) plus HTML5 DnD. `target` is an alias |
29
- | `webmcp` | `{ tool, params }` | Calls a `navigator.modelContext` tool, if the site exposes one |
30
29
 
31
30
  ### Arguments that are not actions
32
31
 
@@ -135,7 +135,7 @@ Not on this surface at all, but one hop away: `reticle_run { tool, args }` calls
135
135
  | --- | --- | --- |
136
136
  | **flows** | `reticle_flow_save*` / `reticle_flow_replay` / `reticle_verify { action: "heal" }` | Replayable **golden journeys**, anchored to testids/signals; drift is legible and self-heals. |
137
137
  | **baselines** | `reticle_baseline {action:"save"}` / `reticle_baseline {action:"diff"}` | Structural **"before" snapshots**; `reticle_baseline {action:"diff"}` flags regressions against them. |
138
- | **project.json** | `reticle_project` | Cross-run **run-history**: "did it behave like last run?" read via `reticle_project`. |
138
+ | **project.json** | `reticle_project` | Cross-run **run-history**: "did it behave like last run?" read via `reticle_project`. Its `cloud.sync` says where this project's runs stand with the platform (on it, waiting, refused, last push); `push: true` syncs before answering. |
139
139
 
140
140
  > `reticle_project` / `project.json` are the **run-history layer**. flows answer "does the journey still work?"; baselines answer "did the structure change?"; project.json answers "is this run consistent with prior runs?".
141
141
 
package/docs/autodrive.md CHANGED
@@ -6,7 +6,7 @@ icon: steering-wheel
6
6
 
7
7
  Driving an app is the expensive part of verifying one. Every snapshot, every act result and every observation lands in your agent's context and is re-read on every turn after, and none of it is kept: the next run starts from nothing and pays again.
8
8
 
9
- `reticle_verify { action: "explore" }` moves the driving into the daemon. A model there drives your app through the same tool surface your agent uses, records what it drove as saved flows, and hands back a few lines.
9
+ `reticle_verify { action: "explore" }` hands the driving to the Reticle Harness. It decides on the Reticle platform, and the daemon on your machine executes each step through the same tool surface your agent uses, records what it drove as saved flows, and hands back a few lines. It needs a linked project (`reticle connect`).
10
10
 
11
11
  ```jsonc
12
12
  reticle_verify {
@@ -32,51 +32,55 @@ So the drive is a one-off: pay for it once, and the journeys it recorded become
32
32
 
33
33
  A drive reads `.reticle` first: every saved flow with the consequence that must still hold, and every signal and testid your app declares that no flow has ever asserted. It builds a plan from that before it opens its eyes on a page.
34
34
 
35
- Anything already recorded is **replayed**, deterministically, with no model call at all. Only the gaps are driven. So a second run over the same app is mostly free, and the model budget is spent on the part nobody has proved yet rather than on rediscovering what is already on disk. A replay that goes red is reported as a regression; one that DRIFTS is reported separately, because the app moved under the recording and that needs re-anchoring rather than a code change.
35
+ With no persona, anything already recorded is **replayed**, deterministically, with no model call at all, and only the gaps are driven (see the plan below). With a persona, it drives that journey and replays nothing else: the saved flows have nothing to do with the journey you named. So a second run over the same app is mostly free, and the model budget is spent on the part nobody has proved yet rather than on rediscovering what is already on disk. A replay that goes red is reported as a regression; one that DRIFTS is reported separately, because the app moved under the recording and that needs re-anchoring rather than a code change.
36
36
 
37
37
  Every journey a drive walks is saved, whatever happens to the drive, including one that broke, ran out of budget, or whose model simply stopped asking for tools.
38
38
 
39
- ## What it needs
39
+ ## The platform plans the drive
40
40
 
41
- **A model in the daemon's environment.** Any one of three, and the choice is reported back so a comparison can never mislabel its own arms:
41
+ With a platform credential (`reticle connect`, or `RETICLE_API_KEY`), the platform plans every drive. With a persona or without one, it proposes the people worth being, orders the saved flows, finds where they branch, and writes into each journey the product rules that apply to it, read from your project's own intents. The rules tell the Harness what the journey is for and where it ends (a journey that names Settings is not finished before Settings). They are not checked as assertions: a rule like "the row shows refunded" is guidance, and the checks are the consequences each step declares. Your machine sends what the project knows, with each saved step as a short hash and never the values it typed. It runs the plan it gets back and reports what happened. The plan is checked before it runs, and a plan that fails the checks is dropped. Every decision while it drives is the platform's too: your machine executes each step it is handed and reports what happened.
42
42
 
43
- | Variable | Driver |
44
- | --- | --- |
45
- | `RETICLE_API_KEY` | `jev`, against the platform's own proxy, using the key `reticle link` already wrote. No model API key of your own, and the cheap path: it answers typed questions about the page rather than generating tool calls as text. |
46
- | `ANTHROPIC_API_KEY` | `anthropic`, which generates its calls as text. |
47
- | `OPENAI_API_KEY` | `openai`, the same, on OpenAI. |
48
- | `JEV_API_KEY` | `jev`, against TypeSafe directly, for anyone who holds their own key. |
43
+ ## With no persona, it writes a plan first
49
44
 
50
- With several configured, `anthropic` is the default and `RETICLE_HARNESS_DRIVER` moves off it; with only a platform key, `jev` is not the cheaper option, it is the only one. Naming a driver that is not configured is an error, never a silent substitution.
45
+ Called with no `persona`, explore writes the whole drive down before it starts, as a drive plan:
51
46
 
52
- A driver can also be chosen per call, as `reticle_verify { action: "explore", driver: "jev" }`. The question people actually have is comparative, and answering it with an environment variable means restarting the daemon between arms.
47
+ - **Journeys.** Each saved flow worth replaying, plus one open journey for each persona the platform proposes, for the request the user declared (`reticle_intent { action: "declare" }`), and for each intent nobody has proved. Replays need no model; only open journeys reach one.
48
+ - **Order.** A flow that `needs` another runs after it. An open journey runs after the flow everything else needs, usually sign-in.
49
+ - **Lanes.** Each journey nothing else needs gets a lane holding what it needs, in order. Lanes run side by side, each in its own leased browser context, four at a time. A journey that needs one from another lane waits for it, and is blocked if that journey failed. A prerequisite that commits something (a payment, an email) runs in one lane only, and the others wait on it rather than commit it twice.
50
+ - **Branches.** Flows that start with the same steps become one journey. The shared steps are driven once and marked as a checkpoint, then each flow continues from that point. Every flow after the first replays back to the checkpoint first, so none of them starts from the page the previous one left.
53
51
 
54
- Three other dials, all optional:
52
+ The plan appears at the top of the HUD's Agent Log: one row per lane, a card per journey, the running journey opened to its steps, and each card marked as it passes, fails or is blocked. The tool's answer carries one line per journey.
53
+
54
+ Two `reticle_flow_replay` arguments make the branches possible, and you can use them directly. `to` stops before a step. `at` continues at a step on the page as it is, without driving the steps before it. A part-way replay checks the steps it drove and records nothing about the flow as a whole.
55
+
56
+ ## What it needs
57
+
58
+ **A project on the Reticle platform.** Every plan includes the Harness: Free comes with monthly Harness credits, one per decision the Harness makes. Run `reticle connect` once in your app's folder. The Harness runs on the platform: planning, personas, and every choice of what to press next. A model key of your own does not drive it, and a machine with no platform link gets the refusal and the way to fix it.
55
59
 
56
60
  | Variable | What it does |
57
61
  | --- | --- |
58
- | `RETICLE_HARNESS_MODEL` | The Anthropic model that drives. Defaults to a mid-tier one on purpose, for the reason below. |
59
- | `RETICLE_HARNESS_MAX_STEPS` | Ceiling on model turns in one drive. Bounds cost, not value. |
60
- | `RETICLE_HARNESS_BASE_URL` | A proxy or gateway instead of the default API host. |
61
-
62
- ## Two models, and what each one is for
62
+ | `RETICLE_API_KEY` | The key `reticle connect` (or `reticle link`) wrote. The daemon uses it to ask the platform to drive. |
63
+ | `RETICLE_HARNESS_MAX_STEPS` | Ceiling on steps in one drive. Bounds cost, not value. |
64
+ | `RETICLE_SECRET_<FIELD>` | The value for a secret field, such as `RETICLE_SECRET_AUTH_PASSWORD` for a field named `auth-password`. Only the field's NAME goes to the platform. The value is typed in on your machine and never sent. |
63
65
 
64
- The `jev` driver decides; it cannot write. Every choice a drive makes is a selection from candidates Reticle enumerated off the page: which element, which tool, what consequence to claim. That is why it costs a fraction of a generating model and answers in a few hundred milliseconds.
66
+ **What you see.** The HUD's Agent Log marks the moment the Harness takes over ("Reticle Harness is driving") and gives its steps their own colour, so you can tell its work from your agent's. Each Harness drive is recorded as its own run, named for its journey and credited to the Harness, beside your agent's run. Any "double-quoted text" in a persona must be on the page when the journey ends, and is checked.
65
67
 
66
- One thing in a drive is not a selection. A text field is a composition: "a business name", "a statement descriptor", "a search term that returns results" cannot be enumerated from the page. Where the field's label is enough to guess, a small table answers it. Where it is not, and a generating model is configured, that model writes the value, and nothing else.
68
+ ## How it decides
67
69
 
68
- Typing is a small minority of what a drive does: clicks and navigations are the overwhelming majority. So this is escalation rather than a second model in the loop, and a drive with no text fields never calls it at all.
70
+ The platform drives with Jev, a System One model that answers typed questions and cannot write. Every choice is a selection from candidates read off the page: which element, which tool, what consequence to claim. Three rules sit around those choices, because each one was a drive that stopped short or claimed nothing:
69
71
 
70
- What it writes is a FIXTURE, not an opinion. Generated values are saved to `.reticle/fill-values.json` and reused forever: the second drive pays nothing, a replay sends exactly what the recording sent, and a value you dislike is a line in a git-checked file you can edit rather than an argument with a model.
72
+ - A journey that names a page it has not reached ("then turn on automatic refunds in Settings") is not finished. The Harness goes there instead of stopping.
73
+ - A dialog whose destructive action already ran is closed rather than treated as the end of the journey.
74
+ - A control the destructive-action gate refused is a write, so the Harness always claims a request or a signal for it. A save the server rejects then fails visibly instead of passing as nothing to prove.
71
75
 
72
76
  ## Turning it off, and who pays for it
73
77
 
74
78
  A project linked to a Reticle workspace reads two things from it before a drive starts, and honours both:
75
79
 
76
- - **The switch.** Autonomous driving can be turned off per project in the dashboard, under Settings → Verification model. A drive then refuses and says where to turn it back on. Everything else is unaffected, including the tools your own agent drives with.
77
- - **Who is paying.** Driving through the platform spends Reticle's model budget, which is free for three months once claimed and included on a paid plan. Outside both, a drive is refused with the claim link rather than run on somebody else's money.
80
+ - **The switch.** Autonomous driving can be turned off per project, from the HUD's Reticle Harness switch or the dashboard (Settings → Verification model). A drive then refuses and says where to turn it back on. Switched off while a platform drive is running, the drive stops at its next turn with `stopReason: "stopped"`, keeps what it drove, and says so in the Agent Log. Everything else is unaffected, including the tools your own agent drives with.
81
+ - **Credits.** Every workspace gets Harness credits each 30 days: Free 500, Pro 4,800 a seat, Enterprise as agreed. One credit is one decision the Harness makes. The HUD shows how many are left; once they are spent a drive is refused with that number and the way to get more.
78
82
 
79
- Neither applies to a drive on a model key of your own: that costs us nothing, so it is not ours to gate. A daemon that cannot reach the platform at all drives normally: an unreachable settings endpoint is not a reason to lose a feature you were never told to stop using.
83
+ A daemon that cannot reach the platform at all drives normally: an unreachable settings endpoint is not a reason to lose a feature you were never told to stop using.
80
84
 
81
85
  ## What it will not do
82
86
 
@@ -1,16 +1,18 @@
1
1
  ---
2
2
  title: reticle cloud commands
3
- description: Sign in, link a repository, and read runs and regressions from Reticle Cloud.
3
+ description: Connect a project in one command, then read runs and regressions from Reticle Cloud.
4
4
  icon: cloud
5
5
  ---
6
6
 
7
- Ten subcommands bind this repository to Reticle Cloud so runs and flows land on a dashboard. They dispatch before the local parser, so `reticle login` is one tool, not a second binary.
7
+ From your app directory, `reticle connect --project "My App"` wires the app, opens browser sign-in approval if needed, links the named cloud project, and syncs the local history already on this machine. Run the [installer](/quickstart) once on the machine first. The commands below remain available when you need to sign in or link separately.
8
8
 
9
9
  ```bash
10
- npx @reticlehq/server <login|logout|whoami|link|project|config|push|runs|regression|share>
10
+ npx @reticlehq/server connect --project "My App"
11
11
  ```
12
12
 
13
- These are thin clients over the `/v1` API. The logic is server-side; these verbs surface it.
13
+ Use `--url https://your-cloud-origin` for a self-hosted cloud. `connect` checks a saved sign-in against that exact host before reuse; if it has expired, the browser approval opens again. It mints or reuses a project-scoped key, so there is no key to paste. A newly wired app may have no run to sync yet: ask your agent to verify one flow and the linked daemon will sync the result. Run `reticle whoami` to check the binding and `reticle push` to send anything still pending.
14
+
15
+ The other cloud commands are thin clients over the `/v1` API. Their logic is server-side.
14
16
 
15
17
  ## Where credentials live
16
18
 
@@ -74,6 +74,12 @@ All three captures below are real, taken by pointing `PLAYWRIGHT_BROWSERS_PATH`
74
74
 
75
75
  **`✓ using the installed Google Chrome`** (or Microsoft Edge) means the pinned build is absent and Reticle drives the browser you already have instead, through Playwright's channel. Nothing to do: the line also names the command that adds Playwright's own build, if you want it.
76
76
 
77
+ **`✓ using <path> (RETICLE_CHROMIUM_PATH)`** means you named the browser with [`RETICLE_CHROMIUM_PATH`](/environment), so the bundled revision is not checked at all. If the path does not exist, is not a file (a directory, say), or is not executable, the line says which instead, and offers no install command, because installing would not fix a wrong path:
78
+
79
+ ```
80
+ chromium ✗ RETICLE_CHROMIUM_PATH points at /opt/nope/chrome, which does not exist; fix the path, or unset it to use Playwright's own Chromium
81
+ ```
82
+
77
83
  When neither is there, Reticle does not refuse. The first command that needs a browser (a lease, `verify`, a drive) runs the bundled Playwright's own pinned install for you, announced on stderr so `reticle mcp`'s stdout stays clean. The two `✗` lines below are what you see when that has not happened yet, or failed.
78
84
 
79
85
  **`✗ wrong revision`** means builds are installed and none of them is the wanted one. One pinned download away. The line names the revision Playwright wants, then every revision the root actually holds, then the exact command:
@@ -53,10 +53,16 @@ A daemon on `:4400` is the normal state the moment any agent connects, and `driv
53
53
 
54
54
  ```bash
55
55
  $ npx @reticlehq/server drive http://localhost:5173
56
- {"event":"reticle_drive_attached","port":4400,"url":"http://localhost:5173","sessionId":"lease-9f2b…","ready":true}
56
+ {"event":"reticle_drive_attached","port":4400,"url":"http://localhost:5173","sessionId":"lease-9f2b…","ready":true,"projectId":"web","title":"Web"}
57
57
  ```
58
58
 
59
- A plain-text line follows it naming the same session, how long it survives untouched (any tool call against it resets that), and, if the page never dialled the bridge, why.
59
+ A plain-text line follows it naming the same session, the app it belongs to (the `projectId` and title the page reported), how long it survives untouched (any tool call against it resets that), and, if the page never dialled the bridge, why.
60
+
61
+ In a monorepo the port you meant for one app can be held by another. When the current directory's `.reticle.json` names a different project from the one the page reported, the line ends with a warning saying so, and the JSON carries both as `projectId` and `expectedProjectId`. Real capture, with app `admin` on the port, `drive` run from app `web`, and the sentence break shown as `...`:
62
+
63
+ ```
64
+ attached to the Reticle daemon on :4497 ... it is driving http://localhost:5177/?__reticle_project=admin (project "admin", "Admin console") as session lease-74bf5522-8eee-4ea2-b66d-4c056c7dad3a, which every Reticle tool can address. It is released after 300s untouched; any tool call against it resets that. Warning: this directory's .reticle.json is project "web", but the page at http://localhost:5177/?__reticle_project=admin is project "admin", so another app is probably serving that port. Check which dev server is on it before trusting anything this session reports.
65
+ ```
60
66
 
61
67
  That session lives in the daemon, so the tools your agent already has open can address it by id. You do **not** need `reticle stop` first, and should not use it: the MCP proxy respawns a daemon as soon as the next tool call arrives, which is a race a manual `stop` loses.
62
68
 
package/docs/cli/init.mdx CHANGED
@@ -74,21 +74,19 @@ With `--no-mcp`, the whole MCP and rules group collapses into one skipped line:
74
74
  --no-mcp ... also skips the agent rule files (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command
75
75
  ```
76
76
 
77
- Run from a workspace root with several apps, it refuses to guess, names them, and exits `1`:
77
+ Run from a workspace root with several apps, it refuses to guess, prints a command for each one, and exits `1`:
78
78
 
79
79
  ```
80
- Several apps found in this workspace. Re-run `reticle init` inside the one you want:
81
- adapters/build/next
82
- adapters/build/vite
83
- apps/atlas
84
- apps/bench-app
85
- apps/electron-smoke
86
- apps/large-dom-bench
87
- apps/next-smoke
88
- apps/tauri-smoke
89
- apps/vibe-builder-demo
90
-
91
- Or name one without changing directory: reticle init --app apps/next-smoke
80
+ Several apps found in this workspace. Re-run `reticle init` for the one you want:
81
+ reticle init --app adapters/build/next
82
+ reticle init --app adapters/build/vite
83
+ reticle init --app apps/atlas
84
+ reticle init --app apps/bench-app
85
+ reticle init --app apps/electron-smoke
86
+ reticle init --app apps/large-dom-bench
87
+ reticle init --app apps/next-smoke
88
+ reticle init --app apps/tauri-smoke
89
+ reticle init --app apps/vibe-builder-demo
92
90
  ```
93
91
 
94
92
  Given `--app`, it says which directory it moved to before printing the plan:
@@ -45,7 +45,11 @@ A session's parenthesised notes are the ones that explain an answer you would ot
45
45
 
46
46
  ### `--json`
47
47
 
48
- The same facts as one event line on stderr, for a caller that parses rather than reads:
48
+ The same facts as one JSON line on stdout, so a caller can pipe it to a parser:
49
+
50
+ ```bash
51
+ npx @reticlehq/server status --json | jq '.running, .sessionCount'
52
+ ```
49
53
 
50
54
  ```json
51
55
  {
@@ -16,10 +16,10 @@ All three are the same command. No flags.
16
16
 
17
17
  ## What it prints
18
18
 
19
- One `reticle_version` line. Real capture:
19
+ The version, and nothing else, on stdout, so `V=$(reticle version)` works:
20
20
 
21
- ```json
22
- { "t": "2026-08-15T18:42:26.275Z", "event": "reticle_version", "version": "2.8.0" }
21
+ ```text
22
+ 3.6.0
23
23
  ```
24
24
 
25
25
  ## Exit codes
package/docs/cli.mdx CHANGED
@@ -13,13 +13,9 @@ npx @reticlehq/server <command>
13
13
  ```
14
14
 
15
15
  <Warning>
16
- **Always invoke it as `npx @reticlehq/server <command>`.** The tables below name the commands as
17
- `reticle <command>`, because `reticle` is the bin this package installs and that is what the CLI
18
- prints about itself. It is **not** a package on npm: the bare name `reticle` belongs to an
19
- unrelated author, so putting `npx` in front of it downloads and runs their code instead of ours.
20
- Once
21
- `@reticlehq/server` is a dependency of your project, `reticle <command>` works from an npm script
22
- or with the bin on your PATH.
16
+ If you used the installer, run `reticle <command>`: it put that bin on your PATH. Otherwise use
17
+ `npx @reticlehq/server <command>`. Do not put `npx` before the bare `reticle` bin name: the npm
18
+ package with that name belongs to an unrelated author.
23
19
  </Warning>
24
20
 
25
21
  <Note>
@@ -30,9 +26,19 @@ npx @reticlehq/server <command>
30
26
 
31
27
  ## Setup and diagnosis
32
28
 
29
+ ### `reticle connect`
30
+
31
+ From an app directory, one command wires an unwired app, proves a browser session, signs in through browser approval if needed, links the chosen cloud project, and syncs existing local history. A saved sign-in is reused only after the intended cloud host accepts it. Run `reticle init` instead for local-only verification.
32
+
33
+ ```bash
34
+ npx @reticlehq/server connect --project "My App"
35
+ ```
36
+
37
+ For a self-hosted cloud, add `--url <cloud origin>`. If the app was already wired, `connect` keeps its local setup and refreshes the cloud link. A fresh app has no run to send until you verify a flow with your agent.
38
+
33
39
  ### `reticle init`
34
40
 
35
- Wires Reticle into the project in the current directory, then boots the app and proves a session connected. It exits non-zero if nothing connected, and prints what is left to do. It does not drive: proving a flow is the FIRST RUN, `reticle verify <url> --explore --persona "<who does what>"`.
41
+ Wires Reticle into the project in the current directory, then boots the app and proves a session connected. It exits non-zero if nothing connected, and prints what is left to do. It does not drive: proving a flow is the FIRST RUN, which your agent drives with `reticle_act_and_wait` and an `until` on its last step, or, with a linked project, `reticle verify <url> --explore --persona "<who does what>"`.
36
42
 
37
43
  ```
38
44
  reticle init [--env KEY=VALUE]... [--app <dir>] [--license <key>]
package/docs/desktop.mdx CHANGED
@@ -210,13 +210,15 @@ installReticleCapture(win);
210
210
 
211
211
  That is all: no CDP flag, no extra packages, and it works on a packaged `file://` renderer. `reticle_screenshot` and `reticle_visual_diff` then work on your app.
212
212
 
213
+ In a multi-window app, name each window: `installReticleCapture(win, { label: 'settings' })`. The session list then shows `window: "settings"`. Electron windows have no name of their own, and two of them often share a url.
214
+
213
215
  Alternatively, since an Electron renderer _is_ Chromium, `--remote-debugging-port=9222` plus `RETICLE_CDP_URL=http://127.0.0.1:9222` also works and additionally enables `fullPage` (the main-process route captures the window as composited, so it cannot scroll-stitch).
214
216
 
215
217
  **Tauri: one Rust command.** The crate is versioned independently of the npm packages:
216
218
 
217
219
  ```toml
218
220
  [dependencies]
219
- reticle-tauri = "3.4"
221
+ reticle-tauri = "3.6"
220
222
  ```
221
223
 
222
224
  ```rust
@@ -344,6 +346,8 @@ Playwright MCP is faster. It is also structurally unable to see an IPC failure,
344
346
 
345
347
  **`reticle open` says there is nothing to open.** That is correct for a desktop app. There is no URL, and there is no `reticle drive` for desktop either. Both launch a browser, which is not what you are testing.
346
348
 
349
+ **Several windows, and the wrong one gets driven.** Every window that loads the SDK is its own session, and `reticle_session { action: "list" }` names each (`window: "main"`: Tauri's label, or the `label` you gave `installReticleCapture` on Electron) with whether it is `hidden`. A page served as an error (a dev server's 404 for a static page, say) carries `documentStatus` and is never auto-selected ahead of a working one. To drive a specific window, pass its `sessionId`. To get rid of a stray hidden window for good, call `reticle_session { action: "end", sessionId, disconnect: true }`. It stays detached across its own reloads until the daemon restarts.
350
+
347
351
  **A screenshot came back partly blank.** The window is occluded or minimized, so it is only partially composited. Bring it forward and capture again.
348
352
 
349
353
  **`{ ok: false, reason: 'full-page-unsupported' }`.** You asked for `fullPage: true` on a platform that cannot render offscreen. Only Tauri on Linux (WebKitGTK) can. Capture the viewport instead, or scroll and capture in sections.
@@ -33,6 +33,7 @@ Reticle works with none of these set. Each one changes a single behaviour, and m
33
33
  | Variable | Read by | What it does |
34
34
  | --- | --- | --- |
35
35
  | `RETICLE_CDP_URL` | daemon | Attach to a browser that is already running, over CDP, instead of launching one. |
36
+ | `RETICLE_CHROMIUM_PATH` | daemon, `drive`, `verify`, `doctor` | The Chromium executable to launch, for a machine where Playwright's own build is missing and cannot be installed and there is no Chrome or Edge in the usual place. When set, nothing else is tried: no Playwright build, no Chrome or Edge, no install. A path that does not exist, is not a file, or is not executable is refused with a message naming it and saying which. `doctor` reports on this path instead of the bundled build. |
36
37
  | `RETICLE_MAX_CONTEXTS` | daemon | The most leased headless browser contexts the pool runs at once. |
37
38
  | `RETICLE_MAX_MESSAGES_PER_SECOND` | daemon | Events per second a page may send before the bridge starts sampling them. It never disconnects. Raise it for a legitimately busy app, such as a streaming dashboard. |
38
39
 
@@ -62,13 +63,8 @@ Reticle works with none of these set. Each one changes a single behaviour, and m
62
63
  | `RETICLE_CLOUD_KEY` | CLI, daemon | The key's earlier name. Still honoured. |
63
64
  | `RETICLE_CLOUD_URL`, `RETICLE_URL` | CLI, daemon | The platform host. `RETICLE_CLOUD_URL` wins when both are set. |
64
65
  | `RETICLE_SYNC_INTERVAL_MS` | `reticle sync --watch` | How often the watching sync runs. Every 60 seconds by default. |
65
- | `ANTHROPIC_API_KEY` | autonomous drive | The key the drive's model uses when no coding agent is in the loop. Without it, the drive is unavailable and everything else is unaffected. |
66
- | `RETICLE_HARNESS_MODEL` | autonomous drive | The model the drive uses. A small one by default. |
67
- | `RETICLE_HARNESS_BASE_URL` | autonomous drive | A proxy or gateway in front of the model API. |
68
- | `RETICLE_HARNESS_MAX_STEPS` | autonomous drive | A hard ceiling on model turns in one drive. It bounds cost. |
69
- | `RETICLE_HARNESS_DRIVER` | autonomous drive | Which model drives: `anthropic` (the default) or `jev`. |
70
- | `JEV_API_KEY`, `RETICLE_HARNESS_JEV_URL` | autonomous drive | A direct key and base URL for the `jev` driver. The usual path is `RETICLE_API_KEY` through the platform. |
71
- | `OPENAI_API_KEY`, `RETICLE_HARNESS_OPENAI_MODEL` | autonomous drive | A direct key and model for the OpenAI driver. |
66
+ | `RETICLE_HARNESS_MAX_STEPS` | autonomous drive | A hard ceiling on steps in one drive. It bounds cost. The drive itself runs on the platform and needs a linked project; every plan, Free included, has monthly Harness credits. |
67
+ | `RETICLE_SECRET_<FIELD>` | autonomous drive, replay | The value of a secret field, typed in on this machine. Only the field's name goes to the platform. |
72
68
 
73
69
  ## Licensing
74
70