@reticlehq/server 3.1.0 → 3.3.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 +30 -28
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +37 -5
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +38 -7
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +131 -19
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -7
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +3 -23
- package/dist/command/cli/launch/cli-launch.js +29 -70
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
- package/dist/command/cli/ports/resolve/cli-port.js +50 -2
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +29 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +3 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +661 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +62 -0
- package/dist/features/harness/platform-config.js +97 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +80 -130
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +38 -9
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +55 -10
- package/dist/language/flows/flow-replay-run.js +217 -58
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +29 -11
- package/dist/language/flows/flow-replay.js +221 -203
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-store-for-session.d.ts +2 -1
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +8 -4
- package/dist/language/flows/flows.d.ts +15 -10
- package/dist/language/flows/flows.js +17 -4
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/narrow-write.d.ts +4 -4
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +13 -9
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +21 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -4
- package/dist/memory/cloud/cloud-sync.js +11 -6
- package/dist/memory/cloud/harness-config.d.ts +12 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +35 -2
- package/dist/memory/cloud/sync-daemon.js +45 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +25 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +253 -141
- package/dist/memory/intent/intent-shard.d.ts +22 -23
- package/dist/memory/intent/intent-shard.js +1 -1
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +71 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +91 -2
- package/dist/memory/journal/on-disk/retention.js +181 -11
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +17 -4
- package/dist/memory/journal/session-end.js +85 -7
- package/dist/memory/journal/session-journal.d.ts +122 -5
- package/dist/memory/journal/session-journal.js +489 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +72 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +20 -3
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/input/real-input.js +2 -1
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.js +1 -0
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/facts/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +13 -0
- package/dist/portal/session/session-manager.js +42 -3
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +26 -10
- package/dist/portal/session/session.js +48 -49
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +23 -12
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/act-preflight.js +6 -1
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.js +49 -8
- package/dist/surface/tools/act-tools.js +25 -24
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +74 -21
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +43 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +50 -3
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +82 -9
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +69 -15
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +23 -3
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +70 -30
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/telemetry/verification-of.js +31 -3
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +11 -9
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +144 -4
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/tools/query.mdx +10 -10
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +2 -2
- package/docs/what-is-recorded.md +62 -0
- package/package.json +5 -5
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
|
@@ -1,17 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { formatStepAddress } from 'open-verification';
|
|
2
|
+
import { assertsState, expectLabel, expectedElementTestid, withoutClauses } from '@reticlehq/core';
|
|
3
3
|
import { span } from '../../trace.js';
|
|
4
|
-
import { anchorLabel, expectElementDrift, resolveTestid, testidDrift } from './flow-anchor.js';
|
|
4
|
+
import { anchorLabel, expectElementDrift, resolveTestid, testidDrift, ambiguousAnchorDrift, } from './flow-anchor.js';
|
|
5
5
|
export { anchorLabel, componentLabel, componentQueryArgs, editDistance, expectElementDrift, nearestIsAmbiguous, nearestTestid, resolveQuery, testidDrift, } from './flow-anchor.js';
|
|
6
6
|
import { routeOfEvent } from '@reticlehq/engine/question/predicate/predicate-route.js';
|
|
7
7
|
import { stepEffect } from '@reticlehq/engine/evidence/step-effect.js';
|
|
8
|
-
import { AnchorKind, DriftReason, EventType, FlowStepTool,
|
|
8
|
+
import { AnchorKind, DriftReason, EventType, FlowStepTool, PredicateKind, } from '@reticlehq/core';
|
|
9
9
|
import { asString, isConsequenceDrift } from '@reticlehq/core';
|
|
10
|
-
import { replayActionArgs, ambiguousTestidNote } from './replay.js';
|
|
11
10
|
import { anchorFieldName } from './fields/flow-secret-field.js';
|
|
12
11
|
import { degradedStepResult, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { inFlightRequestLabels } from '../../surface/tools/act/settle-in-flight.js';
|
|
13
|
+
import { namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
|
|
14
|
+
/** What a still-open named request means, in the drift's own words. */
|
|
15
|
+
const IN_FLIGHT_AT_BUDGET_END = 'the request this step declared had not come back when the budget ended — it is still in flight, ' +
|
|
16
|
+
'so nothing here says the app failed. Raise the step timeout, or look at the endpoint';
|
|
17
|
+
import { isDocumentGoneError, SESSION_DISCONNECTED_REASON, } from '../../portal/session/facts/session-replaced.js';
|
|
18
|
+
import { actOnResolvedRef } from './flow-step-runners.js';
|
|
19
|
+
/**
|
|
20
|
+
* The document this replay was driving went away mid-run.
|
|
21
|
+
*
|
|
22
|
+
* Almost always because the replay itself navigated it — a `startPath` goto, or a step that clicks a
|
|
23
|
+
* link. The steps already in hand are real answers from the document that WAS there, so they travel
|
|
24
|
+
* with the error; the step that was in flight has no answer at all and is deliberately absent.
|
|
25
|
+
* Synthesising one would be a verdict about a page nobody observed, which is the one thing worse
|
|
26
|
+
* than returning no verdict.
|
|
27
|
+
*
|
|
28
|
+
* Typed rather than a message, because the caller has to tell it from a step that genuinely failed:
|
|
29
|
+
* one says the app is wrong, the other says we stopped watching.
|
|
30
|
+
*/
|
|
31
|
+
export class DocumentLostDuringReplay extends Error {
|
|
32
|
+
steps;
|
|
33
|
+
atStep;
|
|
34
|
+
constructor(steps, atStep, lostTo) {
|
|
35
|
+
super(`the document was replaced while step ${String(atStep)} was running`, {
|
|
36
|
+
cause: lostTo,
|
|
37
|
+
});
|
|
38
|
+
this.steps = steps;
|
|
39
|
+
this.atStep = atStep;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
15
42
|
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
43
|
/**
|
|
17
44
|
* The route (pathname) currently in effect — the page a step runs on. Reads the latest ROUTE_CHANGE
|
|
@@ -76,7 +103,7 @@ function summarizeConsequence(events) {
|
|
|
76
103
|
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
77
104
|
}
|
|
78
105
|
/** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
|
|
79
|
-
async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
|
|
106
|
+
export async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
|
|
80
107
|
const { refs, hint } = await resolveTestid(session, value, sleep);
|
|
81
108
|
if (0 === refs.length) {
|
|
82
109
|
return {
|
|
@@ -87,35 +114,50 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
87
114
|
drift: testidDrift(value, hint),
|
|
88
115
|
};
|
|
89
116
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
/*
|
|
118
|
+
* More than one match is DRIFT, not a note on a passing step.
|
|
119
|
+
*
|
|
120
|
+
* This used to take refs[0], act, and return ok:true with `ambiguousTestidNote`. The verdict
|
|
121
|
+
* reads `drift` and `ok` and never reads a note, so "we guessed which element you meant" was
|
|
122
|
+
* indistinguishable from "it did what it did before" -- the one claim a replay makes.
|
|
123
|
+
*
|
|
124
|
+
* The action is NOT dispatched. Acting and then reporting drift would leave the app changed by
|
|
125
|
+
* a click nobody can attribute, which is worse than the ambiguity it reports.
|
|
126
|
+
*/
|
|
127
|
+
if (refs.length > 1) {
|
|
128
|
+
return {
|
|
129
|
+
step: index,
|
|
130
|
+
tool: step.tool,
|
|
131
|
+
anchor: value,
|
|
132
|
+
ok: false,
|
|
133
|
+
drift: ambiguousAnchorDrift(value, refs.length),
|
|
134
|
+
};
|
|
107
135
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
const ref = refs[0] ?? '';
|
|
137
|
+
/*
|
|
138
|
+
* The same dispatch its siblings use, including the one re-resolve on a stale ref (2.3).
|
|
139
|
+
*
|
|
140
|
+
* This runner had its own inline copy and no retry, so a flow died on a re-render purely because
|
|
141
|
+
* the step was anchored by testid rather than by role — the locator deciding how sturdy the replay
|
|
142
|
+
* is, which is exactly backwards. testid is also the anchor `reticle init` steers people towards,
|
|
143
|
+
* so the kind most likely to appear in a real flow was the kind without the cure.
|
|
144
|
+
*
|
|
145
|
+
* The re-resolve keeps the ambiguity rule above: more than one match is drift, never a guess, so
|
|
146
|
+
* it hands back a ref only when the locator still names exactly one element.
|
|
147
|
+
*/
|
|
148
|
+
const result = await actOnResolvedRef(session, step, index, value, ref, confirmDangerous,
|
|
149
|
+
// The field this step types into — from the anchor, so a redacted fill can be supplied from
|
|
150
|
+
// RETICLE_SECRET_<FIELD> without the flow carrying the secret.
|
|
151
|
+
anchorFieldName(step.anchor), async () => {
|
|
152
|
+
const again = await resolveTestid(session, value, sleep);
|
|
153
|
+
return 1 === again.refs.length ? again.refs[0] : undefined;
|
|
154
|
+
});
|
|
155
|
+
if (!result.ok)
|
|
113
156
|
return result;
|
|
114
|
-
}
|
|
115
157
|
// assert the step's expect.element testid is present AFTER the action —
|
|
116
158
|
// unless that testid was marked DYNAMIC (the LLM-output case), in which case its presence/content
|
|
117
159
|
// is NOT asserted (only the action ran). The skip is scoped strictly to the dynamic set.
|
|
118
|
-
const expectTestid = step.expect
|
|
160
|
+
const expectTestid = expectedElementTestid(step.expect);
|
|
119
161
|
if (expectTestid !== undefined && !dynamic.has(expectTestid)) {
|
|
120
162
|
const expectRefs = await resolveTestid(session, expectTestid, sleep);
|
|
121
163
|
if (0 === expectRefs.refs.length) {
|
|
@@ -131,8 +173,6 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
131
173
|
};
|
|
132
174
|
}
|
|
133
175
|
}
|
|
134
|
-
if (note !== undefined)
|
|
135
|
-
result.note = note;
|
|
136
176
|
return result;
|
|
137
177
|
}
|
|
138
178
|
/**
|
|
@@ -156,44 +196,63 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
156
196
|
* quietly stop agreeing, and the disagreement would land as a flow that replays green against a
|
|
157
197
|
* state it was never meant to run in.
|
|
158
198
|
*/
|
|
159
|
-
export async function assertStepExpect(session, expect,
|
|
199
|
+
export async function assertStepExpect(session, expect, waitForSignal, timeoutMs, since) {
|
|
160
200
|
// A testid is already asserted against the live DOM by the step runner. A role/name locator is
|
|
161
201
|
// not that path — stripping every element made a recorded `until` by button name a no-op, so a
|
|
162
202
|
// flow that proved the control at capture time could not go red when it was gone.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
203
|
+
/*
|
|
204
|
+
* Two clauses replay must not wait on, both for reasons that predate the tree.
|
|
205
|
+
*
|
|
206
|
+
* A testid element was already asserted against the live DOM by the step runner, so waiting on it
|
|
207
|
+
* again is a second read of a question already answered. A DYNAMIC testid is one whose content a
|
|
208
|
+
* model writes, and its presence is deliberately never asserted.
|
|
209
|
+
*
|
|
210
|
+
* A role/name locator is NEITHER, and stripping every element made a recorded `until` by button
|
|
211
|
+
* name a no-op — a flow that proved the control at capture time could not go red when it was gone.
|
|
212
|
+
*
|
|
213
|
+
* The DYNAMIC set used to be a parameter here and was dead the whole time: the testid was deleted
|
|
214
|
+
* from the expectation on the line above, and the only thing that read the set asked for the
|
|
215
|
+
* testid that had just been removed. One production caller was already passing an empty set. The
|
|
216
|
+
* rule is real and still applies to the flow's SUCCESS oracle, which is where it lives.
|
|
217
|
+
*/
|
|
218
|
+
const predicate = withoutClauses(expect,
|
|
219
|
+
// Only a PRESENT testid is the runner's to check; an absence stays here or nobody checks it.
|
|
220
|
+
(clause) => PredicateKind.ELEMENT === clause.kind &&
|
|
221
|
+
clause.query.testid !== undefined &&
|
|
222
|
+
true !== clause.absent);
|
|
168
223
|
if (predicate === undefined)
|
|
169
224
|
return undefined;
|
|
170
225
|
const verdict = await waitForSignal(session, predicate, timeoutMs, since);
|
|
171
226
|
if (verdict.pass)
|
|
172
227
|
return undefined;
|
|
228
|
+
// The wait ended because the page stopped being observable, not because the consequence failed:
|
|
229
|
+
// the same fact as a command the dying socket rejected, so it takes the same path (the replay loop
|
|
230
|
+
// reports a lost document). As a drift, a socket that blinked for 26ms read as the app's defect.
|
|
231
|
+
if (true === verdict.observationLost)
|
|
232
|
+
throw new Error(SESSION_DISCONNECTED_REASON);
|
|
233
|
+
// A request the step NAMED that had not come back yet is a blind spot, not an assertion the app
|
|
234
|
+
// failed. The live verdict path already draws this line (namedNetIsInFlight); replay read the
|
|
235
|
+
// same window and never asked, so a slow endpoint was reported as a consequence that never fired.
|
|
236
|
+
// An unrelated open request does not pardon a named URL that never started — matching decides it.
|
|
237
|
+
const openRequests = assertsState(expect)
|
|
238
|
+
? []
|
|
239
|
+
: inFlightRequestLabels(session.eventsSince(since), session.url, session.background);
|
|
240
|
+
const namedInFlight = namedNetIsInFlight(predicate, openRequests);
|
|
173
241
|
return {
|
|
174
242
|
// The store case keeps its own kind because heal and the run report branch on it; everything
|
|
175
243
|
// else is a consequence that did not hold, and the reason carries observed-vs-expected.
|
|
176
|
-
reasonKind: expect
|
|
177
|
-
|
|
244
|
+
reasonKind: assertsState(expect)
|
|
245
|
+
? DriftReason.STATE_MISMATCH
|
|
246
|
+
: namedInFlight
|
|
247
|
+
? DriftReason.NET_STILL_IN_FLIGHT
|
|
248
|
+
: DriftReason.SIGNAL_NOT_OBSERVED,
|
|
249
|
+
reason: namedInFlight
|
|
250
|
+
? `${IN_FLIGHT_AT_BUDGET_END} (${openRequests.join(', ')})`
|
|
251
|
+
: (verdict.failureReason ?? "the step's declared consequence did not hold"),
|
|
178
252
|
anchor: expectLabel(expect),
|
|
179
253
|
nearest: null,
|
|
180
254
|
};
|
|
181
255
|
}
|
|
182
|
-
/** Name the thing that was asserted, for the drift's `anchor` column. */
|
|
183
|
-
function expectLabel(expect) {
|
|
184
|
-
if (expect.signal !== undefined)
|
|
185
|
-
return `signal:${expect.signal}`;
|
|
186
|
-
if (expect.net !== undefined)
|
|
187
|
-
return `net:${expect.net.urlContains ?? expect.net.method ?? '*'}`;
|
|
188
|
-
if (expect.state !== undefined)
|
|
189
|
-
return `state:${expect.state.path}`;
|
|
190
|
-
if (expect.console !== undefined)
|
|
191
|
-
return `console:${expect.console.level ?? '*'}`;
|
|
192
|
-
if (undefined !== expect.element) {
|
|
193
|
-
return expect.element.testid ?? expect.element.name ?? expect.element.role ?? 'element';
|
|
194
|
-
}
|
|
195
|
-
return 'expect';
|
|
196
|
-
}
|
|
197
256
|
/** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
|
|
198
257
|
async function runSignalStep(session, step, index, name, waitForSignal, signalTimeoutMs, since) {
|
|
199
258
|
// Scope to THIS replay's floor, not the whole buffer.
|
|
@@ -221,40 +280,6 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
|
|
|
221
280
|
},
|
|
222
281
|
};
|
|
223
282
|
}
|
|
224
|
-
/**
|
|
225
|
-
* Where to start REPORTING — and whether re-driving the steps before it is allowed at all.
|
|
226
|
-
*
|
|
227
|
-
* "Resume is nearly free, just re-run the prefix" is true of a browser and false on a subject that
|
|
228
|
-
* COMMITS. Re-driving a prefix on a service re-sends every request before step N; on a device it
|
|
229
|
-
* moves an arm again. Neither is a convenience, and a protocol that did it silently would be
|
|
230
|
-
* defective rather than helpful.
|
|
231
|
-
*
|
|
232
|
-
* So the surface's declared profile decides, through the specification's own `resumeStrategy` rather
|
|
233
|
-
* than a local reading of `replayPrefix`. A refusal resumes from 0 — the whole journey is reported,
|
|
234
|
-
* nothing is skipped and nothing is silently repeated beyond what a plain replay already does.
|
|
235
|
-
*/
|
|
236
|
-
function resumableFrom(options, steps) {
|
|
237
|
-
const asked = Math.max(0, options.from ?? 0);
|
|
238
|
-
if (0 === asked)
|
|
239
|
-
return 0;
|
|
240
|
-
if (!mayResumeByReplayingPrefix(options.surface ?? Surface.WEB))
|
|
241
|
-
return 0;
|
|
242
|
-
/*
|
|
243
|
-
* The surface answers for the SUBJECT; a step answers for itself.
|
|
244
|
-
*
|
|
245
|
-
* `web` is the permissive profile and it is right about a browser in general and wrong about the
|
|
246
|
-
* one click that charges a card. The surface check cannot see that, because the distinction is
|
|
247
|
-
* not a property of the realm — it is a property of the step. A flow that declares a prefix step
|
|
248
|
-
* as `commits` is saying: re-running me is not free, whatever the surface thinks.
|
|
249
|
-
*
|
|
250
|
-
* Refusing means resuming from 0, which is the same fail-safe the surface refusal already uses:
|
|
251
|
-
* the whole journey is reported and nothing is silently repeated beyond what a plain replay does.
|
|
252
|
-
* Absent effect is UNKNOWN and stays permissive — every flow recorded before this shipped has no
|
|
253
|
-
* effect on any step, and assuming the worst there would refuse every resume in existence.
|
|
254
|
-
*/
|
|
255
|
-
const commitsInPrefix = steps.slice(0, asked).some((step) => StepEffect.COMMITS === step.effect);
|
|
256
|
-
return commitsInPrefix ? 0 : asked;
|
|
257
|
-
}
|
|
258
283
|
/**
|
|
259
284
|
* Run an `invoke` step: load the named flow and replay it, or fail saying why.
|
|
260
285
|
*
|
|
@@ -288,10 +313,7 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
|
|
|
288
313
|
error: `cannot replay "${name}": it was not found, so this journey would report green having never run it`,
|
|
289
314
|
};
|
|
290
315
|
}
|
|
291
|
-
|
|
292
|
-
// caller's own step list, and applying it inside a sub-journey would silently hide that
|
|
293
|
-
// journey's first steps for a reason that has nothing to do with it.
|
|
294
|
-
const { from: _ignored, ...carried } = options;
|
|
316
|
+
const carried = options;
|
|
295
317
|
const nested = await replayFlow(session, sub, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, {
|
|
296
318
|
...carried,
|
|
297
319
|
via: [{ flow: flow.name, step: index }, ...via],
|
|
@@ -309,16 +331,6 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
|
|
|
309
331
|
}
|
|
310
332
|
export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs, confirmDangerous = false, sleep = realSleep, options = {}) {
|
|
311
333
|
const results = [];
|
|
312
|
-
/*
|
|
313
|
-
* Where to start REPORTING. Everything before it still runs.
|
|
314
|
-
*
|
|
315
|
-
* Resuming is re-driving the prefix, not restoring state: there is no way to put an app back
|
|
316
|
-
* where it was without driving it there, and at a measured ~27ms a step there is no reason to
|
|
317
|
-
* try. So the prefix executes silently and the caller sees the journey from the point it asked
|
|
318
|
-
* about -- which is what makes "fix the break, resume, find the next one" a loop rather than a
|
|
319
|
-
* full re-read each time.
|
|
320
|
-
*/
|
|
321
|
-
const from = resumableFrom(options, flow.steps);
|
|
322
334
|
// testids whose region is LLM-dynamic — their expect-presence is NOT asserted.
|
|
323
335
|
const dynamic = new Set((flow.dynamic ?? [])
|
|
324
336
|
.filter((a) => a.kind === AnchorKind.TESTID)
|
|
@@ -332,108 +344,114 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
332
344
|
// wait for the slowest step would trade a false red for a slow suite. See FlowStep.timeoutMs.
|
|
333
345
|
const waitFor = (step) => step.timeoutMs ?? flow.signalTimeoutMs ?? signalTimeoutMs;
|
|
334
346
|
let index = 0;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const label = anchorLabel(step.anchor);
|
|
345
|
-
// The page this step runs on (the journey's "which page") — captured before the action.
|
|
346
|
-
const page = currentRoute(session);
|
|
347
|
-
// Event-time floor so the consequence reflects only THIS step's aftermath, not prior steps'.
|
|
348
|
-
const cursorBefore = session.elapsed();
|
|
349
|
-
const subSteps = step.steps;
|
|
350
|
-
// Traced per step, so a slow replay says WHICH step and which anchor kind spent the time. The
|
|
351
|
-
// per-step `durationMs` below is what the agent gets back; this is what a developer profiling
|
|
352
|
-
// the replay engine gets, nested under the tool call with the browser round-trips beneath it.
|
|
353
|
-
const result = await span('flow.step', { index, anchor: step.anchor.kind, label }, async () => {
|
|
354
|
-
if (isDegradedAnchor(step.anchor)) {
|
|
355
|
-
// Never QUERY the sentinel — it marks "no anchor was determined", not an element to find.
|
|
356
|
-
return degradedStepResult(step, index, label);
|
|
357
|
-
}
|
|
358
|
-
if (subSteps !== undefined && subSteps.length > 0) {
|
|
359
|
-
return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep, dynamic);
|
|
360
|
-
}
|
|
361
|
-
if (step.anchor.kind === AnchorKind.SIGNAL) {
|
|
362
|
-
return runSignalStep(session, step, index, label, waitForSignal, waitFor(step), replayFloor);
|
|
363
|
-
}
|
|
364
|
-
if (step.anchor.kind === AnchorKind.COMPONENT) {
|
|
365
|
-
return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
347
|
+
try {
|
|
348
|
+
for (const step of flow.steps) {
|
|
349
|
+
if (step.invoke !== undefined) {
|
|
350
|
+
results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options));
|
|
351
|
+
const last = results[results.length - 1];
|
|
352
|
+
if (last !== undefined && false === last.ok)
|
|
353
|
+
break;
|
|
354
|
+
index += 1;
|
|
355
|
+
continue;
|
|
366
356
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
357
|
+
const label = anchorLabel(step.anchor);
|
|
358
|
+
// The page this step runs on (the journey's "which page") — captured before the action.
|
|
359
|
+
const page = currentRoute(session);
|
|
360
|
+
// Event-time floor so the consequence reflects only THIS step's aftermath, not prior steps'.
|
|
361
|
+
const cursorBefore = session.elapsed();
|
|
362
|
+
const subSteps = step.steps;
|
|
363
|
+
// Traced per step, so a slow replay says WHICH step and which anchor kind spent the time. The
|
|
364
|
+
// per-step `durationMs` below is what the agent gets back; this is what a developer profiling
|
|
365
|
+
// the replay engine gets, nested under the tool call with the browser round-trips beneath it.
|
|
366
|
+
const result = await span('flow.step', { index, anchor: step.anchor.kind, label }, async () => {
|
|
367
|
+
if (isDegradedAnchor(step.anchor)) {
|
|
368
|
+
// Never QUERY the sentinel — it marks "no anchor was determined", not an element to find.
|
|
369
|
+
return degradedStepResult(step, index, label);
|
|
370
|
+
}
|
|
371
|
+
if (subSteps !== undefined && subSteps.length > 0) {
|
|
372
|
+
return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep, dynamic);
|
|
373
|
+
}
|
|
374
|
+
if (step.anchor.kind === AnchorKind.SIGNAL) {
|
|
375
|
+
return runSignalStep(session, step, index, label, waitForSignal, waitFor(step), replayFloor);
|
|
376
|
+
}
|
|
377
|
+
if (step.anchor.kind === AnchorKind.COMPONENT) {
|
|
378
|
+
return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
379
|
+
}
|
|
380
|
+
if (step.anchor.kind === AnchorKind.ROLE && step.anchor.name !== undefined) {
|
|
381
|
+
// A NAMED role anchor addresses one element. The nameless one is the degraded placeholder
|
|
382
|
+
// and keeps its old path, where it fails legibly rather than querying a role as a testid.
|
|
383
|
+
return runRoleStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
384
|
+
}
|
|
385
|
+
return runTestidStep(session, step, index, label, dynamic, confirmDangerous, sleep);
|
|
386
|
+
});
|
|
387
|
+
// Once the anchor resolved and the action ran, the step's own expect is evaluated — signal, net,
|
|
388
|
+
// console and store truth alike — deterministically, in the same cheap replay loop, with no LLM.
|
|
389
|
+
/*
|
|
390
|
+
* The step's own expect, and then every sub-step's.
|
|
391
|
+
*
|
|
392
|
+
* `classifyFlowAssertions` already walks act_sequence sub-steps and counts an expect on either
|
|
393
|
+
* level, and states the invariant plainly: this and what replay enforces must move together, or
|
|
394
|
+
* "the difference is a false green or a lost verification". Enforcing only the top level was
|
|
395
|
+
* that difference -- a sequence whose sub-step declared a consequence was GRADED asserted and
|
|
396
|
+
* checked by nothing, so it could not go red while wearing the grade that says it could.
|
|
397
|
+
*
|
|
398
|
+
* WEAKER THAN THE LIVE PATH, and deliberately so rather than silently. A recorded sequence is
|
|
399
|
+
* dispatched as ONE batched command, so there are no per-sub-step cursors to open a window with
|
|
400
|
+
* -- every sub-step expect is evaluated against the window the whole sequence opened, which
|
|
401
|
+
* means sub-step three's claim can be satisfied by sub-step one's consequence. The live
|
|
402
|
+
* `act_sequence` path takes a cursor before each dispatch and does not have this. Narrowing it
|
|
403
|
+
* here means dispatching sub-steps individually on replay, which is a behaviour change to the
|
|
404
|
+
* replay path and belongs in its own commit.
|
|
405
|
+
*/
|
|
406
|
+
const declared = [step.expect, ...(step.steps ?? []).map((sub) => sub.expect)].filter((expectation) => expectation !== undefined);
|
|
407
|
+
for (const expectation of declared) {
|
|
408
|
+
if (!result.ok || result.drift !== undefined)
|
|
409
|
+
break;
|
|
410
|
+
const expectDrift = await assertStepExpect(session, expectation, waitForSignal, waitFor(step), cursorBefore);
|
|
411
|
+
if (expectDrift !== undefined) {
|
|
412
|
+
result.ok = false;
|
|
413
|
+
result.drift = expectDrift;
|
|
414
|
+
}
|
|
371
415
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
416
|
+
if (page !== undefined)
|
|
417
|
+
result.page = page;
|
|
418
|
+
const windowEvents = session.eventsSince(cursorBefore).filter((e) => e.t >= cursorBefore);
|
|
419
|
+
const consequence = summarizeConsequence(windowEvents);
|
|
420
|
+
if (consequence !== undefined)
|
|
421
|
+
result.consequence = consequence;
|
|
422
|
+
// Per-step wall time is NOT shipped: it is `window.until - window.since`, computed from two numbers
|
|
423
|
+
// the next line already puts in the same object, under the same emission condition. A step used to
|
|
424
|
+
// carry the subtraction AND both operands, on every step of every replay.
|
|
425
|
+
const cursorAfter = session.elapsed();
|
|
426
|
+
// ONE builder, shared with the live act path, so a driven step and a replayed one describe what
|
|
427
|
+
// happened in identical words rather than in two vocabularies that agree by coincidence.
|
|
428
|
+
Object.assign(result, stepEffect(windowEvents, { since: cursorBefore, until: cursorAfter }));
|
|
429
|
+
/*
|
|
430
|
+
* A prefix step is setup and is not reported -- UNLESS it failed.
|
|
431
|
+
*
|
|
432
|
+
* A failure there means the resume never reached the step it was asked to resume from, and the
|
|
433
|
+
* run did not start where the caller will read it as having started. Swallowing it would turn
|
|
434
|
+
* "I could not get there" into "I got there and it was fine".
|
|
435
|
+
*/
|
|
436
|
+
// `tool` is dropped HERE rather than at the ten places that set it, so a new step runner cannot
|
|
437
|
+
// forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
|
|
438
|
+
if (FlowStepTool.ACT === result.tool)
|
|
439
|
+
delete result.tool;
|
|
440
|
+
results.push(result);
|
|
441
|
+
// Under `sweep`, a failure whose action still RAN does not stop the run — the page is where the
|
|
442
|
+
// step left it, so the next step is as meaningful as it was going to be. Anything else halts.
|
|
443
|
+
const sweepPast = true === options.sweep &&
|
|
444
|
+
result.drift !== undefined &&
|
|
445
|
+
isConsequenceDrift(result.drift.reasonKind);
|
|
446
|
+
if (!sweepPast && (result.drift !== undefined || !result.ok))
|
|
396
447
|
break;
|
|
397
|
-
|
|
398
|
-
if (expectDrift !== undefined) {
|
|
399
|
-
result.ok = false;
|
|
400
|
-
result.drift = expectDrift;
|
|
401
|
-
}
|
|
448
|
+
index += 1;
|
|
402
449
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
result.consequence = consequence;
|
|
409
|
-
// Per-step wall time is NOT shipped: it is `window.until - window.since`, computed from two numbers
|
|
410
|
-
// the next line already puts in the same object, under the same emission condition. A step used to
|
|
411
|
-
// carry the subtraction AND both operands, on every step of every replay.
|
|
412
|
-
const cursorAfter = session.elapsed();
|
|
413
|
-
// ONE builder, shared with the live act path, so a driven step and a replayed one describe what
|
|
414
|
-
// happened in identical words rather than in two vocabularies that agree by coincidence.
|
|
415
|
-
Object.assign(result, stepEffect(windowEvents, { since: cursorBefore, until: cursorAfter }));
|
|
416
|
-
/*
|
|
417
|
-
* A prefix step is setup and is not reported -- UNLESS it failed.
|
|
418
|
-
*
|
|
419
|
-
* A failure there means the resume never reached the step it was asked to resume from, and the
|
|
420
|
-
* run did not start where the caller will read it as having started. Swallowing it would turn
|
|
421
|
-
* "I could not get there" into "I got there and it was fine".
|
|
422
|
-
*/
|
|
423
|
-
// `tool` is dropped HERE rather than at the ten places that set it, so a new step runner cannot
|
|
424
|
-
// forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
|
|
425
|
-
if (FlowStepTool.ACT === result.tool)
|
|
426
|
-
delete result.tool;
|
|
427
|
-
if (index >= from || !result.ok || result.drift !== undefined)
|
|
428
|
-
results.push(result);
|
|
429
|
-
// Under `sweep`, a failure whose action still RAN does not stop the run — the page is where the
|
|
430
|
-
// step left it, so the next step is as meaningful as it was going to be. Anything else halts.
|
|
431
|
-
const sweepPast = true === options.sweep &&
|
|
432
|
-
result.drift !== undefined &&
|
|
433
|
-
isConsequenceDrift(result.drift.reasonKind);
|
|
434
|
-
if (!sweepPast && (result.drift !== undefined || !result.ok))
|
|
435
|
-
break;
|
|
436
|
-
index += 1;
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
if (!isDocumentGoneError(error))
|
|
453
|
+
throw error;
|
|
454
|
+
throw new DocumentLostDuringReplay(results, index, error);
|
|
437
455
|
}
|
|
438
456
|
return results;
|
|
439
457
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlowErrorCode } from '@reticlehq/core';
|
|
2
|
+
import { type ProjectId } from '@reticlehq/core';
|
|
2
3
|
/** Discriminated result so callers never branch on free strings. */
|
|
3
4
|
export type FlowResult<T> = {
|
|
4
5
|
ok: true;
|
|
@@ -14,5 +15,21 @@ export type FlowResult<T> = {
|
|
|
14
15
|
* An unsafe or absent value collapses to `undefined` — the flat, global store — so a malformed id
|
|
15
16
|
* can never escape `.reticle/flows/`. The store defends the disk boundary itself rather than
|
|
16
17
|
* trusting whatever arrived in a session's HELLO.
|
|
18
|
+
*
|
|
19
|
+
* Held to one SEGMENT, not to a flow name. A project id becomes a DIRECTORY; a flow name is an
|
|
20
|
+
* ADDRESS, and since composites began naming sub-journeys as `onboarding/signup` its pattern has
|
|
21
|
+
* allowed separators. Validating a directory name with the address guard let a page choose how deep
|
|
22
|
+
* under `.reticle/` Reticle wrote — not an escape, since neither pattern admits `..` or a leading
|
|
23
|
+
* separator, but the page naming directories all the same.
|
|
24
|
+
*
|
|
25
|
+
* A FILTER, not a mint. It answers "is this id safe on disk", never "is this id a project" — a
|
|
26
|
+
* session id passes `SAFE_SEGMENT_PATTERN` just as cleanly, so minting the brand here would launder
|
|
27
|
+
* the exact confusion `ProjectId` exists to catch. The brand arrives already attached, from HELLO
|
|
28
|
+
* or from `.reticle.json`; see `ProjectId` in core.
|
|
29
|
+
*
|
|
30
|
+
* The narrowing said it out loud and nobody was listening: `isValidFlowName` narrows its argument to
|
|
31
|
+
* `FlowName`, for a value that is a project. The sibling guards for session and run ids have used
|
|
32
|
+
* the segment pattern since that widening, and `id-guards-are-not-flow-names.test.ts` exists to
|
|
33
|
+
* hold the distinction — this is the fourth id joined into a path, and the one that file missed.
|
|
17
34
|
*/
|
|
18
|
-
export declare const safeProjectId: (projectId?:
|
|
35
|
+
export declare const safeProjectId: (projectId?: ProjectId) => ProjectId | undefined;
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { FlowErrorCode } from '@reticlehq/core';
|
|
2
|
-
import {
|
|
2
|
+
import { SAFE_SEGMENT_PATTERN } from '@reticlehq/core';
|
|
3
3
|
/**
|
|
4
4
|
* A project id that is safe to put in a path, or nothing.
|
|
5
5
|
*
|
|
6
6
|
* An unsafe or absent value collapses to `undefined` — the flat, global store — so a malformed id
|
|
7
7
|
* can never escape `.reticle/flows/`. The store defends the disk boundary itself rather than
|
|
8
8
|
* trusting whatever arrived in a session's HELLO.
|
|
9
|
+
*
|
|
10
|
+
* Held to one SEGMENT, not to a flow name. A project id becomes a DIRECTORY; a flow name is an
|
|
11
|
+
* ADDRESS, and since composites began naming sub-journeys as `onboarding/signup` its pattern has
|
|
12
|
+
* allowed separators. Validating a directory name with the address guard let a page choose how deep
|
|
13
|
+
* under `.reticle/` Reticle wrote — not an escape, since neither pattern admits `..` or a leading
|
|
14
|
+
* separator, but the page naming directories all the same.
|
|
15
|
+
*
|
|
16
|
+
* A FILTER, not a mint. It answers "is this id safe on disk", never "is this id a project" — a
|
|
17
|
+
* session id passes `SAFE_SEGMENT_PATTERN` just as cleanly, so minting the brand here would launder
|
|
18
|
+
* the exact confusion `ProjectId` exists to catch. The brand arrives already attached, from HELLO
|
|
19
|
+
* or from `.reticle.json`; see `ProjectId` in core.
|
|
20
|
+
*
|
|
21
|
+
* The narrowing said it out loud and nobody was listening: `isValidFlowName` narrows its argument to
|
|
22
|
+
* `FlowName`, for a value that is a project. The sibling guards for session and run ids have used
|
|
23
|
+
* the segment pattern since that widening, and `id-guards-are-not-flow-names.test.ts` exists to
|
|
24
|
+
* hold the distinction — this is the fourth id joined into a path, and the one that file missed.
|
|
9
25
|
*/
|
|
10
|
-
export const safeProjectId = (projectId) => projectId !== undefined &&
|
|
26
|
+
export const safeProjectId = (projectId) => projectId !== undefined && SAFE_SEGMENT_PATTERN.test(projectId) ? projectId : undefined;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
* How one anchored step is turned into a live action.
|
|
3
|
-
*
|
|
4
|
-
* Split out of flow-replay.ts when adding the role+name runner pushed that file past the size cap.
|
|
5
|
-
* These three share one shape — resolve an anchor to a ref, then dispatch — and the sharing is the
|
|
6
|
-
* point: the action window must open and close identically no matter which anchor found the element,
|
|
7
|
-
* or a step's events land in the wrong window depending on how it was addressed.
|
|
8
|
-
*/
|
|
9
|
-
import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult } from '@reticlehq/core';
|
|
1
|
+
import { AnchorKind, type FlowAnchor, type FlowStep, type FlowStepResult, type Predicate } from '@reticlehq/core';
|
|
10
2
|
import type { FlowReplaySession, Sleep } from './flow-replay-types.js';
|
|
11
3
|
export declare function runRoleStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
|
|
12
4
|
kind: typeof AnchorKind.ROLE;
|
|
13
5
|
}>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
|
|
14
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
|
|
8
|
+
* including the action window, whose open/close must not depend on which anchor found the element.
|
|
9
|
+
*/
|
|
10
|
+
export declare function actOnResolvedRef(session: FlowReplaySession, step: FlowStep, index: number, label: string, ref: string, confirmDangerous: boolean,
|
|
11
|
+
/**
|
|
12
|
+
* The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
|
|
13
|
+
* redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
|
|
14
|
+
* and looked up at replay under no name at all, so the flow types the placeholder into the form.
|
|
15
|
+
*/
|
|
16
|
+
field?: string,
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the anchor again. Supplied by the caller because only it knows how this step's anchor is
|
|
19
|
+
* found, and the retry below is worthless without it: re-dispatching the SAME dead ref fails
|
|
20
|
+
* identically. What changed between the two attempts is the DOM, so the locator has to be run
|
|
21
|
+
* against it again.
|
|
22
|
+
*/
|
|
23
|
+
reresolve?: () => Promise<string | undefined>): Promise<FlowStepResult>;
|
|
15
24
|
/** Run one component-anchored step: re-resolve via QUERY by:'component', ACT on the live ref, else drift. */
|
|
16
25
|
export declare function runComponentStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
|
|
17
26
|
kind: typeof AnchorKind.COMPONENT;
|
|
@@ -39,6 +48,13 @@ export declare function degradedStepResult(step: FlowStep, index: number, label:
|
|
|
39
48
|
* the step runner's own resolution asking the same thing of the same anchor.
|
|
40
49
|
*/
|
|
41
50
|
export declare function anchorQueryArgs(anchor: FlowAnchor): Record<string, unknown> | null;
|
|
51
|
+
/**
|
|
52
|
+
* The anchor as a PRECONDITION a flow file can store: "this element is on the page".
|
|
53
|
+
*
|
|
54
|
+
* What `requires` means for a flow that starts from state a page load discards, in the shape the
|
|
55
|
+
* flow's own `requires` holds. No tool writes `requires`, so the reset's hint hands over this line.
|
|
56
|
+
*/
|
|
57
|
+
export declare function anchorPrecondition(anchor: FlowAnchor): Predicate | undefined;
|
|
42
58
|
/**
|
|
43
59
|
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
44
60
|
* ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
|