@reticlehq/server 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The self-update command pair — `reticle update` and `reticle rollback`.
|
|
3
|
+
*
|
|
4
|
+
* Split out of cli.ts, which sits at the 600-line cap: these two are one cohesive unit (swap the
|
|
5
|
+
* installed version, restart) and the file they came from is the CLI's dispatch table, which grows
|
|
6
|
+
* for entirely different reasons.
|
|
7
|
+
*/
|
|
8
|
+
import { checkForUpdate } from '../update/update-checker.js';
|
|
9
|
+
import { updateTarget } from '../update/update-nudge.js';
|
|
10
|
+
import { applyUpdate, rollback } from '../update/updater.js';
|
|
11
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
12
|
+
import { log } from '../log.js';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { reticleDepsOf, sdkSyncCommand } from '../update/sdk-sync.js';
|
|
16
|
+
import { detectPackageManager } from '../init/detect.js';
|
|
17
|
+
import { buildNodeIo } from '../init/node-io.js';
|
|
18
|
+
/**
|
|
19
|
+
* Bring the SDK in the CURRENT project to the version being installed.
|
|
20
|
+
*
|
|
21
|
+
* Best-effort and non-fatal on purpose: `reticle update` is run from wherever the human happens to
|
|
22
|
+
* be, which is often not an app at all. A directory with no manifest, or one that has none of our
|
|
23
|
+
* packages, simply has nothing to sync — that is a normal outcome, not a failure, and it must never
|
|
24
|
+
* stop the CLI half from happening.
|
|
25
|
+
*/
|
|
26
|
+
function syncProjectSdk(target) {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
let manifest;
|
|
29
|
+
try {
|
|
30
|
+
manifest = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return; // not an app directory
|
|
34
|
+
}
|
|
35
|
+
const packages = reticleDepsOf(manifest);
|
|
36
|
+
const io = buildNodeIo(cwd);
|
|
37
|
+
const pm = detectPackageManager(new Set(io.rootFiles()), new Set(io.listDirs('node_modules')));
|
|
38
|
+
const cmd = sdkSyncCommand(pm, packages, target);
|
|
39
|
+
if (null === cmd) {
|
|
40
|
+
log('reticle_update_sdk', { synced: false, reason: 'no @reticlehq packages in this project' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const ok = io.exec(cmd.command, cmd.args);
|
|
44
|
+
log('reticle_update_sdk', { synced: ok, packages, to: target, packageManager: pm });
|
|
45
|
+
if (!ok) {
|
|
46
|
+
log('reticle_update_sdk_failed', {
|
|
47
|
+
hint: `run \`${cmd.command} ${cmd.args.join(' ')}\` by hand, then restart the dev server`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** `reticle update` — install the latest server version, sync the app's SDK, and restart. */
|
|
52
|
+
export async function handleUpdate() {
|
|
53
|
+
try {
|
|
54
|
+
const manifest = await checkForUpdate(SERVER_VERSION, () => Date.now());
|
|
55
|
+
// Direction, not inequality: the registry being DIFFERENT is not the registry being newer, and
|
|
56
|
+
// the old gate happily installed a downgrade — reported by a user. See updateTarget.
|
|
57
|
+
const target = updateTarget(manifest);
|
|
58
|
+
if (target === undefined) {
|
|
59
|
+
log('reticle_update', {
|
|
60
|
+
ok: false,
|
|
61
|
+
message: 'already on the latest version',
|
|
62
|
+
version: SERVER_VERSION,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
log('reticle_update', { ok: true, from: SERVER_VERSION, to: target });
|
|
67
|
+
// The app's SDK FIRST, then the CLI. `reticle update` used to swap only the CLI, so the command
|
|
68
|
+
// whose job is keeping an install current was itself a way to produce a version-skewed pair —
|
|
69
|
+
// and the skew message told people to run it to fix an outdated SDK, which it could not do.
|
|
70
|
+
//
|
|
71
|
+
// Before the CLI swap because `applyUpdate` never returns (it execs and exits). If this half
|
|
72
|
+
// fails the daemon is still the older one, which is the direction the HELLO check names clearly
|
|
73
|
+
// and a re-run fixes; the reverse would leave a new daemon talking to an old page.
|
|
74
|
+
syncProjectSdk(target);
|
|
75
|
+
await applyUpdate(target); // calls process.exit; Claude Code restarts
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
log('reticle_update_failed', { error: error instanceof Error ? error.message : String(error) });
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** `reticle rollback` — restore the previous server version and restart. */
|
|
83
|
+
export async function handleRollback() {
|
|
84
|
+
try {
|
|
85
|
+
await rollback(); // calls process.exit
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
log('reticle_rollback_failed', {
|
|
89
|
+
error: error instanceof Error ? error.message : String(error),
|
|
90
|
+
});
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
import { join, basename } from 'node:path';
|
|
13
13
|
import { randomUUID } from 'node:crypto';
|
|
14
14
|
import { RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, } from '@reticlehq/core';
|
|
15
|
-
import { start } from '
|
|
16
|
-
import { resolveCloudConfig, syncRunToCloud, SyncOutcome } from '
|
|
17
|
-
import { ReticleRunner } from '
|
|
18
|
-
import { createRunnerPort } from '
|
|
19
|
-
import { RunStore } from '
|
|
20
|
-
import { renderRunReport } from '
|
|
21
|
-
import { BaselineStore } from '
|
|
22
|
-
import { RecordingStore } from '
|
|
23
|
-
import { FlowStore } from '
|
|
24
|
-
import { ProjectStore } from '
|
|
25
|
-
import { AnnotationStore } from '
|
|
26
|
-
import { createNodeFileSystem } from '
|
|
15
|
+
import { start } from '../index.js';
|
|
16
|
+
import { resolveCloudConfig, syncRunToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
17
|
+
import { ReticleRunner } from '../runs/reticle-runner.js';
|
|
18
|
+
import { createRunnerPort } from '../runs/runner-port.js';
|
|
19
|
+
import { RunStore } from '../runs/run-store.js';
|
|
20
|
+
import { renderRunReport } from '../runs/render-report.js';
|
|
21
|
+
import { BaselineStore } from '../project/baselines.js';
|
|
22
|
+
import { RecordingStore } from '../flows/recordings.js';
|
|
23
|
+
import { FlowStore } from '../flows/flows.js';
|
|
24
|
+
import { ProjectStore } from '../project/project-store.js';
|
|
25
|
+
import { AnnotationStore } from '../flows/annotation-store.js';
|
|
26
|
+
import { createNodeFileSystem } from '../project/fs-port.js';
|
|
27
27
|
const EXIT_PASS = 0;
|
|
28
28
|
const EXIT_FAIL = 1;
|
|
29
29
|
const DEFAULT_SESSION_TIMEOUT_MS = 15_000;
|
|
@@ -63,7 +63,7 @@ export async function runVerify(args, ports) {
|
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
const names = await conn.listFlows();
|
|
66
|
-
if (names.length
|
|
66
|
+
if (0 === names.length) {
|
|
67
67
|
ports.fail(MSG_NO_FLOWS);
|
|
68
68
|
ports.exit(EXIT_FAIL);
|
|
69
69
|
return;
|
|
@@ -89,7 +89,7 @@ export async function runVerify(args, ports) {
|
|
|
89
89
|
*/
|
|
90
90
|
async function pushRunToCloud(run, ports) {
|
|
91
91
|
const config = resolveCloudConfig(process.env);
|
|
92
|
-
if (
|
|
92
|
+
if (null === config)
|
|
93
93
|
return;
|
|
94
94
|
const result = await syncRunToCloud(run, config, (url, init) => fetch(url, init));
|
|
95
95
|
if (result.outcome === SyncOutcome.SYNCED) {
|
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* is `<repo>/.reticle/cloud.json`. Auth for a command = `RETICLE_CLOUD_KEY` env (agent) OR the login token.
|
|
8
8
|
*/
|
|
9
9
|
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
|
10
|
+
import { NodePlatform } from '../platform.js';
|
|
10
11
|
import { spawn } from 'node:child_process';
|
|
11
12
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
12
13
|
import { homedir } from 'node:os';
|
|
13
14
|
import { join } from 'node:path';
|
|
14
15
|
import { z } from 'zod';
|
|
15
|
-
import { createNodeFileSystem } from '
|
|
16
|
-
import { RunStore } from '
|
|
17
|
-
import { resolveProjectCloud } from '
|
|
18
|
-
import { syncRunToCloud, SyncOutcome } from '
|
|
16
|
+
import { createNodeFileSystem } from '../project/fs-port.js';
|
|
17
|
+
import { RunStore } from '../runs/run-store.js';
|
|
18
|
+
import { resolveProjectCloud } from '../cloud/cloud-config.js';
|
|
19
|
+
import { syncRunToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
19
20
|
const DEFAULT_URL = 'http://localhost:8890';
|
|
20
21
|
const RETICLE_DIR = '.reticle';
|
|
21
22
|
const SESSION_FILE = 'session.json';
|
|
@@ -162,8 +163,12 @@ const DevicePollSchema = z.object({
|
|
|
162
163
|
});
|
|
163
164
|
/** Best-effort open the approval page in the default browser; the printed URL is the headless fallback. */
|
|
164
165
|
const openBrowser = (target) => {
|
|
165
|
-
const cmd =
|
|
166
|
-
|
|
166
|
+
const cmd = NodePlatform.MACOS === process.platform
|
|
167
|
+
? 'open'
|
|
168
|
+
: NodePlatform.WINDOWS === process.platform
|
|
169
|
+
? 'cmd'
|
|
170
|
+
: 'xdg-open';
|
|
171
|
+
const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
|
|
167
172
|
try {
|
|
168
173
|
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
169
174
|
child.on('error', () => undefined);
|
|
@@ -193,18 +198,18 @@ const cmdLoginDevice = async () => {
|
|
|
193
198
|
for (;;) {
|
|
194
199
|
await sleep(intervalMs);
|
|
195
200
|
const poll = DevicePollSchema.parse(await api('POST', `${url}/v1/auth/device/token`, null, { deviceCode: started.deviceCode }));
|
|
196
|
-
if (poll.status
|
|
201
|
+
if ('approved' === poll.status && poll.token !== undefined && poll.org !== undefined) {
|
|
197
202
|
await writeSession(url, poll.token, poll.org.name);
|
|
198
203
|
return 0;
|
|
199
204
|
}
|
|
200
|
-
if (poll.status
|
|
205
|
+
if ('pending' === poll.status) {
|
|
201
206
|
if (Date.now() > started.expiresAt) {
|
|
202
207
|
err('device login expired — run `reticle login` again');
|
|
203
208
|
return 1;
|
|
204
209
|
}
|
|
205
210
|
continue;
|
|
206
211
|
}
|
|
207
|
-
err(poll.status
|
|
212
|
+
err('denied' === poll.status
|
|
208
213
|
? 'device login was denied in the browser'
|
|
209
214
|
: 'device login expired — run `reticle login` again');
|
|
210
215
|
return 1;
|
|
@@ -264,7 +269,7 @@ const cmdWhoami = async () => {
|
|
|
264
269
|
verify: cloud.verify,
|
|
265
270
|
},
|
|
266
271
|
});
|
|
267
|
-
if (cloud.config
|
|
272
|
+
if (null === cloud.config)
|
|
268
273
|
hint('this repo is not attached — run `reticle link`');
|
|
269
274
|
return 0;
|
|
270
275
|
};
|
|
@@ -273,18 +278,18 @@ const cmdProject = async (argv) => {
|
|
|
273
278
|
const session = await readSession();
|
|
274
279
|
const token = bearer(session);
|
|
275
280
|
const url = baseUrl(session);
|
|
276
|
-
if (
|
|
281
|
+
if (null === token) {
|
|
277
282
|
err('run `reticle login` first, or set RETICLE_CLOUD_KEY');
|
|
278
283
|
return 2;
|
|
279
284
|
}
|
|
280
285
|
const sub = argv[0];
|
|
281
|
-
if (
|
|
286
|
+
if ('ls' === sub) {
|
|
282
287
|
emit(await api('GET', `${url}/v1/projects`, token));
|
|
283
288
|
return 0;
|
|
284
289
|
}
|
|
285
|
-
if (
|
|
290
|
+
if ('create' === sub) {
|
|
286
291
|
const name = argv.slice(1).join(' ').trim();
|
|
287
|
-
if (name.length
|
|
292
|
+
if (0 === name.length) {
|
|
288
293
|
err('usage: reticle project create <name>');
|
|
289
294
|
return 2;
|
|
290
295
|
}
|
|
@@ -293,17 +298,17 @@ const cmdProject = async (argv) => {
|
|
|
293
298
|
hint(`next: \`reticle link --project ${created.projectId}\` to bind this repo`);
|
|
294
299
|
return 0;
|
|
295
300
|
}
|
|
296
|
-
if (
|
|
301
|
+
if ('rename' === sub) {
|
|
297
302
|
const id = argv[1];
|
|
298
303
|
const name = argv.slice(2).join(' ').trim();
|
|
299
|
-
if (id === undefined || name.length
|
|
304
|
+
if (id === undefined || 0 === name.length) {
|
|
300
305
|
err('usage: reticle project rename <projectId> <new name>');
|
|
301
306
|
return 2;
|
|
302
307
|
}
|
|
303
308
|
emit(await api('PATCH', `${url}/v1/projects/${encodeURIComponent(id)}`, token, { name }));
|
|
304
309
|
return 0;
|
|
305
310
|
}
|
|
306
|
-
if (
|
|
311
|
+
if ('rm' === sub || 'delete' === sub) {
|
|
307
312
|
const id = argv[1];
|
|
308
313
|
if (id === undefined) {
|
|
309
314
|
err('usage: reticle project rm <projectId>');
|
|
@@ -357,7 +362,7 @@ const cmdLink = async (argv) => {
|
|
|
357
362
|
await mkdir(reticleDir, { recursive: true });
|
|
358
363
|
const linkPath = join(reticleDir, CLOUD_LINK_FILE);
|
|
359
364
|
const prev = await readJson(linkPath);
|
|
360
|
-
const prevObj = typeof prev
|
|
365
|
+
const prevObj = 'object' === typeof prev && prev !== null ? prev : {};
|
|
361
366
|
const cloudJson = {
|
|
362
367
|
projectId,
|
|
363
368
|
projectName,
|
|
@@ -369,7 +374,7 @@ const cmdLink = async (argv) => {
|
|
|
369
374
|
await mkdir(home(), { recursive: true });
|
|
370
375
|
const credPath = join(home(), CREDENTIALS_FILE);
|
|
371
376
|
const creds = (await readJson(credPath)) ?? {};
|
|
372
|
-
const credObj = typeof creds
|
|
377
|
+
const credObj = 'object' === typeof creds && creds !== null ? creds : {};
|
|
373
378
|
credObj[projectId] = key;
|
|
374
379
|
await writeFile(credPath, `${JSON.stringify(credObj, null, 2)}\n`);
|
|
375
380
|
emit({ linked: projectName, projectId, cloudJson: linkPath, credentials: credPath });
|
|
@@ -381,15 +386,15 @@ const cmdConfig = async (argv) => {
|
|
|
381
386
|
const f = flags(argv);
|
|
382
387
|
const linkPath = join(process.cwd(), RETICLE_DIR, CLOUD_LINK_FILE);
|
|
383
388
|
const raw = await readJson(linkPath);
|
|
384
|
-
if (
|
|
389
|
+
if (null === raw || typeof raw !== 'object') {
|
|
385
390
|
err('no .reticle/cloud.json here — run `reticle link` first');
|
|
386
391
|
return 2;
|
|
387
392
|
}
|
|
388
393
|
const cfg = raw;
|
|
389
|
-
const sync = typeof cfg['sync']
|
|
394
|
+
const sync = 'object' === typeof cfg['sync'] && cfg['sync'] !== null
|
|
390
395
|
? cfg['sync']
|
|
391
396
|
: { runs: true, memory: true, flows: true };
|
|
392
|
-
const onoff = (v) =>
|
|
397
|
+
const onoff = (v) => 'on' === v ? true : 'off' === v ? false : undefined;
|
|
393
398
|
for (const k of ['runs', 'memory', 'flows']) {
|
|
394
399
|
if (f[k] === undefined)
|
|
395
400
|
continue;
|
|
@@ -417,7 +422,7 @@ const cmdPush = async () => {
|
|
|
417
422
|
const fs = createNodeFileSystem();
|
|
418
423
|
const reticleRoot = join(process.cwd(), RETICLE_DIR);
|
|
419
424
|
const cloud = await resolveProjectCloud(fs, reticleRoot, homedir(), process.env);
|
|
420
|
-
if (cloud.config
|
|
425
|
+
if (null === cloud.config) {
|
|
421
426
|
err('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
|
|
422
427
|
return 1;
|
|
423
428
|
}
|
|
@@ -448,7 +453,7 @@ const cmdPush = async () => {
|
|
|
448
453
|
const repoCloud = async () => {
|
|
449
454
|
const fs = createNodeFileSystem();
|
|
450
455
|
const cloud = await resolveProjectCloud(fs, join(process.cwd(), RETICLE_DIR), homedir(), process.env);
|
|
451
|
-
if (cloud.config
|
|
456
|
+
if (null === cloud.config)
|
|
452
457
|
throw new Error('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
|
|
453
458
|
return cloud.config;
|
|
454
459
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `doctor` says about the daemon it found — pure, so it can be tested without a socket.
|
|
3
|
+
*
|
|
4
|
+
* Doctor already stopped lying about the PORT: `probePresence` separates "a daemon is here" from "a
|
|
5
|
+
* stranger holds it" from "nothing is listening", which was the load-bearing half of the report. It
|
|
6
|
+
* still did not say WHICH daemon, though the `/status` payload it already fetches carries `version`
|
|
7
|
+
* and `contract` and it discarded both.
|
|
8
|
+
*
|
|
9
|
+
* That is the gap worth closing, because skew is invisible everywhere else: a CLI and a daemon on
|
|
10
|
+
* different contracts connect anyway and then disagree about behaviour, which reaches the agent as a
|
|
11
|
+
* bare `-32000` naming no version at all. Doctor is the command a human runs at exactly that moment.
|
|
12
|
+
*
|
|
13
|
+
* Same split as `classifyPort`/`probePresence`: the rule lives here and is tested; the fetching stays
|
|
14
|
+
* at the call site.
|
|
15
|
+
*/
|
|
16
|
+
/** The fields of `/status` this line cares about. Both optional — an old daemon reports neither. */
|
|
17
|
+
export interface DaemonIdentity {
|
|
18
|
+
version?: string;
|
|
19
|
+
contract?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DaemonLine {
|
|
22
|
+
/** The `daemon ✓ …` line itself. */
|
|
23
|
+
text: string;
|
|
24
|
+
/** A skew sentence to print underneath, when this CLI and that daemon disagree. */
|
|
25
|
+
skew?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the daemon line for a port that answered `/status`.
|
|
29
|
+
*
|
|
30
|
+
* `pid` comes from the pid file and is genuinely optional — a daemon started by another user, or one
|
|
31
|
+
* whose pid file was cleaned up, still answers. Printing `(pid null)` would be worse than printing
|
|
32
|
+
* nothing, which is the same class of mistake as the port lie this command already fixed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function daemonLine(port: number, pid: number | null, peer: DaemonIdentity, self: {
|
|
35
|
+
version: string;
|
|
36
|
+
contract: string;
|
|
37
|
+
}): DaemonLine;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `doctor` says about the daemon it found — pure, so it can be tested without a socket.
|
|
3
|
+
*
|
|
4
|
+
* Doctor already stopped lying about the PORT: `probePresence` separates "a daemon is here" from "a
|
|
5
|
+
* stranger holds it" from "nothing is listening", which was the load-bearing half of the report. It
|
|
6
|
+
* still did not say WHICH daemon, though the `/status` payload it already fetches carries `version`
|
|
7
|
+
* and `contract` and it discarded both.
|
|
8
|
+
*
|
|
9
|
+
* That is the gap worth closing, because skew is invisible everywhere else: a CLI and a daemon on
|
|
10
|
+
* different contracts connect anyway and then disagree about behaviour, which reaches the agent as a
|
|
11
|
+
* bare `-32000` naming no version at all. Doctor is the command a human runs at exactly that moment.
|
|
12
|
+
*
|
|
13
|
+
* Same split as `classifyPort`/`probePresence`: the rule lives here and is tested; the fetching stays
|
|
14
|
+
* at the call site.
|
|
15
|
+
*/
|
|
16
|
+
import { describeSkew } from '../version/version-skew.js';
|
|
17
|
+
const FIX = 'Restart it so both ends are the same build: `reticle stop`, then let your agent reconnect.';
|
|
18
|
+
/**
|
|
19
|
+
* Build the daemon line for a port that answered `/status`.
|
|
20
|
+
*
|
|
21
|
+
* `pid` comes from the pid file and is genuinely optional — a daemon started by another user, or one
|
|
22
|
+
* whose pid file was cleaned up, still answers. Printing `(pid null)` would be worse than printing
|
|
23
|
+
* nothing, which is the same class of mistake as the port lie this command already fixed.
|
|
24
|
+
*/
|
|
25
|
+
export function daemonLine(port, pid, peer, self) {
|
|
26
|
+
const parts = [];
|
|
27
|
+
if (null !== pid)
|
|
28
|
+
parts.push(`pid ${String(pid)}`);
|
|
29
|
+
if (peer.version !== undefined)
|
|
30
|
+
parts.push(`v${peer.version}`);
|
|
31
|
+
const detail = 0 === parts.length ? '' : ` (${parts.join(', ')})`;
|
|
32
|
+
const skew = describeSkew({ what: 'the daemon on this port', version: peer.version, contract: peer.contract, fix: FIX }, self);
|
|
33
|
+
const line = { text: ` daemon ✓ running on :${String(port)}${detail}` };
|
|
34
|
+
return skew === undefined ? line : { ...line, skew };
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who is holding the bridge port.
|
|
3
|
+
*
|
|
4
|
+
* `doctor` is what a user runs when the agent cannot reach the bridge, and the commonest real cause
|
|
5
|
+
* is that something else is on 4400. It already stopped saying "nothing is listening" for that case,
|
|
6
|
+
* but it did not say WHICH process — so the reader's next move was a shell command they had to know.
|
|
7
|
+
*
|
|
8
|
+
* Naming the holder is also the safe answer to a trap this project has hit for real: the obvious
|
|
9
|
+
* lookup, `lsof -ti tcp:4400 | xargs kill -9`, ALSO kills the agent's own `reticle mcp` proxy,
|
|
10
|
+
* because that proxy holds a client connection to the same port and `-ti` does not filter to
|
|
11
|
+
* listeners. Every invocation here is `-sTCP:LISTEN`, and the message never suggests `-ti`.
|
|
12
|
+
*
|
|
13
|
+
* The parser is pure and the lookup takes an injected exec, so the rule is tested on every platform
|
|
14
|
+
* while the shelling out stays at the call site.
|
|
15
|
+
*/
|
|
16
|
+
export interface PortHolder {
|
|
17
|
+
pid: number;
|
|
18
|
+
command: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The FIRST listener in `lsof -nP -iTCP:<port> -sTCP:LISTEN -F pc` output, or null.
|
|
22
|
+
*
|
|
23
|
+
* First, not "the only one": a dual-stack listener (IPv4 + IPv6) is reported twice, and on a port
|
|
24
|
+
* conflict the answer the reader needs is "something is there, here is what" rather than an
|
|
25
|
+
* exhaustive list. A second `p` record ends the first.
|
|
26
|
+
*
|
|
27
|
+
* Null on anything incomplete. Half an answer — a pid with no command — prints worse than the
|
|
28
|
+
* un-named message it would replace, which is the same mistake as the port lie this command fixed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parsePortHolder(stdout: string): PortHolder | null;
|
|
31
|
+
/** What `doctor` prints for a port held by something that is not a Reticle daemon. */
|
|
32
|
+
export declare function describeForeignHolder(port: number, holder: PortHolder | null,
|
|
33
|
+
/**
|
|
34
|
+
* The pid we recorded for this port, from the daemon pid file.
|
|
35
|
+
*
|
|
36
|
+
* When it matches the process actually holding the port, the port is held by OUR daemon — one
|
|
37
|
+
* that accepts TCP and never answers `/status`, i.e. wedged. That classifies as FOREIGN, which is
|
|
38
|
+
* right for "can anything bind this?" and wrong to say out loud: the reader goes hunting a
|
|
39
|
+
* stranger, and the fix that works (`reticle stop`, it respawns on the next call) is never named.
|
|
40
|
+
*/
|
|
41
|
+
ourPid?: number | null): string;
|
|
42
|
+
/** The lookup itself. `exec` is injected so the shelling out never runs in a unit test. */
|
|
43
|
+
export declare function findPortHolder(port: number, exec: (command: string, args: readonly string[]) => string | null): PortHolder | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who is holding the bridge port.
|
|
3
|
+
*
|
|
4
|
+
* `doctor` is what a user runs when the agent cannot reach the bridge, and the commonest real cause
|
|
5
|
+
* is that something else is on 4400. It already stopped saying "nothing is listening" for that case,
|
|
6
|
+
* but it did not say WHICH process — so the reader's next move was a shell command they had to know.
|
|
7
|
+
*
|
|
8
|
+
* Naming the holder is also the safe answer to a trap this project has hit for real: the obvious
|
|
9
|
+
* lookup, `lsof -ti tcp:4400 | xargs kill -9`, ALSO kills the agent's own `reticle mcp` proxy,
|
|
10
|
+
* because that proxy holds a client connection to the same port and `-ti` does not filter to
|
|
11
|
+
* listeners. Every invocation here is `-sTCP:LISTEN`, and the message never suggests `-ti`.
|
|
12
|
+
*
|
|
13
|
+
* The parser is pure and the lookup takes an injected exec, so the rule is tested on every platform
|
|
14
|
+
* while the shelling out stays at the call site.
|
|
15
|
+
*/
|
|
16
|
+
/** `lsof -F pc` emits one field per line, prefixed by its selector: `p<pid>`, `c<command>`. */
|
|
17
|
+
const PID_FIELD = 'p';
|
|
18
|
+
const COMMAND_FIELD = 'c';
|
|
19
|
+
/**
|
|
20
|
+
* The FIRST listener in `lsof -nP -iTCP:<port> -sTCP:LISTEN -F pc` output, or null.
|
|
21
|
+
*
|
|
22
|
+
* First, not "the only one": a dual-stack listener (IPv4 + IPv6) is reported twice, and on a port
|
|
23
|
+
* conflict the answer the reader needs is "something is there, here is what" rather than an
|
|
24
|
+
* exhaustive list. A second `p` record ends the first.
|
|
25
|
+
*
|
|
26
|
+
* Null on anything incomplete. Half an answer — a pid with no command — prints worse than the
|
|
27
|
+
* un-named message it would replace, which is the same mistake as the port lie this command fixed.
|
|
28
|
+
*/
|
|
29
|
+
export function parsePortHolder(stdout) {
|
|
30
|
+
let pid;
|
|
31
|
+
let command;
|
|
32
|
+
for (const raw of stdout.split('\n')) {
|
|
33
|
+
const line = raw.trim();
|
|
34
|
+
if (0 === line.length)
|
|
35
|
+
continue;
|
|
36
|
+
const value = line.slice(1);
|
|
37
|
+
if (line.startsWith(PID_FIELD)) {
|
|
38
|
+
if (pid !== undefined)
|
|
39
|
+
break; // a second record: the first one is the answer
|
|
40
|
+
const parsed = Number(value);
|
|
41
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
42
|
+
return null;
|
|
43
|
+
pid = parsed;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (line.startsWith(COMMAND_FIELD) && command === undefined)
|
|
47
|
+
command = value;
|
|
48
|
+
}
|
|
49
|
+
return pid === undefined || command === undefined ? null : { pid, command };
|
|
50
|
+
}
|
|
51
|
+
/** What `doctor` prints for a port held by something that is not a Reticle daemon. */
|
|
52
|
+
export function describeForeignHolder(port, holder,
|
|
53
|
+
/**
|
|
54
|
+
* The pid we recorded for this port, from the daemon pid file.
|
|
55
|
+
*
|
|
56
|
+
* When it matches the process actually holding the port, the port is held by OUR daemon — one
|
|
57
|
+
* that accepts TCP and never answers `/status`, i.e. wedged. That classifies as FOREIGN, which is
|
|
58
|
+
* right for "can anything bind this?" and wrong to say out loud: the reader goes hunting a
|
|
59
|
+
* stranger, and the fix that works (`reticle stop`, it respawns on the next call) is never named.
|
|
60
|
+
*/
|
|
61
|
+
ourPid) {
|
|
62
|
+
const tail = 'a daemon cannot bind it. Stop that process, or run Reticle on a different port ' +
|
|
63
|
+
'(`--port`), then retry.';
|
|
64
|
+
if (null === holder) {
|
|
65
|
+
/**
|
|
66
|
+
* We could not identify the holder — no `lsof` (every Windows user, and 35% of them by the
|
|
67
|
+
* 2026-08 telemetry; also a slim container), or the lookup timed out.
|
|
68
|
+
*
|
|
69
|
+
* This used to assert the port was held by "another process that is not a Reticle daemon",
|
|
70
|
+
* which is a claim the null case has NO evidence for. It is also the likeliest thing to be
|
|
71
|
+
* wrong: reaching here at all means presence is FOREIGN — the port accepts TCP and never
|
|
72
|
+
* answers `/status` — and the commonest cause of that is OUR OWN daemon, wedged. So on the one
|
|
73
|
+
* platform where the lookup cannot run, `doctor` sent the reader hunting a stranger and never
|
|
74
|
+
* named `reticle stop`, the fix that actually works.
|
|
75
|
+
*
|
|
76
|
+
* Say what is known, and name the recorded pid when there is one.
|
|
77
|
+
*/
|
|
78
|
+
const ours = ourPid !== null && ourPid !== undefined
|
|
79
|
+
? ` Reticle's own records show pid ${String(ourPid)} for this port — if that process is ` +
|
|
80
|
+
'still around it is a wedged Reticle daemon, and `reticle stop` clears it.'
|
|
81
|
+
: '';
|
|
82
|
+
return `port ${String(port)} is held by a process Reticle could not identify — ${tail}${ours}`;
|
|
83
|
+
}
|
|
84
|
+
if (ourPid !== null && ourPid !== undefined && ourPid === holder.pid) {
|
|
85
|
+
return (`port ${String(port)} is held by YOUR Reticle daemon (pid ${String(holder.pid)}), and it is ` +
|
|
86
|
+
'not responding — running but wedged, so nothing can use it and nothing else can bind the ' +
|
|
87
|
+
'port. Stop it with `reticle stop`; the next tool call starts a fresh one.');
|
|
88
|
+
}
|
|
89
|
+
return (`port ${String(port)} is held by pid ${String(holder.pid)} ("${holder.command}"), which is not ` +
|
|
90
|
+
`a Reticle daemon — ${tail}`);
|
|
91
|
+
}
|
|
92
|
+
/** The lookup itself. `exec` is injected so the shelling out never runs in a unit test. */
|
|
93
|
+
export function findPortHolder(port, exec) {
|
|
94
|
+
// -n/-P skip DNS and service-name lookups (slow, and irrelevant here). -sTCP:LISTEN is the part
|
|
95
|
+
// that matters: without it this also reports the agent's own MCP proxy, which is a client of the
|
|
96
|
+
// port, not its owner.
|
|
97
|
+
const out = exec('lsof', ['-nP', `-iTCP:${String(port)}`, '-sTCP:LISTEN', '-F', 'pc']);
|
|
98
|
+
return null === out ? null : parsePortHolder(out);
|
|
99
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseCliArgs, CLI_USAGE } from './cli-parse.js';
|
|
2
|
+
import { parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
|
|
3
3
|
export { parseCliArgs, CLI_USAGE };
|
|
4
|
-
export type { CliResult } from './cli-parse.js';
|
|
4
|
+
export type { CliResult } from './cli/cli-parse.js';
|