@reticlehq/server 3.3.0 → 3.5.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 (326) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +12 -14
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +22 -0
  7. package/dist/command/cli/adhoc-suite.js +90 -19
  8. package/dist/command/cli/adhoc-verdict.d.ts +35 -0
  9. package/dist/command/cli/adhoc-verdict.js +90 -3
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +6 -2
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  17. package/dist/command/cli/cli-doctor.js +7 -5
  18. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  19. package/dist/command/cli/cli-flow-commands.js +67 -4
  20. package/dist/command/cli/cli-parse.d.ts +8 -1
  21. package/dist/command/cli/cli-parse.js +24 -4
  22. package/dist/command/cli/cli-update-commands.js +8 -1
  23. package/dist/command/cli/cli-verify.d.ts +24 -3
  24. package/dist/command/cli/cli-verify.js +54 -11
  25. package/dist/command/cli/cloud-cli.js +64 -2
  26. package/dist/command/cli/cloud-kit.js +3 -5
  27. package/dist/command/cli/cloud-login.d.ts +1 -1
  28. package/dist/command/cli/cloud-login.js +27 -8
  29. package/dist/command/cli/demo/demo-page.js +6 -3
  30. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  31. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  32. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  33. package/dist/command/cli/launch/cli-launch.js +21 -9
  34. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  35. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  36. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  37. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  38. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  39. package/dist/command/cli/setup-mcp-cli.js +21 -13
  40. package/dist/command/cli/tutorial.d.ts +2 -2
  41. package/dist/command/cli/tutorial.js +21 -7
  42. package/dist/command/cli.js +79 -11
  43. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  44. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  45. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  46. package/dist/command/daemon/daemon-resolve.js +1 -1
  47. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  48. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  49. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  50. package/dist/command/dev/stale-issue-guard.js +8 -8
  51. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  52. package/dist/command/setup/bringup/connect-lease.js +38 -0
  53. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  54. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  55. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  56. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  57. package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
  58. package/dist/command/setup/bringup/dev-server-wait.js +1 -1
  59. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  60. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  61. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  62. package/dist/command/setup/bringup/static-page-server.js +151 -0
  63. package/dist/command/setup/desktop-shape.d.ts +4 -0
  64. package/dist/command/setup/desktop-shape.js +18 -2
  65. package/dist/command/setup/init/init-port.d.ts +15 -0
  66. package/dist/command/setup/init/init-port.js +44 -0
  67. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  68. package/dist/command/setup/init/init-runtime.js +91 -9
  69. package/dist/command/setup/node-effects.d.ts +27 -14
  70. package/dist/command/setup/node-effects.js +86 -45
  71. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  72. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  73. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  74. package/dist/command/setup/probe/page-probe.js +18 -3
  75. package/dist/command/setup/run-setup.d.ts +28 -1
  76. package/dist/command/setup/run-setup.js +103 -15
  77. package/dist/command/setup/session-pick.d.ts +13 -0
  78. package/dist/command/setup/session-pick.js +20 -0
  79. package/dist/command/setup/setup-command.d.ts +2 -14
  80. package/dist/command/setup/setup-command.js +105 -31
  81. package/dist/command/setup/setup-mcp.d.ts +2 -0
  82. package/dist/command/setup/setup-mcp.js +17 -8
  83. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  84. package/dist/command/setup/terminal/wait-progress.js +17 -0
  85. package/dist/command/version/peer-announce.d.ts +6 -2
  86. package/dist/command/version/peer-announce.js +8 -6
  87. package/dist/command/version/version-nudge.d.ts +17 -3
  88. package/dist/command/version/version-nudge.js +25 -4
  89. package/dist/features/crawl/crawl-empty.js +1 -1
  90. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  91. package/dist/features/crawl/crawl-tools.js +57 -0
  92. package/dist/features/crawl/crawl.js +171 -157
  93. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  94. package/dist/features/exhaust/app-coverage.js +31 -0
  95. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  96. package/dist/features/exhaust/code-coverage.js +77 -0
  97. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  98. package/dist/features/exhaust/exhaust-run.js +82 -0
  99. package/dist/features/exhaust/explorer.d.ts +103 -0
  100. package/dist/features/exhaust/explorer.js +195 -0
  101. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  102. package/dist/features/exhaust/failure-branches.js +92 -0
  103. package/dist/features/exhaust/ledger.d.ts +202 -0
  104. package/dist/features/exhaust/ledger.js +252 -0
  105. package/dist/features/exhaust/session-fold.d.ts +31 -0
  106. package/dist/features/exhaust/session-fold.js +79 -0
  107. package/dist/features/exhaust/session-port.d.ts +36 -0
  108. package/dist/features/exhaust/session-port.js +102 -0
  109. package/dist/features/harness/drive-report.d.ts +0 -9
  110. package/dist/features/harness/drive-report.js +18 -0
  111. package/dist/features/harness/driver.js +4 -7
  112. package/dist/features/harness/fill-values.d.ts +10 -0
  113. package/dist/features/harness/fill-values.js +50 -11
  114. package/dist/features/harness/jev-driver.js +30 -13
  115. package/dist/features/harness/openai-driver.d.ts +12 -0
  116. package/dist/features/harness/openai-driver.js +21 -12
  117. package/dist/features/harness/platform-config.js +5 -6
  118. package/dist/features/license/license.d.ts +2 -2
  119. package/dist/index.d.ts +9 -0
  120. package/dist/index.js +56 -19
  121. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  122. package/dist/judgement/domain/domain-model.js +3 -1
  123. package/dist/judgement/oracles/self-instrument.js +4 -2
  124. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  125. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  126. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  127. package/dist/judgement/runs/verification-sync.js +1 -1
  128. package/dist/language/flows/change/affected.d.ts +9 -0
  129. package/dist/language/flows/change/affected.js +35 -0
  130. package/dist/language/flows/change/gate.d.ts +20 -0
  131. package/dist/language/flows/change/gate.js +12 -1
  132. package/dist/language/flows/flow-author.d.ts +7 -0
  133. package/dist/language/flows/flow-author.js +19 -0
  134. package/dist/language/flows/flow-expect-grammar.js +1 -1
  135. package/dist/language/flows/flow-journey.d.ts +54 -0
  136. package/dist/language/flows/flow-journey.js +119 -0
  137. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  138. package/dist/language/flows/flow-replay-run.js +13 -47
  139. package/dist/language/flows/flow-replay.d.ts +22 -8
  140. package/dist/language/flows/flow-replay.js +55 -9
  141. package/dist/language/flows/flow-result.d.ts +2 -0
  142. package/dist/language/flows/flow-result.js +25 -0
  143. package/dist/language/flows/flow-tools.js +17 -5
  144. package/dist/language/flows/flows.d.ts +10 -1
  145. package/dist/language/flows/flows.js +90 -14
  146. package/dist/language/flows/recording/replay-halt.js +3 -1
  147. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  148. package/dist/language/flows/recording/tape/recordings.js +55 -1
  149. package/dist/language/flows/replay.d.ts +19 -0
  150. package/dist/language/flows/replay.js +42 -6
  151. package/dist/language/flows/verify-change-tools.js +5 -2
  152. package/dist/launch-chromium.d.ts +80 -0
  153. package/dist/launch-chromium.js +218 -0
  154. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  155. package/dist/memory/cloud/cloud-config.js +47 -7
  156. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  157. package/dist/memory/cloud/cloud-sync.js +6 -9
  158. package/dist/memory/cloud/harness-offer.js +5 -6
  159. package/dist/memory/cloud/harness-switch.js +5 -6
  160. package/dist/memory/cloud/sync-cycle.d.ts +77 -3
  161. package/dist/memory/cloud/sync-cycle.js +325 -55
  162. package/dist/memory/cloud/sync-daemon.js +24 -20
  163. package/dist/memory/cloud/sync-disk.js +16 -2
  164. package/dist/memory/intent/intent-shard.d.ts +10 -32
  165. package/dist/memory/intent/intent-shard.js +0 -10
  166. package/dist/memory/intent/intent-tools.js +8 -3
  167. package/dist/memory/journal/drive-flow.d.ts +18 -13
  168. package/dist/memory/journal/drive-flow.js +45 -37
  169. package/dist/memory/journal/envelope.d.ts +8 -8
  170. package/dist/memory/journal/journal-query.d.ts +14 -0
  171. package/dist/memory/journal/journal-query.js +15 -0
  172. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  173. package/dist/memory/journal/session-end.d.ts +9 -1
  174. package/dist/memory/journal/session-end.js +24 -12
  175. package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
  176. package/dist/memory/project/artifact-root-resolver.js +46 -19
  177. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  178. package/dist/memory/project/dir/reticle-dir.js +1 -0
  179. package/dist/memory/project/project-tools.js +3 -1
  180. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  181. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  182. package/dist/portal/bridge/bridge.d.ts +12 -3
  183. package/dist/portal/bridge/bridge.js +49 -3
  184. package/dist/portal/input/js-coverage.d.ts +25 -0
  185. package/dist/portal/input/js-coverage.js +30 -0
  186. package/dist/portal/input/real-input.d.ts +11 -0
  187. package/dist/portal/input/real-input.js +29 -4
  188. package/dist/portal/input/viewport-tools.js +16 -6
  189. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  190. package/dist/portal/pool/playwright-launcher.js +20 -13
  191. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  192. package/dist/portal/session/dev-server/dev-command.js +30 -12
  193. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  194. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  195. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  196. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  197. package/dist/portal/session/facts/observed-state.js +34 -0
  198. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  199. package/dist/portal/session/no-session-diagnosis.js +62 -5
  200. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  201. package/dist/portal/session/no-session-next-action.js +48 -9
  202. package/dist/portal/session/no-session-watch.d.ts +18 -0
  203. package/dist/portal/session/no-session-watch.js +88 -2
  204. package/dist/portal/session/session-health.js +3 -2
  205. package/dist/portal/session/session-manager.d.ts +20 -6
  206. package/dist/portal/session/session-manager.js +19 -3
  207. package/dist/portal/session/session-reaper.d.ts +28 -0
  208. package/dist/portal/session/session-reaper.js +49 -1
  209. package/dist/portal/session/session.d.ts +10 -8
  210. package/dist/portal/session/session.js +24 -32
  211. package/dist/surface/http-server.d.ts +3 -2
  212. package/dist/surface/http-server.js +8 -5
  213. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  214. package/dist/surface/mcp/mcp-proxy.js +2 -2
  215. package/dist/surface/mcp/mcp.js +5 -3
  216. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  217. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  218. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  219. package/dist/surface/mcp/server-instructions.js +9 -7
  220. package/dist/surface/tools/act/after-match.d.ts +17 -0
  221. package/dist/surface/tools/act/after-match.js +30 -0
  222. package/dist/surface/tools/act/durable.d.ts +69 -0
  223. package/dist/surface/tools/act/durable.js +139 -0
  224. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  225. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  226. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  227. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  228. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  229. package/dist/surface/tools/act/proved-controls.js +11 -0
  230. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  231. package/dist/surface/tools/act/sequence-grade.js +32 -1
  232. package/dist/surface/tools/act-capsule.d.ts +7 -0
  233. package/dist/surface/tools/act-capsule.js +46 -1
  234. package/dist/surface/tools/act-sequence-tool.js +25 -8
  235. package/dist/surface/tools/act-tools.js +28 -52
  236. package/dist/surface/tools/assert/assert-grade.js +12 -1
  237. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  238. package/dist/surface/tools/browser-tools.js +14 -2
  239. package/dist/surface/tools/coverage-tools.js +27 -1
  240. package/dist/surface/tools/dynamic-tools.js +29 -2
  241. package/dist/surface/tools/error-recovery.js +5 -6
  242. package/dist/surface/tools/feedback-invite.js +6 -4
  243. package/dist/surface/tools/harness-explore.d.ts +3 -2
  244. package/dist/surface/tools/harness-explore.js +12 -7
  245. package/dist/surface/tools/invoke-tool.js +12 -8
  246. package/dist/surface/tools/lease-tools.js +15 -6
  247. package/dist/surface/tools/live-call-text.js +4 -1
  248. package/dist/surface/tools/observe-tools.js +9 -1
  249. package/dist/surface/tools/tool-kit.d.ts +10 -0
  250. package/dist/surface/tools/tools.js +4 -0
  251. package/dist/telemetry/argument-shape.d.ts +0 -2
  252. package/dist/telemetry/argument-shape.js +1 -5
  253. package/dist/telemetry/error-class.d.ts +2 -3
  254. package/dist/telemetry/error-class.js +2 -3
  255. package/dist/telemetry/harness-drive.d.ts +0 -2
  256. package/dist/telemetry/harness-drive.js +0 -4
  257. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  258. package/dist/telemetry/onboarding-firsts.js +2 -2
  259. package/dist/wire-journal.d.ts +3 -0
  260. package/dist/wire-journal.js +4 -0
  261. package/dist/workspace-packages.d.ts +0 -2
  262. package/dist/workspace-packages.js +1 -9
  263. package/docs/README.md +4 -2
  264. package/docs/actions.mdx +3 -4
  265. package/docs/agent-cheatsheet.md +4 -4
  266. package/docs/architecture.md +6 -6
  267. package/docs/best-practices.mdx +1 -1
  268. package/docs/capabilities.mdx +32 -1
  269. package/docs/cli/cloud.mdx +20 -8
  270. package/docs/cli/doctor.mdx +4 -0
  271. package/docs/cli/feedback.mdx +2 -2
  272. package/docs/cli/gate.mdx +9 -4
  273. package/docs/cli/update.mdx +10 -0
  274. package/docs/cli.mdx +22 -7
  275. package/docs/desktop.mdx +14 -10
  276. package/docs/environment.mdx +87 -0
  277. package/docs/faq.mdx +18 -16
  278. package/docs/first-drive.md +5 -13
  279. package/docs/flows.md +21 -2
  280. package/docs/frameworks.mdx +29 -10
  281. package/docs/getting-started.md +15 -5
  282. package/docs/http-transport.md +6 -3
  283. package/docs/human-control.md +2 -2
  284. package/docs/index.mdx +16 -6
  285. package/docs/install-agentic.mdx +2 -2
  286. package/docs/install-manual.mdx +1 -1
  287. package/docs/integration-patterns.md +3 -3
  288. package/docs/multi-agent-testing.md +1 -1
  289. package/docs/packages/babel-plugin.mdx +1 -1
  290. package/docs/packages/browser.mdx +2 -2
  291. package/docs/packages/next.mdx +3 -0
  292. package/docs/packages/react.mdx +2 -2
  293. package/docs/packages/tauri.mdx +1 -1
  294. package/docs/packages/vite-plugin.mdx +4 -2
  295. package/docs/packages.mdx +9 -9
  296. package/docs/predicates.mdx +50 -5
  297. package/docs/quickstart.mdx +12 -10
  298. package/docs/recipes.mdx +2 -2
  299. package/docs/state-management.mdx +2 -2
  300. package/docs/system-map.md +1 -1
  301. package/docs/telemetry.md +1 -1
  302. package/docs/token-efficiency.md +3 -3
  303. package/docs/tools/act-and-wait.mdx +1 -0
  304. package/docs/tools/act-sequence.mdx +1 -1
  305. package/docs/tools/act.mdx +1 -1
  306. package/docs/tools/console.mdx +1 -1
  307. package/docs/tools/inspect.mdx +1 -1
  308. package/docs/tools/network.mdx +1 -1
  309. package/docs/tools/query.mdx +1 -1
  310. package/docs/tools/sessions.mdx +1 -1
  311. package/docs/tools/snapshot.mdx +1 -1
  312. package/docs/tools/state.mdx +1 -1
  313. package/docs/tools/wait-for.mdx +1 -1
  314. package/docs/usage.md +7 -7
  315. package/docs/use-cases.mdx +157 -0
  316. package/docs/vs-screenshots.mdx +3 -3
  317. package/docs/what-is-recorded.md +2 -2
  318. package/docs/what-is-reticle.mdx +17 -20
  319. package/docs/why-reticle.mdx +14 -21
  320. package/package.json +10 -9
  321. package/dist/judgement/capsule/minimize.d.ts +0 -12
  322. package/dist/judgement/capsule/minimize.js +0 -22
  323. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  324. package/dist/judgement/phenomena/phenomena.js +0 -101
  325. package/dist/language/flows/flow-report.d.ts +0 -22
  326. package/dist/language/flows/flow-report.js +0 -106
