@reticlehq/server 3.2.0 → 3.4.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-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +23 -22
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +41 -0
- package/dist/command/cli/adhoc-suite.js +159 -0
- package/dist/command/cli/adhoc-verdict.d.ts +58 -2
- package/dist/command/cli/adhoc-verdict.js +108 -13
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +30 -8
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +14 -1
- package/dist/command/cli/cli-parse.js +54 -8
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +80 -0
- package/dist/command/cli/cli-verify.js +181 -26
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +9 -29
- package/dist/command/cli/launch/cli-launch.js +27 -49
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
- package/dist/command/cli/ports/resolve/cli-port.js +169 -15
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +67 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +171 -156
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +36 -22
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +13 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +78 -104
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +40 -9
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +33 -12
- package/dist/language/flows/flow-replay-run.js +131 -52
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +27 -12
- package/dist/language/flows/flow-replay.js +127 -111
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +18 -5
- package/dist/language/flows/flows.js +101 -16
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +53 -13
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/verify-change-tools.js +19 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -3
- package/dist/memory/cloud/cloud-sync.js +7 -10
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +78 -1
- package/dist/memory/cloud/sync-cycle.js +321 -53
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +45 -20
- package/dist/memory/cloud/sync-disk.js +27 -8
- package/dist/memory/impact/impact-store.js +6 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +252 -140
- package/dist/memory/intent/intent-shard.d.ts +0 -23
- package/dist/memory/intent/intent-shard.js +1 -11
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +19 -14
- package/dist/memory/journal/drive-flow.js +65 -48
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +13 -2
- package/dist/memory/journal/session-end.js +88 -18
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/fs/fs-port.js +44 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +31 -5
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +21 -13
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +43 -3
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +29 -6
- package/dist/portal/session/session-manager.js +42 -5
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +10 -0
- package/dist/portal/session/session.js +34 -26
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +5 -5
- package/dist/surface/mcp/mcp.js +4 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +21 -8
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +68 -8
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +51 -74
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +26 -3
- package/dist/surface/tools/assert/assert-verdict.js +32 -6
- package/dist/surface/tools/browser-tools.js +17 -4
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/invoke-tool.js +17 -4
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +68 -11
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +23 -7
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +68 -29
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +34 -0
- package/dist/wire-journal.js +104 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +5 -2
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +21 -9
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +32 -2
- package/docs/frameworks.mdx +140 -9
- package/docs/getting-started.md +3 -3
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +37 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +176 -7
- package/docs/quickstart.mdx +2 -2
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +11 -11
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +8 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +62 -0
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +11 -10
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
- package/dist/judgement/phenomena/phenomena.d.ts +0 -26
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -37,10 +37,28 @@ const LoginSchema = z.object({
|
|
|
37
37
|
* code out of a server log they may not even be tailing.
|
|
38
38
|
*/
|
|
39
39
|
const RequestCodeSchema = z.object({ devCode: z.string().optional() });
|
|
40
|
+
const MeSchema = z.object({ email: z.string().min(1).nullable().optional() });
|
|
41
|
+
/** The signed-in address, asked of the cloud. Best-effort: a login never fails for want of it. */
|
|
42
|
+
const signedInEmail = async (url, token) => {
|
|
43
|
+
try {
|
|
44
|
+
return MeSchema.parse(await api('GET', `${url}/v1/me`, token)).email ?? undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
40
50
|
/** Persist a session token under ~/.reticle and print the next step. Shared by both login paths. */
|
|
41
|
-
const writeSession = async (url, token, orgName, orgId, project, link) => {
|
|
51
|
+
const writeSession = async (url, token, orgName, orgId, project, link, email) => {
|
|
42
52
|
await mkdir(join(home(), SESSIONS_DIR), { recursive: true });
|
|
43
|
-
|
|
53
|
+
// Who signed in, so a flow saved on this machine can say which person made it (FlowFile.author).
|
|
54
|
+
const who = email ?? (await signedInEmail(url, token));
|
|
55
|
+
const body = `${JSON.stringify({
|
|
56
|
+
url: normalizeUrl(url),
|
|
57
|
+
token,
|
|
58
|
+
orgName,
|
|
59
|
+
...(orgId === undefined ? {} : { orgId }),
|
|
60
|
+
...(who === undefined ? {} : { email: who }),
|
|
61
|
+
}, null, 2)}\n`;
|
|
44
62
|
// Both, on purpose. The per-host file is what lets another environment stay logged in; the active
|
|
45
63
|
// file is what a bare command with no override resolves through, and keeping it means nothing
|
|
46
64
|
// about the single-environment workflow changes.
|
|
@@ -159,7 +177,7 @@ export const cmdLogin = async (argv, link) => {
|
|
|
159
177
|
code = requested.devCode;
|
|
160
178
|
}
|
|
161
179
|
const parsed = LoginSchema.parse(await api('POST', `${url}/v1/auth/login`, null, { email, code }));
|
|
162
|
-
await writeSession(url, parsed.token, parsed.org.name, parsed.org.id, f['project'], link);
|
|
180
|
+
await writeSession(url, parsed.token, parsed.org.name, parsed.org.id, f['project'], link, email);
|
|
163
181
|
return 0;
|
|
164
182
|
};
|
|
165
183
|
/** `reticle logout` — forget the cached session token (per-project keys under credentials.json stay). */
|
|
@@ -67,16 +67,19 @@ const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
|
|
|
67
67
|
function bareSpecifiersIn(dir) {
|
|
68
68
|
const found = new Set();
|
|
69
69
|
const walk = (at) => {
|
|
70
|
-
for (const
|
|
70
|
+
for (const entry of readdirSync(at, { withFileTypes: true })) {
|
|
71
|
+
const name = entry.name;
|
|
71
72
|
const path = join(at, name);
|
|
72
|
-
if (
|
|
73
|
+
if (entry.isDirectory()) {
|
|
73
74
|
if ('node_modules' !== name)
|
|
74
75
|
walk(path);
|
|
75
76
|
continue;
|
|
76
77
|
}
|
|
77
78
|
if (!name.endsWith('.js') || name.endsWith('.test.js'))
|
|
78
79
|
continue;
|
|
79
|
-
|
|
80
|
+
// `from` as a keyword, not the tail of a string: core's `z.discriminatedUnion('from', [...])`
|
|
81
|
+
// read as an import of everything up to the next quote. A specifier never holds whitespace.
|
|
82
|
+
for (const match of readFileSync(path, 'utf8').matchAll(/(?<![\w$.'"])from\s*'([^.'\s][^'\s]*)'/g)) {
|
|
80
83
|
const spec = match[1];
|
|
81
84
|
if (spec !== undefined && !NOT_SERVABLE.test(spec))
|
|
82
85
|
found.add(spec);
|
|
@@ -43,6 +43,11 @@ export interface ChromiumProbe {
|
|
|
43
43
|
* and kept reading "not installed", with no way to see that neither was ever searched.
|
|
44
44
|
*/
|
|
45
45
|
searchedPaths?: readonly string[] | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* The installed browser Reticle drives instead when its own build is absent (`Google Chrome`,
|
|
48
|
+
* `Microsoft Edge`) — see launch-chromium. Present only when `exists` is false and one was found.
|
|
49
|
+
*/
|
|
50
|
+
fallback?: string | undefined;
|
|
46
51
|
}
|
|
47
52
|
/** The install command, pinned when we know what to pin to. */
|
|
48
53
|
export declare function chromiumInstallCommand(playwrightVersion?: string): string;
|
|
@@ -170,6 +170,11 @@ export function chromiumHint(probe) {
|
|
|
170
170
|
: `✓ installed (${probe.wantedRevision})`;
|
|
171
171
|
}
|
|
172
172
|
const command = chromiumInstallCommand(probe.playwrightVersion);
|
|
173
|
+
// Not a failure: every Reticle browser launches on this one. The install command stays on the
|
|
174
|
+
// line for a reader who wants the exact build the tests run against.
|
|
175
|
+
if (probe.fallback !== undefined && probe.executablePath !== undefined) {
|
|
176
|
+
return `✓ using the installed ${probe.fallback} (Playwright's own Chromium is not installed; ${command} adds it)`;
|
|
177
|
+
}
|
|
173
178
|
if (probe.executablePath === undefined) {
|
|
174
179
|
// Same rule as the missing-browser line below: a verdict with nowhere to check it against reads
|
|
175
180
|
// as a broken check. The roots say whether this process even searched the directory the reader
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** What this reads off `/status`. Everything is optional — an older daemon may send none of it. */
|
|
2
|
-
|
|
2
|
+
interface SessionFacts {
|
|
3
3
|
sessionCount?: unknown;
|
|
4
4
|
why?: unknown;
|
|
5
5
|
}
|
|
@@ -10,3 +10,4 @@ export interface SessionsLine {
|
|
|
10
10
|
why?: string;
|
|
11
11
|
}
|
|
12
12
|
export declare function sessionsLine(facts: SessionFacts): SessionsLine;
|
|
13
|
+
export {};
|
|
@@ -3,34 +3,7 @@
|
|
|
3
3
|
* "show me the app" flow (`reticle open`). Split out of cli.ts so that file stays under the size cap.
|
|
4
4
|
* The decision logic is pure (unit-tested); the IO (fetch, OS browser launch) is injected/isolated.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
interface StatusSession {
|
|
8
|
-
sessionId: string;
|
|
9
|
-
url: string;
|
|
10
|
-
throttled: boolean;
|
|
11
|
-
/**
|
|
12
|
-
* The tab is backgrounded. Reported by the daemon on every session; `false` on one too old to say,
|
|
13
|
-
* which reads as visible — the conservative answer, since it keeps the reuse this command has
|
|
14
|
-
* always done rather than opening a tab on a guess.
|
|
15
|
-
*/
|
|
16
|
-
hidden: boolean;
|
|
17
|
-
stale: boolean;
|
|
18
|
-
pendingMarks: number;
|
|
19
|
-
/** The tab is attached and answering nothing — see Session.unresponsive. Absent on an older daemon. */
|
|
20
|
-
unresponsive?: true;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Reduce the daemon's /status JSON to the compact view `reticle status` prints. Pure: narrows the
|
|
24
|
-
* untrusted wire payload (never `any`) and tolerates a missing/partial body so a malformed response
|
|
25
|
-
* degrades to "running, 0 sessions" instead of throwing.
|
|
26
|
-
*/
|
|
27
|
-
export declare function summarizeStatus(payload: unknown): {
|
|
28
|
-
sessionCount: number;
|
|
29
|
-
sessions: StatusSession[];
|
|
30
|
-
why?: string;
|
|
31
|
-
/** The same diagnosis without the differential, for a surface a person reads. */
|
|
32
|
-
whyLead?: string;
|
|
33
|
-
};
|
|
6
|
+
export { summarizeStatus } from '../../daemon/binding/daemon-status-probe.js';
|
|
34
7
|
/**
|
|
35
8
|
* Warn when the daemon already on this port is a different version than this CLI, and attach anyway.
|
|
36
9
|
*
|
|
@@ -39,6 +12,13 @@ export declare function summarizeStatus(payload: unknown): {
|
|
|
39
12
|
* another agent's session on a version bump, which is worse than a loud line.
|
|
40
13
|
*/
|
|
41
14
|
export declare function warnOnDaemonSkew(port: number): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* The skew sentence for a daemon's `/status` body, or undefined when it speaks our contract.
|
|
17
|
+
*
|
|
18
|
+
* Shared with `init`, which must not ADOPT a skewed daemon: an older one refuses every hello from
|
|
19
|
+
* the page init just instrumented.
|
|
20
|
+
*/
|
|
21
|
+
export declare function daemonSkew(status: unknown): string | undefined;
|
|
42
22
|
/** What `reticle open` should do: reuse an already-connected tab, open a new one, or ask for a url. */
|
|
43
23
|
type OpenDecision = {
|
|
44
24
|
action: 'reuse';
|
|
@@ -75,6 +55,7 @@ export declare function decideOpen(all: {
|
|
|
75
55
|
url: string;
|
|
76
56
|
unresponsive?: boolean;
|
|
77
57
|
hidden?: boolean;
|
|
58
|
+
lastSeenMs?: number;
|
|
78
59
|
}[], url: string | undefined): OpenDecision;
|
|
79
60
|
/** The OS command that opens a URL in the default browser, per platform. Pure — unit-tested. */
|
|
80
61
|
export declare function openCommand(url: string, platform: NodeJS.Platform): {
|
|
@@ -97,4 +78,3 @@ export declare function openInBrowser(url: string, platform?: NodeJS.Platform, r
|
|
|
97
78
|
* by spawning a real launcher on a developer machine.
|
|
98
79
|
*/
|
|
99
80
|
export declare function launcherFailure(code: number | null, signal: NodeJS.Signals | null): string | null;
|
|
100
|
-
export {};
|
|
@@ -1,53 +1,19 @@
|
|
|
1
1
|
import { NodePlatform } from '../../../machine/platform.js';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { isOpaqueOrigin } from '@reticlehq/core';
|
|
3
|
+
import { isOpaqueOrigin, SESSION_HEALTH } from '@reticlehq/core';
|
|
4
4
|
import { daemonFix, describeSkew } from '../../version/version-skew.js';
|
|
5
5
|
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
6
6
|
import { SERVER_VERSION } from '../../version/identity/server-version.js';
|
|
7
7
|
import { log } from '../../../log.js';
|
|
8
8
|
import { fetchStatus } from '../../daemon/binding/daemon-status-probe.js';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* CLI launch + status helpers — the daemon-introspection (`reticle status`) and the one-command
|
|
11
|
+
* "show me the app" flow (`reticle open`). Split out of cli.ts so that file stays under the size cap.
|
|
12
|
+
* The decision logic is pure (unit-tested); the IO (fetch, OS browser launch) is injected/isolated.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const obj = payload;
|
|
18
|
-
// Carried through to the printed line: with no sessions this is the whole answer, and dropping it
|
|
19
|
-
// here would silently undo the reason it is on the wire.
|
|
20
|
-
const why = 'string' === typeof obj['why'] ? obj['why'] : undefined;
|
|
21
|
-
// Absent on a daemon older than this field, which is why every reader falls back to `why`.
|
|
22
|
-
const whyLead = 'string' === typeof obj['whyLead'] ? obj['whyLead'] : undefined;
|
|
23
|
-
const raw = Array.isArray(obj['sessions']) ? obj['sessions'] : [];
|
|
24
|
-
const sessions = raw
|
|
25
|
-
.map((s) => {
|
|
26
|
-
if (typeof s !== 'object' || null === s)
|
|
27
|
-
return null;
|
|
28
|
-
const r = s;
|
|
29
|
-
const sessionId = 'string' === typeof r['sessionId'] ? r['sessionId'] : '';
|
|
30
|
-
if ('' === sessionId)
|
|
31
|
-
return null;
|
|
32
|
-
return {
|
|
33
|
-
sessionId,
|
|
34
|
-
url: 'string' === typeof r['url'] ? r['url'] : '',
|
|
35
|
-
throttled: true === r['throttled'],
|
|
36
|
-
hidden: true === r['hidden'],
|
|
37
|
-
stale: true === r['stale'],
|
|
38
|
-
pendingMarks: 'number' === typeof r['pendingMarks'] ? r['pendingMarks'] : 0,
|
|
39
|
-
...(true === r['unresponsive'] ? { unresponsive: true } : {}),
|
|
40
|
-
};
|
|
41
|
-
})
|
|
42
|
-
.filter((s) => s !== null);
|
|
43
|
-
const sessionCount = 'number' === typeof obj['sessionCount'] ? obj['sessionCount'] : sessions.length;
|
|
44
|
-
return {
|
|
45
|
-
sessionCount,
|
|
46
|
-
sessions,
|
|
47
|
-
...(why === undefined ? {} : { why }),
|
|
48
|
-
...(whyLead === undefined ? {} : { whyLead }),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
14
|
+
// Parsing the daemon's /status payload lives beside the probe that fetches it; re-exported here for
|
|
15
|
+
// the callers that already import it from this module.
|
|
16
|
+
export { summarizeStatus } from '../../daemon/binding/daemon-status-probe.js';
|
|
51
17
|
/** A string field off the /status body, or undefined on a daemon too old to report it. */
|
|
52
18
|
function statusField(payload, key) {
|
|
53
19
|
if (typeof payload !== 'object' || null === payload)
|
|
@@ -63,17 +29,25 @@ function statusField(payload, key) {
|
|
|
63
29
|
* another agent's session on a version bump, which is worse than a loud line.
|
|
64
30
|
*/
|
|
65
31
|
export async function warnOnDaemonSkew(port) {
|
|
66
|
-
const
|
|
32
|
+
const skew = daemonSkew(await fetchStatus(port));
|
|
33
|
+
if (skew !== undefined)
|
|
34
|
+
log('reticle_daemon_skew', { port, warning: skew });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The skew sentence for a daemon's `/status` body, or undefined when it speaks our contract.
|
|
38
|
+
*
|
|
39
|
+
* Shared with `init`, which must not ADOPT a skewed daemon: an older one refuses every hello from
|
|
40
|
+
* the page init just instrumented.
|
|
41
|
+
*/
|
|
42
|
+
export function daemonSkew(status) {
|
|
67
43
|
const daemonVersion = statusField(status, 'version');
|
|
68
|
-
|
|
44
|
+
return describeSkew({
|
|
69
45
|
what: 'the daemon already running on this port',
|
|
70
46
|
version: daemonVersion,
|
|
71
47
|
contract: statusField(status, 'contract'),
|
|
72
48
|
// The daemon is the peer here and this process is the agent side.
|
|
73
49
|
fix: daemonFix(daemonVersion, SERVER_VERSION),
|
|
74
50
|
}, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
|
|
75
|
-
if (skew !== undefined)
|
|
76
|
-
log('reticle_daemon_skew', { port, warning: skew });
|
|
77
51
|
}
|
|
78
52
|
function sameOrigin(a, b) {
|
|
79
53
|
try {
|
|
@@ -116,10 +90,14 @@ export function decideOpen(all, url) {
|
|
|
116
90
|
// three checks that could never resolve. Opening the url instead costs one foreground tab and the
|
|
117
91
|
// hidden one is left exactly where it was — nothing is navigated out from under anybody.
|
|
118
92
|
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
// the
|
|
122
|
-
|
|
93
|
+
// And a tab the SDK has gone SILENT in. The page heartbeats on a native timer every few seconds
|
|
94
|
+
// whatever it is doing, so a quiet page is not a silent one: past the daemon's own staleness
|
|
95
|
+
// threshold the page is frozen (a discarded tab, a sleeping machine, a wedged main thread). This
|
|
96
|
+
// looked only at the url, and `verify --expect` drove a tab last heard from 105s earlier into
|
|
97
|
+
// "command 'match' timed out after 8000ms" instead of opening the url it had been given.
|
|
98
|
+
const sessions = all.filter((s) => true !== s.unresponsive &&
|
|
99
|
+
true !== s.hidden &&
|
|
100
|
+
(s.lastSeenMs ?? 0) <= SESSION_HEALTH.STALE_THRESHOLD_MS);
|
|
123
101
|
if (url === undefined) {
|
|
124
102
|
const first = sessions[0];
|
|
125
103
|
if (first !== undefined)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the CLI whose major matches the SDK the project actually installed.
|
|
3
|
+
*
|
|
4
|
+
* The agent's MCP entry and every rule file `init` writes say `npx @reticlehq/server`, deliberately
|
|
5
|
+
* unpinned (see `npxServerArgs` in init), which resolves the LATEST server — while the project's SDK
|
|
6
|
+
* stays locked at whatever `init` installed. A project wired on the previous major therefore got a
|
|
7
|
+
* daemon from the next one, and every verdict came back `version_skew`; a global install from an
|
|
8
|
+
* older release shadows npx the same way in the other direction. Nothing the project could do fixed
|
|
9
|
+
* it short of upgrading, and the report named the skew without resolving it.
|
|
10
|
+
*
|
|
11
|
+
* So the one entry every command passes through asks, before doing anything: is the SDK in this
|
|
12
|
+
* project on my major? If not, it hands the same arguments to `npx @reticlehq/server@<that version>`
|
|
13
|
+
* and steps aside. Only across majors — a minor apart verifies fine, and re-running for it would
|
|
14
|
+
* cost an npx resolve on every command for nothing.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately free of `@/` and relative imports: the stdio test runs this file in a bare Node child
|
|
17
|
+
* to see the real file descriptors, and that child has neither the alias nor the build.
|
|
18
|
+
*/
|
|
19
|
+
import { type ChildProcess } from 'node:child_process';
|
|
20
|
+
/** The packages an instrumented project installs; they release in lockstep, so any one will do. */
|
|
21
|
+
export declare const SDK_PACKAGES: readonly ["@reticlehq/react", "@reticlehq/browser", "@reticlehq/next", "@reticlehq/vite-plugin"];
|
|
22
|
+
export declare const VersionMatchEnv: {
|
|
23
|
+
/** Set to anything to keep the CLI you ran, whatever the project installed. */
|
|
24
|
+
readonly OPT_OUT: "RETICLE_NO_VERSION_MATCH";
|
|
25
|
+
/** Set on the re-run child, so it can never re-run itself again. */
|
|
26
|
+
readonly REEXECUTED: "RETICLE_VERSION_MATCHED";
|
|
27
|
+
/** What npm puts here for `npx <spec>` — how a version typed on the npx line is recognised. */
|
|
28
|
+
readonly NPX_PACKAGE: "npm_config_package";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The SDK version installed for `projectDir`, found the way Node resolves it: the nearest
|
|
32
|
+
* `node_modules` at or above the directory, so a package hoisted to a workspace root counts.
|
|
33
|
+
*/
|
|
34
|
+
export declare function installedSdkVersion(projectDir: string, readFile: (path: string) => string | undefined): string | undefined;
|
|
35
|
+
/** `readFile` for the real filesystem: the text, or undefined when there is nothing readable. */
|
|
36
|
+
export declare function readTextFile(path: string): string | undefined;
|
|
37
|
+
/** The one line a person sees on stderr when the CLI hands over — stdout belongs to the protocol. */
|
|
38
|
+
export declare function versionMatchNote(sdkVersion: string, cliVersion: string): string;
|
|
39
|
+
export interface VersionMatchInput {
|
|
40
|
+
argv: readonly string[];
|
|
41
|
+
cliVersion: string;
|
|
42
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
43
|
+
projectDir: string;
|
|
44
|
+
readFile: (path: string) => string | undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The version to re-run at, or undefined to carry on in this process. Pure: the filesystem and the
|
|
48
|
+
* environment are handed in.
|
|
49
|
+
*/
|
|
50
|
+
export declare function versionToMatch(input: VersionMatchInput): string | undefined;
|
|
51
|
+
/** How to launch `npx --yes @reticlehq/server@<version> <args>` on this platform. */
|
|
52
|
+
export declare function npxInvocation(version: string, args: readonly string[], on: string): {
|
|
53
|
+
file: string;
|
|
54
|
+
argv: string[];
|
|
55
|
+
shell: boolean;
|
|
56
|
+
};
|
|
57
|
+
/** The two things the re-run needs from its child: when it ends, and a way to pass a signal on. */
|
|
58
|
+
export interface ReexecChild {
|
|
59
|
+
/** Called once, with the exit code — or undefined when the child never started or was killed. */
|
|
60
|
+
onEnd(listener: (code: number | undefined, error: Error | undefined) => void): void;
|
|
61
|
+
kill(signal: NodeJS.Signals): void;
|
|
62
|
+
}
|
|
63
|
+
export type ReexecSpawner = (file: string, argv: string[], options: {
|
|
64
|
+
stdio: 'inherit';
|
|
65
|
+
env: NodeJS.ProcessEnv;
|
|
66
|
+
shell: boolean;
|
|
67
|
+
}) => ReexecChild;
|
|
68
|
+
/** A ChildProcess, seen through `ReexecChild`. */
|
|
69
|
+
export declare function asReexecChild(child: ChildProcess): ReexecChild;
|
|
70
|
+
/** The real spawner. */
|
|
71
|
+
export declare const nodeSpawner: ReexecSpawner;
|
|
72
|
+
/**
|
|
73
|
+
* Hand this invocation to the matching version and exit with its code.
|
|
74
|
+
*
|
|
75
|
+
* `stdio: 'inherit'` gives the child this process's own file descriptors, so an MCP client's JSON-RPC
|
|
76
|
+
* stream reaches it without passing through a byte of JavaScript here — nothing to buffer, reframe or
|
|
77
|
+
* lose. For that to hold, this must run before anything writes to stdout.
|
|
78
|
+
*/
|
|
79
|
+
export declare function reexecAtVersion(version: string, args: readonly string[], env: NodeJS.ProcessEnv, deps: {
|
|
80
|
+
spawn: ReexecSpawner;
|
|
81
|
+
exit: (code: number) => void;
|
|
82
|
+
platform?: string;
|
|
83
|
+
warn?: (line: string) => void;
|
|
84
|
+
}): void;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the CLI whose major matches the SDK the project actually installed.
|
|
3
|
+
*
|
|
4
|
+
* The agent's MCP entry and every rule file `init` writes say `npx @reticlehq/server`, deliberately
|
|
5
|
+
* unpinned (see `npxServerArgs` in init), which resolves the LATEST server — while the project's SDK
|
|
6
|
+
* stays locked at whatever `init` installed. A project wired on the previous major therefore got a
|
|
7
|
+
* daemon from the next one, and every verdict came back `version_skew`; a global install from an
|
|
8
|
+
* older release shadows npx the same way in the other direction. Nothing the project could do fixed
|
|
9
|
+
* it short of upgrading, and the report named the skew without resolving it.
|
|
10
|
+
*
|
|
11
|
+
* So the one entry every command passes through asks, before doing anything: is the SDK in this
|
|
12
|
+
* project on my major? If not, it hands the same arguments to `npx @reticlehq/server@<that version>`
|
|
13
|
+
* and steps aside. Only across majors — a minor apart verifies fine, and re-running for it would
|
|
14
|
+
* cost an npx resolve on every command for nothing.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately free of `@/` and relative imports: the stdio test runs this file in a bare Node child
|
|
17
|
+
* to see the real file descriptors, and that child has neither the alias nor the build.
|
|
18
|
+
*/
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { dirname, join } from 'node:path';
|
|
22
|
+
import { NodePlatform, windowsShellArg } from '@reticlehq/init';
|
|
23
|
+
/** The packages an instrumented project installs; they release in lockstep, so any one will do. */
|
|
24
|
+
export const SDK_PACKAGES = [
|
|
25
|
+
'@reticlehq/react',
|
|
26
|
+
'@reticlehq/browser',
|
|
27
|
+
'@reticlehq/next',
|
|
28
|
+
'@reticlehq/vite-plugin',
|
|
29
|
+
];
|
|
30
|
+
/** The package that carries the `reticle` bin — what the re-run asks npx for. */
|
|
31
|
+
const SERVER_PACKAGE = '@reticlehq/server';
|
|
32
|
+
const NPX = 'npx';
|
|
33
|
+
const NPX_WINDOWS = 'npx.cmd';
|
|
34
|
+
const NPX_YES = '--yes';
|
|
35
|
+
const NODE_MODULES = 'node_modules';
|
|
36
|
+
const PACKAGE_JSON = 'package.json';
|
|
37
|
+
const LATEST_TAG = 'latest';
|
|
38
|
+
const MARKED = '1';
|
|
39
|
+
export const VersionMatchEnv = {
|
|
40
|
+
/** Set to anything to keep the CLI you ran, whatever the project installed. */
|
|
41
|
+
OPT_OUT: 'RETICLE_NO_VERSION_MATCH',
|
|
42
|
+
/** Set on the re-run child, so it can never re-run itself again. */
|
|
43
|
+
REEXECUTED: 'RETICLE_VERSION_MATCHED',
|
|
44
|
+
/** What npm puts here for `npx <spec>` — how a version typed on the npx line is recognised. */
|
|
45
|
+
NPX_PACKAGE: 'npm_config_package',
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Commands whose job is to CHANGE the version, or to report on this binary. Re-running `init` or
|
|
49
|
+
* `update` at the project's old version would undo exactly what the person asked for; `version` and
|
|
50
|
+
* `help` describe the binary they ran. `_daemon` is spawned by a CLI that already decided.
|
|
51
|
+
*/
|
|
52
|
+
const KEEP_COMMANDS = new Set([
|
|
53
|
+
'init',
|
|
54
|
+
'update',
|
|
55
|
+
'rollback',
|
|
56
|
+
'version',
|
|
57
|
+
'help',
|
|
58
|
+
'_daemon',
|
|
59
|
+
]);
|
|
60
|
+
const KEEP_FLAGS = new Set(['--version', '-v', '--help', '-h']);
|
|
61
|
+
const SETUP_COMMAND = 'setup';
|
|
62
|
+
const SETUP_INSTALL = 'install';
|
|
63
|
+
/** A published release — not `workspace:*`, a link, or a tag npx could not pin. */
|
|
64
|
+
const RELEASE_VERSION = /^(\d+)\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
65
|
+
function majorOf(version) {
|
|
66
|
+
return RELEASE_VERSION.exec(version)?.[1];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The SDK version installed for `projectDir`, found the way Node resolves it: the nearest
|
|
70
|
+
* `node_modules` at or above the directory, so a package hoisted to a workspace root counts.
|
|
71
|
+
*/
|
|
72
|
+
export function installedSdkVersion(projectDir, readFile) {
|
|
73
|
+
let dir = projectDir;
|
|
74
|
+
for (;;) {
|
|
75
|
+
for (const pkg of SDK_PACKAGES) {
|
|
76
|
+
const raw = readFile(join(dir, NODE_MODULES, pkg, PACKAGE_JSON));
|
|
77
|
+
if (raw === undefined)
|
|
78
|
+
continue;
|
|
79
|
+
try {
|
|
80
|
+
const version = JSON.parse(raw).version;
|
|
81
|
+
if ('string' === typeof version)
|
|
82
|
+
return version;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// A manifest that will not parse is no evidence; try the next package.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const parent = dirname(dir);
|
|
89
|
+
if (parent === dir)
|
|
90
|
+
return undefined;
|
|
91
|
+
dir = parent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** `readFile` for the real filesystem: the text, or undefined when there is nothing readable. */
|
|
95
|
+
export function readTextFile(path) {
|
|
96
|
+
try {
|
|
97
|
+
return readFileSync(path, 'utf8');
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** The one line a person sees on stderr when the CLI hands over — stdout belongs to the protocol. */
|
|
104
|
+
export function versionMatchNote(sdkVersion, cliVersion) {
|
|
105
|
+
return (`This project's Reticle SDK is ${sdkVersion}; running ${SERVER_PACKAGE}@${sdkVersion} to match ` +
|
|
106
|
+
`(this CLI is ${cliVersion}, a different major). Set ${VersionMatchEnv.OPT_OUT}=1 to keep this one.`);
|
|
107
|
+
}
|
|
108
|
+
/** Did the person name a version on the npx line (`npx @reticlehq/server@<version> …`)? */
|
|
109
|
+
function pinnedOnNpxLine(spec) {
|
|
110
|
+
if (spec === undefined || !spec.startsWith(`${SERVER_PACKAGE}@`))
|
|
111
|
+
return false;
|
|
112
|
+
return spec.slice(SERVER_PACKAGE.length + 1) !== LATEST_TAG;
|
|
113
|
+
}
|
|
114
|
+
function keepsThisBinary(argv) {
|
|
115
|
+
const [command, sub] = argv;
|
|
116
|
+
if (command !== undefined && KEEP_COMMANDS.has(command))
|
|
117
|
+
return true;
|
|
118
|
+
if (SETUP_COMMAND === command && SETUP_INSTALL === sub)
|
|
119
|
+
return true;
|
|
120
|
+
return argv.some((arg) => KEEP_FLAGS.has(arg));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The version to re-run at, or undefined to carry on in this process. Pure: the filesystem and the
|
|
124
|
+
* environment are handed in.
|
|
125
|
+
*/
|
|
126
|
+
export function versionToMatch(input) {
|
|
127
|
+
const { env } = input;
|
|
128
|
+
if (env[VersionMatchEnv.REEXECUTED] !== undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (env[VersionMatchEnv.OPT_OUT] !== undefined)
|
|
131
|
+
return undefined;
|
|
132
|
+
if (pinnedOnNpxLine(env[VersionMatchEnv.NPX_PACKAGE]))
|
|
133
|
+
return undefined;
|
|
134
|
+
if (keepsThisBinary(input.argv))
|
|
135
|
+
return undefined;
|
|
136
|
+
const sdk = installedSdkVersion(input.projectDir, input.readFile);
|
|
137
|
+
if (sdk === undefined)
|
|
138
|
+
return undefined;
|
|
139
|
+
const sdkMajor = majorOf(sdk);
|
|
140
|
+
if (sdkMajor === undefined)
|
|
141
|
+
return undefined;
|
|
142
|
+
return sdkMajor === majorOf(input.cliVersion) ? undefined : sdk;
|
|
143
|
+
}
|
|
144
|
+
/** How to launch `npx --yes @reticlehq/server@<version> <args>` on this platform. */
|
|
145
|
+
export function npxInvocation(version, args, on) {
|
|
146
|
+
const argv = [NPX_YES, `${SERVER_PACKAGE}@${version}`, ...args];
|
|
147
|
+
// A `.cmd` cannot be spawned without a shell since the CVE-2024-27980 fix, and a shell needs every
|
|
148
|
+
// argument quoted — the same rule `npmSpawn` in the updater follows.
|
|
149
|
+
if (NodePlatform.WINDOWS !== on)
|
|
150
|
+
return { file: NPX, argv, shell: false };
|
|
151
|
+
return { file: [NPX_WINDOWS, ...argv.map(windowsShellArg)].join(' '), argv: [], shell: true };
|
|
152
|
+
}
|
|
153
|
+
/** A ChildProcess, seen through `ReexecChild`. */
|
|
154
|
+
export function asReexecChild(child) {
|
|
155
|
+
return {
|
|
156
|
+
onEnd: (listener) => {
|
|
157
|
+
// Node can emit `error` AND `exit` for one child; the listener exits the process, so once.
|
|
158
|
+
let ended = false;
|
|
159
|
+
const end = (code, error) => {
|
|
160
|
+
if (ended)
|
|
161
|
+
return;
|
|
162
|
+
ended = true;
|
|
163
|
+
listener(code, error);
|
|
164
|
+
};
|
|
165
|
+
child.once('error', (error) => end(undefined, error));
|
|
166
|
+
child.once('exit', (code) => end(code ?? undefined, undefined));
|
|
167
|
+
},
|
|
168
|
+
kill: (signal) => {
|
|
169
|
+
child.kill(signal);
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** The real spawner. */
|
|
174
|
+
export const nodeSpawner = (file, argv, options) => asReexecChild(spawn(file, argv, options));
|
|
175
|
+
const FORWARDED_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
176
|
+
const SPAWN_FAILED_EXIT = 1;
|
|
177
|
+
/**
|
|
178
|
+
* Hand this invocation to the matching version and exit with its code.
|
|
179
|
+
*
|
|
180
|
+
* `stdio: 'inherit'` gives the child this process's own file descriptors, so an MCP client's JSON-RPC
|
|
181
|
+
* stream reaches it without passing through a byte of JavaScript here — nothing to buffer, reframe or
|
|
182
|
+
* lose. For that to hold, this must run before anything writes to stdout.
|
|
183
|
+
*/
|
|
184
|
+
export function reexecAtVersion(version, args, env, deps) {
|
|
185
|
+
const inv = npxInvocation(version, args, deps.platform ?? process.platform);
|
|
186
|
+
const child = deps.spawn(inv.file, inv.argv, {
|
|
187
|
+
stdio: 'inherit',
|
|
188
|
+
env: { ...env, [VersionMatchEnv.REEXECUTED]: MARKED },
|
|
189
|
+
shell: inv.shell,
|
|
190
|
+
});
|
|
191
|
+
// A client that stops the proxy signals THIS pid; the real server is the child.
|
|
192
|
+
const forward = (signal) => {
|
|
193
|
+
child.kill(signal);
|
|
194
|
+
};
|
|
195
|
+
for (const signal of FORWARDED_SIGNALS)
|
|
196
|
+
process.on(signal, forward);
|
|
197
|
+
child.onEnd((code, error) => {
|
|
198
|
+
for (const signal of FORWARDED_SIGNALS)
|
|
199
|
+
process.off(signal, forward);
|
|
200
|
+
if (error !== undefined) {
|
|
201
|
+
deps.warn?.(`Could not start ${SERVER_PACKAGE}@${version} to match this project's SDK (${error.message}). ` +
|
|
202
|
+
`Set ${VersionMatchEnv.OPT_OUT}=1 to use the installed CLI instead.`);
|
|
203
|
+
}
|
|
204
|
+
deps.exit(code ?? SPAWN_FAILED_EXIT);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { log } from '../../../log.js';
|
|
18
18
|
import { RETICLE_VERIFY_DEFAULT_PORT } from '../../../index.js';
|
|
19
19
|
import { verifyEndpointMismatch } from '../../../status-payload.js';
|
|
20
|
-
import { probeDaemon } from '../../../surface/mcp/mcp-proxy.js';
|
|
20
|
+
import { probeDaemon, waitForDaemonBind } from '../../../surface/mcp/mcp-proxy.js';
|
|
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';
|
|
@@ -84,7 +84,7 @@ async function serveWithHonestExit(parsed) {
|
|
|
84
84
|
// The child binds asynchronously and, when it cannot, exits 1 long after this process would have
|
|
85
85
|
// reported success. Wait for it to ANSWER — `/status` responding is the only evidence a daemon
|
|
86
86
|
// exists that does not come from the pid file the child may never have earned.
|
|
87
|
-
const bound = await
|
|
87
|
+
const bound = await waitForDaemonBind(parsed.port);
|
|
88
88
|
if (!bound) {
|
|
89
89
|
const settled = await probePresence(parsed.port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
90
90
|
const startupCause = readDaemonStartupCause(logPath(parsed.port), startupStartedAt);
|
|
@@ -113,24 +113,10 @@ async function serveWithHonestExit(parsed) {
|
|
|
113
113
|
}
|
|
114
114
|
log('reticle_daemon_spawned', { port: parsed.port, ...(parsed.http ? { http: true } : {}) });
|
|
115
115
|
}
|
|
116
|
-
/** How long `serve` waits for the child to bind. Generous: a cold daemon start is seconds. */
|
|
117
|
-
const SERVE_BIND_TIMEOUT_MS = 15_000;
|
|
118
|
-
const PRESENCE_POLL_MS = 150;
|
|
119
116
|
/** How long a daemon gets to shut down on its own before `stop` stops asking politely. */
|
|
120
117
|
const GRACEFUL_STOP_MS = 5000;
|
|
121
118
|
/** How long after SIGKILL before we accept the process is not ours to kill and say so. */
|
|
122
119
|
const FORCED_STOP_MS = 2000;
|
|
123
|
-
async function waitForPresence(port, want, timeoutMs) {
|
|
124
|
-
const deadline = Date.now() + timeoutMs;
|
|
125
|
-
for (;;) {
|
|
126
|
-
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
127
|
-
if (presence === want)
|
|
128
|
-
return true;
|
|
129
|
-
if (Date.now() >= deadline)
|
|
130
|
-
return false;
|
|
131
|
-
await new Promise((resolve) => setTimeout(resolve, PRESENCE_POLL_MS));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
120
|
export function handleStop(port, quiet) {
|
|
135
121
|
const pid = readPid(port);
|
|
136
122
|
if (null === pid || !isAlive(pid)) {
|