@reticlehq/server 2.8.0 → 2.9.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 (292) hide show
  1. package/SKILL.md +130 -813
  2. package/dist/bridge/bridge.d.ts +23 -0
  3. package/dist/bridge/bridge.js +80 -2
  4. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  5. package/dist/bridge/bridge.test-harness.js +6 -4
  6. package/dist/capsule/causal-summary.d.ts +16 -1
  7. package/dist/capsule/causal-summary.js +2 -1
  8. package/dist/cli/cli-doctor.js +41 -13
  9. package/dist/cli/cli-flow-commands.js +8 -8
  10. package/dist/cli/cli-launch.d.ts +0 -17
  11. package/dist/cli/cli-launch.js +0 -28
  12. package/dist/cli/cli-port.d.ts +24 -0
  13. package/dist/cli/cli-port.js +66 -35
  14. package/dist/cli/cli-verify.d.ts +21 -0
  15. package/dist/cli/cli-verify.js +45 -1
  16. package/dist/cli/config-discovery.d.ts +35 -0
  17. package/dist/cli/config-discovery.js +163 -0
  18. package/dist/cli/doctor-daemon-line.js +4 -1
  19. package/dist/cli/doctor-project-line.d.ts +37 -0
  20. package/dist/cli/doctor-project-line.js +35 -0
  21. package/dist/cli/doctor-rows.d.ts +51 -0
  22. package/dist/cli/doctor-rows.js +54 -0
  23. package/dist/cli/doctor-sessions-line.js +6 -3
  24. package/dist/cli/drive-attach.d.ts +99 -0
  25. package/dist/cli/drive-attach.js +203 -0
  26. package/dist/cli/drive-command.d.ts +13 -0
  27. package/dist/cli/drive-command.js +103 -0
  28. package/dist/cli/open-note.d.ts +20 -0
  29. package/dist/cli/open-note.js +37 -0
  30. package/dist/cli/status-next-action.d.ts +37 -0
  31. package/dist/cli/status-next-action.js +61 -0
  32. package/dist/cli.js +65 -70
  33. package/dist/events/contradictions.d.ts +32 -0
  34. package/dist/events/contradictions.js +9 -5
  35. package/dist/events/declared.d.ts +45 -0
  36. package/dist/events/declared.js +73 -0
  37. package/dist/events/echo-mismatch.d.ts +8 -2
  38. package/dist/events/echo-mismatch.js +33 -5
  39. package/dist/events/event-filters.d.ts +1 -1
  40. package/dist/events/event-filters.js +7 -4
  41. package/dist/events/predicate-eval.d.ts +17 -91
  42. package/dist/events/predicate-eval.js +85 -326
  43. package/dist/events/predicate-parse.js +31 -3
  44. package/dist/events/predicate-route.d.ts +32 -0
  45. package/dist/events/predicate-route.js +102 -0
  46. package/dist/events/predicate-schema.d.ts +99 -0
  47. package/dist/events/predicate-schema.js +309 -0
  48. package/dist/events/predicate.d.ts +8 -0
  49. package/dist/events/predicate.js +127 -5
  50. package/dist/flows/annotate-tools.js +3 -0
  51. package/dist/flows/annotate.js +18 -4
  52. package/dist/flows/coverage.d.ts +38 -5
  53. package/dist/flows/coverage.js +30 -4
  54. package/dist/flows/decision.d.ts +15 -0
  55. package/dist/flows/decision.js +1 -1
  56. package/dist/flows/flow-replay-run.js +8 -1
  57. package/dist/flows/flow-tools.js +9 -1
  58. package/dist/flows/replay.js +3 -0
  59. package/dist/honesty/accepted-write.d.ts +3 -0
  60. package/dist/honesty/accepted-write.js +8 -3
  61. package/dist/honesty/already-true.js +15 -0
  62. package/dist/honesty/blind-spots.js +4 -0
  63. package/dist/honesty/honesty.d.ts +11 -0
  64. package/dist/honesty/honesty.js +1 -0
  65. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  66. package/dist/honesty/uncaptured-bodies.js +9 -2
  67. package/dist/honesty/unread-outcome.d.ts +8 -0
  68. package/dist/honesty/unread-outcome.js +36 -9
  69. package/dist/honesty/unsettled.d.ts +9 -0
  70. package/dist/honesty/unsettled.js +7 -3
  71. package/dist/honesty/verified.d.ts +29 -3
  72. package/dist/honesty/verified.js +51 -8
  73. package/dist/http-server.d.ts +19 -0
  74. package/dist/http-server.js +202 -4
  75. package/dist/impact/impact-recorder.d.ts +23 -0
  76. package/dist/impact/impact-recorder.js +76 -0
  77. package/dist/impact/impact-store.d.ts +47 -0
  78. package/dist/impact/impact-store.js +171 -0
  79. package/dist/index.d.ts +7 -0
  80. package/dist/index.js +44 -10
  81. package/dist/init/agent-rules.d.ts +20 -0
  82. package/dist/init/agent-rules.js +36 -0
  83. package/dist/init/astro-patch.js +27 -1
  84. package/dist/init/confirm.d.ts +21 -3
  85. package/dist/init/confirm.js +32 -5
  86. package/dist/init/connect-steps.d.ts +2 -0
  87. package/dist/init/connect-steps.js +50 -0
  88. package/dist/init/csp-check.d.ts +38 -0
  89. package/dist/init/csp-check.js +92 -0
  90. package/dist/init/csp-doctor.d.ts +25 -0
  91. package/dist/init/csp-doctor.js +49 -0
  92. package/dist/init/cursor.js +6 -9
  93. package/dist/init/detect.d.ts +8 -0
  94. package/dist/init/detect.js +33 -3
  95. package/dist/init/mcp-clients.d.ts +1 -0
  96. package/dist/init/mcp-clients.js +51 -7
  97. package/dist/init/mcp.d.ts +15 -0
  98. package/dist/init/mcp.js +19 -0
  99. package/dist/init/plan-framework.d.ts +10 -0
  100. package/dist/init/plan-framework.js +25 -1
  101. package/dist/init/plan.d.ts +7 -2
  102. package/dist/init/plan.js +66 -30
  103. package/dist/init/run.js +11 -5
  104. package/dist/init/slash-command.d.ts +1 -1
  105. package/dist/init/slash-command.js +12 -4
  106. package/dist/init/snippets.d.ts +1 -1
  107. package/dist/init/snippets.js +35 -7
  108. package/dist/init/vite-config.js +24 -2
  109. package/dist/input/network-mock-tools.js +8 -1
  110. package/dist/input/scroll-find.d.ts +6 -0
  111. package/dist/input/scroll-find.js +17 -2
  112. package/dist/input/scroll-tools.js +4 -0
  113. package/dist/journal/attach-journal.d.ts +9 -1
  114. package/dist/journal/attach-journal.js +13 -2
  115. package/dist/journal/journal-query.d.ts +13 -0
  116. package/dist/journal/journal-query.js +28 -3
  117. package/dist/journal/workspace-gitignore.d.ts +10 -0
  118. package/dist/journal/workspace-gitignore.js +59 -0
  119. package/dist/mcp/mcp-proxy.d.ts +5 -66
  120. package/dist/mcp/mcp-proxy.js +180 -163
  121. package/dist/mcp/mcp.d.ts +10 -1
  122. package/dist/mcp/mcp.js +47 -27
  123. package/dist/mcp/proxy-handshake.d.ts +82 -1
  124. package/dist/mcp/proxy-handshake.js +109 -2
  125. package/dist/mcp/proxy-lifecycle.d.ts +10 -1
  126. package/dist/mcp/proxy-lifecycle.js +11 -2
  127. package/dist/mcp/proxy-log.d.ts +63 -0
  128. package/dist/mcp/proxy-log.js +132 -0
  129. package/dist/mcp/server-instructions.d.ts +17 -0
  130. package/dist/mcp/server-instructions.js +56 -0
  131. package/dist/mcp/shared-params.d.ts +27 -0
  132. package/dist/mcp/shared-params.js +36 -0
  133. package/dist/project/reticle-dir.d.ts +2 -0
  134. package/dist/project/reticle-dir.js +1 -0
  135. package/dist/session/connection-memory.d.ts +59 -0
  136. package/dist/session/connection-memory.js +108 -0
  137. package/dist/session/dev-command.d.ts +26 -0
  138. package/dist/session/dev-command.js +89 -0
  139. package/dist/session/dev-server-probe.d.ts +68 -0
  140. package/dist/session/dev-server-probe.js +102 -7
  141. package/dist/session/last-act.d.ts +26 -0
  142. package/dist/session/last-act.js +35 -1
  143. package/dist/session/live-control-tools.js +42 -5
  144. package/dist/session/no-session-diagnosis.d.ts +48 -2
  145. package/dist/session/no-session-diagnosis.js +176 -20
  146. package/dist/session/no-session-next-action.d.ts +46 -0
  147. package/dist/session/no-session-next-action.js +85 -0
  148. package/dist/session/no-session-watch.d.ts +40 -1
  149. package/dist/session/no-session-watch.js +165 -11
  150. package/dist/session/pending-commands.d.ts +12 -0
  151. package/dist/session/pending-commands.js +13 -1
  152. package/dist/session/session-manager.d.ts +4 -0
  153. package/dist/session/session-manager.js +35 -5
  154. package/dist/session/session.d.ts +10 -1
  155. package/dist/session/session.js +146 -5
  156. package/dist/telemetry/app-instrumented.d.ts +8 -0
  157. package/dist/telemetry/app-instrumented.js +10 -0
  158. package/dist/telemetry/bug-found.d.ts +18 -0
  159. package/dist/telemetry/bug-found.js +14 -0
  160. package/dist/telemetry/daemon-telemetry.js +24 -0
  161. package/dist/telemetry/install-source.d.ts +20 -0
  162. package/dist/telemetry/install-source.js +34 -0
  163. package/dist/telemetry/instrumentation-stall.d.ts +50 -0
  164. package/dist/telemetry/instrumentation-stall.js +90 -0
  165. package/dist/telemetry/session-metrics.d.ts +8 -0
  166. package/dist/telemetry/session-metrics.js +10 -0
  167. package/dist/telemetry/telemetry.d.ts +3 -1
  168. package/dist/telemetry/telemetry.js +13 -2
  169. package/dist/tools/act-tools.js +166 -29
  170. package/dist/tools/assert-verdict.js +32 -7
  171. package/dist/tools/browser-tools.js +10 -0
  172. package/dist/tools/dynamic-tools.js +16 -7
  173. package/dist/tools/error-recovery.js +9 -5
  174. package/dist/tools/invoke-tool.js +20 -3
  175. package/dist/tools/lease-hint.d.ts +30 -8
  176. package/dist/tools/lease-hint.js +65 -14
  177. package/dist/tools/lease-tools.d.ts +11 -0
  178. package/dist/tools/lease-tools.js +141 -96
  179. package/dist/tools/observe-tools.js +9 -3
  180. package/dist/tools/react-grace.d.ts +4 -0
  181. package/dist/tools/react-grace.js +6 -3
  182. package/dist/tools/read-tools.js +47 -1
  183. package/dist/tools/resolve-target.d.ts +33 -0
  184. package/dist/tools/resolve-target.js +53 -0
  185. package/dist/tools/settle-in-flight.d.ts +15 -0
  186. package/dist/tools/settle-in-flight.js +42 -2
  187. package/dist/tools/snapshot-delta.d.ts +3 -0
  188. package/dist/tools/snapshot-delta.js +67 -6
  189. package/dist/tools/tool-kit.d.ts +8 -0
  190. package/dist/tools/tool-surface.d.ts +60 -0
  191. package/dist/tools/tool-surface.js +85 -19
  192. package/dist/tools/tools.js +45 -4
  193. package/dist/tools/unadvertised-help.js +9 -6
  194. package/docs/README.md +1 -2
  195. package/docs/actions.mdx +41 -8
  196. package/docs/agent-cheatsheet.md +4 -2
  197. package/docs/architecture.md +6 -0
  198. package/docs/benchmarks.md +60 -12
  199. package/docs/best-practices.mdx +11 -6
  200. package/docs/capabilities.mdx +201 -54
  201. package/docs/cli/affected.mdx +6 -4
  202. package/docs/cli/capsules.mdx +9 -8
  203. package/docs/cli/cloud.mdx +30 -4
  204. package/docs/cli/doctor.mdx +41 -8
  205. package/docs/cli/drive.mdx +34 -15
  206. package/docs/cli/gate.mdx +6 -6
  207. package/docs/cli/help.mdx +8 -6
  208. package/docs/cli/hunt.mdx +18 -2
  209. package/docs/cli/identify.mdx +16 -2
  210. package/docs/cli/init.mdx +35 -8
  211. package/docs/cli/kill.mdx +5 -5
  212. package/docs/cli/license.mdx +1 -1
  213. package/docs/cli/open.mdx +6 -0
  214. package/docs/cli/restart.mdx +6 -6
  215. package/docs/cli/serve.mdx +10 -3
  216. package/docs/cli/status.mdx +29 -5
  217. package/docs/cli/stop.mdx +8 -0
  218. package/docs/cli/verify.mdx +20 -1
  219. package/docs/cli/version.mdx +1 -1
  220. package/docs/cli/watch.mdx +9 -2
  221. package/docs/cli.mdx +44 -22
  222. package/docs/debugging.md +3 -1
  223. package/docs/deploy-checks.md +30 -1
  224. package/docs/desktop.mdx +276 -80
  225. package/docs/docs.json +13 -12
  226. package/docs/enterprise.md +2 -0
  227. package/docs/faq.mdx +49 -35
  228. package/docs/fixtures.md +2 -0
  229. package/docs/flows.md +3 -1
  230. package/docs/for-agents.md +3 -3
  231. package/docs/frameworks.mdx +46 -7
  232. package/docs/gate-plan.md +4 -2
  233. package/docs/gates.md +7 -5
  234. package/docs/getting-started.md +53 -12
  235. package/docs/human-control.md +2 -0
  236. package/docs/index.mdx +43 -1
  237. package/docs/install-agentic.mdx +13 -4
  238. package/docs/install-manual.mdx +6 -4
  239. package/docs/instrumentation.mdx +7 -1
  240. package/docs/integration-patterns.md +3 -1
  241. package/docs/local-registry.md +4 -2
  242. package/docs/multi-agent-testing.md +3 -1
  243. package/docs/packages/babel-plugin.mdx +15 -6
  244. package/docs/packages/browser.mdx +2 -2
  245. package/docs/packages/core.mdx +26 -16
  246. package/docs/packages/electron.mdx +5 -7
  247. package/docs/packages/eslint-plugin.mdx +5 -3
  248. package/docs/packages/next.mdx +3 -5
  249. package/docs/packages/react.mdx +3 -3
  250. package/docs/packages/server.mdx +2 -2
  251. package/docs/packages/tauri.mdx +6 -6
  252. package/docs/packages/test.mdx +18 -7
  253. package/docs/packages/vite-plugin.mdx +4 -6
  254. package/docs/packages.mdx +14 -13
  255. package/docs/platform-integration.md +2 -0
  256. package/docs/predicates.mdx +76 -7
  257. package/docs/quickstart.mdx +51 -5
  258. package/docs/recipes.mdx +62 -22
  259. package/docs/skill-file.mdx +3 -1
  260. package/docs/state-management.mdx +2 -0
  261. package/docs/system-map.md +3 -1
  262. package/docs/telemetry-contract.md +16 -5
  263. package/docs/telemetry-events.mdx +63 -18
  264. package/docs/telemetry.md +8 -5
  265. package/docs/testing.md +35 -1
  266. package/docs/token-efficiency.md +8 -2
  267. package/docs/tools-act-and-wait.mdx +64 -14
  268. package/docs/tools-act-sequence.mdx +34 -17
  269. package/docs/tools-act.mdx +24 -11
  270. package/docs/tools-assert.mdx +69 -10
  271. package/docs/tools-console.mdx +11 -7
  272. package/docs/tools-inspect.mdx +11 -8
  273. package/docs/tools-navigate.mdx +41 -5
  274. package/docs/tools-network.mdx +42 -9
  275. package/docs/tools-observe.mdx +66 -10
  276. package/docs/tools-overview.mdx +26 -23
  277. package/docs/tools-query.mdx +52 -8
  278. package/docs/tools-session-and-feedback.mdx +30 -9
  279. package/docs/tools-sessions.mdx +39 -10
  280. package/docs/tools-snapshot.mdx +57 -3
  281. package/docs/tools-state.mdx +24 -1
  282. package/docs/tools-tools-and-run.mdx +40 -9
  283. package/docs/tools-wait-for.mdx +34 -15
  284. package/docs/troubleshooting.mdx +80 -10
  285. package/docs/usage.md +43 -21
  286. package/docs/vs-chrome-devtools-mcp.mdx +41 -0
  287. package/docs/vs-playwright-mcp.mdx +42 -2
  288. package/docs/vs-screenshots.mdx +42 -1
  289. package/docs/what-is-reticle.mdx +1 -1
  290. package/docs/why-reticle.mdx +3 -1
  291. package/package.json +5 -5
  292. package/docs/desktop-apps.md +0 -265
