@reticlehq/server 3.5.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 (328) 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/cli-flow-commands.js +4 -8
  10. package/dist/command/cli/cli-parse.d.ts +1 -1
  11. package/dist/command/cli/cli-parse.js +10 -10
  12. package/dist/command/cli/cli-verify.d.ts +1 -1
  13. package/dist/command/cli/cli-verify.js +35 -25
  14. package/dist/command/cli/cloud-cli.js +51 -12
  15. package/dist/command/cli/cloud-login.d.ts +8 -0
  16. package/dist/command/cli/cloud-login.js +36 -2
  17. package/dist/command/cli/demo/demo-page.js +11 -3
  18. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  19. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  20. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  21. package/dist/command/cli/drive/drive-attach.js +71 -4
  22. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  23. package/dist/command/cli/drive/drive-command.js +13 -7
  24. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  25. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  26. package/dist/command/cli/ports/port-holder.js +24 -0
  27. package/dist/command/cli/setup-mcp-cli.js +6 -4
  28. package/dist/command/cli/tutorial.d.ts +2 -2
  29. package/dist/command/cli/tutorial.js +6 -9
  30. package/dist/command/cli.d.ts +6 -0
  31. package/dist/command/cli.js +28 -22
  32. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  33. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  34. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  35. package/dist/command/daemon/daemon-resolve.js +33 -2
  36. package/dist/command/setup/agent-writer.d.ts +1 -1
  37. package/dist/command/setup/agent-writer.js +3 -2
  38. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  39. package/dist/command/setup/bringup/connect-lease.js +1 -0
  40. package/dist/command/setup/bringup/dev-server-wait.d.ts +8 -0
  41. package/dist/command/setup/bringup/dev-server-wait.js +19 -0
  42. package/dist/command/setup/init/init-port.d.ts +4 -0
  43. package/dist/command/setup/init/init-port.js +10 -0
  44. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  45. package/dist/command/setup/init/init-runtime.js +18 -1
  46. package/dist/command/setup/node-effects.js +2 -1
  47. package/dist/command/setup/remaining-steps.js +2 -1
  48. package/dist/command/setup/run-setup.d.ts +5 -0
  49. package/dist/command/setup/run-setup.js +35 -27
  50. package/dist/command/setup/setup-command.d.ts +2 -1
  51. package/dist/command/setup/setup-command.js +2 -0
  52. package/dist/command/version/version-skew.js +11 -2
  53. package/dist/features/crawl/crawl-tools.js +4 -0
  54. package/dist/features/crawl/crawl.d.ts +6 -0
  55. package/dist/features/crawl/crawl.js +9 -1
  56. package/dist/features/exhaust/ledger.d.ts +7 -0
  57. package/dist/features/exhaust/ledger.js +35 -9
  58. package/dist/features/harness/app-findings.d.ts +2 -0
  59. package/dist/features/harness/app-findings.js +52 -0
  60. package/dist/features/harness/drive-report.d.ts +27 -1
  61. package/dist/features/harness/drive-report.js +162 -11
  62. package/dist/features/harness/drivers.d.ts +10 -14
  63. package/dist/features/harness/drivers.js +10 -14
  64. package/dist/features/harness/fill-values.d.ts +3 -73
  65. package/dist/features/harness/fill-values.js +3 -169
  66. package/dist/features/harness/goals.d.ts +21 -0
  67. package/dist/features/harness/goals.js +77 -0
  68. package/dist/features/harness/harness.d.ts +21 -0
  69. package/dist/features/harness/harness.js +69 -2
  70. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  71. package/dist/features/harness/platform/drive-target.js +81 -0
  72. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  73. package/dist/features/harness/platform/local-apps.js +204 -0
  74. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  75. package/dist/features/harness/platform/remote-drive.js +365 -0
  76. package/dist/features/harness/platform/script.d.ts +48 -0
  77. package/dist/features/harness/platform/script.js +128 -0
  78. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  79. package/dist/features/harness/platform/server-driver.js +179 -0
  80. package/dist/features/harness/platform-config.d.ts +7 -0
  81. package/dist/features/harness/platform-config.js +14 -2
  82. package/dist/features/harness/script/app-controls.d.ts +5 -0
  83. package/dist/features/harness/script/app-controls.js +40 -0
  84. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  85. package/dist/features/harness/script/lane-ids.js +16 -0
  86. package/dist/features/harness/script/script-run.d.ts +41 -0
  87. package/dist/features/harness/script/script-run.js +299 -0
  88. package/dist/features/license/license.d.ts +2 -2
  89. package/dist/features/visual/visual-tools.d.ts +10 -1
  90. package/dist/features/visual/visual-tools.js +40 -3
  91. package/dist/hooks/driven-by.d.ts +4 -0
  92. package/dist/hooks/driven-by.js +16 -0
  93. package/dist/hooks/hook-commands.js +15 -4
  94. package/dist/hooks/tool-log.d.ts +9 -0
  95. package/dist/hooks/tool-log.js +29 -0
  96. package/dist/index.d.ts +1 -0
  97. package/dist/index.js +76 -82
  98. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  99. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  100. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  101. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  102. package/dist/judgement/runs/artifact/run-store.js +26 -1
  103. package/dist/judgement/runs/drive-run.d.ts +22 -0
  104. package/dist/judgement/runs/drive-run.js +99 -1
  105. package/dist/judgement/runs/repair-prompt.js +1 -1
  106. package/dist/judgement/runs/reticle-runner.js +8 -1
  107. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  108. package/dist/judgement/runs/verification-sync.js +4 -3
  109. package/dist/language/flows/change/gate.d.ts +10 -0
  110. package/dist/language/flows/change/gate.js +22 -0
  111. package/dist/language/flows/decision.js +34 -0
  112. package/dist/language/flows/flow-anchor.d.ts +54 -8
  113. package/dist/language/flows/flow-anchor.js +88 -3
  114. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  115. package/dist/language/flows/flow-cross-step.js +21 -0
  116. package/dist/language/flows/flow-learning.js +8 -11
  117. package/dist/language/flows/flow-mutate-tools.js +5 -3
  118. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  119. package/dist/language/flows/flow-replay-run.js +101 -94
  120. package/dist/language/flows/flow-replay.d.ts +23 -1
  121. package/dist/language/flows/flow-replay.js +92 -19
  122. package/dist/language/flows/flow-scope.js +5 -1
  123. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  124. package/dist/language/flows/flow-step-runners.js +236 -42
  125. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  126. package/dist/language/flows/flow-store-for-session.js +2 -2
  127. package/dist/language/flows/flow-tools.js +58 -34
  128. package/dist/language/flows/flows.js +5 -2
  129. package/dist/language/flows/heal-run.js +6 -4
  130. package/dist/language/flows/recording/flake.d.ts +2 -2
  131. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  132. package/dist/language/flows/recording/tape/recordings.js +40 -8
  133. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  134. package/dist/language/flows/replay-from-hud.js +51 -0
  135. package/dist/language/flows/replay.d.ts +9 -5
  136. package/dist/language/flows/replay.js +27 -9
  137. package/dist/language/flows/suite/suite-flakes.js +2 -0
  138. package/dist/language/flows/suite-selection.d.ts +2 -15
  139. package/dist/language/flows/suite-selection.js +4 -3
  140. package/dist/language/flows/verify-change-tools.js +8 -4
  141. package/dist/launch-chromium.d.ts +27 -5
  142. package/dist/launch-chromium.js +38 -8
  143. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  144. package/dist/memory/cloud/cloud-config.js +15 -0
  145. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  146. package/dist/memory/cloud/cloud-sync.js +12 -1
  147. package/dist/memory/cloud/harness-config.d.ts +26 -1
  148. package/dist/memory/cloud/harness-config.js +99 -4
  149. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  150. package/dist/memory/cloud/hud-notices-source.js +128 -0
  151. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  152. package/dist/memory/cloud/memory-scope.js +205 -0
  153. package/dist/memory/cloud/sync-cycle.d.ts +12 -3
  154. package/dist/memory/cloud/sync-cycle.js +16 -3
  155. package/dist/memory/cloud/sync-daemon.js +7 -5
  156. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  157. package/dist/memory/cloud/sync-disk.js +5 -8
  158. package/dist/memory/impact/account-watch.d.ts +19 -0
  159. package/dist/memory/impact/account-watch.js +34 -0
  160. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  161. package/dist/memory/impact/impact-recorder.js +12 -2
  162. package/dist/memory/impact/impact-store.d.ts +16 -0
  163. package/dist/memory/impact/impact-store.js +142 -26
  164. package/dist/memory/intent/intent-shard.d.ts +2 -2
  165. package/dist/memory/intent/intent-tools.js +80 -2
  166. package/dist/memory/journal/envelope.d.ts +8 -8
  167. package/dist/memory/journal/journal-recorder.js +7 -1
  168. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  169. package/dist/memory/journal/on-disk/retention.js +5 -0
  170. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  171. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  172. package/dist/memory/journal/session-end.js +6 -4
  173. package/dist/memory/project/artifact-root-resolver.d.ts +26 -1
  174. package/dist/memory/project/artifact-root-resolver.js +94 -13
  175. package/dist/memory/project/artifact-root.d.ts +42 -5
  176. package/dist/memory/project/artifact-root.js +87 -8
  177. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  178. package/dist/memory/project/dir/reticle-dir.js +3 -1
  179. package/dist/memory/project/project-tools.js +22 -3
  180. package/dist/memory/project/session-root.d.ts +24 -1
  181. package/dist/memory/project/session-root.js +41 -5
  182. package/dist/memory/project/sync-status.d.ts +39 -0
  183. package/dist/memory/project/sync-status.js +90 -0
  184. package/dist/memory/recall/memory-tools.js +2 -0
  185. package/dist/memory/recall/project-memory.d.ts +19 -0
  186. package/dist/memory/recall/project-memory.js +45 -6
  187. package/dist/portal/bridge/bridge.d.ts +3 -1
  188. package/dist/portal/bridge/bridge.js +19 -2
  189. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  190. package/dist/portal/bridge/pairing-token.js +17 -0
  191. package/dist/portal/input/real-input.d.ts +81 -10
  192. package/dist/portal/input/real-input.js +207 -22
  193. package/dist/portal/input/viewport-tools.js +3 -3
  194. package/dist/portal/pool/browser-pool.d.ts +22 -9
  195. package/dist/portal/pool/browser-pool.js +70 -35
  196. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  197. package/dist/portal/pool/playwright-launcher.js +17 -1
  198. package/dist/portal/pool/pool-contract.d.ts +7 -4
  199. package/dist/portal/session/command-payload.d.ts +8 -0
  200. package/dist/portal/session/command-payload.js +19 -0
  201. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  202. package/dist/portal/session/dev-server/dev-command.js +61 -12
  203. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  204. package/dist/portal/session/facts/departure-note.js +47 -0
  205. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  206. package/dist/portal/session/facts/session-replaced.js +5 -0
  207. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  208. package/dist/portal/session/first-run-wiring.js +132 -0
  209. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  210. package/dist/portal/session/human/notes-ledger.js +55 -0
  211. package/dist/portal/session/human/review-store.d.ts +12 -0
  212. package/dist/portal/session/human/review-store.js +18 -10
  213. package/dist/portal/session/live-control-tools.d.ts +0 -13
  214. package/dist/portal/session/live-control-tools.js +28 -3
  215. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  216. package/dist/portal/session/no-session-diagnosis.js +15 -0
  217. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  218. package/dist/portal/session/no-session-next-action.js +31 -5
  219. package/dist/portal/session/no-session-watch.d.ts +14 -1
  220. package/dist/portal/session/no-session-watch.js +63 -10
  221. package/dist/portal/session/presence/session-mirror.js +3 -0
  222. package/dist/portal/session/serving-directory.d.ts +27 -0
  223. package/dist/portal/session/serving-directory.js +45 -0
  224. package/dist/portal/session/session-health.d.ts +7 -0
  225. package/dist/portal/session/session-health.js +6 -0
  226. package/dist/portal/session/session-info.d.ts +8 -1
  227. package/dist/portal/session/session-info.js +6 -0
  228. package/dist/portal/session/session-manager.d.ts +21 -0
  229. package/dist/portal/session/session-manager.js +44 -8
  230. package/dist/portal/session/session-reaper.js +4 -1
  231. package/dist/portal/session/session-successor.d.ts +5 -0
  232. package/dist/portal/session/session-tools.js +26 -3
  233. package/dist/portal/session/session.d.ts +7 -2
  234. package/dist/portal/session/session.js +21 -18
  235. package/dist/start-options.d.ts +6 -0
  236. package/dist/status-payload.d.ts +10 -0
  237. package/dist/status-payload.js +23 -0
  238. package/dist/surface/http-server.js +11 -1
  239. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  240. package/dist/surface/mcp/mcp-proxy.js +36 -5
  241. package/dist/surface/mcp/mcp.d.ts +11 -6
  242. package/dist/surface/mcp/mcp.js +27 -13
  243. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  244. package/dist/surface/mcp/server-instructions.js +14 -4
  245. package/dist/surface/mcp/shared-params.js +2 -2
  246. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  247. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  248. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  249. package/dist/surface/tools/act-sequence-tool.js +2 -2
  250. package/dist/surface/tools/act-tools.js +23 -7
  251. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  252. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  253. package/dist/surface/tools/browser-tools.js +8 -1
  254. package/dist/surface/tools/chat-drives.d.ts +26 -0
  255. package/dist/surface/tools/chat-drives.js +165 -0
  256. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  257. package/dist/surface/tools/empty-session-list.js +80 -0
  258. package/dist/surface/tools/error-recovery.d.ts +6 -0
  259. package/dist/surface/tools/error-recovery.js +37 -4
  260. package/dist/surface/tools/explore-tools.d.ts +10 -1
  261. package/dist/surface/tools/explore-tools.js +82 -13
  262. package/dist/surface/tools/feedback-tools.js +14 -0
  263. package/dist/surface/tools/harness-explore.d.ts +96 -14
  264. package/dist/surface/tools/harness-explore.js +227 -206
  265. package/dist/surface/tools/harness-plan.d.ts +12 -0
  266. package/dist/surface/tools/harness-plan.js +31 -0
  267. package/dist/surface/tools/harness-script.d.ts +8 -0
  268. package/dist/surface/tools/harness-script.js +342 -0
  269. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  270. package/dist/surface/tools/harness-toolset.js +77 -6
  271. package/dist/surface/tools/invoke-tool.js +52 -1
  272. package/dist/surface/tools/lease-seed.d.ts +29 -0
  273. package/dist/surface/tools/lease-seed.js +123 -0
  274. package/dist/surface/tools/lease-tools.d.ts +3 -23
  275. package/dist/surface/tools/lease-tools.js +30 -131
  276. package/dist/surface/tools/live-call-text.js +2 -2
  277. package/dist/surface/tools/next-step.d.ts +19 -0
  278. package/dist/surface/tools/next-step.js +159 -0
  279. package/dist/surface/tools/observe-tools.js +2 -4
  280. package/dist/surface/tools/platform-moment.d.ts +12 -0
  281. package/dist/surface/tools/platform-moment.js +168 -0
  282. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  283. package/dist/surface/tools/query-hint-schema.js +10 -0
  284. package/dist/surface/tools/read-tools.js +8 -2
  285. package/dist/surface/tools/real-input-attempt.js +96 -13
  286. package/dist/surface/tools/tool-kit.d.ts +15 -1
  287. package/dist/surface/tools/tool-kit.js +4 -6
  288. package/dist/surface/tools/tools.js +20 -48
  289. package/dist/telemetry/hud-metrics.d.ts +2 -0
  290. package/dist/telemetry/hud-metrics.js +17 -0
  291. package/dist/telemetry/telemetry.d.ts +12 -0
  292. package/dist/telemetry/telemetry.js +43 -14
  293. package/dist/telemetry/verification-of.js +1 -0
  294. package/dist/wire-journal.js +4 -1
  295. package/docs/README.md +1 -1
  296. package/docs/agent-cheatsheet.md +1 -1
  297. package/docs/autodrive.md +29 -25
  298. package/docs/cli/doctor.mdx +6 -0
  299. package/docs/cli/drive.mdx +8 -2
  300. package/docs/cli/init.mdx +11 -13
  301. package/docs/cli/status.mdx +5 -1
  302. package/docs/cli/version.mdx +3 -3
  303. package/docs/cli.mdx +1 -1
  304. package/docs/desktop.mdx +5 -1
  305. package/docs/environment.mdx +3 -7
  306. package/docs/getting-started.md +13 -11
  307. package/docs/human-control.md +1 -2
  308. package/docs/index.mdx +4 -3
  309. package/docs/install-manual.mdx +2 -2
  310. package/docs/packages/tauri.mdx +2 -2
  311. package/docs/packages/vite-plugin.mdx +22 -1
  312. package/docs/packages.mdx +1 -1
  313. package/docs/quickstart.mdx +17 -7
  314. package/docs/telemetry.md +1 -1
  315. package/docs/tools/session-and-feedback.mdx +9 -9
  316. package/docs/usage.md +5 -2
  317. package/docs/what-is-recorded.md +13 -3
  318. package/docs/what-is-reticle.mdx +3 -1
  319. package/package.json +8 -12
  320. package/dist/features/harness/driver.d.ts +0 -50
  321. package/dist/features/harness/driver.js +0 -236
  322. package/dist/features/harness/jev-driver.d.ts +0 -188
  323. package/dist/features/harness/jev-driver.js +0 -678
  324. package/dist/features/harness/openai-driver.d.ts +0 -58
  325. package/dist/features/harness/openai-driver.js +0 -206
  326. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  327. package/dist/memory/project/dir/fill-value-store.js +0 -88
  328. package/docs/local-registry.md +0 -143
