@reticlehq/server 3.2.0 → 3.4.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/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +23 -22
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +41 -0
- package/dist/command/cli/adhoc-suite.js +159 -0
- package/dist/command/cli/adhoc-verdict.d.ts +58 -2
- package/dist/command/cli/adhoc-verdict.js +108 -13
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +30 -8
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- 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 +14 -1
- package/dist/command/cli/cli-parse.js +54 -8
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +80 -0
- package/dist/command/cli/cli-verify.js +181 -26
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +9 -29
- package/dist/command/cli/launch/cli-launch.js +27 -49
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
- package/dist/command/cli/ports/resolve/cli-port.js +169 -15
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +67 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +171 -156
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +36 -22
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +13 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +78 -104
- 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 +40 -9
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- 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/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- 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 +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- 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/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -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-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- 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 +33 -12
- package/dist/language/flows/flow-replay-run.js +131 -52
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +27 -12
- package/dist/language/flows/flow-replay.js +127 -111
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- 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-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +18 -5
- package/dist/language/flows/flows.js +101 -16
- 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/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/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +53 -13
- 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/verify-change-tools.js +19 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -3
- package/dist/memory/cloud/cloud-sync.js +7 -10
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +78 -1
- package/dist/memory/cloud/sync-cycle.js +321 -53
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +45 -20
- package/dist/memory/cloud/sync-disk.js +27 -8
- package/dist/memory/impact/impact-store.js +6 -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 +252 -140
- package/dist/memory/intent/intent-shard.d.ts +0 -23
- package/dist/memory/intent/intent-shard.js +1 -11
- 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/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +19 -14
- package/dist/memory/journal/drive-flow.js +65 -48
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- 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 +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- 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 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +13 -2
- package/dist/memory/journal/session-end.js +88 -18
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/fs/fs-port.js +44 -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/project-tools.js +3 -1
- 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 +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +31 -5
- 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.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +21 -13
- 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-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- 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/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +43 -3
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +29 -6
- package/dist/portal/session/session-manager.js +42 -5
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- 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 +10 -0
- package/dist/portal/session/session.js +34 -26
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +5 -5
- package/dist/surface/mcp/mcp.js +4 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +21 -8
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- 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/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- 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.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +68 -8
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +51 -74
- 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 +26 -3
- package/dist/surface/tools/assert/assert-verdict.js +32 -6
- package/dist/surface/tools/browser-tools.js +17 -4
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/invoke-tool.js +17 -4
- 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 +68 -11
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +23 -7
- 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 +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +68 -29
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -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 +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +34 -0
- package/dist/wire-journal.js +104 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +5 -2
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +21 -9
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +32 -2
- package/docs/frameworks.mdx +140 -9
- package/docs/getting-started.md +3 -3
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +37 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +176 -7
- package/docs/quickstart.mdx +2 -2
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +11 -11
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +8 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +62 -0
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +11 -10
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
- package/dist/judgement/phenomena/phenomena.d.ts +0 -26
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export { anchorLabel, componentLabel, componentQueryArgs, editDistance, expectElementDrift, nearestIsAmbiguous, nearestTestid, resolveQuery, testidDrift, } from './flow-anchor.js';
|
|
1
|
+
export { nearestIsAmbiguous, nearestTestid, resolveQuery } 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';
|
|
@@ -20,6 +19,8 @@ export declare class DocumentLostDuringReplay extends Error {
|
|
|
20
19
|
readonly atStep: number;
|
|
21
20
|
constructor(steps: FlowStepResult[], atStep: number, lostTo?: unknown);
|
|
22
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>;
|
|
23
24
|
/**
|
|
24
25
|
* After a step's anchor resolves and its action runs, its `expect` is EVALUATED — every kind of it.
|
|
25
26
|
*
|
|
@@ -41,16 +42,26 @@ export declare class DocumentLostDuringReplay extends Error {
|
|
|
41
42
|
* quietly stop agreeing, and the disagreement would land as a flow that replays green against a
|
|
42
43
|
* state it was never meant to run in.
|
|
43
44
|
*/
|
|
44
|
-
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>;
|
|
45
46
|
/**
|
|
46
47
|
* Replay a loaded flow by RE-RESOLVING every step's semantic anchor against the live DOM — never
|
|
47
48
|
* a stale ref. A testid anchor is re-found by reticle_query; a signal anchor waits on a predicate.
|
|
48
49
|
* On the first anchor MISS the step carries legible drift and replay STOPS, returning the partial
|
|
49
50
|
* results. This is the "whose fault is it" contract, not a blind "command failed".
|
|
50
51
|
*/
|
|
51
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* How a replay is loaded, where it starts reporting, and how far it goes.
|
|
54
|
+
*/
|
|
52
55
|
export interface ReplayFromOptions {
|
|
53
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Resume at this step: an index, or a step's `id` (which survives edits that shift indices).
|
|
58
|
+
*
|
|
59
|
+
* There is no state to restore, so the steps before it are re-driven — quickly, as setup: their
|
|
60
|
+
* actions run, their declared consequences are not checked and their results are not reported.
|
|
61
|
+
* The one exception is a setup step that FAILS: the resume never reached the step it was asked
|
|
62
|
+
* for, and swallowing that would report the run as starting where it did not.
|
|
63
|
+
*/
|
|
64
|
+
from?: number | string;
|
|
54
65
|
/**
|
|
55
66
|
* How to load a flow this one INVOKES. Absent means invocations cannot be followed.
|
|
56
67
|
*
|
|
@@ -67,13 +78,6 @@ export interface ReplayFromOptions {
|
|
|
67
78
|
flow: string;
|
|
68
79
|
step: number;
|
|
69
80
|
}[];
|
|
70
|
-
/**
|
|
71
|
-
* Which kind of subject this is. Decides whether re-driving the prefix is allowed at all.
|
|
72
|
-
*
|
|
73
|
-
* Defaults to `web`, which is the permissive answer — correct for the only surface that resumes
|
|
74
|
-
* today, and the reason a caller on a committing surface must say so rather than rely on silence.
|
|
75
|
-
*/
|
|
76
|
-
surface?: Surface;
|
|
77
81
|
/**
|
|
78
82
|
* Keep going past a step whose ACTION ran and whose declared consequence merely did not hold.
|
|
79
83
|
*
|
|
@@ -88,4 +92,15 @@ export interface ReplayFromOptions {
|
|
|
88
92
|
*/
|
|
89
93
|
sweep?: boolean;
|
|
90
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The index a replay resumes at, or `undefined` when `from` names no step of this flow.
|
|
97
|
+
*
|
|
98
|
+
* A step declared `commits` in the prefix refuses the resume (0 = replay everything): re-driving it
|
|
99
|
+
* is not setup, it is a second charge, a second email, a second record.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resumeIndex(from: number | string | undefined, steps: readonly FlowStep[]): number | undefined;
|
|
102
|
+
/** The `from` argument, narrowed: a step index or a step id. */
|
|
103
|
+
export declare function resumeArg(raw: unknown): number | string | undefined;
|
|
104
|
+
/** Why `from` cannot be resumed at, or undefined when it can (or was not given). */
|
|
105
|
+
export declare function unresolvedResume(from: number | string | undefined, steps: readonly FlowStep[]): string | undefined;
|
|
91
106
|
export declare function replayFlow(session: FlowReplaySession, flow: FlowFile, waitForSignal: WaitForSignal, signalTimeoutMs: number, confirmDangerous?: boolean, sleep?: Sleep, options?: ReplayFromOptions): Promise<FlowStepResult[]>;
|
|
@@ -1,23 +1,21 @@
|
|
|
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';
|
|
5
|
-
export {
|
|
6
|
-
import { routeOfEvent } from '@reticlehq/engine/question/predicate/predicate-route.js';
|
|
4
|
+
import { anchorLabel, expectElementDrift, resolveTestid, testidDrift, ambiguousAnchorDrift, } from './flow-anchor.js';
|
|
5
|
+
export { nearestIsAmbiguous, nearestTestid, resolveQuery } from './flow-anchor.js';
|
|
6
|
+
import { routeOfEvent, routeOfUrl } 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, StepEffect, 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 { successToPredicate } from './flow-success.js';
|
|
14
12
|
import { inFlightRequestLabels } from '../../surface/tools/act/settle-in-flight.js';
|
|
15
13
|
import { namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
|
|
16
14
|
/** What a still-open named request means, in the drift's own words. */
|
|
17
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, ' +
|
|
18
16
|
'so nothing here says the app failed. Raise the step timeout, or look at the endpoint';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
17
|
+
import { isDocumentGoneError, SESSION_DISCONNECTED_REASON, } from '../../portal/session/facts/session-replaced.js';
|
|
18
|
+
import { actOnResolvedRef } from './flow-step-runners.js';
|
|
21
19
|
/**
|
|
22
20
|
* The document this replay was driving went away mid-run.
|
|
23
21
|
*
|
|
@@ -50,8 +48,11 @@ const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
50
48
|
function currentRoute(session) {
|
|
51
49
|
const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
|
|
52
50
|
const last = routes.at(-1);
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
// A page that loaded and never navigated has no route event; its URL is still where it is.
|
|
52
|
+
if (last === undefined) {
|
|
53
|
+
const loaded = session.url === undefined ? undefined : routeOfUrl(session.url);
|
|
54
|
+
return loaded !== undefined && loaded.routePath.length > 0 ? loaded.routePath : undefined;
|
|
55
|
+
}
|
|
55
56
|
// The ROUTER's path. This field answers "which page did this step run on", and the document
|
|
56
57
|
// pathname is `/` on every page of a hash-routed app — so a whole desktop replay reported `/` for
|
|
57
58
|
// every step. Sixth place the same reading was wrong; see routeOfEvent.
|
|
@@ -105,7 +106,7 @@ function summarizeConsequence(events) {
|
|
|
105
106
|
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
106
107
|
}
|
|
107
108
|
/** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
|
|
108
|
-
async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
|
|
109
|
+
export async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
|
|
109
110
|
const { refs, hint } = await resolveTestid(session, value, sleep);
|
|
110
111
|
if (0 === refs.length) {
|
|
111
112
|
return {
|
|
@@ -116,35 +117,50 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
116
117
|
drift: testidDrift(value, hint),
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
/*
|
|
121
|
+
* More than one match is DRIFT, not a note on a passing step.
|
|
122
|
+
*
|
|
123
|
+
* This used to take refs[0], act, and return ok:true with `ambiguousTestidNote`. The verdict
|
|
124
|
+
* reads `drift` and `ok` and never reads a note, so "we guessed which element you meant" was
|
|
125
|
+
* indistinguishable from "it did what it did before" -- the one claim a replay makes.
|
|
126
|
+
*
|
|
127
|
+
* The action is NOT dispatched. Acting and then reporting drift would leave the app changed by
|
|
128
|
+
* a click nobody can attribute, which is worse than the ambiguity it reports.
|
|
129
|
+
*/
|
|
130
|
+
if (refs.length > 1) {
|
|
131
|
+
return {
|
|
132
|
+
step: index,
|
|
133
|
+
tool: step.tool,
|
|
134
|
+
anchor: value,
|
|
135
|
+
ok: false,
|
|
136
|
+
drift: ambiguousAnchorDrift(value, refs.length),
|
|
137
|
+
};
|
|
136
138
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
const ref = refs[0] ?? '';
|
|
140
|
+
/*
|
|
141
|
+
* The same dispatch its siblings use, including the one re-resolve on a stale ref (2.3).
|
|
142
|
+
*
|
|
143
|
+
* This runner had its own inline copy and no retry, so a flow died on a re-render purely because
|
|
144
|
+
* the step was anchored by testid rather than by role — the locator deciding how sturdy the replay
|
|
145
|
+
* is, which is exactly backwards. testid is also the anchor `reticle init` steers people towards,
|
|
146
|
+
* so the kind most likely to appear in a real flow was the kind without the cure.
|
|
147
|
+
*
|
|
148
|
+
* The re-resolve keeps the ambiguity rule above: more than one match is drift, never a guess, so
|
|
149
|
+
* it hands back a ref only when the locator still names exactly one element.
|
|
150
|
+
*/
|
|
151
|
+
const result = await actOnResolvedRef(session, step, index, value, ref, confirmDangerous,
|
|
152
|
+
// The field this step types into — from the anchor, so a redacted fill can be supplied from
|
|
153
|
+
// RETICLE_SECRET_<FIELD> without the flow carrying the secret.
|
|
154
|
+
anchorFieldName(step.anchor), async () => {
|
|
155
|
+
const again = await resolveTestid(session, value, sleep);
|
|
156
|
+
return 1 === again.refs.length ? again.refs[0] : undefined;
|
|
157
|
+
});
|
|
158
|
+
if (!result.ok)
|
|
142
159
|
return result;
|
|
143
|
-
}
|
|
144
160
|
// assert the step's expect.element testid is present AFTER the action —
|
|
145
161
|
// unless that testid was marked DYNAMIC (the LLM-output case), in which case its presence/content
|
|
146
162
|
// is NOT asserted (only the action ran). The skip is scoped strictly to the dynamic set.
|
|
147
|
-
const expectTestid = step.expect
|
|
163
|
+
const expectTestid = expectedElementTestid(step.expect);
|
|
148
164
|
if (expectTestid !== undefined && !dynamic.has(expectTestid)) {
|
|
149
165
|
const expectRefs = await resolveTestid(session, expectTestid, sleep);
|
|
150
166
|
if (0 === expectRefs.refs.length) {
|
|
@@ -160,8 +176,6 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
160
176
|
};
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
|
-
if (note !== undefined)
|
|
164
|
-
result.note = note;
|
|
165
179
|
return result;
|
|
166
180
|
}
|
|
167
181
|
/**
|
|
@@ -185,30 +199,52 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
185
199
|
* quietly stop agreeing, and the disagreement would land as a flow that replays green against a
|
|
186
200
|
* state it was never meant to run in.
|
|
187
201
|
*/
|
|
188
|
-
export async function assertStepExpect(session, expect,
|
|
202
|
+
export async function assertStepExpect(session, expect, waitForSignal, timeoutMs, since) {
|
|
189
203
|
// A testid is already asserted against the live DOM by the step runner. A role/name locator is
|
|
190
204
|
// not that path — stripping every element made a recorded `until` by button name a no-op, so a
|
|
191
205
|
// flow that proved the control at capture time could not go red when it was gone.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
/*
|
|
207
|
+
* Two clauses replay must not wait on, both for reasons that predate the tree.
|
|
208
|
+
*
|
|
209
|
+
* A testid element was already asserted against the live DOM by the step runner, so waiting on it
|
|
210
|
+
* again is a second read of a question already answered. A DYNAMIC testid is one whose content a
|
|
211
|
+
* model writes, and its presence is deliberately never asserted.
|
|
212
|
+
*
|
|
213
|
+
* A role/name locator is NEITHER, and stripping every element made a recorded `until` by button
|
|
214
|
+
* name a no-op — a flow that proved the control at capture time could not go red when it was gone.
|
|
215
|
+
*
|
|
216
|
+
* The DYNAMIC set used to be a parameter here and was dead the whole time: the testid was deleted
|
|
217
|
+
* from the expectation on the line above, and the only thing that read the set asked for the
|
|
218
|
+
* testid that had just been removed. One production caller was already passing an empty set. The
|
|
219
|
+
* rule is real and still applies to the flow's SUCCESS oracle, which is where it lives.
|
|
220
|
+
*/
|
|
221
|
+
const predicate = withoutClauses(expect,
|
|
222
|
+
// Only a PRESENT testid is the runner's to check; an absence stays here or nobody checks it.
|
|
223
|
+
(clause) => PredicateKind.ELEMENT === clause.kind &&
|
|
224
|
+
clause.query.testid !== undefined &&
|
|
225
|
+
true !== clause.absent);
|
|
197
226
|
if (predicate === undefined)
|
|
198
227
|
return undefined;
|
|
199
228
|
const verdict = await waitForSignal(session, predicate, timeoutMs, since);
|
|
200
229
|
if (verdict.pass)
|
|
201
230
|
return undefined;
|
|
231
|
+
// The wait ended because the page stopped being observable, not because the consequence failed:
|
|
232
|
+
// the same fact as a command the dying socket rejected, so it takes the same path (the replay loop
|
|
233
|
+
// reports a lost document). As a drift, a socket that blinked for 26ms read as the app's defect.
|
|
234
|
+
if (true === verdict.observationLost)
|
|
235
|
+
throw new Error(SESSION_DISCONNECTED_REASON);
|
|
202
236
|
// A request the step NAMED that had not come back yet is a blind spot, not an assertion the app
|
|
203
237
|
// failed. The live verdict path already draws this line (namedNetIsInFlight); replay read the
|
|
204
238
|
// same window and never asked, so a slow endpoint was reported as a consequence that never fired.
|
|
205
239
|
// An unrelated open request does not pardon a named URL that never started — matching decides it.
|
|
206
|
-
const openRequests = expect
|
|
240
|
+
const openRequests = assertsState(expect)
|
|
241
|
+
? []
|
|
242
|
+
: inFlightRequestLabels(session.eventsSince(since), session.url, session.background);
|
|
207
243
|
const namedInFlight = namedNetIsInFlight(predicate, openRequests);
|
|
208
244
|
return {
|
|
209
245
|
// The store case keeps its own kind because heal and the run report branch on it; everything
|
|
210
246
|
// else is a consequence that did not hold, and the reason carries observed-vs-expected.
|
|
211
|
-
reasonKind: expect
|
|
247
|
+
reasonKind: assertsState(expect)
|
|
212
248
|
? DriftReason.STATE_MISMATCH
|
|
213
249
|
: namedInFlight
|
|
214
250
|
? DriftReason.NET_STILL_IN_FLIGHT
|
|
@@ -220,21 +256,6 @@ export async function assertStepExpect(session, expect, dynamic, waitForSignal,
|
|
|
220
256
|
nearest: null,
|
|
221
257
|
};
|
|
222
258
|
}
|
|
223
|
-
/** Name the thing that was asserted, for the drift's `anchor` column. */
|
|
224
|
-
function expectLabel(expect) {
|
|
225
|
-
if (expect.signal !== undefined)
|
|
226
|
-
return `signal:${expect.signal}`;
|
|
227
|
-
if (expect.net !== undefined)
|
|
228
|
-
return `net:${expect.net.urlContains ?? expect.net.method ?? '*'}`;
|
|
229
|
-
if (expect.state !== undefined)
|
|
230
|
-
return `state:${expect.state.path}`;
|
|
231
|
-
if (expect.console !== undefined)
|
|
232
|
-
return `console:${expect.console.level ?? '*'}`;
|
|
233
|
-
if (undefined !== expect.element) {
|
|
234
|
-
return expect.element.testid ?? expect.element.name ?? expect.element.role ?? 'element';
|
|
235
|
-
}
|
|
236
|
-
return 'expect';
|
|
237
|
-
}
|
|
238
259
|
/** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
|
|
239
260
|
async function runSignalStep(session, step, index, name, waitForSignal, signalTimeoutMs, since) {
|
|
240
261
|
// Scope to THIS replay's floor, not the whole buffer.
|
|
@@ -263,38 +284,28 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
|
|
|
263
284
|
};
|
|
264
285
|
}
|
|
265
286
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* "Resume is nearly free, just re-run the prefix" is true of a browser and false on a subject that
|
|
269
|
-
* COMMITS. Re-driving a prefix on a service re-sends every request before step N; on a device it
|
|
270
|
-
* moves an arm again. Neither is a convenience, and a protocol that did it silently would be
|
|
271
|
-
* defective rather than helpful.
|
|
287
|
+
* The index a replay resumes at, or `undefined` when `from` names no step of this flow.
|
|
272
288
|
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* nothing is skipped and nothing is silently repeated beyond what a plain replay already does.
|
|
289
|
+
* A step declared `commits` in the prefix refuses the resume (0 = replay everything): re-driving it
|
|
290
|
+
* is not setup, it is a second charge, a second email, a second record.
|
|
276
291
|
*/
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
if (0 === asked)
|
|
280
|
-
return 0;
|
|
281
|
-
if (!mayResumeByReplayingPrefix(options.surface ?? Surface.WEB))
|
|
292
|
+
export function resumeIndex(from, steps) {
|
|
293
|
+
if (from === undefined)
|
|
282
294
|
return 0;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return commitsInPrefix ? 0 : asked;
|
|
295
|
+
const at = 'number' === typeof from ? from : steps.findIndex((step) => step.id === from);
|
|
296
|
+
if (at < 0 || at >= steps.length || !Number.isInteger(at))
|
|
297
|
+
return undefined;
|
|
298
|
+
return steps.slice(0, at).some((step) => StepEffect.COMMITS === step.effect) ? 0 : at;
|
|
299
|
+
}
|
|
300
|
+
/** The `from` argument, narrowed: a step index or a step id. */
|
|
301
|
+
export function resumeArg(raw) {
|
|
302
|
+
return 'number' === typeof raw || ('string' === typeof raw && raw.length > 0) ? raw : undefined;
|
|
303
|
+
}
|
|
304
|
+
/** Why `from` cannot be resumed at, or undefined when it can (or was not given). */
|
|
305
|
+
export function unresolvedResume(from, steps) {
|
|
306
|
+
if (resumeIndex(from, steps) !== undefined)
|
|
307
|
+
return undefined;
|
|
308
|
+
return `cannot resume at ${JSON.stringify(from)}: this flow has ${String(steps.length)} step(s) and no step by that id`;
|
|
298
309
|
}
|
|
299
310
|
/**
|
|
300
311
|
* Run an `invoke` step: load the named flow and replay it, or fail saying why.
|
|
@@ -307,7 +318,7 @@ function resumableFrom(options, steps) {
|
|
|
307
318
|
* own, and the sub-journey's outcome is summarised on the invocation, so a reader sees the shape
|
|
308
319
|
* they recorded rather than a flattened list that has lost every boundary.
|
|
309
320
|
*/
|
|
310
|
-
async function runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options) {
|
|
321
|
+
async function runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options, setup = false) {
|
|
311
322
|
const name = step.invoke ?? '';
|
|
312
323
|
const via = options.via ?? [];
|
|
313
324
|
const here = { flow: flow.name, step: index, via };
|
|
@@ -329,10 +340,10 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
|
|
|
329
340
|
error: `cannot replay "${name}": it was not found, so this journey would report green having never run it`,
|
|
330
341
|
};
|
|
331
342
|
}
|
|
332
|
-
// `from` is
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
const { from:
|
|
343
|
+
// `from` is an offset into the CALLER's steps. A sub-journey invoked from the setup prefix runs
|
|
344
|
+
// whole and unchecked (from = its length); one invoked at or after the resume point runs normally.
|
|
345
|
+
const { from: _outer, ...rest } = options;
|
|
346
|
+
const carried = setup ? { ...rest, from: sub.steps.length } : rest;
|
|
336
347
|
const nested = await replayFlow(session, sub, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, {
|
|
337
348
|
...carried,
|
|
338
349
|
via: [{ flow: flow.name, step: index }, ...via],
|
|
@@ -350,16 +361,9 @@ async function runInvokeStep(session, flow, step, index, waitForSignal, signalTi
|
|
|
350
361
|
}
|
|
351
362
|
export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs, confirmDangerous = false, sleep = realSleep, options = {}) {
|
|
352
363
|
const results = [];
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
* Resuming is re-driving the prefix, not restoring state: there is no way to put an app back
|
|
357
|
-
* where it was without driving it there, and at a measured ~27ms a step there is no reason to
|
|
358
|
-
* try. So the prefix executes silently and the caller sees the journey from the point it asked
|
|
359
|
-
* about -- which is what makes "fix the break, resume, find the next one" a loop rather than a
|
|
360
|
-
* full re-read each time.
|
|
361
|
-
*/
|
|
362
|
-
const from = resumableFrom(options, flow.steps);
|
|
364
|
+
// Steps before this index are setup. `from` was validated by the caller; an unresolvable one
|
|
365
|
+
// resumes nowhere rather than everywhere, so it cannot silently widen into a full replay.
|
|
366
|
+
const from = resumeIndex(options.from, flow.steps) ?? flow.steps.length;
|
|
363
367
|
// testids whose region is LLM-dynamic — their expect-presence is NOT asserted.
|
|
364
368
|
const dynamic = new Set((flow.dynamic ?? [])
|
|
365
369
|
.filter((a) => a.kind === AnchorKind.TESTID)
|
|
@@ -376,8 +380,10 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
376
380
|
try {
|
|
377
381
|
for (const step of flow.steps) {
|
|
378
382
|
if (step.invoke !== undefined) {
|
|
379
|
-
results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options));
|
|
383
|
+
results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options, index < from));
|
|
380
384
|
const last = results[results.length - 1];
|
|
385
|
+
if (last !== undefined && index < from && last.ok)
|
|
386
|
+
results.pop();
|
|
381
387
|
if (last !== undefined && false === last.ok)
|
|
382
388
|
break;
|
|
383
389
|
index += 1;
|
|
@@ -433,10 +439,12 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
433
439
|
* replay path and belongs in its own commit.
|
|
434
440
|
*/
|
|
435
441
|
const declared = [step.expect, ...(step.steps ?? []).map((sub) => sub.expect)].filter((expectation) => expectation !== undefined);
|
|
436
|
-
|
|
442
|
+
// A setup step's consequence is not checked: it is re-driven to get somewhere, not to prove it.
|
|
443
|
+
const checked = index < from ? [] : declared;
|
|
444
|
+
for (const expectation of checked) {
|
|
437
445
|
if (!result.ok || result.drift !== undefined)
|
|
438
446
|
break;
|
|
439
|
-
const expectDrift = await assertStepExpect(session, expectation,
|
|
447
|
+
const expectDrift = await assertStepExpect(session, expectation, waitForSignal, waitFor(step), cursorBefore);
|
|
440
448
|
if (expectDrift !== undefined) {
|
|
441
449
|
result.ok = false;
|
|
442
450
|
result.drift = expectDrift;
|
|
@@ -444,6 +452,9 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
444
452
|
}
|
|
445
453
|
if (page !== undefined)
|
|
446
454
|
result.page = page;
|
|
455
|
+
const endPage = currentRoute(session);
|
|
456
|
+
if (endPage !== undefined && endPage !== page)
|
|
457
|
+
result.endPage = endPage;
|
|
447
458
|
const windowEvents = session.eventsSince(cursorBefore).filter((e) => e.t >= cursorBefore);
|
|
448
459
|
const consequence = summarizeConsequence(windowEvents);
|
|
449
460
|
if (consequence !== undefined)
|
|
@@ -466,8 +477,13 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
466
477
|
// forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
|
|
467
478
|
if (FlowStepTool.ACT === result.tool)
|
|
468
479
|
delete result.tool;
|
|
469
|
-
if (index
|
|
470
|
-
|
|
480
|
+
if (index < from && result.ok && result.drift === undefined) {
|
|
481
|
+
index += 1;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (index < from)
|
|
485
|
+
result.note = `setup step for resuming at step ${String(from)} failed`;
|
|
486
|
+
results.push(result);
|
|
471
487
|
// Under `sweep`, a failure whose action still RAN does not stop the run — the page is where the
|
|
472
488
|
// step left it, so the next step is as meaningful as it was going to be. Anything else halts.
|
|
473
489
|
const sweepPast = true === options.sweep &&
|
|
@@ -33,3 +33,5 @@ export type FlowResult<T> = {
|
|
|
33
33
|
* hold the distinction — this is the fourth id joined into a path, and the one that file missed.
|
|
34
34
|
*/
|
|
35
35
|
export declare const safeProjectId: (projectId?: ProjectId) => ProjectId | undefined;
|
|
36
|
+
/** Map a structured FlowErrorCode to a legible one-line message for the agent. */
|
|
37
|
+
export declare function flowErrorMessage(code: FlowErrorCode, detail?: string): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FlowErrorCode } from '@reticlehq/core';
|
|
2
2
|
import { SAFE_SEGMENT_PATTERN } from '@reticlehq/core';
|
|
3
|
+
import { FlowParseNote } from './flow-expect-grammar.js';
|
|
3
4
|
/**
|
|
4
5
|
* A project id that is safe to put in a path, or nothing.
|
|
5
6
|
*
|
|
@@ -24,3 +25,27 @@ import { SAFE_SEGMENT_PATTERN } from '@reticlehq/core';
|
|
|
24
25
|
* hold the distinction — this is the fourth id joined into a path, and the one that file missed.
|
|
25
26
|
*/
|
|
26
27
|
export const safeProjectId = (projectId) => projectId !== undefined && SAFE_SEGMENT_PATTERN.test(projectId) ? projectId : undefined;
|
|
28
|
+
/** Map a structured FlowErrorCode to a legible one-line message for the agent. */
|
|
29
|
+
export function flowErrorMessage(code, detail) {
|
|
30
|
+
if (FlowErrorCode.PARSE_FAILED === code && undefined !== detail)
|
|
31
|
+
return detail;
|
|
32
|
+
// The detail names both versions and the remedy, so it beats anything generic this could say.
|
|
33
|
+
if (FlowErrorCode.WRONG_VERSION === code && undefined !== detail)
|
|
34
|
+
return detail;
|
|
35
|
+
switch (code) {
|
|
36
|
+
case FlowErrorCode.INVALID_NAME:
|
|
37
|
+
return 'invalid flow name — use a single safe segment (letters/digits/-/_), no path separators';
|
|
38
|
+
case FlowErrorCode.NOT_FOUND:
|
|
39
|
+
return 'no such flow on disk — run reticle_flow{action:"list"} to see saved flows';
|
|
40
|
+
case FlowErrorCode.PARSE_FAILED:
|
|
41
|
+
return FlowParseNote.MALFORMED;
|
|
42
|
+
case FlowErrorCode.NO_RECORDING:
|
|
43
|
+
return 'no compiled recording by that name — record one (reticle_record{action:"start"|"stop"}) first';
|
|
44
|
+
// Never "regenerate it": the file is intact and the reader is the wrong one. Telling somebody
|
|
45
|
+
// to rewrite an undamaged flow is the failure this code was split out of PARSE_FAILED to stop.
|
|
46
|
+
case FlowErrorCode.WRONG_VERSION:
|
|
47
|
+
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';
|
|
48
|
+
case FlowErrorCode.STEP_NOT_FOUND:
|
|
49
|
+
return (detail ?? 'the step to resume at is not in this flow — pass a 0-based index or a step id');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -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
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* point: the action window must open and close identically no matter which anchor found the element,
|
|
7
7
|
* or a step's events land in the wrong window depending on how it was addressed.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { expectedElementTestid } from '@reticlehq/core';
|
|
10
|
+
import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, PredicateKind, QueryBy, ReticleCommand, } from '@reticlehq/core';
|
|
10
11
|
import { ReticleTool } from '@reticlehq/core';
|
|
11
12
|
import { replayActionArgs } from './replay.js';
|
|
12
13
|
import { isStaleRefError } from '../../surface/tools/act/act-sequence-retry.js';
|
|
@@ -102,7 +103,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
102
103
|
* to recognise the raw refusal still finds it, plus the fix for this specific caller.
|
|
103
104
|
*/
|
|
104
105
|
const DESTRUCTIVE_ACTION_PATTERN = /potentially destructive (?:\w+ )*(?:action|tool) blocked/i;
|
|
105
|
-
|
|
106
|
+
function replayDestructiveActionHint(rawError) {
|
|
106
107
|
if (!DESTRUCTIVE_ACTION_PATTERN.test(rawError))
|
|
107
108
|
return rawError;
|
|
108
109
|
return (`${rawError} — this is a flow REPLAY: a step's own args.confirmDangerous is stripped before ` +
|
|
@@ -118,7 +119,7 @@ export function replayDestructiveActionHint(rawError) {
|
|
|
118
119
|
* Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
|
|
119
120
|
* including the action window, whose open/close must not depend on which anchor found the element.
|
|
120
121
|
*/
|
|
121
|
-
async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
|
|
122
|
+
export async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
|
|
122
123
|
/**
|
|
123
124
|
* The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
|
|
124
125
|
* redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
|
|
@@ -252,6 +253,21 @@ export function anchorQueryArgs(anchor) {
|
|
|
252
253
|
return roleQueryArgs(anchor);
|
|
253
254
|
return null;
|
|
254
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* The anchor as a PRECONDITION a flow file can store: "this element is on the page".
|
|
258
|
+
*
|
|
259
|
+
* What `requires` means for a flow that starts from state a page load discards, in the shape the
|
|
260
|
+
* flow's own `requires` holds. No tool writes `requires`, so the reset's hint hands over this line.
|
|
261
|
+
*/
|
|
262
|
+
export function anchorPrecondition(anchor) {
|
|
263
|
+
if (anchor.kind === AnchorKind.TESTID) {
|
|
264
|
+
return { kind: PredicateKind.ELEMENT, query: { testid: anchor.value } };
|
|
265
|
+
}
|
|
266
|
+
if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor)) {
|
|
267
|
+
return { kind: PredicateKind.ELEMENT, query: { role: anchor.role, name: anchor.name } };
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
255
271
|
/**
|
|
256
272
|
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
257
273
|
* ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
|
|
@@ -321,7 +337,7 @@ dynamic = new Set()) {
|
|
|
321
337
|
};
|
|
322
338
|
/** The sub-step's own expectation, unless its testid is deliberately unasserted. */
|
|
323
339
|
const declaredBy = (sub) => {
|
|
324
|
-
const testid = sub.expect
|
|
340
|
+
const testid = expectedElementTestid(sub.expect);
|
|
325
341
|
return testid === undefined || dynamic.has(testid) ? undefined : testid;
|
|
326
342
|
};
|
|
327
343
|
/**
|