@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { AppRuntime, ReticleCommand,
|
|
2
|
+
import { AppRuntime, ReticleCommand, VISUAL_NO_PROVIDER_RECOMMENDATION, VisualReason, realmOf, } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from '@reticlehq/core';
|
|
4
4
|
import { sessionIdShape } from '../../surface/tools/tool-kit.js';
|
|
5
5
|
import { ratioSchema } from '../../surface/tools/args/numeric-bounds.js';
|
|
@@ -21,10 +21,7 @@ function captureRoot(deps, sessionId) {
|
|
|
21
21
|
return deps.reticleRoot;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
import {
|
|
25
|
-
import { readFile, unlink } from 'node:fs/promises';
|
|
26
|
-
import { tmpdir } from 'node:os';
|
|
27
|
-
import { basename, dirname } from 'node:path';
|
|
24
|
+
import { desktopCapture } from './desktop-capture.js';
|
|
28
25
|
const rectShape = z.object({
|
|
29
26
|
x: z.number(),
|
|
30
27
|
y: z.number(),
|
|
@@ -37,6 +34,38 @@ function screenshotProvider(deps) {
|
|
|
37
34
|
? p
|
|
38
35
|
: undefined;
|
|
39
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether this call can photograph the session: a lease of this caller, a desktop shell, or a page
|
|
39
|
+
* that the driven browser has.
|
|
40
|
+
*
|
|
41
|
+
* A desktop shell counts before its capture is known to work. Only a CAPTURE command can tell, and
|
|
42
|
+
* a person who connected a desktop app most likely wants that window, not a lease beside it.
|
|
43
|
+
*/
|
|
44
|
+
async function canCapture(deps, session) {
|
|
45
|
+
// A lease of this caller. Another agent's lease is never this caller's to photograph (#1226).
|
|
46
|
+
if (true === deps.pool?.heldBy(session.id, deps.attachId)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (realmOf(session.runtime).isDesktopShell) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// A driven browser photographs only a page it has. Having a driven browser is not enough.
|
|
53
|
+
const driven = screenshotProvider(deps);
|
|
54
|
+
if (driven === undefined) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return driven.isAvailableFor(session.url);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The one session this call photographs, chosen once (#1959).
|
|
61
|
+
*
|
|
62
|
+
* capture() and captureRoot() each resolve the id again. With no id, each could choose another
|
|
63
|
+
* session, and capture() never tried a lease at all. After this, they all get the same, named id.
|
|
64
|
+
*/
|
|
65
|
+
async function captureTarget(deps, args) {
|
|
66
|
+
const session = await deps.sessions.resolveServing(asString(args['sessionId']), (s) => canCapture(deps, s));
|
|
67
|
+
return session.id;
|
|
68
|
+
}
|
|
40
69
|
/** The "you need a driven browser" envelope, shared by both visual tools. */
|
|
41
70
|
const noProvider = {
|
|
42
71
|
ok: false,
|
|
@@ -45,8 +74,9 @@ const noProvider = {
|
|
|
45
74
|
};
|
|
46
75
|
/** The answer when no pixels came back: the shell's own recommendation over the web one. */
|
|
47
76
|
function unavailable(reason, recommendation) {
|
|
48
|
-
if (recommendation !== undefined)
|
|
77
|
+
if (recommendation !== undefined) {
|
|
49
78
|
return { ok: false, reason, recommendation };
|
|
79
|
+
}
|
|
50
80
|
return reason === VisualReason.NO_PROVIDER ? noProvider : { ok: false, reason };
|
|
51
81
|
}
|
|
52
82
|
function asBox(value) {
|
|
@@ -55,10 +85,12 @@ function asBox(value) {
|
|
|
55
85
|
const y = asNumber(b['y']);
|
|
56
86
|
const w = asNumber(b['width']);
|
|
57
87
|
const h = asNumber(b['height']);
|
|
58
|
-
if (x === undefined || y === undefined || w === undefined || h === undefined)
|
|
88
|
+
if (x === undefined || y === undefined || w === undefined || h === undefined) {
|
|
59
89
|
return undefined;
|
|
60
|
-
|
|
90
|
+
}
|
|
91
|
+
if (w <= 0 || h <= 0) {
|
|
61
92
|
return undefined;
|
|
93
|
+
}
|
|
62
94
|
return { x, y, width: w, height: h };
|
|
63
95
|
}
|
|
64
96
|
/** Build capture options from args: explicit clip > ref (resolved via INSPECT) > fullPage. */
|
|
@@ -67,59 +99,21 @@ async function buildOpts(deps, sessionId, args) {
|
|
|
67
99
|
if (clipArg !== undefined) {
|
|
68
100
|
const c = asRecord(clipArg);
|
|
69
101
|
const box = asBox({ box: c });
|
|
70
|
-
if (box !== undefined)
|
|
102
|
+
if (box !== undefined) {
|
|
71
103
|
return { clip: box };
|
|
104
|
+
}
|
|
72
105
|
}
|
|
73
106
|
const ref = asString(args['ref']);
|
|
74
107
|
if (ref !== undefined) {
|
|
75
108
|
const session = deps.sessions.resolve(sessionId);
|
|
76
109
|
const res = await session.command(ReticleCommand.INSPECT, { ref });
|
|
77
110
|
const box = res.ok ? asBox(res.result) : undefined;
|
|
78
|
-
if (box !== undefined)
|
|
111
|
+
if (box !== undefined) {
|
|
79
112
|
return { clip: box };
|
|
113
|
+
}
|
|
80
114
|
}
|
|
81
115
|
return true === args['fullPage'] ? { fullPage: true } : {};
|
|
82
116
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Ask the desktop shell to photograph its own window.
|
|
85
|
-
*
|
|
86
|
-
* Returns undefined for a plain web page (no capture helper) and for a desktop app that did not
|
|
87
|
-
* install `@reticlehq/electron/main`. Deliberately NOT a screen-region capture: photographing
|
|
88
|
-
* the glass returns whatever window is on top, which would save a picture of the user's editor as a
|
|
89
|
-
* visual baseline — the exact false green Reticle exists to eliminate.
|
|
90
|
-
*/
|
|
91
|
-
async function desktopCapture(deps, sessionId, fullPage) {
|
|
92
|
-
const session = deps.sessions.resolve(sessionId);
|
|
93
|
-
// A session that cannot take commands (no live browser behind it) simply has no pixels to give.
|
|
94
|
-
if (typeof session.command !== 'function')
|
|
95
|
-
return {};
|
|
96
|
-
const res = await session.command(ReticleCommand.CAPTURE, { fullPage });
|
|
97
|
-
if (!res.ok)
|
|
98
|
-
return {};
|
|
99
|
-
const r = asRecord(res.result);
|
|
100
|
-
if (r['ok'] !== true) {
|
|
101
|
-
// The shell refused a full-page capture it cannot do. That is a different answer from "capture
|
|
102
|
-
// broke", and the caller has to hear it — otherwise they read `fullPage` as honoured.
|
|
103
|
-
if (r['reason'] === VisualReason.FULL_PAGE_UNSUPPORTED)
|
|
104
|
-
return { reason: VisualReason.FULL_PAGE_UNSUPPORTED };
|
|
105
|
-
// A Tauri release build with the capture compiled out: say that, never the web answer.
|
|
106
|
-
return true === asString(r['reason'])?.includes(RETICLE_TAURI_INERT_MARKER)
|
|
107
|
-
? { reason: VisualReason.NO_PROVIDER, recommendation: TAURI_INERT_RECOMMENDATION }
|
|
108
|
-
: {};
|
|
109
|
-
}
|
|
110
|
-
const path = asString(r['path']);
|
|
111
|
-
if (path === undefined || !isCapturePath(path))
|
|
112
|
-
return {};
|
|
113
|
-
try {
|
|
114
|
-
const bytes = await readFile(path);
|
|
115
|
-
await unlink(path).catch(() => undefined);
|
|
116
|
-
trackCaptureDirectory(path);
|
|
117
|
-
return isCompletePng(bytes) ? { png: new Uint8Array(bytes) } : {};
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return {};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
117
|
/**
|
|
124
118
|
* One picture of the tab a chat-requested drive is driving, or undefined when there is none to take.
|
|
125
119
|
*
|
|
@@ -131,14 +125,16 @@ async function desktopCapture(deps, sessionId, fullPage) {
|
|
|
131
125
|
export async function driveFrame(deps, sessionId, jpegQuality) {
|
|
132
126
|
try {
|
|
133
127
|
const leased = await deps.pool?.screenshotLease(sessionId, { jpegQuality });
|
|
134
|
-
if (leased !== undefined)
|
|
128
|
+
if (leased !== undefined) {
|
|
135
129
|
return leased;
|
|
130
|
+
}
|
|
136
131
|
// The window `reticle drive` opened: neither a lease nor a desktop shell, but its page is ours.
|
|
137
132
|
const url = deps.sessions.resolve(sessionId).url;
|
|
138
133
|
if (true === (await deps.realInput?.isAvailableFor(url))) {
|
|
139
134
|
const shot = await deps.realInput?.screenshot?.(url, { jpegQuality });
|
|
140
|
-
if (shot !== undefined)
|
|
135
|
+
if (shot !== undefined) {
|
|
141
136
|
return shot;
|
|
137
|
+
}
|
|
142
138
|
}
|
|
143
139
|
return (await desktopCapture(deps, sessionId, false)).png;
|
|
144
140
|
}
|
|
@@ -146,42 +142,6 @@ export async function driveFrame(deps, sessionId, jpegQuality) {
|
|
|
146
142
|
return undefined;
|
|
147
143
|
}
|
|
148
144
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
151
|
-
* become a file-read oracle for whatever a compromised renderer names.
|
|
152
|
-
*
|
|
153
|
-
* Two layouts are accepted, and the pair is the whole subtlety:
|
|
154
|
-
*
|
|
155
|
-
* - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
|
|
156
|
-
* so no other local user can read the screenshot or pre-place a symlink at the path we write.
|
|
157
|
-
* - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
|
|
158
|
-
* SEPARATE package on the user's own upgrade schedule: an app still on the older
|
|
159
|
-
* `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
|
|
160
|
-
* start returning no image. It grants the daemon nothing it did not already have.
|
|
161
|
-
*
|
|
162
|
-
* Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
|
|
163
|
-
* `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
|
|
164
|
-
* `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
|
|
165
|
-
*/
|
|
166
|
-
export function isCapturePath(path) {
|
|
167
|
-
if (!basename(path).startsWith(RETICLE_CAPTURE_FILE_PREFIX))
|
|
168
|
-
return false;
|
|
169
|
-
const parent = dirname(path);
|
|
170
|
-
if (parent === tmpdir())
|
|
171
|
-
return true;
|
|
172
|
-
return dirname(parent) === tmpdir() && basename(parent).startsWith(RETICLE_CAPTURE_FILE_PREFIX);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* A PNG is complete only if it ends with IEND. This check exists because a truncated image once
|
|
176
|
-
* sailed through as a saved baseline: the tool answered `saved: true` with a path, and the file was
|
|
177
|
-
* undecodable. A screenshot that cannot be read must fail loudly, never bank a corrupt baseline that
|
|
178
|
-
* a later visual_diff will compare against.
|
|
179
|
-
*/
|
|
180
|
-
function isCompletePng(bytes) {
|
|
181
|
-
if (bytes.byteLength < 12)
|
|
182
|
-
return false;
|
|
183
|
-
return 'IEND' === bytes.subarray(bytes.byteLength - 8, bytes.byteLength - 4).toString('ascii');
|
|
184
|
-
}
|
|
185
145
|
/**
|
|
186
146
|
* Which runtime this session is, for scoping a visual artifact.
|
|
187
147
|
*
|
|
@@ -232,8 +192,9 @@ async function capture(deps, sessionId, args) {
|
|
|
232
192
|
// A driven browser renders the session's URL in a BROWSER — so the pixels are web even when the
|
|
233
193
|
// session named is a desktop window. Scoping those under the desktop runtime would corrupt that
|
|
234
194
|
// runtime's baseline with a picture of a different renderer.
|
|
235
|
-
if (png !== undefined)
|
|
195
|
+
if (png !== undefined) {
|
|
236
196
|
return { png, runtime: AppRuntime.WEB };
|
|
197
|
+
}
|
|
237
198
|
}
|
|
238
199
|
// The window's own backing store, via the Electron/Tauri adapter — the only route whose pixels
|
|
239
200
|
// really are the desktop app.
|
|
@@ -246,8 +207,9 @@ async function capture(deps, sessionId, args) {
|
|
|
246
207
|
}
|
|
247
208
|
return { png: desktop.png, runtime: runtimeOf(deps, sessionId) };
|
|
248
209
|
}
|
|
249
|
-
if (desktop.reason !== undefined)
|
|
210
|
+
if (desktop.reason !== undefined) {
|
|
250
211
|
return desktop;
|
|
212
|
+
}
|
|
251
213
|
/*
|
|
252
214
|
* A LEASED page is a real browser page, so the pixels were always there — the visual tools simply
|
|
253
215
|
* had no route to them. Without this, `reticle_screenshot` answered "no provider" for every context
|
|
@@ -261,20 +223,23 @@ async function capture(deps, sessionId, args) {
|
|
|
261
223
|
// shot of a lease saved the whole viewport and answered `saved: true`.
|
|
262
224
|
await deps.pool?.screenshotLease(sessionId, await buildOpts(deps, sessionId, args));
|
|
263
225
|
// A leased page is a real browser page, whatever the session is.
|
|
264
|
-
if (leased !== undefined)
|
|
226
|
+
if (leased !== undefined) {
|
|
265
227
|
return { png: leased, runtime: AppRuntime.WEB };
|
|
228
|
+
}
|
|
266
229
|
return {
|
|
267
230
|
reason: provider === undefined ? VisualReason.NO_PROVIDER : VisualReason.CAPTURE_FAILED,
|
|
268
231
|
};
|
|
269
232
|
}
|
|
270
233
|
function rectsFrom(value) {
|
|
271
|
-
if (!Array.isArray(value))
|
|
234
|
+
if (!Array.isArray(value)) {
|
|
272
235
|
return undefined;
|
|
236
|
+
}
|
|
273
237
|
const rects = [];
|
|
274
238
|
for (const r of value) {
|
|
275
239
|
const box = asBox({ box: r });
|
|
276
|
-
if (box !== undefined)
|
|
240
|
+
if (box !== undefined) {
|
|
277
241
|
rects.push(box);
|
|
242
|
+
}
|
|
278
243
|
}
|
|
279
244
|
return rects.length > 0 ? rects : undefined;
|
|
280
245
|
}
|
|
@@ -316,10 +281,11 @@ export const VISUAL_TOOLS = [
|
|
|
316
281
|
recommendation: z.string().optional(),
|
|
317
282
|
},
|
|
318
283
|
handler: async (deps, args) => {
|
|
319
|
-
const sessionId =
|
|
284
|
+
const sessionId = await captureTarget(deps, args);
|
|
320
285
|
const { png, reason, recommendation, runtime } = await capture(deps, sessionId, args);
|
|
321
|
-
if (png === undefined)
|
|
286
|
+
if (png === undefined) {
|
|
322
287
|
return unavailable(reason, recommendation);
|
|
288
|
+
}
|
|
323
289
|
const name = asString(args['name']) ?? 'default';
|
|
324
290
|
// Beside the app that was captured. The tool RETURNS this path to the agent, so a wrong root
|
|
325
291
|
// is not only a stray PNG — it is a path the agent is told to look at in a tree that has
|
|
@@ -369,22 +335,24 @@ export const VISUAL_TOOLS = [
|
|
|
369
335
|
},
|
|
370
336
|
handler: async (deps, args) => {
|
|
371
337
|
const baseline = asString(args['baseline']) ?? '';
|
|
372
|
-
const
|
|
338
|
+
const sessionId = await captureTarget(deps, args);
|
|
339
|
+
const store = new VisualStore(deps.fs, captureRoot(deps, sessionId));
|
|
373
340
|
// Capture FIRST, then fetch the baseline for the runtime that produced these pixels. Reading it
|
|
374
341
|
// by the session's runtime looked equivalent and is not: the route decides the renderer, so a
|
|
375
342
|
// desktop session captured through a driven browser must be compared against the WEB baseline,
|
|
376
343
|
// not the desktop one. Comparing across renderers is a confident wrong diff in one direction
|
|
377
344
|
// and a silently overwritten baseline in the other.
|
|
378
|
-
const sessionId = asString(args['sessionId']);
|
|
379
345
|
const captured = await capture(deps, sessionId, args);
|
|
380
346
|
const { png: current, runtime } = captured;
|
|
381
|
-
if (current === undefined)
|
|
347
|
+
if (current === undefined) {
|
|
382
348
|
return unavailable(captured.reason, captured.recommendation);
|
|
349
|
+
}
|
|
383
350
|
// A baseline from another runtime is not missing by accident — it is a different rendering, and
|
|
384
351
|
// "no baseline for this one" is the honest answer.
|
|
385
352
|
const baselineBytes = await store.readBaseline(baseline, runtime);
|
|
386
|
-
if (baselineBytes === undefined)
|
|
353
|
+
if (baselineBytes === undefined) {
|
|
387
354
|
return { ok: false, reason: VisualReason.BASELINE_MISSING };
|
|
355
|
+
}
|
|
388
356
|
const masks = rectsFrom(args['masks']);
|
|
389
357
|
const threshold = asNumber(args['threshold']);
|
|
390
358
|
const maxRatio = asNumber(args['maxRatio']);
|
package/dist/import-graph.js
CHANGED
|
@@ -75,12 +75,11 @@ export function importsOf(sourceRoot, file) {
|
|
|
75
75
|
export function reachableFrom(sourceRoot, entry) {
|
|
76
76
|
/*
|
|
77
77
|
* The entry is normalised the same way `resolveImport` normalises everything this walker
|
|
78
|
-
* DISCOVERS, because callers build it with `join()` and on Windows that yields `features\
|
|
79
|
-
* while every other key in the map is `features/
|
|
80
|
-
* `startsWith('features/
|
|
81
|
-
* out. That is how
|
|
82
|
-
*
|
|
83
|
-
* assertions expect an EMPTY list, so a walker that finds nothing agrees with them. Only its
|
|
78
|
+
* DISCOVERS, because callers build it with `join()` and on Windows that yields `features\x\y.ts`
|
|
79
|
+
* while every other key in the map is `features/x/y.ts`. A caller then filters the result with
|
|
80
|
+
* `startsWith('features/x/')` and the seed — the one path it did not walk to — silently drops
|
|
81
|
+
* out. That is how a boundary guard passed on Windows while checking nothing at all: its real
|
|
82
|
+
* assertions expected an EMPTY list, so a walker that finds nothing agrees with them. Only its
|
|
84
83
|
* negative control could see it, and that is the whole reason the negative control exists.
|
|
85
84
|
*
|
|
86
85
|
* Both separators, not `sep`: on a POSIX machine `sep` is `/`, so splitting on it leaves a
|
package/dist/index.d.ts
CHANGED
|
@@ -102,8 +102,10 @@ export { registerContradictionFold, registeredContradictionFolds, } from '@retic
|
|
|
102
102
|
export type { ContradictionFold } from '@reticlehq/engine/disagreement/contradiction-folds.js';
|
|
103
103
|
export { MCP_SSE_PATH, MCP_MESSAGE_PATH } from '@reticlehq/core';
|
|
104
104
|
export { BrowserPool, DEFAULT_LEASE_TTL_MS } from './portal/pool/browser-pool.js';
|
|
105
|
-
export type { Lease, Launcher, PooledBrowser } from './portal/pool/browser-pool.js';
|
|
105
|
+
export type { Lease, Launcher, PooledBrowser, PooledPageError, } from './portal/pool/browser-pool.js';
|
|
106
106
|
export { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
|
|
107
|
+
export { makePageErrorJournal, uncaughtIdentity } from './portal/pool/page-error-journal.js';
|
|
108
|
+
export type { JournalableSession, PageErrorJournal } from './portal/pool/page-error-journal.js';
|
|
107
109
|
export { appendReticleParams } from './surface/tools/lease-tools.js';
|
|
108
110
|
export { writePid, removePid, isRunning, logPath, readPid, isAlive, } from './command/daemon/daemon.js';
|
|
109
111
|
export { classifyDaemonLife, DaemonEnd, DAEMON_HEARTBEAT_EVENT, DAEMON_HEARTBEAT_MS, type DaemonLife, } from './command/daemon/lifetime/heartbeat.js';
|
package/dist/index.js
CHANGED
|
@@ -54,11 +54,12 @@ import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './
|
|
|
54
54
|
import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
55
55
|
import { findProjectConfig, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
56
56
|
import { probeLaunchableChromium } from './launch-chromium.js';
|
|
57
|
+
import { portAcceptsConnection } from './portal/session/dev-server/dev-server-probe.js';
|
|
57
58
|
import { attachJournal } from './wire-journal.js';
|
|
58
59
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
59
60
|
import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
|
|
60
61
|
import { log } from './log.js';
|
|
61
|
-
import { createBrowserPool, knownProjectRoots, loadHarnessConfig, makeNetworkDetailRouter, platformEnvFor, readerConnect, resolveRealInput, sdkFixForProject, } from './start-wiring.js';
|
|
62
|
+
import { attachPageErrorJournal, createBrowserPool, knownProjectRoots, loadHarnessConfig, makeNetworkDetailRouter, platformEnvFor, readerConnect, resolveRealInput, sdkFixForProject, } from './start-wiring.js';
|
|
62
63
|
export { replayVerdictLine } from './language/flows/replay-from-hud.js';
|
|
63
64
|
// Re-exported from the contract, where the names now live: a consumer importing the server should
|
|
64
65
|
// not have to know that the vocabulary moved.
|
|
@@ -149,14 +150,18 @@ export async function start(options = {}) {
|
|
|
149
150
|
// Then to the agent's own git worktree: every worktree declares the same projectId.
|
|
150
151
|
bridge.sessions.setCheckoutScope(liveCheckoutSource());
|
|
151
152
|
const baselines = new BaselineStore();
|
|
152
|
-
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
|
|
153
|
+
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined, (session) => bridge.sessions.successorId(session));
|
|
153
154
|
// drive precedence: driveUrl (launch+own a browser) → CDP (attach) → none.
|
|
154
155
|
let pool;
|
|
155
156
|
let leaseReaper;
|
|
156
157
|
// Route CDP-authoritative network detail (drive path only) onto the driven session's journal: the
|
|
157
158
|
// page and the SDK session share an origin, so a NET_DETAIL is pushed to the matching connected session.
|
|
158
159
|
const routeNetworkDetail = makeNetworkDetailRouter(bridge, options.driveUrl);
|
|
159
|
-
|
|
160
|
+
// Journal the uncaught page errors the pool hears — an import-time throw lands before any SDK
|
|
161
|
+
// exists to see it, and it was previously invisible to the agent (#1966). The bridge hook is
|
|
162
|
+
// registered here; the pool gets the sink at its creation below.
|
|
163
|
+
const pageErrorJournal = attachPageErrorJournal(bridge);
|
|
164
|
+
const { realInput, owned } = await resolveRealInput(options, () => bridge.close(), routeNetworkDetail, readerConnect(port, security.token));
|
|
160
165
|
let chatDrives;
|
|
161
166
|
if (options.mcp !== false) {
|
|
162
167
|
// cwd/Date.now are confined to start — never inside reticle-dir.ts's pure logic (rule 7).
|
|
@@ -184,7 +189,7 @@ export async function start(options = {}) {
|
|
|
184
189
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
185
190
|
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
186
191
|
const annotations = new AnnotationStore();
|
|
187
|
-
pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
|
|
192
|
+
pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token), (leaseId, error) => pageErrorJournal.onPoolPageError(leaseId, error), (leaseId) => pageErrorJournal.onLeaseClosed(leaseId), bridge.nonces, (leaseId, dialog) => pageErrorJournal.onPoolDialog(leaseId, dialog));
|
|
188
193
|
leaseReaper = new LeaseReaper(pool);
|
|
189
194
|
leaseReaper.start();
|
|
190
195
|
/*
|
|
@@ -223,6 +228,7 @@ export async function start(options = {}) {
|
|
|
223
228
|
bridgePort: port,
|
|
224
229
|
nonces: bridge.nonces,
|
|
225
230
|
browserProbe: probeLaunchableChromium,
|
|
231
|
+
portAccepts: portAcceptsConnection,
|
|
226
232
|
// The daemon's OWN project, so a tool can tell "this session is mine" from "this session
|
|
227
233
|
// belongs to a sibling app under the same daemon". contract_save refuses on the second.
|
|
228
234
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
@@ -346,7 +352,11 @@ export async function startDaemon(options = {}) {
|
|
|
346
352
|
}, AGENT_DETACH_GRACE_MS));
|
|
347
353
|
const reaper = new SessionReaper(bridge.sessions);
|
|
348
354
|
reaper.start();
|
|
349
|
-
|
|
355
|
+
// Same page-error journal the `start` path wires: an import-time crash reaches the agent through
|
|
356
|
+
// it instead of reading as a blank page (#1966). Kept beside the other shared wiring so the two
|
|
357
|
+
// entry points cannot drift.
|
|
358
|
+
const pageErrorJournal = attachPageErrorJournal(bridge);
|
|
359
|
+
const { realInput, owned } = await resolveRealInput(options, () => shared.close(), makeNetworkDetailRouter(bridge, options.driveUrl), readerConnect(port, security.token));
|
|
350
360
|
const fs = createNodeFileSystem();
|
|
351
361
|
const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
352
362
|
const now = options.now ?? (() => Date.now());
|
|
@@ -357,7 +367,7 @@ export async function startDaemon(options = {}) {
|
|
|
357
367
|
const flows = new FlowStore(fs, reticleRoot, { now }, { onWrote: () => syncNudge.run?.() });
|
|
358
368
|
// Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
|
|
359
369
|
// paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
|
|
360
|
-
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
|
|
370
|
+
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined, (session) => bridge.sessions.successorId(session));
|
|
361
371
|
/*
|
|
362
372
|
* Bound AFTER the sync daemon exists, read only when a run is actually written.
|
|
363
373
|
*
|
|
@@ -380,7 +390,7 @@ export async function startDaemon(options = {}) {
|
|
|
380
390
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
381
391
|
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
382
392
|
const annotations = new AnnotationStore();
|
|
383
|
-
const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
|
|
393
|
+
const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token), (leaseId, error) => pageErrorJournal.onPoolPageError(leaseId, error), (leaseId) => pageErrorJournal.onLeaseClosed(leaseId), bridge.nonces, (leaseId, dialog) => pageErrorJournal.onPoolDialog(leaseId, dialog));
|
|
384
394
|
const leaseReaper = new LeaseReaper(pool);
|
|
385
395
|
leaseReaper.start();
|
|
386
396
|
/*
|
|
@@ -453,6 +463,7 @@ export async function startDaemon(options = {}) {
|
|
|
453
463
|
bridgePort: port,
|
|
454
464
|
nonces: bridge.nonces,
|
|
455
465
|
browserProbe: probeLaunchableChromium,
|
|
466
|
+
portAccepts: portAcceptsConnection,
|
|
456
467
|
...agentWiring(bridge.sessions, leaseUrl),
|
|
457
468
|
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
458
469
|
onRunPersisted: () => cloudSync.nudge(),
|
|
@@ -587,6 +598,7 @@ export { registerContradictionFold, registeredContradictionFolds, } from '@retic
|
|
|
587
598
|
export { MCP_SSE_PATH, MCP_MESSAGE_PATH } from '@reticlehq/core';
|
|
588
599
|
export { BrowserPool, DEFAULT_LEASE_TTL_MS } from './portal/pool/browser-pool.js';
|
|
589
600
|
export { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
|
|
601
|
+
export { makePageErrorJournal, uncaughtIdentity } from './portal/pool/page-error-journal.js';
|
|
590
602
|
export { appendReticleParams } from './surface/tools/lease-tools.js';
|
|
591
603
|
export { writePid, removePid, isRunning, logPath, readPid, isAlive, } from './command/daemon/daemon.js';
|
|
592
604
|
// The daemon's own liveness vocabulary, exported so a GATE can read a daemon log back and say how
|
|
@@ -94,6 +94,8 @@ export function causalSummary(events, context = {}) {
|
|
|
94
94
|
const signals = [];
|
|
95
95
|
const fieldsChanged = [];
|
|
96
96
|
let layoutShift;
|
|
97
|
+
// The shift inside this window, summed from each entry's own `shift` (#1784).
|
|
98
|
+
let windowShift;
|
|
97
99
|
let longTasks = 0;
|
|
98
100
|
let ignoredBookkeeping = 0;
|
|
99
101
|
for (const event of events) {
|
|
@@ -166,7 +168,11 @@ export function causalSummary(events, context = {}) {
|
|
|
166
168
|
pushUnique(fieldsChanged, data['field']);
|
|
167
169
|
break;
|
|
168
170
|
case EventType.PERF:
|
|
169
|
-
if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['
|
|
171
|
+
if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['shift']) {
|
|
172
|
+
windowShift = (windowShift ?? 0) + data['shift'];
|
|
173
|
+
}
|
|
174
|
+
else if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
|
|
175
|
+
// An SDK older than #1784 sends only the running total: the most it allows is its max.
|
|
170
176
|
layoutShift = Math.max(layoutShift ?? 0, data['value']);
|
|
171
177
|
}
|
|
172
178
|
else if (data['metric'] === PerfMetric.LONGTASK) {
|
|
@@ -182,6 +188,7 @@ export function causalSummary(events, context = {}) {
|
|
|
182
188
|
// store, and shrinking it because the evidence was trimmed would make the transient window read as
|
|
183
189
|
// narrower than it was — the one number here that must survive the trim intact.
|
|
184
190
|
const settle = stateSettle(stateDiffs);
|
|
191
|
+
const shiftInWindow = windowShift ?? layoutShift;
|
|
185
192
|
return {
|
|
186
193
|
net: {
|
|
187
194
|
total: netTotal,
|
|
@@ -204,7 +211,7 @@ export function causalSummary(events, context = {}) {
|
|
|
204
211
|
? {}
|
|
205
212
|
: { fieldsChanged: cap(fieldsChanged, 'fieldsChanged', elided) }),
|
|
206
213
|
...(0 === Object.keys(elided).length ? {} : { elided }),
|
|
207
|
-
...(
|
|
214
|
+
...(shiftInWindow === undefined ? {} : { layoutShift: shiftInWindow }),
|
|
208
215
|
longTasks,
|
|
209
216
|
...(true === context.stateUnwatched ? { stateUnwatched: true } : {}),
|
|
210
217
|
...(true === context.truncated ? { truncated: true } : {}),
|
|
@@ -4,8 +4,20 @@
|
|
|
4
4
|
* perspective a flow that drifted no longer behaves, so it must not read as green. The actionable
|
|
5
5
|
* "why" is lifted from the replay's decision envelope (or its error) into failureReason.
|
|
6
6
|
*/
|
|
7
|
-
import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
|
|
7
|
+
import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult, type RunStepResult } from '@reticlehq/core';
|
|
8
8
|
/** OK → PASS; UNVERIFIABLE → SKIPPED; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
9
9
|
export declare function runFlowStatusOf(status: ReplayStatus): RunFlowStatus;
|
|
10
|
+
/** One step as synced: what it did, where, and whether it held. */
|
|
11
|
+
/**
|
|
12
|
+
* A run step with how far a sequence got. Kept out of the shared run schema on purpose: that schema
|
|
13
|
+
* ships in the page SDK, where every byte counts, and only the CLI score in this same process reads
|
|
14
|
+
* the field. A schema reader strips it, which loses nothing it needs.
|
|
15
|
+
*/
|
|
16
|
+
export interface ScoredRunStep extends RunStepResult {
|
|
17
|
+
subSteps?: {
|
|
18
|
+
passed: number;
|
|
19
|
+
total: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
10
22
|
/** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
|
|
11
23
|
export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number, flow?: FlowFile): RunFlowResult;
|
|
@@ -15,7 +15,6 @@ export function runFlowStatusOf(status) {
|
|
|
15
15
|
return RunFlowStatus.FAIL;
|
|
16
16
|
}
|
|
17
17
|
const clip = (text) => text.length > RUN_TEXT_MAX ? `${text.slice(0, RUN_TEXT_MAX - 1)}…` : text;
|
|
18
|
-
/** One step as synced: what it did, where, and whether it held. */
|
|
19
18
|
function stepResultOf(step) {
|
|
20
19
|
return {
|
|
21
20
|
step: step.step,
|
|
@@ -26,6 +25,7 @@ function stepResultOf(step) {
|
|
|
26
25
|
...(step.consequence === undefined ? {} : { consequence: clip(step.consequence) }),
|
|
27
26
|
...(step.error === undefined ? {} : { error: clip(step.error) }),
|
|
28
27
|
...(step.drift === undefined ? {} : { drift: step.drift.reasonKind }),
|
|
28
|
+
...(step.subSteps === undefined ? {} : { subSteps: step.subSteps }),
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
@@ -10,12 +10,26 @@
|
|
|
10
10
|
* same artifact byte-for-byte.
|
|
11
11
|
*/
|
|
12
12
|
import { reportRunTelemetry } from '../../telemetry/run-telemetry.js';
|
|
13
|
-
import { boundFlowName, RunFlowStatus, VerifyPhase, } from '@reticlehq/core';
|
|
13
|
+
import { boundFlowName, RunFlowStatus, VerifyPhase, ReplayStatus, } from '@reticlehq/core';
|
|
14
14
|
import { buildVerificationRun, } from './artifact/build-verification-run.js';
|
|
15
15
|
import { mapReplayToFlowResult } from './artifact/replay-mapping.js';
|
|
16
16
|
import { buildRepairPackets } from './repair-prompt.js';
|
|
17
17
|
import { withShotRun } from './default-run-id.js';
|
|
18
18
|
import { buildRisks, classifyChangedFiles, } from './risk-classify.js';
|
|
19
|
+
/**
|
|
20
|
+
* A flow whose replay threw (its browser session was lost, the app stopped answering) proves
|
|
21
|
+
* nothing, and it must not take the other flows of the suite down with it. It is graded as a replay
|
|
22
|
+
* that could not be completed: skipped, never a pass, with the error as the reason.
|
|
23
|
+
*/
|
|
24
|
+
function replayThatCouldNotRun(name, error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
status: ReplayStatus.UNVERIFIABLE,
|
|
29
|
+
steps: [],
|
|
30
|
+
unverifiable: { reason: `the replay stopped before it could be graded: ${message}` },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
19
33
|
export class ReticleRunner {
|
|
20
34
|
#port;
|
|
21
35
|
constructor(port) {
|
|
@@ -58,7 +72,7 @@ export class ReticleRunner {
|
|
|
58
72
|
name: boundFlowName(name),
|
|
59
73
|
at: start,
|
|
60
74
|
});
|
|
61
|
-
const replay = await withShotRun(runId, () => this.#port.replayFlow(name));
|
|
75
|
+
const replay = await withShotRun(runId, () => this.#port.replayFlow(name)).catch((error) => replayThatCouldNotRun(name, error));
|
|
62
76
|
replays.push(replay);
|
|
63
77
|
const mapped = mapReplayToFlowResult(replay, this.#port.now() - start);
|
|
64
78
|
flows.push(mapped);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Verified, type JournalAction, type RunReport } from '@reticlehq/core';
|
|
2
|
+
/** A run with no agent call for this long is dropped. A working agent calls much more often. */
|
|
3
|
+
export declare const RUN_IDLE_LIMIT_MS: number;
|
|
2
4
|
export declare class RunLedger {
|
|
3
5
|
#private;
|
|
4
|
-
/** An agent call starts. The first
|
|
6
|
+
/** An agent call starts. The agent's first call starts its run. A run idle too long is dropped. */
|
|
5
7
|
begin(now: number, owner: string): void;
|
|
6
8
|
/** An agent call ended. The first one that left a verdict sets the time to the first verdict. */
|
|
7
|
-
after(now: number): void;
|
|
9
|
+
after(now: number, owner?: string): void;
|
|
8
10
|
/** A journal record from the agent's own call. A record with no verdict is not a claim. */
|
|
9
11
|
verdict(action: JournalAction): void;
|
|
10
12
|
flowSaved(name: string): void;
|
|
@@ -13,9 +15,9 @@ export declare class RunLedger {
|
|
|
13
15
|
memoryWritten(count?: number): void;
|
|
14
16
|
/** Notes given to the agent. Each one counts once, however often it is shown. */
|
|
15
17
|
memoryRead(notes: readonly string[]): void;
|
|
16
|
-
isEmpty(): boolean;
|
|
17
|
-
/** The report, without the cloud line (the caller knows the project).
|
|
18
|
-
take(now: number): RunReport;
|
|
18
|
+
isEmpty(owner?: string): boolean;
|
|
19
|
+
/** The owner's report, without the cloud line (the caller knows the project). Ends that run. */
|
|
20
|
+
take(now: number, owner?: string): RunReport;
|
|
19
21
|
}
|
|
20
22
|
/** The daemon's ledger. */
|
|
21
23
|
export declare const runLedger: RunLedger;
|