@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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the bridge refused a HELLO — the specific reason when there is one.
|
|
3
|
+
*
|
|
4
|
+
* A daemon left running by one project answers the next project's app and rejects it on token, and
|
|
5
|
+
* the SDK printed `bridge refused the connection: authentication failed`. That sends someone to
|
|
6
|
+
* check a token which is perfectly correct — it is simply a DIFFERENT project's token. The two cases
|
|
7
|
+
* need opposite fixes: "your token is wrong" versus "that daemon is not yours, stop it".
|
|
8
|
+
*
|
|
9
|
+
* The discriminator is EVIDENCE, not derivation. The daemon and the SDK derive project ids by
|
|
10
|
+
* different schemes (the SDK from its Vite root, the daemon from its own cwd), so comparing those
|
|
11
|
+
* would report "different project" on every auth failure — the same confidently-wrong diagnostic
|
|
12
|
+
* this exists to replace. What the daemon knows for certain is which projects it has ALREADY
|
|
13
|
+
* accepted a session from; if it has served X and a HELLO arrives for Y, it demonstrably belongs to
|
|
14
|
+
* someone else.
|
|
15
|
+
*
|
|
16
|
+
* With no evidence — a fresh daemon, or a HELLO naming no project — nothing is inferred and the
|
|
17
|
+
* plain reason stands.
|
|
18
|
+
*/
|
|
19
|
+
export declare function authFailureReason(servedProjects: ReadonlySet<string>, helloProject: string | undefined): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the bridge refused a HELLO — the specific reason when there is one.
|
|
3
|
+
*
|
|
4
|
+
* A daemon left running by one project answers the next project's app and rejects it on token, and
|
|
5
|
+
* the SDK printed `bridge refused the connection: authentication failed`. That sends someone to
|
|
6
|
+
* check a token which is perfectly correct — it is simply a DIFFERENT project's token. The two cases
|
|
7
|
+
* need opposite fixes: "your token is wrong" versus "that daemon is not yours, stop it".
|
|
8
|
+
*
|
|
9
|
+
* The discriminator is EVIDENCE, not derivation. The daemon and the SDK derive project ids by
|
|
10
|
+
* different schemes (the SDK from its Vite root, the daemon from its own cwd), so comparing those
|
|
11
|
+
* would report "different project" on every auth failure — the same confidently-wrong diagnostic
|
|
12
|
+
* this exists to replace. What the daemon knows for certain is which projects it has ALREADY
|
|
13
|
+
* accepted a session from; if it has served X and a HELLO arrives for Y, it demonstrably belongs to
|
|
14
|
+
* someone else.
|
|
15
|
+
*
|
|
16
|
+
* With no evidence — a fresh daemon, or a HELLO naming no project — nothing is inferred and the
|
|
17
|
+
* plain reason stands.
|
|
18
|
+
*/
|
|
19
|
+
const PLAIN = 'authentication failed';
|
|
20
|
+
/** WebSocket close reasons are capped at 123 bytes; a longer one throws and closes with nothing. */
|
|
21
|
+
const MAX_REASON_BYTES = 123;
|
|
22
|
+
export function authFailureReason(servedProjects, helloProject) {
|
|
23
|
+
if (helloProject === undefined || 0 === servedProjects.size)
|
|
24
|
+
return PLAIN;
|
|
25
|
+
if (servedProjects.has(helloProject))
|
|
26
|
+
return PLAIN;
|
|
27
|
+
const reason = `this daemon serves a different project — run \`reticle stop\` and retry`;
|
|
28
|
+
return Buffer.byteLength(reason, 'utf8') <= MAX_REASON_BYTES ? reason : PLAIN;
|
|
29
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReticleEnv } from '@reticlehq/core';
|
|
2
2
|
import { defaultPairingTokenDir, nodePairingTokenDeps, readOrCreatePairingToken, } from './pairing-token.js';
|
|
3
|
-
import { log } from '
|
|
3
|
+
import { log } from '../log.js';
|
|
4
4
|
/**
|
|
5
5
|
* Resolve the bridge's security options from explicit options first, then the environment. Shared by
|
|
6
6
|
* both `start` and `startDaemon` so the token/host/origin contract is enforced identically on
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
|
-
import { Session, SessionManager } from '
|
|
2
|
+
import { Session, SessionManager } from '../session/session.js';
|
|
3
3
|
/** A human clicked ▶ on a saved flow in the panel — replay it with no agent. Wired by the daemon. */
|
|
4
4
|
type ReplayRequestHandler = (sessionId: string, flowName: string) => void;
|
|
5
5
|
/** Called once a browser session connects, so the daemon can push it the replayable-flow list. */
|
|
6
6
|
type SessionReadyHandler = (session: Session) => void;
|
|
7
|
+
/**
|
|
8
|
+
* The close reasons worth telling an AGENT about, not just the SDK.
|
|
9
|
+
*
|
|
10
|
+
* The SDK prints these and stops retrying. The agent then calls a tool and is told "no browser
|
|
11
|
+
* session connected" — which is indistinguishable from an app nobody started. An outdated SDK and a
|
|
12
|
+
* stale pairing token were both invisible for that reason; these two carry their own remedy, so
|
|
13
|
+
* surfacing them is strictly better than the generic answer.
|
|
14
|
+
*/
|
|
15
|
+
export declare const WS_CLOSE_REASON: {
|
|
16
|
+
readonly PROTOCOL_MISMATCH: "protocol version mismatch — upgrade @reticlehq/browser";
|
|
17
|
+
readonly AUTH_FAILED: "authentication failed — reload the page to pick up the current pairing token";
|
|
18
|
+
};
|
|
7
19
|
interface BridgeOptions {
|
|
8
20
|
port: number;
|
|
9
21
|
host?: string;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
|
+
import { authFailureReason } from './auth-failure-reason.js';
|
|
2
3
|
import { WebSocketServer } from 'ws';
|
|
3
|
-
import { EventType, HumanControlKind, RETICLE_WS_PATH, ReticleMessageSchema, LOOPBACK_HOST, MessageKind, ReticleEnv, RETICLE_PROTOCOL_VERSION, TRANSPORT_LIMITS, isLocalPage, isLoopbackHostname, isOpaqueOrigin, OPAQUE_ORIGIN, isHighValueEvent, RATE_CAP_HIGH_VALUE_RESERVE_RATIO, } from '@reticlehq/core';
|
|
4
|
-
import { Session, SessionManager } from '
|
|
4
|
+
import { EventType, HumanControlKind, RETICLE_WS_PATH, ReticleMessageSchema, LOOPBACK_HOST, MessageKind, ReticleEnv, RETICLE_PROTOCOL_VERSION, TRANSPORT_LIMITS, isLocalPage, isLoopbackHostname, isOpaqueOrigin, OPAQUE_ORIGIN, isHighValueEvent, RATE_CAP_HIGH_VALUE_RESERVE_RATIO, CONTRACT_FINGERPRINT, } from '@reticlehq/core';
|
|
5
|
+
import { Session, SessionManager } from '../session/session.js';
|
|
5
6
|
import { tokensMatch } from './token-auth.js';
|
|
6
|
-
import { log } from '
|
|
7
|
+
import { log } from '../log.js';
|
|
8
|
+
import { getSessionMetrics } from '../telemetry/session-metrics.js';
|
|
9
|
+
import { describeSkew, sdkFix, SkewPair } from '../version/version-skew.js';
|
|
10
|
+
import { noteVersionSkew } from '../version/version-nudge.js';
|
|
11
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
7
12
|
/**
|
|
8
13
|
* Take the mutable half of a session's identity from a repeat HELLO.
|
|
9
14
|
*
|
|
@@ -22,15 +27,31 @@ function refreshIdentity(session, hello) {
|
|
|
22
27
|
session.adapters = hello.adapters;
|
|
23
28
|
session.hasCapabilities = hello.hasCapabilities ?? false;
|
|
24
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The close reasons worth telling an AGENT about, not just the SDK.
|
|
32
|
+
*
|
|
33
|
+
* The SDK prints these and stops retrying. The agent then calls a tool and is told "no browser
|
|
34
|
+
* session connected" — which is indistinguishable from an app nobody started. An outdated SDK and a
|
|
35
|
+
* stale pairing token were both invisible for that reason; these two carry their own remedy, so
|
|
36
|
+
* surfacing them is strictly better than the generic answer.
|
|
37
|
+
*/
|
|
38
|
+
export const WS_CLOSE_REASON = {
|
|
39
|
+
PROTOCOL_MISMATCH: 'protocol version mismatch — upgrade @reticlehq/browser',
|
|
40
|
+
AUTH_FAILED: 'authentication failed — reload the page to pick up the current pairing token',
|
|
41
|
+
};
|
|
25
42
|
/** WS close codes + reasons the bridge sends to the SDK (1008 = policy violation, 1013 = try again later). */
|
|
26
43
|
const WS_CLOSE = {
|
|
27
44
|
TOO_MANY_HANDSHAKES: [1013, 'too many pending handshakes'],
|
|
28
45
|
HELLO_TIMEOUT: [1008, 'hello timeout'],
|
|
29
46
|
INVALID_MESSAGE: [1008, 'invalid message'],
|
|
30
47
|
HELLO_DUPLICATE: [1008, 'hello already received'],
|
|
31
|
-
|
|
48
|
+
// Names the recovery, because the SDK prints this reason and then stops retrying — so it is the
|
|
49
|
+
// last thing the developer sees. The common cause is a page served before the daemon existed,
|
|
50
|
+
// which carries no token; a reload re-fetches the connect module and picks the current one up.
|
|
51
|
+
// Kept under the 123-byte WebSocket close-reason limit.
|
|
52
|
+
AUTH_FAILED: [1008, WS_CLOSE_REASON.AUTH_FAILED],
|
|
32
53
|
SESSION_LIMIT: [1013, 'session limit reached'],
|
|
33
|
-
PROTOCOL_MISMATCH: [1008,
|
|
54
|
+
PROTOCOL_MISMATCH: [1008, WS_CLOSE_REASON.PROTOCOL_MISMATCH],
|
|
34
55
|
};
|
|
35
56
|
/** Parse a positive integer env override; anything else (unset, zero, junk) falls through to the default. */
|
|
36
57
|
function positiveIntFromEnv(raw) {
|
|
@@ -46,7 +67,7 @@ function replayRequest(event) {
|
|
|
46
67
|
if (event.data['kind'] !== HumanControlKind.REPLAY)
|
|
47
68
|
return undefined;
|
|
48
69
|
const name = event.data['text'];
|
|
49
|
-
return typeof name
|
|
70
|
+
return 'string' === typeof name && name.length > 0 ? name : undefined;
|
|
50
71
|
}
|
|
51
72
|
/**
|
|
52
73
|
* Canonical comparable form of a WS `Origin`. Web origins collapse to scheme://host[:port]. An
|
|
@@ -76,7 +97,7 @@ function helloProtocolMismatch(text) {
|
|
|
76
97
|
try {
|
|
77
98
|
const json = JSON.parse(text);
|
|
78
99
|
if (json.kind === MessageKind.HELLO &&
|
|
79
|
-
typeof json.protocolVersion
|
|
100
|
+
'number' === typeof json.protocolVersion &&
|
|
80
101
|
json.protocolVersion !== RETICLE_PROTOCOL_VERSION) {
|
|
81
102
|
return json.protocolVersion;
|
|
82
103
|
}
|
|
@@ -88,7 +109,7 @@ function helloProtocolMismatch(text) {
|
|
|
88
109
|
}
|
|
89
110
|
/** Normalize ws RawData (string | Buffer | Buffer[] | ArrayBuffer) into a UTF-8 string. */
|
|
90
111
|
function rawToString(raw) {
|
|
91
|
-
if (typeof raw
|
|
112
|
+
if ('string' === typeof raw)
|
|
92
113
|
return raw;
|
|
93
114
|
if (Array.isArray(raw))
|
|
94
115
|
return Buffer.concat(raw).toString('utf8');
|
|
@@ -108,6 +129,12 @@ export class Bridge {
|
|
|
108
129
|
#wss;
|
|
109
130
|
#clock;
|
|
110
131
|
#token;
|
|
132
|
+
/**
|
|
133
|
+
* Projects this daemon has ACCEPTED a session from. Evidence, not derivation: it is what lets a
|
|
134
|
+
* leaked daemon say "I belong to another project" instead of "authentication failed". See
|
|
135
|
+
* authFailureReason.
|
|
136
|
+
*/
|
|
137
|
+
#servedProjects = new Set();
|
|
111
138
|
#allowedOrigins;
|
|
112
139
|
#maxMessagesPerSecond;
|
|
113
140
|
#maxSessions;
|
|
@@ -124,7 +151,7 @@ export class Bridge {
|
|
|
124
151
|
if ((options.token?.length ?? 0) > TRANSPORT_LIMITS.MAX_TOKEN_LENGTH) {
|
|
125
152
|
throw new Error(`Reticle pairing token exceeds ${String(TRANSPORT_LIMITS.MAX_TOKEN_LENGTH)} characters`);
|
|
126
153
|
}
|
|
127
|
-
if (!isLoopbackHostname(host) && (options.token === undefined || options.token.length
|
|
154
|
+
if (!isLoopbackHostname(host) && (options.token === undefined || 0 === options.token.length)) {
|
|
128
155
|
throw new Error('a pairing token is required when the Reticle bridge binds beyond localhost');
|
|
129
156
|
}
|
|
130
157
|
this.#clock = options.clock ?? (() => Date.now());
|
|
@@ -136,7 +163,7 @@ export class Bridge {
|
|
|
136
163
|
// Binding beyond localhost means the browser dials in from a non-loopback Origin. With no
|
|
137
164
|
// allow-list, #originAllowed rejects every such Origin at the WS handshake, so the bridge comes
|
|
138
165
|
// up but accepts nothing — a silent, fail-closed footgun. Refuse to start with a clear message.
|
|
139
|
-
if (!isLoopbackHostname(host) && this.#allowedOrigins.size
|
|
166
|
+
if (!isLoopbackHostname(host) && 0 === this.#allowedOrigins.size) {
|
|
140
167
|
throw new Error(`${ReticleEnv.ALLOWED_ORIGINS} must list the app origin(s) when the Reticle bridge binds ` +
|
|
141
168
|
'beyond localhost — otherwise every non-loopback browser is rejected at the WebSocket ' +
|
|
142
169
|
'origin check and no connection can be established.');
|
|
@@ -247,10 +274,14 @@ export class Bridge {
|
|
|
247
274
|
const overRate = messagesInWindow > this.#maxMessagesPerSecond;
|
|
248
275
|
const text = rawToString(raw);
|
|
249
276
|
const parsed = this.#parse(text);
|
|
250
|
-
if (
|
|
277
|
+
if (null === parsed) {
|
|
251
278
|
const got = helloProtocolMismatch(text);
|
|
252
279
|
if (got !== null) {
|
|
253
280
|
log('protocol_version_mismatch', { got, expected: RETICLE_PROTOCOL_VERSION });
|
|
281
|
+
// Told to the AGENT too, not only to the SDK that is about to stop retrying. Without this
|
|
282
|
+
// the next tool call answers "no browser session connected", which reads as "no app is
|
|
283
|
+
// running" — so an SDK too old to connect is invisible.
|
|
284
|
+
this.sessions.noteClosure(WS_CLOSE_REASON.PROTOCOL_MISMATCH, this.#clock());
|
|
254
285
|
socket.close(...WS_CLOSE.PROTOCOL_MISMATCH);
|
|
255
286
|
return;
|
|
256
287
|
}
|
|
@@ -297,8 +328,16 @@ export class Bridge {
|
|
|
297
328
|
return;
|
|
298
329
|
}
|
|
299
330
|
if (this.#token !== undefined && !tokensMatch(this.#token, parsed.token)) {
|
|
300
|
-
|
|
301
|
-
|
|
331
|
+
// A daemon left running by ANOTHER project answers this app and rejects it on token. The
|
|
332
|
+
// token is not wrong, it is somebody else's — and "authentication failed" sends the user to
|
|
333
|
+
// check the one thing that is fine. See auth-failure-reason.
|
|
334
|
+
const reason = authFailureReason(this.#servedProjects, parsed.projectId);
|
|
335
|
+
log('authentication_failed', {
|
|
336
|
+
served: [...this.#servedProjects],
|
|
337
|
+
...(parsed.projectId === undefined ? {} : { helloProject: parsed.projectId }),
|
|
338
|
+
});
|
|
339
|
+
this.sessions.noteClosure(WS_CLOSE_REASON.AUTH_FAILED, this.#clock());
|
|
340
|
+
socket.close(WS_CLOSE.AUTH_FAILED[0], reason);
|
|
302
341
|
return;
|
|
303
342
|
}
|
|
304
343
|
const existing = this.sessions.get(parsed.sessionId);
|
|
@@ -309,10 +348,51 @@ export class Bridge {
|
|
|
309
348
|
clearTimeout(helloTimer);
|
|
310
349
|
releasePending();
|
|
311
350
|
session = new Session(parsed, socket, this.#clock);
|
|
351
|
+
// Recorded on ACCEPTANCE, so it is evidence of what this daemon really serves.
|
|
352
|
+
if (parsed.projectId !== undefined)
|
|
353
|
+
this.#servedProjects.add(parsed.projectId);
|
|
312
354
|
this.#onSessionCreate?.(session); // attach the durable journal before any events stream in
|
|
313
355
|
const replaced = this.sessions.add(session);
|
|
314
|
-
|
|
356
|
+
if (replaced !== undefined) {
|
|
357
|
+
// Name the newcomer. A field report had a live session vanish during `reticle_lease` and the
|
|
358
|
+
// only evidence was "session replaced by a newer connection" in the page console — which
|
|
359
|
+
// says a replacement happened but not BY WHAT, so the cause had to be inferred. A session
|
|
360
|
+
// is only ever replaced by one carrying the SAME id, and knowing which URL claimed it is
|
|
361
|
+
// the difference between a diagnosis and a guess.
|
|
362
|
+
log('session_replaced', {
|
|
363
|
+
sessionId: session.id,
|
|
364
|
+
byUrl: session.url,
|
|
365
|
+
previousUrl: replaced.url,
|
|
366
|
+
});
|
|
367
|
+
replaced.disconnect(`session replaced by a newer connection claiming the same id (${session.id}) from ${session.url}`);
|
|
368
|
+
}
|
|
369
|
+
// The daemon is the single judge of skew, and HELLO is where the page announces itself.
|
|
370
|
+
// Reported on the session (reticle_sessions) AND queued for the next tool result, because an
|
|
371
|
+
// agent driving a flow never calls reticle_sessions and would never learn.
|
|
372
|
+
const skew = describeSkew({
|
|
373
|
+
what: 'the page',
|
|
374
|
+
version: parsed.sdkVersion,
|
|
375
|
+
contract: parsed.contract,
|
|
376
|
+
fix: sdkFix(SERVER_VERSION),
|
|
377
|
+
}, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
|
|
378
|
+
if (skew !== undefined) {
|
|
379
|
+
log('version_skew', {
|
|
380
|
+
sessionId: session.id,
|
|
381
|
+
sdk: parsed.sdkVersion,
|
|
382
|
+
daemon: SERVER_VERSION,
|
|
383
|
+
});
|
|
384
|
+
session.versionSkew = skew;
|
|
385
|
+
noteVersionSkew(SkewPair.SDK, skew);
|
|
386
|
+
}
|
|
315
387
|
log('session_connected', { sessionId: session.id, url: session.url });
|
|
388
|
+
// The one fact that separates a broken install from an unused one. Best-effort and wrapped:
|
|
389
|
+
// a metric must never affect whether an app can connect. See SessionSummary.appConnects.
|
|
390
|
+
try {
|
|
391
|
+
getSessionMetrics().recordAppConnected();
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
/* never let a counter interfere with an app connecting */
|
|
395
|
+
}
|
|
316
396
|
this.#onSessionReady?.(session); // daemon pushes the replayable-flow list to the panel
|
|
317
397
|
return;
|
|
318
398
|
}
|
|
@@ -357,7 +437,7 @@ export class Bridge {
|
|
|
357
437
|
if (origin === undefined)
|
|
358
438
|
return this.#token !== undefined;
|
|
359
439
|
const normalized = normalizeOrigin(origin);
|
|
360
|
-
if (
|
|
440
|
+
if (null === normalized)
|
|
361
441
|
return false;
|
|
362
442
|
if (this.#allowedOrigins.has(normalized))
|
|
363
443
|
return true;
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { type ElementQuery } from '@reticlehq/core';
|
|
2
2
|
import type { Bridge } from './bridge.js';
|
|
3
|
-
import { type ToolDeps } from '
|
|
4
|
-
/** The app-advertised testable surface a FakeBrowser reports for an reticle_capabilities round-trip. */
|
|
5
|
-
export declare const FAKE_CAPABILITIES: {
|
|
6
|
-
testids: string[];
|
|
7
|
-
signals: string[];
|
|
8
|
-
stores: string[];
|
|
9
|
-
flows: {
|
|
10
|
-
name: string;
|
|
11
|
-
steps: string[];
|
|
12
|
-
}[];
|
|
13
|
-
};
|
|
3
|
+
import { type ToolDeps } from '../tools/tools.js';
|
|
14
4
|
/** A stand-in for the real @reticlehq/browser SDK: replies to commands and emits events. */
|
|
15
5
|
export declare class FakeBrowser {
|
|
16
6
|
#private;
|
|
@@ -45,6 +35,14 @@ export declare class FakeBrowser {
|
|
|
45
35
|
emit(type: string, data: Record<string, unknown>, ref?: string): void;
|
|
46
36
|
close(): void;
|
|
47
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Poll until `cond` holds.
|
|
40
|
+
*
|
|
41
|
+
* The default was 2000ms and the fifty-browser test blew it on the Windows runner at 5155ms — a
|
|
42
|
+
* statement about the machine, not about the bridge. Raising it is safe in a way that raising a
|
|
43
|
+
* fixed sleep would not be: this POLLS every 10ms and resolves the instant the condition holds, so
|
|
44
|
+
* a passing test is not slowed by one millisecond and only a genuinely failing one waits longer.
|
|
45
|
+
*/
|
|
48
46
|
export declare function waitUntil(cond: () => boolean, timeoutMs?: number): Promise<void>;
|
|
49
47
|
export declare const callTool: (deps: ToolDeps, name: string, args?: Record<string, unknown>) => Promise<unknown>;
|
|
50
48
|
/** Builds the ToolDeps wired to a running bridge, with a frozen clock for deterministic tests. */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
2
|
import { RETICLE_WS_PATH, ReticleCommand, LOOPBACK_HOST, MessageKind, } from '@reticlehq/core';
|
|
3
|
-
import { BaselineStore } from '
|
|
4
|
-
import { createNodeFileSystem } from '
|
|
5
|
-
import { RecordingStore } from '
|
|
6
|
-
import { FlowStore } from '
|
|
7
|
-
import { ProjectStore } from '
|
|
8
|
-
import { AnnotationStore } from '
|
|
9
|
-
import { TOOLS } from '
|
|
3
|
+
import { BaselineStore } from '../project/baselines.js';
|
|
4
|
+
import { createNodeFileSystem } from '../project/fs-port.js';
|
|
5
|
+
import { RecordingStore } from '../flows/recordings.js';
|
|
6
|
+
import { FlowStore } from '../flows/flows.js';
|
|
7
|
+
import { ProjectStore } from '../project/project-store.js';
|
|
8
|
+
import { AnnotationStore } from '../flows/annotation-store.js';
|
|
9
|
+
import { TOOLS } from '../tools/tools.js';
|
|
10
10
|
/** The app-advertised testable surface a FakeBrowser reports for an reticle_capabilities round-trip. */
|
|
11
|
-
|
|
11
|
+
const FAKE_CAPABILITIES = {
|
|
12
12
|
testids: ['toast'],
|
|
13
13
|
signals: ['webhook:received'],
|
|
14
14
|
stores: ['cart'],
|
|
@@ -141,7 +141,7 @@ export class FakeBrowser {
|
|
|
141
141
|
}
|
|
142
142
|
else if (name === ReticleCommand.STATE_READ) {
|
|
143
143
|
result = {
|
|
144
|
-
stores: { workspace: { tab:
|
|
144
|
+
stores: { workspace: { tab: 'workspace' === args['store'] ? 'open' : 'all' } },
|
|
145
145
|
storeNames: ['workspace'],
|
|
146
146
|
component: args['ref'] !== undefined ? { component: 'PayButton', hooks: [0] } : undefined,
|
|
147
147
|
};
|
|
@@ -170,7 +170,15 @@ export class FakeBrowser {
|
|
|
170
170
|
this.#ws.send(JSON.stringify(obj));
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Poll until `cond` holds.
|
|
175
|
+
*
|
|
176
|
+
* The default was 2000ms and the fifty-browser test blew it on the Windows runner at 5155ms — a
|
|
177
|
+
* statement about the machine, not about the bridge. Raising it is safe in a way that raising a
|
|
178
|
+
* fixed sleep would not be: this POLLS every 10ms and resolves the instant the condition holds, so
|
|
179
|
+
* a passing test is not slowed by one millisecond and only a genuinely failing one waits longer.
|
|
180
|
+
*/
|
|
181
|
+
export function waitUntil(cond, timeoutMs = 15_000) {
|
|
174
182
|
return new Promise((resolve, reject) => {
|
|
175
183
|
const start = Date.now();
|
|
176
184
|
const tick = () => {
|
|
@@ -10,6 +10,12 @@ import type { FileSystemPort } from '../project/fs-port.js';
|
|
|
10
10
|
* fix lands, it is exactly a regression flow. Capsules are ordinary flow files plus evidence, so they
|
|
11
11
|
* replay through the existing machinery rather than needing a second runner.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* The capsule file-format version. Declared ABOVE the schema so the schema, the writer and this
|
|
15
|
+
* constant cannot disagree: the number was previously written as a bare `1` in three places — here,
|
|
16
|
+
* in the schema literal, and in act-capsule's writer — with the named one used by nobody.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CAPSULE_VERSION = 1;
|
|
13
19
|
export declare const CapsuleSchema: z.ZodObject<{
|
|
14
20
|
version: z.ZodLiteral<1>;
|
|
15
21
|
id: z.ZodString;
|
|
@@ -29,8 +35,8 @@ export declare const CapsuleSchema: z.ZodObject<{
|
|
|
29
35
|
version: 1;
|
|
30
36
|
id: string;
|
|
31
37
|
expected: string;
|
|
32
|
-
steps: FlowStep[];
|
|
33
38
|
observed: string;
|
|
39
|
+
steps: FlowStep[];
|
|
34
40
|
createdAt: number;
|
|
35
41
|
flow?: string | undefined;
|
|
36
42
|
}, {
|
|
@@ -38,13 +44,12 @@ export declare const CapsuleSchema: z.ZodObject<{
|
|
|
38
44
|
version: 1;
|
|
39
45
|
id: string;
|
|
40
46
|
expected: string;
|
|
41
|
-
steps: FlowStep[];
|
|
42
47
|
observed: string;
|
|
48
|
+
steps: FlowStep[];
|
|
43
49
|
createdAt: number;
|
|
44
50
|
flow?: string | undefined;
|
|
45
51
|
}>;
|
|
46
52
|
export type Capsule = z.infer<typeof CapsuleSchema>;
|
|
47
|
-
export declare const CAPSULE_VERSION = 1;
|
|
48
53
|
/** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
|
|
49
54
|
export declare function isValidCapsuleId(id: string): boolean;
|
|
50
55
|
export declare class CapsuleStore {
|
|
@@ -11,8 +11,14 @@ import { reticleDirPaths } from '../project/reticle-dir.js';
|
|
|
11
11
|
* fix lands, it is exactly a regression flow. Capsules are ordinary flow files plus evidence, so they
|
|
12
12
|
* replay through the existing machinery rather than needing a second runner.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* The capsule file-format version. Declared ABOVE the schema so the schema, the writer and this
|
|
16
|
+
* constant cannot disagree: the number was previously written as a bare `1` in three places — here,
|
|
17
|
+
* in the schema literal, and in act-capsule's writer — with the named one used by nobody.
|
|
18
|
+
*/
|
|
19
|
+
export const CAPSULE_VERSION = 1;
|
|
14
20
|
export const CapsuleSchema = z.object({
|
|
15
|
-
version: z.literal(
|
|
21
|
+
version: z.literal(CAPSULE_VERSION),
|
|
16
22
|
id: z.string(),
|
|
17
23
|
/** The flow the failure happened in, when it came from one (a bare assert has no flow). */
|
|
18
24
|
flow: z.string().optional(),
|
|
@@ -26,7 +32,6 @@ export const CapsuleSchema = z.object({
|
|
|
26
32
|
/** Minimal steps to reproduce (prefix-trimmed). */
|
|
27
33
|
steps: z.array(FlowStepSchema),
|
|
28
34
|
});
|
|
29
|
-
export const CAPSULE_VERSION = 1;
|
|
30
35
|
/** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
|
|
31
36
|
export function isValidCapsuleId(id) {
|
|
32
37
|
return /^[a-zA-Z0-9._-]+$/.test(id) && id !== '.' && id !== '..' && id.length <= 128;
|
package/dist/capsule/capsule.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { EventType } from '@reticlehq/core';
|
|
1
|
+
import { ConsequenceKind, EventType } from '@reticlehq/core';
|
|
2
2
|
import { causalSummary } from './causal-summary.js';
|
|
3
3
|
import { firstDivergence } from './divergence.js';
|
|
4
4
|
/** Observed signal/state changes the flow never declared — the side effects. */
|
|
5
5
|
function blastRadius(expected, observed) {
|
|
6
6
|
const declaredSignals = new Set(expected
|
|
7
|
-
.filter((l) =>
|
|
7
|
+
.filter((l) => ConsequenceKind.SIGNAL === l.kind)
|
|
8
8
|
.map((l) => l.name));
|
|
9
9
|
const declaredStates = new Set(expected
|
|
10
|
-
.filter((l) =>
|
|
10
|
+
.filter((l) => ConsequenceKind.STATE === l.kind)
|
|
11
11
|
.map((l) => l.name));
|
|
12
12
|
const radius = [];
|
|
13
13
|
const add = (value) => {
|
|
@@ -26,9 +26,17 @@ export interface StorageDiff {
|
|
|
26
26
|
to?: unknown;
|
|
27
27
|
}
|
|
28
28
|
export interface CausalSummary {
|
|
29
|
+
/**
|
|
30
|
+
* `total`/`errors` describe the APP's traffic. `ignoredDevTooling` is the count of calls the dev
|
|
31
|
+
* toolchain made about itself (Next's error overlay, HMR, the Vite client — see
|
|
32
|
+
* `DevToolingChannel`), which Reticle deliberately leaves out of the settle decision and out of
|
|
33
|
+
* contradiction hunting. Present only when there were any: a trim is never silent, so an agent can
|
|
34
|
+
* always see that something was ignored and go read it in the timeline.
|
|
35
|
+
*/
|
|
29
36
|
net: {
|
|
30
37
|
total: number;
|
|
31
38
|
errors: number;
|
|
39
|
+
ignoredDevTooling?: number;
|
|
32
40
|
headline?: string;
|
|
33
41
|
};
|
|
34
42
|
consoleErrors: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EventType, PerfMetric } from '@reticlehq/core';
|
|
1
|
+
import { EventType, PerfMetric, isDevToolingUrl } from '@reticlehq/core';
|
|
2
2
|
function pushUnique(list, value) {
|
|
3
|
-
if (typeof value
|
|
3
|
+
if ('string' === typeof value && value.length > 0 && !list.includes(value))
|
|
4
4
|
list.push(value);
|
|
5
5
|
}
|
|
6
6
|
/** Keep a diff value bounded so the per-act summary never bloats: primitives capped to a short string. */
|
|
@@ -10,11 +10,11 @@ function truncate(text) {
|
|
|
10
10
|
return text.length > MAX_DIFF_LEN ? `${text.slice(0, MAX_DIFF_LEN - 1)}…` : text;
|
|
11
11
|
}
|
|
12
12
|
function capValue(value) {
|
|
13
|
-
if (value === undefined ||
|
|
13
|
+
if (value === undefined || null === value)
|
|
14
14
|
return value;
|
|
15
|
-
if (typeof value
|
|
15
|
+
if ('string' === typeof value)
|
|
16
16
|
return truncate(value);
|
|
17
|
-
if (
|
|
17
|
+
if ('number' === typeof value || 'boolean' === typeof value)
|
|
18
18
|
return value;
|
|
19
19
|
// Objects/arrays: a shallow, length-capped JSON repr (never a deep dump on the hot path).
|
|
20
20
|
try {
|
|
@@ -40,6 +40,7 @@ function stateSettle(diffs) {
|
|
|
40
40
|
export function causalSummary(events) {
|
|
41
41
|
let netTotal = 0;
|
|
42
42
|
let netErrors = 0;
|
|
43
|
+
let ignoredDevTooling = 0;
|
|
43
44
|
let headline;
|
|
44
45
|
let consoleErrors = 0;
|
|
45
46
|
const statePathsChanged = [];
|
|
@@ -54,9 +55,13 @@ export function causalSummary(events) {
|
|
|
54
55
|
const data = event.data;
|
|
55
56
|
switch (event.type) {
|
|
56
57
|
case EventType.NET_REQUEST: {
|
|
58
|
+
if (isDevToolingUrl('string' === typeof data['url'] ? data['url'] : undefined)) {
|
|
59
|
+
ignoredDevTooling += 1;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
57
62
|
netTotal += 1;
|
|
58
63
|
const status = data['status'];
|
|
59
|
-
const failed = data['ok']
|
|
64
|
+
const failed = false === data['ok'] || ('number' === typeof status && status >= 400);
|
|
60
65
|
if (failed) {
|
|
61
66
|
netErrors += 1;
|
|
62
67
|
// Headline is the first failing request — the thing most worth the agent's eye.
|
|
@@ -73,9 +78,9 @@ export function causalSummary(events) {
|
|
|
73
78
|
// The subscribed-store observer emits { name, path, value, old } — `value` is the AFTER side
|
|
74
79
|
// (`new` is accepted too for any producer that uses it). Presence of either makes it a real
|
|
75
80
|
// before→after diff rather than a bare reading.
|
|
76
|
-
const path = typeof data['path']
|
|
81
|
+
const path = 'string' === typeof data['path'] ? data['path'] : data['name'];
|
|
77
82
|
const hasAfter = 'value' in data || 'new' in data;
|
|
78
|
-
if (typeof path
|
|
83
|
+
if ('string' === typeof path && path.length > 0 && ('old' in data || hasAfter)) {
|
|
79
84
|
const after = 'value' in data ? data['value'] : data['new'];
|
|
80
85
|
stateDiffs.push({
|
|
81
86
|
path,
|
|
@@ -89,20 +94,20 @@ export function causalSummary(events) {
|
|
|
89
94
|
case EventType.STORAGE_CHANGE: {
|
|
90
95
|
pushUnique(storageKeysChanged, data['key']);
|
|
91
96
|
const key = data['key'];
|
|
92
|
-
if (typeof key
|
|
97
|
+
if ('string' === typeof key && key.length > 0) {
|
|
93
98
|
storageDiffs.push({ key, from: capValue(data['old']), to: capValue(data['new']) });
|
|
94
99
|
}
|
|
95
100
|
break;
|
|
96
101
|
}
|
|
97
102
|
case EventType.ROUTE_CHANGE:
|
|
98
|
-
if (typeof data['pathname']
|
|
103
|
+
if ('string' === typeof data['pathname'])
|
|
99
104
|
route = data['pathname'];
|
|
100
105
|
break;
|
|
101
106
|
case EventType.SIGNAL:
|
|
102
107
|
pushUnique(signals, data['name']);
|
|
103
108
|
break;
|
|
104
109
|
case EventType.PERF:
|
|
105
|
-
if (data['metric'] === PerfMetric.CLS && typeof data['value']
|
|
110
|
+
if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
|
|
106
111
|
layoutShift = Math.max(layoutShift ?? 0, data['value']);
|
|
107
112
|
}
|
|
108
113
|
else if (data['metric'] === PerfMetric.LONGTASK) {
|
|
@@ -114,7 +119,12 @@ export function causalSummary(events) {
|
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
return {
|
|
117
|
-
net: {
|
|
122
|
+
net: {
|
|
123
|
+
total: netTotal,
|
|
124
|
+
errors: netErrors,
|
|
125
|
+
...(0 === ignoredDevTooling ? {} : { ignoredDevTooling }),
|
|
126
|
+
...(headline === undefined ? {} : { headline }),
|
|
127
|
+
},
|
|
118
128
|
consoleErrors,
|
|
119
129
|
statePathsChanged,
|
|
120
130
|
storageKeysChanged,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReticleEvent } from '@reticlehq/core';
|
|
1
|
+
import { ConsequenceKind, type ReticleEvent } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* First-divergence — the brain of the divergence capsule (Tier 2). Given the chain the flow DECLARED
|
|
4
4
|
* should happen (its mustHold, as ordered expected links) and what was OBSERVED, walk the declaration and
|
|
@@ -6,15 +6,20 @@ import { type ReticleEvent } from '@reticlehq/core';
|
|
|
6
6
|
* fault lives — the agent fixes it without re-exploring. Pure; the acceptance bar is that for every
|
|
7
7
|
* hidden-500 / hung-request fixture it names the correct link.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* The links a capsule can walk. Exactly core's ConsequenceKind — a link is a thing the app provably
|
|
11
|
+
* DID, which is the same set signal/net/state names everywhere else. Spelled through the shared
|
|
12
|
+
* constant so the two can never drift apart.
|
|
13
|
+
*/
|
|
9
14
|
export type ExpectedLink = {
|
|
10
|
-
kind:
|
|
15
|
+
kind: typeof ConsequenceKind.SIGNAL;
|
|
11
16
|
name: string;
|
|
12
17
|
} | {
|
|
13
|
-
kind:
|
|
18
|
+
kind: typeof ConsequenceKind.NET;
|
|
14
19
|
urlContains: string;
|
|
15
20
|
status?: number;
|
|
16
21
|
} | {
|
|
17
|
-
kind:
|
|
22
|
+
kind: typeof ConsequenceKind.STATE;
|
|
18
23
|
name: string;
|
|
19
24
|
};
|
|
20
25
|
export interface Divergence {
|