@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
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { spawn } from 'node:child_process';
|
|
18
18
|
import { randomUUID, createHash } from 'node:crypto';
|
|
19
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'node:fs';
|
|
19
|
+
import { appendFileSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmSync, } from 'node:fs';
|
|
20
20
|
import { homedir, platform } from 'node:os';
|
|
21
|
-
import { join } from 'node:path';
|
|
22
|
-
import { TelemetryEventKind, TELEMETRY_EVENT_VERSION, ProjectIdSource, } from '@reticlehq/core';
|
|
23
|
-
import { SERVER_VERSION } from '../server-version.js';
|
|
21
|
+
import { dirname, join } from 'node:path';
|
|
22
|
+
import { TelemetryEventKind, TELEMETRY_EVENT_VERSION, ProjectIdSource, isSessionScoped, } from '@reticlehq/core';
|
|
23
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
24
24
|
const RETICLE_DIR = join(homedir(), '.reticle');
|
|
25
25
|
const ID_FILE = join(RETICLE_DIR, 'telemetry-id');
|
|
26
26
|
const NOTICE_FILE = join(RETICLE_DIR, 'telemetry-notice-shown');
|
|
@@ -43,6 +43,22 @@ const POSTHOG_KEY = 'phc_q4yCxKMXHfWQ43VAz4HxJXZFLqFi4nBbg5v3avemggYo';
|
|
|
43
43
|
*/
|
|
44
44
|
const POSTHOG_PERSONLESS = { $process_person_profile: false };
|
|
45
45
|
const SEND_TIMEOUT_MS = 2000;
|
|
46
|
+
/**
|
|
47
|
+
* Feedback gets its own, much larger budget — and a retry.
|
|
48
|
+
*
|
|
49
|
+
* 2s + drop is right for a usage counter: the metric is worthless if it costs the user latency, and
|
|
50
|
+
* losing one changes nothing. Feedback is the opposite on both counts. It is the only qualitative
|
|
51
|
+
* channel the product has, it carries an agent's entire root-cause analysis, and losing one loses
|
|
52
|
+
* work that cannot be reconstructed.
|
|
53
|
+
*
|
|
54
|
+
* Measured to the collector with a WARM DNS cache: dns 0.003s, connect 0.233s, tls 0.467s, total
|
|
55
|
+
* 0.694s — a third of the old budget gone before any payload moves, on the good path. A fresh
|
|
56
|
+
* short-lived CLI process pays cold DNS and a cold route on top, which is exactly the case that
|
|
57
|
+
* failed in the field and then succeeded on retry from a warmer process.
|
|
58
|
+
*/
|
|
59
|
+
export const FEEDBACK_TIMEOUT_MS = 15_000;
|
|
60
|
+
export const FEEDBACK_RETRIES = 1;
|
|
61
|
+
export const FEEDBACK_RETRY_BACKOFF_MS = 750;
|
|
46
62
|
/**
|
|
47
63
|
* The sender a `detach: true` emit runs in a disowned child (argv: [url, body]) — an in-process fetch
|
|
48
64
|
* keeps Node's event loop alive until the POST finishes, which taxed every short-lived CLI command
|
|
@@ -59,6 +75,7 @@ const Env = {
|
|
|
59
75
|
DISABLE: 'RETICLE_TELEMETRY', // "0" / "false" / "off" → disabled
|
|
60
76
|
DO_NOT_TRACK: 'DO_NOT_TRACK', // the cross-tool opt-out convention (any truthy value)
|
|
61
77
|
URL: 'RETICLE_TELEMETRY_URL', // override the PostHog host (EU cloud / self-hosted)
|
|
78
|
+
FILE: 'RETICLE_TELEMETRY_FILE', // record to a local JSONL file and send NOTHING — see the sink note
|
|
62
79
|
KEY: 'RETICLE_TELEMETRY_KEY', // override the PostHog project key
|
|
63
80
|
CI: 'CI', // presence marks a CI environment
|
|
64
81
|
VITEST: 'VITEST', // set by vitest — unit tests must never phone home
|
|
@@ -67,6 +84,11 @@ const isDisabled = (env, cwd = process.cwd()) => {
|
|
|
67
84
|
const off = new Set(['0', 'false', 'off', 'no']);
|
|
68
85
|
if (off.has((env[Env.DISABLE] ?? '').toLowerCase()))
|
|
69
86
|
return true;
|
|
87
|
+
// Recording to a local file is not "phoning home", and the guards below exist to stop us phoning
|
|
88
|
+
// home. Chief among them is the source-checkout rule — which is exactly where a release sweep is
|
|
89
|
+
// driven from, so a sink that inherited it would record nothing and look like it had worked.
|
|
90
|
+
if ((env[Env.FILE] ?? '') !== '')
|
|
91
|
+
return false;
|
|
70
92
|
if (env[Env.VITEST] !== undefined)
|
|
71
93
|
return true; // a test run is not a user
|
|
72
94
|
// Developing Reticle is not using Reticle. The `.env` carrying RETICLE_TELEMETRY=0 is gitignored,
|
|
@@ -75,8 +97,21 @@ const isDisabled = (env, cwd = process.cwd()) => {
|
|
|
75
97
|
if (isReticleSourceCheckout(cwd))
|
|
76
98
|
return true;
|
|
77
99
|
const dnt = env[Env.DO_NOT_TRACK];
|
|
78
|
-
return typeof dnt
|
|
100
|
+
return 'string' === typeof dnt && dnt !== '' && dnt !== '0';
|
|
79
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* Would telemetry be on here if we ignored the source-checkout guard alone?
|
|
104
|
+
*
|
|
105
|
+
* That guard exists so a fresh clone does not phone home on a contributor's first `reticle serve` —
|
|
106
|
+
* a rule about PASSIVE collection. Feedback is never passive: somebody typed it. Applying the guard
|
|
107
|
+
* to it means anyone dogfooding from their own checkout files nothing and is told "not sent, unknown
|
|
108
|
+
* reason", which silently loses every report from exactly the people most likely to write good ones.
|
|
109
|
+
* Reported from the field, and the reporter only escaped it by happening to run from another repo.
|
|
110
|
+
*
|
|
111
|
+
* Every explicit opt-out still applies — RETICLE_TELEMETRY=0, DO_NOT_TRACK, the persisted opt-out
|
|
112
|
+
* file, and a test run. This lifts one implicit guard for one user-initiated event kind.
|
|
113
|
+
*/
|
|
114
|
+
const onlyBlockedBySourceCheckout = (env, cwd) => isDisabled(env, cwd) && !isDisabled(env, '/');
|
|
80
115
|
/** Read (or mint-and-persist) the anonymous machine id. `firstRun` is true the run that created it. */
|
|
81
116
|
const resolveIdentity = () => {
|
|
82
117
|
try {
|
|
@@ -109,17 +144,40 @@ const resolveIdentity = () => {
|
|
|
109
144
|
* credentials, `.git` suffix and case removed) so `git@github.com:acme/web.git` and
|
|
110
145
|
* `https://github.com/Acme/web` are recognized as the same project rather than two.
|
|
111
146
|
*/
|
|
112
|
-
const projectFingerprint = (cwd) => {
|
|
113
|
-
const { origin } = gitFacts(cwd);
|
|
147
|
+
export const projectFingerprint = (cwd) => {
|
|
148
|
+
const { origin, root } = gitFacts(cwd);
|
|
149
|
+
// Ordered by stability, not by preference: an origin is comparable across machines, a repo root is
|
|
150
|
+
// stable across every directory inside one machine's checkout, a package.json root is the same for
|
|
151
|
+
// a non-git project — and the raw cwd is stable across nothing, which is why it is last. See
|
|
152
|
+
// `project-identity.test.ts` for what a wrong order costs.
|
|
153
|
+
const { key, source } = origin !== undefined
|
|
154
|
+
? { key: origin, source: ProjectIdSource.GIT_ORIGIN }
|
|
155
|
+
: root !== undefined
|
|
156
|
+
? { key: root, source: ProjectIdSource.GIT_ROOT }
|
|
157
|
+
: packageRoot(cwd) !== null
|
|
158
|
+
? { key: packageRoot(cwd) ?? cwd, source: ProjectIdSource.PACKAGE_ROOT }
|
|
159
|
+
: { key: cwd, source: ProjectIdSource.CWD };
|
|
114
160
|
return {
|
|
115
|
-
projectId: createHash('sha256')
|
|
116
|
-
.update(origin ?? cwd)
|
|
117
|
-
.digest('hex')
|
|
118
|
-
.slice(0, 32),
|
|
161
|
+
projectId: createHash('sha256').update(key).digest('hex').slice(0, 32),
|
|
119
162
|
// Reported alongside so the analytics knows whether this id is comparable to another machine's.
|
|
120
|
-
source
|
|
163
|
+
source,
|
|
121
164
|
};
|
|
122
165
|
};
|
|
166
|
+
/** Nearest ancestor holding a package.json — the project boundary when there is no git. */
|
|
167
|
+
const packageRoot = (cwd) => {
|
|
168
|
+
let dir = cwd;
|
|
169
|
+
// Same bound as the git walk: deep monorepos are well inside 20, and an unbounded loop on a
|
|
170
|
+
// pathological path would hang the CLI's startup.
|
|
171
|
+
for (let depth = 0; depth < 20; depth += 1) {
|
|
172
|
+
if (existsSync(join(dir, 'package.json')))
|
|
173
|
+
return dir;
|
|
174
|
+
const parent = dirname(dir);
|
|
175
|
+
if (parent === dir)
|
|
176
|
+
return null;
|
|
177
|
+
dir = parent;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
};
|
|
123
181
|
/** Print the opt-out notice exactly once per machine (honest disclosure, the OSS-trust bar). */
|
|
124
182
|
const showNoticeOnce = () => {
|
|
125
183
|
try {
|
|
@@ -172,7 +230,8 @@ export const setTelemetryEnabled = (enabled, dir = RETICLE_DIR) => {
|
|
|
172
230
|
mkdirSync(dir, { recursive: true });
|
|
173
231
|
writeFileSync(optOutFile, '1', 'utf8');
|
|
174
232
|
};
|
|
175
|
-
|
|
233
|
+
// A disabled emitter delivers nothing, and says so — `false` is the honest answer, not a courtesy.
|
|
234
|
+
const NOOP = { emit: () => Promise.resolve(false), enabled: false, firstRun: false };
|
|
176
235
|
/**
|
|
177
236
|
* Build the telemetry emitter for this process. Resolves identity + opt-out ONCE; each `emit` builds a
|
|
178
237
|
* fully-validated core event, maps it into PostHog capture shape, and fires a best-effort POST.
|
|
@@ -182,7 +241,9 @@ const NOOP = { emit: async () => { }, enabled: false, firstRun: false };
|
|
|
182
241
|
export const createTelemetry = (opts) => {
|
|
183
242
|
const env = opts.env ?? process.env;
|
|
184
243
|
const cwd = opts.cwd ?? process.cwd();
|
|
185
|
-
|
|
244
|
+
// A source checkout silences metrics but must not silence FEEDBACK — see onlyBlockedBySourceCheckout.
|
|
245
|
+
const feedbackOnly = onlyBlockedBySourceCheckout(env, cwd);
|
|
246
|
+
if (isDisabled(env, cwd) && !feedbackOnly)
|
|
186
247
|
return NOOP;
|
|
187
248
|
try {
|
|
188
249
|
if (existsSync(OPT_OUT_FILE))
|
|
@@ -192,13 +253,15 @@ export const createTelemetry = (opts) => {
|
|
|
192
253
|
/* unreadable home dir — fall through; the env-var opt-outs above still apply */
|
|
193
254
|
}
|
|
194
255
|
const apiKey = env[Env.KEY] ?? POSTHOG_KEY;
|
|
195
|
-
if (
|
|
256
|
+
if ('' === apiKey)
|
|
196
257
|
return NOOP; // no key baked in (dev/test build) — nowhere to send, stay silent
|
|
197
258
|
const now = opts.now ?? (() => Date.now());
|
|
198
259
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
199
260
|
const url = `${(env[Env.URL] ?? DEFAULT_URL).replace(/\/+$/, '')}${TELEMETRY_PATH}`;
|
|
200
261
|
const ci = env[Env.CI] !== undefined && env[Env.CI] !== '';
|
|
201
262
|
const os = platform();
|
|
263
|
+
// Negated so it reads the way people say it: UTC+2 is +120, not -120.
|
|
264
|
+
const tzOffsetMin = -new Date().getTimezoneOffset();
|
|
202
265
|
const { anonymousId, firstRun } = resolveIdentity();
|
|
203
266
|
/**
|
|
204
267
|
* One id per PROCESS, minted in memory and never persisted — which is exactly right: a daemon run
|
|
@@ -212,7 +275,22 @@ export const createTelemetry = (opts) => {
|
|
|
212
275
|
((command, args) => {
|
|
213
276
|
spawn(command, args, { detached: true, stdio: 'ignore' }).unref();
|
|
214
277
|
});
|
|
278
|
+
/**
|
|
279
|
+
* Returns whether the event was actually DELIVERED.
|
|
280
|
+
*
|
|
281
|
+
* Every caller but one ignores this and should: a lost metric must never surface to a user. The
|
|
282
|
+
* exception is `reticle_feedback`, which hands a receipt back to an agent — `sent: true` there was
|
|
283
|
+
* unconditional, so a DNS miss or a 4xx both read as "filed", and the only qualitative channel the
|
|
284
|
+
* product has could fail silently while telling the reporter it had worked.
|
|
285
|
+
*
|
|
286
|
+
* A detached send reports false: it is handed to a disowned child precisely so the caller does not
|
|
287
|
+
* wait, so its outcome is genuinely unknown and claiming success would be the same lie.
|
|
288
|
+
*/
|
|
215
289
|
const emit = async (kind, extra) => {
|
|
290
|
+
// In a Reticle source checkout the emitter carries FEEDBACK and nothing else: somebody typed the
|
|
291
|
+
// feedback, and no metric is worth phoning home from a contributor's clone.
|
|
292
|
+
if (feedbackOnly && TelemetryEventKind.FEEDBACK_SUBMITTED !== kind)
|
|
293
|
+
return false;
|
|
216
294
|
const event = {
|
|
217
295
|
v: TELEMETRY_EVENT_VERSION,
|
|
218
296
|
anonymousId,
|
|
@@ -223,6 +301,7 @@ export const createTelemetry = (opts) => {
|
|
|
223
301
|
version: opts.version,
|
|
224
302
|
ci,
|
|
225
303
|
os,
|
|
304
|
+
tzOffsetMin,
|
|
226
305
|
projectIdSource,
|
|
227
306
|
...(extra?.actor !== undefined ? { actor: extra.actor } : {}),
|
|
228
307
|
...(extra?.command !== undefined ? { command: extra.command } : {}),
|
|
@@ -237,19 +316,22 @@ export const createTelemetry = (opts) => {
|
|
|
237
316
|
...(extra?.connection !== undefined ? { connection: extra.connection } : {}),
|
|
238
317
|
...(extra?.init !== undefined ? { init: extra.init } : {}),
|
|
239
318
|
...(extra?.bug !== undefined ? { bug: extra.bug } : {}),
|
|
319
|
+
...(extra?.outage !== undefined ? { outage: extra.outage } : {}),
|
|
240
320
|
};
|
|
241
321
|
// Map the core contract onto PostHog's capture shape: id/name/time move up, the rest are properties.
|
|
242
322
|
// The feedback body is FLATTENED into `feedback_*` properties rather than sent as a nested object:
|
|
243
323
|
// PostHog filters and breakdowns operate on top-level properties, so a nested `feedback.stack`
|
|
244
324
|
// would be invisible to the exact "which stacks report the most bugs" question this exists to
|
|
245
325
|
// answer. One prefix keeps them grouped in the UI without a nested path.
|
|
246
|
-
const { anonymousId: distinctId, event: name, ts, sessionId: eventSessionId, feedback, session, project, verification, versionChange, crash, identity, connection, init, bug, ...rest } = event;
|
|
326
|
+
const { anonymousId: distinctId, event: name, ts, sessionId: eventSessionId, feedback, session, project, verification, versionChange, crash, identity, connection, init, bug, outage, ...rest } = event;
|
|
247
327
|
// `$session_id` is PostHog's OWN session property, so sending ours under that name lights up
|
|
248
328
|
// its built-in session views and funnels for free rather than requiring custom HogQL everywhere.
|
|
329
|
+
// `sessionId` rides only on events that happened inside a daemon run. A one-shot CLI command
|
|
330
|
+
// mints a per-process id that joins to nothing and inflates every session count — see
|
|
331
|
+
// isSessionScoped for the measurement.
|
|
249
332
|
const properties = {
|
|
250
333
|
...rest,
|
|
251
|
-
sessionId: eventSessionId,
|
|
252
|
-
$session_id: eventSessionId,
|
|
334
|
+
...(isSessionScoped(name) ? { sessionId: eventSessionId, $session_id: eventSessionId } : {}),
|
|
253
335
|
};
|
|
254
336
|
// Each block is flattened under its own prefix rather than nested. PostHog filters, breakdowns and
|
|
255
337
|
// insight builders all operate on TOP-LEVEL properties, so a nested `project.stack` is invisible to
|
|
@@ -269,37 +351,68 @@ export const createTelemetry = (opts) => {
|
|
|
269
351
|
connection,
|
|
270
352
|
init,
|
|
271
353
|
bug,
|
|
354
|
+
outage,
|
|
272
355
|
};
|
|
273
356
|
for (const [prefix, block] of Object.entries(blocks)) {
|
|
274
357
|
for (const [key, value] of Object.entries(block ?? {})) {
|
|
275
358
|
properties[`${prefix}_${key}`] = value;
|
|
276
359
|
}
|
|
277
360
|
}
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
361
|
+
const capture = {
|
|
362
|
+
event: name,
|
|
363
|
+
distinct_id: distinctId,
|
|
364
|
+
timestamp: new Date(ts).toISOString(),
|
|
365
|
+
properties: { ...properties, ...POSTHOG_PERSONLESS },
|
|
366
|
+
};
|
|
367
|
+
const body = JSON.stringify({ api_key: apiKey, batch: [capture] });
|
|
368
|
+
const filePath = env[Env.FILE];
|
|
369
|
+
if (filePath !== undefined && filePath !== '') {
|
|
370
|
+
// The SAME payload the wire would have carried, built by the same code path and redacted by
|
|
371
|
+
// the same rules — so what a run records is what a user would have sent. One JSON object per
|
|
372
|
+
// line, appended, so a whole sweep lands in one file in order.
|
|
373
|
+
try {
|
|
374
|
+
appendFileSync(filePath, `${JSON.stringify(capture)}\n`, 'utf8');
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false; // a sink that cannot write must not take the daemon down
|
|
379
|
+
}
|
|
380
|
+
}
|
|
289
381
|
try {
|
|
290
|
-
if (extra?.detach
|
|
382
|
+
if (true === extra?.detach && opts.fetchImpl === undefined) {
|
|
291
383
|
spawnDetached(process.execPath, ['-e', DETACHED_SEND_SCRIPT, url, body]);
|
|
292
|
-
return;
|
|
384
|
+
return false; // handed off, outcome unknowable from here
|
|
385
|
+
}
|
|
386
|
+
// Feedback is slower and retried; everything else keeps the fire-and-forget budget.
|
|
387
|
+
const isFeedback = TelemetryEventKind.FEEDBACK_SUBMITTED === kind;
|
|
388
|
+
const timeoutMs = isFeedback ? FEEDBACK_TIMEOUT_MS : SEND_TIMEOUT_MS;
|
|
389
|
+
const attempts = 1 + (isFeedback ? FEEDBACK_RETRIES : 0);
|
|
390
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
391
|
+
if (0 < attempt) {
|
|
392
|
+
await new Promise((r) => setTimeout(r, FEEDBACK_RETRY_BACKOFF_MS * attempt));
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const response = await doFetch(url, {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
headers: { 'content-type': 'application/json' },
|
|
398
|
+
body,
|
|
399
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
400
|
+
});
|
|
401
|
+
// A 4xx is a rejected payload, not a delivery. It must not read as filed. It is also not
|
|
402
|
+
// worth retrying — the payload will be rejected again — so only a THROW (timeout, DNS,
|
|
403
|
+
// connection reset) goes round again.
|
|
404
|
+
return response.ok !== false;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
if (attempt === attempts - 1)
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
293
410
|
}
|
|
294
|
-
|
|
295
|
-
method: 'POST',
|
|
296
|
-
headers: { 'content-type': 'application/json' },
|
|
297
|
-
body,
|
|
298
|
-
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
299
|
-
});
|
|
411
|
+
return false;
|
|
300
412
|
}
|
|
301
413
|
catch {
|
|
302
414
|
/* best-effort: a lost metric must never surface to the user */
|
|
415
|
+
return false;
|
|
303
416
|
}
|
|
304
417
|
};
|
|
305
418
|
return { emit, enabled: true, firstRun };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tool results count as a completed verification, as one pure rule.
|
|
3
|
+
*
|
|
4
|
+
* It used to live inline in `runTool` and read `status === 'pass'`, mapping everything else to
|
|
5
|
+
* "no verdict". That was wrong in both directions at once, and the shape of the error is visible in
|
|
6
|
+
* the production data:
|
|
7
|
+
*
|
|
8
|
+
* - a FAILING suite emitted nothing, so `verification_completed` only ever fired on greens while
|
|
9
|
+
* `bugsInResult` fired on the reds — bugs with no verifications to divide them by;
|
|
10
|
+
* - an EMPTY suite ("all 0 flows pass") emitted `verified: yes, passed: true`.
|
|
11
|
+
*
|
|
12
|
+
* Extracted because a rule this easy to get wrong, feeding the one number shown to investors, should
|
|
13
|
+
* be readable and testable on its own rather than inferred from a ternary inside a dispatcher.
|
|
14
|
+
*/
|
|
15
|
+
import { type BrowserBrand, type Verification } from '@reticlehq/core';
|
|
16
|
+
/**
|
|
17
|
+
* The verification payload for a result, or undefined when nothing was verified.
|
|
18
|
+
*
|
|
19
|
+
* `falseGreenCaught` is the one that matters: the assertion PASSED and Reticle still refused to call
|
|
20
|
+
* it verified. That is the product's thesis reduced to a boolean.
|
|
21
|
+
*/
|
|
22
|
+
export declare function verificationOf(toolName: string, result: Record<string, unknown>, durationMs: number, brand?: BrowserBrand): Verification | undefined;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tool results count as a completed verification, as one pure rule.
|
|
3
|
+
*
|
|
4
|
+
* It used to live inline in `runTool` and read `status === 'pass'`, mapping everything else to
|
|
5
|
+
* "no verdict". That was wrong in both directions at once, and the shape of the error is visible in
|
|
6
|
+
* the production data:
|
|
7
|
+
*
|
|
8
|
+
* - a FAILING suite emitted nothing, so `verification_completed` only ever fired on greens while
|
|
9
|
+
* `bugsInResult` fired on the reds — bugs with no verifications to divide them by;
|
|
10
|
+
* - an EMPTY suite ("all 0 flows pass") emitted `verified: yes, passed: true`.
|
|
11
|
+
*
|
|
12
|
+
* Extracted because a rule this easy to get wrong, feeding the one number shown to investors, should
|
|
13
|
+
* be readable and testable on its own rather than inferred from a ternary inside a dispatcher.
|
|
14
|
+
*/
|
|
15
|
+
import { VerifiedReason } from '@reticlehq/core';
|
|
16
|
+
import { VERIFICATION_TOOLS } from '../tools/feedback-tools.js';
|
|
17
|
+
import { getBrowserMode } from './browser-mode.js';
|
|
18
|
+
/**
|
|
19
|
+
* The deciding clause, read off the result `decideVerified` wrote it onto.
|
|
20
|
+
*
|
|
21
|
+
* Narrowed against core's own list rather than forwarded: a result is an untyped record here, and a
|
|
22
|
+
* value that is not a member is not a reason — absent beats a string nobody can group by. Derived
|
|
23
|
+
* from the enum, never re-listed, so a new clause is covered the day it exists.
|
|
24
|
+
*/
|
|
25
|
+
const REASONS = new Set(Object.values(VerifiedReason));
|
|
26
|
+
function reasonOf(result) {
|
|
27
|
+
const raw = result['verifiedReason'];
|
|
28
|
+
return 'string' === typeof raw && REASONS.has(raw) ? raw : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Suite statuses. `unverifiable` is a suite that ran but proved nothing — including an empty one. */
|
|
31
|
+
const SuiteStatus = {
|
|
32
|
+
PASS: 'pass',
|
|
33
|
+
FAIL: 'fail',
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The verification payload for a result, or undefined when nothing was verified.
|
|
37
|
+
*
|
|
38
|
+
* `falseGreenCaught` is the one that matters: the assertion PASSED and Reticle still refused to call
|
|
39
|
+
* it verified. That is the product's thesis reduced to a boolean.
|
|
40
|
+
*/
|
|
41
|
+
export function verificationOf(toolName, result, durationMs, brand) {
|
|
42
|
+
if (!VERIFICATION_TOOLS.has(toolName))
|
|
43
|
+
return undefined;
|
|
44
|
+
// A paused session REFUSES the call — nothing driven, nothing asserted. The refusal carries a
|
|
45
|
+
// verdict field so the agent never reads undefined off it, and that field must not be mistaken
|
|
46
|
+
// here for work that happened.
|
|
47
|
+
if (true === result['paused'])
|
|
48
|
+
return undefined;
|
|
49
|
+
const verified = 'string' === typeof result['verified'] ? result['verified'] : undefined;
|
|
50
|
+
// flow_verify reports `status: pass|fail|unverifiable`; assert reports a boolean `pass`. Accept
|
|
51
|
+
// either shape so the whole family is covered without normalizing four tools' contracts for a
|
|
52
|
+
// metric's convenience — but a status that is neither pass nor fail is NOT a verdict, and an empty
|
|
53
|
+
// suite reports exactly that.
|
|
54
|
+
const status = result['status'];
|
|
55
|
+
const passed = 'boolean' === typeof result['pass']
|
|
56
|
+
? result['pass']
|
|
57
|
+
: status === SuiteStatus.PASS
|
|
58
|
+
? true
|
|
59
|
+
: status === SuiteStatus.FAIL
|
|
60
|
+
? false
|
|
61
|
+
: undefined;
|
|
62
|
+
if (verified === undefined && passed === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
const reason = reasonOf(result);
|
|
65
|
+
return {
|
|
66
|
+
via: toolName,
|
|
67
|
+
verified: verified ?? (true === passed ? 'yes' : 'no'),
|
|
68
|
+
passed: passed ?? 'yes' === verified,
|
|
69
|
+
falseGreenCaught: true === passed && 'no' === verified,
|
|
70
|
+
durationMs,
|
|
71
|
+
// Headless CI, a human watching, or somebody's own dev server — three different products behind
|
|
72
|
+
// one number until this was recorded.
|
|
73
|
+
browser: getBrowserMode(),
|
|
74
|
+
// WHICH browser, when the page said. `attached` is the common case and says only that Reticle
|
|
75
|
+
// launched nothing; the brand is the difference between "somebody's own browser" and knowing it
|
|
76
|
+
// was Edge. Omitted when unreported — a desktop webview and an older SDK both have no answer,
|
|
77
|
+
// and "unknown" would be a value on a dashboard rather than a gap.
|
|
78
|
+
...(brand !== undefined ? { brand } : {}),
|
|
79
|
+
// WHY, not just what. `verified: unknown` covered seven different clauses of the honesty rule
|
|
80
|
+
// belonging to three different owners — the agent, the app, and Reticle's own blind spots — and
|
|
81
|
+
// they need opposite responses. A suite verdict has no clause behind it and reports none.
|
|
82
|
+
...(reason === undefined ? {} : { reason }),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* must never fail the run that found it.
|
|
11
11
|
*/
|
|
12
12
|
import { ActionType, AnchorKind } from '@reticlehq/core';
|
|
13
|
-
import { CapsuleStore, capsuleId } from '../capsule/capsule-store.js';
|
|
13
|
+
import { CapsuleStore, capsuleId, CAPSULE_VERSION } from '../capsule/capsule-store.js';
|
|
14
14
|
import { ReticleTool } from './tool-names.js';
|
|
15
15
|
import { asRecord, asString } from './tools-helpers.js';
|
|
16
16
|
/** Returns the capsule id when one was written, or undefined when there was nothing to save. */
|
|
@@ -23,7 +23,7 @@ export async function saveFailedAssertCapsule(inputs) {
|
|
|
23
23
|
.map((l) => ('name' in l ? `${l.kind} ${String(l.name)}` : l.kind))
|
|
24
24
|
.join(' AND ');
|
|
25
25
|
const saved = await new CapsuleStore(deps.fs, deps.reticleRoot).save({
|
|
26
|
-
version:
|
|
26
|
+
version: CAPSULE_VERSION,
|
|
27
27
|
id,
|
|
28
28
|
createdAt: deps.now(),
|
|
29
29
|
origin: 'failed-assert',
|
|
@@ -5,3 +5,15 @@ export declare function assertNotDestructive(action: ActionType, innerArgs: Reco
|
|
|
5
5
|
* source alone never says so.
|
|
6
6
|
*/
|
|
7
7
|
export declare function assertDragNotDestructive(innerArgs: Record<string, unknown>, from: unknown, to: unknown): void;
|
|
8
|
+
/**
|
|
9
|
+
* `reticle_act_and_wait` cannot drive native input, and used to take `args.native` and ignore it.
|
|
10
|
+
*
|
|
11
|
+
* An open `args` passthrough accepted the field, the handler drove the page through the SDK anyway,
|
|
12
|
+
* and the result claimed success — so an agent asking for the one thing a synthetic click cannot do
|
|
13
|
+
* (a file picker, the clipboard, an `isTrusted`-gated handler) got a synthetic click and no hint
|
|
14
|
+
* that its request had been dropped. A silently ignored argument is a false promise; refusing with
|
|
15
|
+
* the route that DOES work is the honest answer.
|
|
16
|
+
*/
|
|
17
|
+
export declare const NATIVE_INPUT_UNSUPPORTED: string;
|
|
18
|
+
/** Refuse rather than silently drop a native-input request the act-then-wait path cannot honour. */
|
|
19
|
+
export declare function assertNativeInputSupported(innerArgs: Record<string, unknown>): void;
|
package/dist/tools/act-danger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionType, DANGEROUS_ACTION_CONFIRM_ARG, isDangerousActionText } from '@reticlehq/core';
|
|
1
|
+
import { ActionType, DANGEROUS_ACTION_CONFIRM_ARG, NATIVE_INPUT_ARG, isDangerousActionText, } from '@reticlehq/core';
|
|
2
2
|
import { asRecord, asString } from './tools-helpers.js';
|
|
3
3
|
/**
|
|
4
4
|
* Refuse to drive a money-moving or destructive control by accident.
|
|
@@ -25,7 +25,7 @@ function descriptorText(value) {
|
|
|
25
25
|
export function assertNotDestructive(action, innerArgs, inspected) {
|
|
26
26
|
if (action !== ActionType.CLICK && action !== ActionType.DBLCLICK)
|
|
27
27
|
return;
|
|
28
|
-
if (innerArgs[DANGEROUS_ACTION_CONFIRM_ARG]
|
|
28
|
+
if (true === innerArgs[DANGEROUS_ACTION_CONFIRM_ARG])
|
|
29
29
|
return;
|
|
30
30
|
if (!isDangerousActionText(descriptorText(inspected)))
|
|
31
31
|
return;
|
|
@@ -36,9 +36,26 @@ export function assertNotDestructive(action, innerArgs, inspected) {
|
|
|
36
36
|
* source alone never says so.
|
|
37
37
|
*/
|
|
38
38
|
export function assertDragNotDestructive(innerArgs, from, to) {
|
|
39
|
-
if (innerArgs[DANGEROUS_ACTION_CONFIRM_ARG]
|
|
39
|
+
if (true === innerArgs[DANGEROUS_ACTION_CONFIRM_ARG])
|
|
40
40
|
return;
|
|
41
41
|
if (!isDangerousActionText(`${descriptorText(from)} ${descriptorText(to)}`))
|
|
42
42
|
return;
|
|
43
43
|
throw new Error(`potentially destructive native action blocked; retry with args.${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* `reticle_act_and_wait` cannot drive native input, and used to take `args.native` and ignore it.
|
|
47
|
+
*
|
|
48
|
+
* An open `args` passthrough accepted the field, the handler drove the page through the SDK anyway,
|
|
49
|
+
* and the result claimed success — so an agent asking for the one thing a synthetic click cannot do
|
|
50
|
+
* (a file picker, the clipboard, an `isTrusted`-gated handler) got a synthetic click and no hint
|
|
51
|
+
* that its request had been dropped. A silently ignored argument is a false promise; refusing with
|
|
52
|
+
* the route that DOES work is the honest answer.
|
|
53
|
+
*/
|
|
54
|
+
export const NATIVE_INPUT_UNSUPPORTED = `reticle_act_and_wait cannot drive native input, so args.${NATIVE_INPUT_ARG} would be ignored. ` +
|
|
55
|
+
`Use reticle_act { args: { ${NATIVE_INPUT_ARG}: true } } for the trusted click, then assert the ` +
|
|
56
|
+
'consequence with reticle_assert / reticle_observe using the `since` cursor it returns.';
|
|
57
|
+
/** Refuse rather than silently drop a native-input request the act-then-wait path cannot honour. */
|
|
58
|
+
export function assertNativeInputSupported(innerArgs) {
|
|
59
|
+
if (true === innerArgs[NATIVE_INPUT_ARG])
|
|
60
|
+
throw new Error(NATIVE_INPUT_UNSUPPORTED);
|
|
61
|
+
}
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* session, a command, or a tool definition — these are total functions over their arguments, which
|
|
6
6
|
* is why they can be tested without a browser and why they were the right thing to move.
|
|
7
7
|
*/
|
|
8
|
-
import { ActionType } from '@reticlehq/core';
|
|
8
|
+
import { ActionType, ConsequenceKind } from '@reticlehq/core';
|
|
9
9
|
import { HonestyGrade } from '../honesty/honesty.js';
|
|
10
10
|
import { asNumber, asRecord, asString } from './tools-helpers.js';
|
|
11
11
|
/** The strongest consequence grade a set of expected links proves (signal > net > state > presence). */
|
|
12
12
|
export function gradeOf(links) {
|
|
13
|
-
if (links.some((l) =>
|
|
13
|
+
if (links.some((l) => ConsequenceKind.SIGNAL === l.kind))
|
|
14
14
|
return HonestyGrade.SIGNAL;
|
|
15
|
-
if (links.some((l) =>
|
|
15
|
+
if (links.some((l) => ConsequenceKind.NET === l.kind))
|
|
16
16
|
return HonestyGrade.NET;
|
|
17
|
-
if (links.some((l) =>
|
|
17
|
+
if (links.some((l) => ConsequenceKind.STATE === l.kind))
|
|
18
18
|
return HonestyGrade.STATE;
|
|
19
19
|
return HonestyGrade.PRESENCE;
|
|
20
20
|
}
|
|
@@ -1,10 +1,2 @@
|
|
|
1
1
|
import { type ToolDef } from './tool-kit.js';
|
|
2
|
-
/**
|
|
3
|
-
* Narrow the wire's `action` to a real ActionType, or undefined.
|
|
4
|
-
*
|
|
5
|
-
* It used to be `asString(args['action']) ?? ''`, so an unknown or missing action became the empty
|
|
6
|
-
* string and travelled on — reaching the browser as a command it could not perform, and reported back
|
|
7
|
-
* as a generic failure rather than "that is not an action". Validate at the boundary, per the project's
|
|
8
|
-
* unknown-plus-narrowing rule, so a typo is rejected where it can still be explained.
|
|
9
|
-
*/
|
|
10
2
|
export declare const ACT_TOOLS: ToolDef[];
|