@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
|
@@ -12,60 +12,96 @@
|
|
|
12
12
|
* listening indefinitely.
|
|
13
13
|
*/
|
|
14
14
|
import { spawn, spawnSync } from 'node:child_process';
|
|
15
|
-
import {
|
|
16
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
17
16
|
import { join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { isAlive } from '../daemon/daemon.js';
|
|
19
|
+
import { DEV_SERVER_LOG_CAP_BYTES, ROTATED_SUFFIX } from './bringup/dev-server-log-cap.js';
|
|
20
|
+
import { devServerLogName, recordHandOver, sweepDeadDevServers, } from './bringup/dev-server-files.js';
|
|
18
21
|
import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
22
|
+
import { reticleStateHome } from '../daemon/daemon.js';
|
|
19
23
|
import { descendants, parseLsofPorts, parseNetstatListeners, parseWmicProcesses, } from './terminal/listeners.js';
|
|
20
|
-
import { loopbackProbeUrls } from './probe/loopback-probe-urls.js';
|
|
24
|
+
import { isAddressMiss, loopbackProbeUrls } from './probe/loopback-probe-urls.js';
|
|
21
25
|
const WINDOWS = 'win32' === process.platform;
|
|
22
26
|
/** A page fetch that is slow is a page fetch that failed, for our purposes. */
|
|
23
27
|
const PROBE_TIMEOUT_MS = 5_000;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
* A child's piped stdout is a Socket at runtime and a Readable in the types, which has no `unref`.
|
|
28
|
-
* Narrowing through Socket keeps that honest rather than asserting it.
|
|
29
|
-
*/
|
|
30
|
-
function releasePipe(stream) {
|
|
31
|
-
if (stream instanceof Socket)
|
|
32
|
-
stream.unref();
|
|
33
|
-
}
|
|
28
|
+
/** The supervisor that owns the server's output and keeps its log bounded. */
|
|
29
|
+
const LOG_CAP_SCRIPT = fileURLToPath(new URL('./bringup/dev-server-log-cap.js', import.meta.url));
|
|
34
30
|
/** The dev server this process started, and the only one it will ever stop. */
|
|
35
31
|
export class OwnedDevServer {
|
|
32
|
+
logDir;
|
|
36
33
|
child;
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
log;
|
|
35
|
+
cwd;
|
|
36
|
+
startedAt = Date.now();
|
|
39
37
|
handedOver = false;
|
|
38
|
+
constructor(logDir = reticleStateHome()) {
|
|
39
|
+
this.logDir = logDir;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Start it with its output going to a bounded log, through a supervisor that outlives `init`.
|
|
43
|
+
*
|
|
44
|
+
* Not a pipe into THIS process. A pipe's read end belonged to `init`; once it exited nobody held
|
|
45
|
+
* it, and the server's next write (a compile error, an HMR line, SvelteKit's first warning) was an
|
|
46
|
+
* EPIPE that killed it: "it connected, then the app went away". And not a bare file either, which
|
|
47
|
+
* has no ceiling: an upstream echo loop grew one to 2.9 GB in five minutes. The supervisor reads
|
|
48
|
+
* the server's pipes for as long as they are open and rotates the file past a cap — see
|
|
49
|
+
* dev-server-log-cap.ts. It is the process group leader, so `pid()` and `stop()` cover both.
|
|
50
|
+
*/
|
|
40
51
|
start(command, cwd, env) {
|
|
41
|
-
|
|
52
|
+
mkdirSync(this.logDir, { recursive: true });
|
|
53
|
+
// Logs of servers that are gone, from every project, so the state home stops growing a file
|
|
54
|
+
// per app ever started. This one's are replaced below anyway.
|
|
55
|
+
sweepDeadDevServers(this.logDir, { alive: isAlive, now: Date.now(), keep: cwd });
|
|
56
|
+
const log = join(this.logDir, devServerLogName(cwd));
|
|
57
|
+
// Unlinked rather than truncated: a previous server for this project may still hold the old
|
|
58
|
+
// file open, and truncating under it would interleave its lines into this run's output.
|
|
59
|
+
rmSync(log, { force: true });
|
|
60
|
+
rmSync(`${log}${ROTATED_SUFFIX}`, { force: true });
|
|
61
|
+
this.child = spawn(process.execPath, [LOG_CAP_SCRIPT, log, String(DEV_SERVER_LOG_CAP_BYTES), command], {
|
|
42
62
|
cwd,
|
|
43
|
-
shell: true,
|
|
44
63
|
// Its own process group, so stopping it stops what it started rather than only the wrapper.
|
|
45
64
|
detached: !WINDOWS,
|
|
46
|
-
|
|
65
|
+
// Nothing of ours: the supervisor holds the server's pipes, and init holds none of its.
|
|
66
|
+
stdio: 'ignore',
|
|
47
67
|
env: { ...process.env, ...env },
|
|
48
68
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.
|
|
69
|
+
this.log = log;
|
|
70
|
+
this.cwd = cwd;
|
|
71
|
+
this.startedAt = Date.now();
|
|
72
|
+
}
|
|
73
|
+
/** Where the server's output goes, for the line that hands it over. */
|
|
74
|
+
logPath() {
|
|
75
|
+
return this.log;
|
|
56
76
|
}
|
|
57
77
|
/** The process group leader, for a caller that has to clean up what it handed over. */
|
|
58
78
|
pid() {
|
|
59
79
|
return this.child?.pid;
|
|
60
80
|
}
|
|
61
81
|
output() {
|
|
62
|
-
|
|
82
|
+
if (undefined === this.log)
|
|
83
|
+
return '';
|
|
84
|
+
try {
|
|
85
|
+
return readFileSync(this.log, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
63
90
|
}
|
|
64
91
|
exited() {
|
|
65
92
|
return undefined !== this.child && null !== this.child.exitCode;
|
|
66
93
|
}
|
|
94
|
+
/** Since the log last grew, which is since the server last printed anything. */
|
|
67
95
|
quietForMs() {
|
|
68
|
-
|
|
96
|
+
let lastOutputAt = this.startedAt;
|
|
97
|
+
try {
|
|
98
|
+
if (undefined !== this.log)
|
|
99
|
+
lastOutputAt = Math.max(lastOutputAt, statSync(this.log).mtimeMs);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* not written yet */
|
|
103
|
+
}
|
|
104
|
+
return Date.now() - lastOutputAt;
|
|
69
105
|
}
|
|
70
106
|
/** Ports anything in this server's process tree is listening on. */
|
|
71
107
|
listeningPorts() {
|
|
@@ -90,24 +126,20 @@ export class OwnedDevServer {
|
|
|
90
126
|
`lsof -a -p ${pids.join(',')} -iTCP -sTCP:LISTEN -P -n 2>/dev/null | awk 'NR>1{print $9}'`,
|
|
91
127
|
]));
|
|
92
128
|
}
|
|
93
|
-
/** Hand the running server to the user. After this, `stop()` does nothing. */
|
|
94
129
|
/**
|
|
95
|
-
* Leave the server running and stop holding it.
|
|
96
|
-
*
|
|
97
|
-
* `unref` on the child is NOT enough: its stdout and stderr are pipes owned by this process, and
|
|
98
|
-
* an open pipe keeps the event loop alive on its own. So `init` printed "setup complete" and then
|
|
99
|
-
* hung until something killed it, turning a successful run into a non-zero exit — which is
|
|
100
|
-
* exactly what the install gate reported, on a run whose every other assertion passed.
|
|
130
|
+
* Leave the server running and stop holding it. After this, `stop()` does nothing.
|
|
101
131
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
132
|
+
* Only `unref` is needed: the output is the supervisor's, so no pipe of ours holds the event loop
|
|
133
|
+
* (`init` once printed "setup complete" and hung on one). `url` is recorded beside the log, so the
|
|
134
|
+
* next `init` attaches to this server rather than starting a second one beside it.
|
|
104
135
|
*/
|
|
105
|
-
handOver() {
|
|
136
|
+
handOver(url) {
|
|
106
137
|
this.handedOver = true;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
138
|
+
this.child?.unref();
|
|
139
|
+
const pid = this.child?.pid;
|
|
140
|
+
if (undefined !== url && undefined !== pid && undefined !== this.cwd) {
|
|
141
|
+
recordHandOver(this.logDir, { pid, url, appDir: this.cwd });
|
|
142
|
+
}
|
|
111
143
|
}
|
|
112
144
|
stop() {
|
|
113
145
|
const child = this.child;
|
|
@@ -164,14 +196,16 @@ function windowsProcessPairs() {
|
|
|
164
196
|
* server that is already running.
|
|
165
197
|
*
|
|
166
198
|
* On loopback, the announced host is tried first and the other loopback families follow when it
|
|
167
|
-
*
|
|
199
|
+
* is REFUSED — so an IPv6-only Vite that printed `127.0.0.1` still counts as served (#884). Only
|
|
200
|
+
* refused: a timeout means the right server is slow, and the caller polls again on the same host.
|
|
201
|
+
* See `isAddressMiss` for the Next 16 first run that hopping on a timeout broke.
|
|
168
202
|
*/
|
|
169
|
-
export async function probePage(url) {
|
|
203
|
+
export async function probePage(url, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
170
204
|
const candidates = loopbackProbeUrls(url);
|
|
171
205
|
let sawTlsRefused = false;
|
|
172
206
|
for (const candidate of candidates) {
|
|
173
207
|
try {
|
|
174
|
-
const res = await fetch(candidate, { signal: AbortSignal.timeout(
|
|
208
|
+
const res = await fetch(candidate, { signal: AbortSignal.timeout(timeoutMs) });
|
|
175
209
|
const body = await res.text();
|
|
176
210
|
const sdkInPage = /@reticlehq|@reticle-connect|reticle-dev/.test(body);
|
|
177
211
|
// Only stamp reachedUrl when we had to leave the announcement — callers that already have
|
|
@@ -187,6 +221,8 @@ export async function probePage(url) {
|
|
|
187
221
|
if (/certificate|SELF_SIGNED|DEPTH_ZERO|ERR_TLS|unable to verify/i.test(message)) {
|
|
188
222
|
sawTlsRefused = true;
|
|
189
223
|
}
|
|
224
|
+
if (!isAddressMiss(err))
|
|
225
|
+
break;
|
|
190
226
|
}
|
|
191
227
|
}
|
|
192
228
|
return { served: false, sdkInPage: false, ...(sawTlsRefused ? { tlsRefused: true } : {}) };
|
|
@@ -16,3 +16,10 @@
|
|
|
16
16
|
* family-split problem, and inventing alternates would probe the wrong machine.
|
|
17
17
|
*/
|
|
18
18
|
export declare function loopbackProbeUrls(url: string): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Whether a failed fetch was refused at the connection level on this address.
|
|
21
|
+
*
|
|
22
|
+
* `fetch` wraps the socket error as `cause`; with happy-eyeballs that cause is an AggregateError of
|
|
23
|
+
* one error per address tried, and it is a miss only when every one of them was.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isAddressMiss(err: unknown): boolean;
|
|
@@ -50,3 +50,43 @@ export function loopbackProbeUrls(url) {
|
|
|
50
50
|
push(host);
|
|
51
51
|
return out;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Errors that mean "nothing is listening at THIS address", so another loopback family may be.
|
|
55
|
+
*
|
|
56
|
+
* Anything else, a timeout above all, means the address was right and the server is slow. A cold
|
|
57
|
+
* Next 16 first compile takes long enough to time a probe out, and treating that as a family miss
|
|
58
|
+
* walked on to `127.0.0.1`/`[::1]`, which answered first, became the app url, and is an origin Next
|
|
59
|
+
* refuses its dev resources to: no hydration, so the SDK never ran and init blamed the page.
|
|
60
|
+
*/
|
|
61
|
+
const ADDRESS_MISS_CODES = new Set([
|
|
62
|
+
'ECONNREFUSED',
|
|
63
|
+
'EADDRNOTAVAIL',
|
|
64
|
+
'ENOTFOUND',
|
|
65
|
+
'EAI_AGAIN',
|
|
66
|
+
'ENETUNREACH',
|
|
67
|
+
'EHOSTUNREACH',
|
|
68
|
+
'EAFNOSUPPORT',
|
|
69
|
+
]);
|
|
70
|
+
function field(value, key) {
|
|
71
|
+
return 'object' === typeof value && null !== value ? Reflect.get(value, key) : undefined;
|
|
72
|
+
}
|
|
73
|
+
const codeOf = (value) => {
|
|
74
|
+
const code = field(value, 'code');
|
|
75
|
+
return 'string' === typeof code ? code : undefined;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Whether a failed fetch was refused at the connection level on this address.
|
|
79
|
+
*
|
|
80
|
+
* `fetch` wraps the socket error as `cause`; with happy-eyeballs that cause is an AggregateError of
|
|
81
|
+
* one error per address tried, and it is a miss only when every one of them was.
|
|
82
|
+
*/
|
|
83
|
+
export function isAddressMiss(err) {
|
|
84
|
+
const cause = field(err, 'cause');
|
|
85
|
+
const direct = codeOf(cause);
|
|
86
|
+
if (undefined !== direct)
|
|
87
|
+
return ADDRESS_MISS_CODES.has(direct);
|
|
88
|
+
const errors = field(cause, 'errors');
|
|
89
|
+
if (!Array.isArray(errors) || 0 === errors.length)
|
|
90
|
+
return false;
|
|
91
|
+
return errors.every((e) => ADDRESS_MISS_CODES.has(codeOf(e) ?? ''));
|
|
92
|
+
}
|
|
@@ -40,8 +40,13 @@ export declare function readPage(probe: PageProbe): PageFinding;
|
|
|
40
40
|
/**
|
|
41
41
|
* What the page adds to the daemon's account. Deliberately a sentence to APPEND, not a replacement:
|
|
42
42
|
* the daemon's diagnosis names the fix, and this says what the page looked like when we checked.
|
|
43
|
+
*
|
|
44
|
+
* `daemonSpoke`: the daemon already gave its account. Whether the page dialled is ITS fact (it
|
|
45
|
+
* sees every hello, including one it refused), so the SDK_PRESENT sentence then states only the
|
|
46
|
+
* page fact. Saying "never dialled the bridge" under "this daemon REFUSED the last page" contradicted
|
|
47
|
+
* the one piece of positive evidence in the run.
|
|
43
48
|
*/
|
|
44
|
-
export declare function describePage(finding: PageFinding, url: string): string;
|
|
49
|
+
export declare function describePage(finding: PageFinding, url: string, daemonSpoke?: boolean): string;
|
|
45
50
|
/**
|
|
46
51
|
* What the page looks like once it has had its short chance to come up.
|
|
47
52
|
*
|
|
@@ -68,4 +73,10 @@ export declare function describePage(finding: PageFinding, url: string): string;
|
|
|
68
73
|
export declare function findingBeforeOpen(probe: () => Promise<PageProbe>, clock: {
|
|
69
74
|
readonly now: () => number;
|
|
70
75
|
readonly sleep: (ms: number) => Promise<void>;
|
|
71
|
-
}, windowMs: number, pollMs: number
|
|
76
|
+
}, windowMs: number, pollMs: number,
|
|
77
|
+
/**
|
|
78
|
+
* The served HTML carries the SDK marker on this shape. When it does not (the connect is in the
|
|
79
|
+
* JS bundle), SDK_MISSING is the permanent state of a healthy page, and waiting for it to change
|
|
80
|
+
* only delays the window that would load the bundle.
|
|
81
|
+
*/
|
|
82
|
+
htmlCarriesSdk?: boolean): Promise<PageFinding>;
|
|
@@ -27,8 +27,16 @@ export function readPage(probe) {
|
|
|
27
27
|
/**
|
|
28
28
|
* What the page adds to the daemon's account. Deliberately a sentence to APPEND, not a replacement:
|
|
29
29
|
* the daemon's diagnosis names the fix, and this says what the page looked like when we checked.
|
|
30
|
+
*
|
|
31
|
+
* `daemonSpoke`: the daemon already gave its account. Whether the page dialled is ITS fact (it
|
|
32
|
+
* sees every hello, including one it refused), so the SDK_PRESENT sentence then states only the
|
|
33
|
+
* page fact. Saying "never dialled the bridge" under "this daemon REFUSED the last page" contradicted
|
|
34
|
+
* the one piece of positive evidence in the run.
|
|
30
35
|
*/
|
|
31
|
-
export function describePage(finding, url) {
|
|
36
|
+
export function describePage(finding, url, daemonSpoke = false) {
|
|
37
|
+
if (PageFinding.SDK_PRESENT === finding && daemonSpoke) {
|
|
38
|
+
return `The SDK IS in the page at ${url}; the daemon's account above says what became of it.`;
|
|
39
|
+
}
|
|
32
40
|
switch (finding) {
|
|
33
41
|
case PageFinding.NOT_SERVED:
|
|
34
42
|
// Leads with the cause. The break-matrix asserts this phrasing because it is what a reader
|
|
@@ -77,10 +85,17 @@ export function describePage(finding, url) {
|
|
|
77
85
|
* `probePage` 1,500 times before reaching this point and a test of the decision cannot get near
|
|
78
86
|
* it through that. Here it takes a probe and a clock and answers in three lines of setup.
|
|
79
87
|
*/
|
|
80
|
-
export async function findingBeforeOpen(probe, clock, windowMs, pollMs
|
|
88
|
+
export async function findingBeforeOpen(probe, clock, windowMs, pollMs,
|
|
89
|
+
/**
|
|
90
|
+
* The served HTML carries the SDK marker on this shape. When it does not (the connect is in the
|
|
91
|
+
* JS bundle), SDK_MISSING is the permanent state of a healthy page, and waiting for it to change
|
|
92
|
+
* only delays the window that would load the bundle.
|
|
93
|
+
*/
|
|
94
|
+
htmlCarriesSdk = true) {
|
|
81
95
|
const readyBy = clock.now() + windowMs;
|
|
82
96
|
let finding = readPage(await probe());
|
|
83
|
-
while ((
|
|
97
|
+
while (((htmlCarriesSdk && PageFinding.SDK_MISSING === finding) ||
|
|
98
|
+
PageFinding.NOT_SERVED === finding) &&
|
|
84
99
|
clock.now() < readyBy) {
|
|
85
100
|
await clock.sleep(pollMs);
|
|
86
101
|
finding = readPage(await probe());
|
|
@@ -51,6 +51,11 @@ export interface DaemonReason {
|
|
|
51
51
|
/** The whole differential, recorded for the agent reading `--json`. Defaults to the lead. */
|
|
52
52
|
readonly full?: string;
|
|
53
53
|
}
|
|
54
|
+
/** A lease this run opened for its connect proof, and the way to hand it back. */
|
|
55
|
+
export interface OwnedBrowser {
|
|
56
|
+
readonly sessionId: string;
|
|
57
|
+
readonly release: () => Promise<void>;
|
|
58
|
+
}
|
|
54
59
|
export interface SetupEffects {
|
|
55
60
|
/** Start the dev server. Resolves once started; the caller owns stopping it. */
|
|
56
61
|
readonly startDevServer: (command: string, cwd: string) => Promise<void>;
|
|
@@ -79,8 +84,19 @@ export interface SetupEffects {
|
|
|
79
84
|
* short (`vite-react`, `vite-vue`, `next-app-router`, `monorepo-subdir`, run 35242186243 against
|
|
80
85
|
* 35232727716). A non-zero launcher exit means UNKNOWN, not "no browser": it reports whether the
|
|
81
86
|
* COMMAND succeeded, never whether a window opened. Do not shorten a wait on it.
|
|
87
|
+
*
|
|
88
|
+
* It may resolve with the launcher's own failure, and that is used for exactly one thing: to ALSO
|
|
89
|
+
* open a Reticle-owned browser, so the proof does not depend on a window that may never appear.
|
|
90
|
+
* Adding a second source of a session is safe on a false alarm; cutting the wait was not.
|
|
91
|
+
*/
|
|
92
|
+
readonly openBrowser: (url: string) => Promise<string | void>;
|
|
93
|
+
/**
|
|
94
|
+
* Open the url in a browser Reticle owns (a pooled lease), for a run with no system browser to
|
|
95
|
+
* lean on: `--no-open`, CI, a container, an agent. Optional: absent means no lease is attempted.
|
|
82
96
|
*/
|
|
83
|
-
readonly
|
|
97
|
+
readonly openLease?: (url: string) => Promise<OwnedBrowser | {
|
|
98
|
+
readonly failed: string;
|
|
99
|
+
}>;
|
|
84
100
|
readonly listSessions: () => Promise<CandidateSession[]>;
|
|
85
101
|
/**
|
|
86
102
|
* The daemon's own account of why nothing connected, if it can be asked.
|
|
@@ -101,6 +117,8 @@ export interface SetupEffects {
|
|
|
101
117
|
}
|
|
102
118
|
export interface SetupInput {
|
|
103
119
|
readonly appDir: string;
|
|
120
|
+
/** This project's id, so a live tab of it can be told from another app's on the same url. */
|
|
121
|
+
readonly projectId?: string | undefined;
|
|
104
122
|
readonly devCommand?: string | undefined;
|
|
105
123
|
/** The app is already served here, so nothing is started. */
|
|
106
124
|
readonly suppliedUrl?: string | undefined;
|
|
@@ -117,6 +135,13 @@ export interface SetupInput {
|
|
|
117
135
|
readonly openBrowser: boolean;
|
|
118
136
|
/** Web, Electron or Tauri. Desktop changes three things; see desktop-shape.ts. */
|
|
119
137
|
readonly shape: AppShape;
|
|
138
|
+
/**
|
|
139
|
+
* The served HTML carries the SDK marker on this shape: Vite's index.html injection, or a plain
|
|
140
|
+
* HTML page with the snippet pasted in. Every other framework delivers the connect in the JS
|
|
141
|
+
* bundle, so "the SDK is NOT in the page" is the permanent state of a healthy page there, and
|
|
142
|
+
* saying it sent people to restart a dev server that was fine.
|
|
143
|
+
*/
|
|
144
|
+
readonly htmlCarriesSdk: boolean;
|
|
120
145
|
readonly phaseTimeoutMs: number;
|
|
121
146
|
readonly pollMs: number;
|
|
122
147
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* and a real browser to find out what happens when neither works.
|
|
15
15
|
*/
|
|
16
16
|
import { judgeWait, portBusyMessage, QUIET_MEANS_HUNG_MS, urlToWatch, WaitVerdict, } from './bringup/dev-server-wait.js';
|
|
17
|
-
import { waitProgressLine } from './terminal/wait-progress.js';
|
|
17
|
+
import { connectProgressLine, waitProgressLine } from './terminal/wait-progress.js';
|
|
18
18
|
/**
|
|
19
19
|
* Windows gets longer to say something before silence counts as a wedge.
|
|
20
20
|
*
|
|
@@ -28,7 +28,7 @@ import { waitProgressLine } from './terminal/wait-progress.js';
|
|
|
28
28
|
*/
|
|
29
29
|
const WINDOWS_QUIET_MEANS_HUNG_MS = 3 * 60_000;
|
|
30
30
|
const WINDOWS_QUIET_MEANS_HUNG_MS_APPLIES = 'win32' === process.platform;
|
|
31
|
-
import { pickSession } from './session-pick.js';
|
|
31
|
+
import { liveSessionOfProject, pickSession } from './session-pick.js';
|
|
32
32
|
import { readPage, describePage, findingBeforeOpen, PageFinding, } from './probe/page-probe.js';
|
|
33
33
|
import { remainingSteps } from './remaining-steps.js';
|
|
34
34
|
import { AppShape, isDesktop, policyFor } from './desktop-shape.js';
|
|
@@ -40,11 +40,27 @@ import { AppShape, isDesktop, policyFor } from './desktop-shape.js';
|
|
|
40
40
|
const SDK_READY_WINDOW_MS = 15_000;
|
|
41
41
|
/** The wait left when no browser was opened: only a tab that is ALREADY loaded can still appear. */
|
|
42
42
|
const NO_BROWSER_GRACE_MS = 3_000;
|
|
43
|
+
/**
|
|
44
|
+
* How long a system browser that SAID it opened gets to produce a session before init proves the
|
|
45
|
+
* connect in a Reticle-owned browser instead. A launcher's exit is not a window: macOS `open` against
|
|
46
|
+
* a default browser that does not answer exits 1 only after the launch check has already counted it
|
|
47
|
+
* as opened, and init then waited out its whole connect budget over a correct install.
|
|
48
|
+
*/
|
|
49
|
+
const SYSTEM_BROWSER_GRACE_MS = 15_000;
|
|
43
50
|
export const SetupPhase = {
|
|
44
51
|
DEV_SERVER: 'dev-server',
|
|
45
52
|
CONNECT: 'connect',
|
|
46
53
|
DONE: 'done',
|
|
47
54
|
};
|
|
55
|
+
/** Hand a lease back. Best-effort: a lease that already expired is already gone. */
|
|
56
|
+
async function releaseLease(lease) {
|
|
57
|
+
try {
|
|
58
|
+
await lease?.release();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* nothing to undo */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
48
64
|
const asProgress = (input, o) => ({
|
|
49
65
|
initDone: true,
|
|
50
66
|
devServerUp: undefined !== o.url,
|
|
@@ -89,6 +105,10 @@ async function daemonWhy(fx) {
|
|
|
89
105
|
export async function runSetupPhases(input, fx) {
|
|
90
106
|
const notes = [];
|
|
91
107
|
const note = (line) => {
|
|
108
|
+
// The page is probed before the browser opens and again after the wait, and an unchanged page
|
|
109
|
+
// gave the reader the same paragraph twice in a row.
|
|
110
|
+
if (notes.at(-1) === line)
|
|
111
|
+
return;
|
|
92
112
|
notes.push(line);
|
|
93
113
|
fx.note(line);
|
|
94
114
|
};
|
|
@@ -181,9 +201,13 @@ export async function runSetupPhases(input, fx) {
|
|
|
181
201
|
const requiredRuntime = isDesktop(input.shape) ? input.shape : undefined;
|
|
182
202
|
// A tab already on this URL is the answer. Opening another is how three sessions appear.
|
|
183
203
|
// `before` is empty so pickSession will take that tab rather than waiting for a new one.
|
|
204
|
+
//
|
|
205
|
+
// Not only when attached: a server init started itself (Next, Angular — nothing announces) has a
|
|
206
|
+
// url too, and a live tab of this project already on it is just as much the answer. Without this
|
|
207
|
+
// every re-run opened one more tab.
|
|
184
208
|
const alreadyConnected = attachedToExisting
|
|
185
209
|
? pickSession(listed, url, new Set(), requiredRuntime)
|
|
186
|
-
:
|
|
210
|
+
: liveSessionOfProject(listed, url, input.projectId, requiredRuntime);
|
|
187
211
|
const before = new Set(listed.map((s) => s.sessionId));
|
|
188
212
|
// Do not put a window in front of somebody until the page behind it can actually do something.
|
|
189
213
|
//
|
|
@@ -198,7 +222,12 @@ export async function runSetupPhases(input, fx) {
|
|
|
198
222
|
// proof that nothing can ever dial in.
|
|
199
223
|
const budgetMs = input.connectBudgetMs ?? Math.max(input.phaseTimeoutMs, policy.connectBudgetMs);
|
|
200
224
|
let openedBrowser = false;
|
|
201
|
-
|
|
225
|
+
let lease;
|
|
226
|
+
// `--no-open` used to skip this whole block, so nothing was opened and the run ended on "never
|
|
227
|
+
// dialled the bridge" over a correct install. With a lease to fall back on it is entered too, and
|
|
228
|
+
// only the system browser is skipped.
|
|
229
|
+
const canLease = undefined !== fx.openLease;
|
|
230
|
+
if (null === alreadyConnected && policy.openBrowser && (input.openBrowser || canLease)) {
|
|
202
231
|
// SERVED is the precondition, not SDK_PRESENT: a url that answers nothing is the only state
|
|
203
232
|
// where a window is certainly useless. Whether the SDK is in the served HTML is a DIFFERENT
|
|
204
233
|
// question — Vite injects a marker, while Nuxt, React Router, Astro, SvelteKit and CRA deliver
|
|
@@ -209,7 +238,7 @@ export async function runSetupPhases(input, fx) {
|
|
|
209
238
|
//
|
|
210
239
|
// So presence decides only WHEN: open the moment it appears (the fast path for the frameworks
|
|
211
240
|
// that inline it), otherwise once the short readiness window is out.
|
|
212
|
-
const finding = await findingBeforeOpen(() => fx.probePage(url), { now: () => fx.now(), sleep: (ms) => fx.sleep(ms) }, Math.min(SDK_READY_WINDOW_MS, budgetMs), input.pollMs);
|
|
241
|
+
const finding = await findingBeforeOpen(() => fx.probePage(url), { now: () => fx.now(), sleep: (ms) => fx.sleep(ms) }, Math.min(SDK_READY_WINDOW_MS, budgetMs), input.pollMs, input.htmlCarriesSdk);
|
|
213
242
|
if (PageFinding.NOT_SERVED === finding || PageFinding.TLS_REFUSED === finding) {
|
|
214
243
|
note(describePage(finding, url));
|
|
215
244
|
note('Not opening a browser: nothing answered that url, so the window could only show an error ' +
|
|
@@ -218,12 +247,23 @@ export async function runSetupPhases(input, fx) {
|
|
|
218
247
|
else {
|
|
219
248
|
// Said BEFORE the window appears, so somebody watching a page that stays inert has already
|
|
220
249
|
// been told which of the two it is.
|
|
221
|
-
if (PageFinding.SDK_MISSING === finding)
|
|
250
|
+
if (PageFinding.SDK_MISSING === finding && input.htmlCarriesSdk) {
|
|
222
251
|
note(describePage(finding, url));
|
|
252
|
+
}
|
|
223
253
|
// `= true` means ATTEMPTED, which is the most that is knowable — see openBrowser above for
|
|
224
254
|
// why the launcher's own exit code must not be read as "no window appeared".
|
|
225
|
-
await fx.openBrowser(url);
|
|
226
|
-
openedBrowser =
|
|
255
|
+
const launcherFailed = input.openBrowser ? await fx.openBrowser(url) : undefined;
|
|
256
|
+
openedBrowser = input.openBrowser;
|
|
257
|
+
if (undefined !== fx.openLease && (!input.openBrowser || undefined !== launcherFailed)) {
|
|
258
|
+
const owned = await fx.openLease(url);
|
|
259
|
+
if ('failed' in owned) {
|
|
260
|
+
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
lease = owned;
|
|
264
|
+
openedBrowser = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
227
267
|
}
|
|
228
268
|
}
|
|
229
269
|
// Waiting the full budget for a session when nothing was opened to create one is dead time, and
|
|
@@ -237,6 +277,9 @@ export async function runSetupPhases(input, fx) {
|
|
|
237
277
|
? budgetMs
|
|
238
278
|
: Math.min(NO_BROWSER_GRACE_MS, budgetMs));
|
|
239
279
|
let session = alreadyConnected;
|
|
280
|
+
const waitStartedAt = fx.now();
|
|
281
|
+
let connectSpokeAtMs;
|
|
282
|
+
let leaseFallbackTried = false;
|
|
240
283
|
while (null === session) {
|
|
241
284
|
// On a desktop app, only the desktop window counts. AppShape and the runtime a page reports use
|
|
242
285
|
// the same three names, so the shape IS the requirement — see session-pick.
|
|
@@ -245,6 +288,32 @@ export async function runSetupPhases(input, fx) {
|
|
|
245
288
|
break;
|
|
246
289
|
if (deadline <= fx.now())
|
|
247
290
|
break;
|
|
291
|
+
if (!leaseFallbackTried &&
|
|
292
|
+
undefined === lease &&
|
|
293
|
+
undefined !== fx.openLease &&
|
|
294
|
+
openedBrowser &&
|
|
295
|
+
input.openBrowser &&
|
|
296
|
+
!isDesktop(input.shape) &&
|
|
297
|
+
fx.now() - waitStartedAt >= SYSTEM_BROWSER_GRACE_MS) {
|
|
298
|
+
leaseFallbackTried = true;
|
|
299
|
+
const owned = await fx.openLease(url);
|
|
300
|
+
if ('failed' in owned) {
|
|
301
|
+
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
lease = owned;
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// Not in silence: a desktop run used to print one line and then nothing for ten minutes.
|
|
309
|
+
const waitedMs = fx.now() - waitStartedAt;
|
|
310
|
+
const progress = connectProgressLine(waitedMs, connectSpokeAtMs, deadline - waitStartedAt, policy.awaiting);
|
|
311
|
+
if (undefined !== progress) {
|
|
312
|
+
// To the person only: it is progress, not a finding, and in `notes` it would split the
|
|
313
|
+
// page description printed before and after this wait into two copies.
|
|
314
|
+
fx.note(progress);
|
|
315
|
+
connectSpokeAtMs = waitedMs;
|
|
316
|
+
}
|
|
248
317
|
await fx.sleep(input.pollMs);
|
|
249
318
|
}
|
|
250
319
|
if (null === session) {
|
|
@@ -275,8 +344,13 @@ export async function runSetupPhases(input, fx) {
|
|
|
275
344
|
fx.note(why.lead);
|
|
276
345
|
notes.push(why.full ?? why.lead);
|
|
277
346
|
}
|
|
278
|
-
|
|
347
|
+
const finding = readPage(await fx.probePage(url));
|
|
348
|
+
// Silent on a bundle-connect shape: its HTML never carries the SDK, so the absence says nothing.
|
|
349
|
+
if (PageFinding.SDK_MISSING !== finding || input.htmlCarriesSdk) {
|
|
350
|
+
note(describePage(finding, url, undefined !== why));
|
|
351
|
+
}
|
|
279
352
|
}
|
|
353
|
+
await releaseLease(lease);
|
|
280
354
|
return stop(input, SetupPhase.CONNECT, { url }, notes);
|
|
281
355
|
}
|
|
282
356
|
// ── connected, which is where ONBOARDING ends ────────────────────────────────────────────────
|
|
@@ -292,16 +366,25 @@ export async function runSetupPhases(input, fx) {
|
|
|
292
366
|
// Both routes, because `explore` needs a model: without ANTHROPIC_API_KEY it answers "No model
|
|
293
367
|
// configured to drive the app". Naming only that one hands the reader a dead end on any machine
|
|
294
368
|
// without a key, which is the same defect this release spent its time removing everywhere else.
|
|
295
|
-
//
|
|
369
|
+
// A heading and a numbered list rather than one paragraph: a reader scans for what to do next.
|
|
296
370
|
//
|
|
297
371
|
// This was a single 524-character note, measured on a real first run: eighty-three words, no
|
|
298
372
|
// break, naming `reticle_*` tools at a reader who had just typed `reticle init` in a terminal and
|
|
299
373
|
// has no such tools to call. The facts were right and the shape made them unreadable, which on
|
|
300
374
|
// the last line of onboarding is the same as not saying them.
|
|
301
|
-
note(
|
|
302
|
-
|
|
375
|
+
note(`✓ Connected. ${url} is instrumented. Onboarding is done; nothing is verified yet.`);
|
|
376
|
+
// Said, because "connected" otherwise reads as "your browser connected": the page that dialled
|
|
377
|
+
// was a headless one Reticle launched and has now closed, and nothing is open on this screen.
|
|
378
|
+
if (undefined !== lease && session.sessionId === lease.sessionId) {
|
|
379
|
+
note('The proof came from a Reticle-owned headless browser (now closed), not a window of yours — ' +
|
|
380
|
+
`open ${url} in your own browser to use the app.`);
|
|
381
|
+
}
|
|
382
|
+
await releaseLease(lease);
|
|
383
|
+
note('');
|
|
384
|
+
note('Next, prove one flow. Your agent does this:');
|
|
385
|
+
note(' 1. Drive one flow and end it with `reticle_act_and_wait` or `reticle_assert`. Those two ' +
|
|
303
386
|
'produce a verdict; nothing else does.');
|
|
304
|
-
note('Or hand over the whole drive: `reticle_verify { action: "explore", persona: "<who does ' +
|
|
387
|
+
note(' 2. Or hand over the whole drive: `reticle_verify { action: "explore", persona: "<who does ' +
|
|
305
388
|
'what>" }` records what it drove, so later runs replay with no model in the loop. Needs ' +
|
|
306
389
|
'ANTHROPIC_API_KEY.');
|
|
307
390
|
return {
|
|
@@ -21,7 +21,20 @@ export interface CandidateSession {
|
|
|
21
21
|
readonly hasCapabilities?: boolean;
|
|
22
22
|
/** Which shell answered. Absent on an SDK too old to report one — see requiredRuntime below. */
|
|
23
23
|
readonly runtime?: string;
|
|
24
|
+
/** The project the page reported. Absent on an SDK too old to report one. */
|
|
25
|
+
readonly projectId?: string;
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A tab of THIS project on `url` that is demonstrably alive right now, or null.
|
|
29
|
+
*
|
|
30
|
+
* Stricter than `pickSession`, because it decides whether to open a window at all: every init run
|
|
31
|
+
* opened another tab even when this project already had a live one on the url, and the pile-up then
|
|
32
|
+
* made every tool called without a sessionId fail with "multiple sessions connected". Reused only on
|
|
33
|
+
* positive evidence — the page names this project, is visible, and was heard from inside the
|
|
34
|
+
* daemon's staleness threshold — because reusing a dead tab is the worse mistake: nothing opens and
|
|
35
|
+
* nothing connects.
|
|
36
|
+
*/
|
|
37
|
+
export declare function liveSessionOfProject(sessions: readonly CandidateSession[], url: string, projectId: string | undefined, requiredRuntime?: string): CandidateSession | null;
|
|
25
38
|
/**
|
|
26
39
|
* The session to drive, or null when nothing on this url qualifies.
|
|
27
40
|
*
|
|
@@ -10,9 +10,29 @@
|
|
|
10
10
|
* the oldest: a tab whose dev server died yesterday. The HUD then plays to a window nobody is
|
|
11
11
|
* watching, and the verdict describes a page the user cannot see.
|
|
12
12
|
*/
|
|
13
|
+
import { SESSION_HEALTH } from '@reticlehq/core';
|
|
13
14
|
/** Sorts a hidden tab after a visible one; among equals, the least stale first. */
|
|
14
15
|
const isLive = (s) => true !== s.hidden && true !== s.throttled;
|
|
15
16
|
const staleness = (s) => s.lastSeenMs ?? Number.POSITIVE_INFINITY;
|
|
17
|
+
/**
|
|
18
|
+
* A tab of THIS project on `url` that is demonstrably alive right now, or null.
|
|
19
|
+
*
|
|
20
|
+
* Stricter than `pickSession`, because it decides whether to open a window at all: every init run
|
|
21
|
+
* opened another tab even when this project already had a live one on the url, and the pile-up then
|
|
22
|
+
* made every tool called without a sessionId fail with "multiple sessions connected". Reused only on
|
|
23
|
+
* positive evidence — the page names this project, is visible, and was heard from inside the
|
|
24
|
+
* daemon's staleness threshold — because reusing a dead tab is the worse mistake: nothing opens and
|
|
25
|
+
* nothing connects.
|
|
26
|
+
*/
|
|
27
|
+
export function liveSessionOfProject(sessions, url, projectId, requiredRuntime) {
|
|
28
|
+
if (undefined === projectId || 0 === projectId.length)
|
|
29
|
+
return null;
|
|
30
|
+
const mine = sessions.filter((s) => projectId === s.projectId &&
|
|
31
|
+
true !== s.hidden &&
|
|
32
|
+
undefined !== s.lastSeenMs &&
|
|
33
|
+
s.lastSeenMs <= SESSION_HEALTH.STALE_THRESHOLD_MS);
|
|
34
|
+
return pickSession(mine, url, new Set(), requiredRuntime);
|
|
35
|
+
}
|
|
16
36
|
/**
|
|
17
37
|
* The session to drive, or null when nothing on this url qualifies.
|
|
18
38
|
*
|
|
@@ -27,22 +27,10 @@ interface SetupCommandInput extends Omit<SetupInput, 'shape'> {
|
|
|
27
27
|
readonly env: Readonly<Record<string, string>>;
|
|
28
28
|
/** Register the MCP server with the coding agents init does not itself reach. */
|
|
29
29
|
readonly registerAgents: boolean;
|
|
30
|
+
/** The bridge's pairing token, for the MCP transport a connect-proof lease is opened over. */
|
|
31
|
+
readonly pairingToken?: string | undefined;
|
|
30
32
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Register with the agents `init` does not reach, and leave the skill where one loads skills from.
|
|
33
|
-
*
|
|
34
|
-
* init covers eight clients and only where it finds them installed, which leaves VS Code's USER
|
|
35
|
-
* scope unwritten — it exists on machines today, and init only ever writes the project-scope file,
|
|
36
|
-
* so a VS Code user has no tools outside the directory they ran init in.
|
|
37
|
-
*/
|
|
38
33
|
export declare function registerOtherAgents(print: (line: string) => void): void;
|
|
39
34
|
type SetupCommandResult = SetupOutcome;
|
|
40
|
-
/**
|
|
41
|
-
* Run the phases against the real world.
|
|
42
|
-
*
|
|
43
|
-
* The dev server is stopped on every ending except success, where it is handed to the user: an
|
|
44
|
-
* instrumented app they can watch is the deliverable, and killing it would leave them with config
|
|
45
|
-
* files and a dead tab.
|
|
46
|
-
*/
|
|
47
35
|
export declare function runSetupCommand(input: SetupCommandInput, print: (line: string) => void): Promise<SetupCommandResult>;
|
|
48
36
|
export {};
|