@@ -67,7 +67,7 @@ export const RECOVERY = {
67
67
  UNKNOWN_SESSION: 'That sessionId is not connected. Call reticle_sessions for the current ids and retry with a valid one.',
68
68
  THROTTLED: 'The target tab is backgrounded/throttled, so actions may silently no-op. Ask the human to bring ' +
69
69
  'the tab to the front, or acquire a guaranteed scriptable context yourself with ' +
70
- '`reticle_run { tool: "reticle_lease", action: "acquire", url }` (a human can equivalently run ' +
70
+ '`reticle_run { tool: "reticle_lease", args: { action: "acquire", url } }` (a human can equivalently run ' +
71
71
  '`reticle drive <url>`).',
72
72
  MISSING_BASELINE: 'That baseline does not exist yet. Call reticle_baseline { action: "list" } to see saved names, or ' +
73
73
  'reticle_baseline { action: "save", name } to capture one before diffing against it.',
@@ -159,7 +159,7 @@ export const RECOVERY = {
159
159
  'assert on the result instead of typing it. Known gap, already reported: no need to file it.',
160
160
  HOVER_NEEDS_POINTER: 'Hover needs a real pointer: a synthetic mouseover does not apply CSS :hover, and Reticle ' +
161
161
  'refuses rather than reporting the styles as applied. Acquire a tab with ' +
162
- 'reticle_run { tool: "reticle_lease", action: "acquire", url } — reticle_lease is not ' +
162
+ 'reticle_run { tool: "reticle_lease", args: { action: "acquire", url } } — reticle_lease is not ' +
163
163
  'advertised under the default profile, so it is reached through reticle_run, not called ' +
164
164
  'directly — or ask the human to drive with `reticle drive` / RETICLE_CDP_URL. This is a ' +
165
165
  'deliberate refusal, not a defect: there is nothing to report.',
@@ -182,7 +182,7 @@ export const RECOVERY = {
182
182
  COMMAND_TIMEOUT: 'The page did not answer within the command window. That is a fact about the page, not a Reticle ' +
183
183
  'failure: check reticle_sessions for `throttled`/`stale` on this session — a backgrounded tab is ' +
184
184
  'throttled and may never answer, so ask the human to bring it to the front or drive your own ' +
185
- 'browser with reticle_run { tool: "reticle_lease", action: "acquire", url } — reticle_lease is ' +
185
+ 'browser with reticle_run { tool: "reticle_lease", args: { action: "acquire", url } } — reticle_lease is ' +
186
186
  'not advertised under the default profile, so it is reached through reticle_run, not called ' +
187
187
  'directly. If the tab is in front, the page is busy or blocked (a long ' +
188
188
  'synchronous task, an alert/confirm dialog); reticle_console usually shows what it hit. Retry ' +
@@ -293,8 +293,7 @@ const RULES = [
293
293
  { match: /contenteditable/i, hint: RECOVERY.UNSUPPORTED_SURFACE },
294
294
  { match: /cannot hover without a real pointer/i, hint: RECOVERY.HOVER_NEEDS_POINTER },
295
295
  { match: /cannot \w+ a (disabled|readonly) </i, hint: RECOVERY.NOT_EDITABLE },
296
- // Three spellings ship — "action", "native action", "WebMCP tool" — and the rule matched one, so
297
- // two thirds of the same deliberate refusal still read as a possible defect.
296
+ // Both "action" and "native action" refusals need the same confirmation guidance.
298
297
  {
299
298
  match: /potentially destructive (?:\w+ )*(?:action|tool) blocked/i,
300
299
  hint: RECOVERY.CONFIRM_DANGEROUS,
@@ -495,7 +494,7 @@ const INVALID_NAME_REJECTION = /^invalid (?:[a-z]+ )*name: /i;
495
494
  * us nothing new.
496
495
  */
497
496
  export const FEEDBACK_ASK = 'This error is not one Reticle recognizes, which means it may be a defect in Reticle rather than in ' +
498
- 'the app. If you believe Reticle misbehaved, call reticle_feedback with a root-cause analysis and ' +
497
+ 'the app. If you believe Reticle misbehaved, call reticle_session { action: "feedback" } with a root-cause analysis and ' +
499
498
  'the call trace before moving on — that report is the only way this gets fixed.';
500
499
  /**
501
500
  * Build the tool-error payload spliced at the MCP boundary. `recovery` is added when the error is
@@ -36,12 +36,14 @@ export const FrictionKind = {
36
36
  * a run of three is where a deliberate retry becomes a loop.
37
37
  */
38
38
  const REPEAT_INVITE_AT = 3;
39
+ /** The call that files feedback. The old `reticle_feedback` name only answers with a redirect. */
40
+ const FEEDBACK_CALL = 'reticle_session { action: "feedback" }';
39
41
  const LINES = {
40
- [FrictionKind.UNKNOWN_TOOL]: 'no such tool — reticle_feedback tells us what you expected it to do, which is how it gets built',
41
- [FrictionKind.UNKNOWN_VERDICT]: 'Reticle could not tell what happened here. If you expected otherwise, reticle_feedback. An ' +
42
+ [FrictionKind.UNKNOWN_TOOL]: `no such tool — ${FEEDBACK_CALL} tells us what you expected it to do, which is how it gets built`,
43
+ [FrictionKind.UNKNOWN_VERDICT]: `Reticle could not tell what happened here. If you expected otherwise, ${FEEDBACK_CALL}. An ` +
42
44
  'unknown verdict is our defect, not yours',
43
- [FrictionKind.REPEATING]: 'stuck on the same call? reticle_feedback — say what you were trying to do and it gets fixed',
44
- [FrictionKind.REFUSED]: 'if that diagnosis was wrong or unhelpful, reticle_feedback — error messages are a product ' +
45
+ [FrictionKind.REPEATING]: `stuck on the same call? ${FEEDBACK_CALL} — say what you were trying to do and it gets fixed`,
46
+ [FrictionKind.REFUSED]: `if that diagnosis was wrong or unhelpful, ${FEEDBACK_CALL} — error messages are a product ` +
45
47
  'surface and we fix the ones that fail',
46
48
  };
47
49
  /**
@@ -98,8 +98,9 @@ export declare const MSG_NO_OPENAI_KEY: string;
98
98
  *
99
99
  * Resolved into the env rather than threaded through four call sites: the drivers and the
100
100
  * preference lookup all read an env record, and giving them a completed one leaves each of them
101
- * exactly as simple as it was. An EXPLICIT variable still wins — someone who exported a key meant
102
- * that key, and CI has no linked project to read.
101
+ * exactly as simple as it was. The key precedence is the resolver's, the same as sync's and the
102
+ * CLI's: the stored key for the link's host, else the exported one (which is how CI, with no
103
+ * keystore, gets here). An explicitly exported HOST still wins, so a proxy is not overridden.
103
104
  *
104
105
  * The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
105
106
  * the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
@@ -7,7 +7,7 @@
7
7
  * nothing grades) but the flows that now exist on disk, because every future run replays those
8
8
  * deterministically with no model in the loop at all.
9
9
  */
10
- import { ReticleEnv, ReticleTool, apiKeyFrom, asRecord } from '@reticlehq/core';
10
+ import { ReticleEnv, ReticleTool, cloudUrlFrom, asRecord } from '@reticlehq/core';
11
11
  import { projectForRoot } from '../../memory/project/project-for-root.js';
12
12
  import { harnessDriver, harnessOptionsFromEnv, } from '../../features/harness/driver.js';
13
13
  import { fillValues } from '../../features/harness/fill-values.js';
@@ -18,10 +18,11 @@ import { buildDomainModel } from '../../judgement/domain/domain-model.js';
18
18
  import { readContract } from '../../memory/project/dir/reticle-dir.js';
19
19
  import { sessionRoot } from '../../memory/project/session-root.js';
20
20
  import { buildHarnessPlan, planAsText } from './harness-plan.js';
21
- import { openAiDriver, openAiOptionsFromEnv } from '../../features/harness/openai-driver.js';
21
+ import { openAiDriver, openAiOptionsFromEnv, } from '../../features/harness/openai-driver.js';
22
22
  import { fetchPlatformConfig } from '../../features/harness/platform-config.js';
23
23
  import { DEFAULT_MAX_STEPS, runHarness, } from '../../features/harness/harness.js';
24
24
  import { reticleToolset } from './harness-toolset.js';
25
+ import { secretEnvKey } from '../../language/flows/flows.js';
25
26
  /**
26
27
  * Why the harness is unavailable, phrased as the way to make it available.
27
28
  *
@@ -53,15 +54,14 @@ const msgUnknownDriver = (asked) => `Unknown harness driver \`${asked}\`. Known
53
54
  *
54
55
  * Resolved into the env rather than threaded through four call sites: the drivers and the
55
56
  * preference lookup all read an env record, and giving them a completed one leaves each of them
56
- * exactly as simple as it was. An EXPLICIT variable still wins — someone who exported a key meant
57
- * that key, and CI has no linked project to read.
57
+ * exactly as simple as it was. The key precedence is the resolver's, the same as sync's and the
58
+ * CLI's: the stored key for the link's host, else the exported one (which is how CI, with no
59
+ * keystore, gets here). An explicitly exported HOST still wins, so a proxy is not overridden.
58
60
  *
59
61
  * The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
60
62
  * the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
61
63
  */
62
64
  export async function withLinkedCredential(deps, env) {
63
- if (apiKeyFrom(env) !== undefined)
64
- return env;
65
65
  try {
66
66
  const linked = await deps.linkedCloud?.();
67
67
  if (linked === undefined || null === linked)
@@ -69,7 +69,7 @@ export async function withLinkedCredential(deps, env) {
69
69
  return {
70
70
  ...env,
71
71
  [ReticleEnv.API_KEY]: linked.apiKey,
72
- [ReticleEnv.CLOUD_URL]: env[ReticleEnv.CLOUD_URL] ?? linked.url,
72
+ [ReticleEnv.CLOUD_URL]: cloudUrlFrom(env) ?? linked.url,
73
73
  };
74
74
  }
75
75
  catch {
@@ -412,7 +412,12 @@ function buildDriver(env, maxSteps, plan, requested, fills) {
412
412
  * With no such key the label heuristic answers, exactly as it did before this existed.
413
413
  */
414
414
  fillValue: fillValues({
415
+ secret: (label) => env[secretEnvKey(label)],
415
416
  ...(fills === undefined ? {} : { cache: fills }),
417
+ // GPT first: a linked machine reaches it through the platform with no key of its own.
418
+ ...(openAiOptionsFromEnv(env) === undefined
419
+ ? {}
420
+ : { openai: openAiOptionsFromEnv(env) }),
416
421
  ...(harnessOptionsFromEnv(env) === undefined
417
422
  ? {}
418
423
  : { generator: harnessOptionsFromEnv(env) }),
@@ -351,9 +351,6 @@ export async function runTool(tool, deps, args) {
351
351
  // closure carries this call's own identity, which is also what makes peak-concurrency measurable.
352
352
  // A daemon that has served even one tool call is doing a job for somebody; see daemon-usefulness.
353
353
  noteToolCall();
354
- // The ONBOARD firsts, at the chokepoint every call already passes through — so a second dispatch
355
- // path cannot quietly stop reporting them the way an extra listener would.
356
- noteOnboardingFirst(tool.name);
357
354
  // The impact record needs the project's own `.reticle` root, and this is the first place every
358
355
  // call knows it. Idempotent: the first root wins for the daemon's lifetime.
359
356
  initImpact({ reticleRoot: deps.reticleRoot });
@@ -385,10 +382,6 @@ export async function runTool(tool, deps, args) {
385
382
  // page and then wandered off" becomes a number instead of an impression.
386
383
  if (ACTION_TOOLS.has(tool.name)) {
387
384
  getSessionMetrics().recordAction();
388
- // The two funnel rungs this branch is the authority on: the first action of the run, and the
389
- // fact that this install has been driven at all. Reported from the set that already decides
390
- // what "drove the page" means, so a fourth driving tool inherits both.
391
- noteActed(args);
392
385
  }
393
386
  const settleTiming = getSessionMetrics().startToolCall(tool.name, args);
394
387
  const startedAt = Date.now();
@@ -533,6 +526,17 @@ export async function runTool(tool, deps, args) {
533
526
  // concurrency slot and peakConcurrentTools would climb forever on an unhealthy session.
534
527
  settleTiming(Date.now() - startedAt);
535
528
  }
529
+ // Milestones describe work that ran. Refusals and paused calls must not consume the firsts.
530
+ // Record action before verdict: act_and_wait can complete both in this same call.
531
+ if (isPlainObject(raw) && !resultIsError(raw) && raw['paused'] !== true && raw['ok'] !== false) {
532
+ noteOnboardingFirst(tool.name);
533
+ const effect = raw['effect'];
534
+ const acted = raw['dispatched'] !== false &&
535
+ (!isPlainObject(effect) || effect['dispatched'] !== false) &&
536
+ (tool.name !== ReticleTool.ACT_AND_WAIT || isPlainObject(effect));
537
+ if (ACTION_TOOLS.has(tool.name) && acted)
538
+ noteActed(args);
539
+ }
536
540
  // The human-feedback ask rides out on the first VERIFICATION that completes — the one moment the
537
541
  // experience is fresh and there is something concrete to react to. It is spliced BEFORE the
538
542
  // session-bound early return, because two of the four verification tools (flow_verify,
@@ -584,7 +588,7 @@ export async function runTool(tool, deps, args) {
584
588
  // reticle_sessions.versionSkew and a CLI log line — two places an agent driving a flow never
585
589
  // looks — so it could work a whole session against a mismatched pair and never learn the one fact
586
590
  // that explains the behaviour. It rides out here on whatever tool it happens to be calling.
587
- const skew = isPlainObject(raw) ? takeVersionSkew() : undefined;
591
+ const skew = isPlainObject(raw) ? takeVersionSkew(deps.peerSkew) : undefined;
588
592
  // A feedback report that was accepted and then failed to send. Same one-shot channel, because the
589
593
  // reporter is the only person who can act on it and they are not reading the daemon log — and a
590
594
  // report announced as accepted and then silently lost is the failure the awaited send existed to
@@ -21,7 +21,7 @@ import { hasAnyAppConnectedBefore } from '../../memory/recall/prior/connection-m
21
21
  import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../../portal/session/lease-visibility.js';
22
22
  import { reticleStateHome } from '../../command/daemon/daemon.js';
23
23
  import { LeaseNotReadyReason, REDACTED_VALUE, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, ReticleCommand, SeedStorageSchema, scrubKnownSecrets, } from '@reticlehq/core';
24
- import { ReticleTool } from '@reticlehq/core';
24
+ import { ReticleTool, SESSION_HEALTH } from '@reticlehq/core';
25
25
  import { asString } from '@reticlehq/core';
26
26
  import { chromiumHint } from '../../command/cli/doctor/browser/chromium-hint.js';
27
27
  /**
@@ -165,6 +165,10 @@ export function scrubSeedFromError(text, seed) {
165
165
  }
166
166
  return out;
167
167
  }
168
+ /** True for a Playwright `storageState()` export (`{ origins: [...] }`), not our seed shape. */
169
+ function looksLikeStorageStateExport(seed) {
170
+ return Array.isArray(seed?.origins);
171
+ }
168
172
  /**
169
173
  * Turn a raw navigation failure (Playwright's `page.goto: net::ERR_… at <url>\nCall log:…`, often
170
174
  * with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
@@ -494,8 +498,13 @@ export const LEASE_ACQUIRE_TOOL = {
494
498
  if (seedStorageArg !== undefined) {
495
499
  const parsed = SeedStorageSchema.safeParse(seedStorageArg);
496
500
  if (!parsed.success) {
497
- const issuesMsg = scrubSeedFromError(parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', '), seedStorageArg);
498
- throw new Error(`reticle_lease{action:"acquire"} seedStorage is invalid: ${issuesMsg}`);
501
+ const msg = scrubSeedFromError(parsed.error.issues
502
+ .map((i) => (i.path.length > 0 ? `${i.path.join('.')}: ${i.message}` : i.message))
503
+ .join(', '), seedStorageArg);
504
+ const hint = looksLikeStorageStateExport(seedStorageArg)
505
+ ? 'This looks like a Playwright storageState() export ({ origins, cookies }); seedStorage takes { local?, session?, cookies? } (map origins[].localStorage into `local`).'
506
+ : 'Expected { local?, session?, cookies? }.';
507
+ throw new Error(`reticle_lease{action:"acquire"} seedStorage is invalid: ${msg}. ${hint}`);
499
508
  }
500
509
  validatedSeed = parsed.data;
501
510
  }
@@ -699,9 +708,7 @@ const PREFER_EXISTING_NOTE = 'a non-leased tab for this app was already connecte
699
708
  * is the right trade for a broadcast and the wrong one here, where a single `[0]` is handed back as
700
709
  * "drive this instead". With `projectId` undefined — and it is an OPTIONAL argument that an
701
710
  * acquiring agent has no reason to pass — every non-leased session qualified and the answer was
702
- * whichever the map yielded first. Measured against a real daemon: leasing `localhost:4312`
703
- * recommended a Phanpy tab on `:5173`, so an agent that complied would have driven a different
704
- * application and reported a verdict about it.
711
+ * whichever the map yielded first: leasing `localhost:4312` recommended a tab on `:5173`, another app.
705
712
  *
706
713
  * So this narrows by the one thing a lease always knows: the ORIGIN it is being taken against.
707
714
  * `projectId` still narrows further when given. Nothing is recommended when nothing matches, which
@@ -718,6 +725,8 @@ function liveTabFor(deps, projectId, url) {
718
725
  const candidates = deps.sessions
719
726
  .all()
720
727
  .filter((session) => session.url !== undefined && originOf(session.url) === wanted)
728
+ // Nor a tab gone silent: one last heard from 105s earlier was named here and answered nothing.
729
+ .filter((session) => (session.lastSeenMs?.() ?? 0) <= SESSION_HEALTH.STALE_THRESHOLD_MS)
721
730
  .map((session) => ({ id: session.id, projectId: session.projectId }));
722
731
  return watchersToNotify(candidates, pool.leasedSessionIds(), projectId)[0];
723
732
  }
@@ -38,7 +38,10 @@ const NO_TOOL_EQUIVALENT = [
38
38
  // \u2014 so the optional group could not match, the clause survived, and the replacement landed
39
39
  // inside the original quoting. What an agent read was the human-equivalent clause twice over,
40
40
  // with backticks nested three deep.
41
- pattern: /`?reticle_run\s*\{\s*tool:\s*\\?"reticle_lease\\?"[^}]*\}`?(\s*\([^)]*\))?(\s*[-\u2014]+\s*reticle_lease is[^.]*\.)?/g,
41
+ // The body allows ONE nested object, because the call is `{ tool, args: { action, url } }`: the
42
+ // hint used to put `action` and `url` beside `tool`, which reticle_run refuses, and a `[^}]*\}`
43
+ // body stopped at the inner brace and left ` }` behind once the hint was corrected.
44
+ pattern: /`?reticle_run\s*\{\s*tool:\s*\\?"reticle_lease\\?"(?:[^{}]|\{[^{}]*\})*\}`?(\s*\([^)]*\))?(\s*[-\u2014]+\s*reticle_lease is[^.]*\.)?/g,
42
45
  instead: 'the CLI: `reticle open <url>` (a human can equivalently run `reticle drive <url>`)',
43
46
  },
44
47
  {
@@ -33,6 +33,7 @@ import { withControl } from '../../portal/session/control-envelope.js';
33
33
  import { asNumber, asRecord, asString } from '@reticlehq/core';
34
34
  import { intentArg, sessionIdShape, commandOrThrow } from './tool-kit.js';
35
35
  import { gradeOfPredicate } from './assert/assert-grade.js';
36
+ import { captureAssertion } from '../../language/flows/replay.js';
36
37
  /**
37
38
  * Evidence-completeness block: present on observe/network/console only when the ring buffer has
38
39
  * evicted events, so a "no such event" answer is distinguishable from "I dropped it" (issue #27).
@@ -302,7 +303,7 @@ export const OBSERVE_TOOLS = [
302
303
  '{ net, urlContains|method|status|count|bodyMatches|bodyContains|requestBodyContains|requestBodyMatches } ' +
303
304
  '{ state, path|equals } { route, pathname (exact) | contains (path+query+hash) } ' +
304
305
  '{ element, testid|role|text } { text } { console, level|contains|absent } { animation, name } ' +
305
- '{ settled } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
306
+ '{ settled } { compare, left, right } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
306
307
  'over element/text presence.'),
307
308
  until: PredicateSchema.optional().describe("Alias for `predicate` (act_and_wait's name)."),
308
309
  timeout_ms: timeoutMsSchema
@@ -446,6 +447,13 @@ export const OBSERVE_TOOLS = [
446
447
  // destroys — see runs/run-context.ts. Recorded WITHOUT an attribution window: this tool drives
447
448
  // nothing, so no event it observed was caused by it.
448
449
  session.recordAction(ReticleTool.ASSERT, asRecord(args), verdictEffect);
450
+ // A passing check over the default window (since the last act) is proof of that act, so it
451
+ // joins that step in the recording — otherwise "act, then assert" saves a bare click. An
452
+ // explicit `since` may reach back past the last step, where replay would not find it. The
453
+ // store itself refuses when a navigation came after that step: see RecordingStore.markNavigated.
454
+ if (Verified.YES === decision['verified'] && args['since'] === undefined) {
455
+ captureAssertion(deps.recordings, predicate);
456
+ }
449
457
  return withControl(session, {
450
458
  ...decision,
451
459
  ...annotateStarvedFailure(session, verdict),
@@ -59,6 +59,16 @@ export interface ToolDeps<Ext = unknown> {
59
59
  * `tools.ts`, which keeps every existing `ToolDeps` construction working unchanged.
60
60
  */
61
61
  snapshots?: SnapshotCache;
62
+ /**
63
+ * The skew between THIS client's MCP server and the daemon, told once to this connection only.
64
+ *
65
+ * Per attach for the same reason as `snapshots`: one daemon serves every agent on the machine, and
66
+ * a daemon-wide queue handed one agent's "restart your MCP server" to whichever agent called next,
67
+ * whose server matched (#1136). Absent ⇒ this connection's server matched, or it did not announce.
68
+ */
69
+ peerSkew?: {
70
+ take(): string | undefined;
71
+ };
62
72
  /** cross-run outcome memory (.reticle/project.json). */
63
73
  project: ProjectStore;
64
74
  /** optional native-input provider. undefined ⇒ everything stays synthetic. */
@@ -236,6 +236,10 @@ export const RAW_TOOLS = [
236
236
  .boolean()
237
237
  .optional()
238
238
  .describe('True when the page exceeded the snapshot cap. The tree is then a document-order PREFIX: an element being absent from it does NOT mean it is absent from the page.'),
239
+ // The refs a capped read never entered — re-read one with `scope`. Undeclared, a validating
240
+ // client stripped it, and a truncated tree arrived with no way to finish reading it.
241
+ unread: z.array(z.string()).optional(),
242
+ unreadOverflow: z.boolean().optional(),
239
243
  note: z
240
244
  .string()
241
245
  .optional()
@@ -19,8 +19,6 @@
19
19
  * never quietly start forwarding free text.
20
20
  */
21
21
  export declare const OTHER_VALUE = "other";
22
- /** True for anything we are willing to report a value for. */
23
- export declare function isSafeEnumParam(name: string): boolean;
24
22
  /**
25
23
  * The reportable form of one parameter: `name` on its own, or `name:value` when the parameter is a
26
24
  * known enum. Never the raw value of anything else.
@@ -27,7 +27,7 @@ import { ActionType, FeedbackKind, QueryBy, SnapshotMode } from '@reticlehq/core
27
27
  const SAFE_ENUM_PARAMS = new Map([
28
28
  // Every entry DERIVED from the enum core already owns, never hand-listed. The first version listed
29
29
  // them literally and was wrong on the day it shipped, not merely at risk: `action` was missing
30
- // `fill`, `dblclick`, `submit`, `scrollIntoView`, `drag` and `webmcp` — so `fill`, one of the most
30
+ // `fill`, `dblclick`, `submit`, `scrollIntoView` and `drag` — so `fill`, one of the most
31
31
  // common actions there is, silently reported as `other` — while carrying a `scroll` that
32
32
  // `ActionType` does not define. `mode` mixed SnapshotMode with unrelated session-lifecycle values.
33
33
  // Deriving is not tidiness here; it is the difference between the data being right and being wrong.
@@ -41,10 +41,6 @@ const SAFE_ENUM_PARAMS = new Map([
41
41
  ['level', new Set(['error', 'warn', 'info', 'log', 'debug'])],
42
42
  ]);
43
43
  export const OTHER_VALUE = 'other';
44
- /** True for anything we are willing to report a value for. */
45
- export function isSafeEnumParam(name) {
46
- return SAFE_ENUM_PARAMS.has(name);
47
- }
48
44
  /**
49
45
  * The reportable form of one parameter: `name` on its own, or `name:value` when the parameter is a
50
46
  * known enum. Never the raw value of anything else.
@@ -2,9 +2,8 @@
2
2
  * Whose defect was that tool error?
3
3
  *
4
4
  * `toolErrors` was one number covering three failures with three different fixes, so the top-line
5
- * count could be acted on only by unpacking `errors[]` by hand and reading the prose. Measured over
6
- * 2026-08-10/11: 60 `state`, 22 `refusal`, 20 `schema` (serialized zod arrays) and 20 stale refs.
7
- * Read as "126 tool errors" that hides the sixth of them where OUR schema failed to explain itself —
5
+ * count could be acted on only by unpacking `errors[]` by hand and reading the prose. Read as one
6
+ * number, it hides the share where OUR schema failed to explain itself (serialized zod arrays),
8
7
  * which is the only bucket we can fix by writing better descriptions.
9
8
  *
10
9
  * Classified from the message SHAPE, never stored raw: this returns a bucket, and the caller keeps
@@ -2,9 +2,8 @@
2
2
  * Whose defect was that tool error?
3
3
  *
4
4
  * `toolErrors` was one number covering three failures with three different fixes, so the top-line
5
- * count could be acted on only by unpacking `errors[]` by hand and reading the prose. Measured over
6
- * 2026-08-10/11: 60 `state`, 22 `refusal`, 20 `schema` (serialized zod arrays) and 20 stale refs.
7
- * Read as "126 tool errors" that hides the sixth of them where OUR schema failed to explain itself —
5
+ * count could be acted on only by unpacking `errors[]` by hand and reading the prose. Read as one
6
+ * number, it hides the share where OUR schema failed to explain itself (serialized zod arrays),
8
7
  * which is the only bucket we can fix by writing better descriptions.
9
8
  *
10
9
  * Classified from the message SHAPE, never stored raw: this returns a bucket, and the caller keeps
@@ -2,5 +2,3 @@
2
2
  export declare function withHarnessDrive<T>(drive: () => Promise<T>): Promise<T>;
3
3
  /** True while a harness drive is in progress. */
4
4
  export declare function harnessDriving(): boolean;
5
- /** Tests only — a leaked span would mis-attribute every verdict after it. */
6
- export declare function resetHarnessDrive(): void;
@@ -33,7 +33,3 @@ export async function withHarnessDrive(drive) {
33
33
  export function harnessDriving() {
34
34
  return depth > 0;
35
35
  }
36
- /** Tests only — a leaked span would mis-attribute every verdict after it. */
37
- export function resetHarnessDrive() {
38
- depth = 0;
39
- }
@@ -1,9 +1,9 @@
1
- /** A tool call the daemon just ran. Called from the dispatch chokepoint, which sees every one. */
1
+ /** A successful page read. The dispatcher calls this after excluding refusals and pauses. */
2
2
  export declare function noteOnboardingFirst(toolName: string): void;
3
3
  /**
4
4
  * The first ACTION of this run, and the fact that this install has been driven at all.
5
5
  *
6
- * Called from the dispatch chokepoint's own action branch rather than matched on a tool name here.
6
+ * Called after the dispatcher observes a dispatched action, rather than on an attempted call.
7
7
  * The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
8
8
  * one call as two different firsts — and the worry was backwards: this server's instructions tell
9
9
  * every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
@@ -15,7 +15,7 @@ import { reportOnboardingStep } from './onboarding-funnel.js';
15
15
  * drives in another has still only crossed each of these once.
16
16
  */
17
17
  const seen = { look: false, act: false, verdict: false, driven: false };
18
- /** A tool call the daemon just ran. Called from the dispatch chokepoint, which sees every one. */
18
+ /** A successful page read. The dispatcher calls this after excluding refusals and pauses. */
19
19
  export function noteOnboardingFirst(toolName) {
20
20
  if (!seen.look && LOOK_TOOLS.has(toolName)) {
21
21
  seen.look = true;
@@ -29,7 +29,7 @@ export function noteOnboardingFirst(toolName) {
29
29
  /**
30
30
  * The first ACTION of this run, and the fact that this install has been driven at all.
31
31
  *
32
- * Called from the dispatch chokepoint's own action branch rather than matched on a tool name here.
32
+ * Called after the dispatcher observes a dispatched action, rather than on an attempted call.
33
33
  * The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
34
34
  * one call as two different firsts — and the worry was backwards: this server's instructions tell
35
35
  * every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
@@ -1,5 +1,6 @@
1
1
  import { Bridge } from './portal/bridge/bridge.js';
2
2
  import { FlowStore } from './language/flows/flows.js';
3
+ import type { FlowFile } from '@reticlehq/core';
3
4
  import type { FileSystemPort } from './memory/project/fs/fs-port.js';
4
5
  import type { TapeStep } from './memory/journal/drive-flow.js';
5
6
  import type { OnboardingStep } from '@reticlehq/core/telemetry';
@@ -26,6 +27,8 @@ export declare function attachJournal(bridge: Bridge, deps: {
26
27
  /** Tell cloud sync a run landed, so it cycles instead of waiting for its timer. */
27
28
  onRunPersisted?: () => void;
28
29
  flows?: FlowStore;
30
+ /** Who is driving, stamped on each flow teardown saves. */
31
+ author?: () => FlowFile['author'];
29
32
  /** What this project is willing to keep — the `retain` block of its `.reticle.json`. */
30
33
  retain?: PruneWorkspaceOptions;
31
34
  }): void;
@@ -65,6 +65,10 @@ export function attachJournal(bridge, deps) {
65
65
  // Teardown: flush the journal tail to disk + persist what this session learned.
66
66
  bridge.attachSessionEnd(makeSessionEnd({
67
67
  ...deps,
68
+ // A drive is saved in the session's own project, where replay will look for it.
69
+ ...(deps.flows === undefined
70
+ ? {}
71
+ : { flowsAt: (root) => new FlowStore(deps.fs, root, { now: Date.now }) }),
68
72
  ...(deps.retain === undefined ? {} : { retain: deps.retain }),
69
73
  // Retention runs from teardown, and it must not delete the journal of a session that is still
70
74
  // being written. The registry is the only thing that knows which those are.
@@ -18,5 +18,3 @@
18
18
  * allowed to depend on anything, which is what makes them useful as fixtures.
19
19
  */
20
20
  export declare function publishedPackageDirs(): string[];
21
- /** The same list, as the last part of each path: `core`, `server`, `dom`, and so on. */
22
- export declare function publishedPackageNames(): string[];
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
- import { basename, join } from 'node:path';
2
+ import { join } from 'node:path';
3
3
  import { workspaceGlobs } from '../../scripts/check-boundaries.mjs';
4
4
  import { REPO_ROOT } from './machine/repo-root.js';
5
5
  /**
@@ -47,11 +47,3 @@ function collect(dir, levels, into) {
47
47
  collect(join(dir, entry.name), levels - 1, into);
48
48
  }
49
49
  }
50
- /** The same list, as the last part of each path: `core`, `server`, `dom`, and so on. */
51
- export function publishedPackageNames() {
52
- // `basename`, not `split('/')`: these directories are built with `join`, so on Windows they hold
53
- // backslashes and contain no forward slash at all — the split then returns the WHOLE ABSOLUTE
54
- // PATH as the package name. Every caller comparing a name against a real one silently stops
55
- // matching, which is the same defect that reported fourteen packages as uncovered.
56
- return publishedPackageDirs().map((dir) => basename(dir));
57
- }
package/docs/README.md CHANGED
@@ -13,8 +13,9 @@ Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `d
13
13
  | Page | What it answers |
14
14
  | --- | --- |
15
15
  | [quickstart.mdx](quickstart.mdx) | five minutes to a real verdict; every response on it was captured live |
16
- | [what-is-reticle.mdx](what-is-reticle.mdx) | the definition, what Reticle is NOT, and what it needs to run |
17
- | [why-reticle.mdx](why-reticle.mdx) | the false-green problem, the measured case, and where we lose |
16
+ | [what-is-reticle.mdx](what-is-reticle.mdx) | the definition, where it fits beside other tools, and what it needs to run |
17
+ | [why-reticle.mdx](why-reticle.mdx) | the false-green problem, the measured case, and what pairs well with it |
18
+ | [use-cases.mdx](use-cases.mdx) | what people verify with it: agent-built changes, security behaviour, accessibility, performance, SEO basics, personas |
18
19
  | [install-agentic.mdx](install-agentic.mdx) | what `npx @reticlehq/server init` writes, and how to read its marks |
19
20
  | [install-manual.mdx](install-manual.mdx) | wiring the MCP server and SDK by hand, per agent and framework |
20
21
  | [skill-setup.md](skill-setup.md) | the setup half of SKILL.md: what to pass `init`, reading its report, and what to do when it cannot finish |
@@ -24,6 +25,7 @@ Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `d
24
25
  | [capabilities.mdx](capabilities.mdx) | coverage, storage, fake clocks, crawl, visual diff, network mocking |
25
26
  | [recipes.mdx](recipes.mdx) | worked examples for real situations, with the response each returned |
26
27
  | [actions.mdx](actions.mdx) | every action and its arguments, including `press` and its history |
28
+ | [environment.mdx](environment.mdx) | every environment variable Reticle reads, what it changes, and which process reads it |
27
29
  | [faq.mdx](faq.mdx) | production, frameworks, comparisons, and the honest limits |
28
30
  | [tools/overview.mdx](tools/overview.mdx) | the 18 advertised tools, the 30 in the cold tail, and why |
29
31
  | [tools/snapshot.mdx](tools/snapshot.mdx) | `reticle_look { action: "page" }`: three modes, from full tree to a 25-token route check |
package/docs/actions.mdx CHANGED
@@ -5,7 +5,7 @@ description: Every action reticle_act and reticle_act_and_wait can perform, with
5
5
  icon: hand-pointer
6
6
  ---
7
7
 
8
- Every action Reticle can perform takes the same shape: a `ref` from [`reticle_snapshot`](/tools/snapshot) or [`reticle_query`](/tools/query), an `action` name, and an optional `args` object. The same seventeen actions are available on [`reticle_act`](/tools/act), which proves nothing, and on [`reticle_act_and_wait`](/tools/act-and-wait), which returns a verdict.
8
+ Every action Reticle can perform takes the same shape: a `ref` from [`reticle_look { action: "page" }`](/tools/snapshot) or [`reticle_look { action: "find" }`](/tools/query), an `action` name, and an optional `args` object. The same actions are available on [`reticle_act`](/tools/act), which proves nothing, and on [`reticle_act_and_wait`](/tools/act-and-wait), which returns a verdict.
9
9
 
10
10
  ```json
11
11
  { "ref": "e104", "action": "fill", "args": { "value": "reticle" } }
@@ -20,13 +20,12 @@ Every action Reticle can perform takes the same shape: a `ref` from [`reticle_sn
20
20
  | `type` | `{ text }` | **Appends** to the current value |
21
21
  | `clear` | none | Empties an input |
22
22
  | `select` | `{ value }` | For `<select>` |
23
- | `check` / `uncheck` | none | Checkbox and radio |
23
+ | `check` / `uncheck` | none | Checkbox and radio, or a `role="checkbox"`/`role="switch"` element (fails if `aria-checked` does not change) |
24
24
  | `submit` | none | Submits the element's `<form>` |
25
25
  | `press` | `{ text }` | The key **name**: `Escape`, `Tab`, `Enter` |
26
26
  | `scrollIntoView` | none | |
27
27
  | `upload` | `{ content?, name?, type?, path? }` | Sets a file on `<input type="file">`. Inline bytes default to `"reticle test file"` / `"file.txt"` / `"text/plain"`. `path` reads a file from disk, scoped to the project root plus any opt-in directories listed in `.reticle.json` as `"uploadRoots": ["~/Downloads", "/Volumes/fixtures"]`. A refusal names every allowed root. |
28
28
  | `drag` | `{ toRef }` | Pointer-based drag (dnd-kit, react-beautiful-dnd) plus HTML5 DnD. `target` is an alias |
29
- | `webmcp` | `{ tool, params }` | Calls a `navigator.modelContext` tool, if the site exposes one |
30
29
 
31
30
  ### Arguments that are not actions
32
31
 
@@ -93,7 +92,7 @@ Several actions in one round trip:
93
92
  ```
94
93
 
95
94
  <Warning>
96
- [`reticle_act_sequence`](/tools/act-sequence) returns `ok: true` when the steps were
95
+ [`reticle_act { steps: [...] }`](/tools/act-sequence) returns `ok: true` when the steps were
97
96
  **dispatched**, not when they worked. Batch the setup, then prove the outcome with
98
97
  [`reticle_act_and_wait`](/tools/act-and-wait) on the final step.
99
98
  </Warning>
@@ -4,7 +4,7 @@ description: 'One screen to get fluent: the look → act → observe → assert
4
4
  icon: bolt
5
5
  ---
6
6
 
7
- The loop is **look → act → observe → assert**, and only `reticle_act_and_wait` and `reticle_assert` produce a verdict. Reach for `reticle_act_and_wait({ ref, action, until })` first: it names the expected consequence before the action, which is the difference between a check and a rationalisation. `verified: "unknown"` is not a pass.
7
+ The loop is **look → act → observe → assert**, and verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`. Reach for `reticle_act_and_wait({ ref, action, until })` first: it names the expected consequence before the action, which is the difference between a check and a rationalisation. `verified: "unknown"` is not a pass.
8
8
 
9
9
  One screen to get fluent. Reticle is the **proof layer for AI agents**: no screenshots, no vision model, evidence not prose. Everything below returns structured data. Full guide: [usage.md](usage.md).
10
10
 
@@ -17,7 +17,7 @@ One screen to get fluent. Reticle is the **proof layer for AI agents**: no scree
17
17
  | **observe** | `reticle_observe` / `reticle_assert { action: "wait" }` | Everything the app did after `since` / block until true. |
18
18
  | **assert** | `reticle_assert` | Evaluate a predicate → `{ pass, evidence, failureReason? }`. The end of every loop. |
19
19
 
20
- > **Only `reticle_act_and_wait` and `reticle_assert` produce a verdict.** Everything else moves or reads the app and proves nothing, so a drive ending without one of those two has no result however many tools it used. `reticle_act` is the tool agents reach for by habit and it catches nothing; `act_and_wait` is where defects actually surface. And `verified: "unknown"` is not a pass: it means Reticle drove the app and could not tell what happened. Report it as unknown.
20
+ > **Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`.** Everything else moves or reads the app and proves nothing, so a drive ending without a verdict has no result however many tools it used. `reticle_act` is the tool agents reach for by habit and it catches nothing; `act_and_wait` is where defects actually surface. And `verified: "unknown"` is not a pass: it means Reticle drove the app and could not tell what happened. Report it as unknown.
21
21
 
22
22
  `reticle_act` returns a `since` cursor; pass it to `reticle_observe({ since })` to scope the window. Elements are addressed by stable refs (`e7`) from `snapshot`/`query`; they re-resolve across re-renders.
23
23
 
@@ -125,9 +125,9 @@ Call an old name and you are told the new one. `reticle_tools` prints the live s
125
125
 
126
126
  - `reticle_verify`: **the only core tool that CONCLUDES.** `{ action:"crawl" }` drives every reachable control itself and reports the whole fault set in one call (destructive: it really clicks). `{ action:"flows" }` replays every saved flow with no model in the loop. Reach for it when you think you are done: every other tool here answers "here is more to look at".
127
127
 
128
- Not on this surface at all, and NOT reachable by any call from it: `reticle_capabilities` (the app's whole testable surface in one call), `reticle_domain` (learn the app + gaps), `reticle_baseline {action:"diff"}`, `reticle_project` (run history), `reticle_screenshot`, `reticle_visual_diff`, `reticle_storage`, `reticle_network_mock`, `reticle_clock`. They need a daemon started with `RETICLE_ADVERTISE_ALL_TOOLS=1`, which advertises the full table.
128
+ Not on this surface at all, but one hop away: `reticle_run { tool, args }` calls any registered tool by name, advertised or not, and `reticle_tools { names: [...] }` loads its arguments. That reaches `reticle_capabilities` (the app's whole testable surface in one call), `reticle_domain` (learn the app + gaps), `reticle_baseline {action:"diff"}`, `reticle_project` (run history), `reticle_screenshot`, `reticle_visual_diff`, `reticle_storage`, `reticle_network_mock` and `reticle_clock`. A daemon started with `RETICLE_ADVERTISE_ALL_TOOLS=1` advertises them directly instead.
129
129
 
130
- **What the wider surface adds, if you start the daemon with `RETICLE_ADVERTISE_ALL_TOOLS=1`:** record/replay a journey (`reticle_record {action:"start"}/stop`, `reticle_replay`), persist a self-healing golden flow (`reticle_flow_save*` / `reticle_flow_replay` / `reticle_verify { action: "heal" }`), compile annotations (`reticle_annotate`), explore autonomously (`reticle_explore` lists controls; `reticle_verify {action:"crawl"}` is now advertised directly, clicks them all and reports anomalies, and is **destructive**), reveal a virtualized off-screen row (`reticle_scroll_to`, for when `reticle_look { action: "find" }` finds nothing because a windowed list hasn't rendered it yet), visual-check (`reticle_screenshot` / `reticle_visual_diff`, pinned with `reticle_viewport` for reproducible baselines), test error/edge states by stubbing the network (`reticle_network_mock`: 500 / offline / delay, driven or leased), control time for toasts/debounces/auto-dismiss (`reticle_clock { freeze | advanceMs | reset }`), or work with a human (`reticle_session {action:"end"}` / `reticle_session {action:"resume"}` / `reticle_session {action:"messages"}`, and **`reticle_session {action:"review"}`** to drain + fix the bugs the human flagged from the panel).
130
+ **What the wider surface adds, through `reticle_run` or advertised directly with `RETICLE_ADVERTISE_ALL_TOOLS=1`:** record/replay a journey (`reticle_record {action:"start"}/stop`, `reticle_replay`), persist a self-healing golden flow (`reticle_flow_save*` / `reticle_flow_replay` / `reticle_verify { action: "heal" }`), compile annotations (`reticle_annotate`), explore autonomously (`reticle_explore` lists controls; `reticle_verify {action:"crawl"}` is now advertised directly, clicks them all and reports anomalies, and is **destructive**), reveal a virtualized off-screen row (`reticle_scroll_to`, for when `reticle_look { action: "find" }` finds nothing because a windowed list hasn't rendered it yet), visual-check (`reticle_screenshot` / `reticle_visual_diff`, pinned with `reticle_viewport` for reproducible baselines), test error/edge states by stubbing the network (`reticle_network_mock`: 500 / offline / delay, driven or leased), control time for toasts/debounces/auto-dismiss (`reticle_clock { freeze | advanceMs | reset }`), or work with a human (`reticle_session {action:"end"}` / `reticle_session {action:"resume"}` / `reticle_session {action:"messages"}`, and **`reticle_session {action:"review"}`** to drain + fix the bugs the human flagged from the panel).
131
131
 
132
132
  ## flows vs baselines vs project.json (the persistence layers)
133
133