@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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* cloud copy is an enhancement, so a push error is logged and swallowed.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
-
import type
|
|
12
|
+
import { type FlowFile, type ReticleVerificationRun, type RunRecord, type VerifyProgressEvent } from '@reticlehq/core';
|
|
13
13
|
/** Env var names — the presence of BOTH is what "logged in" means for cloud sync. */
|
|
14
14
|
export declare const CloudEnv: {
|
|
15
15
|
readonly URL: "RETICLE_CLOUD_URL";
|
|
@@ -115,21 +115,21 @@ declare const ServerVerificationSchema: z.ZodObject<{
|
|
|
115
115
|
}>, "many">;
|
|
116
116
|
summary: z.ZodString;
|
|
117
117
|
}, "strip", z.ZodTypeAny, {
|
|
118
|
+
verificationId: string;
|
|
118
119
|
verdict: string | null;
|
|
119
120
|
flows: {
|
|
120
121
|
status: string;
|
|
121
122
|
name: string;
|
|
122
123
|
}[];
|
|
123
124
|
summary: string;
|
|
124
|
-
verificationId: string;
|
|
125
125
|
}, {
|
|
126
|
+
verificationId: string;
|
|
126
127
|
verdict: string | null;
|
|
127
128
|
flows: {
|
|
128
129
|
status: string;
|
|
129
130
|
name: string;
|
|
130
131
|
}[];
|
|
131
132
|
summary: string;
|
|
132
|
-
verificationId: string;
|
|
133
133
|
}>;
|
|
134
134
|
export type ServerVerification = z.infer<typeof ServerVerificationSchema>;
|
|
135
135
|
/**
|
|
@@ -144,4 +144,47 @@ export declare function submitServerVerification(body: {
|
|
|
144
144
|
flows: string[];
|
|
145
145
|
source: string;
|
|
146
146
|
}, config: CloudConfig | null, fetchImpl: FetchPostJsonLike): Promise<ServerVerification | null>;
|
|
147
|
+
/**
|
|
148
|
+
* Narrate a run in flight to the dashboard.
|
|
149
|
+
*
|
|
150
|
+
* ## Why this exists
|
|
151
|
+
*
|
|
152
|
+
* Everything else here syncs a FINISHED artifact. That is the right shape for evidence and the wrong
|
|
153
|
+
* shape for the minutes a verification is actually running: a browser launches, flows replay one at
|
|
154
|
+
* a time, and until the run ends the dashboard has nothing to show but a spinner. A run that is
|
|
155
|
+
* working and a run that has died are indistinguishable for the whole of its duration, and somebody
|
|
156
|
+
* watching sat on exactly that ambiguity for fifteen minutes.
|
|
157
|
+
*
|
|
158
|
+
* ## Why it is batched
|
|
159
|
+
*
|
|
160
|
+
* One request per flow is one request per flow. A fifty-flow suite would open a hundred connections
|
|
161
|
+
* to say things that are only interesting in aggregate, so events accumulate and go out on a timer.
|
|
162
|
+
* Two seconds is well under the time a person waits before deciding a page is broken, and well over
|
|
163
|
+
* the time a fast flow takes — so a burst of quick replays becomes one request, not twenty.
|
|
164
|
+
*
|
|
165
|
+
* ## Why it can never matter
|
|
166
|
+
*
|
|
167
|
+
* Opt-in like every other cloud call (no credentials → no-op, nothing leaves the machine), and
|
|
168
|
+
* best-effort at every layer: a failed flush is dropped, never retried and never surfaced. This is
|
|
169
|
+
* narration. The run artifact is the record, it syncs separately, and nothing here is graded or
|
|
170
|
+
* allowed to influence a verdict or an exit code.
|
|
171
|
+
*/
|
|
172
|
+
export declare function syncProgressToCloud(runId: string, events: readonly VerifyProgressEvent[], config: CloudConfig | null, fetchImpl: FetchLike): Promise<SyncResult>;
|
|
173
|
+
/** How long events accumulate before a flush. See the batching note above. */
|
|
174
|
+
export declare const PROGRESS_FLUSH_MS = 2000;
|
|
175
|
+
/**
|
|
176
|
+
* A listener that buffers progress and ships it on a timer.
|
|
177
|
+
*
|
|
178
|
+
* Returned with its own `flush` and `stop` so the caller can push the last events out when the run
|
|
179
|
+
* ends — otherwise the most interesting event of all, the one saying it finished, waits for a timer
|
|
180
|
+
* that is about to be cleared.
|
|
181
|
+
*
|
|
182
|
+
* The timer is `unref`'d where the runtime supports it: a narration timer must never be the reason a
|
|
183
|
+
* CLI process stays alive after its work is done.
|
|
184
|
+
*/
|
|
185
|
+
export declare function createProgressReporter(runId: string, config: CloudConfig | null, fetchImpl: FetchLike, flushMs?: number): {
|
|
186
|
+
onProgress: (event: VerifyProgressEvent) => void;
|
|
187
|
+
flush: () => Promise<void>;
|
|
188
|
+
stop: () => void;
|
|
189
|
+
};
|
|
147
190
|
export {};
|
package/dist/cloud/cloud-sync.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* cloud copy is an enhancement, so a push error is logged and swallowed.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import { VERIFY_PROGRESS_MAX_EVENTS, } from '@reticlehq/core';
|
|
12
13
|
/** Env var names — the presence of BOTH is what "logged in" means for cloud sync. */
|
|
13
14
|
export const CloudEnv = {
|
|
14
15
|
URL: 'RETICLE_CLOUD_URL',
|
|
@@ -20,6 +21,7 @@ const CLOUD_RUNS_PATH = '/v1/runs';
|
|
|
20
21
|
const CLOUD_PROJECT_RUNS_PATH = '/v1/project/runs';
|
|
21
22
|
const CLOUD_PROJECT_REGRESSION_PATH = '/v1/project/regression';
|
|
22
23
|
const CLOUD_VERIFICATIONS_PATH = '/v1/verifications';
|
|
24
|
+
const CLOUD_PROGRESS_PATH = '/v1/connect/progress';
|
|
23
25
|
/** Resolve cloud credentials from the environment, or null when not logged in (sync disabled). */
|
|
24
26
|
export function resolveCloudConfig(env) {
|
|
25
27
|
const url = env[CloudEnv.URL];
|
|
@@ -236,3 +238,117 @@ export async function submitServerVerification(body, config, fetchImpl) {
|
|
|
236
238
|
return null;
|
|
237
239
|
}
|
|
238
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Narrate a run in flight to the dashboard.
|
|
243
|
+
*
|
|
244
|
+
* ## Why this exists
|
|
245
|
+
*
|
|
246
|
+
* Everything else here syncs a FINISHED artifact. That is the right shape for evidence and the wrong
|
|
247
|
+
* shape for the minutes a verification is actually running: a browser launches, flows replay one at
|
|
248
|
+
* a time, and until the run ends the dashboard has nothing to show but a spinner. A run that is
|
|
249
|
+
* working and a run that has died are indistinguishable for the whole of its duration, and somebody
|
|
250
|
+
* watching sat on exactly that ambiguity for fifteen minutes.
|
|
251
|
+
*
|
|
252
|
+
* ## Why it is batched
|
|
253
|
+
*
|
|
254
|
+
* One request per flow is one request per flow. A fifty-flow suite would open a hundred connections
|
|
255
|
+
* to say things that are only interesting in aggregate, so events accumulate and go out on a timer.
|
|
256
|
+
* Two seconds is well under the time a person waits before deciding a page is broken, and well over
|
|
257
|
+
* the time a fast flow takes — so a burst of quick replays becomes one request, not twenty.
|
|
258
|
+
*
|
|
259
|
+
* ## Why it can never matter
|
|
260
|
+
*
|
|
261
|
+
* Opt-in like every other cloud call (no credentials → no-op, nothing leaves the machine), and
|
|
262
|
+
* best-effort at every layer: a failed flush is dropped, never retried and never surfaced. This is
|
|
263
|
+
* narration. The run artifact is the record, it syncs separately, and nothing here is graded or
|
|
264
|
+
* allowed to influence a verdict or an exit code.
|
|
265
|
+
*/
|
|
266
|
+
export async function syncProgressToCloud(runId, events, config, fetchImpl) {
|
|
267
|
+
if (null === config)
|
|
268
|
+
return { outcome: SyncOutcome.SKIPPED };
|
|
269
|
+
if (0 === events.length)
|
|
270
|
+
return { outcome: SyncOutcome.SKIPPED };
|
|
271
|
+
try {
|
|
272
|
+
const res = await fetchImpl(`${config.url}${CLOUD_PROGRESS_PATH}`, {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
'content-type': 'application/json',
|
|
276
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
277
|
+
},
|
|
278
|
+
// Bounded on the way OUT as well as in the schema: a receiver that rejects an oversized batch
|
|
279
|
+
// would drop the whole window, and the newest events are the ones somebody is waiting on.
|
|
280
|
+
body: JSON.stringify({ runId, events: events.slice(-VERIFY_PROGRESS_MAX_EVENTS) }),
|
|
281
|
+
});
|
|
282
|
+
return res.ok
|
|
283
|
+
? { outcome: SyncOutcome.SYNCED, status: res.status }
|
|
284
|
+
: { outcome: SyncOutcome.FAILED, status: res.status };
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
return { outcome: SyncOutcome.FAILED, error: err instanceof Error ? err.message : String(err) };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/** How long events accumulate before a flush. See the batching note above. */
|
|
291
|
+
export const PROGRESS_FLUSH_MS = 2_000;
|
|
292
|
+
/**
|
|
293
|
+
* A listener that buffers progress and ships it on a timer.
|
|
294
|
+
*
|
|
295
|
+
* Returned with its own `flush` and `stop` so the caller can push the last events out when the run
|
|
296
|
+
* ends — otherwise the most interesting event of all, the one saying it finished, waits for a timer
|
|
297
|
+
* that is about to be cleared.
|
|
298
|
+
*
|
|
299
|
+
* The timer is `unref`'d where the runtime supports it: a narration timer must never be the reason a
|
|
300
|
+
* CLI process stays alive after its work is done.
|
|
301
|
+
*/
|
|
302
|
+
export function createProgressReporter(runId, config, fetchImpl, flushMs = PROGRESS_FLUSH_MS) {
|
|
303
|
+
/*
|
|
304
|
+
* The WHOLE window, re-sent each flush — not the events since the last one.
|
|
305
|
+
*
|
|
306
|
+
* The receiver REPLACES the row it keeps for a project rather than appending, because one row per
|
|
307
|
+
* project is what stops narration accumulating into a second, ungraded history of the run. A
|
|
308
|
+
* client that sent deltas against that would destroy its own story: each flush would overwrite the
|
|
309
|
+
* row with only the handful of events since the previous one, and a finished run would be
|
|
310
|
+
* represented by whatever its last two seconds happened to contain.
|
|
311
|
+
*
|
|
312
|
+
* Found by running a real verification and watching the dashboard freeze on flow 2 of 3.
|
|
313
|
+
*
|
|
314
|
+
* Re-sending is cheap and idempotent: the window is bounded, the events are tiny, and a replace is
|
|
315
|
+
* the same operation however many times it arrives. `dirty` keeps an unchanged window off the
|
|
316
|
+
* wire, so a run that has gone quiet stops posting rather than resending forever.
|
|
317
|
+
*/
|
|
318
|
+
/* Named `accumulated`, not `window`: this is Node, and shadowing the global reads badly. */
|
|
319
|
+
const accumulated = [];
|
|
320
|
+
let dirty = false;
|
|
321
|
+
let timer = null;
|
|
322
|
+
const flush = async () => {
|
|
323
|
+
if (!dirty || 0 === accumulated.length)
|
|
324
|
+
return;
|
|
325
|
+
// Cleared BEFORE the await so a slow flush does not let the next tick send the same window
|
|
326
|
+
// again; a genuinely new event during the send re-sets it and the next tick picks it up.
|
|
327
|
+
dirty = false;
|
|
328
|
+
await syncProgressToCloud(runId, [...accumulated], config, fetchImpl).catch(() => undefined);
|
|
329
|
+
};
|
|
330
|
+
const start = () => {
|
|
331
|
+
if (null !== timer || null === config)
|
|
332
|
+
return;
|
|
333
|
+
timer = setInterval(() => void flush(), flushMs);
|
|
334
|
+
timer.unref?.();
|
|
335
|
+
};
|
|
336
|
+
return {
|
|
337
|
+
onProgress: (event) => {
|
|
338
|
+
if (null === config)
|
|
339
|
+
return; // not logged in: nothing is buffered and nothing is sent
|
|
340
|
+
// Bounded, keeping the NEWEST: they are the ones somebody is waiting on.
|
|
341
|
+
if (accumulated.length >= VERIFY_PROGRESS_MAX_EVENTS)
|
|
342
|
+
accumulated.shift();
|
|
343
|
+
accumulated.push(event);
|
|
344
|
+
dirty = true;
|
|
345
|
+
start();
|
|
346
|
+
},
|
|
347
|
+
flush,
|
|
348
|
+
stop: () => {
|
|
349
|
+
if (null !== timer)
|
|
350
|
+
clearInterval(timer);
|
|
351
|
+
timer = null;
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A list rather than three hand-written blocks so adding a fourth is one line and cannot be
|
|
5
5
|
* half-done — the bundle, the hashing and the reporting all walk this.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
declare const DERIVED_RECORDS: readonly [{
|
|
8
8
|
readonly kind: "impact";
|
|
9
9
|
readonly file: "impact.json";
|
|
10
10
|
}, {
|
|
@@ -14,7 +14,7 @@ export declare const DERIVED_RECORDS: readonly [{
|
|
|
14
14
|
readonly kind: "intent";
|
|
15
15
|
readonly file: "intent.json";
|
|
16
16
|
}];
|
|
17
|
-
|
|
17
|
+
type DerivedKind = (typeof DERIVED_RECORDS)[number]['kind'];
|
|
18
18
|
/** What the machine reads from disk. Injected so a cycle is testable with no filesystem at all. */
|
|
19
19
|
export interface SyncSource {
|
|
20
20
|
/** Every run artifact currently on disk. */
|
|
@@ -35,7 +35,7 @@ export interface SyncSink {
|
|
|
35
35
|
writeState: (state: CloudSyncState) => void;
|
|
36
36
|
}
|
|
37
37
|
/** A decision a human made on the dashboard, as the machine stores it. */
|
|
38
|
-
|
|
38
|
+
interface PulledIssue {
|
|
39
39
|
status: string;
|
|
40
40
|
flowName: string | null;
|
|
41
41
|
title: string;
|
|
@@ -82,7 +82,7 @@ export interface SyncReport {
|
|
|
82
82
|
/** Set when the cycle could not complete. The local record is untouched either way. */
|
|
83
83
|
error?: string;
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
interface SyncDeps {
|
|
86
86
|
config: {
|
|
87
87
|
url: string;
|
|
88
88
|
apiKey: string;
|
|
@@ -108,3 +108,4 @@ export interface SyncDeps {
|
|
|
108
108
|
export declare function runSyncCycle(deps: SyncDeps): Promise<SyncReport>;
|
|
109
109
|
/** One line a human can read, for the CLI and the daemon log. */
|
|
110
110
|
export declare function describeSync(report: SyncReport): string;
|
|
111
|
+
export {};
|
package/dist/cloud/sync-cycle.js
CHANGED
|
@@ -42,7 +42,7 @@ const SYNC_PULL_PATH = '/v1/sync/pull';
|
|
|
42
42
|
* A list rather than three hand-written blocks so adding a fourth is one line and cannot be
|
|
43
43
|
* half-done — the bundle, the hashing and the reporting all walk this.
|
|
44
44
|
*/
|
|
45
|
-
|
|
45
|
+
const DERIVED_RECORDS = [
|
|
46
46
|
{ kind: 'impact', file: ReticleDir.IMPACT_FILE },
|
|
47
47
|
{ kind: 'flake', file: ReticleDir.FLAKE_FILE },
|
|
48
48
|
{ kind: 'intent', file: ReticleDir.INTENT_FILE },
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import { type SyncReport } from './sync-cycle.js';
|
|
2
2
|
import type { ProjectCloud } from './cloud-config.js';
|
|
3
|
-
|
|
4
|
-
export declare const DAEMON_SYNC_INTERVAL_MS = 60000;
|
|
5
|
-
/**
|
|
6
|
-
* The cadence while something is actually happening.
|
|
7
|
-
*
|
|
8
|
-
* A fixed minute is the wrong shape for both states it has to cover. During a drive the ledger
|
|
9
|
-
* changes on every tool call and a minute of lag is what makes a dashboard feel dead; idle, a minute
|
|
10
|
-
* is already more often than nothing has changed deserves.
|
|
11
|
-
*
|
|
12
|
-
* So the interval follows the work: a cycle that MOVED something earns the fast rate, a cycle that
|
|
13
|
-
* moved nothing backs off to the slow one. No session plumbing and no new configuration — activity
|
|
14
|
-
* is inferred from what the last cycle actually sent, which is the only honest evidence available.
|
|
15
|
-
*
|
|
16
|
-
* Deliberately not per-tool-call. A push in the tool path would put the network in the agent's inner
|
|
17
|
-
* loop, and verification working with the network down is a promise this product makes.
|
|
18
|
-
*/
|
|
19
|
-
export declare const DAEMON_SYNC_ACTIVE_INTERVAL_MS = 5000;
|
|
20
|
-
export interface SyncDaemonDeps {
|
|
3
|
+
interface SyncDaemonDeps {
|
|
21
4
|
reticleRoot: string;
|
|
22
5
|
/** Resolved per tick, not once: a repo linked while the daemon is alive starts syncing itself. */
|
|
23
6
|
cloud: () => Promise<ProjectCloud>;
|
|
@@ -46,7 +29,7 @@ export interface SyncDaemonDeps {
|
|
|
46
29
|
text: string;
|
|
47
30
|
}>;
|
|
48
31
|
}
|
|
49
|
-
|
|
32
|
+
interface SyncDaemon {
|
|
50
33
|
/** Run one cycle now, whatever the timer is doing. Returns undefined when not linked. */
|
|
51
34
|
syncNow: () => Promise<SyncReport | undefined>;
|
|
52
35
|
/**
|
|
@@ -67,3 +50,4 @@ export interface SyncDaemon {
|
|
|
67
50
|
* nothing until one appears, which is what lets `reticle link` take effect without a restart.
|
|
68
51
|
*/
|
|
69
52
|
export declare function startSyncDaemon(deps: SyncDaemonDeps): SyncDaemon;
|
|
53
|
+
export {};
|
|
@@ -25,7 +25,7 @@ import { log } from '../log.js';
|
|
|
25
25
|
import { describeSync, runSyncCycle } from './sync-cycle.js';
|
|
26
26
|
import { diskSink, diskSource, readCloudState } from './sync-disk.js';
|
|
27
27
|
/** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
|
|
28
|
-
|
|
28
|
+
const DAEMON_SYNC_INTERVAL_MS = 60_000;
|
|
29
29
|
/**
|
|
30
30
|
* The cadence while something is actually happening.
|
|
31
31
|
*
|
|
@@ -40,7 +40,7 @@ export const DAEMON_SYNC_INTERVAL_MS = 60_000;
|
|
|
40
40
|
* Deliberately not per-tool-call. A push in the tool path would put the network in the agent's inner
|
|
41
41
|
* loop, and verification working with the network down is a promise this product makes.
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
const DAEMON_SYNC_ACTIVE_INTERVAL_MS = 5_000;
|
|
44
44
|
/** Given to the first cycle so a freshly-started daemon does not race the session that woke it. */
|
|
45
45
|
const FIRST_CYCLE_DELAY_MS = 5_000;
|
|
46
46
|
/**
|
|
@@ -51,7 +51,7 @@ export const CRAWL_TOOLS = [
|
|
|
51
51
|
source: z
|
|
52
52
|
.string()
|
|
53
53
|
.optional()
|
|
54
|
-
.describe('Where the
|
|
54
|
+
.describe('Where the fault is written, as `file:line`; console errors prefer their own stack and fall back to the clicked control.'),
|
|
55
55
|
})),
|
|
56
56
|
counts: z.record(z.number()),
|
|
57
57
|
visited: z.array(z.string()).describe('The controls actually clicked, in order.'),
|
package/dist/crawl/crawl.d.ts
CHANGED
|
@@ -45,12 +45,12 @@ export interface CrawlAnomaly {
|
|
|
45
45
|
desc: string;
|
|
46
46
|
detail: string;
|
|
47
47
|
/**
|
|
48
|
-
* Where the
|
|
48
|
+
* Where the fault is written, as `file:line`.
|
|
49
49
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* alongside
|
|
50
|
+
* Console faults prefer their own component/error stack. The clicked control stays in `ref` and
|
|
51
|
+
* `desc`, and its source is only the fallback: a navigation link explains what exposed a render
|
|
52
|
+
* crash, while the crashing component explains what to fix. Other anomaly kinds still use the
|
|
53
|
+
* control source captured alongside the action. Absent when neither source can be localized.
|
|
54
54
|
*/
|
|
55
55
|
source?: string;
|
|
56
56
|
}
|
package/dist/crawl/crawl.js
CHANGED
|
@@ -2,6 +2,7 @@ import { span } from '../trace.js';
|
|
|
2
2
|
import { ActionType, CRAWL_DEFAULTS, CrawlAnomalyKind, DANGEROUS_ACTION_CONFIRM_ARG, EventType, ReticleCommand, } from '@reticlehq/core';
|
|
3
3
|
import { crawlEmptyNote } from './crawl-empty.js';
|
|
4
4
|
import { parseInteractive, asRecord, asNumber, asString, sourceOf, } from '../tools/tools-helpers.js';
|
|
5
|
+
import { isSessionReplacedError } from '../session/session-replaced.js';
|
|
5
6
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
6
7
|
import { findContradictions } from '../events/contradictions.js';
|
|
7
8
|
/** Said out loud when the page was larger than one snapshot, so a clean sweep cannot mean "all clear". */
|
|
@@ -38,6 +39,115 @@ function isActivity(e) {
|
|
|
38
39
|
function isConsoleError(e) {
|
|
39
40
|
return e.type === EventType.CONSOLE_ERROR || e.type === EventType.ERROR_UNCAUGHT;
|
|
40
41
|
}
|
|
42
|
+
const SOURCE_FRAME = /^(.*):(\d+):(\d+)$/;
|
|
43
|
+
const COMPONENT_STACK_FIELDS = ['componentStack', 'message'];
|
|
44
|
+
const URL_SOURCE_PROTOCOLS = new Set([
|
|
45
|
+
'http:',
|
|
46
|
+
'https:',
|
|
47
|
+
'file:',
|
|
48
|
+
'webpack:',
|
|
49
|
+
'webpack-internal:',
|
|
50
|
+
]);
|
|
51
|
+
/** Turn a browser/dev-server frame URL into the file-shaped pointer the rest of Reticle emits. */
|
|
52
|
+
function sourceFile(raw) {
|
|
53
|
+
let file = raw.trim();
|
|
54
|
+
if (0 === file.length)
|
|
55
|
+
return undefined;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = new URL(file);
|
|
58
|
+
if (URL_SOURCE_PROTOCOLS.has(parsed.protocol))
|
|
59
|
+
file = decodeURIComponent(parsed.pathname);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// A repo-relative or Windows path is already the more useful form; keep it as-is.
|
|
63
|
+
}
|
|
64
|
+
file = file.replace(/[?#].*$/, '').replace(/\\/g, '/');
|
|
65
|
+
const bundledPrefix = file.lastIndexOf('/./');
|
|
66
|
+
if (-1 !== bundledPrefix)
|
|
67
|
+
file = file.slice(bundledPrefix + 3);
|
|
68
|
+
if (file.startsWith('/@fs/'))
|
|
69
|
+
file = file.slice('/@fs/'.length);
|
|
70
|
+
if (/^\/[A-Za-z]:\//.test(file))
|
|
71
|
+
file = file.slice(1);
|
|
72
|
+
else
|
|
73
|
+
file = file.replace(/^\/+/, '');
|
|
74
|
+
if (file.startsWith('./'))
|
|
75
|
+
file = file.slice(2);
|
|
76
|
+
return 0 === file.length ? undefined : file;
|
|
77
|
+
}
|
|
78
|
+
/** The location part of a Chrome/React or Firefox stack frame. */
|
|
79
|
+
function frameLocation(line) {
|
|
80
|
+
let frame = line.trim();
|
|
81
|
+
if (frame.startsWith('at '))
|
|
82
|
+
frame = frame.slice(3).trim();
|
|
83
|
+
else {
|
|
84
|
+
const firefoxSeparator = frame.lastIndexOf('@');
|
|
85
|
+
if (-1 === firefoxSeparator)
|
|
86
|
+
return undefined;
|
|
87
|
+
frame = frame.slice(firefoxSeparator + 1);
|
|
88
|
+
}
|
|
89
|
+
const opening = frame.lastIndexOf('(');
|
|
90
|
+
if (-1 !== opening && frame.endsWith(')'))
|
|
91
|
+
return frame.slice(opening + 1, -1);
|
|
92
|
+
return frame;
|
|
93
|
+
}
|
|
94
|
+
function isFrameworkSource(file) {
|
|
95
|
+
const normalized = `/${file.toLowerCase().replace(/^\/+/, '')}`;
|
|
96
|
+
return normalized.includes('/node_modules/') || normalized.startsWith('/node:');
|
|
97
|
+
}
|
|
98
|
+
/** First editable app frame in a component/error stack, excluding framework internals. */
|
|
99
|
+
function sourceFromStack(stack) {
|
|
100
|
+
for (const line of stack.split('\n')) {
|
|
101
|
+
const location = frameLocation(line);
|
|
102
|
+
if (location === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
const match = SOURCE_FRAME.exec(location);
|
|
105
|
+
if (null === match)
|
|
106
|
+
continue;
|
|
107
|
+
const [, rawFile, rawLine] = match;
|
|
108
|
+
if (rawFile === undefined || rawLine === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
const file = sourceFile(rawFile);
|
|
111
|
+
const lineNumber = Number(rawLine);
|
|
112
|
+
if (file === undefined ||
|
|
113
|
+
isFrameworkSource(file) ||
|
|
114
|
+
!Number.isSafeInteger(lineNumber) ||
|
|
115
|
+
lineNumber <= 0) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
return `${file}:${String(lineNumber)}`;
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/** Prefer where a console fault occurred over the control whose click merely exposed it. */
|
|
123
|
+
function sourceFromConsoleError(event) {
|
|
124
|
+
// React currently passes its component stack as a console.error string argument, so the browser
|
|
125
|
+
// observer carries it inside `message`; richer producers can send `componentStack` directly.
|
|
126
|
+
for (const field of COMPONENT_STACK_FIELDS) {
|
|
127
|
+
const componentStack = asString(event.data[field]);
|
|
128
|
+
if (componentStack === undefined)
|
|
129
|
+
continue;
|
|
130
|
+
const componentSource = sourceFromStack(componentStack);
|
|
131
|
+
if (componentSource !== undefined)
|
|
132
|
+
return componentSource;
|
|
133
|
+
}
|
|
134
|
+
// The ordinary error stack can also identify a component better than ErrorEvent.filename (for
|
|
135
|
+
// example, a bundle or click-handler frame), so try app frames before the structured fallback.
|
|
136
|
+
const stack = asString(event.data['stack']);
|
|
137
|
+
if (stack !== undefined) {
|
|
138
|
+
const stackSource = sourceFromStack(stack);
|
|
139
|
+
if (stackSource !== undefined)
|
|
140
|
+
return stackSource;
|
|
141
|
+
}
|
|
142
|
+
const directFile = asString(event.data['source']);
|
|
143
|
+
const directLine = asNumber(event.data['line']);
|
|
144
|
+
if (directFile === undefined || directLine === undefined || directLine <= 0)
|
|
145
|
+
return undefined;
|
|
146
|
+
const file = sourceFile(directFile);
|
|
147
|
+
return file === undefined || isFrameworkSource(file)
|
|
148
|
+
? undefined
|
|
149
|
+
: `${file}:${String(directLine)}`;
|
|
150
|
+
}
|
|
41
151
|
function failedRequests(events, floor) {
|
|
42
152
|
return events.filter((e) => {
|
|
43
153
|
if (e.type !== EventType.NET_REQUEST)
|
|
@@ -163,6 +273,18 @@ export async function crawl(session, opts, sleep) {
|
|
|
163
273
|
const since = session.elapsed();
|
|
164
274
|
session.beginAction?.(ReticleTool.CRAWL, { ref: item.ref, action: ActionType.CLICK });
|
|
165
275
|
let act;
|
|
276
|
+
/*
|
|
277
|
+
* A full page load reconnects the SDK, which rejects whatever command was in flight.
|
|
278
|
+
*
|
|
279
|
+
* On a server-rendered app that is what following a link DOES — every navigation replaces the
|
|
280
|
+
* session — so the rejection is evidence the click worked, not that it failed. Crawl used to let
|
|
281
|
+
* it propagate and died on link one of every Django, Rails or plain-HTML app.
|
|
282
|
+
*
|
|
283
|
+
* Recorded as a navigation rather than swallowed: a control that took the page somewhere is the
|
|
284
|
+
* opposite of a dead one, and reporting it as dead would be the false negative crawl exists to
|
|
285
|
+
* find.
|
|
286
|
+
*/
|
|
287
|
+
let navigatedAway = false;
|
|
166
288
|
try {
|
|
167
289
|
// One span per control clicked, so a slow crawl names the control rather than reporting a
|
|
168
290
|
// single multi-second total. The settle sleep below is INSIDE it deliberately: it is part of
|
|
@@ -177,6 +299,11 @@ export async function crawl(session, opts, sleep) {
|
|
|
177
299
|
return clicked;
|
|
178
300
|
});
|
|
179
301
|
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
if (!isSessionReplacedError(err))
|
|
304
|
+
throw err;
|
|
305
|
+
navigatedAway = true;
|
|
306
|
+
}
|
|
180
307
|
finally {
|
|
181
308
|
// Close on every exit so a throw cannot leak the window onto the next control's events.
|
|
182
309
|
session.finishAction?.();
|
|
@@ -184,16 +311,18 @@ export async function crawl(session, opts, sleep) {
|
|
|
184
311
|
const events = session.eventsSince(since);
|
|
185
312
|
// Captured at act time, so it survives a click that unmounts its own control.
|
|
186
313
|
const src = sourceOf(asRecord(act?.result)['source']);
|
|
187
|
-
const
|
|
314
|
+
const controlSource = src === undefined ? undefined : `${src.file}:${String(src.line)}`;
|
|
315
|
+
const source = controlSource === undefined ? {} : { source: controlSource };
|
|
188
316
|
const errs = events.filter(isConsoleError);
|
|
189
317
|
for (const e of errs) {
|
|
190
318
|
counts.consoleErrors += 1;
|
|
319
|
+
const errorSource = sourceFromConsoleError(e) ?? controlSource;
|
|
191
320
|
anomalies.push({
|
|
192
321
|
kind: CrawlAnomalyKind.CONSOLE_ERROR,
|
|
193
322
|
ref: item.ref,
|
|
194
323
|
desc: item.desc,
|
|
195
324
|
detail: asString(e.data['message']) ?? e.type,
|
|
196
|
-
...source,
|
|
325
|
+
...(errorSource === undefined ? {} : { source: errorSource }),
|
|
197
326
|
});
|
|
198
327
|
}
|
|
199
328
|
for (const e of failedRequests(events, CRAWL_DEFAULTS.FAILED_STATUS)) {
|
|
@@ -242,8 +371,13 @@ export async function crawl(session, opts, sleep) {
|
|
|
242
371
|
// Deliberately NOT fixed by counting focus as activity: focus moving is not the app reacting, and
|
|
243
372
|
// treating it as such would make a genuinely dead button that takes focus look alive — trading
|
|
244
373
|
// noise for the false negative this check exists to catch.
|
|
245
|
-
const dispatched = asRecord(act
|
|
246
|
-
if (
|
|
374
|
+
const dispatched = asRecord(act?.result)['dispatched'] !== false && true === act?.ok;
|
|
375
|
+
if (
|
|
376
|
+
// A control that took the page somewhere is the OPPOSITE of a dead one. Without this, every
|
|
377
|
+
// link on a server-rendered app would be reported as an anomaly by the check that exists to
|
|
378
|
+
// find controls which do nothing.
|
|
379
|
+
!navigatedAway &&
|
|
380
|
+
dispatched &&
|
|
247
381
|
0 === errs.length &&
|
|
248
382
|
!events.some(isActivity) &&
|
|
249
383
|
!legitimatelyInert(item.desc)) {
|
|
@@ -67,7 +67,8 @@ export declare function installDaemonResilience(proc: ProcessLike, log: LogFn, o
|
|
|
67
67
|
* dormant paths). A crashed-but-serving proxy answers the handshake and `tools/list` from cache; a
|
|
68
68
|
* dead one answers nothing and needs a human.
|
|
69
69
|
*
|
|
70
|
-
* `onCrash` is for reporting only — it must not end the process.
|
|
70
|
+
* `onCrash` is for reporting only — it must not end the process. `onGone` is the ONE exception, and
|
|
71
|
+
* it is the subject of DISCONNECT_STORM below.
|
|
71
72
|
*/
|
|
72
|
-
export declare function installProxyResilience(proc: ProcessLike, log: LogFn, onCrash?: (kind: CrashKind, cause: unknown) => void): void;
|
|
73
|
+
export declare function installProxyResilience(proc: ProcessLike, log: LogFn, onCrash?: (kind: CrashKind, cause: unknown) => void, onGone?: () => void): void;
|
|
73
74
|
export {};
|
|
@@ -53,6 +53,24 @@ const DISCONNECT_CODES = new Set([
|
|
|
53
53
|
* `connect_error` on `mcp_connection_lost`, which is the metric that can actually act on it.
|
|
54
54
|
*/
|
|
55
55
|
const UNREACHABLE_CODES = new Set(['ECONNREFUSED']);
|
|
56
|
+
/**
|
|
57
|
+
* How many absorbed client disconnects mean the client is GONE rather than between reconnects.
|
|
58
|
+
*
|
|
59
|
+
* Absorbing a disconnect and continuing is right for one: the client closed a socket, the proxy
|
|
60
|
+
* rebuilds it. It is catastrophic for a stream of them, because the write that failed is retried
|
|
61
|
+
* immediately, fails identically, and is absorbed again — a tight loop with no backoff and no exit.
|
|
62
|
+
*
|
|
63
|
+
* Measured in the field: one proxy ran four days after its editor closed, at 97-98% of a core, with
|
|
64
|
+
* 1473 minutes of CPU time and ~930 MB/hour of identical log lines, every entry stamped to the same
|
|
65
|
+
* millisecond. Nothing in Reticle's own output showed it — the daemon beside it reported healthy,
|
|
66
|
+
* `sessions: 0` — so it was findable only by running `ps` by hand, and thirteen more idle pairs from
|
|
67
|
+
* earlier sessions were still resident behind it.
|
|
68
|
+
*
|
|
69
|
+
* Chosen well above the handful a genuine reconnect produces and far below a runaway. The proxy's
|
|
70
|
+
* whole purpose is to be the stdio server its editor launched; once that editor is gone there is
|
|
71
|
+
* nobody left to serve, and the correct thing is to leave rather than to burn a core proving it.
|
|
72
|
+
*/
|
|
73
|
+
const DISCONNECT_STORM = 20;
|
|
56
74
|
/** The expected-failure code this value carries, and which class it belongs to. */
|
|
57
75
|
function expectedCode(value) {
|
|
58
76
|
const raw = value?.code;
|
|
@@ -253,22 +271,43 @@ export function installDaemonResilience(proc, log, onFatal) {
|
|
|
253
271
|
* dormant paths). A crashed-but-serving proxy answers the handshake and `tools/list` from cache; a
|
|
254
272
|
* dead one answers nothing and needs a human.
|
|
255
273
|
*
|
|
256
|
-
* `onCrash` is for reporting only — it must not end the process.
|
|
274
|
+
* `onCrash` is for reporting only — it must not end the process. `onGone` is the ONE exception, and
|
|
275
|
+
* it is the subject of DISCONNECT_STORM below.
|
|
257
276
|
*/
|
|
258
|
-
export function installProxyResilience(proc, log, onCrash = reportCrash) {
|
|
277
|
+
export function installProxyResilience(proc, log, onCrash = reportCrash, onGone = () => process.exit(0)) {
|
|
259
278
|
const expected = {
|
|
260
279
|
disconnected: 'reticle_mcp_proxy_client_disconnected',
|
|
261
280
|
unreachable: 'reticle_mcp_proxy_daemon_unreachable',
|
|
262
281
|
};
|
|
282
|
+
// "Absorb the disconnect and keep serving" is right for ONE disconnect and catastrophic for a
|
|
283
|
+
// stream of them — see DISCONNECT_STORM.
|
|
284
|
+
let disconnects = 0;
|
|
285
|
+
let gone = false;
|
|
286
|
+
const absorb = (value) => {
|
|
287
|
+
if (!absorbExpected(value, log, expected))
|
|
288
|
+
return false;
|
|
289
|
+
if (false === expectedCode(value)?.unreachable) {
|
|
290
|
+
disconnects += 1;
|
|
291
|
+
if (disconnects >= DISCONNECT_STORM && !gone) {
|
|
292
|
+
gone = true;
|
|
293
|
+
log('reticle_mcp_proxy_client_gone', {
|
|
294
|
+
disconnects,
|
|
295
|
+
note: 'the client end is gone for good, not between reconnects — exiting instead of retrying forever',
|
|
296
|
+
});
|
|
297
|
+
onGone();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return true;
|
|
301
|
+
};
|
|
263
302
|
proc.on('unhandledRejection', (reason) => {
|
|
264
|
-
if (
|
|
303
|
+
if (absorb(reason))
|
|
265
304
|
return;
|
|
266
305
|
log('reticle_mcp_proxy_unhandled_rejection', { reason: describe(reason) });
|
|
267
306
|
onCrash(CrashKind.UNHANDLED_REJECTION, reason);
|
|
268
307
|
});
|
|
269
308
|
proc.on('uncaughtException', (err) => {
|
|
270
309
|
// The client closing its end of stdio is how this process is SUPPOSED to end its day.
|
|
271
|
-
if (
|
|
310
|
+
if (absorb(err))
|
|
272
311
|
return;
|
|
273
312
|
log('reticle_mcp_proxy_uncaught_exception', {
|
|
274
313
|
error: describe(err),
|
|
@@ -16,15 +16,6 @@
|
|
|
16
16
|
* This module is the one answer both sides now use. It is pure and injectable: no process spawning,
|
|
17
17
|
* no port binding, nothing that needs a real `~/.reticle` to test.
|
|
18
18
|
*/
|
|
19
|
-
import { type DaemonRegistryEntry } from '@reticlehq/core';
|
|
20
|
-
/**
|
|
21
|
-
* Every well-formed registry entry in `home`.
|
|
22
|
-
*
|
|
23
|
-
* A corrupt or half-written entry is skipped rather than thrown on: the registry is written by other
|
|
24
|
-
* processes that can be killed mid-write, and one bad file must not make a machine's daemons
|
|
25
|
-
* undiscoverable. Validated through core's schema so a shape change cannot silently pass here.
|
|
26
|
-
*/
|
|
27
|
-
export declare function readDaemonRegistry(home: string): DaemonRegistryEntry[];
|
|
28
19
|
/**
|
|
29
20
|
* The port of the live daemon serving `projectId`, or undefined when this project has none.
|
|
30
21
|
*
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
|
-
import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core';
|
|
21
|
+
import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core/artifacts';
|
|
22
22
|
/**
|
|
23
23
|
* Every well-formed registry entry in `home`.
|
|
24
24
|
*
|
|
@@ -26,7 +26,7 @@ import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '
|
|
|
26
26
|
* processes that can be killed mid-write, and one bad file must not make a machine's daemons
|
|
27
27
|
* undiscoverable. Validated through core's schema so a shape change cannot silently pass here.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
function readDaemonRegistry(home) {
|
|
30
30
|
let files;
|
|
31
31
|
try {
|
|
32
32
|
files = readdirSync(home);
|