@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
|
@@ -3,7 +3,7 @@ import { notePendingNoSessionReason } from '../../telemetry/tool-refused.js';
|
|
|
3
3
|
import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
|
|
4
4
|
import { Session } from './session.js';
|
|
5
5
|
import { AttachmentHistory } from './presence/attachment-history.js';
|
|
6
|
-
import { pickDocumentSuccessor } from './session-successor.js';
|
|
6
|
+
import { openedBeforeDeparture, pickDocumentSuccessor, } from './session-successor.js';
|
|
7
7
|
import { SESSION_DISCONNECTED_REASON } from './facts/session-replaced.js';
|
|
8
8
|
/** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
|
|
9
9
|
export function originOf(url) {
|
|
@@ -87,6 +87,8 @@ export class SessionManager {
|
|
|
87
87
|
* id forgets its tombstone — the live session is the answer then.
|
|
88
88
|
*/
|
|
89
89
|
#tombstones = new Map();
|
|
90
|
+
/** The tabs already open when each tombstoned session departed: siblings, never its successor. */
|
|
91
|
+
#siblingsAtDeparture = new Map();
|
|
90
92
|
/**
|
|
91
93
|
* The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
|
|
92
94
|
* without passing its own scope, this is applied — so auto-selection is project-scoped by default
|
|
@@ -128,6 +130,7 @@ export class SessionManager {
|
|
|
128
130
|
declareDrivenRedactionKeys(session.id, session.redactKeys);
|
|
129
131
|
this.#attachment.attached(session.id);
|
|
130
132
|
this.#tombstones.delete(session.id);
|
|
133
|
+
this.#siblingsAtDeparture.delete(session.id);
|
|
131
134
|
return previous;
|
|
132
135
|
}
|
|
133
136
|
/**
|
|
@@ -158,6 +161,9 @@ export class SessionManager {
|
|
|
158
161
|
return;
|
|
159
162
|
if (this.#tombstones.has(session.id))
|
|
160
163
|
this.#tombstones.delete(session.id);
|
|
164
|
+
this.#siblingsAtDeparture.set(session.id, new Set([...this.#sessions.values()]
|
|
165
|
+
.filter((s) => s !== session && openedBeforeDeparture(s, session))
|
|
166
|
+
.map((s) => s.id)));
|
|
161
167
|
this.#tombstones.set(session.id, {
|
|
162
168
|
id: session.id,
|
|
163
169
|
url: session.url,
|
|
@@ -168,13 +174,17 @@ export class SessionManager {
|
|
|
168
174
|
if (oldest === undefined)
|
|
169
175
|
break;
|
|
170
176
|
this.#tombstones.delete(oldest);
|
|
177
|
+
this.#siblingsAtDeparture.delete(oldest);
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
#successorOf(sessionId) {
|
|
174
181
|
const departed = this.#tombstones.get(sessionId);
|
|
175
182
|
if (departed === undefined)
|
|
176
183
|
return undefined;
|
|
177
|
-
const
|
|
184
|
+
const siblings = this.#siblingsAtDeparture.get(sessionId);
|
|
185
|
+
const picked = pickDocumentSuccessor(this.all()
|
|
186
|
+
.filter((s) => true !== siblings?.has(s.id))
|
|
187
|
+
.map((s) => ({
|
|
178
188
|
id: s.id,
|
|
179
189
|
url: s.url,
|
|
180
190
|
...(s.projectId === undefined ? {} : { projectId: s.projectId }),
|
|
@@ -186,6 +196,17 @@ export class SessionManager {
|
|
|
186
196
|
get(sessionId) {
|
|
187
197
|
return this.#sessions.get(sessionId);
|
|
188
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Did this id belong to a session that has since disconnected?
|
|
201
|
+
*
|
|
202
|
+
* `resolve` answers a departed id with its live SUCCESSOR, which is what keeps a reload working
|
|
203
|
+
* for everything that observes. A caller that is about to do something DESTRUCTIVE needs the
|
|
204
|
+
* other answer: whether the thing it named is still there. Ending a session it did not name is
|
|
205
|
+
* not a recoverable mistake (#983).
|
|
206
|
+
*/
|
|
207
|
+
departed(sessionId) {
|
|
208
|
+
return this.#tombstones.get(sessionId);
|
|
209
|
+
}
|
|
189
210
|
list() {
|
|
190
211
|
return [...this.#sessions.values()].map((s) => {
|
|
191
212
|
const info = s.info();
|
|
@@ -304,9 +325,15 @@ export class SessionManager {
|
|
|
304
325
|
everConnected() {
|
|
305
326
|
return this.#everConnected;
|
|
306
327
|
}
|
|
307
|
-
/**
|
|
308
|
-
|
|
309
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Record a bridge-initiated close so `resolve` can explain a session that vanished.
|
|
330
|
+
*
|
|
331
|
+
* `page` is what the refused hello said about itself, when it said anything. A refusal is the one
|
|
332
|
+
* piece of positive evidence the no-session diagnosis has, and "a page was refused" without WHICH
|
|
333
|
+
* page leaves the reader unable to tell their app from another project's tab on the same port.
|
|
334
|
+
*/
|
|
335
|
+
noteClosure(reason, at, page) {
|
|
336
|
+
this.#recentClosures.push({ at, reason, ...(page === undefined ? {} : { page }) });
|
|
310
337
|
if (this.#recentClosures.length > MAX_REMEMBERED_CLOSURES)
|
|
311
338
|
this.#recentClosures.shift();
|
|
312
339
|
this.#connectedSinceLastClosure = false;
|
|
@@ -491,6 +518,16 @@ export class SessionManager {
|
|
|
491
518
|
const allThrottled = bestScore >= 1;
|
|
492
519
|
const RECENCY_GAP_MS = allThrottled ? 0 : 1_000;
|
|
493
520
|
const clearWinner = runnerUp === undefined || best.ms + RECENCY_GAP_MS < runnerUp.ms;
|
|
521
|
+
// Healthy tabs heartbeat on the same cadence, so the recency gap between two of them is noise.
|
|
522
|
+
// When exactly one has the window focus it is the one a human is looking at: tabs piled up one
|
|
523
|
+
// per `init` run, and every tool called without a sessionId was refused while one of them was
|
|
524
|
+
// plainly the tab in front. Only among healthy ones — focus says nothing about a wedged tab.
|
|
525
|
+
const focused = allThrottled ? [] : candidates.filter((x) => x.s.health().focused);
|
|
526
|
+
const [onlyFocused, secondFocused] = focused;
|
|
527
|
+
if (!clearWinner && onlyFocused !== undefined && secondFocused === undefined) {
|
|
528
|
+
onlyFocused.s.markAgentActivity();
|
|
529
|
+
return onlyFocused.s;
|
|
530
|
+
}
|
|
494
531
|
if (!clearWinner) {
|
|
495
532
|
// Ambiguous: list sessions with their health so the agent can choose.
|
|
496
533
|
const detail = all
|
|
@@ -22,6 +22,34 @@ export declare function composeEndedNotice(base: string, undelivered: string[]):
|
|
|
22
22
|
* not a crash, since the agent is still connected and revives on its next action. Returns the ids.
|
|
23
23
|
*/
|
|
24
24
|
export declare function reapIdleSessions(sessions: SessionManager): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Drop sessions that have ENDED, gone quiet and lost their socket, so a listing stops offering them.
|
|
27
|
+
*
|
|
28
|
+
* `reticle_session { action: "end" }` sets state and nothing else -- it never calls
|
|
29
|
+
* `sessions.remove`. `SessionManager.list()` does not filter by state, and `reapIdleSessions`
|
|
30
|
+
* above skips ended sessions by design. The only code path in the whole server that removes a
|
|
31
|
+
* session is the socket `close` handler in bridge.ts. So an ended session whose socket is already
|
|
32
|
+
* gone has nothing left that can remove it, and the row is listed for the life of the daemon
|
|
33
|
+
* (#938).
|
|
34
|
+
*
|
|
35
|
+
* That is what the field report describes: `{ ended: true }` three times, the row back on the next
|
|
36
|
+
* `reticle_sessions` call each time, still there after every tab on the origin was closed, 17 hours
|
|
37
|
+
* attached. And Reticle's own advice for a stale session is
|
|
38
|
+
* `Call reticle_session{action:"end"} to free this session` -- which is the one thing that does not
|
|
39
|
+
* free it.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately NOT "remove on end". The panel is client-side, so `setState` has already pushed the
|
|
42
|
+
* ended state to the browser and the HUD keeps it; but `end` is documented idempotent, and removing
|
|
43
|
+
* immediately would make a second call fail to resolve. Waiting for the session to go quiet keeps
|
|
44
|
+
* that, keeps a just-ended session inspectable, and still collects the case that actually hurts: a
|
|
45
|
+
* row nobody can get rid of.
|
|
46
|
+
*
|
|
47
|
+
* Only once the socket is no longer open. An ended session whose tab is still connected but quiet
|
|
48
|
+
* belongs to a page that holds a live connection; dropping the record would orphan that socket,
|
|
49
|
+
* leaving a connection the daemon no longer tracks. Such a session is still removed the ordinary
|
|
50
|
+
* way, by the `close` handler, when the tab goes.
|
|
51
|
+
*/
|
|
52
|
+
export declare function reapEndedSessions(sessions: SessionManager, staleAfterMs?: number): string[];
|
|
25
53
|
/** End every active session immediately (the agent / MCP client disconnected → WARN). Returns ended ids. */
|
|
26
54
|
export declare function endAllSessions(sessions: SessionManager, reason: string): string[];
|
|
27
55
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PresenterTone, SESSION_LIFECYCLE, UNDELIVERED_NOTES_LABEL } from '@reticlehq/core';
|
|
1
|
+
import { PresenterTone, SESSION_LEASE, SESSION_LIFECYCLE, UNDELIVERED_NOTES_LABEL, } from '@reticlehq/core';
|
|
2
2
|
import { log } from '../../log.js';
|
|
3
3
|
/**
|
|
4
4
|
* Server-authoritative session liveness. The browser idle timer is throttled in a backgrounded tab
|
|
@@ -51,6 +51,50 @@ export function reapIdleSessions(sessions) {
|
|
|
51
51
|
log('session_reaped_idle', { sessions: ended });
|
|
52
52
|
return ended;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Drop sessions that have ENDED, gone quiet and lost their socket, so a listing stops offering them.
|
|
56
|
+
*
|
|
57
|
+
* `reticle_session { action: "end" }` sets state and nothing else -- it never calls
|
|
58
|
+
* `sessions.remove`. `SessionManager.list()` does not filter by state, and `reapIdleSessions`
|
|
59
|
+
* above skips ended sessions by design. The only code path in the whole server that removes a
|
|
60
|
+
* session is the socket `close` handler in bridge.ts. So an ended session whose socket is already
|
|
61
|
+
* gone has nothing left that can remove it, and the row is listed for the life of the daemon
|
|
62
|
+
* (#938).
|
|
63
|
+
*
|
|
64
|
+
* That is what the field report describes: `{ ended: true }` three times, the row back on the next
|
|
65
|
+
* `reticle_sessions` call each time, still there after every tab on the origin was closed, 17 hours
|
|
66
|
+
* attached. And Reticle's own advice for a stale session is
|
|
67
|
+
* `Call reticle_session{action:"end"} to free this session` -- which is the one thing that does not
|
|
68
|
+
* free it.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately NOT "remove on end". The panel is client-side, so `setState` has already pushed the
|
|
71
|
+
* ended state to the browser and the HUD keeps it; but `end` is documented idempotent, and removing
|
|
72
|
+
* immediately would make a second call fail to resolve. Waiting for the session to go quiet keeps
|
|
73
|
+
* that, keeps a just-ended session inspectable, and still collects the case that actually hurts: a
|
|
74
|
+
* row nobody can get rid of.
|
|
75
|
+
*
|
|
76
|
+
* Only once the socket is no longer open. An ended session whose tab is still connected but quiet
|
|
77
|
+
* belongs to a page that holds a live connection; dropping the record would orphan that socket,
|
|
78
|
+
* leaving a connection the daemon no longer tracks. Such a session is still removed the ordinary
|
|
79
|
+
* way, by the `close` handler, when the tab goes.
|
|
80
|
+
*/
|
|
81
|
+
export function reapEndedSessions(sessions, staleAfterMs = SESSION_LEASE.STALE_AFTER_MS) {
|
|
82
|
+
const dropped = [];
|
|
83
|
+
// Snapshot first: `remove` mutates the map this iterates.
|
|
84
|
+
for (const session of [...sessions.all()]) {
|
|
85
|
+
if (!session.isEnded())
|
|
86
|
+
continue;
|
|
87
|
+
if (session.socketOpen())
|
|
88
|
+
continue;
|
|
89
|
+
if (session.staleMs() < staleAfterMs)
|
|
90
|
+
continue;
|
|
91
|
+
if (sessions.remove(session))
|
|
92
|
+
dropped.push(session.id);
|
|
93
|
+
}
|
|
94
|
+
if (dropped.length > 0)
|
|
95
|
+
log('session_reaped_ended', { sessions: dropped });
|
|
96
|
+
return dropped;
|
|
97
|
+
}
|
|
54
98
|
/** End every active session immediately (the agent / MCP client disconnected → WARN). Returns ended ids. */
|
|
55
99
|
export function endAllSessions(sessions, reason) {
|
|
56
100
|
const ended = [];
|
|
@@ -81,6 +125,10 @@ export class SessionReaper {
|
|
|
81
125
|
return;
|
|
82
126
|
this.#timer = setInterval(() => {
|
|
83
127
|
reapIdleSessions(this.#sessions);
|
|
128
|
+
// Second rule, same sweep, kept as its own function: one ends a live session that went
|
|
129
|
+
// quiet, the other collects one that already ended. Merging them would put "end this" and
|
|
130
|
+
// "forget this" behind a single condition.
|
|
131
|
+
reapEndedSessions(this.#sessions);
|
|
84
132
|
}, this.#intervalMs);
|
|
85
133
|
this.#timer.unref();
|
|
86
134
|
}
|
|
@@ -26,6 +26,17 @@ export interface SuccessorClock {
|
|
|
26
26
|
* A same-id reconnect is a successor: the id survived in sessionStorage, the document did not.
|
|
27
27
|
*/
|
|
28
28
|
export declare function pickDocumentSuccessor(live: readonly SessionIdentity[], departed: SessionIdentity): SessionIdentity | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Whether `candidate` was already connected before the agent last drove `departed`.
|
|
31
|
+
*
|
|
32
|
+
* Such a tab is a SIBLING, never the document the departed tab's navigation produced: that document
|
|
33
|
+
* can only have connected after the command that caused it. Without this, two tabs of one project
|
|
34
|
+
* were resolved as "unique at the origin" in the gap before the departed tab came back, and a verdict
|
|
35
|
+
* for tab A was graded on tab B (#983, reproduced by driving). Measured as ages rather than instants,
|
|
36
|
+
* so both sides read the same clock at the same moment and the comparison does not drift while a
|
|
37
|
+
* caller polls.
|
|
38
|
+
*/
|
|
39
|
+
export declare function openedBeforeDeparture(candidate: Pick<Session, 'staleMs'>, departed: Pick<Session, 'agentIdleMs'>): boolean;
|
|
29
40
|
export interface SuccessorRegistry {
|
|
30
41
|
get(id: string): Session | undefined;
|
|
31
42
|
all(): readonly Session[];
|
|
@@ -100,6 +100,19 @@ export function pickDocumentSuccessor(live, departed) {
|
|
|
100
100
|
return undefined;
|
|
101
101
|
return others[0];
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Whether `candidate` was already connected before the agent last drove `departed`.
|
|
105
|
+
*
|
|
106
|
+
* Such a tab is a SIBLING, never the document the departed tab's navigation produced: that document
|
|
107
|
+
* can only have connected after the command that caused it. Without this, two tabs of one project
|
|
108
|
+
* were resolved as "unique at the origin" in the gap before the departed tab came back, and a verdict
|
|
109
|
+
* for tab A was graded on tab B (#983, reproduced by driving). Measured as ages rather than instants,
|
|
110
|
+
* so both sides read the same clock at the same moment and the comparison does not drift while a
|
|
111
|
+
* caller polls.
|
|
112
|
+
*/
|
|
113
|
+
export function openedBeforeDeparture(candidate, departed) {
|
|
114
|
+
return candidate.staleMs() > departed.agentIdleMs();
|
|
115
|
+
}
|
|
103
116
|
/**
|
|
104
117
|
* Poll until a successor of `departed` is live, or the budget runs out. Null on timeout — never
|
|
105
118
|
* throws, because failing to follow is a legitimate `observation_lost`, not an error.
|
|
@@ -115,7 +128,7 @@ export async function awaitDocumentSuccessor(sessions, departed, timeoutMs, cloc
|
|
|
115
128
|
// out of the pick, or same-id-first would keep returning the page that just unloaded.
|
|
116
129
|
const picked = pickDocumentSuccessor(sessions
|
|
117
130
|
.all()
|
|
118
|
-
.filter((s) => s !== departed)
|
|
131
|
+
.filter((s) => s !== departed && !openedBeforeDeparture(s, departed))
|
|
119
132
|
.map(identityOf), departedIdentity);
|
|
120
133
|
if (picked !== undefined) {
|
|
121
134
|
const live = sessions.get(picked.id);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The facts a SESSION contributes to a verdict, in the shape `decideVerified` takes.
|
|
3
|
+
*
|
|
4
|
+
* `decideVerified` is pure and has no session, so anything it needs about the link has to be
|
|
5
|
+
* threaded in. Two tools produce verdicts — `reticle_assert` and `reticle_act_and_wait` — and both
|
|
6
|
+
* were threading the same fields with the same conditional-spread idiom and the same comment
|
|
7
|
+
* beside it. A third fact (`versionSkew`) would have been a third copy.
|
|
8
|
+
*
|
|
9
|
+
* One function instead, for the ordinary reason: when a fourth fact arrives, a verdict surface that
|
|
10
|
+
* learns it in one place cannot half-learn it. That failure mode is not hypothetical here — the
|
|
11
|
+
* repo already records `net` assertions being discarded on one half of a surface because a filter
|
|
12
|
+
* was updated on the other.
|
|
13
|
+
*
|
|
14
|
+
* Every field is omitted rather than defaulted when absent. An SDK too old to declare sends
|
|
15
|
+
* nothing, and an empty value is a claim nobody made.
|
|
16
|
+
*/
|
|
17
|
+
/** What a session can tell a verdict. Optional throughout: absent means nobody said. */
|
|
18
|
+
export interface SessionVerdictFacts {
|
|
19
|
+
readonly sdkVersion?: string;
|
|
20
|
+
readonly versionSkew?: string;
|
|
21
|
+
}
|
|
22
|
+
/** The shape read here — structural, so a test double needs no session. */
|
|
23
|
+
export interface VerdictRelevantSession {
|
|
24
|
+
readonly sdkVersion?: string | undefined;
|
|
25
|
+
readonly versionSkew?: string | undefined;
|
|
26
|
+
readonly channels?: readonly string[] | undefined;
|
|
27
|
+
}
|
|
28
|
+
export declare function sessionVerdictFacts(session: VerdictRelevantSession): SessionVerdictFacts;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function sessionVerdictFacts(session) {
|
|
2
|
+
const facts = {};
|
|
3
|
+
// So the unread-body remedy can check it applies to THIS page.
|
|
4
|
+
if (session.sdkVersion !== undefined)
|
|
5
|
+
facts.sdkVersion = session.sdkVersion;
|
|
6
|
+
/*
|
|
7
|
+
* An empty string is NOT a skew. The bridge sets this only on a real mismatch, but a defaulted
|
|
8
|
+
* or cleared field reaching the verdict would cost every call a `yes` it had earned — which is a
|
|
9
|
+
* worse failure than the one this exists to fix.
|
|
10
|
+
*/
|
|
11
|
+
if (session.versionSkew !== undefined && session.versionSkew.length > 0) {
|
|
12
|
+
facts.versionSkew = session.versionSkew;
|
|
13
|
+
}
|
|
14
|
+
return facts;
|
|
15
|
+
}
|
|
@@ -44,6 +44,11 @@ export declare class Session implements HandshakeFacts {
|
|
|
44
44
|
* root, which is what every counter did unconditionally before this existed.
|
|
45
45
|
*/
|
|
46
46
|
artifactRoot: string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Same-origin endpoints this session's project declared as background (`.reticle.json`
|
|
49
|
+
* `background`). Stamped with `artifactRoot`, from the same project. Empty excludes nothing.
|
|
50
|
+
*/
|
|
51
|
+
background: readonly string[];
|
|
47
52
|
url: string;
|
|
48
53
|
title: string;
|
|
49
54
|
adapters: string[];
|
|
@@ -86,6 +91,8 @@ export declare class Session implements HandshakeFacts {
|
|
|
86
91
|
* `reticle_session{action:"end"}` — a flag on the record, not on the page — could not help.
|
|
87
92
|
*/
|
|
88
93
|
unresponsive(): boolean;
|
|
94
|
+
/** False once the socket is closing or closed: dropping this record then orphans nothing. */
|
|
95
|
+
socketOpen(): boolean;
|
|
89
96
|
/** Wall-clock age of the session in milliseconds. */
|
|
90
97
|
staleMs(): number;
|
|
91
98
|
/** The document the most recent stamped event was observed under. See `#documentId`. */
|
|
@@ -109,6 +116,9 @@ export declare class Session implements HandshakeFacts {
|
|
|
109
116
|
* against the same control after a re-render gave it a different ref.
|
|
110
117
|
*/
|
|
111
118
|
private recordActedLabelFrom;
|
|
119
|
+
/** A control whose action PROVED its declared consequence — the coverage ledger's `proved`. */
|
|
120
|
+
recordProvedFrom(actPayload: unknown): void;
|
|
121
|
+
provedLabels(): ReadonlySet<string>;
|
|
112
122
|
actedRefs(): ReadonlySet<string>;
|
|
113
123
|
noteRateLimited(dropped: number): void;
|
|
114
124
|
blindSpots(): Readonly<Record<string, number>>;
|
|
@@ -5,19 +5,19 @@ import { LastAct } from './last-act.js';
|
|
|
5
5
|
import { GapLedger } from '@reticlehq/engine/evidence/gap-ledger.js';
|
|
6
6
|
import { CaptureLedger } from '../../surface/tools/feature-capture.js';
|
|
7
7
|
import { commandTimeoutMessage } from './timing/command-timeout.js';
|
|
8
|
-
import { readHealthEvent,
|
|
8
|
+
import { readHealthEvent, healthNotices } from './session-health.js';
|
|
9
9
|
import { MIRRORED_COMMANDS, mirroredNarration } from './presence/session-mirror.js';
|
|
10
10
|
/** The HUD does not need 200 frames to watch a counter climb; the last state always lands. */
|
|
11
11
|
const IMPACT_PUSH_DEBOUNCE_MS = 700;
|
|
12
12
|
import { PendingCommands, CommandTimeoutError } from './page-commands/pending-commands.js';
|
|
13
13
|
import { span } from '../../trace.js';
|
|
14
14
|
import { keepCallerContextInDaemon, noteToDaemonLog } from './daemon-answers.js';
|
|
15
|
-
import { isKnownRealm, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
|
|
15
|
+
import { isKnownRealm, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, HudUseDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
|
|
16
16
|
import { RingBuffer } from '@reticlehq/engine/window/ring-buffer.js';
|
|
17
17
|
import { readJournalWriteLoss, readQueryEvents, } from '../../memory/journal/journal-query.js';
|
|
18
18
|
import {} from '@reticlehq/engine/window/ambient.js';
|
|
19
|
-
import { ObservedState } from './facts/observed-state.js';
|
|
20
|
-
import { recordBrowserLatency, recordSdkFailure } from '../../telemetry/session-metrics.js';
|
|
19
|
+
import { ObservedState, controlLabelsOf } from './facts/observed-state.js';
|
|
20
|
+
import { recordBrowserLatency, recordHudUse, recordSdkFailure, } from '../../telemetry/session-metrics.js';
|
|
21
21
|
import { LiveControl } from './human/live-control.js';
|
|
22
22
|
import { ReviewStore } from './human/review-store.js';
|
|
23
23
|
import { buildSessionRecommendation } from './presence/session-recommendation.js';
|
|
@@ -63,6 +63,11 @@ export class Session {
|
|
|
63
63
|
* root, which is what every counter did unconditionally before this existed.
|
|
64
64
|
*/
|
|
65
65
|
artifactRoot;
|
|
66
|
+
/**
|
|
67
|
+
* Same-origin endpoints this session's project declared as background (`.reticle.json`
|
|
68
|
+
* `background`). Stamped with `artifactRoot`, from the same project. Empty excludes nothing.
|
|
69
|
+
*/
|
|
70
|
+
background = [];
|
|
66
71
|
url;
|
|
67
72
|
title;
|
|
68
73
|
adapters;
|
|
@@ -188,17 +193,16 @@ export class Session {
|
|
|
188
193
|
}
|
|
189
194
|
/** The attachable health block — single source of truth for the tools. */
|
|
190
195
|
health() {
|
|
191
|
-
// From event t=0, not a cursor: a wedge that began before the current action is exactly the
|
|
192
|
-
// case a per-window reading cannot see, and is the one both reporters hit.
|
|
193
|
-
const stuck = pendingNavigationMs(this.eventsSince(0), this.elapsed());
|
|
194
196
|
const base = {
|
|
195
197
|
lastSeenMs: this.lastSeenMs(),
|
|
196
198
|
throttled: this.throttled(),
|
|
197
199
|
focused: this.#focused,
|
|
198
|
-
...(stuck === undefined ? {} : { pendingNavigationMs: stuck }),
|
|
199
200
|
// Carried onto every act/assert result, not just reticle_sessions: skew drops actions
|
|
200
201
|
// SILENTLY, and the fields it contradicts are on the act verdict.
|
|
201
202
|
...(this.versionSkew === undefined ? {} : { versionSkew: this.versionSkew }),
|
|
203
|
+
// The absence-gated facts: a stuck navigation and an ledger worth naming. Both are absent on
|
|
204
|
+
// a healthy session, and `healthNotices` owns which is which.
|
|
205
|
+
...healthNotices(this.eventsSince(0), this.elapsed(), this.#journalReader?.ledger?.()),
|
|
202
206
|
};
|
|
203
207
|
// attach the escape-hatch hint only when un-scriptable (keeps field absent otherwise).
|
|
204
208
|
const recommendation = buildSessionRecommendation({
|
|
@@ -237,6 +241,10 @@ export class Session {
|
|
|
237
241
|
unresponsive() {
|
|
238
242
|
return this.#unansweredCommands >= UNRESPONSIVE_AFTER_TIMEOUTS;
|
|
239
243
|
}
|
|
244
|
+
/** False once the socket is closing or closed: dropping this record then orphans nothing. */
|
|
245
|
+
socketOpen() {
|
|
246
|
+
return this.#socket.readyState === WS_OPEN;
|
|
247
|
+
}
|
|
240
248
|
/** Wall-clock age of the session in milliseconds. */
|
|
241
249
|
staleMs() {
|
|
242
250
|
return this.#clock() - this.#startedAt;
|
|
@@ -261,6 +269,14 @@ export class Session {
|
|
|
261
269
|
keepCallerContext = keepCallerContextInDaemon;
|
|
262
270
|
/** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
|
|
263
271
|
pushEvent(event, byteSize) {
|
|
272
|
+
// Telemetry about the HUD, not evidence about the app: taken off here, before the buffer, so no
|
|
273
|
+
// verdict, settle window or agent read can ever see a person's clicks on Reticle's own panel.
|
|
274
|
+
if (event.type === EventType.HUD_USED) {
|
|
275
|
+
const use = HudUseDataSchema.safeParse(event.data);
|
|
276
|
+
if (use.success)
|
|
277
|
+
recordHudUse(use.data);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
264
280
|
if (event.documentId !== undefined)
|
|
265
281
|
this.#documentId = event.documentId;
|
|
266
282
|
if (event.editEpoch !== undefined)
|
|
@@ -340,24 +356,16 @@ export class Session {
|
|
|
340
356
|
* against the same control after a re-render gave it a different ref.
|
|
341
357
|
*/
|
|
342
358
|
recordActedLabelFrom(result) {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
this.#observed.recordActedLabel(testid);
|
|
354
|
-
const role = record['role'];
|
|
355
|
-
const name = record['name'];
|
|
356
|
-
if (typeof role !== 'string' || typeof name !== 'string')
|
|
357
|
-
return;
|
|
358
|
-
if (0 === role.length || 0 === name.length)
|
|
359
|
-
return;
|
|
360
|
-
this.#observed.recordActedLabel(`${role} "${name}"`);
|
|
359
|
+
for (const label of controlLabelsOf(result.result))
|
|
360
|
+
this.#observed.recordActedLabel(label);
|
|
361
|
+
}
|
|
362
|
+
/** A control whose action PROVED its declared consequence — the coverage ledger's `proved`. */
|
|
363
|
+
recordProvedFrom(actPayload) {
|
|
364
|
+
for (const label of controlLabelsOf(actPayload))
|
|
365
|
+
this.#observed.recordProvedLabel(label);
|
|
366
|
+
}
|
|
367
|
+
provedLabels() {
|
|
368
|
+
return this.#observed.provedLabels();
|
|
361
369
|
}
|
|
362
370
|
actedRefs() {
|
|
363
371
|
return this.#observed.actedRefs();
|
|
@@ -10,7 +10,8 @@ export interface SharedServer {
|
|
|
10
10
|
*/
|
|
11
11
|
attachMcp(factory: () => McpServer): void;
|
|
12
12
|
/** Register the JSON the daemon returns from GET /status (live sessions + health for `reticle status`). */
|
|
13
|
-
|
|
13
|
+
/** `held`: the request carried STATUS_HOLD_QUERY — a setup run is waiting on this daemon. */
|
|
14
|
+
attachStatus(provider: (held: boolean) => unknown): void;
|
|
14
15
|
/**
|
|
15
16
|
* Register the handler behind POST /drive: open a driveable browser at `url` and answer with the
|
|
16
17
|
* session it registered.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
2
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
3
|
-
import { MCP_SSE_PATH, MCP_MESSAGE_PATH, MCP_SHUTDOWN_EVENT, STATUS_PATH, DRIVE_PATH, } from '@reticlehq/core';
|
|
3
|
+
import { MCP_SSE_PATH, MCP_MESSAGE_PATH, MCP_SHUTDOWN_EVENT, STATUS_PATH, STATUS_HOLD_QUERY, DRIVE_PATH, } from '@reticlehq/core';
|
|
4
4
|
import { getSessionMetrics } from '../telemetry/session-metrics.js';
|
|
5
5
|
import { noteAgentPeer, PEER_VERSION_PARAM, PEER_CONTRACT_PARAM, } from '../command/version/peer-announce.js';
|
|
6
6
|
import { log } from '../log.js';
|
|
@@ -76,7 +76,9 @@ export function createSharedServer(options = {}) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
if ('GET' === req.method && path === STATUS_PATH) {
|
|
79
|
-
|
|
79
|
+
// A setup run waiting on this daemon says so on its polls; see daemon-usefulness.
|
|
80
|
+
const held = url.searchParams.has(STATUS_HOLD_QUERY);
|
|
81
|
+
const body = JSON.stringify(statusProvider?.(held) ?? { running: true });
|
|
80
82
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
81
83
|
res.end(body);
|
|
82
84
|
return;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export { SseFrameParser, type SseFrame } from './sse-frame-parser.js';
|
|
2
|
-
export { probeDaemon, waitForDaemon } from './proxy/proxy-daemon-probe.js';
|
|
2
|
+
export { probeDaemon, waitForDaemon, waitForDaemonBind } from './proxy/proxy-daemon-probe.js';
|
|
3
3
|
/**
|
|
4
4
|
* Re-exported so every existing caller (and every spec) keeps importing the proxy's log from the
|
|
5
5
|
* proxy. Where the code lives is a file-size decision; where it is imported from is an API.
|
|
6
6
|
*/
|
|
7
7
|
export { accountProxyLogWrite, proxyLog, proxyLogPath, PROXY_LOG_CHECK_BYTES, recoverOversizedProxyLog, setProxyLogPort, } from './proxy/proxy-log.js';
|
|
8
|
-
export { reconnectDelayMs
|
|
8
|
+
export { reconnectDelayMs } from './proxy/proxy-backoff.js';
|
|
9
9
|
export { MCP_PROXY_HTTP_AGENT_OPTIONS, postToSession, shouldRetryUnsentPost, type PostFailure, } from './proxy/mcp-post-transport.js';
|
|
10
10
|
/**
|
|
11
11
|
* Overridable so the survival spec can reach exhaustion in seconds instead of minutes — the same
|
|
@@ -6,13 +6,13 @@ import { toolsChangedNotification } from './proxy/proxy-handshake.js';
|
|
|
6
6
|
import { LOOPBACK_HOST, MCP_SSE_PATH, MCP_SHUTDOWN_EVENT, ReticleEnv, CONTRACT_FINGERPRINT, } from '@reticlehq/core';
|
|
7
7
|
import { SseFrameParser } from './sse-frame-parser.js';
|
|
8
8
|
export { SseFrameParser } from './sse-frame-parser.js';
|
|
9
|
-
export { probeDaemon, waitForDaemon } from './proxy/proxy-daemon-probe.js';
|
|
9
|
+
export { probeDaemon, waitForDaemon, waitForDaemonBind } from './proxy/proxy-daemon-probe.js';
|
|
10
10
|
import { probeDaemon } from './proxy/proxy-daemon-probe.js';
|
|
11
11
|
import { SERVER_VERSION } from '../../command/version/identity/server-version.js';
|
|
12
12
|
import { buildServerInstructions } from './server-instructions.js';
|
|
13
|
-
import {
|
|
13
|
+
import { hasAnyProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
|
|
14
14
|
import { reticleStateHome } from '../../command/daemon/daemon.js';
|
|
15
|
-
import {
|
|
15
|
+
import { projectIdsAt } from '../../command/cli/ports/resolve/cli-port.js';
|
|
16
16
|
import { PEER_VERSION_PARAM, PEER_CONTRACT_PARAM } from '../../command/version/peer-announce.js';
|
|
17
17
|
import { onStreamDrop, onClientRequest, onReconnectBudgetSpent, onQueueExpired, OnDrop, OnRequest, } from './proxy/proxy-lifecycle.js';
|
|
18
18
|
import { describePresence, probePresence } from '../../command/daemon/binding/port-presence.js';
|
|
@@ -33,7 +33,7 @@ import { OutageReason, OutageStage, reportMcpOutage } from './faults/mcp-outage.
|
|
|
33
33
|
import { postToSession } from './proxy/mcp-post-transport.js';
|
|
34
34
|
import { reconnectDelayMs } from './proxy/proxy-backoff.js';
|
|
35
35
|
import { PROXY_IDLE_EXIT_EVENT, ProxyIdleExit, resolveProxyIdleExitMs } from './proxy-idle-exit.js';
|
|
36
|
-
export { reconnectDelayMs
|
|
36
|
+
export { reconnectDelayMs } from './proxy/proxy-backoff.js';
|
|
37
37
|
export { MCP_PROXY_HTTP_AGENT_OPTIONS, postToSession, shouldRetryUnsentPost, } from './proxy/mcp-post-transport.js';
|
|
38
38
|
/**
|
|
39
39
|
* How many consecutive failed reconnects before the proxy stops RETRYING. It does not stop serving:
|
|
@@ -289,7 +289,7 @@ export function buildSessionUrl(rawData, port) {
|
|
|
289
289
|
function proxyInstructions(port) {
|
|
290
290
|
try {
|
|
291
291
|
return buildServerInstructions({
|
|
292
|
-
previouslyConnected:
|
|
292
|
+
previouslyConnected: hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())),
|
|
293
293
|
});
|
|
294
294
|
}
|
|
295
295
|
catch {
|
package/dist/surface/mcp/mcp.js
CHANGED
|
@@ -103,7 +103,7 @@ import { firstSentence } from '../tools/first-sentence.js';
|
|
|
103
103
|
* making it a pointer costs nothing.
|
|
104
104
|
*/
|
|
105
105
|
const PREDICATE_KINDS = 'Predicate object: { kind, ...fields }. kind is one of element | text | net | route | console | ' +
|
|
106
|
-
'animation | signal | state | settled | allOf | anyOf | not.';
|
|
106
|
+
'animation | signal | state | settled | compare | allOf | anyOf | not.';
|
|
107
107
|
/**
|
|
108
108
|
* Said once per turn: the bug-catching options, and where to get the rest of the field grammar.
|
|
109
109
|
*
|
|
@@ -118,9 +118,11 @@ const PREDICATE_KINDS = 'Predicate object: { kind, ...fields }. kind is one of e
|
|
|
118
118
|
* paying for six.
|
|
119
119
|
*/
|
|
120
120
|
const PREDICATE_FIELD_GRAMMAR_HINT = ' Bug-catching options: net.count (exact request count — catches double-submit), ' +
|
|
121
|
-
'net.bodyMatches (a
|
|
121
|
+
'net.bodyMatches (a JSON field match on what the SERVER answered — say the FIELD (a dotted path reaches nested ones) and its value, ' +
|
|
122
122
|
'because a substring of the body matches key names too), net.requestBodyMatches (the same on what ' +
|
|
123
123
|
'the UI SENT — the verdict for "applying this filter actually puts it in the payload"), ' +
|
|
124
|
+
'compare (page text vs server answer, no expected value: { left:{from:"text",scope}, ' +
|
|
125
|
+
'right:{from:"net",urlContains,path}, as:"number" }), ' +
|
|
124
126
|
'console.absent:true (action completed with a CLEAN ' +
|
|
125
127
|
'console), absent:true on element/text (it should be gone). Call reticle_tools for the full field ' +
|
|
126
128
|
'grammar of a kind.';
|
|
@@ -3,8 +3,12 @@ export declare function daemonPollDelayMs(attempt: number): number;
|
|
|
3
3
|
* Returns true if something is already listening on the reticle port.
|
|
4
4
|
* Uses a plain TCP probe so we don't create a side-effectful SSE session
|
|
5
5
|
* inside the daemon just to check reachability.
|
|
6
|
+
*
|
|
7
|
+
* `host` defaults to loopback, which is every caller's actual target — except `bootSession`
|
|
8
|
+
* (reticlehq/reticle#1165 review), whose Bridge can bind a non-default host via `RETICLE_HOST`.
|
|
9
|
+
* Hardcoding loopback here made that preflight check the wrong address.
|
|
6
10
|
*/
|
|
7
|
-
export declare function probeDaemon(port: number): Promise<boolean>;
|
|
11
|
+
export declare function probeDaemon(port: number, host?: string): Promise<boolean>;
|
|
8
12
|
/**
|
|
9
13
|
* Poll until a Reticle daemon is SERVING on `port`, or the deadline passes.
|
|
10
14
|
*
|
|
@@ -20,3 +24,11 @@ export declare function waitForDaemon(port: number, probes?: {
|
|
|
20
24
|
tcpOpen: (port: number) => Promise<boolean>;
|
|
21
25
|
status: (port: number) => Promise<unknown>;
|
|
22
26
|
}): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Poll until `/status` on `port` answers as a Reticle daemon; false once the bound passes.
|
|
29
|
+
*
|
|
30
|
+
* One rule for the three commands that start a daemon. `init` used to borrow `waitForDaemon`'s
|
|
31
|
+
* shorter reconnect budget, and on a cold fresh HOME it printed "could not start the Reticle
|
|
32
|
+
* daemon" over a daemon that bound seconds later. Bounded polling, never a fixed sleep.
|
|
33
|
+
*/
|
|
34
|
+
export declare function waitForDaemonBind(port: number): Promise<boolean>;
|
|
@@ -24,8 +24,12 @@ export function daemonPollDelayMs(attempt) {
|
|
|
24
24
|
* Returns true if something is already listening on the reticle port.
|
|
25
25
|
* Uses a plain TCP probe so we don't create a side-effectful SSE session
|
|
26
26
|
* inside the daemon just to check reachability.
|
|
27
|
+
*
|
|
28
|
+
* `host` defaults to loopback, which is every caller's actual target — except `bootSession`
|
|
29
|
+
* (reticlehq/reticle#1165 review), whose Bridge can bind a non-default host via `RETICLE_HOST`.
|
|
30
|
+
* Hardcoding loopback here made that preflight check the wrong address.
|
|
27
31
|
*/
|
|
28
|
-
export function probeDaemon(port) {
|
|
32
|
+
export function probeDaemon(port, host = LOOPBACK_HOST) {
|
|
29
33
|
return new Promise((resolve) => {
|
|
30
34
|
const socket = new net.Socket();
|
|
31
35
|
socket.setTimeout(500);
|
|
@@ -38,7 +42,7 @@ export function probeDaemon(port) {
|
|
|
38
42
|
socket.destroy();
|
|
39
43
|
resolve(false);
|
|
40
44
|
});
|
|
41
|
-
socket.connect(port,
|
|
45
|
+
socket.connect(port, host);
|
|
42
46
|
});
|
|
43
47
|
}
|
|
44
48
|
/**
|
|
@@ -69,3 +73,24 @@ export async function waitForDaemon(port, probes = { tcpOpen: probeDaemon, statu
|
|
|
69
73
|
throw new Error(`reticle daemon did not become ready on port ${String(port)} within ` +
|
|
70
74
|
`${String(DAEMON_READY_TIMEOUT_MS)}ms — ${describePresence(presence, port)}`);
|
|
71
75
|
}
|
|
76
|
+
/** How long a spawned daemon gets to bind before `serve`, `restart` or `init` gives up on it. */
|
|
77
|
+
const BIND_TIMEOUT_MS = 15_000;
|
|
78
|
+
const BIND_POLL_MS = 150;
|
|
79
|
+
/**
|
|
80
|
+
* Poll until `/status` on `port` answers as a Reticle daemon; false once the bound passes.
|
|
81
|
+
*
|
|
82
|
+
* One rule for the three commands that start a daemon. `init` used to borrow `waitForDaemon`'s
|
|
83
|
+
* shorter reconnect budget, and on a cold fresh HOME it printed "could not start the Reticle
|
|
84
|
+
* daemon" over a daemon that bound seconds later. Bounded polling, never a fixed sleep.
|
|
85
|
+
*/
|
|
86
|
+
export async function waitForDaemonBind(port) {
|
|
87
|
+
const deadline = Date.now() + BIND_TIMEOUT_MS;
|
|
88
|
+
for (;;) {
|
|
89
|
+
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
90
|
+
if (presence === PortPresence.DAEMON)
|
|
91
|
+
return true;
|
|
92
|
+
if (Date.now() >= deadline)
|
|
93
|
+
return false;
|
|
94
|
+
await delay(BIND_POLL_MS);
|
|
95
|
+
}
|
|
96
|
+
}
|