@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
@@ -5,7 +5,7 @@ description: Install Reticle, connect your coding agent, and get a real verdict
5
5
  icon: rocket
6
6
  ---
7
7
 
8
- **Run the installer once, then run `reticle connect --project "My App"` in your project.** Reticle embeds a dev-only SDK in your running app, signs you in through browser approval when needed, and syncs your checks to the project you name.
8
+ **Run the installer once, then ask your agent to verify something in your app; its first Reticle call wires the app (the same thing `reticle init` does, which you can also run yourself).** Reticle embeds a dev-only SDK in your running app and proves a browser session connected. No account needed. Add `reticle connect` later if you want a cloud dashboard.
9
9
 
10
10
  By the end of this page your agent will click a button in your app and prove what happened: the request that fired, the state that changed, and the file to open if it didn't.
11
11
 
@@ -53,19 +53,26 @@ npm install -g @reticlehq/server
53
53
  npx @reticlehq/server setup mcp
54
54
  ```
55
55
 
56
- ## Step 2. Connect this project
56
+ ## Step 2. Wire this project
57
57
 
58
58
  In your project directory, before opening your coding agent:
59
59
 
60
60
  ```bash
61
- npx @reticlehq/server connect --project "My App"
61
+ RETICLE_INSTALL_SOURCE=docs_site npx @reticlehq/server init
62
62
  ```
63
63
 
64
- This installs the SDK, wires your app, starts the dev server, and checks that a browser session connected. It then opens browser sign-in approval if needed, links this folder to the named cloud project, and sends existing local history. Approve the short code shown in the browser and terminal; the command resumes by itself. A saved sign-in is reused after the cloud accepts it. Open your coding agent once this command finishes; if it was already open during installation, restart it to load the new tools.
64
+ This installs the dev-only SDK, wires your build config, starts the dev server, and checks that a browser session connected. Nothing from your project leaves your machine. Open your coding agent once this command finishes; if it was already open during installation, restart it to load the new tools. The `RETICLE_INSTALL_SOURCE` prefix is optional and only tells us which page you installed from. It is idempotent: run it twice and the second run reports what was already done rather than doing it again.
65
65
 
66
- Step 1 is once per machine. This step is once per project.
66
+ Step 1 is once per machine. This step is once per project. **Skipping it is the most common reason Reticle "does nothing":** the agent has the tools, but there is no app on the other end for them to see.
67
67
 
68
- Want local-only verification without an account? Run `reticle init` in this step instead. You can run `reticle connect` later to sync your history.
68
+ Want a cloud dashboard too? Run `npx @reticlehq/server connect --project "My App"` instead of, or after, `init`. It does everything `init` does, then opens browser sign-in approval, links this folder to the named cloud project, and sends existing local history. Approve the short code shown in the browser and terminal; the command resumes by itself.
69
+
70
+ <Note>
71
+ **Skipped this step?** Ask your agent to verify something in the app. Its first `reticle_session`
72
+ call finds a project that has never connected, runs `init` there itself, and lists every file it
73
+ changed under `wired`. Asked about something unrelated, it offers to wire the app instead of doing
74
+ it. This works in Claude Code, Codex, Cursor and other MCP agents.
75
+ </Note>
69
76
 
70
77
  Prefer to install the skill and let the agent run setup itself? On Claude Code the plugin registers the MCP server and the skill in one step:
71
78
 
@@ -218,7 +225,7 @@ Read what that actually proves:
218
225
 
219
226
  ## What "verified" can say
220
227
 
221
- <CardGroup cols={3}>
228
+ <CardGroup cols={2}>
222
229
  <Card title="yes" icon="circle-check">
223
230
  The consequence you named actually happened, and the evidence is attached.
224
231
  </Card>
@@ -228,6 +235,9 @@ Read what that actually proves:
228
235
  <Card title="unknown" icon="circle-question">
229
236
  Reticle drove the app and could not tell. **This is not a pass.** Report it as unknown.
230
237
  </Card>
238
+ <Card title="no-fault" icon="circle-minus">
239
+ Nothing was declared to prove, so nothing was checked. **Not a pass either:** add an `until`.
240
+ </Card>
231
241
  </CardGroup>
232
242
 
233
243
  That third state is the one that matters. Most tools have two outcomes and quietly file "I couldn't tell" under "fine". A verification layer that cannot admit uncertainty is just a very confident random number generator.
package/docs/telemetry.md CHANGED
@@ -48,7 +48,7 @@ Eighteen kinds of events, each a single small JSON object. This is the whole lis
48
48
 
49
49
  **Parameter and flag NAMES are collected; their VALUES are not.** We record that `reticle_act` was called with `ref` and `action`, and that you ran `reticle serve --headed --port`. We do not record what you set them to. This distinction is the whole safety property, and it is absolute for CLI flags: `--http-token` holds a secret, `--drive` holds a URL, `--storage-state` holds a file path, so no flag value is ever sent. For tool parameters there is one narrow exception: a short, explicit allowlist of parameters whose values are enums _we_ defined (`action: "click"`), listed in [`argument-shape.ts`](https://github.com/reticlehq/reticle/blob/main/server/src/telemetry/argument-shape.ts). A value outside that allowlist reports as `other`, so a future schema change cannot quietly start forwarding free text. `reticle_act`'s `args` (the text being typed into your app, which on a login form is a password) is **never** in that allowlist.
50
50
 
51
- `daemon_stopped` carries: how long the session ran, how many tool calls and of which tools, how long each tool took (total and worst case), how many failed, how many verifications ran, browser/lease connection attempts with their failure causes, and which MCP clients connected (`claude-code`, `cursor`).
51
+ `daemon_stopped` carries: how long the session ran, how many tool calls and of which tools, how long each tool took (total and worst case), how many failed, how many verifications ran, browser/lease connection attempts with their failure causes, and which MCP clients connected (`claude-code`, `cursor`). It also carries how the in-page HUD was used: presses per control (`hudControls`, by the control's name from a closed list Reticle defines, a toggle as `name:on` / `name:off`), how long the HUD sat as a bubble, collapsed or open and on which page (`hudViewMs`, `hudPanelMs`), which rail slides were shown (`hudSlides`), and the order of those presses and views, cut at 60 (`hudJourney`). Control names, never anything typed or shown on the page.
52
52
 
53
53
  It also carries a snapshot of the **machine's** state (our own process's memory, free and total system RAM, load average and CPU count), taken at shutdown and again on any crash. This is what separates "your machine ran out of memory" from "Reticle has a bug", which otherwise produce identical-looking failures. No hostname, no username, no paths, no process list.
54
54
 
@@ -10,15 +10,15 @@ icon: comments
10
10
 
11
11
  The presenter panel in your app shows a live transcript of what the agent is doing. It reads "live" until the agent says otherwise. So an agent that wanders off without a word leaves a panel claiming work is in progress forever.
12
12
 
13
- | Action | What it does |
14
- | ---------- | ------------------------------------------------------------------------ |
15
- | `yield` | Hand control back between turns. `mode: "waiting"` or `"ask"`. Revivable |
16
- | `end` | The task is done. Frees the session; revives on the next action |
17
- | `tune` | Adjust session behaviour, such as `idleEndMs`, the idle-end window |
18
- | `resume` | Clear a pause the human applied |
19
- | `messages` | Drain the human-to-agent inbox |
20
- | `review` | List, and with `resolve`, retire the mistakes a human pinned to elements |
21
- | `narrate` | State your intent on the presenter panel, with an optional `level` |
13
+ | Action | What it does |
14
+ | --- | --- |
15
+ | `yield` | Hand control back between turns. `mode: "waiting"` or `"ask"`. Revivable |
16
+ | `end` | The task is done. Frees the session; revives on the next action |
17
+ | `tune` | Adjust session behaviour: `idleEndMs`, the idle-end window, or `hud`: `hidden`, `shown`, `removed` (until reload), or a corner (`top-left`, `top-right`, `bottom-left`, `bottom-right`) when the HUD covers a control you must test |
18
+ | `resume` | Clear a pause the human applied |
19
+ | `messages` | Drain the human-to-agent inbox |
20
+ | `review` | List, and with `resolve`, retire the mistakes a human pinned to elements |
21
+ | `narrate` | State your intent on the presenter panel, with an optional `level` |
22
22
 
23
23
  ```json