@@ -15,37 +15,48 @@ icon: hand-pointer
15
15
  ## Example
16
16
 
17
17
  ```json
18
- { "ref": "e102", "action": "fill", "args": { "value": "reticle" } }
18
+ { "ref": "e104", "action": "fill", "args": { "value": "reticle" } }
19
19
  ```
20
20
 
21
21
  Real response:
22
22
 
23
23
  ```json
24
24
  {
25
- "since": 28123,
25
+ "since": 27544,
26
26
  "inputMode": "synthetic",
27
+ "inputModeReason": "not-a-pointer-action",
27
28
  "dispatched": true,
28
- "settled": false,
29
- "settleReason": "timeout",
29
+ "settled": true,
30
+ "settleReason": null,
30
31
  "result": {
31
32
  "ok": true,
32
- "ref": "e102",
33
+ "ref": "e104",
33
34
  "action": "fill",
35
+ "dispatched": true,
36
+ "settled": true,
37
+ "settleReason": null,
34
38
  "testid": "login-password",
35
39
  "component": "Login",
36
40
  "role": "textbox",
37
41
  "name": "Password",
38
42
  "effect": {
39
- "visible": false,
40
- "focusMoved": "null->e102",
43
+ "focusMoved": "null->e104",
41
44
  "valueChanged": true,
42
- "domMutatedWithin": 15
45
+ "domMutatedWithin": 9
43
46
  },
44
47
  "source": { "file": "src/components/Login.tsx", "line": 59, "column": 8 }
45
48
  }
46
49
  }
47
50
  ```