@@ -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
 
@@ -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
 
@@ -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
@@ -38,7 +38,7 @@ For a self-hosted cloud, add `--url <cloud origin>`. If the app was already wire
38
38
 
39
39
  ### `reticle init`
40
40
 
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, `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>"`.
42
42
 
43
43
  ```
44
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.5"
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
 
@@ -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 Cloud: run the installer, then `reticle connect --project "My App"` in your app directory before opening your coding agent.** The installer puts the CLI on the machine and registers its MCP tools with supported agents. `connect` wires your app, checks that a browser session connected, opens sign-in approval if needed, links the named cloud project, and syncs existing history. The first run is what proves anything: ask your agent to verify one journey in your app, or use `reticle_verify { action: "explore", persona: "<the journey worth proving>" }`. A connected app is not a verified one. For local-only verification without an account, use `reticle init` instead. You need Node 20.11 or newer, an app you run locally, and an agent that speaks MCP.
7
+ **To get started: run the installer, then `reticle init` in your app directory before opening your coding agent.** The installer puts the CLI on the machine and registers its MCP tools with supported agents. `init` wires your app and checks that a browser session connected; without it the agent has the tools and nothing to point them at. For a cloud dashboard, use `reticle connect --project "My App"`, which does the same and also links a cloud project and syncs history. The first run is what proves anything: ask your agent to verify one journey in your app, or use `reticle_verify { action: "explore", persona: "<the journey worth proving>" }`. A connected app is not a verified one. You need Node 20.11 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
  >
@@ -58,17 +58,9 @@ Everything is **dev-only** and **localhost-only**. It's tree-shaken out of produ
58
58
 
59
59
  ---
60
60
 
61
- ## Cloud setup: `reticle connect`
61
+ ## Wire your app: on the first run, or `reticle init`
62
62
 
63
- After the one-time [installer](/quickstart), run this in your app directory:
64
-
65
- ```bash
66
- npx @reticlehq/server connect --project "My App"
67
- ```
68
-
69
- Approve the short browser code if Reticle asks you to sign in. The command then links this folder and sends any history already on the machine. Open or restart your coding agent and ask it to verify one real journey. For a new project, the dashboard has no result until that first run is recorded.
70
-
71
- ## Local-only path: `reticle init`
63
+ Installing Reticle changes nothing in your project. The first time your agent uses Reticle in a project that has never connected (its first `reticle_session { action: "list" }`), Reticle runs `init` there itself and returns the files it changed under `wired`, so you can skip straight to asking your agent to verify a flow. Run `init` yourself to wire a project ahead of time, to preview the changes, or when the agent's MCP server was started outside the project (the call then answers `run_init` with this same command).
72
64
 
73
65
  From your project root:
74
66
 
@@ -104,6 +96,16 @@ If you ran `--files-only` with a dev server already running, restart it, then sk
104
96
 
105
97
  ---
106
98
 
99
+ ## Add a cloud dashboard: `reticle connect`
100
+
101
+ Optional. Instead of, or after, `init`, run this in your app directory:
102
+
103
+ ```bash
104
+ npx @reticlehq/server connect --project "My App"
105
+ ```
106
+
107
+ Approve the short browser code if Reticle asks you to sign in. The command then links this folder and sends any history already on the machine. Open or restart your coding agent and ask it to verify one real journey. For a new project, the dashboard has no result until that first run is recorded.
108
+
107
109
  ## Step 1: Connect your coding agent (MCP), once
108
110
 
109
111
  You don't start the server manually; your agent starts it via MCP. Register Reticle **once, at the user (global) scope** so every project picks it up. There's nothing to add per project.
@@ -25,10 +25,9 @@ MCP is **pull-based**: the agent only receives data when it calls a tool. Reticl
25
25
  The floating panel (bottom-center, `present: true`) gives you:
26
26
 
27
27
  - **Pause / Resume**: one toggle. Paused turns the panel + page border **amber** with a `PAUSED` badge.
28
- - **Message box + send.** Type a correction ("check the error state first"), hit Send (or Enter). Your message shows as a `๐Ÿง‘ you:` bubble in the transcript **and** is delivered to the agent.
29
28
  - **End** ends the session: panel turns **emerald**, shows `Session ended ยท <summary>`, then fades away.
30
29
  - **Minimise (โ–พ)** collapses the panel to a bar that streams only the live line; click the bar to restore.
31
- - **Flag a bug**, the button in the corner. Toggle it on, click the element that looks wrong, type what's wrong (โŒ˜/Ctrl+Enter to send, Esc to back out). Reticle pins a numbered marker, logs your flag in the panel, and hands the agent a structured mark.
30
+ - **Flag a bug**, the button in the corner. Toggle it on, click the element that looks wrong, type what's wrong (โŒ˜/Ctrl+Enter to send, Esc to back out). Reticle pins a numbered marker, logs your flag in the panel, and hands the agent a structured mark. Notes are kept in `.reticle/notes.json` (pending and resolved), so a reload or a daemon restart does not lose them, and a linked project syncs them to the dashboard.
32
31
 
33
32
  ## Flag a bug: annotate the mistake where you see it
34
33
 
package/docs/index.mdx CHANGED
@@ -28,7 +28,7 @@ irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1
28
28
 
29
29
  </CodeGroup>
30
30
 
31
- That registers the MCP server with every coding agent it can reach, so the `reticle_*` tools are there the next time you open one. Then, in your project, `npx @reticlehq/server init` wires the app. The [quickstart](/quickstart) walks both.
31
+ That registers the MCP server with every coding agent it can reach, so the `reticle_*` tools are there the next time you open one. Then ask your agent to verify something in your app: its first Reticle call wires the app and lists the files it changed (`npx @reticlehq/server init` does the same by hand). The [quickstart](/quickstart) walks both.
32
32
 
33
33
  ### Or hand the whole thing to your agent
34
34
 
@@ -54,8 +54,9 @@ That one command wires the project, starts the dev server, opens the app and
54
54
  waits for it to connect. Do not walk it by hand: read what it prints, do anything
55
55
  it says is outstanding, and re-run it, which is safe. It exits non-zero until the
56
56
  app connects, and that connection, not a config file, is what "installed" means.
57
- Then prove a flow, which is a separate call:
58
- reticle_verify { action: "explore", persona: "<who does what>" }. Only if it could not run at all, follow
57
+ Then prove a flow, which is a separate call: drive the journey with
58
+ reticle_act_and_wait and an `until` on its last step (or, with a linked project,
59
+ reticle_verify { action: "explore", persona: "<who does what>" }). Only if it could not run at all, follow
59
60
  https://docs.reticle.sh/install-manual.md.
60
61
 
61
62
  Afterwards, use it whenever you change any user-facing behaviour.
@@ -7,7 +7,7 @@ icon: screwdriver-wrench
7
7
 
8
8
  **Installing Reticle by hand is two independent halves: register the MCP server so your agent gets the `reticle_*` tools, then add the SDK to your app so there is something for those tools to talk to.** You need both, in that order.
9
9
 
10
- Do only the first half and the tools appear but every call reports no session. Do only the second and your app connects to a daemon nobody is asking questions.
10
+ Do only the first half and the tools appear but every call reports no session. Do only the second and your app connects to a daemon nobody is asking questions. Stop after the first half and your agent's first Reticle call wires the app itself when you ask it to verify something, and offers to otherwise.
11
11
 
12
12
  <Warning>
13
13
  **You probably do not need this page.** Two commands do all of it:
@@ -175,7 +175,7 @@ Screenshots and headless mode need the Rust crate, which is versioned independen
175
175
 
176
176
  ```toml