24
24
  { "action": "yield", "mode": "waiting" }
package/docs/usage.md CHANGED
@@ -415,7 +415,7 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
415
415
  | `select` | `{ value }` | `<select>` option |
416
416
  | `check` / `uncheck` | n/a | checkbox/radio |
417
417
  | `submit` | n/a | submits the element's `<form>` |
418
- | `press` | `{ key, modifiers? }` | keydown/up (default `Enter`); `modifiers`: an array of `Meta` / `Control` / `Shift` / `Alt` for Cmd+K-style shortcuts |
418
+ | `press` | `{ key, modifiers?, holdMs?, keys?, code? }` | keydown/up (default `Enter`); `modifiers`: an array of `Meta` / `Control` / `Shift` / `Alt` for Cmd+K-style shortcuts. With a `ref` the key is dispatched AT that element; with NO ref it is a document key (Escape, Tab, a shortcut) and goes through the real keyboard, so it can actually move focus. `holdMs` keeps the key DOWN for that long (hold-to-confirm, hold-to-reveal). `keys` holds several keys together, pressed in order and released in reverse, and `code` names the physical key: both stay on the in-page dispatcher, which is the only path that can express them |
419
419
  | `scrollIntoView` | n/a | |
420
420
  | `upload` | `{ name, content?, type? }` | sets a file on `<input type=file>` |