48
51
 
52
+ <Note>
53
+ The `effect` block only lists what is worth knowing. Fields sitting at their uninformative default
54
+ are **omitted**, so a clean action collapses to its consequence. An absent `dispatched`,
55
+ `targetMatched`, `visible` or `enabled` means `true`; an absent `occluded`, `scrolledIntoView`,
56
+ `valueChanged` or `defaultPrevented` means `false`; an absent `focusMoved` or `occludedBy` means
57
+ `null`. Do not read a missing field as a failure.
58
+ </Note>
59
+
49
60
  ## Actions
50
61
 
51
62
  `click` · `dblclick` · `hover` · `focus` · `blur` · `fill` · `type` · `clear` · `select` · `check` · `uncheck` · `submit` · `press` · `upload` · `scrollIntoView` · `drag` · `webmcp`
@@ -62,19 +73,21 @@ Real response:
62
73
  This is where `act` earns its place even though it proves nothing.
63
74
 
64
75
  - **`valueChanged: true`**. The field actually took the value. `false` on a `fill` usually means the input is controlled and rejected it, or it's readonly.
65
- - **`focusMoved: "null->e102"`**, focus went where you expected.
66
- - **`domMutatedWithin: 15`**, something re-rendered within 15ms. A `fill` that mutates nothing often means no handler is attached.
76
+ - **`focusMoved: "null->e104"`**, focus went where you expected.
77
+ - **`domMutatedWithin: 9`**, something re-rendered within 9ms. A `fill` that mutates nothing often means no handler is attached.
67
78
  - **`defaultPrevented`**. A handler called `preventDefault()`, which is why your form didn't submit.
