@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
package/dist/tools/tools.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
|
|
2
|
+
import { QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from './tool-names.js';
|
|
4
4
|
import { withSizeCost } from '../session/output-budget.js';
|
|
5
5
|
import { applySnapshotDelta, SnapshotCache } from './snapshot-delta.js';
|
|
6
6
|
import { asString, asNumber } from './tools-helpers.js';
|
|
7
|
+
import { normalizeQueryArgs } from './query-shape.js';
|
|
7
8
|
import { paginateQueryResult } from './query-paginate.js';
|
|
9
|
+
/**
|
|
10
|
+
* The query strategies, derived from the enum in core — never retyped.
|
|
11
|
+
*
|
|
12
|
+
* The description used to hand-list them and had already drifted: it named six and omitted
|
|
13
|
+
* `component`, which is real and works. Deriving both the schema and the prose from one source is
|
|
14
|
+
* the repo's own rule, and here it is also the difference between refusing `by:'css'` and silently
|
|
15
|
+
* answering "0 matches" to it.
|
|
16
|
+
*/
|
|
17
|
+
const QUERY_BY_VALUES = Object.values(QueryBy);
|
|
18
|
+
const QUERY_BY_LIST = QUERY_BY_VALUES.join(' | ');
|
|
19
|
+
const queryByEnum = z.enum(QUERY_BY_VALUES);
|
|
8
20
|
import { CONTRACT_TOOLS } from './contract-tools.js';
|
|
9
21
|
import { DOMAIN_TOOLS } from '../domain/domain-tools.js';
|
|
10
22
|
import { BROWSER_TOOLS } from './browser-tools.js';
|
|
@@ -49,6 +61,10 @@ const RAW_TOOLS = [
|
|
|
49
61
|
title: z.string().optional(),
|
|
50
62
|
adapters: z.array(z.string()),
|
|
51
63
|
hasCapabilities: z.boolean(),
|
|
64
|
+
versionSkew: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Present only when this page's SDK version differs from the daemon's. A skewed pair connects and then disagrees about tool behaviour — usually surfacing as a bare -32000. Fix it before trusting any verdict from this session."),
|
|
52
68
|
lastSeenMs: z.number(),
|
|
53
69
|
throttled: z.boolean(),
|
|
54
70
|
focused: z.boolean(),
|
|
@@ -151,19 +167,24 @@ const RAW_TOOLS = [
|
|
|
151
167
|
sessionId: resolved.id,
|
|
152
168
|
scope: asString(args['scope']) ?? '',
|
|
153
169
|
mode,
|
|
154
|
-
diff: args['diff']
|
|
170
|
+
diff: true === args['diff'],
|
|
155
171
|
}, SNAPSHOT_CACHE)));
|
|
156
172
|
},
|
|
157
173
|
},
|
|
158
174
|
{
|
|
159
175
|
name: ReticleTool.QUERY,
|
|
160
176
|
example: { by: 'testid', value: 'todo-list' },
|
|
161
|
-
description:
|
|
177
|
+
description: `Find elements by Testing-Library semantics, INCLUDING open shadow roots — \`count_only:true\` gives just the count (~30x smaller); \`limit\` caps descriptors. Pass \`by\` (${QUERY_BY_LIST}) and \`value\` (the query string). Returns matching refs + descriptors + visibility. Pass \`attrs:["href"]\` to project attributes (link/image URLs) onto each match. Pass \`limit\` to cap descriptors (broad role queries can be large) or \`count_only:true\` for just the match count — both cut tokens. On zero matches, also returns hint:{ route, presentRegions[], knownEmptyState } so you can distinguish an empty state from a missing element WITHOUT taking a snapshot.`,
|
|
162
178
|
inputSchema: {
|
|
163
|
-
|
|
179
|
+
// Constrained to the enum, NOT z.string(). A free string let `by:'css'` through to the
|
|
180
|
+
// browser's `default: return []`, so an unsupported strategy answered "0 matches" — which
|
|
181
|
+
// reads as "the element is not on the page". Measured on a live page: by:'css' value:'body'
|
|
182
|
+
// returned count 0. A false negative is the one answer this product must never invent.
|
|
183
|
+
by: queryByEnum.optional().describe(`Query strategy: ${QUERY_BY_LIST}`),
|
|
164
184
|
value: z
|
|
165
185
|
.string()
|
|
166
|
-
.
|
|
186
|
+
.optional()
|
|
187
|
+
.describe('Query value for the selected strategy (e.g. by=role value=button, or by=testid value=submit-btn). Or use the predicate spelling directly: { testid: "submit" }, { text: "Deploy" }.'),
|
|
167
188
|
name: z
|
|
168
189
|
.string()
|
|
169
190
|
.optional()
|
|
@@ -185,6 +206,14 @@ const RAW_TOOLS = [
|
|
|
185
206
|
.boolean()
|
|
186
207
|
.optional()
|
|
187
208
|
.describe('Return just { count } (no element descriptors) — use when you only need "how many match?" and not their refs.'),
|
|
209
|
+
// The predicate's spelling, accepted as an alias for by/value so the shape an agent learns from
|
|
210
|
+
// act_and_wait/assert also works here. See query-shape.ts.
|
|
211
|
+
testid: z.string().optional().describe('Alias for by="testid" value=… (predicate spelling).'),
|
|
212
|
+
text: z.string().optional().describe('Alias for by="text" value=… (predicate spelling).'),
|
|
213
|
+
role: z.string().optional().describe('Alias for by="role" value=… (predicate spelling).'),
|
|
214
|
+
label: z.string().optional().describe('Alias for by="label" value=… (predicate spelling).'),
|
|
215
|
+
placeholder: z.string().optional().describe('Alias for by="placeholder" value=… .'),
|
|
216
|
+
alt: z.string().optional().describe('Alias for by="alt" value=… (predicate spelling).'),
|
|
188
217
|
...sessionIdShape,
|
|
189
218
|
},
|
|
190
219
|
outputSchema: {
|
|
@@ -257,16 +286,20 @@ const RAW_TOOLS = [
|
|
|
257
286
|
.optional()
|
|
258
287
|
.describe('True when a `scope` was given but resolved to nothing — zero matches then means the scope is gone, NOT that the element is absent. The search did not widen to the whole page. Re-check the scope before concluding anything.'),
|
|
259
288
|
},
|
|
260
|
-
handler: (deps,
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
289
|
+
handler: (deps, rawArgs) => {
|
|
290
|
+
// Accept the predicate's named-field spelling too — see query-shape.ts.
|
|
291
|
+
const args = normalizeQueryArgs(rawArgs);
|
|
292
|
+
return commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.QUERY, {
|
|
293
|
+
by: args['by'],
|
|
294
|
+
value: args['value'],
|
|
295
|
+
name: args['name'],
|
|
296
|
+
scope: args['scope'],
|
|
297
|
+
// The handler forwards an explicit allowlist, so a new input is silently dropped unless it is
|
|
298
|
+
// added here — the browser saw no `attrs` and returned undefined while the unit tests, which
|
|
299
|
+
// call matchQuery directly, passed. Schema plus implementation is not the whole wire.
|
|
300
|
+
attrs: args['attrs'],
|
|
301
|
+
}).then((result) => withSizeCost(paginateQueryResult(result, asNumber(args['limit']), true === args['count_only'])));
|
|
302
|
+
},
|
|
270
303
|
},
|
|
271
304
|
{
|
|
272
305
|
name: ReticleTool.INSPECT,
|
|
@@ -383,7 +416,7 @@ const RAW_TOOLS = [
|
|
|
383
416
|
* Guardrail (from the plan): the 12-tool core hot-set keeps its EXACT names and shapes and is never
|
|
384
417
|
* merged — sessions/navigate/snapshot/query/act/act_and_wait/observe/network/console/wait_for/assert/state.
|
|
385
418
|
*/
|
|
386
|
-
const MERGE_PLANS = [
|
|
419
|
+
export const MERGE_PLANS = [
|
|
387
420
|
{
|
|
388
421
|
name: ReticleTool.BASELINE,
|
|
389
422
|
description: 'Semantic-state baselines: { action: "save" } snapshots the current state under a name, "list" returns saved names, "diff" compares the live page against a saved baseline (REMOVED/ADDED elements + console-error count).',
|
|
@@ -419,6 +452,9 @@ const MERGE_PLANS = [
|
|
|
419
452
|
review: ReticleTool.REVIEW,
|
|
420
453
|
narrate: ReticleTool.NARRATE,
|
|
421
454
|
},
|
|
455
|
+
// The handback the lease block orders on every session — the one call an agent MUST make, so it
|
|
456
|
+
// is the one that must not need a schema lookup first.
|
|
457
|
+
example: { action: 'yield', mode: 'waiting' },
|
|
422
458
|
},
|
|
423
459
|
{
|
|
424
460
|
name: ReticleTool.LEASE,
|
|
@@ -430,7 +466,7 @@ const MERGE_PLANS = [
|
|
|
430
466
|
* Retired from the MCP surface entirely (capability preserved elsewhere, per the plan):
|
|
431
467
|
* - run_record: auto-recording on flow_replay already persists run outcomes.
|
|
432
468
|
*/
|
|
433
|
-
const RETIRED_FROM_SURFACE = [
|
|
469
|
+
export const RETIRED_FROM_SURFACE = [
|
|
434
470
|
ReticleTool.RUN_RECORD, // auto-recording on flow_replay already persists run outcomes
|
|
435
471
|
ReticleTool.REFRESH, // absorbed into reticle_navigate { reload: true }
|
|
436
472
|
ReticleTool.WAIT_READY, // server-internal: the first live call already blocks for the session
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guidance for `name`, or undefined when there is nothing useful to add — the tool IS advertised (so
|
|
3
|
+
* any error belongs to the call itself), or the name is not ours to explain.
|
|
4
|
+
*/
|
|
5
|
+
export declare function unadvertisedToolHelp(name: string, advertised: ReadonlySet<string>, known: ReadonlySet<string>): string | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to say when an agent calls a real Reticle tool that this profile does not advertise.
|
|
3
|
+
*
|
|
4
|
+
* The default profile advertises 16 of 46 tools. That is deliberate — every advertised tool's schema
|
|
5
|
+
* is re-sent on every turn, and the trim is most of why `hybrid` is cheap — and the other 30 stay
|
|
6
|
+
* fully callable through `reticle_run`, which `profile-reachability.test.ts` guards.
|
|
7
|
+
*
|
|
8
|
+
* The gap was in the ERROR. The MCP SDK answers `Tool <name> not found`, which is indistinguishable
|
|
9
|
+
* from "this tool does not exist", so an agent that trusts it stops trying. Reported from a real
|
|
10
|
+
* sweep of a Next app-router project: the first pass scored 25 failures that were nothing of the
|
|
11
|
+
* kind — every one of those tools worked through `reticle_run` seconds later.
|
|
12
|
+
*
|
|
13
|
+
* So: a name Reticle owns never comes back as "not found". It comes back with the call that works
|
|
14
|
+
* and the switch that makes it stop being necessary.
|
|
15
|
+
*/
|
|
16
|
+
import { ReticleTool } from './tool-names.js';
|
|
17
|
+
import { TOOL_SURFACE, TOOL_PROFILE_ENV } from './tool-surface.js';
|
|
18
|
+
import { mergedNameRedirect, mergedNameMessage } from './merged-name-redirect.js';
|
|
19
|
+
/**
|
|
20
|
+
* Guidance for `name`, or undefined when there is nothing useful to add — the tool IS advertised (so
|
|
21
|
+
* any error belongs to the call itself), or the name is not ours to explain.
|
|
22
|
+
*/
|
|
23
|
+
export function unadvertisedToolHelp(name, advertised, known) {
|
|
24
|
+
if (advertised.has(name))
|
|
25
|
+
return undefined;
|
|
26
|
+
// A name that MOVED gets the move, not a profile lecture — it is not un-advertised, it is gone.
|
|
27
|
+
const moved = mergedNameRedirect(name);
|
|
28
|
+
if (moved !== undefined)
|
|
29
|
+
return mergedNameMessage(name, moved);
|
|
30
|
+
if (!known.has(name))
|
|
31
|
+
return undefined;
|
|
32
|
+
return (`${name} exists and works, but is not advertised under this tool profile — the schemas for all ` +
|
|
33
|
+
`tools are re-sent every turn, so the default advertises a subset and keeps the rest one call ` +
|
|
34
|
+
`away. It is NOT missing: invoke it with ` +
|
|
35
|
+
`${ReticleTool.RUN} { tool: "${name}", args: { ... } }. ` +
|
|
36
|
+
`Call ${ReticleTool.TOOLS} { names: ["${name}"] } for its parameters. ` +
|
|
37
|
+
`If you need it repeatedly, set ${TOOL_PROFILE_ENV}=${TOOL_SURFACE.ALL} to advertise ` +
|
|
38
|
+
`every tool directly.`);
|
|
39
|
+
}
|
package/dist/trace.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verbose internal flow tracing, for people working ON Reticle.
|
|
3
|
+
*
|
|
4
|
+
* The gap it fills: the journal records what the AGENT did to the app, and telemetry records
|
|
5
|
+
* aggregates. Neither says which of OUR code a tool call went through, in what order, or where the
|
|
6
|
+
* time went — so every "why is this flow slow" and "which path produced this verdict" was
|
|
7
|
+
* reconstructed by hand from reading source. One line per stage answers both.
|
|
8
|
+
*
|
|
9
|
+
* Design, in order of what it cost to get wrong elsewhere in this repo:
|
|
10
|
+
*
|
|
11
|
+
* - OFF by default, and ~126ns per site when off (measured) — `span` calls the function and
|
|
12
|
+
* returns. A trace on every tool call is a real cost on the hot path, and the hot path is a
|
|
13
|
+
* verification loop of 50–200 calls.
|
|
14
|
+
* - ONE line per stage, emitted when it ENDS, carrying its own duration. A start line as well
|
|
15
|
+
* would double the volume to say something the end line already implies.
|
|
16
|
+
* - Correlated by a call id and a depth, carried in AsyncLocalStorage rather than threaded through
|
|
17
|
+
* every signature. Several agents are inside `runTool` at once — interleaving is the normal case
|
|
18
|
+
* — so without an id the output is a pile of unrelated lines, and instrumenting anything would
|
|
19
|
+
* mean changing its parameters, which is how instrumentation stops being added.
|
|
20
|
+
* - Through the existing `log()`, so it lands in the same stderr stream the daemon already
|
|
21
|
+
* redirects to `~/.reticle/daemon-<port>.log`. No new sink, no new file, nothing else to tail.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Whether tracing is on.
|
|
25
|
+
*
|
|
26
|
+
* Read per call, not cached at import, and the reason is `loadDotEnv`: the CLI populates
|
|
27
|
+
* `process.env` from the project's .env AFTER every module has been imported, so a value captured at
|
|
28
|
+
* import time would miss a `RETICLE_TRACE` set there — silently, which is the worst way for a
|
|
29
|
+
* diagnostic switch to fail. (The reason originally given here — "so it can be turned on while the
|
|
30
|
+
* daemon runs" — was wrong: nothing outside a process can change its environment.)
|
|
31
|
+
*
|
|
32
|
+
* Measured cost of reading it per call: 126ns per traced site when tracing is OFF, against a ~9ns
|
|
33
|
+
* bare call. With a handful of spans per tool call that is under a microsecond on a call that takes
|
|
34
|
+
* milliseconds — cheap enough to be worth the correctness, and not the literal zero this comment
|
|
35
|
+
* used to imply.
|
|
36
|
+
*/
|
|
37
|
+
export declare function traceEnabled(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Bind a callback to the call that is running NOW, so it reports inside that call when it fires later.
|
|
40
|
+
*
|
|
41
|
+
* `waitForPredicate` re-checks its predicate from a WebSocket event listener and an interval. Neither
|
|
42
|
+
* is in the awaited chain, so AsyncLocalStorage does not reach them and every re-check used to open a
|
|
43
|
+
* brand new call at depth 0. Measured on one healthy run: 165 spans across 60 callIds, 25 of them a
|
|
44
|
+
* `browser.command` with no matching `tool.handler`.
|
|
45
|
+
*
|
|
46
|
+
* That is not cosmetic. The documented signature of a HUNG tool call is exactly "a browser.command
|
|
47
|
+
* with no tool.handler for that callId, ever" — the invariant that located a real hang. A clean run
|
|
48
|
+
* emitting 23 of them makes the invariant unusable and hides the next hang in its own noise.
|
|
49
|
+
*
|
|
50
|
+
* Returns `fn` unchanged when there is no call to bind to, so a caller outside a span is not given a
|
|
51
|
+
* fabricated parent.
|
|
52
|
+
*/
|
|
53
|
+
export declare function bindSpanContext<A extends unknown[]>(fn: (...args: A) => void): (...args: A) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Run `fn` as a named stage, recording how long it took and how it sat inside the call around it.
|
|
56
|
+
*
|
|
57
|
+
* Wrap a stage worth a line in a bottleneck hunt — a session resolve, a browser round-trip, a
|
|
58
|
+
* predicate evaluation — not every function. The value of the trace is that reading it is faster
|
|
59
|
+
* than reading the code, and that stops being true somewhere around one line per statement.
|
|
60
|
+
*/
|
|
61
|
+
export declare function span<T>(name: string, fields: Record<string, unknown>, fn: () => Promise<T> | T): Promise<T>;
|
|
62
|
+
/**
|
|
63
|
+
* The synchronous form. Same line, same tree, same cost when off.
|
|
64
|
+
*
|
|
65
|
+
* It exists because `reticle init` — the flow every user hits before anything else, and the one
|
|
66
|
+
* whose slowness they experience personally — is synchronous end to end. An async-only span left
|
|
67
|
+
* exactly that flow untraceable.
|
|
68
|
+
*/
|
|
69
|
+
export declare function spanSync<T>(name: string, fields: Record<string, unknown>, fn: () => T): T;
|
package/dist/trace.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verbose internal flow tracing, for people working ON Reticle.
|
|
3
|
+
*
|
|
4
|
+
* The gap it fills: the journal records what the AGENT did to the app, and telemetry records
|
|
5
|
+
* aggregates. Neither says which of OUR code a tool call went through, in what order, or where the
|
|
6
|
+
* time went — so every "why is this flow slow" and "which path produced this verdict" was
|
|
7
|
+
* reconstructed by hand from reading source. One line per stage answers both.
|
|
8
|
+
*
|
|
9
|
+
* Design, in order of what it cost to get wrong elsewhere in this repo:
|
|
10
|
+
*
|
|
11
|
+
* - OFF by default, and ~126ns per site when off (measured) — `span` calls the function and
|
|
12
|
+
* returns. A trace on every tool call is a real cost on the hot path, and the hot path is a
|
|
13
|
+
* verification loop of 50–200 calls.
|
|
14
|
+
* - ONE line per stage, emitted when it ENDS, carrying its own duration. A start line as well
|
|
15
|
+
* would double the volume to say something the end line already implies.
|
|
16
|
+
* - Correlated by a call id and a depth, carried in AsyncLocalStorage rather than threaded through
|
|
17
|
+
* every signature. Several agents are inside `runTool` at once — interleaving is the normal case
|
|
18
|
+
* — so without an id the output is a pile of unrelated lines, and instrumenting anything would
|
|
19
|
+
* mean changing its parameters, which is how instrumentation stops being added.
|
|
20
|
+
* - Through the existing `log()`, so it lands in the same stderr stream the daemon already
|
|
21
|
+
* redirects to `~/.reticle/daemon-<port>.log`. No new sink, no new file, nothing else to tail.
|
|
22
|
+
*/
|
|
23
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
24
|
+
import { ReticleEnv } from '@reticlehq/core';
|
|
25
|
+
import { log } from './log.js';
|
|
26
|
+
/** The `event` field every trace line carries, so one `grep` separates it from milestone logs. */
|
|
27
|
+
const TRACE_EVENT = 'trace';
|
|
28
|
+
const ON_VALUES = new Set(['1', 'true', 'on', 'yes']);
|
|
29
|
+
const context = new AsyncLocalStorage();
|
|
30
|
+
/**
|
|
31
|
+
* Monotonic within a process, and PID-qualified so it stays unique across them.
|
|
32
|
+
*
|
|
33
|
+
* A bare counter was not enough: the e2e battery runs many daemons, and a restarted daemon starts
|
|
34
|
+
* over at 1, so `c7` named a different call in every process. Aggregating that trace silently
|
|
35
|
+
* merged unrelated calls into one — measured on the first real trace collected, before it had a
|
|
36
|
+
* chance to mislead anything.
|
|
37
|
+
*/
|
|
38
|
+
let nextCallId = 0;
|
|
39
|
+
const CALL_ID_PREFIX = `p${String(process.pid)}-c`;
|
|
40
|
+
/**
|
|
41
|
+
* Whether tracing is on.
|
|
42
|
+
*
|
|
43
|
+
* Read per call, not cached at import, and the reason is `loadDotEnv`: the CLI populates
|
|
44
|
+
* `process.env` from the project's .env AFTER every module has been imported, so a value captured at
|
|
45
|
+
* import time would miss a `RETICLE_TRACE` set there — silently, which is the worst way for a
|
|
46
|
+
* diagnostic switch to fail. (The reason originally given here — "so it can be turned on while the
|
|
47
|
+
* daemon runs" — was wrong: nothing outside a process can change its environment.)
|
|
48
|
+
*
|
|
49
|
+
* Measured cost of reading it per call: 126ns per traced site when tracing is OFF, against a ~9ns
|
|
50
|
+
* bare call. With a handful of spans per tool call that is under a microsecond on a call that takes
|
|
51
|
+
* milliseconds — cheap enough to be worth the correctness, and not the literal zero this comment
|
|
52
|
+
* used to imply.
|
|
53
|
+
*/
|
|
54
|
+
export function traceEnabled() {
|
|
55
|
+
const raw = process.env[ReticleEnv.TRACE];
|
|
56
|
+
return raw !== undefined && ON_VALUES.has(raw.trim().toLowerCase());
|
|
57
|
+
}
|
|
58
|
+
/** The scope a stage runs in, plus how to report it. Shared by the async and sync forms. */
|
|
59
|
+
function openSpan(name, fields) {
|
|
60
|
+
const parent = context.getStore();
|
|
61
|
+
const scope = parent === undefined
|
|
62
|
+
? { callId: `${CALL_ID_PREFIX}${String((nextCallId += 1))}`, depth: 0 }
|
|
63
|
+
: parent;
|
|
64
|
+
const depth = parent === undefined ? 0 : parent.depth;
|
|
65
|
+
const startedAt = Date.now();
|
|
66
|
+
return {
|
|
67
|
+
// The child scope is what nested spans see: same call, one level deeper.
|
|
68
|
+
child: { callId: scope.callId, depth: depth + 1 },
|
|
69
|
+
emit: (ok, extra) => {
|
|
70
|
+
log(TRACE_EVENT, {
|
|
71
|
+
span: name,
|
|
72
|
+
ms: Date.now() - startedAt,
|
|
73
|
+
depth,
|
|
74
|
+
callId: scope.callId,
|
|
75
|
+
ok,
|
|
76
|
+
...fields,
|
|
77
|
+
...extra,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Bind a callback to the call that is running NOW, so it reports inside that call when it fires later.
|
|
84
|
+
*
|
|
85
|
+
* `waitForPredicate` re-checks its predicate from a WebSocket event listener and an interval. Neither
|
|
86
|
+
* is in the awaited chain, so AsyncLocalStorage does not reach them and every re-check used to open a
|
|
87
|
+
* brand new call at depth 0. Measured on one healthy run: 165 spans across 60 callIds, 25 of them a
|
|
88
|
+
* `browser.command` with no matching `tool.handler`.
|
|
89
|
+
*
|
|
90
|
+
* That is not cosmetic. The documented signature of a HUNG tool call is exactly "a browser.command
|
|
91
|
+
* with no tool.handler for that callId, ever" — the invariant that located a real hang. A clean run
|
|
92
|
+
* emitting 23 of them makes the invariant unusable and hides the next hang in its own noise.
|
|
93
|
+
*
|
|
94
|
+
* Returns `fn` unchanged when there is no call to bind to, so a caller outside a span is not given a
|
|
95
|
+
* fabricated parent.
|
|
96
|
+
*/
|
|
97
|
+
export function bindSpanContext(fn) {
|
|
98
|
+
const captured = context.getStore();
|
|
99
|
+
if (captured === undefined)
|
|
100
|
+
return fn;
|
|
101
|
+
return (...args) => {
|
|
102
|
+
context.run(captured, () => fn(...args));
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** How much of a thrown value to keep. Enough to identify it, not enough to paste a stack into a log. */
|
|
106
|
+
const ERROR_CHARS = 300;
|
|
107
|
+
/**
|
|
108
|
+
* Run `fn` as a named stage, recording how long it took and how it sat inside the call around it.
|
|
109
|
+
*
|
|
110
|
+
* Wrap a stage worth a line in a bottleneck hunt — a session resolve, a browser round-trip, a
|
|
111
|
+
* predicate evaluation — not every function. The value of the trace is that reading it is faster
|
|
112
|
+
* than reading the code, and that stops being true somewhere around one line per statement.
|
|
113
|
+
*/
|
|
114
|
+
export async function span(name, fields, fn) {
|
|
115
|
+
if (!traceEnabled())
|
|
116
|
+
return fn();
|
|
117
|
+
const { child, emit } = openSpan(name, fields);
|
|
118
|
+
try {
|
|
119
|
+
const value = await context.run(child, async () => fn());
|
|
120
|
+
emit(true, {});
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// A stage that THREW is the one most worth having in the trace: without this line the trace
|
|
125
|
+
// shows a call that entered a stage and never left it, which reads as a hang.
|
|
126
|
+
emit(false, { error: String(err).slice(0, ERROR_CHARS) });
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The synchronous form. Same line, same tree, same cost when off.
|
|
132
|
+
*
|
|
133
|
+
* It exists because `reticle init` — the flow every user hits before anything else, and the one
|
|
134
|
+
* whose slowness they experience personally — is synchronous end to end. An async-only span left
|
|
135
|
+
* exactly that flow untraceable.
|
|
136
|
+
*/
|
|
137
|
+
export function spanSync(name, fields, fn) {
|
|
138
|
+
if (!traceEnabled())
|
|
139
|
+
return fn();
|
|
140
|
+
const { child, emit } = openSpan(name, fields);
|
|
141
|
+
try {
|
|
142
|
+
const value = context.run(child, fn);
|
|
143
|
+
emit(true, {});
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
emit(false, { error: String(err).slice(0, ERROR_CHARS) });
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Record that an agent was told about `version`. Called when the nudge is actually delivered. */
|
|
2
|
+
export declare function creditNudge(version: string, dir?: string, now?: number): void;
|
|
3
|
+
/** True when this exact version was offered to an agent recently enough to have caused the update. */
|
|
4
|
+
export declare function wasNudged(version: string, dir?: string, now?: number): boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Did the update nudge cause the update?
|
|
3
|
+
*
|
|
4
|
+
* `version_changed` says a version moved. It has never said why, so the only question worth asking
|
|
5
|
+
* about the nudge — does telling agents about a release make releases get installed — was
|
|
6
|
+
* unanswerable. That is not academic: 2.4.0 carried a fix for a connect defect affecting every Vite
|
|
7
|
+
* app and reached zero users, and nothing in the data could distinguish "the nudge never fired" from
|
|
8
|
+
* "the nudge fired and nobody acted on it". Those need opposite responses.
|
|
9
|
+
*
|
|
10
|
+
* The nudge is delivered by a DAEMON; `reticle update` runs in a different process. They cannot see
|
|
11
|
+
* each other in memory, so a marker file is the join. It holds the version that was offered and
|
|
12
|
+
* when — no identity, nothing about the machine, and it is overwritten rather than appended.
|
|
13
|
+
*
|
|
14
|
+
* Every function here swallows its own errors. This runs inside `reticle update`, which must finish
|
|
15
|
+
* whatever the telemetry believes.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
const DEFAULT_DIR = join(homedir(), '.reticle');
|
|
21
|
+
const FILE = 'update-nudge.json';
|
|
22
|
+
/**
|
|
23
|
+
* How long a nudge can plausibly have caused an update. A week is generous for "the agent mentioned
|
|
24
|
+
* it and the human got round to it"; beyond that the two are unrelated events that happen to share a
|
|
25
|
+
* version number.
|
|
26
|
+
*/
|
|
27
|
+
const CREDIT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
28
|
+
/** Record that an agent was told about `version`. Called when the nudge is actually delivered. */
|
|
29
|
+
export function creditNudge(version, dir = DEFAULT_DIR, now = Date.now()) {
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
const record = { offered: version, at: now };
|
|
33
|
+
writeFileSync(join(dir, FILE), JSON.stringify(record), 'utf8');
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* a metric must never break the nudge */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** True when this exact version was offered to an agent recently enough to have caused the update. */
|
|
40
|
+
export function wasNudged(version, dir = DEFAULT_DIR, now = Date.now()) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(readFileSync(join(dir, FILE), 'utf8'));
|
|
43
|
+
if (typeof parsed !== 'object' || null === parsed)
|
|
44
|
+
return false;
|
|
45
|
+
const record = parsed;
|
|
46
|
+
if (record.offered !== version || typeof record.at !== 'number')
|
|
47
|
+
return false;
|
|
48
|
+
return now - record.at <= CREDIT_WINDOW_MS;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of `reticle update` that was missing: bringing the APP's SDK to the same version as the
|
|
3
|
+
* daemon being installed.
|
|
4
|
+
*
|
|
5
|
+
* `reticle update` swapped the CLI and stopped. The app kept whatever `@reticlehq/react` it had, so
|
|
6
|
+
* the command whose job is to keep an install current was itself a way to create a version-skewed
|
|
7
|
+
* pair — the failure the HELLO skew check, the nudge and the pin all exist to prevent. The skew
|
|
8
|
+
* message even told people to run `reticle update` to fix an outdated SDK, which could not work.
|
|
9
|
+
*
|
|
10
|
+
* Pinned to an exact version for the same reason `init` pins: an unpinned `add` can resolve out of a
|
|
11
|
+
* stale registry cache and reinstall the skew being fixed. Measured once as pnpm taking 2.2.1 while
|
|
12
|
+
* npm took 2.3.0 in the next project over.
|
|
13
|
+
*/
|
|
14
|
+
import { type PackageManager } from '../init/detect.js';
|
|
15
|
+
/** The Reticle packages this project declares, from either dependency section. */
|
|
16
|
+
export declare function reticleDepsOf(pkgJson: unknown): string[];
|
|
17
|
+
/** The install to run, or null when this project has nothing of ours to sync. */
|
|
18
|
+
export declare function sdkSyncCommand(pm: PackageManager, packages: readonly string[], version: string): {
|
|
19
|
+
command: string;
|
|
20
|
+
args: string[];
|
|
21
|
+
} | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of `reticle update` that was missing: bringing the APP's SDK to the same version as the
|
|
3
|
+
* daemon being installed.
|
|
4
|
+
*
|
|
5
|
+
* `reticle update` swapped the CLI and stopped. The app kept whatever `@reticlehq/react` it had, so
|
|
6
|
+
* the command whose job is to keep an install current was itself a way to create a version-skewed
|
|
7
|
+
* pair — the failure the HELLO skew check, the nudge and the pin all exist to prevent. The skew
|
|
8
|
+
* message even told people to run `reticle update` to fix an outdated SDK, which could not work.
|
|
9
|
+
*
|
|
10
|
+
* Pinned to an exact version for the same reason `init` pins: an unpinned `add` can resolve out of a
|
|
11
|
+
* stale registry cache and reinstall the skew being fixed. Measured once as pnpm taking 2.2.1 while
|
|
12
|
+
* npm took 2.3.0 in the next project over.
|
|
13
|
+
*/
|
|
14
|
+
import { installCommandParts } from '../init/detect.js';
|
|
15
|
+
/** Everything we publish is scoped here. */
|
|
16
|
+
const RETICLE_SCOPE = '@reticlehq/';
|
|
17
|
+
/**
|
|
18
|
+
* The CLI. Never an app dependency: it carries the Node MCP server and `ws`, and putting it in a
|
|
19
|
+
* browser app's tree is what the retired `@reticlehq/core` umbrella got wrong. The other half of
|
|
20
|
+
* `reticle update` owns this one.
|
|
21
|
+
*/
|
|
22
|
+
const SERVER_PACKAGE = '@reticlehq/server';
|
|
23
|
+
function namesIn(section) {
|
|
24
|
+
if ('object' !== typeof section || null === section)
|
|
25
|
+
return [];
|
|
26
|
+
return Object.keys(section);
|
|
27
|
+
}
|
|
28
|
+
/** The Reticle packages this project declares, from either dependency section. */
|
|
29
|
+
export function reticleDepsOf(pkgJson) {
|
|
30
|
+
if ('object' !== typeof pkgJson || null === pkgJson)
|
|
31
|
+
return [];
|
|
32
|
+
const manifest = pkgJson;
|
|
33
|
+
const declared = [...namesIn(manifest['dependencies']), ...namesIn(manifest['devDependencies'])];
|
|
34
|
+
return [...new Set(declared)].filter((name) => name.startsWith(RETICLE_SCOPE) && name !== SERVER_PACKAGE);
|
|
35
|
+
}
|
|
36
|
+
/** The install to run, or null when this project has nothing of ours to sync. */
|
|
37
|
+
export function sdkSyncCommand(pm, packages, version) {
|
|
38
|
+
if (0 === packages.length)
|
|
39
|
+
return null;
|
|
40
|
+
const { command, args } = installCommandParts(pm, packages.map((p) => `${p}@${version}`));
|
|
41
|
+
return { command, args: [...args] };
|
|
42
|
+
}
|
|
@@ -3,7 +3,7 @@ import * as https from 'node:https';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { UpdateCheckIntervalMs } from '@reticlehq/core';
|
|
6
|
-
import { RETICLE_NPM_PACKAGE } from '../server-version.js';
|
|
6
|
+
import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
|
|
7
7
|
import { log } from '../log.js';
|
|
8
8
|
const RETICLE_HOME = join(homedir(), '.reticle');
|
|
9
9
|
const MANIFEST_PATH = join(RETICLE_HOME, 'update-manifest.json');
|
|
@@ -20,12 +20,12 @@ function validateNpmInfo(info) {
|
|
|
20
20
|
if (typeof info.version !== 'string' || !VERSION_RE.test(info.version)) {
|
|
21
21
|
throw new Error('npm registry returned an implausible version');
|
|
22
22
|
}
|
|
23
|
-
const changelog = typeof info.reticle?.changelog
|
|
23
|
+
const changelog = 'string' === typeof info.reticle?.changelog
|
|
24
24
|
? info.reticle.changelog.slice(0, MAX_MANIFEST_TEXT)
|
|
25
25
|
: undefined;
|
|
26
26
|
const breakingChanges = Array.isArray(info.reticle?.breakingChanges)
|
|
27
27
|
? info.reticle.breakingChanges
|
|
28
|
-
.filter((x) => typeof x
|
|
28
|
+
.filter((x) => 'string' === typeof x)
|
|
29
29
|
.slice(0, 100)
|
|
30
30
|
.map((s) => s.slice(0, MAX_MANIFEST_TEXT))
|
|
31
31
|
: undefined;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Is `candidate` a strictly NEWER release than `current`?
|
|
3
|
+
*
|
|
4
|
+
* Replaces a plain `!==`, which told anyone on a version newer than the published one — a
|
|
5
|
+
* prerelease, a local build, a rollback in progress — to "update" to something older. A nudge that
|
|
6
|
+
* prompts a downgrade is one people learn to ignore, and this is the mechanism the entire adoption
|
|
7
|
+
* story rests on. Numeric per segment, so 2.10.0 correctly beats 2.9.0 (string order does not), and
|
|
8
|
+
* a bare release beats its own prerelease.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isNewerVersion(candidate: string, current: string): boolean;
|
|
11
|
+
interface UpdateNudge {
|
|
2
12
|
/** The version available, so the agent can tell the human what they would be moving to. */
|
|
3
13
|
latestVersion: string;
|
|
4
14
|
currentVersion: string;
|
|
@@ -7,14 +17,61 @@ export interface UpdateNudge {
|
|
|
7
17
|
action: string;
|
|
8
18
|
}
|
|
9
19
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
20
|
+
* Describe the release, not just its number.
|
|
21
|
+
*
|
|
22
|
+
* `packages/server/package.json` carries `reticle.changelog` and `reticle.breakingChanges`, and
|
|
23
|
+
* `update-checker` parses both into the manifest — where they stopped. This function named two
|
|
24
|
+
* versions and nothing else, so both fields were written, shipped, parsed and discarded. Latent
|
|
25
|
+
* metadata nobody ever printed.
|
|
26
|
+
*
|
|
27
|
+
* Survivable while releases are additive; actively harmful for one that is not. An agent told only
|
|
28
|
+
* "2.4.1 → 2.5.0" runs `reticle update` mid-task and discovers a retired environment variable and
|
|
29
|
+
* six newly-strict parameters by breaking on them.
|
|
30
|
+
*
|
|
31
|
+
* Bounded on purpose: this rides on a tool result every turn until it is delivered, so a long
|
|
32
|
+
* changelog is a per-turn tax. The count of anything elided is stated rather than silently dropped.
|
|
13
33
|
*/
|
|
34
|
+
export declare function buildNudge(latestVersion: string, currentVersion: string, release?: {
|
|
35
|
+
changelog?: string;
|
|
36
|
+
breakingChanges?: string[];
|
|
37
|
+
}): UpdateNudge;
|
|
14
38
|
export declare function startUpdateCheck(now?: () => number): void;
|
|
39
|
+
/**
|
|
40
|
+
* What `reticle update` should install, or undefined when it should do nothing.
|
|
41
|
+
*
|
|
42
|
+
* `handleUpdate` gated on `manifest.updateAvailable`, a plain `latest !== current`, so whenever the
|
|
43
|
+
* registry's latest was OLDER than the running build — a prerelease, a local build, a rollback in
|
|
44
|
+
* progress, a stale npx cache — it announced the move and then installed the downgrade. Reported by
|
|
45
|
+
* a real user on 2026-08-06: "both the `update_available` banner and `reticle update` report the
|
|
46
|
+
* current and target versions swapped, so an upgrade is described as a downgrade."
|
|
47
|
+
*/
|
|
48
|
+
export declare function updateTarget(manifest: {
|
|
49
|
+
updateAvailable?: boolean;
|
|
50
|
+
latestVersion?: string;
|
|
51
|
+
}, currentVersion?: string): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Arm from an already-cached answer, synchronously, at daemon boot.
|
|
54
|
+
*
|
|
55
|
+
* The network check fires 8s after boot and the nudge is delivered by riding a tool result — but
|
|
56
|
+
* half the sessions that use Reticle at all make exactly ONE tool call, usually in the first
|
|
57
|
+
* seconds. The check had not finished, so the single chance to tell them was gone, and the
|
|
58
|
+
* population most in need of the update is precisely the one that never heard about it. Yesterday's
|
|
59
|
+
* answer is already on disk; use it now and let the network refresh behind it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function armUpdateNudgeFrom(cached: {
|
|
62
|
+
latestVersion?: string;
|
|
63
|
+
updateAvailable?: boolean;
|
|
64
|
+
changelog?: string;
|
|
65
|
+
breakingChanges?: string[];
|
|
66
|
+
} | null, currentVersion?: string): void;
|
|
15
67
|
/** The nudge, once. Returns undefined afterwards so a long session is told exactly one time. */
|
|
16
68
|
export declare function takeUpdateNudge(): UpdateNudge | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* The newer version, if the cached answer says there is one — WITHOUT consuming the agent's
|
|
71
|
+
* one-shot nudge. `reticle status` is a human reading a terminal; the two channels are separate
|
|
72
|
+
* audiences and must not steal each other's message.
|
|
73
|
+
*/
|
|
74
|
+
export declare function availableUpdate(currentVersion?: string): string | undefined;
|
|
17
75
|
/** Tests only — drop the module state so each case starts clean. */
|
|
18
76
|
export declare function resetUpdateNudge(): void;
|
|
19
|
-
|
|
20
|
-
export declare function setPendingUpdate(nudge: UpdateNudge | undefined): void;
|
|
77
|
+
export {};
|