@reticlehq/server 3.1.0 → 3.3.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 (384) hide show
  1. package/SKILL.md +30 -28
  2. package/dist/command/cli/adhoc-suite.d.ts +19 -0
  3. package/dist/command/cli/adhoc-suite.js +88 -0
  4. package/dist/command/cli/adhoc-verdict.d.ts +23 -2
  5. package/dist/command/cli/adhoc-verdict.js +18 -10
  6. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  7. package/dist/command/cli/cli-doctor.js +37 -5
  8. package/dist/command/cli/cli-flow-commands.d.ts +2 -1
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  11. package/dist/command/cli/cli-parse-grammar.js +7 -0
  12. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  13. package/dist/command/cli/cli-parse-verify.js +86 -13
  14. package/dist/command/cli/cli-parse.d.ts +8 -1
  15. package/dist/command/cli/cli-parse.js +38 -7
  16. package/dist/command/cli/cli-verify.d.ts +59 -0
  17. package/dist/command/cli/cli-verify.js +131 -19
  18. package/dist/command/cli/cloud-cli.js +7 -6
  19. package/dist/command/cli/cloud-kit.d.ts +0 -2
  20. package/dist/command/cli/cloud-kit.js +3 -7
  21. package/dist/command/cli/config/config-discovery.d.ts +12 -0
  22. package/dist/command/cli/config/config-discovery.js +19 -0
  23. package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
  24. package/dist/command/cli/doctor/doctor-rows.js +12 -0
  25. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  26. package/dist/command/cli/drive/drive-command.js +1 -1
  27. package/dist/command/cli/launch/cli-launch.d.ts +3 -23
  28. package/dist/command/cli/launch/cli-launch.js +29 -70
  29. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
  30. package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
  31. package/dist/command/cli/mcp-command.js +5 -3
  32. package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
  33. package/dist/command/cli/ports/resolve/cli-port.js +50 -2
  34. package/dist/command/cli/report-command.d.ts +16 -0
  35. package/dist/command/cli/report-command.js +97 -0
  36. package/dist/command/cli/status/status-lines.d.ts +28 -0
  37. package/dist/command/cli/status/status-lines.js +117 -0
  38. package/dist/command/cli.d.ts +1 -1
  39. package/dist/command/cli.js +29 -177
  40. package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
  41. package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
  42. package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
  43. package/dist/command/daemon/binding/loopback-reach.js +63 -0
  44. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  45. package/dist/command/daemon/daemon-resilience.d.ts +13 -1
  46. package/dist/command/daemon/daemon-resilience.js +12 -1
  47. package/dist/command/daemon/daemon.d.ts +2 -11
  48. package/dist/command/daemon/daemon.js +8 -11
  49. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  50. package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
  51. package/dist/command/daemon/lifetime/heartbeat.js +10 -0
  52. package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
  53. package/dist/command/daemon/lifetime/log-cap.js +115 -0
  54. package/dist/command/setup/agent-notice-memory.d.ts +16 -0
  55. package/dist/command/setup/agent-notice-memory.js +65 -0
  56. package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
  57. package/dist/command/setup/bringup/dev-server-wait.js +16 -0
  58. package/dist/command/setup/bringup/ensure-daemon.js +1 -1
  59. package/dist/command/setup/init/init-host.js +8 -0
  60. package/dist/command/setup/init/init-runtime.js +13 -1
  61. package/dist/command/setup/node-effects.js +1 -1
  62. package/dist/command/setup/probe/page-probe.js +9 -2
  63. package/dist/command/setup/run-setup.d.ts +25 -7
  64. package/dist/command/setup/run-setup.js +51 -9
  65. package/dist/command/setup/setup-command.js +31 -3
  66. package/dist/command/setup/terminal/confirm.js +2 -1
  67. package/dist/command/update/updater.d.ts +27 -0
  68. package/dist/command/update/updater.js +35 -2
  69. package/dist/features/crawl/crawl-tools.js +3 -1
  70. package/dist/features/crawl/crawl.d.ts +2 -0
  71. package/dist/features/crawl/crawl.js +3 -1
  72. package/dist/features/harness/drive-report.d.ts +73 -0
  73. package/dist/features/harness/drive-report.js +196 -0
  74. package/dist/features/harness/driver.js +29 -8
  75. package/dist/features/harness/drivers.d.ts +19 -0
  76. package/dist/features/harness/drivers.js +19 -0
  77. package/dist/features/harness/fill-values.d.ts +67 -0
  78. package/dist/features/harness/fill-values.js +153 -0
  79. package/dist/features/harness/jev-driver.d.ts +188 -0
  80. package/dist/features/harness/jev-driver.js +661 -0
  81. package/dist/features/harness/openai-driver.d.ts +46 -0
  82. package/dist/features/harness/openai-driver.js +197 -0
  83. package/dist/features/harness/platform-config.d.ts +62 -0
  84. package/dist/features/harness/platform-config.js +97 -0
  85. package/dist/features/visual/visual-tools.js +26 -2
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.js +80 -130
  88. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  89. package/dist/judgement/capsule/capsule-store.js +58 -6
  90. package/dist/judgement/capsule/capsule.js +5 -0
  91. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  92. package/dist/judgement/capsule/causal-summary.js +7 -0
  93. package/dist/judgement/domain/domain-model.js +38 -9
  94. package/dist/judgement/domain/domain-tools.js +4 -2
  95. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  96. package/dist/judgement/phenomena/phenomena.d.ts +2 -1
  97. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  98. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  99. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  100. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  101. package/dist/judgement/runs/artifact/render-report.js +4 -1
  102. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  103. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  104. package/dist/judgement/runs/context-tools.js +21 -3
  105. package/dist/judgement/runs/drive-run.js +7 -2
  106. package/dist/judgement/runs/verification-sync.d.ts +2 -2
  107. package/dist/judgement/runs/verification-sync.js +9 -2
  108. package/dist/language/flows/affected-tools.js +6 -3
  109. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  110. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  111. package/dist/language/flows/flow-anchor.d.ts +12 -0
  112. package/dist/language/flows/flow-anchor.js +20 -0
  113. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  114. package/dist/language/flows/flow-expect-grammar.js +92 -11
  115. package/dist/language/flows/flow-intent.js +1 -1
  116. package/dist/language/flows/flow-learning.d.ts +3 -0
  117. package/dist/language/flows/flow-learning.js +39 -8
  118. package/dist/language/flows/flow-replay-run.d.ts +55 -10
  119. package/dist/language/flows/flow-replay-run.js +217 -58
  120. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  121. package/dist/language/flows/flow-replay.d.ts +29 -11
  122. package/dist/language/flows/flow-replay.js +221 -203
  123. package/dist/language/flows/flow-result.d.ts +18 -1
  124. package/dist/language/flows/flow-result.js +18 -2
  125. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  126. package/dist/language/flows/flow-step-runners.js +20 -4
  127. package/dist/language/flows/flow-store-for-session.d.ts +2 -1
  128. package/dist/language/flows/flow-success.d.ts +21 -6
  129. package/dist/language/flows/flow-success.js +50 -138
  130. package/dist/language/flows/flow-tools.js +8 -4
  131. package/dist/language/flows/flows.d.ts +15 -10
  132. package/dist/language/flows/flows.js +17 -4
  133. package/dist/language/flows/heal.d.ts +9 -1
  134. package/dist/language/flows/heal.js +24 -2
  135. package/dist/language/flows/learned-sources.d.ts +21 -0
  136. package/dist/language/flows/learned-sources.js +84 -0
  137. package/dist/language/flows/narrow-write.d.ts +4 -4
  138. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  139. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  140. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  141. package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
  142. package/dist/language/flows/replay.js +13 -9
  143. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  144. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  145. package/dist/language/flows/stores/flake-store.js +2 -3
  146. package/dist/language/flows/suite/coverage.d.ts +1 -1
  147. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  148. package/dist/language/flows/suite-selection.d.ts +15 -1
  149. package/dist/language/flows/suite-selection.js +1 -1
  150. package/dist/language/flows/verify-change-tools.js +21 -4
  151. package/dist/memory/cloud/account-state.d.ts +1 -1
  152. package/dist/memory/cloud/account-state.js +5 -4
  153. package/dist/memory/cloud/cloud-config.d.ts +9 -0
  154. package/dist/memory/cloud/cloud-config.js +9 -0
  155. package/dist/memory/cloud/cloud-sync.d.ts +7 -4
  156. package/dist/memory/cloud/cloud-sync.js +11 -6
  157. package/dist/memory/cloud/harness-config.d.ts +12 -0
  158. package/dist/memory/cloud/harness-config.js +58 -0
  159. package/dist/memory/cloud/harness-offer.d.ts +44 -0
  160. package/dist/memory/cloud/harness-offer.js +146 -0
  161. package/dist/memory/cloud/harness-switch.d.ts +11 -0
  162. package/dist/memory/cloud/harness-switch.js +53 -0
  163. package/dist/memory/cloud/sync-cycle.d.ts +12 -0
  164. package/dist/memory/cloud/sync-cycle.js +29 -4
  165. package/dist/memory/cloud/sync-daemon.d.ts +35 -2
  166. package/dist/memory/cloud/sync-daemon.js +45 -0
  167. package/dist/memory/cloud/sync-disk.js +11 -6
  168. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  169. package/dist/memory/impact/impact-recorder.js +2 -1
  170. package/dist/memory/impact/impact-store.d.ts +5 -0
  171. package/dist/memory/impact/impact-store.js +25 -0
  172. package/dist/memory/intent/inline-intent.js +8 -3
  173. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  174. package/dist/memory/intent/intent-shard-store.js +253 -141
  175. package/dist/memory/intent/intent-shard.d.ts +22 -23
  176. package/dist/memory/intent/intent-shard.js +1 -1
  177. package/dist/memory/intent/intent-store.d.ts +13 -8
  178. package/dist/memory/intent/intent-store.js +46 -90
  179. package/dist/memory/intent/intent-tools.js +34 -23
  180. package/dist/memory/journal/ambient-store.js +2 -3
  181. package/dist/memory/journal/attach-journal.d.ts +11 -0
  182. package/dist/memory/journal/attach-journal.js +1 -1
  183. package/dist/memory/journal/deviation-report.js +4 -1
  184. package/dist/memory/journal/deviation-service.js +10 -2
  185. package/dist/memory/journal/drive-flow.d.ts +2 -2
  186. package/dist/memory/journal/drive-flow.js +21 -12
  187. package/dist/memory/journal/envelope-store.js +2 -3
  188. package/dist/memory/journal/journal-query.d.ts +35 -1
  189. package/dist/memory/journal/journal-query.js +46 -0
  190. package/dist/memory/journal/journal-recorder.d.ts +71 -2
  191. package/dist/memory/journal/journal-recorder.js +45 -7
  192. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  193. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  194. package/dist/memory/journal/on-disk/retention.d.ts +91 -2
  195. package/dist/memory/journal/on-disk/retention.js +181 -11
  196. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  197. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  198. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
  199. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
  200. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  201. package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
  202. package/dist/memory/journal/route-template.d.ts +22 -0
  203. package/dist/memory/journal/route-template.js +44 -0
  204. package/dist/memory/journal/self-observation.d.ts +3 -0
  205. package/dist/memory/journal/self-observation.js +67 -0
  206. package/dist/memory/journal/session-end.d.ts +17 -4
  207. package/dist/memory/journal/session-end.js +85 -7
  208. package/dist/memory/journal/session-journal.d.ts +122 -5
  209. package/dist/memory/journal/session-journal.js +489 -32
  210. package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
  211. package/dist/memory/project/artifact-root-resolver.js +89 -0
  212. package/dist/memory/project/artifact-root.d.ts +39 -2
  213. package/dist/memory/project/artifact-root.js +58 -1
  214. package/dist/memory/project/baselines.d.ts +1 -1
  215. package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
  216. package/dist/memory/project/dir/fill-value-store.js +88 -0
  217. package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
  218. package/dist/memory/project/dir/reticle-dir.js +11 -0
  219. package/dist/memory/project/fs/fs-port.d.ts +22 -1
  220. package/dist/memory/project/fs/fs-port.js +72 -5
  221. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  222. package/dist/memory/project/fs/write-atomic.js +48 -0
  223. package/dist/memory/project/learned-routes.d.ts +16 -2
  224. package/dist/memory/project/learned-routes.js +26 -10
  225. package/dist/memory/project/project-for-root.d.ts +30 -0
  226. package/dist/memory/project/project-for-root.js +43 -0
  227. package/dist/memory/project/project-tools.js +10 -5
  228. package/dist/memory/project/session-root.d.ts +35 -6
  229. package/dist/memory/project/session-root.js +70 -8
  230. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  231. package/dist/memory/recall/prior/connection-memory.js +8 -0
  232. package/dist/memory/recall/registered-projects.d.ts +2 -1
  233. package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
  234. package/dist/portal/bridge/auth-failure-reason.js +20 -3
  235. package/dist/portal/bridge/bridge.d.ts +6 -1
  236. package/dist/portal/bridge/bridge.js +36 -2
  237. package/dist/portal/input/real-input.js +2 -1
  238. package/dist/portal/pool/browser-pool.d.ts +10 -0
  239. package/dist/portal/pool/browser-pool.js +10 -0
  240. package/dist/portal/pool/playwright-launcher.js +1 -0
  241. package/dist/portal/pool/pool-contract.d.ts +2 -0
  242. package/dist/portal/pool/storage-seed.d.ts +2 -1
  243. package/dist/portal/pool/zero-install.d.ts +27 -0
  244. package/dist/portal/pool/zero-install.js +54 -0
  245. package/dist/portal/realm/web-realm.js +6 -0
  246. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  247. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  248. package/dist/portal/session/dev-server/served-document.js +57 -0
  249. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  250. package/dist/portal/session/facts/background-traffic.js +25 -0
  251. package/dist/portal/session/facts/session-replaced.d.ts +19 -0
  252. package/dist/portal/session/facts/session-replaced.js +22 -0
  253. package/dist/portal/session/live-control-tools.js +48 -3
  254. package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
  255. package/dist/portal/session/no-session-diagnosis.js +67 -60
  256. package/dist/portal/session/no-session-next-action.js +3 -2
  257. package/dist/portal/session/no-session-watch.js +14 -10
  258. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  259. package/dist/portal/session/rebind.d.ts +18 -0
  260. package/dist/portal/session/rebind.js +38 -0
  261. package/dist/portal/session/session-health.d.ts +61 -0
  262. package/dist/portal/session/session-health.js +40 -1
  263. package/dist/portal/session/session-info.d.ts +6 -0
  264. package/dist/portal/session/session-manager.d.ts +13 -0
  265. package/dist/portal/session/session-manager.js +42 -3
  266. package/dist/portal/session/session-successor.d.ts +11 -0
  267. package/dist/portal/session/session-successor.js +14 -1
  268. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  269. package/dist/portal/session/session-verdict-facts.js +15 -0
  270. package/dist/portal/session/session.d.ts +26 -10
  271. package/dist/portal/session/session.js +48 -49
  272. package/dist/status-payload.d.ts +12 -1
  273. package/dist/status-payload.js +4 -1
  274. package/dist/surface/http-server.js +3 -5
  275. package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
  276. package/dist/surface/mcp/faults/mcp-outage.js +20 -4
  277. package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
  278. package/dist/surface/mcp/mcp-proxy.js +23 -12
  279. package/dist/surface/mcp/mcp.d.ts +10 -1
  280. package/dist/surface/mcp/mcp.js +59 -29
  281. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
  282. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
  283. package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
  284. package/dist/surface/mcp/proxy/proxy-log.js +7 -22
  285. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  286. package/dist/surface/mcp/server-instructions.js +12 -1
  287. package/dist/surface/tools/act/act-preflight.js +6 -1
  288. package/dist/surface/tools/act/already-true.d.ts +40 -0
  289. package/dist/surface/tools/act/already-true.js +36 -0
  290. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
  291. package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
  292. package/dist/surface/tools/act/sequence-grade.js +13 -0
  293. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  294. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  295. package/dist/surface/tools/act-capsule.js +49 -8
  296. package/dist/surface/tools/act-tools.js +25 -24
  297. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  298. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  299. package/dist/surface/tools/assert/assert-grade.js +14 -2
  300. package/dist/surface/tools/assert/assert-verdict.js +74 -21
  301. package/dist/surface/tools/browser-tools.js +4 -3
  302. package/dist/surface/tools/coverage-tools.js +5 -2
  303. package/dist/surface/tools/dynamic-tools.js +43 -46
  304. package/dist/surface/tools/error-recovery.d.ts +6 -0
  305. package/dist/surface/tools/error-recovery.js +15 -0
  306. package/dist/surface/tools/explore-tools.js +80 -7
  307. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  308. package/dist/surface/tools/feedback-tools.js +50 -3
  309. package/dist/surface/tools/first-sentence.d.ts +24 -0
  310. package/dist/surface/tools/first-sentence.js +0 -0
  311. package/dist/surface/tools/harness-explore.d.ts +125 -1
  312. package/dist/surface/tools/harness-explore.js +401 -15
  313. package/dist/surface/tools/harness-plan.d.ts +76 -0
  314. package/dist/surface/tools/harness-plan.js +156 -0
  315. package/dist/surface/tools/harness-toolset.js +12 -1
  316. package/dist/surface/tools/invoke-tool.js +82 -9
  317. package/dist/surface/tools/lease-hint.d.ts +8 -0
  318. package/dist/surface/tools/lease-hint.js +10 -0
  319. package/dist/surface/tools/lease-tools.d.ts +16 -28
  320. package/dist/surface/tools/lease-tools.js +53 -5
  321. package/dist/surface/tools/lineage-tools.js +1 -1
  322. package/dist/surface/tools/live-call-text.d.ts +6 -0
  323. package/dist/surface/tools/live-call-text.js +36 -2
  324. package/dist/surface/tools/observe-tools.js +69 -15
  325. package/dist/surface/tools/read/query-windowed.d.ts +1 -1
  326. package/dist/surface/tools/read/query-windowed.js +1 -1
  327. package/dist/surface/tools/read-tools.js +1 -2
  328. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  329. package/dist/surface/tools/real-input-attempt.js +1 -1
  330. package/dist/surface/tools/tool-kit.d.ts +23 -3
  331. package/dist/surface/tools/tool-kit.js +6 -1
  332. package/dist/surface/tools/tool-surface.js +17 -5
  333. package/dist/surface/tools/tools-helpers.d.ts +8 -7
  334. package/dist/surface/tools/tools-helpers.js +11 -13
  335. package/dist/surface/tools/tools.js +70 -30
  336. package/dist/telemetry/bug-found.js +32 -2
  337. package/dist/telemetry/feedback-context.js +3 -3
  338. package/dist/telemetry/feedback-local.d.ts +1 -0
  339. package/dist/telemetry/feedback-local.js +1 -3
  340. package/dist/telemetry/feedback.d.ts +1 -1
  341. package/dist/telemetry/harness-drive.d.ts +6 -0
  342. package/dist/telemetry/harness-drive.js +39 -0
  343. package/dist/telemetry/hud-metrics.d.ts +32 -0
  344. package/dist/telemetry/hud-metrics.js +95 -0
  345. package/dist/telemetry/mcp-connection.d.ts +14 -1
  346. package/dist/telemetry/mcp-connection.js +17 -2
  347. package/dist/telemetry/onboarding-firsts.d.ts +30 -1
  348. package/dist/telemetry/onboarding-firsts.js +54 -12
  349. package/dist/telemetry/session-metrics.d.ts +5 -0
  350. package/dist/telemetry/session-metrics.js +15 -1
  351. package/dist/telemetry/verification-of.js +31 -3
  352. package/dist/wire-journal.d.ts +31 -0
  353. package/dist/wire-journal.js +100 -0
  354. package/docs/README.md +1 -0
  355. package/docs/agent-cheatsheet.md +7 -3
  356. package/docs/autodrive.md +42 -2
  357. package/docs/benchmarks.md +1 -1
  358. package/docs/capabilities.mdx +1 -1
  359. package/docs/cli/cloud.mdx +11 -9
  360. package/docs/cli/doctor.mdx +1 -0
  361. package/docs/cli/init.mdx +1 -0
  362. package/docs/cli/report.mdx +44 -0
  363. package/docs/cli/status.mdx +35 -20
  364. package/docs/cli.mdx +8 -18
  365. package/docs/desktop.mdx +1 -1
  366. package/docs/faq.mdx +1 -1
  367. package/docs/flows.md +11 -0
  368. package/docs/frameworks.mdx +113 -1
  369. package/docs/install-agentic.mdx +36 -0
  370. package/docs/install-manual.mdx +1 -1
  371. package/docs/packages/tauri.mdx +1 -1
  372. package/docs/packages.mdx +1 -1
  373. package/docs/predicates.mdx +144 -4
  374. package/docs/system-map.md +6 -4
  375. package/docs/telemetry.md +2 -1
  376. package/docs/token-efficiency.md +2 -2
  377. package/docs/tools/overview.mdx +1 -1
  378. package/docs/tools/query.mdx +10 -10
  379. package/docs/troubleshooting.mdx +8 -2
  380. package/docs/usage.md +2 -2
  381. package/docs/what-is-recorded.md +62 -0
  382. package/package.json +5 -5
  383. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  384. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