421
421
  | `drag` | `{ toRef }` | pointer-based drag (dnd-kit / rbd) + HTML5 DnD |
@@ -816,7 +816,7 @@ A session starts on the agent's first activity and must reliably end even when t
816
816
  - **Agent kills the Reticle server process** (so no push can arrive) → the SDK self-ends the session after it can't reach the bridge for `BRIDGE_LOST_MS` (~15s), showing "lost connection to Reticle."
817
817
  - **Slow-but-alive agent** → if it goes quiet long enough to auto-end and then acts again, the session **revives** automatically (an explicit `reticle_session {action:"end"}` stays terminal).
818
818
 
819
- Tune the idle window with `reticle_session({ action: "tune", idleEndMs })`; it updates both the browser timer and the server reaper. The human keeps the panel (with Copy/Export of the run) after any end.
819
+ Tune the idle window with `reticle_session({ action: "tune", idleEndMs })`; it updates both the browser timer and the server reaper. When the HUD covers a control you have to test, `reticle_session({ action: "tune", hud: "top-left" })` moves it (any corner), and `hud: "hidden"`, `"shown"` or `"removed"` (until the page reloads) take it out of the way; the call fails rather than pretending if the page could not apply it. The human keeps the panel (with Copy/Export of the run) after any end.
820
820
 
821
821
  ### `reticle_session {action:"narrate"}`: show the agent's intent
822
822
 
@@ -1053,6 +1053,9 @@ When `inputMode` is `"synthetic"` and the target has hover/enter handlers, the r
1053
1053
  | `provider-declined` / `provider-error` | the CDP provider declined or threw (the latter also sets `warning`) |
1054
1054
  | `not-a-pointer-action` | `fill`/`type`/etc. are always synthetic by design |
1055
1055
  | `synthetic-click-preferred` | a `click`/`dblclick` ran the occlusion-honest synthetic path by default; pass `args:{ native:true }` to force a trusted native click |
1056
+ | `synthetic-element-press-preferred` | the `press` named a `ref`/`target`, so it stayed synthetic on purpose: dispatching AT that element reaches it exactly, while a real keyboard sends the key to whatever holds focus. A `press` with NO ref is a document key (Escape, Tab, a modifier shortcut) and does run through the real keyboard when one is configured |
1057
+ | `synthetic-multi-key-press-preferred` | the `press` named `keys`: several keys held down **together**, pressed in order and released in reverse. Only the in-page dispatcher implements that sequence; a real keyboard chord cannot express it |
1058
+ | `synthetic-key-code-press-preferred` | the `press` named an explicit `code`, which the real driver cannot send: it presses by KEY name, so `{ text: "z", code: "KeyY" }` would strike a different key than the one asked for |
1056
1059
 
