@reticlehq/server 3.1.0 → 3.3.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/SKILL.md +30 -28
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +37 -5
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- 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 +8 -1
- package/dist/command/cli/cli-parse.js +38 -7
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +131 -19
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -7
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +3 -23
- package/dist/command/cli/launch/cli-launch.js +29 -70
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
- package/dist/command/cli/ports/resolve/cli-port.js +50 -2
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +29 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +3 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +661 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +62 -0
- package/dist/features/harness/platform-config.js +97 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +80 -130
- 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 +38 -9
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- 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/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 +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- 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/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -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-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +55 -10
- package/dist/language/flows/flow-replay-run.js +217 -58
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +29 -11
- package/dist/language/flows/flow-replay.js +221 -203
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- 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-store-for-session.d.ts +2 -1
- 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 +8 -4
- package/dist/language/flows/flows.d.ts +15 -10
- package/dist/language/flows/flows.js +17 -4
- 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/narrow-write.d.ts +4 -4
- 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/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +13 -9
- 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/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +21 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -4
- package/dist/memory/cloud/cloud-sync.js +11 -6
- package/dist/memory/cloud/harness-config.d.ts +12 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +35 -2
- package/dist/memory/cloud/sync-daemon.js +45 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +25 -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 +253 -141
- package/dist/memory/intent/intent-shard.d.ts +22 -23
- package/dist/memory/intent/intent-shard.js +1 -1
- 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/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- 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 +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +71 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- 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 +91 -2
- package/dist/memory/journal/on-disk/retention.js +181 -11
- 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 -2
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +17 -4
- package/dist/memory/journal/session-end.js +85 -7
- package/dist/memory/journal/session-journal.d.ts +122 -5
- package/dist/memory/journal/session-journal.js +489 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +72 -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/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- 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 +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +20 -3
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/input/real-input.js +2 -1
- 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.js +1 -0
- 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-server-probe.d.ts +2 -1
- 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/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +13 -0
- package/dist/portal/session/session-manager.js +42 -3
- 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 +26 -10
- package/dist/portal/session/session.js +48 -49
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +23 -12
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/act-preflight.js +6 -1
- 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/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- 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.js +49 -8
- package/dist/surface/tools/act-tools.js +25 -24
- 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 +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +74 -21
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +43 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +50 -3
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +82 -9
- 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 +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +69 -15
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- 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 +23 -3
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +70 -30
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -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 +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/telemetry/verification-of.js +31 -3
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +11 -9
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +144 -4
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/tools/query.mdx +10 -10
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +2 -2
- package/docs/what-is-recorded.md +62 -0
- package/package.json +5 -5
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
|
@@ -23,7 +23,7 @@ import { daemonsServingProjectElsewhere, splitBrainNote } from '../../command/da
|
|
|
23
23
|
import { stallUptime } from './stall-clock.js';
|
|
24
24
|
import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
|
|
25
25
|
import { findOccupiedSiblings } from '../../command/cli/ports/sibling-ports.js';
|
|
26
|
-
import {
|
|
26
|
+
import { isAuthRefusalReason } from '../bridge/auth-failure-reason.js';
|
|
27
27
|
/** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
|
|
28
28
|
const REFRESH_MS = 15_000;
|
|
29
29
|
/** The URL an auto-attach opens. `localhost` for the same reason the probe uses it: both stacks. */
|
|
@@ -208,7 +208,7 @@ export function startNoSessionWatch(options) {
|
|
|
208
208
|
/**
|
|
209
209
|
* Is a refusal on the pairing token the CURRENT state of the bridge?
|
|
210
210
|
*
|
|
211
|
-
* The bridge records the refusal (
|
|
211
|
+
* The bridge records the refusal (the sentence it closed the socket with) and nothing read it
|
|
212
212
|
* as a DIAGNOSIS. It is the one fact that proves an app is running and instrumented: only an SDK
|
|
213
213
|
* dials the bridge, so a refused hello means the wiring works and this daemon would not serve it.
|
|
214
214
|
*
|
|
@@ -223,7 +223,7 @@ export function startNoSessionWatch(options) {
|
|
|
223
223
|
* recorded no refusal, which falls through to the behaviour that was there before -- the safe
|
|
224
224
|
* direction for a fact whose job is to SUPPRESS advice rather than to add any.
|
|
225
225
|
*/
|
|
226
|
-
const lastCloseWasAuthFailure = () => options.sessions.lastClosure?.()?.reason
|
|
226
|
+
const lastCloseWasAuthFailure = () => isAuthRefusalReason(options.sessions.lastClosure?.()?.reason) &&
|
|
227
227
|
true !== options.sessions.connectedSinceLastClosure?.();
|
|
228
228
|
const nextAction = (scope) => {
|
|
229
229
|
const split = splitBrain();
|
|
@@ -307,19 +307,23 @@ export function startNoSessionWatch(options) {
|
|
|
307
307
|
...(0 === siblingListeners.length ? {} : { siblingListeners }),
|
|
308
308
|
});
|
|
309
309
|
};
|
|
310
|
-
|
|
310
|
+
// The lead, then the literal command, then the differential. Both renderings consume the same
|
|
311
|
+
// scope facts so a discovered workspace config cannot become an `init` recommendation below it,
|
|
312
|
+
// and both come from ONE `explain()` call so they cannot describe different branches.
|
|
313
|
+
const rendered = () => {
|
|
311
314
|
const scope = projectScopeFacts();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
const { message, detail } = explain();
|
|
316
|
+
const lead = `${message} ${renderNextAction(nextAction(scope))}${attachFailure ?? ''}`;
|
|
317
|
+
return { lead, full: undefined === detail ? lead : `${lead} ${detail}` };
|
|
318
|
+
};
|
|
319
|
+
options.sessions.setNoSessionHint(() => rendered().full);
|
|
320
|
+
options.sessions.setNoSessionLead(() => rendered().lead);
|
|
318
321
|
options.sessions.setNoSessionReason(() => explain().reason);
|
|
319
322
|
return () => {
|
|
320
323
|
clearInterval(timer);
|
|
321
324
|
options.sessions.setConnectionRecorder(undefined);
|
|
322
325
|
options.sessions.setNoSessionHint(undefined);
|
|
326
|
+
options.sessions.setNoSessionLead(undefined);
|
|
323
327
|
options.sessions.setNoSessionReason(undefined);
|
|
324
328
|
options.sessions.setNoSessionNextAction(undefined);
|
|
325
329
|
};
|
|
@@ -15,11 +15,22 @@
|
|
|
15
15
|
* The clock is injected, per the repo rule: this is arithmetic over timestamps, and a test that
|
|
16
16
|
* needs to wait four seconds to check a four-second gap is a test nobody runs.
|
|
17
17
|
*/
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* What an agent needs to know about the continuity of one session.
|
|
20
|
+
*
|
|
21
|
+
* Every field here measures the SDK-to-daemon LINK. None of them measures the app: the origin the
|
|
22
|
+
* page was served from can stop answering entirely without any of these moving, because the socket
|
|
23
|
+
* that reports them is between the daemon and a document the browser is already holding. Reported
|
|
24
|
+
* as a false read of app health in #938, where a tab whose dev server was dead carried `outages: 0`
|
|
25
|
+
* and `connectedSinceMs: 11min` and was taken for a healthy app.
|
|
26
|
+
*/
|
|
19
27
|
interface Attachment {
|
|
20
28
|
/** How long THIS attachment has lasted. Resets on reconnect — it is not the session's lifetime. */
|
|
21
29
|
connectedSinceMs: number;
|
|
22
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* How many times this session dropped and came back. Zero means the LINK never dropped; it does
|
|
32
|
+
* not mean the app behind it is alive.
|
|
33
|
+
*/
|
|
23
34
|
outages: number;
|
|
24
35
|
/** The most recent gap, when there has been one. */
|
|
25
36
|
lastOutage?: {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands that may be re-issued against the connection that replaced this session.
|
|
3
|
+
*
|
|
4
|
+
* Reads only, and the boundary is the point. Re-reading a page costs nothing and answers the same
|
|
5
|
+
* question the caller asked. An act cannot be replayed: it may already have been dispatched in the
|
|
6
|
+
* page that went away, and performing it a second time behind the caller's back is a double submit
|
|
7
|
+
* nobody asked for. A replaced act still errors, and the caller's own retry — with the same id,
|
|
8
|
+
* which is still the right one — is the safe path.
|
|
9
|
+
*/
|
|
10
|
+
export declare const REBINDABLE_COMMANDS: ReadonlySet<string>;
|
|
11
|
+
/**
|
|
12
|
+
* How far to walk a chain of replacements before giving up.
|
|
13
|
+
*
|
|
14
|
+
* Generous for the real case (a page reloading a handful of times while a call is in flight) and
|
|
15
|
+
* small enough that a cycle costs nothing. The number is a backstop, not a policy: a chain longer
|
|
16
|
+
* than this means something is re-dialling in a loop, and that is a different problem.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_SUCCESSOR_HOPS = 32;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a command in flight may be re-issued against the connection that REPLACED this session,
|
|
3
|
+
* and how far a chain of replacements is worth walking.
|
|
4
|
+
*
|
|
5
|
+
* One question, so one file. It left `session.ts` when that file crossed the thousand-line cap on a
|
|
6
|
+
* merge: two branches had each grown it by a few lines and the sum went over, which is the moment
|
|
7
|
+
* the rule says to split rather than to raise the number. These two constants and the reasoning
|
|
8
|
+
* behind them are the most self-contained thing in there — `session.ts` reads them twice and owns
|
|
9
|
+
* nothing else about rebinding.
|
|
10
|
+
*/
|
|
11
|
+
import { ReticleCommand } from '@reticlehq/core';
|
|
12
|
+
/**
|
|
13
|
+
* Commands that may be re-issued against the connection that replaced this session.
|
|
14
|
+
*
|
|
15
|
+
* Reads only, and the boundary is the point. Re-reading a page costs nothing and answers the same
|
|
16
|
+
* question the caller asked. An act cannot be replayed: it may already have been dispatched in the
|
|
17
|
+
* page that went away, and performing it a second time behind the caller's back is a double submit
|
|
18
|
+
* nobody asked for. A replaced act still errors, and the caller's own retry — with the same id,
|
|
19
|
+
* which is still the right one — is the safe path.
|
|
20
|
+
*/
|
|
21
|
+
export const REBINDABLE_COMMANDS = new Set([
|
|
22
|
+
ReticleCommand.SNAPSHOT,
|
|
23
|
+
ReticleCommand.QUERY,
|
|
24
|
+
ReticleCommand.MATCH,
|
|
25
|
+
ReticleCommand.INSPECT,
|
|
26
|
+
ReticleCommand.STATE_READ,
|
|
27
|
+
ReticleCommand.STORAGE_READ,
|
|
28
|
+
ReticleCommand.CAPABILITIES,
|
|
29
|
+
ReticleCommand.ANIMATIONS,
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* How far to walk a chain of replacements before giving up.
|
|
33
|
+
*
|
|
34
|
+
* Generous for the real case (a page reloading a handful of times while a call is in flight) and
|
|
35
|
+
* small enough that a cycle costs nothing. The number is a backstop, not a policy: a chain longer
|
|
36
|
+
* than this means something is re-dialling in a loop, and that is a different problem.
|
|
37
|
+
*/
|
|
38
|
+
export const MAX_SUCCESSOR_HOPS = 32;
|
|
@@ -38,6 +38,22 @@ export interface SessionHealth {
|
|
|
38
38
|
* on `reticle_sessions` or `reticle_lease` is not on the surface it reads every call.
|
|
39
39
|
*/
|
|
40
40
|
versionSkew?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The session's event ledger, once it has grown enough to be worth a reader's attention.
|
|
43
|
+
*
|
|
44
|
+
* Present only past `LEDGER_NOTICE_FRACTION` of the cap, so a healthy session costs nothing --
|
|
45
|
+
* the same rule `pendingNavigationMs` follows. A ledger below that is doing its job quietly.
|
|
46
|
+
*
|
|
47
|
+
* It is here because nothing reported it. A repeating uncaught error wrote one session's ledger
|
|
48
|
+
* until the disk was full; neither the daemon nor `doctor` nor any
|
|
49
|
+
* health counter said a word, and the user found out when the disk was full (#986). A cap bounds
|
|
50
|
+
* the damage but does not make the writing visible, and this block is the surface an agent
|
|
51
|
+
* already reads on every act and assert.
|
|
52
|
+
*/
|
|
53
|
+
ledger?: {
|
|
54
|
+
bytes: number;
|
|
55
|
+
capBytes: number;
|
|
56
|
+
};
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
43
59
|
* How long a request may be in flight before it is worth reporting on session health.
|
|
@@ -84,6 +100,18 @@ interface BufferEnvelope {
|
|
|
84
100
|
* so a clean/empty result there is trustworthy and costs zero tokens.
|
|
85
101
|
*/
|
|
86
102
|
export declare function bufferEnvelope(session: HealthSubject): BufferEnvelope;
|
|
103
|
+
/**
|
|
104
|
+
* The session registered under `session.id` NOW, for describing a tab after a call that may have
|
|
105
|
+
* replaced its document. A reload or full navigation registers a new Session under the same id, and
|
|
106
|
+
* the object a handler resolved first is the page that unloaded; it reported itself hidden on the way
|
|
107
|
+
* out, so its health said "throttled" about a tab that was fine. Optional-called because test doubles
|
|
108
|
+
* are partial registries.
|
|
109
|
+
*/
|
|
110
|
+
export declare function currentOf<S extends {
|
|
111
|
+
id: string;
|
|
112
|
+
}>(registry: {
|
|
113
|
+
get?: (id: string) => S | undefined;
|
|
114
|
+
}, session: S): S;
|
|
87
115
|
/** The `session` (and optional throttled `warning`) block spliced onto act/assert results. */
|
|
88
116
|
interface HealthEnvelope {
|
|
89
117
|
session?: SessionHealth;
|
|
@@ -142,4 +170,37 @@ interface HealthReport {
|
|
|
142
170
|
* keep its previous value instead of silently flipping a session to "visible" on a partial report.
|
|
143
171
|
*/
|
|
144
172
|
export declare function readHealthEvent(data: Record<string, unknown>): HealthReport;
|
|
173
|
+
/**
|
|
174
|
+
* The ledger block for the health payload, or nothing when it is not worth saying.
|
|
175
|
+
*
|
|
176
|
+
* Absent below `LEDGER_NOTICE_FRACTION` of the cap, and absent when the size has not been measured
|
|
177
|
+
* yet -- a reader must be able to tell "not measured" from "empty", and a zero reported before the
|
|
178
|
+
* first append would be a claim rather than a reading.
|
|
179
|
+
*/
|
|
180
|
+
export declare function ledgerNotice(ledger: {
|
|
181
|
+
bytes: number;
|
|
182
|
+
capBytes: number;
|
|
183
|
+
} | undefined): {
|
|
184
|
+
ledger?: {
|
|
185
|
+
bytes: number;
|
|
186
|
+
capBytes: number;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Every health field that is present only when it means something.
|
|
191
|
+
*
|
|
192
|
+
* One place answers "which optional facts belong on the health block", so a session does not have
|
|
193
|
+
* to know the rule for each. A healthy session gets an empty object back and its health block
|
|
194
|
+
* carries only the fields that are always true.
|
|
195
|
+
*/
|
|
196
|
+
export declare function healthNotices(events: readonly ReticleEvent[], elapsed: number, ledger: {
|
|
197
|
+
bytes: number;
|
|
198
|
+
capBytes: number;
|
|
199
|
+
} | undefined): {
|
|
200
|
+
pendingNavigationMs?: number;
|
|
201
|
+
ledger?: {
|
|
202
|
+
bytes: number;
|
|
203
|
+
capBytes: number;
|
|
204
|
+
};
|
|
205
|
+
};
|
|
145
206
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BrowserBrand, EventType, BUFFER_EVICTION_WARNING, THROTTLED_STARVED_NOTE, THROTTLED_WARNING, } from '@reticlehq/core';
|
|
1
|
+
import { BrowserBrand, EventType, BUFFER_EVICTION_WARNING, THROTTLED_STARVED_NOTE, SESSION_HEALTH, THROTTLED_WARNING, } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* How long a request may be in flight before it is worth reporting on session health.
|
|
4
4
|
*
|
|
@@ -68,6 +68,16 @@ export function bufferEnvelope(session) {
|
|
|
68
68
|
return {};
|
|
69
69
|
return { buffer: { held: total, dropped, note: BUFFER_EVICTION_WARNING } };
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The session registered under `session.id` NOW, for describing a tab after a call that may have
|
|
73
|
+
* replaced its document. A reload or full navigation registers a new Session under the same id, and
|
|
74
|
+
* the object a handler resolved first is the page that unloaded; it reported itself hidden on the way
|
|
75
|
+
* out, so its health said "throttled" about a tab that was fine. Optional-called because test doubles
|
|
76
|
+
* are partial registries.
|
|
77
|
+
*/
|
|
78
|
+
export function currentOf(registry, session) {
|
|
79
|
+
return registry.get?.(session.id) ?? session;
|
|
80
|
+
}
|
|
71
81
|
/**
|
|
72
82
|
* Build the health envelope for a tool result. When the session is **nominal** (focused, not
|
|
73
83
|
* throttled, no escape-hatch recommendation) the block is OMITTED entirely — a healthy session
|
|
@@ -152,3 +162,32 @@ export function readHealthEvent(data) {
|
|
|
152
162
|
brand: Object.values(BrowserBrand).find((known) => known === data['brand']),
|
|
153
163
|
};
|
|
154
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* The ledger block for the health payload, or nothing when it is not worth saying.
|
|
167
|
+
*
|
|
168
|
+
* Absent below `LEDGER_NOTICE_FRACTION` of the cap, and absent when the size has not been measured
|
|
169
|
+
* yet -- a reader must be able to tell "not measured" from "empty", and a zero reported before the
|
|
170
|
+
* first append would be a claim rather than a reading.
|
|
171
|
+
*/
|
|
172
|
+
export function ledgerNotice(ledger) {
|
|
173
|
+
if (ledger === undefined || 0 >= ledger.capBytes)
|
|
174
|
+
return {};
|
|
175
|
+
const fraction = ledger.bytes / ledger.capBytes;
|
|
176
|
+
return fraction < SESSION_HEALTH.LEDGER_NOTICE_FRACTION ? {} : { ledger };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Every health field that is present only when it means something.
|
|
180
|
+
*
|
|
181
|
+
* One place answers "which optional facts belong on the health block", so a session does not have
|
|
182
|
+
* to know the rule for each. A healthy session gets an empty object back and its health block
|
|
183
|
+
* carries only the fields that are always true.
|
|
184
|
+
*/
|
|
185
|
+
export function healthNotices(events, elapsed, ledger) {
|
|
186
|
+
// From event t=0, not a cursor: a wedge that began before the current action is exactly the case
|
|
187
|
+
// a per-window reading cannot see, and is the one both reporters hit.
|
|
188
|
+
const stuck = pendingNavigationMs(events, elapsed);
|
|
189
|
+
return {
|
|
190
|
+
...(stuck === undefined ? {} : { pendingNavigationMs: stuck }),
|
|
191
|
+
...ledgerNotice(ledger),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -45,6 +45,12 @@ export interface SessionInfo {
|
|
|
45
45
|
* A tab that dropped for four seconds and came back is indistinguishable from one that never left
|
|
46
46
|
* (#117), and that difference decides whether a verdict over the window can be trusted. Attached
|
|
47
47
|
* by `SessionManager.list()`, which is the layer that sees every connect and disconnect.
|
|
48
|
+
*
|
|
49
|
+
* Scoped to the SDK-to-daemon LINK, and the names do not say so on their own. A zombie tab whose
|
|
50
|
+
* dev server had died reported `outages: 0` and `connectedSinceMs: 11min`, and both were correct:
|
|
51
|
+
* the page's socket was still attached to the daemon. Read as app health, they said the app was
|
|
52
|
+
* up while nothing was listening on the port (#938). The describe() on the `reticle_sessions`
|
|
53
|
+
* output schema carries that caveat, because that is where an agent reads it.
|
|
48
54
|
*/
|
|
49
55
|
attachment?: {
|
|
50
56
|
connectedSinceMs: number;
|
|
@@ -22,6 +22,15 @@ export declare class SessionManager {
|
|
|
22
22
|
add(session: Session): Session | undefined;
|
|
23
23
|
remove(session: Session): boolean;
|
|
24
24
|
get(sessionId: string): Session | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Did this id belong to a session that has since disconnected?
|
|
27
|
+
*
|
|
28
|
+
* `resolve` answers a departed id with its live SUCCESSOR, which is what keeps a reload working
|
|
29
|
+
* for everything that observes. A caller that is about to do something DESTRUCTIVE needs the
|
|
30
|
+
* other answer: whether the thing it named is still there. Ending a session it did not name is
|
|
31
|
+
* not a recoverable mistake (#983).
|
|
32
|
+
*/
|
|
33
|
+
departed(sessionId: string): SessionIdentity | undefined;
|
|
25
34
|
list(): SessionInfo[];
|
|
26
35
|
/** Continuity for one session — "has this been attached the whole time I am reasoning about?" */
|
|
27
36
|
attachmentOf(sessionId: string): ReturnType<AttachmentHistory['of']>;
|
|
@@ -30,6 +39,10 @@ export declare class SessionManager {
|
|
|
30
39
|
count(): number;
|
|
31
40
|
/** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
|
|
32
41
|
setNoSessionHint(hint: (() => string | undefined) | undefined): void;
|
|
42
|
+
/** Wire the short rendering. Registered from the same `explainNoSession` call as the hint. */
|
|
43
|
+
setNoSessionLead(lead: (() => string | undefined) | undefined): void;
|
|
44
|
+
/** The lead and the next action, for a surface a person reads. */
|
|
45
|
+
noSessionLead(): string | undefined;
|
|
33
46
|
/**
|
|
34
47
|
* The same diagnosis, for a caller that is ASKING about sessions rather than failing to get one.
|
|
35
48
|
*
|
|
@@ -3,7 +3,8 @@ 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
|
+
import { SESSION_DISCONNECTED_REASON } from './facts/session-replaced.js';
|
|
7
8
|
/** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
|
|
8
9
|
export function originOf(url) {
|
|
9
10
|
if (url === undefined)
|
|
@@ -86,6 +87,8 @@ export class SessionManager {
|
|
|
86
87
|
* id forgets its tombstone — the live session is the answer then.
|
|
87
88
|
*/
|
|
88
89
|
#tombstones = new Map();
|
|
90
|
+
/** The tabs already open when each tombstoned session departed: siblings, never its successor. */
|
|
91
|
+
#siblingsAtDeparture = new Map();
|
|
89
92
|
/**
|
|
90
93
|
* The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
|
|
91
94
|
* without passing its own scope, this is applied — so auto-selection is project-scoped by default
|
|
@@ -127,6 +130,7 @@ export class SessionManager {
|
|
|
127
130
|
declareDrivenRedactionKeys(session.id, session.redactKeys);
|
|
128
131
|
this.#attachment.attached(session.id);
|
|
129
132
|
this.#tombstones.delete(session.id);
|
|
133
|
+
this.#siblingsAtDeparture.delete(session.id);
|
|
130
134
|
return previous;
|
|
131
135
|
}
|
|
132
136
|
/**
|
|
@@ -143,7 +147,7 @@ export class SessionManager {
|
|
|
143
147
|
remove(session) {
|
|
144
148
|
if (this.#sessions.get(session.id) !== session)
|
|
145
149
|
return false;
|
|
146
|
-
session.rejectAll(
|
|
150
|
+
session.rejectAll(SESSION_DISCONNECTED_REASON);
|
|
147
151
|
forgetDrivenRedactionKeys(session.id);
|
|
148
152
|
// Recorded, not forgotten: a session that comes back needs its gap measured, and a listing after
|
|
149
153
|
// the reconnect is exactly where that matters.
|
|
@@ -157,6 +161,9 @@ export class SessionManager {
|
|
|
157
161
|
return;
|
|
158
162
|
if (this.#tombstones.has(session.id))
|
|
159
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)));
|
|
160
167
|
this.#tombstones.set(session.id, {
|
|
161
168
|
id: session.id,
|
|
162
169
|
url: session.url,
|
|
@@ -167,13 +174,17 @@ export class SessionManager {
|
|
|
167
174
|
if (oldest === undefined)
|
|
168
175
|
break;
|
|
169
176
|
this.#tombstones.delete(oldest);
|
|
177
|
+
this.#siblingsAtDeparture.delete(oldest);
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
#successorOf(sessionId) {
|
|
173
181
|
const departed = this.#tombstones.get(sessionId);
|
|
174
182
|
if (departed === undefined)
|
|
175
183
|
return undefined;
|
|
176
|
-
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) => ({
|
|
177
188
|
id: s.id,
|
|
178
189
|
url: s.url,
|
|
179
190
|
...(s.projectId === undefined ? {} : { projectId: s.projectId }),
|
|
@@ -185,6 +196,17 @@ export class SessionManager {
|
|
|
185
196
|
get(sessionId) {
|
|
186
197
|
return this.#sessions.get(sessionId);
|
|
187
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
|
+
}
|
|
188
210
|
list() {
|
|
189
211
|
return [...this.#sessions.values()].map((s) => {
|
|
190
212
|
const info = s.info();
|
|
@@ -246,10 +268,27 @@ export class SessionManager {
|
|
|
246
268
|
* different branches. See no-session-watch.ts.
|
|
247
269
|
*/
|
|
248
270
|
#noSessionReason;
|
|
271
|
+
/**
|
|
272
|
+
* The same diagnosis with the differential left off: the lead and the next action, nothing else.
|
|
273
|
+
*
|
|
274
|
+
* Two renderings because there are two readers, and only one of them can spend the words. An
|
|
275
|
+
* agent about to burn a drive on a guess wants the ranked causes, the port-scan caveat and the
|
|
276
|
+
* lease offer. The person watching `reticle init` fail wants to know what is wrong and what to
|
|
277
|
+
* type, and was instead handed roughly five hundred words in one paragraph.
|
|
278
|
+
*/
|
|
279
|
+
#noSessionLead;
|
|
249
280
|
/** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
|
|
250
281
|
setNoSessionHint(hint) {
|
|
251
282
|
this.#noSessionHint = hint;
|
|
252
283
|
}
|
|
284
|
+
/** Wire the short rendering. Registered from the same `explainNoSession` call as the hint. */
|
|
285
|
+
setNoSessionLead(lead) {
|
|
286
|
+
this.#noSessionLead = lead;
|
|
287
|
+
}
|
|
288
|
+
/** The lead and the next action, for a surface a person reads. */
|
|
289
|
+
noSessionLead() {
|
|
290
|
+
return this.#noSessionLead?.();
|
|
291
|
+
}
|
|
253
292
|
/**
|
|
254
293
|
* The same diagnosis, for a caller that is ASKING about sessions rather than failing to get one.
|
|
255
294
|
*
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebSocket } from 'ws';
|
|
2
|
-
import type
|
|
2
|
+
import { type ChannelId, type ImpactSnapshot, type ProjectId } from '@reticlehq/core';
|
|
3
3
|
import type { HandshakeFacts } from './facts/handshake-facts.js';
|
|
4
4
|
import { LastAct } from './last-act.js';
|
|
5
5
|
import { GapLedger } from '@reticlehq/engine/evidence/gap-ledger.js';
|
|
@@ -7,7 +7,7 @@ import { CaptureLedger } from '../../surface/tools/feature-capture.js';
|
|
|
7
7
|
import { type PageRuntime } from './timing/command-timeout.js';
|
|
8
8
|
import { type SessionHealth } from './session-health.js';
|
|
9
9
|
export type { SessionHealth };
|
|
10
|
-
import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type JournalAction, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
|
|
10
|
+
import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type JournalAction, type JournalWriteLoss, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
|
|
11
11
|
import type { JournalReader, JournalRecorder } from '../../memory/journal/journal-recorder.js';
|
|
12
12
|
import { type EventQueryOptions } from '../../memory/journal/journal-query.js';
|
|
13
13
|
import { type AmbientCounts } from '@reticlehq/engine/window/ambient.js';
|
|
@@ -25,8 +25,14 @@ type Clock = () => number;
|
|
|
25
25
|
export declare class Session implements HandshakeFacts {
|
|
26
26
|
#private;
|
|
27
27
|
readonly id: string;
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Stable build-stamped project identity; undefined for SDKs old enough to omit it.
|
|
30
|
+
*
|
|
31
|
+
* MINTED HERE. This is one of the two places a projectId enters the daemon with its provenance
|
|
32
|
+
* intact: the field is `projectId` on a zod-parsed HELLO, so the page said what it is. Every
|
|
33
|
+
* downstream reader takes the brand from this property rather than re-blessing a string.
|
|
34
|
+
*/
|
|
35
|
+
readonly projectId: ProjectId | undefined;
|
|
30
36
|
/**
|
|
31
37
|
* The `.reticle` directory this session's evidence belongs in — impact counters AND capsules.
|
|
32
38
|
*
|
|
@@ -38,6 +44,11 @@ export declare class Session implements HandshakeFacts {
|
|
|
38
44
|
* root, which is what every counter did unconditionally before this existed.
|
|
39
45
|
*/
|
|
40
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[];
|
|
41
52
|
url: string;
|
|
42
53
|
title: string;
|
|
43
54
|
adapters: string[];
|
|
@@ -118,12 +129,13 @@ export declare class Session implements HandshakeFacts {
|
|
|
118
129
|
*/
|
|
119
130
|
setJournal(recorder: JournalRecorder, reader?: JournalReader): void;
|
|
120
131
|
/**
|
|
121
|
-
* Journal-backed event query
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* evidence — the substrate's whole point. The sync `eventsSince`/`window` stay for the hot path.
|
|
132
|
+
* Journal-backed event query — the rule itself lives in `readQueryEvents`, beside the merge and
|
|
133
|
+
* filter it is made of. This is how "what did action N cause" is answered after the buffer has
|
|
134
|
+
* dropped the evidence. The sync `eventsSince`/`window` stay for the hot path.
|
|
125
135
|
*/
|
|
126
136
|
queryEvents(options: EventQueryOptions): Promise<ReticleEvent[]>;
|
|
137
|
+
/** What the durable ledger could not WRITE — see `readJournalWriteLoss` for the rule and why. */
|
|
138
|
+
journalWriteLoss(): Promise<JournalWriteLoss | undefined>;
|
|
127
139
|
/**
|
|
128
140
|
* Open an action-attribution window: events observed until finishAction attribute to the returned
|
|
129
141
|
* action id. Ids are minted independently of command correlation ids so the journal is self-contained.
|
|
@@ -200,9 +212,13 @@ export declare class Session implements HandshakeFacts {
|
|
|
200
212
|
dropped: number;
|
|
201
213
|
};
|
|
202
214
|
/**
|
|
203
|
-
* Did the
|
|
215
|
+
* Did the EVENT STORE lose scarce evidence from a window opened at `cursor`? The input to whether a
|
|
204
216
|
* verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
|
|
205
|
-
* moves for the age and churn evictions that every live page produces continuously.
|
|
217
|
+
* moves for the age and churn evictions that every live page produces continuously. The journal is
|
|
218
|
+
* the other half of that store, so a durable read that could not reach back to `cursor` lost
|
|
219
|
+
* exactly what an eviction would have. Both boundaries are INCLUSIVE (`t` is a millisecond many
|
|
220
|
+
* records share) and an absent `lostThroughT` impeaches every window rather than none — the
|
|
221
|
+
* conservative direction, both times.
|
|
206
222
|
*/
|
|
207
223
|
lostSince(cursor: number): boolean;
|
|
208
224
|
onEvent(listener: (event: ReticleEvent) => void): () => void;
|