68
79
  - **`occluded` / `occludedBy`**. The click landed on something else. This is the answer to at least half of all "the button doesn't work" reports.
69
80
 
70
81
  ## `settled: false` is not a failure
71
82
 
72
- `settleReason: "timeout"` means the page was still busy when Reticle stopped watching. On a `fill` that's normal and uninteresting. On a click you expected to complete something, it's a hint that you wanted `act_and_wait` with an explicit `until`.
83
+ `settled: true` with `settleReason: null`, as above, means a real frame flushed before Reticle stopped watching. `settled: false` with `settleReason: "timeout"` means the page was still busy. On a `fill` that's normal and uninteresting. On a click you expected to complete something, it's a hint that you wanted `act_and_wait` with an explicit `until`. A settle timeout never fails the tool.
73
84
 
74
85
  ## `inputMode` tells you how real the input was
75
86
 
76
87
  `synthetic` means Reticle dispatched events programmatically. `real` means native CDP input, available under `reticle drive`. Synthetic input is fine for the overwhelming majority of apps; a few drag-and-drop and pointer-gesture libraries only respond to the real thing.
77
88
 
89
+ `inputModeReason` explains any choice Reticle made on your behalf, so it is never silent. `not-a-pointer-action` above means a `fill` has no pointer path to take, not that real input was unavailable. Clicks default to the occlusion-honest synthetic path even when CDP is configured; pass `args.native: true` to force a trusted native click for file pickers and clipboard access.
90
+
78
91
  <Warning>
79
92
  When the tab is backgrounded, synthetic timers and pointer gestures can silently no-op. Every
80
93
  response carries a `warning` when the tab is throttled. Pass `refuseWhenThrottled: true` to make
@@ -4,14 +4,16 @@ description: Evaluate a predicate against what the app actually did, and get a v
4
4
  icon: gavel
5
5
  ---
6
6
 
7
- `reticle_assert` is the second of the two tools that produce a verdict. It takes the same predicate grammar as [`reticle_act_and_wait`](/tools-act-and-wait) and evaluates it against what already happened.
7
+ `reticle_assert` evaluates a predicate against what the app actually did and returns a verdict: `verified` plus `pass`, evidence when it holds, and `expected` versus `observed` when it does not. Reach for it when you want a verdict on something you did not just cause, or on several actions at once.
8
+
9
+ It is the second of the two tools that produce a verdict, the other being [`reticle_act_and_wait`](/tools-act-and-wait). It takes the same predicate grammar and evaluates it against what already happened.
8
10
 
9
11
  ## A real failure
10
12
 
11
13
  Failures are more instructive than passes, so here is one. I asserted a route change to `/` on a page that was already at `/`:
12
14
 
13
15
  ```json
14
- { "predicate": { "kind": "route", "path": "/" } }
16
+ { "predicate": { "kind": "route", "pathname": "/" } }
15
17
  ```
16
18
 
17
19
  ```json
@@ -24,10 +26,20 @@ Failures are more instructive than passes, so here is one. I asserted a route ch
24
26
  "observed": "no route change in the window",
25
27
  "expected": "a route change to /",
26
28
  "assertion": "route.changed",
27
- "source": "src/components/Login.tsx:81"
29
+ "source": "src/components/Login.tsx:81",
30
+ "buffer": {
31
+ "held": 536,
32
+ "dropped": 11,
33
+ "note": "event buffer evicted older events (age/size cap): a negative result here may be a false negative; the evidence may have expired. Grade sooner or widen the buffer."
34
+ }
28
35
  }
29
36
  ```
30
37
 
38
+ <Note>
39
+ The route predicate's field is `pathname`. `path` is accepted as an alias and rewritten, but
40
+ `pathname` is what the published schema declares, so it is the one to write.
41
+ </Note>
42
+
31
43
  <Warning>
32
44
  Read `assertion: "route.changed"` carefully. The `route` predicate asserts that the route
33
45
  **changed**, not that it currently equals a value. Asserting the route you are already on will
@@ -37,20 +49,67 @@ Failures are more instructive than passes, so here is one. I asserted a route ch
37
49
 
38
50
  Note also `expected` and `observed` side by side. A verdict that says only "failed" makes you go digging; this one tells you what it wanted, what it saw, and where in your source the relevant element lives.
39
51
 
