@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
|
@@ -6,12 +6,13 @@ import { asRecord, asString } from '@reticlehq/core';
|
|
|
6
6
|
import { routeOfEvent, routeOfUrl } from '@reticlehq/engine/question/predicate/predicate-route.js';
|
|
7
7
|
import { carryReticleIdentity } from '../../surface/tools/lease-tools.js';
|
|
8
8
|
import { replayFlow } from './flow-replay.js';
|
|
9
|
-
import { anchorQueryArgs } from './flow-step-runners.js';
|
|
9
|
+
import { anchorPrecondition, anchorQueryArgs } from './flow-step-runners.js';
|
|
10
10
|
import { queryRefs } from './replay.js';
|
|
11
11
|
import { assertSuccess, dynamicTestids, successLabel, SUCCESS_STEP_TOOL } from './flow-success.js';
|
|
12
12
|
import { buildDecision, unverifiableReason } from './decision.js';
|
|
13
13
|
import { unsuppliedSecrets } from './fields/flow-secret-field.js';
|
|
14
|
-
import { assertStepExpect } from './flow-replay.js';
|
|
14
|
+
import { assertStepExpect, DocumentLostDuringReplay, } from './flow-replay.js';
|
|
15
|
+
import { isDocumentGoneError } from '../../portal/session/facts/session-replaced.js';
|
|
15
16
|
import { classifyFlowAssertions, flattenSteps } from './flow-classify.js';
|
|
16
17
|
import { dischargeFlowIntent, flowIntentStatement, flowReplayVerdictId } from './flow-intent.js';
|
|
17
18
|
import { IntentStore } from '../../memory/intent/intent-store.js';
|
|
@@ -29,6 +30,7 @@ import { resolveProjectCloud } from '../../memory/cloud/cloud-config.js';
|
|
|
29
30
|
import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
|
|
30
31
|
import { log } from '../../log.js';
|
|
31
32
|
import { flowsForSession } from './flow-store-for-session.js';
|
|
33
|
+
import { projectForRoot } from '../../memory/project/project-for-root.js';
|
|
32
34
|
import { FlowParseNote } from './flow-expect-grammar.js';
|
|
33
35
|
export function latestRecordedFlow(events) {
|
|
34
36
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
@@ -45,6 +47,9 @@ export function latestRecordedFlow(events) {
|
|
|
45
47
|
export function flowErrorMessage(code, detail) {
|
|
46
48
|
if (FlowErrorCode.PARSE_FAILED === code && undefined !== detail)
|
|
47
49
|
return detail;
|
|
50
|
+
// The detail names both versions and the remedy, so it beats anything generic this could say.
|
|
51
|
+
if (FlowErrorCode.WRONG_VERSION === code && undefined !== detail)
|
|
52
|
+
return detail;
|
|
48
53
|
switch (code) {
|
|
49
54
|
case FlowErrorCode.INVALID_NAME:
|
|
50
55
|
return 'invalid flow name — use a single safe segment (letters/digits/-/_), no path separators';
|
|
@@ -54,6 +59,10 @@ export function flowErrorMessage(code, detail) {
|
|
|
54
59
|
return FlowParseNote.MALFORMED;
|
|
55
60
|
case FlowErrorCode.NO_RECORDING:
|
|
56
61
|
return 'no compiled recording by that name — record one (reticle_record{action:"start"|"stop"}) first';
|
|
62
|
+
// Never "regenerate it": the file is intact and the reader is the wrong one. Telling somebody
|
|
63
|
+
// to rewrite an undamaged flow is the failure this code was split out of PARSE_FAILED to stop.
|
|
64
|
+
case FlowErrorCode.WRONG_VERSION:
|
|
65
|
+
return 'this flow file was written in a different flow-file format — the file is not damaged, this Reticle cannot read that version. Upgrade or downgrade Reticle rather than editing the flow';
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
/** Map the wire ReplayStatus onto the persisted RunStatus (ok→pass). */
|
|
@@ -76,7 +85,7 @@ async function recordReplayRun(deps, name, status, driftSteps, durationMs, proje
|
|
|
76
85
|
/** The APP's `.reticle`, resolved from the session — never the daemon's own. */
|
|
77
86
|
recordRoot) {
|
|
78
87
|
const runStatus = replayToRunStatus(status);
|
|
79
|
-
await deps.
|
|
88
|
+
await projectForRoot(deps, recordRoot).recordRun({
|
|
80
89
|
kind: RunKind.FLOW_REPLAY,
|
|
81
90
|
name,
|
|
82
91
|
status: runStatus,
|
|
@@ -104,20 +113,46 @@ recordRoot) {
|
|
|
104
113
|
function currentPathOf(session) {
|
|
105
114
|
const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
|
|
106
115
|
const last = routes.at(-1);
|
|
107
|
-
// pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE.
|
|
108
|
-
//
|
|
109
|
-
//
|
|
116
|
+
// pathname + search + hash, because `startPath` is compared against this and must stay NAVIGABLE.
|
|
117
|
+
//
|
|
118
|
+
// Reading the pathname alone made both sides `/` on a hash router — always "same path", so the
|
|
119
|
+
// hint never fired however far the tab had drifted, on the router desktop renderers use by
|
|
120
|
+
// default. Leaving the SEARCH out was the mirror defect: `startPath` keeps its query, so a flow
|
|
121
|
+
// starting at `/admin/events/7?tab=wrap` never matched a tab sitting on exactly that, re-navigated
|
|
122
|
+
// on every replay, and the re-navigation killed the session mid-flow.
|
|
123
|
+
//
|
|
124
|
+
// Comparing without the query instead would have been worse in the direction that matters. The
|
|
125
|
+
// query usually decides what the page renders, so a tab on `?tab=summary` would read as "already
|
|
126
|
+
// at `?tab=wrap`" and the replay would start on the wrong page with nothing saying so. Both sides
|
|
127
|
+
// carry it, which keeps a real difference visible and stops the false one.
|
|
110
128
|
const observed = last === undefined ? undefined : routeOfEvent(last);
|
|
111
129
|
if (observed !== undefined)
|
|
112
|
-
return `${observed.docPath}${observed.hash}`;
|
|
130
|
+
return `${observed.docPath}${observed.search}${observed.hash}`;
|
|
113
131
|
if (session.url === undefined)
|
|
114
132
|
return undefined;
|
|
115
133
|
const fromUrl = routeOfUrl(session.url);
|
|
116
|
-
return fromUrl === undefined ? undefined : `${fromUrl.docPath}${fromUrl.hash}`;
|
|
134
|
+
return fromUrl === undefined ? undefined : `${fromUrl.docPath}${fromUrl.search}${fromUrl.hash}`;
|
|
117
135
|
}
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Is the tab where the flow asked to start? Up to a trailing slash, and up to the query the flow
|
|
138
|
+
* did not ask about.
|
|
139
|
+
*
|
|
140
|
+
* `startPath` is the SPECIFICATION, so it decides what counts. A query it recorded is compared:
|
|
141
|
+
* `?tab=wrap` and `?tab=summary` are different pages, and a replay that starts on the wrong one
|
|
142
|
+
* proves nothing about the right one. A query it did NOT record is ignored: the tab carrying
|
|
143
|
+
* `?next=%2F` on a login page, or the identity params Reticle puts on a leased tab, are not the flow
|
|
144
|
+
* being elsewhere, and navigating to strip them costs a session for nothing.
|
|
145
|
+
*
|
|
146
|
+
* The asymmetry is the whole point and the reason `observed` and `expected` are named rather than
|
|
147
|
+
* `a` and `b`. Comparing with the query on both sides always re-navigated a query-bearing
|
|
148
|
+
* `startPath` (#1059, which killed the session mid-flow); comparing with it on neither side reads a
|
|
149
|
+
* tab on `?tab=summary` as already at `?tab=wrap`.
|
|
150
|
+
*/
|
|
151
|
+
function samePath(observed, expected) {
|
|
152
|
+
const trimmed = (path) => path.replace(/\/$/, '');
|
|
153
|
+
const withoutQuery = (path) => path.replace(/\?[^#]*/, '');
|
|
154
|
+
const comparable = expected.includes('?') ? observed : withoutQuery(observed);
|
|
155
|
+
return trimmed(comparable) === trimmed(expected);
|
|
121
156
|
}
|
|
122
157
|
/**
|
|
123
158
|
* Ask the project what it already knows about this flow.
|
|
@@ -199,9 +234,26 @@ const REAL_ARRIVAL_CLOCK = {
|
|
|
199
234
|
* `resolve` throws). Keyed to the navigated tab's own identity on purpose: matching "any session at
|
|
200
235
|
* the target page" would happily hand replay an unrelated tab that was already sitting there.
|
|
201
236
|
*/
|
|
202
|
-
function arrivedSuccessor(sessions, oldId, target
|
|
237
|
+
function arrivedSuccessor(sessions, oldId, target,
|
|
238
|
+
/** The handle that issued the navigation, when only a genuinely new one counts as arrival. */
|
|
239
|
+
mustReplace) {
|
|
203
240
|
try {
|
|
204
241
|
const candidate = sessions.resolve(oldId);
|
|
242
|
+
/*
|
|
243
|
+
* A RELOAD lands on the path it left, so the path cannot tell the successor from the tab that
|
|
244
|
+
* is still tearing down — `resolve(oldId)` answers with the dying one while it is registered,
|
|
245
|
+
* which looks like instant arrival and hands replay a socket that will never answer again.
|
|
246
|
+
*
|
|
247
|
+
* The discriminator is the OBJECT, not the id. A leased tab carries its identity across a page
|
|
248
|
+
* load (that is what the identity params are for), so it reconnects under the SAME id — waiting
|
|
249
|
+
* for a new id there waits forever, and the first lease this was tried on timed out at step 1
|
|
250
|
+
* on a tab that was perfectly healthy. `SessionManager.add` replaces the instance on every
|
|
251
|
+
* reconnect, so a different instance is exactly "the socket came back".
|
|
252
|
+
*
|
|
253
|
+
* A navigation to a different route has the path as its discriminator and does not need this.
|
|
254
|
+
*/
|
|
255
|
+
if (mustReplace !== undefined && candidate === mustReplace)
|
|
256
|
+
return undefined;
|
|
205
257
|
const path = currentPathOf(candidate);
|
|
206
258
|
return path !== undefined && samePath(path, target) ? candidate : undefined;
|
|
207
259
|
}
|
|
@@ -240,36 +292,83 @@ async function firstStepResolvesHere(session, flow) {
|
|
|
240
292
|
}
|
|
241
293
|
}
|
|
242
294
|
/**
|
|
243
|
-
* Replay's half of the FlowFile contract:
|
|
295
|
+
* Replay's half of the FlowFile contract: put the tab on the flow's `startPath` before step 1, and
|
|
296
|
+
* RELOAD it if it is already there.
|
|
297
|
+
*
|
|
298
|
+
* The reload is the state contract. Replaying "add a task" twice against a tab nobody reset gives
|
|
299
|
+
* the second run a world the recording never saw, and its assertions then describe the first run
|
|
300
|
+
* rather than the app. Our own `replay-determinism.mjs` has always refreshed before every run — the
|
|
301
|
+
* determinism we measured was never the determinism a user got.
|
|
302
|
+
*
|
|
303
|
+
* `requires` is the declared opt-out, and its first caller. A flow that says it starts from state
|
|
304
|
+
* another flow established is the one flow a page load would destroy, so it is never reset — here
|
|
305
|
+
* or from the wrong page. Silence is not an opt-out: a flow that depends on leftovers without
|
|
306
|
+
* saying so is a flow whose green means nothing on a fresh machine, and CI is a fresh machine.
|
|
244
307
|
*
|
|
245
308
|
* A full-page load tears down the session socket, so the navigation must happen here — before any
|
|
246
309
|
* step runs — and the replay continues on the session the SDK reconnects as (found via the same
|
|
247
310
|
* tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
|
|
248
|
-
* when the flow carries no startPath, the
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
311
|
+
* when the flow carries no startPath, the current route is unobservable, the navigation is refused,
|
|
312
|
+
* or the SDK never reconnects in the window, replay proceeds on the connected session as before —
|
|
313
|
+
* with startPathMismatchHint turning any resulting drift into an actionable next move rather than a
|
|
314
|
+
* mystifying one.
|
|
252
315
|
*/
|
|
253
316
|
export async function arriveAtStartPath(sessions, session, flow, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK) {
|
|
254
317
|
const target = flow.startPath;
|
|
255
318
|
if (target === undefined || 0 === target.length)
|
|
256
|
-
return
|
|
319
|
+
return {};
|
|
257
320
|
const current = currentPathOf(session);
|
|
258
|
-
if (current === undefined
|
|
259
|
-
return
|
|
260
|
-
// The
|
|
261
|
-
if (
|
|
262
|
-
return
|
|
321
|
+
if (current === undefined)
|
|
322
|
+
return {};
|
|
323
|
+
// The declared opt-out, both directions. See above.
|
|
324
|
+
if (0 < (flow.requires?.length ?? 0))
|
|
325
|
+
return {};
|
|
326
|
+
const here = samePath(current, target);
|
|
327
|
+
// A route mismatch that step 1 can start from anyway is not worth a page load: navigating away
|
|
328
|
+
// from a persistent anchor could only hurt, and did. Arriving is the goal; resetting a tab that is
|
|
329
|
+
// ALREADY here is a different decision, taken above, and this must not override it.
|
|
330
|
+
if (!here && (await firstStepResolvesHere(session, flow)))
|
|
331
|
+
return {};
|
|
263
332
|
let destination;
|
|
264
333
|
try {
|
|
265
334
|
// startPath is a pathname (a host belongs to the machine, not the journey) — resolve it
|
|
266
|
-
// against the tab's own URL to get something the browser can be sent to.
|
|
267
|
-
|
|
335
|
+
// against the tab's own URL to get something the browser can be sent to. Already HERE, the
|
|
336
|
+
// reset reloads the page the tab is on: `samePath` ignored a query the flow did not record, and
|
|
337
|
+
// navigating to `target` would strip it, so the replay would test a different page than the one
|
|
338
|
+
// it just agreed it was on.
|
|
339
|
+
destination = new URL(here ? current : target, session.url).toString();
|
|
268
340
|
}
|
|
269
341
|
catch {
|
|
270
|
-
return
|
|
342
|
+
return {}; // no usable base URL — nowhere to navigate from
|
|
271
343
|
}
|
|
272
|
-
|
|
344
|
+
// Measured either side of the reset, and only when the reset is a reload: reachable before and
|
|
345
|
+
// gone after is the reload's doing, not the flow file's. Without this, an app holding its session
|
|
346
|
+
// in memory comes back signed out and step 1 blames a component that is completely fine.
|
|
347
|
+
const resolvedBefore = here && (await firstStepResolvesHere(session, flow));
|
|
348
|
+
const arrived = await navigateAndAwait(sessions, session, destination, target, timeoutMs, clock, here);
|
|
349
|
+
if (arrived === undefined)
|
|
350
|
+
return {};
|
|
351
|
+
if (!resolvedBefore || (await firstStepResolvesHere(arrived, flow)))
|
|
352
|
+
return { session: arrived };
|
|
353
|
+
return {
|
|
354
|
+
session: arrived,
|
|
355
|
+
resetCost: resetCostHint(flow, target),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Why step 1 failed after the reset, and the one line that opts the flow out.
|
|
360
|
+
*
|
|
361
|
+
* No tool writes `requires`, so naming the field was advice nobody could act on without guessing its
|
|
362
|
+
* shape. The first anchor IS the precondition: the element step 1 needs, present before it runs.
|
|
363
|
+
*/
|
|
364
|
+
function resetCostHint(flow, target) {
|
|
365
|
+
const first = flow.steps?.[0];
|
|
366
|
+
const needs = first === undefined ? undefined : anchorPrecondition(first.anchor);
|
|
367
|
+
const optOut = needs === undefined
|
|
368
|
+
? 'Declare what it needs (`requires`) in the flow file to opt out of the reset'
|
|
369
|
+
: `Add \`"requires":${JSON.stringify([needs])}\` to the flow file to opt out of the reset`;
|
|
370
|
+
return (`replay reloaded ${target} before step 1 and the first anchor did not come back — this flow ` +
|
|
371
|
+
`starts from state a page load discards. ${optOut}, or record it from a cold page`);
|
|
273
372
|
}
|
|
274
373
|
/**
|
|
275
374
|
* Send a tab somewhere and wait for the session that comes back.
|
|
@@ -283,7 +382,9 @@ export async function arriveAtStartPath(sessions, session, flow, timeoutMs = STA
|
|
|
283
382
|
* to move and this decides how, which is also why the deliberate short-circuits live up there and
|
|
284
383
|
* not in here: a caller that has already decided it must move should not have to argue with them.
|
|
285
384
|
*/
|
|
286
|
-
export async function navigateAndAwait(sessions, session, destination, expectedPath, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK
|
|
385
|
+
export async function navigateAndAwait(sessions, session, destination, expectedPath, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK,
|
|
386
|
+
/** True when the destination is the page we are already on — see `arrivedSuccessor`. */
|
|
387
|
+
isReload = false) {
|
|
287
388
|
// A leased tab is addressed by URL params, so navigating without them would strand the lease.
|
|
288
389
|
const url = carryReticleIdentity(session.url, destination);
|
|
289
390
|
try {
|
|
@@ -291,12 +392,17 @@ export async function navigateAndAwait(sessions, session, destination, expectedP
|
|
|
291
392
|
if (!outcome.ok || true !== asRecord(outcome.result)['ok'])
|
|
292
393
|
return undefined;
|
|
293
394
|
}
|
|
294
|
-
catch {
|
|
295
|
-
|
|
395
|
+
catch (error) {
|
|
396
|
+
// A navigation that unloads the page rejects the very command that asked for it — the transport
|
|
397
|
+
// dies with the document. Reading that as "the navigate failed" skipped the arrival poll below
|
|
398
|
+
// and left replay driving a handle that could never answer again, which is how a flow with a
|
|
399
|
+
// startPath died at step 1 on an app that had loaded perfectly. Anything else is a real refusal.
|
|
400
|
+
if (!isDocumentGoneError(error))
|
|
401
|
+
return undefined;
|
|
296
402
|
}
|
|
297
403
|
const deadline = clock.now() + timeoutMs;
|
|
298
404
|
for (;;) {
|
|
299
|
-
const arrived = arrivedSuccessor(sessions, session.id, expectedPath);
|
|
405
|
+
const arrived = arrivedSuccessor(sessions, session.id, expectedPath, isReload ? session : undefined);
|
|
300
406
|
if (arrived !== undefined)
|
|
301
407
|
return arrived;
|
|
302
408
|
if (clock.now() >= deadline)
|
|
@@ -354,14 +460,55 @@ async function firstUnmetPrecondition(session, flow, since) {
|
|
|
354
460
|
for (const claim of flow.requires ?? []) {
|
|
355
461
|
// Zero budget: a precondition is a claim about the state you are starting FROM. Waiting for one
|
|
356
462
|
// turns "was it true" into "did it become true", which is a different and much weaker question.
|
|
357
|
-
|
|
463
|
+
let drift;
|
|
464
|
+
try {
|
|
465
|
+
drift = await assertStepExpect(session, claim, waitForPredicate, 0, since);
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
if (!isDocumentGoneError(error))
|
|
469
|
+
throw error;
|
|
470
|
+
return `the page went away while this flow's preconditions were being checked, so nothing ran and nothing was proved. Replay again once the page is back.`;
|
|
471
|
+
}
|
|
358
472
|
if (drift !== undefined) {
|
|
359
473
|
return `a precondition of this flow does not hold (${JSON.stringify(claim)}), so nothing ran and nothing was proved. Run the flow that establishes it first, or drive that state yourself.`;
|
|
360
474
|
}
|
|
361
475
|
}
|
|
362
476
|
return undefined;
|
|
363
477
|
}
|
|
364
|
-
|
|
478
|
+
/**
|
|
479
|
+
* The honest answer when the document replay was driving went away mid-run.
|
|
480
|
+
*
|
|
481
|
+
* Not a pass: nothing graded the journey, and the steps in hand are only the ones the departed
|
|
482
|
+
* document answered. Not a failure either — the app was never observed doing anything wrong, and
|
|
483
|
+
* reporting one would be the false red that sends a reader into product code that is fine. So it
|
|
484
|
+
* lands in the bucket this engine already has for "nothing was proved", the same one an unmet
|
|
485
|
+
* precondition uses, with the steps that DID answer attached and no row invented for the one that
|
|
486
|
+
* did not.
|
|
487
|
+
*
|
|
488
|
+
* Why replay reports rather than follows: the successor cannot be identified at the moment the
|
|
489
|
+
* socket closes. `SessionManager.remove` runs from the close handler, writes the tombstone a
|
|
490
|
+
* successor is later matched against, and has nothing to match yet — so the rejection is a plain
|
|
491
|
+
* transport failure by design, and a step in the middle of a journey has no budget to sit and poll
|
|
492
|
+
* with. `navigateAndAwait` CAN wait, because it knows it asked for the navigation, and it does.
|
|
493
|
+
*/
|
|
494
|
+
export function lostDocumentResult(name, lost) {
|
|
495
|
+
return {
|
|
496
|
+
name,
|
|
497
|
+
status: ReplayStatus.OK,
|
|
498
|
+
steps: lost.steps,
|
|
499
|
+
unverifiable: {
|
|
500
|
+
reason: `the page's connection to Reticle was replaced while step ${String(lost.atStep)} was running ` +
|
|
501
|
+
`(a new document loaded, or its socket reconnected), so the run ` +
|
|
502
|
+
`could not be graded — the steps before it are reported and nothing after it was observed. ` +
|
|
503
|
+
`Nothing here says the app failed. If that navigation is part of the journey, record the ` +
|
|
504
|
+
`step's consequence with \`expect\` and replay again; if it was not, the flow started ` +
|
|
505
|
+
`somewhere it no longer belongs.`,
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
export async function replayNamedFlow(deps, args,
|
|
510
|
+
/** Wraps the session the steps run on, so a caller can watch what they resolved. */
|
|
511
|
+
observe = (s) => s) {
|
|
365
512
|
const startedAt = deps.now();
|
|
366
513
|
const name = asString(args['flowName']) ?? '';
|
|
367
514
|
// Resolve within the connecting app's scope so a shared daemon replays THIS project's flow, not a
|
|
@@ -401,8 +548,11 @@ export async function replayNamedFlow(deps, args) {
|
|
|
401
548
|
// The FlowFile contract: replay navigates to the flow's startPath before step 1, and the steps run
|
|
402
549
|
// on the session the SDK reconnects as. When arrival can't be confirmed, replay proceeds on the
|
|
403
550
|
// connected session — and the hint below turns the wrong-page drift into an actionable next move.
|
|
404
|
-
const
|
|
405
|
-
const
|
|
551
|
+
const arrival = await arriveAtStartPath(deps.sessions, connected, loaded.value);
|
|
552
|
+
const session = arrival.session ?? connected;
|
|
553
|
+
// The reset's own cost outranks the wrong-page hint: if the reload is why step 1 cannot start,
|
|
554
|
+
// "navigate there and replay" is advice that would do the same thing again.
|
|
555
|
+
const startPathHint = arrival.resetCost ?? startPathMismatchHint(loaded.value, session);
|
|
406
556
|
// Floor the success oracle at the start of THIS replay so a stale signal from a prior run
|
|
407
557
|
// in the same session can't fake a pass.
|
|
408
558
|
const replayFloor = session.elapsed();
|
|
@@ -467,17 +617,25 @@ export async function replayNamedFlow(deps, args) {
|
|
|
467
617
|
unverifiable: { reason: unmet },
|
|
468
618
|
};
|
|
469
619
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
620
|
+
let steps;
|
|
621
|
+
try {
|
|
622
|
+
steps = await replayFlow(observe(session), replayable, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous'], undefined, {
|
|
623
|
+
// How an `invoke` step finds the flow it runs. Scoped to the same project as the flow being
|
|
624
|
+
// replayed, so a composite cannot reach into another app's store for a same-named sub-journey.
|
|
625
|
+
resolveFlow: async (invoked) => {
|
|
626
|
+
const sub = await flowsForSession(deps, projectId).flows.load(invoked, projectId);
|
|
627
|
+
return sub.ok ? await resolveFlowUploads(deps, sub.value) : undefined;
|
|
628
|
+
},
|
|
629
|
+
// Bug-sweep mode: keep going past a step whose action ran and whose consequence merely did
|
|
630
|
+
// not hold, so one flow reports one verdict per step instead of stopping at the first defect.
|
|
631
|
+
sweep: true === args['sweep'],
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
if (!(error instanceof DocumentLostDuringReplay))
|
|
636
|
+
throw error;
|
|
637
|
+
return lostDocumentResult(name, error);
|
|
638
|
+
}
|
|
481
639
|
// Computed HERE, before the synthetic success row is appended below: once that row is pushed,
|
|
482
640
|
// `steps.length` no longer counts only the flow's own steps and the arithmetic is wrong.
|
|
483
641
|
const halted = haltedFrom(steps, loaded.value.steps.length);
|
|
@@ -522,7 +680,7 @@ export async function replayNamedFlow(deps, args) {
|
|
|
522
680
|
// Anti-reward-hacking baseline: record what this flow asserted ONLY when it passed clean. A
|
|
523
681
|
// failing run must never become the baseline a later weakening is measured against.
|
|
524
682
|
if (status === ReplayStatus.OK) {
|
|
525
|
-
await new AssertionTiersStore(deps.fs,
|
|
683
|
+
await new AssertionTiersStore(deps.fs, replayRoot).recordPassing(name, loaded.value.steps.map((s, i) => ({
|
|
526
684
|
step: i,
|
|
527
685
|
...(s.expect === undefined ? {} : { expect: s.expect }),
|
|
528
686
|
})),
|
|
@@ -542,7 +700,7 @@ export async function replayNamedFlow(deps, args) {
|
|
|
542
700
|
}
|
|
543
701
|
}
|
|
544
702
|
// Push-default: the deviation report over this drive's segments, learned across runs. Best-effort.
|
|
545
|
-
const deviation = await computeReplayDeviation(deps, session, replayFloor);
|
|
703
|
+
const deviation = await computeReplayDeviation(deps, session, replayFloor, replayRoot);
|
|
546
704
|
/*
|
|
547
705
|
* What the team already knows about this flow, fetched on the agent's behalf.
|
|
548
706
|
*
|
|
@@ -669,12 +827,14 @@ export function crossStepOnly(whole, steps) {
|
|
|
669
827
|
* The deviation report over a replay's route segments — compared against the learned envelope, which is
|
|
670
828
|
* then updated. Best-effort: any failure (disk, empty window) yields undefined, never breaking the replay.
|
|
671
829
|
*/
|
|
672
|
-
async function computeReplayDeviation(deps, session, floor
|
|
830
|
+
async function computeReplayDeviation(deps, session, floor,
|
|
831
|
+
/** The APP's root — the same one the replay resolved eleven lines above the old call. */
|
|
832
|
+
root) {
|
|
673
833
|
try {
|
|
674
834
|
const segments = computeSegments(session.eventsSince(floor));
|
|
675
835
|
if (0 === segments.length)
|
|
676
836
|
return undefined;
|
|
677
|
-
return await reportAndAccumulate(new EnvelopeStore(deps.fs,
|
|
837
|
+
return await reportAndAccumulate(new EnvelopeStore(deps.fs, root), segments);
|
|
678
838
|
}
|
|
679
839
|
catch {
|
|
680
840
|
return undefined;
|
|
@@ -691,12 +851,11 @@ function applyStartPathHint(result, hint) {
|
|
|
691
851
|
* The connecting session's project, or undefined when no browser is attached. Flow tools use it to
|
|
692
852
|
* scope storage to the current app on a shared daemon; resolving must NOT throw here (list/load are
|
|
693
853
|
* documented to work headless), so a missing/unknown session degrades to the global/legacy store.
|
|
854
|
+
*
|
|
855
|
+
* Re-exported, not re-implemented. This file carried a BYTE-IDENTICAL second copy, and the two
|
|
856
|
+
* drifted the moment `ProjectId` was threaded: `session-root`'s copy returned the brand while this
|
|
857
|
+
* one returned `string`, so every flow tool importing from here was handed a widened value and lost
|
|
858
|
+
* the brand one line after it was minted. Exactly the weak-annotation failure the brand's own doc
|
|
859
|
+
* records against `RunStore.list()`.
|
|
694
860
|
*/
|
|
695
|
-
export
|
|
696
|
-
try {
|
|
697
|
-
return deps.sessions.resolve(sessionId).projectId;
|
|
698
|
-
}
|
|
699
|
-
catch {
|
|
700
|
-
return undefined;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
861
|
+
export { sessionProjectId } from '../../memory/project/session-root.js';
|
|
@@ -26,6 +26,15 @@ export interface FlowReplaySession {
|
|
|
26
26
|
onEvent(listener: (event: ReticleEvent) => void): () => void;
|
|
27
27
|
/** Buffer clock (ms since connect) — required by the predicate engine's `settled` check. */
|
|
28
28
|
elapsed(): number;
|
|
29
|
+
/**
|
|
30
|
+
* Where the app under test lives, so a third-party beacon is not mistaken for the app still
|
|
31
|
+
* working. Optional for the same reason `beginAction` is — a minimal test double should not have
|
|
32
|
+
* to invent an origin — and it FAILS OPEN: with no origin nothing is foreign, so an omitted value
|
|
33
|
+
* suppresses nothing rather than silently widening what a replay ignores.
|
|
34
|
+
*/
|
|
35
|
+
url?: string;
|
|
36
|
+
/** Same-origin endpoints the project declared as background — see `Session.background`. */
|
|
37
|
+
background?: readonly string[];
|
|
29
38
|
}
|
|
30
39
|
/**
|
|
31
40
|
* The injected predicate-waiter (the real waitForPredicate) — reused, never reimplemented.
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
import { Surface } from 'open-verification';
|
|
2
1
|
export { anchorLabel, componentLabel, componentQueryArgs, editDistance, expectElementDrift, nearestIsAmbiguous, nearestTestid, resolveQuery, testidDrift, } from './flow-anchor.js';
|
|
3
2
|
import type { FlowReplaySession, WaitForSignal, Sleep } from './flow-replay-types.js';
|
|
4
3
|
export type { FlowReplaySession, WaitForSignal, Sleep } from './flow-replay-types.js';
|
|
5
4
|
import { type Drift, type FlowFile, type FlowStep, type FlowStepResult } from '@reticlehq/core';
|
|
5
|
+
/**
|
|
6
|
+
* The document this replay was driving went away mid-run.
|
|
7
|
+
*
|
|
8
|
+
* Almost always because the replay itself navigated it — a `startPath` goto, or a step that clicks a
|
|
9
|
+
* link. The steps already in hand are real answers from the document that WAS there, so they travel
|
|
10
|
+
* with the error; the step that was in flight has no answer at all and is deliberately absent.
|
|
11
|
+
* Synthesising one would be a verdict about a page nobody observed, which is the one thing worse
|
|
12
|
+
* than returning no verdict.
|
|
13
|
+
*
|
|
14
|
+
* Typed rather than a message, because the caller has to tell it from a step that genuinely failed:
|
|
15
|
+
* one says the app is wrong, the other says we stopped watching.
|
|
16
|
+
*/
|
|
17
|
+
export declare class DocumentLostDuringReplay extends Error {
|
|
18
|
+
readonly steps: FlowStepResult[];
|
|
19
|
+
readonly atStep: number;
|
|
20
|
+
constructor(steps: FlowStepResult[], atStep: number, lostTo?: unknown);
|
|
21
|
+
}
|
|
22
|
+
/** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
|
|
23
|
+
export declare function runTestidStep(session: FlowReplaySession, step: FlowStep, index: number, value: string, dynamic: ReadonlySet<string>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
|
|
6
24
|
/**
|
|
7
25
|
* After a step's anchor resolves and its action runs, its `expect` is EVALUATED — every kind of it.
|
|
8
26
|
*
|
|
@@ -24,16 +42,23 @@ import { type Drift, type FlowFile, type FlowStep, type FlowStepResult } from '@
|
|
|
24
42
|
* quietly stop agreeing, and the disagreement would land as a flow that replays green against a
|
|
25
43
|
* state it was never meant to run in.
|
|
26
44
|
*/
|
|
27
|
-
export declare function assertStepExpect(session: FlowReplaySession, expect: NonNullable<FlowStep['expect']>,
|
|
45
|
+
export declare function assertStepExpect(session: FlowReplaySession, expect: NonNullable<FlowStep['expect']>, waitForSignal: WaitForSignal, timeoutMs: number, since: number): Promise<Drift | undefined>;
|
|
28
46
|
/**
|
|
29
47
|
* Replay a loaded flow by RE-RESOLVING every step's semantic anchor against the live DOM — never
|
|
30
48
|
* a stale ref. A testid anchor is re-found by reticle_query; a signal anchor waits on a predicate.
|
|
31
49
|
* On the first anchor MISS the step carries legible drift and replay STOPS, returning the partial
|
|
32
50
|
* results. This is the "whose fault is it" contract, not a blind "command failed".
|
|
33
51
|
*/
|
|
34
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* How a replay is loaded and how far it goes — not WHERE it starts.
|
|
54
|
+
*
|
|
55
|
+
* It carried a `from` (and a `surface` that existed only to gate it): replay from step N, re-driving
|
|
56
|
+
* the prefix silently. The machinery was built, tested and reachable from nothing at all — no tool
|
|
57
|
+
* argument, no CLI flag, no caller anywhere. Deleted rather than wired: unreachable code is read as
|
|
58
|
+
* current by the next person and maintained forever, its passing tests make it look load-bearing,
|
|
59
|
+
* and nobody has asked for resume-from-step. The history keeps it if it is ever wanted.
|
|
60
|
+
*/
|
|
35
61
|
export interface ReplayFromOptions {
|
|
36
|
-
from?: number;
|
|
37
62
|
/**
|
|
38
63
|
* How to load a flow this one INVOKES. Absent means invocations cannot be followed.
|
|
39
64
|
*
|
|
@@ -50,13 +75,6 @@ export interface ReplayFromOptions {
|
|
|
50
75
|
flow: string;
|
|
51
76
|
step: number;
|
|
52
77
|
}[];
|
|
53
|
-
/**
|
|
54
|
-
* Which kind of subject this is. Decides whether re-driving the prefix is allowed at all.
|
|
55
|
-
*
|
|
56
|
-
* Defaults to `web`, which is the permissive answer — correct for the only surface that resumes
|
|
57
|
-
* today, and the reason a caller on a committing surface must say so rather than rely on silence.
|
|
58
|
-
*/
|
|
59
|
-
surface?: Surface;
|
|
60
78
|
/**
|
|
61
79
|
* Keep going past a step whose ACTION ran and whose declared consequence merely did not hold.
|
|
62
80
|
*
|