@reticlehq/server 2.3.0 → 2.5.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 +702 -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} +1 -1
- package/dist/{bridge.js → bridge/bridge.js} +105 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
- 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-doctor.js → cli/cli-doctor.js} +9 -2
- 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.d.ts +143 -0
- package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
- package/dist/{cli-port.js → cli/cli-port.js} +29 -8
- 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.d.ts +2 -2
- package/dist/cli.js +182 -93
- 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 +49 -0
- package/dist/daemon/daemon-resilience.js +191 -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} +80 -48
- 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} +13 -0
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
- 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 +43 -20
- package/dist/events/predicate-eval.js +159 -55
- package/dist/events/predicate.d.ts +2 -2
- package/dist/events/predicate.js +112 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +1 -1
- 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.d.ts +0 -6
- package/dist/flows/decision.js +64 -8
- 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 +5 -7
- package/dist/flows/flow-replay.js +114 -47
- 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 +157 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +67 -13
- 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/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- 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 +16 -3
- package/dist/honesty/blind-spots.js +24 -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 +22 -3
- package/dist/honesty/verified.js +48 -8
- package/dist/http-server.js +14 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +8 -6
- package/dist/index.js +32 -25
- package/dist/init/agent-rules.d.ts +2 -1
- package/dist/init/agent-rules.js +45 -7
- 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.d.ts +17 -0
- package/dist/init/capabilities.js +54 -0
- package/dist/init/cra.d.ts +37 -0
- package/dist/init/cra.js +90 -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 +26 -0
- package/dist/init/detect.js +78 -5
- 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 +39 -0
- package/dist/init/next-patch.js +196 -0
- package/dist/init/node-io.js +48 -6
- package/dist/init/patch-kind.d.ts +20 -0
- package/dist/init/patch-kind.js +10 -0
- package/dist/init/plan-framework.d.ts +50 -0
- package/dist/init/plan-framework.js +341 -0
- package/dist/init/plan.d.ts +119 -6
- package/dist/init/plan.js +230 -131
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +22 -0
- package/dist/init/run.js +393 -28
- package/dist/init/slash-command.d.ts +27 -0
- package/dist/init/slash-command.js +61 -0
- package/dist/init/snippets.d.ts +41 -3
- package/dist/init/snippets.js +185 -10
- package/dist/init/vite-config.d.ts +4 -10
- package/dist/init/vite-config.js +15 -7
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.d.ts +15 -0
- package/dist/input/driven-redaction.js +76 -0
- package/dist/input/network-detail.d.ts +9 -3
- package/dist/input/network-detail.js +43 -23
- 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 +31 -0
- package/dist/mcp/mcp-outage.js +48 -0
- package/dist/mcp/mcp-proxy.d.ts +124 -0
- package/dist/mcp/mcp-proxy.js +697 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +110 -35
- 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/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/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 +38 -0
- package/dist/session/no-session-diagnosis.js +70 -0
- package/dist/session/no-session-watch.d.ts +19 -0
- package/dist/session/no-session-watch.js +61 -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/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +26 -7
- package/dist/session/session-info.js +42 -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 +3 -0
- package/dist/session/session-manager.js +58 -9
- 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 +20 -2
- package/dist/session/session.js +76 -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/daemon-telemetry.d.ts +19 -0
- package/dist/telemetry/daemon-telemetry.js +12 -2
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +4 -4
- package/dist/telemetry/feedback-context.d.ts +22 -0
- package/dist/telemetry/feedback-context.js +41 -1
- 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.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +2 -0
- package/dist/telemetry/session-metrics.js +68 -6
- package/dist/telemetry/telemetry.d.ts +27 -2
- package/dist/telemetry/telemetry.js +119 -29
- 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 +84 -115
- 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 +3 -1
- package/dist/tools/assert-verdict.js +8 -5
- package/dist/tools/browser-tools.js +30 -11
- package/dist/tools/contract-tools.js +1 -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 +90 -18
- package/dist/tools/error-recovery.d.ts +20 -2
- package/dist/tools/error-recovery.js +179 -4
- 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 -6
- package/dist/tools/invoke-tool.js +57 -32
- 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-result.d.ts +17 -0
- package/dist/tools/navigate-result.js +31 -0
- package/dist/tools/observe-tools.js +54 -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 +85 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +88 -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 +8 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +204 -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/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.md +28 -0
- package/docs/flows.md +166 -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/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +184 -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-launch.js +0 -112
- package/dist/cli-parse.d.ts +0 -140
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -35
- package/dist/mcp-proxy.js +0 -190
- 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-doctor.d.ts → cli/cli-doctor.d.ts} +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
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* `console-error`, `duplicate-request`. Never a selector, a URL, an element, or a description of
|
|
16
16
|
* anyone's app: that a class of defect was found, never what it was found in.
|
|
17
17
|
*/
|
|
18
|
+
import { ReticleTool } from '../tools/tool-names.js';
|
|
18
19
|
import { BugSource, ContradictionKind } from '@reticlehq/core';
|
|
19
20
|
/** Cap per tool call — a pathological crawl must not turn one call into a thousand events. */
|
|
20
21
|
const MAX_BUGS_PER_CALL = 25;
|
|
@@ -24,7 +25,7 @@ function kindsOf(value) {
|
|
|
24
25
|
return value
|
|
25
26
|
.map((entry) => {
|
|
26
27
|
const kind = entry?.kind;
|
|
27
|
-
return typeof kind
|
|
28
|
+
return 'string' === typeof kind ? kind : undefined;
|
|
28
29
|
})
|
|
29
30
|
.filter((kind) => kind !== undefined);
|
|
30
31
|
}
|
|
@@ -35,16 +36,111 @@ function kindsOf(value) {
|
|
|
35
36
|
* the case the product exists for — the screen looked right, the write failed, and every other tool
|
|
36
37
|
* on the market would have called it green. That pairing is what `falseGreen` marks.
|
|
37
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* The verdict this result carries, whatever envelope it arrived in.
|
|
41
|
+
*
|
|
42
|
+
* `reticle_assert` reports a top-level boolean `pass`. `reticle_act_and_wait` — the tool agents
|
|
43
|
+
* actually reach for, and the one whose description calls it the act->observe->assert loop — reports
|
|
44
|
+
* `verdict.pass` with the summary at `verified`. Reading only `result.pass` meant a FAILED
|
|
45
|
+
* act_and_wait produced no bug at all, and a contradiction found next to a PASSING one was recorded
|
|
46
|
+
* with `falseGreen: false` — deflating the single number this product exists to publish, by the
|
|
47
|
+
* shape of its own envelope.
|
|
48
|
+
*/
|
|
49
|
+
function verdictOf(result) {
|
|
50
|
+
if ('boolean' === typeof result['pass'])
|
|
51
|
+
return result['pass'];
|
|
52
|
+
const verdict = result['verdict'];
|
|
53
|
+
if ('object' === typeof verdict && verdict !== null) {
|
|
54
|
+
const nested = verdict['pass'];
|
|
55
|
+
if ('boolean' === typeof nested)
|
|
56
|
+
return nested;
|
|
57
|
+
}
|
|
58
|
+
// `verified` is the field the agent is told to gate on; "unknown" is deliberately not a verdict.
|
|
59
|
+
const verified = result['verified'];
|
|
60
|
+
if ('yes' === verified)
|
|
61
|
+
return true;
|
|
62
|
+
if ('no' === verified)
|
|
63
|
+
return false;
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/** The assertion label, from either envelope — act_and_wait nests it under `verdict`. */
|
|
67
|
+
function assertionOf(result) {
|
|
68
|
+
if (result['assertion'] !== undefined)
|
|
69
|
+
return result['assertion'];
|
|
70
|
+
const verdict = result['verdict'];
|
|
71
|
+
return 'object' === typeof verdict && verdict !== null
|
|
72
|
+
? verdict['assertion']
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A verdict that could not be reached is not a defect found. `inconclusive` is set when the
|
|
77
|
+
* assertion was never evaluated — an under-specified call, or nothing instrumented to read — so
|
|
78
|
+
* counting it would put the agent's own mistakes into the number we publish.
|
|
79
|
+
*/
|
|
80
|
+
function isInconclusive(result) {
|
|
81
|
+
const verdict = result['verdict'];
|
|
82
|
+
return ('object' === typeof verdict &&
|
|
83
|
+
verdict !== null &&
|
|
84
|
+
'string' === typeof verdict['inconclusive']);
|
|
85
|
+
}
|
|
86
|
+
/** A suite row whose replay never STARTED — see SuiteFlowResult.couldNotRun. */
|
|
87
|
+
function couldNotRun(failure) {
|
|
88
|
+
return ('object' === typeof failure &&
|
|
89
|
+
failure !== null &&
|
|
90
|
+
true === failure.couldNotRun);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
|
|
94
|
+
*
|
|
95
|
+
* It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
|
|
96
|
+
* `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
|
|
97
|
+
* poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
|
|
98
|
+
* it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
|
|
99
|
+
* one defect that genuinely was a bad agent predicate carried none. One session would have published
|
|
100
|
+
* "2 defects in the app" against a true count of 0.
|
|
101
|
+
*
|
|
102
|
+
* A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
|
|
103
|
+
* the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
|
|
104
|
+
* publishing a false accusation about somebody's product is not.
|
|
105
|
+
*
|
|
106
|
+
* Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
|
|
107
|
+
* today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
|
|
108
|
+
* a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
|
|
109
|
+
* Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
|
|
110
|
+
* output is no owner at all.
|
|
111
|
+
*/
|
|
38
112
|
export function bugsInResult(toolName, result) {
|
|
113
|
+
// `reticle_run` is a WRAPPER: its handler calls runTool on the real tool, which already reported
|
|
114
|
+
// that result's defects under the real tool's name — then the outer chokepoint reports the very
|
|
115
|
+
// same object again as `reticle_run`. One sweep of 34 events was a perfect mirror image:
|
|
116
|
+
// flow-regression x8/reticle_flow_verify AND x8/reticle_run; assertion-failed x8/reticle_verify_change
|
|
117
|
+
// AND x8/reticle_run. Sixteen of the thirty-four were echoes, and a headline number that doubles
|
|
118
|
+
// because of HOW a tool was reached is not a measurement.
|
|
119
|
+
//
|
|
120
|
+
// Nothing is lost by staying silent here: when the inner tool refuses or throws, the wrapper
|
|
121
|
+
// returns { error, tool, params, hint }, which carries no verdict and no failures to classify.
|
|
122
|
+
if (toolName === ReticleTool.RUN)
|
|
123
|
+
return [];
|
|
39
124
|
const bugs = [];
|
|
40
|
-
const
|
|
125
|
+
const verdict = verdictOf(result);
|
|
126
|
+
const passed = true === verdict;
|
|
41
127
|
// 1. Contradictions — channels disagreeing. Invisible to a human watching the screen.
|
|
42
128
|
for (const kind of kindsOf(result['contradictions'])) {
|
|
43
|
-
|
|
129
|
+
// A contradiction is two of the APP's own channels disagreeing — never the agent's phrasing.
|
|
130
|
+
// Omitted for the one kind Reticle can produce on its own: see contradictionAttribution.
|
|
131
|
+
bugs.push({
|
|
132
|
+
source: BugSource.CONTRADICTION,
|
|
133
|
+
kind,
|
|
134
|
+
falseGreen: passed,
|
|
135
|
+
tool: toolName,
|
|
136
|
+
});
|
|
44
137
|
}
|
|
45
138
|
// 2. Crawl anomalies — found autonomously, with nobody writing a test. Crawl reports its own
|
|
46
139
|
// contradictions inside `anomalies` too, so classify by kind rather than by which array it sat in.
|
|
47
140
|
for (const kind of kindsOf(result['anomalies'])) {
|
|
141
|
+
// Crawl reports contradictions in this array too, so the same kind must get the same owner
|
|
142
|
+
// wherever it arrives — a `request-never-settled` found by the crawler is no better evidence
|
|
143
|
+
// about whose fault it was than one found by an assertion.
|
|
48
144
|
bugs.push({
|
|
49
145
|
source: BugSource.CRAWL,
|
|
50
146
|
kind,
|
|
@@ -52,28 +148,44 @@ export function bugsInResult(toolName, result) {
|
|
|
52
148
|
// — even though no assertion was made. See the definition on `falseGreen`.
|
|
53
149
|
falseGreen: CONTRADICTION_KINDS.has(kind),
|
|
54
150
|
tool: toolName,
|
|
151
|
+
// Nobody wrote a predicate here, so there is no bad request to blame: a crawl anomaly is a
|
|
152
|
+
// fault the app produced on its own.
|
|
55
153
|
});
|
|
56
154
|
}
|
|
57
155
|
// 3. A failed assertion — the agent declared a consequence and it did not hold.
|
|
58
156
|
// Only when nothing above already explained it, or one defect would be counted twice.
|
|
59
|
-
|
|
60
|
-
|
|
157
|
+
// A red the APP earned, not one Reticle produced. `verified: "unknown"` is the honesty layer
|
|
158
|
+
// saying nothing was proved either way, and a top-level `error` is a refusal — measured over one
|
|
159
|
+
// sweep, every bug_found emitted was one of these two: "no session connected" from reticle_run and
|
|
160
|
+
// an unverifiable suite from reticle_verify_change, counted as defects in the user's app.
|
|
161
|
+
const reticleFailure = 'unknown' === result['verified'] || 'string' === typeof result['error'];
|
|
162
|
+
if (false === verdict && 0 === bugs.length && !isInconclusive(result) && !reticleFailure) {
|
|
163
|
+
const reason = assertionOf(result);
|
|
164
|
+
const kind = 'string' === typeof reason && reason.length > 0 ? reason.slice(0, 64) : 'assertion-failed';
|
|
61
165
|
bugs.push({
|
|
62
166
|
source: BugSource.ASSERTION,
|
|
63
|
-
kind
|
|
167
|
+
kind,
|
|
64
168
|
falseGreen: false,
|
|
65
169
|
tool: toolName,
|
|
170
|
+
// Omitted when the label cannot say whose fault it was — see ASSERTION_ATTRIBUTION.
|
|
66
171
|
});
|
|
67
172
|
}
|
|
68
173
|
// 4. Replay failures — a saved flow that used to pass no longer does. That is a regression caught,
|
|
69
174
|
// which is a different and stronger claim than "a check failed".
|
|
70
|
-
if (
|
|
71
|
-
for (const
|
|
175
|
+
if ('fail' === result['status'] && Array.isArray(result['failures'])) {
|
|
176
|
+
for (const failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
|
|
177
|
+
// A suite that failed because nothing could RUN is not a regression in the user's app. This
|
|
178
|
+
// rule never looked past `status`, so a project whose flow files did not resolve reported one
|
|
179
|
+
// "defect found" per flow — Reticle's own failure, published as the customer's.
|
|
180
|
+
if (couldNotRun(failure))
|
|
181
|
+
continue;
|
|
72
182
|
bugs.push({
|
|
73
183
|
source: BugSource.REPLAY,
|
|
74
184
|
kind: 'flow-regression',
|
|
75
185
|
falseGreen: false,
|
|
76
186
|
tool: toolName,
|
|
187
|
+
// A saved flow that used to pass and no longer does is a regression in the app. Rows that
|
|
188
|
+
// never RAN are skipped above precisely so Reticle's own failures do not land here.
|
|
77
189
|
});
|
|
78
190
|
}
|
|
79
191
|
}
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* `_daemon` is EXCLUDED, and that exclusion is the whole point of this function. `reticle mcp` and
|
|
5
|
-
* `reticle serve` start the daemon by re-running this very binary, so the child re-entered the CLI
|
|
6
|
-
* entry point and emitted a second event for what a person experienced as ONE action. The old
|
|
7
|
-
* `invoke` metric was therefore inflated ~2x — and inflated worst on the agent-driven sessions that
|
|
8
|
-
* matter most, while one-shot commands like `version` counted once. That skewed the RATIO between
|
|
9
|
-
* commands, not merely the scale, which is the kind of error you cannot correct for after the fact.
|
|
10
|
-
* The daemon\'s own lifecycle is already reported by `daemon_started` / `daemon_stopped`; a spawned
|
|
11
|
-
* daemon is not a CLI run.
|
|
12
|
-
*/
|
|
1
|
+
/** True when this command represents a person typing something. Exported for the gate. */
|
|
2
|
+
export declare function isHumanCliCommand(command: string): boolean;
|
|
13
3
|
export declare function reportCliRun(argv: readonly string[]): void;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Lives in its own module so `cli.ts` stays a dispatcher, and so the `_daemon` exclusion below sits
|
|
5
5
|
* next to the explanation of why it exists rather than buried in a startup function.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import { DAEMON_INNER_COMMAND, knownCommand } from '../cli-parse.js';
|
|
7
|
+
import { TelemetryEventKind } from '@reticlehq/core';
|
|
8
|
+
import { DAEMON_INNER_COMMAND, knownCommand } from '../cli/cli-parse.js';
|
|
9
9
|
import { getTelemetry } from './telemetry.js';
|
|
10
10
|
import { describeCliFlags } from './argument-shape.js';
|
|
11
11
|
/**
|
|
@@ -20,22 +20,45 @@ import { describeCliFlags } from './argument-shape.js';
|
|
|
20
20
|
* The daemon\'s own lifecycle is already reported by `daemon_started` / `daemon_stopped`; a spawned
|
|
21
21
|
* daemon is not a CLI run.
|
|
22
22
|
*/
|
|
23
|
+
/**
|
|
24
|
+
* The agent's MCP transport, not a person. `reticle mcp` is what an MCP client runs to open a stdio
|
|
25
|
+
* connection — nobody types it. Measured over one real day it was 475 of 561 `cli_command_run`
|
|
26
|
+
* events (85%), on an event whose whole purpose is human intent, and the agent attaching is already
|
|
27
|
+
* reported by `mcp_client_connected` with strictly more detail (reconnect, daemon age, client).
|
|
28
|
+
*
|
|
29
|
+
* `serve` is NOT excluded: nothing spawns it on a person's behalf, so typing it is a real act.
|
|
30
|
+
*/
|
|
31
|
+
const AGENT_TRANSPORT_COMMAND = 'mcp';
|
|
32
|
+
/** True when this command represents a person typing something. Exported for the gate. */
|
|
33
|
+
export function isHumanCliCommand(command) {
|
|
34
|
+
return command !== AGENT_TRANSPORT_COMMAND && command !== DAEMON_INNER_COMMAND;
|
|
35
|
+
}
|
|
23
36
|
export function reportCliRun(argv) {
|
|
24
37
|
const firstArg = argv[0];
|
|
25
38
|
if (firstArg === DAEMON_INNER_COMMAND)
|
|
26
39
|
return;
|
|
40
|
+
const command = knownCommand(firstArg);
|
|
27
41
|
const telemetry = getTelemetry();
|
|
42
|
+
// FIRST — before the human-command filter. The install happened whichever command ran, and on most
|
|
43
|
+
// machines the very first contact is the agent spawning `reticle mcp`, which that filter excludes.
|
|
44
|
+
// Measured over one sweep: 15 `init_completed` and ZERO `reticle_installed`, in a run that
|
|
45
|
+
// installed the SDK into nine apps — the top of every funnel, missing.
|
|
28
46
|
// `detach` so a quick command (`version`/`gate`) exits immediately instead of waiting out the POST.
|
|
29
47
|
if (telemetry.firstRun)
|
|
30
48
|
void telemetry.emit(TelemetryEventKind.RETICLE_INSTALLED, { detach: true });
|
|
49
|
+
if (!isHumanCliCommand(command))
|
|
50
|
+
return;
|
|
31
51
|
void telemetry.emit(TelemetryEventKind.CLI_COMMAND_RUN, {
|
|
32
52
|
detach: true,
|
|
33
|
-
actor
|
|
53
|
+
// No `actor`: with the agent's transport excluded, this event is human BY DEFINITION. It was
|
|
54
|
+
// constant `human` on all 561 events in a real day — zero information, and wrong for the 475
|
|
55
|
+
// that were the agent. `actor` stays where it earns its place: verification_completed and
|
|
56
|
+
// bug_found, where it splits the agent's own loop from a human/CI-triggered run.
|
|
34
57
|
// A fixed, low-cardinality vocabulary WE define, so it is safe to send whole and it is the closest
|
|
35
58
|
// honest read we have on intent: `verify` and `gate` mean something very different from `status`.
|
|
36
59
|
// An unrecognized first arg reports as `unknown` rather than being echoed — an echo would put
|
|
37
60
|
// whatever someone mistyped, including a path or a URL, straight onto the wire.
|
|
38
|
-
command
|
|
61
|
+
command,
|
|
39
62
|
// Which flags were PRESENT, by name only — never their values. `--http-token` alone makes that
|
|
40
63
|
// rule absolute rather than case-by-case; `--drive` and `--storage-state` carry a URL and a path.
|
|
41
64
|
flags: describeCliFlags(argv),
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How often a long-lived daemon flushes its counters.
|
|
3
|
+
*
|
|
4
|
+
* ponytail: coarse on purpose. A daemon that is SIGKILLed — OOM, a closed laptop, `kill -9` — never
|
|
5
|
+
* reaches its shutdown handler, so without this the whole session's counters die with it. Thirty
|
|
6
|
+
* minutes bounds that loss to one window while keeping the event volume near zero for the common case
|
|
7
|
+
* (most daemons stop cleanly and send exactly one summary). If the lost windows ever show up as a
|
|
8
|
+
* real gap, the upgrade is a small on-disk journal replayed at the next start, not a shorter timer.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* How often a running daemon rolls up its window.
|
|
12
|
+
*
|
|
13
|
+
* Exported because it is the BOUND ON WHAT IS LOST: a daemon that has served a tool never
|
|
14
|
+
* idle-exits, so nothing calls shutdown, so its last partial window dies with the process. At 30
|
|
15
|
+
* minutes against a median 28-minute session that meant the median session reported nothing at all.
|
|
16
|
+
* Only non-empty windows emit, so a shorter interval costs nothing on the 74% of daemons that never
|
|
17
|
+
* serve a tool.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SESSION_FLUSH_MS: number;
|
|
1
20
|
/** Stops the timers this installed. Called from the daemon's shutdown path. */
|
|
2
21
|
export interface DaemonTelemetry {
|
|
3
22
|
/**
|
|
@@ -23,7 +23,16 @@ const PROJECT_PROFILE_DELAY_MS = 5_000;
|
|
|
23
23
|
* (most daemons stop cleanly and send exactly one summary). If the lost windows ever show up as a
|
|
24
24
|
* real gap, the upgrade is a small on-disk journal replayed at the next start, not a shorter timer.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* How often a running daemon rolls up its window.
|
|
28
|
+
*
|
|
29
|
+
* Exported because it is the BOUND ON WHAT IS LOST: a daemon that has served a tool never
|
|
30
|
+
* idle-exits, so nothing calls shutdown, so its last partial window dies with the process. At 30
|
|
31
|
+
* minutes against a median 28-minute session that meant the median session reported nothing at all.
|
|
32
|
+
* Only non-empty windows emit, so a shorter interval costs nothing on the 74% of daemons that never
|
|
33
|
+
* serve a tool.
|
|
34
|
+
*/
|
|
35
|
+
export const SESSION_FLUSH_MS = 5 * 60 * 1000;
|
|
27
36
|
/**
|
|
28
37
|
* Report the daemon as started, profile the project once, and keep the session counters flushing.
|
|
29
38
|
* Every timer is `unref`'d so telemetry can never be the reason a daemon refuses to exit.
|
|
@@ -51,7 +60,8 @@ export function installDaemonTelemetry(cwd, now = () => Date.now()) {
|
|
|
51
60
|
const flush = setInterval(() => {
|
|
52
61
|
if (metrics.empty)
|
|
53
62
|
return; // an idle window is not worth an event
|
|
54
|
-
|
|
63
|
+
// NOT daemon_stopped: the daemon is still running. See SESSION_PROGRESS.
|
|
64
|
+
void getTelemetry().emit(TelemetryEventKind.SESSION_PROGRESS, {
|
|
55
65
|
session: metrics.summarize(false),
|
|
56
66
|
});
|
|
57
67
|
metrics.reset();
|
|
@@ -21,7 +21,7 @@ export function isReticleSourceCheckout(cwd) {
|
|
|
21
21
|
if (existsSync(pkg)) {
|
|
22
22
|
try {
|
|
23
23
|
const parsed = JSON.parse(readFileSync(pkg, 'utf8'));
|
|
24
|
-
const name = typeof parsed
|
|
24
|
+
const name = 'object' === typeof parsed && parsed !== null
|
|
25
25
|
? parsed.name
|
|
26
26
|
: undefined;
|
|
27
27
|
if (name === MONOREPO_NAME)
|
|
@@ -42,7 +42,7 @@ export function isReticleSourceCheckout(cwd) {
|
|
|
42
42
|
/** Strip one layer of matching quotes from a .env value. */
|
|
43
43
|
function unquote(value) {
|
|
44
44
|
const first = value[0];
|
|
45
|
-
if ((
|
|
45
|
+
if (('"' === first || "'" === first) && value.endsWith(first) && value.length >= 2) {
|
|
46
46
|
return value.slice(1, -1);
|
|
47
47
|
}
|
|
48
48
|
return value;
|
|
@@ -67,13 +67,13 @@ export function loadDotEnv(dir, env = process.env) {
|
|
|
67
67
|
}
|
|
68
68
|
for (const raw of text.split('\n')) {
|
|
69
69
|
const line = raw.trim();
|
|
70
|
-
if (line.length
|
|
70
|
+
if (0 === line.length || line.startsWith('#'))
|
|
71
71
|
continue;
|
|
72
72
|
const eq = line.indexOf('=');
|
|
73
73
|
if (eq <= 0)
|
|
74
74
|
continue; // not a KEY=value pair — skip rather than fail the whole file
|
|
75
75
|
const key = line.slice(0, eq).trim();
|
|
76
|
-
if (key.length
|
|
76
|
+
if (0 === key.length || env[key] !== undefined)
|
|
77
77
|
continue;
|
|
78
78
|
env[key] = unquote(line.slice(eq + 1).trim());
|
|
79
79
|
}
|
|
@@ -19,6 +19,25 @@ import { createHash } from 'node:crypto';
|
|
|
19
19
|
* quoted value often contains the very numbers and paths the later rules would only partly catch.
|
|
20
20
|
*/
|
|
21
21
|
const VARIABLE_PARTS = [
|
|
22
|
+
// ── Identifiers and credentials FIRST ────────────────────────────────────────────────────────
|
|
23
|
+
//
|
|
24
|
+
// The rules below this block were written to make messages GROUPABLE — blank the variable parts so
|
|
25
|
+
// the same defect hashes the same everywhere. Redaction was a side effect of that, and a side
|
|
26
|
+
// effect is not a guarantee. A telemetry audit caught `bob@acme.com` arriving verbatim in
|
|
27
|
+
// `crash_message`; probing further, API-key-shaped tokens survived intact and a JWT was only
|
|
28
|
+
// half-masked. This function also feeds `session.errors[]` on EVERY session summary, so the
|
|
29
|
+
// exposure was every session that logged an error naming a user, not a rare crash path.
|
|
30
|
+
//
|
|
31
|
+
// Order matters: these run before the generic patterns, because `\d+` and the hex rule would chew
|
|
32
|
+
// a token into pieces that no longer match a credential shape while still leaking most of it —
|
|
33
|
+
// which is exactly what happened to the JWT.
|
|
34
|
+
/[\w.+-]+@[\w-]+\.[\w.-]+/g, // email addresses
|
|
35
|
+
/\beyJ[\w-]*\.[\w-]*\.?[\w-]*/g, // JWTs (header segment is always base64 `eyJ`)
|
|
36
|
+
/\b(?:sk|pk|rk|ghp|gho|ghs|ghu|ghr|github_pat|xox[abposr]|AKIA|ASIA|glpat)[_-][A-Za-z0-9_-]{8,}/gi,
|
|
37
|
+
// Anything else long enough and dense enough to be a secret rather than a word. The catch-all for
|
|
38
|
+
// formats nobody here has seen: on a privacy boundary, unrecognised is not the same as safe.
|
|
39
|
+
/\b[A-Za-z0-9_-]{24,}\b/g,
|
|
40
|
+
// ── Then the grouping rules ──────────────────────────────────────────────────────────────────
|
|
22
41
|
/"[^"]*"|'[^']*'|`[^`]*`/g, // quoted values — names, selectors, snippets
|
|
23
42
|
/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, // URLs
|
|
24
43
|
/(?:\/[\w.-]+){2,}\/?/g, // POSIX-ish paths
|
|
@@ -53,7 +72,7 @@ export function reticleFrames(stack) {
|
|
|
53
72
|
if (!/@reticlehq[/\\]|[/\\]reticle[/\\]dist[/\\]/.test(line))
|
|
54
73
|
continue;
|
|
55
74
|
const location = line.match(/([\w.-]+\.(?:js|mjs|cjs|ts)):(\d+)/);
|
|
56
|
-
if (
|
|
75
|
+
if (null === location)
|
|
57
76
|
continue;
|
|
58
77
|
// `at Object.runTool (/path/to/invoke-tool.js:88:3)` → `runTool`. The function name is half the
|
|
59
78
|
// value of a frame in an RCA — "it died in act-tools.js" narrows to a file, "it died in
|
|
@@ -62,7 +81,7 @@ export function reticleFrames(stack) {
|
|
|
62
81
|
const where = `${location[1] ?? ''}:${location[2] ?? ''}`;
|
|
63
82
|
// `Object.` / `Module.` prefixes are V8 noise that make the same function look like two.
|
|
64
83
|
const name = fn?.replace(/^(?:Object|Module|Function)\./, '');
|
|
65
|
-
frames.push(name === undefined ||
|
|
84
|
+
frames.push(name === undefined || '' === name ? where : `${name}@${where}`);
|
|
66
85
|
}
|
|
67
86
|
return frames;
|
|
68
87
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FeedbackKind, FeedbackSource } from '@reticlehq/core';
|
|
2
|
-
import { BUG_FLAG, RATING_FLAG } from '../cli-parse.js';
|
|
2
|
+
import { BUG_FLAG, RATING_FLAG } from '../cli/cli-parse.js';
|
|
3
3
|
import { describeFeedbackPayload, submitFeedback } from './feedback.js';
|
|
4
4
|
import { describeTelemetry, setTelemetryEnabled } from './telemetry.js';
|
|
5
|
-
import { TelemetryAction } from '../cli-parse.js';
|
|
5
|
+
import { TelemetryAction } from '../cli/cli-parse.js';
|
|
6
6
|
import { clearIdentity, IDENTIFY_NOTICE, readIdentity, saveIdentity, submitIdentity, UsageContextKind, } from './identify.js';
|
|
7
7
|
/**
|
|
8
8
|
* `reticle feedback [--rating 1-5] [--bug] "what happened"` — the human half of the feedback channel.
|
|
@@ -18,7 +18,7 @@ export async function handleFeedback(text, rating, bug) {
|
|
|
18
18
|
const line = (s) => {
|
|
19
19
|
process.stdout.write(`${s}\n`);
|
|
20
20
|
};
|
|
21
|
-
if (
|
|
21
|
+
if ('' === text && rating === undefined) {
|
|
22
22
|
line(`usage: reticle feedback [${RATING_FLAG} 1-5] [${BUG_FLAG}] "what worked, what didn't"`);
|
|
23
23
|
line(' your words go to the maintainers; nothing from your app is ever included.');
|
|
24
24
|
process.exitCode = 1;
|
|
@@ -28,7 +28,7 @@ export async function handleFeedback(text, rating, bug) {
|
|
|
28
28
|
source: FeedbackSource.HUMAN,
|
|
29
29
|
kind: bug ? FeedbackKind.BUG : FeedbackKind.EXPERIENCE,
|
|
30
30
|
// A rating with no words is a real report, but the schema wants text — say what the rating means.
|
|
31
|
-
text:
|
|
31
|
+
text: '' === text ? `rated ${String(rating)}/5 with no comment` : text,
|
|
32
32
|
...(rating !== undefined ? { rating } : {}),
|
|
33
33
|
});
|
|
34
34
|
line(`sending ${JSON.stringify(describeFeedbackPayload(receipt.context))}`);
|
|
@@ -27,6 +27,28 @@ export interface SessionFacts {
|
|
|
27
27
|
engine?: string;
|
|
28
28
|
/** True when Reticle drives the page over CDP rather than observing the human's own browser. */
|
|
29
29
|
driven?: boolean;
|
|
30
|
+
/** What the APP said it is running, from the SDK's hello. The only session-scoped stack evidence. */
|
|
31
|
+
adapters?: string[];
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Reconcile what the DIRECTORY says with what the APP says.
|
|
35
|
+
*
|
|
36
|
+
* `detectStack` reads the daemon's cwd, which is not the session's project whenever one daemon
|
|
37
|
+
* serves several apps — the normal case for anyone with two dev servers, and the default in the
|
|
38
|
+
* fixtures. Reported from the field: feedback filed from an astro session arrived stamped
|
|
39
|
+
* `"stack":"sveltekit"`, because that was the directory the daemon happened to be started in. The
|
|
40
|
+
* sessionId was passed and ignored.
|
|
41
|
+
*
|
|
42
|
+
* The rule: keep the directory's answer only while the app agrees with it. An adapter that implies a
|
|
43
|
+
* different family means the directory is describing somebody else's project, so the app's own
|
|
44
|
+
* report wins — less specific, and true, which is the right trade for an attribution field.
|
|
45
|
+
*/
|
|
46
|
+
export declare function reconcileStack(fromCwd: {
|
|
47
|
+
stack?: string;
|
|
48
|
+
stackMajor?: number;
|
|
49
|
+
}, adapters: readonly string[] | undefined): {
|
|
50
|
+
stack?: string;
|
|
51
|
+
stackMajor?: number;
|
|
52
|
+
};
|
|
31
53
|
/** Assemble the full context. Every field independently optional — a partial context still ships. */
|
|
32
54
|
export declare function feedbackContext(cwd: string, session?: SessionFacts): FeedbackContext;
|
|
@@ -116,6 +116,43 @@ function detectClient() {
|
|
|
116
116
|
return {};
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Which cwd-derived stacks each SDK adapter is consistent with.
|
|
121
|
+
*
|
|
122
|
+
* The adapter names a UI library; the directory may name a meta-framework built on it. `react` and
|
|
123
|
+
* `next` are the same app described at two levels, so the more specific one is kept. `react` and
|
|
124
|
+
* `sveltekit` are not — that pairing means the daemon's directory is not this session's project.
|
|
125
|
+
*/
|
|
126
|
+
const ADAPTER_IMPLIES = {
|
|
127
|
+
react: ['react', 'next', 'remix'],
|
|
128
|
+
svelte: ['svelte', 'sveltekit'],
|
|
129
|
+
vue: ['vue', 'nuxt'],
|
|
130
|
+
solid: ['solid'],
|
|
131
|
+
angular: ['angular'],
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Reconcile what the DIRECTORY says with what the APP says.
|
|
135
|
+
*
|
|
136
|
+
* `detectStack` reads the daemon's cwd, which is not the session's project whenever one daemon
|
|
137
|
+
* serves several apps — the normal case for anyone with two dev servers, and the default in the
|
|
138
|
+
* fixtures. Reported from the field: feedback filed from an astro session arrived stamped
|
|
139
|
+
* `"stack":"sveltekit"`, because that was the directory the daemon happened to be started in. The
|
|
140
|
+
* sessionId was passed and ignored.
|
|
141
|
+
*
|
|
142
|
+
* The rule: keep the directory's answer only while the app agrees with it. An adapter that implies a
|
|
143
|
+
* different family means the directory is describing somebody else's project, so the app's own
|
|
144
|
+
* report wins — less specific, and true, which is the right trade for an attribution field.
|
|
145
|
+
*/
|
|
146
|
+
export function reconcileStack(fromCwd, adapters) {
|
|
147
|
+
const claimed = adapters?.find((adapter) => adapter in ADAPTER_IMPLIES);
|
|
148
|
+
if (claimed === undefined)
|
|
149
|
+
return fromCwd;
|
|
150
|
+
const consistent = ADAPTER_IMPLIES[claimed] ?? [];
|
|
151
|
+
if (fromCwd.stack !== undefined && consistent.includes(fromCwd.stack))
|
|
152
|
+
return fromCwd;
|
|
153
|
+
// The major belonged to the directory's framework, so it cannot travel with a different stack.
|
|
154
|
+
return { stack: claimed };
|
|
155
|
+
}
|
|
119
156
|
function asRuntime(value) {
|
|
120
157
|
return Object.values(AppRuntime).find((known) => known === value);
|
|
121
158
|
}
|
|
@@ -131,7 +168,10 @@ export function feedbackContext(cwd, session) {
|
|
|
131
168
|
const engine = asEngine(session?.engine);
|
|
132
169
|
const client = detectClient();
|
|
133
170
|
return {
|
|
134
|
-
|
|
171
|
+
// Session-scoped when the app reports something that contradicts the directory — see
|
|
172
|
+
// reconcileStack. A daemon serving two apps otherwise stamps both with whichever one it was
|
|
173
|
+
// started next to.
|
|
174
|
+
...reconcileStack(detectStack(cwd), session?.adapters),
|
|
135
175
|
...client,
|
|
136
176
|
...(runtime !== undefined ? { runtime } : {}),
|
|
137
177
|
...(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;
|