@reticlehq/server 2.12.0 → 2.13.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 +67 -109
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -1
- package/dist/bridge/bridge.js +247 -164
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +239 -22
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +30 -12
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +3 -1
- package/dist/init/dev-script.js +8 -5
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +22 -1
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +61 -3
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +60 -0
- package/dist/setup/dev-server-wait.js +65 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +187 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +56 -8
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +18 -0
- package/dist/update/update-nudge.js +20 -0
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -1,43 +1,90 @@
|
|
|
1
|
-
import { Verified } from '@reticlehq/core';
|
|
1
|
+
import { Verified, } from '@reticlehq/core';
|
|
2
2
|
import { estimateTokens } from '../session/output-budget.js';
|
|
3
3
|
import { ImpactStore } from './impact-store.js';
|
|
4
4
|
/**
|
|
5
|
-
* The daemon's
|
|
5
|
+
* The daemon's impact recorders — ONE PER PROJECT ROOT.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* This was a single module-level store, and "the first root wins for the daemon's lifetime" was
|
|
8
|
+
* written down as if it were a design decision. It is only correct for a daemon that serves one
|
|
9
|
+
* project, and this daemon has never been that: `artifactRootFor` exists precisely because one
|
|
10
|
+
* daemon serves many, and nine modules already route their artifacts by the SESSION's project.
|
|
11
|
+
*
|
|
12
|
+
* Measured, not theorised: with a daemon rooted at repo A, two verdicts driven against app B landed
|
|
13
|
+
* in A's `impact.json` — B's own ledger never moved — and A's cloud link then carried them to a
|
|
14
|
+
* PRODUCTION dashboard belonging to a different account than B was linked to. The impact ledger is
|
|
15
|
+
* what the whole dashboard is computed from, so this was the one artifact whose misrouting was
|
|
16
|
+
* guaranteed to be visible and billed.
|
|
17
|
+
*
|
|
18
|
+
* Keyed by root path, so two projects driven through one daemon keep separate ledgers, and the
|
|
19
|
+
* daemon's own root remains the answer for anything that cannot name a project.
|
|
20
|
+
*/
|
|
21
|
+
/** One store per `.reticle` root. */
|
|
22
|
+
const stores = new Map();
|
|
23
|
+
/** The daemon's own root — used when a call cannot say which project it was for. */
|
|
24
|
+
let defaultRoot;
|
|
25
|
+
/** Held so a per-root store is built with the same clock and project name as the default. */
|
|
26
|
+
let storeOpts = {};
|
|
27
|
+
/** Get-or-create the store for one root. */
|
|
28
|
+
function storeFor(root) {
|
|
29
|
+
const key = root ?? defaultRoot;
|
|
30
|
+
if (key === undefined || 0 === key.length)
|
|
31
|
+
return undefined;
|
|
32
|
+
let found = stores.get(key);
|
|
33
|
+
if (found === undefined) {
|
|
34
|
+
found = new ImpactStore({ ...storeOpts, reticleRoot: key });
|
|
35
|
+
stores.set(key, found);
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wire the DEFAULT root — the daemon's own. Idempotent; the first one wins.
|
|
41
|
+
*
|
|
42
|
+
* Still idempotent, and still first-wins, because the daemon's own root genuinely does not change
|
|
43
|
+
* while it runs. What changed is that this is no longer the ONLY root: a call that knows its
|
|
44
|
+
* project passes one, and this is the fallback for calls that do not.
|
|
11
45
|
*/
|
|
12
|
-
let store;
|
|
13
|
-
/** Wire the recorder to a project. Idempotent; the first root wins for the daemon's lifetime. */
|
|
14
46
|
export function initImpact(opts) {
|
|
15
47
|
// No root, no record. Programmatic callers and test doubles build their own deps and are not
|
|
16
48
|
// obliged to carry one, and a courtesy counter must never be the reason a tool call throws.
|
|
17
49
|
if (opts.reticleRoot === undefined || 0 === opts.reticleRoot.length)
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
|
|
50
|
+
return storeFor(undefined);
|
|
51
|
+
if (defaultRoot === undefined) {
|
|
52
|
+
defaultRoot = opts.reticleRoot;
|
|
53
|
+
const { projectName, now } = opts;
|
|
54
|
+
storeOpts = {
|
|
55
|
+
...(projectName === undefined ? {} : { projectName }),
|
|
56
|
+
...(now === undefined ? {} : { now }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return storeFor(opts.reticleRoot);
|
|
21
60
|
}
|
|
22
|
-
/** The live store, if one has been initialised. */
|
|
23
|
-
export function impactStore() {
|
|
24
|
-
return
|
|
61
|
+
/** The live store for a root (default when omitted), if one has been initialised. */
|
|
62
|
+
export function impactStore(root) {
|
|
63
|
+
return storeFor(root);
|
|
25
64
|
}
|
|
26
|
-
/** Test seam: drop the
|
|
65
|
+
/** Test seam: drop the stores so a suite can start from a clean record. */
|
|
27
66
|
export function resetImpactForTest() {
|
|
28
|
-
|
|
67
|
+
stores.clear();
|
|
68
|
+
defaultRoot = undefined;
|
|
69
|
+
storeOpts = {};
|
|
29
70
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Record one delta against a project's ledger. Never throws: stats must not break a tool call.
|
|
73
|
+
*
|
|
74
|
+
* `root` is the `.reticle` directory this call's evidence belongs to. Omitted means "could not tell
|
|
75
|
+
* which project", which falls back to the daemon's own root — the old behaviour, kept for the paths
|
|
76
|
+
* that genuinely have no session to ask.
|
|
77
|
+
*/
|
|
78
|
+
export function recordImpact(delta, meta = {}, root) {
|
|
32
79
|
try {
|
|
33
|
-
|
|
80
|
+
storeFor(root)?.record(delta, meta);
|
|
34
81
|
}
|
|
35
82
|
catch {
|
|
36
83
|
// Counting is a courtesy to the user; failing to count is not worth failing their call over.
|
|
37
84
|
}
|
|
38
85
|
}
|
|
39
|
-
export function impactSnapshot() {
|
|
40
|
-
return
|
|
86
|
+
export function impactSnapshot(root) {
|
|
87
|
+
return storeFor(root)?.snapshot();
|
|
41
88
|
}
|
|
42
89
|
/**
|
|
43
90
|
* What one tool call did, as counters.
|
|
@@ -71,6 +118,42 @@ export function deltaForToolResult(raw, durationMs, refused) {
|
|
|
71
118
|
}
|
|
72
119
|
return delta;
|
|
73
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* What one FAILED verdict was, in a line.
|
|
123
|
+
*
|
|
124
|
+
* Only a verified:"no" produces one. That is the same rule the counters follow and the same rule
|
|
125
|
+
* the product states to agents: an "unknown" is not a defect, it is Reticle admitting it could not
|
|
126
|
+
* tell — recording those here would fill the user's short list with the tool's own blind spots.
|
|
127
|
+
*
|
|
128
|
+
* Everything is read from the result the tool already returned, so this costs nothing extra and
|
|
129
|
+
* cannot disagree with the verdict it describes.
|
|
130
|
+
*/
|
|
131
|
+
export function defectForToolResult(raw, at) {
|
|
132
|
+
if (!isRecord(raw) || raw['verified'] !== Verified.NO)
|
|
133
|
+
return undefined;
|
|
134
|
+
const effect = isRecord(raw['effect']) ? raw['effect'] : undefined;
|
|
135
|
+
const verdict = isRecord(raw['verdict']) ? raw['verdict'] : undefined;
|
|
136
|
+
const named = text(effect?.['name']) ?? text(effect?.['testid']) ?? text(effect?.['component']);
|
|
137
|
+
const why = text(verdict?.['failureReason']) ?? text(raw['because']);
|
|
138
|
+
const defect = {
|
|
139
|
+
at,
|
|
140
|
+
// The control that was acted on, when the result names one — "Sign In did not sign in" is a
|
|
141
|
+
// sentence somebody can act on; "a verdict failed" is not.
|
|
142
|
+
title: named === undefined ? (why ?? 'a declared consequence did not hold') : named,
|
|
143
|
+
};
|
|
144
|
+
if (named !== undefined && why !== undefined)
|
|
145
|
+
defect.detail = why;
|
|
146
|
+
const source = text(raw['source']);
|
|
147
|
+
if (source !== undefined)
|
|
148
|
+
defect.source = source;
|
|
149
|
+
return defect;
|
|
150
|
+
}
|
|
151
|
+
function text(value) {
|
|
152
|
+
if ('string' !== typeof value)
|
|
153
|
+
return undefined;
|
|
154
|
+
const trimmed = value.trim();
|
|
155
|
+
return 0 === trimmed.length ? undefined : trimmed;
|
|
156
|
+
}
|
|
74
157
|
function isRecord(value) {
|
|
75
158
|
return 'object' === typeof value && null !== value;
|
|
76
159
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { type ImpactCounts, type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
|
|
1
|
+
import { type ImpactCounts, type ImpactDefect, type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
|
|
2
2
|
export interface ImpactPaths {
|
|
3
3
|
project: string;
|
|
4
4
|
global: string;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Where this project's dashboard lives, if `reticle link` recorded one.
|
|
8
|
+
*
|
|
9
|
+
* Read from the link file rather than derived from the API url, because the API origin and the
|
|
10
|
+
* console origin are different hosts in every deployment that is not a laptop. Synchronous and
|
|
11
|
+
* best-effort, like every other read in this file: a missing or malformed link file means no link
|
|
12
|
+
* in the HUD, never a failed tool call.
|
|
13
|
+
*/
|
|
14
|
+
export declare function readDashboardUrl(reticleRoot: string): string | undefined;
|
|
6
15
|
/** Read a scope, tolerating absence and corruption: a broken file starts over rather than throwing. */
|
|
7
16
|
export declare function readScope(path: string, now: number): ImpactScope;
|
|
8
17
|
/** YYYY-MM-DD in local time - the day boundary a person recognises, not UTC's. */
|
|
@@ -17,6 +26,11 @@ export declare function isoDay(now: number): string;
|
|
|
17
26
|
*/
|
|
18
27
|
export interface ImpactFoldMeta {
|
|
19
28
|
runMs?: number;
|
|
29
|
+
/**
|
|
30
|
+
* The defect this call caught, when it caught one. Carried beside the counters rather than in
|
|
31
|
+
* them because it is not a tally: `counts.failed` says how many, this says which.
|
|
32
|
+
*/
|
|
33
|
+
defect?: ImpactDefect;
|
|
20
34
|
}
|
|
21
35
|
/** Fold one delta into a scope: totals, today's bucket, records and streak. Pure. */
|
|
22
36
|
export declare function applyDelta(scope: ImpactScope, delta: Partial<ImpactCounts>, now: number, meta?: ImpactFoldMeta): ImpactScope;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { ReticleDir, IMPACT_DAILY_BUCKETS, IMPACT_SCHEMA_VERSION, ImpactScopeSchema, addImpactCounts, emptyImpactCounts, emptyImpactRecords, estimateImpactSavings, } from '@reticlehq/core';
|
|
4
|
+
import { ReticleDir, IMPACT_DAILY_BUCKETS, IMPACT_DEFECT_LIMIT, IMPACT_SCHEMA_VERSION, ImpactScopeSchema, addImpactCounts, emptyImpactCounts, emptyImpactRecords, estimateImpactSavings, } from '@reticlehq/core';
|
|
5
5
|
/**
|
|
6
6
|
* Where Reticle keeps the record of what it has done for you.
|
|
7
7
|
*
|
|
@@ -14,6 +14,8 @@ import { ReticleDir, IMPACT_DAILY_BUCKETS, IMPACT_SCHEMA_VERSION, ImpactScopeSch
|
|
|
14
14
|
*/
|
|
15
15
|
/** Writes are debounced: a verification loop is 50-200 calls, and each one is a counter bump. */
|
|
16
16
|
const WRITE_DEBOUNCE_MS = 800;
|
|
17
|
+
/** The project's cloud binding, written by `reticle link`. Absent for an unlinked project. */
|
|
18
|
+
const CLOUD_LINK_FILE = 'cloud.json';
|
|
17
19
|
/**
|
|
18
20
|
* `reticleRoot` is the project's own `.reticle` directory (what every other store here is handed);
|
|
19
21
|
* the global scope always lives beside the daemon's own state in `~/.reticle`.
|
|
@@ -24,6 +26,26 @@ function impactPaths(reticleRoot) {
|
|
|
24
26
|
global: join(homedir(), ReticleDir.ROOT, ReticleDir.IMPACT_FILE),
|
|
25
27
|
};
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Where this project's dashboard lives, if `reticle link` recorded one.
|
|
31
|
+
*
|
|
32
|
+
* Read from the link file rather than derived from the API url, because the API origin and the
|
|
33
|
+
* console origin are different hosts in every deployment that is not a laptop. Synchronous and
|
|
34
|
+
* best-effort, like every other read in this file: a missing or malformed link file means no link
|
|
35
|
+
* in the HUD, never a failed tool call.
|
|
36
|
+
*/
|
|
37
|
+
export function readDashboardUrl(reticleRoot) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(readFileSync(join(reticleRoot, CLOUD_LINK_FILE), 'utf8'));
|
|
40
|
+
if ('object' !== typeof raw || null === raw)
|
|
41
|
+
return undefined;
|
|
42
|
+
const value = raw['dashboardUrl'];
|
|
43
|
+
return 'string' === typeof value && value.length > 0 ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
27
49
|
function emptyScope(now) {
|
|
28
50
|
const counts = emptyImpactCounts();
|
|
29
51
|
return {
|
|
@@ -32,6 +54,7 @@ function emptyScope(now) {
|
|
|
32
54
|
records: emptyImpactRecords(),
|
|
33
55
|
savings: estimateImpactSavings(counts),
|
|
34
56
|
since: now,
|
|
57
|
+
defects: [],
|
|
35
58
|
};
|
|
36
59
|
}
|
|
37
60
|
/** Read a scope, tolerating absence and corruption: a broken file starts over rather than throwing. */
|
|
@@ -105,7 +128,21 @@ export function applyDelta(scope, delta, now, meta = {}) {
|
|
|
105
128
|
}
|
|
106
129
|
records.bestStreakDays = Math.max(records.bestStreakDays, records.streakDays);
|
|
107
130
|
records.longestRunMs = Math.max(records.longestRunMs, meta.runMs ?? 0);
|
|
108
|
-
|
|
131
|
+
/*
|
|
132
|
+
* Newest first, capped. Prepending is what makes the HUD's short list the CURRENT breakage rather
|
|
133
|
+
* than the first ten things that ever broke — which, after a week, is a list about the past.
|
|
134
|
+
*/
|
|
135
|
+
const defects = meta.defect === undefined
|
|
136
|
+
? scope.defects
|
|
137
|
+
: [meta.defect, ...scope.defects].slice(0, IMPACT_DEFECT_LIMIT);
|
|
138
|
+
return {
|
|
139
|
+
counts,
|
|
140
|
+
days,
|
|
141
|
+
records,
|
|
142
|
+
savings: estimateImpactSavings(counts),
|
|
143
|
+
since: scope.since,
|
|
144
|
+
defects,
|
|
145
|
+
};
|
|
109
146
|
}
|
|
110
147
|
/**
|
|
111
148
|
* The live impact record for one project.
|
|
@@ -117,6 +154,7 @@ export class ImpactStore {
|
|
|
117
154
|
#paths;
|
|
118
155
|
#now;
|
|
119
156
|
#projectName;
|
|
157
|
+
#dashboardUrl;
|
|
120
158
|
#project;
|
|
121
159
|
#global;
|
|
122
160
|
#timer;
|
|
@@ -125,6 +163,7 @@ export class ImpactStore {
|
|
|
125
163
|
this.#paths = impactPaths(opts.reticleRoot);
|
|
126
164
|
this.#now = opts.now ?? (() => Date.now());
|
|
127
165
|
this.#projectName = opts.projectName;
|
|
166
|
+
this.#dashboardUrl = readDashboardUrl(opts.reticleRoot);
|
|
128
167
|
const now = this.#now();
|
|
129
168
|
this.#project = readScope(this.#paths.project, now);
|
|
130
169
|
this.#global = readScope(this.#paths.global, now);
|
|
@@ -148,6 +187,8 @@ export class ImpactStore {
|
|
|
148
187
|
};
|
|
149
188
|
if (this.#projectName !== undefined)
|
|
150
189
|
snap.projectName = this.#projectName;
|
|
190
|
+
if (this.#dashboardUrl !== undefined)
|
|
191
|
+
snap.dashboardUrl = this.#dashboardUrl;
|
|
151
192
|
return snap;
|
|
152
193
|
}
|
|
153
194
|
/** Write now (process exit, tests). */
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveProjectCloud } from './cloud/cloud-config.js';
|
|
5
|
+
import { startSyncDaemon } from './cloud/sync-daemon.js';
|
|
6
|
+
import { PROJECT_REGISTRY_FILE, emptyProjectRegistry, parseProjectRegistry, projectCandidates, } from '@reticlehq/core';
|
|
5
7
|
import { discoverProjectConfigs } from './cli/config-discovery.js';
|
|
6
8
|
import { projectCandidatesFrom, resolveArtifactRoot, } from './project/artifact-root.js';
|
|
7
9
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -14,6 +16,8 @@ import { openLoopbackAlias } from './daemon/loopback-alias.js';
|
|
|
14
16
|
import { reportAppInstrumented } from './telemetry/app-instrumented.js';
|
|
15
17
|
import { resolveBridgeSecurityWithAutoToken } from './bridge/bridge-security.js';
|
|
16
18
|
import { Bridge } from './bridge/bridge.js';
|
|
19
|
+
import { sdkFixForDirectory } from './version/sdk-fix.js';
|
|
20
|
+
import { SERVER_VERSION } from './version/server-version.js';
|
|
17
21
|
import { BaselineStore } from './project/baselines.js';
|
|
18
22
|
import { RecordingStore } from './flows/recordings.js';
|
|
19
23
|
import { initImpact } from './impact/impact-recorder.js';
|
|
@@ -39,6 +43,7 @@ import { statusPayload } from './status-payload.js';
|
|
|
39
43
|
import { CdpRealInputProvider, LaunchedRealInputProvider } from './input/real-input.js';
|
|
40
44
|
import { cpus } from 'node:os';
|
|
41
45
|
import { BrowserPool } from './pool/browser-pool.js';
|
|
46
|
+
import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './session/lease-visibility.js';
|
|
42
47
|
import { playwrightLauncher, resolveMaxContexts } from './pool/playwright-launcher.js';
|
|
43
48
|
import { LeaseReaper } from './pool/lease-reaper.js';
|
|
44
49
|
import { readJournalEnabled, readProjectId } from './cli/cli-port.js';
|
|
@@ -187,7 +192,14 @@ function makeNetworkDetailRouter(bridge, driveUrl) {
|
|
|
187
192
|
function attachJournal(bridge, deps) {
|
|
188
193
|
const journalAttach = makeJournalAttach(deps);
|
|
189
194
|
const ambientStore = new AmbientStore(deps.fs, deps.reticleRoot);
|
|
195
|
+
// Built once, not per session: the resolver walks config discovery and the user-level registry,
|
|
196
|
+
// and neither changes between two tabs connecting a second apart.
|
|
197
|
+
const resolveArtifactRoot = artifactRootResolver(deps.reticleRoot);
|
|
190
198
|
bridge.attachSessionCreate((session) => {
|
|
199
|
+
// Stamp the project's own `.reticle` before ANY counter fires for this session. Without it every
|
|
200
|
+
// verdict is recorded against wherever the daemon was started, which is how one app's evidence
|
|
201
|
+
// reached a different account's production dashboard.
|
|
202
|
+
session.artifactRoot = resolveArtifactRoot(session.projectId).root;
|
|
191
203
|
journalAttach(session);
|
|
192
204
|
// Seed the learned ambient map so a fresh session starts knowing which regions churn, instead of
|
|
193
205
|
// re-learning from zero. Best-effort + async: a late seed still helps, a failure is silent.
|
|
@@ -256,6 +268,14 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
|
|
|
256
268
|
return {};
|
|
257
269
|
}
|
|
258
270
|
/** Start the Reticle bridge (browser WS endpoint) and, by default, the MCP stdio server. */
|
|
271
|
+
/**
|
|
272
|
+
* The SDK-upgrade sentence for THIS project's package.json, evaluated at each HELLO so a
|
|
273
|
+
* just-edited manifest is what we name. Falls back to the framework-neutral sensor when cwd is
|
|
274
|
+
* not an app.
|
|
275
|
+
*/
|
|
276
|
+
function sdkFixForCwd() {
|
|
277
|
+
return sdkFixForDirectory(SERVER_VERSION, process.cwd());
|
|
278
|
+
}
|
|
259
279
|
/**
|
|
260
280
|
* Resolve a session's artifact root, from everything this machine knows about where projects live.
|
|
261
281
|
*
|
|
@@ -264,6 +284,39 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
|
|
|
264
284
|
* this daemon is up, and a resolution that used a startup snapshot would keep answering with a map
|
|
265
285
|
* from before the project the agent is now driving existed.
|
|
266
286
|
*/
|
|
287
|
+
/**
|
|
288
|
+
* Every `.reticle` root on this machine that sync should consider, from the same two sources the
|
|
289
|
+
* artifact resolver uses.
|
|
290
|
+
*
|
|
291
|
+
* Read on every call rather than snapshotted, for the same reason: `reticle link` can run in
|
|
292
|
+
* another terminal while this daemon is up, and a startup snapshot would keep that repo silent
|
|
293
|
+
* until somebody restarted a process they have no reason to suspect.
|
|
294
|
+
*
|
|
295
|
+
* Returns directories that MIGHT be linked; the caller resolves each and skips the ones that are
|
|
296
|
+
* not. Deciding that here would mean reading every cloud.json on every tick.
|
|
297
|
+
*/
|
|
298
|
+
function knownProjectRoots() {
|
|
299
|
+
const roots = new Set();
|
|
300
|
+
try {
|
|
301
|
+
const path = join(homedir(), ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
|
|
302
|
+
if (existsSync(path)) {
|
|
303
|
+
const registry = parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')));
|
|
304
|
+
for (const candidate of projectCandidates(registry))
|
|
305
|
+
roots.add(join(candidate.directory, ReticleDir.ROOT));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// A registry that cannot be read is an empty one — never a reason to stop syncing.
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
for (const config of discoverProjectConfigs(process.cwd()).found)
|
|
313
|
+
roots.add(join(config.directory, ReticleDir.ROOT));
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Same: a diagnostic walk that throws must not take the sync loop with it.
|
|
317
|
+
}
|
|
318
|
+
return [...roots];
|
|
319
|
+
}
|
|
267
320
|
function artifactRootResolver(daemonRoot) {
|
|
268
321
|
return (projectId) => {
|
|
269
322
|
let registry = emptyProjectRegistry();
|
|
@@ -298,7 +351,7 @@ export async function start(options = {}) {
|
|
|
298
351
|
// "nothing recorded yet" over a month of history on disk is the worst version of this feature.
|
|
299
352
|
initImpact({ reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT) });
|
|
300
353
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
301
|
-
const bridge = new Bridge({ port, ...security });
|
|
354
|
+
const bridge = new Bridge({ port, sdkFix: sdkFixForCwd, ...security });
|
|
302
355
|
// Server-authoritative liveness: a Node-side reaper (immune to browser throttling) ends sessions
|
|
303
356
|
// whose agent has gone idle, so a forgotten/crashed agent never leaves the HUD "running" forever.
|
|
304
357
|
const reaper = new SessionReaper(bridge.sessions);
|
|
@@ -332,6 +385,25 @@ export async function start(options = {}) {
|
|
|
332
385
|
pool = createBrowserPool(options.headless ?? true);
|
|
333
386
|
leaseReaper = new LeaseReaper(pool);
|
|
334
387
|
leaseReaper.start();
|
|
388
|
+
/*
|
|
389
|
+
* Tell a tab that ARRIVES during a lease why its HUD is silent.
|
|
390
|
+
*
|
|
391
|
+
* The acquire-time notice only reaches tabs already connected, so opening the app — or merely
|
|
392
|
+
* reloading it — while an agent held a lease landed somebody on a dark HUD with nothing
|
|
393
|
+
* explaining it. That is the more common way to hit it, because a person opens the dashboard
|
|
394
|
+
* precisely when they want to watch. Reported as "dashboard is being watched, but the agent chat
|
|
395
|
+
* shows nothing".
|
|
396
|
+
*
|
|
397
|
+
* Registered separately from the journal hook rather than folded into it: this one needs the
|
|
398
|
+
* pool, which does not exist on the no-drive path, and session-create handlers are additive.
|
|
399
|
+
*/
|
|
400
|
+
const leasePool = pool;
|
|
401
|
+
bridge.attachSessionCreate((session) => {
|
|
402
|
+
const leasedIds = leasePool.leasedSessionIds();
|
|
403
|
+
const leasedProjects = leasedIds.map((id) => bridge.sessions.get(id)?.projectId);
|
|
404
|
+
if (shouldGreetWithLeaseNotice(session, leasedIds, leasedProjects))
|
|
405
|
+
session.pushNarration(AGENT_ALREADY_DRIVING_ELSEWHERE);
|
|
406
|
+
});
|
|
335
407
|
const deps = {
|
|
336
408
|
sessions: bridge.sessions,
|
|
337
409
|
pool,
|
|
@@ -391,14 +463,19 @@ export async function startDaemon(options = {}) {
|
|
|
391
463
|
initImpact({ reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT) });
|
|
392
464
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
393
465
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
394
|
-
const bridge = new Bridge({ port, server: shared.httpServer, ...security });
|
|
466
|
+
const bridge = new Bridge({ port, server: shared.httpServer, sdkFix: sdkFixForCwd, ...security });
|
|
395
467
|
// The daemon owns listen (below), so the real bind error is reported there; absorb bridge.ready's
|
|
396
468
|
// mirror rejection so a port collision can't surface as an unhandled promise rejection.
|
|
397
469
|
void bridge.ready.catch(() => undefined);
|
|
470
|
+
// Declared before attachStatus so the closure below can report it; assigned further down, before
|
|
471
|
+
// listen — the first status request cannot arrive until after the bind.
|
|
472
|
+
let verifyHttp;
|
|
398
473
|
// `reticle status` GETs this for a live, at-a-glance view of connected tabs + their health.
|
|
399
474
|
// The same diagnosis agents get on an empty `reticle_sessions`, so `reticle status` — the
|
|
400
475
|
// most-run command in the field — stops answering "sessionCount: 0" and nothing else.
|
|
401
|
-
|
|
476
|
+
// `verifyPort` rides along so a later `serve --http` can tell whether this daemon already honours
|
|
477
|
+
// the requested `--http-port` instead of silently ignoring the flag (#687).
|
|
478
|
+
shared.attachStatus(() => statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port));
|
|
402
479
|
// Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
|
|
403
480
|
// connection drops (it stopped, or is waiting on the human), end every session and push a clear
|
|
404
481
|
// "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
|
|
@@ -426,6 +503,23 @@ export async function startDaemon(options = {}) {
|
|
|
426
503
|
const pool = createBrowserPool(options.headless ?? true);
|
|
427
504
|
const leaseReaper = new LeaseReaper(pool);
|
|
428
505
|
leaseReaper.start();
|
|
506
|
+
/*
|
|
507
|
+
* Automatic cloud sync, for the session nobody thinks about.
|
|
508
|
+
*
|
|
509
|
+
* The daemon outlives every individual tool call and is already running for the whole session, so
|
|
510
|
+
* it is the only place "keep the dashboard current while work is happening" costs nobody an extra
|
|
511
|
+
* process to remember. Safe for an UNLINKED project: it resolves the link on every tick and does
|
|
512
|
+
* nothing until one exists, so `reticle link` takes effect without a restart.
|
|
513
|
+
*/
|
|
514
|
+
const cloudSync = startSyncDaemon({
|
|
515
|
+
reticleRoot,
|
|
516
|
+
cloud: () => resolveProjectCloud(fs, reticleRoot, homedir(), process.env),
|
|
517
|
+
// Every OTHER linked repo on this machine, not just the directory the daemon was started in.
|
|
518
|
+
// One daemon serves many projects, and pushing only its own root left the rest silently
|
|
519
|
+
// reporting nothing — indistinguishable, on the dashboard, from nobody having verified anything.
|
|
520
|
+
otherRoots: () => Promise.resolve(knownProjectRoots()),
|
|
521
|
+
cloudFor: (root) => resolveProjectCloud(fs, root, homedir(), process.env),
|
|
522
|
+
});
|
|
429
523
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
430
524
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
431
525
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -433,7 +527,7 @@ export async function startDaemon(options = {}) {
|
|
|
433
527
|
// Ordered AFTER the pool: the watch may open a browser itself the moment it finds a listening dev
|
|
434
528
|
// server for a wired project (see no-session-watch.ts), and it opens it through the pool — the same
|
|
435
529
|
// path reticle_lease takes, in this process, binding nothing.
|
|
436
|
-
wireSessionScope(bridge.sessions, readProjectId(process.cwd()), port, () => pool.
|
|
530
|
+
wireSessionScope(bridge.sessions, readProjectId(process.cwd()), port, (sessionId) => pool.wasReapedLease(sessionId), (url) => pool.acquire(url));
|
|
437
531
|
const deps = {
|
|
438
532
|
sessions: bridge.sessions,
|
|
439
533
|
pool,
|
|
@@ -444,9 +538,18 @@ export async function startDaemon(options = {}) {
|
|
|
444
538
|
project,
|
|
445
539
|
fs,
|
|
446
540
|
reticleRoot,
|
|
541
|
+
// The long-lived daemon needs this MORE than the standalone MCP process does, not less: it is
|
|
542
|
+
// the one that outlives a single project and serves every app on the machine. Omitting it here
|
|
543
|
+
// silently disabled per-session artifact resolution for every agent that attaches to a running
|
|
544
|
+
// daemon — the common case — so flows were listed, loaded and healed against wherever the
|
|
545
|
+
// daemon happened to be launched. Wired in only one of the two places, a resolver is a resolver
|
|
546
|
+
// that does not run.
|
|
547
|
+
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
447
548
|
now,
|
|
448
549
|
bridgePort: port,
|
|
449
550
|
browserProbe: probeChromium,
|
|
551
|
+
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
552
|
+
onRunPersisted: () => cloudSync.nudge(),
|
|
450
553
|
};
|
|
451
554
|
const profile = resolveToolSurface(options.toolProfile);
|
|
452
555
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
@@ -461,9 +564,11 @@ export async function startDaemon(options = {}) {
|
|
|
461
564
|
// Optional OEM/CI verify endpoint: a host platform POSTs to /verify and gets an ReticleVerificationRun,
|
|
462
565
|
// driving the same flow-replay machinery the agent uses — no MCP stdio, no human. Each verdict is
|
|
463
566
|
// persisted via RunStore. Localhost-bound + token-guarded. Off unless `reticle serve --http`.
|
|
464
|
-
|
|
567
|
+
// (`verifyHttp` itself is declared above attachStatus, which reports its port.)
|
|
465
568
|
if (true === options.httpVerify) {
|
|
466
|
-
|
|
569
|
+
// Wakes cloud sync when the HTTP verify server persists a run — that path does not push
|
|
570
|
+
// inline the way the MCP one does, so without this its runs waited for the timer.
|
|
571
|
+
const runStore = new RunStore(fs, reticleRoot, { onWrote: () => cloudSync.nudge() });
|
|
467
572
|
const runner = new ReticleRunner(createRunnerPort(effectiveDeps));
|
|
468
573
|
const token = options.httpVerifyToken ?? process.env[ReticleEnv.VERIFY_TOKEN] ?? '';
|
|
469
574
|
verifyHttp = await startVerifyServer({ runner, token, persist: (run) => runStore.write(run) }, options.httpVerifyPort ?? RETICLE_VERIFY_DEFAULT_PORT);
|
|
@@ -552,6 +657,7 @@ export async function startDaemon(options = {}) {
|
|
|
552
657
|
if (vh !== undefined)
|
|
553
658
|
await new Promise((resolve) => vh.server.close(() => resolve()));
|
|
554
659
|
leaseReaper.stop();
|
|
660
|
+
cloudSync.stop();
|
|
555
661
|
await loopbackAlias.close?.();
|
|
556
662
|
await pool.shutdown();
|
|
557
663
|
await owned?.dispose();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `reticle init` writes a behavioral rule into the coding agent's instruction file so the agent
|
|
3
3
|
* VERIFIES a feature with Reticle after building it — not only when the user remembers to say "test
|
|
4
|
-
* it with reticle". Registering the MCP server (mcp.ts /
|
|
4
|
+
* it with reticle". Registering the MCP server (mcp.ts / mcp-clients.ts) only makes the tools AVAILABLE;
|
|
5
5
|
* this is the piece that makes the agent reach for them, every session, unprompted.
|
|
6
6
|
*
|
|
7
7
|
* It carries a SECOND standing instruction, and that one is not about this project at all: send
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* written into whichever instruction file the detected agent reads: `CLAUDE.md` (Claude Code),
|
|
16
16
|
* `.cursor/rules/reticle.mdc` (Cursor, `alwaysApply` so it stays in context), or `AGENTS.md` as the
|
|
17
17
|
* cross-agent fallback. Idempotent via a marker block — safe to append to an existing file and a
|
|
18
|
-
* re-run is a no-op. Mirrors the apply/already result shape used across init (see
|
|
18
|
+
* re-run is a no-op. Mirrors the apply/already result shape used across init (see mcp-clients.ts).
|
|
19
19
|
*/
|
|
20
20
|
/** Project-root instruction files the agent re-reads every session. */
|
|
21
21
|
export declare const CLAUDE_MD_PATH = "CLAUDE.md";
|
|
@@ -69,13 +69,20 @@ export declare const CLI: string;
|
|
|
69
69
|
* inside a host that asks the human for permission — so a dev server it starts is visible in the
|
|
70
70
|
* transcript, attributable, and stoppable.
|
|
71
71
|
*
|
|
72
|
-
* The guards are the whole reason this is safe, so no renderer may drop them:
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* The guards are the whole reason this is safe, so no renderer may drop them: one server at a time,
|
|
73
|
+
* read the command rather than invent one, never kill anything you did not start, and leave the
|
|
74
|
+
* permission prompt to the host. "Background it and say so" is no longer among them because the
|
|
75
|
+
* branch above now states it at the point of action, and this text is read every turn — a rule
|
|
76
|
+
* stated twice costs every agent tokens forever and is the first thing to drift.
|
|
77
|
+
*
|
|
78
|
+
* The RESTART branch outranks the rest and is stated first. It used to be an aside, while "if a dev
|
|
79
|
+
* server is already listening, use it" was a numbered guard — so an agent resolving the two
|
|
80
|
+
* reasonably reused a server started before `init` edited the build config, served a bundle with no
|
|
81
|
+
* SDK in it, and landed on an empty session list with every other step done correctly.
|
|
75
82
|
*
|
|
76
83
|
* Exported because slash-command.ts renders the same rule; there must not be two versions of it.
|
|
77
84
|
*/
|
|
78
|
-
export declare const DEV_SERVER_POLICY = "**
|
|
85
|
+
export declare const DEV_SERVER_POLICY = "**A dev server already running when `reticle init` ran does not have Reticle in its bundle.** It read the build config at boot; `init` edited it afterwards. It serves the old bundle and no session appears. In order:\n\n1. **A dev server was already running?** Restart it, then hard-reload the tab. \"Something is listening\" does not mean the right bundle is served.\n2. **Nothing was running?** Start it in the BACKGROUND and say so in one line. `reticle_sessions` gives you this project's own dev command in `next_action`; use that, never compose one. Started after `init`, it needs no restart.\n\nStopping to ask is how a verification turn ends with nothing verified.\n\nFour guards, none optional:\n\n1. **Never run two at once.** One dev server on the app's port. Restarting a stale one means stopping it first, not starting a second alongside it.\n2. **Never guess the command.** It comes from `package.json` scripts. No recognisable dev script means say so and stop, not invent one.\n3. **Never kill anything you did not start**, and never a daemon or a port holder. The one exception is the restart above, and say in one line that you did it.\n4. **The permission prompt belongs to your host.** Never bypass, suppress or auto-approve it, and take a refusal as the answer.";
|
|
79
86
|
/**
|
|
80
87
|
* The operative rules, carried by every file that loads on every turn.
|
|
81
88
|
*
|
package/dist/init/agent-rules.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `reticle init` writes a behavioral rule into the coding agent's instruction file so the agent
|
|
3
3
|
* VERIFIES a feature with Reticle after building it — not only when the user remembers to say "test
|
|
4
|
-
* it with reticle". Registering the MCP server (mcp.ts /
|
|
4
|
+
* it with reticle". Registering the MCP server (mcp.ts / mcp-clients.ts) only makes the tools AVAILABLE;
|
|
5
5
|
* this is the piece that makes the agent reach for them, every session, unprompted.
|
|
6
6
|
*
|
|
7
7
|
* It carries a SECOND standing instruction, and that one is not about this project at all: send
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* written into whichever instruction file the detected agent reads: `CLAUDE.md` (Claude Code),
|
|
16
16
|
* `.cursor/rules/reticle.mdc` (Cursor, `alwaysApply` so it stays in context), or `AGENTS.md` as the
|
|
17
17
|
* cross-agent fallback. Idempotent via a marker block — safe to append to an existing file and a
|
|
18
|
-
* re-run is a no-op. Mirrors the apply/already result shape used across init (see
|
|
18
|
+
* re-run is a no-op. Mirrors the apply/already result shape used across init (see mcp-clients.ts).
|
|
19
19
|
*/
|
|
20
20
|
import { NPX } from './mcp.js';
|
|
21
21
|
import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
|
|
@@ -76,21 +76,32 @@ export const CLI = `${NPX} ${RETICLE_NPM_PACKAGE}`;
|
|
|
76
76
|
* inside a host that asks the human for permission — so a dev server it starts is visible in the
|
|
77
77
|
* transcript, attributable, and stoppable.
|
|
78
78
|
*
|
|
79
|
-
* The guards are the whole reason this is safe, so no renderer may drop them:
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* The guards are the whole reason this is safe, so no renderer may drop them: one server at a time,
|
|
80
|
+
* read the command rather than invent one, never kill anything you did not start, and leave the
|
|
81
|
+
* permission prompt to the host. "Background it and say so" is no longer among them because the
|
|
82
|
+
* branch above now states it at the point of action, and this text is read every turn — a rule
|
|
83
|
+
* stated twice costs every agent tokens forever and is the first thing to drift.
|
|
84
|
+
*
|
|
85
|
+
* The RESTART branch outranks the rest and is stated first. It used to be an aside, while "if a dev
|
|
86
|
+
* server is already listening, use it" was a numbered guard — so an agent resolving the two
|
|
87
|
+
* reasonably reused a server started before `init` edited the build config, served a bundle with no
|
|
88
|
+
* SDK in it, and landed on an empty session list with every other step done correctly.
|
|
82
89
|
*
|
|
83
90
|
* Exported because slash-command.ts renders the same rule; there must not be two versions of it.
|
|
84
91
|
*/
|
|
85
|
-
export const DEV_SERVER_POLICY = `**
|
|
92
|
+
export const DEV_SERVER_POLICY = `**A dev server already running when \`reticle init\` ran does not have Reticle in its bundle.** It read the build config at boot; \`init\` edited it afterwards. It serves the old bundle and no session appears. In order:
|
|
93
|
+
|
|
94
|
+
1. **A dev server was already running?** Restart it, then hard-reload the tab. "Something is listening" does not mean the right bundle is served.
|
|
95
|
+
2. **Nothing was running?** Start it in the BACKGROUND and say so in one line. \`reticle_sessions\` gives you this project's own dev command in \`next_action\`; use that, never compose one. Started after \`init\`, it needs no restart.
|
|
96
|
+
|
|
97
|
+
Stopping to ask is how a verification turn ends with nothing verified.
|
|
86
98
|
|
|
87
|
-
|
|
99
|
+
Four guards, none optional:
|
|
88
100
|
|
|
89
|
-
1. **Never
|
|
90
|
-
2. **Never guess the command.** It comes from \`package.json\` scripts.
|
|
91
|
-
3. **Never kill anything
|
|
92
|
-
4. **
|
|
93
|
-
5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.`;
|
|
101
|
+
1. **Never run two at once.** One dev server on the app's port. Restarting a stale one means stopping it first, not starting a second alongside it.
|
|
102
|
+
2. **Never guess the command.** It comes from \`package.json\` scripts. No recognisable dev script means say so and stop, not invent one.
|
|
103
|
+
3. **Never kill anything you did not start**, and never a daemon or a port holder. The one exception is the restart above, and say in one line that you did it.
|
|
104
|
+
4. **The permission prompt belongs to your host.** Never bypass, suppress or auto-approve it, and take a refusal as the answer.`;
|
|
94
105
|
/**
|
|
95
106
|
* The operative rules, carried by every file that loads on every turn.
|
|
96
107
|
*
|
|
@@ -15,3 +15,19 @@ import { type SourcePatch } from './patch-kind.js';
|
|
|
15
15
|
import { UiLibrary } from './detect.js';
|
|
16
16
|
export declare function patchAstroConfig(source: string): SourcePatch;
|
|
17
17
|
export declare function patchAstroLayout(source: string, port: number | undefined, projectId: string | undefined, uiLibrary?: UiLibrary): SourcePatch;
|
|
18
|
+
/** Where Astro keeps ambient types — `create-astro` ships this file. */
|
|
19
|
+
export declare const ASTRO_ENV_DTS_PATH = "src/env.d.ts";
|
|
20
|
+
/**
|
|
21
|
+
* The declarations `astro check` needs for the Vite `define` names (#677).
|
|
22
|
+
*
|
|
23
|
+
* Matching SvelteKit's `hooks.client.ts` typing: `string | undefined`, because the define can be
|
|
24
|
+
* absent when the pairing token file is missing.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ASTRO_ENV_DTS_DECLARES: string;
|
|
27
|
+
/**
|
|
28
|
+
* Append the Vite-define ambient declarations to `src/env.d.ts`, or create the file.
|
|
29
|
+
*
|
|
30
|
+
* Without this, `create-astro`'s default `"astro check && astro build"` fails with four
|
|
31
|
+
* `ts(2304) Cannot find name '__RETICLE_TOKEN__'` errors after a clean init (#677).
|
|
32
|
+
*/
|
|
33
|
+
export declare function patchAstroEnvDts(existing: string | null): SourcePatch;
|