@reticlehq/server 2.7.0 → 2.8.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 (236) hide show
  1. package/README.md +14 -5
  2. package/SKILL.md +64 -29
  3. package/dist/capsule/causal-summary.d.ts +20 -0
  4. package/dist/capsule/causal-summary.js +31 -6
  5. package/dist/capsule/divergence.js +7 -1
  6. package/dist/cli/chromium-hint.d.ts +33 -5
  7. package/dist/cli/chromium-hint.js +82 -12
  8. package/dist/cli/cli-doctor.js +28 -25
  9. package/dist/cli/cli-kill.d.ts +58 -0
  10. package/dist/cli/cli-kill.js +162 -0
  11. package/dist/cli/cli-launch.d.ts +17 -0
  12. package/dist/cli/cli-launch.js +28 -0
  13. package/dist/cli/cli-parse.d.ts +16 -1
  14. package/dist/cli/cli-parse.js +32 -9
  15. package/dist/cli/cli-port.d.ts +8 -0
  16. package/dist/cli/cli-port.js +16 -0
  17. package/dist/cli/cli-verify.js +2 -2
  18. package/dist/cli/doctor-sessions-line.d.ts +12 -0
  19. package/dist/cli/doctor-sessions-line.js +66 -0
  20. package/dist/cli/port-holder.d.ts +10 -0
  21. package/dist/cli/port-holder.js +26 -2
  22. package/dist/cli.js +99 -14
  23. package/dist/crawl/crawl-tools.js +12 -2
  24. package/dist/crawl/crawl.d.ts +13 -0
  25. package/dist/crawl/crawl.js +50 -2
  26. package/dist/events/contradictions.d.ts +14 -0
  27. package/dist/events/contradictions.js +26 -17
  28. package/dist/events/predicate-eval.d.ts +20 -1
  29. package/dist/events/predicate-eval.js +125 -3
  30. package/dist/events/predicate.js +170 -18
  31. package/dist/flows/flow-classify.js +1 -1
  32. package/dist/flows/flow-tools.js +12 -4
  33. package/dist/flows/heal-run.js +1 -1
  34. package/dist/honesty/blind-spots.js +6 -2
  35. package/dist/honesty/unsettled.d.ts +40 -0
  36. package/dist/honesty/unsettled.js +78 -0
  37. package/dist/honesty/verified.d.ts +7 -0
  38. package/dist/honesty/verified.js +3 -3
  39. package/dist/index.js +3 -0
  40. package/dist/init/agent-rules.d.ts +16 -0
  41. package/dist/init/agent-rules.js +96 -22
  42. package/dist/init/astro-patch.d.ts +2 -1
  43. package/dist/init/astro-patch.js +11 -5
  44. package/dist/init/capabilities.d.ts +22 -0
  45. package/dist/init/capabilities.js +76 -0
  46. package/dist/init/confirm.d.ts +96 -0
  47. package/dist/init/confirm.js +138 -0
  48. package/dist/init/cra.js +3 -2
  49. package/dist/init/non-js-project.js +29 -5
  50. package/dist/init/plan-framework.js +53 -15
  51. package/dist/init/plan.d.ts +40 -4
  52. package/dist/init/plan.js +163 -40
  53. package/dist/init/run.d.ts +24 -2
  54. package/dist/init/run.js +148 -42
  55. package/dist/init/slash-command.d.ts +1 -1
  56. package/dist/init/slash-command.js +46 -6
  57. package/dist/init/snippets.d.ts +44 -4
  58. package/dist/init/snippets.js +161 -37
  59. package/dist/init/vite-config.js +29 -3
  60. package/dist/input/network-mock-tools.d.ts +19 -0
  61. package/dist/input/network-mock-tools.js +29 -4
  62. package/dist/license/license.js +1 -1
  63. package/dist/mcp/mcp-proxy.d.ts +69 -3
  64. package/dist/mcp/mcp-proxy.js +205 -20
  65. package/dist/mcp/mcp.js +23 -7
  66. package/dist/mcp/proxy-lifecycle.d.ts +1 -1
  67. package/dist/mcp/proxy-lifecycle.js +1 -1
  68. package/dist/pool/browser-pool.d.ts +22 -2
  69. package/dist/pool/browser-pool.js +44 -4
  70. package/dist/project/learned-routes.d.ts +24 -0
  71. package/dist/project/learned-routes.js +63 -0
  72. package/dist/project/project-store.d.ts +6 -0
  73. package/dist/project/project-store.js +25 -1
  74. package/dist/session/no-session-diagnosis.d.ts +19 -0
  75. package/dist/session/no-session-diagnosis.js +71 -18
  76. package/dist/session/no-session-watch.js +12 -0
  77. package/dist/session/ref-provenance.d.ts +54 -0
  78. package/dist/session/ref-provenance.js +61 -0
  79. package/dist/session/session-lease.js +1 -1
  80. package/dist/telemetry/automation-hint.d.ts +17 -0
  81. package/dist/telemetry/automation-hint.js +71 -0
  82. package/dist/telemetry/bug-found.d.ts +0 -20
  83. package/dist/telemetry/bug-found.js +38 -21
  84. package/dist/telemetry/cli-telemetry.js +7 -1
  85. package/dist/telemetry/feedback.js +1 -1
  86. package/dist/telemetry/init-telemetry.js +9 -1
  87. package/dist/telemetry/install-source.d.ts +36 -0
  88. package/dist/telemetry/install-source.js +40 -0
  89. package/dist/telemetry/run-telemetry.js +4 -1
  90. package/dist/telemetry/session-metrics.js +5 -0
  91. package/dist/telemetry/telemetry.d.ts +5 -1
  92. package/dist/telemetry/telemetry.js +15 -2
  93. package/dist/telemetry/tool-refused.d.ts +19 -0
  94. package/dist/telemetry/tool-refused.js +55 -0
  95. package/dist/telemetry/verification-of.js +46 -7
  96. package/dist/temp-dir.d.ts +27 -0
  97. package/dist/temp-dir.js +46 -0
  98. package/dist/tools/act-tools.js +45 -1
  99. package/dist/tools/assert-grade.d.ts +1 -1
  100. package/dist/tools/assert-grade.js +27 -5
  101. package/dist/tools/assert-verdict.js +18 -1
  102. package/dist/tools/contract-tools.js +4 -0
  103. package/dist/tools/dynamic-tools.js +1 -1
  104. package/dist/tools/error-recovery.d.ts +10 -0
  105. package/dist/tools/error-recovery.js +64 -4
  106. package/dist/tools/feedback-invite.js +1 -1
  107. package/dist/tools/invoke-tool.js +139 -23
  108. package/dist/tools/lease-tools.d.ts +29 -1
  109. package/dist/tools/lease-tools.js +58 -5
  110. package/dist/tools/navigate-result.js +1 -1
  111. package/dist/tools/observe-tools.js +1 -1
  112. package/dist/tools/observed-nothing.js +1 -1
  113. package/dist/tools/react-grace.d.ts +48 -0
  114. package/dist/tools/react-grace.js +87 -0
  115. package/dist/tools/read-tools.js +20 -4
  116. package/dist/tools/settle-in-flight.d.ts +79 -0
  117. package/dist/tools/settle-in-flight.js +106 -0
  118. package/dist/tools/snapshot-delta.d.ts +7 -0
  119. package/dist/tools/snapshot-delta.js +130 -6
  120. package/dist/tools/tool-surface.js +19 -0
  121. package/dist/tools/tools.js +16 -4
  122. package/docs/README.md +56 -11
  123. package/docs/actions.mdx +87 -0
  124. package/docs/agent-cheatsheet.md +93 -29
  125. package/docs/architecture.md +46 -35
  126. package/docs/benchmarks.md +76 -72
  127. package/docs/best-practices.mdx +211 -0
  128. package/docs/capabilities.mdx +387 -0
  129. package/docs/cli/affected.mdx +51 -0
  130. package/docs/cli/capsules.mdx +69 -0
  131. package/docs/cli/cloud.mdx +127 -0
  132. package/docs/cli/doctor.mdx +76 -0
  133. package/docs/cli/drive.mdx +55 -0
  134. package/docs/cli/feedback.mdx +67 -0
  135. package/docs/cli/gate.mdx +64 -0
  136. package/docs/cli/help.mdx +92 -0
  137. package/docs/cli/hunt.mdx +57 -0
  138. package/docs/cli/identify.mdx +62 -0
  139. package/docs/cli/init.mdx +94 -0
  140. package/docs/cli/kill.mdx +91 -0
  141. package/docs/cli/license.mdx +48 -0
  142. package/docs/cli/mcp.mdx +61 -0
  143. package/docs/cli/open.mdx +68 -0
  144. package/docs/cli/restart.mdx +60 -0
  145. package/docs/cli/rollback.mdx +35 -0
  146. package/docs/cli/serve.mdx +62 -0
  147. package/docs/cli/status.mdx +64 -0
  148. package/docs/cli/stop.mdx +56 -0
  149. package/docs/cli/telemetry.mdx +57 -0
  150. package/docs/cli/update.mdx +52 -0
  151. package/docs/cli/verify.mdx +57 -0
  152. package/docs/cli/version.mdx +39 -0
  153. package/docs/cli/watch.mdx +55 -0
  154. package/docs/cli.mdx +219 -0
  155. package/docs/debugging.md +13 -9
  156. package/docs/deploy-checks.md +15 -11
  157. package/docs/desktop-apps.md +49 -40
  158. package/docs/desktop.mdx +188 -0
  159. package/docs/docs.json +363 -0
  160. package/docs/enterprise.md +20 -16
  161. package/docs/faq.mdx +88 -0
  162. package/docs/fixtures.md +11 -7
  163. package/docs/flows.md +26 -22
  164. package/docs/for-agents.md +71 -0
  165. package/docs/frameworks.mdx +199 -0
  166. package/docs/gate-plan.md +45 -41
  167. package/docs/gates.md +22 -18
  168. package/docs/getting-started.md +76 -65
  169. package/docs/human-control.md +22 -18
  170. package/docs/index.mdx +101 -0
  171. package/docs/install-agentic.mdx +215 -0
  172. package/docs/install-manual.mdx +194 -0
  173. package/docs/instrumentation.mdx +232 -0
  174. package/docs/integration-patterns.md +38 -34
  175. package/docs/local-registry.md +16 -12
  176. package/docs/multi-agent-testing.md +16 -12
  177. package/docs/packages/babel-plugin.mdx +70 -0
  178. package/docs/packages/browser.mdx +111 -0
  179. package/docs/packages/core.mdx +106 -0
  180. package/docs/packages/electron.mdx +87 -0
  181. package/docs/packages/eslint-plugin.mdx +84 -0
  182. package/docs/packages/next.mdx +91 -0
  183. package/docs/packages/react.mdx +91 -0
  184. package/docs/packages/server.mdx +98 -0
  185. package/docs/packages/tauri.mdx +63 -0
  186. package/docs/packages/test.mdx +108 -0
  187. package/docs/packages/vite-plugin.mdx +96 -0
  188. package/docs/packages.mdx +217 -0
  189. package/docs/platform-integration.md +33 -26
  190. package/docs/predicates.mdx +179 -0
  191. package/docs/quickstart.mdx +211 -0
  192. package/docs/recipes.mdx +242 -0
  193. package/docs/skill-file.mdx +105 -0
  194. package/docs/state-management.mdx +197 -0
  195. package/docs/style.css +14 -0
  196. package/docs/system-map.md +47 -32
  197. package/docs/telemetry-contract.md +93 -50
  198. package/docs/telemetry-events.mdx +430 -0
  199. package/docs/telemetry.md +57 -52
  200. package/docs/testing.md +10 -6
  201. package/docs/token-efficiency.md +30 -26
  202. package/docs/tools-act-and-wait.mdx +123 -0
  203. package/docs/tools-act-sequence.mdx +120 -0
  204. package/docs/tools-act.mdx +82 -0
  205. package/docs/tools-assert.mdx +70 -0
  206. package/docs/tools-console.mdx +59 -0
  207. package/docs/tools-inspect.mdx +85 -0
  208. package/docs/tools-navigate.mdx +63 -0
  209. package/docs/tools-network.mdx +86 -0
  210. package/docs/tools-observe.mdx +89 -0
  211. package/docs/tools-overview.mdx +157 -0
  212. package/docs/tools-query.mdx +82 -0
  213. package/docs/tools-session-and-feedback.mdx +91 -0
  214. package/docs/tools-sessions.mdx +65 -0
  215. package/docs/tools-snapshot.mdx +75 -0
  216. package/docs/tools-state.mdx +81 -0
  217. package/docs/tools-tools-and-run.mdx +125 -0
  218. package/docs/tools-wait-for.mdx +66 -0
  219. package/docs/troubleshooting.mdx +193 -0
  220. package/docs/usage.md +175 -153
  221. package/docs/vs-chrome-devtools-mcp.mdx +82 -0
  222. package/docs/vs-playwright-mcp.mdx +85 -0
  223. package/docs/vs-screenshots.mdx +96 -0
  224. package/docs/what-is-reticle.mdx +63 -0
  225. package/docs/why-reticle.mdx +107 -0
  226. package/package.json +2 -2
  227. package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
  228. package/docs/matrix/2.5.0/codex-machine.json +0 -20
  229. package/docs/matrix/2.5.0/cursor-machine.json +0 -22
  230. package/docs/matrix/2.5.0/gemini-machine.json +0 -22
  231. package/docs/matrix/2.5.0/opencode-machine.json +0 -22
  232. package/docs/matrix/2.5.0/vscode-machine.json +0 -22
  233. package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
  234. package/docs/matrix/MATRIX.md +0 -26
  235. package/docs/matrix/README.md +0 -59
  236. package/docs/mint.json +0 -61
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: The SKILL file: paste and go
3
+ sidebarTitle: SKILL file
4
+ description: One URL you paste into any coding agent to teach it Reticle. How it works, what it contains, and when you need it at all.
5
+ icon: graduation-cap
6
+ ---
7
+
8
+ `SKILL.md` is a single document you hand to a coding agent. It teaches the agent to install Reticle, wire it into your app, and then drive a verification loop properly. Without you explaining any of it.
9
+
10
+ ```
11
+ https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md
12
+ ```
13
+
14
+ That URL is live and returns the current version. Paste it into your agent and say "read this and set up Reticle".
15
+
16
+ <Note>
17
+ You often do not need it. `npx @reticlehq/server init` writes a `/reticle` slash command for
18
+ Claude Code automatically, and installs agent rule files for Claude and Cursor. The SKILL file is
19
+ for agents that `init` cannot configure, for a from-scratch install, or when you want the agent to
20
+ have the full protocol in context rather than a summary.
21
+ </Note>
22
+
23
+ ## It has two modes, and it picks one itself
24
+
25
+ The first thing the document tells an agent to do is check which situation it is in:
26
+
27
+ ```bash
28
+ cat .reticle.json 2>/dev/null || echo "NOT_FOUND"
29
+ ```
30
+
31
+ - **Not found** → run **Setup mode**: register the MCP server, install the SDK, wire the entry point, validate the connection, and prove it on one flow while the user watches.
32
+ - **Found** → run **Test mode**: connect, orient, decide what to test, drive it, report.
33
+
34
+ One document, two jobs, no flag for you to remember. The second time you invoke it, it skips straight to the useful part.
35
+
36
+ ## What Setup mode actually does
37
+
38
+ | Phase | What the agent does |
39
+ | --- | --- |
40
+ | Save the skill | Store it as `/reticle` so the loop is one word away from then on |
41
+ | Run `init` | Without asking you questions it can answer itself |
42
+ | Manual fallbacks | Per-agent MCP config, per-framework SDK wiring, for anything `init` marked `⚠` |
43
+ | Validate | Confirm a session actually connected. Not that a checklist went green |
44
+ | Prove it | Drive one real flow while you watch, so the install ends in evidence |
45
+
46
+ That last row is the one that matters. Plenty of installs "succeed" and then nothing works. The skill does not consider itself finished until a real flow has produced a real verdict.
47
+
48
+ ## What Test mode actually does
49
+
50
+ Five phases: connect, orient, decide what to test, run the tests, report. The reporting format is specified, which matters more than it sounds. An agent left to summarise freely will summarise optimistically.
51
+
52
+ The rules it operates under are the same ones these docs keep repeating:
53
+
54
+ - Only `reticle_act_and_wait` and `reticle_assert` produce a verdict.
55
+ - `verified: "unknown"` is not a pass.
56
+ - Never weaken a check to make it pass.
57
+
58
+ ## Feedback is the first section, on purpose
59
+
60
+ Before setup, before anything, the document tells the agent that reporting problems is part of the job. And that this applies **during** setup, which is where Reticle is most likely to break and least likely to hear about it.
61
+
62
+ It even covers the case where nothing is installed yet:
63
+
64
+ ```bash
65
+ npx @reticlehq/server feedback --agent --kind gap "init finished but no session ever connected"
66
+ ```
67
+
68
+ That works with no daemon running, which is the entire point. The install that failed halfway is the one worth hearing about, and in that state the MCP tools do not exist.
69
+
70
+ <Tip>
71
+ It also draws a line that saves everyone time: report defects in **Reticle**. A bug found in the
72
+ app under test is Reticle working correctly, and belongs in the answer to the user.
73
+ </Tip>
74
+
75
+ ## It ships inside the package too
76
+
77
+ `SKILL.md` is bundled into `@reticlehq/server` at publish time, alongside a copy of this entire documentation site. So an agent that has already installed Reticle can read it from disk without a network round trip:
78
+
79
+ ```
80
+ node_modules/@reticlehq/server/SKILL.md
81
+ node_modules/@reticlehq/server/docs/
82
+ ```
83
+
84
+ Handy in a sandbox with no outbound network, which is an increasingly common place for an agent to live.
85
+
86
+ ## Should you read it?
87
+
88
+ Probably not, unless you are curious. It is roughly 60 KB and written for an agent, which means it is exhaustive in the way a runbook is exhaustive. Every fallback, every framework, every failure mode spelled out rather than summarised.
89
+
90
+ If you want the human version of the same material, you are already in it:
91
+
92
+ <CardGroup cols={2}>
93
+ <Card title="Agentic install" icon="wand-magic-sparkles" href="/install-agentic">
94
+ What `reticle init` does, with real output.
95
+ </Card>
96
+ <Card title="Manual install" icon="screwdriver-wrench" href="/install-manual">
97
+ Every step `init` cannot automate.
98
+ </Card>
99
+ <Card title="Agent cheat sheet" icon="bolt" href="/agent-cheatsheet">
100
+ The one-screen version of the drive loop.
101
+ </Card>
102
+ <Card title="Best practices" icon="star" href="/best-practices">
103
+ The habits that make verdicts worth trusting.
104
+ </Card>
105
+ </CardGroup>
@@ -0,0 +1,197 @@
1
+ ---
2
+ title: State management
3
+ description: Register any store so Reticle can read what your app believes. Zustand and Redux work natively, and eight more have adapters.
4
+ icon: database
5
+ ---
6
+
7
+ Reading application state is what separates a verdict from a screenshot. A `200` proves the server was reachable. A rendered row proves React ran. Only state proves your app accepted the change.
8
+
9
+ Reticle needs one thing from your store: a way to read it, and a way to know when it changed.
10
+
11
+ ## The contract
12
+
13
+ ```ts
14
+ interface StoreLike {
15
+ getState(): unknown;
16
+ subscribe(listener: () => void): () => void;
17
+ }
18
+ ```
19
+
20
+ Anything with that shape registers directly, with no adapter.
21
+
22
+ ```ts
23
+ import { registerStore } from '@reticlehq/browser';
24
+
25
+ registerStore('app', myStore);
26
+ ```
27
+
28
+ <Warning>
29
+ Pass the **store**, not `() => store.getState()`. The store form wires `subscribe`, so every mutation
30
+ emits a state diff. The getter form still works for compatibility but leaves the store on pull-only
31
+ reads: no change events, and an empty `stateDiffs` in every verdict.
32
+
33
+ That reads as "nothing changed" and actually means "I was never watching". It is the most common instrumentation mistake there is.
34
+
35
+ </Warning>
36
+
37
+ ## Works natively
38
+
39
+ <CardGroup cols={2}>
40
+ <Card title="Zustand" icon="bolt">
41
+ A zustand hook already has `getState` and `subscribe`. Pass it straight in.
42
+ </Card>
43
+ <Card title="Redux" icon="boxes-stacked">
44
+ A Redux store is `StoreLike` by definition. Same one-liner.
45
+ </Card>
46
+ </CardGroup>
47
+
48
+ ```ts
49
+ registerStore('app', useApp); // zustand
50
+ registerStore('app', store); // redux
51
+ ```
52
+
53
+ This is what the Reticle fixture app does, and it is why its verdicts carry `stateDiffs: [{ "path": "auth", "from": null, "to": "…" }]`.
54
+
55
+ ## Adapters
56
+
57
+ Eight libraries do not fit `StoreLike` for a reason specific to each. Every adapter returns a `StoreLike` you hand straight to `registerStore`.
58
+
59
+ Each example below is the one documented in the adapter's own source.
60
+
61
+ ### TanStack Query
62
+
63
+ ```ts
64
+ import { tanstackQueryStore } from '@reticlehq/browser';
65
+
66
+ registerStore('queries', tanstackQueryStore(queryClient));
67
+ ```
68
+
69
+ <Tip>
70
+ Register your server cache even if you already register a client store. "The screen is plausible
71
+ and the network is silent" is the normal failure with query caches: a mutation forgets to
72
+ invalidate, the UI keeps rendering a number that was true a minute ago, and nothing fires for
73
+ anyone to notice. The cache's freshness metadata is the only witness.
74
+ </Tip>
75
+
76
+ ### Pinia
77
+
78
+ ```ts
79
+ import { piniaStore } from '@reticlehq/browser';
80
+
81
+ registerStore('cart', piniaStore(useCartStore()));
82
+ ```
83
+
84
+ ### Svelte stores
85
+
86
+ ```ts
87
+ import { svelteStore } from '@reticlehq/browser';
88
+
89
+ registerStore('cart', svelteStore(cartStore));
90
+ ```
91
+
92
+ ### Jotai
93
+
94
+ Atoms are not a store, so you name the ones you care about.
95
+
96
+ ```ts
97
+ import { jotaiStore } from '@reticlehq/browser';
98
+ import { getDefaultStore } from 'jotai';
99
+
100
+ registerStore('app', jotaiStore(getDefaultStore(), { cart: cartAtom, user: userAtom }));
101
+ ```
102
+
103
+ ### Valtio
104
+
105
+ ```ts
106
+ import { valtioStore } from '@reticlehq/browser';
107
+ import { snapshot, subscribe } from 'valtio/vanilla';
108
+
109
+ registerStore('app', valtioStore(state, snapshot, subscribe));
110
+ ```
111
+
112
+ ### MobX
113
+
114
+ ```ts
115
+ import { mobxStore } from '@reticlehq/browser';
116
+ import { reaction, toJS } from 'mobx';
117
+
118
+ registerStore('app', mobxStore(store, toJS, reaction));
119
+ ```
120
+
121
+ You pass `toJS` and `reaction` in rather than Reticle importing MobX. The SDK stays free of your state library, and your bundle stays free of a copy it did not ask for.
122
+
123
+ ### XState
124
+
125
+ ```ts
126
+ import { xstateStore } from '@reticlehq/browser';
127
+
128
+ registerStore('machine', xstateStore(actor));
129
+ ```
130
+
131
+ An XState actor's `subscribe` returns a subscription object rather than an unsubscribe function, which is the single reason it does not satisfy `StoreLike` already.
132
+
133
+ ### Recoil
134
+
135
+ The most involved, because Recoil has no store object to hand over.
136
+
137
+ ```ts
138
+ import { recoilStore } from '@reticlehq/browser';
139
+
140
+ const latest = useRef(snapshot_UNSTABLE());
141
+ const listeners = useRef(new Set<() => void>()).current;
142
+
143
+ useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
144
+ latest.current = snapshot;
145
+ for (const l of listeners) l();
146
+ });
147
+
148
+ registerStore(
149
+ 'recoil',
150
+ recoilStore(
151
+ { cart: cartAtom },
152
+ () => latest.current,
153
+ (l) => {
154
+ listeners.add(l);
155
+ return () => listeners.delete(l);
156
+ },
157
+ ),
158
+ );
159
+ ```
160
+
161
+ ### Anything else
162
+
163
+ `pushStore` gives you a store plus the `push` that updates it, so you own the write side. Useful for state that lives somewhere Reticle cannot reach: a web worker, a websocket, a third-party SDK.
164
+
165
+ ```ts
166
+ import { pushStore } from '@reticlehq/browser';
167
+
168
+ const { store, push } = pushStore({ status: 'idle' });
169
+ registerStore('worker', store);
170
+
171
+ worker.onmessage = (e) => push(e.data);
172
+ ```
173
+
174
+ ## Reading it back
175
+
176
+ ```json
177
+ { "store": "app", "path": "cart.items.0.qty" }
178
+ ```
179
+
180
+ `reticle_state` walks a dot-path with numeric array indices. `depth` collapses anything deeper to a size marker, which is the difference between a cheap look and a very expensive one on a large store.
181
+
182
+ You can also assert on it directly, which is stronger than reading it afterwards:
183
+
184
+ ```json
185
+ { "kind": "state", "store": "app", "path": "deployments.0.status", "equals": "live" }
186
+ ```
187
+
188
+ That catches a UI-versus-store desync, a deploy that only looks shipped, deterministically and with no model involved.
189
+
190
+ <CardGroup cols={2}>
191
+ <Card title="reticle_state" icon="database" href="/tools-state">
192
+ Arguments, depth control, and reading a component's hook state.
193
+ </Card>
194
+ <Card title="Instrument your app" icon="wrench" href="/instrumentation">
195
+ Stores are one of three things worth registering. The other two are signals and testids.
196
+ </Card>
197
+ </CardGroup>
package/docs/style.css ADDED
@@ -0,0 +1,14 @@
1
+ /* Any .css file at the docs root is auto-loaded by Mintlify; docs.json has no field for it.
2
+ Tagline under the footer logo: the footer schema is {socials, links} and takes no text. */
3
+ #footer a.select-none::after {
4
+ content: 'Your agent says it works. Reticle checks.';
5
+ display: block;
6
+ margin-top: 0.75rem;
7
+ font-size: 0.875rem;
8
+ line-height: 1.25rem;
9
+ color: rgb(var(--gray-500));
10
+ }
11
+
12
+ .dark #footer a.select-none::after {
13
+ color: rgb(var(--gray-400));
14
+ }
@@ -1,16 +1,31 @@
1
- # System map
1
+ ---
2
+ title: System map
3
+ description: 'How a tool call travels from a coding agent to your app and back, and which of those failures are silent.'
4
+ icon: map
5
+ ---
2
6
 
