@reticlehq/server 2.13.0 → 2.14.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 +8 -18
- package/bin/reticle.js +3 -0
- package/dist/bridge/auth-failure-reason.js +12 -1
- package/dist/bridge/bridge-security.d.ts +8 -0
- package/dist/bridge/bridge-security.js +6 -1
- package/dist/bridge/bridge.d.ts +3 -0
- package/dist/bridge/bridge.js +23 -1
- package/dist/bridge/bridge.test-harness.d.ts +10 -1
- package/dist/bridge/bridge.test-harness.js +7 -1
- package/dist/bridge/pairing-token.d.ts +17 -0
- package/dist/bridge/pairing-token.js +26 -0
- package/dist/bridge/token-auth.d.ts +7 -0
- package/dist/bridge/token-auth.js +7 -1
- package/dist/capsule/capsule-store.d.ts +4 -4
- package/dist/capsule/capsule.d.ts +1 -1
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +21 -1
- package/dist/capsule/causal-summary.js +8 -6
- package/dist/capsule/divergence.d.ts +1 -1
- package/dist/capsule/divergence.js +26 -10
- package/dist/chromium-launch-options.d.ts +2 -1
- package/dist/cli/adhoc-verdict.d.ts +58 -0
- package/dist/cli/adhoc-verdict.js +115 -0
- package/dist/cli/chromium-hint.d.ts +18 -4
- package/dist/cli/chromium-hint.js +54 -6
- package/dist/cli/cli-doctor.js +1 -2
- package/dist/cli/cli-flow-commands.d.ts +16 -2
- package/dist/cli/cli-flow-commands.js +28 -9
- package/dist/cli/cli-parse-feedback.js +1 -1
- package/dist/cli/cli-parse.d.ts +2 -1
- package/dist/cli/cli-parse.js +38 -0
- package/dist/cli/cli-port.d.ts +5 -13
- package/dist/cli/cli-port.js +5 -38
- package/dist/cli/cli-update-commands.js +4 -4
- package/dist/cli/cli-verify.d.ts +24 -7
- package/dist/cli/cli-verify.js +87 -13
- package/dist/cli/cloud-keystore.d.ts +2 -1
- package/dist/cli/cloud-login.d.ts +2 -1
- package/dist/cli/cloud-session.d.ts +2 -1
- package/dist/cli/cloud-session.js +1 -1
- package/dist/cli/daemon-start-options.d.ts +2 -1
- package/dist/cli/device-flow.d.ts +2 -2
- package/dist/cli/doctor-daemon-line.d.ts +2 -1
- package/dist/cli/doctor-project-line.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +2 -1
- package/dist/cli/drive-attach.d.ts +4 -3
- package/dist/cli/gate-hook-message.d.ts +2 -1
- package/dist/cli/status-next-action.js +10 -11
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +21 -9
- package/dist/cloud/account-state.d.ts +20 -0
- package/dist/cloud/account-state.js +65 -0
- package/dist/cloud/cloud-sync.d.ts +46 -3
- package/dist/cloud/cloud-sync.js +116 -0
- package/dist/cloud/sync-cycle.d.ts +5 -4
- package/dist/cloud/sync-cycle.js +1 -1
- package/dist/cloud/sync-daemon.d.ts +3 -19
- package/dist/cloud/sync-daemon.js +2 -2
- package/dist/crawl/crawl-tools.js +1 -1
- package/dist/crawl/crawl.d.ts +5 -5
- package/dist/crawl/crawl.js +138 -4
- package/dist/daemon/daemon-resilience.d.ts +3 -2
- package/dist/daemon/daemon-resilience.js +43 -4
- package/dist/daemon/daemon-resolve.d.ts +0 -9
- package/dist/daemon/daemon-resolve.js +2 -2
- package/dist/daemon/dev-servers.d.ts +1 -1
- package/dist/daemon/dev-servers.js +1 -1
- package/dist/daemon/loopback-alias.d.ts +2 -1
- package/dist/daemon/startup-failure.d.ts +11 -0
- package/dist/daemon/startup-failure.js +46 -0
- package/dist/events/contradictions.d.ts +10 -16
- package/dist/events/contradictions.js +149 -17
- package/dist/events/declared.d.ts +13 -1
- package/dist/events/declared.js +59 -3
- package/dist/events/echo-mismatch.js +75 -6
- package/dist/events/lineage.d.ts +52 -0
- package/dist/events/lineage.js +122 -0
- package/dist/events/net-evidence.d.ts +21 -0
- package/dist/events/net-evidence.js +81 -0
- package/dist/events/predicate-console.d.ts +13 -0
- package/dist/events/predicate-console.js +138 -0
- package/dist/events/predicate-element.d.ts +13 -0
- package/dist/events/predicate-element.js +214 -0
- package/dist/events/predicate-eval.d.ts +15 -3
- package/dist/events/predicate-eval.js +128 -110
- package/dist/events/predicate-parse.js +34 -1
- package/dist/events/predicate-request-body.d.ts +50 -0
- package/dist/events/predicate-request-body.js +120 -0
- package/dist/events/predicate-route.d.ts +21 -1
- package/dist/events/predicate-route.js +149 -21
- package/dist/events/predicate-schema.d.ts +8 -1
- package/dist/events/predicate-schema.js +34 -0
- package/dist/events/predicate.d.ts +2 -0
- package/dist/events/predicate.js +52 -159
- package/dist/events/split-text-miss.d.ts +6 -4
- package/dist/events/split-text-miss.js +12 -5
- package/dist/flows/affected-tools.js +22 -9
- package/dist/flows/annotate.d.ts +2 -1
- package/dist/flows/annotate.js +19 -1
- package/dist/flows/coverage.d.ts +2 -2
- package/dist/flows/flake.d.ts +3 -3
- package/dist/flows/flow-expect-grammar.d.ts +33 -0
- package/dist/flows/flow-expect-grammar.js +155 -0
- package/dist/flows/flow-replay-run.d.ts +1 -1
- package/dist/flows/flow-replay-run.js +28 -15
- package/dist/flows/flow-replay.d.ts +10 -0
- package/dist/flows/flow-replay.js +57 -22
- package/dist/flows/flow-secret-field.d.ts +14 -0
- package/dist/flows/flow-secret-field.js +22 -0
- package/dist/flows/flow-step-runners.d.ts +1 -0
- package/dist/flows/flow-step-runners.js +91 -34
- package/dist/flows/flow-success.js +22 -0
- package/dist/flows/flow-tools.js +27 -8
- package/dist/flows/flow-upload-resolve.d.ts +23 -0
- package/dist/flows/flow-upload-resolve.js +46 -0
- package/dist/flows/flows.d.ts +15 -0
- package/dist/flows/flows.js +17 -14
- package/dist/flows/git-changed.d.ts +23 -4
- package/dist/flows/git-changed.js +32 -6
- package/dist/flows/heal-run.js +2 -2
- package/dist/flows/predicate-to-expect.d.ts +3 -2
- package/dist/flows/predicate-to-expect.js +11 -5
- package/dist/flows/recording-backtrack.d.ts +24 -0
- package/dist/flows/recording-backtrack.js +73 -0
- package/dist/flows/recordings.d.ts +6 -0
- package/dist/flows/replay-halt.d.ts +26 -0
- package/dist/flows/replay-halt.js +30 -0
- package/dist/flows/replay.d.ts +0 -8
- package/dist/flows/replay.js +15 -3
- package/dist/flows/suite-flakes.d.ts +2 -1
- package/dist/flows/verify-change-tools.js +16 -4
- package/dist/honesty/already-true.d.ts +14 -0
- package/dist/honesty/already-true.js +47 -4
- package/dist/honesty/blind-spots.d.ts +3 -1
- package/dist/honesty/blind-spots.js +24 -1
- package/dist/honesty/body-capture-remedy.d.ts +42 -0
- package/dist/honesty/body-capture-remedy.js +95 -0
- package/dist/honesty/feature-capture.d.ts +3 -2
- package/dist/honesty/feature-capture.js +1 -1
- package/dist/honesty/gap-ledger.d.ts +1 -1
- package/dist/honesty/gap-ledger.js +1 -1
- package/dist/honesty/instrumentation-gaps.d.ts +8 -1
- package/dist/honesty/instrumentation-gaps.js +12 -2
- package/dist/honesty/observability.d.ts +4 -3
- package/dist/honesty/tool-hit-rate.d.ts +3 -2
- package/dist/honesty/uncaptured-bodies.d.ts +3 -1
- package/dist/honesty/uncaptured-bodies.js +6 -2
- package/dist/honesty/unsettled.d.ts +9 -0
- package/dist/honesty/unsettled.js +45 -0
- package/dist/honesty/verified.d.ts +32 -1
- package/dist/honesty/verified.js +37 -10
- package/dist/impact/impact-store.d.ts +5 -14
- package/dist/impact/impact-store.js +49 -6
- package/dist/index.js +1 -1
- package/dist/input/network-detail.d.ts +26 -1
- package/dist/input/network-detail.js +44 -12
- package/dist/input/network-mock-tools.js +24 -17
- package/dist/input/real-input.js +1 -1
- package/dist/input/viewport-tools.js +25 -13
- package/dist/intent/inline-intent.d.ts +0 -10
- package/dist/intent/inline-intent.js +1 -1
- package/dist/intent/intent-shard-store.d.ts +3 -2
- package/dist/intent/intent-shard-store.js +1 -1
- package/dist/intent/intent-shard.d.ts +14 -14
- package/dist/intent/intent-shard.js +4 -4
- package/dist/intent/intent-store.d.ts +1 -1
- package/dist/intent/intent-store.js +1 -1
- package/dist/intent/intent-subject.d.ts +2 -1
- package/dist/intent/open-intents.d.ts +3 -2
- package/dist/license/license.d.ts +4 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/attach-memory.d.ts +2 -1
- package/dist/mcp/client-identity.d.ts +2 -1
- package/dist/mcp/mcp-outage.d.ts +1 -1
- package/dist/mcp/mcp-outage.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +3 -1
- package/dist/mcp/mcp-proxy.js +32 -7
- package/dist/mcp/mcp.js +3 -1
- package/dist/mcp/proxy-handshake.d.ts +2 -1
- package/dist/mcp/proxy-idle-exit.d.ts +45 -0
- package/dist/mcp/proxy-idle-exit.js +94 -0
- package/dist/mcp/server-instructions.d.ts +2 -1
- package/dist/memory/project-memory.d.ts +2 -3
- package/dist/memory/project-memory.js +1 -1
- package/dist/oracles/propose-consequences.js +5 -5
- package/dist/oracles/self-instrument.d.ts +2 -2
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/platform.d.ts +3 -5
- package/dist/platform.js +3 -5
- package/dist/pool/browser-pool.d.ts +92 -0
- package/dist/pool/browser-pool.js +119 -5
- package/dist/pool/playwright-launcher.js +29 -2
- package/dist/pool/storage-seed.d.ts +23 -0
- package/dist/pool/storage-seed.js +198 -0
- package/dist/project/artifact-root.d.ts +2 -1
- package/dist/project/memory-fs.d.ts +2 -1
- package/dist/project/reticle-dir.js +1 -1
- package/dist/project/session-root.d.ts +8 -0
- package/dist/project/session-root.js +9 -2
- package/dist/runs/reticle-runner.d.ts +14 -1
- package/dist/runs/reticle-runner.js +39 -2
- package/dist/session/attachment-history.d.ts +2 -1
- package/dist/session/fake-session.d.ts +37 -0
- package/dist/session/fake-session.js +81 -0
- package/dist/session/lease-visibility.d.ts +2 -1
- package/dist/session/live-control-tools.js +6 -0
- package/dist/session/no-session-diagnosis.d.ts +17 -1
- package/dist/session/no-session-diagnosis.js +32 -6
- package/dist/session/no-session-next-action.d.ts +14 -0
- package/dist/session/no-session-next-action.js +18 -0
- package/dist/session/no-session-watch.js +46 -3
- package/dist/session/registered-projects.d.ts +54 -0
- package/dist/session/registered-projects.js +73 -0
- package/dist/session/review-store.d.ts +2 -1
- package/dist/session/session-health.d.ts +62 -1
- package/dist/session/session-health.js +82 -2
- package/dist/session/session-manager.d.ts +11 -1
- package/dist/session/session-manager.js +18 -3
- package/dist/session/session-replaced.d.ts +24 -0
- package/dist/session/session-replaced.js +29 -0
- package/dist/session/session-successor.js +20 -4
- package/dist/session/session.d.ts +13 -5
- package/dist/session/session.js +31 -23
- package/dist/setup/agent-configs.d.ts +3 -2
- package/dist/setup/agent-writer.d.ts +3 -2
- package/dist/setup/approval-migration.d.ts +2 -1
- package/dist/setup/approval-migration.js +1 -1
- package/dist/setup/auto-approve.d.ts +11 -19
- package/dist/setup/auto-approve.js +15 -12
- package/dist/{init → setup}/confirm.js +1 -1
- package/dist/setup/desktop-shape.d.ts +3 -2
- package/dist/setup/dev-server-wait.d.ts +8 -0
- package/dist/setup/dev-server-wait.js +3 -1
- package/dist/setup/drive-agent.d.ts +2 -3
- package/dist/setup/drive-agent.js +1 -1
- package/dist/setup/drive-plan.d.ts +3 -2
- package/dist/setup/ensure-daemon.d.ts +2 -1
- package/dist/setup/existing-app.d.ts +12 -0
- package/dist/setup/existing-app.js +13 -0
- package/dist/setup/init-host.d.ts +11 -0
- package/dist/setup/init-host.js +26 -0
- package/dist/setup/init-runtime.d.ts +4 -10
- package/dist/setup/init-runtime.js +23 -16
- package/dist/setup/license-key.d.ts +2 -1
- package/dist/setup/listeners.d.ts +2 -1
- package/dist/setup/loopback-probe-urls.d.ts +18 -0
- package/dist/setup/loopback-probe-urls.js +52 -0
- package/dist/setup/node-effects.d.ts +3 -0
- package/dist/setup/node-effects.js +26 -11
- package/dist/setup/page-probe.d.ts +5 -0
- package/dist/setup/relaunch.d.ts +2 -1
- package/dist/setup/run-setup.d.ts +5 -1
- package/dist/setup/run-setup.js +155 -48
- package/dist/setup/setup-command.d.ts +3 -2
- package/dist/setup/setup-command.js +8 -0
- package/dist/setup/setup-options.d.ts +5 -3
- package/dist/setup/setup-options.js +3 -2
- package/dist/setup/wait-progress.d.ts +28 -0
- package/dist/setup/wait-progress.js +43 -0
- package/dist/telemetry/app-instrumented.d.ts +2 -1
- package/dist/telemetry/app-instrumented.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +1 -1
- package/dist/telemetry/automation-hint.js +1 -1
- package/dist/telemetry/cli-telemetry.js +1 -1
- package/dist/telemetry/connect-failure.d.ts +1 -1
- package/dist/telemetry/connect-failure.js +1 -1
- package/dist/telemetry/crash-cause.d.ts +3 -2
- package/dist/telemetry/crash-cause.js +1 -1
- package/dist/telemetry/daemon-telemetry.d.ts +3 -2
- package/dist/telemetry/daemon-telemetry.js +1 -1
- package/dist/telemetry/error-class.d.ts +3 -2
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-cli.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +3 -2
- package/dist/telemetry/feedback-context.js +5 -4
- package/dist/telemetry/feedback-local.d.ts +3 -2
- package/dist/telemetry/feedback.d.ts +4 -3
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/git-facts.d.ts +2 -2
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/identify.d.ts +1 -1
- package/dist/telemetry/identify.js +1 -1
- package/dist/telemetry/init-telemetry.d.ts +2 -14
- package/dist/telemetry/init-telemetry.js +4 -13
- package/dist/telemetry/install-source.d.ts +7 -13
- package/dist/telemetry/install-source.js +7 -29
- package/dist/telemetry/license-activation.d.ts +3 -2
- package/dist/telemetry/license-activation.js +1 -1
- package/dist/telemetry/machine-snapshot.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +1 -1
- package/dist/telemetry/project-profile.d.ts +0 -14
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/proxy-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +1 -17
- package/dist/telemetry/telemetry.js +4 -4
- package/dist/telemetry/tool-refused.d.ts +1 -1
- package/dist/telemetry/tool-refused.js +1 -1
- package/dist/tools/act-observation.d.ts +3 -2
- package/dist/tools/act-preflight.js +8 -3
- package/dist/tools/act-target.d.ts +4 -0
- package/dist/tools/act-target.js +14 -5
- package/dist/tools/act-tools.d.ts +0 -2
- package/dist/tools/act-tools.js +45 -15
- package/dist/tools/assert-source.d.ts +30 -0
- package/dist/tools/assert-source.js +21 -0
- package/dist/tools/assert-verdict.d.ts +1 -1
- package/dist/tools/assert-verdict.js +14 -4
- package/dist/tools/browser-tools.js +28 -8
- package/dist/tools/complete-snapshot.d.ts +3 -2
- package/dist/tools/dynamic-tools.js +8 -0
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +23 -0
- package/dist/tools/feedback-tools.js +1 -1
- package/dist/tools/invoke-tool.js +2 -0
- package/dist/tools/lease-tools.d.ts +48 -20
- package/dist/tools/lease-tools.js +311 -76
- package/dist/tools/lineage-tools.d.ts +14 -0
- package/dist/tools/lineage-tools.js +64 -0
- package/dist/tools/merge-tools.js +1 -3
- package/dist/tools/navigate-arrival.d.ts +45 -1
- package/dist/tools/navigate-arrival.js +39 -6
- package/dist/tools/navigate-result.d.ts +8 -1
- package/dist/tools/navigate-result.js +14 -4
- package/dist/tools/numeric-bounds.d.ts +0 -11
- package/dist/tools/numeric-bounds.js +4 -4
- package/dist/tools/observe-tools.js +22 -26
- package/dist/tools/observed-nothing.d.ts +9 -0
- package/dist/tools/observed-nothing.js +2 -1
- package/dist/tools/read-tools.js +12 -17
- package/dist/tools/real-input-attempt.d.ts +14 -6
- package/dist/tools/real-input-attempt.js +75 -18
- package/dist/tools/reconcile-tools.js +2 -1
- package/dist/tools/reload-result.d.ts +6 -1
- package/dist/tools/reload-result.js +7 -2
- package/dist/tools/resolve-target.d.ts +7 -1
- package/dist/tools/resolve-target.js +72 -9
- package/dist/tools/settle-in-flight.d.ts +1 -1
- package/dist/tools/settle-in-flight.js +9 -2
- package/dist/tools/snapshot-delta.d.ts +24 -1
- package/dist/tools/snapshot-delta.js +66 -7
- package/dist/tools/tool-names.d.ts +1 -0
- package/dist/tools/tool-names.js +1 -0
- package/dist/tools/tool-surface.js +20 -2
- package/dist/tools/tools.js +120 -11
- package/dist/tools/verify-next-baton.d.ts +2 -1
- package/dist/update/sdk-sync.d.ts +1 -1
- package/dist/update/sdk-sync.js +1 -1
- package/dist/update/updater.js +1 -1
- package/dist/version/sdk-fix.d.ts +2 -1
- package/dist/version/version-skew.d.ts +0 -8
- package/dist/version/version-skew.js +1 -1
- package/docs/README.md +8 -6
- package/docs/actions.mdx +1 -1
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +1 -1
- package/docs/cli/mcp.mdx +3 -1
- package/docs/cli/watch.mdx +1 -1
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +8 -3
- package/docs/faq.mdx +9 -0
- package/docs/first-drive.md +136 -0
- package/docs/frameworks.mdx +29 -7
- package/docs/gate-plan.md +2 -2
- package/docs/gates.md +19 -11
- package/docs/getting-started.md +7 -1
- package/docs/http-transport.md +164 -0
- package/docs/index.mdx +5 -3
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +15 -3
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +2 -0
- package/docs/system-map.md +2 -2
- package/docs/telemetry-contract.md +4 -4
- package/docs/telemetry-events.mdx +1 -1
- package/docs/telemetry.md +3 -3
- package/docs/tools-navigate.mdx +14 -6
- package/docs/usage.md +4 -4
- package/package.json +7 -4
- package/dist/init/agent-rules.d.ts +0 -116
- package/dist/init/agent-rules.js +0 -279
- package/dist/init/app-choice.d.ts +0 -20
- package/dist/init/app-choice.js +0 -25
- package/dist/init/astro-host.d.ts +0 -21
- package/dist/init/astro-host.js +0 -27
- package/dist/init/astro-patch.d.ts +0 -33
- package/dist/init/astro-patch.js +0 -300
- package/dist/init/capabilities.d.ts +0 -40
- package/dist/init/capabilities.js +0 -150
- package/dist/init/closing-hint.d.ts +0 -36
- package/dist/init/closing-hint.js +0 -120
- package/dist/init/connect-steps.d.ts +0 -2
- package/dist/init/connect-steps.js +0 -50
- package/dist/init/cra.d.ts +0 -79
- package/dist/init/cra.js +0 -178
- package/dist/init/csp-check.d.ts +0 -38
- package/dist/init/csp-check.js +0 -107
- package/dist/init/csp-doctor.d.ts +0 -32
- package/dist/init/csp-doctor.js +0 -54
- package/dist/init/desktop-doctor.d.ts +0 -41
- package/dist/init/desktop-doctor.js +0 -198
- package/dist/init/detect.d.ts +0 -96
- package/dist/init/detect.js +0 -209
- package/dist/init/dev-script.d.ts +0 -50
- package/dist/init/dev-script.js +0 -66
- package/dist/init/format-generated.d.ts +0 -18
- package/dist/init/format-generated.js +0 -39
- package/dist/init/install-hint.d.ts +0 -9
- package/dist/init/install-hint.js +0 -29
- package/dist/init/mcp-clients.d.ts +0 -89
- package/dist/init/mcp-clients.js +0 -299
- package/dist/init/mcp-registered.d.ts +0 -12
- package/dist/init/mcp-registered.js +0 -14
- package/dist/init/mcp.d.ts +0 -89
- package/dist/init/mcp.js +0 -129
- package/dist/init/next-patch.d.ts +0 -39
- package/dist/init/next-patch.js +0 -196
- package/dist/init/node-io.d.ts +0 -7
- package/dist/init/node-io.js +0 -117
- package/dist/init/non-js-project.d.ts +0 -27
- package/dist/init/non-js-project.js +0 -84
- package/dist/init/patch-kind.d.ts +0 -20
- package/dist/init/patch-kind.js +0 -10
- package/dist/init/plan-framework.d.ts +0 -64
- package/dist/init/plan-framework.js +0 -565
- package/dist/init/plan.d.ts +0 -242
- package/dist/init/plan.js +0 -703
- package/dist/init/preflight.d.ts +0 -28
- package/dist/init/preflight.js +0 -38
- package/dist/init/project-id.d.ts +0 -12
- package/dist/init/project-id.js +0 -25
- package/dist/init/refresh-rules.d.ts +0 -39
- package/dist/init/refresh-rules.js +0 -51
- package/dist/init/run.d.ts +0 -117
- package/dist/init/run.js +0 -806
- package/dist/init/slash-command.d.ts +0 -27
- package/dist/init/slash-command.js +0 -132
- package/dist/init/snippets.d.ts +0 -134
- package/dist/init/snippets.js +0 -572
- package/dist/init/vite-config.d.ts +0 -18
- package/dist/init/vite-config.js +0 -114
- package/dist/init/windows-quote.d.ts +0 -38
- package/dist/init/windows-quote.js +0 -70
- package/dist/init/workspace-apps.d.ts +0 -30
- package/dist/init/workspace-apps.js +0 -178
- package/dist/project/remember-project.d.ts +0 -24
- package/dist/project/remember-project.js +0 -40
- /package/dist/{init → setup}/confirm.d.ts +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One complete, inert `Session` for tests that only care about two or three of its methods.
|
|
3
|
+
*
|
|
4
|
+
* Test-only. Every consumer of `Session` used to hand-roll a `Partial<Session>` and cast it through
|
|
5
|
+
* `as Session`, and the cast is the problem: it silences the type system, so a method added to
|
|
6
|
+
* `Session` breaks nothing at compile time and then throws at runtime inside files that have
|
|
7
|
+
* nothing to do with the change. That has now happened four times -- `occludedBy` (#227),
|
|
8
|
+
* `bufferHealth` (#228), the case recorded at the top of `assert-contradiction-plumbing.test.ts`,
|
|
9
|
+
* and `lostSince`, which needed the identical one-line stub added to seven files (#726).
|
|
10
|
+
*
|
|
11
|
+
* This returns a REAL `Session`, built with an inert socket and a frozen clock, so a new method
|
|
12
|
+
* arrives with a working default instead of `undefined`. `Session` holds `#private` fields, so a
|
|
13
|
+
* structural stub cannot be one -- constructing the real thing is what makes the guarantee hold.
|
|
14
|
+
*
|
|
15
|
+
* Overrides are applied as own properties, shadowing the prototype method for that instance. Pass
|
|
16
|
+
* only the behaviour a test actually asserts on; leave the rest to the defaults.
|
|
17
|
+
*/
|
|
18
|
+
import { type HelloMessage } from '@reticlehq/core';
|
|
19
|
+
import { Session } from './session.js';
|
|
20
|
+
/**
|
|
21
|
+
* The handshake a fake session reports.
|
|
22
|
+
*
|
|
23
|
+
* `sessionId: 'demo'` matches what the hand-rolled stubs used, so tests asserting on the id in a
|
|
24
|
+
* message keep passing without an override.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fakeHello(overrides?: Partial<HelloMessage>): HelloMessage;
|
|
27
|
+
/**
|
|
28
|
+
* A real `Session` with inert defaults, plus whatever this test needs it to do differently.
|
|
29
|
+
*
|
|
30
|
+
* @param overrides Written onto the instance after construction, shadowing the real member for
|
|
31
|
+
* this session only. `runtime`, `engine`, `brand` and `actionCount` are getter-only accessors on
|
|
32
|
+
* the prototype -- a plain assignment to one throws -- so those are redefined as instance values
|
|
33
|
+
* instead. Overriding them is what a test wants: the real ones are fed by `applyHealth` and the
|
|
34
|
+
* journal, which a fake session has neither of.
|
|
35
|
+
* @param hello Handshake fields, when a test cares about the id, url or redact keys.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createFakeSession(overrides?: Partial<Session>, hello?: Partial<HelloMessage>): Session;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One complete, inert `Session` for tests that only care about two or three of its methods.
|
|
3
|
+
*
|
|
4
|
+
* Test-only. Every consumer of `Session` used to hand-roll a `Partial<Session>` and cast it through
|
|
5
|
+
* `as Session`, and the cast is the problem: it silences the type system, so a method added to
|
|
6
|
+
* `Session` breaks nothing at compile time and then throws at runtime inside files that have
|
|
7
|
+
* nothing to do with the change. That has now happened four times -- `occludedBy` (#227),
|
|
8
|
+
* `bufferHealth` (#228), the case recorded at the top of `assert-contradiction-plumbing.test.ts`,
|
|
9
|
+
* and `lostSince`, which needed the identical one-line stub added to seven files (#726).
|
|
10
|
+
*
|
|
11
|
+
* This returns a REAL `Session`, built with an inert socket and a frozen clock, so a new method
|
|
12
|
+
* arrives with a working default instead of `undefined`. `Session` holds `#private` fields, so a
|
|
13
|
+
* structural stub cannot be one -- constructing the real thing is what makes the guarantee hold.
|
|
14
|
+
*
|
|
15
|
+
* Overrides are applied as own properties, shadowing the prototype method for that instance. Pass
|
|
16
|
+
* only the behaviour a test actually asserts on; leave the rest to the defaults.
|
|
17
|
+
*/
|
|
18
|
+
import { MessageKind, RETICLE_PROTOCOL_VERSION } from '@reticlehq/core';
|
|
19
|
+
import { Session } from './session.js';
|
|
20
|
+
/** Accepts and discards. A fake session is never expected to reach a peer. */
|
|
21
|
+
const INERT_SOCKET = {
|
|
22
|
+
send: () => undefined,
|
|
23
|
+
close: () => undefined,
|
|
24
|
+
};
|
|
25
|
+
/** Frozen at 0, so `elapsed()`, `lastSeenMs()` and `agentIdleMs()` are all deterministic zeroes. */
|
|
26
|
+
const FROZEN_CLOCK = () => 0;
|
|
27
|
+
/**
|
|
28
|
+
* The handshake a fake session reports.
|
|
29
|
+
*
|
|
30
|
+
* `sessionId: 'demo'` matches what the hand-rolled stubs used, so tests asserting on the id in a
|
|
31
|
+
* message keep passing without an override.
|
|
32
|
+
*/
|
|
33
|
+
export function fakeHello(overrides = {}) {
|
|
34
|
+
return {
|
|
35
|
+
kind: MessageKind.HELLO,
|
|
36
|
+
protocolVersion: RETICLE_PROTOCOL_VERSION,
|
|
37
|
+
sessionId: 'demo',
|
|
38
|
+
url: 'http://localhost/',
|
|
39
|
+
title: 'Demo',
|
|
40
|
+
adapters: [],
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Whether `key` resolves to a getter somewhere on the prototype chain rather than a plain slot. */
|
|
45
|
+
function isAccessor(target, key) {
|
|
46
|
+
let proto = target;
|
|
47
|
+
while (null !== proto) {
|
|
48
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
|
|
49
|
+
if (undefined !== descriptor)
|
|
50
|
+
return undefined !== descriptor.get;
|
|
51
|
+
proto = Object.getPrototypeOf(proto);
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A real `Session` with inert defaults, plus whatever this test needs it to do differently.
|
|
57
|
+
*
|
|
58
|
+
* @param overrides Written onto the instance after construction, shadowing the real member for
|
|
59
|
+
* this session only. `runtime`, `engine`, `brand` and `actionCount` are getter-only accessors on
|
|
60
|
+
* the prototype -- a plain assignment to one throws -- so those are redefined as instance values
|
|
61
|
+
* instead. Overriding them is what a test wants: the real ones are fed by `applyHealth` and the
|
|
62
|
+
* journal, which a fake session has neither of.
|
|
63
|
+
* @param hello Handshake fields, when a test cares about the id, url or redact keys.
|
|
64
|
+
*/
|
|
65
|
+
export function createFakeSession(overrides = {}, hello = {}) {
|
|
66
|
+
const session = new Session(fakeHello(hello), INERT_SOCKET, FROZEN_CLOCK);
|
|
67
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
68
|
+
if (isAccessor(session, key)) {
|
|
69
|
+
Object.defineProperty(session, key, {
|
|
70
|
+
value,
|
|
71
|
+
configurable: true,
|
|
72
|
+
enumerable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
session[key] = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return session;
|
|
81
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* message to tabs that are ALREADY connected, not a new capability.
|
|
16
16
|
*/
|
|
17
17
|
/** The minimum a session has to expose to be a candidate watcher. Structural, so tests need no SDK. */
|
|
18
|
-
|
|
18
|
+
interface WatcherCandidate {
|
|
19
19
|
id: string;
|
|
20
20
|
projectId: string | undefined;
|
|
21
21
|
}
|
|
@@ -65,3 +65,4 @@ export declare function shouldGreetWithLeaseNotice(session: WatcherCandidate, le
|
|
|
65
65
|
* Pure: no IO, no clock, no session objects. The caller does the posting.
|
|
66
66
|
*/
|
|
67
67
|
export declare function watchersToNotify(sessions: readonly WatcherCandidate[], leasedSessionIds: readonly string[], projectId: string | undefined): string[];
|
|
68
|
+
export {};
|
|
@@ -157,6 +157,12 @@ export const LIVE_CONTROL_TOOLS = [
|
|
|
157
157
|
// said nothing; the second means an agent is waiting on a channel that does not exist.
|
|
158
158
|
return Promise.resolve(noteEmptyRead({ messages }, 'messages', {
|
|
159
159
|
noun: 'messages from the human since the last poll',
|
|
160
|
+
// The HUD no longer carries a text input — two boxes on one screen, and nothing said which
|
|
161
|
+
// was which. Nothing in the shipped product produces one of these today, so an empty queue
|
|
162
|
+
// here is structural rather than a quiet human, and a caller polling it deserves to know
|
|
163
|
+
// that rather than infer silence.
|
|
164
|
+
caveat: 'the HUD has no message box, so nothing currently sends on this channel — an empty ' +
|
|
165
|
+
'queue here is not evidence about what the human thinks',
|
|
160
166
|
}));
|
|
161
167
|
},
|
|
162
168
|
},
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
|
|
19
19
|
* the hot resolve() path and this stays unit-testable.
|
|
20
20
|
*/
|
|
21
|
-
import { NoSessionReason } from '@reticlehq/core';
|
|
21
|
+
import { NoSessionReason } from '@reticlehq/core/telemetry';
|
|
22
22
|
import { type LeaseBrowserState } from './lease-availability.js';
|
|
23
23
|
export interface NoSessionFacts {
|
|
24
24
|
/** Whether ANY session has connected to this daemon since it booted. */
|
|
@@ -128,6 +128,15 @@ export interface NoSessionFacts {
|
|
|
128
128
|
* which is the common case and must not gain a paragraph.
|
|
129
129
|
*/
|
|
130
130
|
siblingListeners?: readonly number[];
|
|
131
|
+
/**
|
|
132
|
+
* The URL of the session that most recently disappeared, when the tombstone still holds it.
|
|
133
|
+
*
|
|
134
|
+
* A route that 500s tears the page down and the SDK never reconnects. `everConnected` already
|
|
135
|
+
* knew a tab HAD been here; without this the diagnosis could not say WHERE, so a server error
|
|
136
|
+
* and a closed tab read as the same empty list (#808). Absent when nothing was ever connected,
|
|
137
|
+
* or the ring has forgotten — inventing a URL is worse than the silence this exists to end.
|
|
138
|
+
*/
|
|
139
|
+
lastKnownUrl?: string;
|
|
131
140
|
}
|
|
132
141
|
/**
|
|
133
142
|
* Every branch ends with this, and `recoveryFor` keys on it to suppress the generic no-session
|
|
@@ -135,6 +144,13 @@ export interface NoSessionFacts {
|
|
|
135
144
|
* generic one that contradicts it.
|
|
136
145
|
*/
|
|
137
146
|
export declare const SELF_RECOVERING_MARKER = "Then call reticle_sessions again \u2014 it will appear within a second of the page loading.";
|
|
147
|
+
/**
|
|
148
|
+
* The last URL a connected session was on when it disappeared without a successor HELLO.
|
|
149
|
+
*
|
|
150
|
+
* Shared with the observation-lost verdict so the two surfaces cannot drift into naming different
|
|
151
|
+
* routes for the same teardown (#808).
|
|
152
|
+
*/
|
|
153
|
+
export declare function pageTornDownWhileOn(url: string): string;
|
|
138
154
|
/**
|
|
139
155
|
* The diagnosis, and the CODE for the branch that produced it.
|
|
140
156
|
*
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
|
|
19
19
|
* the hot resolve() path and this stays unit-testable.
|
|
20
20
|
*/
|
|
21
|
-
import { NoSessionReason } from '@reticlehq/core';
|
|
21
|
+
import { NoSessionReason } from '@reticlehq/core/telemetry';
|
|
22
22
|
import { leaseCaveat } from './lease-availability.js';
|
|
23
23
|
import { DEV_SERVER_PORTS } from '../cli/cli-port.js';
|
|
24
24
|
import { siblingListenerNote } from '../cli/sibling-ports.js';
|
|
@@ -75,13 +75,32 @@ function leaseAdvice(base, facts) {
|
|
|
75
75
|
const SELF_SERVE = 'You do not have to wait for the human: reticle_lease {action:"acquire", url} opens a browser ' +
|
|
76
76
|
'Reticle drives itself, and returns a sessionId you can use immediately (reach it with ' +
|
|
77
77
|
'reticle_run {tool:"reticle_lease"} if it is not advertised directly; release it when you finish).';
|
|
78
|
+
/** The hedge that is true for every vanished tab we did not watch leave. */
|
|
79
|
+
const TAB_GONE_WHAT = 'The tab was closed, navigated away, or hard-reloaded.';
|
|
78
80
|
/**
|
|
79
|
-
* The
|
|
81
|
+
* The last URL a connected session was on when it disappeared without a successor HELLO.
|
|
80
82
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
83
|
+
* Shared with the observation-lost verdict so the two surfaces cannot drift into naming different
|
|
84
|
+
* routes for the same teardown (#808).
|
|
85
|
+
*/
|
|
86
|
+
export function pageTornDownWhileOn(url) {
|
|
87
|
+
return `the page was torn down while on ${url}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* What happened to the tab, with the last URL named when we still hold it.
|
|
91
|
+
*
|
|
92
|
+
* Without the URL this is the sentence the field has always seen. With it, a route that tore the
|
|
93
|
+
* page down is no longer the same dead end as "the human closed the tab".
|
|
84
94
|
*/
|
|
95
|
+
function tabGoneWhat(url) {
|
|
96
|
+
if (undefined === url || '' === url)
|
|
97
|
+
return TAB_GONE_WHAT;
|
|
98
|
+
const torn = pageTornDownWhileOn(url);
|
|
99
|
+
const first = torn.charAt(0);
|
|
100
|
+
if ('' === first)
|
|
101
|
+
return TAB_GONE_WHAT;
|
|
102
|
+
return `${first.toUpperCase()}${torn.slice(1)}. ${TAB_GONE_WHAT}`;
|
|
103
|
+
}
|
|
85
104
|
/**
|
|
86
105
|
* The one cause that produces a PERFECTLY healthy everything and still never connects.
|
|
87
106
|
*
|
|
@@ -138,6 +157,13 @@ const RESTARTED_LEAD = 'no browser session connected. This daemon has served non
|
|
|
138
157
|
'durably. So the wiring is correct and the port is correct: what is failing now is the SDK ' +
|
|
139
158
|
'reaching initialise on the page. Re-running the install is the wrong move here: it cannot help ' +
|
|
140
159
|
'on a project that has demonstrably connected, and it can overwrite a working config.';
|
|
160
|
+
/**
|
|
161
|
+
* The ports the scan actually covers, rendered for the message.
|
|
162
|
+
*
|
|
163
|
+
* Derived from DEV_SERVER_PORTS rather than re-typed: a message that lists ports the scan does not
|
|
164
|
+
* check, or omits ones it does, is a new version of the same defect — a confident claim about
|
|
165
|
+
* evidence that was never gathered.
|
|
166
|
+
*/
|
|
141
167
|
const SCANNED_PORTS = [...DEV_SERVER_PORTS].join(', ');
|
|
142
168
|
/**
|
|
143
169
|
* How to say "open the app" to an agent that may have no `reticle` binary.
|
|
@@ -349,7 +375,7 @@ export function explainNoSession(facts) {
|
|
|
349
375
|
`carry on.${alreadyListeningClause(listening)} ${RETRY}`);
|
|
350
376
|
}
|
|
351
377
|
return reason(NoSessionReason.TAB_GONE, 'no browser session connected, but one WAS connected to this daemon earlier, so the wiring ' +
|
|
352
|
-
|
|
378
|
+
`is correct. ${tabGoneWhat(facts.lastKnownUrl)} Ask the human to reopen ` +
|
|
353
379
|
`the app (or run ${OPEN_CMD_BARE}), or reload the tab.${alreadyListeningClause(listening)} ` +
|
|
354
380
|
`${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`);
|
|
355
381
|
}
|
|
@@ -51,6 +51,20 @@ interface NextActionFacts {
|
|
|
51
51
|
* that works.
|
|
52
52
|
*/
|
|
53
53
|
previouslyConnected?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* A page reached this daemon and was REFUSED on the pairing token.
|
|
56
|
+
*
|
|
57
|
+
* Positive evidence, and the only fact here that proves the app is both running and instrumented:
|
|
58
|
+
* nothing dials the bridge but an SDK, so a refused hello means the wiring works and the daemon
|
|
59
|
+
* would not serve it. That happens whenever an IDE registers the MCP server GLOBALLY -- the daemon
|
|
60
|
+
* starts with a cwd of `/` or `$HOME`, finds no `.reticle.json` there, and refuses every page
|
|
61
|
+
* while `doctor` run from the app directory reports the project wired (#685).
|
|
62
|
+
*
|
|
63
|
+
* Without it, `initialized: false` plus a listening port reads as "the app may carry no SDK" and
|
|
64
|
+
* the action handed back is `reticle init` -- over a config that already works, which is the one
|
|
65
|
+
* action that cannot help and can overwrite it.
|
|
66
|
+
*/
|
|
67
|
+
authRefused?: boolean;
|
|
54
68
|
}
|
|
55
69
|
export declare function nextActionFor(facts: NextActionFacts): NoSessionNextAction;
|
|
56
70
|
/** The same action as one sentence, for the prose message the human reads. */
|
|
@@ -103,6 +103,24 @@ export function nextActionFor(facts) {
|
|
|
103
103
|
// a second port is the exact confusion the probe exists to prevent.
|
|
104
104
|
const ports = facts.listening.join(', ');
|
|
105
105
|
const only = 1 === facts.listening.length ? facts.listening[0] : undefined;
|
|
106
|
+
// A refused dial outranks the absence of a local config, for the same reason `previouslyConnected`
|
|
107
|
+
// does: it is positive evidence about the app, and `initialized` only ever answers "is there a
|
|
108
|
+
// `.reticle.json` in the ONE directory this daemon stands in". A globally-registered daemon stands
|
|
109
|
+
// in `/` or `$HOME`, so that answer is no for every project it serves -- and every page it turns
|
|
110
|
+
// away is proof that the project it turned away is wired.
|
|
111
|
+
if (!facts.initialized && true === facts.authRefused) {
|
|
112
|
+
return {
|
|
113
|
+
action: NoSessionAction.OPEN_APP,
|
|
114
|
+
...(only === undefined ? {} : { port: only }),
|
|
115
|
+
reason: `a page dialled this daemon and was refused on the pairing token, so the app IS running ` +
|
|
116
|
+
`and instrumented (${ports} listening) — this is a SCOPE problem, not an install problem. ` +
|
|
117
|
+
'This daemon was started outside the project (an IDE that registers the MCP server ' +
|
|
118
|
+
'globally starts it in `/` or your home directory), so it has no `.reticle.json` to match ' +
|
|
119
|
+
'the page against. Do NOT run `reticle init`: the config it would write is already correct ' +
|
|
120
|
+
'somewhere else. Take a context you own instead with reticle_lease {action:"acquire", url, ' +
|
|
121
|
+
"projectId}, or restart the daemon from the app's own directory.",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
106
124
|
if (!facts.initialized && true !== facts.previouslyConnected) {
|
|
107
125
|
return {
|
|
108
126
|
action: NoSessionAction.RUN_INIT,
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* daemon that outlives the agent by hours has no business scanning ports it does not need.
|
|
11
11
|
*/
|
|
12
12
|
import { probeDevServers, probeDevServerStates } from './dev-server-probe.js';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { registeredElsewhere } from './registered-projects.js';
|
|
13
15
|
import { explainNoSession } from './no-session-diagnosis.js';
|
|
14
16
|
import { detectDevCommand } from './dev-command.js';
|
|
15
17
|
import { nextActionFor, renderNextAction } from './no-session-next-action.js';
|
|
@@ -21,6 +23,7 @@ import { daemonsServingProjectElsewhere, splitBrainNote } from '../daemon/daemon
|
|
|
21
23
|
import { stallUptime } from './stall-clock.js';
|
|
22
24
|
import { probeDaemon } from '../mcp/mcp-proxy.js';
|
|
23
25
|
import { findOccupiedSiblings } from '../cli/sibling-ports.js';
|
|
26
|
+
import { WS_CLOSE_REASON } from '../bridge/bridge.js';
|
|
24
27
|
/** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
|
|
25
28
|
const REFRESH_MS = 15_000;
|
|
26
29
|
/** The URL an auto-attach opens. `localhost` for the same reason the probe uses it: both stacks. */
|
|
@@ -81,15 +84,32 @@ export function startNoSessionWatch(options) {
|
|
|
81
84
|
return { initialized };
|
|
82
85
|
const discovery = discoverProjectConfigs(directory);
|
|
83
86
|
const elsewhere = discovery.found.filter((config) => config.directory !== directory);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
: {
|
|
87
|
+
if (elsewhere.length > 0) {
|
|
88
|
+
return {
|
|
87
89
|
initialized,
|
|
88
90
|
configsElsewhere: elsewhere.map((config) => ({
|
|
89
91
|
directory: config.directory,
|
|
90
92
|
...(config.projectId === undefined ? {} : { projectId: config.projectId }),
|
|
91
93
|
})),
|
|
92
94
|
};
|
|
95
|
+
}
|
|
96
|
+
// The walk found nothing — which is the honest answer from a daemon standing at `/` or `$HOME`,
|
|
97
|
+
// where several IDEs start a GLOBALLY registered MCP server. There is nothing above `/` and no
|
|
98
|
+
// repo root at either, so the walk cannot reach a project it has nonetheless paired with before.
|
|
99
|
+
// The registry can. See registeredElsewhere: "I am standing in the wrong place" and "you never
|
|
100
|
+
// installed this" are opposite diagnoses with opposite fixes, and without this we gave the
|
|
101
|
+
// second one — six times, on projects that were already correctly instrumented.
|
|
102
|
+
const registered = registeredElsewhere(homedir(), directory);
|
|
103
|
+
if (registered.length > 0) {
|
|
104
|
+
return {
|
|
105
|
+
initialized,
|
|
106
|
+
configsElsewhere: registered.map((project) => ({
|
|
107
|
+
directory: project.directory,
|
|
108
|
+
...(project.projectId === undefined ? {} : { projectId: project.projectId }),
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { initialized, searchedDirectories: discovery.searched };
|
|
93
113
|
};
|
|
94
114
|
// Read at boot: the daemon's own identity does not change under it, and this is the key the
|
|
95
115
|
// durable bit is stored under.
|
|
@@ -185,6 +205,19 @@ export function startNoSessionWatch(options) {
|
|
|
185
205
|
refresh();
|
|
186
206
|
const timer = setInterval(refresh, REFRESH_MS);
|
|
187
207
|
timer.unref();
|
|
208
|
+
/**
|
|
209
|
+
* Did the most recent bridge-initiated close refuse a page on its token?
|
|
210
|
+
*
|
|
211
|
+
* The bridge records it (`noteClosure(WS_CLOSE_REASON.AUTH_FAILED)`) and nothing read it as a
|
|
212
|
+
* DIAGNOSIS. It is the one fact that proves an app is running and instrumented: only an SDK dials
|
|
213
|
+
* the bridge, so a refused hello means the wiring works and this daemon would not serve it.
|
|
214
|
+
*
|
|
215
|
+
* Called optionally because this watch is constructed against a structural slice of the manager,
|
|
216
|
+
* and several callers pass a double that predates `lastClosure`. A manager that cannot answer has
|
|
217
|
+
* recorded no refusal, which falls through to the behaviour that was there before -- the safe
|
|
218
|
+
* direction for a fact whose only job is to SUPPRESS an `init` suggestion.
|
|
219
|
+
*/
|
|
220
|
+
const lastCloseWasAuthFailure = () => options.sessions.lastClosure?.()?.reason === WS_CLOSE_REASON.AUTH_FAILED;
|
|
188
221
|
const nextAction = (scope) => {
|
|
189
222
|
const split = splitBrain();
|
|
190
223
|
return nextActionFor({
|
|
@@ -192,6 +225,9 @@ export function startNoSessionWatch(options) {
|
|
|
192
225
|
initialized: scope.initialized,
|
|
193
226
|
...(scope.configsElsewhere === undefined ? {} : { configsElsewhere: scope.configsElsewhere }),
|
|
194
227
|
previouslyConnected: connectedBefore(),
|
|
228
|
+
// Read when asked, like every other fact here: a page can dial at any moment, and a daemon
|
|
229
|
+
// that cached "nothing has been refused" at boot would keep saying so.
|
|
230
|
+
authRefused: lastCloseWasAuthFailure(),
|
|
195
231
|
...(split === undefined ? {} : { splitBrain: split }),
|
|
196
232
|
listening,
|
|
197
233
|
// Read when asked, like everything else here: a `package.json` can gain a dev script, and a
|
|
@@ -236,6 +272,13 @@ export function startNoSessionWatch(options) {
|
|
|
236
272
|
const departed = options.sessions.lastDeparted();
|
|
237
273
|
return departed === undefined ? false : (options.wasReapedLease?.(departed) ?? false);
|
|
238
274
|
})(),
|
|
275
|
+
// The URL of the session that went away, when the tombstone still holds it. Optional: a
|
|
276
|
+
// stub SessionManager in tests has no lastKnown, and a daemon that never saw a session
|
|
277
|
+
// has none to name.
|
|
278
|
+
...(() => {
|
|
279
|
+
const known = options.sessions.lastKnown?.();
|
|
280
|
+
return undefined === known ? {} : { lastKnownUrl: known.url };
|
|
281
|
+
})(),
|
|
239
282
|
// How long this daemon has been waiting with no app. The diagnosis uses it to surface
|
|
240
283
|
// "install never finished" — the same condition telemetry already knows about.
|
|
241
284
|
...(() => {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** One project this machine has paired before, in the shape the no-session facts already carry. */
|
|
2
|
+
export interface RegisteredProject {
|
|
3
|
+
directory: string;
|
|
4
|
+
projectId?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Projects this machine has paired before, read from `~/.reticle/projects.json`.
|
|
8
|
+
*
|
|
9
|
+
* The no-session diagnosis learns where projects are by WALKING: up from the daemon's cwd, then
|
|
10
|
+
* across the declared workspaces of whatever repo root it lands in. That walk is the right first
|
|
11
|
+
* answer and it has one blind spot, which happens to be the most reported install problem we have.
|
|
12
|
+
*
|
|
13
|
+
* Several IDEs register the MCP server GLOBALLY, so the daemon starts with a cwd of `/` or `$HOME`.
|
|
14
|
+
* From there the walk finds nothing — there is nothing above `/`, and no repo root at either — so
|
|
15
|
+
* `configsElsewhere` comes back empty and the diagnosis falls through to the branches that reason
|
|
16
|
+
* from "no config anywhere", whose advice is `reticle init`. Reported six separate times, on a
|
|
17
|
+
* project that was already correctly instrumented; one reporter burned a turn re-running `init` over
|
|
18
|
+
* a working config while `doctor`, run from the app directory, said `project ✓ wired here`.
|
|
19
|
+
*
|
|
20
|
+
* The registry is the evidence the walk cannot reach: every project that has ever paired with this
|
|
21
|
+
* machine is in it, with its directory. A daemon that cannot SEE a project can still KNOW of it, and
|
|
22
|
+
* "I am standing in the wrong place" is a completely different diagnosis from "you never installed
|
|
23
|
+
* this" — opposite fixes, and we were giving the second one.
|
|
24
|
+
*
|
|
25
|
+
* Read fresh rather than cached: a project can pair for the first time while this daemon is up, and
|
|
26
|
+
* a stale empty read would keep the wrong diagnosis for the life of the process. Fails soft to
|
|
27
|
+
* empty, because a diagnosis is the last thing that should throw.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registeredProjects(home: string): RegisteredProject[];
|
|
30
|
+
/**
|
|
31
|
+
* Is this daemon standing somewhere a project could never live?
|
|
32
|
+
*
|
|
33
|
+
* THE NARROWING THAT MAKES THIS SAFE, and it is not optional. An empty config walk has two causes:
|
|
34
|
+
* the daemon is parked outside every project (the global-registration case this fixes), or it is
|
|
35
|
+
* standing in a real directory that genuinely has not been wired yet (a new project, where
|
|
36
|
+
* `reticle init` is exactly the right advice). Both look identical from the walk.
|
|
37
|
+
*
|
|
38
|
+
* Consulting the registry for BOTH would break the second: any developer machine has registered
|
|
39
|
+
* projects, so a brand-new unwired project would be told "your config is elsewhere, restart the
|
|
40
|
+
* daemon there" instead of "run init". That trades the reported false negative for an unreported
|
|
41
|
+
* one, on the more common path — a bad trade, and caught here by two existing tests going red.
|
|
42
|
+
*
|
|
43
|
+
* The filesystem root and the user's home directory are the discriminator. Neither is ever an app
|
|
44
|
+
* directory, and both are exactly where an IDE's global MCP registration starts a daemon. Anywhere
|
|
45
|
+
* else, an empty walk still means what it always meant.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isUnscopedRoot(directory: string, home: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The registered projects that are NOT the directory this daemon is standing in.
|
|
50
|
+
*
|
|
51
|
+
* Same rule the config walk applies: a project at the daemon's own cwd is not "elsewhere", and
|
|
52
|
+
* reporting it as such would tell a correctly-scoped daemon to go and look somewhere else.
|
|
53
|
+
*/
|
|
54
|
+
export declare function registeredElsewhere(home: string, directory: string): RegisteredProject[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, parse, resolve } from 'node:path';
|
|
3
|
+
import { PROJECT_REGISTRY_FILE, ReticleDir, parseProjectRegistry, projectCandidates, } from '@reticlehq/core';
|
|
4
|
+
/**
|
|
5
|
+
* Projects this machine has paired before, read from `~/.reticle/projects.json`.
|
|
6
|
+
*
|
|
7
|
+
* The no-session diagnosis learns where projects are by WALKING: up from the daemon's cwd, then
|
|
8
|
+
* across the declared workspaces of whatever repo root it lands in. That walk is the right first
|
|
9
|
+
* answer and it has one blind spot, which happens to be the most reported install problem we have.
|
|
10
|
+
*
|
|
11
|
+
* Several IDEs register the MCP server GLOBALLY, so the daemon starts with a cwd of `/` or `$HOME`.
|
|
12
|
+
* From there the walk finds nothing — there is nothing above `/`, and no repo root at either — so
|
|
13
|
+
* `configsElsewhere` comes back empty and the diagnosis falls through to the branches that reason
|
|
14
|
+
* from "no config anywhere", whose advice is `reticle init`. Reported six separate times, on a
|
|
15
|
+
* project that was already correctly instrumented; one reporter burned a turn re-running `init` over
|
|
16
|
+
* a working config while `doctor`, run from the app directory, said `project ✓ wired here`.
|
|
17
|
+
*
|
|
18
|
+
* The registry is the evidence the walk cannot reach: every project that has ever paired with this
|
|
19
|
+
* machine is in it, with its directory. A daemon that cannot SEE a project can still KNOW of it, and
|
|
20
|
+
* "I am standing in the wrong place" is a completely different diagnosis from "you never installed
|
|
21
|
+
* this" — opposite fixes, and we were giving the second one.
|
|
22
|
+
*
|
|
23
|
+
* Read fresh rather than cached: a project can pair for the first time while this daemon is up, and
|
|
24
|
+
* a stale empty read would keep the wrong diagnosis for the life of the process. Fails soft to
|
|
25
|
+
* empty, because a diagnosis is the last thing that should throw.
|
|
26
|
+
*/
|
|
27
|
+
export function registeredProjects(home) {
|
|
28
|
+
try {
|
|
29
|
+
const path = join(home, ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
|
|
30
|
+
if (!existsSync(path))
|
|
31
|
+
return [];
|
|
32
|
+
const registry = parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')));
|
|
33
|
+
return projectCandidates(registry).map((candidate) => ({
|
|
34
|
+
directory: candidate.directory,
|
|
35
|
+
projectId: candidate.projectId,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Is this daemon standing somewhere a project could never live?
|
|
44
|
+
*
|
|
45
|
+
* THE NARROWING THAT MAKES THIS SAFE, and it is not optional. An empty config walk has two causes:
|
|
46
|
+
* the daemon is parked outside every project (the global-registration case this fixes), or it is
|
|
47
|
+
* standing in a real directory that genuinely has not been wired yet (a new project, where
|
|
48
|
+
* `reticle init` is exactly the right advice). Both look identical from the walk.
|
|
49
|
+
*
|
|
50
|
+
* Consulting the registry for BOTH would break the second: any developer machine has registered
|
|
51
|
+
* projects, so a brand-new unwired project would be told "your config is elsewhere, restart the
|
|
52
|
+
* daemon there" instead of "run init". That trades the reported false negative for an unreported
|
|
53
|
+
* one, on the more common path — a bad trade, and caught here by two existing tests going red.
|
|
54
|
+
*
|
|
55
|
+
* The filesystem root and the user's home directory are the discriminator. Neither is ever an app
|
|
56
|
+
* directory, and both are exactly where an IDE's global MCP registration starts a daemon. Anywhere
|
|
57
|
+
* else, an empty walk still means what it always meant.
|
|
58
|
+
*/
|
|
59
|
+
export function isUnscopedRoot(directory, home) {
|
|
60
|
+
const at = resolve(directory);
|
|
61
|
+
return at === resolve(home) || at === parse(at).root;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The registered projects that are NOT the directory this daemon is standing in.
|
|
65
|
+
*
|
|
66
|
+
* Same rule the config walk applies: a project at the daemon's own cwd is not "elsewhere", and
|
|
67
|
+
* reporting it as such would tell a correctly-scoped daemon to go and look somewhere else.
|
|
68
|
+
*/
|
|
69
|
+
export function registeredElsewhere(home, directory) {
|
|
70
|
+
if (!isUnscopedRoot(directory, home))
|
|
71
|
+
return [];
|
|
72
|
+
return registeredProjects(home).filter((project) => project.directory !== directory);
|
|
73
|
+
}
|
|
@@ -24,7 +24,7 @@ export interface ReviewMark {
|
|
|
24
24
|
/** Tests only — assertions on `m1` must not depend on which test ran first. */
|
|
25
25
|
export declare function resetMarkIdsForTest(): void;
|
|
26
26
|
/** What `resolve` actually did, including WHICH mark — so a caller can verify it hit the right one. */
|
|
27
|
-
|
|
27
|
+
interface ResolveOutcome {
|
|
28
28
|
resolved: boolean;
|
|
29
29
|
id: string;
|
|
30
30
|
/** The retired mark's note. Absent when nothing was resolved. */
|
|
@@ -56,3 +56,4 @@ export declare class ReviewStore {
|
|
|
56
56
|
*/
|
|
57
57
|
resolveDetail(id: string): ResolveOutcome;
|
|
58
58
|
}
|
|
59
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BrowserBrand } from '@reticlehq/core';
|
|
1
|
+
import { BrowserBrand, type ReticleEvent } from '@reticlehq/core';
|
|
2
2
|
import type { Session } from './session.js';
|
|
3
3
|
/**
|
|
4
4
|
* The health block spliced onto act/assert results. Defined here rather than in session.ts: this is
|
|
@@ -10,7 +10,58 @@ export interface SessionHealth {
|
|
|
10
10
|
focused: boolean;
|
|
11
11
|
/** present only when hidden/throttled — points at the `reticle drive` escape hatch. */
|
|
12
12
|
recommendation?: string;
|
|
13
|
+
/**
|
|
14
|
+
* How long the OLDEST unanswered request has been in flight, when that is long enough to matter.
|
|
15
|
+
*
|
|
16
|
+
* Present only past `PENDING_NAVIGATION_NOTICE_MS`, so a healthy session costs nothing. Both
|
|
17
|
+
* reporters on the hung-server issue said this alone would have been enough for them: one sat on
|
|
18
|
+
* an RSC fetch stuck pending for 170+ seconds across many calls, the other on a dev server that
|
|
19
|
+
* accepted connections and never sent a body. Reticle held the evidence both times and the truth
|
|
20
|
+
* was eventually established with `curl`.
|
|
21
|
+
*/
|
|
22
|
+
pendingNavigationMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Present only when the page's SDK version differs from the daemon's — see version-skew.ts.
|
|
25
|
+
*
|
|
26
|
+
* On the HEALTH block, not only on `reticle_sessions`, because skew causes SILENT action failures
|
|
27
|
+
* and the fields it contradicts ride on the act result. Reported from the field: `act_and_wait`
|
|
28
|
+
* returning `dispatched: true, settled: true, domMutatedWithin: 12-40ms` while React state never
|
|
29
|
+
* changed, across eight attempts and three interaction strategies. The banner existed the whole
|
|
30
|
+
* time — on `reticle_sessions` and `reticle_lease`, which is not the surface an agent reads on
|
|
31
|
+
* every call.
|
|
32
|
+
*/
|
|
33
|
+
versionSkew?: string;
|
|
13
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* How long a request may be in flight before it is worth reporting on session health.
|
|
37
|
+
*
|
|
38
|
+
* A drive always has requests in the air, so a low bar puts a scary number on every healthy session
|
|
39
|
+
* and trains agents to ignore the field — the same way a guard that fires on routine controls trains
|
|
40
|
+
* them to pass `confirmDangerous` reflexively.
|
|
41
|
+
*
|
|
42
|
+
* 10s, and it is a HEURISTIC with no clean separation available: "a slow endpoint" and "a server
|
|
43
|
+
* that will never answer" are the same observation until one of them finishes. The number is chosen
|
|
44
|
+
* to sit between the two things actually measured, not derived from anything.
|
|
45
|
+
*
|
|
46
|
+
* Below it: this repo's own battery runs its API with `REFLECT_MS=6000` on purpose, and at the 5s
|
|
47
|
+
* this first shipped with, a healthy app with one slow route was permanently non-nominal and carried
|
|
48
|
+
* a scary number on every result.
|
|
49
|
+
*
|
|
50
|
+
* Above it: both reported wedges — 10.4s on one, 170s+ on the other. 10.4s clears this by 400ms,
|
|
51
|
+
* which is thin, and an app with an 8-second endpoint will not be warned about a wedge until 10s.
|
|
52
|
+
* That is the trade: an over-firing field gets ignored, and a field nobody reads catches nothing.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PENDING_NAVIGATION_NOTICE_MS = 10000;
|
|
55
|
+
/**
|
|
56
|
+
* The age of the oldest request that STARTED and never completed, or undefined when there is none
|
|
57
|
+
* worth reporting.
|
|
58
|
+
*
|
|
59
|
+
* Matched on request id, the way `detectHungRequests` and the route oracle's `unansweredIn` do: a
|
|
60
|
+
* NET_PENDING with no NET_REQUEST carrying the same id. Three readings of one fact is two too many,
|
|
61
|
+
* but they read different windows — this one is the whole session, which is the point: a wedge that
|
|
62
|
+
* began before the current action is exactly the case a per-window reading cannot see.
|
|
63
|
+
*/
|
|
64
|
+
export declare function pendingNavigationMs(events: readonly ReticleEvent[], nowMs: number): number | undefined;
|
|
14
65
|
/** The evidence-completeness block spliced onto observe/network/console results. */
|
|
15
66
|
interface BufferEnvelope {
|
|
16
67
|
buffer?: {
|
|
@@ -49,10 +100,20 @@ export declare function refuseIfThrottled(session: Session, refuse: unknown): vo
|
|
|
49
100
|
* Suffix the starvation note onto a FAILED predicate verdict when the tab is throttled. A pass is
|
|
50
101
|
* returned untouched — starvation is context for a failure, not a reason to doubt a hold. Pure and
|
|
51
102
|
* generic over the verdict shape so assert/wait_for/act_and_wait share one rule.
|
|
103
|
+
*
|
|
104
|
+
* The sentence FOLLOWS the field, and does not decide for itself. `annotateThrottledMiss`
|
|
105
|
+
* (predicate.ts) owns the question of whether this failure was reached by not having seen
|
|
106
|
+
* something, which is the only reading a starved tab casts doubt on; it stamps
|
|
107
|
+
* `THROTTLED_STARVED_NOTE` when it was. Deciding again here got two answers to one question:
|
|
108
|
+
* an `absent: true` assertion that MATCHED 13 elements was graded honestly in the field an agent
|
|
109
|
+
* gates on, and told in prose that the tab may never have rendered. It also doubled up on a failure
|
|
110
|
+
* that already carried a more specific reason (an unreadable locator, a superseded window), where
|
|
111
|
+
* the concrete diagnosis is the one that should lead.
|
|
52
112
|
*/
|
|
53
113
|
export declare function annotateStarvedFailure<V extends {
|
|
54
114
|
pass?: boolean;
|
|
55
115
|
failureReason?: string;
|
|
116
|
+
inconclusive?: string;
|
|
56
117
|
}>(session: Session, verdict: V): V;
|
|
57
118
|
/** The page's own visibility/runtime report, narrowed out of an untrusted PAGE_HEALTH payload. */
|
|
58
119
|
interface HealthReport {
|