@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
|
@@ -37,6 +37,16 @@ export interface DevProxy {
|
|
|
37
37
|
* only: a remote page's port says nothing about any process on this machine.
|
|
38
38
|
*/
|
|
39
39
|
export declare function servingDirectoryOf(origin: string, io?: ServingDirectoryIo): string | undefined;
|
|
40
|
+
/** How many ports one scan asks about. Each lookup blocks the daemon for two `lsof` runs. */
|
|
41
|
+
export declare const MAX_FOLDER_LOOKUPS = 16;
|
|
42
|
+
/**
|
|
43
|
+
* The folder of each port in `ports`, or undefined when it cannot be read.
|
|
44
|
+
*
|
|
45
|
+
* `known` is the last scan's answer. A port that stays open keeps it, so a machine with many
|
|
46
|
+
* listeners pays once. A port past the bound waits for the next scan. A closed port leaves the map,
|
|
47
|
+
* so the next process on it is asked about.
|
|
48
|
+
*/
|
|
49
|
+
export declare function listenerFolders(ports: readonly number[], known: ReadonlyMap<number, string | undefined>, directoryOf: (port: number) => string | undefined): Map<number, string | undefined>;
|
|
40
50
|
/**
|
|
41
51
|
* The live answers for a worktree-scoped resolve: the calling agent's checkout (from the directory
|
|
42
52
|
* its MCP proxy runs in) and each tab's checkout (from the dev server that served it).
|
|
@@ -40,13 +40,15 @@ function readOrUndefined(path) {
|
|
|
40
40
|
function readPortless() {
|
|
41
41
|
const dir = process.env[PORTLESS_STATE_DIR_ENV] ?? join(homedir(), PORTLESS_STATE_DIR);
|
|
42
42
|
const port = Number(readOrUndefined(join(dir, PORTLESS_PORT_FILE))?.trim());
|
|
43
|
-
if (!Number.isInteger(port))
|
|
43
|
+
if (!Number.isInteger(port)) {
|
|
44
44
|
return undefined;
|
|
45
|
+
}
|
|
45
46
|
let routes = [];
|
|
46
47
|
try {
|
|
47
48
|
const parsed = PortlessRoutesSchema.safeParse(JSON.parse(readOrUndefined(join(dir, PORTLESS_ROUTES_FILE)) ?? '[]'));
|
|
48
|
-
if (parsed.success)
|
|
49
|
+
if (parsed.success) {
|
|
49
50
|
routes = parsed.data;
|
|
51
|
+
}
|
|
50
52
|
}
|
|
51
53
|
catch {
|
|
52
54
|
// A table being rewritten reads as empty: the page stays unplaced, never placed wrong.
|
|
@@ -75,27 +77,55 @@ export function servingDirectoryOf(origin, io = liveIo) {
|
|
|
75
77
|
catch {
|
|
76
78
|
return undefined;
|
|
77
79
|
}
|
|
78
|
-
if (!isLoopbackHostname(url.hostname) && !isLocalhostSubdomain(url.hostname))
|
|
80
|
+
if (!isLoopbackHostname(url.hostname) && !isLocalhostSubdomain(url.hostname)) {
|
|
79
81
|
return undefined;
|
|
82
|
+
}
|
|
80
83
|
// Exact first: a dev proxy serves every worktree on one port, under the name it announced.
|
|
81
84
|
const byUrl = io.devServers().find((entry) => originOf(entry.url) === url.origin);
|
|
82
|
-
if (byUrl !== undefined)
|
|
85
|
+
if (byUrl !== undefined) {
|
|
83
86
|
return byUrl.root;
|
|
87
|
+
}
|
|
84
88
|
const pagePort = '' === url.port ? DEFAULT_PORT[url.protocol] : Number(url.port);
|
|
85
|
-
if (pagePort === undefined)
|
|
89
|
+
if (pagePort === undefined) {
|
|
86
90
|
return undefined;
|
|
91
|
+
}
|
|
87
92
|
// Behind a dev proxy the page's port is the proxy's, and `lsof` on it names the proxy's folder.
|
|
88
93
|
// The proxy's route table names the dev server's own port.
|
|
89
94
|
const proxy = isLocalhostSubdomain(url.hostname) ? io.devProxy?.() : undefined;
|
|
90
95
|
const route = proxy?.routes.find((r) => r.hostname === url.hostname.toLowerCase());
|
|
91
|
-
if (route === undefined && true === proxy?.ports.includes(pagePort))
|
|
96
|
+
if (route === undefined && true === proxy?.ports.includes(pagePort)) {
|
|
92
97
|
return undefined;
|
|
98
|
+
}
|
|
93
99
|
const port = route?.port ?? pagePort;
|
|
94
100
|
const announced = io.devServers().find((entry) => entry.port === port);
|
|
95
|
-
if (announced !== undefined)
|
|
101
|
+
if (announced !== undefined) {
|
|
96
102
|
return announced.root;
|
|
103
|
+
}
|
|
97
104
|
return io.listenerCwd(port) ?? undefined;
|
|
98
105
|
}
|
|
106
|
+
/** How many ports one scan asks about. Each lookup blocks the daemon for two `lsof` runs. */
|
|
107
|
+
export const MAX_FOLDER_LOOKUPS = 16;
|
|
108
|
+
/**
|
|
109
|
+
* The folder of each port in `ports`, or undefined when it cannot be read.
|
|
110
|
+
*
|
|
111
|
+
* `known` is the last scan's answer. A port that stays open keeps it, so a machine with many
|
|
112
|
+
* listeners pays once. A port past the bound waits for the next scan. A closed port leaves the map,
|
|
113
|
+
* so the next process on it is asked about.
|
|
114
|
+
*/
|
|
115
|
+
export function listenerFolders(ports, known, directoryOf) {
|
|
116
|
+
const folders = new Map();
|
|
117
|
+
let lookups = 0;
|
|
118
|
+
for (const port of ports) {
|
|
119
|
+
if (known.has(port)) {
|
|
120
|
+
folders.set(port, known.get(port));
|
|
121
|
+
}
|
|
122
|
+
else if (lookups < MAX_FOLDER_LOOKUPS) {
|
|
123
|
+
lookups += 1;
|
|
124
|
+
folders.set(port, directoryOf(port));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return folders;
|
|
128
|
+
}
|
|
99
129
|
/** The checkout whose dev server serves `origin` right now, or undefined. */
|
|
100
130
|
function checkoutServing(origin) {
|
|
101
131
|
const serving = servingDirectoryOf(origin);
|
|
@@ -122,8 +152,9 @@ export function liveCheckoutSource(placeOrigin = checkoutServing) {
|
|
|
122
152
|
: undefined;
|
|
123
153
|
},
|
|
124
154
|
ofTab: (tab) => {
|
|
125
|
-
if (placed.has(tab))
|
|
155
|
+
if (placed.has(tab)) {
|
|
126
156
|
return placed.get(tab);
|
|
157
|
+
}
|
|
127
158
|
const origin = originOf(tab.url);
|
|
128
159
|
const checkout = origin === undefined ? undefined : placeOrigin(origin);
|
|
129
160
|
placed.set(tab, checkout);
|
|
@@ -64,6 +64,8 @@ export declare class SessionManager {
|
|
|
64
64
|
isParked(sessionId: string): boolean;
|
|
65
65
|
remove(session: Session): boolean;
|
|
66
66
|
get(sessionId: string): Session | undefined;
|
|
67
|
+
/** The id the same tab connected as after this session's page went away, if one has. */
|
|
68
|
+
successorId(sessionId: string): string | undefined;
|
|
67
69
|
/**
|
|
68
70
|
* Did this id belong to a session that has since disconnected?
|
|
69
71
|
*
|
|
@@ -149,6 +151,14 @@ export declare class SessionManager {
|
|
|
149
151
|
/** Every departed session's URL, oldest first. The port scan has to see all of them, not only the newest. */
|
|
150
152
|
tombstoneUrls(): readonly string[];
|
|
151
153
|
resolve(sessionId?: string, scope?: ResolveScope): Session;
|
|
154
|
+
/**
|
|
155
|
+
* Resolve the session for a call that only some sessions can serve.
|
|
156
|
+
*
|
|
157
|
+
* With no sessionId, resolve() chooses by health and recency alone. A lease that could resize the
|
|
158
|
+
* page lost to a focused tab that could not, and the call was refused (#1959). Here, when exactly
|
|
159
|
+
* one candidate can serve the call, that candidate is chosen.
|
|
160
|
+
*/
|
|
161
|
+
resolveServing(sessionId: string | undefined, canServe: (session: Session) => boolean | Promise<boolean>): Promise<Session>;
|
|
152
162
|
}
|
|
153
163
|
export interface Tab {
|
|
154
164
|
id: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NO_SESSION_CONNECTED_ERROR, originOf, samePath } from '@reticlehq/core';
|
|
1
|
+
import { NO_SESSION_CONNECTED_ERROR, NO_SESSION_NAVIGATE_FIX, originOf, samePath, } from '@reticlehq/core';
|
|
2
2
|
import { notePendingNoSessionReason } from '../../telemetry/tool-refused.js';
|
|
3
3
|
import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
|
|
4
4
|
import { Session } from './session.js';
|
|
@@ -6,6 +6,7 @@ import { AttachmentHistory } from './presence/attachment-history.js';
|
|
|
6
6
|
import { claimedSessionOf, openedBeforeDeparture, pickDocumentSuccessor, } from './session-successor.js';
|
|
7
7
|
import { SESSION_DISCONNECTED_REASON, SESSION_PARKED_REASON } from './facts/session-replaced.js';
|
|
8
8
|
import { isErrorDocument } from './session-info.js';
|
|
9
|
+
import { carryOnNewDocument, departedNetworkOf, } from './facts/departed-network.js';
|
|
9
10
|
/** Core's `originOf`, re-exported for this module's existing importers. */
|
|
10
11
|
export { originOf };
|
|
11
12
|
/**
|
|
@@ -14,8 +15,9 @@ export { originOf };
|
|
|
14
15
|
* no scope, return the list unchanged (legacy single-/multi-session behavior).
|
|
15
16
|
*/
|
|
16
17
|
function scopeSessions(sessions, scope) {
|
|
17
|
-
if (scope === undefined)
|
|
18
|
+
if (scope === undefined) {
|
|
18
19
|
return sessions;
|
|
20
|
+
}
|
|
19
21
|
if (scope.projectId !== undefined) {
|
|
20
22
|
return sessions.filter((s) => s.projectId === scope.projectId);
|
|
21
23
|
}
|
|
@@ -36,18 +38,10 @@ function scopeSessions(sessions, scope) {
|
|
|
36
38
|
* target it: both facts are already in hand here.
|
|
37
39
|
*/
|
|
38
40
|
export function scopeMissError(connected, scope) {
|
|
39
|
-
const who = scope
|
|
40
|
-
? `project '${scope.projectId}'`
|
|
41
|
-
: scope?.url !== undefined
|
|
42
|
-
? `your app at ${scope.url}`
|
|
43
|
-
: 'the active project';
|
|
41
|
+
const who = scopeName(scope);
|
|
44
42
|
const anyUntagged = connected.some((s) => s.projectId === undefined);
|
|
45
43
|
const anyTagged = connected.some((s) => s.projectId !== undefined);
|
|
46
|
-
const how = anyUntagged
|
|
47
|
-
? 'with no projectId'
|
|
48
|
-
: anyUntagged && anyTagged
|
|
49
|
-
? 'under a different project or with no projectId'
|
|
50
|
-
: 'under a different project';
|
|
44
|
+
const how = whereConnected(anyUntagged, anyTagged);
|
|
51
45
|
const listed = connected
|
|
52
46
|
.map((s) => s.projectId !== undefined
|
|
53
47
|
? `'${s.projectId}' (${s.url}, sessionId '${s.id}')`
|
|
@@ -61,6 +55,48 @@ export function scopeMissError(connected, scope) {
|
|
|
61
55
|
`${listed}. The daemon scopes to the .reticle.json of the directory it was started in, so ` +
|
|
62
56
|
`this is a scope mismatch, not a dead app. ${advice}`);
|
|
63
57
|
}
|
|
58
|
+
/** What the scope names, in the words of the refusal. */
|
|
59
|
+
function scopeName(scope) {
|
|
60
|
+
if (scope?.projectId !== undefined) {
|
|
61
|
+
return `project '${scope.projectId}'`;
|
|
62
|
+
}
|
|
63
|
+
if (scope?.url !== undefined) {
|
|
64
|
+
return `your app at ${scope.url}`;
|
|
65
|
+
}
|
|
66
|
+
return 'the active project';
|
|
67
|
+
}
|
|
68
|
+
/** Where the connected sessions are, given which of them carry a projectId. */
|
|
69
|
+
function whereConnected(anyUntagged, anyTagged) {
|
|
70
|
+
if (!anyUntagged) {
|
|
71
|
+
return 'under a different project';
|
|
72
|
+
}
|
|
73
|
+
if (!anyTagged) {
|
|
74
|
+
return 'with no projectId';
|
|
75
|
+
}
|
|
76
|
+
return 'under a different project or with no projectId';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A session's rank for auto-selection: lower is better.
|
|
80
|
+
*
|
|
81
|
+
* 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat),
|
|
82
|
+
* 2 = an error document, 3 = attached but not answering commands. A wedged tab keeps streaming
|
|
83
|
+
* events, so recency RATES IT HIGHEST — it was beating a healthy sibling on the one signal scoring
|
|
84
|
+
* looked at, and every call auto-targeted the one tab guaranteed to time out. An error document (a
|
|
85
|
+
* dev server's 404 that still mounted the SDK) ranks below any working page, hidden or not: it is
|
|
86
|
+
* never the app the agent was asked about.
|
|
87
|
+
*/
|
|
88
|
+
function rankOf(session) {
|
|
89
|
+
if (session.unresponsive()) {
|
|
90
|
+
return 3;
|
|
91
|
+
}
|
|
92
|
+
if (isErrorDocument(session.page)) {
|
|
93
|
+
return 2;
|
|
94
|
+
}
|
|
95
|
+
if (session.throttled()) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
64
100
|
/**
|
|
65
101
|
* Owns the set of connected browser sessions and the smart auto-selection that resolves which one a
|
|
66
102
|
* tool targets when the agent omits an explicit sessionId. Extracted from session.ts so each file
|
|
@@ -95,6 +131,11 @@ export class SessionManager {
|
|
|
95
131
|
#tombstones = new Map();
|
|
96
132
|
/** The tabs already open when each tombstoned session departed: siblings, never its successor. */
|
|
97
133
|
#siblingsAtDeparture = new Map();
|
|
134
|
+
/**
|
|
135
|
+
* The network record each departed document made after the agent's last act, kept until the same
|
|
136
|
+
* tab's next document connects (#1944). Bounded with the tombstones. See departed-network.ts.
|
|
137
|
+
*/
|
|
138
|
+
#departedNetwork = new Map();
|
|
98
139
|
/**
|
|
99
140
|
* The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
|
|
100
141
|
* without passing its own scope, this is applied — so auto-selection is project-scoped by default
|
|
@@ -124,8 +165,9 @@ export class SessionManager {
|
|
|
124
165
|
}
|
|
125
166
|
add(session) {
|
|
126
167
|
this.#everConnected = true;
|
|
127
|
-
if (session.projectId !== undefined)
|
|
168
|
+
if (session.projectId !== undefined) {
|
|
128
169
|
this.#connectedProjects.add(session.projectId);
|
|
170
|
+
}
|
|
129
171
|
// Ordering, not lifetime: this is what makes a remembered refusal distinguishable from a live
|
|
130
172
|
// one. Here for the same reason `#everConnected` is — `add` is the one method every path that
|
|
131
173
|
// registers a session goes through.
|
|
@@ -143,10 +185,24 @@ export class SessionManager {
|
|
|
143
185
|
// that silently fails to register is a leak nothing would report.
|
|
144
186
|
declareDrivenRedactionKeys(session.id, session.redactKeys);
|
|
145
187
|
this.#attachment.attached(session.id);
|
|
188
|
+
this.#carryDepartedNetwork(session, previous);
|
|
146
189
|
this.#tombstones.delete(session.id);
|
|
147
190
|
this.#siblingsAtDeparture.delete(session.id);
|
|
148
191
|
return previous;
|
|
149
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Give a new document the requests its tab's previous document made after the last act (#1944).
|
|
195
|
+
* `previous` is that document when its socket has not closed yet; else the record kept at removal.
|
|
196
|
+
*/
|
|
197
|
+
#carryDepartedNetwork(session, previous) {
|
|
198
|
+
const departed = previous !== undefined && previous !== session
|
|
199
|
+
? departedNetworkOf(previous)
|
|
200
|
+
: this.#departedNetwork.get(session.id);
|
|
201
|
+
this.#departedNetwork.delete(session.id);
|
|
202
|
+
if (departed !== undefined) {
|
|
203
|
+
carryOnNewDocument(session, departed);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
150
206
|
/**
|
|
151
207
|
* The other tabs of the same app as `driven`.
|
|
152
208
|
*
|
|
@@ -171,28 +227,39 @@ export class SessionManager {
|
|
|
171
227
|
session.disconnect(SESSION_PARKED_REASON);
|
|
172
228
|
this.remove(session);
|
|
173
229
|
this.#tombstones.delete(session.id);
|
|
230
|
+
this.#departedNetwork.delete(session.id);
|
|
174
231
|
}
|
|
175
232
|
isParked(sessionId) {
|
|
176
233
|
return this.#parked.has(sessionId);
|
|
177
234
|
}
|
|
178
235
|
#parked = new Set();
|
|
179
236
|
remove(session) {
|
|
180
|
-
if (this.#sessions.get(session.id) !== session)
|
|
237
|
+
if (this.#sessions.get(session.id) !== session) {
|
|
181
238
|
return false;
|
|
239
|
+
}
|
|
182
240
|
session.rejectAll(SESSION_DISCONNECTED_REASON);
|
|
183
241
|
forgetDrivenRedactionKeys(session.id);
|
|
184
242
|
// Recorded, not forgotten: a session that comes back needs its gap measured, and a listing after
|
|
185
243
|
// the reconnect is exactly where that matters.
|
|
186
244
|
this.#attachment.detached(session.id);
|
|
187
245
|
this.#rememberTombstone(session);
|
|
246
|
+
// Kept only beside a tombstone, so the tombstone cap bounds it too.
|
|
247
|
+
const departedNetwork = this.#tombstones.has(session.id)
|
|
248
|
+
? departedNetworkOf(session)
|
|
249
|
+
: undefined;
|
|
250
|
+
if (departedNetwork !== undefined) {
|
|
251
|
+
this.#departedNetwork.set(session.id, departedNetwork);
|
|
252
|
+
}
|
|
188
253
|
return this.#sessions.delete(session.id);
|
|
189
254
|
}
|
|
190
255
|
#rememberTombstone(session) {
|
|
191
256
|
const origin = originOf(session.url);
|
|
192
|
-
if (origin === undefined)
|
|
257
|
+
if (origin === undefined) {
|
|
193
258
|
return;
|
|
194
|
-
|
|
259
|
+
}
|
|
260
|
+
if (this.#tombstones.has(session.id)) {
|
|
195
261
|
this.#tombstones.delete(session.id);
|
|
262
|
+
}
|
|
196
263
|
this.#siblingsAtDeparture.set(session.id, new Set([...this.#sessions.values()]
|
|
197
264
|
.filter((s) => s !== session && openedBeforeDeparture(s, session))
|
|
198
265
|
.map((s) => s.id)));
|
|
@@ -206,16 +273,19 @@ export class SessionManager {
|
|
|
206
273
|
});
|
|
207
274
|
while (this.#tombstones.size > MAX_TOMBSTONES) {
|
|
208
275
|
const oldest = this.#tombstones.keys().next().value;
|
|
209
|
-
if (oldest === undefined)
|
|
276
|
+
if (oldest === undefined) {
|
|
210
277
|
break;
|
|
278
|
+
}
|
|
211
279
|
this.#tombstones.delete(oldest);
|
|
212
280
|
this.#siblingsAtDeparture.delete(oldest);
|
|
281
|
+
this.#departedNetwork.delete(oldest);
|
|
213
282
|
}
|
|
214
283
|
}
|
|
215
284
|
#successorOf(sessionId) {
|
|
216
285
|
const departed = this.#tombstones.get(sessionId);
|
|
217
|
-
if (departed === undefined)
|
|
286
|
+
if (departed === undefined) {
|
|
218
287
|
return undefined;
|
|
288
|
+
}
|
|
219
289
|
const siblings = this.#siblingsAtDeparture.get(sessionId);
|
|
220
290
|
const picked = pickDocumentSuccessor(this.all()
|
|
221
291
|
.filter((s) => true !== siblings?.has(s.id))
|
|
@@ -224,13 +294,18 @@ export class SessionManager {
|
|
|
224
294
|
url: s.url,
|
|
225
295
|
...(s.projectId === undefined ? {} : { projectId: s.projectId }),
|
|
226
296
|
})), departed);
|
|
227
|
-
if (picked === undefined)
|
|
297
|
+
if (picked === undefined) {
|
|
228
298
|
return undefined;
|
|
299
|
+
}
|
|
229
300
|
return this.#sessions.get(picked.id);
|
|
230
301
|
}
|
|
231
302
|
get(sessionId) {
|
|
232
303
|
return this.#sessions.get(sessionId);
|
|
233
304
|
}
|
|
305
|
+
/** The id the same tab connected as after this session's page went away, if one has. */
|
|
306
|
+
successorId(sessionId) {
|
|
307
|
+
return this.#successorOf(sessionId)?.id;
|
|
308
|
+
}
|
|
234
309
|
/**
|
|
235
310
|
* Did this id belong to a session that has since disconnected?
|
|
236
311
|
*
|
|
@@ -396,8 +471,9 @@ export class SessionManager {
|
|
|
396
471
|
*/
|
|
397
472
|
noteClosure(reason, at, page) {
|
|
398
473
|
this.#recentClosures.push({ at, reason, ...(page === undefined ? {} : { page }) });
|
|
399
|
-
if (this.#recentClosures.length > MAX_REMEMBERED_CLOSURES)
|
|
474
|
+
if (this.#recentClosures.length > MAX_REMEMBERED_CLOSURES) {
|
|
400
475
|
this.#recentClosures.shift();
|
|
476
|
+
}
|
|
401
477
|
this.#connectedSinceLastClosure = false;
|
|
402
478
|
}
|
|
403
479
|
/** The most recent bridge-initiated close, if any. */
|
|
@@ -443,8 +519,9 @@ export class SessionManager {
|
|
|
443
519
|
*/
|
|
444
520
|
lastKnown() {
|
|
445
521
|
let last;
|
|
446
|
-
for (const record of this.#tombstones.values())
|
|
522
|
+
for (const record of this.#tombstones.values()) {
|
|
447
523
|
last = record;
|
|
524
|
+
}
|
|
448
525
|
return last;
|
|
449
526
|
}
|
|
450
527
|
/**
|
|
@@ -456,8 +533,9 @@ export class SessionManager {
|
|
|
456
533
|
lastKnownFor(projectId) {
|
|
457
534
|
let last;
|
|
458
535
|
for (const record of this.#tombstones.values()) {
|
|
459
|
-
if (record.projectId === projectId)
|
|
536
|
+
if (record.projectId === projectId) {
|
|
460
537
|
last = record;
|
|
538
|
+
}
|
|
461
539
|
}
|
|
462
540
|
return last;
|
|
463
541
|
}
|
|
@@ -496,40 +574,19 @@ export class SessionManager {
|
|
|
496
574
|
const ended = sessionId.startsWith(LEASE_ID_PREFIX)
|
|
497
575
|
? ` Lease '${sessionId}' has ended (it expired or was released); lease a new tab to go on.`
|
|
498
576
|
: '';
|
|
499
|
-
if (0 === own.length)
|
|
577
|
+
if (0 === own.length) {
|
|
500
578
|
return (`no connected session with id '${sessionId}'.${ended} ` +
|
|
501
579
|
'Omit sessionId to let Reticle scope to your project, or lease a tab of your own.');
|
|
580
|
+
}
|
|
502
581
|
const named = own.map((s) => `'${s.id}' (${s.url})`).join(', ');
|
|
503
582
|
return (`no connected session with id '${sessionId}'.${ended} Your app's tabs: ${named}. ` +
|
|
504
583
|
'Retry with one of those, or omit sessionId entirely and let Reticle scope to your project.');
|
|
505
584
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
|
|
513
|
-
return found;
|
|
514
|
-
}
|
|
515
|
-
if (0 === this.#sessions.size) {
|
|
516
|
-
const closure = this.lastClosure();
|
|
517
|
-
// A diagnosis beats a checklist: it names which of the three causes this actually is.
|
|
518
|
-
const hint = this.#noSessionHint?.();
|
|
519
|
-
// BOTH, when there are both — the two answer different questions and the pair is strictly
|
|
520
|
-
// more than either. This used to be an either/or, and a recorded refusal DISCARDED the whole
|
|
521
|
-
// diagnosis: the one case where the daemon has hard evidence about why nothing is connected
|
|
522
|
-
// was also the one case where it threw away everything else it had learned, including the
|
|
523
|
-
// port scan and the next action. The refusal leads, because it is the fact.
|
|
524
|
-
const refusal = closure === undefined
|
|
525
|
-
? ''
|
|
526
|
-
: ` NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`;
|
|
527
|
-
// Hand the branch code to the refusal that is about to be reported for this throw. The
|
|
528
|
-
// diagnosis is computed HERE and the refusal is classified from the message downstream, so
|
|
529
|
-
// without this every no-session refusal lands in one undifferentiated bucket (#615).
|
|
530
|
-
notePendingNoSessionReason(this.#noSessionReason?.());
|
|
531
|
-
throw new Error(`${hint ?? NO_SESSION_CONNECTED_ERROR}${refusal}`);
|
|
532
|
-
}
|
|
585
|
+
/**
|
|
586
|
+
* The sessions an id-less call may choose from: in the project scope, then in the caller's own
|
|
587
|
+
* checkout. Throws when those scopes leave nothing, or leave tabs of more than one project.
|
|
588
|
+
*/
|
|
589
|
+
#candidates(scope) {
|
|
533
590
|
// Scope to the agent's active project FIRST, so a stray tab from another app/origin (e.g. a
|
|
534
591
|
// leftover dashboard on a different port) is structurally unselectable — it never enters the
|
|
535
592
|
// candidate set, no matter how recently it was heard from. This is the anti-cross-talk guard.
|
|
@@ -541,8 +598,9 @@ export class SessionManager {
|
|
|
541
598
|
const byCheckout = this.#checkoutScope === undefined
|
|
542
599
|
? { sessions: inProject }
|
|
543
600
|
: scopeToCallerCheckout(inProject, this.#checkoutScope.caller(), this.#checkoutScope.ofTab);
|
|
544
|
-
if (byCheckout.refusal !== undefined)
|
|
601
|
+
if (byCheckout.refusal !== undefined) {
|
|
545
602
|
throw new Error(byCheckout.refusal);
|
|
603
|
+
}
|
|
546
604
|
const all = byCheckout.sessions;
|
|
547
605
|
if (0 === all.length) {
|
|
548
606
|
// Sessions exist, but none belong to the scoped project — never fall back to a foreign tab.
|
|
@@ -551,27 +609,20 @@ export class SessionManager {
|
|
|
551
609
|
// exists to be. Naming the tab and its sessionId costs the agent one extra argument.
|
|
552
610
|
throw new Error(scopeMissError(connected, effectiveScope));
|
|
553
611
|
}
|
|
554
|
-
if (1 === all.length) {
|
|
555
|
-
const [only] = all;
|
|
556
|
-
if (only === undefined)
|
|
557
|
-
throw new Error('session lookup failed');
|
|
558
|
-
only.markAgentActivity();
|
|
559
|
-
return only;
|
|
560
|
-
}
|
|
561
612
|
// Two projects and nothing to choose between them: refuse, BEFORE any scoring.
|
|
562
613
|
//
|
|
563
614
|
// Reported from the field: a call from `apps/next-app-router` was answered by a `rowy-*`
|
|
564
615
|
// session, and `act_and_wait` spent 16.9s returning `verified:'no'` about an app never under
|
|
565
616
|
// test — a confident false verdict, with a source pointer, about code the agent never touched.
|
|
566
617
|
//
|
|
567
|
-
// The recency rule
|
|
568
|
-
// the right question for two tabs of ONE app and the wrong question for two apps: the
|
|
569
|
-
// tab of the wrong project is still the wrong project. The reported case had a clear
|
|
570
|
-
// so the ambiguity check passed and the wrong app was selected.
|
|
618
|
+
// The recency rule in resolve() cannot prevent it. It asks "is one of these clearly fresher",
|
|
619
|
+
// which is the right question for two tabs of ONE app and the wrong question for two apps: the
|
|
620
|
+
// fresher tab of the wrong project is still the wrong project. The reported case had a clear
|
|
621
|
+
// winner, so the ambiguity check passed and the wrong app was selected.
|
|
571
622
|
//
|
|
572
623
|
// This sits ahead of scoring so freshness cannot rescue it. It only fires when nothing scoped
|
|
573
|
-
// the call — an explicit `sessionId`
|
|
574
|
-
// already narrowed `all` to one project by here.
|
|
624
|
+
// the call — an explicit `sessionId` never reaches here, and an explicit or default scope has
|
|
625
|
+
// already narrowed `all` to one project by here. One session is one project, so it never fires.
|
|
575
626
|
const projects = new Set(all.map((s) => s.projectId).filter((p) => p !== undefined));
|
|
576
627
|
if (projects.size > 1) {
|
|
577
628
|
const listed = all
|
|
@@ -584,25 +635,54 @@ export class SessionManager {
|
|
|
584
635
|
'never touched — the daemon scopes to the directory it was started in, so running it above ' +
|
|
585
636
|
'several apps leaves it unable to tell them apart.');
|
|
586
637
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
638
|
+
return all;
|
|
639
|
+
}
|
|
640
|
+
resolve(sessionId, scope) {
|
|
641
|
+
if (sessionId !== undefined) {
|
|
642
|
+
const found = this.#sessions.get(sessionId) ?? this.#successorOf(sessionId);
|
|
643
|
+
if (found === undefined) {
|
|
644
|
+
throw new Error(this.#unknownSessionError(sessionId));
|
|
645
|
+
}
|
|
646
|
+
found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
|
|
647
|
+
return found;
|
|
648
|
+
}
|
|
649
|
+
if (0 === this.#sessions.size) {
|
|
650
|
+
const closure = this.lastClosure();
|
|
651
|
+
// A diagnosis beats a checklist: it names which of the three causes this actually is.
|
|
652
|
+
const hint = this.#noSessionHint?.();
|
|
653
|
+
// BOTH, when there are both — the two answer different questions and the pair is strictly
|
|
654
|
+
// more than either. This used to be an either/or, and a recorded refusal DISCARDED the whole
|
|
655
|
+
// diagnosis: the one case where the daemon has hard evidence about why nothing is connected
|
|
656
|
+
// was also the one case where it threw away everything else it had learned, including the
|
|
657
|
+
// port scan and the next action. The refusal leads, because it is the fact.
|
|
658
|
+
const refusal = closure === undefined
|
|
659
|
+
? ''
|
|
660
|
+
: ` NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`;
|
|
661
|
+
// Hand the branch code to the refusal that is about to be reported for this throw. The
|
|
662
|
+
// diagnosis is computed HERE and the refusal is classified from the message downstream, so
|
|
663
|
+
// without this every no-session refusal lands in one undifferentiated bucket (#615).
|
|
664
|
+
notePendingNoSessionReason(this.#noSessionReason?.());
|
|
665
|
+
throw new Error(`${hint ?? NO_SESSION_CONNECTED_ERROR}${refusal}${NO_SESSION_NAVIGATE_FIX}`);
|
|
666
|
+
}
|
|
667
|
+
const all = this.#candidates(scope);
|
|
668
|
+
if (1 === all.length) {
|
|
669
|
+
const [only] = all;
|
|
670
|
+
if (only === undefined) {
|
|
671
|
+
throw new Error('session lookup failed');
|
|
672
|
+
}
|
|
673
|
+
only.markAgentActivity();
|
|
674
|
+
return only;
|
|
675
|
+
}
|
|
676
|
+
// Multiple sessions: score each (lower = better candidate for auto-selection). See rankOf().
|
|
677
|
+
const scored = all.map((s) => ({ s, score: rankOf(s), ms: s.lastSeenMs() }));
|
|
599
678
|
const bestScore = Math.min(...scored.map((x) => x.score));
|
|
600
679
|
const candidates = scored.filter((x) => x.score === bestScore);
|
|
601
680
|
// Sort candidates by recency (ascending lastSeenMs = most recently active first).
|
|
602
681
|
candidates.sort((a, b) => a.ms - b.ms);
|
|
603
682
|
const [best, runnerUp] = candidates;
|
|
604
|
-
if (best === undefined)
|
|
683
|
+
if (best === undefined) {
|
|
605
684
|
throw new Error('session lookup failed');
|
|
685
|
+
}
|
|
606
686
|
// Only auto-select if there is a clear winner.
|
|
607
687
|
//
|
|
608
688
|
// When at least one non-throttled (focused/visible) session exists, require a >1 s recency
|
|
@@ -636,6 +716,44 @@ export class SessionManager {
|
|
|
636
716
|
best.s.markAgentActivity();
|
|
637
717
|
return best.s;
|
|
638
718
|
}
|
|
719
|
+
/**
|
|
720
|
+
* Resolve the session for a call that only some sessions can serve.
|
|
721
|
+
*
|
|
722
|
+
* With no sessionId, resolve() chooses by health and recency alone. A lease that could resize the
|
|
723
|
+
* page lost to a focused tab that could not, and the call was refused (#1959). Here, when exactly
|
|
724
|
+
* one candidate can serve the call, that candidate is chosen.
|
|
725
|
+
*/
|
|
726
|
+
async resolveServing(sessionId, canServe) {
|
|
727
|
+
// A named session, or none connected: resolve() already gives the answer and its refusals.
|
|
728
|
+
if (sessionId !== undefined || 0 === this.#sessions.size) {
|
|
729
|
+
return this.resolve(sessionId);
|
|
730
|
+
}
|
|
731
|
+
// The same candidates resolve() chooses from, so the project and checkout scopes still apply.
|
|
732
|
+
// canServe can ask a browser, so each answer is awaited in turn.
|
|
733
|
+
const serving = [];
|
|
734
|
+
for (const candidate of this.#candidates()) {
|
|
735
|
+
if (await canServe(candidate)) {
|
|
736
|
+
serving.push(candidate);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Exactly one can serve the call: there is nothing to guess.
|
|
740
|
+
const [onlyServing, secondServing] = serving;
|
|
741
|
+
if (onlyServing !== undefined && secondServing === undefined) {
|
|
742
|
+
onlyServing.markAgentActivity();
|
|
743
|
+
return onlyServing;
|
|
744
|
+
}
|
|
745
|
+
// None can serve: return the usual choice, so the tool gives its own refusal and its advice.
|
|
746
|
+
// Several can serve, and the usual choice cannot: choosing among them is a guess, so refuse
|
|
747
|
+
// and name them. canServe admits only the caller's own sessions, so no other agent's lease is
|
|
748
|
+
// named here.
|
|
749
|
+
const chosen = this.resolve();
|
|
750
|
+
if (0 < serving.length && !serving.includes(chosen)) {
|
|
751
|
+
const ids = serving.map((s) => `'${s.id}'`).join(', ');
|
|
752
|
+
throw new Error(`no sessionId was given, and ${String(serving.length)} sessions can serve this call: ` +
|
|
753
|
+
`${ids}. Pass sessionId to choose one.`);
|
|
754
|
+
}
|
|
755
|
+
return chosen;
|
|
756
|
+
}
|
|
639
757
|
}
|
|
640
758
|
/**
|
|
641
759
|
* Which of a project's tabs belong to the agent's own git checkout.
|
|
@@ -649,18 +767,29 @@ export class SessionManager {
|
|
|
649
767
|
* dropped. A tab whose checkout cannot be read stays, because it can be the agent's own.
|
|
650
768
|
*/
|
|
651
769
|
export function scopeToCallerCheckout(sessions, caller, checkoutOfTab) {
|
|
652
|
-
if (caller === undefined)
|
|
770
|
+
if (caller === undefined) {
|
|
653
771
|
return { sessions: [...sessions] };
|
|
772
|
+
}
|
|
654
773
|
const placed = sessions.map((session) => ({ session, checkout: checkoutOfTab(session) }));
|
|
655
774
|
const mine = placed.filter((p) => isSame(p.checkout?.root, caller.root)).map((p) => p.session);
|
|
656
|
-
if (mine.length > 0)
|
|
775
|
+
if (mine.length > 0) {
|
|
657
776
|
return { sessions: mine };
|
|
777
|
+
}
|
|
658
778
|
const siblings = placed.filter((p) => isSame(p.checkout?.commonDir, caller.commonDir));
|
|
659
779
|
if (siblings.length !== placed.length) {
|
|
660
780
|
return { sessions: placed.filter((p) => !siblings.includes(p)).map((p) => p.session) };
|
|
661
781
|
}
|
|
662
|
-
if (0 === siblings.length)
|
|
782
|
+
if (0 === siblings.length) {
|
|
663
783
|
return { sessions: [] };
|
|
784
|
+
}
|
|
785
|
+
// The caller is the folder the MCP proxy started in, not always where the agent works. Claude Code
|
|
786
|
+
// starts the proxy in the main checkout and then moves the agent into a linked worktree (#1916).
|
|
787
|
+
// So from the main checkout, one linked worktree that serves every tab is the agent's own: drive it.
|
|
788
|
+
// The tab names its `worktree`. Tabs of two worktrees, or a proxy in a linked worktree, stay refused.
|
|
789
|
+
const roots = new Set(siblings.map((p) => p.checkout?.root));
|
|
790
|
+
if (!caller.linked && 1 === roots.size && true === siblings[0]?.checkout?.linked) {
|
|
791
|
+
return { sessions: siblings.map((p) => p.session) };
|
|
792
|
+
}
|
|
664
793
|
const listed = siblings
|
|
665
794
|
.map((p) => `${describeCheckout(p.checkout)} (sessionId '${p.session.id}', ${p.session.url})`)
|
|
666
795
|
.join(', ');
|
|
@@ -675,8 +804,9 @@ export function scopeToCallerCheckout(sessions, caller, checkoutOfTab) {
|
|
|
675
804
|
const isSame = (a, b) => a !== undefined && samePath(a, b);
|
|
676
805
|
/** `/wt/a (branch feat/a)`, or the folder alone on a detached HEAD. */
|
|
677
806
|
export function describeCheckout(checkout) {
|
|
678
|
-
if (checkout === undefined)
|
|
807
|
+
if (checkout === undefined) {
|
|
679
808
|
return 'an unknown folder';
|
|
809
|
+
}
|
|
680
810
|
return checkout.branch === undefined
|
|
681
811
|
? checkout.root
|
|
682
812
|
: `${checkout.root} (branch ${checkout.branch})`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { noteOwnReload, notePageLoaded } from '../../features/harness/drive-runs.js';
|
|
2
2
|
import { asProjectId } from '@reticlehq/core';
|
|
3
3
|
import { narrationArgs } from './narration-args.js';
|
|
4
|
+
import { sdkBehindNote } from '../../command/version/version-skew.js';
|
|
4
5
|
import { refusedResult } from './page-commands/undeclared-command.js';
|
|
5
6
|
import { impactSnapshot, recordImpact } from '../../memory/impact/impact-recorder.js';
|
|
6
7
|
import { callingAgent } from '../../hooks/coding-agents.js';
|
|
@@ -580,9 +581,9 @@ export class Session {
|
|
|
580
581
|
this.recordActedRef(ref);
|
|
581
582
|
if (name === ReticleCommand.REFRESH)
|
|
582
583
|
noteOwnReload(this.id, this.#clock());
|
|
583
|
-
// Refused before it is sent
|
|
584
|
-
|
|
585
|
-
const refused = refusedResult(name, this.commands, `${COMMAND_ID_PREFIX}refused
|
|
584
|
+
// Refused before it is sent when the page did not declare it. See `undeclared-command.ts`.
|
|
585
|
+
const behind = sdkBehindNote(this.sdkVersion);
|
|
586
|
+
const refused = refusedResult(name, this.commands, `${COMMAND_ID_PREFIX}refused`, behind);
|
|
586
587
|
if (refused !== undefined)
|
|
587
588
|
return Promise.resolve(refused);
|
|
588
589
|
// The page already re-dialled under this same id. Nothing has been sent yet, so handing the
|