@reticlehq/server 3.8.2 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/SKILL.md +13 -12
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -160
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +46 -5
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -24
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +47 -4
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +22 -17
- package/dist/command/cli/cli-parse.js +54 -32
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +61 -31
- package/dist/command/cli/cli-verify.d.ts +17 -3
- package/dist/command/cli/cli-verify.js +59 -21
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +11 -0
- package/dist/command/cli/ports/port-holder.js +36 -4
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +2 -0
- package/dist/command/cli/status/status-lines.js +72 -17
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +24 -6
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +112 -70
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +22 -4
- package/dist/command/daemon/daemon.js +52 -10
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +149 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +11 -15
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +63 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +36 -15
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +14 -3
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +67 -99
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -7
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +51 -5
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
- package/dist/language/flows/recording/tape/recordings.js +112 -49
- package/dist/language/flows/replay.d.ts +36 -12
- package/dist/language/flows/replay.js +193 -50
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +30 -10
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +35 -18
- package/dist/portal/input/real-input.js +99 -33
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +81 -21
- package/dist/portal/pool/browser-pool.d.ts +16 -30
- package/dist/portal/pool/browser-pool.js +64 -52
- package/dist/portal/pool/context-permissions.d.ts +34 -17
- package/dist/portal/pool/context-permissions.js +94 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +56 -0
- package/dist/portal/pool/lease-page-error.js +160 -0
- package/dist/portal/pool/page-error-journal.d.ts +63 -0
- package/dist/portal/pool/page-error-journal.js +208 -0
- package/dist/portal/pool/playwright-launcher.js +71 -15
- package/dist/portal/pool/pool-contract.d.ts +49 -1
- package/dist/portal/pool/pool-lease.d.ts +70 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +91 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
- package/dist/portal/session/dev-server/dev-command.js +110 -16
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +76 -27
- package/dist/portal/session/no-session-next-action.d.ts +14 -0
- package/dist/portal/session/no-session-next-action.js +56 -7
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +41 -4
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +211 -81
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +30 -2
- package/dist/start-wiring.js +74 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +8 -5
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +38 -18
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +10 -0
- package/dist/surface/tools/act/after-match.js +40 -0
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +17 -1
- package/dist/surface/tools/act/resolve-target.js +59 -9
- package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
- package/dist/surface/tools/act/sequence-grade.js +45 -27
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +81 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +83 -95
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +18 -12
- package/dist/surface/tools/browser-tools.js +87 -13
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +11 -3
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +131 -40
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +29 -21
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +42 -1
- package/dist/surface/tools/lease-hint.js +52 -3
- package/dist/surface/tools/lease-readiness.d.ts +47 -0
- package/dist/surface/tools/lease-readiness.js +126 -0
- package/dist/surface/tools/lease-seed.d.ts +16 -1
- package/dist/surface/tools/lease-seed.js +37 -4
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +104 -124
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +18 -0
- package/dist/surface/tools/tool-kit.js +9 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +54 -18
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +17 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +15 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/architecture.md +3 -6
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +13 -12
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +34 -24
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +17 -22
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +6 -4
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +18 -14
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { VisualReason } from '@reticlehq/core';
|
|
2
|
+
import type { ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ask the desktop shell to photograph its own window.
|
|
5
|
+
*
|
|
6
|
+
* Returns undefined for a plain web page (no capture helper) and for a desktop app that did not
|
|
7
|
+
* install `@reticlehq/electron/main`. Deliberately NOT a screen-region capture: photographing
|
|
8
|
+
* the glass returns whatever window is on top, which would save a picture of the user's editor as a
|
|
9
|
+
* visual baseline — the exact false green Reticle exists to eliminate.
|
|
10
|
+
*/
|
|
11
|
+
export declare function desktopCapture(deps: ToolDeps, sessionId: string | undefined, fullPage: boolean): Promise<{
|
|
12
|
+
png?: Uint8Array;
|
|
13
|
+
reason?: VisualReason;
|
|
14
|
+
recommendation?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
18
|
+
* become a file-read oracle for whatever a compromised renderer names.
|
|
19
|
+
*
|
|
20
|
+
* Two layouts are accepted, and the pair is the whole subtlety:
|
|
21
|
+
*
|
|
22
|
+
* - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
|
|
23
|
+
* so no other local user can read the screenshot or pre-place a symlink at the path we write.
|
|
24
|
+
* - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
|
|
25
|
+
* SEPARATE package on the user's own upgrade schedule: an app still on the older
|
|
26
|
+
* `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
|
|
27
|
+
* start returning no image. It grants the daemon nothing it did not already have.
|
|
28
|
+
*
|
|
29
|
+
* Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
|
|
30
|
+
* `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
|
|
31
|
+
* `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isCapturePath(path: string): boolean;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The desktop shell's photograph of its own window, read from the file the shell wrote.
|
|
3
|
+
*
|
|
4
|
+
* Shared by `reticle_screenshot`, the drive frames and the step shots. The step shots use it only
|
|
5
|
+
* for a page SDK before 3.7, which has no `render` command and declares `capture` instead.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, unlink } from 'node:fs/promises';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { basename, dirname } from 'node:path';
|
|
10
|
+
import { asRecord, asString, ReticleCommand, RETICLE_CAPTURE_FILE_PREFIX, RETICLE_TAURI_INERT_MARKER, TAURI_INERT_RECOMMENDATION, VisualReason, } from '@reticlehq/core';
|
|
11
|
+
import { trackCaptureDirectory } from './capture-cleanup.js';
|
|
12
|
+
/**
|
|
13
|
+
* Ask the desktop shell to photograph its own window.
|
|
14
|
+
*
|
|
15
|
+
* Returns undefined for a plain web page (no capture helper) and for a desktop app that did not
|
|
16
|
+
* install `@reticlehq/electron/main`. Deliberately NOT a screen-region capture: photographing
|
|
17
|
+
* the glass returns whatever window is on top, which would save a picture of the user's editor as a
|
|
18
|
+
* visual baseline — the exact false green Reticle exists to eliminate.
|
|
19
|
+
*/
|
|
20
|
+
export async function desktopCapture(deps, sessionId, fullPage) {
|
|
21
|
+
const session = deps.sessions.resolve(sessionId);
|
|
22
|
+
// A session that cannot take commands (no live browser behind it) simply has no pixels to give.
|
|
23
|
+
if (typeof session.command !== 'function') {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
const res = await session.command(ReticleCommand.CAPTURE, { fullPage });
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const r = asRecord(res.result);
|
|
31
|
+
if (r['ok'] !== true) {
|
|
32
|
+
// The shell refused a full-page capture it cannot do. That is a different answer from "capture
|
|
33
|
+
// broke", and the caller has to hear it — otherwise they read `fullPage` as honoured.
|
|
34
|
+
if (r['reason'] === VisualReason.FULL_PAGE_UNSUPPORTED) {
|
|
35
|
+
return { reason: VisualReason.FULL_PAGE_UNSUPPORTED };
|
|
36
|
+
}
|
|
37
|
+
// A Tauri release build with the capture compiled out: say that, never the web answer.
|
|
38
|
+
return true === asString(r['reason'])?.includes(RETICLE_TAURI_INERT_MARKER)
|
|
39
|
+
? { reason: VisualReason.NO_PROVIDER, recommendation: TAURI_INERT_RECOMMENDATION }
|
|
40
|
+
: {};
|
|
41
|
+
}
|
|
42
|
+
const path = asString(r['path']);
|
|
43
|
+
if (path === undefined || !isCapturePath(path)) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const bytes = await readFile(path);
|
|
48
|
+
await unlink(path).catch(() => undefined);
|
|
49
|
+
trackCaptureDirectory(path);
|
|
50
|
+
return isCompletePng(bytes) ? { png: new Uint8Array(bytes) } : {};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
58
|
+
* become a file-read oracle for whatever a compromised renderer names.
|
|
59
|
+
*
|
|
60
|
+
* Two layouts are accepted, and the pair is the whole subtlety:
|
|
61
|
+
*
|
|
62
|
+
* - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
|
|
63
|
+
* so no other local user can read the screenshot or pre-place a symlink at the path we write.
|
|
64
|
+
* - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
|
|
65
|
+
* SEPARATE package on the user's own upgrade schedule: an app still on the older
|
|
66
|
+
* `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
|
|
67
|
+
* start returning no image. It grants the daemon nothing it did not already have.
|
|
68
|
+
*
|
|
69
|
+
* Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
|
|
70
|
+
* `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
|
|
71
|
+
* `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
|
|
72
|
+
*/
|
|
73
|
+
export function isCapturePath(path) {
|
|
74
|
+
if (!basename(path).startsWith(RETICLE_CAPTURE_FILE_PREFIX)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const parent = dirname(path);
|
|
78
|
+
if (parent === tmpdir()) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return dirname(parent) === tmpdir() && basename(parent).startsWith(RETICLE_CAPTURE_FILE_PREFIX);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A PNG is complete only if it ends with IEND. This check exists because a truncated image once
|
|
85
|
+
* sailed through as a saved baseline: the tool answered `saved: true` with a path, and the file was
|
|
86
|
+
* undecodable. A screenshot that cannot be read must fail loudly, never bank a corrupt baseline that
|
|
87
|
+
* a later visual_diff will compare against.
|
|
88
|
+
*/
|
|
89
|
+
function isCompletePng(bytes) {
|
|
90
|
+
if (bytes.byteLength < 12) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return 'IEND' === bytes.subarray(bytes.byteLength - 8, bytes.byteLength - 4).toString('ascii');
|
|
94
|
+
}
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* nothing at all, the next one is awaited, so the agent is told `{ error }` instead of a path to a
|
|
15
15
|
* file that will never exist.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* A page SDK before 3.7 has no `render`. When such a page declares the desktop shell's `capture`,
|
|
18
|
+
* that command is asked instead. Its answer is a device-pixel PNG, and the daemon ships no JPEG
|
|
19
|
+
* encoder, so that picture is kept as a `.png` and stays on this machine (the platform takes only
|
|
20
|
+
* JPEG). A page that declares neither command is not asked: the result says to update its SDK.
|
|
20
21
|
*/
|
|
21
22
|
import { createHash } from 'node:crypto';
|
|
22
23
|
import { join } from 'node:path';
|
|
@@ -25,7 +26,12 @@ import { runIdOfCall } from '../../judgement/runs/drive-run.js';
|
|
|
25
26
|
import { noteLanding } from '../../judgement/runs/artifact/run-store.js';
|
|
26
27
|
import { isValidRunId, stepShotDir } from '../../memory/project/dir/reticle-dir.js';
|
|
27
28
|
import { UPLOAD_TEXT_MAX } from '@reticlehq/core/artifacts';
|
|
29
|
+
import { sdkBehindNote } from '../../command/version/version-skew.js';
|
|
30
|
+
import { desktopCapture } from './desktop-capture.js';
|
|
28
31
|
const JPEG_EXT = '.jpg';
|
|
32
|
+
const PNG_EXT = '.png';
|
|
33
|
+
/** Why a page that declared neither `render` nor `capture` was not asked for a picture. */
|
|
34
|
+
const NO_PAGE_CAPTURE = 'this page declared no command that draws it (neither "render" nor "capture"), so nothing was sent';
|
|
29
35
|
const TOOL_PREFIX = /^reticle_/;
|
|
30
36
|
const SEQ_WIDTH = 4;
|
|
31
37
|
/** A prior capture on this tab produced nothing — see the header. */
|
|
@@ -34,19 +40,58 @@ const NOTHING = 'none';
|
|
|
34
40
|
const CDP_QUALITY = Math.round(STEP_SCREENSHOT.JPEG_QUALITY * 100);
|
|
35
41
|
/** What worked last on each tab: the next result names that source. */
|
|
36
42
|
const lastSource = new Map();
|
|
43
|
+
/**
|
|
44
|
+
* The file type of the last picture of each tab: the next path is named with it.
|
|
45
|
+
*
|
|
46
|
+
* ponytail: a tab that changes type between two steps gets one predicted path with the old type;
|
|
47
|
+
* the run record names the real file. Wait for the capture if that ever matters.
|
|
48
|
+
*/
|
|
49
|
+
const lastExt = new Map();
|
|
37
50
|
/** The next sequence number per run directory, read from its record the first time. */
|
|
38
51
|
const nextSeq = new Map();
|
|
52
|
+
/** The PNG signature's first four bytes, and where the IHDR chunk keeps the width and height. */
|
|
53
|
+
const PNG_MAGIC = [0x89, 0x50, 0x4e, 0x47];
|
|
54
|
+
const PNG_WIDTH_AT = 16;
|
|
55
|
+
const PNG_HEIGHT_AT = 20;
|
|
56
|
+
/** Width and height from a PNG's header, or undefined when the bytes are not a PNG. */
|
|
57
|
+
function pngSize(bytes) {
|
|
58
|
+
if (bytes.length < PNG_HEIGHT_AT + 4 || PNG_MAGIC.some((b, i) => b !== bytes[i])) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
62
|
+
const width = view.getUint32(PNG_WIDTH_AT);
|
|
63
|
+
const height = view.getUint32(PNG_HEIGHT_AT);
|
|
64
|
+
return width > 0 && height > 0 ? { width, height } : undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The command that asks this page for its own picture, or undefined when it declared none.
|
|
68
|
+
*
|
|
69
|
+
* `render` came in SDK 3.7. An older SDK declares only the desktop shell's `capture`. A page that
|
|
70
|
+
* declared nothing is too old to say, so it is asked for `render` as before.
|
|
71
|
+
*/
|
|
72
|
+
function pageCaptureCommand(session) {
|
|
73
|
+
const declared = session.commands;
|
|
74
|
+
if (declared === undefined || declared.includes(ReticleCommand.RENDER)) {
|
|
75
|
+
return ReticleCommand.RENDER;
|
|
76
|
+
}
|
|
77
|
+
if (declared.includes(ReticleCommand.CAPTURE)) {
|
|
78
|
+
return ReticleCommand.CAPTURE;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
39
82
|
/**
|
|
40
83
|
* Width and height from a JPEG's frame header, or undefined when the bytes are not a JPEG.
|
|
41
84
|
* Read rather than trusted: the run record and the upload both state the size.
|
|
42
85
|
*/
|
|
43
86
|
export function jpegSize(bytes) {
|
|
44
|
-
if (bytes.length < 4 || 0xff !== bytes[0] || 0xd8 !== bytes[1])
|
|
87
|
+
if (bytes.length < 4 || 0xff !== bytes[0] || 0xd8 !== bytes[1]) {
|
|
45
88
|
return undefined;
|
|
89
|
+
}
|
|
46
90
|
let i = 2;
|
|
47
91
|
while (i + 9 < bytes.length) {
|
|
48
|
-
if (0xff !== bytes[i])
|
|
92
|
+
if (0xff !== bytes[i]) {
|
|
49
93
|
return undefined;
|
|
94
|
+
}
|
|
50
95
|
const marker = bytes[i + 1] ?? 0;
|
|
51
96
|
const length = ((bytes[i + 2] ?? 0) << 8) | (bytes[i + 3] ?? 0);
|
|
52
97
|
// The start-of-frame markers carry the size; 0xC4, 0xC8 and 0xCC share the range and are not frames.
|
|
@@ -63,22 +108,32 @@ export function jpegSize(bytes) {
|
|
|
63
108
|
async function browserPixels(deps, session) {
|
|
64
109
|
const opts = { jpegQuality: CDP_QUALITY, cssScale: true };
|
|
65
110
|
const leased = await deps.pool?.screenshotLease(session.id, opts);
|
|
66
|
-
if (leased !== undefined)
|
|
111
|
+
if (leased !== undefined) {
|
|
67
112
|
return leased;
|
|
113
|
+
}
|
|
114
|
+
// A lease capture that failed still has a reason. Leaving it on the floor made the only text
|
|
115
|
+
// the page's "I cannot draw", which says nothing about the browser that was asked first.
|
|
116
|
+
const failed = deps.pool?.leaseCaptureError(session.id);
|
|
117
|
+
if (failed !== undefined) {
|
|
118
|
+
throw new Error(failed);
|
|
119
|
+
}
|
|
68
120
|
// Only a provider driving THIS tab: a launched provider photographs the one page it owns whatever
|
|
69
121
|
// url it is handed, which is how a screenshot of one tab was once filed under another (#1407).
|
|
70
|
-
if (true !== (await deps.realInput?.isAvailableFor(session.url)))
|
|
122
|
+
if (true !== (await deps.realInput?.isAvailableFor(session.url))) {
|
|
71
123
|
return undefined;
|
|
124
|
+
}
|
|
72
125
|
return deps.realInput?.screenshot?.(session.url, opts);
|
|
73
126
|
}
|
|
74
127
|
/** The page drawn by the SDK. The answer is base64 in chunks — see `ReticleCommand.RENDER`. */
|
|
75
128
|
async function renderedPixels(session) {
|
|
76
129
|
const res = await session.command(ReticleCommand.RENDER, {});
|
|
77
|
-
if (!res.ok)
|
|
130
|
+
if (!res.ok) {
|
|
78
131
|
return res.error ?? 'the page did not answer';
|
|
132
|
+
}
|
|
79
133
|
const r = asRecord(res.result);
|
|
80
|
-
if (true !== r['ok'])
|
|
134
|
+
if (true !== r['ok']) {
|
|
81
135
|
return 'string' === typeof r['reason'] ? r['reason'] : 'render failed';
|
|
136
|
+
}
|
|
82
137
|
const chunks = Array.isArray(r['chunks']) ? r['chunks'] : [];
|
|
83
138
|
return new Uint8Array(Buffer.from(chunks.filter((c) => 'string' === typeof c).join(''), 'base64'));
|
|
84
139
|
}
|
|
@@ -88,20 +143,47 @@ async function capture(deps, session) {
|
|
|
88
143
|
try {
|
|
89
144
|
const real = await browserPixels(deps, session);
|
|
90
145
|
const size = real === undefined ? undefined : jpegSize(real);
|
|
91
|
-
if (real !== undefined && size !== undefined)
|
|
92
|
-
return { bytes: real, source: ScreenshotSource.BROWSER, ...size };
|
|
146
|
+
if (real !== undefined && size !== undefined) {
|
|
147
|
+
return { bytes: real, source: ScreenshotSource.BROWSER, ext: JPEG_EXT, ...size };
|
|
148
|
+
}
|
|
93
149
|
}
|
|
94
150
|
catch (error) {
|
|
95
151
|
reasons.push(error instanceof Error ? error.message : String(error));
|
|
96
152
|
}
|
|
153
|
+
const command = pageCaptureCommand(session);
|
|
154
|
+
if (command === undefined) {
|
|
155
|
+
// Nothing is sent: the page said it has no command for this.
|
|
156
|
+
reasons.push(NO_PAGE_CAPTURE);
|
|
157
|
+
}
|
|
158
|
+
if (command !== ReticleCommand.RENDER) {
|
|
159
|
+
if (command === ReticleCommand.CAPTURE) {
|
|
160
|
+
const png = (await desktopCapture(deps, session.id, false).catch(() => undefined))?.png;
|
|
161
|
+
const size = png === undefined ? undefined : pngSize(png);
|
|
162
|
+
if (png !== undefined && size !== undefined) {
|
|
163
|
+
return { bytes: png, source: ScreenshotSource.BROWSER, ext: PNG_EXT, ...size };
|
|
164
|
+
}
|
|
165
|
+
reasons.push('the desktop shell did not capture its window');
|
|
166
|
+
}
|
|
167
|
+
const behind = sdkBehindNote(session.sdkVersion);
|
|
168
|
+
if (behind !== undefined) {
|
|
169
|
+
reasons.push(behind);
|
|
170
|
+
}
|
|
171
|
+
return `no screenshot: ${reasons.join('; ')}`;
|
|
172
|
+
}
|
|
97
173
|
try {
|
|
98
174
|
const drawn = await renderedPixels(session);
|
|
99
|
-
if ('string' === typeof drawn)
|
|
175
|
+
if ('string' === typeof drawn) {
|
|
100
176
|
reasons.push(`the page could not draw itself: ${drawn}`);
|
|
177
|
+
const behind = sdkBehindNote(session.sdkVersion);
|
|
178
|
+
if (behind !== undefined) {
|
|
179
|
+
reasons.push(behind);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
101
182
|
else {
|
|
102
183
|
const size = jpegSize(drawn);
|
|
103
|
-
if (size !== undefined)
|
|
104
|
-
return { bytes: drawn, source: ScreenshotSource.RENDERED, ...size };
|
|
184
|
+
if (size !== undefined) {
|
|
185
|
+
return { bytes: drawn, source: ScreenshotSource.RENDERED, ext: JPEG_EXT, ...size };
|
|
186
|
+
}
|
|
105
187
|
reasons.push('the page drew something that is not a JPEG');
|
|
106
188
|
}
|
|
107
189
|
}
|
|
@@ -113,12 +195,15 @@ async function capture(deps, session) {
|
|
|
113
195
|
/** Which source this tab's next result names, or undefined when the last capture got nothing. */
|
|
114
196
|
async function predict(deps, session) {
|
|
115
197
|
const last = lastSource.get(session.id);
|
|
116
|
-
if (NOTHING === last)
|
|
198
|
+
if (NOTHING === last) {
|
|
117
199
|
return undefined;
|
|
118
|
-
|
|
200
|
+
}
|
|
201
|
+
if (last !== undefined) {
|
|
119
202
|
return last;
|
|
120
|
-
|
|
203
|
+
}
|
|
204
|
+
if (true === deps.pool?.leasedSessionIds().includes(session.id)) {
|
|
121
205
|
return ScreenshotSource.BROWSER;
|
|
206
|
+
}
|
|
122
207
|
const driven = deps.realInput?.screenshot !== undefined &&
|
|
123
208
|
true === (await deps.realInput.isAvailableFor(session.url));
|
|
124
209
|
return driven ? ScreenshotSource.BROWSER : ScreenshotSource.RENDERED;
|
|
@@ -168,8 +253,9 @@ async function shoot(deps, session, tool, { step, verdict }) {
|
|
|
168
253
|
// Read before anything is awaited: the action this call's verdict was recorded on.
|
|
169
254
|
const actionId = true === verdict ? session.lastActionId : undefined;
|
|
170
255
|
const runId = runIdOfCall(session.id);
|
|
171
|
-
if (!isValidRunId(runId))
|
|
256
|
+
if (!isValidRunId(runId)) {
|
|
172
257
|
return { error: `no screenshot: unusable run id ${runId}` };
|
|
258
|
+
}
|
|
173
259
|
const dir = stepShotDir(root, runId);
|
|
174
260
|
void filmTab(deps, session, dir);
|
|
175
261
|
const seq = await allocate(deps.fs, dir);
|
|
@@ -185,14 +271,17 @@ async function shoot(deps, session, tool, { step, verdict }) {
|
|
|
185
271
|
// Started before anything else is awaited: the picture is of THIS moment.
|
|
186
272
|
const taken = capture(deps, session).then((pixels) => fresh && 'string' === typeof pixels ? capture(deps, session) : pixels);
|
|
187
273
|
const predicted = fresh ? undefined : await predict(deps, session);
|
|
188
|
-
const
|
|
189
|
-
const
|
|
274
|
+
const name = (ext) => `${String(seq).padStart(SEQ_WIDTH, '0')}-${tool.replace(TOOL_PREFIX, '')}${ext}`;
|
|
275
|
+
const predictedPath = join(dir, name(lastExt.get(session.id) ?? JPEG_EXT));
|
|
190
276
|
const land = taken.then(async (pixels) => {
|
|
191
277
|
if ('string' === typeof pixels) {
|
|
192
278
|
lastSource.set(session.id, NOTHING);
|
|
193
279
|
return;
|
|
194
280
|
}
|
|
195
281
|
lastSource.set(session.id, pixels.source);
|
|
282
|
+
lastExt.set(session.id, pixels.ext);
|
|
283
|
+
const file = name(pixels.ext);
|
|
284
|
+
const path = join(dir, file);
|
|
196
285
|
try {
|
|
197
286
|
await deps.fs.mkdir(dir);
|
|
198
287
|
await deps.fs.writeFileBytes(path, pixels.bytes);
|
|
@@ -215,12 +304,15 @@ async function shoot(deps, session, tool, { step, verdict }) {
|
|
|
215
304
|
}
|
|
216
305
|
});
|
|
217
306
|
noteLanding(dir, land);
|
|
218
|
-
if (predicted !== undefined)
|
|
219
|
-
return { path, source: predicted, pending: true };
|
|
307
|
+
if (predicted !== undefined) {
|
|
308
|
+
return { path: predictedPath, source: predicted, pending: true };
|
|
309
|
+
}
|
|
220
310
|
// Nothing worked last time: wait, and tell the truth either way.
|
|
221
311
|
const pixels = await taken;
|
|
222
312
|
await land;
|
|
223
|
-
return 'string' === typeof pixels
|
|
313
|
+
return 'string' === typeof pixels
|
|
314
|
+
? { error: pixels }
|
|
315
|
+
: { path: join(dir, name(pixels.ext)), source: pixels.source };
|
|
224
316
|
}
|
|
225
317
|
/** The replay hook that gives every reported flow step its screenshot, filed as `<flow>#<step>`. */
|
|
226
318
|
export function replayStepShooter(deps, sessionId) {
|
|
@@ -267,8 +359,9 @@ const films = new Map();
|
|
|
267
359
|
const framesIn = new Map();
|
|
268
360
|
/** Is this a tab Reticle drives, with a real browser behind it? */
|
|
269
361
|
async function driven(deps, session) {
|
|
270
|
-
if (true === deps.pool?.leasedSessionIds().includes(session.id))
|
|
362
|
+
if (true === deps.pool?.leasedSessionIds().includes(session.id)) {
|
|
271
363
|
return true;
|
|
364
|
+
}
|
|
272
365
|
return (deps.realInput?.screenshot !== undefined &&
|
|
273
366
|
true === (await deps.realInput.isAvailableFor(session.url)));
|
|
274
367
|
}
|
|
@@ -281,10 +374,12 @@ async function filmTab(deps, session, dir) {
|
|
|
281
374
|
film.still = 1;
|
|
282
375
|
return;
|
|
283
376
|
}
|
|
284
|
-
if (!(await driven(deps, session).catch(() => false)))
|
|
377
|
+
if (!(await driven(deps, session).catch(() => false))) {
|
|
285
378
|
return;
|
|
286
|
-
|
|
379
|
+
}
|
|
380
|
+
if (films.has(session.id)) {
|
|
287
381
|
return;
|
|
382
|
+
}
|
|
288
383
|
const timer = setInterval(() => {
|
|
289
384
|
void filmNow(session.id);
|
|
290
385
|
}, FRAME.EVERY_MS);
|
|
@@ -295,16 +390,18 @@ async function filmTab(deps, session, dir) {
|
|
|
295
390
|
/** Stop filming a tab. Safe to call for a tab that is not filmed. */
|
|
296
391
|
export function stopFilming(sessionId) {
|
|
297
392
|
const film = films.get(sessionId);
|
|
298
|
-
if (film === undefined)
|
|
393
|
+
if (film === undefined) {
|
|
299
394
|
return;
|
|
395
|
+
}
|
|
300
396
|
clearInterval(film.timer);
|
|
301
397
|
films.delete(sessionId);
|
|
302
398
|
}
|
|
303
399
|
/** One tick of a tab's film: one frame, when the budget, the gap and the page allow. Never throws. */
|
|
304
400
|
export async function filmNow(sessionId) {
|
|
305
401
|
const film = films.get(sessionId);
|
|
306
|
-
if (film === undefined || film.busy !== undefined)
|
|
402
|
+
if (film === undefined || film.busy !== undefined) {
|
|
307
403
|
return;
|
|
404
|
+
}
|
|
308
405
|
film.busy = frame(sessionId, film).finally(() => {
|
|
309
406
|
film.busy = undefined;
|
|
310
407
|
});
|
|
@@ -315,8 +412,9 @@ async function frame(sessionId, film) {
|
|
|
315
412
|
try {
|
|
316
413
|
session = film.deps.sessions.resolve(sessionId);
|
|
317
414
|
// An ended tab has nothing more to show.
|
|
318
|
-
if (session.isEnded())
|
|
415
|
+
if (session.isEnded()) {
|
|
319
416
|
session = undefined;
|
|
417
|
+
}
|
|
320
418
|
}
|
|
321
419
|
catch {
|
|
322
420
|
session = undefined;
|
|
@@ -327,18 +425,21 @@ async function frame(sessionId, film) {
|
|
|
327
425
|
}
|
|
328
426
|
const dir = film.dir;
|
|
329
427
|
const kept = framesIn.get(dir) ?? 0;
|
|
330
|
-
if (kept >= FRAME.MAX_PER_RUN)
|
|
428
|
+
if (kept >= FRAME.MAX_PER_RUN) {
|
|
331
429
|
return;
|
|
430
|
+
}
|
|
332
431
|
film.idle += 1;
|
|
333
432
|
const gap = 2 ** Math.floor(kept / (FRAME.MAX_PER_RUN / FRAME_STAGES));
|
|
334
|
-
if (film.idle < Math.max(gap, film.still))
|
|
433
|
+
if (film.idle < Math.max(gap, film.still)) {
|
|
335
434
|
return;
|
|
435
|
+
}
|
|
336
436
|
film.idle = 0;
|
|
337
437
|
try {
|
|
338
438
|
const bytes = await framePixels(film.deps, session);
|
|
339
439
|
const size = bytes === undefined ? undefined : jpegSize(bytes);
|
|
340
|
-
if (bytes === undefined || size === undefined)
|
|
440
|
+
if (bytes === undefined || size === undefined) {
|
|
341
441
|
return;
|
|
442
|
+
}
|
|
342
443
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
343
444
|
if (film.recent.includes(sha256)) {
|
|
344
445
|
film.still = Math.min(film.still * 2, MAX_STILL_TICKS);
|
|
@@ -348,8 +449,9 @@ async function frame(sessionId, film) {
|
|
|
348
449
|
film.still = 1;
|
|
349
450
|
const seq = await allocate(film.deps.fs, dir);
|
|
350
451
|
// The step shots' own limit is the platform's: a frame never takes the last of it.
|
|
351
|
-
if (seq > STEP_SCREENSHOT.MAX_PER_RUN)
|
|
452
|
+
if (seq > STEP_SCREENSHOT.MAX_PER_RUN) {
|
|
352
453
|
return;
|
|
454
|
+
}
|
|
353
455
|
framesIn.set(dir, kept + 1);
|
|
354
456
|
const file = `${String(seq).padStart(SEQ_WIDTH, '0')}-${FRAME.TOOL}${JPEG_EXT}`;
|
|
355
457
|
const record = {
|
|
@@ -377,9 +479,11 @@ async function frame(sessionId, film) {
|
|
|
377
479
|
async function framePixels(deps, session) {
|
|
378
480
|
const opts = { jpegQuality: FRAME.JPEG_QUALITY, cssScale: true };
|
|
379
481
|
const leased = await deps.pool?.screenshotLease(session.id, opts);
|
|
380
|
-
if (leased !== undefined)
|
|
482
|
+
if (leased !== undefined) {
|
|
381
483
|
return leased;
|
|
382
|
-
|
|
484
|
+
}
|
|
485
|
+
if (true !== (await deps.realInput?.isAvailableFor(session.url))) {
|
|
383
486
|
return undefined;
|
|
487
|
+
}
|
|
384
488
|
return deps.realInput?.screenshot?.(session.url, opts);
|
|
385
489
|
}
|
|
@@ -8,24 +8,6 @@ import type { ToolDef, ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
|
8
8
|
* gone mid-drive must not break the drive.
|
|
9
9
|
*/
|
|
10
10
|
export declare function driveFrame(deps: ToolDeps, sessionId: string, jpegQuality: number): Promise<Uint8Array | undefined>;
|
|
11
|
-
/**
|
|
12
|
-
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
13
|
-
* become a file-read oracle for whatever a compromised renderer names.
|
|
14
|
-
*
|
|
15
|
-
* Two layouts are accepted, and the pair is the whole subtlety:
|
|
16
|
-
*
|
|
17
|
-
* - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
|
|
18
|
-
* so no other local user can read the screenshot or pre-place a symlink at the path we write.
|
|
19
|
-
* - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
|
|
20
|
-
* SEPARATE package on the user's own upgrade schedule: an app still on the older
|
|
21
|
-
* `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
|
|
22
|
-
* start returning no image. It grants the daemon nothing it did not already have.
|
|
23
|
-
*
|
|
24
|
-
* Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
|
|
25
|
-
* `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
|
|
26
|
-
* `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
|
|
27
|
-
*/
|
|
28
|
-
export declare function isCapturePath(path: string): boolean;
|
|
29
11
|
/**
|
|
30
12
|
* The opt-in pixel layer. Both tools require a DRIVEN browser (reticle drive /
|
|
31
13
|
* RETICLE_CDP_URL) — the always-on SDK ships no screenshotter — and return a structured NO_PROVIDER
|