@reticlehq/server 2.4.0 → 2.6.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 +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BUFFER_EVICTION_WARNING, THROTTLED_WARNING } from '@reticlehq/core';
|
|
1
|
+
import { BrowserBrand, BUFFER_EVICTION_WARNING, THROTTLED_WARNING } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* Buffer-honesty envelope for observe/network/console. When the ring buffer has evicted anything
|
|
4
4
|
* (age/size cap), a "no such event" answer may be a false negative — so we attach the drop count and
|
|
@@ -7,7 +7,7 @@ import { BUFFER_EVICTION_WARNING, THROTTLED_WARNING } from '@reticlehq/core';
|
|
|
7
7
|
*/
|
|
8
8
|
export function bufferEnvelope(session) {
|
|
9
9
|
const { total, dropped } = session.bufferHealth();
|
|
10
|
-
if (
|
|
10
|
+
if (0 === dropped)
|
|
11
11
|
return {};
|
|
12
12
|
return { buffer: { held: total, dropped, note: BUFFER_EVICTION_WARNING } };
|
|
13
13
|
}
|
|
@@ -31,7 +31,7 @@ export function healthEnvelope(session) {
|
|
|
31
31
|
* warn-only so background testing never breaks.
|
|
32
32
|
*/
|
|
33
33
|
export function refuseIfThrottled(session, refuse) {
|
|
34
|
-
if (
|
|
34
|
+
if (true === refuse && session.throttled()) {
|
|
35
35
|
throw new Error(`refusing to act: ${THROTTLED_WARNING}`);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -42,9 +42,10 @@ export function refuseIfThrottled(session, refuse) {
|
|
|
42
42
|
*/
|
|
43
43
|
export function readHealthEvent(data) {
|
|
44
44
|
return {
|
|
45
|
-
hidden: typeof data['hidden']
|
|
46
|
-
focused: typeof data['focused']
|
|
47
|
-
runtime: typeof data['runtime']
|
|
48
|
-
engine: typeof data['engine']
|
|
45
|
+
hidden: 'boolean' === typeof data['hidden'] ? data['hidden'] : undefined,
|
|
46
|
+
focused: 'boolean' === typeof data['focused'] ? data['focused'] : undefined,
|
|
47
|
+
runtime: 'string' === typeof data['runtime'] ? data['runtime'] : undefined,
|
|
48
|
+
engine: 'string' === typeof data['engine'] ? data['engine'] : undefined,
|
|
49
|
+
brand: Object.values(BrowserBrand).find((known) => known === data['brand']),
|
|
49
50
|
};
|
|
50
51
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
* The shape `reticle_sessions` returns for one connected tab.
|
|
3
|
-
*
|
|
4
|
-
* Split from session.ts when that file crossed the 600-line cap. A pure description of what a
|
|
5
|
-
* session looks like from the outside has no reason to live inside the class that happens to build
|
|
6
|
-
* it, and separating them keeps the wire-facing shape reviewable on its own.
|
|
7
|
-
*/
|
|
1
|
+
import type { SessionHealth } from './session-health.js';
|
|
8
2
|
export interface SessionInfo {
|
|
9
3
|
sessionId: string;
|
|
10
4
|
url: string;
|
|
11
5
|
/** Stable build-stamped project identity; absent for v1.0 SDKs that don't send it. */
|
|
12
6
|
projectId?: string;
|
|
13
|
-
title
|
|
7
|
+
/** Absent when the page has no title — never the empty string. Fall back to `url`. */
|
|
8
|
+
title?: string;
|
|
14
9
|
adapters: string[];
|
|
15
10
|
hasCapabilities: boolean;
|
|
11
|
+
/** Present only when the page's SDK version differs from the daemon's — see version-skew.ts. */
|
|
12
|
+
versionSkew?: string;
|
|
16
13
|
/** ms since the SDK last reported anything (silence ⇒ likely throttled). */
|
|
17
14
|
lastSeenMs: number;
|
|
18
15
|
hidden: boolean;
|
|
@@ -26,4 +23,42 @@ export interface SessionInfo {
|
|
|
26
23
|
pendingMarks?: number;
|
|
27
24
|
/** present with pendingMarks — nudges the agent to drain them with reticle_review. */
|
|
28
25
|
review_suggestion?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Continuity of this attachment — `{ connectedSinceMs, outages, lastOutage? }`.
|
|
28
|
+
*
|
|
29
|
+
* A tab that dropped for four seconds and came back is indistinguishable from one that never left
|
|
30
|
+
* (#117), and that difference decides whether a verdict over the window can be trusted. Attached
|
|
31
|
+
* by `SessionManager.list()`, which is the layer that sees every connect and disconnect.
|
|
32
|
+
*/
|
|
33
|
+
attachment?: {
|
|
34
|
+
connectedSinceMs: number;
|
|
35
|
+
outages: number;
|
|
36
|
+
lastOutage?: {
|
|
37
|
+
startedMs: number;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
29
41
|
}
|
|
42
|
+
/** The read-only slice of a Session that the projection needs. Keeps this module class-free. */
|
|
43
|
+
interface SessionView {
|
|
44
|
+
id: string;
|
|
45
|
+
url: string;
|
|
46
|
+
projectId: string | undefined;
|
|
47
|
+
title: string;
|
|
48
|
+
adapters: string[];
|
|
49
|
+
hasCapabilities: boolean;
|
|
50
|
+
versionSkew: string | undefined;
|
|
51
|
+
hidden: boolean;
|
|
52
|
+
health: () => SessionHealth;
|
|
53
|
+
staleMs: () => number;
|
|
54
|
+
pendingMarkCount: () => number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Project a live Session into the shape the tools return.
|
|
58
|
+
*
|
|
59
|
+
* Lives beside the SessionInfo type rather than on the class: it is a pure read of already-computed
|
|
60
|
+
* state, it is where every "what does the agent see about this tab" decision belongs, and Session is
|
|
61
|
+
* a stateful class already at its size cap — every field added to a listing was costing a line there.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildSessionInfo(session: SessionView): SessionInfo;
|
|
64
|
+
export {};
|
|
@@ -1 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The shape `reticle_sessions` returns for one connected tab.
|
|
3
|
+
*
|
|
4
|
+
* Split from session.ts when that file crossed the 600-line cap. A pure description of what a
|
|
5
|
+
* session looks like from the outside has no reason to live inside the class that happens to build
|
|
6
|
+
* it, and separating them keeps the wire-facing shape reviewable on its own.
|
|
7
|
+
*/
|
|
8
|
+
import { SESSION_LEASE } from '@reticlehq/core';
|
|
9
|
+
/**
|
|
10
|
+
* Project a live Session into the shape the tools return.
|
|
11
|
+
*
|
|
12
|
+
* Lives beside the SessionInfo type rather than on the class: it is a pure read of already-computed
|
|
13
|
+
* state, it is where every "what does the agent see about this tab" decision belongs, and Session is
|
|
14
|
+
* a stateful class already at its size cap — every field added to a listing was costing a line there.
|
|
15
|
+
*/
|
|
16
|
+
export function buildSessionInfo(session) {
|
|
17
|
+
const base = {
|
|
18
|
+
sessionId: session.id,
|
|
19
|
+
url: session.url,
|
|
20
|
+
...(session.projectId === undefined ? {} : { projectId: session.projectId }),
|
|
21
|
+
// Omitted when blank, never `""`. An empty title cannot be told apart from "we sampled before
|
|
22
|
+
// the page set one", and two such tabs make the listing unusable. Moving the sample later does
|
|
23
|
+
// not fix it — a page may set its title after connect, or never — so the projection refuses the
|
|
24
|
+
// bad answer and the reader falls back to `url`, which is right here in the same record.
|
|
25
|
+
...('' === session.title.trim() ? {} : { title: session.title }),
|
|
26
|
+
adapters: session.adapters,
|
|
27
|
+
hasCapabilities: session.hasCapabilities,
|
|
28
|
+
// On every listing, not buried in a log — skew explains failures that read as app bugs.
|
|
29
|
+
...(session.versionSkew === undefined ? {} : { versionSkew: session.versionSkew }),
|
|
30
|
+
hidden: session.hidden,
|
|
31
|
+
...session.health(),
|
|
32
|
+
};
|
|
33
|
+
if (session.staleMs() > SESSION_LEASE.STALE_AFTER_MS) {
|
|
34
|
+
base.stale = true;
|
|
35
|
+
base.cleanup_suggestion =
|
|
36
|
+
'Call reticle_session{action:"end"} to free this session before starting new work.';
|
|
37
|
+
}
|
|
38
|
+
// Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
|
|
39
|
+
const marks = session.pendingMarkCount();
|
|
40
|
+
if (marks > 0) {
|
|
41
|
+
base.pendingMarks = marks;
|
|
42
|
+
const s = 1 === marks ? '' : 's';
|
|
43
|
+
base.review_suggestion = `The human flagged ${String(marks)} issue${s} on this tab — call reticle_session{action:"review"} to see and fix ${1 === marks ? 'it' : 'them'}.`;
|
|
44
|
+
}
|
|
45
|
+
return base;
|
|
46
|
+
}
|
|
@@ -11,15 +11,4 @@ export interface SessionLease {
|
|
|
11
11
|
opened_at: number;
|
|
12
12
|
IMPORTANT: string;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Kept SHORT on purpose, and this is the reason to read before editing it.
|
|
16
|
-
*
|
|
17
|
-
* This block is a fixed, identical cost paid by EVERY session. The long-form version was 433 bytes
|
|
18
|
-
* on a `reticle_query` whose actual answer was 259 — 57% of the first call of every session spent
|
|
19
|
-
* re-explaining something that never varies. Measured at 815 → 1058 average observation tokens, and
|
|
20
|
-
* the benchmark's fresh-session-per-scenario shape pays it ten times over.
|
|
21
|
-
*
|
|
22
|
-
* Any edit here is a per-session tax on every user: say it in as few words as still work.
|
|
23
|
-
*/
|
|
24
|
-
export declare const SESSION_LEASE_NOTICE = "When you stop driving, call reticle_session {action:\"yield\", mode:\"waiting\"|\"ask\"} \u2014 the panel reads \"live\" until you do. {action:\"end\"} when the task is done; it revives on your next action.";
|
|
25
14
|
export declare function buildSessionLease(sessionId: string, openedAt: number): SessionLease;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Any edit here is a per-session tax on every user: say it in as few words as still work.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"} — the panel reads "live" until you do. {action:"end"} when the task is done; it revives on your next action.';
|
|
12
12
|
export function buildSessionLease(sessionId, openedAt) {
|
|
13
13
|
return { sessionId, opened_at: openedAt, IMPORTANT: SESSION_LEASE_NOTICE };
|
|
14
14
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Session, type SessionInfo } from './session.js';
|
|
2
|
+
import { AttachmentHistory } from './attachment-history.js';
|
|
2
3
|
/**
|
|
3
4
|
* The agent's active project, used to scope auto-selection. `projectId` is the stable build-stamped
|
|
4
5
|
* identity (authoritative when present); `url` is the app origin, a fallback hint for older SDKs that
|
|
@@ -18,9 +19,14 @@ export declare class SessionManager {
|
|
|
18
19
|
remove(session: Session): boolean;
|
|
19
20
|
get(sessionId: string): Session | undefined;
|
|
20
21
|
list(): SessionInfo[];
|
|
22
|
+
/** Continuity for one session — "has this been attached the whole time I am reasoning about?" */
|
|
23
|
+
attachmentOf(sessionId: string): ReturnType<AttachmentHistory['of']>;
|
|
21
24
|
/** Every connected session — used by the liveness reaper to sweep for idle/disconnected ones. */
|
|
22
25
|
all(): Session[];
|
|
23
26
|
count(): number;
|
|
27
|
+
/** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
|
|
28
|
+
setNoSessionHint(hint: (() => string | undefined) | undefined): void;
|
|
29
|
+
everConnected(): boolean;
|
|
24
30
|
/** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
|
|
25
31
|
noteClosure(reason: string, at: number): void;
|
|
26
32
|
/** The most recent bridge-initiated close, if any. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NO_SESSION_CONNECTED_ERROR } from '@reticlehq/core';
|
|
2
2
|
import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
|
|
3
3
|
import { Session } from './session.js';
|
|
4
|
+
import { AttachmentHistory } from './attachment-history.js';
|
|
4
5
|
/** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
|
|
5
6
|
export function originOf(url) {
|
|
6
7
|
if (url === undefined)
|
|
@@ -29,14 +30,29 @@ function scopeSessions(sessions, scope) {
|
|
|
29
30
|
}
|
|
30
31
|
return sessions;
|
|
31
32
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Honest, scoped error when sessions exist but none match the agent's project.
|
|
35
|
+
*
|
|
36
|
+
* The daemon takes its default scope from the `.reticle.json` of whichever directory started it, so
|
|
37
|
+
* attaching from a second project — or another worktree of the SAME project — makes every tool
|
|
38
|
+
* refuse while `reticle_sessions` shows the tab sitting right there. The old message asked "is that
|
|
39
|
+
* app running with @reticlehq/core enabled?", which is the one thing that is definitely true, so the
|
|
40
|
+
* reader goes looking at their app instead of at the scope. Name what IS connected and how to
|
|
41
|
+
* target it: both facts are already in hand here.
|
|
42
|
+
*/
|
|
43
|
+
function scopeMissError(connected, scope) {
|
|
34
44
|
const who = scope?.projectId !== undefined
|
|
35
45
|
? `project '${scope.projectId}'`
|
|
36
46
|
: scope?.url !== undefined
|
|
37
47
|
? `your app at ${scope.url}`
|
|
38
48
|
: 'the active project';
|
|
39
|
-
|
|
49
|
+
const listed = connected
|
|
50
|
+
.map((s) => `'${s.projectId ?? 'untagged'}' (${s.url}, sessionId '${s.id}')`)
|
|
51
|
+
.join(', ');
|
|
52
|
+
return (`no browser session for ${who}, but ${String(connected.length)} session(s) ARE connected under a ` +
|
|
53
|
+
`different project: ${listed}. The daemon scopes to the .reticle.json of the directory it was ` +
|
|
54
|
+
`started in, so this is a scope mismatch, not a dead app. Pass the sessionId above to target one, ` +
|
|
55
|
+
`or restart the daemon from that app's directory.`);
|
|
40
56
|
}
|
|
41
57
|
/**
|
|
42
58
|
* Owns the set of connected browser sessions and the smart auto-selection that resolves which one a
|
|
@@ -47,6 +63,12 @@ function scopeMissError(scope) {
|
|
|
47
63
|
const MAX_REMEMBERED_CLOSURES = 5;
|
|
48
64
|
export class SessionManager {
|
|
49
65
|
#sessions = new Map();
|
|
66
|
+
/**
|
|
67
|
+
* Continuity per session, so a listing can answer "was this attached the whole time".
|
|
68
|
+
*
|
|
69
|
+
* Owned here because this is the one place that sees both halves — every add and every remove.
|
|
70
|
+
*/
|
|
71
|
+
#attachment = new AttachmentHistory();
|
|
50
72
|
/**
|
|
51
73
|
* The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
|
|
52
74
|
* without passing its own scope, this is applied — so auto-selection is project-scoped by default
|
|
@@ -58,12 +80,14 @@ export class SessionManager {
|
|
|
58
80
|
this.#defaultScope = scope;
|
|
59
81
|
}
|
|
60
82
|
add(session) {
|
|
83
|
+
this.#everConnected = true;
|
|
61
84
|
const previous = this.#sessions.get(session.id);
|
|
62
85
|
this.#sessions.set(session.id, session);
|
|
63
86
|
// Publish what this app declared sensitive to the driven-path rule. Here rather than in the
|
|
64
87
|
// bridge because EVERY path that registers a session goes through this method, and a declaration
|
|
65
88
|
// that silently fails to register is a leak nothing would report.
|
|
66
89
|
declareDrivenRedactionKeys(session.id, session.redactKeys);
|
|
90
|
+
this.#attachment.attached(session.id);
|
|
67
91
|
return previous;
|
|
68
92
|
}
|
|
69
93
|
remove(session) {
|
|
@@ -71,13 +95,24 @@ export class SessionManager {
|
|
|
71
95
|
return false;
|
|
72
96
|
session.rejectAll('session disconnected');
|
|
73
97
|
forgetDrivenRedactionKeys(session.id);
|
|
98
|
+
// Recorded, not forgotten: a session that comes back needs its gap measured, and a listing after
|
|
99
|
+
// the reconnect is exactly where that matters.
|
|
100
|
+
this.#attachment.detached(session.id);
|
|
74
101
|
return this.#sessions.delete(session.id);
|
|
75
102
|
}
|
|
76
103
|
get(sessionId) {
|
|
77
104
|
return this.#sessions.get(sessionId);
|
|
78
105
|
}
|
|
79
106
|
list() {
|
|
80
|
-
return [...this.#sessions.values()].map((s) =>
|
|
107
|
+
return [...this.#sessions.values()].map((s) => {
|
|
108
|
+
const info = s.info();
|
|
109
|
+
const attachment = this.#attachment.of(s.id);
|
|
110
|
+
return attachment === undefined ? info : { ...info, attachment };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** Continuity for one session — "has this been attached the whole time I am reasoning about?" */
|
|
114
|
+
attachmentOf(sessionId) {
|
|
115
|
+
return this.#attachment.of(sessionId);
|
|
81
116
|
}
|
|
82
117
|
/** Every connected session — used by the liveness reaper to sweep for idle/disconnected ones. */
|
|
83
118
|
all() {
|
|
@@ -112,6 +147,25 @@ export class SessionManager {
|
|
|
112
147
|
* was hung up on. Keeping the last few reasons turns an unexplained disappearance into a fact.
|
|
113
148
|
*/
|
|
114
149
|
#recentClosures = [];
|
|
150
|
+
/**
|
|
151
|
+
* What the daemon knows about WHY nothing is connected, refreshed in the background.
|
|
152
|
+
*
|
|
153
|
+
* Measured: of the sessions that call any tool, half make exactly one call — usually
|
|
154
|
+
* reticle_sessions — and stop, because the answer they get names two things they cannot check and
|
|
155
|
+
* nothing they can do. The daemon can tell "no app is running" from "an app is running and never
|
|
156
|
+
* dialled us" from "one was connected and left", and each has a different fix. Optional so a
|
|
157
|
+
* bridge constructed without a daemon (every unit test) keeps the plain message.
|
|
158
|
+
*/
|
|
159
|
+
#noSessionHint;
|
|
160
|
+
/** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
|
|
161
|
+
setNoSessionHint(hint) {
|
|
162
|
+
this.#noSessionHint = hint;
|
|
163
|
+
}
|
|
164
|
+
/** Whether any session has connected since this daemon booted — half the diagnosis. */
|
|
165
|
+
#everConnected = false;
|
|
166
|
+
everConnected() {
|
|
167
|
+
return this.#everConnected;
|
|
168
|
+
}
|
|
115
169
|
/** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
|
|
116
170
|
noteClosure(reason, at) {
|
|
117
171
|
this.#recentClosures.push({ at, reason });
|
|
@@ -122,38 +176,96 @@ export class SessionManager {
|
|
|
122
176
|
lastClosure() {
|
|
123
177
|
return this.#recentClosures[this.#recentClosures.length - 1];
|
|
124
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* A dead `sessionId`, answered with what the caller needs to recover — not with an errand.
|
|
181
|
+
*
|
|
182
|
+
* Telemetry, 2026-08-10: one agent called `reticle_navigate` twelve times against an id that was
|
|
183
|
+
* no longer connected. That single loop is **12 of the 58 tool errors recorded all day, 21%**. The
|
|
184
|
+
* refusal said only `no connected session with id 'x'`, and the recovery hint told it to call
|
|
185
|
+
* `reticle_sessions` and retry — two extra round trips to learn something this method already
|
|
186
|
+
* knows at the moment it refuses. An agent charged two calls to recover will often just repeat the
|
|
187
|
+
* one it made.
|
|
188
|
+
*
|
|
189
|
+
* So: name the live ids inline, and when there are none say that instead, because "retry with a
|
|
190
|
+
* valid one" is advice nobody can act on when no valid one exists.
|
|
191
|
+
*/
|
|
192
|
+
#unknownSessionError(sessionId) {
|
|
193
|
+
const live = [...this.#sessions.values()];
|
|
194
|
+
if (0 === live.length) {
|
|
195
|
+
return (`no connected session with id '${sessionId}', and no sessions are connected at all — so ` +
|
|
196
|
+
'there is no other id to retry with. The app is not dialling this daemon; call ' +
|
|
197
|
+
'reticle_sessions for the diagnosis rather than retrying this call.');
|
|
198
|
+
}
|
|
199
|
+
const named = live.map((s) => `'${s.id}' (${s.url})`).join(', ');
|
|
200
|
+
return (`no connected session with id '${sessionId}'. Connected right now: ${named}. ` +
|
|
201
|
+
'Retry with one of those, or omit sessionId entirely and let Reticle scope to your project.');
|
|
202
|
+
}
|
|
125
203
|
resolve(sessionId, scope) {
|
|
126
204
|
if (sessionId !== undefined) {
|
|
127
205
|
const found = this.#sessions.get(sessionId);
|
|
128
206
|
if (found === undefined) {
|
|
129
|
-
throw new Error(
|
|
207
|
+
throw new Error(this.#unknownSessionError(sessionId));
|
|
130
208
|
}
|
|
131
209
|
found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
|
|
132
210
|
return found;
|
|
133
211
|
}
|
|
134
|
-
if (this.#sessions.size
|
|
212
|
+
if (0 === this.#sessions.size) {
|
|
135
213
|
const closure = this.lastClosure();
|
|
214
|
+
// A diagnosis beats a checklist: it names which of the three causes this actually is.
|
|
215
|
+
const hint = this.#noSessionHint?.();
|
|
216
|
+
if (hint !== undefined && closure === undefined)
|
|
217
|
+
throw new Error(hint);
|
|
136
218
|
throw new Error(closure === undefined
|
|
137
219
|
? NO_SESSION_CONNECTED_ERROR
|
|
138
|
-
: `${NO_SESSION_CONNECTED_ERROR} NOTE: the bridge closed a
|
|
220
|
+
: `${NO_SESSION_CONNECTED_ERROR} NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`);
|
|
139
221
|
}
|
|
140
222
|
// Scope to the agent's active project FIRST, so a stray tab from another app/origin (e.g. a
|
|
141
223
|
// leftover dashboard on a different port) is structurally unselectable — it never enters the
|
|
142
224
|
// candidate set, no matter how recently it was heard from. This is the anti-cross-talk guard.
|
|
143
225
|
// An explicit per-call scope wins; otherwise the daemon's active-project default applies.
|
|
144
226
|
const effectiveScope = scope ?? this.#defaultScope;
|
|
145
|
-
const
|
|
146
|
-
|
|
227
|
+
const connected = [...this.#sessions.values()];
|
|
228
|
+
const all = scopeSessions(connected, effectiveScope);
|
|
229
|
+
if (0 === all.length) {
|
|
147
230
|
// Sessions exist, but none belong to the scoped project — never fall back to a foreign tab.
|
|
148
|
-
|
|
231
|
+
// ponytail: still a refusal, deliberately. Auto-targeting the only connected tab would be the
|
|
232
|
+
// friendlier 90% case and would also silently defeat the anti-cross-talk guard this scope
|
|
233
|
+
// exists to be. Naming the tab and its sessionId costs the agent one extra argument.
|
|
234
|
+
throw new Error(scopeMissError(connected, effectiveScope));
|
|
149
235
|
}
|
|
150
|
-
if (all.length
|
|
236
|
+
if (1 === all.length) {
|
|
151
237
|
const [only] = all;
|
|
152
238
|
if (only === undefined)
|
|
153
239
|
throw new Error('session lookup failed');
|
|
154
240
|
only.markAgentActivity();
|
|
155
241
|
return only;
|
|
156
242
|
}
|
|
243
|
+
// Two projects and nothing to choose between them: refuse, BEFORE any scoring.
|
|
244
|
+
//
|
|
245
|
+
// Reported from the field: a call from `apps/next-app-router` was answered by a `rowy-*`
|
|
246
|
+
// session, and `act_and_wait` spent 16.9s returning `verified:'no'` about an app never under
|
|
247
|
+
// test — a confident false verdict, with a source pointer, about code the agent never touched.
|
|
248
|
+
//
|
|
249
|
+
// The recency rule below cannot prevent it. It asks "is one of these clearly fresher", which is
|
|
250
|
+
// the right question for two tabs of ONE app and the wrong question for two apps: the fresher
|
|
251
|
+
// tab of the wrong project is still the wrong project. The reported case had a clear winner,
|
|
252
|
+
// so the ambiguity check passed and the wrong app was selected.
|
|
253
|
+
//
|
|
254
|
+
// This sits ahead of scoring so freshness cannot rescue it. It only fires when nothing scoped
|
|
255
|
+
// the call — an explicit `sessionId` returns far above, and an explicit or default scope has
|
|
256
|
+
// already narrowed `all` to one project by here.
|
|
257
|
+
const projects = new Set(all.map((s) => s.projectId).filter((p) => p !== undefined));
|
|
258
|
+
if (projects.size > 1) {
|
|
259
|
+
const listed = all
|
|
260
|
+
.filter((s) => s.projectId !== undefined)
|
|
261
|
+
.map((s) => `'${String(s.projectId)}' (${s.url}, sessionId '${s.id}')`)
|
|
262
|
+
.join(', ');
|
|
263
|
+
throw new Error(`${String(projects.size)} different projects are connected and nothing says which one this ` +
|
|
264
|
+
`call is about: ${listed}. Pass sessionId to target one. Reticle refuses rather than ` +
|
|
265
|
+
'guessing here because picking the wrong app produces a confident verdict about code you ' +
|
|
266
|
+
'never touched — the daemon scopes to the directory it was started in, so running it above ' +
|
|
267
|
+
'several apps leaves it unable to tell them apart.');
|
|
268
|
+
}
|
|
157
269
|
// Multiple sessions: score each (lower = better candidate for auto-selection).
|
|
158
270
|
// 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat).
|
|
159
271
|
const scored = all.map((s) => ({ s, score: s.throttled() ? 1 : 0, ms: s.lastSeenMs() }));
|
|
@@ -173,7 +285,7 @@ export class SessionManager {
|
|
|
173
285
|
// desktop), the gap requirement is dropped: every session is already in "background" mode
|
|
174
286
|
// so we just pick the one with the freshest heartbeat and let the agent proceed. Requiring
|
|
175
287
|
// a gap here only produces spurious "ambiguous" errors while the user works elsewhere.
|
|
176
|
-
const allThrottled =
|
|
288
|
+
const allThrottled = 1 === bestScore;
|
|
177
289
|
const RECENCY_GAP_MS = allThrottled ? 0 : 1_000;
|
|
178
290
|
const clearWinner = runnerUp === undefined || best.ms + RECENCY_GAP_MS < runnerUp.ms;
|
|
179
291
|
if (!clearWinner) {
|
|
@@ -16,7 +16,7 @@ interface WaitForReadyOptions {
|
|
|
16
16
|
now: () => number;
|
|
17
17
|
/** Injected delay between polls. */
|
|
18
18
|
sleep: (ms: number) => Promise<void>;
|
|
19
|
-
/** Poll interval (ms). Default
|
|
19
|
+
/** Poll interval (ms). Default 25 — see DEFAULT_POLL_MS. */
|
|
20
20
|
pollMs?: number;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Same reasoning as session-reconnect: the check is an in-memory lookup and the thing awaited is an
|
|
3
|
+
* event (a session registering), so a coarse grid only adds latency to the first call an agent
|
|
4
|
+
* makes. Four times the checks of something with no I/O costs nothing measurable.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_POLL_MS = 25;
|
|
2
7
|
/**
|
|
3
8
|
* One-line orientation for a FRESH agent — the Reticle loop, returned by reticle_wait_ready (the first call)
|
|
4
9
|
* so an agent learns how to drive Reticle without reading docs. Named so it is not a free string; kept
|
|
@@ -20,7 +20,7 @@ const IDLE_WAITING_SUMMARY = 'Agent idle — your turn. Continue in your termina
|
|
|
20
20
|
* it rides the end banner so the human can copy it into their terminal. No notes → the base unchanged.
|
|
21
21
|
*/
|
|
22
22
|
export function composeEndedNotice(base, undelivered) {
|
|
23
|
-
if (undelivered.length
|
|
23
|
+
if (0 === undelivered.length)
|
|
24
24
|
return base;
|
|
25
25
|
const notes = undelivered.map((text) => `"${text}"`).join(', ');
|
|
26
26
|
return `${base} · ${UNDELIVERED_NOTES_LABEL} ${notes}`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block until a reloaded page has re-announced itself under the same session id.
|
|
3
|
+
*
|
|
4
|
+
* A reload tears the SDK down. The id survives (see session-continuity), and the daemon replaces the
|
|
5
|
+
* old Session with a new one when the page says HELLO again — so "is it back?" is exactly "does a
|
|
6
|
+
* DIFFERENT object hold this id now?". Identity, not a flag: the returning page always constructs a
|
|
7
|
+
* new Session, and nothing else can claim an id it already owns.
|
|
8
|
+
*
|
|
9
|
+
* Polls a live getter rather than subscribing, because there is no reconnect event to subscribe to
|
|
10
|
+
* and inventing one for this would be a wire change for a two-second wait.
|
|
11
|
+
*/
|
|
12
|
+
interface WaitForReconnectOptions {
|
|
13
|
+
/** The session registered under this id right now, or undefined if none is. */
|
|
14
|
+
current: () => object | undefined;
|
|
15
|
+
/** The session object that was live when the reload was dispatched. */
|
|
16
|
+
previous: object;
|
|
17
|
+
/** Give up after this much elapsed time and let the caller report it honestly. */
|
|
18
|
+
timeoutMs: number;
|
|
19
|
+
now: () => number;
|
|
20
|
+
sleep: (ms: number) => Promise<void>;
|
|
21
|
+
pollMs?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* How long a reload is worth waiting for before answering "not confirmed".
|
|
25
|
+
*
|
|
26
|
+
* A production build is back in well under a second; a cold dev-server route rebuild is the slow
|
|
27
|
+
* case this budget is sized for. It is a CEILING, not a delay — the common path returns on the first
|
|
28
|
+
* or second poll.
|
|
29
|
+
*/
|
|
30
|
+
export declare const RELOAD_RECONNECT_TIMEOUT_MS = 5000;
|
|
31
|
+
export declare function waitForReconnect(opts: WaitForReconnectOptions): Promise<boolean>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block until a reloaded page has re-announced itself under the same session id.
|
|
3
|
+
*
|
|
4
|
+
* A reload tears the SDK down. The id survives (see session-continuity), and the daemon replaces the
|
|
5
|
+
* old Session with a new one when the page says HELLO again — so "is it back?" is exactly "does a
|
|
6
|
+
* DIFFERENT object hold this id now?". Identity, not a flag: the returning page always constructs a
|
|
7
|
+
* new Session, and nothing else can claim an id it already owns.
|
|
8
|
+
*
|
|
9
|
+
* Polls a live getter rather than subscribing, because there is no reconnect event to subscribe to
|
|
10
|
+
* and inventing one for this would be a wire change for a two-second wait.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* How often to look for the reconnected session.
|
|
14
|
+
*
|
|
15
|
+
* 25ms, not 100. What is being polled is an in-memory map lookup — no I/O, no browser round-trip —
|
|
16
|
+
* while the thing being waited for is an EVENT (the reloaded page's HELLO). At 100ms the agent paid
|
|
17
|
+
* up to a full interval of dead time after the page was already back, on every navigate-with-reload.
|
|
18
|
+
* Measured across the fixture fleet, `reticle_navigate` clustered at 105 / 202-206 / 308 / 407ms —
|
|
19
|
+
* the poll grid, visible in the data. Four times the checks of something this cheap costs nothing
|
|
20
|
+
* anyone can measure.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_POLL_MS = 25;
|
|
23
|
+
/**
|
|
24
|
+
* How long a reload is worth waiting for before answering "not confirmed".
|
|
25
|
+
*
|
|
26
|
+
* A production build is back in well under a second; a cold dev-server route rebuild is the slow
|
|
27
|
+
* case this budget is sized for. It is a CEILING, not a delay — the common path returns on the first
|
|
28
|
+
* or second poll.
|
|
29
|
+
*/
|
|
30
|
+
export const RELOAD_RECONNECT_TIMEOUT_MS = 5000;
|
|
31
|
+
export async function waitForReconnect(opts) {
|
|
32
|
+
const poll = opts.pollMs ?? DEFAULT_POLL_MS;
|
|
33
|
+
const start = opts.now();
|
|
34
|
+
for (;;) {
|
|
35
|
+
const live = opts.current();
|
|
36
|
+
if (live !== undefined && live !== opts.previous)
|
|
37
|
+
return true;
|
|
38
|
+
if (opts.now() - start >= opts.timeoutMs)
|
|
39
|
+
return false;
|
|
40
|
+
await opts.sleep(poll);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -3,7 +3,7 @@ import { LastAct } from './last-act.js';
|
|
|
3
3
|
import { type PageRuntime } from './command-timeout.js';
|
|
4
4
|
import { type SessionHealth } from './session-health.js';
|
|
5
5
|
export type { SessionHealth };
|
|
6
|
-
import { PresenterTone, SessionState, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
|
|
6
|
+
import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
|
|
7
7
|
import type { JournalReader, JournalRecorder } from '../journal/journal-recorder.js';
|
|
8
8
|
import { type EventQueryOptions } from '../journal/journal-query.js';
|
|
9
9
|
import { type AmbientCounts } from '../journal/ambient.js';
|
|
@@ -27,6 +27,8 @@ export declare class Session {
|
|
|
27
27
|
title: string;
|
|
28
28
|
adapters: string[];
|
|
29
29
|
hasCapabilities: boolean;
|
|
30
|
+
/** Set when the page's SDK version differs from the daemon's (see version-skew.ts). */
|
|
31
|
+
versionSkew?: string;
|
|
30
32
|
/**
|
|
31
33
|
* Extra key names this app declared sensitive via `connect({ redact: { keys } })`. Held so the
|
|
32
34
|
* DRIVEN path can redact them too — a request body the daemon captures from the network stack
|
|
@@ -46,10 +48,12 @@ export declare class Session {
|
|
|
46
48
|
* `runtime` is optional so an older SDK (which does not report it) still works — it simply loses
|
|
47
49
|
* the desktop-specific timeout diagnosis rather than breaking.
|
|
48
50
|
*/
|
|
49
|
-
applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string): void;
|
|
51
|
+
applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string, brand?: BrowserBrand): void;
|
|
50
52
|
/** The shell (web/electron/tauri) and rendering engine the SDK reported. Undefined on an older SDK. */
|
|
51
53
|
get runtime(): PageRuntime | undefined;
|
|
52
54
|
get engine(): string | undefined;
|
|
55
|
+
/** The browser brand the SDK reported. Absent (never guessed) when the page did not say. */
|
|
56
|
+
get brand(): BrowserBrand | undefined;
|
|
53
57
|
/** Throttled if the tab is hidden OR we have not heard from it recently. */
|
|
54
58
|
throttled(): boolean;
|
|
55
59
|
/** The attachable health block — single source of truth for the tools. */
|
|
@@ -60,6 +64,14 @@ export declare class Session {
|
|
|
60
64
|
/** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
|
|
61
65
|
pushEvent(event: ReticleEvent, byteSize?: number): void;
|
|
62
66
|
recordActedRef(ref: string): void;
|
|
67
|
+
actedLabels(): ReadonlySet<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Derive the snapshot-shaped label (`button "Deploy"`) from an act reply and remember it.
|
|
70
|
+
*
|
|
71
|
+
* Same shape the interactive snapshot emits, so coverage can match a control the agent drove
|
|
72
|
+
* against the same control after a re-render gave it a different ref.
|
|
73
|
+
*/
|
|
74
|
+
private recordActedLabelFrom;
|
|
63
75
|
actedRefs(): ReadonlySet<string>;
|
|
64
76
|
noteRateLimited(dropped: number): void;
|
|
65
77
|
blindSpots(): Readonly<Record<string, number>>;
|
|
@@ -124,6 +136,7 @@ export declare class Session {
|
|
|
124
136
|
dropped: number;
|
|
125
137
|
};
|
|
126
138
|
onEvent(listener: (event: ReticleEvent) => void): () => void;
|
|
139
|
+
onDisconnect(listener: () => void): () => void;
|
|
127
140
|
/** Send a command to the browser and await its reply (or time out). */
|
|
128
141
|
command(name: string, args?: Record<string, unknown>, timeoutMs?: number): Promise<CommandResult>;
|
|
129
142
|
handleResult(result: CommandResult): void;
|