@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,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bare `reticle` in a folder that Reticle has not wired: the first verdict, with no agent, no
|
|
3
|
+
* `init` and no model.
|
|
4
|
+
*
|
|
5
|
+
* It finds the app (the folder's own dev server, or the one dev server that runs on this computer),
|
|
6
|
+
* starts the dev server when it does not run, opens the page in a Reticle browser with the
|
|
7
|
+
* zero-install reader, and runs a smoke check that cannot guess: the page loads, no console error,
|
|
8
|
+
* no failed request, and each same-origin link opens. Then it prints the verdict and the next step.
|
|
9
|
+
*
|
|
10
|
+
* The decisions are here, behind `FirstVerdictPorts`, so each one is a unit test. The browser, the
|
|
11
|
+
* daemon and the dev server are in `first-verdict-node.ts`.
|
|
12
|
+
*/
|
|
13
|
+
import { CRAWL_DEFAULTS, EventType, NetInitiator } from '@reticlehq/core';
|
|
14
|
+
import { OnboardingStop } from '@reticlehq/core/telemetry';
|
|
15
|
+
import { isAuthPath } from '../../surface/tools/lease-seed.js';
|
|
16
|
+
/** Bare `reticle` runs the first verdict only with no arguments, in a folder with no Reticle config. */
|
|
17
|
+
export function bareRunsFirstVerdict(argv, wired) {
|
|
18
|
+
return 0 === argv.length && !wired;
|
|
19
|
+
}
|
|
20
|
+
const LOCALHOST = 'http://localhost';
|
|
21
|
+
const localUrl = (port) => `${LOCALHOST}:${String(port)}`;
|
|
22
|
+
export const AppChoice = {
|
|
23
|
+
/** Check this url. */
|
|
24
|
+
USE: 'use',
|
|
25
|
+
/** Start the folder's dev command, then check what it serves. */
|
|
26
|
+
START: 'start',
|
|
27
|
+
/** Nothing to check: say why. */
|
|
28
|
+
NONE: 'none',
|
|
29
|
+
};
|
|
30
|
+
export const MSG_NO_APP = [
|
|
31
|
+
'Reticle found no web app here, and no dev server that runs on this computer.',
|
|
32
|
+
'Run this command in the folder of your web app, or start its dev server first.',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Which app to check. In an app folder, only that app: a server on another port can be another
|
|
36
|
+
* app, and a verdict on the wrong app is worse than none. Outside one, the one server that runs.
|
|
37
|
+
*/
|
|
38
|
+
export function chooseApp(facts) {
|
|
39
|
+
if (facts.devCommand !== undefined) {
|
|
40
|
+
const pinned = facts.devCommand.port;
|
|
41
|
+
if (pinned !== undefined && facts.pinnedServing) {
|
|
42
|
+
return { action: AppChoice.USE, url: localUrl(pinned) };
|
|
43
|
+
}
|
|
44
|
+
return { action: AppChoice.START };
|
|
45
|
+
}
|
|
46
|
+
const [only, ...others] = facts.running;
|
|
47
|
+
if (only !== undefined && 0 === others.length) {
|
|
48
|
+
return { action: AppChoice.USE, url: localUrl(only) };
|
|
49
|
+
}
|
|
50
|
+
if (only !== undefined) {
|
|
51
|
+
return {
|
|
52
|
+
action: AppChoice.NONE,
|
|
53
|
+
stop: OnboardingStop.NO_WEB_APP,
|
|
54
|
+
lines: [
|
|
55
|
+
'Reticle found more than one dev server, so it did not choose one:',
|
|
56
|
+
...facts.running.map((port) => ` ${localUrl(port)}`),
|
|
57
|
+
'Run this command in the folder of the app to check.',
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { action: AppChoice.NONE, stop: OnboardingStop.NO_WEB_APP, lines: MSG_NO_APP };
|
|
62
|
+
}
|
|
63
|
+
const ABORTED = /abort/i;
|
|
64
|
+
const text = (value) => 'string' === typeof value ? value : undefined;
|
|
65
|
+
const num = (value) => ('number' === typeof value ? value : undefined);
|
|
66
|
+
/** A request that failed: an error status, or no answer that the page did not cancel itself. */
|
|
67
|
+
function failure(e) {
|
|
68
|
+
if (EventType.NET_REQUEST !== e.type || NetInitiator.DOCUMENT === e.data['initiator']) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const where = `${text(e.data['method']) ?? 'GET'} ${text(e.data['url']) ?? ''}`;
|
|
72
|
+
const status = num(e.data['status']);
|
|
73
|
+
if (status !== undefined && status >= CRAWL_DEFAULTS.FAILED_STATUS) {
|
|
74
|
+
return `${where}: HTTP ${String(status)}`;
|
|
75
|
+
}
|
|
76
|
+
const error = text(e.data['error']);
|
|
77
|
+
if (0 === status && error !== undefined && !ABORTED.test(error)) {
|
|
78
|
+
return `${where}: ${error}`;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The facts of the page now on screen. Events of an earlier document are left out: the check
|
|
84
|
+
* reloads each page, and what the old document did is not this page's fault.
|
|
85
|
+
*/
|
|
86
|
+
export function pageFactsOf(url, events, nav) {
|
|
87
|
+
const loads = events.filter((e) => EventType.NET_REQUEST === e.type && NetInitiator.DOCUMENT === e.data['initiator']);
|
|
88
|
+
const load = loads.at(-1);
|
|
89
|
+
const documentId = load?.documentId ?? events.at(-1)?.documentId;
|
|
90
|
+
const current = events.filter((e) => documentId === undefined || e.documentId === undefined || e.documentId === documentId);
|
|
91
|
+
const documentStatus = num(load?.data['status']);
|
|
92
|
+
return {
|
|
93
|
+
url,
|
|
94
|
+
arrived: nav.arrived,
|
|
95
|
+
...(nav.landedOn === undefined ? {} : { landedOn: nav.landedOn }),
|
|
96
|
+
...(documentStatus === undefined ? {} : { documentStatus }),
|
|
97
|
+
consoleErrors: current
|
|
98
|
+
.filter((e) => EventType.CONSOLE_ERROR === e.type || EventType.ERROR_UNCAUGHT === e.type)
|
|
99
|
+
.map((e) => text(e.data['message']) ?? e.type),
|
|
100
|
+
failedRequests: current.flatMap((e) => {
|
|
101
|
+
const failed = failure(e);
|
|
102
|
+
return failed === undefined ? [] : [failed];
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A link that ends the signed-in session. Opening it would check the rest of the app signed out.
|
|
108
|
+
* ponytail: the same words the crawl leaves alone; `cli` does not reach `crawl`, so it is said here.
|
|
109
|
+
*/
|
|
110
|
+
const ENDS_SESSION = /\b(?:sign ?out|log ?out|sign ?off|log ?off)\b/i;
|
|
111
|
+
/** The most links one check opens. Each one is a page load. */
|
|
112
|
+
export const MAX_LINKS_OPENED = 8;
|
|
113
|
+
const WEB_PROTOCOLS = new Set(['http:', 'https:']);
|
|
114
|
+
/**
|
|
115
|
+
* The same-origin pages the links on `pageUrl` lead to, once each, without the page itself and
|
|
116
|
+
* without a link that ends the session. `found` counts them all, so a capped list says so.
|
|
117
|
+
*/
|
|
118
|
+
export function linksToOpen(pageUrl, links, max = MAX_LINKS_OPENED) {
|
|
119
|
+
const here = new URL(pageUrl);
|
|
120
|
+
const key = (u) => `${u.pathname.replace(/\/+$/, '')}${u.search}`;
|
|
121
|
+
const seen = new Set([key(here)]);
|
|
122
|
+
const pages = [];
|
|
123
|
+
for (const link of links) {
|
|
124
|
+
let target;
|
|
125
|
+
try {
|
|
126
|
+
target = new URL(link.href, here);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!WEB_PROTOCOLS.has(target.protocol) || target.origin !== here.origin) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ENDS_SESSION.test(link.name) || ENDS_SESSION.test(target.pathname.replace(/[/-]/g, ' '))) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (seen.has(key(target))) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
seen.add(key(target));
|
|
141
|
+
target.hash = '';
|
|
142
|
+
pages.push(target.href);
|
|
143
|
+
}
|
|
144
|
+
return { open: pages.slice(0, max), found: pages.length };
|
|
145
|
+
}
|
|
146
|
+
const PASS = ' ✓';
|
|
147
|
+
const FAIL = ' ✗';
|
|
148
|
+
const NOTE = ' -';
|
|
149
|
+
const pathOf = (url) => {
|
|
150
|
+
try {
|
|
151
|
+
const u = new URL(url);
|
|
152
|
+
return `${u.pathname}${u.search}`;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return url;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/** Why a page did not load, or undefined when it loaded. */
|
|
159
|
+
function notLoaded(page) {
|
|
160
|
+
// A page that went somewhere else (a redirect to a dashboard, or to sign in) did open.
|
|
161
|
+
if (!page.arrived && page.landedOn === undefined) {
|
|
162
|
+
return 'it did not load, or Reticle could not connect to it';
|
|
163
|
+
}
|
|
164
|
+
if (page.documentStatus !== undefined && page.documentStatus >= CRAWL_DEFAULTS.FAILED_STATUS) {
|
|
165
|
+
return `the server answered HTTP ${String(page.documentStatus)}`;
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
/** The faults of one page, one line each. */
|
|
170
|
+
function faultsOf(page, label) {
|
|
171
|
+
const missed = notLoaded(page);
|
|
172
|
+
return [
|
|
173
|
+
...(missed === undefined ? [] : [`${FAIL} ${label} did not open: ${missed}.`]),
|
|
174
|
+
...page.consoleErrors.map((message) => `${FAIL} Console error on ${label}: ${message}`),
|
|
175
|
+
...page.failedRequests.map((request) => `${FAIL} Failed request on ${label}: ${request}`),
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The verdict. "yes" only when the start page loaded with no console error and no failed request,
|
|
180
|
+
* and every link it opened did the same. A page that sends a visitor to sign in is a verdict too:
|
|
181
|
+
* the check is "yes" up to there, and it says what it could not reach.
|
|
182
|
+
*/
|
|
183
|
+
export function judgeSmoke(facts) {
|
|
184
|
+
const start = facts.start;
|
|
185
|
+
const landed = start.landedOn === undefined ? undefined : new URL(start.landedOn);
|
|
186
|
+
const signIn = landed !== undefined && isAuthPath(landed.pathname) && !isAuthPath(new URL(start.url).pathname);
|
|
187
|
+
const faults = [
|
|
188
|
+
...faultsOf(start, pathOf(start.url)),
|
|
189
|
+
...facts.links.flatMap((link) => faultsOf(link, pathOf(link.url))),
|
|
190
|
+
];
|
|
191
|
+
const lines = [];
|
|
192
|
+
if (signIn && landed !== undefined) {
|
|
193
|
+
lines.push(`${NOTE} The app sent the page to its sign-in page (${landed.pathname}). ` +
|
|
194
|
+
'Reticle verified up to here. It did not check the pages behind sign-in.');
|
|
195
|
+
}
|
|
196
|
+
const linksOk = facts.links.filter((link) => 0 === faultsOf(link, '').length).length;
|
|
197
|
+
if (0 === faults.length) {
|
|
198
|
+
lines.push(`${PASS} The page loads.`, `${PASS} No console error.`, `${PASS} No failed request.`);
|
|
199
|
+
}
|
|
200
|
+
lines.push(...faults);
|
|
201
|
+
if (0 === facts.linksFound) {
|
|
202
|
+
lines.push(`${NOTE} The page has no same-origin link, so no link was opened.`);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
lines.push(`${0 === faults.length ? PASS : NOTE} ${String(linksOk)} of ${String(facts.links.length)} ` +
|
|
206
|
+
`link(s) opened with no fault.`);
|
|
207
|
+
}
|
|
208
|
+
if (facts.links.length < facts.linksFound) {
|
|
209
|
+
lines.push(`${NOTE} Reticle opened ${String(facts.links.length)} of ${String(facts.linksFound)} ` +
|
|
210
|
+
'same-origin links. It did not check the others.');
|
|
211
|
+
}
|
|
212
|
+
const verified = 0 === faults.length ? 'yes' : 'no';
|
|
213
|
+
lines.push('yes' === verified
|
|
214
|
+
? `verified: yes — the smoke check passed on ${start.url}`
|
|
215
|
+
: `verified: no — ${String(faults.length)} fault(s) on ${start.url}`);
|
|
216
|
+
return { verified, lines };
|
|
217
|
+
}
|
|
218
|
+
export const MSG_NEXT_STEP = [
|
|
219
|
+
'Next: connect your coding agent, so it can verify each change it makes:',
|
|
220
|
+
' npx @reticlehq/server init',
|
|
221
|
+
];
|
|
222
|
+
const STEP_PROJECT_DETECTED = 'project_detected';
|
|
223
|
+
const STEP_APP_CONNECTED = 'app_connected';
|
|
224
|
+
const EXIT_OK = 0;
|
|
225
|
+
const EXIT_FAIL = 1;
|
|
226
|
+
/** Find the app, check it, print the verdict. Returns the exit code: 0 only for "verified: yes". */
|
|
227
|
+
export async function runFirstVerdict(ports) {
|
|
228
|
+
const found = await ports.findApp();
|
|
229
|
+
if ('stop' in found) {
|
|
230
|
+
for (const line of found.lines) {
|
|
231
|
+
ports.out(line);
|
|
232
|
+
}
|
|
233
|
+
const step = OnboardingStop.NO_WEB_APP === found.stop ? STEP_PROJECT_DETECTED : STEP_APP_CONNECTED;
|
|
234
|
+
ports.stop({ step, stop: found.stop });
|
|
235
|
+
return EXIT_FAIL;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
ports.out(`Reticle is checking ${found.url}. It installs nothing in your app.`);
|
|
239
|
+
let facts;
|
|
240
|
+
try {
|
|
241
|
+
facts = await ports.smoke(found.url);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
facts = { failed: [error instanceof Error ? error.message : String(error)] };
|
|
245
|
+
}
|
|
246
|
+
if ('failed' in facts) {
|
|
247
|
+
for (const line of [
|
|
248
|
+
'verified: unknown — Reticle could not check the page:',
|
|
249
|
+
...facts.failed,
|
|
250
|
+
]) {
|
|
251
|
+
ports.out(line);
|
|
252
|
+
}
|
|
253
|
+
ports.stop({ step: STEP_APP_CONNECTED, stop: OnboardingStop.APP_NOT_CONNECTED });
|
|
254
|
+
return EXIT_FAIL;
|
|
255
|
+
}
|
|
256
|
+
const verdict = judgeSmoke(facts);
|
|
257
|
+
for (const line of [...verdict.lines, '', ...MSG_NEXT_STEP]) {
|
|
258
|
+
ports.out(line);
|
|
259
|
+
}
|
|
260
|
+
return 'yes' === verdict.verified ? EXIT_OK : EXIT_FAIL;
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
found.release?.();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** The `reason` on a stop this command records, so a reader can tell it from an `init` stop. */
|
|
267
|
+
export const FIRST_VERDICT_REASON = 'first_verdict';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run the CLI whose major matches the SDK the project actually installed.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Every rule file `init` writes says `npx @reticlehq/server`, unpinned, which resolves the LATEST
|
|
5
|
+
* server. The agent's MCP entry names one version or an installed copy (see `serverInvocation` in
|
|
6
|
+
* init), and that copy is a release older or newer than the SDK too — while the project's SDK
|
|
6
7
|
* stays locked at whatever `init` installed. A project wired on the previous major therefore got a
|
|
7
8
|
* daemon from the next one, and every verdict came back `version_skew`; a global install from an
|
|
8
9
|
* older release shadows npx the same way in the other direction. Nothing the project could do fixed
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run the CLI whose major matches the SDK the project actually installed.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Every rule file `init` writes says `npx @reticlehq/server`, unpinned, which resolves the LATEST
|
|
5
|
+
* server. The agent's MCP entry names one version or an installed copy (see `serverInvocation` in
|
|
6
|
+
* init), and that copy is a release older or newer than the SDK too — while the project's SDK
|
|
6
7
|
* stays locked at whatever `init` installed. A project wired on the previous major therefore got a
|
|
7
8
|
* daemon from the next one, and every verdict came back `version_skew`; a global install from an
|
|
8
9
|
* older release shadows npx the same way in the other direction. Nothing the project could do fixed
|
|
@@ -24,7 +24,7 @@ export declare function handleServe(parsed: {
|
|
|
24
24
|
}): void;
|
|
25
25
|
export declare function handleStop(port: number, quiet: boolean): void;
|
|
26
26
|
/**
|
|
27
|
-
* `reticle restart` — free the port, then put a daemon back on it and prove it bound.
|
|
27
|
+
* `reticle serve --restart` — free the port, then put a daemon back on it and prove it bound.
|
|
28
28
|
*
|
|
29
29
|
* The two halves are reported separately on purpose. A restart that killed the old daemon and then
|
|
30
30
|
* could not start a new one leaves the user in a different place from one that could not kill
|
|
@@ -21,7 +21,8 @@ import { probeDaemon, waitForDaemonBind } from '../../../surface/mcp/mcp-proxy.j
|
|
|
21
21
|
import { stateDirProblem } from '../../daemon/state-dir.js';
|
|
22
22
|
import { readPid, isAlive, removePid, spawnDaemon, logPath, reticleStateHome, } from '../../daemon/daemon.js';
|
|
23
23
|
import { readDaemonStartupCause } from '../../daemon/lifetime/startup-failure.js';
|
|
24
|
-
import { PortPresence, probePresence
|
|
24
|
+
import { PortPresence, probePresence } from '../../daemon/binding/port-presence.js';
|
|
25
|
+
import { describePortState } from '../../daemon/port-state.js';
|
|
25
26
|
import { daemonSpawnArgs } from '../daemon-start-options.js';
|
|
26
27
|
import { fetchStatus } from '../../daemon/binding/daemon-status-probe.js';
|
|
27
28
|
import { requestShutdown, runKill } from '../cli-kill.js';
|
|
@@ -82,9 +83,9 @@ async function serveWithHonestExit(parsed) {
|
|
|
82
83
|
log('reticle_daemon_start_refused', {
|
|
83
84
|
port: parsed.port,
|
|
84
85
|
presence,
|
|
85
|
-
reason:
|
|
86
|
+
reason: describePortState(presence, parsed.port),
|
|
86
87
|
});
|
|
87
|
-
process.stderr.write(`${
|
|
88
|
+
process.stderr.write(`${describePortState(presence, parsed.port)}\n`);
|
|
88
89
|
process.exit(1);
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
@@ -107,7 +108,7 @@ async function serveWithHonestExit(parsed) {
|
|
|
107
108
|
log('reticle_daemon_start_failed_parent', {
|
|
108
109
|
port: parsed.port,
|
|
109
110
|
presence: settled,
|
|
110
|
-
reason:
|
|
111
|
+
reason: describePortState(settled, parsed.port),
|
|
111
112
|
log: logPath(parsed.port),
|
|
112
113
|
stateDir: stateDirProblem(reticleStateHome()) === undefined ? 'writable' : 'unwritable',
|
|
113
114
|
...(startupCause === undefined ? {} : { cause: startupCause }),
|
|
@@ -119,7 +120,7 @@ async function serveWithHonestExit(parsed) {
|
|
|
119
120
|
const dirProblem = stateDirProblem(reticleStateHome());
|
|
120
121
|
process.stderr.write(dirProblem === undefined
|
|
121
122
|
? startupCause === undefined
|
|
122
|
-
? `the daemon did not come up on :${String(parsed.port)} — ${
|
|
123
|
+
? `the daemon did not come up on :${String(parsed.port)} — ${describePortState(settled, parsed.port)}\n` +
|
|
123
124
|
`see ${logPath(parsed.port)}\n`
|
|
124
125
|
: `the daemon did not come up on :${String(parsed.port)}.\n${startupCause}\n` +
|
|
125
126
|
`see ${logPath(parsed.port)}\n`
|
|
@@ -196,7 +197,7 @@ function waitForStop(port, pid, quiet) {
|
|
|
196
197
|
}, 100);
|
|
197
198
|
}
|
|
198
199
|
/**
|
|
199
|
-
* `reticle restart` — free the port, then put a daemon back on it and prove it bound.
|
|
200
|
+
* `reticle serve --restart` — free the port, then put a daemon back on it and prove it bound.
|
|
200
201
|
*
|
|
201
202
|
* The two halves are reported separately on purpose. A restart that killed the old daemon and then
|
|
202
203
|
* could not start a new one leaves the user in a different place from one that could not kill
|
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { log } from '../../log.js';
|
|
11
11
|
import { isAlive, spawnDaemon, reticleStateHome } from '../daemon/daemon.js';
|
|
12
|
-
import { PortPresence, probePresence, presenceIsUsable,
|
|
12
|
+
import { PortPresence, probePresence, presenceIsUsable, } from '../daemon/binding/port-presence.js';
|
|
13
|
+
import { describePortState } from '../daemon/port-state.js';
|
|
13
14
|
import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, flushProxyExit, reportWakeRefused, } from '../../surface/mcp/mcp-proxy.js';
|
|
14
15
|
import { installProxyResilience } from '../daemon/daemon-resilience.js';
|
|
15
16
|
import { readProjectId, readProjectPort } from './ports/resolve/cli-port.js';
|
|
16
17
|
import { resolveMcpPort, daemonProjectAt, relocationReason, } from '../daemon/daemon-resolve.js';
|
|
17
18
|
import { daemonSpawnArgs } from './daemon-start-options.js';
|
|
18
19
|
import { WakeAction, decideWake } from '../daemon/wake-decision.js';
|
|
19
|
-
import { runKill } from './cli-kill.js';
|
|
20
|
+
import { runKill, stopWedgedDaemon } from './cli-kill.js';
|
|
20
21
|
import { pickDaemonPortToBind } from '../daemon/binding/free-port.js';
|
|
21
22
|
import { fetchStatus, summarizeStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
22
23
|
import { isNewerVersion } from '../update/update-nudge.js';
|
|
@@ -117,6 +118,9 @@ export async function handleMcp(opts) {
|
|
|
117
118
|
reason: relocationReason(opts.port, to, projectId, reticleStateHome(), isAlive),
|
|
118
119
|
});
|
|
119
120
|
};
|
|
121
|
+
// A Reticle daemon stuck on the requested port is stopped, not moved away from: the app's SDK
|
|
122
|
+
// dials this port, and a daemon on another one gets no page (#1937).
|
|
123
|
+
await stopWedgedDaemon(opts.port);
|
|
120
124
|
// `let`: a wake that finds the port taken resolves again, and the proxy follows (#1651, #1652).
|
|
121
125
|
let port = await resolvePort();
|
|
122
126
|
if (port !== opts.port) {
|
|
@@ -151,7 +155,12 @@ export async function handleMcp(opts) {
|
|
|
151
155
|
// the bridge port answered "a daemon is here" — the proxy connected, the stream ended, and each
|
|
152
156
|
// client request woke into the identical non-answer. Rejecting here is what puts the proxy
|
|
153
157
|
// dormant with a reason, instead of pretending the wake succeeded.
|
|
154
|
-
|
|
158
|
+
let presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
159
|
+
// A Reticle daemon that holds the port and does not answer: stop it and keep the port, as at
|
|
160
|
+
// boot. Relocating left the app dialling a port with no working daemon (#1937).
|
|
161
|
+
if (PortPresence.FOREIGN === presence && (await stopWedgedDaemon(port))) {
|
|
162
|
+
presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
163
|
+
}
|
|
155
164
|
// Identity, not just presence. `resolveMcpPort` above already refuses to adopt a stranger's
|
|
156
165
|
// daemon at boot; this is the same question asked on every WAKE, which is where it was missing.
|
|
157
166
|
//
|
|
@@ -192,7 +201,7 @@ export async function handleMcp(opts) {
|
|
|
192
201
|
throw new Error(PortPresence.DAEMON === presence
|
|
193
202
|
? `the daemon on port ${String(port)} belongs to a different project, so this one will ` +
|
|
194
203
|
'not adopt it. Stop it, or start this project on its own port.'
|
|
195
|
-
:
|
|
204
|
+
: describePortState(presence, port));
|
|
196
205
|
}
|
|
197
206
|
const scriptPath = process.argv[1];
|
|
198
207
|
if (scriptPath === undefined) {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
export interface PortHolder {
|
|
17
17
|
pid: number;
|
|
18
18
|
command: string;
|
|
19
|
+
/** Its command line is a Reticle daemon's. Set only by `identifyPortHolder`. */
|
|
20
|
+
reticleDaemon?: boolean;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Run a lookup and return its stdout, or null if it could not run.
|
|
@@ -53,6 +55,15 @@ ourPid?: number | null): string;
|
|
|
53
55
|
export declare function findPortHolder(port: number, exec: (command: string, args: readonly string[]) => string | null): PortHolder | null;
|
|
54
56
|
/** The path in `lsof -a -p <pid> -d cwd -Fn` output, or null when it printed none. */
|
|
55
57
|
export declare function parseProcessCwd(stdout: string): string | null;
|
|
58
|
+
/** Each port in `lsof -nP -iTCP -sTCP:LISTEN -Fn` output, once. A dual-stack bind prints two lines. */
|
|
59
|
+
export declare function parseListeningPorts(stdout: string): number[];
|
|
60
|
+
/**
|
|
61
|
+
* Every TCP port on this machine with a listener, or nothing when the lookup cannot run.
|
|
62
|
+
*
|
|
63
|
+
* Off on Windows. A process there shows no working directory, so `findListenerCwd` starts PowerShell
|
|
64
|
+
* for each port, and the daemon blocks for each start.
|
|
65
|
+
*/
|
|
66
|
+
export declare function findListeningPorts(exec: (command: string, args: readonly string[]) => string | null, platform?: NodeJS.Platform): number[];
|
|
56
67
|
/**
|
|
57
68
|
* The folder that holds the `node_modules` a command line runs from, or null.
|
|
58
69
|
*
|
|
@@ -93,7 +93,7 @@ ourPid) {
|
|
|
93
93
|
// Naming the command matters more than naming the process. "Stop that process" describes an
|
|
94
94
|
// outcome and leaves the reader to invent a route to it, and the route they invent is the `-ti`
|
|
95
95
|
// pipeline that takes their agent's proxy down with the holder.
|
|
96
|
-
const tail = 'a daemon cannot bind it. Free it with `reticle restart --force` (it signals the listener only, ' +
|
|
96
|
+
const tail = 'a daemon cannot bind it. Free it with `reticle serve --restart --force` (it signals the listener only, ' +
|
|
97
97
|
"never the agent's mcp proxy), or run Reticle on a different port (`--port`), then retry.";
|
|
98
98
|
if (null === holder) {
|
|
99
99
|
/**
|
|
@@ -111,14 +111,22 @@ ourPid) {
|
|
|
111
111
|
*/
|
|
112
112
|
const ours = ourPid !== null && ourPid !== undefined
|
|
113
113
|
? ` Reticle's own records show pid ${String(ourPid)} for this port — if that process is ` +
|
|
114
|
-
'still around it is a
|
|
114
|
+
'still around it is a Reticle daemon that did not answer in time. It may be busy. If it ' +
|
|
115
|
+
'stays silent, `reticle stop` clears it.'
|
|
115
116
|
: '';
|
|
116
117
|
return `port ${String(port)} is held by a process Reticle could not identify — ${tail}${ours}`;
|
|
117
118
|
}
|
|
119
|
+
if (true === holder.reticleDaemon && ourPid !== holder.pid) {
|
|
120
|
+
// Not the pid we recorded, so `reticle stop` cannot find it. `serve --restart` finds the listener.
|
|
121
|
+
// It did not answer a short probe. A busy daemon does that too, and other agents can use it.
|
|
122
|
+
return (`port ${String(port)} is held by a Reticle daemon (pid ${String(holder.pid)}) that did not ` +
|
|
123
|
+
'answer in time. It may be busy. Run `reticle status` again in a few seconds. If it stays ' +
|
|
124
|
+
'silent, run `reticle serve --restart`: it stops that process and starts a new daemon on this port.');
|
|
125
|
+
}
|
|
118
126
|
if (ourPid !== null && ourPid !== undefined && ourPid === holder.pid) {
|
|
119
127
|
return (`port ${String(port)} is held by YOUR Reticle daemon (pid ${String(holder.pid)}), and it is ` +
|
|
120
|
-
'not responding
|
|
121
|
-
'
|
|
128
|
+
'not responding: it did not answer in time. It may be busy. Run `reticle status` again in a ' +
|
|
129
|
+
'few seconds. If it stays silent, run `reticle stop`; the next tool call starts a fresh one.');
|
|
122
130
|
}
|
|
123
131
|
return (`port ${String(port)} is held by pid ${String(holder.pid)} ("${holder.command}"), which is not ` +
|
|
124
132
|
`a Reticle daemon — ${tail}`);
|
|
@@ -142,6 +150,30 @@ export function parseProcessCwd(stdout) {
|
|
|
142
150
|
}
|
|
143
151
|
return null;
|
|
144
152
|
}
|
|
153
|
+
/** Each port in `lsof -nP -iTCP -sTCP:LISTEN -Fn` output, once. A dual-stack bind prints two lines. */
|
|
154
|
+
export function parseListeningPorts(stdout) {
|
|
155
|
+
const ports = new Set();
|
|
156
|
+
for (const line of stdout.split('\n')) {
|
|
157
|
+
const port = line.startsWith(NAME_FIELD) ? /:(\d+)$/.exec(line)?.[1] : undefined;
|
|
158
|
+
if (port !== undefined) {
|
|
159
|
+
ports.add(Number(port));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return [...ports];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Every TCP port on this machine with a listener, or nothing when the lookup cannot run.
|
|
166
|
+
*
|
|
167
|
+
* Off on Windows. A process there shows no working directory, so `findListenerCwd` starts PowerShell
|
|
168
|
+
* for each port, and the daemon blocks for each start.
|
|
169
|
+
*/
|
|
170
|
+
export function findListeningPorts(exec, platform = process.platform) {
|
|
171
|
+
if ('win32' === platform) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
const out = exec('lsof', ['-nP', '-iTCP', '-sTCP:LISTEN', '-Fn']);
|
|
175
|
+
return null === out ? [] : parseListeningPorts(out);
|
|
176
|
+
}
|
|
145
177
|
/** A Windows PowerShell cold start alone can take more than the lsof bound. */
|
|
146
178
|
const WINDOWS_LOOKUP_TIMEOUT_MS = 8_000;
|
|
147
179
|
/** The listener's command line: Windows does not show another process's working directory. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OnboardingStep } from '@reticlehq/core/telemetry';
|
|
2
|
+
import { type AgentRow, type WaitClock } from '../setup/agent-next-steps.js';
|
|
2
3
|
/**
|
|
3
4
|
* The terminal half of `reticle setup mcp`.
|
|
4
5
|
*
|
|
@@ -15,7 +16,9 @@ import type { OnboardingStep } from '@reticlehq/core/telemetry';
|
|
|
15
16
|
*/
|
|
16
17
|
export type StepReporter = (step: OnboardingStep) => void;
|
|
17
18
|
/** How many coding agents on this machine have Reticle's tools after this run. */
|
|
18
|
-
export declare function handleSetupMcp(reportStep: StepReporter, standalone?: boolean
|
|
19
|
+
export declare function handleSetupMcp(reportStep: StepReporter, standalone?: boolean,
|
|
20
|
+
/** Wait for the agents to connect. The caller decides: a terminal, and not CI. */
|
|
21
|
+
waitForConnect?: boolean): number;
|
|
19
22
|
/**
|
|
20
23
|
* The one line the launchers read and strip: what they cannot know and the next steps depend on.
|
|
21
24
|
*
|
|
@@ -43,3 +46,26 @@ export declare function handleSetupInstall(opts: {
|
|
|
43
46
|
installSecs: number;
|
|
44
47
|
mcp: boolean;
|
|
45
48
|
}, reportStep: StepReporter): void;
|
|
49
|
+
/** Wait only where a person watches: a terminal, and not CI. */
|
|
50
|
+
export declare function shouldWaitForAgents(isTty: boolean | undefined, isCi: boolean): boolean;
|
|
51
|
+
/** What the wait reads, and the clock it runs on. Injected, so a test does not wait. */
|
|
52
|
+
export interface ConnectionWait {
|
|
53
|
+
readonly read: () => ReadonlySet<string>;
|
|
54
|
+
readonly clock: WaitClock;
|
|
55
|
+
}
|
|
56
|
+
/** Wait (bounded) for the registered agents to connect, then say which did and what is missing. */
|
|
57
|
+
export declare function reportConnections(rows: readonly AgentRow[], print: (line: string) => void, wait?: ConnectionWait): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* `reticle init`: the same block and wait as `reticle setup mcp`, after its restart notice. Print the
|
|
60
|
+
* "Your agents:" block now. Returns the wait, for the caller to run last: a no-op when
|
|
61
|
+
* nobody watches.
|
|
62
|
+
*/
|
|
63
|
+
export declare function agentsBlock(rows: readonly AgentRow[], print: (line: string) => void, interactive: boolean, wait?: ConnectionWait): () => Promise<void>;
|
|
64
|
+
/** The agents on this machine and whether Reticle is in each config. Read only. */
|
|
65
|
+
export declare function readAgentRows(cwd: string): AgentRow[];
|
|
66
|
+
/**
|
|
67
|
+
* `reticle setup wait-agents`: the installer's last step. The installer sends `setup install` output
|
|
68
|
+
* to a file, so that run cannot wait; this one runs with the terminal attached. Waits only where a
|
|
69
|
+
* person watches.
|
|
70
|
+
*/
|
|
71
|
+
export declare function handleSetupWaitAgents(interactive: boolean): Promise<void>;
|