package/docs/cli.mdx CHANGED
@@ -71,26 +71,16 @@ Exits `0`. That parenthetical about the bridge port is doing a lot of work. Mixi
71
71
 
72
72
  Whether the daemon is up and which apps have connected.
73
73
 
74
- ```json
75
- {
76
- "t": "2026-08-15T18:45:58.190Z",
77
- "event": "reticle_status",
78
- "port": 4400,
79
- "running": true,
80
- "pid": 72698,
81
- "sessionCount": 1,
82
- "sessions": [
83
- {
84
- "sessionId": "s223e0fad-e012-43f9-a23d-6d585470c90d",
85
- "url": "http://localhost:4310/",
86
- "throttled": false,
87
- "stale": false,
88
- "pendingMarks": 0
89
- }
90
- ]
91
- }
74
+ ```text
75
+ reticle status
76
+ daemon ✓ running on :4400 (pid 90790)
77
+ sessions ✓ 1 page connected
78
+ http://localhost:4310/
79
+ agent link enumerated
92
80
  ```
93
81
 
82
+ Add `--json` for the same facts as one machine-readable event line. See [`reticle status`](/cli/status) for the fields.
83
+
94
84
  Exits `0`. A session in the list is the install genuinely finished. Not a tick in a checklist, a connected app.
95
85
 
96
86
  ### `reticle telemetry [status|enable|disable]`
package/docs/desktop.mdx CHANGED
@@ -214,7 +214,7 @@ Alternatively, since an Electron renderer _is_ Chromium, `--remote-debugging-por
214
214
 
