@reticlehq/server 2.11.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 +93 -111
- 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 -14
- package/dist/bridge/bridge.js +248 -165
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- 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 +8 -7
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +39 -1
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -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 +17 -1
- package/dist/cli/cli-parse.js +238 -12
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +2 -1
- package/dist/cli/cli-verify.js +5 -4
- package/dist/cli/cloud-cli.js +428 -253
- 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.d.ts +2 -1
- 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 +12 -1
- package/dist/cli/doctor-rows.js +12 -1
- 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/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +174 -0
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -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 +167 -186
- package/dist/cloud/cloud-config.d.ts +24 -2
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +16 -5
- package/dist/cloud/cloud-sync.js +64 -1
- 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 +12 -13
- package/dist/crawl/crawl.js +25 -3
- package/dist/daemon/daemon-resolve.d.ts +122 -0
- package/dist/daemon/daemon-resolve.js +187 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/dev/stale-issue-guard.d.ts +0 -2
- package/dist/dev/stale-issue-guard.js +1 -1
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +59 -0
- package/dist/events/contradictions.js +253 -27
- package/dist/events/declared.d.ts +17 -0
- package/dist/events/declared.js +43 -1
- package/dist/events/echo-mismatch.js +65 -1
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +13 -0
- package/dist/events/predicate-asks.js +22 -0
- package/dist/events/predicate-eval.js +117 -27
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +43 -3
- 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-schema.d.ts +44 -0
- package/dist/events/predicate-schema.js +126 -23
- package/dist/events/predicate.d.ts +20 -0
- package/dist/events/predicate.js +77 -6
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +6 -8
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- 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 +263 -23
- 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-success.js +15 -3
- package/dist/flows/flow-tools.js +87 -33
- package/dist/flows/flows.d.ts +14 -1
- package/dist/flows/flows.js +502 -24
- 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 +36 -15
- 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/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -3
- package/dist/honesty/blind-spots.d.ts +51 -15
- package/dist/honesty/blind-spots.js +64 -4
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +39 -6
- package/dist/honesty/instrumentation-gaps.js +88 -5
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +16 -0
- package/dist/honesty/verified.js +84 -5
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +13 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +13 -4
- package/dist/impact/impact-store.js +44 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +30 -6
- package/dist/init/agent-rules.js +54 -13
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -3
- package/dist/init/capabilities.js +23 -3
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +27 -28
- package/dist/init/confirm.js +51 -11
- package/dist/init/cra.d.ts +34 -5
- package/dist/init/cra.js +96 -25
- 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 +50 -0
- package/dist/init/dev-script.js +66 -0
- 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 +17 -6
- package/dist/init/mcp.js +27 -11
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +147 -37
- package/dist/init/plan.d.ts +30 -37
- package/dist/init/plan.js +50 -60
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +32 -35
- package/dist/init/run.js +151 -202
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +90 -31
- 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.d.ts +2 -0
- package/dist/input/real-input.js +28 -7
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +37 -0
- package/dist/intent/inline-intent.js +193 -0
- 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 +49 -0
- package/dist/intent/intent-store.js +133 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +143 -0
- package/dist/intent/open-intents.d.ts +42 -0
- package/dist/intent/open-intents.js +40 -0
- package/dist/journal/envelope.d.ts +26 -26
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -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 +83 -0
- package/dist/mcp/attach-memory.js +185 -0
- 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 -8
- package/dist/mcp/mcp-proxy.js +64 -62
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +52 -24
- 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/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +16 -0
- package/dist/mcp/tool-catalog-cache.js +22 -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/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +75 -5
- package/dist/pool/browser-pool.js +144 -5
- package/dist/pool/playwright-launcher.js +12 -1
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.js +6 -1
- package/dist/project/project-tools.js +6 -35
- package/dist/project/reticle-dir.d.ts +17 -2
- package/dist/project/reticle-dir.js +22 -4
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- 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 +24 -3
- 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/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +33 -0
- package/dist/session/no-session-diagnosis.js +82 -24
- package/dist/session/no-session-next-action.d.ts +12 -0
- package/dist/session/no-session-next-action.js +77 -6
- package/dist/session/no-session-watch.d.ts +18 -6
- package/dist/session/no-session-watch.js +125 -75
- package/dist/session/output-budget.js +4 -1
- 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-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -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 +111 -4
- 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.d.ts +37 -0
- package/dist/session/session-successor.js +113 -0
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +53 -3
- package/dist/session/session.js +162 -9
- 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/automation-hint.d.ts +2 -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-outbox.d.ts +2 -1
- 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 -9
- package/dist/telemetry/project-profile.js +12 -7
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- 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/telemetry/verification-of.js +2 -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 +196 -204
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +20 -3
- package/dist/tools/assert-verdict.js +58 -20
- package/dist/tools/browser-tools.js +6 -22
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +123 -69
- package/dist/tools/dynamic-tools.js +53 -13
- package/dist/tools/error-recovery.d.ts +25 -14
- package/dist/tools/error-recovery.js +78 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +121 -11
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +35 -5
- package/dist/tools/lease-tools.js +191 -13
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +109 -65
- package/dist/tools/read-tools.js +31 -8
- package/dist/tools/real-input-attempt.d.ts +32 -0
- package/dist/tools/real-input-attempt.js +212 -2
- package/dist/tools/reconcile-tools.js +22 -8
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +24 -0
- package/dist/tools/tool-kit.js +21 -0
- package/dist/tools/tool-names.d.ts +5 -0
- package/dist/tools/tool-names.js +5 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- 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 +78 -12
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- 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 +25 -10
- package/dist/update/update-nudge.js +32 -3
- package/dist/update/updater.d.ts +1 -1
- 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/README.md +2 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli/verify.mdx +4 -5
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +2 -2
- package/docs/gates.md +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/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +4 -3
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
package/dist/cli.js
CHANGED
|
@@ -5,23 +5,31 @@ import { realpathSync } from 'node:fs';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { stateDirProblem } from './daemon/state-dir.js';
|
|
7
7
|
import { statusNextAction } from './cli/status-next-action.js';
|
|
8
|
-
import {
|
|
8
|
+
import { readDevServers } from './daemon/dev-servers.js';
|
|
9
|
+
import { hasConnectedBefore, hasProjectConnectedBefore } from './session/connection-memory.js';
|
|
10
|
+
import { attachStatusFields } from './mcp/attach-memory.js';
|
|
9
11
|
import { reticleStateHome } from './daemon/daemon.js';
|
|
12
|
+
import { handleMcp } from './cli/mcp-command.js';
|
|
13
|
+
import { daemonSpawnArgs, daemonStartOptions } from './cli/daemon-start-options.js';
|
|
14
|
+
import { daemonsServingProjectElsewhere, resolveDaemonForProject, splitBrainNote, wrongDaemonNote, } from './daemon/daemon-resolve.js';
|
|
10
15
|
import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
|
|
11
|
-
import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
|
|
16
|
+
import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv, devServersForProject, } from '@reticlehq/core';
|
|
12
17
|
import { loadDotEnv } from './telemetry/dev-repo.js';
|
|
18
|
+
import { licenseKeyFromEnvFiles } from './license/license-env.js';
|
|
19
|
+
import { LICENSE_KEY_ENV } from './license/license.js';
|
|
13
20
|
import { createNodeFileSystem } from './project/fs-port.js';
|
|
14
21
|
import { affectedSavedFlows } from './flows/flow-sources.js';
|
|
15
22
|
import { availableUpdate } from './update/update-nudge.js';
|
|
16
23
|
import { handleUpdate, handleRollback } from './cli/cli-update-commands.js';
|
|
17
|
-
import { startDaemon } from './index.js';
|
|
24
|
+
import { startDaemon, RETICLE_VERIFY_DEFAULT_PORT } from './index.js';
|
|
25
|
+
import { verifyEndpointMismatch } from './status-payload.js';
|
|
18
26
|
import { isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
|
|
19
27
|
import { SERVER_VERSION } from './version/server-version.js';
|
|
20
28
|
import { log } from './log.js';
|
|
21
29
|
import { readPid, isAlive, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, logPath, } from './daemon/daemon.js';
|
|
22
30
|
import { PortPresence, probePresence, presenceIsUsable, describePresence, } from './daemon/port-presence.js';
|
|
23
|
-
import { waitForDaemon,
|
|
24
|
-
import { installDaemonResilience,
|
|
31
|
+
import { waitForDaemon, probeDaemon } from './mcp/mcp-proxy.js';
|
|
32
|
+
import { installDaemonResilience, recordExitReason, DaemonExitReason, } from './daemon/daemon-resilience.js';
|
|
25
33
|
import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
|
|
26
34
|
import { DaemonHeartbeat, resolveHeartbeatMs } from './daemon/heartbeat.js';
|
|
27
35
|
import { everServedToolCall } from './daemon/daemon-usefulness.js';
|
|
@@ -31,12 +39,12 @@ import { handleVerify } from './cli/cli-verify.js';
|
|
|
31
39
|
import { runKill } from './cli/cli-kill.js';
|
|
32
40
|
import { summarizeHunt } from './hunt/hunt-report.js';
|
|
33
41
|
import { runInit } from './init/run.js';
|
|
34
|
-
import {
|
|
42
|
+
import { continueAfterInit } from './setup/init-runtime.js';
|
|
35
43
|
import { handleDoctor } from './cli/cli-doctor.js';
|
|
36
44
|
import { buildNodeIo } from './init/node-io.js';
|
|
37
45
|
import { describeLicense } from './license/license.js';
|
|
38
46
|
import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId, } from './cli/cli-port.js';
|
|
39
|
-
import { DAEMON_INNER_COMMAND, PORT_FLAG,
|
|
47
|
+
import { DAEMON_INNER_COMMAND, PORT_FLAG, parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
|
|
40
48
|
import { handleFeedback, handleIdentify, handleTelemetry } from './telemetry/feedback-cli.js';
|
|
41
49
|
import { installDaemonTelemetry } from './telemetry/daemon-telemetry.js';
|
|
42
50
|
import { reportCliRun } from './telemetry/cli-telemetry.js';
|
|
@@ -52,14 +60,13 @@ function handleInit(parsed) {
|
|
|
52
60
|
dryRun: parsed.dryRun,
|
|
53
61
|
install: parsed.install,
|
|
54
62
|
...(parsed.app === undefined ? {} : { app: parsed.app }),
|
|
63
|
+
captureBodies: true === parsed.captureBodies,
|
|
55
64
|
// The outcome is reported by confirmInstall instead, once it knows whether an app connected —
|
|
56
65
|
// `init` writing files was never the same thing as `init` working (#269).
|
|
57
66
|
deferOutcome: true,
|
|
67
|
+
continuesToRuntime: true !== parsed.filesOnly && true !== parsed.dryRun,
|
|
58
68
|
}, io);
|
|
59
|
-
void
|
|
60
|
-
if (!result.ok)
|
|
61
|
-
process.exit(1);
|
|
62
|
-
});
|
|
69
|
+
void continueAfterInit(parsed, result, io, cwd);
|
|
63
70
|
}
|
|
64
71
|
function handleServe(parsed) {
|
|
65
72
|
void serveWithHonestExit(parsed);
|
|
@@ -81,6 +88,20 @@ async function serveWithHonestExit(parsed) {
|
|
|
81
88
|
status: fetchStatus,
|
|
82
89
|
});
|
|
83
90
|
if (presence === PortPresence.DAEMON) {
|
|
91
|
+
// A daemon that is already up was started with ITS flags, not these — `serve` only attaches.
|
|
92
|
+
// Exiting 0 here regardless is how `--http-port` came to be accepted and ignored (#687): the
|
|
93
|
+
// running daemon kept serving whatever it was started with, and the flag vanished without a
|
|
94
|
+
// word. Ask the daemon which verify port it actually serves and refuse when it is not the one
|
|
95
|
+
// requested — a flag that cannot be honoured must say so, not report success.
|
|
96
|
+
if (parsed.http) {
|
|
97
|
+
const mismatch = verifyEndpointMismatch(await fetchStatus(parsed.port), parsed.httpPort ?? RETICLE_VERIFY_DEFAULT_PORT);
|
|
98
|
+
if (mismatch !== undefined) {
|
|
99
|
+
log('reticle_daemon_start_refused', { port: parsed.port, reason: mismatch });
|
|
100
|
+
process.stderr.write(`${mismatch}\n`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
84
105
|
log('reticle_daemon_already_running', { port: parsed.port });
|
|
85
106
|
return;
|
|
86
107
|
}
|
|
@@ -100,19 +121,7 @@ async function serveWithHonestExit(parsed) {
|
|
|
100
121
|
process.exit(1);
|
|
101
122
|
return;
|
|
102
123
|
}
|
|
103
|
-
const daemonArgs =
|
|
104
|
-
if (parsed.driveUrl !== undefined) {
|
|
105
|
-
daemonArgs.push(DRIVE_FLAG, parsed.driveUrl);
|
|
106
|
-
if (!parsed.headless)
|
|
107
|
-
daemonArgs.push(HEADED_FLAG);
|
|
108
|
-
}
|
|
109
|
-
if (parsed.http) {
|
|
110
|
-
daemonArgs.push(HTTP_FLAG);
|
|
111
|
-
if (parsed.httpPort !== undefined)
|
|
112
|
-
daemonArgs.push(HTTP_PORT_FLAG, String(parsed.httpPort));
|
|
113
|
-
if (parsed.httpToken !== undefined)
|
|
114
|
-
daemonArgs.push(HTTP_TOKEN_FLAG, parsed.httpToken);
|
|
115
|
-
}
|
|
124
|
+
const daemonArgs = daemonSpawnArgs(parsed);
|
|
116
125
|
spawnDaemon(process.execPath, scriptPath, daemonArgs, parsed.port);
|
|
117
126
|
// The child binds asynchronously and, when it cannot, exits 1 long after this process would have
|
|
118
127
|
// reported success. Wait for it to ANSWER — `/status` responding is the only evidence a daemon
|
|
@@ -229,7 +238,20 @@ function withNextAction(facts) {
|
|
|
229
238
|
const next = statusNextAction(facts);
|
|
230
239
|
return next === undefined ? {} : { nextAction: next };
|
|
231
240
|
}
|
|
232
|
-
|
|
241
|
+
/**
|
|
242
|
+
* What `status` says about a project whose daemons have split in two.
|
|
243
|
+
*
|
|
244
|
+
* Both halves in one place because they are one condition asked from two positions, and a command
|
|
245
|
+
* can be standing on either. Silent — no key at all — when there is nothing to report, so a healthy
|
|
246
|
+
* run reads exactly as it did.
|
|
247
|
+
*/
|
|
248
|
+
function splitBrainFields(port, projectId) {
|
|
249
|
+
const home = reticleStateHome();
|
|
250
|
+
const elsewhere = splitBrainNote(port, daemonsServingProjectElsewhere(projectId, port, home, isAlive, (other) => hasProjectConnectedBefore(home, other, projectId)));
|
|
251
|
+
const note = elsewhere ?? wrongDaemonNote(port, resolveDaemonForProject(projectId, home, isAlive));
|
|
252
|
+
return note === undefined ? {} : { splitBrain: note };
|
|
253
|
+
}
|
|
254
|
+
export async function handleStatus(port) {
|
|
233
255
|
const pid = readPid(port);
|
|
234
256
|
// Durable, so it survives the daemon idling out — which is the state `status` is most often run in.
|
|
235
257
|
const projectId = readProjectId(process.cwd());
|
|
@@ -238,49 +260,94 @@ function handleStatus(port) {
|
|
|
238
260
|
// path does the first without the second — so this is the commonest reason `status` has nothing to
|
|
239
261
|
// report, and it was not among the facts this command could state.
|
|
240
262
|
const initialized = projectId !== undefined;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
263
|
+
// The OTHER half of the install, and the half this command could not see. A host that registers
|
|
264
|
+
// Reticle and never lists its tools looks exactly like an abandoned install from here — same idle
|
|
265
|
+
// daemon, same zero sessions — and the user sees a tool that does nothing. See attach-memory.ts.
|
|
266
|
+
const client = attachStatusFields(reticleStateHome(), port);
|
|
267
|
+
// What the dev servers themselves said. The reason this command could previously only guess at
|
|
268
|
+
// whether the app was running is that it had no way to see one; now the ones carrying Reticle
|
|
269
|
+
// announce, and the advice below stops contradicting the terminal the reader is looking at.
|
|
270
|
+
// Scoped to THIS project: the registry is machine-wide, and an unrelated app's dev server must
|
|
271
|
+
// not make this project look like it is running.
|
|
272
|
+
const devServerPorts = devServersForProject(readDevServers(reticleStateHome()), {
|
|
273
|
+
projectId,
|
|
274
|
+
root: process.cwd(),
|
|
275
|
+
}).map((d) => d.port);
|
|
276
|
+
// The failure where every individual check is green and the chain is broken: the agent's proxy and
|
|
277
|
+
// the app resolved their ports independently and landed on two different daemons. Reported from
|
|
278
|
+
// BOTH stances, because neither one can see it alone.
|
|
279
|
+
const split = splitBrainFields(port, projectId);
|
|
280
|
+
// Doctor and status must answer the same liveness question. A live pid only says that some
|
|
281
|
+
// process exists; a Reticle daemon is running only when the shared port probe reaches /status.
|
|
282
|
+
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
283
|
+
if (!presenceIsUsable(presence)) {
|
|
284
|
+
log('reticle_status', {
|
|
285
|
+
port,
|
|
286
|
+
running: false,
|
|
287
|
+
presence,
|
|
288
|
+
...(presence === PortPresence.FOREIGN ? { reason: describePresence(presence, port) } : {}),
|
|
289
|
+
// `init` promises this command says why the app has not connected. Without it the answer was
|
|
290
|
+
// `running: false` and nothing else, which reads as "Reticle is broken" for what is usually
|
|
291
|
+
// just a daemon that has not been asked to do anything yet.
|
|
292
|
+
...withNextAction({
|
|
293
|
+
running: false,
|
|
294
|
+
sessionCount: 0,
|
|
295
|
+
previouslyConnected,
|
|
296
|
+
initialized,
|
|
297
|
+
devServerPorts,
|
|
298
|
+
}),
|
|
299
|
+
...client,
|
|
300
|
+
...split,
|
|
256
301
|
});
|
|
257
302
|
return;
|
|
258
303
|
}
|
|
259
|
-
// The daemon
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
304
|
+
// The daemon answered the same probe doctor trusts — ask it for live sessions + health.
|
|
305
|
+
const payload = await fetchStatus(port);
|
|
306
|
+
// `status` is the second-most-run command and the one a HUMAN types. The update nudge otherwise
|
|
307
|
+
// only rides a tool result, which the majority of daemons never produce — so the people most in
|
|
308
|
+
// need of an upgrade were the ones with no path to hearing about it.
|
|
309
|
+
const update = availableUpdate();
|
|
310
|
+
const nudge = update === undefined ? {} : { updateAvailable: update };
|
|
311
|
+
if (payload === undefined) {
|
|
312
|
+
log('reticle_status', {
|
|
313
|
+
port,
|
|
314
|
+
running: true,
|
|
315
|
+
pid,
|
|
316
|
+
...nudge,
|
|
317
|
+
...withNextAction({
|
|
269
318
|
running: true,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
319
|
+
sessionCount: 0,
|
|
320
|
+
previouslyConnected,
|
|
321
|
+
initialized,
|
|
322
|
+
devServerPorts,
|
|
323
|
+
}),
|
|
324
|
+
...client,
|
|
325
|
+
...split,
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const summary = summarizeStatus(payload);
|
|
330
|
+
// Only when the daemon did NOT already explain itself. It has the whole diagnosis in-process and
|
|
331
|
+
// puts it on the wire as `why`; printing a second, thinner opinion beside it risks two confident
|
|
332
|
+
// answers pointing different ways, which is worse than one.
|
|
333
|
+
const next = summary.why === undefined
|
|
334
|
+
? withNextAction({
|
|
335
|
+
running: true,
|
|
336
|
+
...summary,
|
|
337
|
+
previouslyConnected,
|
|
338
|
+
initialized,
|
|
339
|
+
devServerPorts,
|
|
340
|
+
})
|
|
341
|
+
: {};
|
|
342
|
+
log('reticle_status', {
|
|
343
|
+
port,
|
|
344
|
+
running: true,
|
|
345
|
+
pid,
|
|
346
|
+
...summary,
|
|
347
|
+
...next,
|
|
348
|
+
...client,
|
|
349
|
+
...split,
|
|
350
|
+
...nudge,
|
|
284
351
|
});
|
|
285
352
|
}
|
|
286
353
|
/** Print the running package version (resolved once in server-version.ts). */
|
|
@@ -398,8 +465,17 @@ async function waitForNewSession(port, before) {
|
|
|
398
465
|
return false;
|
|
399
466
|
}
|
|
400
467
|
function handleOpen(requestedPort, url) {
|
|
468
|
+
// Our project's daemon first: `discoverDaemonPort` returns the LOWEST live daemon on the machine,
|
|
469
|
+
// whoever it belongs to, so on a machine running two projects `reticle open` could drive the other
|
|
470
|
+
// one's browser. It stays as the last resort for a caller with no project id, where any daemon is
|
|
471
|
+
// better than none and there is no identity to confuse.
|
|
472
|
+
const myProject = readProjectId(process.cwd());
|
|
401
473
|
probeDaemon(requestedPort)
|
|
402
|
-
.then((here) =>
|
|
474
|
+
.then((here) => here
|
|
475
|
+
? requestedPort
|
|
476
|
+
: (resolveDaemonForProject(myProject, reticleStateHome(), isAlive) ??
|
|
477
|
+
discoverDaemonPort() ??
|
|
478
|
+
requestedPort))
|
|
403
479
|
.then(async (port) => {
|
|
404
480
|
await ensureDaemon(port);
|
|
405
481
|
const { sessions } = summarizeStatus(await fetchStatus(port));
|
|
@@ -474,25 +550,13 @@ function handleOpen(requestedPort, url) {
|
|
|
474
550
|
});
|
|
475
551
|
}
|
|
476
552
|
function handleDaemonInner(parsed) {
|
|
477
|
-
const options =
|
|
478
|
-
port: parsed.port,
|
|
479
|
-
...(parsed.driveUrl !== undefined
|
|
480
|
-
? { driveUrl: parsed.driveUrl, headless: parsed.headless }
|
|
481
|
-
: {}),
|
|
482
|
-
...(parsed.http
|
|
483
|
-
? {
|
|
484
|
-
httpVerify: true,
|
|
485
|
-
...(parsed.httpPort !== undefined ? { httpVerifyPort: parsed.httpPort } : {}),
|
|
486
|
-
...(parsed.httpToken !== undefined ? { httpVerifyToken: parsed.httpToken } : {}),
|
|
487
|
-
}
|
|
488
|
-
: {}),
|
|
489
|
-
};
|
|
553
|
+
const options = daemonStartOptions(parsed);
|
|
490
554
|
startDaemon(options)
|
|
491
555
|
.then((server) => {
|
|
492
556
|
log('reticle_daemon_ready', { port: parsed.port, pid: process.pid });
|
|
493
557
|
// Daemon lifecycle telemetry: started, a one-shot project profile, the periodic counter flush,
|
|
494
558
|
// and the rich summary on shutdown. All of it lives in one module — see daemon-telemetry.ts.
|
|
495
|
-
const daemonTelemetry = installDaemonTelemetry(process.cwd());
|
|
559
|
+
const daemonTelemetry = installDaemonTelemetry(process.cwd(), undefined, parsed.port);
|
|
496
560
|
// Publish to the discovery registry so a build plugin can find this daemon by projectId — no
|
|
497
561
|
// hand-reconciled port. Written from the child (only it knows its cwd); removePid drops it.
|
|
498
562
|
const registryProjectId = readProjectId(process.cwd());
|
|
@@ -585,113 +649,18 @@ function handleDaemonInner(parsed) {
|
|
|
585
649
|
process.exit(1);
|
|
586
650
|
});
|
|
587
651
|
}
|
|
588
|
-
/**
|
|
589
|
-
* MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
|
|
590
|
-
* stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
|
|
591
|
-
* configure Reticle in.mcp.json — users never need to manage the daemon manually.
|
|
592
|
-
*
|
|
593
|
-
* Pass --drive <url> to have the daemon launch its own Playwright browser at that
|
|
594
|
-
* URL. The agent then has full autonomous control without relying on the user's browser.
|
|
595
|
-
*/
|
|
596
|
-
function handleMcp(opts) {
|
|
597
|
-
const { port, driveUrl, headless, http, httpPort, httpToken } = opts;
|
|
598
|
-
// The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
|
|
599
|
-
// is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
|
|
600
|
-
// report it, keep serving: the reconnect and dormant paths already know how to rebuild the only
|
|
601
|
-
// state this process has. See installProxyResilience for why its rule inverts the daemon's.
|
|
602
|
-
// The proxy's crash handlers must write to the proxy's LOG FILE, not just stderr. The editor
|
|
603
|
-
// swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
|
|
604
|
-
// failure a user reports as "it disconnected" left nothing behind to read.
|
|
605
|
-
setProxyLogPort(port);
|
|
606
|
-
installProxyResilience(process, proxyLog);
|
|
607
|
-
/**
|
|
608
|
-
* Make sure a daemon is on the port, spawning one if not.
|
|
609
|
-
*
|
|
610
|
-
* Called on first start AND on every reconnect. The proxy used to only retry the socket, so a
|
|
611
|
-
* daemon that exited — crashed, `reticle stop`, or self-shut-down as idle — meant the retries hit
|
|
612
|
-
* a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
|
|
613
|
-
* disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
|
|
614
|
-
*/
|
|
615
|
-
const ensure = async () => {
|
|
616
|
-
// The same question every other surface asks. It used to be a bare TCP connect, so a stranger on
|
|
617
|
-
// the bridge port answered "a daemon is here" — the proxy connected, the stream ended, and each
|
|
618
|
-
// client request woke into the identical non-answer. Rejecting here is what puts the proxy
|
|
619
|
-
// dormant with a reason, instead of pretending the wake succeeded.
|
|
620
|
-
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
621
|
-
if (presenceIsUsable(presence))
|
|
622
|
-
return;
|
|
623
|
-
if (PortPresence.FREE !== presence)
|
|
624
|
-
throw new Error(describePresence(presence, port));
|
|
625
|
-
const scriptPath = process.argv[1];
|
|
626
|
-
if (scriptPath === undefined) {
|
|
627
|
-
log('reticle_mcp_no_script', {});
|
|
628
|
-
process.exit(1);
|
|
629
|
-
}
|
|
630
|
-
const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
|
|
631
|
-
if (driveUrl !== undefined) {
|
|
632
|
-
daemonArgs.push(DRIVE_FLAG, driveUrl);
|
|
633
|
-
if (!headless)
|
|
634
|
-
daemonArgs.push(HEADED_FLAG);
|
|
635
|
-
}
|
|
636
|
-
// Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
|
|
637
|
-
if (http) {
|
|
638
|
-
daemonArgs.push(HTTP_FLAG);
|
|
639
|
-
if (httpPort !== undefined)
|
|
640
|
-
daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
|
|
641
|
-
if (httpToken !== undefined)
|
|
642
|
-
daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
|
|
643
|
-
}
|
|
644
|
-
spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
|
|
645
|
-
// Announce the daemon only once the PORT ACCEPTS. This line used to be written the instant the
|
|
646
|
-
// child was spawned, which on a Windows first bootstrap meant `reticle_mcp_daemon_started`
|
|
647
|
-
// followed about ten seconds later by `reticle_mcp_daemon_unavailable` and a first
|
|
648
|
-
// `reticle_sessions` that expired. A readiness signal that precedes readiness is worse than
|
|
649
|
-
// none: a client that believes it stops waiting for the thing that has not happened.
|
|
650
|
-
await waitForDaemon(port);
|
|
651
|
-
log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
|
|
652
|
-
};
|
|
653
|
-
// Start the proxy WHATEVER happened to the daemon.
|
|
654
|
-
//
|
|
655
|
-
// This used to exit(1) when `ensure` failed, which is the third way an MCP server disappears on a
|
|
656
|
-
// user: something else is holding the bridge port — a foreign daemon from another project, a
|
|
657
|
-
// half-dead process, a port a colleague's tool grabbed — the spawned daemon cannot bind, and the
|
|
658
|
-
// editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
|
|
659
|
-
// answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
|
|
660
|
-
// every client request. Present-and-complaining beats absent, because absent needs a human.
|
|
661
|
-
// `void`: the chain handles its own failure and the process must not wait on it — the proxy is
|
|
662
|
-
// started from `finally` either way.
|
|
663
|
-
void ensure()
|
|
664
|
-
.catch((err) => {
|
|
665
|
-
log('reticle_mcp_daemon_unavailable', {
|
|
666
|
-
error: err instanceof Error ? err.message : String(err),
|
|
667
|
-
note: 'serving anyway — the next tool call will try to start a daemon again',
|
|
668
|
-
});
|
|
669
|
-
})
|
|
670
|
-
.finally(() => {
|
|
671
|
-
// Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
|
|
672
|
-
//
|
|
673
|
-
// The `.catch` is load-bearing despite that. `startMcpProxy` rejects when its FIRST connect
|
|
674
|
-
// fails — which is precisely the case this block exists to tolerate, a daemon that is not
|
|
675
|
-
// there yet — and with nothing attached that reject became an unhandledRejection. Reported
|
|
676
|
-
// from a win32 user as a crash reading `connect ECONNREFUSED` with an EMPTY frame list,
|
|
677
|
-
// because the stack of a refused socket is entirely node internals and the privacy filter
|
|
678
|
-
// keeps only Reticle frames. So the one path we most want diagnosable arrived as an anonymous
|
|
679
|
-
// crash. The proxy itself is unaffected — it has already installed its stdin reader and goes
|
|
680
|
-
// on serving from cache, waking a daemon on the next request — which is exactly why this
|
|
681
|
-
// must be logged as the expected condition it is rather than reported as a defect.
|
|
682
|
-
void startMcpProxy(port, ensure).catch((err) => {
|
|
683
|
-
log('reticle_mcp_proxy_first_connect_failed', {
|
|
684
|
-
port,
|
|
685
|
-
error: err instanceof Error ? err.message : String(err),
|
|
686
|
-
note: 'serving from cache; the next client request will try to start a daemon',
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
652
|
function main() {
|
|
692
653
|
// Before anything reads process.env — notably the telemetry gate and the bridge's security
|
|
693
654
|
// options — fold in a project-local `.env`. Values already in the environment always win.
|
|
694
655
|
loadDotEnv(process.cwd());
|
|
656
|
+
// The licence key specifically is searched for HARDER than the rest of the environment, because
|
|
657
|
+
// the daemon is spawned without an explicit cwd and inherits the editor's. A key in the app's own
|
|
658
|
+
// `.env` — or in the repo root when the editor started inside the app — was never read, and the
|
|
659
|
+
// customer then produced events that said `missing`, which is indistinguishable from having no
|
|
660
|
+
// licence at all. Only the key is taken; see license-env.ts for why nothing else is.
|
|
661
|
+
const licenseKey = licenseKeyFromEnvFiles(process.cwd());
|
|
662
|
+
if (licenseKey !== undefined)
|
|
663
|
+
process.env[LICENSE_KEY_ENV] = licenseKey;
|
|
695
664
|
const argv = process.argv.slice(2);
|
|
696
665
|
// Every invocation passes through here — the single chokepoint for the "how often is it used / how
|
|
697
666
|
// many distinct machines + projects" metrics. Fire-and-forget: a metric must never delay or fail a run.
|
|
@@ -718,7 +687,19 @@ function main() {
|
|
|
718
687
|
if (projectPort !== undefined && isLikelyDevServerPort(projectPort)) {
|
|
719
688
|
process.stderr.write(`${devServerPortWarning(projectPort)}\n`);
|
|
720
689
|
}
|
|
721
|
-
|
|
690
|
+
// Registry BEFORE the default, and after both explicit sources.
|
|
691
|
+
//
|
|
692
|
+
// This is the line that ends the split brain. Build plugins have always asked the registry which
|
|
693
|
+
// daemon serves this project; the CLI asked a number and then attached to whoever owned it. So
|
|
694
|
+
// every project on the machine funnelled into one daemon whose identity was whichever project won
|
|
695
|
+
// the race, and one kill on one well-known port was a machine-wide outage.
|
|
696
|
+
//
|
|
697
|
+
// An explicit port still wins, because a person who typed one is answering this question
|
|
698
|
+
// themselves. Below that, our OWN daemon wherever it is listening. Only then the default, which is
|
|
699
|
+
// now a starting preference rather than an assumption.
|
|
700
|
+
const myProjectId = readProjectId(process.cwd());
|
|
701
|
+
const myDaemonPort = resolveDaemonForProject(myProjectId, reticleStateHome(), isAlive);
|
|
702
|
+
const defaultPort = envPort ?? projectPort ?? myDaemonPort ?? RETICLE_DEFAULT_PORT;
|
|
722
703
|
// Headed by default; hidden only where there is no display to be headed on. A run nobody can see
|
|
723
704
|
// is a run nobody trusts, and every "did it actually do anything?" cost a human round-trip.
|
|
724
705
|
const parsed = parseCliArgs(argv, defaultPort, process.env['CI'] !== undefined);
|
|
@@ -750,7 +731,7 @@ function main() {
|
|
|
750
731
|
void handleRestart(parsed.port, parsed.force);
|
|
751
732
|
break;
|
|
752
733
|
case 'status':
|
|
753
|
-
handleStatus(parsed.port);
|
|
734
|
+
void handleStatus(parsed.port);
|
|
754
735
|
break;
|
|
755
736
|
case 'license':
|
|
756
737
|
handleLicense();
|
|
@@ -792,7 +773,7 @@ function main() {
|
|
|
792
773
|
void handleHunt(parsed.dir);
|
|
793
774
|
break;
|
|
794
775
|
case 'gate':
|
|
795
|
-
void handleGate(parsed.files, parsed.since);
|
|
776
|
+
void handleGate(parsed.files, parsed.since, parsed.hook);
|
|
796
777
|
break;
|
|
797
778
|
case 'watch':
|
|
798
779
|
handleWatch();
|
|
@@ -804,7 +785,7 @@ function main() {
|
|
|
804
785
|
void handleRollback();
|
|
805
786
|
break;
|
|
806
787
|
case 'mcp':
|
|
807
|
-
handleMcp(parsed);
|
|
788
|
+
void handleMcp(parsed);
|
|
808
789
|
break;
|
|
809
790
|
case '_daemon':
|
|
810
791
|
handleDaemonInner(parsed);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { FileSystemPort } from '../project/fs-port.js';
|
|
2
2
|
import { type CloudConfig } from './cloud-sync.js';
|
|
3
3
|
/** `<reticleRoot>/cloud.json` — the project's cloud binding + sync policy (non-secret). */
|
|
4
|
-
|
|
4
|
+
/** Re-exported so the existing importers keep working; the string itself lives in core. */
|
|
5
|
+
export declare const CLOUD_LINK_FILE: "cloud.json";
|
|
5
6
|
/** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
|
|
6
7
|
export declare const CREDENTIALS_FILE = "credentials.json";
|
|
7
8
|
/** What the daemon mirrors to the cloud for a project. Each surface is independently toggleable. */
|
|
8
|
-
|
|
9
|
+
interface SyncPolicy {
|
|
9
10
|
/** Push verification-run artifacts to the dashboard Runs tab. */
|
|
10
11
|
runs: boolean;
|
|
11
12
|
/** Push per-flow project-memory outcomes (the regression history). */
|
|
@@ -27,9 +28,30 @@ export interface ProjectCloud {
|
|
|
27
28
|
/** The cloud project id this project is linked to (for display/logging); null when env-fallback. */
|
|
28
29
|
projectId: string | null;
|
|
29
30
|
}
|
|
31
|
+
/** Look up the API key for a cloud project id in the user credentials map. */
|
|
32
|
+
/**
|
|
33
|
+
* The API key this machine holds for one project ON ONE CLOUD.
|
|
34
|
+
*
|
|
35
|
+
* The store used to be keyed by cloud project id ALONE, which collides in the case that is not rare
|
|
36
|
+
* at all — it is the default. `reticle link` names every project "default", so a repo linked to a
|
|
37
|
+
* self-hosted install and a repo linked to the hosted service both claim the slot `default`, and
|
|
38
|
+
* whichever was linked last wins. Measured: two repos on one machine, and the production key was
|
|
39
|
+
* being sent to a localhost server, which answered 401.
|
|
40
|
+
*
|
|
41
|
+
* That is the same class of defect as sending a session token to a host that did not issue it, and
|
|
42
|
+
* it deserves the same answer: a credential is scoped to the cloud that minted it, and no credential
|
|
43
|
+
* at all is better than one belonging to somewhere else.
|
|
44
|
+
*
|
|
45
|
+
* Two shapes are accepted. `{ key, url }` is what is written now and is checked against the URL
|
|
46
|
+
* being dialled. A bare string is the legacy shape, has no URL to check, and is still honoured —
|
|
47
|
+
* refusing it would silently unlink every repo that predates this change, which is a worse outage
|
|
48
|
+
* than the collision. Re-linking upgrades a repo to the safe shape.
|
|
49
|
+
*/
|
|
50
|
+
export declare const credentialSlot: (url: string, projectId: string, orgId?: string) => string;
|
|
30
51
|
/**
|
|
31
52
|
* Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
|
|
32
53
|
* user credential store; if the project isn't linked (no cloud.json), falls back to the global env creds
|
|
33
54
|
* so the single-project / CI flow is unchanged. `homeDir` is injected (testable; `os.homedir()` at call).
|
|
34
55
|
*/
|
|
35
56
|
export declare function resolveProjectCloud(fs: FileSystemPort, reticleRoot: string, homeDir: string, env: NodeJS.ProcessEnv): Promise<ProjectCloud>;
|
|
57
|
+
export {};
|
|
@@ -13,7 +13,8 @@ import { join } from 'node:path';
|
|
|
13
13
|
import { ReticleDir } from '@reticlehq/core';
|
|
14
14
|
import { resolveCloudConfig } from './cloud-sync.js';
|
|
15
15
|
/** `<reticleRoot>/cloud.json` — the project's cloud binding + sync policy (non-secret). */
|
|
16
|
-
|
|
16
|
+
/** Re-exported so the existing importers keep working; the string itself lives in core. */
|
|
17
|
+
export const CLOUD_LINK_FILE = ReticleDir.CLOUD_LINK_FILE;
|
|
17
18
|
/** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
|
|
18
19
|
export const CREDENTIALS_FILE = 'credentials.json';
|
|
19
20
|
const DEFAULT_SYNC_POLICY = { runs: true, memory: true, flows: true };
|
|
@@ -43,6 +44,7 @@ function parseLink(raw) {
|
|
|
43
44
|
const sync = 'object' === typeof o.sync && o.sync !== null ? o.sync : {};
|
|
44
45
|
return {
|
|
45
46
|
projectId: o.projectId,
|
|
47
|
+
orgId: 'string' === typeof o.orgId && o.orgId.length > 0 ? o.orgId : undefined,
|
|
46
48
|
url: o.url,
|
|
47
49
|
sync: {
|
|
48
50
|
runs: asBool(sync.runs, DEFAULT_SYNC_POLICY.runs),
|
|
@@ -53,12 +55,69 @@ function parseLink(raw) {
|
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
/** Look up the API key for a cloud project id in the user credentials map. */
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The API key this machine holds for one project ON ONE CLOUD.
|
|
60
|
+
*
|
|
61
|
+
* The store used to be keyed by cloud project id ALONE, which collides in the case that is not rare
|
|
62
|
+
* at all — it is the default. `reticle link` names every project "default", so a repo linked to a
|
|
63
|
+
* self-hosted install and a repo linked to the hosted service both claim the slot `default`, and
|
|
64
|
+
* whichever was linked last wins. Measured: two repos on one machine, and the production key was
|
|
65
|
+
* being sent to a localhost server, which answered 401.
|
|
66
|
+
*
|
|
67
|
+
* That is the same class of defect as sending a session token to a host that did not issue it, and
|
|
68
|
+
* it deserves the same answer: a credential is scoped to the cloud that minted it, and no credential
|
|
69
|
+
* at all is better than one belonging to somewhere else.
|
|
70
|
+
*
|
|
71
|
+
* Two shapes are accepted. `{ key, url }` is what is written now and is checked against the URL
|
|
72
|
+
* being dialled. A bare string is the legacy shape, has no URL to check, and is still honoured —
|
|
73
|
+
* refusing it would silently unlink every repo that predates this change, which is a worse outage
|
|
74
|
+
* than the collision. Re-linking upgrades a repo to the safe shape.
|
|
75
|
+
*/
|
|
76
|
+
export const credentialSlot = (url, projectId, orgId) => orgId === undefined || 0 === orgId.length
|
|
77
|
+
? `${normalizeCloudUrl(url)}::${projectId}`
|
|
78
|
+
: `${normalizeCloudUrl(url)}::org::${orgId}::${projectId}`;
|
|
79
|
+
function credentialFor(raw, projectId, url, orgId) {
|
|
57
80
|
if (typeof raw !== 'object' || null === raw)
|
|
58
81
|
return null;
|
|
59
|
-
const
|
|
60
|
-
|
|
82
|
+
const store = raw;
|
|
83
|
+
/*
|
|
84
|
+
* The ORG slot first, because cloud + project is still not an identity.
|
|
85
|
+
*
|
|
86
|
+
* `link` names every project "default", so two ACCOUNTS on one cloud both claimed the slot
|
|
87
|
+
* `<url>::default` and the last link won. Measured on a laptop: a brand-new workspace ran
|
|
88
|
+
* `reticle login` and was handed a stored key belonging to a different organisation — valid, so
|
|
89
|
+
* every validation passed, and every run it pushed would have landed in a stranger's dashboard.
|
|
90
|
+
* Keying by cloud fixed two clouds colliding; only keying by ORG fixes two tenants colliding.
|
|
91
|
+
*/
|
|
92
|
+
const orgScoped = orgId === undefined ? undefined : store[credentialSlot(url, projectId, orgId)];
|
|
93
|
+
if ('string' === typeof orgScoped && orgScoped.length > 0)
|
|
94
|
+
return orgScoped;
|
|
95
|
+
/*
|
|
96
|
+
* Then the cloud+project slot, which is what every repo linked before this change still holds. It
|
|
97
|
+
* is ambiguous between tenants by construction, which is why it is consulted second and why the
|
|
98
|
+
* writer above stopped producing it as the only entry.
|
|
99
|
+
*/
|
|
100
|
+
const composite = store[credentialSlot(url, projectId)];
|
|
101
|
+
if ('string' === typeof composite && composite.length > 0)
|
|
102
|
+
return composite;
|
|
103
|
+
const entry = store[projectId];
|
|
104
|
+
if ('string' === typeof entry)
|
|
105
|
+
return entry.length > 0 ? entry : null;
|
|
106
|
+
if (typeof entry !== 'object' || null === entry)
|
|
107
|
+
return null;
|
|
108
|
+
const record = entry;
|
|
109
|
+
const key = record['key'];
|
|
110
|
+
const forUrl = record['url'];
|
|
111
|
+
if ('string' !== typeof key || 0 === key.length)
|
|
112
|
+
return null;
|
|
113
|
+
// A credential stamped with a DIFFERENT cloud is not this project's credential, however much the
|
|
114
|
+
// project ids match. Refuse rather than dial somewhere with somebody else's key.
|
|
115
|
+
if ('string' === typeof forUrl && normalizeCloudUrl(forUrl) !== normalizeCloudUrl(url))
|
|
116
|
+
return null;
|
|
117
|
+
return key;
|
|
61
118
|
}
|
|
119
|
+
/** Trailing slashes are not identity: `https://x/` and `https://x` are one cloud. */
|
|
120
|
+
const normalizeCloudUrl = (url) => url.replace(/\/+$/, '');
|
|
62
121
|
/**
|
|
63
122
|
* Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
|
|
64
123
|
* user credential store; if the project isn't linked (no cloud.json), falls back to the global env creds
|
|
@@ -80,7 +139,7 @@ export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
|
|
|
80
139
|
memory: link.sync.memory ?? DEFAULT_SYNC_POLICY.memory,
|
|
81
140
|
flows: link.sync.flows ?? DEFAULT_SYNC_POLICY.flows,
|
|
82
141
|
};
|
|
83
|
-
const key = credentialFor(await readJson(fs, join(homeDir, ReticleDir.ROOT, CREDENTIALS_FILE)), link.projectId);
|
|
142
|
+
const key = credentialFor(await readJson(fs, join(homeDir, ReticleDir.ROOT, CREDENTIALS_FILE)), link.projectId, link.url, link.orgId);
|
|
84
143
|
const config = key !== null ? { url: link.url.replace(/\/+$/, ''), apiKey: key } : null;
|
|
85
144
|
return { config, policy, verify: link.verify, projectId: link.projectId };
|
|
86
145
|
}
|