1057
1060
  (No `inputModeReason` is set when real input simply isn't configured; synthetic is the expected default there.)
1058
1061
 
@@ -15,7 +15,7 @@ Everything lands in `.reticle/` in your project, and it is yours. Three kinds of
15
15
  | --- | --- | --- |
16
16
  | **runs** | a verdict and its evidence | what was claimed, whether it held, the request or state change that decided it, and the `file:line` of the element driven |
17
17
  | **flows** | a journey you drove, saved so it can be replayed | the steps, the anchors they resolve by, and the values that were typed |
18
- | **memory** | what this project has learned across sessions | intents you declared, envelopes of normal behaviour, notes about anchors that drift |
18
+ | **memory** | what this project has learned across sessions | intents you declared, envelopes of normal behaviour, notes about anchors that drift, the coverage ledger (`coverage.json`), the notes people pinned in the HUD (`notes.json`), and the user's latest request as the agent relayed it (`request.json`, local only) |
19
19
 
20
20
  `.reticle/` splits into a part meant for git and a part meant to stay local. Flows are the part worth committing: a saved flow is a regression test. Evidence is the part that is not.
21
21
 
@@ -27,7 +27,7 @@ Everything lands in `.reticle/` in your project, and it is yours. Three kinds of
27
27
 
28
28
  So: **drive staging.** Not because the risk is large, but because it is real and avoiding it is free.
29
29
 
30
- Network bodies are **not** captured by default. Page content that reaches a verdict is the element text and attributes the assertion needed, not a copy of the DOM.
30
+ Network bodies are **not** captured by default. When you turn body capture on (`captureNetworkBodies: true`, or `VITE_RETICLE_CAPTURE_BODIES=1`), redaction runs **before each body is kept**. Built-in rules mask keys such as `password`, `apiKey` and `authorization`, selected token keys, and high-confidence secret shapes such as JWTs and `sk_live_…` keys. Other credential values can remain under names or in formats those rules do not recognize; add app-specific field names with `redact.keys` and use staging. Page content that reaches a verdict is the element text and attributes the assertion needed, not a copy of the DOM.
31
31
 
32
32
  ## What leaves, only if you ask
33
33
 
@@ -39,6 +39,14 @@ Once linked, you choose what syncs:
39
39
  npx @reticlehq/server config --runs on|off --flows on|off --memory on|off
40
40
  ```
41
41
 
42
+ `--memory` covers the coverage ledger and the HUD notes as well, and the notes are the words people typed. The user's request (`request.json`, the prompt the agent relayed, redacted) goes to the platform with the runs from a linked project, so the dashboard can show what each run was for; `"shareRequests": false` in `.reticle.json` keeps it on the machine. An unlinked project sends it nowhere, and it is kept out of git either way.
43
+
44
+ The Harness runs on the platform, so the platform sees what it drives: each step's result goes there so it can choose the next one. A secret field is sent by name only; its value is typed in on your machine.
45
+
46
+ **Your apps, for the platform's chat.** While a project is linked, the daemon tells the platform every ten seconds, with or without an app open, which apps on this machine have Reticle connected, so the chat can list them and you can pick one instead of typing an address. For each app it sends: a key made from a hash of this machine and the project's folder (the folder path itself never leaves), the project's name (its package name, else its folder's name), the address the app is open at with Reticle's own query parameters removed, the page title, what it is built with (`react`, `vite`, `electron`, …), this machine's name, the Reticle version, and what this daemon can do for the platform. The platform keeps the last report for each app until the project is deleted. Its answer can ask this machine to open one of those apps again, or show you a short notice (for example, to update Reticle), which appears in the daemon's log and on the HUD.
47
+
48
+ **Drives asked for from the platform's chat.** When someone in your workspace asks the chat to drive one of your apps, the daemon drives it here, on your machine: in the tab you have open, or in a browser of its own, headless or in a window, as the chat asked. It only opens addresses on this machine or apps already open here. On a recent platform, the platform decides each step: it sends Reticle tool calls from a fixed list (reading the page, acting on it, asserting, recording and replaying flows), the daemon runs them in that one tab, and sends back what each returned, which includes what the page showed. On an older platform, the daemon drives and sends back each step's result and how the drive ended. Either way the platform keeps the drive as a check you can open in Runs. Unless the request said not to record, the daemon also sends a picture of the tab it is driving, a JPEG about once a second, so the chat can show the drive live and replay it afterwards. Pictures are kept with that check (at most a few minutes' worth per drive) and deleted with the project. A tab the daemon opened, headless or in a window, can be pictured, with Reticle's own panel left out; so can the window `reticle drive` opened, which is pictured as you see it, panel included. A tab in your own browser sends steps only. Unlinking the project stops all of it.
49
+
42
50
  ## Telemetry, which is separate from all of the above
43
51
 
44
52
  The CLI sends anonymous usage events: event names and a random installation id. No code, no page content, no URLs from your app. It is how we know which parts of the product are reached at all.
@@ -49,7 +57,9 @@ DO_NOT_TRACK=1 # the convention, honoured
49
57
  npx @reticlehq/server telemetry disable # this machine, permanently
50
58
  ```
51
59
 
52
- Feedback is the only free text that ever leaves, it is never collected passively, and `RETICLE_FEEDBACK=0` disables it.
60
+ Telemetry carries no free text. Free text leaves in three ways, each one something you chose: a feedback report (never collected passively; `RETICLE_FEEDBACK=0` disables it), HUD notes on a linked project with `--memory on`, and the user's request from a linked project unless `"shareRequests": false`.
61
+
62
+ The daemon also fetches a small public file, `https://reticle.sh/hud/notices.v1.json`, for the notices in the HUD's rail. The request carries nothing about you or your project. `RETICLE_TELEMETRY=0` or `DO_NOT_TRACK=1` stops it, and `RETICLE_HUD_NOTICES_URL` points it elsewhere.
53
63
 
54
64
  ## What `init` writes to your project
55
65
 
@@ -31,7 +31,9 @@ Reticle sits next to several familiar categories, and it works alongside each of
31
31
 
32
32
  **Monitoring.** Reticle runs against environments you control (a dev server, a staging or preview deployment) and replays saved flows in CI. The SDK refuses to connect in a production build, so it complements an APM or error tracker rather than watching live traffic.
33
33
 
34
- **Your agent.** Reticle has no model of its own for the verify loop. It gives the agent you already use a way to see the running app. Your agent, your model, your keys. (`explore` can drive a journey for you with a model inside the daemon; see [Autodrive](/autodrive).)
34
+ **Not an accessibility auditor.** `reticle_look { action: "page" }` addresses elements by role and name, which is not the same as auditing contrast, focus order, or screen reader behaviour. Use a dedicated accessibility tool for that.
35
+
36
+ **Your agent.** Reticle has no model of its own for the verify loop. It gives the agent you already use a way to see the running app. Your agent, your model, your keys. (On a linked project, `explore` has the Reticle Harness drive a journey for you on the Reticle platform; see [Autodrive](/autodrive).)
35
37
 
36
38
  **The verdict.** A verdict of `unknown` means Reticle drove the app and could not tell. It is deliberately not rounded up to a pass.
37
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/server",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "Reticle CLI, bridge and MCP server: a coding agent drives your running web app from the inside and gets a verdict with evidence, for change verification, access control, accessibility, performance and SEO checks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,15 +19,15 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@modelcontextprotocol/sdk": "^1.30.1",
22
+ "@modelcontextprotocol/sdk": "^1.31.0",
23
23
  "ws": "^8.21.3",
24
24
  "zod": "^3.24.1",
25
25
  "zod-to-json-schema": "^3.24.0",
26
- "@reticlehq/browser": "3.5.0",
27
- "@reticlehq/engine": "3.5.0",
28
- "@reticlehq/core": "3.5.0",
26
+ "@reticlehq/browser": "3.6.0",
27
+ "@reticlehq/core": "3.6.0",
28
+ "@reticlehq/engine": "3.6.0",
29
29
  "open-verification": "1.0.0",
30
- "@reticlehq/init": "3.5.0"
30
+ "@reticlehq/init": "3.6.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/pngjs": "^6.0.5",
@@ -64,13 +64,9 @@
64
64
  "cli"
65
65
  ],