215
215
  ```toml
216
216
  [dependencies]
217
- reticle-tauri = "3.1"
217
+ reticle-tauri = "3.3"
218
218
  ```
219
219
 
220
220
  ```rust
package/docs/faq.mdx CHANGED
@@ -102,7 +102,7 @@ What is measured is detection: 10 of 10 injected regressions caught, with zero f
102
102
  <Accordion title="Why does my agent only see some of Reticle's tools?">
103
103
  Because MCP re-sends every advertised tool definition on **every turn**, so the tool list is rent, not a one-time cost. Reticle advertises the verify loop directly and keeps the cold tail one call away.
104
104
 
105
- `reticle_tools` lists every tool (pass `names: [...]` for full parameters), but on the default surface listing is all it does: that surface is closed and ships no `reticle_run`, so a name outside the nine cannot be called until the daemon advertises it. If you want the whole surface advertised directly, set `RETICLE_ADVERTISE_ALL_TOOLS=1` in the **daemon's** environment and restart it, which costs roughly seven times the per-turn schema tax and is meant for suites that call by name rather than for running agents.
105
+ `reticle_tools` lists every registered tool (pass `names: [...]` for full parameters), and `reticle_run { tool, args }` calls any of them by name, whether or not it is advertised. That hatch is what makes a trimmed surface honest: a tool it does not show is one hop away rather than out of reach. If you want the whole surface advertised directly, set `RETICLE_ADVERTISE_ALL_TOOLS=1` in the **daemon's** environment and restart it, which costs roughly seven times the per-turn schema tax and is meant for suites that call by name rather than for running agents.
106
106
 
107
107
  <Card title="The surface, explained" icon="wrench" href="/tools/overview">
108
108
  Which tools are advertised directly, and why each earned its slot.
package/docs/flows.md CHANGED
@@ -87,6 +87,16 @@ reticle_flow {action:"load"}({ flowName: "create-task" }) // → the flow JSON
87
87
  reticle_flow_replay({ flowName: "create-task" }) // re-resolve each anchor against the LIVE DOM, run it
88
88
  ```