3
- > How a tool call gets from a coding agent to your app and back, what each hop can do wrong, and which of those failures are **silent**. Written for anyone touching the transport, the daemon, the bridge, or a gate — and for contributors trying to work out where their change lives.
7
+ > How a tool call gets from a coding agent to your app and back, what each hop can do wrong, and which of those failures are **silent**. Written for anyone touching the transport, the daemon, the bridge, or a gate, and for contributors trying to work out where their change lives.
4
8
  >
5
9
  > Companion pages: [`harness-rules.md`](../apps/e2e/harness-rules.md) (what a gate must do about all this) and [`telemetry-contract.md`](./telemetry-contract.md) (the rules for anything that emits).
6
10
 
7
- ## 1. Topology — 5 processes, 4 hops
8
-
9
- ```
10
- coding agent ──stdio──▶ reticle mcp ──HTTP POST──▶ daemon :4400 ──WebSocket──▶ browser SDK ──▶ your app
11
- (client) JSON-RPC (proxy) ◀───SSE───── (bridge+tools) ◀───────── (@reticlehq/browser)
12
- │
13
- └─▶ .reticle/ flows · capsules · baselines · runs
11
+ ## 1. Topology: 5 processes, 4 hops
12
+
13
+ ```mermaid
14
+ flowchart LR
15
+ C["coding agent<br/>(client)"]
16
+ P["reticle mcp<br/>(proxy)"]
17
+ D["daemon :4400<br/>(bridge + tools)"]
18
+ S["browser SDK<br/>@reticlehq/browser"]
19
+ A["your app"]
20
+ F[(".reticle/<br/>flows · capsules<br/>baselines · runs")]
21
+
22
+ C -- "stdio / JSON-RPC" --> P
23
+ P -- "HTTP POST" --> D
24
+ D -- "SSE" --> P
25
+ D -- "WebSocket" --> S
26
+ S --> D
27
+ S --> A
28
+ D <--> F
14
29
  ```
15
30
 
16
31
  Each hop has its own failure vocabulary, its own recovery, and its own way of lying. Nothing stated this end to end until this page, which is why gate results have been contradictory.
@@ -23,39 +38,39 @@ Each hop has its own failure vocabulary, its own recovery, and its own way of ly
23
38
  | 2 | proxy probes `:4400` (`probeDaemon`, plain TCP, 500ms) | proxy | a squatter that accepts and never serves looks alive |
24
39
  | 3 | absent → spawn daemon detached, poll to `DAEMON_READY_TIMEOUT_MS` (10s) | proxy | slow CI; raise with `RETICLE_DAEMON_READY_TIMEOUT_MS` |
25
40
  | 4 | proxy opens SSE, waits for the `endpoint` frame → POST URL | proxy | an empty frame used to read as "connected"; now refused (`buildSessionUrl`) |
26
- | 5 | `initialize` — answered **locally** after `LOCAL_HANDSHAKE_MS` (12s) when the daemon cannot | proxy | any client with a startup budget under ~12s loses Reticle for the whole session |
41
+ | 5 | `initialize`, answered **locally** after `LOCAL_HANDSHAKE_MS` (12s) when the daemon cannot | proxy | any client with a startup budget under ~12s loses Reticle for the whole session |
27
42
  | 6 | daemon binds `:4400`, writes the pid file, starts bridge + idle watcher | daemon | `EADDRINUSE` → child exits 1, **parent already exited 0** |
28
43
  | 7 | app calls `reticle.connect()` → `ws://localhost:4400/reticle` | SDK | wrong port; missing pairing token; mixed content (Safari throws synchronously on `new WebSocket`) |