66
66
  "reticle": {
67
- "changelog": "Reticle writes and tightens your regression flows for you: every drive is recorded automatically and replays with no model in the loop. The agent now sees nine tools instead of seventeen, `init` stops at a connected app and proving a flow is a separate first run, and Windows is supported. Upgrading from 2.x: see MIGRATION.md.",
67
+ "changelog": "Drive your localhost app from the Reticle platform's chat: the daemon of a linked project drives it on your machine, the chat shows it live and keeps it as a check with a replay. The Harness now runs only on the platform, on every plan, and plans, replays and learns each app. A page on SDK 3.1-3.4 is no longer version-skewed, a pressed key sends its keyCode, and a screenshot never carries another tab's pixels. Upgrading from 3.5: see MIGRATION.md.",
68
68
  "breakingChanges": [
69
- "The default MCP surface is the merged nine. reticle_snapshot/query/inspect/state -> reticle_look { action }, reticle_network/console -> reticle_observe { action }, reticle_sessions/feedback -> reticle_session { action }, reticle_wait_for -> reticle_assert { action: \"wait\" }, reticle_act_sequence -> reticle_act { steps }. Old names redirect when CALLED, but an allowedTools allowlist or an MCP permission rule naming one refuses first -- update those by hand.",
70
- "Twelve tools are unreachable on the default surface and have NO redirect: reticle_capabilities, reticle_clock, reticle_context, reticle_flow_replay, reticle_flow_save, reticle_intent, reticle_network_mock, reticle_record, reticle_run, reticle_screenshot, reticle_storage, reticle_visual_diff. The default surface ships no reticle_run hatch. Start the daemon with RETICLE_ADVERTISE_ALL_TOOLS=1 (read once at startup) and call them by name. Saved flows still replay via reticle_verify { action: \"flows\" }.",
71
- "`reticle init` no longer drives: it ends at a connected session. --flow, --no-drive and --drive-model now exit non-zero, each naming its replacement. Prove a flow with reticle_verify { action: \"explore\", persona: \"<who does what>\" }, or npx @reticlehq/server verify <url> --explore --persona \"...\". init's exit code now means \"nothing connected\" rather than \"no verdict\". --files-only is unchanged.",
72
- "@reticlehq/server now requires Node >=20.11 (was >=20.0.0).",
73
- "@reticlehq/server, @reticlehq/init and @reticlehq/test declare license FSL-1.1-ALv2 instead of \"SEE LICENSE IN LICENSE\". The terms are identical; add the SPDX id to a licence allowlist."
69
+ "reticle_verify { action: \"explore\" } and reticle verify --explore no longer drive on a model key of your own. The Harness decides on the Reticle platform and needs a linked project (npx @reticlehq/server connect); every plan includes Harness credits. ANTHROPIC_API_KEY, OPENAI_API_KEY and JEV_API_KEY are no longer read for driving, RETICLE_HARNESS_DRIVER and RETICLE_HARNESS_MODEL are ignored, and the explore tool's `driver` argument is removed. A secret field's value comes from RETICLE_SECRET_<FIELD> on your machine."
74
70
  ]
75
71
  },