89
89
 
90
+ ## Delete a flow
91
+
92
+ ```jsonc
93
+ reticle_flow {action:"delete"}({ flowName: "create-task" }) // → { deleted: true }
94
+ ```
95
+
96
+ A renamed or obsolete flow otherwise lingers in `reticle_flow {action:"list"}` and in every `reticle_verify {action:"flows"}` suite run, where it fails forever against a screen nobody intends to keep.
97
+
98
+ **Deleting a flow that is not there is an error, not a no-op.** It answers `{ error, code: "not_found" }` rather than `{ deleted: true }`, so a mistyped name cannot read as a completed cleanup while the real flow stays in the suite. Check the spelling against `reticle_flow {action:"list"}` and try again.
99
+
90
100
  **Watch it replay on the page.** When the presenter is on (`present: true`), a replay isn't silent. Each step drives the real page, so the synthetic cursor flies to the element, the focus ring lands, and the activity log streams the journey live. You (or a teammate) literally watch the saved journey re-walk itself on your app, then see the verdict land. It's the fastest way to _see_ that a flow still works, not just read a green checkmark.
91
101
 
92
102
  `reticle_flow_replay` returns a status:
@@ -164,6 +174,7 @@ With `apply: false` the flow file is **never modified**; you get the proposed di
164
174
  | `reticle_flow_save_recorded` | `{ flowName? }` | persist a human-recorded (toolbar) flow |
165
175
  | `reticle_flow {action:"list"}` | `{}` | flows on disk |
166
176
  | `reticle_flow {action:"load"}` | `{ flowName }` | the flow JSON |
177
+ | `reticle_flow {action:"delete"}` | `{ flowName }` | `{ deleted: true }`, or `{ error, code }` (`not_found` when no such flow) |
167
178
  | `reticle_flow_replay` | `{ flowName }` | `{ status, steps, decision? }` (decision on drift/fail) |
168
179
  | `reticle_verify {action:"flows"}` | `{ names?, sessionId? }` | suite verdict `{ status, passed, failed, failures[] }` |
169
180
  | `reticle_verify { action: "heal" }` | `{ flowName, apply? }` | propose / apply nearest-match rebind |
@@ -35,6 +35,7 @@ Two columns say different things and it is worth keeping them apart. **CI gate**
35
35
  | **Plain HTML** | yes | no | no | no | Wired, unverified |
36
36
  | **Vue (non-Nuxt)** | as Vite | no | install only | no | Install gated, drive unverified |
37
37
  | **Preact** | as Vite | no | no | unproven | Sensor, React adapter via `preact/compat` |
38
+ | **Server-rendered (Django, Flask, FastAPI, Streamlit, Rails)** | yes | no | no | no | Wired, unverified |
38
39
 
39
40
  <Warning>
40
41
  "Wired, unverified" means exactly that. The recipe exists and was written from a real integration,
@@ -85,7 +86,41 @@ module.exports = withReticle({/* your config */});
85
86
 
86
87
  ## Remix and Astro
87
88
 