52
+ ## A failure that names the real value
53
+
54
+ The same shape on a store read, which is the strongest kind of failure you can get, because it quotes the app's own data back at you:
55
+
56
+ ```json
57
+ {
58
+ "predicate": { "kind": "state", "store": "app", "path": "deployments.0.status", "equals": "live" }
59
+ }
60
+ ```
61
+
62
+ ```json
63
+ {
64
+ "verified": "no",
65
+ "verifiedReason": "assertion_failed",
66
+ "because": "the declared consequence did not hold",
67
+ "pass": false,
68
+ "failureReason": "state 'deployments.0.status' is \"queued\", expected \"live\"",
69
+ "observed": "deployments.0.status = \"queued\"",
70
+ "expected": "deployments.0.status = \"live\"",
71
+ "assertion": "state.equals",
72
+ "evidence": { "store": "app", "path": "deployments.0.status", "value": "queued" },
73
+ "source": "src/components/Login.tsx:81"
74
+ }
75
+ ```
76
+
77
+ ## A pass can still come with a warning
78
+
79
+ A presence-only predicate passes and tells you not to trust it much:
80
+
81
+ ```json
82
+ {
83
+ "verified": "yes",
84
+ "pass": true,
85
+ "evidence": [
86
+ {
87
+ "ref": "e606",
88
+ "role": "alert",
89
+ "name": "Invalid email or password",
90
+ "visible": true,
91
+ "source": "src/components/Login.tsx:72",
92
+ "states": ["present", "visible", "enabled"]
93
+ }
94
+ ],
95
+ "advice": "This predicate only checks element/text presence, not an observable consequence. A locator healed to the wrong element (or a stale render) can satisfy it while the feature is broken. Prefer a { signal } or { net } assertion (or allOf it with one) so green means the feature actually worked."
96
+ }
97
+ ```
98
+
40
99
  ## Arguments
41
100
 
42
- | Argument | What it does |
43
- | ------------------------ | ---------------------------------------------------------- |
44
- | `predicate` (or `until`) | `{ kind, ...fields }` |
45
- | `timeout_ms` | If above zero, wait this long before failing |
46
- | `since` | Cursor from a prior act. Scope to events after that action |
101
+ | Argument | What it does |
102
+ | ------------------------ | ----------------------------------------------------------------- |
103
+ | `predicate` (or `until`) | `{ kind, ...fields }`. See the [predicate reference](/predicates) |
104
+ | `timeout_ms` | If above zero, wait this long before failing. Default `0` |
105
+ | `since` | Cursor from a prior act. Scope to events after that action |
47
106
 
48
107
  ## Scope it with `since`
49
108
 
50
- Without `since`, an assertion evaluates against the recent buffer, which may contain events from before your action. That can pass for the wrong reason. The request you are asserting fired, but it fired two clicks ago.
109
+ By default an assertion already counts only events since your last act, so a signal buffered before the action cannot fake a pass. Pass `since` when you want to set that window explicitly: to scope to one particular earlier action, or to widen it deliberately across several.
51
110
 
52
111
  ```json
53
- { "predicate": { "kind": "net", "method": "POST" }, "since": 28123 }
112
+ { "predicate": { "kind": "net", "method": "POST" }, "since": 44395 }
54
113
  ```
55
114
 
56
115
  The cursor comes from the preceding `reticle_act` response. Use it whenever you are asserting something an action was supposed to cause.
@@ -4,6 +4,8 @@ description: The console log, filtered by level, and an empty result that tells
4
4
  icon: terminal
5
5
  ---
6
6
 
7
+ `reticle_console` returns the app's console log, filtered by level, and tells you whether an empty result is a real reading or a missing one. Reach for it as the fast triage read after something goes wrong, and assert `{ "kind": "console", "absent": true }` when you want a clean console to be part of the pass.
8
+
7
9
  ```json
8
10
  { "limit": 3 }
9
11
  ```
@@ -14,13 +16,13 @@ Real response from a page with a clean console:
14
16
  {
15
17
  "logs": [],
16
18
  "buffer": {
17
- "held": 15,
18
- "dropped": 571,
19
+ "held": 535,
20
+ "dropped": 10,
19
21
  "note": "event buffer evicted older events (age/size cap): a negative result here may be a false negative; the evidence may have expired. Grade sooner or widen the buffer."
20
22
  },
21
23
  "observed": true,
22
24
  "note": "no console lines; the observation ran and found none, which is a result, not a missing reading",
23
- "cost": { "bytes": 340, "tokens": 85 }
25
+ "cost": { "bytes": 338, "tokens": 85 }
24
26
  }
25
27
  ```
26
28
 
@@ -29,9 +31,9 @@ Real response from a page with a clean console:
29
31
  An empty array is ambiguous in almost every tool that returns one. Did nothing happen, or did the observation not run? Reticle answers both:
30
32
 
31
33
  - **`observed: true`** with that `note`. The observation ran and genuinely found nothing. You may treat a clean console as a fact.
32
- - **`buffer.dropped: 571`**, older events were evicted. If you are asking about something from a while ago, the honest answer is "not in what I still have", not "it didn't happen".
34
+ - **`buffer.dropped: 10`**, older events were evicted. If you are asking about something from a while ago, the honest answer is "not in what I still have", not "it didn't happen".
33
35
 
34
- Read both fields together. `observed: true` with a large `dropped` count means _recently_ clean, which is usually what you wanted but is not the same as _always_ clean.
36
+ Read both fields together. `observed: true` with a non-zero `dropped` count means _recently_ clean, which is usually what you wanted but is not the same as _always_ clean.
35
37
 
36
38
  ## Arguments
37
39
 
@@ -47,7 +49,7 @@ Read both fields together. `observed: true` with a large `dropped` count means _
47
49
  Plenty of features "work" while quietly throwing. Assert the clean console as part of the action rather than checking afterwards:
48
50
 
49
51
  ```json
