@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.
- package/README.md +14 -5
- package/SKILL.md +64 -29
- package/dist/capsule/causal-summary.d.ts +20 -0
- package/dist/capsule/causal-summary.js +31 -6
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +33 -5
- package/dist/cli/chromium-hint.js +82 -12
- package/dist/cli/cli-doctor.js +28 -25
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-launch.d.ts +17 -0
- package/dist/cli/cli-launch.js +28 -0
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +8 -0
- package/dist/cli/cli-port.js +16 -0
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +66 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli.js +99 -14
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/events/contradictions.d.ts +14 -0
- package/dist/events/contradictions.js +26 -17
- package/dist/events/predicate-eval.d.ts +20 -1
- package/dist/events/predicate-eval.js +125 -3
- package/dist/events/predicate.js +170 -18
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-tools.js +12 -4
- package/dist/flows/heal-run.js +1 -1
- package/dist/honesty/blind-spots.js +6 -2
- package/dist/honesty/unsettled.d.ts +40 -0
- package/dist/honesty/unsettled.js +78 -0
- package/dist/honesty/verified.d.ts +7 -0
- package/dist/honesty/verified.js +3 -3
- package/dist/index.js +3 -0
- package/dist/init/agent-rules.d.ts +16 -0
- package/dist/init/agent-rules.js +96 -22
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +11 -5
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +96 -0
- package/dist/init/confirm.js +138 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/non-js-project.js +29 -5
- package/dist/init/plan-framework.js +53 -15
- package/dist/init/plan.d.ts +40 -4
- package/dist/init/plan.js +163 -40
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +148 -42
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +46 -6
- package/dist/init/snippets.d.ts +44 -4
- package/dist/init/snippets.js +161 -37
- package/dist/init/vite-config.js +29 -3
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +29 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +69 -3
- package/dist/mcp/mcp-proxy.js +205 -20
- package/dist/mcp/mcp.js +23 -7
- package/dist/mcp/proxy-lifecycle.d.ts +1 -1
- package/dist/mcp/proxy-lifecycle.js +1 -1
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/session/no-session-diagnosis.d.ts +19 -0
- package/dist/session/no-session-diagnosis.js +71 -18
- package/dist/session/no-session-watch.js +12 -0
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/session-lease.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +0 -20
- package/dist/telemetry/bug-found.js +38 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +36 -0
- package/dist/telemetry/install-source.js +40 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.js +5 -0
- package/dist/telemetry/telemetry.d.ts +5 -1
- package/dist/telemetry/telemetry.js +15 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +46 -7
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +45 -1
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +18 -1
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +1 -1
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +64 -4
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +139 -23
- package/dist/tools/lease-tools.d.ts +29 -1
- package/dist/tools/lease-tools.js +58 -5
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +1 -1
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +48 -0
- package/dist/tools/react-grace.js +87 -0
- package/dist/tools/read-tools.js +20 -4
- package/dist/tools/settle-in-flight.d.ts +79 -0
- package/dist/tools/settle-in-flight.js +106 -0
- package/dist/tools/snapshot-delta.d.ts +7 -0
- package/dist/tools/snapshot-delta.js +130 -6
- package/dist/tools/tool-surface.js +19 -0
- package/dist/tools/tools.js +16 -4
- package/docs/README.md +56 -11
- package/docs/actions.mdx +87 -0
- package/docs/agent-cheatsheet.md +93 -29
- package/docs/architecture.md +46 -35
- package/docs/benchmarks.md +76 -72
- package/docs/best-practices.mdx +211 -0
- package/docs/capabilities.mdx +387 -0
- package/docs/cli/affected.mdx +51 -0
- package/docs/cli/capsules.mdx +69 -0
- package/docs/cli/cloud.mdx +127 -0
- package/docs/cli/doctor.mdx +76 -0
- package/docs/cli/drive.mdx +55 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +92 -0
- package/docs/cli/hunt.mdx +57 -0
- package/docs/cli/identify.mdx +62 -0
- package/docs/cli/init.mdx +94 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +68 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +62 -0
- package/docs/cli/status.mdx +64 -0
- package/docs/cli/stop.mdx +56 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +57 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +55 -0
- package/docs/cli.mdx +219 -0
- package/docs/debugging.md +13 -9
- package/docs/deploy-checks.md +15 -11
- package/docs/desktop-apps.md +49 -40
- package/docs/desktop.mdx +188 -0
- package/docs/docs.json +363 -0
- package/docs/enterprise.md +20 -16
- package/docs/faq.mdx +88 -0
- package/docs/fixtures.md +11 -7
- package/docs/flows.md +26 -22
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +199 -0
- package/docs/gate-plan.md +45 -41
- package/docs/gates.md +22 -18
- package/docs/getting-started.md +76 -65
- package/docs/human-control.md +22 -18
- package/docs/index.mdx +101 -0
- package/docs/install-agentic.mdx +215 -0
- package/docs/install-manual.mdx +194 -0
- package/docs/instrumentation.mdx +232 -0
- package/docs/integration-patterns.md +38 -34
- package/docs/local-registry.md +16 -12
- package/docs/multi-agent-testing.md +16 -12
- package/docs/packages/babel-plugin.mdx +70 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +106 -0
- package/docs/packages/electron.mdx +87 -0
- package/docs/packages/eslint-plugin.mdx +84 -0
- package/docs/packages/next.mdx +91 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +108 -0
- package/docs/packages/vite-plugin.mdx +96 -0
- package/docs/packages.mdx +217 -0
- package/docs/platform-integration.md +33 -26
- package/docs/predicates.mdx +179 -0
- package/docs/quickstart.mdx +211 -0
- package/docs/recipes.mdx +242 -0
- package/docs/skill-file.mdx +105 -0
- package/docs/state-management.mdx +197 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +47 -32
- package/docs/telemetry-contract.md +93 -50
- package/docs/telemetry-events.mdx +430 -0
- package/docs/telemetry.md +57 -52
- package/docs/testing.md +10 -6
- package/docs/token-efficiency.md +30 -26
- package/docs/tools-act-and-wait.mdx +123 -0
- package/docs/tools-act-sequence.mdx +120 -0
- package/docs/tools-act.mdx +82 -0
- package/docs/tools-assert.mdx +70 -0
- package/docs/tools-console.mdx +59 -0
- package/docs/tools-inspect.mdx +85 -0
- package/docs/tools-navigate.mdx +63 -0
- package/docs/tools-network.mdx +86 -0
- package/docs/tools-observe.mdx +89 -0
- package/docs/tools-overview.mdx +157 -0
- package/docs/tools-query.mdx +82 -0
- package/docs/tools-session-and-feedback.mdx +91 -0
- package/docs/tools-sessions.mdx +65 -0
- package/docs/tools-snapshot.mdx +75 -0
- package/docs/tools-state.mdx +81 -0
- package/docs/tools-tools-and-run.mdx +125 -0
- package/docs/tools-wait-for.mdx +66 -0
- package/docs/troubleshooting.mdx +193 -0
- package/docs/usage.md +175 -153
- package/docs/vs-chrome-devtools-mcp.mdx +82 -0
- package/docs/vs-playwright-mcp.mdx +85 -0
- package/docs/vs-screenshots.mdx +96 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +107 -0
- package/package.json +2 -2
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- 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
|
+
}
|
package/docs/system-map.md
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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** |
|
|
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
|
|
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
|
|
111
|
+
### Hop 3: daemon lifecycle
|
|
97
112
|
|
|
98
113
|
| Failure | Silent | Note |
|
|
99
114
|
| --- | --- | --- |
|
|
100
|
-
| `serve` exits 0 on `EADDRINUSE` | **yes** | #115
|
|
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
|
|
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
|
|
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`
|
|
114
|
-
| `rejectAll('session disconnected')` | **fixed** | used to become `verified:"no" / assertion_failed
|
|
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
|
|
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
|
|
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 |
|
|
140
|
-
| client | per-agent MCP compatibility |
|
|
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
|
|
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.
|