88
- Both run on Vite, so both use the Vite plugin exactly as React does. Astro needs the React integration if you want component identity in `.jsx` islands. Static `.astro` markup gets DOM, network and routing without it.
89
+ Remix uses the Vite plugin with `inject: false`: Remix SSRs its own HTML, so the plugin's connect-injection does not fire, but the stamping half still puts `file:line` on a verdict.
90
+
91
+ Astro is the same on the plugin (wire it with `inject: false` if you want source pointers on JSX islands) and uses a different channel for the pairing token. `vite.define` does not reach Astro's client pipeline on 7.2+, so the identifier stays literal and the bridge refuses the dial. `init` reads the token in frontmatter and puts it on a `<meta name="reticle-pairing-token">`. Reading it per request also means a token written after the dev server started is picked up on the next load, with no restart.
92
+
93
+ Use a processed page `<script>` with a static SDK import. Vite owns that module graph from the first transform:
94
+
95
+ ```astro
96
+ ---
97
+ import { readFileSync } from 'node:fs';
98
+ import { homedir } from 'node:os';
99
+ import { join } from 'node:path';
100
+ const pairingToken = (() => {
101
+ if (!import.meta.env.DEV) return '';
102
+ const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
103
+ try { return readFileSync(join(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
104
+ })();
105
+ const pairingRoot = import.meta.env.DEV ? process.cwd() : '';
106
+ ---
107
+ <meta name="reticle-pairing-token" content={pairingToken} />
108
+ <meta name="reticle-pairing-root" content={pairingRoot} />
109
+ <script>
110
+ import { reticle, install } from '@reticlehq/react';
111
+ if (import.meta.env.DEV) {
112
+ const token = document.querySelector('meta[name="reticle-pairing-token"]')?.getAttribute('content') ?? '';
113
+ const root = document.querySelector('meta[name="reticle-pairing-root"]')?.getAttribute('content') ?? '';
114
+ install();
115
+ reticle.connect({
116
+ ...(0 < token.length ? { token } : {}),
117
+ ...(0 < root.length ? { root } : {}),
118
+ });
119
+ }
120
+ </script>
121
+ ```
122
+
123
+ The `<script>` tag must not sit inside a conditional. Astro hoists them statically, so `{isDev && (<script />)}` is never hoisted. `init` writes the same `<meta>` tags and a local `src/components/ReticleDev.ts` that the script statically imports. It also raises `vite.build.target` to `es2022` (Astro's default down-levels the SDK), declares the SDK in `optimizeDeps.include`, and keeps `.reticle/` out of the watcher. Static `.astro` markup still gets DOM, network and routing without the React integration.
89
124
 
90
125
  ## SvelteKit
91
126
 
@@ -205,6 +240,83 @@ The same shape works for webpack, Parcel, and the Vue and Svelte CLIs.
205
240
 
206
241
  Keep it out of your production template. Reticle self-disables when the build reports `NODE_ENV=production`, but a second lock on a door leading to your users is a reasonable number.
207
242
 
243
+ ## Server-rendered apps (Django, Flask, FastAPI, Rails)
244
+
245
+ There is no JS build to import the SDK from, and no single template to paste a script tag into: an app has many templates and may not have a base one. The SDK loads from a URL instead, so a page with no build step is not a blocker.
246
+
247
+ `reticle init` detects these and prints the right thing for the stack it found, writing `.reticle.json` as it goes. What follows is the same wiring, written down so you can read it before running anything, or wire it by hand.
248
+
249
+ ### Django
250
+
251
+ Middleware is the Django-shaped answer: one place, runs for every page, no template to find.
252
+
253
+ ```python
254
+ # reticle_dev.py: add to MIDDLEWARE while DEBUG is on:
255
+ # MIDDLEWARE = [..., "reticle_dev.ReticleDevMiddleware"]
256
+ from django.conf import settings
257
+
258
+ _SNIPPET = """<script type="module">
259
+ import { reticle } from 'https://cdn.jsdelivr.net/npm/@reticlehq/browser@<version>/+esm';
260
+ reticle.connect({ projectId: '<your-project-id>', token: '<pairing token>' });
261
+ </script>"""
262
+
263
+
264
+ class ReticleDevMiddleware:
265
+ """Inject the Reticle SDK into HTML responses during development only."""
266
+
267
+ def __init__(self, get_response):
268
+ self.get_response = get_response
269
+
270
+ def __call__(self, request):
271
+ response = self.get_response(request)
272
+ if not settings.DEBUG:
273
+ return response
274
+ if "text/html" not in response.get("Content-Type", ""):
275
+ return response
276
+ # Streaming responses have no .content to rewrite; leave them alone.
277
+ if getattr(response, "streaming", False):
278
+ return response
279
+ body = response.content.decode(response.charset)
280
+ if "</body>" not in body:
281
+ return response
282
+ response.content = body.replace("</body>", _SNIPPET + "</body>", 1).encode(
283
+ response.charset
284
+ )
285
+ if response.has_header("Content-Length"):
286
+ response["Content-Length"] = str(len(response.content))
287
+ return response
288
+ ```
289
+
290
+ `reticle init` emits this with your real version, project id and pairing token filled in. Copy it from there rather than from this page: the placeholders above are the three things that have to be right.
291
+
292
+ Four details in that middleware are load-bearing, and each is there because the obvious version is wrong:
293
+
294
+ - **`settings.DEBUG`** is the guard. Reticle is dev-only, and this is the lock that cannot be forgotten at deploy time.
295
+ - **the `text/html` check** keeps it from corrupting a JSON API response or a file download.
296
+ - **the `streaming` check** exists because a `StreamingHttpResponse` has no `.content` to rewrite, and touching it raises.
297
+ - **`</body>`, not `<head>`** so the SDK sees a parsed document.
298
+
299
+ Re-setting `Content-Length` matters for the same reason: the body grew, and a stale length truncates the page in the browser rather than failing loudly.
300
+
301
+ ### Flask and FastAPI with Jinja
302
+
303
+ Same shape, one hook instead of a middleware class: an `@app.after_request` in Flask, or a middleware in FastAPI, with the same four guards: a debug flag, `text/html` only, not streaming, and injected before `</body>`.
304
+
305
+ ### Streamlit
306
+
307
+ Different again: Streamlit owns the top-level document, so there is no served template, and scripts passed to `st.markdown` do not run. Use `st.html` on Streamlit 1.63 or newer. `reticle init` prints the helper, which also guards against connecting twice across reruns.
308
+
309
+ ### What you get, and what you do not
310
+
311
+ Everything that comes from reading the live page works: queries, snapshots, actions, network and console channels, verdicts.
312
+
313
+ What is missing is the **source pointer**. `file:line` on a verdict comes from the build plugin stamping `data-reticle-source`, and there is no build step here to stamp it. You get every other signal and no `source` field, the same way a Vue single-file component does.
314
+
315
+ <Warning>
316
+ Wired, unverified. No app in CI drives one of these to a verdict, so nothing will tell us if it
317
+ breaks. If a page does not register a session, that is worth reporting.
318
+ </Warning>
319
+
208
320
  ## Desktop is a different shape
209
321
 
210
322
  Electron and Tauri invert the direction: your app dials the daemon, and there is no URL for an agent to open. Reticle also reaches the main-process and Rust IPC boundary, which the renderer cannot see.
@@ -150,6 +150,42 @@ Four files in your project, none of them mysterious.
150
150
  ```
151
151
  The `projectId` is how Reticle scopes sessions to this project, so two apps running at once
152
152
  don't get confused for each other.
153
+
154
+ **Keeping `.reticle/` small.** Reticle bounds the directory for you, by count and by total size.
155
+ Add a `retain` block to change what it keeps:
156
+
157
+ ```json
158
+ {
159
+ "retain": { "sessions": 20, "visual": 10, "feedback": 20, "budgetMb": 512 }
160
+ }
161
+ ```
162
+
163
+ Those are the defaults, and every field is optional. `sessions` is how many session journals
164
+ survive, `visual` how many overlay diffs (pixel baselines are never deleted, because the next
165
+ comparison needs them), `feedback` how many local copies of reports already sent, and `budgetMb`
166
+ the total the whole evidence tier may occupy. `0` means keep none, and `"sessions": 0` also stops
167
+ journals being written in the first place.
168
+
169
+ Saved flows, capsules, baselines, the contract and the intent ledger are not touched by any of
170
+ this. They are the part meant to be committed, and a bound that deleted them would be deleting
171
+ the regression checks your team shares.
172
+
173
+ **Your app's own background calls.** An analytics or heartbeat endpoint on your app's own host
174
+ looks exactly like the app's work, so Reticle never guesses. If one of them keeps a verdict
175
+ waiting, or fails and gets blamed on an unrelated action, name it:
176
+
177
+ ```json
178
+ {
179
+ "background": ["/api/analytics/events", "/api/heartbeat"]
180
+ }
181
+ ```
182
+
183
+ Each entry is matched the way `urlContains` is. A listed request stops holding the wait open and
184
+ stops contradicting a claim; when a contradiction is still reported, it lists what was set aside.
185
+ It is never hidden from an assertion that names it: `net { urlContains: "/api/heartbeat" }` still
186
+ sees it. Keep entries specific: `/api` would set aside your whole backend.
187
+ Third-party calls (another site's beacon or SDK) need no entry; they are already set aside.
188
+
153
189
  </Accordion>
154
190
 
155
191
  <Accordion title="vite.config.ts (the plugin)">
@@ -175,7 +175,7 @@ Screenshots and headless mode need the Rust crate, which is versioned independen
175
175
 
176
176
  ```toml
177
177
  [dependencies]
178
- reticle-tauri = "3.1"
178
+ reticle-tauri = "3.3"
179
179
  ```
180
180
 
181
181
  ```rust
@@ -21,7 +21,7 @@ Tauri IPC observation needs nothing on the Rust side. An `invoke('load_todos')`
21
21
 
22
22
  ```toml
23
23
  [dependencies]
24
- reticle-tauri = "3.1"
24
+ reticle-tauri = "3.3"
25
25
  ```
26
26
 
27
27
  Its own dependencies are `tauri = "2"` plus the per-platform capture backends: `objc2` and friends on macOS, `webview2-com` and `windows` on Windows, `webkit2gtk` and `cairo-rs` on Linux and the BSDs.
package/docs/packages.mdx CHANGED
@@ -239,7 +239,7 @@ A lint rule makes the signal layer self-enforcing, which is the only way a conve
239
239
 
240
240
  ```toml
241
241
  [dependencies]
242
- reticle-tauri = "3.1"
242
+ reticle-tauri = "3.3"
243
243
  ```
244
244
 
245
245
  <Warning>
@@ -48,7 +48,17 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
48
48
  }
49
49
  ```
50
50
 
51
- `query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `scope`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `present`.
51
+ `query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `value`, `by`, `component`, `source`, `scope`, `self`, `attrs`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `inViewport`, `present`.
52
+
53
+ `value` is the one worth knowing about. `{ "role": "textbox", "name": "Amount" }` collapses to "a textbox called Amount exists", which passes against an empty field; adding `value` is what makes it an assertion about what the user will actually send. `by` selects the locator strategy explicitly, `component` and `source` match React component identity and source location, and `self` restricts a scoped match to the scope element itself rather than its descendants.
54
+
55
+ `attrs` is a list of attribute **names**, such as `["href", "data-status"]`. It **projects** those attributes onto each match so you can read them back, and it does not filter. `["data-status=complete"]` is refused: it would otherwise look up an attribute literally named `data-status=complete`, find none, and leave the predicate resting on the locator alone, which is a green for any value the element happens to hold.
56
+
57
+ To READ the values, call `reticle_query` with `attrs` and look at the `attrs` map on each match. To ASSERT one: a form field's value goes in the query as `value`, while a state expressed as a data attribute is better asserted as the consequence it stands for, such as a `signal`, a `net` call, or the text the user actually sees.
58
+
59
+ ```json
60
+ { "kind": "element", "query": { "role": "textbox", "name": "Amount", "value": "274.58" } }
61
+ ```
52
62
 
53
63
  Add `"absent": true` to assert something is **gone**. The predicate for a removal, a dismissed toast, or a regression check.
54
64
 
@@ -62,6 +72,26 @@ Add `"absent": true` to assert something is **gone**. The predicate for a remova
62
72
 
63
73
  Use `scope` to restrict the search to a CSS selector or element ref. If Reticle reports that the text is split across the scope's children, retry its generated predicate with `"self": true`; that checks the scope root's combined subtree text while still enforcing `contains`.
64
74
 
75
+ `satisfies` asserts a PROPERTY of the rendered text instead of its exact bytes: the same shape and the same properties [`state` accepts](#satisfies-asserting-a-property-instead-of-exact-bytes), applied where the user actually reads the value:
76
+
77
+ ```json
78
+ {
79
+ "kind": "text",
80
+ "scope": "[data-testid=\"release-note\"]",
81
+ "self": true,
82
+ "satisfies": { "property": "nonEmpty" }
83
+ }
84
+ ```
85
+
86
+ That is the assertion a generated feature can pass twice. `contains` demands bytes a model's output does not repeat, so it either fails on correct output or gets weakened to a word so common it proves nothing; "the box rendered and there is something in it" is the failure the feature actually has.
87
+
88
+ Two rules, both refusals rather than guesses:
89
+
90
+ - **One of `contains` or `satisfies` is required.** With neither, the predicate matches any element with any text and cannot fail.
91
+ - **`satisfies` on its own needs a `scope`** (the element whose text is read, with `self: true` for the root's own subtree). Without one the locator is every element on the page, and the property would run against whichever matched first.
92
+
93
+ Both may be given, and then both must hold. `satisfies` narrows, it never excuses.
94
+
65
95
  ### net
66
96
 
67
97
  ```json