29
44
  | 8 | HELLO: `sessionId`, `projectId`, `protocolVersion`, capabilities | SDK → bridge | version mismatch → `1008` close → **the SDK stops retrying, permanently** |
30
45
  | 9 | bridge authenticates: pairing token / loopback tier / Origin allow-list | bridge | another project's daemon answers → `authFailureReason` names it |
31
- | 10 | Session registered, `everConnected = true` | daemon | — |
46
+ | 10 | Session registered, `everConnected = true` | daemon | n/a |
32
47
  | 11 | tool call → `runTool` → `session.send` → WS command → SDK → reply | all | any hop above, mid-flight |
33
48
 
34
- **Sequencing hazard:** steps 6 and 7 race. The product absorbs it internally — the first live tool call blocks briefly for a session to appear rather than failing — but a gate that boots an app and immediately asks whether a session exists is outside that protection and has been burned by it. Poll for the session; never sample once.
49
+ **Sequencing hazard:** steps 6 and 7 race. The product absorbs it internally (the first live tool call blocks briefly for a session to appear rather than failing), but a gate that boots an app and immediately asks whether a session exists is outside that protection and has been burned by it. Poll for the session; never sample once.
35
50
 
36
- ## 3. The tool graph — what produces what
51
+ ## 3. The tool graph: what produces what
37
52
 
