@reticlehq/server 3.8.2 → 3.10.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 +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/SKILL.md +13 -12
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -160
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +46 -5
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -24
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +47 -4
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +22 -17
- package/dist/command/cli/cli-parse.js +54 -32
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +61 -31
- package/dist/command/cli/cli-verify.d.ts +17 -3
- package/dist/command/cli/cli-verify.js +59 -21
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +11 -0
- package/dist/command/cli/ports/port-holder.js +36 -4
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +2 -0
- package/dist/command/cli/status/status-lines.js +72 -17
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +24 -6
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +112 -70
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +22 -4
- package/dist/command/daemon/daemon.js +52 -10
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +149 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +11 -15
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +63 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +36 -15
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +14 -3
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +67 -99
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -7
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +51 -5
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
- package/dist/language/flows/recording/tape/recordings.js +112 -49
- package/dist/language/flows/replay.d.ts +36 -12
- package/dist/language/flows/replay.js +193 -50
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +30 -10
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +35 -18
- package/dist/portal/input/real-input.js +99 -33
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +81 -21
- package/dist/portal/pool/browser-pool.d.ts +16 -30
- package/dist/portal/pool/browser-pool.js +64 -52
- package/dist/portal/pool/context-permissions.d.ts +34 -17
- package/dist/portal/pool/context-permissions.js +94 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +56 -0
- package/dist/portal/pool/lease-page-error.js +160 -0
- package/dist/portal/pool/page-error-journal.d.ts +63 -0
- package/dist/portal/pool/page-error-journal.js +208 -0
- package/dist/portal/pool/playwright-launcher.js +71 -15
- package/dist/portal/pool/pool-contract.d.ts +49 -1
- package/dist/portal/pool/pool-lease.d.ts +70 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +91 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
- package/dist/portal/session/dev-server/dev-command.js +110 -16
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +76 -27
- package/dist/portal/session/no-session-next-action.d.ts +14 -0
- package/dist/portal/session/no-session-next-action.js +56 -7
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +41 -4
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +211 -81
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +30 -2
- package/dist/start-wiring.js +74 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +8 -5
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +38 -18
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +10 -0
- package/dist/surface/tools/act/after-match.js +40 -0
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +17 -1
- package/dist/surface/tools/act/resolve-target.js +59 -9
- package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
- package/dist/surface/tools/act/sequence-grade.js +45 -27
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +81 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +83 -95
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +18 -12
- package/dist/surface/tools/browser-tools.js +87 -13
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +11 -3
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +131 -40
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +29 -21
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +42 -1
- package/dist/surface/tools/lease-hint.js +52 -3
- package/dist/surface/tools/lease-readiness.d.ts +47 -0
- package/dist/surface/tools/lease-readiness.js +126 -0
- package/dist/surface/tools/lease-seed.d.ts +16 -1
- package/dist/surface/tools/lease-seed.js +37 -4
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +104 -124
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +18 -0
- package/dist/surface/tools/tool-kit.js +9 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +54 -18
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +17 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +15 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/architecture.md +3 -6
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +13 -12
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +34 -24
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +17 -22
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +6 -4
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +18 -14
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steps that reach past the page the journey is on: one opens another page (`navigate`), one sends a
|
|
3
|
+
* request to the app (`request`). Neither acts on an element, and both depend on a caller that owns
|
|
4
|
+
* what the replay cannot reach by itself: the sessions, the origin, the cookies.
|
|
5
|
+
*/
|
|
6
|
+
import { FlowStepTool, } from '@reticlehq/core';
|
|
7
|
+
/*
|
|
8
|
+
* A step that opens a page in the middle of a journey. The replay goes on with the page that loaded,
|
|
9
|
+
* and the step's `expect` is judged there (see `OpenPage`).
|
|
10
|
+
*/
|
|
11
|
+
/** The step's result before its `expect` is judged, and the session to go on with. */
|
|
12
|
+
export async function openStepPage(step, index, open) {
|
|
13
|
+
const url = step.navigate ?? '';
|
|
14
|
+
const base = { step: index, tool: FlowStepTool.NAVIGATE, anchor: url };
|
|
15
|
+
// Never skipped: a journey that reports green without the page it was meant to check is a false
|
|
16
|
+
// green arriving through the step meant to make the check stronger.
|
|
17
|
+
if (open === undefined) {
|
|
18
|
+
return {
|
|
19
|
+
result: {
|
|
20
|
+
...base,
|
|
21
|
+
ok: false,
|
|
22
|
+
error: `this replay cannot open a page, so the step that opens ${url} did not run`,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const opened = await open(url);
|
|
27
|
+
if ('error' in opened) {
|
|
28
|
+
return { result: { ...base, ok: false, error: opened.error } };
|
|
29
|
+
}
|
|
30
|
+
return { result: { ...base, ok: true }, session: opened.session };
|
|
31
|
+
}
|
|
32
|
+
/** Why an answer does not match the step's `response`, or undefined when it does. */
|
|
33
|
+
function responseMismatch(response, answer) {
|
|
34
|
+
const statuses = response.status === undefined ? [] : [response.status].flat();
|
|
35
|
+
if (statuses.length > 0 && !statuses.includes(answer.status)) {
|
|
36
|
+
return `the answer was ${String(answer.status)}, expected ${statuses.join(' or ')}`;
|
|
37
|
+
}
|
|
38
|
+
if (response.contains !== undefined && !answer.body.includes(response.contains)) {
|
|
39
|
+
return `the answer (${String(answer.status)}) does not contain "${response.contains}"`;
|
|
40
|
+
}
|
|
41
|
+
if (response.absent !== undefined && answer.body.includes(response.absent)) {
|
|
42
|
+
return `the answer (${String(answer.status)}) contains "${response.absent}", which it must not`;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/*
|
|
47
|
+
* A step that sends one request and judges the answer. It never skips: a rule the flow says it
|
|
48
|
+
* checks and did not is a false green.
|
|
49
|
+
*/
|
|
50
|
+
/** Methods that only read. Any other method changes data and needs the run's confirmation. */
|
|
51
|
+
const READ_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
52
|
+
export async function runRequestStep(step, index, send, judged,
|
|
53
|
+
/**
|
|
54
|
+
* The run's own yes to steps that change data, never the flow file's: flow files are shared, and a
|
|
55
|
+
* saved `DELETE` would otherwise run with the user's cookies on any app the flow is pointed at.
|
|
56
|
+
*/
|
|
57
|
+
confirmDangerous) {
|
|
58
|
+
const request = step.request;
|
|
59
|
+
const label = request === undefined ? '' : `${request.method} ${request.url}`;
|
|
60
|
+
const base = { step: index, tool: FlowStepTool.REQUEST, anchor: label };
|
|
61
|
+
if (request === undefined || send === undefined) {
|
|
62
|
+
return {
|
|
63
|
+
...base,
|
|
64
|
+
ok: false,
|
|
65
|
+
error: `this replay cannot send a request, so ${label} did not run`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!READ_METHODS.has(request.method.toUpperCase()) && !confirmDangerous) {
|
|
69
|
+
return {
|
|
70
|
+
...base,
|
|
71
|
+
ok: false,
|
|
72
|
+
error: `potentially destructive request blocked: ${label} changes data, so it was not sent. ` +
|
|
73
|
+
'Confirm it for this run: reticle_verify{action:"flows", confirmDangerous: true}, or ' +
|
|
74
|
+
'`reticle verify <url> --confirm-dangerous`. It cannot be confirmed in the flow file.',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const answer = await send(request);
|
|
78
|
+
if ('error' in answer) {
|
|
79
|
+
return { ...base, ok: false, error: answer.error };
|
|
80
|
+
}
|
|
81
|
+
const mismatch = !judged || step.response === undefined ? undefined : responseMismatch(step.response, answer);
|
|
82
|
+
return mismatch === undefined ? { ...base, ok: true } : { ...base, ok: false, error: mismatch };
|
|
83
|
+
}
|
|
84
|
+
/** True for a step that reaches past the page: `navigate` or `request`. */
|
|
85
|
+
export function isPageStep(step) {
|
|
86
|
+
return step.navigate !== undefined || step.request !== undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run a `navigate` or `request` step. `judge` evaluates an `expect` on a page; it comes from the
|
|
90
|
+
* replay so this file does not import the replay back. `checked` is false for a setup step, whose
|
|
91
|
+
* consequence is not checked. A navigate step returns the session to go on with.
|
|
92
|
+
*/
|
|
93
|
+
export async function runPageStep(step, index, ctx) {
|
|
94
|
+
if (step.request !== undefined) {
|
|
95
|
+
return {
|
|
96
|
+
result: await runRequestStep(step, index, ctx.send, ctx.checked, ctx.confirmDangerous),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const opened = await openStepPage(step, index, ctx.open);
|
|
100
|
+
if (opened.session === undefined) {
|
|
101
|
+
return opened;
|
|
102
|
+
}
|
|
103
|
+
const result = opened.result;
|
|
104
|
+
const page = ctx.route(opened.session);
|
|
105
|
+
if (page !== undefined) {
|
|
106
|
+
result.page = page;
|
|
107
|
+
}
|
|
108
|
+
const drift = step.expect === undefined || !ctx.checked
|
|
109
|
+
? undefined
|
|
110
|
+
: await ctx.judge(opened.session, step.expect);
|
|
111
|
+
if (drift !== undefined) {
|
|
112
|
+
result.ok = false;
|
|
113
|
+
result.drift = drift;
|
|
114
|
+
}
|
|
115
|
+
return { result, session: opened.session };
|
|
116
|
+
}
|
|
117
|
+
/** How long a `request` step waits for the answer. */
|
|
118
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
119
|
+
/** The `Cookie` header the page would send to `host`, from the driven browser's storage state. */
|
|
120
|
+
function cookieHeader(state, host) {
|
|
121
|
+
const cookies = state?.cookies;
|
|
122
|
+
if (!Array.isArray(cookies)) {
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
return cookies
|
|
126
|
+
.filter((c) => {
|
|
127
|
+
const domain = c.domain;
|
|
128
|
+
if (typeof domain !== 'string') {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const bare = domain.replace(/^\./, '');
|
|
132
|
+
return host === bare || host.endsWith(`.${bare}`);
|
|
133
|
+
})
|
|
134
|
+
.map((c) => `${c.name}=${c.value}`)
|
|
135
|
+
.join('; ');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The sender a `request` step uses: same origin as the page only, the page's cookies unless the step
|
|
139
|
+
* is anonymous, no redirects followed (a 302 is the answer to judge). `pageUrl` is read at send time,
|
|
140
|
+
* because a `navigate` step changes the page.
|
|
141
|
+
*/
|
|
142
|
+
export function requestSender(pageUrl, provider,
|
|
143
|
+
/** The `Cookie` header of the account a step names in `as`, or undefined when not supplied. */
|
|
144
|
+
accountCookie = () => undefined) {
|
|
145
|
+
return async (request) => {
|
|
146
|
+
const url = pageUrl();
|
|
147
|
+
if (url === undefined) {
|
|
148
|
+
return { error: 'the page has no known address, so the request has nowhere to go' };
|
|
149
|
+
}
|
|
150
|
+
const origin = new URL(url).origin;
|
|
151
|
+
const target = new URL(request.url, origin);
|
|
152
|
+
if (target.origin !== origin) {
|
|
153
|
+
return {
|
|
154
|
+
error: `${request.url} is not on the app's origin ${origin}; a flow sends requests only to its app`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const headers = {};
|
|
158
|
+
if (request.body !== undefined) {
|
|
159
|
+
headers['content-type'] = request.contentType ?? 'application/json';
|
|
160
|
+
}
|
|
161
|
+
if (request.as !== undefined) {
|
|
162
|
+
const cookie = accountCookie(request.as);
|
|
163
|
+
if (cookie === undefined || '' === cookie) {
|
|
164
|
+
return {
|
|
165
|
+
error: `the session of the account "${request.as}" was not supplied, so the request was not ` +
|
|
166
|
+
'sent. Supply its Cookie header as a secret for this run.',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
headers['cookie'] = cookie;
|
|
170
|
+
}
|
|
171
|
+
else if (true !== request.anonymous) {
|
|
172
|
+
const state = await provider?.storageState?.(url);
|
|
173
|
+
if (state === undefined) {
|
|
174
|
+
return {
|
|
175
|
+
error: 'the page cookies cannot be read here (this is not a driven browser), so the request ' +
|
|
176
|
+
'cannot go as the signed-in user. Mark it `anonymous`, or replay with `reticle verify`.',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const cookie = cookieHeader(state, target.hostname);
|
|
180
|
+
if (cookie !== '') {
|
|
181
|
+
headers['cookie'] = cookie;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const answer = await fetch(target, {
|
|
186
|
+
method: request.method,
|
|
187
|
+
headers,
|
|
188
|
+
...(request.body === undefined ? {} : { body: request.body }),
|
|
189
|
+
redirect: 'manual',
|
|
190
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
191
|
+
});
|
|
192
|
+
return { status: answer.status, body: await answer.text() };
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
return { error: `the request ${request.method} ${target.pathname} failed: ${String(error)}` };
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sequential half of `reticle_verify { action: "flows" | "change" }`.
|
|
3
|
+
*
|
|
4
|
+
* Flows replay one after the other against one live tab, with no limit on the total time. When the
|
|
5
|
+
* browser session was lost part-way through, every remaining flow waited for a page that never came,
|
|
6
|
+
* and the MCP client gave up (about 300 s) with no result at all - including for the flows that had
|
|
7
|
+
* already run. So the loop now stops at two points, and says so:
|
|
8
|
+
*
|
|
9
|
+
* - the total time budget is spent: no further flow is started;
|
|
10
|
+
* - the session is gone after a flow: the rest cannot run, so they are not attempted.
|
|
11
|
+
*
|
|
12
|
+
* Kept free of IO (the clock, the liveness probe and the replay are injected) so both stops are
|
|
13
|
+
* tested with a fake clock.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { SuiteStopReason, SuiteVerdictStatus } from '@reticlehq/core';
|
|
17
|
+
/** Below the roughly 300 s at which common MCP clients stop waiting, so a result can still be returned. */
|
|
18
|
+
export declare const DEFAULT_SUITE_BUDGET_MS = 240000;
|
|
19
|
+
/** An hour: more than that is not a budget, and a client will have given up long before. */
|
|
20
|
+
export declare const MAX_SUITE_BUDGET_MS = 3600000;
|
|
21
|
+
export interface SuiteStop {
|
|
22
|
+
reason: SuiteStopReason;
|
|
23
|
+
/** For SESSION_LOST: the flow that was running when the session went. */
|
|
24
|
+
flow?: string;
|
|
25
|
+
budgetMs?: number;
|
|
26
|
+
nextAction: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SequentialRun<R> {
|
|
29
|
+
results: R[];
|
|
30
|
+
/** Selected flows that were never started, in order. */
|
|
31
|
+
notRun: string[];
|
|
32
|
+
stopped?: SuiteStop;
|
|
33
|
+
}
|
|
34
|
+
export interface SequentialRunOptions<R> {
|
|
35
|
+
now: () => number;
|
|
36
|
+
budgetMs: number;
|
|
37
|
+
/** True while the session the flows drive is still there. Probed after each flow. */
|
|
38
|
+
sessionAlive: () => boolean;
|
|
39
|
+
run: (name: string) => Promise<R>;
|
|
40
|
+
}
|
|
41
|
+
export declare function runSequentially<R>(names: readonly string[], options: SequentialRunOptions<R>): Promise<SequentialRun<R>>;
|
|
42
|
+
/**
|
|
43
|
+
* Mark a verdict `partial` when the suite stopped early. The ran flows keep their counts and
|
|
44
|
+
* failures; the summary leads with what did not run, because a green count over half a suite must
|
|
45
|
+
* not read as a pass.
|
|
46
|
+
*/
|
|
47
|
+
export declare function withStop<V extends {
|
|
48
|
+
status: SuiteVerdictStatus;
|
|
49
|
+
summary: string;
|
|
50
|
+
}>(verdict: V, notRun: readonly string[], stopped: SuiteStop | undefined): V & {
|
|
51
|
+
notRun?: string[];
|
|
52
|
+
stopped?: SuiteStop;
|
|
53
|
+
};
|
|
54
|
+
/** The `budgetMs` input of the suite tools. */
|
|
55
|
+
export declare const BUDGET_MS_ARG: z.ZodOptional<z.ZodNumber>;
|
|
56
|
+
/** The two output fields a "partial" suite adds. */
|
|
57
|
+
export declare const PARTIAL_OUTPUT: {
|
|
58
|
+
notRun: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
59
|
+
stopped: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
reason: z.ZodEnum<{
|
|
61
|
+
time_budget: "time_budget";
|
|
62
|
+
session_lost: "session_lost";
|
|
63
|
+
}>;
|
|
64
|
+
flow: z.ZodOptional<z.ZodString>;
|
|
65
|
+
budgetMs: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
nextAction: z.ZodString;
|
|
67
|
+
}, z.core.$strip>>;
|
|
68
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sequential half of `reticle_verify { action: "flows" | "change" }`.
|
|
3
|
+
*
|
|
4
|
+
* Flows replay one after the other against one live tab, with no limit on the total time. When the
|
|
5
|
+
* browser session was lost part-way through, every remaining flow waited for a page that never came,
|
|
6
|
+
* and the MCP client gave up (about 300 s) with no result at all - including for the flows that had
|
|
7
|
+
* already run. So the loop now stops at two points, and says so:
|
|
8
|
+
*
|
|
9
|
+
* - the total time budget is spent: no further flow is started;
|
|
10
|
+
* - the session is gone after a flow: the rest cannot run, so they are not attempted.
|
|
11
|
+
*
|
|
12
|
+
* Kept free of IO (the clock, the liveness probe and the replay are injected) so both stops are
|
|
13
|
+
* tested with a fake clock.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { SuiteStopReason, SuiteVerdictStatus } from '@reticlehq/core';
|
|
17
|
+
/** Below the roughly 300 s at which common MCP clients stop waiting, so a result can still be returned. */
|
|
18
|
+
export const DEFAULT_SUITE_BUDGET_MS = 240_000;
|
|
19
|
+
/** An hour: more than that is not a budget, and a client will have given up long before. */
|
|
20
|
+
export const MAX_SUITE_BUDGET_MS = 3_600_000;
|
|
21
|
+
export async function runSequentially(names, options) {
|
|
22
|
+
const deadline = options.now() + options.budgetMs;
|
|
23
|
+
const results = [];
|
|
24
|
+
for (const [index, name] of names.entries()) {
|
|
25
|
+
// The first flow always starts: a budget that stops a suite before anything ran proves nothing.
|
|
26
|
+
if (index > 0 && options.now() >= deadline) {
|
|
27
|
+
return {
|
|
28
|
+
results,
|
|
29
|
+
notRun: names.slice(index),
|
|
30
|
+
stopped: {
|
|
31
|
+
reason: SuiteStopReason.TIME_BUDGET,
|
|
32
|
+
budgetMs: options.budgetMs,
|
|
33
|
+
nextAction: 'run the flows listed in notRun in a new call (pass `names`), or raise `budgetMs` if your client waits longer',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
results.push(await options.run(name));
|
|
38
|
+
if (index < names.length - 1 && !options.sessionAlive()) {
|
|
39
|
+
return {
|
|
40
|
+
results,
|
|
41
|
+
notRun: names.slice(index + 1),
|
|
42
|
+
stopped: {
|
|
43
|
+
reason: SuiteStopReason.SESSION_LOST,
|
|
44
|
+
flow: name,
|
|
45
|
+
nextAction: 'reconnect the app (open it in a tab, or call reticle_sessions to find the live one), then run the flows listed in notRun with `names`',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { results, notRun: [] };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Mark a verdict `partial` when the suite stopped early. The ran flows keep their counts and
|
|
54
|
+
* failures; the summary leads with what did not run, because a green count over half a suite must
|
|
55
|
+
* not read as a pass.
|
|
56
|
+
*/
|
|
57
|
+
export function withStop(verdict, notRun, stopped) {
|
|
58
|
+
if (stopped === undefined) {
|
|
59
|
+
return verdict;
|
|
60
|
+
}
|
|
61
|
+
const why = stopped.reason === SuiteStopReason.TIME_BUDGET
|
|
62
|
+
? `the ${String(Math.round((stopped.budgetMs ?? 0) / 1000))} s total budget ran out`
|
|
63
|
+
: `the browser session was lost while "${stopped.flow ?? ''}" was running`;
|
|
64
|
+
return {
|
|
65
|
+
...verdict,
|
|
66
|
+
status: SuiteVerdictStatus.PARTIAL,
|
|
67
|
+
summary: `PARTIAL - ${why}; ${String(notRun.length)} flow(s) did not run (${notRun.join(', ')}). Of those that ran: ${verdict.summary}`,
|
|
68
|
+
notRun: [...notRun],
|
|
69
|
+
stopped,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** The `budgetMs` input of the suite tools. */
|
|
73
|
+
export const BUDGET_MS_ARG = z
|
|
74
|
+
.number()
|
|
75
|
+
.finite()
|
|
76
|
+
.int()
|
|
77
|
+
.positive()
|
|
78
|
+
.max(MAX_SUITE_BUDGET_MS)
|
|
79
|
+
.optional()
|
|
80
|
+
.describe(`Total time budget for the sequential run, in milliseconds (default ${String(DEFAULT_SUITE_BUDGET_MS)}, below the ~300 s at which common MCP clients stop waiting). When it is spent no further flow is started: the call returns status "partial" with the finished results and the flows in notRun.`);
|
|
81
|
+
/** The two output fields a "partial" suite adds. */
|
|
82
|
+
export const PARTIAL_OUTPUT = {
|
|
83
|
+
notRun: z
|
|
84
|
+
.array(z.string())
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Present on a "partial" suite: the selected flows that were never started because the time budget ran out or the session was lost.'),
|
|
87
|
+
stopped: z
|
|
88
|
+
.object({
|
|
89
|
+
reason: z.enum([SuiteStopReason.TIME_BUDGET, SuiteStopReason.SESSION_LOST]),
|
|
90
|
+
flow: z.string().optional(),
|
|
91
|
+
budgetMs: z.number().optional(),
|
|
92
|
+
nextAction: z.string(),
|
|
93
|
+
})
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Present on a "partial" suite: why it ended early (time_budget | session_lost), the flow that lost the session, and one next action.'),
|
|
96
|
+
};
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { sessionRoot
|
|
2
|
+
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
3
3
|
import { verdictForSuite } from '../../judgement/outcome/verify-change-verdict.js';
|
|
4
4
|
import { attributedFailures } from '../../judgement/outcome/attributed-failure.js';
|
|
5
5
|
import { Verified, isAdvisory } from '@reticlehq/core';
|
|
6
6
|
import { ReticleTool } from '@reticlehq/core';
|
|
7
7
|
import { asNumber, asRecord, asString } from '@reticlehq/core';
|
|
8
8
|
import { workerCountSchema } from '../../surface/tools/args/numeric-bounds.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { resolveChangedFiles } from '../../command/cli/cli-flow-commands.js';
|
|
10
|
+
import { namedFlowsForSession } from './flow-store-for-session.js';
|
|
11
11
|
import { affectedSavedFlows } from './change/flow-sources.js';
|
|
12
12
|
import { FLOW_TOOLS } from './flow-tools.js';
|
|
13
13
|
import { RESULT_FIELD, changeResult } from '../../surface/tools/reticle-result.js';
|
|
14
14
|
import { findContradictions } from '@reticlehq/engine/disagreement/contradictions.js';
|
|
15
|
+
import { isOneActionOnly } from './flow-cross-step.js';
|
|
15
16
|
import { parseControls } from '../../surface/tools/coverage-tools.js';
|
|
16
17
|
import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
|
|
18
|
+
import { BUDGET_MS_ARG } from './suite/sequential-run.js';
|
|
17
19
|
const VERIFY_CHANGE = {
|
|
18
20
|
name: ReticleTool.VERIFY_CHANGE,
|
|
19
21
|
description: 'Did my change break anything? Give it the files you edited (or `since` for a git ref) and it works out which saved flows cover them, replays exactly those, and answers with one `verified` (yes|no|unknown) plus `because`. `unknown` when NO saved flow covers the change — that is the honest answer, never a green: nothing ran, so nothing was proved. Returns { verified, because, changedFiles, flowsRun, suite, unknownProvenance }.',
|
|
@@ -30,6 +32,11 @@ const VERIFY_CHANGE = {
|
|
|
30
32
|
parallel: workerCountSchema
|
|
31
33
|
.optional()
|
|
32
34
|
.describe('Replay this many affected flows at once (needs the browser pool).'),
|
|
35
|
+
ignoreHTTPSErrors: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Accept a self-signed or local-CA certificate for parallel flow leases. Omit to inherit the source lease setting; otherwise certificate checks stay on.'),
|
|
39
|
+
budgetMs: BUDGET_MS_ARG,
|
|
33
40
|
sessionId: z
|
|
34
41
|
.string()
|
|
35
42
|
.optional()
|
|
@@ -68,16 +75,14 @@ const VERIFY_CHANGE = {
|
|
|
68
75
|
// used to say it was: `deps.reticleRoot` is the daemon's `.reticle` SUBDIRECTORY, and the
|
|
69
76
|
// daemon's cwd is wherever the MCP host started it — `/` or `$HOME` for a globally-registered
|
|
70
77
|
// server. Both answer about the wrong repository while reporting a clean "nothing changed",
|
|
71
|
-
// which reads as a pass. `
|
|
72
|
-
|
|
78
|
+
// which reads as a pass. `projectDir` is the directory holding the resolved root.
|
|
79
|
+
//
|
|
80
|
+
// The flows come from the store `flows` replays from, and flow_save writes to. A read that
|
|
81
|
+
// resolved differently would report "no flows covered this change" over flows that exist,
|
|
82
|
+
// which reads as a clean result rather than an error (#1996).
|
|
83
|
+
const { flows, projectDir } = await namedFlowsForSession(deps, asString(args['sessionId']));
|
|
84
|
+
const changed = await resolveChangedFiles(files, since, projectDir);
|
|
73
85
|
const changedFiles = changed.files;
|
|
74
|
-
// Same root flow_save wrote to. A read that resolved differently would report "no flows
|
|
75
|
-
// covered this change" over flows that exist, which reads as a clean result rather than
|
|
76
|
-
// an error, so the disagreement would be invisible.
|
|
77
|
-
// Both halves of the address from one place. The root was already resolved from the session;
|
|
78
|
-
// the id used to come from the daemon's cwd, so they named different projects.
|
|
79
|
-
const sid = asString(args['sessionId']);
|
|
80
|
-
const flows = await loadNamedFlows(deps.fs, sessionRoot(deps, sid), sessionProjectId(deps, sid));
|
|
81
86
|
const { affected, unknownProvenance } = affectedSavedFlows(flows, changedFiles);
|
|
82
87
|
if (0 === changedFiles.length) {
|
|
83
88
|
return {
|
|
@@ -126,11 +131,16 @@ const VERIFY_CHANGE = {
|
|
|
126
131
|
// flows produced, and never events from whatever the agent did before calling this.
|
|
127
132
|
const cursor = cursorBeforeReplay(deps, args);
|
|
128
133
|
const verify = FLOW_TOOLS.find((tool) => tool.name === ReticleTool.FLOW_VERIFY);
|
|
129
|
-
if (verify === undefined)
|
|
134
|
+
if (verify === undefined) {
|
|
130
135
|
throw new Error('the whole-suite replay tool is not registered');
|
|
136
|
+
}
|
|
131
137
|
const suite = asRecord(await verify.handler(deps, {
|
|
132
138
|
names: affected,
|
|
133
139
|
...(asNumber(args['parallel']) === undefined ? {} : { parallel: args['parallel'] }),
|
|
140
|
+
...(asNumber(args['budgetMs']) === undefined ? {} : { budgetMs: args['budgetMs'] }),
|
|
141
|
+
...('boolean' === typeof args['ignoreHTTPSErrors']
|
|
142
|
+
? { ignoreHTTPSErrors: args['ignoreHTTPSErrors'] }
|
|
143
|
+
: {}),
|
|
134
144
|
...(asString(args['sessionId']) === undefined ? {} : { sessionId: args['sessionId'] }),
|
|
135
145
|
}));
|
|
136
146
|
const failed = suite.failed ?? 0;
|
|
@@ -254,9 +264,10 @@ async function inspectAfterReplay(deps, args, cursor) {
|
|
|
254
264
|
appOrigin: session.url,
|
|
255
265
|
background: session.background,
|
|
256
266
|
namedNetUrls: [],
|
|
257
|
-
// The
|
|
267
|
+
// The cursor is where the replay started. The replay is many user actions, not one, so a
|
|
268
|
+
// finding that only means something inside one action's window is left to the steps (#2013).
|
|
258
269
|
actionSince: cursor,
|
|
259
|
-
});
|
|
270
|
+
}).filter((found) => !isOneActionOnly(found));
|
|
260
271
|
const snapshot = await session.command(ReticleCommand.SNAPSHOT, {
|
|
261
272
|
mode: SnapshotMode.INTERACTIVE,
|
|
262
273
|
});
|
|
@@ -87,6 +87,7 @@ interface ProbeMachine {
|
|
|
87
87
|
env: NodeJS.ProcessEnv;
|
|
88
88
|
exists: (path: string) => boolean;
|
|
89
89
|
pathProblem: (path: string) => ChromiumPathProblem | undefined;
|
|
90
|
+
platform?: NodeJS.Platform;
|
|
90
91
|
}
|
|
91
92
|
/**
|
|
92
93
|
* The lease preflight's probe: "can a launch succeed?", which now includes the Chrome/Edge fallback
|
package/dist/launch-chromium.js
CHANGED
|
@@ -48,11 +48,15 @@ const CHANNEL_PATHS = {
|
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
const WINDOWS_INSTALL_ROOTS = ['LOCALAPPDATA', 'PROGRAMFILES', 'PROGRAMFILES(X86)'];
|
|
51
|
+
function darwinChannelCandidates(appPath, env) {
|
|
52
|
+
const home = env['HOME']?.replace(/\/+$/, '');
|
|
53
|
+
return undefined === home || 0 === home.length ? [appPath] : [appPath, `${home}${appPath}`];
|
|
54
|
+
}
|
|
51
55
|
/** Candidate executable paths for a channel on this platform; empty where it is not installable. */
|
|
52
56
|
export function channelExecutableCandidates(channel, platform, env) {
|
|
53
57
|
const paths = CHANNEL_PATHS[channel];
|
|
54
58
|
if ('darwin' === platform)
|
|
55
|
-
return
|
|
59
|
+
return darwinChannelCandidates(paths.darwin, env);
|
|
56
60
|
if ('linux' === platform)
|
|
57
61
|
return [paths.linux];
|
|
58
62
|
if ('win32' === platform) {
|
|
@@ -63,9 +67,21 @@ export function channelExecutableCandidates(channel, platform, env) {
|
|
|
63
67
|
}
|
|
64
68
|
return [];
|
|
65
69
|
}
|
|
70
|
+
function findInstalledBrowser(deps) {
|
|
71
|
+
for (const channel of CHANNEL_ORDER) {
|
|
72
|
+
const found = channelExecutableCandidates(channel, deps.platform, deps.env).find((p) => deps.exists(p));
|
|
73
|
+
if (undefined !== found) {
|
|
74
|
+
// Playwright resolves a channel from its own registry (/Applications on macOS). A browser in
|
|
75
|
+
// ~/Applications is not in that registry, so launch it by executablePath instead.
|
|
76
|
+
const outsideRegistry = 'darwin' === deps.platform && found !== CHANNEL_PATHS[channel].darwin;
|
|
77
|
+
return outsideRegistry ? { channel, executablePath: found } : { channel };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
66
82
|
/** The first installed Chrome/Edge on this machine, or undefined. Looks only; installs nothing. */
|
|
67
83
|
export function findInstalledChannel(deps) {
|
|
68
|
-
return
|
|
84
|
+
return findInstalledBrowser(deps)?.channel;
|
|
69
85
|
}
|
|
70
86
|
const usingChannelLine = (channel) => `[reticle] Playwright's Chromium is not installed; using the installed ${CHANNEL_LABEL[channel]} instead.\n`;
|
|
71
87
|
const installingLine = (command) => `[reticle] No Chromium, Chrome or Edge found; installing Playwright's Chromium (${command}) — a one-time download…\n`;
|
|
@@ -98,10 +114,12 @@ export async function resolveChromiumTarget(deps) {
|
|
|
98
114
|
}
|
|
99
115
|
if (deps.exists(bundled))
|
|
100
116
|
return { found: true };
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
deps.announce(usingChannelLine(channel));
|
|
104
|
-
return
|
|
117
|
+
const installed = findInstalledBrowser(deps);
|
|
118
|
+
if (undefined !== installed) {
|
|
119
|
+
deps.announce(usingChannelLine(installed.channel));
|
|
120
|
+
return undefined === installed.executablePath
|
|
121
|
+
? { found: true, channel: installed.channel }
|
|
122
|
+
: { found: true, executablePath: installed.executablePath };
|
|
105
123
|
}
|
|
106
124
|
deps.announce(installingLine(chromiumInstallCommand(bundledPlaywrightVersion())));
|
|
107
125
|
const ok = await deps.install();
|
|
@@ -207,6 +225,7 @@ const THIS_MACHINE = {
|
|
|
207
225
|
env: process.env,
|
|
208
226
|
exists: existsSync,
|
|
209
227
|
pathProblem: (path) => chromiumPathProblem(path, process.platform),
|
|
228
|
+
platform: process.platform,
|
|
210
229
|
};
|
|
211
230
|
/**
|
|
212
231
|
* The lease preflight's probe: "can a launch succeed?", which now includes the Chrome/Edge fallback
|
|
@@ -217,16 +236,29 @@ export async function probeLaunchableChromium(machine = THIS_MACHINE) {
|
|
|
217
236
|
const configured = configuredChromiumProbe(machine.env, machine.pathProblem);
|
|
218
237
|
if (configured !== undefined)
|
|
219
238
|
return configured;
|
|
239
|
+
const platform = machine.platform ?? process.platform;
|
|
220
240
|
try {
|
|
221
241
|
const { chromium } = await import('playwright');
|
|
222
|
-
const target = await resolveChromiumTarget(
|
|
242
|
+
const target = await resolveChromiumTarget({
|
|
243
|
+
...defaultDeps(chromium),
|
|
244
|
+
exists: machine.exists,
|
|
245
|
+
pathProblem: machine.pathProblem,
|
|
246
|
+
platform,
|
|
247
|
+
env: machine.env,
|
|
248
|
+
...(machine === THIS_MACHINE
|
|
249
|
+
? {}
|
|
250
|
+
: {
|
|
251
|
+
install: () => Promise.resolve(false),
|
|
252
|
+
announce: () => { },
|
|
253
|
+
}),
|
|
254
|
+
});
|
|
223
255
|
if (target.found)
|
|
224
256
|
return { exists: true };
|
|
225
257
|
}
|
|
226
258
|
catch {
|
|
227
|
-
// No playwright at all —
|
|
259
|
+
// No playwright at all — probeChromiumWithFallback below says so, with where it looked.
|
|
228
260
|
}
|
|
229
|
-
return
|
|
261
|
+
return probeChromiumWithFallback(machine);
|
|
230
262
|
}
|
|
231
263
|
/**
|
|
232
264
|
* `doctor`'s probe: the plain Chromium probe, plus the installed browser that stands in when the
|
|
@@ -236,13 +268,18 @@ export async function probeChromiumWithFallback(machine = THIS_MACHINE) {
|
|
|
236
268
|
const configured = configuredChromiumProbe(machine.env, machine.pathProblem);
|
|
237
269
|
if (configured !== undefined)
|
|
238
270
|
return configured;
|
|
239
|
-
const probe = await probeChromium();
|
|
271
|
+
const probe = await probeChromium(machine.exists);
|
|
240
272
|
if (probe.exists)
|
|
241
273
|
return probe;
|
|
274
|
+
const platform = machine.platform ?? process.platform;
|
|
242
275
|
const channel = findInstalledChannel({
|
|
243
276
|
exists: machine.exists,
|
|
244
|
-
platform
|
|
277
|
+
platform,
|
|
245
278
|
env: machine.env,
|
|
246
279
|
});
|
|
247
|
-
|
|
280
|
+
if (undefined !== channel) {
|
|
281
|
+
return { ...probe, fallback: CHANNEL_LABEL[channel] };
|
|
282
|
+
}
|
|
283
|
+
const fallbackPaths = CHANNEL_ORDER.flatMap((ch) => channelExecutableCandidates(ch, platform, machine.env));
|
|
284
|
+
return 0 === fallbackPaths.length ? probe : { ...probe, fallbackPaths };
|
|
248
285
|
}
|
|
@@ -15,6 +15,19 @@ export interface BackgroundSyncGate {
|
|
|
15
15
|
/** Who is sending, when it is ON: a new value after a new sign-in or a new key. */
|
|
16
16
|
credential?: string;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The switch a person set to stop sending without being asked, or undefined when nobody set it.
|
|
20
|
+
*
|
|
21
|
+
* Every send that nobody asked for reads it, the daemon's and the one `start()` runs on close. The
|
|
22
|
+
* close cycle read nothing, so a person who switched it off still sent this project and every
|
|
23
|
+
* other linked one when `reticle drive` or a self-booting `verify` closed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function backgroundSyncOptOut(reticleRoot: string, env: NodeJS.ProcessEnv): BackgroundSyncGate | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* The gate of the one cycle `start()` sends on close, with nobody signed in. CI's key in the
|
|
28
|
+
* environment still sends there, as the docs promise. A switch that a person set still stops it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function closeCycleGate(reticleRoot: string, env: NodeJS.ProcessEnv): BackgroundSyncGate;
|
|
18
31
|
/** May the daemon send this root's work now? Reads the disk, never the network. */
|
|
19
32
|
export declare function backgroundSyncGate(input: {
|
|
20
33
|
reticleRoot: string;
|
|
@@ -29,9 +29,14 @@ export const BackgroundSyncState = {
|
|
|
29
29
|
};
|
|
30
30
|
const OFF_VALUES = new Set(['0', 'false', 'off', 'no']);
|
|
31
31
|
const BACKGROUND_SYNC_SETTING = 'backgroundSync';
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The switch a person set to stop sending without being asked, or undefined when nobody set it.
|
|
34
|
+
*
|
|
35
|
+
* Every send that nobody asked for reads it, the daemon's and the one `start()` runs on close. The
|
|
36
|
+
* close cycle read nothing, so a person who switched it off still sent this project and every
|
|
37
|
+
* other linked one when `reticle drive` or a self-booting `verify` closed.
|
|
38
|
+
*/
|
|
39
|
+
export function backgroundSyncOptOut(reticleRoot, env) {
|
|
35
40
|
if (OFF_VALUES.has((env[ReticleEnv.BACKGROUND_SYNC] ?? '').toLowerCase())) {
|
|
36
41
|
return {
|
|
37
42
|
state: BackgroundSyncState.OPTED_OUT,
|
|
@@ -44,6 +49,22 @@ export function backgroundSyncGate(input) {
|
|
|
44
49
|
why: `off in this project ("${BACKGROUND_SYNC_SETTING}": false in .reticle.json)`,
|
|
45
50
|
};
|
|
46
51
|
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The gate of the one cycle `start()` sends on close, with nobody signed in. CI's key in the
|
|
56
|
+
* environment still sends there, as the docs promise. A switch that a person set still stops it.
|
|
57
|
+
*/
|
|
58
|
+
export function closeCycleGate(reticleRoot, env) {
|
|
59
|
+
return (backgroundSyncOptOut(reticleRoot, env) ?? { state: BackgroundSyncState.ON, why: 'on close' });
|
|
60
|
+
}
|
|
61
|
+
/** May the daemon send this root's work now? Reads the disk, never the network. */
|
|
62
|
+
export function backgroundSyncGate(input) {
|
|
63
|
+
const { reticleRoot, cloud, env, signedIn } = input;
|
|
64
|
+
const optedOut = backgroundSyncOptOut(reticleRoot, env);
|
|
65
|
+
if (optedOut !== undefined) {
|
|
66
|
+
return optedOut;
|
|
67
|
+
}
|
|
47
68
|
// `link` is set only from the project's own link file; an environment key leaves it unset.
|
|
48
69
|
if (cloud.link === undefined) {
|
|
49
70
|
return {
|