@@ -76,6 +106,22 @@ The one that catches the expensive bug:
76
106
 
77
107
  **Exactly one.** Double-submit fails at two, and nothing on screen would have told you.
78
108
 
109
+ Two fields read the RESPONSE BODY, so a request that succeeded with the wrong number is still a failure:
110
+
111
+ ```json
112
+ { "kind": "net", "urlContains": "/api/refund", "bodyContains": "\"refunded\":11.87" }
113
+ ```
114
+
115
+ `bodyContains` is a substring test over the response body. It is deliberately not a JSON path: the string above is the whole assertion, it needs no schema, and it behaves the same on JSON, form encoding and plain text.
116
+
117
+ ```json
118
+ { "kind": "net", "urlContains": "/api/job", "bodyMatches": { "status": "completed" } }
119
+ ```
120
+
121
+ `bodyMatches` is a shallow field match, keyed like `signal.dataMatches` and taking the same operators (`*`, `$gte`, `$contains`). Reach for it whenever the value is an enum, because that is the case a substring cannot decide: `bodyContains: "completed"` passes on a body carrying `"completedAt": null`, where the needle matched the KEY and the job was still queued. `completed`/`completedAt`, `success`/`successRate`, `sent`/`unsent` all have that shape.
122
+
123
+ Both require body capture, `reticle({ captureNetworkBodies: true })`, and say so when the body was never recorded rather than reporting an ordinary mismatch.
124
+
79
125
  ### route
80
126
 
81
127
  ```json
@@ -158,6 +204,72 @@ Omit `equals` entirely to assert presence. A real `$length` check against this f
158
204
 
159
205
  Note `grade: state` in the `because` line, and that the evidence collapses each row to a size marker rather than returning forty objects.
160
206
 
207
+ #### satisfies: asserting a property instead of exact bytes
208
+
209
+ `equals` cannot express output that is right differently every run. A generated summary, a classification, a computed total: each is correct without being predictable, and an assertion that demands exact bytes either fails on correct output or gets weakened until it proves nothing. `satisfies` is the other half of `state`, and it stays decidable here with no model and no network.
210
+
211
+ ```json
212
+ { "kind": "state", "store": "app", "path": "summary.text", "satisfies": { "property": "nonEmpty" } }
213
+ ```
214
+
215
+ | `property` | Extra fields | Holds when |
216
+ | --- | --- | --- |
217
+ | `nonEmpty` | none | it produced something at all, the honest floor for any output |
218
+ | `oneOf` | `values` | it landed inside the allowed set, exact membership, no coercion |
219
+ | `withinTolerance` | `of`, `tolerance` | a number near enough to the expected one, inclusive on the bound |
220
+ | `matchesPattern` | `pattern` | its string form matches that regular expression |
221
+ | `type` | `is` | it is that kind of thing; `array` and `object` are distinguished, which `typeof` cannot do |
222
+
223
+ ```json
224
+ {
225
+ "kind": "allOf",
226
+ "predicates": [
227
+ {
228
+ "kind": "state",
229
+ "path": "invoice.total",
230
+ "satisfies": { "property": "withinTolerance", "of": 274.58, "tolerance": 0.01 }
231
+ },
232
+ {
233
+ "kind": "state",
234
+ "path": "invoice.status",
235
+ "satisfies": { "property": "oneOf", "values": ["draft", "sent"] }
236
+ },
237
+ { "kind": "state", "path": "invoice.lines", "satisfies": { "property": "type", "is": "array" } }
238
+ ]
239
+ }
240
+ ```
241
+
242
+ `equals` and `satisfies` may both be given, and then both must hold. A failing check always says what it saw, because an assertion that reports only `false` sends you back for another call to find out why, and that round trip is most of what a verdict costs.
243
+
244
+ #### The relative properties: comparing with before
245
+
246
+ The properties above read the present tense. Four read the past one. They compare this value with the reading taken **before the action**, which is the window a verdict is already scoped to:
247
+
248
+ | property | holds when |
249
+ | ----------- | --------------------------- |
250
+ | `unchanged` | the value is the one it was |
251
+ | `changed` | it is not |
252
+ | `increased` | the number went up |
253
+ | `decreased` | the number went down |
254
+
255
+ ```json
256
+ {
257
+ "kind": "state",
258
+ "path": "cart.total",
259
+ "satisfies": { "property": "decreased", "by": { "op": "equals", "value": 12 } }
260
+ }
261
+ ```
262
+
263
+ `increased` and `decreased` take an optional `by`, in the protocol's own comparison vocabulary: `op` is `equals`, `at-least` or `at-most`, with an optional `tolerance` on all three. Without `by`, any movement in the right direction holds.
264
+
265
+ **Give a money assertion a tolerance.** `100 - 88.13` is `11.870000000000005`, so `{ "op": "equals", "value": 11.87 }` misses by a rounding error and reads as the app charging the wrong amount. `"tolerance": 0.01` is the fix; the failure message says so when the miss is that small.
266
+
267
+ A number is read out of displayed text, so `"$1,234.50"` compares as `1234.5`. A reading with no digits in it is **not** zero. `"sold out"` reports that nothing was subtracted rather than pretending the total fell to nothing.
268
+
269
+ These only work where a before-reading was taken, which today is `reticle_act_and_wait`. Asked anywhere else they report `unknown` with "no before-reading was taken", never a failure, because nothing was compared and a `false` would blame the app for a reading nobody took. For the same reason a relative property is never `already_true`: `unchanged` is trivially true against a baseline taken a microsecond earlier, so the pre-action check skips it and lets the action decide.
270
+
271
+ A saved flow cannot keep one yet: `FlowExpect` has no slot for `satisfies`, and rather than record a weaker assertion than the one you made, the converter records none.
272
+
161
273
  A miss quotes the real value back at you:
162
274
 
163
275
  ```json
@@ -191,6 +303,33 @@ This is the predicate that catches a UI-versus-store desync. A deploy that only
191
303
 
192
304
  Network and DOM idle. This is the deterministic replacement for a fixed sleep, not a proof of anything: combine it with a real consequence rather than asserting it alone. Omitting `until` on [`reticle_act_and_wait`](/tools/act-and-wait) waits for exactly this.
193
305
 
