@reticlehq/server 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
package/dist/cli.js
CHANGED
|
@@ -2,30 +2,35 @@
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { realpathSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { stateDirProblem } from './daemon/state-dir.js';
|
|
6
|
+
import { reticleStateHome } from './daemon/daemon.js';
|
|
7
|
+
import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
|
|
6
8
|
import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
|
|
7
9
|
import { loadDotEnv } from './telemetry/dev-repo.js';
|
|
8
10
|
import { createNodeFileSystem } from './project/fs-port.js';
|
|
9
11
|
import { affectedSavedFlows } from './flows/flow-sources.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
+
import { availableUpdate } from './update/update-nudge.js';
|
|
13
|
+
import { handleUpdate, handleRollback } from './cli/cli-update-commands.js';
|
|
12
14
|
import { start, startDaemon } from './index.js';
|
|
13
|
-
import { isCloudCommand, runCloudCommand } from './cloud-cli.js';
|
|
14
|
-
import { SERVER_VERSION } from './server-version.js';
|
|
15
|
+
import { isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
|
|
16
|
+
import { SERVER_VERSION } from './version/server-version.js';
|
|
15
17
|
import { log } from './log.js';
|
|
16
|
-
import { readPid, isAlive,
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
18
|
+
import { readPid, isAlive, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, logPath, } from './daemon/daemon.js';
|
|
19
|
+
import { PortPresence, probePresence, presenceIsUsable, describePresence, } from './daemon/port-presence.js';
|
|
20
|
+
import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from './mcp/mcp-proxy.js';
|
|
21
|
+
import { installDaemonResilience, installProxyResilience, recordExitReason, DaemonExitReason, } from './daemon/daemon-resilience.js';
|
|
22
|
+
import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
|
|
23
|
+
import { DaemonHeartbeat, resolveHeartbeatMs } from './daemon/heartbeat.js';
|
|
24
|
+
import { everServedToolCall } from './daemon/daemon-usefulness.js';
|
|
25
|
+
import { fetchStatus, summarizeStatus, warnOnDaemonSkew, decideOpen, openInBrowser, } from './cli/cli-launch.js';
|
|
26
|
+
import { handleVerify } from './cli/cli-verify.js';
|
|
22
27
|
import { summarizeHunt } from './hunt/hunt-report.js';
|
|
23
28
|
import { runInit } from './init/run.js';
|
|
24
|
-
import { handleDoctor } from './cli-doctor.js';
|
|
29
|
+
import { handleDoctor } from './cli/cli-doctor.js';
|
|
25
30
|
import { buildNodeIo } from './init/node-io.js';
|
|
26
31
|
import { describeLicense } from './license/license.js';
|
|
27
|
-
import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId } from './cli-port.js';
|
|
28
|
-
import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, parseCliArgs, CLI_USAGE, } from './cli-parse.js';
|
|
32
|
+
import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId, } from './cli/cli-port.js';
|
|
33
|
+
import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, parseCliArgs, CLI_USAGE, } from './cli/cli-parse.js';
|
|
29
34
|
import { handleFeedback, handleIdentify, handleTelemetry } from './telemetry/feedback-cli.js';
|
|
30
35
|
import { installDaemonTelemetry } from './telemetry/daemon-telemetry.js';
|
|
31
36
|
import { reportCliRun } from './telemetry/cli-telemetry.js';
|
|
@@ -33,15 +38,50 @@ import { reportCliRun } from './telemetry/cli-telemetry.js';
|
|
|
33
38
|
export { parseCliArgs, CLI_USAGE };
|
|
34
39
|
function handleInit(parsed) {
|
|
35
40
|
const cwd = process.cwd();
|
|
36
|
-
const result = runInit({
|
|
41
|
+
const result = runInit({
|
|
42
|
+
cwd,
|
|
43
|
+
port: parsed.port,
|
|
44
|
+
mcp: parsed.mcp,
|
|
45
|
+
dryRun: parsed.dryRun,
|
|
46
|
+
install: parsed.install,
|
|
47
|
+
...(parsed.app === undefined ? {} : { app: parsed.app }),
|
|
48
|
+
}, buildNodeIo(cwd));
|
|
37
49
|
if (!result.ok)
|
|
38
50
|
process.exit(1);
|
|
39
51
|
}
|
|
40
52
|
function handleServe(parsed) {
|
|
41
|
-
|
|
53
|
+
void serveWithHonestExit(parsed);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Report the BIND, not the spawn.
|
|
57
|
+
*
|
|
58
|
+
* `serve` used to log `reticle_daemon_spawned` and exit 0 unconditionally, while the child died at
|
|
59
|
+
* `reticle_daemon_start_failed` with an EADDRINUSE nothing joined back to the parent. Three surfaces
|
|
60
|
+
* then disagreed about what was true and none of them named the port.
|
|
61
|
+
*
|
|
62
|
+
* Two changes, both about saying what is actually the case: refuse up front when the port is held by
|
|
63
|
+
* something that is not a Reticle daemon (a spawn there cannot succeed), and after spawning, wait
|
|
64
|
+
* for the daemon to actually answer before claiming it started.
|
|
65
|
+
*/
|
|
66
|
+
async function serveWithHonestExit(parsed) {
|
|
67
|
+
const presence = await probePresence(parsed.port, {
|
|
68
|
+
tcpOpen: probeDaemon,
|
|
69
|
+
status: fetchStatus,
|
|
70
|
+
});
|
|
71
|
+
if (presence === PortPresence.DAEMON) {
|
|
42
72
|
log('reticle_daemon_already_running', { port: parsed.port });
|
|
43
73
|
return;
|
|
44
74
|
}
|
|
75
|
+
if (presence === PortPresence.FOREIGN) {
|
|
76
|
+
log('reticle_daemon_start_refused', {
|
|
77
|
+
port: parsed.port,
|
|
78
|
+
presence,
|
|
79
|
+
reason: describePresence(presence, parsed.port),
|
|
80
|
+
});
|
|
81
|
+
process.stderr.write(`${describePresence(presence, parsed.port)}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
45
85
|
const scriptPath = process.argv[1];
|
|
46
86
|
if (scriptPath === undefined) {
|
|
47
87
|
log('reticle_serve_no_script', {});
|
|
@@ -62,11 +102,50 @@ function handleServe(parsed) {
|
|
|
62
102
|
daemonArgs.push(HTTP_TOKEN_FLAG, parsed.httpToken);
|
|
63
103
|
}
|
|
64
104
|
spawnDaemon(process.execPath, scriptPath, daemonArgs, parsed.port);
|
|
105
|
+
// The child binds asynchronously and, when it cannot, exits 1 long after this process would have
|
|
106
|
+
// reported success. Wait for it to ANSWER — `/status` responding is the only evidence a daemon
|
|
107
|
+
// exists that does not come from the pid file the child may never have earned.
|
|
108
|
+
const bound = await waitForPresence(parsed.port, PortPresence.DAEMON, SERVE_BIND_TIMEOUT_MS);
|
|
109
|
+
if (!bound) {
|
|
110
|
+
const settled = await probePresence(parsed.port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
111
|
+
log('reticle_daemon_start_failed_parent', {
|
|
112
|
+
port: parsed.port,
|
|
113
|
+
presence: settled,
|
|
114
|
+
reason: describePresence(settled, parsed.port),
|
|
115
|
+
log: logPath(parsed.port),
|
|
116
|
+
stateDir: stateDirProblem(reticleStateHome()) === undefined ? 'writable' : 'unwritable',
|
|
117
|
+
});
|
|
118
|
+
// Name the CAUSE when we can see it. An unwritable state directory is a first-run failure mode
|
|
119
|
+
// (locked-down home, read-only container mount, managed profile) that produced only "nothing is
|
|
120
|
+
// listening" — the symptom restated — and then pointed at a log INSIDE that directory, which
|
|
121
|
+
// cannot exist. Found by stress-testing 2.6.0 with `chmod 555 ~/.reticle`.
|
|
122
|
+
const dirProblem = stateDirProblem(reticleStateHome());
|
|
123
|
+
process.stderr.write(dirProblem === undefined
|
|
124
|
+
? `the daemon did not come up on :${String(parsed.port)} — ${describePresence(settled, parsed.port)}\n` +
|
|
125
|
+
`see ${logPath(parsed.port)}\n`
|
|
126
|
+
: `the daemon did not come up on :${String(parsed.port)}.\n${dirProblem}\n`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
65
130
|
log('reticle_daemon_spawned', { port: parsed.port, ...(parsed.http ? { http: true } : {}) });
|
|
66
131
|
}
|
|
132
|
+
/** How long `serve` waits for the child to bind. Generous: a cold daemon start is seconds. */
|
|
133
|
+
const SERVE_BIND_TIMEOUT_MS = 15_000;
|
|
134
|
+
const PRESENCE_POLL_MS = 150;
|
|
135
|
+
async function waitForPresence(port, want, timeoutMs) {
|
|
136
|
+
const deadline = Date.now() + timeoutMs;
|
|
137
|
+
for (;;) {
|
|
138
|
+
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
139
|
+
if (presence === want)
|
|
140
|
+
return true;
|
|
141
|
+
if (Date.now() >= deadline)
|
|
142
|
+
return false;
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, PRESENCE_POLL_MS));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
67
146
|
function handleStop(port, quiet) {
|
|
68
147
|
const pid = readPid(port);
|
|
69
|
-
if (
|
|
148
|
+
if (null === pid || !isAlive(pid)) {
|
|
70
149
|
removePid(port);
|
|
71
150
|
if (!quiet)
|
|
72
151
|
log('reticle_daemon_not_running', { port });
|
|
@@ -92,17 +171,31 @@ function handleStop(port, quiet) {
|
|
|
92
171
|
}
|
|
93
172
|
function handleStatus(port) {
|
|
94
173
|
const pid = readPid(port);
|
|
95
|
-
if (
|
|
96
|
-
|
|
174
|
+
if (null === pid || !isAlive(pid)) {
|
|
175
|
+
// `running: false` on its own has been reported about a port that was demonstrably occupied,
|
|
176
|
+
// because the pid file is not the port. Ask the port before answering.
|
|
177
|
+
void probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }).then((presence) => {
|
|
178
|
+
log('reticle_status', {
|
|
179
|
+
port,
|
|
180
|
+
running: presenceIsUsable(presence),
|
|
181
|
+
presence,
|
|
182
|
+
...(presence === PortPresence.FOREIGN ? { reason: describePresence(presence, port) } : {}),
|
|
183
|
+
});
|
|
184
|
+
});
|
|
97
185
|
return;
|
|
98
186
|
}
|
|
99
187
|
// The daemon is up — ask it for live sessions + health so status is at-a-glance, not just a pid.
|
|
100
188
|
void fetchStatus(port).then((payload) => {
|
|
189
|
+
// `status` is the second-most-run command and the one a HUMAN types. The update nudge otherwise
|
|
190
|
+
// only rides a tool result, which the majority of daemons never produce — so the people most in
|
|
191
|
+
// need of an upgrade were the ones with no path to hearing about it.
|
|
192
|
+
const update = availableUpdate();
|
|
193
|
+
const nudge = update === undefined ? {} : { updateAvailable: update };
|
|
101
194
|
if (payload === undefined) {
|
|
102
|
-
log('reticle_status', { port, running: true, pid });
|
|
195
|
+
log('reticle_status', { port, running: true, pid, ...nudge });
|
|
103
196
|
return;
|
|
104
197
|
}
|
|
105
|
-
log('reticle_status', { port, running: true, pid, ...summarizeStatus(payload) });
|
|
198
|
+
log('reticle_status', { port, running: true, pid, ...summarizeStatus(payload), ...nudge });
|
|
106
199
|
});
|
|
107
200
|
}
|
|
108
201
|
/** Print the running package version (resolved once in server-version.ts). */
|
|
@@ -172,43 +265,15 @@ async function handleHunt(dir) {
|
|
|
172
265
|
});
|
|
173
266
|
}
|
|
174
267
|
}
|
|
175
|
-
/** `reticle update` — install the latest server version and restart (moved off the MCP surface). */
|
|
176
|
-
async function handleUpdate() {
|
|
177
|
-
try {
|
|
178
|
-
const manifest = await checkForUpdate(SERVER_VERSION, () => Date.now());
|
|
179
|
-
if (!manifest.updateAvailable || manifest.latestVersion === undefined) {
|
|
180
|
-
log('reticle_update', {
|
|
181
|
-
ok: false,
|
|
182
|
-
message: 'already on the latest version',
|
|
183
|
-
version: SERVER_VERSION,
|
|
184
|
-
});
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
log('reticle_update', { ok: true, from: SERVER_VERSION, to: manifest.latestVersion });
|
|
188
|
-
await applyUpdate(manifest.latestVersion); // calls process.exit; Claude Code restarts
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
log('reticle_update_failed', { error: error instanceof Error ? error.message : String(error) });
|
|
192
|
-
process.exitCode = 1;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/** `reticle rollback` — restore the previous server version and restart. */
|
|
196
|
-
async function handleRollback() {
|
|
197
|
-
try {
|
|
198
|
-
await rollback(); // calls process.exit
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
log('reticle_rollback_failed', {
|
|
202
|
-
error: error instanceof Error ? error.message : String(error),
|
|
203
|
-
});
|
|
204
|
-
process.exitCode = 1;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
268
|
/** Ensure a daemon is reachable on `port` (probe the real port; spawn + wait only if nothing's there). */
|
|
208
269
|
function ensureDaemon(port) {
|
|
209
|
-
return probeDaemon(port).then((listening) => {
|
|
270
|
+
return probeDaemon(port).then(async (listening) => {
|
|
271
|
+
// Attaching to whatever already owns the port is the whole point of a daemon — but it means an
|
|
272
|
+
// upgrade does NOT take effect until that daemon dies, and nothing used to say so. Say it here,
|
|
273
|
+
// where both versions are in hand, and keep attaching: killing another agent's daemon on a
|
|
274
|
+
// version bump is worse than a loud warning.
|
|
210
275
|
if (listening)
|
|
211
|
-
return
|
|
276
|
+
return warnOnDaemonSkew(port);
|
|
212
277
|
const scriptPath = process.argv[1];
|
|
213
278
|
if (scriptPath === undefined)
|
|
214
279
|
throw new Error('cannot locate the reticle daemon script');
|
|
@@ -222,6 +287,24 @@ function ensureDaemon(port) {
|
|
|
222
287
|
* the daemon, then reuses the already-connected tab or opens a new browser at the url. Idempotent:
|
|
223
288
|
* re-running never piles up duplicate tabs.
|
|
224
289
|
*/
|
|
290
|
+
/** How long `reticle open` waits for the launched page to register before reporting on it. */
|
|
291
|
+
const OPEN_SESSION_WAIT_MS = 8000;
|
|
292
|
+
/**
|
|
293
|
+
* Poll until a NEW session shows up, or the wait runs out. Returns whether one did.
|
|
294
|
+
*
|
|
295
|
+
* Compares against the count taken before launching rather than against zero, so opening a second
|
|
296
|
+
* app while one is already connected is not reported as an instant success.
|
|
297
|
+
*/
|
|
298
|
+
async function waitForNewSession(port, before) {
|
|
299
|
+
const deadline = Date.now() + OPEN_SESSION_WAIT_MS;
|
|
300
|
+
while (Date.now() < deadline) {
|
|
301
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
302
|
+
const { sessions } = summarizeStatus(await fetchStatus(port));
|
|
303
|
+
if (sessions.length > before)
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
225
308
|
function handleOpen(requestedPort, url) {
|
|
226
309
|
probeDaemon(requestedPort)
|
|
227
310
|
.then((here) => (here ? requestedPort : (discoverDaemonPort() ?? requestedPort)))
|
|
@@ -229,7 +312,7 @@ function handleOpen(requestedPort, url) {
|
|
|
229
312
|
await ensureDaemon(port);
|
|
230
313
|
const { sessions } = summarizeStatus(await fetchStatus(port));
|
|
231
314
|
const decision = decideOpen(sessions, url);
|
|
232
|
-
if (
|
|
315
|
+
if ('need-url' === decision.action) {
|
|
233
316
|
log('reticle_open', {
|
|
234
317
|
port,
|
|
235
318
|
error: 'no app connected — pass a url: reticle open <url>. Desktop app (Electron/Tauri)? ' +
|
|
@@ -237,12 +320,53 @@ function handleOpen(requestedPort, url) {
|
|
|
237
320
|
});
|
|
238
321
|
return;
|
|
239
322
|
}
|
|
240
|
-
if (decision.action
|
|
323
|
+
if ('reuse' === decision.action) {
|
|
241
324
|
log('reticle_open', { port, reusing: decision.url });
|
|
242
325
|
return;
|
|
243
326
|
}
|
|
244
|
-
|
|
245
|
-
|
|
327
|
+
// A tab on the right origin, on the WRONG page. Kept (that is what stops a tab piling up per
|
|
328
|
+
// run) but never reported as done: `reusing` here read as "your url is open" for a page nobody
|
|
329
|
+
// had opened.
|
|
330
|
+
if ('left-as-is' === decision.action) {
|
|
331
|
+
log('reticle_open', {
|
|
332
|
+
port,
|
|
333
|
+
reusing: decision.url,
|
|
334
|
+
requested: decision.requested,
|
|
335
|
+
note: `a tab is connected on this origin but sitting on ${decision.url} — it was LEFT THERE, ` +
|
|
336
|
+
`not navigated to ${decision.requested}. Drive it with reticle_navigate, or open the url ` +
|
|
337
|
+
'in the browser yourself.',
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const launchError = await openInBrowser(decision.url);
|
|
342
|
+
if (launchError !== null) {
|
|
343
|
+
log('reticle_open', {
|
|
344
|
+
port,
|
|
345
|
+
error: `could not launch a browser: ${launchError}`,
|
|
346
|
+
recovery: 'Open the url yourself, or run `reticle doctor` — it checks the pieces this command ' +
|
|
347
|
+
'depends on.',
|
|
348
|
+
});
|
|
349
|
+
process.exit(1);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// Say whether a SESSION appeared, not merely that a launcher was invoked. `{"opened": url}`
|
|
353
|
+
// was printed unconditionally, so a run where nothing ever opened looked identical to a run
|
|
354
|
+
// where it did — reported from the field as twenty minutes lost to a phantom port problem.
|
|
355
|
+
const connected = await waitForNewSession(port, sessions.length);
|
|
356
|
+
log('reticle_open', {
|
|
357
|
+
port,
|
|
358
|
+
opened: decision.url,
|
|
359
|
+
connected,
|
|
360
|
+
...(connected
|
|
361
|
+
? {}
|
|
362
|
+
: {
|
|
363
|
+
note: 'the browser was launched but no Reticle session appeared. The app may still be ' +
|
|
364
|
+
'loading, or it is not wired to this bridge — run `reticle doctor`, and check the ' +
|
|
365
|
+
'app connects on port ' +
|
|
366
|
+
String(port) +
|
|
367
|
+
'.',
|
|
368
|
+
}),
|
|
369
|
+
});
|
|
246
370
|
})
|
|
247
371
|
.catch((err) => {
|
|
248
372
|
log('reticle_open', { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -284,12 +408,20 @@ function handleDaemonInner(parsed) {
|
|
|
284
408
|
removePid(parsed.port);
|
|
285
409
|
process.exit(1);
|
|
286
410
|
});
|
|
287
|
-
const shutdown = () => {
|
|
411
|
+
const shutdown = (reason) => {
|
|
412
|
+
// Recorded BEFORE the async chain: `installExitTrace` reads it when Node is on its way out,
|
|
413
|
+
// which is after everything below has run. Without it the exit line says `code: 0` and a
|
|
414
|
+
// reader cannot tell a tidy stop from the bridge disappearing — see #123.
|
|
415
|
+
recordExitReason(reason);
|
|
288
416
|
// Awaited before the close/exit chain: `process.exit(0)` kills an in-flight POST, and this is
|
|
289
417
|
// the one event carrying the whole session. A failed send resolves anyway (emit swallows its
|
|
290
418
|
// own errors), so this can delay the exit by at most the send timeout, never prevent it.
|
|
291
419
|
void daemonTelemetry
|
|
292
|
-
|
|
420
|
+
// The reason rides out on the session summary, which is the only event that fires at this
|
|
421
|
+
// exact moment. The proxy emits the matching `mcp_connection_lost` and cannot know it —
|
|
422
|
+
// it sees a socket end and nothing more, which is how a scheduled idle exit came to make up
|
|
423
|
+
// 299 of 321 "outages". See SessionSummary.exit.
|
|
424
|
+
.shutdown(reason)
|
|
293
425
|
.then(() => server.close())
|
|
294
426
|
.then(() => {
|
|
295
427
|
removePid(parsed.port);
|
|
@@ -302,19 +434,43 @@ function handleDaemonInner(parsed) {
|
|
|
302
434
|
process.exit(1);
|
|
303
435
|
});
|
|
304
436
|
};
|
|
305
|
-
|
|
306
|
-
|
|
437
|
+
// Wrapped rather than passed directly: a signal handler receives the signal name as its first
|
|
438
|
+
// argument, which would otherwise arrive as the reason.
|
|
439
|
+
process.on('SIGTERM', () => shutdown(DaemonExitReason.SIGNAL));
|
|
440
|
+
process.on('SIGINT', () => shutdown(DaemonExitReason.SIGNAL));
|
|
307
441
|
// Self-shut-down when idle so a detached daemon (and any headless Chromium it launched) never
|
|
308
442
|
// lingers on the user's machine after the editor closes. Reuses the same clean shutdown path.
|
|
443
|
+
const attachedGraceEnv = process.env[ReticleEnv.IDLE_ATTACHED];
|
|
309
444
|
const idleShutdown = new IdleShutdown({
|
|
310
445
|
graceMs: resolveIdleShutdownMs(process.env[ReticleEnv.IDLE_SHUTDOWN]),
|
|
446
|
+
checkIntervalMs: resolveIdleCheckMs(process.env[ReticleEnv.IDLE_CHECK]),
|
|
311
447
|
isIdle: server.isIdle ?? (() => false),
|
|
448
|
+
...(server.agentAttached === undefined ? {} : { agentAttached: server.agentAttached }),
|
|
449
|
+
// Only when the var is actually SET. `resolveIdleShutdownMs(undefined)` returns the 5-minute
|
|
450
|
+
// DEFAULT, so passing it unconditionally would override the derived attached grace with the
|
|
451
|
+
// very number this change exists to stop using — shipping the bug while looking fixed.
|
|
452
|
+
...(attachedGraceEnv === undefined || '' === attachedGraceEnv.trim()
|
|
453
|
+
? {}
|
|
454
|
+
: { attachedGraceMs: resolveIdleShutdownMs(attachedGraceEnv) }),
|
|
312
455
|
onShutdown: () => {
|
|
313
456
|
log('reticle_daemon_idle_exit', { port: parsed.port });
|
|
314
|
-
shutdown();
|
|
457
|
+
shutdown(DaemonExitReason.IDLE);
|
|
315
458
|
},
|
|
316
459
|
});
|
|
317
460
|
idleShutdown.start();
|
|
461
|
+
// Say so, regularly, so a GAP in this log is itself evidence. `installExitTrace` hooks
|
|
462
|
+
// `'exit'`, which a SIGKILL never fires — so a killed daemon left nothing behind, and the one
|
|
463
|
+
// that exited tidily logged `code: 0`. A reader could not tell "shut down cleanly" from "the
|
|
464
|
+
// bridge every app on this machine needs is gone", and a correct SvelteKit install was written
|
|
465
|
+
// up as an install failure on exactly that ambiguity. See daemon/heartbeat.ts.
|
|
466
|
+
new DaemonHeartbeat({
|
|
467
|
+
log,
|
|
468
|
+
intervalMs: resolveHeartbeatMs(process.env[ReticleEnv.HEARTBEAT]),
|
|
469
|
+
facts: () => ({
|
|
470
|
+
sessions: server.bridge.sessions.count(),
|
|
471
|
+
served: everServedToolCall(),
|
|
472
|
+
}),
|
|
473
|
+
}).start();
|
|
318
474
|
})
|
|
319
475
|
.catch((error) => {
|
|
320
476
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -333,43 +489,85 @@ function handleDaemonInner(parsed) {
|
|
|
333
489
|
*/
|
|
334
490
|
function handleMcp(opts) {
|
|
335
491
|
const { port, driveUrl, headless, http, httpPort, httpToken } = opts;
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
|
|
362
|
-
log('reticle_mcp_daemon_started', {
|
|
363
|
-
port,
|
|
364
|
-
...(driveUrl !== undefined ? { driveUrl } : {}),
|
|
365
|
-
});
|
|
492
|
+
// The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
|
|
493
|
+
// is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
|
|
494
|
+
// report it, keep serving: the reconnect and dormant paths already know how to rebuild the only
|
|
495
|
+
// state this process has. See installProxyResilience for why its rule inverts the daemon's.
|
|
496
|
+
// The proxy's crash handlers must write to the proxy's LOG FILE, not just stderr. The editor
|
|
497
|
+
// swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
|
|
498
|
+
// failure a user reports as "it disconnected" left nothing behind to read.
|
|
499
|
+
setProxyLogPort(port);
|
|
500
|
+
installProxyResilience(process, proxyLog);
|
|
501
|
+
/**
|
|
502
|
+
* Make sure a daemon is on the port, spawning one if not.
|
|
503
|
+
*
|
|
504
|
+
* Called on first start AND on every reconnect. The proxy used to only retry the socket, so a
|
|
505
|
+
* daemon that exited — crashed, `reticle stop`, or self-shut-down as idle — meant the retries hit
|
|
506
|
+
* a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
|
|
507
|
+
* disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
|
|
508
|
+
*/
|
|
509
|
+
const ensure = async () => {
|
|
510
|
+
if (await probeDaemon(port))
|
|
511
|
+
return;
|
|
512
|
+
const scriptPath = process.argv[1];
|
|
513
|
+
if (scriptPath === undefined) {
|
|
514
|
+
log('reticle_mcp_no_script', {});
|
|
515
|
+
process.exit(1);
|
|
366
516
|
}
|
|
367
|
-
|
|
368
|
-
|
|
517
|
+
const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
|
|
518
|
+
if (driveUrl !== undefined) {
|
|
519
|
+
daemonArgs.push(DRIVE_FLAG, driveUrl);
|
|
520
|
+
if (!headless)
|
|
521
|
+
daemonArgs.push(HEADED_FLAG);
|
|
522
|
+
}
|
|
523
|
+
// Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
|
|
524
|
+
if (http) {
|
|
525
|
+
daemonArgs.push(HTTP_FLAG);
|
|
526
|
+
if (httpPort !== undefined)
|
|
527
|
+
daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
|
|
528
|
+
if (httpToken !== undefined)
|
|
529
|
+
daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
|
|
530
|
+
}
|
|
531
|
+
spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
|
|
532
|
+
log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
|
|
533
|
+
await waitForDaemon(port);
|
|
534
|
+
};
|
|
535
|
+
// Start the proxy WHATEVER happened to the daemon.
|
|
536
|
+
//
|
|
537
|
+
// This used to exit(1) when `ensure` failed, which is the third way an MCP server disappears on a
|
|
538
|
+
// user: something else is holding the bridge port — a foreign daemon from another project, a
|
|
539
|
+
// half-dead process, a port a colleague's tool grabbed — the spawned daemon cannot bind, and the
|
|
540
|
+
// editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
|
|
541
|
+
// answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
|
|
542
|
+
// every client request. Present-and-complaining beats absent, because absent needs a human.
|
|
543
|
+
// `void`: the chain handles its own failure and the process must not wait on it — the proxy is
|
|
544
|
+
// started from `finally` either way.
|
|
545
|
+
void ensure()
|
|
369
546
|
.catch((err) => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
547
|
+
log('reticle_mcp_daemon_unavailable', {
|
|
548
|
+
error: err instanceof Error ? err.message : String(err),
|
|
549
|
+
note: 'serving anyway — the next tool call will try to start a daemon again',
|
|
550
|
+
});
|
|
551
|
+
})
|
|
552
|
+
.finally(() => {
|
|
553
|
+
// Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
|
|
554
|
+
//
|
|
555
|
+
// The `.catch` is load-bearing despite that. `startMcpProxy` rejects when its FIRST connect
|
|
556
|
+
// fails — which is precisely the case this block exists to tolerate, a daemon that is not
|
|
557
|
+
// there yet — and with nothing attached that reject became an unhandledRejection. Reported
|
|
558
|
+
// from a win32 user as a crash reading `connect ECONNREFUSED` with an EMPTY frame list,
|
|
559
|
+
// because the stack of a refused socket is entirely node internals and the privacy filter
|
|
560
|
+
// keeps only Reticle frames. So the one path we most want diagnosable arrived as an anonymous
|
|
561
|
+
// crash. The proxy itself is unaffected — it has already installed its stdin reader and goes
|
|
562
|
+
// on serving from cache, waking a daemon on the next request — which is exactly why this
|
|
563
|
+
// must be logged as the expected condition it is rather than reported as a defect.
|
|
564
|
+
void startMcpProxy(port, ensure).catch((err) => {
|
|
565
|
+
log('reticle_mcp_proxy_first_connect_failed', {
|
|
566
|
+
port,
|
|
567
|
+
error: err instanceof Error ? err.message : String(err),
|
|
568
|
+
note: 'serving from cache; the next client request will try to start a daemon',
|
|
569
|
+
});
|
|
570
|
+
});
|
|
373
571
|
});
|
|
374
572
|
}
|
|
375
573
|
function handleLegacyDrive(parsed) {
|
|
@@ -424,7 +622,11 @@ function main() {
|
|
|
424
622
|
const parsed = parseCliArgs(argv, defaultPort, process.env['CI'] !== undefined);
|
|
425
623
|
switch (parsed.kind) {
|
|
426
624
|
case 'error':
|
|
625
|
+
// Two audiences, two channels. The structured line is for logs and scripts; the plain text is
|
|
626
|
+
// for the person who just typed the command. Emitting only the JSON meant a single typo came
|
|
627
|
+
// back as an escaped one-line wall of the entire help text — see the ParseError builders.
|
|
427
628
|
log('reticle_usage_error', { message: parsed.message });
|
|
629
|
+
process.stderr.write(parsed.message === CLI_USAGE ? `${CLI_USAGE}\n` : `${parsed.message}\n\n${CLI_USAGE}\n`);
|
|
428
630
|
process.exit(1);
|
|
429
631
|
break;
|
|
430
632
|
case 'init':
|
|
@@ -446,7 +648,7 @@ function main() {
|
|
|
446
648
|
handleTelemetry(parsed.action);
|
|
447
649
|
break;
|
|
448
650
|
case 'feedback':
|
|
449
|
-
void handleFeedback(parsed.text, parsed.rating, parsed.bug);
|
|
651
|
+
void handleFeedback(parsed.text, parsed.rating, parsed.bug, parsed.feedbackKind, parsed.agent);
|
|
450
652
|
break;
|
|
451
653
|
case 'identify':
|
|
452
654
|
void handleIdentify(parsed);
|
|
@@ -13,7 +13,6 @@ export interface SyncPolicy {
|
|
|
13
13
|
/** Sync saved flow files (the shared regression suite). */
|
|
14
14
|
flows: boolean;
|
|
15
15
|
}
|
|
16
|
-
export declare const DEFAULT_SYNC_POLICY: SyncPolicy;
|
|
17
16
|
/** Where a verification actually executes. Reserved for the hosted-runner path; default local. */
|
|
18
17
|
export declare const VerifyMode: {
|
|
19
18
|
readonly LOCAL: "local";
|
|
@@ -16,10 +16,10 @@ import { resolveCloudConfig } from './cloud-sync.js';
|
|
|
16
16
|
export const CLOUD_LINK_FILE = 'cloud.json';
|
|
17
17
|
/** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
|
|
18
18
|
export const CREDENTIALS_FILE = 'credentials.json';
|
|
19
|
-
|
|
19
|
+
const DEFAULT_SYNC_POLICY = { runs: true, memory: true, flows: true };
|
|
20
20
|
/** Where a verification actually executes. Reserved for the hosted-runner path; default local. */
|
|
21
21
|
export const VerifyMode = { LOCAL: 'local', SERVER: 'server' };
|
|
22
|
-
const asBool = (v, fallback) => (typeof v
|
|
22
|
+
const asBool = (v, fallback) => ('boolean' === typeof v ? v : fallback);
|
|
23
23
|
/** Read + JSON-parse a file, or null on any problem (missing/malformed) — never throws. */
|
|
24
24
|
async function readJson(fs, path) {
|
|
25
25
|
try {
|
|
@@ -33,14 +33,14 @@ async function readJson(fs, path) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Validate the shape of `.reticle/cloud.json`. Requires projectId + url; policy/verify default in. */
|
|
35
35
|
function parseLink(raw) {
|
|
36
|
-
if (typeof raw !== 'object' ||
|
|
36
|
+
if (typeof raw !== 'object' || null === raw)
|
|
37
37
|
return null;
|
|
38
38
|
const o = raw;
|
|
39
|
-
if (typeof o.projectId !== 'string' || o.projectId.length
|
|
39
|
+
if (typeof o.projectId !== 'string' || 0 === o.projectId.length)
|
|
40
40
|
return null;
|
|
41
|
-
if (typeof o.url !== 'string' || o.url.length
|
|
41
|
+
if (typeof o.url !== 'string' || 0 === o.url.length)
|
|
42
42
|
return null;
|
|
43
|
-
const sync = typeof o.sync
|
|
43
|
+
const sync = 'object' === typeof o.sync && o.sync !== null ? o.sync : {};
|
|
44
44
|
return {
|
|
45
45
|
projectId: o.projectId,
|
|
46
46
|
url: o.url,
|
|
@@ -54,10 +54,10 @@ function parseLink(raw) {
|
|
|
54
54
|
}
|
|
55
55
|
/** Look up the API key for a cloud project id in the user credentials map. */
|
|
56
56
|
function credentialFor(raw, projectId) {
|
|
57
|
-
if (typeof raw !== 'object' ||
|
|
57
|
+
if (typeof raw !== 'object' || null === raw)
|
|
58
58
|
return null;
|
|
59
59
|
const key = raw[projectId];
|
|
60
|
-
return typeof key
|
|
60
|
+
return 'string' === typeof key && key.length > 0 ? key : null;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
|
|
@@ -66,7 +66,7 @@ function credentialFor(raw, projectId) {
|
|
|
66
66
|
*/
|
|
67
67
|
export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
|
|
68
68
|
const link = parseLink(await readJson(fs, join(reticleRoot, CLOUD_LINK_FILE)));
|
|
69
|
-
if (
|
|
69
|
+
if (null === link) {
|
|
70
70
|
// No per-project link → the env vars are the whole story (legacy single-project / CI behaviour).
|
|
71
71
|
return {
|
|
72
72
|
config: resolveCloudConfig(env),
|
|
@@ -15,12 +15,6 @@ export declare const CloudEnv: {
|
|
|
15
15
|
readonly URL: "RETICLE_CLOUD_URL";
|
|
16
16
|
readonly KEY: "RETICLE_CLOUD_KEY";
|
|
17
17
|
};
|
|
18
|
-
/** Paths the OSS server pushes to (match the cloud app's contract). */
|
|
19
|
-
export declare const CLOUD_FLOWS_PATH = "/v1/flows";
|
|
20
|
-
export declare const CLOUD_RUNS_PATH = "/v1/runs";
|
|
21
|
-
export declare const CLOUD_PROJECT_RUNS_PATH = "/v1/project/runs";
|
|
22
|
-
export declare const CLOUD_PROJECT_REGRESSION_PATH = "/v1/project/regression";
|
|
23
|
-
export declare const CLOUD_VERIFICATIONS_PATH = "/v1/verifications";
|
|
24
18
|
export interface CloudConfig {
|
|
25
19
|
url: string;
|
|
26
20
|
apiKey: string;
|
|
@@ -33,7 +27,7 @@ export declare const SyncOutcome: {
|
|
|
33
27
|
readonly FAILED: "failed";
|
|
34
28
|
};
|
|
35
29
|
export type SyncOutcome = (typeof SyncOutcome)[keyof typeof SyncOutcome];
|
|
36
|
-
|
|
30
|
+
interface SyncResult {
|
|
37
31
|
outcome: SyncOutcome;
|
|
38
32
|
status?: number;
|
|
39
33
|
error?: string;
|