@reticlehq/server 3.9.0 → 3.10.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 +13 -12
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -187
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/cli-doctor.js +1 -1
- package/dist/command/cli/cli-flow-commands.js +2 -9
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +17 -15
- package/dist/command/cli/cli-parse.js +44 -27
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +58 -29
- package/dist/command/cli/cli-verify.d.ts +11 -2
- package/dist/command/cli/cli-verify.js +27 -8
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor-repairs.js +1 -1
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
- package/dist/command/cli/ports/port-holder.d.ts +9 -0
- package/dist/command/cli/ports/port-holder.js +27 -3
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +22 -0
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +7 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +46 -41
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +12 -1
- package/dist/command/daemon/daemon.js +35 -4
- package/dist/command/daemon/lifetime/death-cause.js +21 -2
- package/dist/command/daemon/lifetime/heartbeat.js +7 -2
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-runtime.js +6 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +6 -3
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/version-skew.js +1 -1
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/visual/visual-tools.js +66 -19
- package/dist/index.js +7 -4
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +44 -0
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
- package/dist/language/flows/recording/tape/recordings.js +71 -5
- package/dist/language/flows/replay.d.ts +19 -5
- package/dist/language/flows/replay.js +99 -7
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +28 -9
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +22 -20
- package/dist/portal/input/real-input.js +41 -31
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +75 -18
- package/dist/portal/pool/browser-pool.d.ts +11 -29
- package/dist/portal/pool/browser-pool.js +35 -17
- package/dist/portal/pool/context-permissions.d.ts +17 -3
- package/dist/portal/pool/context-permissions.js +36 -5
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +44 -17
- package/dist/portal/pool/lease-page-error.js +147 -5
- package/dist/portal/pool/page-error-journal.d.ts +7 -1
- package/dist/portal/pool/page-error-journal.js +27 -0
- package/dist/portal/pool/playwright-launcher.js +51 -8
- package/dist/portal/pool/pool-contract.d.ts +27 -1
- package/dist/portal/pool/pool-lease.d.ts +36 -1
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +23 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
- package/dist/portal/session/dev-server/dev-command.js +61 -15
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -25
- package/dist/portal/session/no-session-next-action.d.ts +2 -0
- package/dist/portal/session/no-session-next-action.js +12 -3
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +38 -3
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +174 -63
- package/dist/start-wiring.d.ts +2 -1
- package/dist/start-wiring.js +2 -1
- package/dist/surface/mcp/mcp.js +3 -2
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +1 -1
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/after-match.d.ts +4 -1
- package/dist/surface/tools/act/after-match.js +8 -1
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +15 -1
- package/dist/surface/tools/act/resolve-target.js +24 -1
- package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
- package/dist/surface/tools/act/sequence-grade.js +37 -26
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +78 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +74 -86
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +16 -37
- package/dist/surface/tools/browser-tools.js +12 -6
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +9 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +91 -21
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +3 -2
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +35 -1
- package/dist/surface/tools/lease-hint.js +42 -0
- package/dist/surface/tools/lease-readiness.d.ts +26 -0
- package/dist/surface/tools/lease-readiness.js +79 -0
- package/dist/surface/tools/lease-seed.d.ts +14 -1
- package/dist/surface/tools/lease-seed.js +35 -3
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +62 -110
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +6 -0
- package/dist/surface/tools/tool-kit.js +2 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +28 -5
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +1 -0
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +8 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +1 -1
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/license.mdx +9 -7
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +33 -23
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +2 -2
- package/docs/environment.mdx +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +2 -2
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +13 -13
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -9,25 +9,17 @@
|
|
|
9
9
|
import { expectedElementTestid } from '@reticlehq/core';
|
|
10
10
|
import { AnchorKind, DEGRADED_ANCHOR_ROLE, DOCUMENT_ANCHOR_ROLE, DriftReason, PredicateKind, QueryBy, ReplayStatus, ReticleCommand, } from '@reticlehq/core';
|
|
11
11
|
import { ReticleTool, isGlobalPressCall } from '@reticlehq/core';
|
|
12
|
-
import { replayActionArgs } from './replay.js';
|
|
13
|
-
import { isStaleRefError } from '../../surface/tools/act/act-sequence-retry.js';
|
|
12
|
+
import { namelessRefs, replayActionArgs } from './replay.js';
|
|
13
|
+
import { burstGap, holdGap, isStaleRefError, pacedArgs, } from '../../surface/tools/act/act-sequence-retry.js';
|
|
14
14
|
import { waitForReaction } from '../../surface/tools/act/react-grace.js';
|
|
15
|
-
import { unreachableTargetNote } from '../../surface/tools/act/act-observation.js';
|
|
15
|
+
import { unreachableSubStepDrift, unreachableTargetNote, } from '../../surface/tools/act/act-observation.js';
|
|
16
16
|
import { anchorFieldName } from './flows.js';
|
|
17
|
-
import { ambiguityDrift, refFor, anchorLabel, componentLabel, componentQueryArgs, expectElementDrift, queryOnce, reresolved, resolveQuery, testidDrift, } from './flow-anchor.js';
|
|
17
|
+
import { ambiguityDrift, refFor, anchorLabel, componentLabel, componentQueryArgs, expectElementDrift, queryOnce, reresolved, resolveRoleAnchor, roleQueryArgs, resolveQuery, roleAnchorLabel, testidDrift, } from './flow-anchor.js';
|
|
18
18
|
import { nearestRoleName } from './role-anchor-nearest.js';
|
|
19
19
|
import { roleDriftReason } from '../../judgement/outcome/role-drift-reason.js';
|
|
20
|
+
import { captureBaselines } from '@reticlehq/engine/evidence/baseline.js';
|
|
20
21
|
/** The live act_sequence tool gives each step this transport budget when none was supplied. */
|
|
21
22
|
export const DEFAULT_SEQUENCE_STEP_TIMEOUT_MS = 8_000;
|
|
22
|
-
/** Query args for a NAMED role anchor — the handle that identifies an instance, not a JSX site. */
|
|
23
|
-
function roleQueryArgs(anchor) {
|
|
24
|
-
return {
|
|
25
|
-
by: QueryBy.ROLE,
|
|
26
|
-
value: anchor.role,
|
|
27
|
-
...(anchor.name === undefined ? {} : { name: anchor.name }),
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/** Run one role+name-anchored step: re-resolve via QUERY by:'role', ACT on the live ref, else drift. */
|
|
31
23
|
/**
|
|
32
24
|
* The controls actually on the page, as role + name, for the nearest-match scan.
|
|
33
25
|
*
|
|
@@ -36,8 +28,9 @@ function roleQueryArgs(anchor) {
|
|
|
36
28
|
*/
|
|
37
29
|
async function nearestRoleNameOnPage(session, anchor) {
|
|
38
30
|
const wanted = 'string' === typeof anchor.name ? anchor.name : undefined;
|
|
39
|
-
if (wanted === undefined)
|
|
31
|
+
if (wanted === undefined) {
|
|
40
32
|
return null;
|
|
33
|
+
}
|
|
41
34
|
try {
|
|
42
35
|
// Straight off the QUERY result: resolveQuery reduces to refs, and the NAMES are what this needs.
|
|
43
36
|
const result = await session.command(ReticleCommand.QUERY, {
|
|
@@ -48,8 +41,9 @@ async function nearestRoleNameOnPage(session, anchor) {
|
|
|
48
41
|
const elements = 'object' === typeof payload && payload !== null
|
|
49
42
|
? payload.elements
|
|
50
43
|
: undefined;
|
|
51
|
-
if (!Array.isArray(elements))
|
|
44
|
+
if (!Array.isArray(elements)) {
|
|
52
45
|
return null;
|
|
46
|
+
}
|
|
53
47
|
const candidates = elements
|
|
54
48
|
.map((element) => 'object' === typeof element && element !== null
|
|
55
49
|
? element.name
|
|
@@ -64,27 +58,33 @@ async function nearestRoleNameOnPage(session, anchor) {
|
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
export async function runRoleStep(session, step, index, anchor, confirmDangerous, sleep) {
|
|
67
|
-
const label =
|
|
68
|
-
const { refs } = await
|
|
61
|
+
const label = roleAnchorLabel(anchor);
|
|
62
|
+
const { refs } = await resolveRoleAnchor(session, anchor, sleep);
|
|
69
63
|
/*
|
|
70
|
-
* More than one match is DRIFT, not a guess
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* reads `drift` and `ok` and nothing else, so a green here is indistinguishable from a replay that
|
|
74
|
-
* did what it did before.
|
|
75
|
-
*
|
|
76
|
-
* A role+name anchor is MORE likely to land here than a testid is: `button "View result"` is
|
|
77
|
-
* user-visible text, and a table repeats it by nature, so several matches is the expected shape of
|
|
78
|
-
* a recorded row action rather than an accident.
|
|
79
|
-
*
|
|
80
|
-
* The check itself lives in `ambiguityDrift`, shared with the testid and component runners: written
|
|
81
|
-
* per-runner, this rule got applied to two anchor kinds and forgotten for the third.
|
|
64
|
+
* More than one match is DRIFT, not a guess: taking `refs[0]` replayed a recorded row action on
|
|
65
|
+
* row 1 and passed whenever every row shared the consequence. `ambiguityDrift` holds the rule,
|
|
66
|
+
* shared with the testid and component runners.
|
|
82
67
|
*/
|
|
83
68
|
const ambiguous = ambiguityDrift(anchor, refs);
|
|
84
69
|
if (ambiguous !== null) {
|
|
85
70
|
return { step: index, tool: step.tool, anchor: label, ok: false, drift: ambiguous };
|
|
86
71
|
}
|
|
87
72
|
const ref = refFor(anchor, refs);
|
|
73
|
+
if (ref === undefined && anchor.name === undefined) {
|
|
74
|
+
// There is no name to suggest a nearest match from; say what was looked for and what was found.
|
|
75
|
+
return {
|
|
76
|
+
step: index,
|
|
77
|
+
tool: step.tool,
|
|
78
|
+
anchor: label,
|
|
79
|
+
ok: false,
|
|
80
|
+
drift: {
|
|
81
|
+
reasonKind: DriftReason.COMPONENT_NOT_FOUND,
|
|
82
|
+
reason: `no ${anchor.role} with no accessible name was found at the recorded position (${label}); the page has ${String(refs.length)} such control(s). Give the control an accessible name or a test id, or re-record the step`,
|
|
83
|
+
anchor: label,
|
|
84
|
+
nearest: null,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
88
|
if (ref === undefined) {
|
|
89
89
|
// `nearest` used to be the literal null, so heal answered "no nearest match cleared the
|
|
90
90
|
// confidence floor" for EVERY role-anchored drift — a structural limit reported as a judgement
|
|
@@ -115,7 +115,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
115
115
|
* re-make the guess the check above exists to refuse — one layer deeper, where the caller cannot
|
|
116
116
|
* see it. `reresolved` is the same rule as that check, so the two cannot disagree.
|
|
117
117
|
*/
|
|
118
|
-
async () => reresolved(anchor, (await
|
|
118
|
+
async () => reresolved(anchor, (await resolveRoleAnchor(session, anchor, sleep)).refs));
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
121
|
* `assertActionAllowed`'s refusal — "potentially destructive action blocked; retry with
|
|
@@ -132,8 +132,9 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
132
132
|
*/
|
|
133
133
|
const DESTRUCTIVE_ACTION_PATTERN = /potentially destructive (?:\w+ )*(?:action|tool) blocked/i;
|
|
134
134
|
function replayDestructiveActionHint(rawError) {
|
|
135
|
-
if (!DESTRUCTIVE_ACTION_PATTERN.test(rawError))
|
|
135
|
+
if (!DESTRUCTIVE_ACTION_PATTERN.test(rawError)) {
|
|
136
136
|
return rawError;
|
|
137
|
+
}
|
|
137
138
|
return (`${rawError} — this is a flow REPLAY: a step's own args.confirmDangerous is stripped before ` +
|
|
138
139
|
'dispatch and never persists, so editing the flow file does nothing. Pass it at the call level ' +
|
|
139
140
|
`instead: ${ReticleTool.FLOW_REPLAY} { confirmDangerous: true } acknowledges every flagged step ` +
|
|
@@ -211,15 +212,17 @@ reresolve) {
|
|
|
211
212
|
if (fresh !== undefined && 'drift' in fresh) {
|
|
212
213
|
return { step: index, tool: step.tool, anchor: label, ok: false, drift: fresh.drift };
|
|
213
214
|
}
|
|
214
|
-
if (fresh !== undefined)
|
|
215
|
+
if (fresh !== undefined) {
|
|
215
216
|
act = await dispatch(fresh.ref);
|
|
217
|
+
}
|
|
216
218
|
}
|
|
217
219
|
const result = { step: index, tool: step.tool, anchor: label, ok: act.ok };
|
|
218
|
-
if (!act.ok)
|
|
220
|
+
if (!act.ok) {
|
|
219
221
|
result.error = replayDestructiveActionHint(act.error ?? 'command failed');
|
|
222
|
+
}
|
|
220
223
|
// A click no person could have made decides nothing, on replay as on a live act.
|
|
221
224
|
const unreachable = act.ok ? unreachableTargetNote(act.result) : undefined;
|
|
222
|
-
if (unreachable !== undefined)
|
|
225
|
+
if (unreachable !== undefined) {
|
|
223
226
|
return {
|
|
224
227
|
...result,
|
|
225
228
|
ok: false,
|
|
@@ -230,6 +233,7 @@ reresolve) {
|
|
|
230
233
|
nearest: null,
|
|
231
234
|
},
|
|
232
235
|
};
|
|
236
|
+
}
|
|
233
237
|
return result;
|
|
234
238
|
}
|
|
235
239
|
/** How long to let the app finish re-rendering before the one retry. Matches the sequence path. */
|
|
@@ -266,7 +270,9 @@ export async function runComponentStep(session, step, index, anchor, confirmDang
|
|
|
266
270
|
if (ambiguous !== null) {
|
|
267
271
|
return { step: index, tool: step.tool, anchor: label, ok: false, drift: ambiguous };
|
|
268
272
|
}
|
|
269
|
-
|
|
273
|
+
// The only match, or the recorded position (`ambiguityDrift` has cleared every other case). It took
|
|
274
|
+
// `refs[0]`, so a recorded position that passed the check still acted on the first match.
|
|
275
|
+
const ref = refFor(anchor, refs) ?? '';
|
|
270
276
|
// Shared with the role path so both get the same action window AND the same stale-ref retry —
|
|
271
277
|
// this used to dispatch inline, which is how one anchor kind could gain a fix the other lacked.
|
|
272
278
|
return await actOnResolvedRef(session, step, index, label, ref, confirmDangerous, anchorFieldName(anchor),
|
|
@@ -340,10 +346,12 @@ export function degradedStepResult(step, index, label) {
|
|
|
340
346
|
* the step runner's own resolution asking the same thing of the same anchor.
|
|
341
347
|
*/
|
|
342
348
|
export function anchorQueryArgs(anchor) {
|
|
343
|
-
if (anchor.kind === AnchorKind.TESTID)
|
|
349
|
+
if (anchor.kind === AnchorKind.TESTID) {
|
|
344
350
|
return { by: QueryBy.TESTID, value: anchor.value };
|
|
345
|
-
|
|
351
|
+
}
|
|
352
|
+
if (anchor.kind === AnchorKind.COMPONENT) {
|
|
346
353
|
return componentQueryArgs(anchor);
|
|
354
|
+
}
|
|
347
355
|
if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor) && !isDocumentAnchor(anchor)) {
|
|
348
356
|
return roleQueryArgs(anchor);
|
|
349
357
|
}
|
|
@@ -388,8 +396,9 @@ const staleTargetReason = (step) => `step ${String(step)} could not be acted on:
|
|
|
388
396
|
*/
|
|
389
397
|
function staleSubStep(error, live, from, to) {
|
|
390
398
|
const named = /ref '([^']*)'/.exec(error)?.[1];
|
|
391
|
-
if (named === undefined)
|
|
399
|
+
if (named === undefined) {
|
|
392
400
|
return undefined;
|
|
401
|
+
}
|
|
393
402
|
const sharing = live.flatMap((one, at) => at >= from && at < to && one.ref === named ? [at] : []);
|
|
394
403
|
const [only] = sharing;
|
|
395
404
|
return 1 === sharing.length ? only : undefined;
|
|
@@ -407,17 +416,27 @@ function staleSubStep(error, live, from, to) {
|
|
|
407
416
|
* Left alone, so the ordinary verdict stands, when anything else is in the run: a drifted anchor is
|
|
408
417
|
* drift whatever else happened, and a failure that is not staleness is a failure.
|
|
409
418
|
*/
|
|
410
|
-
export function staleTargetResult(name, steps
|
|
419
|
+
export function staleTargetResult(name, steps,
|
|
420
|
+
/**
|
|
421
|
+
* `startControlRequired`: replay loaded the start page itself, with no redirect, and the caller
|
|
422
|
+
* asked that a missing step 1 control fail. Then the miss means the app does not offer the control
|
|
423
|
+
* the journey starts with (#2061), so it stays drift. Without it, the miss is not proved.
|
|
424
|
+
*/
|
|
425
|
+
options = {}) {
|
|
411
426
|
// A target no person could reach is the same kind of answer: nothing about the app was decided.
|
|
412
|
-
const
|
|
413
|
-
|
|
427
|
+
const unreached = true === options.startControlRequired ? undefined : unreachedStepResult(name, steps);
|
|
428
|
+
const unreachable = unreachableTargetResult(name, steps) ?? unreached;
|
|
429
|
+
if (unreachable !== undefined) {
|
|
414
430
|
return unreachable;
|
|
415
|
-
|
|
431
|
+
}
|
|
432
|
+
if (steps.some((step) => step.drift !== undefined)) {
|
|
416
433
|
return undefined;
|
|
434
|
+
}
|
|
417
435
|
const failed = steps.filter((step) => !step.ok);
|
|
418
436
|
const [only] = failed;
|
|
419
|
-
if (failed.length !== 1 || only === undefined || !isStaleRefError(only.error))
|
|
437
|
+
if (failed.length !== 1 || only === undefined || !isStaleRefError(only.error)) {
|
|
420
438
|
return undefined;
|
|
439
|
+
}
|
|
421
440
|
return {
|
|
422
441
|
name,
|
|
423
442
|
status: ReplayStatus.UNVERIFIABLE,
|
|
@@ -433,8 +452,9 @@ export function staleTargetResult(name, steps) {
|
|
|
433
452
|
*/
|
|
434
453
|
export function unreachableTargetResult(name, steps) {
|
|
435
454
|
const step = steps.find((s) => DriftReason.TARGET_UNREACHABLE === s.drift?.reasonKind);
|
|
436
|
-
if (step?.drift === undefined)
|
|
455
|
+
if (step?.drift === undefined) {
|
|
437
456
|
return undefined;
|
|
457
|
+
}
|
|
438
458
|
return {
|
|
439
459
|
name,
|
|
440
460
|
status: ReplayStatus.UNVERIFIABLE,
|
|
@@ -456,7 +476,8 @@ const ANCHOR_MISSES = new Set([
|
|
|
456
476
|
* FAIL, and the dashboard showed a failure for a journey that was never tested.
|
|
457
477
|
*
|
|
458
478
|
* Only when no earlier step ran. A control that is missing after earlier steps acted is on a page
|
|
459
|
-
* those steps made, so it stays drift, and heal reads it.
|
|
479
|
+
* those steps made, so it stays drift, and heal reads it. An ambiguous control is the exception: it
|
|
480
|
+
* is on the page, and the replay cannot choose, so it is not proved after any number of steps. A step that acted and whose expectation
|
|
460
481
|
* did not hold is a consequence drift and stays a failure. The failing step keeps its index and
|
|
461
482
|
* its `drift.reasonKind`, which the synced run carries in `stepResults`.
|
|
462
483
|
*/
|
|
@@ -464,14 +485,31 @@ function unreachedStepResult(name, steps) {
|
|
|
464
485
|
const at = steps.findIndex((s) => !s.ok);
|
|
465
486
|
const step = steps[at];
|
|
466
487
|
const drift = step?.drift;
|
|
467
|
-
if (step === undefined || drift === undefined || !ANCHOR_MISSES.has(drift.reasonKind))
|
|
488
|
+
if (step === undefined || drift === undefined || !ANCHOR_MISSES.has(drift.reasonKind)) {
|
|
468
489
|
return undefined;
|
|
469
|
-
|
|
490
|
+
}
|
|
491
|
+
const acted = steps.slice(0, at).some((s) => s.ok);
|
|
492
|
+
// An anchor that several live elements match is on the page, so the earlier steps did not lose it.
|
|
493
|
+
// The replay cannot choose one, so nothing about the app was decided, whatever ran before.
|
|
494
|
+
if (DriftReason.ANCHOR_AMBIGUOUS === drift.reasonKind && acted) {
|
|
495
|
+
return {
|
|
496
|
+
name,
|
|
497
|
+
status: ReplayStatus.UNVERIFIABLE,
|
|
498
|
+
steps: [...steps],
|
|
499
|
+
unverifiable: {
|
|
500
|
+
reason: `step ${String(step.step + 1)}: ${drift.reason}. The replay stopped there, so this run ` +
|
|
501
|
+
`proved nothing about the app. Narrow the anchor on this step.`,
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (acted) {
|
|
470
506
|
return undefined;
|
|
507
|
+
}
|
|
471
508
|
// ponytail: one close, unambiguous survivor reads as a rename (heal's case), so it stays drift. A
|
|
472
509
|
// page with exactly one sibling of the missing control (`toggle-1` for `toggle-3`) reads the same.
|
|
473
|
-
if (drift.nearest !== null && true !== drift.ambiguous)
|
|
510
|
+
if (drift.nearest !== null && true !== drift.ambiguous) {
|
|
474
511
|
return undefined;
|
|
512
|
+
}
|
|
475
513
|
return {
|
|
476
514
|
name,
|
|
477
515
|
status: ReplayStatus.UNVERIFIABLE,
|
|
@@ -485,35 +523,40 @@ function unreachedStepResult(name, steps) {
|
|
|
485
523
|
}
|
|
486
524
|
/**
|
|
487
525
|
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
488
|
-
* ACT_SEQUENCE
|
|
489
|
-
* execute identically.
|
|
490
|
-
*
|
|
491
|
-
* `replayFlow` had no sequence branch at all. It dispatches on `anchor.kind`, so a saved sequence fell
|
|
492
|
-
* to the testid runner and ran ONE act with `action: ''` (a saved sequence carries no top-level
|
|
493
|
-
* action) — sub-steps 2..n never executed. Fixing the anchor without this would have turned a visible
|
|
494
|
-
* drift into a silent partial replay reporting ok.
|
|
526
|
+
* ACT_SEQUENCE, the shape `replayProgram` uses, so a recorded sequence and a saved one run the same.
|
|
495
527
|
*/
|
|
496
528
|
export async function runSequenceStep(session, step, index, subs, confirmDangerous, sleep,
|
|
497
529
|
/** Testids whose presence is deliberately NOT asserted — the LLM-output case. */
|
|
498
530
|
dynamic = new Set(),
|
|
499
|
-
/**
|
|
500
|
-
*
|
|
501
|
-
* sub-step's dispatch. Omitted for a setup step, whose consequences are not checked.
|
|
502
|
-
*/
|
|
531
|
+
/** Judge a sub-step's `expect` from `since`, just before its dispatch, against the `before`
|
|
532
|
+
* reading; omitted for a setup step, whose consequences are not checked. */
|
|
503
533
|
judge) {
|
|
534
|
+
const gap = burstGap(step.burstMs);
|
|
535
|
+
let lastSent;
|
|
504
536
|
/**
|
|
505
537
|
* Find one sub-step's element as the page is NOW. Either the live step to dispatch, or the step
|
|
506
538
|
* result that ends the run: a degraded anchor, or one that is no longer on the page.
|
|
507
539
|
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* for a click nobody can attribute. The first look keeps taking the first match.
|
|
540
|
+
* An anchor that matches several elements is drift, on the first look and on the look after a
|
|
541
|
+
* re-render alike, as it is for a single step: the first match may be an element the recording did
|
|
542
|
+
* not mean, and acting on it reports a result for a click nobody can attribute.
|
|
512
543
|
*
|
|
513
544
|
* `ahead` is a look at a sub-step before its turn. It is one query with no settle. An anchor that is
|
|
514
545
|
* not on the page yet is `pending`, not drift, because an earlier sub-step can render it.
|
|
515
546
|
*/
|
|
516
|
-
const
|
|
547
|
+
const lookUpSub = async (anchor, queryArgs, ahead) => {
|
|
548
|
+
const nameless = AnchorKind.ROLE === anchor.kind && anchor.name === undefined;
|
|
549
|
+
if (ahead) {
|
|
550
|
+
return nameless
|
|
551
|
+
? { refs: namelessRefs(await session.command(ReticleCommand.QUERY, queryArgs)) }
|
|
552
|
+
: await queryOnce(session, queryArgs);
|
|
553
|
+
}
|
|
554
|
+
if (AnchorKind.ROLE === anchor.kind) {
|
|
555
|
+
return await resolveRoleAnchor(session, anchor, sleep);
|
|
556
|
+
}
|
|
557
|
+
return await resolveQuery(session, queryArgs, sleep);
|
|
558
|
+
};
|
|
559
|
+
const resolveSub = async (sub, subIndex, ahead = false) => {
|
|
517
560
|
const label = `${anchorLabel(sub.anchor)} (sub-step ${String(subIndex)})`;
|
|
518
561
|
if (isDocumentPressStep(sub)) {
|
|
519
562
|
// Dispatched as it ran live, at the document with no ref (#1559).
|
|
@@ -521,24 +564,31 @@ judge) {
|
|
|
521
564
|
live: {
|
|
522
565
|
ref: '',
|
|
523
566
|
action: sub.action ?? '',
|
|
524
|
-
args: replayActionArgs(sub.args, confirmDangerous, undefined),
|
|
567
|
+
args: pacedArgs(replayActionArgs(sub.args, confirmDangerous, undefined), gap),
|
|
525
568
|
},
|
|
526
569
|
};
|
|
527
570
|
}
|
|
528
571
|
const queryArgs = anchorQueryArgs(sub.anchor);
|
|
529
|
-
if (null === queryArgs)
|
|
572
|
+
if (null === queryArgs) {
|
|
530
573
|
return { stop: degradedStepResult(step, index, label) };
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
574
|
+
}
|
|
575
|
+
// A role anchor is found as a single step finds it, a nameless one among the nameless controls
|
|
576
|
+
// of its role (#2084: querying every control sent a file upload to the first named button).
|
|
577
|
+
const { refs, hint } = await lookUpSub(sub.anchor, queryArgs, ahead);
|
|
578
|
+
// Several matches with no usable recorded position is drift on every look, as in the single-step
|
|
579
|
+
// runners. The first look used to take `refs[0]`: a click recorded on one item of a list (every
|
|
580
|
+
// item from one JSX line) replayed on another item and failed a correct app. A look ahead is not
|
|
581
|
+
// a verdict, and an earlier sub-step can still change what is on the page.
|
|
582
|
+
const ambiguous = ahead ? null : ambiguityDrift(sub.anchor, refs);
|
|
535
583
|
if (ambiguous !== null) {
|
|
536
584
|
return { stop: { step: index, tool: step.tool, anchor: label, ok: false, drift: ambiguous } };
|
|
537
585
|
}
|
|
538
|
-
|
|
586
|
+
// The only match, or the recorded position while the page has exactly as many namesakes.
|
|
587
|
+
const ref = refFor(sub.anchor, refs);
|
|
539
588
|
if (ref === undefined) {
|
|
540
|
-
if (ahead)
|
|
589
|
+
if (ahead) {
|
|
541
590
|
return { pending: true };
|
|
591
|
+
}
|
|
542
592
|
return {
|
|
543
593
|
stop: {
|
|
544
594
|
step: index,
|
|
@@ -560,9 +610,8 @@ judge) {
|
|
|
560
610
|
live: {
|
|
561
611
|
ref,
|
|
562
612
|
action: sub.action ?? '',
|
|
563
|
-
// Each sub-step carries its OWN anchor, so each gets its own field name
|
|
564
|
-
|
|
565
|
-
args: replayActionArgs(sub.args, confirmDangerous, anchorFieldName(sub.anchor)),
|
|
613
|
+
// Each sub-step carries its OWN anchor, so each gets its own field name (a login's fill).
|
|
614
|
+
args: pacedArgs(replayActionArgs(sub.args, confirmDangerous, anchorFieldName(sub.anchor)), gap),
|
|
566
615
|
},
|
|
567
616
|
};
|
|
568
617
|
};
|
|
@@ -580,13 +629,16 @@ judge) {
|
|
|
580
629
|
const resolveFrom = async (from) => {
|
|
581
630
|
for (let at = from; at < subs.length; at += 1) {
|
|
582
631
|
const sub = subs[at];
|
|
583
|
-
if (sub === undefined)
|
|
632
|
+
if (sub === undefined) {
|
|
584
633
|
continue;
|
|
585
|
-
|
|
586
|
-
|
|
634
|
+
}
|
|
635
|
+
const resolved = await resolveSub(sub, at, at > from);
|
|
636
|
+
if ('pending' in resolved) {
|
|
587
637
|
return { end: at };
|
|
588
|
-
|
|
638
|
+
}
|
|
639
|
+
if ('stop' in resolved) {
|
|
589
640
|
return resolved;
|
|
641
|
+
}
|
|
590
642
|
live[at] = resolved.live;
|
|
591
643
|
}
|
|
592
644
|
return { end: subs.length };
|
|
@@ -623,18 +675,20 @@ judge) {
|
|
|
623
675
|
*
|
|
624
676
|
* The page names the ref it could not find, which says WHICH sub-step failed and so which ones
|
|
625
677
|
* already ran, as long as no other sub-step holds the same ref. Only the rest is resolved again and
|
|
626
|
-
* sent, so nothing acts twice
|
|
627
|
-
*
|
|
628
|
-
* again at the same point is a target that does not hold still, and the run says so, with how far
|
|
629
|
-
* it got.
|
|
678
|
+
* sent, so nothing acts twice. Only a stale ref is retried, and only while the retry gets further:
|
|
679
|
+
* stale again at the same point is a target that does not hold still, and the run says so.
|
|
630
680
|
*/
|
|
631
681
|
const dispatchFrom = async (from, to) => {
|
|
632
682
|
let start = from;
|
|
633
683
|
let staleBefore = -1;
|
|
634
684
|
for (;;) {
|
|
635
685
|
const act = await send(live.slice(start, to));
|
|
636
|
-
if (act.ok)
|
|
637
|
-
|
|
686
|
+
if (act.ok) {
|
|
687
|
+
// A sub-step no person could have clicked decides nothing (#2113).
|
|
688
|
+
const drift = unreachableSubStepDrift(act.result, (at) => `${anchorLabel(subs[start + at]?.anchor ?? step.anchor)} (sub-step ${String(start + at)})`);
|
|
689
|
+
Object.assign(result, drift === undefined ? {} : { ok: false, anchor: drift.anchor, drift });
|
|
690
|
+
return drift === undefined;
|
|
691
|
+
}
|
|
638
692
|
const error = act.error ?? 'command failed';
|
|
639
693
|
if (!isStaleRefError(error)) {
|
|
640
694
|
result.ok = false;
|
|
@@ -656,19 +710,22 @@ judge) {
|
|
|
656
710
|
await waitForReaction(session, 0, STALE_REF_GRACE_MS, { sleep });
|
|
657
711
|
for (let at = staleAt; at < to; at += 1) {
|
|
658
712
|
const sub = subs[at];
|
|
659
|
-
if (sub === undefined)
|
|
713
|
+
if (sub === undefined) {
|
|
660
714
|
continue;
|
|
661
|
-
|
|
715
|
+
}
|
|
716
|
+
const resolved = await resolveSub(sub, at);
|
|
662
717
|
if ('stop' in resolved) {
|
|
663
718
|
result.ok = false;
|
|
664
719
|
result.anchor = resolved.stop.anchor;
|
|
665
|
-
if (resolved.stop.drift !== undefined)
|
|
720
|
+
if (resolved.stop.drift !== undefined) {
|
|
666
721
|
result.drift = resolved.stop.drift;
|
|
722
|
+
}
|
|
667
723
|
return false;
|
|
668
724
|
}
|
|
669
725
|
// A second look settles, so it is never `pending`.
|
|
670
|
-
if ('live' in resolved)
|
|
726
|
+
if ('live' in resolved) {
|
|
671
727
|
live[at] = resolved.live;
|
|
728
|
+
}
|
|
672
729
|
}
|
|
673
730
|
start = staleAt;
|
|
674
731
|
}
|
|
@@ -687,8 +744,9 @@ judge) {
|
|
|
687
744
|
*/
|
|
688
745
|
const assertDeclared = async (testid) => {
|
|
689
746
|
const found = await resolveQuery(session, { by: QueryBy.TESTID, value: testid }, sleep);
|
|
690
|
-
if (0 < found.refs.length)
|
|
747
|
+
if (0 < found.refs.length) {
|
|
691
748
|
return true;
|
|
749
|
+
}
|
|
692
750
|
result.ok = false;
|
|
693
751
|
result.drift = expectElementDrift(testid, found.hint);
|
|
694
752
|
return false;
|
|
@@ -697,48 +755,65 @@ judge) {
|
|
|
697
755
|
* A sequence that declares nothing goes as ONE batch — the cheap path, and the common one.
|
|
698
756
|
*
|
|
699
757
|
* When a sub-step DOES declare, the sub-steps are walked and each expectation is checked right
|
|
700
|
-
* after its own action
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* The walk costs a round trip per sub-step, so only a flow that declared something pays for it.
|
|
758
|
+
* after its own action, as live: checked at the end, "click Pay, and the receipt appears" passed
|
|
759
|
+
* because a LATER sub-step produced the receipt. A sequence driven with `burst` is walked too, one
|
|
760
|
+
* gap between actions, as live: at the default settle a timing-sensitive page answered differently
|
|
761
|
+
* (#2130). The walk costs a round trip per sub-step, so only these sequences pay for it.
|
|
706
762
|
*/
|
|
707
763
|
const judged = (sub) => judge !== undefined && sub.expect !== undefined;
|
|
708
|
-
const declaring = subs.some((sub) => declaredBy(sub) !== undefined || judged(sub));
|
|
764
|
+
const declaring = gap !== undefined || subs.some((sub) => declaredBy(sub) !== undefined || judged(sub));
|
|
765
|
+
// Sub-steps completed with their checks held, for the score; a failed batch leaves its own unknown.
|
|
766
|
+
let completed = 0;
|
|
767
|
+
const finish = (r) => ({
|
|
768
|
+
...r,
|
|
769
|
+
subSteps: {
|
|
770
|
+
passed: r.ok && r.drift === undefined ? subs.length : completed,
|
|
771
|
+
total: subs.length,
|
|
772
|
+
},
|
|
773
|
+
});
|
|
709
774
|
for (let next = 0; next < subs.length;) {
|
|
710
775
|
const stretch = await resolveFrom(next);
|
|
711
|
-
if ('stop' in stretch)
|
|
712
|
-
return stretch.stop;
|
|
776
|
+
if ('stop' in stretch) {
|
|
777
|
+
return finish(stretch.stop);
|
|
778
|
+
}
|
|
713
779
|
const { end } = stretch;
|
|
714
780
|
if (!declaring) {
|
|
715
|
-
if (!(await dispatchFrom(next, end)))
|
|
716
|
-
return result;
|
|
781
|
+
if (!(await dispatchFrom(next, end))) {
|
|
782
|
+
return finish(result);
|
|
783
|
+
}
|
|
784
|
+
completed = end;
|
|
717
785
|
}
|
|
718
786
|
else {
|
|
719
787
|
for (let at = next; at < end; at += 1) {
|
|
720
788
|
const sub = subs[at];
|
|
721
|
-
if (sub === undefined)
|
|
789
|
+
if (sub === undefined) {
|
|
722
790
|
continue;
|
|
791
|
+
}
|
|
792
|
+
await holdGap(() => session.elapsed(), lastSent, gap, sleep);
|
|
793
|
+
const before = judged(sub) ? await captureBaselines(session, sub.expect) : undefined;
|
|
723
794
|
const since = session.elapsed();
|
|
724
|
-
|
|
725
|
-
|
|
795
|
+
lastSent = since;
|
|
796
|
+
if (!(await dispatchFrom(at, at + 1))) {
|
|
797
|
+
return finish(result);
|
|
798
|
+
}
|
|
726
799
|
const testid = declaredBy(sub);
|
|
727
|
-
if (testid !== undefined && !(await assertDeclared(testid)))
|
|
728
|
-
return result;
|
|
800
|
+
if (testid !== undefined && !(await assertDeclared(testid))) {
|
|
801
|
+
return finish(result);
|
|
802
|
+
}
|
|
729
803
|
// The claim is about THIS sub-step: a later one has not run, and an earlier one is before
|
|
730
804
|
// `since`. Judged at the end, a middle route expect failed and a late claim passed early.
|
|
731
805
|
const drift = judge !== undefined && sub.expect !== undefined
|
|
732
|
-
? await judge(sub.expect, since)
|
|
806
|
+
? await judge(sub.expect, since, before)
|
|
733
807
|
: undefined;
|
|
734
808
|
if (drift !== undefined) {
|
|
735
809
|
result.ok = false;
|
|
736
810
|
result.drift = drift;
|
|
737
|
-
return result;
|
|
811
|
+
return finish(result);
|
|
738
812
|
}
|
|
813
|
+
completed = at + 1;
|
|
739
814
|
}
|
|
740
815
|
}
|
|
741
816
|
next = end;
|
|
742
817
|
}
|
|
743
|
-
return result;
|
|
818
|
+
return finish(result);
|
|
744
819
|
}
|
|
@@ -1,29 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The flow store a SESSION's flows belong in.
|
|
3
|
-
*
|
|
4
|
-
* `deps.flows` is bound to the daemon's own root at construction, which is right only when the
|
|
5
|
-
* daemon happens to have been started in the project it is driving. A user-scoped MCP registration
|
|
6
|
-
* is the common case and the editor starts it wherever it likes, so "where the daemon was launched"
|
|
7
|
-
* and "which app is being verified" are usually different directories.
|
|
8
|
-
*
|
|
9
|
-
* This used to live inside flow-tools and was applied at exactly ONE of ten call sites. The result
|
|
10
|
-
* is the half-applied state `sessionRoot` warns about in its own comment, reached in practice:
|
|
11
|
-
*
|
|
12
|
-
* - the HUD listed the daemon repo's flows while driving somebody else's app — Electron and Tauri
|
|
13
|
-
* flows shown over a React dashboard, none of which existed in the checkout being verified
|
|
14
|
-
* - `flow_replay` answered `flow_not_found` for a flow sitting on disk, because it looked in the
|
|
15
|
-
* daemon's root while `reticle verify` (which roots itself at cwd) replayed the same flow fine
|
|
16
|
-
* - and the two save paths disagreed with each other, so where a flow landed depended on which
|
|
17
|
-
* tool wrote it
|
|
18
|
-
*
|
|
19
|
-
* Reported as "running the daemon from here but taking the directory from there", which is exactly
|
|
20
|
-
* what it was.
|
|
21
|
-
*
|
|
22
|
-
* Rebinding is cheap — a FlowStore is a filesystem port, a root and a clock — and the common case
|
|
23
|
-
* allocates nothing: when no resolver is wired, or it names the root already in use, `deps.flows` is
|
|
24
|
-
* returned unchanged.
|
|
25
|
-
*/
|
|
26
1
|
import { FlowStore } from './flows.js';
|
|
2
|
+
import type { NamedFlow } from './change/flow-sources.js';
|
|
27
3
|
import { type ProjectTarget } from '../../memory/project/session-root.js';
|
|
28
4
|
import type { ProjectId } from '@reticlehq/core';
|
|
29
5
|
import type { ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
@@ -37,3 +13,28 @@ export declare function flowsForSessionId(deps: ToolDeps, sessionId: string | un
|
|
|
37
13
|
root: string;
|
|
38
14
|
projectId: ProjectId | undefined;
|
|
39
15
|
};
|
|
16
|
+
/** The {name, steps} of every flow in `store` that `projectId` can see. Unreadable flows are left out. */
|
|
17
|
+
export declare function namedFlowsIn(store: FlowStore, projectId: ProjectId | undefined): Promise<NamedFlow[]>;
|
|
18
|
+
/**
|
|
19
|
+
* The saved flows of a session, from the store `flowsForSessionId` resolves, and the project
|
|
20
|
+
* directory that holds that store.
|
|
21
|
+
*
|
|
22
|
+
* `affected` and `change` read flows through here, and `flows` replays from the same store. They
|
|
23
|
+
* used to build their own store from `sessionRoot`, which skips the root a lease claimed when no
|
|
24
|
+
* resolver is wired and no session is named. Then `affected` named a flow that `flows` could not
|
|
25
|
+
* find (#1996). The directory is returned too, so `git diff` runs in the tree the flows belong to.
|
|
26
|
+
*
|
|
27
|
+
* `sessionTarget` declines when several projects are connected and none is named, because a caller
|
|
28
|
+
* that needs no session must still work. These callers answer about ONE project, and that fallback
|
|
29
|
+
* is the daemon's own, so its flows read as the driven tab's (#1550). Here the refusal stands:
|
|
30
|
+
* `resolve` names the connected projects and asks for a `sessionId`. It throws nothing for several
|
|
31
|
+
* tabs of one project, or for no session at all.
|
|
32
|
+
*
|
|
33
|
+
* `unmatchedRoot` is set when the project has no folder: the root is an evidence bucket, not a
|
|
34
|
+
* tree, so there is no `git` to ask and no flows to find.
|
|
35
|
+
*/
|
|
36
|
+
export declare function namedFlowsForSession(deps: ToolDeps, sessionId: string | undefined): Promise<{
|
|
37
|
+
flows: NamedFlow[];
|
|
38
|
+
projectDir: string;
|
|
39
|
+
unmatchedRoot?: string;
|
|
40
|
+
}>;
|