177
177
  [dependencies]
178
- reticle-tauri = "3.5"
178
+ reticle-tauri = "3.6"
179
179
  ```
180
180
 
181
181
  ```rust
@@ -21,10 +21,10 @@ Tauri IPC observation needs nothing on the Rust side. An `invoke('load_todos')`
21
21
 
22
22
  ```toml
23
23
  [dependencies]
24
- reticle-tauri = "3.5"
24
+ reticle-tauri = "3.6"
25
25
  ```
26
26
 
27
- Its own dependencies are `tauri = "2"` plus the per-platform capture backends: `objc2` and friends on macOS, `webview2-com` and `windows` on Windows, `webkit2gtk` and `cairo-rs` on Linux and the BSDs.
27
+ Its own dependencies are `tauri = "2.12"` (the Windows backend must use the same `webview2-com` and `windows` versions Tauri does, so it follows Tauri's minor version) plus the per-platform capture backends: `objc2` and friends on macOS, `webview2-com` and `windows` on Windows, `webkit2gtk` and `cairo-rs` on Linux and the BSDs.
28
28
 
29
29
  ## Wiring
30
30
 
@@ -61,18 +61,39 @@ Plus a dynamic import of your dev module if one exists. It looks for `src/reticl
61
61
  | `session` | `string` | Omitted, so the SDK generates one |
62
62
  | `projectId` | `string` | Derived from the package name plus the root path |
63
63
  | `token` | `string` | `readPairingToken()`, from `~/.reticle/pairing-token` |
64
+ | `present` | `boolean` | `true`. Set `false` to hide Reticle's presenter while keeping the agent connection active |
64
65
  | `sourceMapping` | `boolean` | `true`. Set `false` to skip the Babel pass |
65
66
  | `inject` | `boolean` | `true`. Set `false` to call `connect()` yourself |
66
67
  | `desktop` | `boolean` | `false`. When `true` the connect passes `allowInProduction: true`, which an Electron or Tauri renderer needs, and a `vite build` in a non-production mode (`--mode development`) is instrumented too. A production-mode build is stubbed like a web build |
67
68
  | `captureNetworkBodies` | `boolean` | `false`. Also enabled by `VITE_RETICLE_CAPTURE_BODIES=1` |
68
69
  | `onWarn` | `(message: string) => void` | `console.warn` |
69
70
 
71
+ `reticle({ present: false })` still lets the plugin create and configure the connection; it only hides Reticle's visible presenter. `reticle({ inject: false })` disables that generated connection, so your application must call `connect()` itself. In that manual call, pass the pairing token explicitly:
72
+
73
+ ```ts
74
+ reticle.connect({
75
+ token: __RETICLE_TOKEN__,
76
+ root: __RETICLE_ROOT__,
77
+ sdkVersion: __RETICLE_SDK_VERSION__,
78
+ });
79
+ ```
80
+
81
+ Add ambient declarations for these identifiers to `src/vite-env.d.ts` (or another included `.d.ts`) in a TypeScript app:
82
+
83
+ ```ts
84
+ declare const __RETICLE_TOKEN__: string;
85
+ declare const __RETICLE_ROOT__: string;
86
+ declare const __RETICLE_SDK_VERSION__: string;
87
+ ```
88
+
89
+ Vite replaces these `define` identifiers in application source at build time; the declarations let TypeScript typecheck them and do not create runtime globals. The SDK does not discover the pairing token, so pass it explicitly in a hand-written connection:
90
+
70
91
  ## Other exports
71
92
 
72
93
  | Export | Type | What it is |
73
94
  | --- | --- | --- |
74
95
  | `RETICLE_VITE_PLUGIN_NAME` | `'reticle'` | The plugin name |
75
- | `RETICLE_TOKEN_GLOBAL` | `'__RETICLE_TOKEN__'` | The global the token is stamped onto |
96
+ | `RETICLE_TOKEN_GLOBAL` | `'__RETICLE_TOKEN__'` | The Vite `define` identifier replaced with the pairing token |
76
97
  | `RETICLE_CONNECT_MODULE` | `'/@reticle-connect'` | The virtual module id |
77
98
  | `RENDER_PREHOOK_SOURCE` | `string` | The inline render prehook snippet |
78
99
  | `RETICLE_DEV_MODULE_CANDIDATES` | readonly tuple | The four dev-module paths it looks for |
package/docs/packages.mdx CHANGED
@@ -239,7 +239,7 @@ A lint rule makes the signal layer self-enforcing, which is the only way a conve
239
239
 
240
240
  ```toml
241
241
  [dependencies]
242
- reticle-tauri = "3.5"
242
+ reticle-tauri = "3.6"
243
243
  ```
244
244
 
245
245
  <Warning>