306
+ ## Asserting a failure on purpose
307
+
308
+ A fault-injection test (mock a 500, check the error UI) is asserting that a request **fails**. Say so in the predicate, or the verdict will disagree with you:
309
+
310
+ ```json
311
+ {
312
+ "kind": "allOf",
313
+ "predicates": [
314
+ { "kind": "text", "contains": "Something went wrong" },
315
+ { "kind": "net", "method": "POST", "urlContains": "/api/save", "status": 503 }
316
+ ]
317
+ }
318
+ ```
319
+
320
+ Asserting only the error text is not enough, and this is the most common way to get a confusing `verified: "no"`. Reticle watches for "the UI moved forward while a request failed", a swallowed rejection, and one of the most expensive bugs it catches. A fault-injection test looks **exactly** like that bug from the outside: a request failed, the screen changed, nothing said that was the plan.
321
+
322
+ Naming the failing call is what tells the two apart. A `net` clause with `status` of 400 or more, or `"ok": false`, which is the honest field for IPC, where there is no status code, marks that call as expected to fail, and a failure there stops being a disagreement.
323
+
324
+ <Warning>
325
+ Declare it in the top level or inside `allOf`. A declaration inside `anyOf` is deliberately
326
+ ignored: only one branch of an `anyOf` has to hold, so honouring it would suppress a real
327
+ contradiction on the strength of a branch that never ran. `not` is ignored for the same reason in
328
+ reverse: `not { net status: 500 }` asserts the call did *not* fail.
329
+ </Warning>
330
+
331
+ One case is inferred for you: an `element` or `text` clause naming an access denial ("not authorised", "sign in to continue") marks the auth statuses as expected, because a denial screen is a correct outcome rather than a fault. Everything else has to be named.
332
+
194
333
  ## Combinators
195
334
 
196
335
  ```json
@@ -210,7 +349,8 @@ Network and DOM idle. This is the deterministic replacement for a fixed sleep, n
210
349
  ```
211
350
 
212
351
  <Warning>
213
- A combinator needs its own `kind`, and the children go in `predicates`. A bare
352
+ A combinator needs its own `kind`, and the children go in `predicates`, or in `of`, which is
353
+ accepted as an alias for it. `not` takes a single child in `predicate`, or in `of`. A bare
214
354
  `{ "allOf": [ … ] }` does not parse, and neither does `{ "kind": "allOf", "allOf": [ … ] }`.
215
355
 
216
356
  Reticle refuses the call rather than running half of it. The real refusal names the field, the fix and the consequence: _"predicates: Required; unknown field allOf. Nothing ran, the predicate was not evaluated, so no verdict was produced. allOf accepts: predicates."_ Better to be told than to get a green from an assertion that never executed.
@@ -249,9 +389,9 @@ On a failure each child reports separately too, so you can see which one broke:
249
389
  | Field | Where | What it does |
250
390
  | --- | --- | --- |
251
391
  | `timeout_ms` | `assert`, `wait_for`, `act_and_wait` | Wait up to N ms for it to become true |
252
- | `since` | `net` and `console` leaves, and the call itself | Only consider events after this cursor |
392
+ | `since` | `net`, `route`, `console`, `animation` and `signal` leaves, and the call itself | Only consider events after this cursor |
253
393
 
254
- The `since` cursor comes from a prior `reticle_act` response. Use it whenever you are asserting something an action was supposed to cause. Without it, an assertion can pass on an event from two clicks ago.
394
+ The `since` cursor comes from a prior `reticle_act` response. Use it whenever you are asserting something an action was supposed to cause. Without it, an assertion can pass on an event from two clicks ago. That applies to every one of the five kinds above, not only to network calls: an unscoped `signal` matches a fire from an earlier action just as readily.
255
395
 
256
396
  <Card title="Where predicates get used" icon="circle-check" href="/tools/act-and-wait">
257
397
  Naming the consequence before the action is the whole point.
@@ -56,11 +56,13 @@ Three different counts, all measured, and the difference between them matters:
56
56
 
57
57
  | Count | What it is |
58
58
  | --- | --- |
59
- | **68** | name constants in `ReticleTool` |
60
- | **48** | tools advertised under `RETICLE_ADVERTISE_ALL_TOOLS=1`, i.e. what `tool-surface-sweep-test` drives (51 calls) |
61
- | **19** | advertised by default; the rest are reached through `reticle_run` |
59
+ | **76** | name constants in `ReticleTool` |
60
+ | **30** | tools advertised under `RETICLE_ADVERTISE_ALL_TOOLS=1`, i.e. what `tool-surface-sweep-test` drives |
61
+ | **10** | advertised by default; the rest are reached through `reticle_run { tool, args }` |
62
62
 
