@reticlehq/server 3.8.0 → 3.8.1

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 (106) hide show
  1. package/SKILL.md +2 -2
  2. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  3. package/dist/command/cli/answers/gate-hook-message.js +5 -1
  4. package/dist/command/cli/auth/cloud-session.d.ts +1 -19
  5. package/dist/command/cli/auth/device-flow.d.ts +3 -37
  6. package/dist/command/cli/cli-doctor.js +30 -23
  7. package/dist/command/cli/cli-flow-commands.js +7 -1
  8. package/dist/command/cli/cli-parse-serve.d.ts +24 -0
  9. package/dist/command/cli/cli-parse-serve.js +124 -0
  10. package/dist/command/cli/cli-parse.d.ts +1 -10
  11. package/dist/command/cli/cli-parse.js +62 -121
  12. package/dist/command/cli/cli-usage.js +5 -2
  13. package/dist/command/cli/cloud-link.d.ts +1 -20
  14. package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
  15. package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
  16. package/dist/command/cli/ports/port-holder.js +6 -1
  17. package/dist/command/cli/project-name.d.ts +1 -7
  18. package/dist/command/cli/project-name.js +1 -0
  19. package/dist/command/daemon/daemon.d.ts +6 -0
  20. package/dist/command/daemon/daemon.js +52 -2
  21. package/dist/command/daemon/win32-escape.d.ts +32 -0
  22. package/dist/command/daemon/win32-escape.js +119 -0
  23. package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
  24. package/dist/command/setup/bringup/dev-server-wait.js +5 -1
  25. package/dist/command/version/version-skew.d.ts +2 -0
  26. package/dist/command/version/version-skew.js +7 -3
  27. package/dist/features/crawl/crawl-tools.js +2 -2
  28. package/dist/features/exhaust/ledger.d.ts +15 -96
  29. package/dist/features/exhaust/ledger.js +3 -3
  30. package/dist/features/license/license.d.ts +2 -14
  31. package/dist/hooks/hook-commands.js +19 -9
  32. package/dist/judgement/capsule/capsule-store.d.ts +2 -39
  33. package/dist/language/flows/annotate-tools.js +1 -1
  34. package/dist/language/flows/fields/flow-secret-field.js +1 -1
  35. package/dist/language/flows/flow-expect-grammar.js +14 -6
  36. package/dist/language/flows/flow-lease.d.ts +27 -0
  37. package/dist/language/flows/flow-lease.js +59 -0
  38. package/dist/language/flows/flow-replay-run.d.ts +1 -87
  39. package/dist/language/flows/flow-replay-run.js +88 -283
  40. package/dist/language/flows/flow-replayable.d.ts +1 -1
  41. package/dist/language/flows/flow-start-path.d.ts +108 -0
  42. package/dist/language/flows/flow-start-path.js +313 -0
  43. package/dist/language/flows/flow-tools.d.ts +2 -23
  44. package/dist/language/flows/flow-tools.js +34 -66
  45. package/dist/language/flows/recording/flake.d.ts +3 -27
  46. package/dist/language/flows/recording/flake.js +1 -1
  47. package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
  48. package/dist/machine/git-tracks.js +1 -0
  49. package/dist/memory/cloud/cloud-sync.d.ts +2 -24
  50. package/dist/memory/intent/intent-shard.d.ts +41 -266
  51. package/dist/memory/journal/envelope-store.js +1 -1
  52. package/dist/memory/journal/envelope.d.ts +6 -154
  53. package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
  54. package/dist/memory/journal/on-disk/ambient-file.js +1 -1
  55. package/dist/memory/journal/self-observation.js +3 -1
  56. package/dist/memory/journal/session-journal.js +39 -20
  57. package/dist/portal/input/viewport-tools.d.ts +19 -15
  58. package/dist/portal/pool/browser-pool.d.ts +5 -55
  59. package/dist/portal/pool/browser-pool.js +121 -100
  60. package/dist/portal/pool/pool-lease.d.ts +128 -0
  61. package/dist/portal/pool/pool-lease.js +49 -0
  62. package/dist/portal/production/production-activation.d.ts +5 -27
  63. package/dist/portal/session/control-envelope.d.ts +1 -1
  64. package/dist/portal/session/zero-install-first-call.d.ts +3 -0
  65. package/dist/portal/session/zero-install-first-call.js +33 -12
  66. package/dist/surface/mcp/mcp.d.ts +3 -3
  67. package/dist/surface/mcp/mcp.js +4 -6
  68. package/dist/surface/tools/act/act-danger.js +32 -17
  69. package/dist/surface/tools/act/durable.js +1 -1
  70. package/dist/surface/tools/act-sequence-tool.js +3 -3
  71. package/dist/surface/tools/act-tools.js +13 -14
  72. package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
  73. package/dist/surface/tools/coverage-tools.js +1 -1
  74. package/dist/surface/tools/dynamic-tools.d.ts +8 -0
  75. package/dist/surface/tools/dynamic-tools.js +36 -16
  76. package/dist/surface/tools/empty-session-list.d.ts +5 -29
  77. package/dist/surface/tools/feedback-tools.js +1 -1
  78. package/dist/surface/tools/gap-novelty.d.ts +1 -1
  79. package/dist/surface/tools/gaps/capability-absences.js +4 -3
  80. package/dist/surface/tools/harness-gate.d.ts +1 -1
  81. package/dist/surface/tools/harness-toolset.js +3 -4
  82. package/dist/surface/tools/lease-permissions.d.ts +1 -1
  83. package/dist/surface/tools/lease-readiness.d.ts +23 -1
  84. package/dist/surface/tools/lease-readiness.js +75 -9
  85. package/dist/surface/tools/lease-tools.d.ts +1 -52
  86. package/dist/surface/tools/lease-tools.js +79 -142
  87. package/dist/surface/tools/observe-tools.js +3 -1
  88. package/dist/surface/tools/read-tools.js +5 -5
  89. package/dist/surface/tools/session-ports.d.ts +57 -0
  90. package/dist/surface/tools/session-ports.js +102 -0
  91. package/dist/surface/tools/tool-kit.d.ts +5 -3
  92. package/dist/surface/tools/tools.js +1 -1
  93. package/dist/telemetry/daemon-telemetry.d.ts +3 -1
  94. package/dist/telemetry/daemon-telemetry.js +9 -2
  95. package/dist/telemetry/telemetry.d.ts +3 -1
  96. package/dist/telemetry/telemetry.js +2 -0
  97. package/docs/agent-cheatsheet.md +12 -0
  98. package/docs/cli/gate.mdx +1 -1
  99. package/docs/cli/verify.mdx +8 -1
  100. package/docs/first-drive.md +7 -0
  101. package/docs/multi-agent-testing.md +2 -0
  102. package/docs/predicates.mdx +6 -0
  103. package/docs/skill.md +3 -3
  104. package/docs/troubleshooting.mdx +10 -0
  105. package/docs/usage.md +1 -1
  106. package/package.json +8 -9
