@reticlehq/server 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { McpScope, type Feedback } from '@reticlehq/core';
|
|
2
2
|
/** The context fields — the whole `Feedback` shape minus what the author supplies. */
|
|
3
3
|
export type FeedbackContext = Pick<Feedback, 'stack' | 'stackMajor' | 'runtime' | 'engine' | 'driver' | 'client' | 'clientVersion' | 'mcpScope'>;
|
|
4
|
-
/**
|
|
5
|
-
* The project's framework and its MAJOR version, or `{}` when package.json is absent, unreadable, or
|
|
6
|
-
* names nothing we recognize. The major comes from `parseMajor` — the same range parser `reticle init`
|
|
7
|
-
* uses to decide React 19 source mapping — so a range like `^15.0.0-canary.3` narrows the same way in
|
|
8
|
-
* both places instead of via a second, subtly different reader.
|
|
9
|
-
*/
|
|
10
4
|
export declare function detectStack(cwd: string, read?: (path: string) => string): {
|
|
11
5
|
stack?: string;
|
|
12
6
|
stackMajor?: number;
|
|
7
|
+
stackSource?: 'cwd' | 'workspace';
|
|
13
8
|
};
|
|
14
9
|
/**
|
|
15
10
|
* User scope unless the project checks in its own MCP registration. `reticle init` deliberately
|
|
@@ -27,6 +22,28 @@ export interface SessionFacts {
|
|
|
27
22
|
engine?: string;
|
|
28
23
|
/** True when Reticle drives the page over CDP rather than observing the human's own browser. */
|
|
29
24
|
driven?: boolean;
|
|
25
|
+
/** What the APP said it is running, from the SDK's hello. The only session-scoped stack evidence. */
|
|
26
|
+
adapters?: string[];
|
|
30
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Reconcile what the DIRECTORY says with what the APP says.
|
|
30
|
+
*
|
|
31
|
+
* `detectStack` reads the daemon's cwd, which is not the session's project whenever one daemon
|
|
32
|
+
* serves several apps — the normal case for anyone with two dev servers, and the default in the
|
|
33
|
+
* fixtures. Reported from the field: feedback filed from an astro session arrived stamped
|
|
34
|
+
* `"stack":"sveltekit"`, because that was the directory the daemon happened to be started in. The
|
|
35
|
+
* sessionId was passed and ignored.
|
|
36
|
+
*
|
|
37
|
+
* The rule: keep the directory's answer only while the app agrees with it. An adapter that implies a
|
|
38
|
+
* different family means the directory is describing somebody else's project, so the app's own
|
|
39
|
+
* report wins — less specific, and true, which is the right trade for an attribution field.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reconcileStack(fromCwd: {
|
|
42
|
+
stack?: string;
|
|
43
|
+
stackMajor?: number;
|
|
44
|
+
}, adapters: readonly string[] | undefined): {
|
|
45
|
+
stack?: string;
|
|
46
|
+
stackMajor?: number;
|
|
47
|
+
};
|
|
31
48
|
/** Assemble the full context. Every field independently optional — a partial context still ships. */
|
|
32
49
|
export declare function feedbackContext(cwd: string, session?: SessionFacts): FeedbackContext;
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* holds to. Detection is entirely best-effort: an unreadable package.json or an SDK too old to report
|
|
10
10
|
* its runtime yields `undefined`, never a throw and never a guess.
|
|
11
11
|
*/
|
|
12
|
-
import { readFileSync } from 'node:fs';
|
|
13
|
-
import { join } from 'node:path';
|
|
12
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
13
|
+
import { isAbsolute, join } from 'node:path';
|
|
14
14
|
import { AppRuntime, McpScope, PageDriver } from '@reticlehq/core';
|
|
15
15
|
import { parseMajor } from '../init/detect.js';
|
|
16
|
+
import { findWorkspaceApps } from '../init/run.js';
|
|
16
17
|
/**
|
|
17
18
|
* Dependency → reported stack, in resolution order. Meta-frameworks come FIRST: a Next app also
|
|
18
19
|
* depends on react, and reporting `react` for it would collapse the single most important segment
|
|
@@ -44,10 +45,10 @@ const PROJECT_MCP_FILES = ['.mcp.json', join('.cursor', 'mcp.json'), join('.vsco
|
|
|
44
45
|
* uses to decide React 19 source mapping — so a range like `^15.0.0-canary.3` narrows the same way in
|
|
45
46
|
* both places instead of via a second, subtly different reader.
|
|
46
47
|
*/
|
|
47
|
-
|
|
48
|
+
function stackOfManifest(dir, read) {
|
|
48
49
|
let pkg;
|
|
49
50
|
try {
|
|
50
|
-
pkg = JSON.parse(read(join(
|
|
51
|
+
pkg = JSON.parse(read(join(dir, PACKAGE_JSON)));
|
|
51
52
|
}
|
|
52
53
|
catch {
|
|
53
54
|
return {};
|
|
@@ -62,6 +63,65 @@ export function detectStack(cwd, read = readFile) {
|
|
|
62
63
|
}
|
|
63
64
|
return {};
|
|
64
65
|
}
|
|
66
|
+
export function detectStack(cwd, read = readFile) {
|
|
67
|
+
// The cwd wins when it IS an app — never redirect away from the real answer.
|
|
68
|
+
const here = stackOfManifest(cwd, read);
|
|
69
|
+
if (here.stack !== undefined)
|
|
70
|
+
return { ...here, stackSource: 'cwd' };
|
|
71
|
+
// Otherwise look for the app. The daemon's cwd is wherever the agent's client happened to launch,
|
|
72
|
+
// which for a real repo is the ROOT while the app sits in `frontend/`, `web/`, or a declared
|
|
73
|
+
// workspace. Reading one directory and giving up is why this returned nothing for every project
|
|
74
|
+
// that actually had Reticle set up: 0 of 77 instrumented projects, and 0 of 166 `size: huge` ones.
|
|
75
|
+
// The bigger the repo, the more certainly we failed.
|
|
76
|
+
//
|
|
77
|
+
// `findWorkspaceApps` is init's discovery, reused rather than reimplemented: it already reads
|
|
78
|
+
// declared workspaces (pnpm-workspace.yaml, package.json `workspaces`) AND scans top-level
|
|
79
|
+
// directories, and it was widened once already for a repo shape a user hit twice. A second
|
|
80
|
+
// detector here would drift from it the first time either changed.
|
|
81
|
+
try {
|
|
82
|
+
for (const app of findWorkspaceApps(profileIo(cwd))) {
|
|
83
|
+
const found = stackOfManifest(join(cwd, app), read);
|
|
84
|
+
// Reported as `workspace` so the share of projects needing discovery is measurable — that
|
|
85
|
+
// number is what says whether our inference needs an agent to correct it.
|
|
86
|
+
if (found.stack !== undefined)
|
|
87
|
+
return { ...found, stackSource: 'workspace' };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Discovery touches the filesystem; a permission error must never cost us the profile event.
|
|
92
|
+
}
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The three filesystem methods `findWorkspaceApps` needs, rooted at `cwd`.
|
|
97
|
+
*
|
|
98
|
+
* Deliberately not the whole `InitIo`: this path must not be able to write, exec or print. What it
|
|
99
|
+
* cannot do is a stronger guarantee than a rule saying it shouldn't.
|
|
100
|
+
*/
|
|
101
|
+
function profileIo(cwd) {
|
|
102
|
+
const abs = (rel) => (isAbsolute(rel) ? rel : join(cwd, rel));
|
|
103
|
+
return {
|
|
104
|
+
readFile: (rel) => {
|
|
105
|
+
try {
|
|
106
|
+
return readFileSync(abs(rel), 'utf8');
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
exists: (rel) => existsSync(abs(rel)),
|
|
113
|
+
listDirs: (rel) => {
|
|
114
|
+
try {
|
|
115
|
+
return readdirSync(abs(rel), { withFileTypes: true })
|
|
116
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith('.') && 'node_modules' !== e.name)
|
|
117
|
+
.map((e) => e.name);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
65
125
|
/**
|
|
66
126
|
* User scope unless the project checks in its own MCP registration. `reticle init` deliberately
|
|
67
127
|
* registers ONCE at user scope, so `project` here means the team wired it up by hand — which is
|
|
@@ -116,6 +176,43 @@ function detectClient() {
|
|
|
116
176
|
return {};
|
|
117
177
|
}
|
|
118
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Which cwd-derived stacks each SDK adapter is consistent with.
|
|
181
|
+
*
|
|
182
|
+
* The adapter names a UI library; the directory may name a meta-framework built on it. `react` and
|
|
183
|
+
* `next` are the same app described at two levels, so the more specific one is kept. `react` and
|
|
184
|
+
* `sveltekit` are not — that pairing means the daemon's directory is not this session's project.
|
|
185
|
+
*/
|
|
186
|
+
const ADAPTER_IMPLIES = {
|
|
187
|
+
react: ['react', 'next', 'remix'],
|
|
188
|
+
svelte: ['svelte', 'sveltekit'],
|
|
189
|
+
vue: ['vue', 'nuxt'],
|
|
190
|
+
solid: ['solid'],
|
|
191
|
+
angular: ['angular'],
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Reconcile what the DIRECTORY says with what the APP says.
|
|
195
|
+
*
|
|
196
|
+
* `detectStack` reads the daemon's cwd, which is not the session's project whenever one daemon
|
|
197
|
+
* serves several apps — the normal case for anyone with two dev servers, and the default in the
|
|
198
|
+
* fixtures. Reported from the field: feedback filed from an astro session arrived stamped
|
|
199
|
+
* `"stack":"sveltekit"`, because that was the directory the daemon happened to be started in. The
|
|
200
|
+
* sessionId was passed and ignored.
|
|
201
|
+
*
|
|
202
|
+
* The rule: keep the directory's answer only while the app agrees with it. An adapter that implies a
|
|
203
|
+
* different family means the directory is describing somebody else's project, so the app's own
|
|
204
|
+
* report wins — less specific, and true, which is the right trade for an attribution field.
|
|
205
|
+
*/
|
|
206
|
+
export function reconcileStack(fromCwd, adapters) {
|
|
207
|
+
const claimed = adapters?.find((adapter) => adapter in ADAPTER_IMPLIES);
|
|
208
|
+
if (claimed === undefined)
|
|
209
|
+
return fromCwd;
|
|
210
|
+
const consistent = ADAPTER_IMPLIES[claimed] ?? [];
|
|
211
|
+
if (fromCwd.stack !== undefined && consistent.includes(fromCwd.stack))
|
|
212
|
+
return fromCwd;
|
|
213
|
+
// The major belonged to the directory's framework, so it cannot travel with a different stack.
|
|
214
|
+
return { stack: claimed };
|
|
215
|
+
}
|
|
119
216
|
function asRuntime(value) {
|
|
120
217
|
return Object.values(AppRuntime).find((known) => known === value);
|
|
121
218
|
}
|
|
@@ -131,7 +228,10 @@ export function feedbackContext(cwd, session) {
|
|
|
131
228
|
const engine = asEngine(session?.engine);
|
|
132
229
|
const client = detectClient();
|
|
133
230
|
return {
|
|
134
|
-
|
|
231
|
+
// Session-scoped when the app reports something that contradicts the directory — see
|
|
232
|
+
// reconcileStack. A daemon serving two apps otherwise stamps both with whichever one it was
|
|
233
|
+
// started next to.
|
|
234
|
+
...reconcileStack(detectStack(cwd), session?.adapters),
|
|
135
235
|
...client,
|
|
136
236
|
...(runtime !== undefined ? { runtime } : {}),
|
|
137
237
|
...(engine !== undefined ? { engine } : {}),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling the reporter that a BACKGROUND feedback delivery failed.
|
|
3
|
+
*
|
|
4
|
+
* The agent's `reticle_feedback` no longer waits for the POST — a ~340ms network round-trip on the
|
|
5
|
+
* one call an agent makes mid-task is the product blocking the user's work to talk about itself. But
|
|
6
|
+
* not waiting reopens the exact hole the awaited version was written to close: `sent` used to be
|
|
7
|
+
* unconditional, so a DNS miss and a 4xx both reported "filed", and this is the ONLY qualitative
|
|
8
|
+
* channel the product has. A lost report that was announced as filed is worse than no channel.
|
|
9
|
+
*
|
|
10
|
+
* So the wait goes and the honesty stays: the receipt says ACCEPTED (validated, redacted, queued),
|
|
11
|
+
* never "delivered", and if the send then fails the agent is told on its next tool result through
|
|
12
|
+
* the same one-shot envelope the update and skew nudges use. Told once — a reporter who cannot fix
|
|
13
|
+
* the network does not need it repeated, and a banner on every call is one agents learn to skip.
|
|
14
|
+
*/
|
|
15
|
+
/** Record that a queued report never made it. */
|
|
16
|
+
export declare function noteFeedbackUndelivered(reason: string): void;
|
|
17
|
+
/** The undelivered-feedback notice, once. */
|
|
18
|
+
export declare function takeFeedbackUndelivered(): string | undefined;
|
|
19
|
+
/** Tests only — drop the module state so each case starts clean. */
|
|
20
|
+
export declare function resetFeedbackDelivery(): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling the reporter that a BACKGROUND feedback delivery failed.
|
|
3
|
+
*
|
|
4
|
+
* The agent's `reticle_feedback` no longer waits for the POST — a ~340ms network round-trip on the
|
|
5
|
+
* one call an agent makes mid-task is the product blocking the user's work to talk about itself. But
|
|
6
|
+
* not waiting reopens the exact hole the awaited version was written to close: `sent` used to be
|
|
7
|
+
* unconditional, so a DNS miss and a 4xx both reported "filed", and this is the ONLY qualitative
|
|
8
|
+
* channel the product has. A lost report that was announced as filed is worse than no channel.
|
|
9
|
+
*
|
|
10
|
+
* So the wait goes and the honesty stays: the receipt says ACCEPTED (validated, redacted, queued),
|
|
11
|
+
* never "delivered", and if the send then fails the agent is told on its next tool result through
|
|
12
|
+
* the same one-shot envelope the update and skew nudges use. Told once — a reporter who cannot fix
|
|
13
|
+
* the network does not need it repeated, and a banner on every call is one agents learn to skip.
|
|
14
|
+
*/
|
|
15
|
+
/** Set when a background feedback send failed and the reporter has not been told yet. */
|
|
16
|
+
let pending;
|
|
17
|
+
/** Record that a queued report never made it. */
|
|
18
|
+
export function noteFeedbackUndelivered(reason) {
|
|
19
|
+
pending = reason;
|
|
20
|
+
}
|
|
21
|
+
/** The undelivered-feedback notice, once. */
|
|
22
|
+
export function takeFeedbackUndelivered() {
|
|
23
|
+
const notice = pending;
|
|
24
|
+
pending = undefined;
|
|
25
|
+
return notice;
|
|
26
|
+
}
|
|
27
|
+
/** Tests only — drop the module state so each case starts clean. */
|
|
28
|
+
export function resetFeedbackDelivery() {
|
|
29
|
+
pending = undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** One queued report: the payload, plus what is needed to find and remove it again. */
|
|
2
|
+
export interface OutboxEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
t: string;
|
|
5
|
+
payload: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function outboxPath(home?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Persist a report and return its id. Never throws: failing to queue must not also fail the send
|
|
10
|
+
* that was about to happen anyway.
|
|
11
|
+
*/
|
|
12
|
+
export declare function queueFeedback(payload: unknown, now?: () => Date, home?: string): string | null;
|
|
13
|
+
/** Read every queued report. A malformed line is skipped, never fatal — the rest still matter. */
|
|
14
|
+
export declare function readOutbox(home?: string): OutboxEntry[];
|
|
15
|
+
/**
|
|
16
|
+
* Drop a report that was delivered. Rewrites the file without it.
|
|
17
|
+
*
|
|
18
|
+
* A rewrite rather than a tombstone because the file's whole purpose is "what still needs sending" —
|
|
19
|
+
* a reader should be able to `cat` it and see exactly the backlog, with no filtering rule to learn.
|
|
20
|
+
* Volume is a handful of lines a week, so the cost is irrelevant.
|
|
21
|
+
*/
|
|
22
|
+
export declare function markDelivered(id: string | null, home?: string): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A durable outbox for feedback, written BEFORE the network is touched.
|
|
3
|
+
*
|
|
4
|
+
* Feedback is the only qualitative channel this product has, and it carries an agent's whole
|
|
5
|
+
* root-cause analysis — often an hour of driving distilled into one payload. It was sent with the
|
|
6
|
+
* same 2-second fire-and-forget budget as a usage counter, with no retry and no persistence, so a
|
|
7
|
+
* 1.3-second network hiccup destroyed the report outright. Measured to the collector from one
|
|
8
|
+
* machine with a WARM DNS cache: dns 0.003s, connect 0.233s, tls 0.467s, total 0.694s — roughly a
|
|
9
|
+
* third of the entire budget spent before a byte of payload moves, and that is the good case. A
|
|
10
|
+
* fresh short-lived CLI process pays cold DNS and a cold route on top.
|
|
11
|
+
*
|
|
12
|
+
* So the order is inverted: write it down first, then try to send. A failed send is then a queued
|
|
13
|
+
* report rather than a lost one, and `sent: false` becomes a statement about the network instead of
|
|
14
|
+
* an epitaph.
|
|
15
|
+
*
|
|
16
|
+
* JSONL, appended, one payload per line. Append-only is the point — a crash mid-write costs at most
|
|
17
|
+
* the line being written, never the ones already there.
|
|
18
|
+
*/
|
|
19
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { randomUUID } from 'node:crypto';
|
|
23
|
+
import { ReticleDir } from '@reticlehq/core';
|
|
24
|
+
export function outboxPath(home = homedir()) {
|
|
25
|
+
return join(home, ReticleDir.ROOT, 'feedback-outbox.jsonl');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Persist a report and return its id. Never throws: failing to queue must not also fail the send
|
|
29
|
+
* that was about to happen anyway.
|
|
30
|
+
*/
|
|
31
|
+
export function queueFeedback(payload, now = () => new Date(), home = homedir()) {
|
|
32
|
+
const id = randomUUID();
|
|
33
|
+
try {
|
|
34
|
+
mkdirSync(join(home, ReticleDir.ROOT), { recursive: true });
|
|
35
|
+
const entry = { id, t: now().toISOString(), payload };
|
|
36
|
+
appendFileSync(outboxPath(home), `${JSON.stringify(entry)}\n`, 'utf8');
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Read every queued report. A malformed line is skipped, never fatal — the rest still matter. */
|
|
44
|
+
export function readOutbox(home = homedir()) {
|
|
45
|
+
try {
|
|
46
|
+
const path = outboxPath(home);
|
|
47
|
+
if (!existsSync(path))
|
|
48
|
+
return [];
|
|
49
|
+
return readFileSync(path, 'utf8')
|
|
50
|
+
.split('\n')
|
|
51
|
+
.filter((line) => 0 < line.trim().length)
|
|
52
|
+
.flatMap((line) => {
|
|
53
|
+
try {
|
|
54
|
+
return [JSON.parse(line)];
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Drop a report that was delivered. Rewrites the file without it.
|
|
67
|
+
*
|
|
68
|
+
* A rewrite rather than a tombstone because the file's whole purpose is "what still needs sending" —
|
|
69
|
+
* a reader should be able to `cat` it and see exactly the backlog, with no filtering rule to learn.
|
|
70
|
+
* Volume is a handful of lines a week, so the cost is irrelevant.
|
|
71
|
+
*/
|
|
72
|
+
export function markDelivered(id, home = homedir()) {
|
|
73
|
+
if (null === id)
|
|
74
|
+
return;
|
|
75
|
+
try {
|
|
76
|
+
const remaining = readOutbox(home).filter((entry) => entry.id !== id);
|
|
77
|
+
const path = outboxPath(home);
|
|
78
|
+
if (0 === remaining.length) {
|
|
79
|
+
writeFileSync(path, '', 'utf8');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
writeFileSync(path, `${remaining.map((entry) => JSON.stringify(entry)).join('\n')}\n`, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// A report that stays queued after a successful send is a duplicate at worst. Losing one is not.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -39,8 +39,21 @@ export declare function feedbackDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
|
39
39
|
/** What the author supplies; the environment context is detected, never asked for. */
|
|
40
40
|
export type FeedbackInput = Pick<Feedback, 'source' | 'kind' | 'text' | 'trace' | 'rating' | 'need' | 'impact' | 'currentApproach' | 'model'>;
|
|
41
41
|
export interface FeedbackReceipt {
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* DELIVERY confirmed. Only ever true when the send was awaited and the endpoint accepted it —
|
|
44
|
+
* never as an optimistic stand-in for "we handed it to the emitter". That distinction is the whole
|
|
45
|
+
* value of this field: it was once unconditional, so a DNS miss and a 4xx both reported "filed".
|
|
46
|
+
*
|
|
47
|
+
* A backgrounded send (the agent tool) therefore returns `sent: false` with `accepted: true` —
|
|
48
|
+
* see below. It is not a failure, and the note says so.
|
|
49
|
+
*/
|
|
43
50
|
sent: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Validated, redacted and handed to the emitter. TRUE says the report is well-formed and on its
|
|
53
|
+
* way; it does NOT say it arrived. If a backgrounded send then fails, the reporter is told on its
|
|
54
|
+
* next tool result — see feedback-delivery.ts.
|
|
55
|
+
*/
|
|
56
|
+
accepted: boolean;
|
|
44
57
|
reason?: string;
|
|
45
58
|
/** Which redaction rules fired. Empty when nothing needed removing. */
|
|
46
59
|
redacted: string[];
|
|
@@ -56,6 +69,14 @@ export declare function submitFeedback(input: FeedbackInput, opts?: {
|
|
|
56
69
|
cwd?: string;
|
|
57
70
|
session?: SessionFacts;
|
|
58
71
|
env?: NodeJS.ProcessEnv;
|
|
72
|
+
/**
|
|
73
|
+
* Return as soon as the report is validated and queued, instead of waiting out the POST.
|
|
74
|
+
*
|
|
75
|
+
* Used by the AGENT tool: ~340ms of network on a call made mid-task is the product blocking the
|
|
76
|
+
* user's work to talk about itself. NOT used by `reticle feedback`, where a human typed the
|
|
77
|
+
* command and is waiting for an answer — there, confirmed delivery is the answer.
|
|
78
|
+
*/
|
|
79
|
+
background?: boolean;
|
|
59
80
|
}): Promise<FeedbackReceipt>;
|
|
60
81
|
/**
|
|
61
82
|
* The receipt's context PLUS the fields the emitter stamps on every event regardless of kind — the
|
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
import { FeedbackSchema, FEEDBACK_TEXT_MAX, FEEDBACK_TRACE_MAX, FEEDBACK_FIELD_MAX, TelemetryEventKind, } from '@reticlehq/core';
|
|
25
25
|
import { platform } from 'node:os';
|
|
26
26
|
import { getTelemetry } from './telemetry.js';
|
|
27
|
-
import {
|
|
27
|
+
import { noteFeedbackUndelivered } from './feedback-delivery.js';
|
|
28
|
+
import { isReticleSourceCheckout } from './dev-repo.js';
|
|
29
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
28
30
|
import { feedbackContext } from './feedback-context.js';
|
|
31
|
+
import { markDelivered, outboxPath, queueFeedback } from './feedback-outbox.js';
|
|
29
32
|
/** Kills the feedback channel ONLY — adoption counters keep flowing. */
|
|
30
33
|
export const FEEDBACK_ENV = 'RETICLE_FEEDBACK';
|
|
31
34
|
/** What replaces a redacted match. Visible on purpose: a reader must see that something was removed. */
|
|
@@ -67,7 +70,7 @@ export function redactFeedbackText(input, max) {
|
|
|
67
70
|
removed.add(label);
|
|
68
71
|
// A rule with a leading capture keeps it (the scheme, the `api_key=` key) so the reader still
|
|
69
72
|
// sees WHAT was removed, not just that something was.
|
|
70
|
-
const prefix = typeof groups[0]
|
|
73
|
+
const prefix = 'string' === typeof groups[0] ? groups[0] : '';
|
|
71
74
|
return `${prefix}${REDACTED}`;
|
|
72
75
|
});
|
|
73
76
|
}
|
|
@@ -76,7 +79,7 @@ export function redactFeedbackText(input, max) {
|
|
|
76
79
|
/** True when the feedback channel is switched off on its own (the emitter's switches apply as well). */
|
|
77
80
|
export function feedbackDisabled(env = process.env) {
|
|
78
81
|
const value = (env[FEEDBACK_ENV] ?? '').toLowerCase();
|
|
79
|
-
return
|
|
82
|
+
return '0' === value || 'false' === value || 'off' === value || 'no' === value;
|
|
80
83
|
}
|
|
81
84
|
/**
|
|
82
85
|
* The fields an author writes, and therefore the fields that must be redacted. ONE list, used both to
|
|
@@ -118,7 +121,7 @@ export async function submitFeedback(input, opts = {}) {
|
|
|
118
121
|
// addition. Deriving the set means a sixth field is covered the moment it exists.
|
|
119
122
|
const redactions = new Map();
|
|
120
123
|
for (const [key, value] of Object.entries(input)) {
|
|
121
|
-
if (typeof value !== 'string' ||
|
|
124
|
+
if (typeof value !== 'string' || '' === value)
|
|
122
125
|
continue;
|
|
123
126
|
if (!AUTHOR_WRITTEN_FIELDS.has(key))
|
|
124
127
|
continue;
|
|
@@ -127,13 +130,25 @@ export async function submitFeedback(input, opts = {}) {
|
|
|
127
130
|
const body = redactions.get('text') ?? { text: '', removed: [] };
|
|
128
131
|
const redacted = [...new Set([...redactions.values()].flatMap((r) => r.removed))];
|
|
129
132
|
if (feedbackDisabled(env)) {
|
|
130
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
sent: false,
|
|
135
|
+
accepted: false,
|
|
136
|
+
reason: `feedback is disabled by ${FEEDBACK_ENV}`,
|
|
137
|
+
redacted,
|
|
138
|
+
context,
|
|
139
|
+
};
|
|
131
140
|
}
|
|
132
141
|
const telemetry = getTelemetry();
|
|
133
142
|
if (!telemetry.enabled) {
|
|
134
143
|
return {
|
|
135
144
|
sent: false,
|
|
136
|
-
|
|
145
|
+
accepted: false,
|
|
146
|
+
// Named precisely, because the commonest cause is not what the old wording suggested: a
|
|
147
|
+
// Reticle SOURCE CHECKOUT disables telemetry by cwd, and that is exactly where release runs and
|
|
148
|
+
// contributor sessions happen — so the reports most worth having were the ones silently lost.
|
|
149
|
+
reason: isReticleSourceCheckout(opts.cwd ?? process.cwd())
|
|
150
|
+
? 'this is a Reticle source checkout, where telemetry is disabled by design — so feedback has nowhere to go. Open an issue at https://github.com/reticlehq/reticle/issues instead, or run from the app you are verifying.'
|
|
151
|
+
: `telemetry is disabled on this machine, so feedback has nowhere to go. Re-enable with \`reticle telemetry enable\`, or open an issue at https://github.com/reticlehq/reticle/issues.`,
|
|
137
152
|
redacted,
|
|
138
153
|
context,
|
|
139
154
|
};
|
|
@@ -151,13 +166,57 @@ export async function submitFeedback(input, opts = {}) {
|
|
|
151
166
|
if (!parsed.success) {
|
|
152
167
|
return {
|
|
153
168
|
sent: false,
|
|
169
|
+
accepted: false,
|
|
154
170
|
reason: parsed.error.issues[0]?.message ?? 'invalid feedback',
|
|
155
171
|
redacted,
|
|
156
172
|
context,
|
|
157
173
|
};
|
|
158
174
|
}
|
|
159
|
-
|
|
160
|
-
|
|
175
|
+
// BACKGROUND: return now, deliver after. The agent calls this mid-task and a ~340ms POST is the
|
|
176
|
+
// product blocking the user's work to talk about itself. The honesty that the awaited version was
|
|
177
|
+
// written for is kept elsewhere: `sent` stays false (nothing is confirmed yet), `accepted` says
|
|
178
|
+
// the report is well-formed and queued, and a send that then fails reaches the reporter on its
|
|
179
|
+
// next tool result instead of vanishing.
|
|
180
|
+
// Written down BEFORE the network is touched, on both paths. A failed send is then a queued report
|
|
181
|
+
// rather than a lost one — see feedback-outbox. Reported from the field: a 1.3s hiccup destroyed a
|
|
182
|
+
// root-cause analysis that took an hour of driving to produce, and it survived only because the
|
|
183
|
+
// reporter happened to have written the markdown by hand first.
|
|
184
|
+
const queued = queueFeedback(parsed.data);
|
|
185
|
+
if (true === opts.background) {
|
|
186
|
+
void telemetry
|
|
187
|
+
.emit(TelemetryEventKind.FEEDBACK_SUBMITTED, { feedback: parsed.data })
|
|
188
|
+
.then((ok) => {
|
|
189
|
+
if (ok)
|
|
190
|
+
markDelivered(queued);
|
|
191
|
+
else
|
|
192
|
+
noteFeedbackUndelivered('the telemetry endpoint did not accept it');
|
|
193
|
+
})
|
|
194
|
+
.catch((error) => {
|
|
195
|
+
noteFeedbackUndelivered(error instanceof Error ? error.message : String(error));
|
|
196
|
+
});
|
|
197
|
+
return { sent: false, accepted: true, redacted, context };
|
|
198
|
+
}
|
|
199
|
+
// `sent` reflects DELIVERY, not handing the payload to the emitter. It was unconditional, so a DNS
|
|
200
|
+
// miss or a 4xx both reported "filed" — and this is the only qualitative channel the product has,
|
|
201
|
+
// so a silent failure here loses the report AND tells the reporter it worked.
|
|
202
|
+
const delivered = await telemetry.emit(TelemetryEventKind.FEEDBACK_SUBMITTED, {
|
|
203
|
+
feedback: parsed.data,
|
|
204
|
+
});
|
|
205
|
+
if (delivered) {
|
|
206
|
+
markDelivered(queued);
|
|
207
|
+
return { sent: true, accepted: true, redacted, context };
|
|
208
|
+
}
|
|
209
|
+
// Undelivered, but no longer lost. `accepted` is true when it reached the outbox: the report is
|
|
210
|
+
// well-formed and on disk, which is a materially different situation from "it is gone".
|
|
211
|
+
return {
|
|
212
|
+
sent: false,
|
|
213
|
+
accepted: queued !== null,
|
|
214
|
+
reason: queued !== null
|
|
215
|
+
? `the report could not be delivered (the network call failed or was rejected), but it is SAVED at ${outboxPath()} and nothing was lost. It will be retried; you can also open an issue at https://github.com/reticlehq/reticle/issues with the same detail.`
|
|
216
|
+
: 'the report could not be delivered and could not be saved locally either — it was NOT filed. Retry, or open an issue at https://github.com/reticlehq/reticle/issues with the same detail.',
|
|
217
|
+
redacted,
|
|
218
|
+
context,
|
|
219
|
+
};
|
|
161
220
|
}
|
|
162
221
|
/**
|
|
163
222
|
* The receipt's context PLUS the fields the emitter stamps on every event regardless of kind — the
|
|
@@ -6,6 +6,13 @@ export interface GitFacts {
|
|
|
6
6
|
origin?: string;
|
|
7
7
|
/** Which forge hosts it, bucketed. Absent unless state is `remote`. */
|
|
8
8
|
forge?: RepoForge;
|
|
9
|
+
/**
|
|
10
|
+
* The directory holding `.git` — the repo boundary. Absent only when state is `none`.
|
|
11
|
+
*
|
|
12
|
+
* Never sent: it is a local path. It exists so an UNPUSHED repo can still key its projectId to
|
|
13
|
+
* something stable across every directory inside it, which a raw cwd is not.
|
|
14
|
+
*/
|
|
15
|
+
root?: string;
|
|
9
16
|
}
|
|
10
17
|
/** `git@github.com:Acme/Web.git` / `https://u:p@github.com/Acme/Web` → `github.com/acme/web`. */
|
|
11
18
|
export declare const normalizeGitOrigin: (url: string) => string;
|
|
@@ -88,17 +88,17 @@ export function gitFacts(cwd, read = defaultRead, exists = defaultExists) {
|
|
|
88
88
|
}
|
|
89
89
|
catch {
|
|
90
90
|
// A `.git` with no readable config is still a repo — report it as one rather than as absent.
|
|
91
|
-
return { state: GitState.LOCAL_ONLY };
|
|
91
|
+
return { state: GitState.LOCAL_ONLY, root: dir };
|
|
92
92
|
}
|
|
93
93
|
// The url line inside the [remote "origin"] section — the first url after that header wins.
|
|
94
94
|
const section = config.split(/\[remote\s+"origin"\]/)[1];
|
|
95
95
|
const url = section?.match(/^\s*url\s*=\s*(.+)$/m)?.[1];
|
|
96
96
|
if (url === undefined)
|
|
97
|
-
return { state: GitState.LOCAL_ONLY };
|
|
97
|
+
return { state: GitState.LOCAL_ONLY, root: dir };
|
|
98
98
|
const origin = normalizeGitOrigin(url);
|
|
99
|
-
if (
|
|
100
|
-
return { state: GitState.LOCAL_ONLY };
|
|
101
|
-
return { state: GitState.REMOTE, origin, forge: forgeOf(origin) };
|
|
99
|
+
if ('' === origin)
|
|
100
|
+
return { state: GitState.LOCAL_ONLY, root: dir };
|
|
101
|
+
return { state: GitState.REMOTE, origin, forge: forgeOf(origin), root: dir };
|
|
102
102
|
}
|
|
103
103
|
const parent = dirname(dir);
|
|
104
104
|
if (parent === dir)
|
|
@@ -180,13 +180,14 @@ export function profileProject(cwd, now) {
|
|
|
180
180
|
featuresUsed.push(FeatureFamily.BUG_CAPSULES);
|
|
181
181
|
if (hasHistory)
|
|
182
182
|
featuresUsed.push(FeatureFamily.CROSS_RUN_HISTORY);
|
|
183
|
-
const { stack, stackMajor } = detectStack(cwd);
|
|
183
|
+
const { stack, stackMajor, stackSource } = detectStack(cwd);
|
|
184
184
|
const ageWeeks = projectAgeWeeks(cwd, now);
|
|
185
185
|
const git = gitFacts(cwd);
|
|
186
186
|
return {
|
|
187
187
|
git: git.state,
|
|
188
188
|
...(git.forge !== undefined ? { forge: git.forge } : {}),
|
|
189
189
|
...(stack !== undefined ? { stack } : {}),
|
|
190
|
+
...(stackSource !== undefined ? { stackSource } : {}),
|
|
190
191
|
...(stackMajor !== undefined ? { stackMajor } : {}),
|
|
191
192
|
size: sizeBucket(countSourceFiles(cwd)),
|
|
192
193
|
monorepo: isMonorepo(cwd),
|
|
@@ -41,7 +41,15 @@ export declare class SessionMetrics {
|
|
|
41
41
|
*/
|
|
42
42
|
recordBrowserLatency(ms: number): void;
|
|
43
43
|
/** A call for a tool that does not exist. Non-zero means our surface is confusing the agent. */
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* An agent reached for a tool that does not exist — and WHAT it reached for.
|
|
46
|
+
*
|
|
47
|
+
* The name is the point. A count says the surface confused someone; the name says which
|
|
48
|
+
* capability they expected us to have, in their own vocabulary. Safe to keep: it is a name from
|
|
49
|
+
* our own namespace, never app data. Bounded and truncated, because a guess is a short
|
|
50
|
+
* identifier and anything longer is not one.
|
|
51
|
+
*/
|
|
52
|
+
recordUnknownTool(name?: string): void;
|
|
45
53
|
recordToolCall(tool: string, args?: Record<string, unknown>): void;
|
|
46
54
|
/**
|
|
47
55
|
* One failed tool call. Grouped by fingerprint, but stored WITH its skeleton message and the tool
|
|
@@ -62,6 +70,28 @@ export declare class SessionMetrics {
|
|
|
62
70
|
breadcrumb: string[];
|
|
63
71
|
inFlight: string | undefined;
|
|
64
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Ask the agent for a verdict, once, when it has driven the page without asking for one.
|
|
75
|
+
*
|
|
76
|
+
* Measured 2026-08-10/11: of 170 sessions that made a tool call, 140 produced no verdict — and
|
|
77
|
+
* **137 of those never called a verdict-producing tool even once.** They drove the app 269 times
|
|
78
|
+
* with `reticle_act` and never asked whether it worked. This counter already existed
|
|
79
|
+
* (`abandonedActions`, non-zero in 58 sessions); it was reported to US and never to the agent.
|
|
80
|
+
*
|
|
81
|
+
* Three is the threshold because the median verdict-less session made four tool calls: any higher
|
|
82
|
+
* and it never fires for the sessions that need it. Re-arms after a verdict, so a second
|
|
83
|
+
* abandoned run is caught — the loop can break more than once in a long session.
|
|
84
|
+
*/
|
|
85
|
+
takeUnverifiedNudge(): string | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* An app's SDK dialled this daemon.
|
|
88
|
+
*
|
|
89
|
+
* Zero of these at shutdown means the daemon ran and no app ever connected — a broken install.
|
|
90
|
+
* Non-zero with no tool calls is the opposite problem. See SessionSummary.appConnects.
|
|
91
|
+
*/
|
|
92
|
+
recordAppConnected(): void;
|
|
93
|
+
/** One action driven at the page. Settled by the next verdict; see #unsettledActions. */
|
|
94
|
+
recordAction(): void;
|
|
65
95
|
recordVerification(): void;
|
|
66
96
|
/**
|
|
67
97
|
* One defect found in the app under test — the outcome number, kept broken down by kind.
|
|
@@ -80,12 +110,26 @@ export declare class SessionMetrics {
|
|
|
80
110
|
* express failure misses the only question worth asking: how often can people not get a browser?
|
|
81
111
|
*/
|
|
82
112
|
recordConnectAttempt(kind: BrowserLaunchKind): (failure?: ConnectFailure) => void;
|
|
83
|
-
recordClient(name: string): void;
|
|
113
|
+
recordClient(name: string, version?: string): void;
|
|
114
|
+
/**
|
|
115
|
+
* The agent's client went away.
|
|
116
|
+
*
|
|
117
|
+
* This is the only part of `endReason` that a query cannot derive from the counters: "the client
|
|
118
|
+
* detached" and "the agent stopped asking" produce identical numbers and are different findings —
|
|
119
|
+
* one is the task ending, the other is us losing them.
|
|
120
|
+
*/
|
|
121
|
+
recordClientLeft(): void;
|
|
122
|
+
/** How many times in a row the current tool has been called. Read by the friction invite. */
|
|
123
|
+
get currentRun(): number;
|
|
124
|
+
/** Reticle invited the agent to send feedback. The denominator for feedback_submitted. */
|
|
125
|
+
recordFeedbackPrompt(): void;
|
|
126
|
+
/** Which tool surface was advertised to agents this session. */
|
|
127
|
+
recordSurface(surface: string): void;
|
|
84
128
|
/**
|
|
85
129
|
* Roll the counters into the event payload. `final` marks a clean shutdown; a periodic flush passes
|
|
86
130
|
* false so a session killed before it can exit is not lost entirely (see the flush note in cli.ts).
|
|
87
131
|
*/
|
|
88
|
-
summarize(final: boolean): SessionSummary;
|
|
132
|
+
summarize(final: boolean, exit?: SessionSummary['exit']): SessionSummary;
|
|
89
133
|
/** True when nothing at all happened — a flush of an idle daemon is not worth an event. */
|
|
90
134
|
get empty(): boolean;
|
|
91
135
|
/** Zero the counters after a non-final flush so the next flush reports the NEXT window, not a total. */
|