@reticlehq/server 2.6.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 +251 -33
- package/dist/bridge/bridge.d.ts +6 -1
- package/dist/bridge/bridge.js +27 -4
- 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 +68 -0
- package/dist/cli/chromium-hint.js +145 -0
- package/dist/cli/cli-doctor.js +31 -36
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-launch.d.ts +18 -0
- package/dist/cli/cli-launch.js +32 -1
- 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 +36 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/doctor-daemon-line.js +10 -1
- 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 +139 -23
- 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/daemon/daemon-resilience.js +2 -2
- package/dist/daemon/loopback-alias.d.ts +32 -0
- package/dist/daemon/loopback-alias.js +80 -0
- package/dist/events/contradictions.d.ts +14 -0
- package/dist/events/contradictions.js +26 -17
- package/dist/events/predicate-eval.d.ts +26 -3
- package/dist/events/predicate-eval.js +284 -95
- package/dist/events/predicate-parse.d.ts +1 -1
- package/dist/events/predicate-parse.js +18 -4
- package/dist/events/predicate.js +170 -18
- package/dist/events/ring-buffer.d.ts +8 -0
- package/dist/events/ring-buffer.js +34 -1
- 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/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +9 -1
- 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 +27 -4
- package/dist/http-server.js +11 -1
- package/dist/index.js +29 -2
- package/dist/init/agent-rules.d.ts +16 -0
- package/dist/init/agent-rules.js +96 -17
- 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/detect.d.ts +8 -0
- package/dist/init/detect.js +15 -0
- package/dist/init/non-js-project.d.ts +27 -0
- package/dist/init/non-js-project.js +84 -0
- package/dist/init/plan-framework.d.ts +9 -0
- package/dist/init/plan-framework.js +72 -16
- package/dist/init/plan.d.ts +40 -4
- package/dist/init/plan.js +183 -43
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +181 -46
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +46 -6
- package/dist/init/snippets.d.ts +64 -5
- package/dist/init/snippets.js +220 -31
- 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 +215 -23
- package/dist/mcp/mcp.js +58 -7
- package/dist/mcp/proxy-lifecycle.d.ts +20 -1
- package/dist/mcp/proxy-lifecycle.js +22 -1
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/pool/playwright-launcher.js +10 -2
- 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/live-control-tools.js +39 -2
- package/dist/session/live-control.d.ts +7 -9
- package/dist/session/live-control.js +34 -1
- package/dist/session/no-session-diagnosis.d.ts +23 -4
- package/dist/session/no-session-diagnosis.js +128 -44
- 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/review-store.d.ts +19 -0
- package/dist/session/review-store.js +34 -5
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +9 -0
- package/dist/session/session-manager.js +11 -0
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +12 -0
- package/dist/status-payload.d.ts +13 -1
- package/dist/status-payload.js +6 -1
- package/dist/telemetry/app-instrumented.d.ts +37 -0
- package/dist/telemetry/app-instrumented.js +54 -0
- 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/daemon-telemetry.d.ts +4 -4
- package/dist/telemetry/daemon-telemetry.js +8 -4
- 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.d.ts +3 -3
- package/dist/telemetry/session-metrics.js +9 -4
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +72 -8
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +77 -11
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +25 -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 +70 -5
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +149 -32
- 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-kit.d.ts +52 -5
- package/dist/tools/tool-kit.js +55 -24
- package/dist/tools/tool-surface.js +28 -9
- package/dist/tools/tools.js +38 -4
- package/dist/visual/capture-cleanup.d.ts +16 -0
- package/dist/visual/capture-cleanup.js +39 -0
- package/dist/visual/visual-tools.d.ts +18 -0
- package/dist/visual/visual-tools.js +24 -5
- package/docs/README.md +56 -11
- package/docs/actions.mdx +87 -0
- package/docs/agent-cheatsheet.md +102 -26
- 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 +23 -19
- 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 +101 -46
- package/docs/telemetry-events.mdx +430 -0
- package/docs/telemetry.md +63 -50
- 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
package/dist/tools/tool-kit.js
CHANGED
|
@@ -14,32 +14,63 @@ export const sessionIdShape = {
|
|
|
14
14
|
.describe('OMIT THIS unless you mean a specific tab — Reticle scopes to your project, prefers the active one, and refuses rather than guesses when ambiguous. Pass an id from reticle_sessions only to target a particular tab. The previous wording ("omit when only ONE session is open") was wrong and cost real turns: with three sessions connected — an app plus two pool leases — resolution is still unambiguous, but that sentence sent the agent off to list and filter sessions by hand before every single call.'),
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* envelope
|
|
17
|
+
* Every key `runTool` splices onto a session-bound result, as a closed list.
|
|
18
|
+
*
|
|
19
|
+
* These are wire strings and were inlined at the splice sites, which is exactly how four of them
|
|
20
|
+
* came to be spliced and never declared. `sessionEnvelopeShape` is derived from this object and
|
|
21
|
+
* `envelope-keys-declared.test.ts` fails if a member is missing from it, so the two cannot drift.
|
|
22
|
+
*
|
|
23
|
+
* Each of these is a channel that exists BECAUSE the agent had no other way to learn the fact.
|
|
24
|
+
* Undeclared, a schema-strict client validating `structuredContent` drops it, and the channel is
|
|
25
|
+
* indistinguishable from never having been built.
|
|
22
26
|
*/
|
|
23
|
-
export const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
export const EnvelopeKey = {
|
|
28
|
+
/** Session health — which tab, how fresh. */
|
|
29
|
+
SESSION: 'session',
|
|
30
|
+
/** One-shot pool-lease reminder. */
|
|
31
|
+
SESSION_LEASE: 'session_lease',
|
|
32
|
+
/** One-shot "these sessions are old, clean them up". */
|
|
33
|
+
SESSION_AGE_WARNING: 'session_age_warning',
|
|
34
|
+
/** The delivered-once human-in-the-loop guidance envelope. Losing it is a safety failure. */
|
|
35
|
+
CONTROL: 'control',
|
|
36
|
+
/**
|
|
37
|
+
* Rides alongside `session` whenever the tab is THROTTLED. It was declared on `reticle_act`'s
|
|
38
|
+
* schema and nowhere else, so on a validating profile every other session-bound tool silently
|
|
39
|
+
* dropped it — a throttled tab, where drives can no-op, returned a healthy-looking result.
|
|
40
|
+
*/
|
|
41
|
+
WARNING: 'warning',
|
|
42
|
+
/** One-shot "ask the human how this went". */
|
|
43
|
+
FEEDBACK_PROMPT: 'feedback_prompt',
|
|
44
|
+
/** One-shot "a newer Reticle exists". */
|
|
45
|
+
UPDATE_AVAILABLE: 'update_available',
|
|
46
|
+
/**
|
|
47
|
+
* "You have driven this page N times without asking for a verdict."
|
|
48
|
+
*
|
|
49
|
+
* The largest known lever on the metric this product is judged by: verdict-less sessions
|
|
50
|
+
* overwhelmingly never call a verdict-producing tool at all. It was spliced undeclared, so on a
|
|
51
|
+
* validating client the nudge was built, fired, and thrown away before any agent saw it.
|
|
52
|
+
*/
|
|
53
|
+
VERIFY_NEXT: 'verify_next',
|
|
54
|
+
/** The contextual "tell us what just went wrong" invitation. Same story, same release. */
|
|
55
|
+
FEEDBACK_INVITE: 'feedback_invite',
|
|
56
|
+
/** SDK/daemon version skew — often the one fact that explains everything else in the session. */
|
|
57
|
+
VERSION_SKEW: 'version_skew',
|
|
58
|
+
/** A feedback report that was accepted and then failed to send. Only the reporter can act on it. */
|
|
59
|
+
FEEDBACK_UNDELIVERED: 'feedback_undelivered',
|
|
42
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Fields that `runTool` / `withControl` splice onto EVERY session-bound tool result at runtime.
|
|
63
|
+
* Merged into each session-bound tool's outputSchema so a schema-strict client (structuredContent
|
|
64
|
+
* validation) keeps them instead of silently dropping them.
|
|
65
|
+
*
|
|
66
|
+
* Derived from `EnvelopeKey` rather than re-listed: this shape and the splice sites disagreed for a
|
|
67
|
+
* whole release, and every key that went missing was a channel nobody could see was missing.
|
|
68
|
+
*/
|
|
69
|
+
export const sessionEnvelopeShape = Object.fromEntries(Object.values(EnvelopeKey).map((key) => [
|
|
70
|
+
key,
|
|
71
|
+
// `warning` is the one with a narrower type; the rest are opaque envelopes the client forwards.
|
|
72
|
+
EnvelopeKey.WARNING === key ? z.string().optional() : z.unknown().optional(),
|
|
73
|
+
]));
|
|
43
74
|
/** Unwrap a browser command result or throw its error so the agent sees a clean failure. */
|
|
44
75
|
export async function commandOrThrow(deps, sessionId,
|
|
45
76
|
// ReticleCommand, not string: the union already exists and every caller passes a member. Typed as
|
|
@@ -100,16 +100,16 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
100
100
|
ReticleTool.ACT_AND_WAIT,
|
|
101
101
|
// ACT_SEQUENCE is here because its absence was measurably CAUSING the biggest loop in the data.
|
|
102
102
|
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
103
|
+
// In the field `reticle_act` is called overwhelmingly more often than `reticle_act_sequence`, and
|
|
104
|
+
// `act` leads the repeat table by a wide margin — inside those sessions the repeated calls are
|
|
105
|
+
// clicks and fills, a login form driven one round trip at a time, which is exactly the antipattern
|
|
106
|
+
// SKILL.md warns about and exactly what this tool exists to collapse.
|
|
107
107
|
//
|
|
108
|
-
// The repeats are NOT retries: looping sessions have a
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
108
|
+
// The repeats are NOT retries: looping sessions have a LOWER error rate than non-looping ones. The
|
|
109
|
+
// calls succeed and get repeated, because the batching tool was reachable only through
|
|
110
|
+
// `reticle_run` — so an agent had to already know it existed to use it, and essentially nobody
|
|
111
|
+
// did. A tool an agent must already know about is a tool that never gets called; the same argument
|
|
112
|
+
// that put INSPECT and FEEDBACK here.
|
|
113
113
|
ReticleTool.ACT_SEQUENCE,
|
|
114
114
|
ReticleTool.OBSERVE,
|
|
115
115
|
ReticleTool.NETWORK,
|
|
@@ -144,6 +144,25 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
144
144
|
// deleting the instruction from the lease and the pause hint — deletes the handback protocol,
|
|
145
145
|
// because there is nowhere else those two calls are ever named.
|
|
146
146
|
ReticleTool.SESSION,
|
|
147
|
+
// CAPABILITIES is the highest orientation-per-token tool on the surface: one call returns the app's
|
|
148
|
+
// whole testable surface — its testids, signals, stores and named flows — and `{fromDisk:true}`
|
|
149
|
+
// returns the same from a checked-in contract with NO browser attached, so an agent with zero
|
|
150
|
+
// context starts oriented instead of exploring.
|
|
151
|
+
//
|
|
152
|
+
// Same argument as the four above, and it is the fifth time this file has made it: a tool an agent
|
|
153
|
+
// must already know about, and reach through reticle_run, is a tool that never gets called.
|
|
154
|
+
//
|
|
155
|
+
// The cost is real and is NOT the bargain #262 claimed. That issue argued the marginal cost sits
|
|
156
|
+
// "well below the 274-token average" because the input schema is small; measured, its description
|
|
157
|
+
// plus schema is closer to 325 tokens, which is above the average rather than below it. This is
|
|
158
|
+
// also a straight +1 rather than a swap: the tools that write-up proposed demoting (`project`,
|
|
159
|
+
// `domain`) were never in the default set to begin with.
|
|
160
|
+
//
|
|
161
|
+
// So it is a deliberate bet, taken with that correction on the table: that orientation replaces
|
|
162
|
+
// exploratory snapshots, which are among the most expensive calls an agent makes. The measurement
|
|
163
|
+
// that settles it is whether sessions calling this early take fewer and cheaper snapshots than
|
|
164
|
+
// those that do not. If that does not show up, this is the entry to revert.
|
|
165
|
+
ReticleTool.CAPABILITIES,
|
|
147
166
|
]);
|
|
148
167
|
/** Is the truthy form of a boolean env var set? `1`, `true`, `yes` — anything else is off. */
|
|
149
168
|
function envFlagOn(raw) {
|
package/dist/tools/tools.js
CHANGED
|
@@ -78,6 +78,10 @@ const RAW_TOOLS = [
|
|
|
78
78
|
recommendation: z.string().optional(),
|
|
79
79
|
}))
|
|
80
80
|
.describe('Connected browser sessions with health state. `projectId` groups sessions by app (stable across port changes); `leased` marks a pool-managed headless context vs a human tab.'),
|
|
81
|
+
why: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Present ONLY when `sessions` is empty: why nothing is connected, and the next action that fixes it. An empty list is never the end of the road — read this before concluding the app cannot be driven.'),
|
|
81
85
|
},
|
|
82
86
|
handler: async (deps) => {
|
|
83
87
|
const provider = deps.realInput;
|
|
@@ -87,6 +91,14 @@ const RAW_TOOLS = [
|
|
|
87
91
|
realInputAvailable: provider !== undefined ? await provider.isAvailableFor(s.url) : false,
|
|
88
92
|
leased: leasedIds.has(s.sessionId),
|
|
89
93
|
})));
|
|
94
|
+
// An empty list is the most common thing this tool ever returns, and on its own it is a dead
|
|
95
|
+
// end: the agent asked the one question it knows to ask, got a confident-looking answer with
|
|
96
|
+
// no next step, and stopped. The daemon already knows WHY nothing is connected — say it here
|
|
97
|
+
// rather than only when a later tool fails for want of a session.
|
|
98
|
+
if (0 === sessions.length) {
|
|
99
|
+
const why = deps.sessions.noSessionHint();
|
|
100
|
+
return { sessions, ...(why === undefined ? {} : { why }) };
|
|
101
|
+
}
|
|
90
102
|
return { sessions };
|
|
91
103
|
},
|
|
92
104
|
},
|
|
@@ -193,6 +205,10 @@ const RAW_TOOLS = [
|
|
|
193
205
|
.string()
|
|
194
206
|
.optional()
|
|
195
207
|
.describe('CSS selector or element ref to restrict the search to a subtree.'),
|
|
208
|
+
self: z
|
|
209
|
+
.boolean()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe('Return the `scope` element ITSELF instead of searching inside it. Use when the target is a plain layout container with no role, name, testid or text of its own — which is routinely the element that carries the handler, and is otherwise unreachable because every query excludes its own scope root. Requires `scope`.'),
|
|
196
212
|
attrs: z
|
|
197
213
|
.array(z.string())
|
|
198
214
|
.optional()
|
|
@@ -297,7 +313,13 @@ const RAW_TOOLS = [
|
|
|
297
313
|
// The handler forwards an explicit allowlist, so a new input is silently dropped unless it is
|
|
298
314
|
// added here — the browser saw no `attrs` and returned undefined while the unit tests, which
|
|
299
315
|
// call matchQuery directly, passed. Schema plus implementation is not the whole wire.
|
|
316
|
+
//
|
|
317
|
+
// It happened again with `self`, in the same session that wrote this comment: declared on the
|
|
318
|
+
// tool, implemented in the browser, verified by unit tests that call `runQuery` directly, and
|
|
319
|
+
// dropped here — so the live call returned zero matches with no error. `query-forwarding.test.ts`
|
|
320
|
+
// now asserts the payload, because a comment is not a guard.
|
|
300
321
|
attrs: args['attrs'],
|
|
322
|
+
self: args['self'],
|
|
301
323
|
}).then((result) => withSizeCost(paginateQueryResult(result, asNumber(args['limit']), true === args['count_only'])));
|
|
302
324
|
},
|
|
303
325
|
},
|
|
@@ -336,10 +358,22 @@ const RAW_TOOLS = [
|
|
|
336
358
|
})
|
|
337
359
|
.partial()
|
|
338
360
|
.optional(),
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
361
|
+
scroll: z
|
|
362
|
+
.object({
|
|
363
|
+
scrollTop: z.number(),
|
|
364
|
+
scrollHeight: z.number(),
|
|
365
|
+
clientHeight: z.number(),
|
|
366
|
+
overflowY: z.string(),
|
|
367
|
+
})
|
|
368
|
+
.optional(),
|
|
369
|
+
// Theme compliance vs the app's design tokens: { colorToken, colorTokens, backgroundToken,
|
|
370
|
+
// backgroundTokens, offTheme, tokenCount, themeScope }. The plural fields carry EVERY token
|
|
371
|
+
// matching the resolved colour; the singular ones abstain (null) when several tokens share it,
|
|
372
|
+
// because returning an arbitrary winner was the defect (#313). `themeScope` names the theme
|
|
373
|
+
// that was active at capture, so two inspects minutes apart are comparable at all.
|
|
374
|
+
// Kept as unknown — the structured-content serializer can truncate a large inspect payload's
|
|
375
|
+
// fields to strings, which a strict shape would reject; the full object is always present in
|
|
376
|
+
// the text content the agent reads.
|
|
343
377
|
theme: z.unknown().optional(),
|
|
344
378
|
/**
|
|
345
379
|
* Where this element is written, as `file:line`. Present when the app is built with the
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remember the private parent of a capture after the daemon has read it.
|
|
3
|
+
*
|
|
4
|
+
* Legacy captures live directly in `tmpdir()` and therefore have no directory to reclaim. The
|
|
5
|
+
* caller has already applied the capture-path trust-boundary check; this second exact parent check
|
|
6
|
+
* keeps the cleanup set limited to the one-level private layout.
|
|
7
|
+
*/
|
|
8
|
+
export declare function trackCaptureDirectory(path: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Remove empty private capture directories during the daemon's existing awaited shutdown path.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately `rmdir`, never recursive removal: captures are unlinked after reading, so a genuine
|
|
13
|
+
* directory is empty by shutdown. If anything unexpected remains, refusing to remove the directory
|
|
14
|
+
* is safer than widening a renderer-supplied path into a recursive delete.
|
|
15
|
+
*/
|
|
16
|
+
export declare function cleanupCaptureDirectories(): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { rmdir } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { basename, dirname } from 'node:path';
|
|
4
|
+
import { RETICLE_CAPTURE_FILE_PREFIX } from '@reticlehq/core';
|
|
5
|
+
/** Private shell directories whose captures this daemon has successfully consumed. */
|
|
6
|
+
const captureDirectories = new Set();
|
|
7
|
+
/**
|
|
8
|
+
* Remember the private parent of a capture after the daemon has read it.
|
|
9
|
+
*
|
|
10
|
+
* Legacy captures live directly in `tmpdir()` and therefore have no directory to reclaim. The
|
|
11
|
+
* caller has already applied the capture-path trust-boundary check; this second exact parent check
|
|
12
|
+
* keeps the cleanup set limited to the one-level private layout.
|
|
13
|
+
*/
|
|
14
|
+
export function trackCaptureDirectory(path) {
|
|
15
|
+
const parent = dirname(path);
|
|
16
|
+
if (dirname(parent) === tmpdir() && basename(parent).startsWith(RETICLE_CAPTURE_FILE_PREFIX)) {
|
|
17
|
+
captureDirectories.add(parent);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Remove empty private capture directories during the daemon's existing awaited shutdown path.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately `rmdir`, never recursive removal: captures are unlinked after reading, so a genuine
|
|
24
|
+
* directory is empty by shutdown. If anything unexpected remains, refusing to remove the directory
|
|
25
|
+
* is safer than widening a renderer-supplied path into a recursive delete.
|
|
26
|
+
*/
|
|
27
|
+
export async function cleanupCaptureDirectories() {
|
|
28
|
+
await Promise.all([...captureDirectories].map(async (dir) => {
|
|
29
|
+
try {
|
|
30
|
+
await rmdir(dir);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Best-effort shutdown housekeeping. A non-empty or already-removed directory is harmless.
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
captureDirectories.delete(dir);
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import type { ToolDef } from '../tools/tools.js';
|
|
2
|
+
/**
|
|
3
|
+
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
4
|
+
* become a file-read oracle for whatever a compromised renderer names.
|
|
5
|
+
*
|
|
6
|
+
* Two layouts are accepted, and the pair is the whole subtlety:
|
|
7
|
+
*
|
|
8
|
+
* - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
|
|
9
|
+
* so no other local user can read the screenshot or pre-place a symlink at the path we write.
|
|
10
|
+
* - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
|
|
11
|
+
* SEPARATE package on the user's own upgrade schedule: an app still on the older
|
|
12
|
+
* `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
|
|
13
|
+
* start returning no image. It grants the daemon nothing it did not already have.
|
|
14
|
+
*
|
|
15
|
+
* Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
|
|
16
|
+
* `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
|
|
17
|
+
* `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isCapturePath(path: string): boolean;
|
|
2
20
|
/**
|
|
3
21
|
* The opt-in pixel layer. Both tools require a DRIVEN browser (reticle drive /
|
|
4
22
|
* RETICLE_CDP_URL) — the always-on SDK ships no screenshotter — and return a structured NO_PROVIDER
|
|
@@ -5,6 +5,7 @@ import { sessionIdShape } from '../tools/tool-kit.js';
|
|
|
5
5
|
import { asNumber, asRecord, asString } from '../tools/tools-helpers.js';
|
|
6
6
|
import { diffPng } from './visual-diff.js';
|
|
7
7
|
import { VisualStore } from './visual-store.js';
|
|
8
|
+
import { trackCaptureDirectory } from './capture-cleanup.js';
|
|
8
9
|
import { readFile, unlink } from 'node:fs/promises';
|
|
9
10
|
import { tmpdir } from 'node:os';
|
|
10
11
|
import { basename, dirname } from 'node:path';
|
|
@@ -87,6 +88,7 @@ async function desktopCapture(deps, sessionId, fullPage) {
|
|
|
87
88
|
try {
|
|
88
89
|
const bytes = await readFile(path);
|
|
89
90
|
await unlink(path).catch(() => undefined);
|
|
91
|
+
trackCaptureDirectory(path);
|
|
90
92
|
return isCompletePng(bytes) ? { png: new Uint8Array(bytes) } : {};
|
|
91
93
|
}
|
|
92
94
|
catch {
|
|
@@ -94,12 +96,29 @@ async function desktopCapture(deps, sessionId, fullPage) {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
/**
|
|
97
|
-
* Only read a capture the shell wrote
|
|
98
|
-
*
|
|
99
|
-
*
|
|
99
|
+
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
100
|
+
* become a file-read oracle for whatever a compromised renderer names.
|
|
101
|
+
*
|
|
102
|
+
* Two layouts are accepted, and the pair is the whole subtlety:
|
|
103
|
+
*
|
|
104
|
+
* - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
|
|
105
|
+
* so no other local user can read the screenshot or pre-place a symlink at the path we write.
|
|
106
|
+
* - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
|
|
107
|
+
* SEPARATE package on the user's own upgrade schedule: an app still on the older
|
|
108
|
+
* `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
|
|
109
|
+
* start returning no image. It grants the daemon nothing it did not already have.
|
|
110
|
+
*
|
|
111
|
+
* Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
|
|
112
|
+
* `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
|
|
113
|
+
* `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
|
|
100
114
|
*/
|
|
101
|
-
function isCapturePath(path) {
|
|
102
|
-
|
|
115
|
+
export function isCapturePath(path) {
|
|
116
|
+
if (!basename(path).startsWith(RETICLE_CAPTURE_FILE_PREFIX))
|
|
117
|
+
return false;
|
|
118
|
+
const parent = dirname(path);
|
|
119
|
+
if (parent === tmpdir())
|
|
120
|
+
return true;
|
|
121
|
+
return dirname(parent) === tmpdir() && basename(parent).startsWith(RETICLE_CAPTURE_FILE_PREFIX);
|
|
103
122
|
}
|
|
104
123
|
/**
|
|
105
124
|
* A PNG is complete only if it ends with IEND. This check exists because a truncated image once
|
package/docs/README.md
CHANGED
|
@@ -1,31 +1,73 @@
|
|
|
1
1
|
# Docs index
|
|
2
2
|
|
|
3
|
-
Two audiences share this directory
|
|
3
|
+
Two audiences share this directory: people using Reticle and people working on it. Every page carries a `description` in its frontmatter saying what it answers; this index is the shortcut when you want to scan all of them at once.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `docs.json`, which is the Mintlify config for this directory. Five tabs. **Guides** to learn in order, grouped by what you are doing (get started, install, instrument, verify and ship, work with agents, and when it goes wrong). **Reference** to look one thing up. **Desktop** because Electron and Tauri are a different shape of problem and deserve their own front door, **Resources** to decide or to check a claim, and **Contributing** for people working on Reticle. Contributor docs sit in a tab rather than hidden, since they were reachable by URL anyway, and an unlisted page is a page nobody finds.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## For people using Reticle
|
|
10
10
|
|
|
11
|
-
**Start:** [
|
|
11
|
+
**Start:** [quickstart.mdx](quickstart.mdx) → [usage.md](usage.md) (the full tool reference).
|
|
12
12
|
|
|
13
13
|
| Page | What it answers |
|
|
14
14
|
| --- | --- |
|
|
15
|
+
| [quickstart.mdx](quickstart.mdx) | five minutes to a real verdict; every response on it was captured live |
|
|
16
|
+
| [what-is-reticle.mdx](what-is-reticle.mdx) | the definition, what Reticle is NOT, and what it needs to run |
|
|
17
|
+
| [why-reticle.mdx](why-reticle.mdx) | the false-green problem, the measured case, and where we lose |
|
|
18
|
+
| [install-agentic.mdx](install-agentic.mdx) | what `npx @reticlehq/server init` writes, and how to read its marks |
|
|
19
|
+
| [install-manual.mdx](install-manual.mdx) | wiring the MCP server and SDK by hand, per agent and framework |
|
|
15
20
|
| [getting-started.md](getting-started.md) | install it, connect an agent, verify something |
|
|
16
|
-
| [usage.md](usage.md) |
|
|
21
|
+
| [usage.md](usage.md) | the long-form narrative; most of it now has a focused page, and it says so at the top |
|
|
22
|
+
| [predicates.mdx](predicates.mdx) | the predicate grammar, and which kinds actually prove something |
|
|
23
|
+
| [capabilities.mdx](capabilities.mdx) | coverage, storage, fake clocks, crawl, visual diff, network mocking |
|
|
24
|
+
| [recipes.mdx](recipes.mdx) | worked examples for real situations, with the response each returned |
|
|
25
|
+
| [actions.mdx](actions.mdx) | every action and its arguments, including `press` and its history |
|
|
26
|
+
| [faq.mdx](faq.mdx) | production, frameworks, comparisons, and the honest limits |
|
|
27
|
+
| [tools-overview.mdx](tools-overview.mdx) | the 18 advertised tools, the 30 in the cold tail, and why |
|
|
28
|
+
| [tools-snapshot.mdx](tools-snapshot.mdx) | `reticle_snapshot`: three modes, from full tree to a 25-token route check |
|
|
29
|
+
| [tools-query.mdx](tools-query.mdx) | `reticle_query` finds elements by testing-library semantics |
|
|
30
|
+
| [tools-inspect.mdx](tools-inspect.mdx) | `reticle_inspect`: one element, down to source and design tokens |
|
|
31
|
+
| [tools-navigate.mdx](tools-navigate.mdx) | `reticle_navigate`, and why `ok` does not mean the page arrived |
|
|
32
|
+
| [tools-act.mdx](tools-act.mdx) | `reticle_act` acts, proves nothing, and says so |
|
|
33
|
+
| [tools-act-and-wait.mdx](tools-act-and-wait.mdx) | `reticle_act_and_wait`, the tool that produces a verdict |
|
|
34
|
+
| [tools-act-sequence.mdx](tools-act-sequence.mdx) | `reticle_act_sequence`: batch a form into one round trip |
|
|
35
|
+
| [tools-observe.mdx](tools-observe.mdx) | `reticle_observe`, the whole timeline when you don't know what broke |
|
|
36
|
+
| [tools-network.mdx](tools-network.mdx) | `reticle_network`: the request log, redaction, and buffer honesty |
|
|
37
|
+
| [tools-console.mdx](tools-console.mdx) | `reticle_console`, and an empty result that proves it looked |
|
|
38
|
+
| [tools-state.mdx](tools-state.mdx) | `reticle_state`: what the app believes, not what it drew |
|
|
39
|
+
| [tools-wait-for.mdx](tools-wait-for.mdx) | `reticle_wait_for`, for consequences you did not cause |
|
|
40
|
+
| [tools-assert.mdx](tools-assert.mdx) | `reticle_assert`: verdicts, and a real failure explained |
|
|
41
|
+
| [tools-sessions.mdx](tools-sessions.mdx) | `reticle_sessions`: the health fields that decide if driving works |
|
|
42
|
+
| [tools-tools-and-run.mdx](tools-tools-and-run.mdx) | `reticle_tools` / `reticle_run`, reaching the other 30 |
|
|
43
|
+
| [tools-session-and-feedback.mdx](tools-session-and-feedback.mdx) | `reticle_session` / `reticle_feedback`, the human boundary |
|
|
44
|
+
| [packages.mdx](packages.mdx) | every library at a glance: what it does, why it exists, when you need it |
|
|
45
|
+
| [packages/](packages/) | one page per published package, with its real exported API and dependencies |
|
|
46
|
+
| [best-practices.mdx](best-practices.mdx) | the habits that make a verdict worth trusting |
|
|
47
|
+
| [skill-file.mdx](skill-file.mdx) | the paste-one-URL skill that teaches an agent Reticle |
|
|
17
48
|
| [agent-cheatsheet.md](agent-cheatsheet.md) | the condensed version an agent keeps in context |
|
|
49
|
+
| [for-agents.md](for-agents.md) | how to fetch these docs as Markdown or `llms.txt` |
|
|
18
50
|
| [architecture.md](architecture.md) | how it works, and why it is built this way |
|
|
19
51
|
| [platform-integration.md](platform-integration.md) | Vite, Next, Remix, Astro, plain HTML |
|
|
20
|
-
| [desktop
|
|
52
|
+
| [desktop.mdx](desktop.mdx) | why desktop is different, and what only Reticle does there |
|
|
53
|
+
| [desktop-apps.md](desktop-apps.md) | Electron and Tauri, in full |
|
|
54
|
+
| [frameworks.mdx](frameworks.mdx) | what is supported, what is wired-but-unverified, and the wiring |
|
|
55
|
+
| [state-management.mdx](state-management.mdx) | zustand, Redux, and eight adapters |
|
|
56
|
+
| [instrumentation.mdx](instrumentation.mdx) | stores, signals, testids, and how verdicts get stronger |
|
|
57
|
+
| [troubleshooting.mdx](troubleshooting.mdx) | nothing connected, click did nothing, verdict says unknown |
|
|
58
|
+
| [cli.mdx](cli.mdx) | the CLI at a glance, with real output and measured exit codes |
|
|
59
|
+
| [cli/](cli/) | one page per command: every flag, what it prints, and its exit codes |
|
|
21
60
|
| [integration-patterns.md](integration-patterns.md) | wiring it into a real codebase |
|
|
22
61
|
| [flows.md](flows.md) | record → save → replay → heal |
|
|
23
|
-
| [testing.md](testing.md) | `@reticlehq/test
|
|
62
|
+
| [testing.md](testing.md) | `@reticlehq/test`, turning a session into a CI suite |
|
|
24
63
|
| [multi-agent-testing.md](multi-agent-testing.md) | more than one agent on one app |
|
|
25
64
|
| [human-control.md](human-control.md) | taking the wheel back from the agent |
|
|
26
65
|
| [deploy-checks.md](deploy-checks.md) | running Reticle against a deployed build |
|
|
27
66
|
| [token-efficiency.md](token-efficiency.md) | why it costs less than a screenshot loop |
|
|
28
67
|
| [benchmarks.md](benchmarks.md) | how the numbers were measured, including where Reticle loses |
|
|
68
|
+
| [vs-playwright-mcp.mdx](vs-playwright-mcp.mdx) | outside-in vs inside-out, and when Playwright is right |
|
|
69
|
+
| [vs-chrome-devtools-mcp.mdx](vs-chrome-devtools-mcp.mdx) | cheaper per look, catches less: the trade, measured |
|
|
70
|
+
| [vs-screenshots.mdx](vs-screenshots.mdx) | why a better vision model does not fix a non-visual bug |
|
|
29
71
|
| [telemetry.md](telemetry.md) | what is collected, and how to turn it off |
|
|
30
72
|
| [local-registry.md](local-registry.md) | installing an unpublished build |
|
|
31
73
|
| [enterprise.md](enterprise.md) | the licensed surface |
|
|
@@ -36,20 +78,23 @@ Two audiences share this directory, which is why it looked like twenty-five unso
|
|
|
36
78
|
|
|
37
79
|
| Page | What it answers |
|
|
38
80
|
| --- | --- |
|
|
39
|
-
| [gates.md](gates.md) | **I changed some files
|
|
81
|
+
| [gates.md](gates.md) | **I changed some files. Which gate do I run?** |
|
|
40
82
|
| [gate-plan.md](gate-plan.md) | why the gates are shaped this way, and what is still unbuilt |
|
|
41
83
|
| [system-map.md](system-map.md) | how a tool call reaches the app, and which failures are silent |
|
|
42
84
|
| [telemetry-contract.md](telemetry-contract.md) | required reading before touching anything that emits |
|
|
43
|
-
| [
|
|
85
|
+
| [telemetry-events.mdx](telemetry-events.mdx) | every event kind and its payload, and the question each one exists to answer |
|
|
86
|
+
| [debugging.md](debugging.md) | debugging Reticle itself: the four signals and what each answers |
|
|
44
87
|
| [fixtures.md](fixtures.md) | the sibling `reticle-fixtures` repo: install complexity, not regressions |
|
|
45
|
-
| [matrix/README.md](matrix/README.md) | submitting an MCP-client compatibility record
|
|
88
|
+
| [matrix/README.md](matrix/README.md) | submitting an MCP-client compatibility record, the best first contribution |
|
|
46
89
|
| [matrix/MATRIX.md](matrix/MATRIX.md) | which clients are known to work (generated; do not hand-edit) |
|
|
47
90
|
| [`../apps/README.md`](../apps/README.md) | what belongs in `apps/`, and what does not |
|
|
48
91
|
| [`../apps/e2e/harness-rules.md`](../apps/e2e/harness-rules.md) | the four rules every gate obeys, and the incident behind each |
|
|
49
92
|
| [`../bench/README.md`](../bench/README.md) | what in `bench/` is live and what is a kept one-off study |
|
|
50
93
|
|
|
51
|
-
`fixtures-dispatch-receiver.yml` is not a doc
|
|
94
|
+
`fixtures-dispatch-receiver.yml` is not a doc; it is the workflow template `reticle-fixtures` needs, kept in this repo so the two ends of the contract cannot drift apart.
|
|
52
95
|
|
|
53
96
|
---
|
|
54
97
|
|
|
55
|
-
**Adding a page?**
|
|
98
|
+
**Adding a page?** Three things, all required. Give it `title` + `description` frontmatter and no `# H1`. Mintlify renders the frontmatter title as the heading, so an H1 shows up twice. Add its slug to the right tab in `docs.json`. Add a row to the right table above. A page missing from `docs.json` is one nobody can reach; a page missing from this index is one no contributor knows exists.
|
|
99
|
+
|
|
100
|
+
**Adding a second product?** Do not nest these files under a product folder pre-emptively; that renames every live URL to buy nothing. Mintlify supports `navigation.products`, where each product declares its own `directory`. When the second product exists, put its pages in `docs/<product>/`, switch `docs.json` from `tabs` to `products`, and give this product `"directory": ""` so today's URLs keep working.
|
package/docs/actions.mdx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Action reference: lights, camera, click
|
|
3
|
+
sidebarTitle: Actions
|
|
4
|
+
description: Every action reticle_act and reticle_act_and_wait can perform, with its arguments and the one that has a history.
|
|
5
|
+
icon: hand-pointer
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
```json
|
|
9
|
+
{ "ref": "e42", "action": "fill", "args": { "value": "hello" } }
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| Action | Arguments | Notes |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `click` / `dblclick` | none | Dispatches a real click |
|
|
15
|
+
| `hover` | none | `mouseover` + `mouseenter`, so JS hover state triggers |
|
|
16
|
+
| `focus` / `blur` | none | |
|
|
17
|
+
| `fill` | `{ value }` | Sets value via a React-safe native setter, then `input` and `change` |
|
|
18
|
+
| `type` | `{ text }` | **Appends** to the current value |
|
|
19
|
+
| `clear` | none | Empties an input |
|
|
20
|
+
| `select` | `{ value }` | For `<select>` |
|
|
21
|
+
| `check` / `uncheck` | none | Checkbox and radio |
|
|
22
|
+
| `submit` | none | Submits the element's `<form>` |
|
|
23
|
+
| `press` | `{ text }` | The key **name**: `Escape`, `Tab`, `Enter` |
|
|
24
|
+
| `scrollIntoView` | none | |
|
|
25
|
+
| `upload` | `{ name, content?, type? }` | Sets a file on `<input type="file">` |
|
|
26
|
+
| `drag` | `{ toRef }` | Pointer-based drag (dnd-kit, react-beautiful-dnd) plus HTML5 DnD |
|
|
27
|
+
| `webmcp` | `{ tool, params }` | Calls a `navigator.modelContext` tool, if the site exposes one |
|
|
28
|
+
|
|
29
|
+
## `fill` versus `type`
|
|
30
|
+
|
|
31
|
+
`fill` replaces. `type` appends. Reaching for `type` when you meant `fill` is how a field ends up containing `hellohello`, and neither call will complain.
|
|
32
|
+
|
|
33
|
+
## `press` takes `text`, not `key`
|
|
34
|
+
|
|
35
|
+
Both are accepted today, and `text` wins when both are present. Use `text`. It is what the tool description documents.
|
|
36
|
+
|
|
37
|
+
<Warning>
|
|
38
|
+
This one has history worth knowing. The implementation used to read `args.key` and **default to
|
|
39
|
+
`Enter`**, so the documented call, `{ action: "press", args: { text: "Escape" } }`, silently sent
|
|
40
|
+
`Enter` and reported success.
|
|
41
|
+
|
|
42
|
+
Three consequences, worst last: the requested key never arrived, so Escape-to-close and Tab-traversal went unverified while looking verified; nothing in the result said the argument had been ignored; and `Enter` is not a neutral substitute. On a focused field inside a form it **submits it**. A request to close a dialog could file the form behind it.
|
|
43
|
+
|
|
44
|
+
Two field reports were exactly this, both diagnosed as "synthetic events don't reach the app". Wrong root cause: the event reached the app perfectly well and simply said `Enter`.
|
|
45
|
+
|
|
46
|
+
</Warning>
|
|
47
|
+
|
|
48
|
+
That bug is fixed, and the reason it is written down here is that it is the exact shape of failure Reticle exists to catch. An action that reports success while doing something else.
|
|
49
|
+
|
|
50
|
+
## Batching
|
|
51
|
+
|
|
52
|
+
Several actions in one round trip:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"steps": [
|
|
57
|
+
{ "ref": "e101", "action": "fill", "args": { "value": "admin@reticle.dev" } },
|
|
58
|
+
{ "ref": "e102", "action": "fill", "args": { "value": "hunter2" } },
|
|
59
|
+
{ "ref": "e103", "action": "click" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<Warning>
|
|
65
|
+
[`reticle_act_sequence`](/tools-act-sequence) returns `ok: true` when the steps were
|
|
66
|
+
**dispatched**, not when they worked. Batch the setup, then prove the outcome with
|
|
67
|
+
[`reticle_act_and_wait`](/tools-act-and-wait) on the final step.
|
|
68
|
+
</Warning>
|
|
69
|
+
|
|
70
|
+
## Real versus synthetic input
|
|
71
|
+
|
|
72
|
+
`inputMode` in the response says which you got. `synthetic` means events dispatched programmatically. Correct for the overwhelming majority of apps. `real` means native CDP input, available under `reticle drive`, and needed by a few drag-and-drop and pointer-gesture libraries.
|
|
73
|
+
|
|
74
|
+
<Warning>
|
|
75
|
+
On a throttled tab, synthetic timers and pointer gestures can silently no-op. Pass
|
|
76
|
+
`refuseWhenThrottled: true` to fail loudly instead of acting into a tab the browser has paused.
|
|
77
|
+
</Warning>
|
|
78
|
+
|
|
79
|
+
## What every action reports
|
|
80
|
+
|
|
81
|
+
`valueChanged`, `focusMoved`, `domMutatedWithin`, `defaultPrevented`, `occluded` and `occludedBy`. Plus the element's `testid`, `component` and `source` file and line.
|
|
82
|
+
|
|
83
|
+
`occluded` is the answer to a large share of "the button doesn't work" reports: the click landed on the modal overlay sitting on top of it.
|
|
84
|
+
|
|
85
|
+
<Card title="Full field-by-field walkthrough" icon="hand-pointer" href="/tools-act">
|
|
86
|
+
What each effect field tells you, with a real response.
|
|
87
|
+
</Card>
|