@reticlehq/server 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Verification at the deploy choke point (Vercel / Netlify)
|
|
2
|
+
|
|
3
|
+
The strongest place to enforce verification is the moment code becomes a preview URL. Every deploy already produces one, and both Vercel and Netlify let a third party attach a **check** to it — pass/fail, shown on the PR, with no workflow file to write.
|
|
4
|
+
|
|
5
|
+
That makes the non-developer story complete: the SDK is auto-injected by the build plugin, flows are minted from toolbar recordings with auto-proposed consequences, and verification runs at publish — **without anyone writing a test.**
|
|
6
|
+
|
|
7
|
+
> Docs-only in v2.2.0. The pieces below all ship today (`reticle verify` exits 0/1 and persists a run artifact); what is _not_ built is a hosted Reticle app that registers itself as a provider. Wire it with the CI recipe until that exists.
|
|
8
|
+
|
|
9
|
+
## The shape
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
git push → preview deploy → Reticle verifies the preview URL → check passes/fails on the PR
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`reticle verify <preview-url>` is the whole integration surface:
|
|
16
|
+
|
|
17
|
+
- exits **0** when every saved flow passes, **1** otherwise — the only contract a check needs,
|
|
18
|
+
- prints a legible ✓/✗ report for the PR log,
|
|
19
|
+
- persists a `ReticleVerificationRun` artifact (`.reticle/runs/<id>.json`) so `reticle gate` and `reticle_run_export` can consume the same verdict.
|
|
20
|
+
|
|
21
|
+
## Recipe A — CI (works today, any provider)
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
# .github/workflows/verify.yml
|
|
25
|
+
name: reticle
|
|
26
|
+
on: [deployment_status]
|
|
27
|
+
jobs:
|
|
28
|
+
verify:
|
|
29
|
+
if: github.event.deployment_status.state == 'success'
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: pnpm/action-setup@v4
|
|
34
|
+
- run: pnpm install --frozen-lockfile
|
|
35
|
+
- run: npx playwright install --with-deps chromium
|
|
36
|
+
# Reticle drives the preview and owns its own browser + daemon.
|
|
37
|
+
- run: npx reticle verify "${{ github.event.deployment_status.target_url }}" --timeout 60000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The job's own pass/fail becomes the PR check. Nothing else is required.
|
|
41
|
+
|
|
42
|
+
**Non-loopback previews need pairing.** For a real preview URL (not `localhost`), Reticle injects `reticle.connect()` with a one-time token and allow-lists the preview origin — so the app does not need to be rebuilt per environment. Confirm the SDK actually runs on the deployed build: a production build that tree-shakes the dev-only SDK will connect to nothing, and `verify` will (correctly) fail with _"no app connected."_
|
|
43
|
+
|
|
44
|
+
## Recipe B — the Checks API pattern (what a hosted Reticle would do)
|
|
45
|
+
|
|
46
|
+
Both providers expose the same shape, which is why this is one integration rather than two:
|
|
47
|
+
|
|
48
|
+
| Provider | Hook | Result surface |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| Vercel | Deployment webhook → run the check → report back | Checks on the deployment / PR |
|
|
51
|
+
| Netlify | Deploy-succeeded webhook → run the check → report back | Deploy summary / PR |
|
|
52
|
+
|
|
53
|
+
The flow is: receive the deploy webhook → `reticle verify <target_url>` → post the verdict (and the `repair.failurePackets[]` from the run artifact) back as the check output. The artifact is stable and versioned precisely so a host platform can render it without parsing logs.
|
|
54
|
+
|
|
55
|
+
## Pair it with the local gate
|
|
56
|
+
|
|
57
|
+
The deploy check catches what reaches a preview. `reticle gate` catches it earlier — an agent that edits a covered file cannot "finish" without re-verifying:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
reticle gate --since origin/main # exit 1 unless passing artifacts cover the affected flows
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use both: `gate` in the agent's Stop hook (see `agent-cheatsheet`), `verify` at the deploy. They read the same run artifacts, so a green gate locally and a green check on the PR mean the same thing.
|
|
64
|
+
|
|
65
|
+
## Honest limits
|
|
66
|
+
|
|
67
|
+
- **`verify` needs one connected session.** If several tabs of the app are open against the same daemon it refuses rather than guessing which to drive.
|
|
68
|
+
- **It replays flows sequentially against one tab**, so flows must not depend on each other's leftover state (a flow that logs in contaminates the next one). Author self-contained flows, or use `reticle_flow_verify { parallel }`, which gives each flow an isolated context.
|
|
69
|
+
- **No saved flows means nothing to verify** — `verify` fails rather than reporting a vacuous pass.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Desktop apps: Electron & Tauri
|
|
2
|
+
|
|
3
|
+
Reticle verifies desktop apps the same way it verifies web apps — from **inside** the app, over a localhost WebSocket. There is no browser to open and no screenshot to interpret.
|
|
4
|
+
|
|
5
|
+
- [How you actually test a desktop app](#how-you-actually-test-a-desktop-app)
|
|
6
|
+
- [Electron](#electron)
|
|
7
|
+
- [Tauri](#tauri)
|
|
8
|
+
- [What IPC looks like to an agent](#what-ipc-looks-like-to-an-agent)
|
|
9
|
+
- [Troubleshooting](#troubleshooting)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## How you actually test a desktop app
|
|
14
|
+
|
|
15
|
+
The usual question is _"it's a desktop app — what URL does the agent open?"_ None. The direction is reversed from what browser tooling trains you to expect:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
┌──────────────┐ MCP ┌───────────────────┐ WebSocket ┌───────────────────────┐
|
|
19
|
+
│ coding agent │◀────────▶│ reticle daemon │◀──────────────▶│ your Electron/Tauri │
|
|
20
|
+
│ │ stdio │ (localhost:4400) │ the app │ window + the SDK │
|
|
21
|
+
└──────────────┘ └───────────────────┘ dials OUT └───────────────────────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Your app **connects to the daemon**, not the other way round. So the workflow is:
|
|
25
|
+
|
|
26
|
+
1. Start the daemon once: `npx @reticlehq/server serve`
|
|
27
|
+
2. Start your app exactly as you always do: `npm run dev`, `electron .`, `cargo tauri dev`.
|
|
28
|
+
3. That's it. `reticle status` now lists your window as a session, and the agent drives it.
|
|
29
|
+
|
|
30
|
+
`reticle open` has nothing to open for a desktop app and will say so, and there is no `reticle drive` for desktop — those launch a browser, which is not what you are testing. Headless works on both runtimes; see below.
|
|
31
|
+
|
|
32
|
+
## What works, measured
|
|
33
|
+
|
|
34
|
+
Every tool below was run against both demo apps against a live daemon.
|
|
35
|
+
|
|
36
|
+
The rows in **bold** are the ones a committed battery re-proves on every change — `pnpm test:e2e:desktop`, which starts a real Electron main process and a **packaged** Tauri binary (`tauri://localhost`, not `tauri dev`) and drives them headless. The rest were measured by hand. That distinction matters: this table used to report a hand-run score with nothing in the repo that reproduced it, so it could go stale without anything failing.
|
|
37
|
+
|
|
38
|
+
| Capability | Electron | Tauri | Note |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| sessions, snapshot, query, inspect | ✅ | ✅ | `inspect` returns `src/App.tsx:104` in a dev build; a packaged production renderer has no source map, so it reports `n/a` |
|
|
41
|
+
| capabilities, state (live store) | ✅ | ✅ | `reticle_state` reads the real store |
|
|
42
|
+
| act (click/fill/type/select) | ✅ | ✅ | |
|
|
43
|
+
| **act_and_wait, wait_for, assert** | ✅ | ✅ | signal / state / route / net / console predicates |
|
|
44
|
+
| console errors | ✅ | ✅ | catches what the UI never shows |
|
|
45
|
+
| network — HTTP | ✅ | ✅ | on `file://` a relative URL has no origin; use an absolute one |
|
|
46
|
+
| **network — IPC** | ✅ | ✅ | `ipc://<channel>`, incl. failures. Electron: `invoke` and `sendSync` carry a verdict; a one-way `send` is recorded as `oneWay: true` with NO status, because the renderer never learns the outcome. Tauri: both the `ipc://` (macOS/Linux) and `http://ipc.localhost` (Windows) transports |
|
|
47
|
+
| route | ✅ | ✅ | use a **hash** router — see below |
|
|
48
|
+
| storage, animations, observe, explore | ✅ | ✅ | |
|
|
49
|
+
| baseline (semantic), record → replay, crawl | ✅ | ✅ | `crawl` found real anomalies in both |
|
|
50
|
+
| navigate (reload) | ✅ | ✅ | |
|
|
51
|
+
| **screenshot / visual_diff** | ✅ | ✅ | one line in Electron's main process; one Rust command in Tauri — see below. Reticle's own presenter panel is hidden for the shot, so a baseline records your app and not the instrument |
|
|
52
|
+
| **drivable while window occluded** | ✅ | ✅ | including minimized, app-hidden, and behind a fullscreen app on another Space |
|
|
53
|
+
| **headless** | ✅ | ✅ | Electron never shows the window; Tauri shows, loads, then hides |
|
|
54
|
+
| **a missing preload is DECLARED, not silent** | ✅ | n/a | without `@reticlehq/electron/preload` every IPC call is invisible, so verdicts carry `coverage: partial` naming the missing line instead of reading clean |
|
|
55
|
+
| network_mock, viewport | ❌ | ❌ | need a Reticle-driven browser |
|
|
56
|
+
|
|
57
|
+
### Screenshots
|
|
58
|
+
|
|
59
|
+
**Electron: one line in the main process.**
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const { installReticleCapture } = require('@reticlehq/electron/main');
|
|
63
|
+
const win = new BrowserWindow({ ... });
|
|
64
|
+
installReticleCapture(win);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That is all — no CDP flag, no extra packages, works on a packaged `file://` renderer. `reticle_screenshot` and `reticle_visual_diff` then work on your app.
|
|
68
|
+
|
|
69
|
+
Alternatively, since an Electron renderer _is_ Chromium, `--remote-debugging-port=9222` + `RETICLE_CDP_URL=http://127.0.0.1:9222` also works and additionally enables `fullPage` (the main-process route captures the window as composited, so it cannot scroll-stitch).
|
|
70
|
+
|
|
71
|
+
**Why the main process, and not a screen capture.** `webContents.capturePage()` reads the window's own backing store. Capturing a screen _region_ instead was tried and deliberately rejected: it photographs whatever is on top, so an app window behind your editor yields a picture of the editor — saved as a visual baseline that a later diff would trust. A screenshot tool that can silently return another window's pixels manufactures exactly the false green Reticle exists to eliminate. One caveat remains: a fully occluded or minimized window is only partially composited, so parts of the capture may come back blank. Bring the window forward for a complete image — but it is never the wrong window.
|
|
72
|
+
|
|
73
|
+
**Tauri: one Rust command.**
|
|
74
|
+
|
|
75
|
+
```rust
|
|
76
|
+
tauri::Builder::default()
|
|
77
|
+
.invoke_handler(tauri::generate_handler![reticle_tauri::reticle_capture])
|
|
78
|
+
.on_page_load(reticle_tauri::on_page_load)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Nothing on the JavaScript side: the SDK invokes the command through Tauri's own internals, because Tauri has no preload stage where a shim could be installed. `reticle_screenshot` and `reticle_visual_diff` then work on your app, including headless.
|
|
82
|
+
|
|
83
|
+
`reticle_capture` renders the webview rather than reading the screen — like Electron's `capturePage()` — so it needs no screen-recording permission, cannot return another window's pixels, and is correct with nothing on screen at all. Each platform uses its own webview API:
|
|
84
|
+
|
|
85
|
+
| Platform | API | Status |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| macOS | `WKWebView.takeSnapshot` | Verified against a running app |
|
|
88
|
+
| Linux, BSD | WebKitGTK `webkit_web_view_get_snapshot` | Snapshot + PNG encoding verified under `xvfb`; not yet driven through a full Tauri app |
|
|
89
|
+
| Windows | WebView2 `CapturePreview` | **Untested — compiles, never executed** |
|
|
90
|
+
|
|
91
|
+
The Windows path is written and type-checked against the real `webview2-com` API (which caught two genuine type errors), but nobody has run it on Windows. CI now re-checks it against that target on every PR (`cargo check --target x86_64-pc-windows-msvc`), so "compiles" is a gate rather than a claim — it had been asserted for months by a workflow comment while no such job existed. Executed is still a different word from compiled: it is shipped rather than withheld so it can be tried, and labelled rather than listed flatly so that trying it is a choice. If it works for you, say so and this row changes; treat a green from it as unconfirmed until then.
|
|
92
|
+
|
|
93
|
+
All three capture the visible viewport by default, so a baseline taken on a developer's Mac is comparable against the same app in Linux CI. On a platform with no webview API to call, capture reports no-provider rather than returning a plausible wrong image.
|
|
94
|
+
|
|
95
|
+
**`{ fullPage: true }` works on Tauri/Linux only.** WebKitGTK can render the whole document offscreen; `takeSnapshot` (macOS) and `CapturePreview` (Windows) only give what is composited, and Electron's `capturePage()` is the same. Asked for a full page they cannot produce, all of them return `{ ok:false, reason:'full-page-unsupported' }` rather than quietly handing back the viewport — a baseline that omits everything below the fold, while every later diff of it reports green about a region that was never captured. No baseline is written on a refusal.
|
|
96
|
+
|
|
97
|
+
An app that already has its own capture can expose `window.__reticleIpc.capture()` returning a PNG path instead; the SDK prefers it over the built-in command.
|
|
98
|
+
|
|
99
|
+
### A correction: the Tauri macOS "liveness constraint" was wrong
|
|
100
|
+
|
|
101
|
+
Earlier versions of this document said a Tauri app on macOS is only drivable while its window is on the active Space and unoccluded, and that hiding it suspends the webview. **That is not true, and the mistake is worth recording because it cost three features.**
|
|
102
|
+
|
|
103
|
+
Re-measured against the live app, a loaded Tauri webview answers Reticle commands at full speed while: minimized, app-hidden with Cmd-H, fully occluded, on another Space behind a fullscreen app, and with no window on screen at all. A full 43-tool drive passes in every one of those states.
|
|
104
|
+
|
|
105
|
+
What actually failed was narrower: **a webview that has never been presented never loads its page.** Every "suspension" experiment hid or moved the window from `setup`, i.e. before the first present, so the page never ran and every command timed out at 8s. The timeouts were real; the diagnosis was not. The `alwaysOnTop` workaround was then built to fix a problem that did not exist, measured as "still broken" for the same reason, and deleted.
|
|
106
|
+
|
|
107
|
+
The lesson generalises past this document: four experiments agreeing does not make a conclusion controlled, if all four share the same confound.
|
|
108
|
+
|
|
109
|
+
### Headless
|
|
110
|
+
|
|
111
|
+
**Electron: yes.** `show: false` plus `backgroundThrottling: false` in `webPreferences`. The second one is load-bearing — Chromium runs an unshown window's timers in slow motion, which turns every settle wait into a flake. Screenshots still work, because `capturePage` reads the backing store rather than the screen. Verified with a full tool drive against a window that was never shown.
|
|
112
|
+
|
|
113
|
+
**Tauri: yes — show, load, then hide.**
|
|
114
|
+
|
|
115
|
+
```rust
|
|
116
|
+
.on_page_load(reticle_tauri::on_page_load) // hides the window when RETICLE_HEADLESS=1
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Run with `RETICLE_HEADLESS=1 pnpm tauri dev`. Nothing ends up on screen, and screenshots keep working because the capture renders the webview rather than the screen.
|
|
120
|
+
|
|
121
|
+
The ordering is the whole trick. Hiding the window during `setup` hides it before the webview has ever been presented, and a webview that has never been presented never loads its page — which is what made headless Tauri look impossible. Hiding it after its first page load leaves everything running. Verified with a full 43-tool drive plus a screenshot and a visual diff against a window that is not on screen.
|
|
122
|
+
|
|
123
|
+
`xvfb-run -a pnpm tauri dev` also works on Linux and needs no app-side change at all.
|
|
124
|
+
|
|
125
|
+
### How it compares to Playwright MCP
|
|
126
|
+
|
|
127
|
+
Both attached to the same running Electron app, same task ("archive a todo, then verify it worked"):
|
|
128
|
+
|
|
129
|
+
| tool | ~tokens | ms | verdict |
|
|
130
|
+
| ---------------------- | ------- | ------- | ------------------------------------------ |
|
|
131
|
+
| reticle (lean) | **350** | 1364 | caught the failure |
|
|
132
|
+
| playwright-mcp | 1069 | **980** | blind to it — no network/IPC in its output |
|
|
133
|
+
| playwright-mcp → Tauri | — | — | cannot attach (no CDP in WKWebView) |
|
|
134
|
+
|
|
135
|
+
Playwright MCP is faster. It is also structurally unable to see an IPC failure, because its channel is the accessibility tree. Full method, numbers and caveats: [`bench/desktop`](../bench/desktop).
|
|
136
|
+
|
|
137
|
+
### Routing: use a hash router
|
|
138
|
+
|
|
139
|
+
A packaged renderer runs on `file://`, where `pushState('/settings')` rewrites the URL to `file:///settings` — a path that does not exist, so the next reload lands on a blank page and the app is gone. This is why HashRouter is the standard choice for packaged Electron/Tauri apps. Reticle's route observer handles both, and a `{ kind: 'route', contains: … }` assertion matches the fragment.
|
|
140
|
+
|
|
141
|
+
## Electron
|
|
142
|
+
|
|
143
|
+
Two steps. The first is the ordinary web setup; the second is the only desktop-specific part.
|
|
144
|
+
|
|
145
|
+
**1. The renderer** — one line in `vite.config.ts`, exactly like a web app:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { reticle } from '@reticlehq/vite-plugin';
|
|
149
|
+
|
|
150
|
+
export default defineConfig({
|
|
151
|
+
base: './', // file:// needs relative asset paths
|
|
152
|
+
plugins: [react(), reticle({ desktop: true })],
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`desktop: true` does the two things a desktop shell needs and a web app must never get: the plugin also runs for `vite build` (a packaged renderer is a production build with **no dev server**, so the default serve-only gating would ship an app with no `connect()` at all), and `connect()` is called with `allowInProduction` so the SDK's production backstop does not refuse to start. Keep it behind your own dev-only build so an instrumented bundle can never reach a release binary.
|
|
157
|
+
|
|
158
|
+
Nothing to add in your app code. (You can still call `reticle.connect()` by hand and pass `inject: false` if you want control.)
|
|
159
|
+
|
|
160
|
+
**2. The preload** — one line, before you expose anything:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm i -D @reticlehq/electron
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
// electron/preload.cjs
|
|
168
|
+
require('@reticlehq/electron/preload');
|
|
169
|
+
|
|
170
|
+
const { contextBridge, ipcRenderer } = require('electron');
|
|
171
|
+
contextBridge.exposeInMainWorld('api', {
|
|
172
|
+
loadTodos: () => ipcRenderer.invoke('todos:load'),
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
That line is what makes your main-process calls visible. It has to live in the preload, and it is not a stylistic choice: `contextBridge.exposeInMainWorld` hands the renderer a **deeply frozen, non-configurable** object, so nothing running in the page can instrument `window.api`. The preload is the last point where `ipcRenderer.invoke` is still an ordinary, writable function. Patching there covers every channel you go on to expose, whatever you named it.
|
|
177
|
+
|
|
178
|
+
**Preload sandboxing.** A sandboxed preload can't resolve `node_modules`, so the bare `require` above fails. Either bundle your preload (electron-vite and Electron Forge do this by default — the require is inlined at build time and sandboxing stays on), or set `sandbox: false` in `webPreferences`.
|
|
179
|
+
|
|
180
|
+
**Packaged renderers.** An app that loads its renderer with `loadFile` runs on `file://`, which is a production Vite build. Pass `allowInProduction: true` to `connect()` for that mode, or keep the SDK gated behind `import.meta.env.DEV` so it never enters the shipped binary at all.
|
|
181
|
+
|
|
182
|
+
Working example: [`apps/electron-smoke`](../apps/electron-smoke).
|
|
183
|
+
|
|
184
|
+
## Tauri
|
|
185
|
+
|
|
186
|
+
Frontend side, nothing desktop-specific:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// src/main.tsx
|
|
190
|
+
import { reticle } from '@reticlehq/browser';
|
|
191
|
+
|
|
192
|
+
if (import.meta.env.DEV) reticle.connect();
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Nothing else is needed for IPC. A Tauri `invoke` travels as a real `fetch` to Tauri's `ipc://` custom protocol, so Reticle already sees it; every `invoke('load_todos')` shows up as `ipc://load_todos`. Reticle also reads Tauri's `Tauri-Response` header, because the transport answers **HTTP 200 whether the Rust command returned `Ok` or `Err`** — without that translation a failed command would be recorded as a successful request.
|
|
196
|
+
|
|
197
|
+
The one required step is **CSP**. Tauri ships a restrictive default that blocks the bridge WebSocket before it opens, and the failure is silent from the app's side. In `src-tauri/tauri.conf.json`:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"app": {
|
|
202
|
+
"security": {
|
|
203
|
+
"csp": "default-src 'self' ipc: http://ipc.localhost; connect-src 'self' ipc: http://ipc.localhost ws://localhost:4400 ws://127.0.0.1:4400"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Keep `ipc: http://ipc.localhost` in `connect-src` — Tauri v2 needs it for `invoke` itself. Add your dev-server origin too if you use `devUrl`. This is a dev-only config; drop the `ws://` entries from your release config.
|
|
210
|
+
|
|
211
|
+
Working example: [`apps/tauri-smoke`](../apps/tauri-smoke).
|
|
212
|
+
|
|
213
|
+
## What IPC looks like to an agent
|
|
214
|
+
|
|
215
|
+
A desktop app reaches its backend over IPC, not HTTP. `fetch`/`XHR` patching cannot see that, so without the IPC observer every backend call in your app is a blind spot — `reticle_network` returns nothing, `act_and_wait` has no in-flight request to settle on, and `assert { net }` is vacuously true. That is a false green by construction.
|
|
216
|
+
|
|
217
|
+
Reticle records each IPC call as an ordinary request, so the tools you already use work unchanged:
|
|
218
|
+
|
|
219
|
+
```jsonc
|
|
220
|
+
// reticle_network { urlContains: "ipc://" }
|
|
221
|
+
{
|
|
222
|
+
"calls": [
|
|
223
|
+
{ "method": "ipc", "url": "ipc://todos:load", "status": 200, "ms": 134 },
|
|
224
|
+
{ "method": "ipc", "url": "ipc://todos:archive", "status": 500, "ms": 83 },
|
|
225
|
+
],
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
IPC has no status code; `200`/`500` are synthetic, mapped from whether the call succeeded or failed, precisely so that `reticle_network { status: 500 }` and `assert { kind: "net", status: 500 }` keep working. On Tauri you will see `status: 500` next to `statusText: "OK"` — that is not a bug: the transport really did answer 200, and the 500 is the command's own verdict. `ok` is authoritative, and on Electron `error` carries the message your main process returned:
|
|
230
|
+
|
|
231
|
+
```jsonc
|
|
232
|
+
// reticle_assert { predicate: { kind: "net", urlContains: "ipc://todos:archive", status: 500 } }
|
|
233
|
+
{
|
|
234
|
+
"pass": true,
|
|
235
|
+
"evidence": {
|
|
236
|
+
"url": "ipc://todos:archive",
|
|
237
|
+
"ok": false,
|
|
238
|
+
"status": 500,
|
|
239
|
+
"error": "archive is not implemented in the backend",
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Both example apps ship a planted false green — an Archive button that updates the UI optimistically and swallows the rejection. The screen says "archived", a screenshot agrees, a DOM assertion agrees. Only the IPC record disagrees. That is the case desktop support exists for.
|
|
245
|
+
|
|
246
|
+
## Troubleshooting
|
|
247
|
+
|
|
248
|
+
**`reticle status` shows no session.** Check the app's console (Electron: devtools, or forward `console-message` to your terminal — a desktop renderer has no visible console otherwise). A refused connect always logs why.
|
|
249
|
+
|
|
250
|
+
**Tauri: nothing connects and the app console shows a CSP violation.** The `connect-src` above is missing or does not include your daemon's port.
|
|
251
|
+
|
|
252
|
+
**Electron: `module not found: @reticlehq/electron/preload`.** The preload is sandboxed. Bundle it, or set `sandbox: false` — see [Electron](#electron).
|
|
253
|
+
|
|
254
|
+
**IPC calls do not appear, but the app works.** Electron: the shim's `require` must run _before_ your preload captures its own reference to `ipcRenderer`. Put it on the first line. Tauri: `invoke` imported from `@tauri-apps/api/core` is observed; a hand-rolled `postMessage` protocol is not, and neither is Tauri's `postMessage` transport fallback on platforms where the `ipc://` custom protocol is unavailable.
|
|
255
|
+
|
|
256
|
+
**Why not just patch `invoke` / `window.api` directly?** Because neither can be. Tauri defines `__TAURI_INTERNALS__.invoke` as `writable: false, configurable: false`, and Electron's `contextBridge` object is deeply frozen and installed non-configurably. Both were verified, not assumed — which is why the two runtimes use the two different mechanisms above rather than one uniform monkey-patch.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Reticle for enterprises
|
|
2
|
+
|
|
3
|
+
> Premium access (how you get + activate it), what's gated, the security/data-handling posture, and the licensing model. Integration mechanics live in [`platform-integration.md`](./platform-integration.md).
|
|
4
|
+
|
|
5
|
+
## How premium access works (offline, no phone-home)
|
|
6
|
+
|
|
7
|
+
Enterprise (`ee/`) features ship **inside the open package** — they're source-available (free for development, testing, and evaluation). A **license key activates them in production**. Activation is verified locally with Ed25519; nothing about your usage ever leaves your machine.
|
|
8
|
+
|
|
9
|
+
**The flow, end to end:**
|
|
10
|
+
|
|
11
|
+
1. **Buy** — contact **[hey@reticle.sh](mailto:hey@reticle.sh)**; we issue you a signed license key (org, plan, expiry, feature set).
|
|
12
|
+
2. **Install** — set it on the machine running the Reticle server:
|
|
13
|
+
```bash
|
|
14
|
+
export RETICLE_LICENSE_KEY="<your key>"
|
|
15
|
+
# the release already bakes the issuer public key (RETICLE_LICENSE_PUBLIC_KEY)
|
|
16
|
+
```
|
|
17
|
+
3. **Verify** — `reticle license` shows your status:
|
|
18
|
+
```
|
|
19
|
+
active licensed to Acme Corp (enterprise), expires 2027-06-20 · features: sso, audit
|
|
20
|
+
eval evaluation mode — enterprise features run free (no issuer key configured)
|
|
21
|
+
missing set RETICLE_LICENSE_KEY to activate enterprise features in production
|
|
22
|
+
expired renew to keep using enterprise features
|
|
23
|
+
```
|
|
24
|
+
4. **Unlock** — enterprise features now run in production; without a valid key they refuse to run there (a clear error, never a silent half-feature). In eval/dev they always run free.
|
|
25
|
+
5. **Renew** — keys carry an expiry; `reticle license` warns before it lapses.
|
|
26
|
+
|
|
27
|
+
> Procuring a license: contact **[hey@reticle.sh](mailto:hey@reticle.sh)**. Keys are issued offline and signed with Ed25519; the activation you run (`reticle license`) verifies them locally with no network call.
|
|
28
|
+
|
|
29
|
+
## What's gated (and the roadmap)
|
|
30
|
+
|
|
31
|
+
The licensing **mechanism** is open core (inspectable, FSL) — only the **features** under `ee/` are gated:
|
|
32
|
+
|
|
33
|
+
- **Today:** the activation gate + an example gated feature (audit event recording).
|
|
34
|
+
- **Roadmap** (the reliably enterprise-only set): **SSO/SAML, SCIM, RBAC / team permissions, audit logs, multi-org management, verify-before-merge policy gates, and the hosted control-plane connectors.** These are the things a security/compliance org pays for; the core verification engine stays free forever.
|
|
35
|
+
|
|
36
|
+
> What's premium vs free, and pricing, are business decisions for the owner — this doc describes the _mechanism_, not the price list.
|
|
37
|
+
|
|
38
|
+
## Security & data handling
|
|
39
|
+
|
|
40
|
+
The honest one-pager a security review needs. Reticle is built so the answer to "where does our data go?" is **nowhere — it runs on your machine, in your infra.**
|
|
41
|
+
|
|
42
|
+
| Question | Answer |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Does the SDK ship to production? | **No** — dev/preview-only, tree-shaken from production builds. |
|
|
45
|
+
| Where does the server run? | **Localhost** — the bridge binds `127.0.0.1`; the verify endpoint is localhost-bound + token-guarded (constant-time), with request/body-size/timeout limits. |
|
|
46
|
+
| Does anything phone home? | **No app data, ever.** License checks are offline (Ed25519). The CLI sends anonymous, opt-out usage metrics only (random id + event names — no code, no PII; see [telemetry](telemetry.md)); disable fleet-wide with `RETICLE_TELEMETRY=0` or per-machine with `reticle telemetry disable`. Feedback your team explicitly sends us (`reticle feedback`) is the only free text that ever leaves; it is never collected passively, is redacted client-side, and is disabled fleet-wide with `RETICLE_FEEDBACK=0`. |
|
|
47
|
+
| Where do artifacts live? | Your disk: `.reticle/runs/<id>.json` (atomic writes, bounded retention), `.reticle/flows/`, `.reticle/contract.json`. You own them. |
|
|
48
|
+
| What can the server read? | The DOM/network/console/routing/state of the app under test — locally. |
|
|
49
|
+
| Leak risk downstream? | The **`prod-preview` profile** redacts source `file:line`, raw bodies, and app-state values. |
|
|
50
|
+
| Path safety | Run/flow ids are validated as single path segments on read **and** write (no traversal). |
|
|
51
|
+
|
|
52
|
+
**Verify it yourself:** the SDK + server are source-available — read the tree-shaking, the `127.0.0.1` bind, and the offline license verify. `SECURITY.md` has the disclosure process; an SBOM is available on request. SOC 2 is a GA-stage item (no Reticle-hosted data exists today to certify); the posture above is the honest current state.
|
|
53
|
+
|
|
54
|
+
## Licensing model (per package)
|
|
55
|
+
|
|
56
|
+
| Scope | License | Why |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Embeddable SDK (`browser`, `protocol`, `react`, `babel-plugin`, `next`, `vite-plugin`, `eslint-plugin`) | **Apache-2.0** | safe to ship inside your customers' apps; explicit patent grant |
|
|
59
|
+
| Server / CLI / MCP (`server`, `test`, umbrella) | **FSL-1.1-ALv2** | free for any use except reselling Reticle itself; converts to Apache after 2 years |
|
|
60
|
+
| Enterprise features (`packages/server/src/ee/`) | **Reticle Enterprise License** | source-available; free for dev/eval, license key required in production |
|
|
61
|
+
|
|
62
|
+
Embedding / OEM / enterprise: **[hey@reticle.sh](mailto:hey@reticle.sh)**.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copy this into `reticle-fixtures` as `.github/workflows/verify-on-dispatch.yml`.
|
|
2
|
+
#
|
|
3
|
+
# It is kept HERE, in the repo that sends the dispatch, on purpose: the two halves have to agree on
|
|
4
|
+
# an event name and a payload shape, and a contract whose two ends live in different repositories
|
|
5
|
+
# drifts the first time somebody edits one of them. This file is the sending end's statement of what
|
|
6
|
+
# the receiving end must look like.
|
|
7
|
+
#
|
|
8
|
+
# It has never run. Nothing in this repository can verify it — the dispatch needs a token that does
|
|
9
|
+
# not exist yet, and the receiver needs to be committed to another repository. Treat every line as
|
|
10
|
+
# unverified until somebody has watched one green run, and do not let its presence here be mistaken
|
|
11
|
+
# for Tier 2 being live.
|
|
12
|
+
name: Verify a Reticle commit against the fixtures
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
repository_dispatch:
|
|
16
|
+
types: [reticle-commit]
|
|
17
|
+
workflow_dispatch:
|
|
18
|
+
inputs:
|
|
19
|
+
sha:
|
|
20
|
+
description: Reticle commit to verify
|
|
21
|
+
required: true
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
verify:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
# Nine real apps, each installed and booted. This is why Tier 2 cannot block a merge.
|
|
27
|
+
timeout-minutes: 90
|
|
28
|
+
steps:
|
|
29
|
+
- name: Check out the fixtures (the pristine surface)
|
|
30
|
+
uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
# `clean` is the branch that has NEVER been instrumented. Running init over an already
|
|
33
|
+
# wired app reports "already wired" for every step and proves nothing, which is exactly how
|
|
34
|
+
# an install regression ships unnoticed.
|
|
35
|
+
ref: clean
|
|
36
|
+
|
|
37
|
+
- name: Check out the Reticle commit under test
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
with:
|
|
40
|
+
repository: reticlehq/reticle
|
|
41
|
+
ref: ${{ github.event.client_payload.sha || inputs.sha }}
|
|
42
|
+
path: reticle
|
|
43
|
+
|
|
44
|
+
- uses: pnpm/action-setup@v4
|
|
45
|
+
- uses: actions/setup-node@v4
|
|
46
|
+
with:
|
|
47
|
+
node-version: 22
|
|
48
|
+
|
|
49
|
+
- name: Build the release candidate
|
|
50
|
+
working-directory: reticle
|
|
51
|
+
run: |
|
|
52
|
+
pnpm install --frozen-lockfile
|
|
53
|
+
pnpm build
|
|
54
|
+
|
|
55
|
+
- run: npx playwright install --with-deps chromium
|
|
56
|
+
|
|
57
|
+
- name: Materialise the fixture apps
|
|
58
|
+
run: node scripts/materialise.mjs
|
|
59
|
+
|
|
60
|
+
- name: Install into every fixture and open each one in a browser
|
|
61
|
+
# `--local` is the whole point: without it the gate can only confirm what already shipped,
|
|
62
|
+
# which is the wrong half of the question before a release.
|
|
63
|
+
run: node scripts/verify.mjs --local ./reticle
|
|
64
|
+
env:
|
|
65
|
+
# The fixtures gate's own idle shutdown once fired mid-install and scored three fixtures as
|
|
66
|
+
# install failures. A gate owns its daemon for the whole run — see the reticle repo's
|
|
67
|
+
# apps/e2e/harness-rules.md, rule 2.
|
|
68
|
+
RETICLE_IDLE_SHUTDOWN_MS: '0'
|
|
69
|
+
|
|
70
|
+
- name: Upload the run
|
|
71
|
+
if: always()
|
|
72
|
+
uses: actions/upload-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: fixtures-run
|
|
75
|
+
path: runs/
|
|
76
|
+
if-no-files-found: ignore
|
package/docs/fixtures.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Fixture apps
|
|
2
|
+
|
|
3
|
+
The apps in `apps/` are ours. We chose their defects, so passing against them says less than it looks like — and they are all wired to Reticle already, which makes them useless for the one question that matters before a release: **does the install still work on an app that has never seen Reticle?**
|
|
4
|
+
|
|
5
|
+
That question is answered in a separate repo: **[`reticle-fixtures`](https://github.com/reticlehq/reticle-fixtures)**.
|
|
6
|
+
|
|
7
|
+
## Why it is a separate repo
|
|
8
|
+
|
|
9
|
+
Three reasons, each of which bit us before the split:
|
|
10
|
+
|
|
11
|
+
- **They are somebody else's package managers.** `react-admin` is a yarn monorepo. Putting it under `apps/*` makes it a member of this pnpm workspace and breaks `pnpm install` for everyone.
|
|
12
|
+
- **They need a branch model this repo cannot give them.** A fresh install is only meaningful on a surface that has never been instrumented. Re-running `init` over an already-wired app reports `·` (already wired) for every step and proves nothing — which is exactly how an install regression ships unnoticed. `reticle-fixtures` keeps a pristine `clean` branch for that, plus `main` at the latest version and `reticle/<version>` per release.
|
|
13
|
+
- **They are large.** The upstream checkouts were eight gigabytes of somebody else's git history sitting gitignored inside this repo.
|
|
14
|
+
|
|
15
|
+
## What runs there
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node scripts/materialise.mjs # build apps/ from fixtures.json at pinned refs
|
|
19
|
+
node scripts/verify.mjs # install into every clean app, boot it, assert a session connects
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The last step is the one that earns its keep. Every install bug found so far has been silent: the app boots, the `init` report reads clean, and nothing connects. Next.js shipped in that state through a whole release — three independent defects, none visible to any check short of opening a browser and looking at `reticle status`.
|
|
23
|
+
|
|
24
|
+
## What stays here
|
|
25
|
+
|
|
26
|
+
`apps/` — bench-app, next-smoke, the Remix and Astro examples, the Electron and Tauri smoke apps, atlas, and the API. These are the CI gates (`pnpm test:e2e`, `pnpm test:e2e:desktop`), and `integration-coverage.test.ts` fails if one of them goes missing while `SKILL.md` still offers its framework.
|
|
27
|
+
|
|
28
|
+
The split is: **`apps/` proves the tools work. `reticle-fixtures` proves the install works.**
|
|
29
|
+
|
|
30
|
+
## Wiring Tier 2 (not yet live)
|
|
31
|
+
|
|
32
|
+
CI can ask this repo to verify a Reticle commit automatically — the [`vite-ecosystem-ci`](https://github.com/vitejs/vite-ecosystem-ci) shape, where a separate repo holds the downstream apps and the main repo asks it to run. The sending half is built (`fixtures-dispatch` in `.github/workflows/ci.yml`); the receiving half is a template at [`docs/fixtures-dispatch-receiver.yml`](./fixtures-dispatch-receiver.yml).
|
|
33
|
+
|
|
34
|
+
**It is inert until two things happen, and neither can be done from inside this repository:**
|
|
35
|
+
|
|
36
|
+
1. A fine-grained PAT with **Actions: write** on `reticlehq/reticle-fixtures`, added to `reticlehq/reticle` as the secret `FIXTURES_DISPATCH_TOKEN`. Until it exists the job logs that Tier 2 is unwired and exits 0 — it never reddens CI, because a dispatch failure has nothing to do with the change being pushed.
|
|
37
|
+
2. The template copied into `reticle-fixtures` as `.github/workflows/verify-on-dispatch.yml`.
|
|
38
|
+
|
|
39
|
+
The template lives in the _sending_ repo deliberately: the two halves must agree on an event name and payload shape, and a contract whose ends live in different repositories drifts the first time somebody edits one of them.
|
|
40
|
+
|
|
41
|
+
**Nothing here has ever run.** Treat every line as unverified until somebody has watched one green run. Tier 2 being present in the plan is not Tier 2 being live.
|
|
42
|
+
|
|
43
|
+
### Why Tier 2 exists when Tier 1 already passes
|
|
44
|
+
|
|
45
|
+
`pnpm gate:install` (Tier 1) scaffolds pristine apps and catches install **regressions**. It cannot catch install **complexity**: `rowy` is 70 dependencies of somebody else's product, `phanpy` has a 130-line vite config holding ten plugins inside a `defineConfig` callback. That is where install bugs actually hide, and a scaffold deliberately has none of it. The two tiers answer different questions and neither substitutes for the other.
|