@reticlehq/server 2.4.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} +63 -13
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -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-parse.d.ts → cli/cli-parse.d.ts} +2 -11
- package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
- 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.d.ts +2 -2
- package/dist/cli.js +174 -92
- 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.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 +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 +53 -7
- 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.js +4 -1
- 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 +3 -2
- package/dist/init/detect.js +16 -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 +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +23 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +132 -8
- package/dist/init/plan.d.ts +73 -8
- package/dist/init/plan.js +133 -34
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +5 -0
- package/dist/init/run.js +210 -36
- 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/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 +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 +52 -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 +14 -2
- package/dist/session/session.js +69 -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 +81 -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/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-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
package/dist/index.js
CHANGED
|
@@ -2,10 +2,11 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
|
|
4
4
|
import { originOf } from './session/session-manager.js';
|
|
5
|
+
import { setBrowserMode, BrowserMode } from './telemetry/browser-mode.js';
|
|
5
6
|
import { replayNamedFlow } from './flows/flow-tools.js';
|
|
6
7
|
import { createSharedServer } from './http-server.js';
|
|
7
|
-
import { resolveBridgeSecurityWithAutoToken } from './bridge-security.js';
|
|
8
|
-
import { Bridge } from './bridge.js';
|
|
8
|
+
import { resolveBridgeSecurityWithAutoToken } from './bridge/bridge-security.js';
|
|
9
|
+
import { Bridge } from './bridge/bridge.js';
|
|
9
10
|
import { BaselineStore } from './project/baselines.js';
|
|
10
11
|
import { RecordingStore } from './flows/recordings.js';
|
|
11
12
|
import { FlowStore } from './flows/flows.js';
|
|
@@ -17,15 +18,18 @@ import { ReticleRunner } from './runs/reticle-runner.js';
|
|
|
17
18
|
import { createRunnerPort } from './runs/runner-port.js';
|
|
18
19
|
import { RunStore } from './runs/run-store.js';
|
|
19
20
|
import { startVerifyServer } from './runs/verify-server.js';
|
|
20
|
-
import { createMcpServer } from './mcp.js';
|
|
21
|
+
import { createMcpServer } from './mcp/mcp.js';
|
|
21
22
|
import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY } from './session/session-reaper.js';
|
|
22
|
-
import {
|
|
23
|
+
import { wireSessionScope } from './session/no-session-watch.js';
|
|
24
|
+
import { buildIdlePredicate } from './daemon/daemon-usefulness.js';
|
|
25
|
+
import { resolveToolSurface } from './tools/tool-surface.js';
|
|
26
|
+
import { statusPayload } from './status-payload.js';
|
|
23
27
|
import { CdpRealInputProvider, LaunchedRealInputProvider } from './input/real-input.js';
|
|
24
28
|
import { cpus } from 'node:os';
|
|
25
29
|
import { BrowserPool } from './pool/browser-pool.js';
|
|
26
30
|
import { playwrightLauncher, resolveMaxContexts } from './pool/playwright-launcher.js';
|
|
27
31
|
import { LeaseReaper } from './pool/lease-reaper.js';
|
|
28
|
-
import { readJournalEnabled, readProjectId } from './cli-port.js';
|
|
32
|
+
import { readJournalEnabled, readProjectId } from './cli/cli-port.js';
|
|
29
33
|
import { makeJournalAttach } from './journal/attach-journal.js';
|
|
30
34
|
import { makeSessionEnd } from './journal/session-end.js';
|
|
31
35
|
import { AmbientStore } from './journal/ambient-store.js';
|
|
@@ -41,7 +45,7 @@ function replayVerdictLine(result) {
|
|
|
41
45
|
}
|
|
42
46
|
export { ReticleTool } from './tools/tool-names.js';
|
|
43
47
|
export { RingBuffer } from './events/ring-buffer.js';
|
|
44
|
-
export { Bridge } from './bridge.js';
|
|
48
|
+
export { Bridge } from './bridge/bridge.js';
|
|
45
49
|
export { Session, SessionManager } from './session/session.js';
|
|
46
50
|
export { buildSessionRecommendation } from './session/session-recommendation.js';
|
|
47
51
|
export { TOOLS } from './tools/tools.js';
|
|
@@ -61,9 +65,9 @@ export { MCP_SSE_PATH, MCP_MESSAGE_PATH } from '@reticlehq/core';
|
|
|
61
65
|
export { BrowserPool, DEFAULT_LEASE_TTL_MS } from './pool/browser-pool.js';
|
|
62
66
|
export { playwrightLauncher, resolveMaxContexts } from './pool/playwright-launcher.js';
|
|
63
67
|
export { appendReticleParams } from './tools/lease-tools.js';
|
|
64
|
-
export { writePid, removePid, isRunning, logPath, readPid, isAlive } from './daemon.js';
|
|
68
|
+
export { writePid, removePid, isRunning, logPath, readPid, isAlive } from './daemon/daemon.js';
|
|
65
69
|
export { scrollToFind } from './input/scroll-find.js';
|
|
66
|
-
export { CORE_TOOL_NAMES,
|
|
70
|
+
export { CORE_TOOL_NAMES, TOOL_SURFACE, TOOL_PROFILE_ENV, filterTools, resolveToolSurface, } from './tools/tool-surface.js';
|
|
67
71
|
export { AnnotationStore } from './flows/annotation-store.js';
|
|
68
72
|
export { replayFlow, nearestTestid } from './flows/flow-replay.js';
|
|
69
73
|
export { ensureReticleDir, writeContract, readContract, reticleDirPaths, flowPath, baselinePath, } from './project/reticle-dir.js';
|
|
@@ -84,14 +88,14 @@ export { buildReactionReport } from './events/reaction.js';
|
|
|
84
88
|
export { CdpRealInputProvider, LaunchedRealInputProvider, DriveError, performGesture, boxCenter, isPointerAction, } from './input/real-input.js';
|
|
85
89
|
/** Default localhost port for the verify HTTP endpoint (see docs/platform-integration.md). */
|
|
86
90
|
export const RETICLE_VERIFY_DEFAULT_PORT = 7331;
|
|
87
|
-
export { resolveBridgeSecurity } from './bridge-security.js';
|
|
91
|
+
export { resolveBridgeSecurity } from './bridge/bridge-security.js';
|
|
88
92
|
/**
|
|
89
93
|
* Build the shared browser pool (one headless Chromium, N capped isolated leased contexts). Lazy —
|
|
90
94
|
* no Chromium launches until the first lease — so creating it is free even when never used.
|
|
91
95
|
*/
|
|
92
96
|
function createBrowserPool(headless) {
|
|
93
97
|
const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
|
|
94
|
-
const genSessionId = () => `lease-${typeof globalThis.crypto?.randomUUID
|
|
98
|
+
const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
|
|
95
99
|
? globalThis.crypto.randomUUID()
|
|
96
100
|
: String(Date.now())}`;
|
|
97
101
|
return new BrowserPool(playwrightLauncher({ headless }), { maxContexts, genSessionId });
|
|
@@ -174,6 +178,9 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
|
|
|
174
178
|
const driveUrl = options.driveUrl;
|
|
175
179
|
if (driveUrl !== undefined && driveUrl.length > 0) {
|
|
176
180
|
const headless = options.headless ?? true;
|
|
181
|
+
// The one place that knows. Everything downstream reads it rather than re-deriving it, and a
|
|
182
|
+
// daemon that launches nothing keeps the default (ATTACHED) rather than guessing.
|
|
183
|
+
setBrowserMode(headless ? BrowserMode.HEADLESS : BrowserMode.HEADED);
|
|
177
184
|
const injectConnect = options.injectConnect;
|
|
178
185
|
const storageState = options.storageState;
|
|
179
186
|
const factory = options.realInputFactory ??
|
|
@@ -254,8 +261,9 @@ export async function start(options = {}) {
|
|
|
254
261
|
fs,
|
|
255
262
|
reticleRoot,
|
|
256
263
|
now,
|
|
264
|
+
bridgePort: port,
|
|
257
265
|
};
|
|
258
|
-
const profile =
|
|
266
|
+
const profile = resolveToolSurface(options.toolProfile);
|
|
259
267
|
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile);
|
|
260
268
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
261
269
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
@@ -294,11 +302,7 @@ export async function startDaemon(options = {}) {
|
|
|
294
302
|
// mirror rejection so a port collision can't surface as an unhandled promise rejection.
|
|
295
303
|
void bridge.ready.catch(() => undefined);
|
|
296
304
|
// `reticle status` GETs this for a live, at-a-glance view of connected tabs + their health.
|
|
297
|
-
shared.attachStatus(() => (
|
|
298
|
-
running: true,
|
|
299
|
-
sessionCount: bridge.sessions.count(),
|
|
300
|
-
sessions: bridge.sessions.list(),
|
|
301
|
-
}));
|
|
305
|
+
shared.attachStatus(() => statusPayload(bridge.sessions.count(), bridge.sessions.list()));
|
|
302
306
|
// Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
|
|
303
307
|
// connection drops (it stopped, or is waiting on the human), end every session and push a clear
|
|
304
308
|
// "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
|
|
@@ -315,10 +319,9 @@ export async function startDaemon(options = {}) {
|
|
|
315
319
|
reaper.start();
|
|
316
320
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
317
321
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
+
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
323
|
+
// sessions, and the agent is told to check two things it cannot see. See no-session-diagnosis.ts.
|
|
324
|
+
wireSessionScope(bridge.sessions, readProjectId(process.cwd()), port);
|
|
322
325
|
const { realInput, owned } = await resolveRealInput(options, () => shared.close(), makeNetworkDetailRouter(bridge, options.driveUrl));
|
|
323
326
|
const fs = createNodeFileSystem();
|
|
324
327
|
const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
@@ -342,15 +345,16 @@ export async function startDaemon(options = {}) {
|
|
|
342
345
|
fs,
|
|
343
346
|
reticleRoot,
|
|
344
347
|
now,
|
|
348
|
+
bridgePort: port,
|
|
345
349
|
};
|
|
346
|
-
const profile =
|
|
350
|
+
const profile = resolveToolSurface(options.toolProfile);
|
|
347
351
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
348
352
|
shared.attachMcp(() => createMcpServer(effectiveDeps, profile));
|
|
349
353
|
// Optional OEM/CI verify endpoint: a host platform POSTs to /verify and gets an ReticleVerificationRun,
|
|
350
354
|
// driving the same flow-replay machinery the agent uses — no MCP stdio, no human. Each verdict is
|
|
351
355
|
// persisted via RunStore. Localhost-bound + token-guarded. Off unless `reticle serve --http`.
|
|
352
356
|
let verifyHttp;
|
|
353
|
-
if (options.httpVerify
|
|
357
|
+
if (true === options.httpVerify) {
|
|
354
358
|
const runStore = new RunStore(fs, reticleRoot);
|
|
355
359
|
const runner = new ReticleRunner(createRunnerPort(effectiveDeps));
|
|
356
360
|
const token = options.httpVerifyToken ?? process.env[ReticleEnv.VERIFY_TOKEN] ?? '';
|
|
@@ -410,9 +414,12 @@ export async function startDaemon(options = {}) {
|
|
|
410
414
|
...(realInput !== undefined ? { realInput } : {}),
|
|
411
415
|
...(verifyHttp !== undefined ? { verifyPort: verifyHttp.port } : {}),
|
|
412
416
|
...(security.token !== undefined ? { token: security.token } : {}),
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
isIdle: () =>
|
|
417
|
+
// Nobody is using this daemon — self-shuts-down once it holds for the grace window. See
|
|
418
|
+
// daemon-usefulness.ts for the two ways that can be true.
|
|
419
|
+
isIdle: buildIdlePredicate(() => agentConnected, bridge.sessions, pool),
|
|
420
|
+
// Exposed so the shutdown watcher can give an ATTACHED daemon a longer grace. The predicate above
|
|
421
|
+
// says WHETHER it is idle; this says how long that quiet should be tolerated — see idle-grace.
|
|
422
|
+
agentAttached: () => agentConnected,
|
|
416
423
|
close: async () => {
|
|
417
424
|
reaper.stop();
|
|
418
425
|
const vh = verifyHttp;
|
|
@@ -20,7 +20,7 @@ export declare const AgentRuleStatus: {
|
|
|
20
20
|
readonly ALREADY: "already";
|
|
21
21
|
};
|
|
22
22
|
export type AgentRuleStatus = (typeof AgentRuleStatus)[keyof typeof AgentRuleStatus];
|
|
23
|
-
|
|
23
|
+
interface AgentRuleResult {
|
|
24
24
|
status: AgentRuleStatus;
|
|
25
25
|
/** Full file content to write when status is APPLY; the unchanged input otherwise. */
|
|
26
26
|
content: string;
|
|
@@ -35,3 +35,4 @@ export declare function markedBlock(): string;
|
|
|
35
35
|
export declare function mergeMarkedInstruction(existing: string | null | undefined): AgentRuleResult;
|
|
36
36
|
/** The Cursor rule file (.mdc): `alwaysApply` keeps the rule in every turn's context. */
|
|
37
37
|
export declare function cursorRuleFile(): string;
|
|
38
|
+
export {};
|
package/dist/init/agent-rules.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* cross-agent fallback. Idempotent via a marker block — safe to append to an existing file and a
|
|
11
11
|
* re-run is a no-op. Mirrors the apply/already result shape used across init (see cursor.ts).
|
|
12
12
|
*/
|
|
13
|
+
import { NPX } from './mcp.js';
|
|
14
|
+
import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
|
|
13
15
|
/** Project-root instruction files the agent re-reads every session. */
|
|
14
16
|
export const CLAUDE_MD_PATH = 'CLAUDE.md';
|
|
15
17
|
export const AGENTS_MD_PATH = 'AGENTS.md';
|
|
@@ -18,19 +20,31 @@ export const CURSOR_RULE_PATH = '.cursor/rules/reticle.mdc';
|
|
|
18
20
|
/** Markers delimiting the managed block so a re-run is idempotent and the block stays updatable. */
|
|
19
21
|
const RULE_BEGIN = '<!-- reticle:begin (managed by `reticle init` — edit outside these markers) -->';
|
|
20
22
|
const RULE_END = '<!-- reticle:end -->';
|
|
23
|
+
/**
|
|
24
|
+
* How the agent must INVOKE the CLI.
|
|
25
|
+
*
|
|
26
|
+
* `reticle init` installs the SDK packages, never the server — so there is no `reticle` on the
|
|
27
|
+
* agent's PATH, and every bare `reticle <cmd>` this rule named was an instruction that could not be
|
|
28
|
+
* followed. Reported from a field sweep: three of them, in the file the agent re-reads every session.
|
|
29
|
+
*
|
|
30
|
+
* `npx @reticlehq/server` is the same invocation the MCP registration uses, and it is derived from
|
|
31
|
+
* the package's own name so the two can never drift apart.
|
|
32
|
+
*/
|
|
33
|
+
const CLI = `${NPX} ${RETICLE_NPM_PACKAGE}`;
|
|
21
34
|
/** The rule body (markdown). States WHEN to verify, HOW, and never-weaken-a-check. */
|
|
22
35
|
const RULE_BODY = `## Verifying with Reticle
|
|
23
36
|
|
|
24
|
-
This app is instrumented by **Reticle** — an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`
|
|
37
|
+
This app is instrumented by **Reticle** — an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`${CLI}\` CLI (run it through npx — Reticle's server is not installed into this project). Verifying is part of "done", not an optional extra.
|
|
25
38
|
|
|
26
39
|
- **When:** after you build or change any user-facing feature, flow, or UI, verify it with Reticle **before** telling the user it is complete. Reading the code or passing unit tests is not enough — drive the real running app.
|
|
27
40
|
- **How:** drive the actual flow (\`reticle_act\` / \`reticle_act_and_wait\`), then read the observable effect (\`reticle_assert\`, \`reticle_snapshot\`, \`reticle_state\`, \`reticle_network\`). Confirm the change produced the effect it was meant to, with no new console errors or failed requests.
|
|
28
|
-
- **Covered flows:** run
|
|
41
|
+
- **Covered flows:** run \`${CLI} gate\` — it reports which recorded flows the changed files affect and whether they still pass.
|
|
29
42
|
- **Never weaken a check to make it green.** Downgrading, skipping, or deleting an assertion is a finding, not a fix.
|
|
30
43
|
- **If Reticle can't run** (no daemon, or this is not a running web app), say so — do not skip verification silently.
|
|
31
|
-
- **If a tool result carries \`
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
44
|
+
- **If a tool result carries \`version_skew\`, act on it before trusting anything else.** Two pieces of Reticle are on different wire contracts, so tools can misbehave in ways neither side reports — a green here is not evidence. The field says which pair disagrees and names the exact fix: run \`${CLI} stop\` and retry when the DAEMON is stale, or tell the human to install the matching SDK and restart their dev server when the PAGE is. Do that first, then redo whatever you were verifying.
|
|
45
|
+
- **If a tool result carries \`update_available\`, say so.** Reticle checked npm and a newer version exists. Tell the human the version numbers and offer to run \`${CLI} update\` — between tasks, never mid-verification, because it restarts the daemon and drops the session. Finish what you are doing first.
|
|
46
|
+
- **Wish Reticle could do something? Ask for it with \`reticle_feedback\`.** Reticle is built for agents, so what you find awkward or missing is what decides the roadmap — and you are the only one who can report it, because that thought disappears when your context does. Put the report itself in \`text\` and pick a \`kind\` — both are required: \`feature_request\` for something that does not exist, \`improvement\` for something that works but costs too many calls, \`bug\` for a tool that returned something wrong, \`gap\` for something Reticle could not observe at all, \`ambiguity\` for a verdict you could not act on. Say what you are actually trying to achieve (\`need\`) and how you work around it today (\`currentApproach\`), and set \`model\` to the model you are running. File it the moment you notice, then carry on — nothing is too minor.
|
|
47
|
+
- **If Reticle itself fails you, tell its maintainers with \`reticle_feedback\`.** A tool that returned something wrong, a thing Reticle could not observe at all, or a verdict you could not act on — file it as \`text\` — a root-cause analysis with the call trace — under \`kind: bug\` (wrong result), \`gap\` (could not observe it) or \`ambiguity\` (undecidable verdict), then carry on with the task. Working around a broken tool in silence is how the same defect survives. This is about defects in *Reticle*; a bug you find in *this app* is Reticle working, and belongs in your report to the user instead.`;
|
|
34
48
|
export const AgentRuleStatus = {
|
|
35
49
|
APPLY: 'apply',
|
|
36
50
|
ALREADY: 'already',
|
|
@@ -46,15 +60,39 @@ export function markedBlock() {
|
|
|
46
60
|
*/
|
|
47
61
|
export function mergeMarkedInstruction(existing) {
|
|
48
62
|
if (existing !== null && existing !== undefined && existing.includes(RULE_BEGIN)) {
|
|
49
|
-
|
|
63
|
+
// A block that can never be updated is not managed. Returning ALREADY on sight of the marker
|
|
64
|
+
// meant a project that ran init once kept that release's rule text forever, so every improvement
|
|
65
|
+
// reached only new projects — and a rule about a field introduced later (`version_skew`) could
|
|
66
|
+
// never arrive at all. The markers promise the inside is ours; idempotence comes from comparing
|
|
67
|
+
// CONTENT instead of presence.
|
|
68
|
+
return refreshMarkedBlock(existing);
|
|
50
69
|
}
|
|
51
70
|
const block = markedBlock();
|
|
52
|
-
if (
|
|
71
|
+
if (null === existing || existing === undefined || 0 === existing.trim().length) {
|
|
53
72
|
return { status: AgentRuleStatus.APPLY, content: block };
|
|
54
73
|
}
|
|
55
74
|
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
56
75
|
return { status: AgentRuleStatus.APPLY, content: `${existing}${separator}${block}` };
|
|
57
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Swap the managed block for the current one, leaving every character outside the markers alone.
|
|
79
|
+
*
|
|
80
|
+
* A malformed file — a begin marker with no end — is left untouched and reported ALREADY. Rewriting
|
|
81
|
+
* from a marker to end-of-file would eat whatever the human wrote after it, and silently destroying
|
|
82
|
+
* someone's instruction file is far worse than a stale rule.
|
|
83
|
+
*/
|
|
84
|
+
function refreshMarkedBlock(existing) {
|
|
85
|
+
const start = existing.indexOf(RULE_BEGIN);
|
|
86
|
+
const endAt = existing.indexOf(RULE_END, start);
|
|
87
|
+
if (-1 === endAt)
|
|
88
|
+
return { status: AgentRuleStatus.ALREADY, content: existing };
|
|
89
|
+
const current = existing.slice(start, endAt + RULE_END.length);
|
|
90
|
+
const wanted = markedBlock().trimEnd();
|
|
91
|
+
if (current === wanted)
|
|
92
|
+
return { status: AgentRuleStatus.ALREADY, content: existing };
|
|
93
|
+
const content = `${existing.slice(0, start)}${wanted}${existing.slice(endAt + RULE_END.length)}`;
|
|
94
|
+
return { status: AgentRuleStatus.APPLY, content };
|
|
95
|
+
}
|
|
58
96
|
/** The Cursor rule file (.mdc): `alwaysApply` keeps the rule in every turn's context. */
|
|
59
97
|
export function cursorRuleFile() {
|
|
60
98
|
return `---
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which app in a workspace `reticle init` should wire, when the caller says.
|
|
3
|
+
*
|
|
4
|
+
* Refusing to guess between several apps is right — instrumenting one silently would leave the rest
|
|
5
|
+
* unverified while reporting success. But "re-run inside the one you want" is not an answer a
|
|
6
|
+
* script, a CI step, or an agent that cannot change directory can act on, so the refusal was a dead
|
|
7
|
+
* end for exactly the callers most likely to hit it.
|
|
8
|
+
*
|
|
9
|
+
* A name that is not one of the discovered apps is refused rather than trusted: it is almost always
|
|
10
|
+
* a typo or a stale path, and wiring a directory that is not an app writes files nothing compiles.
|
|
11
|
+
*/
|
|
12
|
+
type AppChoice = {
|
|
13
|
+
ok: true;
|
|
14
|
+
app: string | undefined;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function chooseWorkspaceApp(requested: string | undefined, apps: readonly string[]): AppChoice;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which app in a workspace `reticle init` should wire, when the caller says.
|
|
3
|
+
*
|
|
4
|
+
* Refusing to guess between several apps is right — instrumenting one silently would leave the rest
|
|
5
|
+
* unverified while reporting success. But "re-run inside the one you want" is not an answer a
|
|
6
|
+
* script, a CI step, or an agent that cannot change directory can act on, so the refusal was a dead
|
|
7
|
+
* end for exactly the callers most likely to hit it.
|
|
8
|
+
*
|
|
9
|
+
* A name that is not one of the discovered apps is refused rather than trusted: it is almost always
|
|
10
|
+
* a typo or a stale path, and wiring a directory that is not an app writes files nothing compiles.
|
|
11
|
+
*/
|
|
12
|
+
export function chooseWorkspaceApp(requested, apps) {
|
|
13
|
+
if (requested === undefined || '' === requested)
|
|
14
|
+
return { ok: true, app: undefined };
|
|
15
|
+
// Tab-completion adds a trailing slash; the discovered names never carry one.
|
|
16
|
+
const wanted = requested.replace(/\/+$/, '');
|
|
17
|
+
if (apps.includes(wanted))
|
|
18
|
+
return { ok: true, app: wanted };
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
message: 0 === apps.length
|
|
22
|
+
? `--app ${requested} was given, but no app was found in this workspace`
|
|
23
|
+
: `--app ${requested} is not one of the apps found here: ${apps.join(', ')}`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Astro file owns the document, and therefore where the SDK script belongs.
|
|
3
|
+
*
|
|
4
|
+
* The previous rule was "exactly one `.astro` file in `src/layouts/`". Measured on two real Astro
|
|
5
|
+
* projects, it fired on neither: one has no `src/layouts/` at all and renders from
|
|
6
|
+
* `src/pages/index.astro`; the other has three files there, two of which are partials. Both got a
|
|
7
|
+
* printed recipe instead of a working install.
|
|
8
|
+
*
|
|
9
|
+
* A file COUNT cannot tell a layout from a partial, and a directory NAME cannot tell you where the
|
|
10
|
+
* document shell is. `</body>` can — the file that closes the body is the one every page renders
|
|
11
|
+
* through, and the only place a script reaches the whole app.
|
|
12
|
+
*
|
|
13
|
+
* Ambiguity is still refused rather than guessed. Two document shells means two entry points, and
|
|
14
|
+
* silently instrumenting one of them would leave half the app unverified while reporting success.
|
|
15
|
+
*/
|
|
16
|
+
interface AstroCandidate {
|
|
17
|
+
path: string;
|
|
18
|
+
source: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function pickAstroHost(candidates: readonly AstroCandidate[]): AstroCandidate | undefined;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Astro file owns the document, and therefore where the SDK script belongs.
|
|
3
|
+
*
|
|
4
|
+
* The previous rule was "exactly one `.astro` file in `src/layouts/`". Measured on two real Astro
|
|
5
|
+
* projects, it fired on neither: one has no `src/layouts/` at all and renders from
|
|
6
|
+
* `src/pages/index.astro`; the other has three files there, two of which are partials. Both got a
|
|
7
|
+
* printed recipe instead of a working install.
|
|
8
|
+
*
|
|
9
|
+
* A file COUNT cannot tell a layout from a partial, and a directory NAME cannot tell you where the
|
|
10
|
+
* document shell is. `</body>` can — the file that closes the body is the one every page renders
|
|
11
|
+
* through, and the only place a script reaches the whole app.
|
|
12
|
+
*
|
|
13
|
+
* Ambiguity is still refused rather than guessed. Two document shells means two entry points, and
|
|
14
|
+
* silently instrumenting one of them would leave half the app unverified while reporting success.
|
|
15
|
+
*/
|
|
16
|
+
/** Tolerant of whitespace and casing: `</body>`, `</BODY >`, `</body\n>`. */
|
|
17
|
+
const CLOSES_BODY = /<\/\s*body\s*>/i;
|
|
18
|
+
/** Layout files win over pages: a page shell is one route, a layout shell is what routes inherit. */
|
|
19
|
+
const LAYOUT_DIR = 'layouts/';
|
|
20
|
+
export function pickAstroHost(candidates) {
|
|
21
|
+
const owners = candidates.filter((c) => CLOSES_BODY.test(c.source));
|
|
22
|
+
if (0 === owners.length)
|
|
23
|
+
return undefined;
|
|
24
|
+
const layouts = owners.filter((c) => c.path.includes(LAYOUT_DIR));
|
|
25
|
+
const preferred = layouts.length > 0 ? layouts : owners;
|
|
26
|
+
return 1 === preferred.length ? preferred[0] : undefined;
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, conservative patchers for an Astro app: the config gets the token/root `define` and the
|
|
3
|
+
* raised build target, one layout gets the dev-only connect `<script>`.
|
|
4
|
+
*
|
|
5
|
+
* Astro was the last gated framework where `init` printed a correct recipe and applied none of it —
|
|
6
|
+
* the only ⚠ left on a supported stack, and the user's first session was two hand-copied snippets
|
|
7
|
+
* away. Astro is Vite-based but renders its own HTML, so the Vite plugin's injection never fires and
|
|
8
|
+
* there is no entry module to wire: the connect has to live in a page or layout `<script>`, and the
|
|
9
|
+
* token has to be inlined by the config because nothing else is in the page's path to inject it.
|
|
10
|
+
*
|
|
11
|
+
* Both patchers bail to `manual` (the printed recipe) on any shape they do not fully recognise.
|
|
12
|
+
* Half-editing a build config is worse than a documented manual step.
|
|
13
|
+
*/
|
|
14
|
+
import { type SourcePatch } from './patch-kind.js';
|
|
15
|
+
export declare function patchAstroConfig(source: string): SourcePatch;
|
|
16
|
+
export declare function patchAstroLayout(source: string, port: number | undefined, projectId: string | undefined): SourcePatch;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, conservative patchers for an Astro app: the config gets the token/root `define` and the
|
|
3
|
+
* raised build target, one layout gets the dev-only connect `<script>`.
|
|
4
|
+
*
|
|
5
|
+
* Astro was the last gated framework where `init` printed a correct recipe and applied none of it —
|
|
6
|
+
* the only ⚠ left on a supported stack, and the user's first session was two hand-copied snippets
|
|
7
|
+
* away. Astro is Vite-based but renders its own HTML, so the Vite plugin's injection never fires and
|
|
8
|
+
* there is no entry module to wire: the connect has to live in a page or layout `<script>`, and the
|
|
9
|
+
* token has to be inlined by the config because nothing else is in the page's path to inject it.
|
|
10
|
+
*
|
|
11
|
+
* Both patchers bail to `manual` (the printed recipe) on any shape they do not fully recognise.
|
|
12
|
+
* Half-editing a build config is worse than a documented manual step.
|
|
13
|
+
*/
|
|
14
|
+
import { RETICLE_DEFAULT_PORT, bridgeWsUrl } from '@reticlehq/core';
|
|
15
|
+
import { PatchKind } from './patch-kind.js';
|
|
16
|
+
/** Present in a patched config AND in a hand-followed recipe — so both count as already wired. */
|
|
17
|
+
const CONFIG_MARKER = '__RETICLE_TOKEN__';
|
|
18
|
+
/** Present in a patched layout and in the printed recipe's script. */
|
|
19
|
+
const LAYOUT_MARKER = 'reticle.connect';
|
|
20
|
+
/**
|
|
21
|
+
* The SDK, declared so Vite pre-bundles it BEFORE the first page load.
|
|
22
|
+
*
|
|
23
|
+
* The connect script does `await import('@reticlehq/react')`. Undeclared, Vite meets that import
|
|
24
|
+
* mid-load, pre-bundles it, and the hashed `/node_modules/.vite/deps/@reticlehq_react.js?v=…` URL
|
|
25
|
+
* the browser already requested stops existing — the import rejects with "Failed to fetch
|
|
26
|
+
* dynamically imported module", connect() never runs, and the page looks entirely normal. Measured
|
|
27
|
+
* on astro-nanostores, intermittent on whether the dep cache was warm. The Vite plugin has declared
|
|
28
|
+
* the SDK for this exact reason since the bug was first found on React; Astro is hand-patched and
|
|
29
|
+
* never got it.
|
|
30
|
+
*/
|
|
31
|
+
const SDK_INCLUDE_LITERAL = `'@reticlehq/react'`;
|
|
32
|
+
/**
|
|
33
|
+
* An `include:` the app already declared — ours joins that array instead of adding a second key.
|
|
34
|
+
* Deliberately not anchored to a newline: `optimizeDeps: { include: ['x'] }` on one line is the
|
|
35
|
+
* common way to write it, and requiring a line break made the merge miss it and duplicate the key.
|
|
36
|
+
*/
|
|
37
|
+
const EXISTING_INCLUDE = /(\s*include\s*:\s*\[)/;
|
|
38
|
+
const DEFINE_CONFIG = /defineConfig\s*\(\s*\{/;
|
|
39
|
+
/** A `vite:` key whose value is an object LITERAL — the one shape we can merge into safely. */
|
|
40
|
+
const VITE_OBJECT_KEY = /(^\s*vite\s*:\s*\{)/m;
|
|
41
|
+
/** Any `vite:` key at all. One that is not an object literal (a spread, an imported config) is ours to refuse. */
|
|
42
|
+
const ANY_VITE_KEY = /^\s*vite\s*:/m;
|
|
43
|
+
const BODY_CLOSE = '</body>';
|
|
44
|
+
const HELPER = `
|
|
45
|
+
function reticleToken() {
|
|
46
|
+
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
47
|
+
try { return readFileSync(join(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
const HELPER_IMPORTS = `import { readFileSync } from 'node:fs';
|
|
51
|
+
import { homedir } from 'node:os';
|
|
52
|
+
import { join } from 'node:path';
|
|
53
|
+
`;
|
|
54
|
+
/**
|
|
55
|
+
* The keys we add inside `vite:`, each with what it must contain.
|
|
56
|
+
*
|
|
57
|
+
* Kept as a list rather than one blob because a config that already sets one of these must be merged
|
|
58
|
+
* INTO, not duplicated: the real astro-nanostores config has `build: { chunkSizeWarningLimit }`, and
|
|
59
|
+
* inserting our own `build` beside it gave the object two — where the last one wins, so
|
|
60
|
+
* `target: 'es2022'` was silently discarded while init reported success. Losing that target is not
|
|
61
|
+
* cosmetic: Astro's default down-levels the modern SDK bundle and dies on a destructuring transform.
|
|
62
|
+
*/
|
|
63
|
+
const VITE_KEYS = [
|
|
64
|
+
{ key: 'build', inner: `\n target: 'es2022',` },
|
|
65
|
+
{
|
|
66
|
+
key: 'optimizeDeps',
|
|
67
|
+
inner: `\n include: [${SDK_INCLUDE_LITERAL}],\n esbuildOptions: { target: 'es2022' },`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: 'define',
|
|
71
|
+
inner: `\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),`,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
/** Whole-key form, for a `vite:` block that does not have the key at all. */
|
|
75
|
+
const WHOLE = {
|
|
76
|
+
build: `\n build: { target: 'es2022' },`,
|
|
77
|
+
optimizeDeps: `\n optimizeDeps: { include: [${SDK_INCLUDE_LITERAL}], esbuildOptions: { target: 'es2022' } },`,
|
|
78
|
+
define: `\n define: {\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),\n },`,
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Add our keys to an existing `vite: { ... }` block, merging into any the app already set.
|
|
82
|
+
*
|
|
83
|
+
* Returns null when a colliding key is not an object literal (`build: sharedConfig`) — there is no
|
|
84
|
+
* brace to merge into, and duplicating or replacing it would corrupt someone's build.
|
|
85
|
+
*/
|
|
86
|
+
function mergeIntoViteBlock(source, braceAt) {
|
|
87
|
+
let out = source;
|
|
88
|
+
for (const { key, inner } of VITE_KEYS) {
|
|
89
|
+
// Scoped to the vite block by searching from its opening brace: a `build:` under `markdown:` or
|
|
90
|
+
// at the top level is somebody else's key and must not be touched.
|
|
91
|
+
const existing = new RegExp(`(\\n\\s*${key}\\s*:\\s*)([\\{A-Za-z_])`).exec(out.slice(braceAt));
|
|
92
|
+
if (existing?.index === undefined) {
|
|
93
|
+
out = `${out.slice(0, braceAt)}${WHOLE[key] ?? ''}${out.slice(braceAt)}`;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// The character after the colon decides: `{` is a literal we can open; anything else is a
|
|
97
|
+
// reference we must not touch.
|
|
98
|
+
if (existing[2] !== '{')
|
|
99
|
+
return null;
|
|
100
|
+
const at = braceAt + existing.index + existing[0].length; // just past the `{`
|
|
101
|
+
// An `include:` the app already wrote is an ARRAY, and a second `include:` key in the same
|
|
102
|
+
// object literal is not a merge — the last one wins and one of the two silently disappears.
|
|
103
|
+
// That is how `build.target` was lost while init still reported success. Join their array.
|
|
104
|
+
// Scoped to THIS block's braces: an `include:` under a later key is somebody else's array, and
|
|
105
|
+
// appending the SDK to it would both miss the target and edit a config we were not asked to.
|
|
106
|
+
const own = 'optimizeDeps' === key ? EXISTING_INCLUDE.exec(blockAfter(out, at)) : null;
|
|
107
|
+
if (own?.index === undefined) {
|
|
108
|
+
out = insertAt(out, at, inner);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Their array first, then the rest of our keys at the block's start — inserting at the LOWER
|
|
112
|
+
// offset second keeps the first offset valid.
|
|
113
|
+
out = insertAt(out, at + own.index + own[0].length, `${SDK_INCLUDE_LITERAL}, `);
|
|
114
|
+
out = insertAt(out, at, withoutInclude(inner));
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function insertAt(source, at, text) {
|
|
119
|
+
return `${source.slice(0, at)}${text}${source.slice(at)}`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The text from just inside an opening brace to its match — a brace count, not a parser.
|
|
123
|
+
*
|
|
124
|
+
* Enough for the one question asked of it (does THIS object literal already declare `include`), and
|
|
125
|
+
* honest about its limit: a `{` or `}` inside a string literal in someone's Vite config would fool
|
|
126
|
+
* it. That only ever shortens or lengthens the window we search for `include:` in, so the worst case
|
|
127
|
+
* is the same duplicate-key merge we would have done before, never a corrupted file.
|
|
128
|
+
*/
|
|
129
|
+
function blockAfter(source, at) {
|
|
130
|
+
let depth = 1;
|
|
131
|
+
for (let i = at; i < source.length; i++) {
|
|
132
|
+
if ('{' === source[i])
|
|
133
|
+
depth += 1;
|
|
134
|
+
else if ('}' === source[i]) {
|
|
135
|
+
depth -= 1;
|
|
136
|
+
if (0 === depth)
|
|
137
|
+
return source.slice(at, i);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return source.slice(at);
|
|
141
|
+
}
|
|
142
|
+
/** The optimizeDeps inner minus our `include:` line, for when the app already has one to join. */
|
|
143
|
+
function withoutInclude(inner) {
|
|
144
|
+
return inner.replace(`\n include: [${SDK_INCLUDE_LITERAL}],`, '');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The `vite:` block Astro needs. `build.target` is raised because Astro's default down-levels the
|
|
148
|
+
* modern SDK bundle and dies on a destructuring transform; `__RETICLE_ROOT__` is defined because
|
|
149
|
+
* without it every source pointer comes back as an absolute path from the machine that ran `init`.
|
|
150
|
+
*/
|
|
151
|
+
const VITE_BLOCK = ` vite: {
|
|
152
|
+
build: { target: 'es2022' },
|
|
153
|
+
optimizeDeps: { include: [${SDK_INCLUDE_LITERAL}], esbuildOptions: { target: 'es2022' } },
|
|
154
|
+
define: {
|
|
155
|
+
__RETICLE_TOKEN__: JSON.stringify(reticleToken()),
|
|
156
|
+
__RETICLE_ROOT__: JSON.stringify(process.cwd()),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
`;
|
|
160
|
+
export function patchAstroConfig(source) {
|
|
161
|
+
if (source.includes(CONFIG_MARKER))
|
|
162
|
+
return { kind: PatchKind.ALREADY };
|
|
163
|
+
// A `vite: { ... }` block is an object literal, so our keys can go straight after the brace and
|
|
164
|
+
// whatever is already in there is untouched. Refusing outright was the only genuine install defect
|
|
165
|
+
// left in the gate: the config bailed while the LAYOUT patch applied anyway, leaving an app with a
|
|
166
|
+
// connect snippet, no inlined token and no raised build target — which cannot connect, reported as
|
|
167
|
+
// one OK step and one warning.
|
|
168
|
+
const viteObject = VITE_OBJECT_KEY.exec(source);
|
|
169
|
+
if (viteObject?.index !== undefined) {
|
|
170
|
+
const at = viteObject.index + viteObject[0].length;
|
|
171
|
+
const merged = mergeIntoViteBlock(source, at);
|
|
172
|
+
if (merged !== null) {
|
|
173
|
+
return {
|
|
174
|
+
kind: PatchKind.APPLY,
|
|
175
|
+
code: `${HELPER_IMPORTS}${merged.trimStart()}\n${HELPER}`.trimEnd() + '\n',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
kind: PatchKind.MANUAL,
|
|
180
|
+
reason: 'this config sets a `vite` key Reticle needs (build / optimizeDeps / define) to something ' +
|
|
181
|
+
'other than an object literal — merging into it is your call, not a text edit Reticle should make',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (ANY_VITE_KEY.test(source)) {
|
|
185
|
+
// `vite: sharedConfig` — not an object literal, so there is no brace to merge into and guessing
|
|
186
|
+
// would corrupt a build config. The recipe is the honest answer here.
|
|
187
|
+
return {
|
|
188
|
+
kind: PatchKind.MANUAL,
|
|
189
|
+
reason: 'this config sets `vite:` to something other than an object literal — merging into it is your call, not a text edit Reticle should make',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const opening = DEFINE_CONFIG.exec(source);
|
|
193
|
+
if (opening?.index === undefined) {
|
|
194
|
+
return {
|
|
195
|
+
kind: PatchKind.MANUAL,
|
|
196
|
+
reason: "couldn't find a `defineConfig({ ... })` call to extend",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const at = opening.index + opening[0].length;
|
|
200
|
+
const withBlock = `${source.slice(0, at)}\n${VITE_BLOCK}${source.slice(at)}`;
|
|
201
|
+
return {
|
|
202
|
+
kind: PatchKind.APPLY,
|
|
203
|
+
code: `${HELPER_IMPORTS}${withBlock.trimStart()}\n${HELPER}`.trimEnd() + '\n',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** The dev-only connect that goes inside the layout's `<body>`. */
|
|
207
|
+
function astroConnectScript(port, projectId) {
|
|
208
|
+
const url = port !== undefined && port !== RETICLE_DEFAULT_PORT
|
|
209
|
+
? `\n url: '${bridgeWsUrl(port)}',`
|
|
210
|
+
: '';
|
|
211
|
+
const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
|
|
212
|
+
return ` <script>
|
|
213
|
+
if (import.meta.env.DEV) {
|
|
214
|
+
const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
|
|
215
|
+
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
|
|
216
|
+
const { reticle, install } = await import('@reticlehq/react');
|
|
217
|
+
install();
|
|
218
|
+
reticle.connect({${id}${url}
|
|
219
|
+
...(token.length > 0 ? { token } : {}),
|
|
220
|
+
...(root.length > 0 ? { root } : {}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
</script>
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
export function patchAstroLayout(source, port, projectId) {
|
|
227
|
+
if (source.includes(LAYOUT_MARKER))
|
|
228
|
+
return { kind: PatchKind.ALREADY };
|
|
229
|
+
const at = source.lastIndexOf(BODY_CLOSE);
|
|
230
|
+
if (at < 0) {
|
|
231
|
+
return {
|
|
232
|
+
kind: PatchKind.MANUAL,
|
|
233
|
+
reason: "couldn't find a `</body>` to place the connect script before",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
kind: PatchKind.APPLY,
|
|
238
|
+
code: `${source.slice(0, at)}${astroConnectScript(port, projectId)}${source.slice(at)}`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -35,7 +35,10 @@ export function scanTestids(sources) {
|
|
|
35
35
|
*/
|
|
36
36
|
const STORE_LIBRARIES = [
|
|
37
37
|
['@tanstack/react-query', "registerStore('queries', tanstackQueryStore(queryClient))"],
|
|
38
|
-
[
|
|
38
|
+
[
|
|
39
|
+
'zustand',
|
|
40
|
+
"registerStore('app', useStore) // pass the store itself, not () => store.getState()",
|
|
41
|
+
],
|
|
39
42
|
['@reduxjs/toolkit', "registerStore('app', store)"],
|
|
40
43
|
['redux', "registerStore('app', store)"],
|
|
41
44
|
['jotai', "registerStore('app', jotaiStore(getDefaultStore(), { cart, user }))"],
|