@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
|
@@ -49,6 +49,10 @@ export interface NetworkDetail {
|
|
|
49
49
|
* Omitted when the whole body fits, so the caveat means something when it appears.
|
|
50
50
|
*/
|
|
51
51
|
requestBodyTruncated?: boolean;
|
|
52
|
+
/** The response body, captured only where the page cannot (a server-sent download); capped. */
|
|
53
|
+
responseBody?: string;
|
|
54
|
+
/** True when `responseBody` was cut to the cap: a body check on the rest cannot be decided. */
|
|
55
|
+
responseBodyTruncated?: boolean;
|
|
52
56
|
/**
|
|
53
57
|
* The document that ISSUED the request, which is what identifies the session it belongs to.
|
|
54
58
|
*
|
|
@@ -73,6 +77,8 @@ export declare function buildNetworkDetail(raw: {
|
|
|
73
77
|
headers: Record<string, string>;
|
|
74
78
|
resourceType?: string;
|
|
75
79
|
requestBody?: string;
|
|
80
|
+
/** The response body, when the capture could read it (a server-sent download). */
|
|
81
|
+
responseBody?: string;
|
|
76
82
|
pageUrl?: string;
|
|
77
83
|
}, policy?: RedactionPolicy): NetworkDetail;
|
|
78
84
|
/** The minimal Playwright surfaces this attachment reads — kept structural so it's fake-testable. */
|
|
@@ -19,8 +19,9 @@ const DEFAULT_POLICY = { isSensitiveKey: defaultIsSensitiveKey };
|
|
|
19
19
|
* which is key-based over a parsed object — not string-shaped.
|
|
20
20
|
*/
|
|
21
21
|
function redactByKey(value, policy) {
|
|
22
|
-
if (Array.isArray(value))
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
23
|
return value.map((v) => redactByKey(v, policy));
|
|
24
|
+
}
|
|
24
25
|
if (value !== null && 'object' === typeof value) {
|
|
25
26
|
const out = {};
|
|
26
27
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -86,6 +87,10 @@ function projectHeaders(headers, policy) {
|
|
|
86
87
|
* about the function's inputs.
|
|
87
88
|
*/
|
|
88
89
|
export function buildNetworkDetail(raw, policy = DEFAULT_POLICY) {
|
|
90
|
+
// Capped and redacted exactly like the request body, and the cut is declared the same way.
|
|
91
|
+
const responseBody = raw.responseBody === undefined || 0 === raw.responseBody.length
|
|
92
|
+
? undefined
|
|
93
|
+
: projectWireBody(raw.responseBody, policy);
|
|
89
94
|
const wireBody = raw.requestBody === undefined || 0 === raw.requestBody.length
|
|
90
95
|
? undefined
|
|
91
96
|
: projectWireBody(raw.requestBody, policy);
|
|
@@ -101,6 +106,12 @@ export function buildNetworkDetail(raw, policy = DEFAULT_POLICY) {
|
|
|
101
106
|
requestBody: wireBody.body,
|
|
102
107
|
...(wireBody.truncated ? { requestBodyTruncated: true } : {}),
|
|
103
108
|
}),
|
|
109
|
+
...(responseBody === undefined
|
|
110
|
+
? {}
|
|
111
|
+
: {
|
|
112
|
+
responseBody: responseBody.body,
|
|
113
|
+
...(responseBody.truncated ? { responseBodyTruncated: true } : {}),
|
|
114
|
+
}),
|
|
104
115
|
...(raw.pageUrl === undefined || 0 === raw.pageUrl.length ? {} : { pageUrl: raw.pageUrl }),
|
|
105
116
|
};
|
|
106
117
|
}
|
|
@@ -8,6 +8,17 @@ function mockProvider(deps) {
|
|
|
8
8
|
const p = deps.realInput;
|
|
9
9
|
return p !== undefined && 'function' === typeof p.setMocks ? p : undefined;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether this call can mock the session: a lease of this caller, or a page the driven browser has.
|
|
13
|
+
* Having a driven browser is not enough: it mocks its own page, whatever session was named.
|
|
14
|
+
*/
|
|
15
|
+
async function canMock(deps, session) {
|
|
16
|
+
if (true === deps.pool?.heldBy(session.id, deps.attachId)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const driven = mockProvider(deps);
|
|
20
|
+
return driven !== undefined && driven.isAvailableFor(session.url);
|
|
21
|
+
}
|
|
11
22
|
const ruleShape = z.object({
|
|
12
23
|
urlContains: z
|
|
13
24
|
.string()
|
|
@@ -22,6 +33,11 @@ const ruleShape = z.object({
|
|
|
22
33
|
.boolean()
|
|
23
34
|
.optional()
|
|
24
35
|
.describe('Simulate a network failure (offline) instead of a response.'),
|
|
36
|
+
forwardTo: z
|
|
37
|
+
.string()
|
|
38
|
+
.url()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Send the matching request on to this origin, e.g. "http://127.0.0.1:15500"; the path and query stay and the real server answers.'),
|
|
25
41
|
});
|
|
26
42
|
/**
|
|
27
43
|
* Narrow the validated tool args into MockRule[], omitting undefined keys (exactOptionalPropertyTypes).
|
|
@@ -41,8 +57,9 @@ const ruleShape = z.object({
|
|
|
41
57
|
* to parse.
|
|
42
58
|
*/
|
|
43
59
|
export function toRules(value) {
|
|
44
|
-
if (value === undefined)
|
|
60
|
+
if (value === undefined) {
|
|
45
61
|
return [];
|
|
62
|
+
}
|
|
46
63
|
const parsed = z.array(ruleShape).safeParse(value);
|
|
47
64
|
if (!parsed.success) {
|
|
48
65
|
const problem = parsed.error.issues
|
|
@@ -50,23 +67,32 @@ export function toRules(value) {
|
|
|
50
67
|
.join('; ');
|
|
51
68
|
throw new Error(`reticle_network_mock could not read \`mocks\` (${problem}). Nothing was applied and mocking ` +
|
|
52
69
|
'was left unchanged — an unreadable rule is NOT treated as a request to clear. A rule is ' +
|
|
53
|
-
'{ urlContains, method?, status?, body?, contentType?, delayMs?, abort? }; pass `clear: true` ' +
|
|
70
|
+
'{ urlContains, method?, status?, body?, contentType?, delayMs?, abort?, forwardTo? }; pass `clear: true` ' +
|
|
54
71
|
'or an empty array if you meant to turn mocking off.');
|
|
55
72
|
}
|
|
56
73
|
return parsed.data.map((r) => {
|
|
57
74
|
const rule = { urlContains: r.urlContains };
|
|
58
|
-
if (r.method !== undefined)
|
|
75
|
+
if (r.method !== undefined) {
|
|
59
76
|
rule.method = r.method;
|
|
60
|
-
|
|
77
|
+
}
|
|
78
|
+
if (r.status !== undefined) {
|
|
61
79
|
rule.status = r.status;
|
|
62
|
-
|
|
80
|
+
}
|
|
81
|
+
if (r.body !== undefined) {
|
|
63
82
|
rule.body = r.body;
|
|
64
|
-
|
|
83
|
+
}
|
|
84
|
+
if (r.contentType !== undefined) {
|
|
65
85
|
rule.contentType = r.contentType;
|
|
66
|
-
|
|
86
|
+
}
|
|
87
|
+
if (r.delayMs !== undefined) {
|
|
67
88
|
rule.delayMs = r.delayMs;
|
|
68
|
-
|
|
89
|
+
}
|
|
90
|
+
if (r.abort !== undefined) {
|
|
69
91
|
rule.abort = r.abort;
|
|
92
|
+
}
|
|
93
|
+
if (r.forwardTo !== undefined) {
|
|
94
|
+
rule.forwardTo = r.forwardTo;
|
|
95
|
+
}
|
|
70
96
|
return rule;
|
|
71
97
|
});
|
|
72
98
|
}
|
|
@@ -95,7 +121,7 @@ export const NETWORK_MOCK_TOOLS = [
|
|
|
95
121
|
mocks: z
|
|
96
122
|
.array(ruleShape)
|
|
97
123
|
.optional()
|
|
98
|
-
.describe('Interception rules, first match wins: { urlContains, method?, status?, body?, contentType?, delayMs?, abort? }. Omit or pass [] to clear.'),
|
|
124
|
+
.describe('Interception rules, first match wins: { urlContains, method?, status?, body?, contentType?, delayMs?, abort?, forwardTo? }. Omit or pass [] to clear.'),
|
|
99
125
|
clear: z.boolean().optional().describe('Clear all active mocks (same as mocks: []).'),
|
|
100
126
|
...sessionIdShape,
|
|
101
127
|
},
|
|
@@ -107,20 +133,30 @@ export const NETWORK_MOCK_TOOLS = [
|
|
|
107
133
|
recommendation: z.string().optional(),
|
|
108
134
|
},
|
|
109
135
|
handler: async (deps, args) => {
|
|
110
|
-
const session = deps.sessions.
|
|
136
|
+
const session = await deps.sessions.resolveServing(asString(args['sessionId']), (s) => canMock(deps, s));
|
|
111
137
|
const rules = true === args['clear'] ? [] : toRules(args['mocks']);
|
|
112
|
-
|
|
138
|
+
// A lease is mocked as the lease, and the driven browser mocks only a page it has. The driven
|
|
139
|
+
// provider mocks its own page whatever session was named: taken first, it mocked another tab,
|
|
140
|
+
// answered `applied: true`, and the lease's real write reached the backend.
|
|
141
|
+
const driven = mockProvider(deps);
|
|
142
|
+
const provider = driven !== undefined &&
|
|
143
|
+
deps.pool?.leaseUrl(session.id) === undefined &&
|
|
144
|
+
(await driven.isAvailableFor(session.url))
|
|
145
|
+
? driven
|
|
146
|
+
: undefined;
|
|
113
147
|
if (provider !== undefined) {
|
|
114
148
|
const applied = await provider.setMocks(session.url, rules);
|
|
115
|
-
if (applied)
|
|
149
|
+
if (applied) {
|
|
116
150
|
return mocksApplied(deps, session.id, rules.length);
|
|
151
|
+
}
|
|
117
152
|
}
|
|
118
153
|
// A lease is a Playwright-owned page — CDP intercept was always there, this tool just had no
|
|
119
154
|
// route to it. Tried after the driven provider: when both exist, drive is the page the caller
|
|
120
155
|
// means, and a lease is the fallback rather than a competitor.
|
|
121
156
|
const leased = await deps.pool?.setMocksLease(session.id, rules);
|
|
122
|
-
if (true === leased)
|
|
157
|
+
if (true === leased) {
|
|
123
158
|
return mocksApplied(deps, session.id, rules.length);
|
|
159
|
+
}
|
|
124
160
|
return {
|
|
125
161
|
applied: false,
|
|
126
162
|
count: 0,
|
|
@@ -26,6 +26,12 @@ export interface MockRule {
|
|
|
26
26
|
delayMs?: number;
|
|
27
27
|
/** Simulate a network failure (offline / connection refused) instead of a response. */
|
|
28
28
|
abort?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Send the request on to this origin (protocol, host and port; the path and query stay). An app that
|
|
31
|
+
* calls a fixed address (`http://localhost:5000`) can then reach its API on any port, for example when
|
|
32
|
+
* the host already uses that port. The real server answers.
|
|
33
|
+
*/
|
|
34
|
+
forwardTo?: string;
|
|
29
35
|
}
|
|
30
36
|
/** What to do with a request after matching: fulfill a canned response, abort it, or let it through. */
|
|
31
37
|
interface MockOutcome {
|
|
@@ -34,6 +40,8 @@ interface MockOutcome {
|
|
|
34
40
|
body?: string;
|
|
35
41
|
contentType?: string;
|
|
36
42
|
delayMs?: number;
|
|
43
|
+
/** For `continue`: the url the request goes on with (a `forwardTo` rule). */
|
|
44
|
+
url?: string;
|
|
37
45
|
}
|
|
38
46
|
/**
|
|
39
47
|
* Decide the outcome for a request from the active rules. Pure: no IO, no clock. The first rule
|
|
@@ -6,12 +6,26 @@ const DEFAULT_CONTENT_TYPE = 'application/json';
|
|
|
6
6
|
*/
|
|
7
7
|
export function matchMock(rules, req) {
|
|
8
8
|
for (const rule of rules) {
|
|
9
|
-
if (!req.url.includes(rule.urlContains))
|
|
9
|
+
if (!req.url.includes(rule.urlContains)) {
|
|
10
10
|
continue;
|
|
11
|
-
|
|
11
|
+
}
|
|
12
|
+
if (rule.method !== undefined && rule.method.toUpperCase() !== req.method.toUpperCase()) {
|
|
12
13
|
continue;
|
|
13
|
-
|
|
14
|
+
}
|
|
15
|
+
if (true === rule.abort) {
|
|
14
16
|
return { kind: 'abort' };
|
|
17
|
+
}
|
|
18
|
+
if (rule.forwardTo !== undefined) {
|
|
19
|
+
const to = new URL(rule.forwardTo);
|
|
20
|
+
const next = new URL(req.url);
|
|
21
|
+
next.protocol = to.protocol;
|
|
22
|
+
next.host = to.host;
|
|
23
|
+
const outcome = { kind: 'continue', url: next.toString() };
|
|
24
|
+
if (rule.delayMs !== undefined) {
|
|
25
|
+
outcome.delayMs = rule.delayMs;
|
|
26
|
+
}
|
|
27
|
+
return outcome;
|
|
28
|
+
}
|
|
15
29
|
/*
|
|
16
30
|
* A rule that says only WHEN says nothing about WHAT.
|
|
17
31
|
*
|
|
@@ -35,10 +49,12 @@ export function matchMock(rules, req) {
|
|
|
35
49
|
status: rule.status ?? DEFAULT_STATUS,
|
|
36
50
|
contentType: rule.contentType ?? DEFAULT_CONTENT_TYPE,
|
|
37
51
|
};
|
|
38
|
-
if (rule.body !== undefined)
|
|
52
|
+
if (rule.body !== undefined) {
|
|
39
53
|
outcome.body = rule.body;
|
|
40
|
-
|
|
54
|
+
}
|
|
55
|
+
if (rule.delayMs !== undefined) {
|
|
41
56
|
outcome.delayMs = rule.delayMs;
|
|
57
|
+
}
|
|
42
58
|
return outcome;
|
|
43
59
|
}
|
|
44
60
|
return { kind: 'continue' };
|
|
@@ -47,18 +63,21 @@ export function matchMock(rules, req) {
|
|
|
47
63
|
export async function applyOutcome(route, outcome, sleep) {
|
|
48
64
|
if ('continue' === outcome.kind) {
|
|
49
65
|
// Sleep FIRST, then let it through: the delay is the whole perturbation, and the response that
|
|
50
|
-
// comes back is the server's own.
|
|
51
|
-
|
|
66
|
+
// comes back is the server's own. Fallback, not continue: a lease's context route (the CSP
|
|
67
|
+
// widening, #2016) must still see the request.
|
|
68
|
+
if (outcome.delayMs !== undefined && outcome.delayMs > 0) {
|
|
52
69
|
await sleep(outcome.delayMs);
|
|
53
|
-
|
|
70
|
+
}
|
|
71
|
+
await (outcome.url === undefined ? route.fallback() : route.fallback({ url: outcome.url }));
|
|
54
72
|
return;
|
|
55
73
|
}
|
|
56
74
|
if ('abort' === outcome.kind) {
|
|
57
75
|
await route.abort('failed');
|
|
58
76
|
return;
|
|
59
77
|
}
|
|
60
|
-
if (outcome.delayMs !== undefined && outcome.delayMs > 0)
|
|
78
|
+
if (outcome.delayMs !== undefined && outcome.delayMs > 0) {
|
|
61
79
|
await sleep(outcome.delayMs);
|
|
80
|
+
}
|
|
62
81
|
await route.fulfill({
|
|
63
82
|
status: outcome.status ?? DEFAULT_STATUS,
|
|
64
83
|
contentType: outcome.contentType ?? DEFAULT_CONTENT_TYPE,
|
|
@@ -73,8 +92,9 @@ const realSleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
|
73
92
|
*/
|
|
74
93
|
export async function installNetworkMocks(page, rules, sleep = realSleep) {
|
|
75
94
|
await page.unroute('**/*').catch(() => undefined);
|
|
76
|
-
if (0 === rules.length)
|
|
95
|
+
if (0 === rules.length) {
|
|
77
96
|
return;
|
|
97
|
+
}
|
|
78
98
|
await page.route('**/*', (route) => {
|
|
79
99
|
const req = route.request();
|
|
80
100
|
const outcome = matchMock(rules, { url: req.url(), method: req.method() });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A screenshot of a Playwright page, shared by the CDP and launched providers so the capture path
|
|
3
|
+
* lives in one place.
|
|
4
|
+
*/
|
|
5
|
+
import type { Page } from 'playwright';
|
|
6
|
+
import type { ElementBox } from './real-input.js';
|
|
7
|
+
/** Options for a page screenshot — full-page scroll capture and/or a clip box. */
|
|
8
|
+
export interface ScreenshotOpts {
|
|
9
|
+
fullPage?: boolean;
|
|
10
|
+
/** Restrict the capture to one element/region (viewport CSS px). */
|
|
11
|
+
clip?: ElementBox;
|
|
12
|
+
/**
|
|
13
|
+
* A live picture, as a JPEG at this quality: what a person is watching, taken as it is. The
|
|
14
|
+
* baseline tweaks (hiding the HUD, stopping animations) would make their window flicker each second.
|
|
15
|
+
*/
|
|
16
|
+
jpegQuality?: number;
|
|
17
|
+
/** One image pixel per CSS pixel, so a high-density display does not double a step screenshot. */
|
|
18
|
+
cssScale?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
|
|
22
|
+
* screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function capturePage(page: Page, opts: ScreenshotOpts): Promise<Uint8Array>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SCREENSHOT_DETERMINISM, captureWithChromeHidden, } from '../pool/playwright-launcher.js';
|
|
2
|
+
/**
|
|
3
|
+
* Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
|
|
4
|
+
* screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
|
|
5
|
+
*/
|
|
6
|
+
export async function capturePage(page, opts) {
|
|
7
|
+
// A step capture injects nothing into the page, so Reticle's own hosts are hidden another way.
|
|
8
|
+
if (true === opts.cssScale) {
|
|
9
|
+
return captureWithChromeHidden({ evaluate: (script) => page.evaluate(script) }, () => shoot(page, opts));
|
|
10
|
+
}
|
|
11
|
+
return shoot(page, opts);
|
|
12
|
+
}
|
|
13
|
+
/** Playwright screenshot options for one capture: a JPEG, a clip, the full page, or the viewport. */
|
|
14
|
+
function screenshotOptions(opts) {
|
|
15
|
+
if (opts.jpegQuality !== undefined) {
|
|
16
|
+
return {
|
|
17
|
+
type: 'jpeg',
|
|
18
|
+
quality: opts.jpegQuality,
|
|
19
|
+
...(true === opts.cssScale ? { scale: 'css' } : {}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (opts.clip !== undefined) {
|
|
23
|
+
return { ...SCREENSHOT_DETERMINISM, clip: opts.clip };
|
|
24
|
+
}
|
|
25
|
+
if (true === opts.fullPage) {
|
|
26
|
+
return { ...SCREENSHOT_DETERMINISM, fullPage: true };
|
|
27
|
+
}
|
|
28
|
+
return { ...SCREENSHOT_DETERMINISM };
|
|
29
|
+
}
|
|
30
|
+
async function shoot(page, opts) {
|
|
31
|
+
const buf = await page.screenshot(screenshotOptions(opts));
|
|
32
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
33
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { type ScriptCoverage } from './js-coverage.js';
|
|
13
13
|
import type { Browser, Page } from 'playwright';
|
|
14
14
|
import { DriveError } from './drive-error.js';
|
|
15
|
-
import { ActionType, InputMode } from '@reticlehq/core';
|
|
15
|
+
import { ActionType, InputMode, type BoxFraction } from '@reticlehq/core';
|
|
16
16
|
import { type MockRule } from './network-mock.js';
|
|
17
17
|
import { type NetworkDetail } from './network-detail.js';
|
|
18
18
|
/** Viewport CSS-px box as returned by the INSPECT command (getBoundingClientRect). */
|
|
@@ -49,8 +49,12 @@ export type RealInputArgs = {
|
|
|
49
49
|
* for exactly as long as one driven in the page.
|
|
50
50
|
*/
|
|
51
51
|
holdMs?: number;
|
|
52
|
-
/** For drag: the resolved box of the drop-target ref (toRef). */
|
|
52
|
+
/** For drag: the resolved box of the drop-target ref (toRef), or the source box for `to` alone. */
|
|
53
53
|
toBox?: ElementBox;
|
|
54
|
+
/** For a pointer action: the point inside the box, as fractions of it. Absent means the centre. */
|
|
55
|
+
at?: BoxFraction;
|
|
56
|
+
/** For drag: the end point inside `toBox`, as fractions of it. Absent means its centre. */
|
|
57
|
+
to?: BoxFraction;
|
|
54
58
|
steps?: number;
|
|
55
59
|
};
|
|
56
60
|
/** What a provider reports for one driven gesture. Exported so a fake can build it by name. */
|
|
@@ -80,19 +84,8 @@ export interface RealInputResult {
|
|
|
80
84
|
*/
|
|
81
85
|
heldMs?: number;
|
|
82
86
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
fullPage?: boolean;
|
|
86
|
-
/** Restrict the capture to one element/region (viewport CSS px). */
|
|
87
|
-
clip?: ElementBox;
|
|
88
|
-
/**
|
|
89
|
-
* A live picture, as a JPEG at this quality: what a person is watching, taken as it is. The
|
|
90
|
-
* baseline tweaks (hiding the HUD, stopping animations) would make their window flicker each second.
|
|
91
|
-
*/
|
|
92
|
-
jpegQuality?: number;
|
|
93
|
-
/** One image pixel per CSS pixel, so a high-density display does not double a step screenshot. */
|
|
94
|
-
cssScale?: boolean;
|
|
95
|
-
}
|
|
87
|
+
export { capturePage, type ScreenshotOpts } from './page-capture.js';
|
|
88
|
+
import { type ScreenshotOpts } from './page-capture.js';
|
|
96
89
|
/** The capability surface reticle_act depends on. A FAKE implementing this is injected in tests. */
|
|
97
90
|
export interface RealInputProvider {
|
|
98
91
|
/** Whether a Playwright Page currently matches this SDK session URL. */
|
|
@@ -120,6 +113,14 @@ export interface RealInputProvider {
|
|
|
120
113
|
*/
|
|
121
114
|
storageState?(sessionUrl: string): Promise<unknown>;
|
|
122
115
|
applyStorageState?(sessionUrl: string, state: unknown): Promise<boolean>;
|
|
116
|
+
/**
|
|
117
|
+
* Close every page of the driven context except the one this provider drives, and say how many.
|
|
118
|
+
*
|
|
119
|
+
* A flow that opens another tab (a modifier-click on a link) leaves it behind; its page connects
|
|
120
|
+
* as a second session, and everything after that asks for "the" session and is refused. Optional:
|
|
121
|
+
* only a provider that owns its context can know which pages are its own.
|
|
122
|
+
*/
|
|
123
|
+
closeOtherPages?(): Promise<number>;
|
|
123
124
|
/**
|
|
124
125
|
* Install (or replace, or with [] clear) network-mock rules on the correlated page — stub a 500,
|
|
125
126
|
* force offline, delay a response — for deterministic error/edge-state testing. Returns true when
|
|
@@ -162,6 +163,11 @@ export declare function boxCenter(box: ElementBox): {
|
|
|
162
163
|
cx: number;
|
|
163
164
|
cy: number;
|
|
164
165
|
};
|
|
166
|
+
/** The point `at` names inside a box, or its centre when `at` is absent. */
|
|
167
|
+
export declare function boxPoint(box: ElementBox, at: BoxFraction | undefined): {
|
|
168
|
+
cx: number;
|
|
169
|
+
cy: number;
|
|
170
|
+
};
|
|
165
171
|
/**
|
|
166
172
|
* Which actions are driven by a native pointer. `press` is excluded on purpose — a key has no
|
|
167
173
|
* pointer, and widening this would change what every existing caller of it means.
|
|
@@ -182,10 +188,15 @@ export declare function performGesture(page: Page, action: ActionType, box: Elem
|
|
|
182
188
|
/** Injected so a test can assert the achieved hold deterministically; defaults to the real clock. */
|
|
183
189
|
now?: () => number): Promise<RealInputResult>;
|
|
184
190
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
191
|
+
* Run a real pointer gesture with Reticle's own UI out of the hit test, and put it back after.
|
|
192
|
+
*
|
|
193
|
+
* A real pointer goes to the topmost element under it. When the HUD sat over the app's control, a
|
|
194
|
+
* driven click went to the HUD and the verdict said the app did nothing; a drag ended short (#992).
|
|
195
|
+
* The restore runs whatever the gesture did, so the HUD does not stay unclickable for a person.
|
|
187
196
|
*/
|
|
188
|
-
export declare function
|
|
197
|
+
export declare function withChromePassThrough<T>(page: {
|
|
198
|
+
evaluate: (script: string) => Promise<unknown>;
|
|
199
|
+
}, gesture: () => Promise<T>): Promise<T>;
|
|
189
200
|
interface CdpProviderOptions {
|
|
190
201
|
cdpUrl: string;
|
|
191
202
|
/** Injected so the settle delay is deterministic in tests; defaults to a real Node timer. */
|
|
@@ -262,6 +273,11 @@ export interface LaunchedProviderOptions {
|
|
|
262
273
|
* inside-app view never loses fidelity to the outside-in view. Omitted → no network detail captured.
|
|
263
274
|
*/
|
|
264
275
|
onNetworkDetail?: (detail: NetworkDetail) => void;
|
|
276
|
+
/**
|
|
277
|
+
* Reticle's own reader for a page with no SDK, as the lease pool gives one (see zero-install.ts).
|
|
278
|
+
* Undefined, or a loader that answers undefined, leaves such a page with no session.
|
|
279
|
+
*/
|
|
280
|
+
zeroInstallScript?: () => string | undefined;
|
|
265
281
|
}
|
|
266
282
|
/** The only place the dynamic value import of Playwright lives for the launched (drive) path. */
|
|
267
283
|
export declare const launchedChromium: LaunchFn;
|
|
@@ -282,6 +298,7 @@ export declare class LaunchedRealInputProvider implements OwnedRealInputProvider
|
|
|
282
298
|
* driven session had no fixture port at all, and every suite seeded nothing while reading as if it
|
|
283
299
|
* seeded everything. Same two methods, same contract, one page instead of a page lookup.
|
|
284
300
|
*/
|
|
301
|
+
closeOtherPages(): Promise<number>;
|
|
285
302
|
storageState(_sessionUrl: string): Promise<unknown>;
|
|
286
303
|
/**
|
|
287
304
|
* Put a captured state back: cookies through the context, per-origin storage through the page.
|
|
@@ -14,20 +14,34 @@ import { DriveError } from './drive-error.js';
|
|
|
14
14
|
import { pressViaKeyboard } from './real-input-press.js';
|
|
15
15
|
import { stampedDriveUrl } from './drive-url-stamp.js';
|
|
16
16
|
import { launchChromium } from '../../launch-chromium.js';
|
|
17
|
-
import {
|
|
17
|
+
import { pageViewportOptions } from '../../chromium-launch-options.js';
|
|
18
|
+
import { chromiumLaunchHint, gotoOptions } from '../pool/playwright-launcher.js';
|
|
18
19
|
import { BrowserLaunchKind } from '@reticlehq/core/telemetry';
|
|
19
20
|
import { getSessionMetrics } from '../../telemetry/session-metrics.js';
|
|
20
21
|
import { classifyConnectFailure } from '../../telemetry/connect-failure.js';
|
|
21
|
-
import { ActionType, DriveErrorCode, DRIVE_PLAYWRIGHT_MISSING_MSG, ClickButton, clickButtonFromArgs, InputMode, pressModifiersFromArgs, } from '@reticlehq/core';
|
|
22
|
+
import { ActionType, DriveErrorCode, DRIVE_PLAYWRIGHT_MISSING_MSG, ClickButton, clickButtonFromArgs, InputMode, pointInBox, pressModifiersFromArgs, RETICLE_CHROME_PASS_THROUGH_SCRIPT, RETICLE_CHROME_RESTORE_POINTER_SCRIPT, } from '@reticlehq/core';
|
|
22
23
|
import { installNetworkMocks } from './network-mock.js';
|
|
23
24
|
import { attachNetworkDetail } from './network-detail.js';
|
|
25
|
+
import { attachDownloadCapture } from './download-capture.js';
|
|
24
26
|
import { injectedConnectArgs } from '../pool/zero-install.js';
|
|
27
|
+
import { EARLY_CAPTURE_ARG, queueEarlyConsole } from '../pool/lease-page-error.js';
|
|
28
|
+
// The screenshot capture lives in page-capture.ts; re-exported for existing importers.
|
|
29
|
+
export { capturePage } from './page-capture.js';
|
|
30
|
+
import { capturePage } from './page-capture.js';
|
|
25
31
|
export { DriveError };
|
|
26
32
|
export { unspellablePressReason } from './real-input-press.js';
|
|
27
33
|
/** Center of a viewport box in CSS px. Pure — unit-tested directly. */
|
|
28
34
|
export function boxCenter(box) {
|
|
29
35
|
return { cx: box.x + box.width / 2, cy: box.y + box.height / 2 };
|
|
30
36
|
}
|
|
37
|
+
/** The point `at` names inside a box, or its centre when `at` is absent. */
|
|
38
|
+
export function boxPoint(box, at) {
|
|
39
|
+
if (at === undefined) {
|
|
40
|
+
return boxCenter(box);
|
|
41
|
+
}
|
|
42
|
+
const p = pointInBox(box, at);
|
|
43
|
+
return { cx: p.x, cy: p.y };
|
|
44
|
+
}
|
|
31
45
|
/**
|
|
32
46
|
* Which actions are driven by a native pointer. `press` is excluded on purpose — a key has no
|
|
33
47
|
* pointer, and widening this would change what every existing caller of it means.
|
|
@@ -85,7 +99,28 @@ now = Date.now) {
|
|
|
85
99
|
...(heldMs > 0 ? { heldMs } : {}),
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
|
-
|
|
102
|
+
return withChromePassThrough(page, () => pointerGesture(page, action, box, args, sleep));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run a real pointer gesture with Reticle's own UI out of the hit test, and put it back after.
|
|
106
|
+
*
|
|
107
|
+
* A real pointer goes to the topmost element under it. When the HUD sat over the app's control, a
|
|
108
|
+
* driven click went to the HUD and the verdict said the app did nothing; a drag ended short (#992).
|
|
109
|
+
* The restore runs whatever the gesture did, so the HUD does not stay unclickable for a person.
|
|
110
|
+
*/
|
|
111
|
+
export async function withChromePassThrough(page, gesture) {
|
|
112
|
+
await page.evaluate(RETICLE_CHROME_PASS_THROUGH_SCRIPT);
|
|
113
|
+
try {
|
|
114
|
+
return await gesture();
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
// A click that navigated takes the old page and its held values with it.
|
|
118
|
+
await page.evaluate(RETICLE_CHROME_RESTORE_POINTER_SCRIPT).catch(() => undefined);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** The pointer half of `performGesture`: every action that lands at a point. */
|
|
122
|
+
async function pointerGesture(page, action, box, args, sleep) {
|
|
123
|
+
const center = boxPoint(box, args.at);
|
|
89
124
|
const { cx, cy } = center;
|
|
90
125
|
const real = (performed) => ({
|
|
91
126
|
performed,
|
|
@@ -125,7 +160,7 @@ now = Date.now) {
|
|
|
125
160
|
if (args.toBox === undefined) {
|
|
126
161
|
return real(false);
|
|
127
162
|
}
|
|
128
|
-
const dst =
|
|
163
|
+
const dst = boxPoint(args.toBox, args.to);
|
|
129
164
|
const steps = args.steps ?? DEFAULT_DRAG_STEPS;
|
|
130
165
|
await page.mouse.move(cx, cy);
|
|
131
166
|
await page.mouse.down();
|
|
@@ -144,31 +179,6 @@ now = Date.now) {
|
|
|
144
179
|
}
|
|
145
180
|
return real(false);
|
|
146
181
|
}
|
|
147
|
-
/**
|
|
148
|
-
* Capture a PNG from a Playwright page. Shared by the CDP + launched providers so the
|
|
149
|
-
* screenshot path lives in one place (mirrors performGesture). Returns the raw PNG bytes.
|
|
150
|
-
*/
|
|
151
|
-
export async function capturePage(page, opts) {
|
|
152
|
-
// A step capture injects nothing into the page, so Reticle's own hosts are hidden another way.
|
|
153
|
-
if (true === opts.cssScale) {
|
|
154
|
-
return captureWithChromeHidden({ evaluate: (script) => page.evaluate(script) }, () => shoot(page, opts));
|
|
155
|
-
}
|
|
156
|
-
return shoot(page, opts);
|
|
157
|
-
}
|
|
158
|
-
async function shoot(page, opts) {
|
|
159
|
-
const buf = await page.screenshot(opts.jpegQuality !== undefined
|
|
160
|
-
? {
|
|
161
|
-
type: 'jpeg',
|
|
162
|
-
quality: opts.jpegQuality,
|
|
163
|
-
...(true === opts.cssScale ? { scale: 'css' } : {}),
|
|
164
|
-
}
|
|
165
|
-
: opts.clip !== undefined
|
|
166
|
-
? { ...SCREENSHOT_DETERMINISM, clip: opts.clip }
|
|
167
|
-
: true === opts.fullPage
|
|
168
|
-
? { ...SCREENSHOT_DETERMINISM, fullPage: true }
|
|
169
|
-
: { ...SCREENSHOT_DETERMINISM });
|
|
170
|
-
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
171
|
-
}
|
|
172
182
|
const nodeSleep = (ms) => new Promise((resolve) => {
|
|
173
183
|
setTimeout(resolve, ms);
|
|
174
184
|
});
|
|
@@ -352,6 +362,8 @@ export class CdpRealInputProvider {
|
|
|
352
362
|
}
|
|
353
363
|
}
|
|
354
364
|
const INJECT_CONNECT_WAIT_MS = 8_000;
|
|
365
|
+
/** True on a page whose Reticle SDK has loaded. */
|
|
366
|
+
const SDK_PRESENT = '!!globalThis.__reticleInstance';
|
|
355
367
|
/** The only place the dynamic value import of Playwright lives for the launched (drive) path. */
|
|
356
368
|
export const launchedChromium = async (headless) => {
|
|
357
369
|
let mod;
|
|
@@ -386,6 +398,7 @@ export class LaunchedRealInputProvider {
|
|
|
386
398
|
#injectConnect;
|
|
387
399
|
#storageState;
|
|
388
400
|
#onNetworkDetail;
|
|
401
|
+
#zeroInstallScript;
|
|
389
402
|
#browser;
|
|
390
403
|
#page;
|
|
391
404
|
/** Pages collecting V8 coverage — see js-coverage.ts. */
|
|
@@ -399,14 +412,28 @@ export class LaunchedRealInputProvider {
|
|
|
399
412
|
this.#injectConnect = options.injectConnect;
|
|
400
413
|
this.#storageState = options.storageState;
|
|
401
414
|
this.#onNetworkDetail = options.onNetworkDetail;
|
|
415
|
+
this.#zeroInstallScript = options.zeroInstallScript;
|
|
402
416
|
}
|
|
403
417
|
async navigate() {
|
|
404
418
|
this.#browser = await this.#launch(this.#headless);
|
|
405
|
-
const page = await this.#browser.newPage(
|
|
419
|
+
const page = await this.#browser.newPage({
|
|
420
|
+
...pageViewportOptions(this.#headless),
|
|
421
|
+
...(this.#storageState !== undefined ? { storageState: this.#storageState } : {}),
|
|
422
|
+
});
|
|
406
423
|
this.#page = page;
|
|
424
|
+
// Before goto: a console error the page logs while it loads comes before its SDK connects.
|
|
425
|
+
await page.addInitScript(queueEarlyConsole, EARLY_CAPTURE_ARG);
|
|
407
426
|
// Capture CDP-authoritative response detail into the driven session's journal (best-effort).
|
|
408
|
-
|
|
409
|
-
|
|
427
|
+
const sink = this.#onNetworkDetail;
|
|
428
|
+
if (sink !== undefined) {
|
|
429
|
+
attachNetworkDetail(page, sink);
|
|
430
|
+
// A server-sent download never reaches the page's network events; CDP request interception
|
|
431
|
+
// does see it (download-capture.ts). Attached before the first navigation, best-effort.
|
|
432
|
+
await page
|
|
433
|
+
.context()
|
|
434
|
+
.newCDPSession(page)
|
|
435
|
+
.then((cdp) => attachDownloadCapture(cdp, sink, () => page.url()))
|
|
436
|
+
.catch(() => undefined);
|
|
410
437
|
}
|
|
411
438
|
try {
|
|
412
439
|
// Same navigation rule as the pool, and for the same measured reason: Playwright's default
|
|
@@ -420,8 +447,35 @@ export class LaunchedRealInputProvider {
|
|
|
420
447
|
catch (e) {
|
|
421
448
|
throw new DriveError(DriveErrorCode.NAVIGATE_FAILED, e instanceof Error ? e.message : String(e));
|
|
422
449
|
}
|
|
450
|
+
await this.#supplyReader(page);
|
|
423
451
|
await this.#tryInjectConnect(page);
|
|
424
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Give a page with no Reticle SDK the zero-install reader, so `verify` on an app with no `init`
|
|
455
|
+
* still gets a session. The wait for the page's own SDK comes first, as on a leased tab, and the
|
|
456
|
+
* script checks the singleton, so a page with an SDK never gets a second one.
|
|
457
|
+
*/
|
|
458
|
+
async #supplyReader(page) {
|
|
459
|
+
const script = this.#zeroInstallScript?.();
|
|
460
|
+
if (script === undefined) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
await page.waitForFunction(SDK_PRESENT, { timeout: INJECT_CONNECT_WAIT_MS });
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
// No SDK on the page: supply the reader below.
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
// Also on every later document, so a full navigation keeps the session.
|
|
472
|
+
await page.addInitScript(script);
|
|
473
|
+
await page.evaluate(script);
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
// A page that refuses the reader has no session. verify's no-session guard reports it.
|
|
477
|
+
}
|
|
478
|
+
}
|
|
425
479
|
/**
|
|
426
480
|
* Wait for the page's Reticle singleton to exist, then re-invoke connect with our token + loopback
|
|
427
481
|
* URL so a hosted (non-localhost) preview pairs to our bridge without the app being reconfigured.
|
|
@@ -433,7 +487,7 @@ export class LaunchedRealInputProvider {
|
|
|
433
487
|
return;
|
|
434
488
|
}
|
|
435
489
|
try {
|
|
436
|
-
await page.waitForFunction(
|
|
490
|
+
await page.waitForFunction(SDK_PRESENT, {
|
|
437
491
|
timeout: INJECT_CONNECT_WAIT_MS,
|
|
438
492
|
});
|
|
439
493
|
const arg = JSON.stringify(injectedConnectArgs(opts));
|
|
@@ -482,6 +536,18 @@ export class LaunchedRealInputProvider {
|
|
|
482
536
|
* driven session had no fixture port at all, and every suite seeded nothing while reading as if it
|
|
483
537
|
* seeded everything. Same two methods, same contract, one page instead of a page lookup.
|
|
484
538
|
*/
|
|
539
|
+
async closeOtherPages() {
|
|
540
|
+
const page = this.#livePage();
|
|
541
|
+
if (page === undefined) {
|
|
542
|
+
return 0;
|
|
543
|
+
}
|
|
544
|
+
const others = page
|
|
545
|
+
.context()
|
|
546
|
+
.pages()
|
|
547
|
+
.filter((other) => other !== page && !other.isClosed());
|
|
548
|
+
await Promise.all(others.map((other) => other.close()));
|
|
549
|
+
return others.length;
|
|
550
|
+
}
|
|
485
551
|
async storageState(_sessionUrl) {
|
|
486
552
|
const page = this.#livePage();
|
|
487
553
|
if (page === undefined) {
|