38
53
  Three different counts, all measured, and the difference between them matters:
39
54
 
40
55
  | Count | What it is |
41
56
  | --- | --- |
42
57
  | **68** | name constants in `ReticleTool` |
43
- | **48** | tools advertised under `RETICLE_ADVERTISE_ALL_TOOLS=1` — what `tool-surface-sweep-test` drives (51 calls) |
58
+ | **48** | tools advertised under `RETICLE_ADVERTISE_ALL_TOOLS=1`, i.e. what `tool-surface-sweep-test` drives (51 calls) |
44
59
  | **17** | advertised by default; the rest are reached through `reticle_run` |
45
60
 
46
- 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`](./gate-plan.md), Phase 4).
61
+ 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`](./gate-plan.md), Phase 4).
47
62
 
48
63
  | Token | Produced by | Consumed by | Invalidated by |
49
64
  | --- | --- | --- | --- |
50
65
  | `sessionId` | `reticle_sessions`, HELLO | ~every tool | tab close; **survives reload** via `sessionStorage` |
51
- | `ref` (`e115`) | `snapshot`, `query`, `scroll_to` | `inspect`, `act`, `act_and_wait`, `state` | any re-render — refuses cleanly rather than clicking the new occupant |
66
+ | `ref` (`e115`) | `snapshot`, `query`, `scroll_to` | `inspect`, `act`, `act_and_wait`, `state` | any re-render; refuses cleanly rather than clicking the new occupant |
52
67
  | `actionId` (`a2`) | `act`, `act_and_wait` | `observe`, `network`, `console` filters | window expiry |
