@reticlehq/server 3.3.0 → 3.5.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/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +6 -2
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +24 -4
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +64 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.d.ts +1 -1
- package/dist/command/cli/cloud-login.js +27 -8
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +21 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +79 -11
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
- package/dist/command/setup/bringup/dev-server-wait.js +1 -1
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +91 -9
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +86 -45
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +28 -1
- package/dist/command/setup/run-setup.js +103 -15
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +17 -8
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +10 -0
- package/dist/features/harness/fill-values.js +50 -11
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.d.ts +12 -0
- package/dist/features/harness/openai-driver.js +21 -12
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/features/license/license.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -19
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +77 -3
- package/dist/memory/cloud/sync-cycle.js +325 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/intent/intent-tools.js +8 -3
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
- package/dist/memory/project/artifact-root-resolver.js +46 -19
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +12 -3
- package/dist/portal/bridge/bridge.js +49 -3
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/input/viewport-tools.js +16 -6
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +10 -8
- package/dist/portal/session/session.js +24 -32
- package/dist/surface/http-server.d.ts +3 -2
- package/dist/surface/http-server.js +8 -5
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +28 -52
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/error-recovery.js +5 -6
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +12 -7
- package/dist/surface/tools/invoke-tool.js +12 -8
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tool-kit.d.ts +10 -0
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +1 -5
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -4
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +20 -8
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +22 -7
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +15 -5
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +2 -2
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +4 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +12 -10
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
package/LICENSE-ENTERPRISE
CHANGED
|
@@ -34,4 +34,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
34
34
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
35
|
SOFTWARE.
|
|
36
36
|
|
|
37
|
-
For
|
|
37
|
+
For a Reticle Enterprise subscription: hey@reticle.sh
|
package/README.md
CHANGED
|
@@ -15,13 +15,13 @@ Point your agent at it. On Claude Code, register it at user scope:
|
|
|
15
15
|
claude mcp add reticle -s user -- npx @reticlehq/server mcp
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Default tools (
|
|
18
|
+
Default tools (10):
|
|
19
19
|
|
|
20
|
-
`
|
|
20
|
+
`reticle_navigate`, `reticle_look`, `reticle_act`, `reticle_act_and_wait`, `reticle_observe`, `reticle_assert`, `reticle_session`, `reticle_tools`, `reticle_run`, `reticle_verify`.
|
|
21
21
|
|
|
22
22
|
Only `reticle_act_and_wait` and `reticle_assert` return a verdict; everything else moves or reads the app.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
More tools ship but are not advertised, to keep the per-turn tool payload small: screenshots and visual diff, saved flows, recording, baselines, clock control, viewport, crawl, coverage, network mocking, leases and more. Reach any of them by name with `reticle_run`, list them with `reticle_tools`, or advertise the whole surface with `RETICLE_ADVERTISE_ALL_TOOLS=1` (this roughly doubles the per-turn tool payload).
|
|
25
25
|
|
|
26
26
|
See the [main README](https://github.com/reticlehq/reticle).
|
|
27
27
|
|
package/SKILL.md
CHANGED
|
@@ -21,7 +21,7 @@ curl -fsSL https://raw.githubusercontent.com/reticlehq/reticle/main/install/inst
|
|
|
21
21
|
irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1 | iex # Windows
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
It registers the MCP server with
|
|
24
|
+
It registers the MCP server with the coding agents it finds and, where an agent has a per-server approval rule (Claude Code, for one), pre-approves Reticle's own tools. They reopen this client and the tools are there. If they want to choose each step instead, the manual install is `https://docs.reticle.sh/install-manual.md`. Do not register it yourself and then work around your client not having reloaded: that is the sequence that breaks.
|
|
25
25
|
|
|
26
26
|
`init` is ONBOARDING, and where it stops: wire, boot, wait for a session. Idempotent, reporting `·` for what is already there. Two things it cannot do for you, in this order:
|
|
27
27
|
|
|
@@ -48,13 +48,13 @@ Everything between here and there is a rule the steps assume. Read it as you go,
|
|
|
48
48
|
|
|
49
49
|
**Do not tell the user Reticle is set up until a verdict exists.** The common failure is an agent that writes the config, reports success, and leaves an uninstrumented page. The command then exits non-zero: believe the exit code.
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## Finish the setup steps
|
|
52
52
|
|
|
53
|
-
**
|
|
53
|
+
**Finish the setup steps; ask the user only when a step needs their decision.** Running `init`, fixing wiring it could not, starting the dev server and opening the browser are setup steps, not decisions.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
The repo already answers which framework, package manager, port, editor or MCP client, so work those out rather than asking. Say what you did in one line.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Two places always need the user:
|
|
58
58
|
|
|
59
59
|
1. **No recognisable dev script in `package.json`.** Say so; do not invent one.
|
|
60
60
|
2. **Your host asks the human to approve a command.** That prompt belongs to the host. Never bypass or suppress it, and take a refusal as the answer. `init` writing a pre-approval rule for the `reticle` server is not that: it is a scoped, announced config change the human asked for by running the command, and it covers only Reticle's own tools.
|
|
@@ -79,8 +79,6 @@ npx @reticlehq/server feedback --agent --kind <kind> "what happened"
|
|
|
79
79
|
|
|
80
80
|
Nothing is too minor. Report defects in **Reticle**; a bug in the app under test is Reticle working, and belongs in your answer. Never put app source, secrets, or user data in a report.
|
|
81
81
|
|
|
82
|
-
When the person you work with is stuck, offer them a call with the founder (https://calendar.app.google/h9NRDbBBQetyTzWM6) or `npx @reticlehq/server identify --context company --email <theirs>`. Offer it; never book or submit for them.
|
|
83
|
-
|
|
84
82
|
## Do not re-read this file to look something up
|
|
85
83
|
|
|
86
84
|
Fetch the one page that answers the question instead. **Appending `.md` to any docs URL returns its source with no site chrome.**
|
|
@@ -112,7 +110,7 @@ You need it once, while setting a project up. If `reticle_session` already lists
|
|
|
112
110
|
|
|
113
111
|
# VERIFY
|
|
114
112
|
|
|
115
|
-
**
|
|
113
|
+
**Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`** (plus `reticle_verify` replays). Everything else (a bare `act`, `look`, `navigate`, `observe`) moves or reads the app and proves nothing. A drive that ends without a verdict has no result, however many tools it used.
|
|
116
114
|
|
|
117
115
|
A verdict of `verified: "unknown"` is not a pass. It means Reticle drove the app and could not tell what happened. Report it as unknown. `verified: "no-fault"` is not a pass either. It means the page settled and no channel reported a problem, but nothing was declared to prove, so assert a consequence the action CHANGES. **Never weaken a check to make it pass.**
|
|
118
116
|
|
|
@@ -124,10 +122,10 @@ Work down this list and stop at the first row that fits. Do not hand-drive a flo
|
|
|
124
122
|
| --- | --- | --- |
|
|
125
123
|
| "Did my edit break anything?" | `reticle_verify({ action: "change", files: ["src/App.tsx"] })` | 1 |
|
|
126
124
|
| "Does every saved journey still work?" | `reticle_verify({ action: "flows" })` | 1 |
|
|
127
|
-
| "Does this new behaviour work?" |
|
|
125
|
+
| "Does this new behaviour work?" | `reticle_act_and_wait` on the step that ENDS the journey | 1+ |
|
|
128
126
|
| No MCP available at all | `npx @reticlehq/server verify <url>`, then `gate --since HEAD~1` (below); `verify` has no `--since` | 2, no MCP |
|
|
129
127
|
|
|
130
|
-
Replay before you drive
|
|
128
|
+
Replay before you drive: a covered journey re-verifies for a few hundred tokens, and driving it costs tens of thousands.
|
|
131
129
|
|
|
132
130
|
`{action:"change"}` answers `unknown` when no saved flow covers your files. Nothing ran, so nothing was proved: drive it yourself, and never read it as a pass. `"no"` names the step that broke and what it found instead, so a regression arrives located. If a locator was RENAMED rather than broken, `{action:"heal"}` rebinds it, re-asserting the saved consequence first and refusing if that stops firing, so it repairs a locator and never an intent.
|
|
133
131
|
|
|
@@ -159,7 +157,7 @@ Whether that flow is worth anything depends on how you drove it. A step keeps a
|
|
|
159
157
|
|
|
160
158
|
## When you do have to drive by hand
|
|
161
159
|
|
|
162
|
-
|
|
160
|
+
Done means the journey's END is proved. Drive every step your change touches and put the verdict on the step that ends it:
|
|
163
161
|
|
|
164
162
|
```
|
|
165
163
|
reticle_session() // connected? if empty, read `why` — it names the fix
|
|
@@ -171,7 +169,7 @@ reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf",
|
|
|
171
169
|
]}}) // ← the verdict
|
|
172
170
|
```
|
|
173
171
|
|
|
174
|
-
|
|
172
|
+
`until` names the consequence **before** the action, which makes it a check. Add `durable: true` when the change should survive a reload.
|
|
175
173
|
|
|
176
174
|
The advertised surface is deliberately small (`default` 10, `all` 30, the wider one behind `RETICLE_ADVERTISE_ALL_TOOLS=1`), and is not all there is. `reticle_tools` lists EVERY registered tool, `{ names: [...] }` loads full argument grammar, and `reticle_run { tool, args }` calls any of them, advertised or not. A retired name answers with where it went, not "not found".
|
|
177
175
|
|
|
@@ -180,6 +178,6 @@ The advertised surface is deliberately small (`default` 10, `all` 30, the wider
|
|
|
180
178
|
|
|
181
179
|
## When the verdict lands
|
|
182
180
|
|
|
183
|
-
Report what you drove, what it produced, and the `file:line` for anything broken.
|
|
181
|
+
Report what you drove, what it produced, and the `file:line` for anything broken.
|
|
184
182
|
|
|
185
|
-
|
|
183
|
+
Beyond change verification (security behaviour, accessibility, performance, SEO basics, personas), what each checks and the call to make: `https://docs.reticle.sh/use-cases.md`.
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export declare const CHROMIUM_ANTI_THROTTLING_ARGS: readonly ["--disable-background-timer-throttling", "--disable-backgrounding-occluded-windows", "--disable-renderer-backgrounding"];
|
|
19
19
|
/** The options object handed to `chromium.launch` at both launch sites. */
|
|
20
|
-
interface ChromiumLaunchOptions {
|
|
20
|
+
export interface ChromiumLaunchOptions {
|
|
21
21
|
headless: boolean;
|
|
22
22
|
args: string[];
|
|
23
|
+
/** An installed Chrome/Edge to drive instead of Playwright's own build; absent = the bundled one. */
|
|
24
|
+
channel?: string;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Build the launch options for a headless or headed Chromium. Pure; returns a fresh mutable array
|
|
26
28
|
* each call so no caller can mutate the constant behind everyone else's back.
|
|
27
29
|
*/
|
|
28
|
-
export declare function chromiumLaunchOptions(headless: boolean): ChromiumLaunchOptions;
|
|
29
|
-
export {};
|
|
30
|
+
export declare function chromiumLaunchOptions(headless: boolean, channel?: string): ChromiumLaunchOptions;
|
|
@@ -24,6 +24,10 @@ export const CHROMIUM_ANTI_THROTTLING_ARGS = [
|
|
|
24
24
|
* Build the launch options for a headless or headed Chromium. Pure; returns a fresh mutable array
|
|
25
25
|
* each call so no caller can mutate the constant behind everyone else's back.
|
|
26
26
|
*/
|
|
27
|
-
export function chromiumLaunchOptions(headless) {
|
|
28
|
-
return {
|
|
27
|
+
export function chromiumLaunchOptions(headless, channel) {
|
|
28
|
+
return {
|
|
29
|
+
headless,
|
|
30
|
+
args: [...CHROMIUM_ANTI_THROTTLING_ARGS],
|
|
31
|
+
...(channel === undefined ? {} : { channel }),
|
|
32
|
+
};
|
|
29
33
|
}
|
|
@@ -17,3 +17,25 @@ export interface AdhocSuiteOptions {
|
|
|
17
17
|
* this product exists to prevent.
|
|
18
18
|
*/
|
|
19
19
|
export declare function runAdhocSuite(options: AdhocSuiteOptions): Promise<AdhocVerdict>;
|
|
20
|
+
export interface AdhocExploreOptions {
|
|
21
|
+
port: number;
|
|
22
|
+
/** The app to explore; opened in a Reticle browser when the daemon has no tab on its origin. */
|
|
23
|
+
url: string;
|
|
24
|
+
/** Who to be, or what to accomplish. */
|
|
25
|
+
persona?: string;
|
|
26
|
+
token?: string;
|
|
27
|
+
connect?: (endpoint: URL) => Promise<ToolCaller>;
|
|
28
|
+
/** The tabs the daemon has connected. See runAdhocVerdict. */
|
|
29
|
+
sessions: () => Promise<readonly {
|
|
30
|
+
url: string;
|
|
31
|
+
}[]>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `verify <url> --explore` against the daemon that already owns the port: open the url, let the
|
|
35
|
+
* daemon's model drive it and record flows, then replay them — the same three steps the path that
|
|
36
|
+
* boots its own daemon takes, so the exit code means the same thing on both.
|
|
37
|
+
*
|
|
38
|
+
* It used to refuse here, and that refusal was the first thing a new user met: `init` leaves its
|
|
39
|
+
* daemon on the port and then recommends exactly this command as the first run.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runAdhocExplore(options: AdhocExploreOptions): Promise<AdhocVerdict>;
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
* is stopped, the agent keeps its link.
|
|
16
16
|
*/
|
|
17
17
|
import { ReticleTool } from '@reticlehq/core';
|
|
18
|
-
import { connectOverSse, endpointFor, refusalText, verdictOf, } from './adhoc-verdict.js';
|
|
18
|
+
import { connectOverSse, endpointFor, leaseIfNoTab, releaseLease, refusalText, verdictOf, } from './adhoc-verdict.js';
|
|
19
19
|
/** The only status that is a pass. `unverifiable` means nothing was checked, which is not one. */
|
|
20
20
|
const PASSED = 'pass';
|
|
21
|
+
/** The `reticle_verify` actions this file asks for. */
|
|
22
|
+
const EXPLORE_ACTION = 'explore';
|
|
21
23
|
/**
|
|
22
24
|
* How long a SUITE may take, against the SDK's 60s default for one request.
|
|
23
25
|
*
|
|
@@ -52,37 +54,106 @@ export async function runAdhocSuite(options) {
|
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
56
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
return await replaySuite(caller, options);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
code: 1,
|
|
62
|
+
lines: [
|
|
63
|
+
`the suite could not be run: ${error instanceof Error ? error.message : String(error)}`,
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await caller.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** The saved suite through an open connection, as a verdict. Shared by the suite and explore paths. */
|
|
72
|
+
async function replaySuite(caller, options) {
|
|
73
|
+
// Spread rather than set: an explicit `undefined` is a key the tools reject as an unknown
|
|
74
|
+
// parameter, which would turn "no tab named" into a refusal. Same rule as the one-shot path.
|
|
75
|
+
const result = await caller.call(ReticleTool.VERIFY, {
|
|
76
|
+
action: 'flows',
|
|
77
|
+
...(options.select === undefined ? {} : { select: options.select }),
|
|
78
|
+
...(options.sessionId === undefined ? {} : { sessionId: options.sessionId }),
|
|
79
|
+
}, SUITE_TIMEOUT_MS);
|
|
80
|
+
const refusal = refusalText(result);
|
|
81
|
+
if (refusal !== undefined)
|
|
82
|
+
return { code: 1, lines: ['status: unverifiable', refusal] };
|
|
83
|
+
const report = verdictOf(result, 'status');
|
|
84
|
+
const status = 'string' === typeof report?.['status'] ? report['status'] : 'unverifiable';
|
|
85
|
+
const summary = 'string' === typeof report?.['summary'] ? report['summary'] : undefined;
|
|
86
|
+
return {
|
|
87
|
+
code: PASSED === status ? 0 : 1,
|
|
88
|
+
lines: [
|
|
89
|
+
`status: ${status}`,
|
|
90
|
+
...(summary === undefined ? [] : [summary]),
|
|
91
|
+
JSON.stringify(report ?? result, null, 2),
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* `verify <url> --explore` against the daemon that already owns the port: open the url, let the
|
|
97
|
+
* daemon's model drive it and record flows, then replay them — the same three steps the path that
|
|
98
|
+
* boots its own daemon takes, so the exit code means the same thing on both.
|
|
99
|
+
*
|
|
100
|
+
* It used to refuse here, and that refusal was the first thing a new user met: `init` leaves its
|
|
101
|
+
* daemon on the port and then recommends exactly this command as the first run.
|
|
102
|
+
*/
|
|
103
|
+
export async function runAdhocExplore(options) {
|
|
104
|
+
const connect = options.connect ?? connectOverSse;
|
|
105
|
+
let caller;
|
|
106
|
+
try {
|
|
107
|
+
caller = await connect(endpointFor(options.port, options.token));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
68
110
|
return {
|
|
69
|
-
code:
|
|
111
|
+
code: 1,
|
|
70
112
|
lines: [
|
|
71
|
-
`
|
|
72
|
-
...(summary === undefined ? [] : [summary]),
|
|
73
|
-
JSON.stringify(report ?? result, null, 2),
|
|
113
|
+
`could not reach the daemon on port ${String(options.port)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
74
114
|
],
|
|
75
115
|
};
|
|
76
116
|
}
|
|
117
|
+
let leased;
|
|
118
|
+
try {
|
|
119
|
+
const opened = await leaseIfNoTab(caller, options.url, options.sessions);
|
|
120
|
+
if ('failed' in opened)
|
|
121
|
+
return { code: 1, lines: ['status: unverifiable', ...opened.failed] };
|
|
122
|
+
leased = opened.leased;
|
|
123
|
+
const pin = leased === undefined ? {} : { sessionId: leased };
|
|
124
|
+
const drove = await caller.call(ReticleTool.VERIFY, {
|
|
125
|
+
action: EXPLORE_ACTION,
|
|
126
|
+
...(options.persona === undefined ? {} : { persona: options.persona }),
|
|
127
|
+
...pin,
|
|
128
|
+
}, SUITE_TIMEOUT_MS);
|
|
129
|
+
const refusal = refusalText(drove);
|
|
130
|
+
if (refusal !== undefined)
|
|
131
|
+
return { code: 1, lines: ['status: unverifiable', refusal] };
|
|
132
|
+
const report = verdictOf(drove, 'savedFlows');
|
|
133
|
+
const recorded = [report?.['savedFlows'], report?.['rewroteFlows']].flatMap((list) => Array.isArray(list) ? list.filter((name) => 'string' === typeof name) : []);
|
|
134
|
+
const summary = 'string' === typeof report?.['summary'] ? report['summary'] : undefined;
|
|
135
|
+
const note = 'string' === typeof report?.['note'] ? report['note'] : undefined;
|
|
136
|
+
const drive = [
|
|
137
|
+
...(summary === undefined ? [] : [summary]),
|
|
138
|
+
...(note === undefined ? [] : [note]),
|
|
139
|
+
];
|
|
140
|
+
// A drive that left nothing to replay proved nothing, however long it ran.
|
|
141
|
+
if (0 === recorded.length)
|
|
142
|
+
return { code: 1, lines: ['status: unverifiable', ...drive] };
|
|
143
|
+
const suite = await replaySuite(caller, pin);
|
|
144
|
+
return { code: suite.code, lines: [...drive, ...suite.lines] };
|
|
145
|
+
}
|
|
77
146
|
catch (error) {
|
|
78
147
|
return {
|
|
79
148
|
code: 1,
|
|
80
149
|
lines: [
|
|
81
|
-
`the
|
|
150
|
+
`the explore could not be run: ${error instanceof Error ? error.message : String(error)}`,
|
|
82
151
|
],
|
|
83
152
|
};
|
|
84
153
|
}
|
|
85
154
|
finally {
|
|
155
|
+
if (leased !== undefined)
|
|
156
|
+
await releaseLease(caller, leased);
|
|
86
157
|
await caller.close().catch(() => undefined);
|
|
87
158
|
}
|
|
88
159
|
}
|
|
@@ -48,7 +48,42 @@ interface AdhocVerdictOptions {
|
|
|
48
48
|
token?: string;
|
|
49
49
|
/** Injected so a test does not need a live daemon. */
|
|
50
50
|
connect?: (endpoint: URL) => Promise<ToolCaller>;
|
|
51
|
+
/**
|
|
52
|
+
* The tabs the daemon has connected, so a url with none on its origin can be opened here rather
|
|
53
|
+
* than refused. Absent = never lease, which is the old behaviour and what the unit tests that do
|
|
54
|
+
* not care get.
|
|
55
|
+
*/
|
|
56
|
+
sessions?: () => Promise<readonly {
|
|
57
|
+
url: string;
|
|
58
|
+
}[]>;
|
|
51
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Open `url` in a Reticle-leased browser when the daemon has no tab on its origin.
|
|
62
|
+
*
|
|
63
|
+
* `{}` means a tab is already there (drive it); `{ leased }` names the lease this call opened, which
|
|
64
|
+
* the caller must hand to `releaseLease`; `{ failed }` is the lease's own refusal, as lines to print —
|
|
65
|
+
* a missing browser, an app that is not up — so nothing is asserted against a page that never opened.
|
|
66
|
+
*/
|
|
67
|
+
export declare function leaseIfNoTab(caller: ToolCaller, url: string, sessions: () => Promise<readonly {
|
|
68
|
+
url: string;
|
|
69
|
+
}[]>): Promise<{
|
|
70
|
+
leased?: string;
|
|
71
|
+
alreadyAt?: boolean;
|
|
72
|
+
} | {
|
|
73
|
+
failed: string[];
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Open `url` in a Reticle-leased browser unconditionally: `{ leased }` names the lease, which the
|
|
77
|
+
* caller hands to `releaseLease`; `{ failed }` is the lease's own refusal, as lines to print.
|
|
78
|
+
* `init`'s connect proof on a machine with no system browser takes this path directly.
|
|
79
|
+
*/
|
|
80
|
+
export declare function acquireLease(caller: ToolCaller, url: string): Promise<{
|
|
81
|
+
leased: string;
|
|
82
|
+
} | {
|
|
83
|
+
failed: string[];
|
|
84
|
+
}>;
|
|
85
|
+
/** Hand back a lease `leaseIfNoTab` opened. Best-effort: an expired lease is already gone. */
|
|
86
|
+
export declare function releaseLease(caller: ToolCaller, sessionId: string): Promise<void>;
|
|
52
87
|
/** The narrow slice of an MCP client this needs — one call, then close. */
|
|
53
88
|
export interface ToolCaller {
|
|
54
89
|
call(name: string, args: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
|
|
@@ -3,6 +3,75 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
|
3
3
|
import { LOOPBACK_HOST, MCP_SSE_PATH } from '@reticlehq/core';
|
|
4
4
|
import { TOKEN_QUERY_PARAM } from '../../portal/bridge/token-auth.js';
|
|
5
5
|
import { ReticleTool } from '@reticlehq/core';
|
|
6
|
+
import { decideOpen } from './launch/cli-launch.js';
|
|
7
|
+
/** The lease tool is reached through `reticle_run`, the route that works on every tool profile. */
|
|
8
|
+
const LeaseAction = { ACQUIRE: 'acquire', RELEASE: 'release' };
|
|
9
|
+
/**
|
|
10
|
+
* Generous on purpose: on a machine with no Chrome, Edge or Playwright Chromium the FIRST lease
|
|
11
|
+
* downloads Chromium before it can open anything (see launch-chromium), which outlives the MCP
|
|
12
|
+
* client's 60s default and would report a timeout for a lease that was about to succeed.
|
|
13
|
+
*/
|
|
14
|
+
const LEASE_TIMEOUT_MS = 300_000;
|
|
15
|
+
const leaseFailedLine = (url) => `could not open ${url} in a Reticle browser, so nothing was checked:`;
|
|
16
|
+
/**
|
|
17
|
+
* Open `url` in a Reticle-leased browser when the daemon has no tab on its origin.
|
|
18
|
+
*
|
|
19
|
+
* `{}` means a tab is already there (drive it); `{ leased }` names the lease this call opened, which
|
|
20
|
+
* the caller must hand to `releaseLease`; `{ failed }` is the lease's own refusal, as lines to print —
|
|
21
|
+
* a missing browser, an app that is not up — so nothing is asserted against a page that never opened.
|
|
22
|
+
*/
|
|
23
|
+
export async function leaseIfNoTab(caller, url, sessions) {
|
|
24
|
+
const open = [...(await sessions())];
|
|
25
|
+
if ('open' !== decideOpen(open, url).action) {
|
|
26
|
+
return open.some((s) => sameDocument(s.url, url)) ? { alreadyAt: true } : {};
|
|
27
|
+
}
|
|
28
|
+
return acquireLease(caller, url);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether two urls name the same document, ignoring a trailing slash and the hash.
|
|
32
|
+
*
|
|
33
|
+
* `verify <url>` navigated a tab already showing that url, which reloads it. In a desktop window
|
|
34
|
+
* that reload is a disconnect: the assert ran against a session that was gone ~20ms later and
|
|
35
|
+
* answered `unknown — session disconnected`.
|
|
36
|
+
*/
|
|
37
|
+
function sameDocument(a, b) {
|
|
38
|
+
try {
|
|
39
|
+
const x = new URL(a);
|
|
40
|
+
const y = new URL(b);
|
|
41
|
+
const path = (u) => u.pathname.replace(/\/+$/, '');
|
|
42
|
+
return x.origin === y.origin && path(x) === path(y) && x.search === y.search;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Open `url` in a Reticle-leased browser unconditionally: `{ leased }` names the lease, which the
|
|
50
|
+
* caller hands to `releaseLease`; `{ failed }` is the lease's own refusal, as lines to print.
|
|
51
|
+
* `init`'s connect proof on a machine with no system browser takes this path directly.
|
|
52
|
+
*/
|
|
53
|
+
export async function acquireLease(caller, url) {
|
|
54
|
+
const acquired = await caller.call(ReticleTool.RUN, { tool: ReticleTool.LEASE, args: { action: LeaseAction.ACQUIRE, url } }, LEASE_TIMEOUT_MS);
|
|
55
|
+
const sessionId = verdictOf(acquired, 'sessionId')?.['sessionId'];
|
|
56
|
+
if ('string' === typeof sessionId)
|
|
57
|
+
return { leased: sessionId };
|
|
58
|
+
const why = verdictOf(acquired, 'error')?.['error'];
|
|
59
|
+
return {
|
|
60
|
+
failed: [
|
|
61
|
+
leaseFailedLine(url),
|
|
62
|
+
'string' === typeof why ? why : (refusalText(acquired) ?? JSON.stringify(acquired)),
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Hand back a lease `leaseIfNoTab` opened. Best-effort: an expired lease is already gone. */
|
|
67
|
+
export async function releaseLease(caller, sessionId) {
|
|
68
|
+
await caller
|
|
69
|
+
.call(ReticleTool.RUN, {
|
|
70
|
+
tool: ReticleTool.LEASE,
|
|
71
|
+
args: { action: LeaseAction.RELEASE, sessionId },
|
|
72
|
+
})
|
|
73
|
+
.catch(() => undefined);
|
|
74
|
+
}
|
|
6
75
|
/** `verified` is the only field that decides the exit code. Everything else is for the reader. */
|
|
7
76
|
const PROVED = 'yes';
|
|
8
77
|
export function endpointFor(port, token) {
|
|
@@ -112,12 +181,28 @@ export async function runAdhocVerdict(options) {
|
|
|
112
181
|
],
|
|
113
182
|
};
|
|
114
183
|
}
|
|
184
|
+
let leased;
|
|
115
185
|
try {
|
|
116
186
|
// Spread rather than set: an explicit `sessionId: undefined` is a key the tools reject as an
|
|
117
187
|
// unknown parameter, which would turn "no tab named" into a refusal.
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
188
|
+
const url = options.url !== undefined && options.url.length > 0 ? options.url : undefined;
|
|
189
|
+
// A url and no tab on its origin: this used to refuse with "open the app first" and name some
|
|
190
|
+
// OTHER listening port as the one to open. The caller already said which url; open it here, in
|
|
191
|
+
// a Reticle browser, and hand the lease back when the verdict is in.
|
|
192
|
+
let alreadyAt = false;
|
|
193
|
+
if (url !== undefined && options.sessionId === undefined && options.sessions !== undefined) {
|
|
194
|
+
const opened = await leaseIfNoTab(caller, url, options.sessions);
|
|
195
|
+
if ('failed' in opened)
|
|
196
|
+
return { code: 1, lines: ['verified: unknown', ...opened.failed] };
|
|
197
|
+
leased = opened.leased;
|
|
198
|
+
alreadyAt = true === opened.alreadyAt;
|
|
199
|
+
}
|
|
200
|
+
const aimed = options.sessionId ?? leased;
|
|
201
|
+
const pin = aimed === undefined ? {} : { sessionId: aimed };
|
|
202
|
+
// A fresh lease, or a tab already showing this url, is already AT it; navigating again would
|
|
203
|
+
// only reload the page — and reload a desktop window out from under the assert.
|
|
204
|
+
if (url !== undefined && leased === undefined && !alreadyAt) {
|
|
205
|
+
await caller.call(ReticleTool.NAVIGATE, { url, ...pin });
|
|
121
206
|
}
|
|
122
207
|
const result = await caller.call(ReticleTool.ASSERT, { predicate: options.predicate, ...pin });
|
|
123
208
|
const refusal = refusalText(result);
|
|
@@ -147,6 +232,8 @@ export async function runAdhocVerdict(options) {
|
|
|
147
232
|
};
|
|
148
233
|
}
|
|
149
234
|
finally {
|
|
235
|
+
if (leased !== undefined)
|
|
236
|
+
await releaseLease(caller, leased);
|
|
150
237
|
await caller.close().catch(() => undefined);
|
|
151
238
|
}
|
|
152
239
|
}
|
|
@@ -21,6 +21,12 @@ interface GateHookInput {
|
|
|
21
21
|
downgraded: readonly string[];
|
|
22
22
|
/** Flows whose coverage was deleted while their files changed. */
|
|
23
23
|
deleted: readonly string[];
|
|
24
|
+
/** Changed interactive components no saved flow has ever driven. */
|
|
25
|
+
unflowed?: readonly string[];
|
|
26
|
+
/** Coverage levels below their best, already worded (`branched 100% -> 50%`). */
|
|
27
|
+
coverageRegressed?: readonly string[];
|
|
28
|
+
/** Changed files the browser loaded and never ran a function of. */
|
|
29
|
+
unexecuted?: readonly string[];
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* The message, or undefined when there is nothing to say.
|
|
@@ -37,6 +37,12 @@ export function gateHookMessage(exit, input) {
|
|
|
37
37
|
lines.push(` assertions weakened to something fakeable: ${name(input.downgraded)} — a check that cannot fail is not a check`);
|
|
38
38
|
if (input.deleted.length > 0)
|
|
39
39
|
lines.push(` coverage deleted while its files changed: ${name(input.deleted)}`);
|
|
40
|
+
if (input.unflowed !== undefined && input.unflowed.length > 0)
|
|
41
|
+
lines.push(` changed, and no saved flow drives it: ${name(input.unflowed)} — drive it with reticle_act_and_wait and a declared \`until\``);
|
|
42
|
+
if (input.coverageRegressed !== undefined && input.coverageRegressed.length > 0)
|
|
43
|
+
lines.push(` coverage fell: ${name(input.coverageRegressed)} — something new is uncovered; reticle_verify { action: "crawl", exhaustive: true } names it. If the drop was intended, \`reticle gate --accept-coverage\` records it`);
|
|
44
|
+
if (input.unexecuted !== undefined && input.unexecuted.length > 0)
|
|
45
|
+
lines.push(` changed, and no drive ever ran it: ${name(input.unexecuted)}`);
|
|
40
46
|
if (input.quarantined.length > 0)
|
|
41
47
|
lines.push(` held back as flaky, so proving nothing: ${name(input.quarantined)}`);
|
|
42
48
|
lines.push('');
|
|
@@ -33,6 +33,4 @@ interface CredentialWrite {
|
|
|
33
33
|
* re-link, and refusing it would strand the repo.
|
|
34
34
|
*/
|
|
35
35
|
export declare const applyCredential: (store: Record<string, unknown>, write: CredentialWrite) => Record<string, unknown>;
|
|
36
|
-
/** Where the store lives, given the reticle home directory. */
|
|
37
|
-
export declare const credentialsPath: (homeDir: string, file: string) => string;
|
|
38
36
|
export {};
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* two are kept readable (a repo linked by an older CLI must not lose its key) and written only when
|
|
15
15
|
* doing so cannot mislead somebody else.
|
|
16
16
|
*/
|
|
17
|
-
import { join } from 'node:path';
|
|
18
17
|
import { credentialSlot } from '../../../memory/cloud/cloud-config.js';
|
|
19
18
|
/** Trailing slashes are not identity: `https://x/` and `https://x` are one cloud. */
|
|
20
19
|
const normalizeUrl = (url) => url.replace(/\/+$/, '');
|
|
@@ -93,5 +92,3 @@ export const applyCredential = (store, write) => {
|
|
|
93
92
|
store[projectId] = { key, url };
|
|
94
93
|
return store;
|
|
95
94
|
};
|
|
96
|
-
/** Where the store lives, given the reticle home directory. */
|
|
97
|
-
export const credentialsPath = (homeDir, file) => join(homeDir, file);
|
|
@@ -18,16 +18,20 @@ declare const SessionSchema: z.ZodObject<{
|
|
|
18
18
|
* is ours" and mints, which is the safe direction.
|
|
19
19
|
*/
|
|
20
20
|
orgId: z.ZodOptional<z.ZodString>;
|
|
21
|
+
/** Who signed in. Absent in sessions written before it was recorded. */
|
|
22
|
+
email: z.ZodOptional<z.ZodString>;
|
|
21
23
|
}, "strip", z.ZodTypeAny, {
|
|
22
|
-
url: string;
|
|
23
24
|
token: string;
|
|
25
|
+
url: string;
|
|
24
26
|
orgName: string;
|
|
25
27
|
orgId?: string | undefined;
|
|
28
|
+
email?: string | undefined;
|
|
26
29
|
}, {
|
|
27
|
-
url: string;
|
|
28
30
|
token: string;
|
|
31
|
+
url: string;
|
|
29
32
|
orgName: string;
|
|
30
33
|
orgId?: string | undefined;
|
|
34
|
+
email?: string | undefined;
|
|
31
35
|
}>;
|
|
32
36
|
export type Session = z.infer<typeof SessionSchema>;
|
|
33
37
|
/** Trailing slashes are not identity: `https://x/` and `https://x` are one host. */
|
|
@@ -19,6 +19,8 @@ const SessionSchema = z.object({
|
|
|
19
19
|
* is ours" and mints, which is the safe direction.
|
|
20
20
|
*/
|
|
21
21
|
orgId: z.string().optional(),
|
|
22
|
+
/** Who signed in. Absent in sessions written before it was recorded. */
|
|
23
|
+
email: z.string().optional(),
|
|
22
24
|
});
|
|
23
25
|
/** Trailing slashes are not identity: `https://x/` and `https://x` are one host. */
|
|
24
26
|
export const normalizeUrl = (url) => url.replace(/\/+$/, '');
|
|
@@ -53,18 +53,18 @@ export declare const DevicePollSchema: z.ZodObject<{
|
|
|
53
53
|
}>>;
|
|
54
54
|
}, "strip", z.ZodTypeAny, {
|
|
55
55
|
status: string;
|
|
56
|
+
token?: string | undefined;
|
|
56
57
|
org?: {
|
|
57
58
|
name: string;
|
|
58
59
|
id?: string | undefined;
|
|
59
60
|
} | undefined;
|
|
60
|
-
token?: string | undefined;
|
|
61
61
|
}, {
|
|
62
62
|
status: string;
|
|
63
|
+
token?: string | undefined;
|
|
63
64
|
org?: {
|
|
64
65
|
name: string;
|
|
65
66
|
id?: string | undefined;
|
|
66
67
|
} | undefined;
|
|
67
|
-
token?: string | undefined;
|
|
68
68
|
}>;
|
|
69
69
|
/**
|
|
70
70
|
* Best-effort open the approval page in the default browser.
|
|
@@ -11,11 +11,12 @@ import { projectWiringLine } from './doctor/doctor-project-line.js';
|
|
|
11
11
|
import { hasProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
|
|
12
12
|
import { sessionsLine } from './doctor/doctor-sessions-line.js';
|
|
13
13
|
import { captureLookup, describeForeignHolder, findPortHolder } from './ports/port-holder.js';
|
|
14
|
-
import { chromiumHint
|
|
14
|
+
import { chromiumHint } from './doctor/browser/chromium-hint.js';
|
|
15
|
+
import { probeChromiumWithFallback } from '../../launch-chromium.js';
|
|
15
16
|
import { SERVER_VERSION } from '../version/identity/server-version.js';
|
|
16
17
|
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
17
|
-
import { CspBasis, diagnoseDesktop, diagnoseObservedWebCsp, diagnoseWebCsp, isDesktopProject, resolveWebCspFindings, } from '@reticlehq/init';
|
|
18
|
-
import { RETICLE_CONFIG_BASENAME, diagnosePortMismatch, readProjectId, readProjectPort, } from './ports/resolve/cli-port.js';
|
|
18
|
+
import { CspBasis, diagnoseDesktop, diagnoseObservedWebCsp, diagnoseWebCsp, isDesktopProject, resolveWebCspFindings, webCspOptionsFor, } from '@reticlehq/init';
|
|
19
|
+
import { RETICLE_CONFIG_BASENAME, diagnosePortMismatch, readProjectFramework, readProjectId, readProjectPort, } from './ports/resolve/cli-port.js';
|
|
19
20
|
import { DoctorRow, doctorRow } from './doctor/doctor-rows.js';
|
|
20
21
|
import { attachState, describeAttachState } from '../../surface/mcp/attach-memory.js';
|
|
21
22
|
import { findOccupiedSiblings, siblingListenerNote } from './ports/sibling-ports.js';
|
|
@@ -68,7 +69,7 @@ export async function handleDoctor(port) {
|
|
|
68
69
|
// the branches below already say so in their own terms.
|
|
69
70
|
let sessions;
|
|
70
71
|
let daemonStatus;
|
|
71
|
-
line(doctorRow(DoctorRow.CHROMIUM, chromiumHint(await
|
|
72
|
+
line(doctorRow(DoctorRow.CHROMIUM, chromiumHint(await probeChromiumWithFallback())));
|
|
72
73
|
// Ask the PORT, not just the pid file. "not running on :4400" has been printed about a port that
|
|
73
74
|
// was demonstrably occupied, which sends the reader to start a daemon that cannot bind. The three
|
|
74
75
|
// states are genuinely different problems with different fixes, so doctor names which one it is.
|
|
@@ -201,7 +202,8 @@ export async function handleDoctor(port) {
|
|
|
201
202
|
: observedDocuments.flatMap((document) => diagnoseObservedWebCsp(document, port));
|
|
202
203
|
const connected = hasConnectedDocument(summarizeStatus(daemonStatus).sessions, devServers.map((server) => server.url), projectId);
|
|
203
204
|
const csp = resolveWebCspFindings({
|
|
204
|
-
|
|
205
|
+
// With the wiring `init` recorded, so the two commands name the same rule for one policy.
|
|
206
|
+
predicted: diagnoseWebCsp(readProjectFile, port, [], webCspOptionsFor(readProjectFramework(process.cwd()))),
|
|
205
207
|
...(observedCsp === undefined ? {} : { observed: observedCsp }),
|
|
206
208
|
connected,
|
|
207
209
|
});
|
|
@@ -37,11 +37,8 @@ export declare function handleWatch(): void;
|
|
|
37
37
|
* failing reproduction plus the consequence that should have held; replay one with reticle_flow_replay.
|
|
38
38
|
*/
|
|
39
39
|
export declare function handleCapsules(): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* `reticle gate <file...>` — exit non-zero unless passing artifacts cover the flows affected by the
|
|
42
|
-
* changed files. Flaky flows are quarantined (surfaced, not blocking). The environment-side enforcement
|
|
43
|
-
* that makes verification unavoidable. Never throws; a fault fails closed (exit 1).
|
|
44
|
-
*/
|
|
45
40
|
export declare function handleGate(files: string[], since: string | undefined,
|
|
46
41
|
/** Hook mode: prose for a human, and silence when there was simply nothing to check. */
|
|
47
|
-
hook?: boolean
|
|
42
|
+
hook?: boolean,
|
|
43
|
+
/** Accept today's coverage levels as the best before judging, for a drop that was intended. */
|
|
44
|
+
acceptCoverage?: boolean): Promise<void>;
|