50
- { "ref": "e103", "action": "click", "until": { "kind": "console", "absent": true } }
52
+ { "ref": "e605", "action": "click", "until": { "kind": "console", "absent": true } }
51
53
  ```
52
54
 
53
55
  That fails if the click succeeds _and_ logs an error, which is exactly the case a human reviewer tends to wave through.
@@ -55,5 +57,7 @@ That fails if the click succeeds _and_ logs an error, which is exactly the case
55
57
  <Tip>
56
58
  `level: "error"` is the fast triage read. If it comes back empty with `observed: true`, stop
57
59
  looking at the console and go look at [`reticle_network`](/tools-network) or
58
- [`reticle_state`](/tools-state) instead.
60
+ [`reticle_state`](/tools-state) instead. That is a real capture from this fixture:
61
+ `{"logs":[],"observed":true,"cost":{"bytes":337,"tokens":85}}`, eighty-five tokens to rule out a
62
+ whole class of bug.
59
63
  </Tip>
@@ -9,14 +9,14 @@ icon: crosshairs
9
9
  ## Example
10
10
 
11
11
  ```json
12
- { "ref": "e20" }
12
+ { "ref": "e120" }
13
13
  ```
14
14
 
15
15
  Real response:
16
16
 
17
17
  ```json
18
18
  {
19
- "ref": "e20",
19
+ "ref": "e120",
20
20
  "role": "button",
21
21
  "name": "Compose",
22
22
  "visible": true,
@@ -24,7 +24,7 @@ Real response:
24
24
  "tag": "button",
25
25
  "occluded": false,
26
26
  "states": ["present", "visible", "enabled"],
27
- "box": { "x": 14, "y": 211.75, "width": 219, "height": 37.5 },
27
+ "box": { "x": 14, "y": 212.75, "width": 219, "height": 38 },
28
28
  "styles": {
29
29
  "color": "rgb(147, 152, 168)",
30
30
  "backgroundColor": "rgba(0, 0, 0, 0)",
@@ -33,14 +33,15 @@ Real response:
33
33
  "display": "flex",
34
34
  "visibility": "visible"
35
35
  },
36
+ "scroll": { "scrollTop": 0, "scrollHeight": 36, "clientHeight": 36, "overflowY": "visible" },
36
37
  "theme": {
37
38
  "colorToken": "--muted",
38
- "colorTokens": ["--muted"],
39
39
  "backgroundToken": null,
40
- "backgroundTokens": [],
41
40
  "offTheme": false,
42
- "tokenCount": 17,
43
- "themeScope": ":root"
41
+ "tokenCount": 18,
42
+ "themeScope": null,
43
+ "colorTokens": ["--muted"],
44
+ "backgroundTokens": []
44
45
  },
45
46
  "component": {
46
47
  "componentStack": ["Sidebar", "App", "QueryClientProvider"],
@@ -55,7 +56,9 @@ Real response:
55
56
 
56
57
  **`theme`** catches a bug class most tools cannot see. `colorToken: "--muted"` means this colour came from your design system. `offTheme: false` means nothing here is a hardcoded hex that slipped past review. An agent that pastes `color: #939AA8` instead of using the token produces a change that looks identical and quietly forks your design system. This field is how that gets caught.
57
58
 
58
- Read `colorTokens` rather than `colorToken` when it matters. Design systems routinely give several tokens the same value, and the singular field is `null` whenever more than one matches, because naming an arbitrary winner is worse than admitting the palette cannot tell them apart. `themeScope` says which theme the reading was taken under, so two inspects either side of a theme toggle are comparable instead of quietly contradictory.
59
+ Read `colorTokens` rather than `colorToken` when it matters. Design systems routinely give several tokens the same value, and the singular field is `null` whenever more than one matches, because naming an arbitrary winner is worse than admitting the palette cannot tell them apart. `themeScope` says which theme the reading was taken under, so two inspects either side of a theme toggle are comparable instead of quietly contradictory. It is `null`, as above, when the element sits under no scoped theme at all.
60
+
61
+ **`scroll`** reports the element's own scroll box. `scrollHeight` above `clientHeight` means there is content below the fold inside this element, which is a different problem from the page being scrolled wrong.
59
62
 
60
63
  **`occluded`** is the difference between "visible" and "actually clickable". An element can be present, visible and enabled while sitting under a modal overlay. `states` will look perfect; `occluded: true` is the honest answer.
61
64
 
@@ -4,21 +4,37 @@ description: Move the connected tab to a URL or reload it, and understand why ok
4
4
  icon: compass
5
5
  ---
6
6
 
7
+ `reticle_navigate` moves the connected browser tab to a URL, or reloads it in place with `{ "reload": true }`. Reach for it for a genuine document load: a deep link, a hard refresh, or the first visit. Clicking a link inside a single-page app is a route change, not a navigation, and does not need this tool.
8
+
7
9
  ```json
8
10
  { "url": "/deployments" }
9
11
  ```
10
12
 
11
- Real response:
13
+ Real response, with the throttled-tab `session` and `warning` fields trimmed off:
12
14
 
13
15
  ```json
14
16
  {
15
17
  "ok": true,
16
- "url": "http://localhost:4312/deployments",
18
+ "url": "http://localhost:4310/deployments",
17
19
  "confirmed": false,
18
20
  "note": "ok means the navigation was DISPATCHED, not that the page arrived. The SDK is torn down by the navigation itself, so nothing here can see the new document. Call reticle_sessions to confirm a session reconnected at the new URL before acting; if none appears, the page did not load or is not instrumented."
19
21
  }
20
22
  ```
21
23
 
24
+ ## `confirmed: true` when the page comes back in time
25
+
26
+ The daemon waits briefly for the SDK to re-announce itself. When it does, you are told so and can act immediately without a `reticle_sessions` round trip. This is a real reload response:
27
+
28
+ ```json
29
+ {
30
+ "ok": true,
31
+ "confirmed": true,
32
+ "note": "the page came back and re-announced itself under the same session id, so this session is live again. Anything captured before the reload is gone with the old document."
33
+ }
34
+ ```
35
+
36
+ Read the second sentence, which is the one that costs people a turn. `confirmed: true` restores the session, not the evidence: every event, network call and console line captured before the reload went with the old document.
37
+
22
38
  ## `confirmed: false` is the honest part
23
39
 
24
40
  A full navigation destroys the document the SDK was living in. Reticle cannot report on the new page from inside the old one, because the old one no longer exists. So it tells you what it actually knows: the navigation was dispatched.
@@ -40,8 +56,10 @@ then confirm a session reconnected:
40
56
  with [`reticle_sessions`](/tools-sessions), and only then snapshot. If no session appears at the new URL, the page did not load or is not instrumented. And you know that immediately rather than three calls later.
41
57
 
42
58
  <Warning>
43
- **Refs do not survive a navigation.** A new document means new refs. The same "Sign in" button was
44
- `e5` before a navigation and `e103` after. Re-snapshot; never carry refs across.
59
+ **Refs do not survive a navigation.** A new document means new refs. Across one afternoon on the
60
+ same fixture, the same "Sign in" button was `e105`, then `e305`, then `e3`. Re-snapshot; never
61
+ carry refs across. Reticle refuses a stale ref rather than clicking whatever now occupies the
62
+ slot, so the failure is loud, but it still costs you a turn.
45
63
  </Warning>
46
64
 
47
65
  ## Arguments
@@ -57,7 +75,25 @@ with [`reticle_sessions`](/tools-sessions), and only then snapshot. If no sessio
57
75
  In a single-page app, clicking a link usually changes the route without destroying the document. That is a route change, not a navigation. Refs survive, and you can assert it directly:
58
76
 
59
77
  ```json
60
- { "ref": "e19", "action": "click", "until": { "kind": "route", "path": "/deployments" } }
78
+ { "ref": "e618", "action": "click", "until": { "kind": "route", "pathname": "/deployments" } }
79
+ ```
80
+
81
+ That is a verified call. Its verdict quotes the transition itself:
82
+
83
+ ```json
84
+ {
85
+ "verified": "yes",
86
+ "verdict": {
87
+ "pass": true,
88
+ "evidence": {
89
+ "from": "http://localhost:4310/",
90
+ "to": "http://localhost:4310/deployments",
91
+ "pathname": "/deployments",
92
+ "search": "",
93
+ "hash": ""
94
+ }
95
+ }
96
+ }
61
97
  ```
62
98
 
63
99
  Use `reticle_navigate` for a genuine document load. A deep link, a hard reload, or the first visit.
@@ -4,7 +4,7 @@ description: A filtered log of every request the app made, with bodies, status,
4
4
  icon: satellite-dish
5
5
  ---
6
6
 
7
- `reticle_network` is how an agent checks the thing a screenshot fundamentally cannot show: whether the request actually happened, what it sent, and what came back.
7
+ `reticle_network` returns a filtered log of every request the app made, with request and response bodies, status, timing, and automatic credential redaction. Reach for it to answer "did that call actually fire, and what came back?", which is the thing a screenshot fundamentally cannot show.
8
8
 
9
9
  ## Example
10
10
 
@@ -12,11 +12,22 @@ icon: satellite-dish
12
12
  { "limit": 3 }
13
13
  ```
14
14
 
15
- Real response:
15
+ Real response, with the second call's `headers` block trimmed off:
16
16
 
17
17
  ```json
18
18
  {
19
19
  "calls": [
20
+ {
21
+ "method": "POST",
22
+ "url": "http://localhost:8787/api/login",
23
+ "status": 401,
24
+ "statusText": "Unauthorized",
25
+ "contentType": "application/json; charset=utf-8",
26
+ "responseSize": 37,
27
+ "requestBody": "{\"email\":\"admin@reticle.dev\",\"password\":\"[REDACTED]\"}",
28
+ "responseBody": "{\"error\":\"invalid email or password\"}",
29
+ "ms": 23
30
+ },
20
31
  {
21
32
  "method": "POST",
22
33
  "url": "http://localhost:8787/api/login",
@@ -26,28 +37,30 @@ Real response:
26
37
  "responseSize": 67,
27
38
  "requestBody": "{\"email\":\"admin@reticle.dev\",\"password\":\"[REDACTED]\"}",
28
39
  "responseBody": "{\"token\":\"[REDACTED]\",\"user\":{\"email\":\"admin@reticle.dev\"}}",
29
- "ms": 67
40
+ "ms": 4
30
41
  }
31
42
  ],
32
43
  "buffer": {
33
- "held": 15,
34
- "dropped": 529,
44
+ "held": 535,
45
+ "dropped": 10,
35
46
  "note": "event buffer evicted older events (age/size cap): a negative result here may be a false negative; the evidence may have expired. Grade sooner or widen the buffer."
36
47
  },
37
- "cost": { "bytes": 547, "tokens": 137 }
48
+ "cost": { "bytes": 1133, "tokens": 284 }
38
49
  }
39
50
  ```
40
51
 
52
+ Two attempts at the same login, the first rejected and the second accepted, with the server's own error text attached to the failure. That is the whole diagnosis in one read.
53
+
41
54
  ## Redaction is automatic
42
55
 
43
- Look at `requestBody`. The password is `[REDACTED]` and so is the returned token, without anyone configuring anything. Reticle strips credential-shaped values before they leave the page, so your agent's context. And any transcript of it, never holds them.
56
+ Look at `requestBody`. The password is `[REDACTED]` and so is the returned token, without anyone configuring anything. Reticle strips credential-shaped values before they leave the page, so neither your agent's context nor any transcript of it ever holds them.
44
57
 
45
58
  The email is not redacted, because an email is not a credential. The line is drawn at secrets.
46
59
 
47
60
  ## The buffer note is the important bit
48
61
 
49
62
  ```json
50
- "buffer": { "held": 15, "dropped": 529, "note": "…the evidence may have expired…" }
63
+ "buffer": { "held": 535, "dropped": 10, "note": "…the evidence may have expired…" }
51
64
  ```
52
65
 
53
66
  Reticle keeps a bounded event buffer. When it overflows, older events are evicted. And if you then ask "did a POST to `/api/orders` happen?", the honest answer is not "no". It is "not in what I still have."
@@ -76,9 +89,29 @@ The bug where one click fires two POSTs is invisible on screen and obvious here:
76
89
 
77
90
  If `calls` has two entries with near-identical timestamps, you have found it. Better still, assert it up front so the agent cannot rationalise it afterwards. `reticle_act_and_wait` accepts an exact count predicate, which turns "roughly one request" into a check that fails at two.
78
91
 
92
+ ## A zero-match filter says what did fire
93
+
94
+ That exact call, on a fixture that never touches `/api/orders`, comes back diagnosable rather than blank:
95
+
96
+ ```json
97
+ {
98
+ "calls": [],
99
+ "hint": {
100
+ "totalInWindow": 1,
101
+ "present": [{ "method": "POST", "url": "http://localhost:8787/api/login", "status": 200 }]
102
+ },
103
+ "buffer": { "held": 1, "dropped": 281, "note": "…the evidence may have expired…" },
104
+ "cost": { "bytes": 328, "tokens": 82 }
105
+ }
106
+ ```
107
+
108
+ `hint.present` is the difference between "your endpoint never fired" and "you filtered on the wrong URL". Read it before you go looking in the app.
109
+
79
110
  ## Checking that nothing fired
80
111
 
81
- `ok: false` on an empty result is a genuinely useful answer. Provided the buffer note says nothing was dropped. Read both.
112
+ `{ "ok": false }` keeps only the calls that failed, so an empty result there is a genuinely useful answer: nothing broke. Provided the buffer note says nothing was dropped. Read both.
113
+
114
+ Desktop IPC (`ipc://`) has no status code, so its 200 and 500 are derived. Filter those on `ok` rather than `status`.
82
115
 
83
116
  <Card title="Prove it, don't just look at it" icon="circle-check" href="/quickstart">
84
117
  `reticle_network` reads. It does not produce a verdict. Name the expected request in
@@ -4,7 +4,9 @@ description: The full timeline of everything the app did in a window. DOM, netwo
4
4
  icon: timeline
5
5
  ---
6
6
 
7
- `reticle_observe` is the wide-angle lens. Where `reticle_network` and `reticle_console` answer one question each, `observe` returns everything that happened in a window, in order.
7
+ `reticle_observe` returns the full timeline of everything the app did in a window: DOM, network, routing, console, animations and signals, in order, with a summary on top. Reach for it when you do not yet know what went wrong. It reads and diagnoses; it does not produce a verdict.
8
+
9
+ It is the wide-angle lens. Where `reticle_network` and `reticle_console` answer one question each, `observe` returns everything.
8
10
 
9
11
  ## Example
10
12
 
@@ -19,21 +21,32 @@ Real response:
19
21
  "window_ms": 4000,
20
22
  "events": [
21
23
  {
22
- "t": 925870,
24
+ "t": 69964,
23
25
  "type": "page.health",
24
- "seq": 586,
26
+ "seq": 544,
25
27
  "data": {
26
- "hidden": true,
27
- "focused": false,
28
+ "hidden": false,
29
+ "focused": true,
28
30
  "runtime": "web",
29
31
  "engine": "blink",
30
- "brand": "chrome",
32
+ "brand": "other",
31
33
  "reason": "heartbeat"
32
34
  }
35
+ },
36
+ {
37
+ "t": 1282696,
38
+ "type": "net.detail",
39
+ "data": {
40
+ "url": "http://localhost:4310/",
41
+ "method": "GET",
42
+ "status": 200,
43
+ "resourceType": "document",
44
+ "pageUrl": "http://localhost:4310/"
45
+ }
33
46
  }
34
47
  ],
35
48
  "summary": {
36
- "total": 1,
49
+ "total": 2,
37
50
  "network": 0,
38
51
  "domAdded": 0,
39
52
  "domRemoved": 0,
@@ -43,11 +56,16 @@ Real response:
43
56
  "animations": 0,
44
57
  "signals": 0
45
58
  },
46
- "cost": { "events": 1, "bytes": 374 }
59
+ "cost": { "events": 2, "bytes": 845 },
60
+ "buffer": {
61
+ "held": 536,
62
+ "dropped": 11,
63
+ "note": "event buffer evicted older events (age/size cap): a negative result here may be a false negative; the evidence may have expired. Grade sooner or widen the buffer."
64
+ }
47
65
  }
48
66
  ```
49
67
 
50
- A quiet window, honestly reported as quiet.
68
+ A quiet window, honestly reported as quiet. The `headers` block on that `net.detail` event is trimmed out for length.
51
69
 
52
70
  ## Read the summary first
53
71
 
@@ -68,11 +86,49 @@ The `summary` block is the cheap answer. `consoleErrors: 0` and `network: 0` tel
68
86
  Every `reticle_act` response includes a `since` cursor. Passing it to `observe` scopes the timeline to exactly what followed that action, with no guessing about how many milliseconds to look back:
69
87
 
70
88
  ```json
71
- { "since": 28123 }
89
+ { "since": 30224, "filters": ["signal", "net"] }
72
90
  ```
73
91
 
74
92
  This is more precise than `window_ms` and it does not drift when the machine is slow. Which matters, because a timing-based window that works on your laptop and fails in CI is a flaky test waiting to happen.
75
93
 
94
+ Every event scoped this way carries its `actionId` and `attribution`, so you can tell what the action caused from what merely happened alongside it:
95
+
96
+ ```json
97
+ {
98
+ "events": [
99
+ {
100
+ "t": 30691,
101
+ "type": "signal",
102
+ "seq": 29,
103
+ "data": { "name": "auth:granted", "data": { "email": "admin@reticle.dev" } },
104
+ "actionId": "a1",
105
+ "attribution": "window"
106
+ }
107
+ ],
108
+ "summary": { "total": 3, "network": 1, "consoleErrors": 0, "signals": 1 },
109
+ "cost": { "events": 3, "bytes": 1457 }
110
+ }
111
+ ```
112
+
113
+ ## `contradictions` is the field to read first
114
+
115
+ When two channels disagree, `observe` says so without being asked:
116
+
117
+ ```json
118
+ "contradictions": [
119
+ {
120
+ "kind": "response-ignored",
121
+ "claim": "1 write(s) succeeded on the server",
122
+ "counter": "nothing on the client changed, no DOM, store or route movement",
123
+ "detail": "POST http://localhost:8787/api/login 200"
124
+ }
125
+ ]
126
+ ```
127
+
128
+ Punctuation in that block is lightly normalised for the docs; the field names and values are verbatim.
129
+
130
+ A successful write that moved nothing on the client is the exact shape of a false green. Note that this one was a false alarm caused by a narrow filter: the same window's DOM and state did change, and the filtered read simply could not see it. Widen the window before you act on a contradiction.
131
+
76
132
  ## When to use it
77
133
 
78
134
  Use `observe` when you do not yet know what went wrong. Use the narrow tools when you do.