@@ -5,7 +5,7 @@
5
5
  * It lives apart from `cli.ts` because it has its own timers and its own failure rule — nothing here
6
6
  * may ever be able to fail a daemon start — and because `cli.ts` is a dispatcher.
7
7
  */
8
- import { type SessionSummary } from '@reticlehq/core/telemetry';
8
+ import { DaemonSpawnMethod, type SessionSummary } from '@reticlehq/core/telemetry';
9
9
  /**
10
10
  * How often a running daemon rolls up its window.
11
11
  *
@@ -29,6 +29,8 @@ export declare const SESSION_FLUSH_MS: number;
29
29
  * costs nothing on a daemon that never serves a tool, because only a NON-EMPTY window emits.
30
30
  */
31
31
  export declare const FIRST_FLUSH_MS: number;
32
+ /** How the parent started this daemon, from the env it set. Anything else reads as absent. */
33
+ export declare function daemonSpawnMethod(env?: Readonly<Record<string, string | undefined>>): DaemonSpawnMethod | undefined;
32
34
  /** Stops the timers this installed. Called from the daemon's shutdown path. */
33
35
  interface DaemonTelemetry {
34
36
  /**
@@ -5,7 +5,8 @@
5
5
  * It lives apart from `cli.ts` because it has its own timers and its own failure rule — nothing here
6
6
  * may ever be able to fail a daemon start — and because `cli.ts` is a dispatcher.
7
7
  */
8
- import { TelemetryEventKind } from '@reticlehq/core/telemetry';
8
+ import { DaemonSpawnMethod, TelemetryEventKind, } from '@reticlehq/core/telemetry';
9
+ import { ReticleEnv } from '@reticlehq/core';
9
10
  import { getTelemetry } from './telemetry.js';
10
11
  import { getSessionMetrics } from './session-metrics.js';
11
12
  import { profileProject } from './project-profile.js';
@@ -78,6 +79,11 @@ function withNudgeState(summary) {
78
79
  ...(nudge.offered === undefined ? {} : { updateOffered: nudge.offered }),
79
80
  };
80
81
  }