53
68
  | `since` cursor | every read | every read | ring-buffer eviction |
54
- | `capsuleSaved` | `act_and_wait` | `replay` | — |
69
+ | `capsuleSaved` | `act_and_wait` | `replay` | n/a |
55
70
  | flow | `record_start` → acts → `flow_save_recorded` | `flow_verify`, `flow_replay`, `flow_heal`, `affected`, `gate` | DOM drift → `flow_heal` |
56
71
  | baseline | `baseline_save`, `viewport` | `visual_diff`, `diff` | viewport change |
57
72
  | `session_lease` | direct tool results | the agent (`yield` / `end`) | **dropped on the `reticle_run` path** (#119) |
58
- | run artifact | `run_export`, `reticle verify` | CI, `reticle gate` | — |
73
+ | run artifact | `run_export`, `reticle verify` | CI, `reticle gate` | n/a |
59
74
 
60
75
  Two structural facts:
61
76
 
@@ -66,16 +81,16 @@ Two structural facts:
66
81
 
67
82
  **Silent** = fails with no error surfaced to the agent or the user.
68
83
 
69
- ### Hop 1 — client ↔ proxy
84
+ ### Hop 1: client ↔ proxy
70
85
 
71
86
  | Failure | Silent | Guarded by |
72
87
  | --- | --- | --- |
73
- | proxy killed externally (the `lsof -ti` recipe) | **yes, total** | nothing — needs `reticle kill` (#114). See [harness rule 1](../apps/e2e/harness-rules.md) |
88
+ | proxy killed externally (the `lsof -ti` recipe) | **yes, total** | `reticle kill` frees the port and leaves the agent's own proxy alone. See [harness rule 1](../apps/e2e/harness-rules.md) |
74
89
  | proxy uncaught exception | no | `installProxyResilience` + `proxyLog` |
75
90
  | proxy exits when the reconnect budget is spent | _was the worst bug in the product_ | `onReconnectBudgetSpent` → DORMANT, never exit |
76
91
  | client closes stdin with calls in flight | no | `SHUTDOWN_DRAIN_MS` (5s) |
77
92
 
78
- ### Hop 2 — proxy ↔ daemon
93
+ ### Hop 2: proxy ↔ daemon
79
94
 
80
95
  Three distinct populations of unanswered call, **all covered**:
81
96
 
@@ -93,35 +108,35 @@ Still fragile:
93
108
  | stale tool catalog served while dormant | **yes** | `ToolCatalogCache` answers `tools/list` from cache; after a version change a client can hold a catalog the daemon no longer implements |
94
109
  | handshake replay regresses | **yes** | the daemon builds a fresh `McpServer` per SSE connection, so every `tools/call` would hit an uninitialized server |
95
110
 
96
- ### Hop 3 — daemon lifecycle
111
+ ### Hop 3: daemon lifecycle
97
112
 
98
113
  | Failure | Silent | Note |
99
114
  | --- | --- | --- |
100
- | `serve` exits 0 on `EADDRINUSE` | **yes** | #115 — the parent reports the spawn, the child reports the bind, nothing joins them |
115
+ | `serve` exits 0 on `EADDRINUSE` | **yes** | #115: the parent reports the spawn, the child reports the bind, nothing joins them |
101
116
  | liveness is the pid file, never the port | **yes** | `daemon/daemon.ts:156` |
102
- | idle shutdown fires mid-install | **yes** | 5min base / 30min attached — already scored three fixtures as install failures |
117
+ | idle shutdown fires mid-install | **yes** | 5min base / 30min attached; already scored three fixtures as install failures |
103
118
  | `isUselessDaemon` ends a daemon that served nothing | **yes** | lifetime facts, but a gate that installs for six minutes before its first call is that daemon |
104
119
  | a daemon spawned inside a tool call is reaped when the call ends | **yes** | `SIGTERM` within ~1s, `detached` or not |
105
120
  | no `daemon_alive` heartbeat | **fixed** | a killed daemon and a tidy exit used to read identically; the daemon now beats every 30s and `classifyDaemonLife` turns silence into `died_silently` (#123) |
106
121
  | no port arbitration | **yes** | a second daemon can bind a port a first one held |
107
122
 
108
- ### Hop 4 — bridge ↔ SDK
123
+ ### Hop 4: bridge ↔ SDK
109
124
 
110
125
  | Failure | Silent | Note |
111
126
  | --- | --- | --- |
112
127
  | protocol mismatch → `1008` | no, but **terminal** | the SDK stops retrying by design; the app is dark until reload (#127) |
113
- | another project's daemon answers | no | `authFailureReason` — evidence-based, only claims it when the daemon has demonstrably served another project |
114
- | `rejectAll('session disconnected')` | **fixed** | used to become `verified:"no" / assertion_failed` — Reticle blaming the app, by file and line, for its own lost connection. Now `verified:"unknown" / observation_lost` (#124) |
128
+ | another project's daemon answers | no | `authFailureReason` is evidence-based, only claiming it when the daemon has demonstrably served another project |
129
+ | `rejectAll('session disconnected')` | **fixed** | used to become `verified:"no" / assertion_failed`, i.e. Reticle blaming the app, by file and line, for its own lost connection. Now `verified:"unknown" / observation_lost` (#124) |
115
130
  | reaper ends a quiet session | no | revivable on the agent's next action |
116
131
  | opaque origins (`tauri://localhost`, `file://`) | no | kept verbatim in the allow-list rather than collapsed to `"null"` |
117
132
 
118
- ### Hop 5 — SDK internals
133
+ ### Hop 5: SDK internals
119
134
 
120
135
  | Failure | Silent | Note |
121
136
  | --- | --- | --- |
122
137
  | fixed 1s reconnect, no backoff, unbounded | **yes** | and the noise lands in the console channel we report to the agent (#116) |
123
138
  | `onUnreachable` suppressed after the first success | **yes** | a **mid-session** outage never warns at all |
124
- | offline queue overflow (`MAX_QUEUE = 500`) | no | gap marker stamped at the **oldest** drop — the honest floor for where the hole starts |
139
+ | offline queue overflow (`MAX_QUEUE = 500`) | no | gap marker stamped at the **oldest** drop, the honest floor for where the hole starts |
125
140
  | `sessionStorage` throws in a sandboxed iframe | no | falls back; a session must still start |
126
141
 
127
142
  ## 5. Where the gates are
@@ -136,8 +151,8 @@ Still fragile:
136
151
  | tools | surface, schema, refusals | `tool-surface-sweep-test`, `tool-fuzz-test` | latency/failure budget |
137
152
  | verdict | honesty | `release-smoke-test` | transport ≠ app (#124); false-green rate |
138
153
  | telemetry | silent absence | `telemetry-events-test`, `telemetry-contract.test.ts` | chokepoint coverage |
139
- | install | pristine-surface wiring | — | the whole class |
140
- | client | per-agent MCP compatibility | — | the whole class |
154
+ | install | pristine-surface wiring | nothing | the whole class |
155
+ | client | per-agent MCP compatibility | nothing | the whole class |
141
156
 
142
157
  Nine of eleven layers have a gate. The two that do not are exactly the two that need a second repo and a human. The plan for both is in [`gate-plan.md`](./gate-plan.md).
143
158
 
@@ -146,7 +161,7 @@ Nine of eleven layers have a gate. The two that do not are exactly the two that
146
161
  `runTool` opens one `tool.handler` root span per call and every stage underneath inherits it. With `RETICLE_TRACE=1` that makes three assertions available from any existing battery run, at the cost of one log parser:
147
162
 
148
163
  - exactly one root span at `depth: 0` per tool call
149
- - **zero orphan spans** — 23 orphans on one healthy run is the documented signature of a hung call
164
+ - **zero orphan spans**; 23 orphans on one healthy run is the documented signature of a hung call
150
165
  - `settleTiming` always fires; it sits in a `finally` so a thrown call cannot leak a concurrency slot
151
166
 
152
167
  Today that output is produced and discarded.