@reticlehq/server 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { span } from '../trace.js';
|
|
2
|
+
import { AnchorKind, DriftReason, EventType, ReticleCommand, QueryBy, PredicateKind, } from '@reticlehq/core';
|
|
2
3
|
import { asRecord, asString } from '../tools/tools-helpers.js';
|
|
3
4
|
import { replayActionArgs, ambiguousTestidNote, queryRefs } from './replay.js';
|
|
4
|
-
import {
|
|
5
|
+
import { degradedStepResult, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
|
|
6
|
+
import { successToPredicate } from './flow-success.js';
|
|
5
7
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
6
8
|
/**
|
|
7
9
|
* A single ASCII-ish edit distance (case-insensitive). Small inputs (testids), so O(n*m) is fine.
|
|
@@ -59,6 +61,22 @@ export function nearestTestid(missing, present) {
|
|
|
59
61
|
*/
|
|
60
62
|
const ANCHOR_SETTLE_ATTEMPTS = 8;
|
|
61
63
|
const ANCHOR_SETTLE_DELAY_MS = 150;
|
|
64
|
+
/**
|
|
65
|
+
* The budget that actually governs, in wall-clock milliseconds.
|
|
66
|
+
*
|
|
67
|
+
* Attempts were the bound, and on an event-chatty page that collapsed. `settleTick` ends on EITHER
|
|
68
|
+
* an event OR the tick, so a page emitting continuously (API calls, a large form render, CSS
|
|
69
|
+
* transition start/end pairs) burned all eight attempts in **224–758ms measured** — a budget
|
|
70
|
+
* documented as 1.2s, spent in a fifth of it, before a newly routed page had mounted its controls.
|
|
71
|
+
* Cross-route replays drifted `testid_not_found` at 278ms while the same flow passed on a quiet page.
|
|
72
|
+
*
|
|
73
|
+
* An event arriving is evidence the page is still working. It should EXTEND the wait, not spend it.
|
|
74
|
+
* So the deadline decides when to give up, and the attempt cap below survives only as a backstop
|
|
75
|
+
* against a pathological storm spinning this loop hot.
|
|
76
|
+
*/
|
|
77
|
+
const ANCHOR_SETTLE_BUDGET_MS = ANCHOR_SETTLE_ATTEMPTS * ANCHOR_SETTLE_DELAY_MS;
|
|
78
|
+
/** Backstop only. Generous on purpose: the deadline is the real bound, this just prevents a spin. */
|
|
79
|
+
const ANCHOR_SETTLE_MAX_ATTEMPTS = 40;
|
|
62
80
|
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
81
|
/** Extract the live element refs + the zero-match near-miss hint from a QUERY command result. */
|
|
64
82
|
function readQuery(result) {
|
|
@@ -67,10 +85,10 @@ function readQuery(result) {
|
|
|
67
85
|
return { refs };
|
|
68
86
|
const payload = asRecord(result.result);
|
|
69
87
|
const rawHint = payload['hint'];
|
|
70
|
-
if (typeof rawHint
|
|
88
|
+
if ('object' === typeof rawHint && rawHint !== null) {
|
|
71
89
|
const hint = asRecord(rawHint);
|
|
72
90
|
const present = Array.isArray(hint['presentTestids'])
|
|
73
|
-
? hint['presentTestids'].filter((t) => typeof t
|
|
91
|
+
? hint['presentTestids'].filter((t) => 'string' === typeof t)
|
|
74
92
|
: [];
|
|
75
93
|
return {
|
|
76
94
|
refs,
|
|
@@ -78,7 +96,7 @@ function readQuery(result) {
|
|
|
78
96
|
route: asString(hint['route']) ?? '',
|
|
79
97
|
presentTestids: present,
|
|
80
98
|
presentRegions: [],
|
|
81
|
-
knownEmptyState: hint['knownEmptyState']
|
|
99
|
+
knownEmptyState: true === hint['knownEmptyState'],
|
|
82
100
|
},
|
|
83
101
|
};
|
|
84
102
|
}
|
|
@@ -107,7 +125,7 @@ export function nearestIsAmbiguous(missing, present) {
|
|
|
107
125
|
return count >= 2;
|
|
108
126
|
}
|
|
109
127
|
/** Build the legible-drift record for a testid anchor that resolved to zero live elements. */
|
|
110
|
-
function testidDrift(value, hint) {
|
|
128
|
+
export function testidDrift(value, hint) {
|
|
111
129
|
const present = hint?.presentTestids ?? [];
|
|
112
130
|
const drift = {
|
|
113
131
|
reasonKind: DriftReason.TESTID_NOT_FOUND,
|
|
@@ -125,10 +143,42 @@ function testidDrift(value, hint) {
|
|
|
125
143
|
* so a present anchor costs one query; a genuinely missing one costs the full (bounded) settle and
|
|
126
144
|
* then drifts. The last result's near-miss hint is returned for the drift record.
|
|
127
145
|
*/
|
|
128
|
-
|
|
146
|
+
/**
|
|
147
|
+
* One wait between anchor attempts: the fixed tick, or the next DOM event — whichever comes first.
|
|
148
|
+
*
|
|
149
|
+
* An element mounting IS a mutation, and the session already streams those, so sleeping out the rest
|
|
150
|
+
* of a 150ms tick after the thing has already appeared is pure latency. Measured on next-app-router:
|
|
151
|
+
* a single `flow.step` span was 1079ms around nine QUERY round-trips of 1–2ms each — the cost was
|
|
152
|
+
* entirely the sleeping, and four such steps made up 4.3s of that app's 7.6s.
|
|
153
|
+
*
|
|
154
|
+
* It can only resolve the wait EARLIER, never end the loop earlier: the attempt budget above is
|
|
155
|
+
* untouched, so a genuinely missing anchor still spends the full settle before it drifts. An early
|
|
156
|
+
* "not found" would be a false drift, which is the failure this loop exists to prevent.
|
|
157
|
+
*/
|
|
158
|
+
async function settleTick(session, sleep) {
|
|
159
|
+
let unsubscribe;
|
|
160
|
+
try {
|
|
161
|
+
await new Promise((resolve) => {
|
|
162
|
+
let done = false;
|
|
163
|
+
const finish = () => {
|
|
164
|
+
if (done)
|
|
165
|
+
return;
|
|
166
|
+
done = true;
|
|
167
|
+
resolve();
|
|
168
|
+
};
|
|
169
|
+
unsubscribe = session.onEvent(finish);
|
|
170
|
+
void sleep(ANCHOR_SETTLE_DELAY_MS).then(finish);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
unsubscribe?.();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export async function resolveQuery(session, queryArgs, sleep, now = Date.now) {
|
|
129
178
|
let last = readQuery(await session.command(ReticleCommand.QUERY, queryArgs));
|
|
130
|
-
|
|
131
|
-
|
|
179
|
+
const deadline = now() + ANCHOR_SETTLE_BUDGET_MS;
|
|
180
|
+
for (let attempt = 1; 0 === last.refs.length && now() < deadline && attempt < ANCHOR_SETTLE_MAX_ATTEMPTS; attempt += 1) {
|
|
181
|
+
await settleTick(session, sleep);
|
|
132
182
|
last = readQuery(await session.command(ReticleCommand.QUERY, queryArgs));
|
|
133
183
|
}
|
|
134
184
|
return last;
|
|
@@ -188,7 +238,7 @@ function summarizeConsequence(events) {
|
|
|
188
238
|
const data = n.data ?? {};
|
|
189
239
|
const method = asString(data['method']) ?? 'GET';
|
|
190
240
|
const path = trimUrl(asString(data['url']) ?? '');
|
|
191
|
-
const status = typeof data['status']
|
|
241
|
+
const status = 'number' === typeof data['status'] ? ` ${data['status']}` : '';
|
|
192
242
|
parts.push(`${method} ${path}${status}`.trim());
|
|
193
243
|
}
|
|
194
244
|
const errors = events.filter((e) => e.type === EventType.CONSOLE_ERROR || e.type === EventType.ERROR_UNCAUGHT).length;
|
|
@@ -206,7 +256,7 @@ export function componentLabel(anchor) {
|
|
|
206
256
|
return anchor.component ?? anchor.name ?? anchor.role ?? 'component';
|
|
207
257
|
}
|
|
208
258
|
/** The value of a step's primary anchor, for labelling the result row. */
|
|
209
|
-
function anchorLabel(anchor) {
|
|
259
|
+
export function anchorLabel(anchor) {
|
|
210
260
|
if (anchor.kind === AnchorKind.TESTID)
|
|
211
261
|
return anchor.value;
|
|
212
262
|
if (anchor.kind === AnchorKind.SIGNAL)
|
|
@@ -227,7 +277,7 @@ export function componentQueryArgs(anchor) {
|
|
|
227
277
|
/** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
|
|
228
278
|
async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
|
|
229
279
|
const { refs, hint } = await resolveTestid(session, value, sleep);
|
|
230
|
-
if (refs.length
|
|
280
|
+
if (0 === refs.length) {
|
|
231
281
|
return {
|
|
232
282
|
step: index,
|
|
233
283
|
tool: step.tool,
|
|
@@ -263,7 +313,7 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
263
313
|
const expectTestid = step.expect?.element?.testid;
|
|
264
314
|
if (expectTestid !== undefined && !dynamic.has(expectTestid)) {
|
|
265
315
|
const expectRefs = await resolveTestid(session, expectTestid, sleep);
|
|
266
|
-
if (expectRefs.refs.length
|
|
316
|
+
if (0 === expectRefs.refs.length) {
|
|
267
317
|
return {
|
|
268
318
|
step: index,
|
|
269
319
|
tool: step.tool,
|
|
@@ -278,27 +328,50 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
278
328
|
return result;
|
|
279
329
|
}
|
|
280
330
|
/**
|
|
281
|
-
* After a step's anchor resolves
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
331
|
+
* After a step's anchor resolves and its action runs, its `expect` is EVALUATED — every kind of it.
|
|
332
|
+
*
|
|
333
|
+
* For a long time only `expect.state` was, so a recorded `expect.signal` or `expect.net` was written
|
|
334
|
+
* to disk and read by nothing while `flow_save` graded the flow "asserted". Driven end to end over
|
|
335
|
+
* MCP: annotate a step with a signal that never fires, save (grade "asserted"), replay -> status
|
|
336
|
+
* "ok". A green that cannot go red, inside the feature whose job is catching exactly that.
|
|
337
|
+
*
|
|
338
|
+
* It compiles through the SAME `successToPredicate` the flow-level `success` has always used, so
|
|
339
|
+
* there is one definition of what an expect means and the step form cannot drift from it again.
|
|
340
|
+
* Turning this on makes previously-green flows go red. That is the point — they were green because
|
|
341
|
+
* nothing was looking.
|
|
285
342
|
*/
|
|
286
|
-
async function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
343
|
+
async function assertStepExpect(session, expect, dynamic, waitForSignal, timeoutMs, since) {
|
|
344
|
+
// `element` is deliberately dropped: the step runner already asserts expect.element.testid against
|
|
345
|
+
// the live DOM before we get here, and re-asserting it through the predicate engine would be a
|
|
346
|
+
// second round trip for the same fact.
|
|
347
|
+
const { element: _element, ...consequences } = expect;
|
|
348
|
+
const predicate = successToPredicate(consequences, dynamic);
|
|
349
|
+
if (predicate === undefined)
|
|
350
|
+
return undefined;
|
|
292
351
|
const verdict = await waitForSignal(session, predicate, timeoutMs, since);
|
|
293
352
|
if (verdict.pass)
|
|
294
353
|
return undefined;
|
|
295
354
|
return {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
355
|
+
// The store case keeps its own kind because heal and the run report branch on it; everything
|
|
356
|
+
// else is a consequence that did not hold, and the reason carries observed-vs-expected.
|
|
357
|
+
reasonKind: expect.state !== undefined ? DriftReason.STATE_MISMATCH : DriftReason.SIGNAL_NOT_OBSERVED,
|
|
358
|
+
reason: verdict.failureReason ?? "the step's declared consequence did not hold",
|
|
359
|
+
anchor: expectLabel(expect),
|
|
299
360
|
nearest: null,
|
|
300
361
|
};
|
|
301
362
|
}
|
|
363
|
+
/** Name the thing that was asserted, for the drift's `anchor` column. */
|
|
364
|
+
function expectLabel(expect) {
|
|
365
|
+
if (expect.signal !== undefined)
|
|
366
|
+
return `signal:${expect.signal}`;
|
|
367
|
+
if (expect.net !== undefined)
|
|
368
|
+
return `net:${expect.net.urlContains ?? expect.net.method ?? '*'}`;
|
|
369
|
+
if (expect.state !== undefined)
|
|
370
|
+
return `state:${expect.state.path}`;
|
|
371
|
+
if (expect.console !== undefined)
|
|
372
|
+
return `console:${expect.console.level ?? '*'}`;
|
|
373
|
+
return 'expect';
|
|
374
|
+
}
|
|
302
375
|
/** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
|
|
303
376
|
async function runSignalStep(session, step, index, name, waitForSignal, signalTimeoutMs, since) {
|
|
304
377
|
// Scope to THIS replay's floor, not the whole buffer.
|
|
@@ -310,7 +383,7 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
|
|
|
310
383
|
// satisfied a LATER flow's signal step even when that flow's own action never fired it — a
|
|
311
384
|
// cross-flow false green on the exact suite-verify path the regression-cost claim rests on. The
|
|
312
385
|
// replay-start floor excludes prior flows/runs while still seeing this run's adjacent-step signal.
|
|
313
|
-
const verdict = await waitForSignal(session, { kind:
|
|
386
|
+
const verdict = await waitForSignal(session, { kind: PredicateKind.SIGNAL, name }, signalTimeoutMs, since);
|
|
314
387
|
if (verdict.pass)
|
|
315
388
|
return { step: index, tool: step.tool, anchor: name, ok: true };
|
|
316
389
|
return {
|
|
@@ -348,28 +421,39 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
348
421
|
const page = currentRoute(session);
|
|
349
422
|
// Event-time floor so the consequence reflects only THIS step's aftermath, not prior steps'.
|
|
350
423
|
const cursorBefore = session.elapsed();
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
424
|
+
const subSteps = step.steps;
|
|
425
|
+
// Traced per step, so a slow replay says WHICH step and which anchor kind spent the time. The
|
|
426
|
+
// per-step `durationMs` below is what the agent gets back; this is what a developer profiling
|
|
427
|
+
// the replay engine gets, nested under the tool call with the browser round-trips beneath it.
|
|
428
|
+
const result = await span('flow.step', { index, anchor: step.anchor.kind, label }, async () => {
|
|
429
|
+
if (isDegradedAnchor(step.anchor)) {
|
|
430
|
+
// Never QUERY the sentinel — it marks "no anchor was determined", not an element to find.
|
|
431
|
+
return degradedStepResult(step, index, label);
|
|
432
|
+
}
|
|
433
|
+
if (subSteps !== undefined && subSteps.length > 0) {
|
|
434
|
+
return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep);
|
|
435
|
+
}
|
|
436
|
+
if (step.anchor.kind === AnchorKind.SIGNAL) {
|
|
437
|
+
return runSignalStep(session, step, index, label, waitForSignal, signalTimeoutMs, replayFloor);
|
|
438
|
+
}
|
|
439
|
+
if (step.anchor.kind === AnchorKind.COMPONENT) {
|
|
440
|
+
return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
441
|
+
}
|
|
442
|
+
if (step.anchor.kind === AnchorKind.ROLE && step.anchor.name !== undefined) {
|
|
443
|
+
// A NAMED role anchor addresses one element. The nameless one is the degraded placeholder
|
|
444
|
+
// and keeps its old path, where it fails legibly rather than querying a role as a testid.
|
|
445
|
+
return runRoleStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
446
|
+
}
|
|
447
|
+
return runTestidStep(session, step, index, label, dynamic, confirmDangerous, sleep);
|
|
448
|
+
});
|
|
449
|
+
// Once the anchor resolved and the action ran, the step's own expect is evaluated — signal, net,
|
|
450
|
+
// console and store truth alike — deterministically, in the same cheap replay loop, with no LLM.
|
|
451
|
+
const stepExpect = step.expect;
|
|
452
|
+
if (result.ok && result.drift === undefined && stepExpect !== undefined) {
|
|
453
|
+
const expectDrift = await assertStepExpect(session, stepExpect, dynamic, waitForSignal, signalTimeoutMs, cursorBefore);
|
|
454
|
+
if (expectDrift !== undefined) {
|
|
371
455
|
result.ok = false;
|
|
372
|
-
result.drift =
|
|
456
|
+
result.drift = expectDrift;
|
|
373
457
|
}
|
|
374
458
|
}
|
|
375
459
|
if (page !== undefined)
|
|
@@ -4,12 +4,7 @@
|
|
|
4
4
|
* app's flows. These pure helpers pick the flows that belong on a given session's panel and shape them
|
|
5
5
|
* into replay chips — testable without a bridge or a filesystem.
|
|
6
6
|
*/
|
|
7
|
-
import { type FlowFile } from '@reticlehq/core';
|
|
8
|
-
/** A replayable flow as the HUD renders it: a name + an optional page it can start from. */
|
|
9
|
-
export interface FlowChip {
|
|
10
|
-
name: string;
|
|
11
|
-
start?: string;
|
|
12
|
-
}
|
|
7
|
+
import { type FlowChip, type FlowFile } from '@reticlehq/core';
|
|
13
8
|
/**
|
|
14
9
|
* A flow belongs on a session's HUD when it carries no projectId (legacy/global — visible everywhere
|
|
15
10
|
* for back-compat) OR its projectId matches the connecting session's. A flow stamped for project A
|
|
@@ -8,16 +8,36 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult } from '@reticlehq/core';
|
|
10
10
|
import type { FlowReplaySession, Sleep } from './flow-replay.js';
|
|
11
|
-
/** Run one role+name-anchored step: re-resolve via QUERY by:'role', ACT on the live ref, else drift. */
|
|
12
11
|
export declare function runRoleStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
|
|
13
12
|
kind: typeof AnchorKind.ROLE;
|
|
14
13
|
}>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
|
|
15
|
-
/**
|
|
16
|
-
* Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
|
|
17
|
-
* including the action window, whose open/close must not depend on which anchor found the element.
|
|
18
|
-
*/
|
|
19
|
-
export declare function actOnResolvedRef(session: FlowReplaySession, step: FlowStep, index: number, label: string, ref: string, confirmDangerous: boolean): Promise<FlowStepResult>;
|
|
20
14
|
/** Run one component-anchored step: re-resolve via QUERY by:'component', ACT on the live ref, else drift. */
|
|
21
15
|
export declare function runComponentStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
|
|
22
16
|
kind: typeof AnchorKind.COMPONENT;
|
|
23
17
|
}>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
|
|
18
|
+
/**
|
|
19
|
+
* DEGRADED_ANCHOR_ROLE is a MARKER ("no anchor could be determined"), never a locator.
|
|
20
|
+
*
|
|
21
|
+
* Recognising it is most of the fix: a nameless-ROLE anchor used to fall through to the testid
|
|
22
|
+
* runner, which asked the DOM eight times for a testid literally named "unresolved", found none (it
|
|
23
|
+
* never exists), and reported a MISSING ELEMENT. It then ran edit-distance against the word
|
|
24
|
+
* "unresolved" and offered the nearest testid as a rebind target — which flow_verify printed while
|
|
25
|
+
* flow_heal refused it on confidence. Two tools contradicting each other over a candidate that never
|
|
26
|
+
* meant anything.
|
|
27
|
+
*
|
|
28
|
+
* Both recorders emit this sentinel, so the check belongs here on the replay side, where all of them
|
|
29
|
+
* land.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isDegradedAnchor(anchor: FlowAnchor): boolean;
|
|
32
|
+
export declare function degradedStepResult(step: FlowStep, index: number, label: string): FlowStepResult;
|
|
33
|
+
/**
|
|
34
|
+
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
35
|
+
* ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
|
|
36
|
+
* execute identically.
|
|
37
|
+
*
|
|
38
|
+
* `replayFlow` had no sequence branch at all. It dispatches on `anchor.kind`, so a saved sequence fell
|
|
39
|
+
* to the testid runner and ran ONE act with `action: ''` (a saved sequence carries no top-level
|
|
40
|
+
* action) — sub-steps 2..n never executed. Fixing the anchor without this would have turned a visible
|
|
41
|
+
* drift into a silent partial replay reporting ok.
|
|
42
|
+
*/
|
|
43
|
+
export declare function runSequenceStep(session: FlowReplaySession, step: FlowStep, index: number, subs: readonly FlowStep[], confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* point: the action window must open and close identically no matter which anchor found the element,
|
|
7
7
|
* or a step's events land in the wrong window depending on how it was addressed.
|
|
8
8
|
*/
|
|
9
|
-
import { AnchorKind, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
|
|
9
|
+
import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
|
|
10
10
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
11
11
|
import { replayActionArgs } from './replay.js';
|
|
12
|
-
import { componentLabel, componentQueryArgs, resolveQuery } from './flow-replay.js';
|
|
12
|
+
import { anchorLabel, componentLabel, componentQueryArgs, resolveQuery, testidDrift, } from './flow-replay.js';
|
|
13
|
+
import { nearestRoleName } from './role-anchor-nearest.js';
|
|
14
|
+
import { roleDriftReason } from './role-drift-reason.js';
|
|
13
15
|
/** Query args for a NAMED role anchor — the handle that identifies an instance, not a JSX site. */
|
|
14
16
|
function roleQueryArgs(anchor) {
|
|
15
17
|
return {
|
|
@@ -19,11 +21,53 @@ function roleQueryArgs(anchor) {
|
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
/** Run one role+name-anchored step: re-resolve via QUERY by:'role', ACT on the live ref, else drift. */
|
|
24
|
+
/**
|
|
25
|
+
* The controls actually on the page, as role + name, for the nearest-match scan.
|
|
26
|
+
*
|
|
27
|
+
* Reads the interactive snapshot rather than a second query per candidate: one round trip, and it is
|
|
28
|
+
* the same view the coverage tool uses, so "what is on this page" has one definition.
|
|
29
|
+
*/
|
|
30
|
+
async function nearestRoleNameOnPage(session, anchor) {
|
|
31
|
+
const wanted = 'string' === typeof anchor.name ? anchor.name : undefined;
|
|
32
|
+
if (wanted === undefined)
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
// Straight off the QUERY result: resolveQuery reduces to refs, and the NAMES are what this needs.
|
|
36
|
+
const result = await session.command(ReticleCommand.QUERY, {
|
|
37
|
+
by: QueryBy.ROLE,
|
|
38
|
+
value: anchor.role,
|
|
39
|
+
});
|
|
40
|
+
const payload = result.result;
|
|
41
|
+
const elements = 'object' === typeof payload && payload !== null
|
|
42
|
+
? payload.elements
|
|
43
|
+
: undefined;
|
|
44
|
+
if (!Array.isArray(elements))
|
|
45
|
+
return null;
|
|
46
|
+
const candidates = elements
|
|
47
|
+
.map((element) => 'object' === typeof element && element !== null
|
|
48
|
+
? element.name
|
|
49
|
+
: undefined)
|
|
50
|
+
.filter((name) => 'string' === typeof name && name.length > 0)
|
|
51
|
+
.map((name) => ({ role: anchor.role, name }));
|
|
52
|
+
return nearestRoleName(anchor.role, wanted, candidates);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// A drift report must never fail because the suggestion lookup did.
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
22
59
|
export async function runRoleStep(session, step, index, anchor, confirmDangerous, sleep) {
|
|
23
60
|
const label = `${anchor.role} "${String(anchor.name)}"`;
|
|
24
61
|
const { refs } = await resolveQuery(session, roleQueryArgs(anchor), sleep);
|
|
25
62
|
const ref = refs[0];
|
|
26
63
|
if (ref === undefined) {
|
|
64
|
+
// `nearest` used to be the literal null, so heal answered "no nearest match cleared the
|
|
65
|
+
// confidence floor" for EVERY role-anchored drift — a structural limit reported as a judgement
|
|
66
|
+
// about candidates, when nothing had looked. It looks now, and it is deliberately stricter than
|
|
67
|
+
// the testid path: a role name is user-visible text, so a near-match is far more likely to be a
|
|
68
|
+
// different control than a renamed one. A candidate here is INFORMATION for the agent; heal
|
|
69
|
+
// still refuses to rebind a role anchor on its own.
|
|
70
|
+
const nearest = await nearestRoleNameOnPage(session, anchor);
|
|
27
71
|
return {
|
|
28
72
|
step: index,
|
|
29
73
|
tool: step.tool,
|
|
@@ -31,9 +75,9 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
31
75
|
ok: false,
|
|
32
76
|
drift: {
|
|
33
77
|
reasonKind: DriftReason.COMPONENT_NOT_FOUND,
|
|
34
|
-
reason:
|
|
78
|
+
reason: roleDriftReason(anchor.role, String(anchor.name), nearest),
|
|
35
79
|
anchor: label,
|
|
36
|
-
nearest
|
|
80
|
+
nearest,
|
|
37
81
|
},
|
|
38
82
|
};
|
|
39
83
|
}
|
|
@@ -43,7 +87,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
43
87
|
* Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
|
|
44
88
|
* including the action window, whose open/close must not depend on which anchor found the element.
|
|
45
89
|
*/
|
|
46
|
-
|
|
90
|
+
async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous) {
|
|
47
91
|
session.beginAction?.(ReticleTool.FLOW_REPLAY, { ref, action: step.action ?? '' });
|
|
48
92
|
let act;
|
|
49
93
|
try {
|
|
@@ -65,7 +109,7 @@ export async function actOnResolvedRef(session, step, index, label, ref, confirm
|
|
|
65
109
|
export async function runComponentStep(session, step, index, anchor, confirmDangerous, sleep) {
|
|
66
110
|
const label = componentLabel(anchor);
|
|
67
111
|
const { refs } = await resolveQuery(session, componentQueryArgs(anchor), sleep);
|
|
68
|
-
if (refs.length
|
|
112
|
+
if (0 === refs.length) {
|
|
69
113
|
return {
|
|
70
114
|
step: index,
|
|
71
115
|
tool: step.tool,
|
|
@@ -100,3 +144,109 @@ export async function runComponentStep(session, step, index, anchor, confirmDang
|
|
|
100
144
|
result.error = act.error ?? 'command failed';
|
|
101
145
|
return result;
|
|
102
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* DEGRADED_ANCHOR_ROLE is a MARKER ("no anchor could be determined"), never a locator.
|
|
149
|
+
*
|
|
150
|
+
* Recognising it is most of the fix: a nameless-ROLE anchor used to fall through to the testid
|
|
151
|
+
* runner, which asked the DOM eight times for a testid literally named "unresolved", found none (it
|
|
152
|
+
* never exists), and reported a MISSING ELEMENT. It then ran edit-distance against the word
|
|
153
|
+
* "unresolved" and offered the nearest testid as a rebind target — which flow_verify printed while
|
|
154
|
+
* flow_heal refused it on confidence. Two tools contradicting each other over a candidate that never
|
|
155
|
+
* meant anything.
|
|
156
|
+
*
|
|
157
|
+
* Both recorders emit this sentinel, so the check belongs here on the replay side, where all of them
|
|
158
|
+
* land.
|
|
159
|
+
*/
|
|
160
|
+
export function isDegradedAnchor(anchor) {
|
|
161
|
+
return (anchor.kind === AnchorKind.ROLE &&
|
|
162
|
+
anchor.role === DEGRADED_ANCHOR_ROLE &&
|
|
163
|
+
anchor.name === undefined);
|
|
164
|
+
}
|
|
165
|
+
/** What a step with no resolvable anchor reports, instead of a missing-element story. */
|
|
166
|
+
const DEGRADED_STEP_REASON = 'recorded without a resolvable anchor (no data-testid, no accessible role+name), so it can never ' +
|
|
167
|
+
'resolve on replay — add a data-testid to the element and record this flow again';
|
|
168
|
+
export function degradedStepResult(step, index, label) {
|
|
169
|
+
return {
|
|
170
|
+
step: index,
|
|
171
|
+
tool: step.tool,
|
|
172
|
+
anchor: label,
|
|
173
|
+
ok: false,
|
|
174
|
+
drift: {
|
|
175
|
+
reasonKind: DriftReason.ANCHOR_DEGRADED,
|
|
176
|
+
reason: DEGRADED_STEP_REASON,
|
|
177
|
+
anchor: label,
|
|
178
|
+
// Deliberately null: the old path produced a nearest match to the WORD "unresolved", which is
|
|
179
|
+
// not a candidate for anything.
|
|
180
|
+
nearest: null,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** QUERY args for an element anchor — null when the anchor addresses no element. */
|
|
185
|
+
function anchorQueryArgs(anchor) {
|
|
186
|
+
if (anchor.kind === AnchorKind.TESTID)
|
|
187
|
+
return { by: QueryBy.TESTID, value: anchor.value };
|
|
188
|
+
if (anchor.kind === AnchorKind.COMPONENT)
|
|
189
|
+
return componentQueryArgs(anchor);
|
|
190
|
+
if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor))
|
|
191
|
+
return roleQueryArgs(anchor);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
196
|
+
* ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
|
|
197
|
+
* execute identically.
|
|
198
|
+
*
|
|
199
|
+
* `replayFlow` had no sequence branch at all. It dispatches on `anchor.kind`, so a saved sequence fell
|
|
200
|
+
* to the testid runner and ran ONE act with `action: ''` (a saved sequence carries no top-level
|
|
201
|
+
* action) — sub-steps 2..n never executed. Fixing the anchor without this would have turned a visible
|
|
202
|
+
* drift into a silent partial replay reporting ok.
|
|
203
|
+
*/
|
|
204
|
+
export async function runSequenceStep(session, step, index, subs, confirmDangerous, sleep) {
|
|
205
|
+
const live = [];
|
|
206
|
+
for (const [subIndex, sub] of subs.entries()) {
|
|
207
|
+
const label = `${anchorLabel(sub.anchor)} (sub-step ${String(subIndex)})`;
|
|
208
|
+
const queryArgs = anchorQueryArgs(sub.anchor);
|
|
209
|
+
if (null === queryArgs)
|
|
210
|
+
return degradedStepResult(step, index, label);
|
|
211
|
+
const { refs, hint } = await resolveQuery(session, queryArgs, sleep);
|
|
212
|
+
const ref = refs[0];
|
|
213
|
+
if (ref === undefined) {
|
|
214
|
+
return {
|
|
215
|
+
step: index,
|
|
216
|
+
tool: step.tool,
|
|
217
|
+
anchor: label,
|
|
218
|
+
ok: false,
|
|
219
|
+
drift: sub.anchor.kind === AnchorKind.TESTID
|
|
220
|
+
? testidDrift(sub.anchor.value, hint)
|
|
221
|
+
: {
|
|
222
|
+
reasonKind: DriftReason.COMPONENT_NOT_FOUND,
|
|
223
|
+
reason: `anchor ${label} not found`,
|
|
224
|
+
anchor: label,
|
|
225
|
+
nearest: null,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
live.push({
|
|
230
|
+
ref,
|
|
231
|
+
action: sub.action ?? '',
|
|
232
|
+
args: replayActionArgs(sub.args, confirmDangerous),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
session.beginAction?.(ReticleTool.FLOW_REPLAY, { steps: live.length });
|
|
236
|
+
let act;
|
|
237
|
+
try {
|
|
238
|
+
act = await session.command(ReticleCommand.ACT_SEQUENCE, { steps: live });
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
session.finishAction?.();
|
|
242
|
+
}
|
|
243
|
+
const result = {
|
|
244
|
+
step: index,
|
|
245
|
+
tool: step.tool,
|
|
246
|
+
anchor: anchorLabel(step.anchor),
|
|
247
|
+
ok: act.ok,
|
|
248
|
+
};
|
|
249
|
+
if (!act.ok)
|
|
250
|
+
result.error = act.error ?? 'command failed';
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
|
|
13
13
|
* Pure: no IO, no clock.
|
|
14
14
|
*/
|
|
15
|
-
import { AnchorKind } from '@reticlehq/core';
|
|
15
|
+
import { AnchorKind, PredicateKind } from '@reticlehq/core';
|
|
16
16
|
/** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
|
|
17
17
|
export function dynamicTestids(flow) {
|
|
18
18
|
return new Set((flow.dynamic ?? [])
|
|
@@ -28,7 +28,7 @@ export function successLabel(success) {
|
|
|
28
28
|
if (success.net !== undefined)
|
|
29
29
|
return success.net.urlContains ?? success.net.method ?? 'net';
|
|
30
30
|
if (success.console !== undefined) {
|
|
31
|
-
return `console:${success.console.absent
|
|
31
|
+
return `console:${true === success.console.absent ? 'clean' : (success.console.level ?? 'error')}`;
|
|
32
32
|
}
|
|
33
33
|
if (success.state !== undefined)
|
|
34
34
|
return `state:${success.state.path}`;
|
|
@@ -39,11 +39,11 @@ export function successToPredicate(success, dynamic) {
|
|
|
39
39
|
const parts = [];
|
|
40
40
|
if (success.signal !== undefined) {
|
|
41
41
|
parts.push(success.signalData !== undefined
|
|
42
|
-
? { kind:
|
|
43
|
-
: { kind:
|
|
42
|
+
? { kind: PredicateKind.SIGNAL, name: success.signal, dataMatches: success.signalData }
|
|
43
|
+
: { kind: PredicateKind.SIGNAL, name: success.signal });
|
|
44
44
|
}
|
|
45
45
|
if (success.net !== undefined) {
|
|
46
|
-
const net = { kind:
|
|
46
|
+
const net = { kind: PredicateKind.NET };
|
|
47
47
|
if (success.net.method !== undefined)
|
|
48
48
|
net.method = success.net.method;
|
|
49
49
|
if (success.net.urlContains !== undefined)
|
|
@@ -56,12 +56,14 @@ export function successToPredicate(success, dynamic) {
|
|
|
56
56
|
// exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
|
|
57
57
|
// before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
|
|
58
58
|
// only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
|
|
59
|
-
parts.push({ kind:
|
|
59
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
60
60
|
}
|
|
61
61
|
parts.push(net);
|
|
62
62
|
}
|
|
63
63
|
if (success.console !== undefined) {
|
|
64
|
-
const con = {
|
|
64
|
+
const con = {
|
|
65
|
+
kind: PredicateKind.CONSOLE,
|
|
66
|
+
};
|
|
65
67
|
if (success.console.level !== undefined)
|
|
66
68
|
con.level = success.console.level;
|
|
67
69
|
if (success.console.absent !== undefined) {
|
|
@@ -70,13 +72,16 @@ export function successToPredicate(success, dynamic) {
|
|
|
70
72
|
// error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
|
|
71
73
|
// the console is read only after the page quiets, by which point any error is in the buffer.
|
|
72
74
|
if (success.console.absent)
|
|
73
|
-
parts.push({ kind:
|
|
75
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
74
76
|
}
|
|
75
77
|
parts.push(con);
|
|
76
78
|
}
|
|
77
79
|
const state = success.state;
|
|
78
80
|
if (state !== undefined) {
|
|
79
|
-
const part = {
|
|
81
|
+
const part = {
|
|
82
|
+
kind: PredicateKind.STATE,
|
|
83
|
+
path: state.path,
|
|
84
|
+
};
|
|
80
85
|
if (state.store !== undefined)
|
|
81
86
|
part.store = state.store;
|
|
82
87
|
if (state.equals !== undefined)
|
|
@@ -85,8 +90,8 @@ export function successToPredicate(success, dynamic) {
|
|
|
85
90
|
// wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
|
|
86
91
|
// value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
|
|
87
92
|
// on `settled` so the read happens after the page quiets, by which point the leak has landed.
|
|
88
|
-
if (state.hold
|
|
89
|
-
parts.push({ kind:
|
|
93
|
+
if (true === state.hold)
|
|
94
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
90
95
|
parts.push(part);
|
|
91
96
|
}
|
|
92
97
|
const element = success.element;
|
|
@@ -102,15 +107,15 @@ export function successToPredicate(success, dynamic) {
|
|
|
102
107
|
if (element.name !== undefined)
|
|
103
108
|
query['name'] = element.name;
|
|
104
109
|
if (Object.keys(query).length > 0)
|
|
105
|
-
parts.push({ kind:
|
|
110
|
+
parts.push({ kind: PredicateKind.ELEMENT, query });
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
const [first] = parts;
|
|
109
|
-
if (parts.length
|
|
114
|
+
if (0 === parts.length)
|
|
110
115
|
return undefined;
|
|
111
|
-
if (parts.length
|
|
116
|
+
if (1 === parts.length && first !== undefined)
|
|
112
117
|
return first;
|
|
113
|
-
return { kind:
|
|
118
|
+
return { kind: PredicateKind.ALL_OF, predicates: parts };
|
|
114
119
|
}
|
|
115
120
|
/**
|
|
116
121
|
* Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
|