@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
|
@@ -1,58 +1,33 @@
|
|
|
1
|
+
import { asProjectId } from '@reticlehq/core';
|
|
1
2
|
import { refusedResult } from './page-commands/undeclared-command.js';
|
|
2
3
|
import { recordImpact } from '../../memory/impact/impact-recorder.js';
|
|
3
4
|
import { LastAct } from './last-act.js';
|
|
4
5
|
import { GapLedger } from '@reticlehq/engine/evidence/gap-ledger.js';
|
|
5
6
|
import { CaptureLedger } from '../../surface/tools/feature-capture.js';
|
|
6
7
|
import { commandTimeoutMessage } from './timing/command-timeout.js';
|
|
7
|
-
import { readHealthEvent,
|
|
8
|
+
import { readHealthEvent, healthNotices } from './session-health.js';
|
|
8
9
|
import { MIRRORED_COMMANDS, mirroredNarration } from './presence/session-mirror.js';
|
|
9
10
|
/** The HUD does not need 200 frames to watch a counter climb; the last state always lands. */
|
|
10
11
|
const IMPACT_PUSH_DEBOUNCE_MS = 700;
|
|
11
12
|
import { PendingCommands, CommandTimeoutError } from './page-commands/pending-commands.js';
|
|
12
13
|
import { span } from '../../trace.js';
|
|
13
14
|
import { keepCallerContextInDaemon, noteToDaemonLog } from './daemon-answers.js';
|
|
14
|
-
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';
|
|
15
16
|
import { RingBuffer } from '@reticlehq/engine/window/ring-buffer.js';
|
|
16
|
-
import {
|
|
17
|
+
import { readJournalWriteLoss, readQueryEvents, } from '../../memory/journal/journal-query.js';
|
|
17
18
|
import {} from '@reticlehq/engine/window/ambient.js';
|
|
18
19
|
import { ObservedState } from './facts/observed-state.js';
|
|
19
|
-
import { recordBrowserLatency, recordSdkFailure } from '../../telemetry/session-metrics.js';
|
|
20
|
+
import { recordBrowserLatency, recordHudUse, recordSdkFailure, } from '../../telemetry/session-metrics.js';
|
|
20
21
|
import { LiveControl } from './human/live-control.js';
|
|
21
22
|
import { ReviewStore } from './human/review-store.js';
|
|
22
23
|
import { buildSessionRecommendation } from './presence/session-recommendation.js';
|
|
23
24
|
import { buildPresenterArgs } from './human/presenter-args.js';
|
|
24
25
|
import { buildSessionLease } from './presence/session-lease.js';
|
|
25
26
|
import { buildSessionInfo } from './session-info.js';
|
|
27
|
+
import { MAX_SUCCESSOR_HOPS, REBINDABLE_COMMANDS } from './rebind.js';
|
|
26
28
|
const DEFAULT_COMMAND_TIMEOUT_MS = 8000;
|
|
27
29
|
/** Prefix on correlated command ids (c1, c2, …) — distinguishes them from mark ids. */
|
|
28
30
|
const COMMAND_ID_PREFIX = 'c';
|
|
29
|
-
/**
|
|
30
|
-
* Commands that may be re-issued against the connection that replaced this session.
|
|
31
|
-
*
|
|
32
|
-
* Reads only, and the boundary is the point. Re-reading a page costs nothing and answers the same
|
|
33
|
-
* question the caller asked. An act cannot be replayed: it may already have been dispatched in the
|
|
34
|
-
* page that went away, and performing it a second time behind the caller's back is a double submit
|
|
35
|
-
* nobody asked for. A replaced act still errors, and the caller's own retry — with the same id,
|
|
36
|
-
* which is still the right one — is the safe path.
|
|
37
|
-
*/
|
|
38
|
-
/**
|
|
39
|
-
* How far to walk a chain of replacements before giving up.
|
|
40
|
-
*
|
|
41
|
-
* Generous for the real case (a page reloading a handful of times while a call is in flight) and
|
|
42
|
-
* small enough that a cycle costs nothing. The number is a backstop, not a policy: a chain longer
|
|
43
|
-
* than this means something is re-dialling in a loop, and that is a different problem.
|
|
44
|
-
*/
|
|
45
|
-
const MAX_SUCCESSOR_HOPS = 32;
|
|
46
|
-
const REBINDABLE_COMMANDS = new Set([
|
|
47
|
-
ReticleCommand.SNAPSHOT,
|
|
48
|
-
ReticleCommand.QUERY,
|
|
49
|
-
ReticleCommand.MATCH,
|
|
50
|
-
ReticleCommand.INSPECT,
|
|
51
|
-
ReticleCommand.STATE_READ,
|
|
52
|
-
ReticleCommand.STORAGE_READ,
|
|
53
|
-
ReticleCommand.CAPABILITIES,
|
|
54
|
-
ReticleCommand.ANIMATIONS,
|
|
55
|
-
]);
|
|
56
31
|
/** Prefix on minted action ids (a1, a2, …) — the journal's action identity, independent of commands. */
|
|
57
32
|
const ACTION_ID_PREFIX = 'a';
|
|
58
33
|
/**
|
|
@@ -69,7 +44,13 @@ const WS_OPEN = 1;
|
|
|
69
44
|
*/
|
|
70
45
|
export class Session {
|
|
71
46
|
id;
|
|
72
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Stable build-stamped project identity; undefined for SDKs old enough to omit it.
|
|
49
|
+
*
|
|
50
|
+
* MINTED HERE. This is one of the two places a projectId enters the daemon with its provenance
|
|
51
|
+
* intact: the field is `projectId` on a zod-parsed HELLO, so the page said what it is. Every
|
|
52
|
+
* downstream reader takes the brand from this property rather than re-blessing a string.
|
|
53
|
+
*/
|
|
73
54
|
projectId;
|
|
74
55
|
/**
|
|
75
56
|
* The `.reticle` directory this session's evidence belongs in — impact counters AND capsules.
|
|
@@ -82,6 +63,11 @@ export class Session {
|
|
|
82
63
|
* root, which is what every counter did unconditionally before this existed.
|
|
83
64
|
*/
|
|
84
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 = [];
|
|
85
71
|
url;
|
|
86
72
|
title;
|
|
87
73
|
adapters;
|
|
@@ -145,7 +131,7 @@ export class Session {
|
|
|
145
131
|
#editEpoch;
|
|
146
132
|
constructor(hello, socket, clock) {
|
|
147
133
|
this.id = hello.sessionId;
|
|
148
|
-
this.projectId = hello.projectId;
|
|
134
|
+
this.projectId = hello.projectId === undefined ? undefined : asProjectId(hello.projectId);
|
|
149
135
|
this.url = hello.url;
|
|
150
136
|
this.title = hello.title;
|
|
151
137
|
this.adapters = hello.adapters;
|
|
@@ -207,17 +193,16 @@ export class Session {
|
|
|
207
193
|
}
|
|
208
194
|
/** The attachable health block — single source of truth for the tools. */
|
|
209
195
|
health() {
|
|
210
|
-
// From event t=0, not a cursor: a wedge that began before the current action is exactly the
|
|
211
|
-
// case a per-window reading cannot see, and is the one both reporters hit.
|
|
212
|
-
const stuck = pendingNavigationMs(this.eventsSince(0), this.elapsed());
|
|
213
196
|
const base = {
|
|
214
197
|
lastSeenMs: this.lastSeenMs(),
|
|
215
198
|
throttled: this.throttled(),
|
|
216
199
|
focused: this.#focused,
|
|
217
|
-
...(stuck === undefined ? {} : { pendingNavigationMs: stuck }),
|
|
218
200
|
// Carried onto every act/assert result, not just reticle_sessions: skew drops actions
|
|
219
201
|
// SILENTLY, and the fields it contradicts are on the act verdict.
|
|
220
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?.()),
|
|
221
206
|
};
|
|
222
207
|
// attach the escape-hatch hint only when un-scriptable (keeps field absent otherwise).
|
|
223
208
|
const recommendation = buildSessionRecommendation({
|
|
@@ -280,6 +265,14 @@ export class Session {
|
|
|
280
265
|
keepCallerContext = keepCallerContextInDaemon;
|
|
281
266
|
/** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
|
|
282
267
|
pushEvent(event, byteSize) {
|
|
268
|
+
// Telemetry about the HUD, not evidence about the app: taken off here, before the buffer, so no
|
|
269
|
+
// verdict, settle window or agent read can ever see a person's clicks on Reticle's own panel.
|
|
270
|
+
if (event.type === EventType.HUD_USED) {
|
|
271
|
+
const use = HudUseDataSchema.safeParse(event.data);
|
|
272
|
+
if (use.success)
|
|
273
|
+
recordHudUse(use.data);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
283
276
|
if (event.documentId !== undefined)
|
|
284
277
|
this.#documentId = event.documentId;
|
|
285
278
|
if (event.editEpoch !== undefined)
|
|
@@ -408,17 +401,16 @@ export class Session {
|
|
|
408
401
|
this.#journalReader = reader;
|
|
409
402
|
}
|
|
410
403
|
/**
|
|
411
|
-
* Journal-backed event query
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* evidence — the substrate's whole point. The sync `eventsSince`/`window` stay for the hot path.
|
|
404
|
+
* Journal-backed event query — the rule itself lives in `readQueryEvents`, beside the merge and
|
|
405
|
+
* filter it is made of. This is how "what did action N cause" is answered after the buffer has
|
|
406
|
+
* dropped the evidence. The sync `eventsSince`/`window` stay for the hot path.
|
|
415
407
|
*/
|
|
416
408
|
async queryEvents(options) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return
|
|
409
|
+
return readQueryEvents(this.#journalReader, this.#buffer, options);
|
|
410
|
+
}
|
|
411
|
+
/** What the durable ledger could not WRITE — see `readJournalWriteLoss` for the rule and why. */
|
|
412
|
+
async journalWriteLoss() {
|
|
413
|
+
return readJournalWriteLoss(this.#journalReader, this.#buffer);
|
|
422
414
|
}
|
|
423
415
|
/**
|
|
424
416
|
* Open an action-attribution window: events observed until finishAction attribute to the returned
|
|
@@ -541,12 +533,19 @@ export class Session {
|
|
|
541
533
|
return this.#buffer.bufferHealth();
|
|
542
534
|
}
|
|
543
535
|
/**
|
|
544
|
-
* Did the
|
|
536
|
+
* Did the EVENT STORE lose scarce evidence from a window opened at `cursor`? The input to whether a
|
|
545
537
|
* verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
|
|
546
|
-
* moves for the age and churn evictions that every live page produces continuously.
|
|
538
|
+
* moves for the age and churn evictions that every live page produces continuously. The journal is
|
|
539
|
+
* the other half of that store, so a durable read that could not reach back to `cursor` lost
|
|
540
|
+
* exactly what an eviction would have. Both boundaries are INCLUSIVE (`t` is a millisecond many
|
|
541
|
+
* records share) and an absent `lostThroughT` impeaches every window rather than none — the
|
|
542
|
+
* conservative direction, both times.
|
|
547
543
|
*/
|
|
548
544
|
lostSince(cursor) {
|
|
549
|
-
|
|
545
|
+
if (this.#buffer.lostSince(cursor))
|
|
546
|
+
return true;
|
|
547
|
+
const lost = this.#journalReader?.readLoss?.();
|
|
548
|
+
return lost !== undefined && (lost.lostThroughT === undefined || lost.lostThroughT >= cursor);
|
|
550
549
|
}
|
|
551
550
|
onEvent(listener) {
|
|
552
551
|
this.#listeners.add(listener);
|
package/dist/status-payload.d.ts
CHANGED
|
@@ -19,12 +19,23 @@ interface StatusPayload {
|
|
|
19
19
|
* beside a live session.
|
|
20
20
|
*/
|
|
21
21
|
why?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The same diagnosis with the differential left off: the lead and the next action.
|
|
24
|
+
*
|
|
25
|
+
* Two fields because there are two readers. `why` is for an agent about to spend a drive on a
|
|
26
|
+
* guess, and carries the ranked causes, the port-scan caveat and the lease offer. `whyLead` is
|
|
27
|
+
* for a surface a PERSON reads: `reticle init` prints the daemon's account of a failed connect,
|
|
28
|
+
* and was printing roughly five hundred words of it in one paragraph.
|
|
29
|
+
*/
|
|
30
|
+
whyLead?: string;
|
|
22
31
|
/** Port of the verify HTTP endpoint this daemon serves — present only when started with `--http`. */
|
|
23
32
|
verifyPort?: number;
|
|
24
33
|
}
|
|
25
34
|
export declare function statusPayload(sessionCount: number, sessions: SessionInfo[],
|
|
26
35
|
/** The no-session diagnosis, injected so this stays pure and the port probe stays off this path. */
|
|
27
|
-
why?: string, verifyPort?: number
|
|
36
|
+
why?: string, verifyPort?: number,
|
|
37
|
+
/** The short rendering of the same diagnosis. Absent on a daemon that has no provider wired. */
|
|
38
|
+
whyLead?: string): StatusPayload;
|
|
28
39
|
/**
|
|
29
40
|
* Why `serve --http` cannot claim success against an already-running daemon — or undefined when
|
|
30
41
|
* that daemon already serves the verify HTTP endpoint on the wanted port.
|
package/dist/status-payload.js
CHANGED
|
@@ -2,7 +2,9 @@ import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
|
2
2
|
import { SERVER_VERSION } from './command/version/identity/server-version.js';
|
|
3
3
|
export function statusPayload(sessionCount, sessions,
|
|
4
4
|
/** The no-session diagnosis, injected so this stays pure and the port probe stays off this path. */
|
|
5
|
-
why, verifyPort
|
|
5
|
+
why, verifyPort,
|
|
6
|
+
/** The short rendering of the same diagnosis. Absent on a daemon that has no provider wired. */
|
|
7
|
+
whyLead) {
|
|
6
8
|
return {
|
|
7
9
|
running: true,
|
|
8
10
|
version: SERVER_VERSION,
|
|
@@ -12,6 +14,7 @@ why, verifyPort) {
|
|
|
12
14
|
// Only when there is nothing to explain away: a diagnosis printed beside a live session would
|
|
13
15
|
// contradict it.
|
|
14
16
|
...(0 === sessionCount && why !== undefined ? { why } : {}),
|
|
17
|
+
...(0 === sessionCount && whyLead !== undefined ? { whyLead } : {}),
|
|
15
18
|
...(verifyPort === undefined ? {} : { verifyPort }),
|
|
16
19
|
};
|
|
17
20
|
}
|
|
@@ -4,7 +4,6 @@ import { MCP_SSE_PATH, MCP_MESSAGE_PATH, MCP_SHUTDOWN_EVENT, STATUS_PATH, DRIVE_
|
|
|
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';
|
|
7
|
-
import { reportMcpConnected } from '../telemetry/mcp-connection.js';
|
|
8
7
|
import { isLoopbackHost, isLocalWebOrigin, isLoopbackPeer, requestToken, tokensMatch, } from '../portal/bridge/token-auth.js';
|
|
9
8
|
import { LOOPBACK_IDLE_MS } from './loopback-agent.js';
|
|
10
9
|
/**
|
|
@@ -177,10 +176,9 @@ export function createSharedServer(options = {}) {
|
|
|
177
176
|
.connect(transport)
|
|
178
177
|
.then(() => {
|
|
179
178
|
log('mcp_client_connected', { sessionId: sid });
|
|
180
|
-
// The
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
reportMcpConnected();
|
|
179
|
+
// The telemetry for this moved to `oninitialized`, where the client has said its name.
|
|
180
|
+
// An SSE stream that opens here and never handshakes is a probe, not a client, and
|
|
181
|
+
// reporting it from this point produced a row that could never name who attached.
|
|
184
182
|
})
|
|
185
183
|
.catch((err) => {
|
|
186
184
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* often does a real user's MCP server go down, and does it come back — had no answer outside the
|
|
7
7
|
* machine it happened on.
|
|
8
8
|
*
|
|
9
|
-
* Capped at
|
|
9
|
+
* Capped at one benign and one fault event PER STAGE per proxy process, and the cap is the design
|
|
10
|
+
* rather than a limitation. One
|
|
10
11
|
* measured afternoon produced 547 proxy reconnects; an event per reconnect would bill for the
|
|
11
12
|
* pathology instead of measuring it, which is the same mistake the per-call `tool` event made before
|
|
12
13
|
* it was removed. What a dashboard needs is the SHARE OF SESSIONS that lose MCP at all (the first
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* often does a real user's MCP server go down, and does it come back — had no answer outside the
|
|
7
7
|
* machine it happened on.
|
|
8
8
|
*
|
|
9
|
-
* Capped at
|
|
9
|
+
* Capped at one benign and one fault event PER STAGE per proxy process, and the cap is the design
|
|
10
|
+
* rather than a limitation. One
|
|
10
11
|
* measured afternoon produced 547 proxy reconnects; an event per reconnect would bill for the
|
|
11
12
|
* pathology instead of measuring it, which is the same mistake the per-call `tool` event made before
|
|
12
13
|
* it was removed. What a dashboard needs is the SHARE OF SESSIONS that lose MCP at all (the first
|
|
@@ -28,6 +29,19 @@ const KNOWN_REASONS = new Set(Object.values(OutageReason));
|
|
|
28
29
|
function outageReason(raw) {
|
|
29
30
|
return KNOWN_REASONS.has(raw) ? raw : OutageReason.OTHER;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The daemon retiring on schedule is not a fault, and it is by far the commonest drop.
|
|
34
|
+
*
|
|
35
|
+
* Kept as the CLASS rather than the reason itself, because the cap is keyed on it: one benign drop
|
|
36
|
+
* and one fault per stage, not one event per reason. Keying on the reason would put the volume the
|
|
37
|
+
* cap exists to prevent straight back — a flapping proxy has several reasons available to it and
|
|
38
|
+
* would bill for each of them — while keying on the stage alone is what let a scheduled shutdown at
|
|
39
|
+
* minute one permanently suppress a real outage at minute forty, biasing the metric toward exactly
|
|
40
|
+
* the rows `daemon_shutdown` was introduced to exclude.
|
|
41
|
+
*/
|
|
42
|
+
function isBenign(reason) {
|
|
43
|
+
return OutageReason.DAEMON_SHUTDOWN === reason;
|
|
44
|
+
}
|
|
31
45
|
const reported = new Set();
|
|
32
46
|
/** Reset between tests; a real process reports each stage at most once. */
|
|
33
47
|
export function resetOutageReporting() {
|
|
@@ -39,13 +53,15 @@ export function resetOutageReporting() {
|
|
|
39
53
|
* fails — the reconnect it is describing.
|
|
40
54
|
*/
|
|
41
55
|
export function reportMcpOutage(stage, facts) {
|
|
42
|
-
|
|
56
|
+
const reason = outageReason(facts.reason);
|
|
57
|
+
const slot = `${stage}:${isBenign(reason) ? 'benign' : 'fault'}`;
|
|
58
|
+
if (reported.has(slot))
|
|
43
59
|
return;
|
|
44
|
-
reported.add(
|
|
60
|
+
reported.add(slot);
|
|
45
61
|
void getTelemetry().emit(TelemetryEventKind.MCP_CONNECTION_LOST, {
|
|
46
62
|
outage: {
|
|
47
63
|
stage,
|
|
48
|
-
reason
|
|
64
|
+
reason,
|
|
49
65
|
attempts: facts.attempts,
|
|
50
66
|
// Always sent, including zero: zero is the finding — a drop no agent could feel. See
|
|
51
67
|
// McpOutageSchema.pendingLost.
|
|
@@ -107,4 +107,13 @@ export declare function startMcpProxy(port: number,
|
|
|
107
107
|
*/
|
|
108
108
|
ensureDaemon?: () => Promise<void>,
|
|
109
109
|
/** Observe the real shutdown boundary in transport tests without terminating their worker. */
|
|
110
|
-
exitProcess?: (code: number) => void
|
|
110
|
+
exitProcess?: (code: number) => void,
|
|
111
|
+
/**
|
|
112
|
+
* Tell the resilience layer this session proved itself usable.
|
|
113
|
+
*
|
|
114
|
+
* The same `endpoint` frame that earns a fresh retry budget below also clears the disconnect-storm
|
|
115
|
+
* counter, and for the same reason: response headers are produced by a daemon that accepts SSE and
|
|
116
|
+
* drops it, and an `endpoint` frame is not. Optional, because a transport test drives this
|
|
117
|
+
* function with no resilience installed.
|
|
118
|
+
*/
|
|
119
|
+
onUsableSession?: () => void): Promise<never>;
|
|
@@ -10,9 +10,9 @@ export { probeDaemon, waitForDaemon } 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';
|
|
@@ -22,7 +22,7 @@ import { flushProxySessionMetrics } from '../../telemetry/proxy-telemetry.js';
|
|
|
22
22
|
* the whole defect this import closes was the proxy answering a DIFFERENT question from every other
|
|
23
23
|
* surface, and a second copy of the probe is how that comes back.
|
|
24
24
|
*/
|
|
25
|
-
import { fetchStatus as fetchDaemonStatus } from '../../command/
|
|
25
|
+
import { fetchStatus as fetchDaemonStatus } from '../../command/daemon/binding/daemon-status-probe.js';
|
|
26
26
|
/**
|
|
27
27
|
* Re-exported so every existing caller (and every spec) keeps importing the proxy's log from the
|
|
28
28
|
* proxy. Where the code lives is a file-size decision; where it is imported from is an API.
|
|
@@ -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 {
|
|
@@ -304,7 +304,16 @@ export function startMcpProxy(port,
|
|
|
304
304
|
*/
|
|
305
305
|
ensureDaemon,
|
|
306
306
|
/** Observe the real shutdown boundary in transport tests without terminating their worker. */
|
|
307
|
-
exitProcess = (code) => process.exit(code)
|
|
307
|
+
exitProcess = (code) => process.exit(code),
|
|
308
|
+
/**
|
|
309
|
+
* Tell the resilience layer this session proved itself usable.
|
|
310
|
+
*
|
|
311
|
+
* The same `endpoint` frame that earns a fresh retry budget below also clears the disconnect-storm
|
|
312
|
+
* counter, and for the same reason: response headers are produced by a daemon that accepts SSE and
|
|
313
|
+
* drops it, and an `endpoint` frame is not. Optional, because a transport test drives this
|
|
314
|
+
* function with no resilience installed.
|
|
315
|
+
*/
|
|
316
|
+
onUsableSession = () => undefined) {
|
|
308
317
|
return new Promise((_resolve, reject) => {
|
|
309
318
|
// A client just started us, which is the only honest evidence that Reticle is registered with
|
|
310
319
|
// one at all: a config entry proves somebody wrote JSON, not that a client ever read it. Paired
|
|
@@ -452,13 +461,11 @@ exitProcess = (code) => process.exit(code)) {
|
|
|
452
461
|
const msg = parseJsonRpc(line);
|
|
453
462
|
if (null === msg || msg.id === undefined || !pending.take(msg.id))
|
|
454
463
|
return;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
});
|
|
461
|
-
}
|
|
464
|
+
// No outage is reported here, deliberately. A POST leg that died while the SSE stream is
|
|
465
|
+
// still up is not the agent losing its tools: `postSocketFailures` on the session summary
|
|
466
|
+
// already counts it, and reporting it as a lost stream both double-counted and — because
|
|
467
|
+
// the outage cap is per process — let a transient POST failure in the first minute suppress
|
|
468
|
+
// the real stream outage in the fortieth.
|
|
462
469
|
proxyLog('reticle_mcp_proxy_post_unanswered', {
|
|
463
470
|
port,
|
|
464
471
|
method: String(msg.method),
|
|
@@ -490,6 +497,10 @@ exitProcess = (code) => process.exit(code)) {
|
|
|
490
497
|
reportMcpOutage(OutageStage.RECOVERED, { reason: lastDropReason, attempts: cost });
|
|
491
498
|
attempts = 0;
|
|
492
499
|
attemptsBeforeDormant = 0;
|
|
500
|
+
// The disconnects behind us were reconnects, not a runaway. Counting them for the life of
|
|
501
|
+
// the process made twenty daemon restarts over a working week indistinguishable from twenty
|
|
502
|
+
// in eight seconds, and the proxy left on the former.
|
|
503
|
+
onUsableSession();
|
|
493
504
|
// The new session's McpServer has never seen the client's initialize — replay it first, then
|
|
494
505
|
// flush whatever the client sent while we were reconnecting.
|
|
495
506
|
for (const line of replay.replayLines())
|
|
@@ -12,7 +12,16 @@ export declare const CALL_TOOL_METHOD = "tools/call";
|
|
|
12
12
|
* Non-session-bound tools (and tools with no outputSchema) are returned unchanged.
|
|
13
13
|
*/
|
|
14
14
|
export declare function withSessionEnvelope(name: string, outputSchema: z.ZodRawShape | undefined): z.ZodRawShape | undefined;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Abbreviations whose internal period is NOT a sentence end.
|
|
17
|
+
*
|
|
18
|
+
* The splitter looked for the first `". "`, which "e.g. " satisfies — so every lean-profile
|
|
19
|
+
* description containing one was cut off mid-abbreviation. `reticle_act`'s ref parameter reached the
|
|
20
|
+
* agent as "Element ref from reticle_snapshot or reticle_query (e.g." and stopped: the example gone,
|
|
21
|
+
* and any contract stated after it gone too. Silent, and it degraded the DEFAULT profile only, which
|
|
22
|
+
* is the one nobody reads the raw strings for.
|
|
23
|
+
*/
|
|
24
|
+
export { firstSentence } from '../tools/first-sentence.js';
|
|
16
25
|
/**
|
|
17
26
|
* Serialize a tool result to the MCP `text` content block. The agent consumes this text — the
|
|
18
27
|
* typed contract travels separately as `structuredContent`, unchanged by encoding — so the text
|
package/dist/surface/mcp/mcp.js
CHANGED
|
@@ -36,6 +36,7 @@ import { log } from '../../log.js';
|
|
|
36
36
|
import { SERVER_VERSION } from '../../command/version/identity/server-version.js';
|
|
37
37
|
import { setMcpClientNameHook } from '../../telemetry/feedback-context.js';
|
|
38
38
|
import { getSessionMetrics } from '../../telemetry/session-metrics.js';
|
|
39
|
+
import { reportMcpConnected } from '../../telemetry/mcp-connection.js';
|
|
39
40
|
import { parsePredicate } from '@reticlehq/engine/question/predicate/predicate-parse.js';
|
|
40
41
|
/**
|
|
41
42
|
* Merge the runtime-spliced envelope (health/lease/age/control) into a session-bound tool's declared
|
|
@@ -70,16 +71,9 @@ function withExample(description, example) {
|
|
|
70
71
|
* and any contract stated after it gone too. Silent, and it degraded the DEFAULT profile only, which
|
|
71
72
|
* is the one nobody reads the raw strings for.
|
|
72
73
|
*/
|
|
73
|
-
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
const base = nl >= 0 ? description.slice(0, nl) : description;
|
|
77
|
-
// Mask abbreviations with an equal-length filler so offsets stay valid in the original string.
|
|
78
|
-
const masked = ABBREVIATIONS.reduce((text, abbr) => text.split(abbr).join('\u0000'.repeat(abbr.length)), base);
|
|
79
|
-
const dot = masked.search(/\.\s/);
|
|
80
|
-
const sentence = dot >= 0 ? base.slice(0, dot + 1) : base;
|
|
81
|
-
return sentence.length > 160 ? `${sentence.slice(0, 159)}…` : sentence;
|
|
82
|
-
}
|
|
74
|
+
// Re-exported because callers (and `mcp.test.ts`) have imported it from here since it lived here.
|
|
75
|
+
export { firstSentence } from '../tools/first-sentence.js';
|
|
76
|
+
import { firstSentence } from '../tools/first-sentence.js';
|
|
83
77
|
/**
|
|
84
78
|
* Parameters carrying the recursive predicate grammar. Advertised compactly in lean profiles.
|
|
85
79
|
*
|
|
@@ -124,8 +118,8 @@ const PREDICATE_KINDS = 'Predicate object: { kind, ...fields }. kind is one of e
|
|
|
124
118
|
* paying for six.
|
|
125
119
|
*/
|
|
126
120
|
const PREDICATE_FIELD_GRAMMAR_HINT = ' Bug-catching options: net.count (exact request count — catches double-submit), ' +
|
|
127
|
-
'net.
|
|
128
|
-
'
|
|
121
|
+
'net.bodyMatches (a shallow JSON match on what the SERVER answered — say the FIELD and its value, ' +
|
|
122
|
+
'because a substring of the body matches key names too), net.requestBodyMatches (the same on what ' +
|
|
129
123
|
'the UI SENT — the verdict for "applying this filter actually puts it in the payload"), ' +
|
|
130
124
|
'console.absent:true (action completed with a CLEAN ' +
|
|
131
125
|
'console), absent:true on element/text (it should be gone). Call reticle_tools for the full field ' +
|
|
@@ -218,21 +212,33 @@ tools = tableForSurface(surface)) {
|
|
|
218
212
|
const shipped = new Set(tableForSurface(surface).map((tool) => tool.name));
|
|
219
213
|
const ours = tools.filter((tool) => shipped.has(tool.name));
|
|
220
214
|
const theirs = tools.filter((tool) => !shipped.has(tool.name));
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
/*
|
|
216
|
+
* `merged` used to drop `reticle_run`, on the reasoning that the hatch "exists to DISPATCH to a
|
|
217
|
+
* tool the surface does not advertise, and this surface advertises everything, so it has no job
|
|
218
|
+
* left".
|
|
219
|
+
*
|
|
220
|
+
* THE PREMISE WAS FALSE, and it had been false for some time. `MERGED_TOOL_NAMES` is
|
|
221
|
+
* `CORE_TOOL_NAMES` minus the merged-away names plus `LOOK`; `EXTENDED_TOOL_NAMES` — screenshot,
|
|
222
|
+
* visual diff, clock, network mock, storage, record, the flow pair, intent, context, capabilities
|
|
223
|
+
* — is not in it. So eleven registered tools were advertised by nothing, callable by nothing, and
|
|
224
|
+
* catalogued by nothing: `reticle_tools { names: ["reticle_screenshot"] }` answered `unknown
|
|
225
|
+
* tool`, on a build where the tool exists and is registered.
|
|
226
|
+
*
|
|
227
|
+
* The extended set's own comments say each demoted tool is "still one `reticle_run` hop from any
|
|
228
|
+
* agent that wants it". That is the trade the demotion was justified by, and dropping the hatch
|
|
229
|
+
* silently cancelled the half of it we owed the caller. One of the casualties was
|
|
230
|
+
* `reticle_visual_diff`, which covers the only bug class the registry marks as one Reticle cannot
|
|
231
|
+
* otherwise see.
|
|
232
|
+
*
|
|
233
|
+
* So the hatch stays, and the catalogue lists everything the hatch can reach. `reticle_tools`
|
|
234
|
+
* keeps its second job either way: every input schema on a trimmed surface is LEAN, and
|
|
235
|
+
* `{ names: [...] }` is the only way to get full parameters — which matters most here, because a
|
|
236
|
+
* merged tool's schema is the union of its members' fields with all of them optional. Recovery
|
|
237
|
+
* messages across this server also say "Call reticle_tools", and a surface without it turns that
|
|
238
|
+
* advice into a dead end (see surface-sizes.test.ts).
|
|
239
|
+
*/
|
|
231
240
|
const advertisedHere = filterTools([...ours], surface);
|
|
232
|
-
const
|
|
233
|
-
? new Set([...advertisedHere, ...theirs].map((tool) => tool.name))
|
|
234
|
-
: undefined;
|
|
235
|
-
const meta = buildDynamicTools([...tools], origin, callable).filter((tool) => surface !== TOOL_SURFACE.MERGED || tool.name !== ReticleTool.RUN);
|
|
241
|
+
const meta = buildDynamicTools([...tools], origin, undefined);
|
|
236
242
|
return [...advertisedHere, ...theirs, ...meta];
|
|
237
243
|
}
|
|
238
244
|
/**
|
|
@@ -512,8 +518,14 @@ tools = tableForSurface(profile)) {
|
|
|
512
518
|
*/
|
|
513
519
|
server.server.oninitialized = () => {
|
|
514
520
|
const info = server.server.getClientVersion();
|
|
515
|
-
if (info?.name
|
|
516
|
-
|
|
521
|
+
if (info?.name === undefined)
|
|
522
|
+
return;
|
|
523
|
+
getSessionMetrics().recordClient(info.name, info.version);
|
|
524
|
+
// The one signal that separates "Reticle is running" from "somebody is USING it", reported from
|
|
525
|
+
// the first moment it can carry WHO. It used to fire from the SSE `connect()` resolution, which
|
|
526
|
+
// happens before the handshake — so every connect row was anonymous, and the field meant to say
|
|
527
|
+
// which agent client converts best was empty on every event ever sent.
|
|
528
|
+
reportMcpConnected(info.name);
|
|
517
529
|
};
|
|
518
530
|
// Which agent is on the other end, for a feedback report. Registered as a lazy hook rather than
|
|
519
531
|
// read here: the handshake has not happened yet at construction time, and a report filed twenty
|
|
@@ -540,7 +552,25 @@ tools = tableForSurface(profile)) {
|
|
|
540
552
|
const advertised = advertisedTools(profile, tools);
|
|
541
553
|
// The names this surface actually hands the agent. Every piece of advice leaving this server is
|
|
542
554
|
// rewritten against it, so a static string cannot route a reader to a tool they were not given.
|
|
543
|
-
|
|
555
|
+
/**
|
|
556
|
+
* What a name in advice can actually REACH, which is not the same as what is advertised.
|
|
557
|
+
*
|
|
558
|
+
* `liveCallText` rewrites mentions of tools this surface cannot reach — it exists because advice
|
|
559
|
+
* naming `reticle_flow_save` on a surface without it sent agents at a tool that answered "not
|
|
560
|
+
* reachable on this tool surface". Gated on the ADVERTISED set, it kept firing once the dispatch
|
|
561
|
+
* hatch came back: `reticle_flow_save` and `reticle_record` are reachable through
|
|
562
|
+
* `reticle_run` but not advertised, so every mention of them — INCLUDING their own names and the
|
|
563
|
+
* parameter docs that name the recording to save — was rewritten to
|
|
564
|
+
* `reticle_verify { action: "explore", persona }`. Two tools with the same wrong name, and a
|
|
565
|
+
* `flowName` description telling the reader to pass the name that a tool which is not the one
|
|
566
|
+
* they called was called with.
|
|
567
|
+
*
|
|
568
|
+
* With a hatch, every registered tool is reachable and no rewriting is owed. Without one,
|
|
569
|
+
* advertised IS reachable, which is the case this was written for.
|
|
570
|
+
*/
|
|
571
|
+
const advertisedNames = advertised.some((tool) => ReticleTool.RUN === tool.name)
|
|
572
|
+
? new Set(tools.map((tool) => tool.name))
|
|
573
|
+
: new Set(advertised.map((tool) => tool.name));
|
|
544
574
|
installFriendlyArgErrors(server, new Map(advertised
|
|
545
575
|
.filter((tool) => tool.example !== undefined)
|
|
546
576
|
.map((tool) => [tool.name, JSON.stringify(tool.example)])),
|
|
@@ -5,5 +5,18 @@ export declare function daemonPollDelayMs(attempt: number): number;
|
|
|
5
5
|
* inside the daemon just to check reachability.
|
|
6
6
|
*/
|
|
7
7
|
export declare function probeDaemon(port: number): Promise<boolean>;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Poll until a Reticle daemon is SERVING on `port`, or the deadline passes.
|
|
10
|
+
*
|
|
11
|
+
* "Accepts a TCP connection" is not the question. A daemon wedged mid-start accepts and never
|
|
12
|
+
* serves, and so does a stranger holding the port — `port-presence.ts` calls both FOREIGN for
|
|
13
|
+
* exactly this reason, and every other decision point in the codebase was migrated onto it. This
|
|
14
|
+
* one was not, so the wake path spawned a daemon, waited on a bare connect, and dialled a corpse.
|
|
15
|
+
*
|
|
16
|
+
* Probes are injected (defaulting to the real pair) so the rule is testable without a socket, which
|
|
17
|
+
* is the same shape `probePresence`'s own callers use.
|
|
18
|
+
*/
|
|
19
|
+
export declare function waitForDaemon(port: number, probes?: {
|
|
20
|
+
tcpOpen: (port: number) => Promise<boolean>;
|
|
21
|
+
status: (port: number) => Promise<unknown>;
|
|
22
|
+
}): Promise<void>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as net from 'node:net';
|
|
2
2
|
import { LOOPBACK_HOST } from '@reticlehq/core';
|
|
3
|
+
import { describePresence, presenceIsUsable, probePresence, PortPresence, } from '../../../command/daemon/binding/port-presence.js';
|
|
4
|
+
import { fetchStatus } from '../../../command/daemon/binding/daemon-status-probe.js';
|
|
3
5
|
const DEFAULT_DAEMON_READY_TIMEOUT_MS = 10_000;
|
|
4
6
|
/**
|
|
5
7
|
* How long to wait for the spawned daemon's port to accept connections before giving up. The default
|
|
@@ -39,16 +41,31 @@ export function probeDaemon(port) {
|
|
|
39
41
|
socket.connect(port, LOOPBACK_HOST);
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Poll until a Reticle daemon is SERVING on `port`, or the deadline passes.
|
|
46
|
+
*
|
|
47
|
+
* "Accepts a TCP connection" is not the question. A daemon wedged mid-start accepts and never
|
|
48
|
+
* serves, and so does a stranger holding the port — `port-presence.ts` calls both FOREIGN for
|
|
49
|
+
* exactly this reason, and every other decision point in the codebase was migrated onto it. This
|
|
50
|
+
* one was not, so the wake path spawned a daemon, waited on a bare connect, and dialled a corpse.
|
|
51
|
+
*
|
|
52
|
+
* Probes are injected (defaulting to the real pair) so the rule is testable without a socket, which
|
|
53
|
+
* is the same shape `probePresence`'s own callers use.
|
|
54
|
+
*/
|
|
55
|
+
export async function waitForDaemon(port, probes = { tcpOpen: probeDaemon, status: fetchStatus }) {
|
|
44
56
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
45
57
|
let attempt = 0;
|
|
58
|
+
let presence = PortPresence.FREE;
|
|
46
59
|
while (Date.now() < deadline) {
|
|
47
|
-
|
|
48
|
-
if (
|
|
60
|
+
presence = await probePresence(port, probes);
|
|
61
|
+
if (presenceIsUsable(presence))
|
|
49
62
|
return;
|
|
50
63
|
attempt++;
|
|
51
64
|
await delay(daemonPollDelayMs(attempt));
|
|
52
65
|
}
|
|
53
|
-
|
|
66
|
+
// The state we timed out in is the whole diagnosis — FREE means the daemon never came up, FOREIGN
|
|
67
|
+
// means something is squatting the port. Reporting only the timeout sent readers hunting the
|
|
68
|
+
// wrong one.
|
|
69
|
+
throw new Error(`reticle daemon did not become ready on port ${String(port)} within ` +
|
|
70
|
+
`${String(DAEMON_READY_TIMEOUT_MS)}ms — ${describePresence(presence, port)}`);
|
|
54
71
|
}
|