63
- 68 → 48 is **family folding**: `reticle_baseline`, `reticle_session`, `reticle_record`, `reticle_flow` and `reticle_lease` each absorb their members behind an `action` parameter. So "every tool is callable" is asserted over 48 surfaces, not 68 behaviours; a family member reachable only through an `action` value the sweep never passes is not covered by it. Worth closing when the per-tool budget lands (see [`gate-plan.md`](https://github.com/reticlehq/reticle/blob/main/docs/gate-plan.md), Phase 4).
63
+ The middle number is **family folding**: `reticle_baseline`, `reticle_session`, `reticle_record`, `reticle_flow` and `reticle_lease` each absorb their members behind an `action` parameter. So "every tool is callable" is asserted over the advertised surfaces, not over every named behaviour; a family member reachable only through an `action` value the sweep never passes is not covered by it. Worth closing when the per-tool budget lands (see [`gate-plan.md`](https://github.com/reticlehq/reticle/blob/main/docs/gate-plan.md), Phase 4).
64
+
65
+ These three were each wrong by the time somebody checked: the table read 68 / 48 / 19 against a measured 76 / 30 / 10, and the middle one had a derived token cost hanging off it. `surface-sizes.test.ts` is the authority and fails when a surface changes size, so prefer reading it to trusting this row.
64
66
 
65
67
  | Token | Produced by | Consumed by | Invalidated by |
66
68
  | --- | --- | --- | --- |
package/docs/telemetry.md CHANGED
@@ -37,6 +37,7 @@ Eighteen kinds of events, each a single small JSON object. This is the whole lis
37
37
  | `runtime_crashed` | The daemon hits an uncaught error | The error's type, **Reticle's own** stack frames, and the message with variables stripped; see below |
38
38
  | `mcp_client_connected` | An agent attaches to the daemon | Whether it is a reconnect, and how long the daemon had been idle |
39
39
  | `mcp_connection_lost` | The agent's MCP tools go away | Which stage (`first`, or `budget_spent` when it stopped retrying), the cause, and the attempt count. **At most twice per session**, because a reconnect loop can fire many times in a row and an event each would bill for the pathology instead of measuring it |
40
+ | `onboarding_step` | One step of getting set up begins, finishes, fails, is skipped, or is walked away from | Which phase (`install`, `onboard`, `first_run`), which step, what became of it, how long it took, and the framework. The step name comes from a closed list we define -- it can only ever be one of the names in `core/src/onboarding.ts`, so nothing you typed or any path on your disk can arrive in it. `unattended` says whether anybody was asked anything |
40
41
  | `init_completed` | `reticle init` finishes | Whether it worked, a classified reason when it did not, and which published install route you came through, DECLARED by that route via `RETICLE_INSTALL_SOURCE` and never inferred, and `unknown` when nothing declared one |
41
42
  | `bug_found` | Reticle finds a defect in the app under test | The **kind** of defect (`signal-contradicted`, `console-error`, …) and how it was found, never what it was found in |
42
43
  | `tool_refused` | A tool cannot do what an agent asked | Which tool, why (one of six buckets: no session, no match, unsupported, bad arguments, not ready, other), and whether the same call was retried. **Never the message**, because a refusal interpolates whatever you asked for, so only the tool name and the bucket are sent. Capped at 50 per daemon run |
@@ -57,7 +58,7 @@ It also carries a snapshot of the **machine's** state (our own process's memory,
57
58
 
58
59
  It exists because `verified: "unknown"` covered seven different situations belonging to three different owners (your app, your agent, and Reticle's own blind spots), and they arrived as one value. It is a **name from our own vocabulary**, never a description of your app: `contradicted` says two channels disagreed, not which ones, about what, or on which page.
59
60
 
60
- When the clause is `unclean_capture`, one further name says which of our three losses caused it: `buffer_loss` (our server's event buffer), `transport_gap` (our browser-side queue), `blind_spot` (a boundary in the page, such as a cross-origin frame), or `other`. All four are facts about **Reticle's** ability to observe, not about what it observed. This field is how we found that Reticle was refusing to answer over windows that were completely intact.
61
+ When the clause is `unclean_capture`, one further name says which of our losses caused it: `buffer_loss` (our server's event buffer), `transport_gap` (our browser-side queue), `journal_loss` (our on-disk event ledger reached its size cap), `blind_spot` (a boundary in the page, such as a cross-origin frame), or `other`. All five are facts about **Reticle's** ability to observe, not about what it observed. This field is how we found that Reticle was refusing to answer over windows that were completely intact.
61
62
 
62
63
  ### Bugs Reticle finds
63
64
 
@@ -53,11 +53,11 @@ Measured live, all servers in one run, same tokenizer (`bench/harness/schema-tax
53
53
  | **Reticle, the tool surface** | 18 | **~4,930** |
54
54
  | Playwright MCP | 23 | 3,725 |
55
55
  | Chrome DevTools MCP | 29 | 5,116 |
56
- | Reticle, `RETICLE_ADVERTISE_ALL_TOOLS=1` | 48 | ~30,200 |
56
+ | Reticle, `RETICLE_ADVERTISE_ALL_TOOLS=1` | 30 | ~30,200 |
57
57
 
58
58
  _Measured 2026-08-12 (`bench/raw/schema-tax.json`). Reticle's default surface has gained a tool since, so treat the first row as a floor._
59
59
 
60
- The default surface is the verify loop plus `reticle_tools`, and it is CLOSED: those nine names are the whole of what can be called. There is no `reticle_run` dispatch hatch on it, so the cold tail is not merely un-advertised, it is unreachable until the daemon is started with `RETICLE_ADVERTISE_ALL_TOOLS=1`.
60
+ The default surface is the verify loop plus `reticle_tools` and `reticle_run`. The cold tail is un-advertised rather than unreachable: `reticle_run { tool, args }` calls any registered tool by name, and `RETICLE_ADVERTISE_ALL_TOOLS=1` advertises the whole table outright for suites that prefer that. Dropping the hatch was tried, and it left eleven registered tools callable by nothing at all.
61
61
 
62
62
  `RETICLE_ADVERTISE_ALL_TOOLS=1` advertises everything WITH output schemas. It is a verification switch for suites that call by name, not a mode to run agents in. It is roughly 7x the per-turn cost, which is why it is opt-in.
63
63
 
@@ -13,7 +13,7 @@ That is not a limitation, it's the design. MCP re-sends every advertised tool de
13
13
 
14
14
  A wider surface costs tokens on every message. Less obviously, it makes the model wander: give it fifty ways to look at a page and it will try several before doing anything useful.
15
15
 
16
- What the nine do **not** reach are the specialised tools: `reticle_screenshot`, `reticle_visual_diff`, `reticle_storage`, `reticle_network_mock`, `reticle_clock`. There is no dispatch hatch on this surface by design, so those need a daemon started with `RETICLE_ADVERTISE_ALL_TOOLS=1`.
16
+ What the verify loop does **not** advertise are the specialised tools: `reticle_screenshot`, `reticle_visual_diff`, `reticle_storage`, `reticle_network_mock`, `reticle_clock`. Each is one `reticle_run { tool, args }` hop away, and a daemon started with `RETICLE_ADVERTISE_ALL_TOOLS=1` advertises them outright.
17
17
 
18
18
  <Note>
19
19
  Measured off the real wire on 2026-09-16, by reading `tools/list` from a fresh daemon: the default
@@ -52,16 +52,16 @@ Seven buttons, each with a `ref` to act on and a source file to open, for 236 to
52
52
 
53
53
  ## Arguments
54
54
 
55
- | Argument | What it does |
56
- | ------------ | -------------------------------------------------------------------------- |
57
- | `by` | `role` · `text` · `label` · `placeholder` · `testid` · `alt` · `component` |
58
- | `value` | The query value for that strategy |
59
- | `name` | Accessible-name filter. Narrows a broad `role` query |
60
- | `scope` | CSS selector or ref, to search inside a subtree |
61
- | `limit` | Cap the descriptors returned. Cuts tokens hard on broad queries |
62
- | `count_only` | Return just `{ count }`, around 30x smaller |
63
- | `attrs` | Extra attributes per match, e.g. `['href']` to inventory links |
64
- | `self` | Return the `scope` element itself instead of searching inside it |
55
+ | Argument | What it does |
56
+ | --- | --- |
57
+ | `by` | `role` · `text` · `label` · `placeholder` · `testid` · `alt` · `component` |
58
+ | `value` | The query value for that strategy |
59
+ | `name` | Accessible-name filter. Narrows a broad `role` query |
60
+ | `scope` | CSS selector or ref, to search inside a subtree |
61
+ | `limit` | Cap the descriptors returned. Cuts tokens hard on broad queries |
62
+ | `count_only` | Return just `{ count }`, around 30x smaller |
63
+ | `attrs` | Attribute NAMES to project per match, e.g. `['href']` to inventory links. The values come back in each match's `attrs` map. It does not filter, so `name=value` is refused |
64
+ | `self` | Return the `scope` element itself instead of searching inside it |
65
65
 
66
66
  Every argument also has a predicate spelling, so `{ "role": "button" }` works as shorthand for `{ "by": "role", "value": "button" }`.
67
67
 
@@ -39,7 +39,7 @@ By far the most common. Reticle's own diagnostic is unusually good here. It tell
39
39
  ```json
40
40
  {
41
41
  "sessionCount": 0,
42
- "why": "no browser session connected, but one WAS connected to this daemon earlier, so the wiring is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen the app (or run `reticle open`), or reload the tab. You do not have to wait for the human: reticle_lease {action:\"acquire\", url} opens a browser Reticle drives itself, and returns a sessionId you can use immediately (reach it with reticle_run {tool:\"reticle_lease\"} if it is not advertised directly; release it when you finish). Then call reticle_session { action: "list" } again."
42
+ "why": "no browser session connected, but one WAS connected to this daemon earlier, so the wiring is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen the app (or run `reticle open`), or reload the tab. You do not have to wait for the human: reticle_lease {action:\"acquire\", url} opens a browser Reticle drives itself, and returns a sessionId you can use immediately (reach it with reticle_run {tool:\"reticle_lease\"} if it is not advertised directly; release it when you finish). Then call reticle_session { action: \"list\" } again."
43
43
  }
44
44
  ```
45
45
 
@@ -186,7 +186,7 @@ The usual cause is a throttled tab: throttling suppresses the quiescence detecti
186
186
  If it persists on an unthrottled context, that is worth reporting. Reticle's own response says so:
187
187
 
188
188
  ```json
189
- "feedback_invite": "Reticle could not tell what happened here. If you expected otherwise, reticle_session { action: "feedback" }. An unknown verdict is our defect, not yours"
189
+ "feedback_invite": "Reticle could not tell what happened here. If you expected otherwise, reticle_session { action: \"feedback\" }. An unknown verdict is our defect, not yours"
190
190
  ```
191
191
 
192
192
  ## A negative result you are not sure about
@@ -227,6 +227,12 @@ React 19 dropped `_debugSource`. Wire up `@reticlehq/babel-plugin`. Or just use
227
227
 
228
228
  `@reticlehq/next` declares `peer next >=13` and `@reticlehq/react` declares `peer react >=18`. On a Next canary or React RC, npm refuses. See [the prerelease notes](/getting-started#installing-alongside-a-nextjs-or-react-prerelease).
229
229
 
230
+ ## The tool list is empty
231
+
232
+ Some clients read their MCP tool list once, at startup, and never again. Cursor Cloud Agent is the usual report: the daemon is listening on port 4400, the app is instrumented, and `reticle_*` is absent for the whole session. Restarting the client picks the tools up when a restart is possible. When it is not, the same tools are on the HTTP transport.
233
+
234
+ `GET /mcp/sse` opens the session. The first SSE frame is an `endpoint` event whose data is the path to POST to, including the session id. POST JSON-RPC there. The POST answers `202` with an empty body, and the result comes back on the SSE stream. The handshake and a working client are in [HTTP transport](/http-transport).
235
+
230
236
  ## Restarting things
231
237
 
232
238
  ```bash