@reticlehq/server 3.2.0 → 3.4.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 +23 -22
- 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 +41 -0
- package/dist/command/cli/adhoc-suite.js +159 -0
- package/dist/command/cli/adhoc-verdict.d.ts +58 -2
- package/dist/command/cli/adhoc-verdict.js +108 -13
- 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 +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +30 -8
- 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-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +14 -1
- package/dist/command/cli/cli-parse.js +54 -8
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +80 -0
- package/dist/command/cli/cli-verify.js +181 -26
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- 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/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +9 -29
- package/dist/command/cli/launch/cli-launch.js +27 -49
- 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 +38 -0
- package/dist/command/cli/ports/resolve/cli-port.js +169 -15
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +67 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -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/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-host.js +8 -0
- 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 +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- 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 +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- 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.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- 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.d.ts +2 -0
- package/dist/features/crawl/crawl.js +171 -156
- 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 +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +36 -22
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +13 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +78 -104
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +40 -9
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- 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/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- 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-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- 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.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.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-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +33 -12
- package/dist/language/flows/flow-replay-run.js +131 -52
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +27 -12
- package/dist/language/flows/flow-replay.js +127 -111
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +18 -5
- package/dist/language/flows/flows.js +101 -16
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
- 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 +53 -13
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/verify-change-tools.js +19 -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 -3
- package/dist/memory/cloud/cloud-sync.js +7 -10
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- 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 +78 -1
- package/dist/memory/cloud/sync-cycle.js +321 -53
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +45 -20
- package/dist/memory/cloud/sync-disk.js +27 -8
- package/dist/memory/impact/impact-store.js +6 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +252 -140
- package/dist/memory/intent/intent-shard.d.ts +0 -23
- package/dist/memory/intent/intent-shard.js +1 -11
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +19 -14
- package/dist/memory/journal/drive-flow.js +65 -48
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +13 -2
- package/dist/memory/journal/session-end.js +88 -18
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/fs/fs-port.js +44 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- 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 +31 -5
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +21 -13
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- 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.d.ts +2 -1
- 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/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -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/live-control-tools.js +48 -3
- 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/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +43 -3
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +29 -6
- package/dist/portal/session/session-manager.js +42 -5
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +10 -0
- package/dist/portal/session/session.js +34 -26
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +5 -5
- package/dist/surface/mcp/mcp.js +4 -2
- 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-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +21 -8
- 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/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -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 +19 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -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/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +68 -8
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +51 -74
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +26 -3
- package/dist/surface/tools/assert/assert-verdict.js +32 -6
- package/dist/surface/tools/browser-tools.js +17 -4
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/invoke-tool.js +17 -4
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +68 -11
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +23 -7
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +68 -29
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +34 -0
- package/dist/wire-journal.js +104 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +5 -2
- package/docs/actions.mdx +3 -3
- 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 +21 -9
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- 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 +32 -2
- package/docs/frameworks.mdx +140 -9
- package/docs/getting-started.md +3 -3
- 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 +37 -1
- 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/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +176 -7
- package/docs/quickstart.mdx +2 -2
- 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/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +11 -11
- 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 +8 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +62 -0
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +11 -10
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
- package/dist/judgement/phenomena/phenomena.d.ts +0 -26
- 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
|
|
|
@@ -46,15 +46,15 @@ Everything between here and there is a rule the steps assume. Read it as you go,
|
|
|
46
46
|
- The `reticle_*` tools appeared. Your client can reach a daemon. The app is very likely still uninstrumented.
|
|
47
47
|
- A session is listed. The app dialled in. The user has still seen nothing happen.
|
|
48
48
|
|
|
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
|
|
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.
|
|
@@ -63,7 +63,7 @@ There are exactly two places you may stop, and neither is a question about a pre
|
|
|
63
63
|
|
|
64
64
|
## Feedback is first-class
|
|
65
65
|
|
|
66
|
-
Reticle is built for agents
|
|
66
|
+
Reticle is built for agents: your experience decides what gets built or fixed, and it disappears when your context does.
|
|
67
67
|
|
|
68
68
|
The moment Reticle breaks, lies, cannot see something you needed, costs too many calls, or gives you trouble during setup:
|
|
69
69
|
|
|
@@ -74,10 +74,10 @@ reticle_session { action: "feedback", kind, text }
|
|
|
74
74
|
`kind` is one of `bug`, `gap`, `ambiguity`, `feature_request`, `improvement`. One call, then carry on with the task. If the tools are unreachable (setup unfinished, daemon down), file the same report from the shell:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
npx @reticlehq/server feedback --agent --kind <
|
|
77
|
+
npx @reticlehq/server feedback --agent --kind <kind> "what happened"
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
Nothing is too minor. Report defects in **Reticle**; a bug
|
|
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
82
|
## Do not re-read this file to look something up
|
|
83
83
|
|
|
@@ -110,7 +110,7 @@ You need it once, while setting a project up. If `reticle_session` already lists
|
|
|
110
110
|
|
|
111
111
|
# VERIFY
|
|
112
112
|
|
|
113
|
-
**
|
|
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.
|
|
114
114
|
|
|
115
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.**
|
|
116
116
|
|
|
@@ -122,10 +122,10 @@ Work down this list and stop at the first row that fits. Do not hand-drive a flo
|
|
|
122
122
|
| --- | --- | --- |
|
|
123
123
|
| "Did my edit break anything?" | `reticle_verify({ action: "change", files: ["src/App.tsx"] })` | 1 |
|
|
124
124
|
| "Does every saved journey still work?" | `reticle_verify({ action: "flows" })` | 1 |
|
|
125
|
-
| "Does this new behaviour work?" |
|
|
126
|
-
| No MCP available at all | `npx @reticlehq/server verify <url
|
|
125
|
+
| "Does this new behaviour work?" | `reticle_act_and_wait` on the step that ENDS the journey | 1+ |
|
|
126
|
+
| No MCP available at all | `npx @reticlehq/server verify <url>`, then `gate --since HEAD~1` (below); `verify` has no `--since` | 2, no MCP |
|
|
127
127
|
|
|
128
|
-
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.
|
|
129
129
|
|
|
130
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.
|
|
131
131
|
|
|
@@ -149,11 +149,15 @@ The verdict that passes is the one that proves it. Declared it separately with `
|
|
|
149
149
|
|
|
150
150
|
The first drive is expensive; the rest should not be, and you need not ask: **what you drive by hand is saved as a flow automatically**. From then on that journey re-verifies in one deterministic call, and `{action:"change"}` answers `yes` or `no` for those files instead of `unknown`.
|
|
151
151
|
|
|
152
|
-
Whether that flow is worth anything depends on how you drove it. A step keeps a consequence only if you declared one, so `reticle_act_and_wait({ ref, action, until })` replays as a test while a bare `reticle_act` replays as a click that passes even when the feature is broken.
|
|
152
|
+
Whether that flow is worth anything depends on how you drove it. A step keeps a consequence only if you declared one, so `reticle_act_and_wait({ ref, action, until })` replays as a test while a bare `reticle_act` replays as a click that passes even when the feature is broken.
|
|
153
|
+
|
|
154
|
+
## Before you say you are done
|
|
155
|
+
|
|
156
|
+
`npx @reticlehq/server gate --since HEAD~1` exits non-zero unless a passing run covers every saved flow your edit affects, naming the uncovered ones. Reasoning about your diff cannot pass it; as a shell command it works when the tools do not.
|
|
153
157
|
|
|
154
158
|
## When you do have to drive by hand
|
|
155
159
|
|
|
156
|
-
|
|
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:
|
|
157
161
|
|
|
158
162
|
```
|
|
159
163
|
reticle_session() // connected? if empty, read `why` — it names the fix
|
|
@@ -165,18 +169,15 @@ reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf",
|
|
|
165
169
|
]}}) // ← the verdict
|
|
166
170
|
```
|
|
167
171
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
Then report what you drove, what it produced, and the `file:line` for anything broken.
|
|
172
|
+
`until` names the consequence **before** the action, which makes it a check. Add `durable: true` when the change should survive a reload.
|
|
171
173
|
|
|
172
|
-
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 name
|
|
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".
|
|
173
175
|
|
|
174
|
-
- Batching, regression suites, reading a verdict: `https://docs.reticle.sh/agent-cheatsheet.md`
|
|
175
176
|
- Every predicate and action: `https://docs.reticle.sh/predicates.md`, `https://docs.reticle.sh/actions.md`
|
|
176
177
|
- The complete tool surface: `https://docs.reticle.sh/usage.md`
|
|
177
178
|
|
|
178
179
|
## When the verdict lands
|
|
179
180
|
|
|
180
|
-
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.
|
|
181
182
|
|
|
182
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type AdhocVerdict, type ToolCaller } from './adhoc-verdict.js';
|
|
2
|
+
export interface AdhocSuiteOptions {
|
|
3
|
+
port: number;
|
|
4
|
+
/** Run only flows carrying ANY of these labels. Omitted runs the whole suite. */
|
|
5
|
+
select?: string[];
|
|
6
|
+
/** Which connected tab to drive. Omitted means "whatever is connected". */
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
/** Injected so a test does not need a live daemon. */
|
|
10
|
+
connect?: (endpoint: URL) => Promise<ToolCaller>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run the saved suite through the daemon on this port.
|
|
14
|
+
*
|
|
15
|
+
* Exit code 0 ONLY for `status: "pass"`. `unverifiable` exits non-zero on purpose: it is what an
|
|
16
|
+
* empty suite reports, and a CI step that reads "nothing was checked" as success is the false green
|
|
17
|
+
* this product exists to prevent.
|
|
18
|
+
*/
|
|
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>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The saved-flow suite, against the daemon that is already running.
|
|
3
|
+
*
|
|
4
|
+
* `runAdhocVerdict` next door removed one half of a dead end: an agent whose client never loaded
|
|
5
|
+
* the `reticle_*` tools could not reach a verdict, because `verify` refuses when the daemon owns
|
|
6
|
+
* the port and a working install is exactly the state where the daemon owns the port.
|
|
7
|
+
*
|
|
8
|
+
* This is the other half. `--expect` serves a project with NO saved flows; a project WITH them was
|
|
9
|
+
* still stuck, and worse off, because the whole promise of a saved suite is that it runs again
|
|
10
|
+
* without an agent. The documented way out was to stop the daemon, which kills the agent's MCP
|
|
11
|
+
* link, and our own guidance says never to do it.
|
|
12
|
+
*
|
|
13
|
+
* Same trick, and deliberately no new surface: ask the running daemon the question an agent would
|
|
14
|
+
* ask it, over the transport `start()` already attaches unconditionally. Nothing is bound, nothing
|
|
15
|
+
* is stopped, the agent keeps its link.
|
|
16
|
+
*/
|
|
17
|
+
import { ReticleTool } from '@reticlehq/core';
|
|
18
|
+
import { connectOverSse, endpointFor, leaseIfNoTab, releaseLease, refusalText, verdictOf, } from './adhoc-verdict.js';
|
|
19
|
+
/** The only status that is a pass. `unverifiable` means nothing was checked, which is not one. */
|
|
20
|
+
const PASSED = 'pass';
|
|
21
|
+
/** The `reticle_verify` actions this file asks for. */
|
|
22
|
+
const EXPLORE_ACTION = 'explore';
|
|
23
|
+
/**
|
|
24
|
+
* How long a SUITE may take, against the SDK's 60s default for one request.
|
|
25
|
+
*
|
|
26
|
+
* A suite is not one request's worth of work: it replays every saved flow, each of which drives a
|
|
27
|
+
* real browser through a real journey, and a project with a few dozen of them takes minutes. The
|
|
28
|
+
* default timed the call out mid-run and reported "the suite could not be run", which is a sentence
|
|
29
|
+
* about the transport wearing the shape of a verdict — the flows were running the whole time.
|
|
30
|
+
*
|
|
31
|
+
* Found by driving it against a real daemon on a project with 45 saved flows.
|
|
32
|
+
*/
|
|
33
|
+
const SUITE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
34
|
+
/**
|
|
35
|
+
* Run the saved suite through the daemon on this port.
|
|
36
|
+
*
|
|
37
|
+
* Exit code 0 ONLY for `status: "pass"`. `unverifiable` exits non-zero on purpose: it is what an
|
|
38
|
+
* empty suite reports, and a CI step that reads "nothing was checked" as success is the false green
|
|
39
|
+
* this product exists to prevent.
|
|
40
|
+
*/
|
|
41
|
+
export async function runAdhocSuite(options) {
|
|
42
|
+
const connect = options.connect ?? connectOverSse;
|
|
43
|
+
let caller;
|
|
44
|
+
try {
|
|
45
|
+
caller = await connect(endpointFor(options.port, options.token));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
code: 1,
|
|
50
|
+
lines: [
|
|
51
|
+
`could not reach the daemon on port ${String(options.port)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
52
|
+
'It answers `reticle status`; if that works and this does not, the daemon is older than this CLI.',
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
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) {
|
|
110
|
+
return {
|
|
111
|
+
code: 1,
|
|
112
|
+
lines: [
|
|
113
|
+
`could not reach the daemon on port ${String(options.port)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
114
|
+
],
|
|
115
|
+
};
|
|
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
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return {
|
|
148
|
+
code: 1,
|
|
149
|
+
lines: [
|
|
150
|
+
`the explore could not be run: ${error instanceof Error ? error.message : String(error)}`,
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
if (leased !== undefined)
|
|
156
|
+
await releaseLease(caller, leased);
|
|
157
|
+
await caller.close().catch(() => undefined);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -30,7 +30,7 @@ export interface AdhocVerdict {
|
|
|
30
30
|
/** Lines to print, in order. */
|
|
31
31
|
lines: string[];
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
interface AdhocVerdictOptions {
|
|
34
34
|
port: number;
|
|
35
35
|
/** Navigated to first when given; omitted to assert against wherever the session already is. */
|
|
36
36
|
url?: string;
|
|
@@ -48,12 +48,67 @@ export 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
|
-
call(name: string, args: Record<string, unknown
|
|
89
|
+
call(name: string, args: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
|
|
55
90
|
close(): Promise<void>;
|
|
56
91
|
}
|
|
92
|
+
export declare function endpointFor(port: number, token: string | undefined): URL;
|
|
93
|
+
export declare function connectOverSse(endpoint: URL): Promise<ToolCaller>;
|
|
94
|
+
/**
|
|
95
|
+
* The report object an MCP tool result carries, or undefined when the shape is not one.
|
|
96
|
+
*
|
|
97
|
+
* `field` is the key that makes a payload the report this caller wants: `verified` for a one-shot
|
|
98
|
+
* verdict, `status` for a suite. It is a parameter rather than a fixed name because the suite path
|
|
99
|
+
* reproduced this function's own incident the moment it reused it — the headline printed
|
|
100
|
+
* `unverifiable` while the JSON under it said `"status":"fail"` with 74 failing flows, because a
|
|
101
|
+
* suite report has no `verified` key and the text reader would only accept one that did.
|
|
102
|
+
*/
|
|
103
|
+
export declare function verdictOf(result: unknown, field?: string): Record<string, unknown> | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* The tool's own words when it refused, rather than the envelope carrying them.
|
|
106
|
+
*
|
|
107
|
+
* A refusal from these tools is the most useful text they produce — it names the cause and the next
|
|
108
|
+
* action — and printing the MCP result object around it buries that in JSON escaping. The reader is
|
|
109
|
+
* at a terminal; give them the sentence.
|
|
110
|
+
*/
|
|
111
|
+
export declare function refusalText(result: unknown): string | undefined;
|
|
57
112
|
/**
|
|
58
113
|
* Navigate (optionally) and assert, against the daemon already on this port.
|
|
59
114
|
*
|
|
@@ -62,3 +117,4 @@ export interface ToolCaller {
|
|
|
62
117
|
* this whole product exists to prevent.
|
|
63
118
|
*/
|
|
64
119
|
export declare function runAdhocVerdict(options: AdhocVerdictOptions): Promise<AdhocVerdict>;
|
|
120
|
+
export {};
|
|
@@ -3,26 +3,103 @@ 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
|
-
function endpointFor(port, token) {
|
|
77
|
+
export function endpointFor(port, token) {
|
|
9
78
|
const url = new URL(`http://${LOOPBACK_HOST}:${String(port)}${MCP_SSE_PATH}`);
|
|
10
79
|
if (token !== undefined && token.length > 0)
|
|
11
80
|
url.searchParams.set(TOKEN_QUERY_PARAM, token);
|
|
12
81
|
return url;
|
|
13
82
|
}
|
|
14
|
-
async function connectOverSse(endpoint) {
|
|
83
|
+
export async function connectOverSse(endpoint) {
|
|
15
84
|
const client = new Client({ name: 'reticle-cli', version: '1' }, { capabilities: {} });
|
|
16
85
|
await client.connect(new SSEClientTransport(endpoint));
|
|
17
86
|
return {
|
|
18
|
-
call: async (name, args) => await client.callTool({ name, arguments: args }),
|
|
87
|
+
call: async (name, args, timeoutMs) => await client.callTool({ name, arguments: args }, undefined, timeoutMs === undefined ? undefined : { timeout: timeoutMs }),
|
|
19
88
|
close: async () => {
|
|
20
89
|
await client.close();
|
|
21
90
|
},
|
|
22
91
|
};
|
|
23
92
|
}
|
|
24
|
-
/**
|
|
25
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The report object an MCP tool result carries, or undefined when the shape is not one.
|
|
95
|
+
*
|
|
96
|
+
* `field` is the key that makes a payload the report this caller wants: `verified` for a one-shot
|
|
97
|
+
* verdict, `status` for a suite. It is a parameter rather than a fixed name because the suite path
|
|
98
|
+
* reproduced this function's own incident the moment it reused it — the headline printed
|
|
99
|
+
* `unverifiable` while the JSON under it said `"status":"fail"` with 74 failing flows, because a
|
|
100
|
+
* suite report has no `verified` key and the text reader would only accept one that did.
|
|
101
|
+
*/
|
|
102
|
+
export function verdictOf(result, field = 'verified') {
|
|
26
103
|
const structured = result?.structuredContent;
|
|
27
104
|
if ('object' === typeof structured && null !== structured) {
|
|
28
105
|
return structured;
|
|
@@ -30,10 +107,10 @@ function verdictOf(result) {
|
|
|
30
107
|
// A daemon that answers with the verdict as TEXT left this undefined, so the headline read
|
|
31
108
|
// `verified: unknown` while the JSON printed underneath it said `"verified":"no"` — one response
|
|
32
109
|
// giving two answers to the same question. The text is the same object, so read it.
|
|
33
|
-
return verdictFromText(result);
|
|
110
|
+
return verdictFromText(result, field);
|
|
34
111
|
}
|
|
35
|
-
/** The
|
|
36
|
-
function verdictFromText(result) {
|
|
112
|
+
/** The report object inside an MCP text part, when the result carried it there instead. */
|
|
113
|
+
function verdictFromText(result, field) {
|
|
37
114
|
const content = result?.content;
|
|
38
115
|
if (!Array.isArray(content))
|
|
39
116
|
return undefined;
|
|
@@ -45,7 +122,7 @@ function verdictFromText(result) {
|
|
|
45
122
|
const parsed = JSON.parse(text);
|
|
46
123
|
// Only a shape that actually carries a verdict — anything else stays `unknown`, which is the
|
|
47
124
|
// honest answer for a result this function could not read.
|
|
48
|
-
if ('object' === typeof parsed && null !== parsed &&
|
|
125
|
+
if ('object' === typeof parsed && null !== parsed && field in parsed) {
|
|
49
126
|
return parsed;
|
|
50
127
|
}
|
|
51
128
|
}
|
|
@@ -62,7 +139,7 @@ function verdictFromText(result) {
|
|
|
62
139
|
* action — and printing the MCP result object around it buries that in JSON escaping. The reader is
|
|
63
140
|
* at a terminal; give them the sentence.
|
|
64
141
|
*/
|
|
65
|
-
function refusalText(result) {
|
|
142
|
+
export function refusalText(result) {
|
|
66
143
|
const r = result;
|
|
67
144
|
if (true !== r?.isError || !Array.isArray(r.content))
|
|
68
145
|
return undefined;
|
|
@@ -104,12 +181,28 @@ export async function runAdhocVerdict(options) {
|
|
|
104
181
|
],
|
|
105
182
|
};
|
|
106
183
|
}
|
|
184
|
+
let leased;
|
|
107
185
|
try {
|
|
108
186
|
// Spread rather than set: an explicit `sessionId: undefined` is a key the tools reject as an
|
|
109
187
|
// unknown parameter, which would turn "no tab named" into a refusal.
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
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 });
|
|
113
206
|
}
|
|
114
207
|
const result = await caller.call(ReticleTool.ASSERT, { predicate: options.predicate, ...pin });
|
|
115
208
|
const refusal = refusalText(result);
|
|
@@ -139,6 +232,8 @@ export async function runAdhocVerdict(options) {
|
|
|
139
232
|
};
|
|
140
233
|
}
|
|
141
234
|
finally {
|
|
235
|
+
if (leased !== undefined)
|
|
236
|
+
await releaseLease(caller, leased);
|
|
142
237
|
await caller.close().catch(() => undefined);
|
|
143
238
|
}
|
|
144
239
|
}
|