76
72
  "publishConfig": {
@@ -1,50 +0,0 @@
1
- /**
2
- * The model binding: the one file in the harness that knows there is a network.
3
- *
4
- * Everything about the loop lives in harness.ts against the `ModelDriver` interface, so this file is
5
- * a translation layer and nothing else. That split is not tidiness — it is what lets the loop be
6
- * tested against a scripted model with no API key, on every run of the unit gate.
7
- *
8
- * It speaks the Messages API over `fetch` rather than through a vendor SDK. The loop is ours (the
9
- * harness owns the step budget and has to record every call even when a run is cut short), so an SDK
10
- * would contribute one HTTP POST and a dependency — and this package's dependency list is short on
11
- * purpose.
12
- */
13
- import type { ModelDriver } from './harness.js';
14
- /**
15
- * The default driver model.
16
- *
17
- * Deliberately not the largest model available. The harness is not reasoning about business logic; it
18
- * is exploring an interface and stating consequences, and the verdict is produced by the engine
19
- * reading what it drove. If finding defects required a frontier model HERE, the engine would not be
20
- * doing its job — so the model tier is also a standing test of the design.
21
- */
22
- export declare const DEFAULT_HARNESS_MODEL = "claude-sonnet-5";
23
- export declare const DEFAULT_HARNESS_BASE_URL = "https://api.anthropic.com";
24
- /** The one call this file makes. Injected so a test can drive the translation with no network. */
25
- export type HarnessFetch = (url: string, init: {
26
- method: string;
27
- headers: Record<string, string>;
28
- body: string;
29
- }) => Promise<{
30
- ok: boolean;
31
- status: number;
32
- text(): Promise<string>;
33
- }>;
34
- export interface HarnessDriverOptions {
35
- apiKey: string;
36
- model?: string;
37
- baseUrl?: string;
38
- /** Injected for tests. Defaults to the platform `fetch`. */
39
- fetch?: HarnessFetch;
40
- }
41
- /**
42
- * Read the harness's configuration out of the environment.
43
- *
44
- * Returns `undefined` when there is no key, which is the ONLY thing that decides whether the harness
45
- * is available. Stated as a read rather than a throw because "you have not configured this" is a
46
- * routine answer a caller offers a way out of, not a failure.
47
- */
48
- export declare function harnessOptionsFromEnv(env: Record<string, string | undefined>): HarnessDriverOptions | undefined;
49
- /** Build a driver backed by the Messages API. */
50
- export declare function harnessDriver(options: HarnessDriverOptions): ModelDriver;
@@ -1,236 +0,0 @@
1
- /**
2
- * The model binding: the one file in the harness that knows there is a network.
3
- *
4
- * Everything about the loop lives in harness.ts against the `ModelDriver` interface, so this file is
5
- * a translation layer and nothing else. That split is not tidiness — it is what lets the loop be
6
- * tested against a scripted model with no API key, on every run of the unit gate.
7
- *
8
- * It speaks the Messages API over `fetch` rather than through a vendor SDK. The loop is ours (the
9
- * harness owns the step budget and has to record every call even when a run is cut short), so an SDK
10
- * would contribute one HTTP POST and a dependency — and this package's dependency list is short on
11
- * purpose.
12
- */
13
- import { z } from 'zod';
14
- import { ReticleEnv, platformCredentialFrom } from '@reticlehq/core';
15
- /**
16
- * The default driver model.
17
- *
18
- * Deliberately not the largest model available. The harness is not reasoning about business logic; it
19
- * is exploring an interface and stating consequences, and the verdict is produced by the engine
20
- * reading what it drove. If finding defects required a frontier model HERE, the engine would not be
21
- * doing its job — so the model tier is also a standing test of the design.
22
- */
23
- export const DEFAULT_HARNESS_MODEL = 'claude-sonnet-5';
24
- export const DEFAULT_HARNESS_BASE_URL = 'https://api.anthropic.com';
25
- /** Output cap per turn. A driving turn is a sentence and a tool call, never an essay. */
26
- const MAX_TOKENS = 4096;
27
- const ANTHROPIC_VERSION = '2023-06-01';
28
- const MESSAGES_PATH = '/v1/messages';
29
- /**
30
- * How much of one tool result the model is allowed to see.
31
- *
32
- * A snapshot of a dense page or a network window on a busy app is far larger than anything the model
33
- * needs to choose its next click, and a handful of them will fill a context window that then has to
34
- * be paid for on every subsequent turn. Truncation is announced in the payload rather than silent,
35
- * because a model that cannot tell it was given a partial reading will reason about it as a complete
36
- * one — and the tools already have cheaper lenses (`count_only`, `interactive`, `diff`) for exactly
37
- * this. The full result is in the journal either way; nothing is lost from the record.
38
- */
39
- const MAX_RESULT_CHARS = 8000;
40
- /** The cache breakpoint marker. One object, because it is the same fact in three places. */
41
- const CACHE_BREAKPOINT = { type: 'ephemeral' };
42
- /** What one turn of the wire looks like, narrowed at the boundary rather than trusted. */
43
- const TextBlock = z.object({ type: z.literal('text'), text: z.string() });
44
- const ToolUseBlock = z.object({
45
- type: z.literal('tool_use'),
46
- id: z.string(),
47
- name: z.string(),
48
- input: z.unknown(),
49
- });
50
- const OtherBlock = z.object({ type: z.string() }).passthrough();
51
- const MessageResponse = z.object({
52
- content: z.array(z.union([TextBlock, ToolUseBlock, OtherBlock])).default([]),
53
- usage: z
54
- .object({
55
- input_tokens: z.number().default(0),
56
- output_tokens: z.number().default(0),
57
- cache_read_input_tokens: z.number().nullish(),
58
- cache_creation_input_tokens: z.number().nullish(),
59
- })
60
- .default({ input_tokens: 0, output_tokens: 0 }),
61
- });
62
- /**
63
- * Read the harness's configuration out of the environment.
64
- *
65
- * Returns `undefined` when there is no key, which is the ONLY thing that decides whether the harness
66
- * is available. Stated as a read rather than a throw because "you have not configured this" is a
67
- * routine answer a caller offers a way out of, not a failure.
68
- */
69
- export function harnessOptionsFromEnv(env) {
70
- const model = env[ReticleEnv.HARNESS_MODEL];
71
- const baseUrl = env[ReticleEnv.HARNESS_BASE_URL];
72
- const withOptions = (apiKey, fallbackUrl) => {
73
- const url = baseUrl === undefined || 0 === baseUrl.length ? fallbackUrl : baseUrl;
74
- return {
75
- apiKey,
76
- ...(model === undefined || 0 === model.length ? {} : { model }),
77
- ...(url === undefined || 0 === url.length ? {} : { baseUrl: url }),
78
- };
79
- };
80
- const apiKey = env[ReticleEnv.HARNESS_KEY];
81
- if (apiKey !== undefined && 0 < apiKey.length)
82
- return withOptions(apiKey);
83
- /**
84
- * The platform key, against the platform.
85
- *
86
- * Reticle's own service answers the Messages API at `/v1/messages` and forwards with ITS provider
87
- * key, so a user who has run `reticle link` can drive with a frontier model having never held an
88
- * Anthropic key at all. Without this they would be told to set `ANTHROPIC_API_KEY` to a value
89
- * beginning `rk_live_` — which works, and reads like a mistake, and is the sort of instruction
90
- * people correct on the way past and then cannot explain why nothing runs.
91
- *
92
- * An explicit `ANTHROPIC_API_KEY` still wins: someone who exported one meant it.
93
- */
94
- const cloud = platformCredentialFrom(env);
95
- if (cloud === undefined)
96
- return undefined;
97
- return withOptions(cloud.apiKey, cloud.url);
98
- }
99
- /** Build a driver backed by the Messages API. */
100
- export function harnessDriver(options) {
101
- const model = options.model ?? DEFAULT_HARNESS_MODEL;
102
- const baseUrl = options.baseUrl ?? DEFAULT_HARNESS_BASE_URL;
103
- const call = options.fetch ?? ((url, init) => fetch(url, init));
104
- return {
105
- async turn({ system, tools, history }) {
106
- const response = await call(`${baseUrl}${MESSAGES_PATH}`, {
107
- method: 'POST',
108
- headers: {
109
- 'content-type': 'application/json',
110
- 'x-api-key': options.apiKey,
111
- 'anthropic-version': ANTHROPIC_VERSION,
112
- },
113
- body: JSON.stringify({
114
- model,
115
- max_tokens: MAX_TOKENS,
116
- system: [
117
- {
118
- type: 'text',
119
- text: system,
120
- // The system prompt and the tool list are byte-identical on every turn of a drive, and
121
- // a drive is dozens of turns. Caching the prefix is most of the cost of driving an app.
122
- cache_control: CACHE_BREAKPOINT,
123
- },
124
- ],
125
- tools: tools.map(toWireTool),
126
- messages: withConversationCache(history.map(toWireMessage)),
127
- }),
128
- });
129
- const body = await response.text();
130
- if (!response.ok) {
131
- // Thrown, not returned: the loop calls a model that will not answer a BROKEN drive, and a
132
- // broken drive is never reported as clean. Swallowing this would be a verdict about an app
133
- // nobody drove.
134
- throw new Error(`the model refused the request (HTTP ${String(response.status)}): ${body}`);
135
- }
136
- const parsed = MessageResponse.parse(JSON.parse(body));
137
- const text = parsed.content
138
- .filter((block) => 'text' === block.type)
139
- .map((block) => block.text)
140
- .join('\n');
141
- const calls = parsed.content
142
- .filter((block) => 'tool_use' === block.type)
143
- .map((block) => ({
144
- id: block.id,
145
- name: block.name,
146
- args: isRecord(block.input) ? block.input : {},
147
- }));
148
- return {
149
- text,
150
- calls,
151
- usage: {
152
- input: parsed.usage.input_tokens,
153
- output: parsed.usage.output_tokens,
154
- // Reported so a missed cache is VISIBLE. A breakpoint that silently stops matching costs
155
- // full price and looks exactly like one that is working from every other angle.
156
- cacheRead: parsed.usage.cache_read_input_tokens ?? 0,
157
- cacheWrite: parsed.usage.cache_creation_input_tokens ?? 0,
158
- },
159
- };
160
- },
161
- };
162
- }
163
- /**
164
- * Cache the conversation so far, so the next turn does not pay to re-read it.
165
- *
166
- * The API is stateless: every turn re-sends the whole conversation, and a drive is dozens of turns.
167
- * The system prompt and the tool list are already cached from the first turn — but those are the half
168
- * that does not grow. The messages are the half that does, and without a breakpoint every byte of
169
- * them is processed again at full price on every single turn.
170
- *
171
- * ONE breakpoint, on the last content block of the most-recently-appended turn. Next turn, this
172
- * entire conversation is the request's prefix, so it is read at roughly a tenth of the input price
173
- * rather than processed again. Earlier entries stay valid read points, so the saving accrues as the
174
- * conversation grows — which is exactly the shape of the cost.
175
- *
176
- * Not several breakpoints: four is the hard limit per request and the system block already spends
177
- * one. More here would buy nothing, because the lookup from a later breakpoint already finds the
178
- * earlier turn's entry as its prefix.
179
- *
180
- * The one thing that would break it is a single turn adding more than twenty content blocks — the
181
- * lookback window is twenty, and a breakpoint that cannot see the previous entry silently misses. A
182
- * turn here is one text block, N tool_use blocks and N tool_result blocks, so it would take ten
183
- * parallel calls in one turn to reach that. Worth knowing; not worth a second breakpoint until it
184
- * happens, and the cache-read count in `usage` is where it would show up.
185
- */
186
- function withConversationCache(messages) {
187
- const last = messages[messages.length - 1];
188
- if (last === undefined)
189
- return [...messages];
190
- const content = last.content.map((block, index) => index === last.content.length - 1 ? { ...block, cache_control: CACHE_BREAKPOINT } : block);
191
- return [...messages.slice(0, -1), { ...last, content }];
192
- }
193
- function toWireTool(tool) {
194
- return {
195
- name: tool.name,
196
- description: tool.description,
197
- input_schema: tool.inputSchema,
198
- };
199
- }
200
- function toWireMessage(entry) {
201
- if ('user' === entry.role) {
202
- // A text BLOCK rather than a bare string: only a block can carry a cache breakpoint, and the
203
- // opening message is the one that would otherwise be a string. It changes the billing and
204
- // nothing the model reads.
205
- return { role: 'user', content: [{ type: 'text', text: entry.text }] };
206
- }
207
- if ('assistant' === entry.role) {
208
- const blocks = [];
209
- if (entry.text.length > 0)
210
- blocks.push({ type: 'text', text: entry.text });
211
- for (const call of entry.calls) {
212
- blocks.push({ type: 'tool_use', id: call.id, name: call.name, input: call.args });
213
- }
214
- return { role: 'assistant', content: blocks };
215
- }
216
- // Every outcome goes back in ONE user message. Splitting tool results across messages teaches the
217
- // model to stop asking for calls in parallel, which is most of what makes a drive fast.
218
- return {
219
- role: 'user',
220
- content: entry.outcomes.map((outcome) => ({
221
- type: 'tool_result',
222
- tool_use_id: outcome.id,
223
- content: truncate(JSON.stringify(outcome.result ?? null)),
224
- is_error: outcome.isError,
225
- })),
226
- };
227
- }
228
- function truncate(text) {
229
- if (text.length <= MAX_RESULT_CHARS)
230
- return text;
231
- const dropped = String(text.length - MAX_RESULT_CHARS);
232
- return `${text.slice(0, MAX_RESULT_CHARS)}\n…[truncated ${dropped} characters — ask for a narrower lens (count_only, interactive, diff, or a path) if you need the rest]`;
233
- }
234
- function isRecord(value) {
235
- return 'object' === typeof value && null !== value && !Array.isArray(value);
236
- }