@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
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as production from '../../portal/production/production-activation.js';
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
-
import { leaseNotConnectedHint, leaseReaderFailedHint, leaseStillLoadingHint, } from './lease-hint.js';
|
|
15
|
-
import { connectOrInject, moveReusedLease, notReadyReason, probeLeaseAlive, } from './lease-readiness.js';
|
|
14
|
+
import { keptLeaseAnswer, notLoadedCause, leaseNotConnectedHint, leaseReaderFailedHint, leaseStillLoadingHint, } from './lease-hint.js';
|
|
15
|
+
import { LEASE_READY_TIMEOUT_MS, awaitLeaseAnswering, connectOrInject, connectWaitUntil, moveReusedLease, notReadyReason, probeLeaseAlive, reconnectHeldLease, HeldLeaseReload, } from './lease-readiness.js';
|
|
16
16
|
import { probeSdkMarker } from './gaps/sdk-marker-probe.js';
|
|
17
17
|
import { observeWebDocument } from '../../portal/session/dev-server/served-document.js';
|
|
18
18
|
import { diagnoseObservedWebCsp } from '@reticlehq/init';
|
|
@@ -21,14 +21,17 @@ import { hasAnyAppConnectedBefore } from '../../memory/recall/prior/connection-m
|
|
|
21
21
|
import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../../portal/session/lease-visibility.js';
|
|
22
22
|
import { reticleStateHome } from '../../command/daemon/daemon.js';
|
|
23
23
|
import { claimArtifactRoot, dropArtifactRootClaim } from '../../memory/project/root-claims.js';
|
|
24
|
-
import { LeaseNotReadyReason,
|
|
24
|
+
import { LeaseNotReadyReason, RETICLE_DEFAULT_PORT, SeedStorageSchema, originOf, } from '@reticlehq/core';
|
|
25
25
|
import { HudVisibility, ReticleTool, SESSION_HEALTH } from '@reticlehq/core';
|
|
26
26
|
import { HOST_STUB_NOTE } from './tool-kit.js';
|
|
27
27
|
import { PoolAtCapacityError } from '../../portal/pool/browser-pool.js';
|
|
28
|
-
import { asString } from '@reticlehq/core';
|
|
28
|
+
import { asNumber, asString } from '@reticlehq/core';
|
|
29
|
+
import { timeoutMsSchema } from './args/numeric-bounds.js';
|
|
29
30
|
import { chromiumPreflightRefusal } from '../../command/cli/doctor/browser/chromium-hint.js';
|
|
30
31
|
import { LEASE_PERMISSIONS_ARG, hintOf, notificationReadBack, parseLeasePermissions, permissionRefusal, refuseRegrant, } from './lease-permissions.js';
|
|
31
32
|
import { LEASE_EMULATE_ARG, leaseEmulationOf } from '../../portal/input/viewport-tools.js';
|
|
33
|
+
import { LEASE_DETERMINISTIC_ARG, leaseDeterminismOf } from '../../portal/pool/determinism.js';
|
|
34
|
+
import { LEASE_COVERAGE_TOOL, LEASE_JS_COVERAGE_ARG } from './lease-coverage-tools.js';
|
|
32
35
|
export { sessionMutationPort, sessionPerturbationPort, suiteFixturePort, suiteFixtureSeed, } from './session-ports.js';
|
|
33
36
|
/**
|
|
34
37
|
* Everything the daemon already knows about why a leased tab might not have dialled in.
|
|
@@ -86,38 +89,17 @@ async function leaseEvidence(deps, port, url) {
|
|
|
86
89
|
}
|
|
87
90
|
const POOL_UNAVAILABLE = 'browser pool unavailable — the lease tools need the daemon-managed pool (start Reticle via `reticle mcp`).';
|
|
88
91
|
const ALREADY_HELD_LEASE = 'you already hold a lease on this origin';
|
|
92
|
+
const DETERMINISTIC_NOTE = 'Time stands still on this tab. Step it with reticle_clock { stepFrames: N }, then read or capture.';
|
|
89
93
|
function alreadyHeldHint(sessionId, origin) {
|
|
90
94
|
return (`${ALREADY_HELD_LEASE} (${origin}): ${sessionId} — reused it rather than opening a second ` +
|
|
91
95
|
'tab, which would make every later call require sessionId. It is still signed in as whoever ' +
|
|
92
96
|
'used it last (cookies and storage come with it): release it first for a fresh context or ' +
|
|
93
|
-
'another identity.');
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
|
|
97
|
-
* adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
|
|
98
|
-
*/
|
|
99
|
-
export function appendReticleParams(url, session, projectId, hud) {
|
|
100
|
-
try {
|
|
101
|
-
const u = new URL(url);
|
|
102
|
-
u.searchParams.set(RETICLE_URL_PARAM.SESSION, session);
|
|
103
|
-
if (projectId !== undefined && projectId.length > 0) {
|
|
104
|
-
u.searchParams.set(RETICLE_URL_PARAM.PROJECT, projectId);
|
|
105
|
-
}
|
|
106
|
-
if (hud !== undefined) {
|
|
107
|
-
u.searchParams.set(RETICLE_URL_PARAM.HUD, hud);
|
|
108
|
-
}
|
|
109
|
-
return u.toString();
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
const sep = url.includes('?') ? '&' : '?';
|
|
113
|
-
const proj = projectId !== undefined && projectId.length > 0
|
|
114
|
-
? `&${RETICLE_URL_PARAM.PROJECT}=${encodeURIComponent(projectId)}`
|
|
115
|
-
: '';
|
|
116
|
-
return `${url}${sep}${RETICLE_URL_PARAM.SESSION}=${encodeURIComponent(session)}${proj}`;
|
|
117
|
-
}
|
|
97
|
+
'another identity, or pass fresh: true to open a separate tab and keep this one.');
|
|
118
98
|
}
|
|
99
|
+
export { appendReticleParams, carryReticleIdentity, resolveLeasedSessionId, } from './lease-url-identity.js';
|
|
100
|
+
import { appendReticleParams, carryReticleIdentity, resolveLeasedSessionId, } from './lease-url-identity.js';
|
|
119
101
|
export { cleanNavError, evaluateSeedPrecondition, scrubSeedFromError } from './lease-seed.js';
|
|
120
|
-
import { evaluateSeedPrecondition, looksLikeStorageStateExport, navFailureMessage, scrubSeedFromError, } from './lease-seed.js';
|
|
102
|
+
import { evaluateSeedPrecondition, looksLikeStorageStateExport, navFailureMessage, scrubSeedFromError, serverAcceptedAfterTimeout, } from './lease-seed.js';
|
|
121
103
|
/** A fresh, collision-resistant lease id. Uses crypto at the I/O boundary (not pure logic). */
|
|
122
104
|
function newLeaseId() {
|
|
123
105
|
const uuid = 'function' === typeof globalThis.crypto?.randomUUID
|
|
@@ -125,84 +107,19 @@ function newLeaseId() {
|
|
|
125
107
|
: `${String(Date.now())}-${String(performance.now())}`;
|
|
126
108
|
return `lease-${uuid}`;
|
|
127
109
|
}
|
|
128
|
-
/**
|
|
129
|
-
* The session a lease's tab actually registered as, or undefined while nothing has.
|
|
130
|
-
*
|
|
131
|
-
* Normally that is the lease id: `appendReticleParams` stamps `__reticle_session` on the URL and the
|
|
132
|
-
* SDK adopts it. An app that passes an explicit `session` to `connect()` keeps its own name instead,
|
|
133
|
-
* which is legitimate — a single-app fixture does it deliberately so a battery can address it by a
|
|
134
|
-
* known id. Matching on the lease id alone could never see those tabs, so the lease reported
|
|
135
|
-
* `ready: false` and a hint blaming a port mismatch while the session was connected and driveable.
|
|
136
|
-
*
|
|
137
|
-
* The URL marker is the evidence, and it is what makes this safe: two concurrent leases on one origin
|
|
138
|
-
* carry different markers, so neither can adopt the other's tab. Parsed rather than substring-matched,
|
|
139
|
-
* so `lease-abc` cannot claim `lease-abcdef`.
|
|
140
|
-
*/
|
|
141
|
-
export function resolveLeasedSessionId(sessions, leaseId) {
|
|
142
|
-
if (sessions.get(leaseId) !== undefined) {
|
|
143
|
-
return leaseId;
|
|
144
|
-
}
|
|
145
|
-
return sessions.all().find((s) => sessionParamOf(s.url) === leaseId)?.id;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Carry a claimed identity across a navigation.
|
|
149
|
-
*
|
|
150
|
-
* A LEASED tab is addressed by `__reticle_session` in its URL: that param is how the pool finds the
|
|
151
|
-
* context it opened, and — since succession was tightened — it is also the ONLY thing that lets the
|
|
152
|
-
* reconnecting document inherit the lease. `reticle_navigate` sent the caller's raw URL, so the very
|
|
153
|
-
* first navigation stripped the marker, the tab re-announced as an ordinary anonymous session, and
|
|
154
|
-
* the lease was orphaned: "no sessions are connected at all", with a browser window still open on
|
|
155
|
-
* screen. A leased browser that cannot survive being navigated is not a usable one.
|
|
156
|
-
*
|
|
157
|
-
* A tab that claims nothing is left exactly as it was — this must never bolt Reticle's params onto
|
|
158
|
-
* a human's own URL.
|
|
159
|
-
*/
|
|
160
|
-
export function carryReticleIdentity(fromUrl, toUrl) {
|
|
161
|
-
const session = sessionParamOf(fromUrl);
|
|
162
|
-
if (session === undefined) {
|
|
163
|
-
return toUrl;
|
|
164
|
-
}
|
|
165
|
-
// Already addressed (a caller passing the stamped URL back) — appending would be a no-op at best.
|
|
166
|
-
if (sessionParamOf(toUrl) !== undefined) {
|
|
167
|
-
return toUrl;
|
|
168
|
-
}
|
|
169
|
-
const projectId = projectParamOf(fromUrl);
|
|
170
|
-
return appendReticleParams(toUrl, session, projectId);
|
|
171
|
-
}
|
|
172
|
-
function projectParamOf(url) {
|
|
173
|
-
if (url === undefined) {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
try {
|
|
177
|
-
return new URL(url).searchParams.get(RETICLE_URL_PARAM.PROJECT) ?? undefined;
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function sessionParamOf(url) {
|
|
184
|
-
if (url === undefined) {
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
return new URL(url).searchParams.get(RETICLE_URL_PARAM.SESSION) ?? undefined;
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
110
|
/**
|
|
195
111
|
* Acquire a leased, SDK-connected context for a caller that is not the lease TOOL (the parallel suite).
|
|
196
112
|
* Returns the session id plus its release; the caller must release in a finally so a crashed flow never
|
|
197
113
|
* leaks a pool slot and starves the rest of the suite.
|
|
198
114
|
*/
|
|
199
|
-
export async function acquireLeasedSession(pool, sessions, url, projectId, seedStorage) {
|
|
115
|
+
export async function acquireLeasedSession(pool, sessions, url, projectId, seedStorage, ignoreHTTPSErrors) {
|
|
200
116
|
const sessionId = newLeaseId();
|
|
201
117
|
let lease;
|
|
202
118
|
try {
|
|
203
119
|
lease = await pool.acquire(appendReticleParams(url, sessionId, projectId), {
|
|
204
120
|
sessionId,
|
|
205
121
|
...(seedStorage !== undefined ? { seedStorage } : {}),
|
|
122
|
+
...(true === ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
|
|
206
123
|
});
|
|
207
124
|
}
|
|
208
125
|
catch (err) {
|
|
@@ -248,7 +165,7 @@ export function hasOriginLock(origin) {
|
|
|
248
165
|
}
|
|
249
166
|
export const LEASE_ACQUIRE_TOOL = {
|
|
250
167
|
name: ReticleTool.LEASE_ACQUIRE,
|
|
251
|
-
description: 'Lease a fresh isolated browser context from the shared pool (shown or hidden as the daemon was started; `headed: true` forces a window) and navigate it to the app URL (the app must already be running; one with no Reticle SDK gets one supplied, reported as zeroInstall). If this origin is already leased
|
|
168
|
+
description: 'Lease a fresh isolated browser context from the shared pool (shown or hidden as the daemon was started; `headed: true` forces a window) and navigate it to the app URL (the app must already be running; one with no Reticle SDK gets one supplied, reported as zeroInstall). If this origin is already leased, this returns THAT session rather than minting a second tab (a tab that has not connected is loaded again in its own context, so its cookies and sign-in are kept) — a second acquire on the same origin poisons default session resolution. Returns the sessionId the leased tab registers — pass it to other tools. The pool keeps all leases in ONE browser and caps concurrency; if every slot is taken this is refused at once, naming the cap, rather than queued. Release with reticle_lease{action:"release"} when the flow is done. PREFER AN ALREADY-OPEN TAB: if reticle_sessions lists a non-leased session for this app, drive THAT instead — a lease is invisible to the person watching the app, whose HUD lives in their own tab, and a tab flagged hidden/throttled is often still driveable. Lease for isolation you actually need (a second identity, a clean context, parallel flows) or when driving the open tab has failed — this call answers with `preferExisting` when a live tab was available.',
|
|
252
169
|
inputSchema: {
|
|
253
170
|
url: z
|
|
254
171
|
.string()
|
|
@@ -259,6 +176,10 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
259
176
|
.optional()
|
|
260
177
|
.describe('Stable project id to stamp on the leased tab so the agent can scope to it.'),
|
|
261
178
|
seedStorage: SeedStorageSchema.optional().describe('Initial storage state (local, session and cookies) to seed before the first navigation (e.g. `seedStorage: { local: { token: "..." } }` to start already authenticated).'),
|
|
179
|
+
fresh: z
|
|
180
|
+
.boolean()
|
|
181
|
+
.optional()
|
|
182
|
+
.describe('Open a separate tab even when this connection already holds a lease on the origin, and keep that one. For parallel work that shares one MCP connection (sub-agents). Pass the returned sessionId to every later call.'),
|
|
262
183
|
initScript: z
|
|
263
184
|
.string()
|
|
264
185
|
.optional()
|
|
@@ -277,26 +198,39 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
277
198
|
.describe('How the HUD starts on the page: shown (default), hidden, or removed.'),
|
|
278
199
|
permissions: LEASE_PERMISSIONS_ARG,
|
|
279
200
|
emulate: LEASE_EMULATE_ARG,
|
|
201
|
+
deterministic: LEASE_DETERMINISTIC_ARG,
|
|
202
|
+
jsCoverage: LEASE_JS_COVERAGE_ARG,
|
|
280
203
|
production: production.PRODUCTION_LEASE_ARG,
|
|
204
|
+
timeout_ms: timeoutMsSchema
|
|
205
|
+
.optional()
|
|
206
|
+
.describe(`How long to wait for the leased tab to connect and answer before answering ready:false with notReadyReason. Default ${String(LEASE_READY_TIMEOUT_MS)}. 0 looks once without waiting.`),
|
|
281
207
|
root: z
|
|
282
208
|
.string()
|
|
283
209
|
.optional()
|
|
284
|
-
.describe("Absolute path of the .reticle folder this lease's runs belong in — the caller's project.
|
|
210
|
+
.describe("Absolute path of the .reticle folder this lease's runs belong in — the caller's project. An existing project folder is accepted even when it has no .reticle yet (the first run writes it). Omitted, the page's project id decides, and a page with none lands in ~/.reticle/unmatched, which nothing syncs."),
|
|
285
211
|
},
|
|
286
212
|
outputSchema: {
|
|
287
213
|
sessionId: z.string(),
|
|
288
214
|
url: z.string(),
|
|
289
215
|
ready: z
|
|
290
216
|
.boolean()
|
|
291
|
-
.describe('Whether the leased tab is connected AND
|
|
217
|
+
.describe('Whether the leased tab is connected AND answered a probe under sessionId, so the next call finds it. acquire waits for this, up to timeout_ms. It is NOT a claim that the app has hydrated, so an act can miss: make the first act reticle_act_and_wait with an `until`, or reticle_assert { action: "wait" }. Empty reads may mean not hydrated. False ⇒ read notReadyReason.'),
|
|
292
218
|
zeroInstall: z
|
|
293
219
|
.boolean()
|
|
294
220
|
.optional()
|
|
295
221
|
.describe('Present when this app ships no Reticle SDK and the lease supplied one. Verdicts work on the DOM, network, console and routes; there is no framework adapter, so no component state and no source file:line. Install with `npx @reticlehq/server init` for those.'),
|
|
222
|
+
deterministic: z
|
|
223
|
+
.string()
|
|
224
|
+
.optional()
|
|
225
|
+
.describe('Present when this tab runs a seeded random source and a stepped clock. Its time moves only on reticle_clock { stepFrames }, so a wait for a timer or an animation never ends until you step.'),
|
|
296
226
|
hostStub: z
|
|
297
227
|
.string()
|
|
298
228
|
.optional()
|
|
299
229
|
.describe('Present when this leased tab runs an init script stubbing the bindings a desktop shell injects. Verdicts on this lease are about the app with that stub, not about the real shell.'),
|
|
230
|
+
cspWidenedFor: z
|
|
231
|
+
.string()
|
|
232
|
+
.optional()
|
|
233
|
+
.describe("Present when the lease added this origin (the daemon's WebSocket) to the page CSP connect-src, so the zero-install reader can connect. The rest of the page CSP applies as the server sent it."),
|
|
300
234
|
notReadyReason: z
|
|
301
235
|
.enum([
|
|
302
236
|
LeaseNotReadyReason.SDK_NEVER_DIALLED,
|
|
@@ -359,7 +293,11 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
359
293
|
const headed = true === args['headed'];
|
|
360
294
|
const hud = Object.values(HudVisibility).find((v) => v === args['hud']);
|
|
361
295
|
const permissions = production.leasePermissions(parseLeasePermissions(args['permissions']), args['production']);
|
|
362
|
-
|
|
296
|
+
// Determinism is set on the context, like the emulation, so it travels with it.
|
|
297
|
+
const deterministic = leaseDeterminismOf(args['deterministic']);
|
|
298
|
+
const emulated = leaseEmulationOf(args['emulate']);
|
|
299
|
+
const emulate = deterministic === undefined ? emulated : { ...(emulated ?? {}), deterministic };
|
|
300
|
+
const jsCoverage = true === args['jsCoverage'];
|
|
363
301
|
// The stub of the shell bindings, when the caller supplied one. Installed by the pool before
|
|
364
302
|
// the first navigation, so it is already defined when the app's first module runs.
|
|
365
303
|
const initScript = asString(args['initScript']);
|
|
@@ -408,14 +346,17 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
408
346
|
// an init script has to exist before the page's own scripts run, which a loaded page cannot
|
|
409
347
|
// do, and the resolve below then finds nothing and mints a fresh context with the stub. A
|
|
410
348
|
// lease of the other kind is left alone, exactly as it is when a window was asked for.
|
|
411
|
-
|
|
349
|
+
// `fresh` asks for a separate tab: the held one is neither reused nor released (#1932).
|
|
350
|
+
const fresh = true === args['fresh'];
|
|
351
|
+
const heldOn = (o) => fresh || o === undefined ? undefined : pool.leaseIdOnOrigin(o, deps.attachId);
|
|
352
|
+
const heldForStub = heldOn(origin);
|
|
412
353
|
if (initScript !== undefined &&
|
|
413
354
|
heldForStub !== undefined &&
|
|
414
355
|
pool.isHeaded(heldForStub) === headed &&
|
|
415
356
|
pool.hostStubScript?.(heldForStub) !== initScript) {
|
|
416
357
|
await pool.release(heldForStub);
|
|
417
358
|
}
|
|
418
|
-
const held =
|
|
359
|
+
const held = heldOn(origin);
|
|
419
360
|
const existing = held !== undefined && pool.isHeaded(held) === headed ? held : undefined;
|
|
420
361
|
// A context's certificate setting is fixed when it is made, so an explicit request for the
|
|
421
362
|
// other one cannot be answered by the held lease: handing it back would keep checks off after
|
|
@@ -428,6 +369,7 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
428
369
|
if ((validatedSeed !== undefined ||
|
|
429
370
|
certMismatch ||
|
|
430
371
|
emulate !== undefined ||
|
|
372
|
+
jsCoverage ||
|
|
431
373
|
activated !== undefined) &&
|
|
432
374
|
existing !== undefined) {
|
|
433
375
|
// Seeding or a different certificate setting asked for: release its own context so the new
|
|
@@ -442,6 +384,9 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
442
384
|
// context — the tab-poisoning this branch exists to prevent, reached through the branch
|
|
443
385
|
// itself. It bites when the first acquire returned `ready: false`, since the mint path only
|
|
444
386
|
// aliases once its wait has resolved, so nothing recorded the app's own name (#692).
|
|
387
|
+
// A tab that never registered is loaded again in its OWN context, and kept while it is open:
|
|
388
|
+
// releasing it and minting threw away the cookies, so a signed-in tab came back signed out (#1924).
|
|
389
|
+
const reload = await reconnectHeldLease((ms) => pool.gotoLease(existing, carryReticleIdentity(pool.leaseUrl(existing), url), ms), async () => (await pool.viewportOfLease(existing)) !== undefined, () => pool.leaseLoading(existing), () => resolveLeasedSessionId(deps.sessions, existing) !== undefined, deps.now() + (asNumber(args['timeout_ms']) ?? LEASE_READY_TIMEOUT_MS), deps.now);
|
|
445
390
|
const resolved = resolveLeasedSessionId(deps.sessions, existing);
|
|
446
391
|
if (resolved !== undefined) {
|
|
447
392
|
// The other name this lease answers to, told to the pool exactly as the mint path tells it.
|
|
@@ -475,14 +420,20 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
475
420
|
// Asked of the pool, not remembered here: the stub was installed when THIS lease was
|
|
476
421
|
// minted, and the caller reusing it now may be seeing it for the first time.
|
|
477
422
|
...(true === pool.stubInstalled?.(existing) ? { hostStub: HOST_STUB_NOTE } : {}),
|
|
423
|
+
...cspWidenedOf(pool, existing),
|
|
478
424
|
...(alreadyOpen === undefined
|
|
479
425
|
? {}
|
|
480
426
|
: { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
|
|
481
427
|
};
|
|
482
428
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
429
|
+
if (reload === HeldLeaseReload.RELOADED || reload === HeldLeaseReload.NOT_LOADED) {
|
|
430
|
+
const cause = reload === HeldLeaseReload.RELOADED
|
|
431
|
+
? await notConnectedHint(deps, url, pool.dialFailureUrl?.(existing))
|
|
432
|
+
: await notLoadedCause(url, deps.portAccepts);
|
|
433
|
+
return keptLeaseAnswer({ sessionId: existing, origin, url, reload }, pool, cause);
|
|
434
|
+
}
|
|
435
|
+
// The tab is gone: its page or its context was closed. Free the slot and mint, rather than
|
|
436
|
+
// handing back a dead id or leaving both contexts occupied.
|
|
486
437
|
await pool.release(existing);
|
|
487
438
|
}
|
|
488
439
|
const sessionId = newLeaseId();
|
|
@@ -505,6 +456,7 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
505
456
|
...(permissions !== undefined ? { permissions } : {}),
|
|
506
457
|
...(true === args['ignoreHTTPSErrors'] ? { ignoreHTTPSErrors: true } : {}),
|
|
507
458
|
...(emulate === undefined ? {} : { emulate }),
|
|
459
|
+
...(jsCoverage ? { jsCoverage } : {}),
|
|
508
460
|
});
|
|
509
461
|
}
|
|
510
462
|
catch (err) {
|
|
@@ -519,19 +471,27 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
519
471
|
throw new Error(scrubSeedFromError(err.message, validatedSeed));
|
|
520
472
|
}
|
|
521
473
|
// A raw page.goto failure is noisy and leaks the internal URL params — surface a clean,
|
|
522
|
-
// actionable message instead, one that tells a slow first compile from a missing app.
|
|
523
|
-
|
|
474
|
+
// actionable message instead, one that tells a slow first compile from a missing app. A
|
|
475
|
+
// timeout on a port that accepted the connection is a server still compiling (#2005).
|
|
476
|
+
const accepted = await serverAcceptedAfterTimeout(url, err, validatedSeed, deps.portAccepts);
|
|
477
|
+
throw new Error(navFailureMessage(url, err, validatedSeed, accepted));
|
|
524
478
|
}
|
|
525
479
|
// Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
|
|
526
480
|
// Resolved rather than assumed: an app that names its own session registers under that name,
|
|
527
481
|
// and the id we hand back has to be the one the agent can actually drive.
|
|
528
482
|
let registeredId;
|
|
529
|
-
const
|
|
530
|
-
const waited = await production.guardedForProduction(pool, lease, activated);
|
|
531
|
-
const { ready, zeroInstall, stillLoading, readerFailure } = await connectOrInject(waited, () => {
|
|
483
|
+
const isRegistered = () => {
|
|
532
484
|
registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
|
|
533
485
|
return registeredId !== undefined;
|
|
534
|
-
}
|
|
486
|
+
};
|
|
487
|
+
const waitStartedAt = deps.now();
|
|
488
|
+
const deadline = waitStartedAt + (asNumber(args['timeout_ms']) ?? LEASE_READY_TIMEOUT_MS);
|
|
489
|
+
const waited = await production.guardedForProduction(pool, lease, activated);
|
|
490
|
+
const { ready: connected, zeroInstall, stillLoading, readerFailure, } = await connectOrInject(waited, isRegistered, connectWaitUntil(deadline, deps.now));
|
|
491
|
+
// Connected is a row in the sessions map. Ready is a reply from it, so the next call works.
|
|
492
|
+
const answerReason = connected
|
|
493
|
+
? await awaitLeaseAnswering(() => (isRegistered() ? deps.sessions.get(registeredId ?? lease.sessionId) : undefined), deadline, deps.now)
|
|
494
|
+
: undefined;
|
|
535
495
|
// Tell the pool the other name this lease answers to. Every later touch and release arrives
|
|
536
496
|
// under the id we are about to hand back, and the pool is keyed by the id it navigated with;
|
|
537
497
|
// without this the touches miss, the lease ages out despite continuous activity, and the
|
|
@@ -541,6 +501,7 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
541
501
|
}
|
|
542
502
|
const effectiveSessionId = registeredId ?? lease.sessionId;
|
|
543
503
|
const session = deps.sessions.get(effectiveSessionId);
|
|
504
|
+
const ready = connected && answerReason === undefined;
|
|
544
505
|
if (validatedSeed !== undefined && session !== undefined) {
|
|
545
506
|
const failureReason = evaluateSeedPrecondition(url, session.url, validatedSeed, lease.navStatus);
|
|
546
507
|
if (failureReason !== undefined) {
|
|
@@ -552,18 +513,17 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
552
513
|
if (readerFailure === undefined) {
|
|
553
514
|
tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
|
|
554
515
|
}
|
|
555
|
-
// ready means the SDK
|
|
516
|
+
// ready means the SDK answers — not that contracts match. Carry the skew warning on acquire
|
|
556
517
|
// so the agent does not learn it only after a CDP tool invents a closed page (#688).
|
|
557
|
-
const mintReason = readerFailure?.reason ??
|
|
558
|
-
notReadyReason(ready, session, LeaseNotReadyReason.SDK_NEVER_DIALLED);
|
|
518
|
+
const mintReason = readerFailure?.reason ?? (connected ? answerReason : LeaseNotReadyReason.SDK_NEVER_DIALLED);
|
|
559
519
|
const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
|
|
560
520
|
let notReadyHint;
|
|
561
|
-
if (!
|
|
521
|
+
if (!connected) {
|
|
562
522
|
if (readerFailure !== undefined) {
|
|
563
523
|
notReadyHint = leaseReaderFailedHint(url, readerFailure);
|
|
564
524
|
}
|
|
565
525
|
else if (true === stillLoading) {
|
|
566
|
-
notReadyHint = leaseStillLoadingHint(url,
|
|
526
|
+
notReadyHint = leaseStillLoadingHint(url, deps.now() - waitStartedAt);
|
|
567
527
|
}
|
|
568
528
|
else if (activated !== undefined) {
|
|
569
529
|
notReadyHint = production.notActivatedHint(url);
|
|
@@ -596,6 +556,8 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
596
556
|
...(versionSkew === undefined ? {} : { versionSkew }),
|
|
597
557
|
...production.summary(activated),
|
|
598
558
|
...(initScript === undefined ? {} : { hostStub: HOST_STUB_NOTE }),
|
|
559
|
+
...(deterministic === undefined ? {} : { deterministic: DETERMINISTIC_NOTE }),
|
|
560
|
+
...cspWidenedOf(pool, lease.sessionId),
|
|
599
561
|
...hintOf(notReadyHint, ready ? await notificationReadBack(pool, lease.sessionId, permissions) : undefined),
|
|
600
562
|
};
|
|
601
563
|
}
|
|
@@ -607,6 +569,11 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
607
569
|
}
|
|
608
570
|
},
|
|
609
571
|
};
|
|
572
|
+
/** `cspWidenedFor` when the lease changed its page CSP: a change to the page is declared (#2016). */
|
|
573
|
+
function cspWidenedOf(pool, leaseId) {
|
|
574
|
+
const origin = pool.cspWidenedFor?.(leaseId);
|
|
575
|
+
return origin === undefined ? {} : { cspWidenedFor: origin };
|
|
576
|
+
}
|
|
610
577
|
/** The whole not-connected diagnosis, gathered and worded. Split out so the acquire path stays flat. */
|
|
611
578
|
async function notConnectedHint(deps, url, dialledUrl) {
|
|
612
579
|
const bridgePort = deps.bridgePort ?? RETICLE_DEFAULT_PORT;
|
|
@@ -618,11 +585,12 @@ async function notConnectedHint(deps, url, dialledUrl) {
|
|
|
618
585
|
}
|
|
619
586
|
const LEASE_RELEASE_TOOL = {
|
|
620
587
|
name: ReticleTool.LEASE_RELEASE,
|
|
621
|
-
description: 'Release a leased browser context by sessionId, closing it and freeing the pool slot for a queued acquire. Call this when a flow finishes so the pool stays within its concurrency cap.',
|
|
588
|
+
description: 'Release a leased browser context by sessionId, closing it and freeing the pool slot for a queued acquire. Call this when a flow finishes so the pool stays within its concurrency cap. With no sessionId, it releases the one lease this agent holds.',
|
|
622
589
|
inputSchema: {
|
|
623
590
|
sessionId: z
|
|
624
591
|
.string()
|
|
625
|
-
.
|
|
592
|
+
.optional()
|
|
593
|
+
.describe('The leased sessionId returned by reticle_lease{action:"acquire"}. Omit it when this agent holds one lease.'),
|
|
626
594
|
},
|
|
627
595
|
outputSchema: {
|
|
628
596
|
released: z.boolean(),
|
|
@@ -633,10 +601,7 @@ const LEASE_RELEASE_TOOL = {
|
|
|
633
601
|
if (pool === undefined) {
|
|
634
602
|
throw new Error(POOL_UNAVAILABLE);
|
|
635
603
|
}
|
|
636
|
-
const sessionId = asString(args['sessionId']);
|
|
637
|
-
if (sessionId === undefined || 0 === sessionId.length) {
|
|
638
|
-
throw new Error('reticle_lease{action:"release"} requires a sessionId');
|
|
639
|
-
}
|
|
604
|
+
const sessionId = asString(args['sessionId']) ?? ownOnlyLease(deps, pool);
|
|
640
605
|
// An id that holds no lease (a typo, or one that expired) answered `released: true`.
|
|
641
606
|
if (pool.leaseUrl(sessionId) === undefined) {
|
|
642
607
|
const held = pool.leasedSessionIds();
|
|
@@ -646,6 +611,21 @@ const LEASE_RELEASE_TOOL = {
|
|
|
646
611
|
return { released: true, leased: await releaseLease(deps, pool, sessionId) };
|
|
647
612
|
},
|
|
648
613
|
};
|
|
614
|
+
/**
|
|
615
|
+
* The one lease this caller holds, for a release with no sessionId. Another agent's lease is never
|
|
616
|
+
* this caller's to release (#1226), so none or several is a refusal that names what is held.
|
|
617
|
+
*/
|
|
618
|
+
function ownOnlyLease(deps, pool) {
|
|
619
|
+
const own = pool.leasedSessionIds().filter((id) => pool.heldBy(id, deps.attachId));
|
|
620
|
+
const [only] = own;
|
|
621
|
+
if (only !== undefined && 1 === own.length) {
|
|
622
|
+
return only;
|
|
623
|
+
}
|
|
624
|
+
const held = 0 === own.length
|
|
625
|
+
? 'this agent holds no lease'
|
|
626
|
+
: `this agent holds ${String(own.length)} leases: ${own.join(', ')}`;
|
|
627
|
+
throw new Error(`reticle_lease{action:"release"} needs a sessionId: ${held}.`);
|
|
628
|
+
}
|
|
649
629
|
/**
|
|
650
630
|
* Release one lease and everything that hangs off it. Returns how many leases are still held.
|
|
651
631
|
*
|
|
@@ -736,4 +716,4 @@ function tellWatchers(deps, projectId, text) {
|
|
|
736
716
|
// A HUD that missed one line is a smaller problem than a lease reported as broken.
|
|
737
717
|
}
|
|
738
718
|
}
|
|
739
|
-
export const LEASE_TOOLS = [LEASE_ACQUIRE_TOOL, LEASE_RELEASE_TOOL];
|
|
719
|
+
export const LEASE_TOOLS = [LEASE_ACQUIRE_TOOL, LEASE_RELEASE_TOOL, LEASE_COVERAGE_TOOL];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reticle's identity params on a lease URL: stamp them, read them back, carry them across a
|
|
3
|
+
* navigation, and match a registered session to the lease that stamped it.
|
|
4
|
+
*
|
|
5
|
+
* Split out of lease-tools.ts at the 1000-line cap. Pure: no I/O.
|
|
6
|
+
*/
|
|
7
|
+
import { type HudVisibility } from '@reticlehq/core';
|
|
8
|
+
/**
|
|
9
|
+
* Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
|
|
10
|
+
* adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function appendReticleParams(url: string, session: string, projectId?: string, hud?: HudVisibility): string;
|
|
13
|
+
/**
|
|
14
|
+
* The session a lease's tab actually registered as, or undefined while nothing has.
|
|
15
|
+
*
|
|
16
|
+
* Normally that is the lease id: `appendReticleParams` stamps `__reticle_session` on the URL and the
|
|
17
|
+
* SDK adopts it. An app that passes an explicit `session` to `connect()` keeps its own name instead,
|
|
18
|
+
* which is legitimate — a single-app fixture does it deliberately so a battery can address it by a
|
|
19
|
+
* known id. Matching on the lease id alone could never see those tabs, so the lease reported
|
|
20
|
+
* `ready: false` and a hint blaming a port mismatch while the session was connected and driveable.
|
|
21
|
+
*
|
|
22
|
+
* The URL marker is the evidence, and it is what makes this safe: two concurrent leases on one origin
|
|
23
|
+
* carry different markers, so neither can adopt the other's tab. Parsed rather than substring-matched,
|
|
24
|
+
* so `lease-abc` cannot claim `lease-abcdef`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveLeasedSessionId(sessions: {
|
|
27
|
+
get: (id: string) => unknown;
|
|
28
|
+
all: () => {
|
|
29
|
+
id: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
}[];
|
|
32
|
+
}, leaseId: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Carry a claimed identity across a navigation.
|
|
35
|
+
*
|
|
36
|
+
* A LEASED tab is addressed by `__reticle_session` in its URL: that param is how the pool finds the
|
|
37
|
+
* context it opened, and — since succession was tightened — it is also the ONLY thing that lets the
|
|
38
|
+
* reconnecting document inherit the lease. `reticle_navigate` sent the caller's raw URL, so the very
|
|
39
|
+
* first navigation stripped the marker, the tab re-announced as an ordinary anonymous session, and
|
|
40
|
+
* the lease was orphaned: "no sessions are connected at all", with a browser window still open on
|
|
41
|
+
* screen. A leased browser that cannot survive being navigated is not a usable one.
|
|
42
|
+
*
|
|
43
|
+
* A tab that claims nothing is left exactly as it was — this must never bolt Reticle's params onto
|
|
44
|
+
* a human's own URL.
|
|
45
|
+
*/
|
|
46
|
+
export declare function carryReticleIdentity(fromUrl: string | undefined, toUrl: string): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reticle's identity params on a lease URL: stamp them, read them back, carry them across a
|
|
3
|
+
* navigation, and match a registered session to the lease that stamped it.
|
|
4
|
+
*
|
|
5
|
+
* Split out of lease-tools.ts at the 1000-line cap. Pure: no I/O.
|
|
6
|
+
*/
|
|
7
|
+
import { RETICLE_URL_PARAM } from '@reticlehq/core';
|
|
8
|
+
/**
|
|
9
|
+
* Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
|
|
10
|
+
* adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
|
|
11
|
+
*/
|
|
12
|
+
export function appendReticleParams(url, session, projectId, hud) {
|
|
13
|
+
try {
|
|
14
|
+
const u = new URL(url);
|
|
15
|
+
u.searchParams.set(RETICLE_URL_PARAM.SESSION, session);
|
|
16
|
+
if (projectId !== undefined && projectId.length > 0) {
|
|
17
|
+
u.searchParams.set(RETICLE_URL_PARAM.PROJECT, projectId);
|
|
18
|
+
}
|
|
19
|
+
if (hud !== undefined) {
|
|
20
|
+
u.searchParams.set(RETICLE_URL_PARAM.HUD, hud);
|
|
21
|
+
}
|
|
22
|
+
return u.toString();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
const sep = url.includes('?') ? '&' : '?';
|
|
26
|
+
const proj = projectId !== undefined && projectId.length > 0
|
|
27
|
+
? `&${RETICLE_URL_PARAM.PROJECT}=${encodeURIComponent(projectId)}`
|
|
28
|
+
: '';
|
|
29
|
+
return `${url}${sep}${RETICLE_URL_PARAM.SESSION}=${encodeURIComponent(session)}${proj}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The session a lease's tab actually registered as, or undefined while nothing has.
|
|
34
|
+
*
|
|
35
|
+
* Normally that is the lease id: `appendReticleParams` stamps `__reticle_session` on the URL and the
|
|
36
|
+
* SDK adopts it. An app that passes an explicit `session` to `connect()` keeps its own name instead,
|
|
37
|
+
* which is legitimate — a single-app fixture does it deliberately so a battery can address it by a
|
|
38
|
+
* known id. Matching on the lease id alone could never see those tabs, so the lease reported
|
|
39
|
+
* `ready: false` and a hint blaming a port mismatch while the session was connected and driveable.
|
|
40
|
+
*
|
|
41
|
+
* The URL marker is the evidence, and it is what makes this safe: two concurrent leases on one origin
|
|
42
|
+
* carry different markers, so neither can adopt the other's tab. Parsed rather than substring-matched,
|
|
43
|
+
* so `lease-abc` cannot claim `lease-abcdef`.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveLeasedSessionId(sessions, leaseId) {
|
|
46
|
+
if (sessions.get(leaseId) !== undefined) {
|
|
47
|
+
return leaseId;
|
|
48
|
+
}
|
|
49
|
+
return sessions.all().find((s) => sessionParamOf(s.url) === leaseId)?.id;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Carry a claimed identity across a navigation.
|
|
53
|
+
*
|
|
54
|
+
* A LEASED tab is addressed by `__reticle_session` in its URL: that param is how the pool finds the
|
|
55
|
+
* context it opened, and — since succession was tightened — it is also the ONLY thing that lets the
|
|
56
|
+
* reconnecting document inherit the lease. `reticle_navigate` sent the caller's raw URL, so the very
|
|
57
|
+
* first navigation stripped the marker, the tab re-announced as an ordinary anonymous session, and
|
|
58
|
+
* the lease was orphaned: "no sessions are connected at all", with a browser window still open on
|
|
59
|
+
* screen. A leased browser that cannot survive being navigated is not a usable one.
|
|
60
|
+
*
|
|
61
|
+
* A tab that claims nothing is left exactly as it was — this must never bolt Reticle's params onto
|
|
62
|
+
* a human's own URL.
|
|
63
|
+
*/
|
|
64
|
+
export function carryReticleIdentity(fromUrl, toUrl) {
|
|
65
|
+
const session = sessionParamOf(fromUrl);
|
|
66
|
+
if (session === undefined) {
|
|
67
|
+
return toUrl;
|
|
68
|
+
}
|
|
69
|
+
// Already addressed (a caller passing the stamped URL back) — appending would be a no-op at best.
|
|
70
|
+
if (sessionParamOf(toUrl) !== undefined) {
|
|
71
|
+
return toUrl;
|
|
72
|
+
}
|
|
73
|
+
const projectId = projectParamOf(fromUrl);
|
|
74
|
+
return appendReticleParams(toUrl, session, projectId);
|
|
75
|
+
}
|
|
76
|
+
function projectParamOf(url) {
|
|
77
|
+
if (url === undefined) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return new URL(url).searchParams.get(RETICLE_URL_PARAM.PROJECT) ?? undefined;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function sessionParamOf(url) {
|
|
88
|
+
if (url === undefined) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return new URL(url).searchParams.get(RETICLE_URL_PARAM.SESSION) ?? undefined;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|