@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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's link to a running daemon: an MCP client over the daemon's HTTP/SSE transport, the lease
|
|
3
|
+
* calls that open and close a tab, and the readers of a tool result.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `adhoc-verdict.ts` so a caller that needs only the link (`reticle compare`, and the
|
|
6
|
+
* `reticle_verify { action: "compare" }` tool that runs the same compare) does not reach the
|
|
7
|
+
* browser launcher, and through it the installer.
|
|
8
|
+
*/
|
|
9
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
11
|
+
import { LOOPBACK_HOST, MCP_SSE_PATH, ReticleTool, asString as asText } from '@reticlehq/core';
|
|
12
|
+
import { CLI_CLIENT_NAME } from '../../../judgement/runs/verification-sync.js';
|
|
13
|
+
import { TOKEN_QUERY_PARAM } from '../../../portal/bridge/token-auth.js';
|
|
14
|
+
import { LEASE_KEPT_SENTENCE, LEASE_RELEASED_SENTENCE } from '../../../surface/tools/lease-hint.js';
|
|
15
|
+
/** The lease tool is reached through `reticle_run`, the route that works on every tool profile. */
|
|
16
|
+
const LeaseAction = { ACQUIRE: 'acquire', RELEASE: 'release' };
|
|
17
|
+
/**
|
|
18
|
+
* Generous on purpose: on a machine with no Chrome, Edge or Playwright Chromium the FIRST lease
|
|
19
|
+
* downloads Chromium before it can open anything (see launch-chromium), which outlives the MCP
|
|
20
|
+
* client's 60s default and would report a timeout for a lease that was about to succeed.
|
|
21
|
+
*/
|
|
22
|
+
const LEASE_TIMEOUT_MS = 300_000;
|
|
23
|
+
export const leaseFailedLine = (url) => `could not open ${url} in a Reticle browser, so nothing was checked:`;
|
|
24
|
+
/**
|
|
25
|
+
* Why a lease's tab is not ready: its `notReadyReason`, then its `hint`, when it gave them. The CLI
|
|
26
|
+
* released the lease, so the hint's kept-lease sentence becomes the released-lease sentence.
|
|
27
|
+
*/
|
|
28
|
+
function notReadyLine(report) {
|
|
29
|
+
const reason = asText(report?.['notReadyReason']);
|
|
30
|
+
const hint = asText(report?.['hint'])?.replace(LEASE_KEPT_SENTENCE, LEASE_RELEASED_SENTENCE);
|
|
31
|
+
const head = reason === undefined ? 'the tab did not connect.' : `the tab did not connect (${reason}).`;
|
|
32
|
+
return hint === undefined ? head : `${head} ${hint}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Open `url` in a Reticle-leased browser unconditionally: `{ leased }` names the lease, which the
|
|
36
|
+
* caller hands to `releaseLease`; `{ failed }` is the lease's own refusal, as lines to print.
|
|
37
|
+
* `init`'s connect proof on a machine with no system browser takes this path directly.
|
|
38
|
+
*/
|
|
39
|
+
export async function acquireLease(caller, url,
|
|
40
|
+
/** Open it in a window somebody can watch, whatever the daemon was started as. */
|
|
41
|
+
headed,
|
|
42
|
+
/** The `.reticle` the lease's runs belong in: the caller's project, not wherever the page resolves. */
|
|
43
|
+
root) {
|
|
44
|
+
const ask = (withRoot) => caller.call(ReticleTool.RUN, {
|
|
45
|
+
tool: ReticleTool.LEASE,
|
|
46
|
+
args: {
|
|
47
|
+
action: LeaseAction.ACQUIRE,
|
|
48
|
+
url,
|
|
49
|
+
...(true === headed ? { headed } : {}),
|
|
50
|
+
...(withRoot && root !== undefined ? { root } : {}),
|
|
51
|
+
},
|
|
52
|
+
}, LEASE_TIMEOUT_MS);
|
|
53
|
+
let acquired = await ask(true);
|
|
54
|
+
// A daemon from an older release does not know `root` and refuses the whole call, so the page
|
|
55
|
+
// was never opened. Ask once more without it: the lease works, only its runs land where that
|
|
56
|
+
// daemon puts them.
|
|
57
|
+
if (root !== undefined && REFUSES_ROOT.test(refusalOf(acquired) ?? '')) {
|
|
58
|
+
acquired = await ask(false);
|
|
59
|
+
}
|
|
60
|
+
const report = verdictOf(acquired, 'sessionId');
|
|
61
|
+
const sessionId = report?.['sessionId'];
|
|
62
|
+
// `ready: false` still carries a sessionId, but no session is registered under it. Driving it
|
|
63
|
+
// answered "names no session" and hid the cause the lease gave. Only an explicit false counts.
|
|
64
|
+
if ('string' === typeof sessionId && false === report?.['ready']) {
|
|
65
|
+
await releaseLease(caller, sessionId);
|
|
66
|
+
return { failed: [leaseFailedLine(url), notReadyLine(report)] };
|
|
67
|
+
}
|
|
68
|
+
// `zeroInstall`: the page never dialled in, so the lease supplied Reticle's own reader.
|
|
69
|
+
if ('string' === typeof sessionId) {
|
|
70
|
+
return { leased: sessionId, zeroInstall: true === report?.['zeroInstall'] };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
failed: [leaseFailedLine(url), refusalOf(acquired) ?? JSON.stringify(acquired)],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** The lease refusal an older daemon answers when it does not know the `root` argument. */
|
|
77
|
+
const REFUSES_ROOT = /unknown parameter[^\n]*\broot\b/i;
|
|
78
|
+
/** What the lease said no with: the `error` of its payload, else the refusal text. */
|
|
79
|
+
function refusalOf(acquired) {
|
|
80
|
+
const why = verdictOf(acquired, 'error')?.['error'];
|
|
81
|
+
return 'string' === typeof why ? why : refusalText(acquired);
|
|
82
|
+
}
|
|
83
|
+
/** Hand back a lease `leaseIfNoTab` opened. Best-effort: an expired lease is already gone. */
|
|
84
|
+
export async function releaseLease(caller, sessionId) {
|
|
85
|
+
await caller
|
|
86
|
+
.call(ReticleTool.RUN, {
|
|
87
|
+
tool: ReticleTool.LEASE,
|
|
88
|
+
args: { action: LeaseAction.RELEASE, sessionId },
|
|
89
|
+
})
|
|
90
|
+
.catch(() => undefined);
|
|
91
|
+
}
|
|
92
|
+
export function endpointFor(port, token) {
|
|
93
|
+
const url = new URL(`http://${LOOPBACK_HOST}:${String(port)}${MCP_SSE_PATH}`);
|
|
94
|
+
if (token !== undefined && token.length > 0) {
|
|
95
|
+
url.searchParams.set(TOKEN_QUERY_PARAM, token);
|
|
96
|
+
}
|
|
97
|
+
return url;
|
|
98
|
+
}
|
|
99
|
+
export async function connectOverSse(endpoint) {
|
|
100
|
+
const client = new Client({ name: CLI_CLIENT_NAME, version: '1' }, { capabilities: {} });
|
|
101
|
+
await client.connect(new SSEClientTransport(endpoint));
|
|
102
|
+
return {
|
|
103
|
+
call: async (name, args, timeoutMs) => await client.callTool({ name, arguments: args }, undefined, timeoutMs === undefined ? undefined : { timeout: timeoutMs }),
|
|
104
|
+
close: async () => {
|
|
105
|
+
await client.close();
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The report object an MCP tool result carries, or undefined when the shape is not one.
|
|
111
|
+
*
|
|
112
|
+
* `field` is the key that makes a payload the report this caller wants: `verified` for a one-shot
|
|
113
|
+
* verdict, `status` for a suite. It is a parameter rather than a fixed name because the suite path
|
|
114
|
+
* reproduced this function's own incident the moment it reused it — the headline printed
|
|
115
|
+
* `unverifiable` while the JSON under it said `"status":"fail"` with 74 failing flows, because a
|
|
116
|
+
* suite report has no `verified` key and the text reader would only accept one that did.
|
|
117
|
+
*/
|
|
118
|
+
export function verdictOf(result, field = 'verified') {
|
|
119
|
+
const structured = result?.structuredContent;
|
|
120
|
+
if ('object' === typeof structured && null !== structured) {
|
|
121
|
+
return structured;
|
|
122
|
+
}
|
|
123
|
+
// A daemon that answers with the verdict as TEXT left this undefined, so the headline read
|
|
124
|
+
// `verified: unknown` while the JSON printed underneath it said `"verified":"no"` — one response
|
|
125
|
+
// giving two answers to the same question. The text is the same object, so read it.
|
|
126
|
+
return verdictFromText(result, field);
|
|
127
|
+
}
|
|
128
|
+
/** The report object inside an MCP text part, when the result carried it there instead. */
|
|
129
|
+
function verdictFromText(result, field) {
|
|
130
|
+
const content = result?.content;
|
|
131
|
+
if (!Array.isArray(content)) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
for (const part of content) {
|
|
135
|
+
const text = part.text;
|
|
136
|
+
if ('string' !== typeof text) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(text);
|
|
141
|
+
// Only a shape that actually carries a verdict — anything else stays `unknown`, which is the
|
|
142
|
+
// honest answer for a result this function could not read.
|
|
143
|
+
if ('object' === typeof parsed && null !== parsed && field in parsed) {
|
|
144
|
+
return parsed;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* Not JSON; the next part may still be. */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The tool's own words when it refused, rather than the envelope carrying them.
|
|
155
|
+
*
|
|
156
|
+
* A refusal from these tools is the most useful text they produce — it names the cause and the next
|
|
157
|
+
* action — and printing the MCP result object around it buries that in JSON escaping. The reader is
|
|
158
|
+
* at a terminal; give them the sentence.
|
|
159
|
+
*/
|
|
160
|
+
export function refusalText(result) {
|
|
161
|
+
const r = result;
|
|
162
|
+
if (true !== r?.isError || !Array.isArray(r.content)) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const text = r.content
|
|
166
|
+
.map((part) => part.text)
|
|
167
|
+
.filter((t) => 'string' === typeof t)
|
|
168
|
+
.join('\n');
|
|
169
|
+
if (0 === text.length) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
// The tools answer with a JSON envelope whose `error` is the sentence; unwrap when it is one.
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(text);
|
|
175
|
+
const inner = parsed?.error;
|
|
176
|
+
return 'string' === typeof inner ? inner : text;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return text;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** The payload of a tool result: its structured content, or the JSON object in its text. */
|
|
183
|
+
export function payloadOf(result) {
|
|
184
|
+
const structured = result?.structuredContent;
|
|
185
|
+
if ('object' === typeof structured && null !== structured) {
|
|
186
|
+
return structured;
|
|
187
|
+
}
|
|
188
|
+
const content = result?.content;
|
|
189
|
+
for (const part of Array.isArray(content) ? content : []) {
|
|
190
|
+
const text = part.text;
|
|
191
|
+
if ('string' !== typeof text) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(text);
|
|
196
|
+
if ('object' === typeof parsed && null !== parsed) {
|
|
197
|
+
return parsed;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Not JSON; the next part may be.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
@@ -48,6 +48,11 @@ export interface ChromiumProbe {
|
|
|
48
48
|
* `Microsoft Edge`) — see launch-chromium. Present only when `exists` is false and one was found.
|
|
49
49
|
*/
|
|
50
50
|
fallback?: string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Where Reticle looked for an installed Chrome or Edge when neither Playwright's build nor a
|
|
53
|
+
* fallback browser was found.
|
|
54
|
+
*/
|
|
55
|
+
fallbackPaths?: readonly string[] | undefined;
|
|
51
56
|
/** `executablePath` came from RETICLE_CHROMIUM_PATH, so the bundled build is not in play at all. */
|
|
52
57
|
configured?: boolean | undefined;
|
|
53
58
|
/** Why that configured executable cannot be launched. Present only when `exists` is false. */
|
|
@@ -118,7 +123,7 @@ export declare function parseChromiumRevision(executablePath: string): {
|
|
|
118
123
|
* `executablePath()` resolves the revision the daemon will actually launch AND the root it will look
|
|
119
124
|
* in, which is why the probe never reads `PLAYWRIGHT_BROWSERS_PATH` itself.
|
|
120
125
|
*/
|
|
121
|
-
export declare function probeChromium(): Promise<ChromiumProbe>;
|
|
126
|
+
export declare function probeChromium(exists?: (path: string) => boolean): Promise<ChromiumProbe>;
|
|
122
127
|
/** The bundled playwright's version, or undefined if it cannot be read (the hint then goes unpinned). */
|
|
123
128
|
export declare function bundledPlaywrightVersion(): string | undefined;
|
|
124
129
|
/**
|
|
@@ -173,7 +173,7 @@ function installedRevisions(root) {
|
|
|
173
173
|
* `executablePath()` resolves the revision the daemon will actually launch AND the root it will look
|
|
174
174
|
* in, which is why the probe never reads `PLAYWRIGHT_BROWSERS_PATH` itself.
|
|
175
175
|
*/
|
|
176
|
-
export async function probeChromium() {
|
|
176
|
+
export async function probeChromium(exists = existsSync) {
|
|
177
177
|
const searched = playwrightResolutionRoots();
|
|
178
178
|
try {
|
|
179
179
|
const { chromium } = await import('playwright');
|
|
@@ -181,14 +181,14 @@ export async function probeChromium() {
|
|
|
181
181
|
const parsed = parseChromiumRevision(executablePath);
|
|
182
182
|
return {
|
|
183
183
|
executablePath,
|
|
184
|
-
exists:
|
|
184
|
+
exists: exists(executablePath),
|
|
185
185
|
playwrightVersion: bundledPlaywrightVersion(),
|
|
186
186
|
...(null === parsed
|
|
187
187
|
? {}
|
|
188
188
|
: {
|
|
189
189
|
wantedRevision: parsed.revision,
|
|
190
190
|
browsersRoot: parsed.root,
|
|
191
|
-
installedRevisions: installedRevisions(parsed.root),
|
|
191
|
+
installedRevisions: exists === existsSync ? installedRevisions(parsed.root) : [],
|
|
192
192
|
}),
|
|
193
193
|
};
|
|
194
194
|
}
|
|
@@ -248,6 +248,9 @@ export function bundledPlaywrightVersion() {
|
|
|
248
248
|
export function chromiumPreflightRefusal(probe) {
|
|
249
249
|
if (probe.exists)
|
|
250
250
|
return undefined;
|
|
251
|
+
if (undefined !== probe.fallback) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
251
254
|
if (true === probe.configured && probe.executablePath !== undefined) {
|
|
252
255
|
return configuredChromiumRefusal(probe.executablePath, probe.problem ?? ChromiumPathProblem.MISSING);
|
|
253
256
|
}
|
|
@@ -281,16 +284,19 @@ export function chromiumHint(probe) {
|
|
|
281
284
|
const where = 0 === searched.length ? '' : ` — looked in ${searched.join(', ')}`;
|
|
282
285
|
return `✗ the playwright package is not installed${where}; run: ${command}`;
|
|
283
286
|
}
|
|
287
|
+
const fallbackPaths = probe.fallbackPaths ?? [];
|
|
284
288
|
const present = probe.installedRevisions ?? [];
|
|
285
289
|
if (present.length > 0 && probe.wantedRevision !== undefined) {
|
|
286
290
|
// The reported machine. Every fact here is one the reader could not otherwise get: what is
|
|
287
291
|
// wanted, what is there, and where "there" is. `npx playwright install chromium` unpinned adds
|
|
288
292
|
// a build to this list and changes nothing, which is what happened.
|
|
293
|
+
const fallbackWhere = 0 === fallbackPaths.length ? '' : `; also looked for ${fallbackPaths.join(', ')}`;
|
|
289
294
|
return (`✗ wrong revision — the bundled playwright wants ${probe.wantedRevision}; ` +
|
|
290
|
-
`${probe.browsersRoot ?? ''} holds ${present.join(', ')}. run: ${command}`);
|
|
295
|
+
`${probe.browsersRoot ?? ''} holds ${present.join(', ')}${fallbackWhere}. run: ${command}`);
|
|
291
296
|
}
|
|
292
297
|
// Naming the path is what turns "missing" from a verdict into evidence: a reader with browsers on
|
|
293
298
|
// disk can see immediately whether this is a missing install or a lookup pointed somewhere else
|
|
294
299
|
// (PLAYWRIGHT_BROWSERS_PATH, a different browsers root, a revision bump).
|
|
295
|
-
|
|
300
|
+
const lookedFor = [probe.executablePath, ...fallbackPaths].join(', ');
|
|
301
|
+
return `✗ missing — looked for ${lookedFor}; run: ${command}`;
|
|
296
302
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* at the call site.
|
|
15
15
|
*/
|
|
16
16
|
import { describeSkew } from '../../version/version-skew.js';
|
|
17
|
+
import { mcpConnectionHint } from '../answers/mcp-connection-hint.js';
|
|
17
18
|
import { DoctorRow, doctorRow } from './doctor-rows.js';
|
|
18
19
|
/**
|
|
19
20
|
* `reticle stop`, not `reticle kill`.
|
|
@@ -34,14 +35,16 @@ const FIX = 'Restart it so both ends are the same build: `reticle stop`, then le
|
|
|
34
35
|
*/
|
|
35
36
|
export function daemonLine(port, pid, peer, self) {
|
|
36
37
|
const parts = [];
|
|
37
|
-
if (null !== pid)
|
|
38
|
+
if (null !== pid) {
|
|
38
39
|
parts.push(`pid ${String(pid)}`);
|
|
39
|
-
|
|
40
|
+
}
|
|
41
|
+
if (peer.version !== undefined) {
|
|
40
42
|
parts.push(`v${peer.version}`);
|
|
43
|
+
}
|
|
41
44
|
const detail = 0 === parts.length ? '' : ` (${parts.join(', ')})`;
|
|
42
45
|
const skew = describeSkew({ what: 'the daemon on this port', version: peer.version, contract: peer.contract, fix: FIX }, self);
|
|
43
46
|
const line = {
|
|
44
|
-
text: doctorRow(DoctorRow.DAEMON, `✓ running on :${String(port)}${detail}`),
|
|
47
|
+
text: doctorRow(DoctorRow.DAEMON, `✓ running on :${String(port)}${detail}. ${mcpConnectionHint(port)}`),
|
|
45
48
|
};
|
|
46
49
|
return skew === undefined ? line : { ...line, skew };
|
|
47
50
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle doctor --fix`: repair a half-done install, then say what still needs a person.
|
|
3
|
+
*
|
|
4
|
+
* A repair changes only Reticle's own state: its pid file, its own stuck daemon, its own Chromium,
|
|
5
|
+
* and its entry in an agent's MCP config. A row that needs a change to the user's app, a process
|
|
6
|
+
* that Reticle cannot prove is its daemon, or an action only the user can do, is never changed
|
|
7
|
+
* here. Its row already names the step, and this prints that step.
|
|
8
|
+
*
|
|
9
|
+
* Each repair checks again before it acts, so a second run finds nothing to do and changes nothing.
|
|
10
|
+
*/
|
|
11
|
+
import { DoctorRow } from './doctor-rows.js';
|
|
12
|
+
type DoctorRowLabel = (typeof DoctorRow)[keyof typeof DoctorRow];
|
|
13
|
+
/** One safe repair. `needed` reads the machine; `apply` changes only Reticle's own state. */
|
|
14
|
+
export interface DoctorRepair {
|
|
15
|
+
/** The doctor row this repair makes green. */
|
|
16
|
+
readonly row: DoctorRowLabel;
|
|
17
|
+
/** What the repair is about, in a few words, so two repairs on one row read apart. */
|
|
18
|
+
readonly what: string;
|
|
19
|
+
/** The manual step, printed when the repair is not possible or does not take. */
|
|
20
|
+
readonly step: string;
|
|
21
|
+
needed(): Promise<boolean>;
|
|
22
|
+
apply(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** What `--fix` says about each repair. */
|
|
25
|
+
export declare const FixOutcome: {
|
|
26
|
+
readonly FIXED: "fixed";
|
|
27
|
+
readonly NOTHING: "nothing to do";
|
|
28
|
+
readonly STILL_NEEDS: "still needs";
|
|
29
|
+
};
|
|
30
|
+
/** The last line of a plain `doctor` run, when `--fix` can repair `count` rows. */
|
|
31
|
+
export declare function fixFooter(count: number): string | undefined;
|
|
32
|
+
/** How many repairs have work to do now. */
|
|
33
|
+
export declare function countNeeded(repairs: readonly DoctorRepair[]): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Run doctor, apply the needed repairs in the given order, run doctor again, and print one line for
|
|
36
|
+
* each repair and one for each row that still fails. True when no row fails after the repairs.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runDoctorFix(deps: {
|
|
39
|
+
/** Runs doctor and returns its rows. Prints them only when `show` is true. */
|
|
40
|
+
doctor: (show: boolean) => Promise<string[]>;
|
|
41
|
+
repairs: readonly DoctorRepair[];
|
|
42
|
+
print: (line: string) => void;
|
|
43
|
+
}): Promise<boolean>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle doctor --fix`: repair a half-done install, then say what still needs a person.
|
|
3
|
+
*
|
|
4
|
+
* A repair changes only Reticle's own state: its pid file, its own stuck daemon, its own Chromium,
|
|
5
|
+
* and its entry in an agent's MCP config. A row that needs a change to the user's app, a process
|
|
6
|
+
* that Reticle cannot prove is its daemon, or an action only the user can do, is never changed
|
|
7
|
+
* here. Its row already names the step, and this prints that step.
|
|
8
|
+
*
|
|
9
|
+
* Each repair checks again before it acts, so a second run finds nothing to do and changes nothing.
|
|
10
|
+
*/
|
|
11
|
+
import { DoctorRow, doctorRow, LABEL_COLUMN } from './doctor-rows.js';
|
|
12
|
+
/** What `--fix` says about each repair. */
|
|
13
|
+
export const FixOutcome = {
|
|
14
|
+
FIXED: 'fixed',
|
|
15
|
+
NOTHING: 'nothing to do',
|
|
16
|
+
STILL_NEEDS: 'still needs',
|
|
17
|
+
};
|
|
18
|
+
/** The mark doctor puts on a row that fails. */
|
|
19
|
+
const FAIL_MARK = '✗';
|
|
20
|
+
const FIX_HEADING = 'reticle doctor --fix';
|
|
21
|
+
/** The last line of a plain `doctor` run, when `--fix` can repair `count` rows. */
|
|
22
|
+
export function fixFooter(count) {
|
|
23
|
+
if (0 === count) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return `Run \`reticle doctor --fix\` to repair ${String(count)} of these.`;
|
|
27
|
+
}
|
|
28
|
+
/** How many repairs have work to do now. */
|
|
29
|
+
export async function countNeeded(repairs) {
|
|
30
|
+
let count = 0;
|
|
31
|
+
for (const repair of repairs) {
|
|
32
|
+
if (await repair.needed()) {
|
|
33
|
+
count++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return count;
|
|
37
|
+
}
|
|
38
|
+
/** The row label at the head of a doctor line, or undefined for a line that is not a row. */
|
|
39
|
+
function rowLabelOf(line) {
|
|
40
|
+
const label = line.slice(0, LABEL_COLUMN + 2).trim();
|
|
41
|
+
return 0 === label.length ? undefined : label;
|
|
42
|
+
}
|
|
43
|
+
async function applyOne(repair) {
|
|
44
|
+
if (!(await repair.needed())) {
|
|
45
|
+
return FixOutcome.NOTHING;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await repair.apply();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// The check below decides. A repair that threw and a repair that did nothing say the same thing.
|
|
52
|
+
}
|
|
53
|
+
if (await repair.needed()) {
|
|
54
|
+
return `${FixOutcome.STILL_NEEDS}: ${repair.step}`;
|
|
55
|
+
}
|
|
56
|
+
return FixOutcome.FIXED;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Run doctor, apply the needed repairs in the given order, run doctor again, and print one line for
|
|
60
|
+
* each repair and one for each row that still fails. True when no row fails after the repairs.
|
|
61
|
+
*/
|
|
62
|
+
export async function runDoctorFix(deps) {
|
|
63
|
+
await deps.doctor(true);
|
|
64
|
+
const results = [];
|
|
65
|
+
const stillFailing = new Set();
|
|
66
|
+
for (const repair of deps.repairs) {
|
|
67
|
+
const outcome = await applyOne(repair);
|
|
68
|
+
if (outcome.startsWith(FixOutcome.STILL_NEEDS)) {
|
|
69
|
+
stillFailing.add(repair.row);
|
|
70
|
+
}
|
|
71
|
+
results.push(doctorRow(repair.row, `${repair.what}: ${outcome}`));
|
|
72
|
+
}
|
|
73
|
+
const after = await deps.doctor(false);
|
|
74
|
+
const failures = after.filter((line) => line.includes(FAIL_MARK));
|
|
75
|
+
deps.print('');
|
|
76
|
+
deps.print(FIX_HEADING);
|
|
77
|
+
for (const line of results) {
|
|
78
|
+
deps.print(line);
|
|
79
|
+
}
|
|
80
|
+
for (const line of failures) {
|
|
81
|
+
// A repair that did not take already printed its step for this row.
|
|
82
|
+
const label = rowLabelOf(line);
|
|
83
|
+
if (label !== undefined && stillFailing.has(label)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
deps.print(` ${FixOutcome.STILL_NEEDS}: ${line.trim()}`);
|
|
87
|
+
}
|
|
88
|
+
return 0 === failures.length;
|
|
89
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** One agent's `reticle` entry, as its config holds it. */
|
|
2
|
+
export interface RegisteredEntry {
|
|
3
|
+
readonly client: string;
|
|
4
|
+
readonly entry: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface ConfigIo {
|
|
7
|
+
readFile(path: string): string | null;
|
|
8
|
+
exists(path: string): boolean;
|
|
9
|
+
homeDir(): string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The `reticle` entry of each agent config here: Claude Code's user, local and project scopes, and
|
|
13
|
+
* every file-backed client that `init` registers with.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registeredEntries(io: ConfigIo, cwd: string): RegisteredEntry[];
|
|
16
|
+
/**
|
|
17
|
+
* The row text, or undefined when every registered launcher exists and no entry is stale.
|
|
18
|
+
*
|
|
19
|
+
* @param floatingNote `claudeStaleEntryNote`, when Claude Code's user-scope entry is the floating
|
|
20
|
+
* npx form. `setup mcp` keeps that entry, so the row names the remove-then-add step.
|
|
21
|
+
*/
|
|
22
|
+
export declare function launcherLine(entries: readonly RegisteredEntry[], exists: (path: string) => boolean, floatingNote?: string): string | undefined;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `launcher` row of `reticle doctor`: an agent config starts Reticle from an installed copy
|
|
3
|
+
* (`node <path> mcp`), and that file is gone.
|
|
4
|
+
*
|
|
5
|
+
* `init` registers the installed bin when there is one, so a sandbox boot does not download the
|
|
6
|
+
* package again. When the package is removed later, the agent shows the server as failed and says
|
|
7
|
+
* no more. This row names the path and the command that writes a working entry. It prints only
|
|
8
|
+
* when a launcher is missing, like `version`.
|
|
9
|
+
*/
|
|
10
|
+
import { McpClient, RETICLE_NPM_PACKAGE, claudeReticleEntries, clientSpec, detectMcpClients, isReticleLauncher, } from '@reticlehq/init';
|
|
11
|
+
import { MCP_SERVER_NAME } from '@reticlehq/core';
|
|
12
|
+
const SETUP_MCP = `npx ${RETICLE_NPM_PACKAGE} setup mcp`;
|
|
13
|
+
/** `setup mcp` keeps an existing Claude Code entry, so that entry goes first. */
|
|
14
|
+
const CLAUDE_REMOVE = `claude mcp remove ${MCP_SERVER_NAME} -s user`;
|
|
15
|
+
const record = (value) => 'object' === typeof value && null !== value ? value : {};
|
|
16
|
+
/** Every string in `command` and `args`, in both the `{ command, args }` and the array shape. */
|
|
17
|
+
function strings(entry) {
|
|
18
|
+
const { command, args } = record(entry);
|
|
19
|
+
return [command, args].flat().filter((v) => 'string' === typeof v);
|
|
20
|
+
}
|
|
21
|
+
function parsed(text) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(text ?? '');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The `reticle` entry of each agent config here: Claude Code's user, local and project scopes, and
|
|
31
|
+
* every file-backed client that `init` registers with.
|
|
32
|
+
*/
|
|
33
|
+
export function registeredEntries(io, cwd) {
|
|
34
|
+
const claude = claudeReticleEntries(io, cwd).map((entry) => ({
|
|
35
|
+
client: McpClient.CLAUDE_CODE,
|
|
36
|
+
entry,
|
|
37
|
+
}));
|
|
38
|
+
const others = detectMcpClients(io).map((c) => ({
|
|
39
|
+
client: c.id,
|
|
40
|
+
entry: record(record(parsed(c.existing))[clientSpec(c.id).serversKey])[MCP_SERVER_NAME],
|
|
41
|
+
}));
|
|
42
|
+
return [...claude, ...others].filter((e) => e.entry !== undefined);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The row text, or undefined when every registered launcher exists and no entry is stale.
|
|
46
|
+
*
|
|
47
|
+
* @param floatingNote `claudeStaleEntryNote`, when Claude Code's user-scope entry is the floating
|
|
48
|
+
* npx form. `setup mcp` keeps that entry, so the row names the remove-then-add step.
|
|
49
|
+
*/
|
|
50
|
+
export function launcherLine(entries, exists, floatingNote) {
|
|
51
|
+
for (const { client, entry } of entries) {
|
|
52
|
+
const missing = strings(entry).find((s) => isReticleLauncher(s) && !exists(s));
|
|
53
|
+
if (missing === undefined) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const fix = McpClient.CLAUDE_CODE === client ? `${CLAUDE_REMOVE}, then ${SETUP_MCP}` : SETUP_MCP;
|
|
57
|
+
return `✗ ${client} starts Reticle from ${missing}, and that file does not exist. Run: ${fix}`;
|
|
58
|
+
}
|
|
59
|
+
return floatingNote === undefined ? undefined : `✗ ${McpClient.CLAUDE_CODE}: ${floatingNote}`;
|
|
60
|
+
}
|
|
@@ -40,6 +40,8 @@ export declare const DoctorRow: {
|
|
|
40
40
|
* verification." Every row in this list checks a COMPONENT; none checked a LINK.
|
|
41
41
|
*/
|
|
42
42
|
readonly AGENT_LINK: "agent link";
|
|
43
|
+
/** An agent config starts Reticle from an installed copy that is gone. Only then. */
|
|
44
|
+
readonly LAUNCHER: "launcher";
|
|
43
45
|
/** Is the APP in this folder wired, not just the tools. Printed by `doctor-project-line.ts`. */
|
|
44
46
|
readonly PROJECT: "project";
|
|
45
47
|
/**
|
|
@@ -68,6 +70,8 @@ export declare const DoctorRow: {
|
|
|
68
70
|
readonly PROMPTS: "prompts";
|
|
69
71
|
/** The daemon's background sync for this project: on or off, why, and the last push. */
|
|
70
72
|
readonly SYNC: "sync";
|
|
73
|
+
/** Which coding agents found on this machine have Reticle's MCP server registered. */
|
|
74
|
+
readonly MCP: "mcp";
|
|
71
75
|
};
|
|
72
76
|
type DoctorRowLabel = (typeof DoctorRow)[keyof typeof DoctorRow];
|
|
73
77
|
export declare const DOCTOR_ROW_LABELS: readonly DoctorRowLabel[];
|
|
@@ -40,6 +40,8 @@ export const DoctorRow = {
|
|
|
40
40
|
* verification." Every row in this list checks a COMPONENT; none checked a LINK.
|
|
41
41
|
*/
|
|
42
42
|
AGENT_LINK: 'agent link',
|
|
43
|
+
/** An agent config starts Reticle from an installed copy that is gone. Only then. */
|
|
44
|
+
LAUNCHER: 'launcher',
|
|
43
45
|
/** Is the APP in this folder wired, not just the tools. Printed by `doctor-project-line.ts`. */
|
|
44
46
|
PROJECT: 'project',
|
|
45
47
|
/**
|
|
@@ -68,6 +70,8 @@ export const DoctorRow = {
|
|
|
68
70
|
PROMPTS: 'prompts',
|
|
69
71
|
/** The daemon's background sync for this project: on or off, why, and the last push. */
|
|
70
72
|
SYNC: 'sync',
|
|
73
|
+
/** Which coding agents found on this machine have Reticle's MCP server registered. */
|
|
74
|
+
MCP: 'mcp',
|
|
71
75
|
};
|
|
72
76
|
export const DOCTOR_ROW_LABELS = Object.values(DoctorRow);
|
|
73
77
|
/** Indent every row carries, before the label. */
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { PortPresence } from '../daemon/binding/port-presence.js';
|
|
2
|
+
import { type StepReporter } from './setup-mcp-cli.js';
|
|
3
|
+
import { type PortHolder } from './ports/port-holder.js';
|
|
4
|
+
import { type ChromiumProbe } from './doctor/browser/chromium-hint.js';
|
|
5
|
+
import type { DoctorRepair } from './doctor/doctor-fix.js';
|
|
6
|
+
/** A pid file whose process is gone. `removePid` itself never removes the file of a live process. */
|
|
7
|
+
export declare function stalePidRepair(deps: {
|
|
8
|
+
readPid: () => number | null;
|
|
9
|
+
isAlive: (pid: number) => boolean;
|
|
10
|
+
removePid: () => void;
|
|
11
|
+
}): DoctorRepair;
|
|
12
|
+
/**
|
|
13
|
+
* A Reticle daemon that holds the port and does not answer. Only a holder whose command line is a
|
|
14
|
+
* Reticle daemon's qualifies; `stopWedgedDaemon` checks that again, after a grace period, before it
|
|
15
|
+
* sends a signal. A stranger and the agent's own `reticle mcp` proxy are never stopped.
|
|
16
|
+
*/
|
|
17
|
+
export declare function wedgedDaemonRepair(port: number, deps: {
|
|
18
|
+
presence: () => Promise<PortPresence>;
|
|
19
|
+
holder: () => PortHolder | null;
|
|
20
|
+
stop: () => Promise<boolean>;
|
|
21
|
+
}): DoctorRepair;
|
|
22
|
+
/**
|
|
23
|
+
* No browser that a launch can use. Only when Playwright's own build is missing or at the wrong
|
|
24
|
+
* revision and no installed Chrome or Edge stands in. A RETICLE_CHROMIUM_PATH the user set is never
|
|
25
|
+
* replaced, and an absent playwright package is not a download this can fix.
|
|
26
|
+
*/
|
|
27
|
+
export declare function chromiumRepair(deps: {
|
|
28
|
+
probe: () => Promise<ChromiumProbe>;
|
|
29
|
+
install: () => Promise<unknown>;
|
|
30
|
+
/** The bundled playwright's version, which pins the manual install command. */
|
|
31
|
+
playwrightVersion: string | undefined;
|
|
32
|
+
}): DoctorRepair;
|
|
33
|
+
/** Agents found on this machine that do not have Reticle's MCP server registered. */
|
|
34
|
+
export declare function mcpRepair(deps: {
|
|
35
|
+
gaps: () => readonly string[];
|
|
36
|
+
register: () => void;
|
|
37
|
+
}): DoctorRepair;
|
|
38
|
+
/**
|
|
39
|
+
* An agent config starts Reticle from an installed copy that no longer exists. `setup mcp`
|
|
40
|
+
* replaces Reticle's own entry in the JSON clients. It keeps an existing Claude Code entry, so for
|
|
41
|
+
* Claude Code the row still fails after the repair, and the step is the row's remove-then-add.
|
|
42
|
+
*/
|
|
43
|
+
export declare function launcherRepair(deps: {
|
|
44
|
+
/** The launcher row, or undefined when every registered launcher exists. */
|
|
45
|
+
line: () => string | undefined;
|
|
46
|
+
register: () => void;
|
|
47
|
+
}): DoctorRepair;
|
|
48
|
+
/** The launcher row for the agent configs of this machine and folder. */
|
|
49
|
+
export declare function readLauncherLine(cwd: string): string | undefined;
|
|
50
|
+
/** Which agents found here have Reticle, and which do not. */
|
|
51
|
+
export interface McpRegistration {
|
|
52
|
+
readonly missing: readonly string[];
|
|
53
|
+
readonly present: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/** The `mcp` doctor row. */
|
|
56
|
+
export declare function mcpRegistrationRow(registration: McpRegistration): string;
|
|
57
|
+
/**
|
|
58
|
+
* Ask `setupMcp` what it would do, and do none of it. Config writes and registration commands are
|
|
59
|
+
* recorded, not run; only the two `--version` probes run, because they say whether an agent is here.
|
|
60
|
+
* No telemetry: this is a check, not a registration.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readMcpRegistration(): McpRegistration;
|
|
63
|
+
/** The repairs for this machine and port, in the order `--fix` applies them. */
|
|
64
|
+
export declare function machineRepairs(port: number, reportStep: StepReporter): DoctorRepair[];
|