82
+ /** How the parent started this daemon, from the env it set. Anything else reads as absent. */
83
+ export function daemonSpawnMethod(env = process.env) {
84
+ const value = env[ReticleEnv.SPAWN_METHOD];
85
+ return Object.values(DaemonSpawnMethod).find((method) => method === value);
86
+ }
81
87
  /**
82
88
  * Report the daemon as started, profile the project once, and keep the session counters flushing.
83
89
  * Every timer is `unref`'d so telemetry can never be the reason a daemon refuses to exit.
@@ -91,7 +97,8 @@ export function installDaemonTelemetry(cwd, now = () => Date.now(),
91
97
  */
92
98
  port) {
93
99
  const metrics = getSessionMetrics(now);
94
- void getTelemetry().emit(TelemetryEventKind.DAEMON_STARTED);
100
+ const spawnMethod = daemonSpawnMethod();
101
+ void getTelemetry().emit(TelemetryEventKind.DAEMON_STARTED, spawnMethod === undefined ? undefined : { spawnMethod });
95
102
  // Reports a past send could not deliver. Off the boot path, and never able to throw into it.
96
103
  void drainFeedbackOutbox().catch(() => undefined);
97
104
  // Ask npm whether a newer Reticle exists, so the agent can tell the human. Nothing checked before
@@ -1,4 +1,4 @@
1
- import { TelemetryEventKind, ProjectIdSource, type Crash, type Feedback, type Identity, type BugFound, type InitOutcome, type OnboardingStep, type InstallSource, type McpConnection, type McpOutage, type DaemonDeath, type ProxyExit, type WakeRefusal, type ToolRefusal, type AppInstrumentation, type HarnessRun, type HudEvent, type PlatformLink, type ProjectProfile, type SessionSummary, type TelemetryActor, type Verification, type VersionChange } from '@reticlehq/core/telemetry';
1
+ import { TelemetryEventKind, ProjectIdSource, type Crash, type Feedback, type Identity, type BugFound, type InitOutcome, type OnboardingStep, type InstallSource, type McpConnection, type McpOutage, type DaemonDeath, type ProxyExit, type WakeRefusal, type DaemonSpawnMethod, type ToolRefusal, type AppInstrumentation, type HarnessRun, type HudEvent, type PlatformLink, type ProjectProfile, type SessionSummary, type TelemetryActor, type Verification, type VersionChange } from '@reticlehq/core/telemetry';
2
2
  /**
3
3
  * Presence of this file marks the MACHINE as the Reticle team's own: every event carries
4
4
  * `internal: true`. The persistent twin of `RETICLE_INTERNAL=1`, for a dev machine where setting a
@@ -113,6 +113,8 @@ export interface TelemetryExtra {
113
113
  daemonDeath?: DaemonDeath;
114
114
  /** `wake_refused`: what held the port. */
115
115
  wakeRefusal?: WakeRefusal;
116
+ /** `daemon_started`: how the parent started the daemon. */
117
+ spawnMethod?: DaemonSpawnMethod;
116
118
  /**
117
119
  * `platform_linked`: the workspace just linked. Overrides the envelope's own reading, which looks
118
120
  * at the daemon's working directory and not at the project the HUD linked.
@@ -470,6 +470,8 @@ export const createTelemetry = (opts) => {
470
470
  ...(extra?.proxyExit !== undefined ? { proxyExit: extra.proxyExit } : {}),
471
471
  ...(extra?.daemonDeath !== undefined ? { daemonDeath: extra.daemonDeath } : {}),
472
472
  ...(extra?.wakeRefusal !== undefined ? { wakeRefusal: extra.wakeRefusal } : {}),
473
+ // A SCALAR: the event build and the wire schema, no `blocks` entry.
474
+ ...(extra?.spawnMethod !== undefined ? { spawnMethod: extra.spawnMethod } : {}),
473
475
  };
474
476
  // The wire schema's caps are only real if something applies them. An event that fails is
475
477
  // DROPPED, never sent and never repaired: the payload is exactly what the caps exist to stop.
@@ -48,6 +48,7 @@ Two consequences worth knowing:
48
48
 
49
49
  - **You can assert what a field contains.** Do not read the value with `reticle_look { action: "find" }` and compare it in prose: that produces no verdict and does not count as verification.
50
50
  - **`{ role, text }` checks the text,** so it does not match every button with that role.
51
+ - **A `text` predicate reads rendered text, not a field's value.** Text typed into an `<input>` or a `<textarea>` is not on the page as text, so `{ kind: "text", contains }` does not see it. Assert it with `value` on an `element` predicate, as above.
51
52
 
52
53
  Fields that nothing can check are refused rather than ignored: `by` without a `value`, and `label`, `placeholder`, `testid` or `component` when a higher-precedence field already selected the element. `alt` is the one that can still be checked beside another locator: on an image it is compared with the image's `alt` attribute (so `alt: ""` passes on a decorative image but not on one missing its alt), and on anything that is not an image it is refused, with a hint to use `name`. An element query is a first-match dispatch, not a conjunction.
53
54
 
@@ -164,6 +165,17 @@ Recent additions, each of which answers a question agents were previously asking
164
165
  2. `reticle_run({ tool: "reticle_domain", args: {} })` learns the app BEFORE testing: the saved flows, what each asserts, and the **gaps** (declared signals/testids that no flow verifies, i.e. untested intent). Tells you what to test and where the real risk is without crawling the whole app. Falls back to `reticle_run({ tool: "reticle_capabilities", args: {} })` for the raw testable surface (`testids`, `signals`, `stores`, `flows`).
165
166
  3. Run the loop: **look → act → observe → assert**, cross-checking the 4 layers on anything that matters.
166
167
 
168
+ ## No MCP tools in this host
169
+
170
+ A cloud agent or a worker can have a shell and no MCP tools. Use the CLI. `verify --expect` asks a running daemon and never starts one, so start it first:
171
+
172
+ ```bash
173
+ npx @reticlehq/server serve --headless
174
+ npx @reticlehq/server verify http://localhost:5173/ --expect '{"kind":"text","contains":"Welcome"}'
175
+ ```
176
+
177
+ It exits `0` only on `verified: "yes"`. `--expect-file <path>` reads the predicate from a file, and `--timeout N` sets how long it waits (default `4000` ms). To move a connected tab to another page first, run `npx @reticlehq/server open <url> --navigate`. For an action that `--expect` cannot do, use the [HTTP MCP transport](/http-transport).
178
+
167
179
  ## Token note
168
180
 
169
181
  - **Keep observation cheap.** Prefer `reticle_look { action: "find" }` / scoped or `interactive` `reticle_look { action: "page" }` / `reticle_assert` over dumping the full tree. A full verify loop is ~100 tokens; see [Token efficiency](/token-efficiency) (~73× leaner than full-tree snapshots).
package/docs/cli/gate.mdx CHANGED
@@ -49,7 +49,7 @@ One `reticle_gate` line. Real capture from this repository, with the flow lists
49
49
  }
50
50
  ```
51
51
 
52
- `downgraded`, `deletedCoverage`, `unflowed`, `coverageRegressed`, `unexecuted`, `coverageStale` and `coverageAccepted` appear only when they are non-empty, and none was present in that run. The coverage categories need a coverage ledger, which a project gets from `reticle_verify { action: "crawl", exhaustive: true }` or `reticle_verify { action: "coverage", app: true }`; without one they never fire. The CLI prints this as a single line of JSON; it is pretty-printed here to be readable.
52
+ `downgraded`, `deletedCoverage`, `unflowed`, `coverageRegressed`, `unexecuted`, `coverageStale` and `coverageAccepted` appear only when they are non-empty, `flowsWithoutSources` only when it is `true`, and none was present in that run. The coverage categories need a coverage ledger, which a project gets from `reticle_verify { action: "crawl", exhaustive: true }` or `reticle_verify { action: "coverage", app: true }`; without one they never fire. The CLI prints this as a single line of JSON; it is pretty-printed here to be readable.
53
53
 
54
54
  ## Exit codes
55
55
 
@@ -7,7 +7,7 @@ icon: circle-check
7
7
  `reticle verify` is the CI command: no model in the loop, no MCP. On a free port it boots the engine in drive mode, waits for the in-page SDK to dial back, replays every saved flow, renders the verdict, and exits. When a Reticle daemon already owns the port, it asks that daemon to replay the flows instead.
8
8
 
9
9
  ```bash
10
- npx @reticlehq/server verify <url> [--headed] [--timeout N] [--storage-state <file>]
10
+ npx @reticlehq/server verify <url> [--headed] [--timeout N] [--storage-state <file>] [--expect <json> | --expect-file <path>]
11
11
  ```
12
12
 
13
13
  It uses the same runner and the same verdict as the MCP and HTTP paths, so a platform agent that can only run a shell command gets a byte-identical artifact.
@@ -28,6 +28,13 @@ It uses the same runner and the same verdict as the MCP and HTTP paths, so a pla
28
28
  | `--explore` / `--persona "<who does what>"` | boolean / string | off | No saved flows yet? The Harness drives the app and records them. `--persona` implies `--explore`. Needs a linked project (`reticle connect`, or `RETICLE_API_KEY` in CI). It runs at any instrumentation score |
29
29
  | `--confirm-dangerous` | boolean | off | Let this run press the controls the replay guard holds as destructive, such as a "Place order" or "Delete" button in a saved flow. Without it such a step is refused, so a checkout flow cannot pass. It covers this run only and is never saved into a flow. |
30
30
 
31
+ On Windows PowerShell, write the predicate to a file and pass `--expect-file` so the JSON never goes through shell quoting:
32
+
33
+ ```powershell
34
+ '{"kind":"text","contains":"Saved"}' | Set-Content expect.json
35
+ npx @reticlehq/server verify http://localhost:3000 --expect-file expect.json
36
+ ```
37
+
31
38
  ## What it prints
32
39
 
33
40
  The rendered run report on stdout. On failure it names the cause on stderr instead, and there are two refusals worth knowing:
@@ -72,6 +72,13 @@ node server/dist/command/cli.js verify http://localhost:4310/ --port 4400 \
72
72
  --expect '{"kind":"text","contains":"definitely not on this page"}'
73
73
  ```
74
74
 
75
+ On Windows PowerShell, `npx.cmd` re-parses `--expect '{...}'` and strips the inner quotes. Prefer `--expect-file`:
76
+
77
+ ```powershell
78
+ '{"kind":"text","contains":"Saved"}' | Set-Content expect.json
79
+ npx @reticlehq/server verify http://localhost:3000 --expect-file expect.json
80
+ ```
81
+
75
82
  ## 3. Drive it properly, with the tools
76
83
 
77
84
  If your editor has the Reticle MCP server, the same app is now drivable. The four calls worth knowing, in the order you actually use them:
@@ -36,6 +36,8 @@ reticle_run { tool: "reticle_lease", args: { action: "release", sessionId } }
36
36
 
37
37
  `reticle_run { tool: "reticle_lease", args: { action: "acquire" } }` opens a fresh isolated headless context against your **already-running** app, stamps the lease identity into the URL so the app's own SDK registers under a sessionId you can target, and waits until that tab has connected (`ready: true`) before returning, so the sessionId is usable immediately. Release when the flow finishes.
38
38
 
39
+ **Lease the exact host that the dev server binds.** `http://localhost:3000` and `http://127.0.0.1:3000` are two origins, and they are not the same address. A dev server that binds only `::1` (Vite can do this for `localhost`) refuses a lease on `127.0.0.1`, and a server that binds only `127.0.0.1` does not answer on `::1`. The two origins also have their own cookies and storage, and a lease is reused for each origin. Copy the url that the dev server prints, host and port included.
40
+
39
41
  ## Permissions
40
42
 
41
43
  A lease starts with every permission undecided. Pass `permissions` to grant some on the app's origin before the first navigation, so the first render already sees them:
@@ -73,6 +73,12 @@ Add `"absent": true` to assert something is **gone**. The predicate for a remova
73
73
 
74
74
  `contains` is the field the published schema declares. `value`, `text` and `query` are accepted as aliases and rewritten to it. The match is case-sensitive. `ignoreCase: true` relaxes that. Curly quotes (`‘ ’ “ ”`), the en dash and the em dash match their plain forms. So `It's saved - done` matches a page that shows `It’s saved – done`. Text that Reticle reports back keeps the page's own characters. `absent: true` asserts the text is not present, and `visible: true` requires it to be on screen rather than merely in the DOM.
75
75
 
76
+ `text` reads the text that the page renders. It does not read what is typed into an `<input>` or a `<textarea>`, because that is the field's value, not text on the page. `value` on a `text` predicate is only an alias for `contains`, so it also reads rendered text. To assert what a field holds, use an `element` predicate with `value`:
77
+
78
+ ```json
79
+ { "kind": "element", "query": { "role": "textbox", "name": "Notes", "value": "Ship on Friday" } }
80
+ ```
81
+
76
82
  Use `scope` to restrict the search to a CSS selector or element ref. If Reticle reports that the text is split across the scope's children, retry its generated predicate with `"self": true`; that checks the scope root's combined subtree text while still enforcing `contains`.
77
83
 
78
84
  `satisfies` asserts a PROPERTY of the rendered text instead of its exact bytes: the same shape and the same properties [`state` accepts](#satisfies-asserting-a-property-instead-of-exact-bytes), applied where the user actually reads the value:
package/docs/skill.md CHANGED
@@ -3,7 +3,7 @@ name: reticle-docs
3
3
  description: Install, instrument and verify this running web app from the inside (DOM, network, routing, console and framework state) instead of screenshots or guessing. Drives one real flow end to end and returns a verdict with the file:line to fix. Use when the user asks to set up or install Reticle, when a user-facing change needs proving before you call it done, when a test passes but the UI is broken, or when the user types /reticle.
4
4
  license: Apache-2.0
5
5
  metadata:
6
- version: 3.8.0
6
+ version: 3.8.1
7
7
  homepage: https://docs.reticle.sh
8
8
  repository: https://github.com/reticlehq/reticle
9
9
  internal: true
@@ -149,7 +149,7 @@ Stop at the first row that fits. Replaying a saved journey costs a few hundred t
149
149
  | "Did my edit break anything?" | `reticle_verify { action: "change", files: ["src/App.tsx"] }` |
150
150
  | "Does every saved journey still work?" | `reticle_verify { action: "flows" }` |
151
151
  | "Does this new behaviour work?" | `reticle_act_and_wait` on the step that ENDS the journey |
152
- | No MCP at all | `npx @reticlehq/server verify <url> --expect '<predicate JSON>'` |
152
+ | No MCP tools (a cloud agent, a worker) | `npx @reticlehq/server serve`, then `npx @reticlehq/server verify <url> --expect '<predicate JSON>'`. `open <url> --navigate` moves a connected tab to `<url>` |
153
153
 
154
154
  `change` answers `unknown` when no saved flow covers your files: nothing ran, so drive it yourself and never read it as a pass. A locator that was renamed rather than broken: `reticle_verify { action: "heal", flowName }` proposes the rebind, and `apply: true` writes it after it proves the saved consequence again.
155
155
 
@@ -172,7 +172,7 @@ A journey in one call: `reticle_act { steps: [{ target, action, args?, expect? }
172
172
  | ref no longer resolves | the DOM re-rendered: target by `testid` or `role`+`name` |
173
173
  | predicate did not parse | it names the field: fix the shape against the table above |
174
174
  | destructive action blocked | `args.confirmDangerous: true`, only if the user wants that press |
175
- | version skew | `npx @reticlehq/server stop`, then retry |
175
+ | version skew | update the CLI and the app SDK (`npx @reticlehq/server update` in the app's folder), then restart the MCP host |
176
176
  | port held by a non-Reticle process | stop that process, or run Reticle on another `--port` |
177
177
  | tab served from another worktree | start the dev server in your own worktree; keep the port |
178
178
 
@@ -240,6 +240,16 @@ A dev server on `https://` with a self-signed or mkcert certificate makes `retic
240
240
 
241
241
  It is off by default. When it is on, certificate checks are skipped for every site opened in that lease's browser context, including resources loaded from other sites. Other leases keep their checks. A context cannot change the setting once it is made, so acquiring again with the other value replaces the lease instead of reusing it.
242
242
 
243
+ ## The page never connects under a strict CSP
244
+
245
+ A build that sends a strict Content-Security-Policy blocks the bridge socket. A production build that you run locally (`next start`, `vite preview`) often sends one. The app runs, and the SDK never connects, with no error in the app. Add the bridge to `connect-src`, with your bridge port if it is not `4400`:
246
+
247
+ ```text
248
+ connect-src 'self' ws://localhost:4400 ws://127.0.0.1:4400
249
+ ```
250
+
251
+ Add it only in the builds that Reticle drives, never in the policy that you deploy. If the policy also blocks inline scripts, `script-src` stops a pasted connect snippet before it runs: serve that snippet as an external module. `reticle doctor` reads the policy that the page serves and names the line to add, and a lease result says the same. For [production mode](/production-mode), add the `wss://` bridge from the ticket instead. Desktop apps have their own note in [Desktop](/desktop).
252
+
243
253
  ## `ERESOLVE` installing next to a prerelease
244
254
 
245
255
  `@reticlehq/next` declares `peer next >=13` and `@reticlehq/react` declares `peer react >=18`. On a Next canary or React RC, npm refuses. See [the prerelease notes](/getting-started#installing-alongside-a-next-js-or-react-prerelease).
package/docs/usage.md CHANGED
@@ -387,7 +387,7 @@ A **predicate** declares what should be true. `reticle_assert` / `reticle_assert
387
387
  { "kind": "state", "store": "app", "path": "deployments.0.status", "equals": "live" }
388
388
  ```
389
389
 
390
- `visible` excludes elements whose boxes are fully clipped by an ancestor's `overflow:hidden` or `overflow:clip`; a partly clipped element stays visible. Expanding a clamped card can therefore prove that its previously hidden paragraph became visible. `overflow:auto` and `overflow:scroll` keep their existing visibility semantics, including content beyond the scrollport. Ordinary document content outside the window remains visible; `inViewport` checks the window intersection after any hidden/clip ancestor clipping, without introducing scrollport clipping.
390
+ `visible` excludes elements whose boxes are fully clipped by an ancestor's `overflow:hidden` or `overflow:clip`; a partly clipped element stays visible. It also excludes screen-reader-only text: a positioned box with a `clip: rect()` of no area, a `clip-path: inset()` whose edges meet, or a 1×1 box that clips its own overflow. That text stays `present` and still names its control. Expanding a clamped card can therefore prove that its previously hidden paragraph became visible. `overflow:auto` and `overflow:scroll` keep their existing visibility semantics, including content beyond the scrollport. Ordinary document content outside the window remains visible; `inViewport` checks the window intersection after any hidden/clip ancestor clipping, without introducing scrollport clipping.
391
391
 
392
392
  A `state` assertion is graded as a **consequence** (a wrong element or stale render cannot fake it), and is usable the same three ways anywhere predicates flow: ad-hoc (`reticle_assert` / `reticle_act_and_wait` `until`), as a flow step invariant (`reticle_annotate { kind: "assert-state", statePath, store?, equals? }`), and as a flow's golden end-condition (`reticle_annotate { kind: "success-state", statePath, … }`). On a miss it names the real store value and the keys that were available: legible, not a blind fail.
393
393
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/server",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "mcpName": "io.github.reticlehq/reticle",
5
5
  "description": "Reticle CLI, bridge and MCP server: a coding agent drives your running web app from the inside and gets a verdict with evidence, for change verification, access control, accessibility, performance and SEO checks.",
6
6
  "type": "module",
@@ -22,13 +22,12 @@
22
22
  "dependencies": {
23
23
  "@modelcontextprotocol/sdk": "^1.31.0",
24
24
  "ws": "^8.21.3",
25
- "zod": "^3.24.1",
26
- "zod-to-json-schema": "^3.24.0",
27
- "@reticlehq/browser": "3.8.0",
28
- "@reticlehq/init": "3.8.0",
29
- "@reticlehq/core": "3.8.0",
30
- "@reticlehq/engine": "3.8.0",
31
- "open-verification": "1.0.0"
25
+ "zod": "^4.6.6",
26
+ "@reticlehq/engine": "3.8.1",
27
+ "@reticlehq/core": "3.8.1",
28
+ "@reticlehq/init": "3.8.1",
29
+ "open-verification": "1.0.0",
30
+ "@reticlehq/browser": "3.8.1"
32
31
  },
33
32
  "devDependencies": {
34
33
  "@types/pngjs": "^6.0.5",
@@ -65,7 +64,7 @@
65
64
  "cli"
66
65
  ],
67
66
  "reticle": {
68
- "changelog": "Reticle works in git worktrees: each agent reaches only the tabs its own worktree serves, and each run names its branch. reticle init now installs a prompt hook for the agents it finds: each prompt is kept, redacted, in .reticle/, and a linked project sends it with its runs unless \"shareRequests\": false. Background sync sends a linked project while you are signed in, or with RETICLE_API_KEY in CI. See MIGRATION.md (3.7 to 3.8).",
67
+ "changelog": "On Windows, the daemon now keeps running when the agent process that started it ends. A collapsed Next.js \"1 Issue\" badge no longer turns every check into unknown. Reticle moves to zod 4 for a high-severity memory advisory. reticle_lease can omit url and lease the project's own dev server. reticle doctor checks that the daemon answers, and streamed SSE response bodies can be captured. Replay waits for a start page that is still loading, a re-acquired lease moves to the page asked for, and a guarded app driven by fast clicks is no longer accused of applying a stale response. Update the SDK in your app as well as the CLI.",
69
68
  "breakingChanges": []
70
69
  },
71
70
  "publishConfig": {