@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
|
@@ -12,21 +12,36 @@
|
|
|
12
12
|
* tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
|
|
13
13
|
* Pure: no IO, no clock.
|
|
14
14
|
*/
|
|
15
|
-
import { type FlowExpect, type FlowFile } from '@reticlehq/core';
|
|
16
|
-
import type { EvalResult
|
|
15
|
+
import { type FlowExpect, type FlowFile, type Predicate } from '@reticlehq/core';
|
|
16
|
+
import type { EvalResult } from '@reticlehq/engine/question/predicate/predicate.js';
|
|
17
17
|
import type { FlowReplaySession, WaitForSignal } from './flow-replay-types.js';
|
|
18
18
|
/** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
|
|
19
19
|
export declare function dynamicTestids(flow: FlowFile): Set<string>;
|
|
20
20
|
/** The synthetic step.tool a replay appends when it asserts the flow's success oracle. */
|
|
21
21
|
export declare const SUCCESS_STEP_TOOL = "success";
|
|
22
22
|
/** A short human label for the success end-condition, for result rows. */
|
|
23
|
-
export declare function successLabel(success:
|
|
24
|
-
/**
|
|
25
|
-
|
|
23
|
+
export declare function successLabel(success: Predicate | undefined): string;
|
|
24
|
+
/**
|
|
25
|
+
* What a flow's success oracle actually asserts, once the file has been read and the dynamic
|
|
26
|
+
* (LLM-output) testids have been taken out of it.
|
|
27
|
+
*
|
|
28
|
+
* Most of what this function used to do is gone, and that is the migration working as intended: it
|
|
29
|
+
* compiled a flat `FlowExpect` into a `Predicate`, and a step's `expect` IS a predicate now, lifted
|
|
30
|
+
* by the schema when the file is read. It keeps the name and the signature because it is on
|
|
31
|
+
* `@reticlehq/test`'s published surface, and it still accepts the flat shape for the same reason a
|
|
32
|
+
* v1 file still loads — a spec written against the old spelling is not wrong, it is old.
|
|
33
|
+
*
|
|
34
|
+
* What remains is the DYNAMIC SKIP, and it is not cosmetic. A testid whose content a model writes is
|
|
35
|
+
* asserted for PRESENCE by the step layer and never for content; a success oracle bound to one would
|
|
36
|
+
* assert exactly what the flow has already declared unassertable. Dropping every such clause can
|
|
37
|
+
* leave nothing at all, and `undefined` is the honest answer — vacuously met, the same as declaring
|
|
38
|
+
* no success, because nothing here was checkable.
|
|
39
|
+
*/
|
|
40
|
+
export declare function successToPredicate(success: Predicate | FlowExpect | undefined, dynamic: ReadonlySet<string>): Predicate | undefined;
|
|
26
41
|
/**
|
|
27
42
|
* Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
|
|
28
43
|
* real waitForPredicate in production, a fake in unit tests). Passes when: no success was declared,
|
|
29
44
|
* OR every success field was dynamic-skipped (vacuously met), OR the compiled predicate held within
|
|
30
45
|
* the injected timeout. Never reads the wall clock.
|
|
31
46
|
*/
|
|
32
|
-
export declare function assertSuccess(session: FlowReplaySession, success:
|
|
47
|
+
export declare function assertSuccess(session: FlowReplaySession, success: Predicate | undefined, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since?: number): Promise<EvalResult>;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
|
|
13
13
|
* Pure: no IO, no clock.
|
|
14
14
|
*/
|
|
15
|
-
import { AnchorKind, PredicateKind } from '@reticlehq/core';
|
|
15
|
+
import { AnchorKind, PredicateKind, clauseOfKind, flowExpectToPredicate, withoutClauses, } from '@reticlehq/core';
|
|
16
16
|
/** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
|
|
17
17
|
export function dynamicTestids(flow) {
|
|
18
18
|
return new Set((flow.dynamic ?? [])
|
|
@@ -23,145 +23,59 @@ export function dynamicTestids(flow) {
|
|
|
23
23
|
export const SUCCESS_STEP_TOOL = 'success';
|
|
24
24
|
/** A short human label for the success end-condition, for result rows. */
|
|
25
25
|
export function successLabel(success) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
/*
|
|
27
|
+
* NOT `expectLabel`, and the difference is the reader.
|
|
28
|
+
*
|
|
29
|
+
* That one names a clause for a drift row, where `signal:` tells you which channel failed. This
|
|
30
|
+
* one is `mustHold` in the domain model an agent reads BEFORE testing — a list of what each flow
|
|
31
|
+
* guarantees — and there the prefix is noise on the commonest case. A signal's own name is the
|
|
32
|
+
* sentence: "order:placed" is what must hold.
|
|
33
|
+
*/
|
|
34
|
+
const signal = clauseOfKind(success, PredicateKind.SIGNAL);
|
|
35
|
+
if (signal?.name !== undefined)
|
|
36
|
+
return signal.name;
|
|
37
|
+
const net = clauseOfKind(success, PredicateKind.NET);
|
|
38
|
+
if (net !== undefined)
|
|
39
|
+
return net.urlContains ?? net.method ?? 'net';
|
|
40
|
+
const console_ = clauseOfKind(success, PredicateKind.CONSOLE);
|
|
41
|
+
if (console_ !== undefined) {
|
|
42
|
+
return `console:${true === console_.absent ? 'clean' : (console_.level ?? 'error')}`;
|
|
32
43
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
const state = clauseOfKind(success, PredicateKind.STATE);
|
|
45
|
+
if (state !== undefined)
|
|
46
|
+
return `state:${state.path}`;
|
|
47
|
+
const text = clauseOfKind(success, PredicateKind.TEXT);
|
|
48
|
+
if (text?.contains !== undefined)
|
|
49
|
+
return `text:${text.contains}`;
|
|
50
|
+
const element = clauseOfKind(success, PredicateKind.ELEMENT);
|
|
51
|
+
const q = element?.query;
|
|
52
|
+
return q?.testid ?? q?.name ?? q?.role ?? 'success';
|
|
38
53
|
}
|
|
39
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* What a flow's success oracle actually asserts, once the file has been read and the dynamic
|
|
56
|
+
* (LLM-output) testids have been taken out of it.
|
|
57
|
+
*
|
|
58
|
+
* Most of what this function used to do is gone, and that is the migration working as intended: it
|
|
59
|
+
* compiled a flat `FlowExpect` into a `Predicate`, and a step's `expect` IS a predicate now, lifted
|
|
60
|
+
* by the schema when the file is read. It keeps the name and the signature because it is on
|
|
61
|
+
* `@reticlehq/test`'s published surface, and it still accepts the flat shape for the same reason a
|
|
62
|
+
* v1 file still loads — a spec written against the old spelling is not wrong, it is old.
|
|
63
|
+
*
|
|
64
|
+
* What remains is the DYNAMIC SKIP, and it is not cosmetic. A testid whose content a model writes is
|
|
65
|
+
* asserted for PRESENCE by the step layer and never for content; a success oracle bound to one would
|
|
66
|
+
* assert exactly what the flow has already declared unassertable. Dropping every such clause can
|
|
67
|
+
* leave nothing at all, and `undefined` is the honest answer — vacuously met, the same as declaring
|
|
68
|
+
* no success, because nothing here was checkable.
|
|
69
|
+
*/
|
|
40
70
|
export function successToPredicate(success, dynamic) {
|
|
41
|
-
|
|
42
|
-
if (success.signal !== undefined) {
|
|
43
|
-
const signal = {
|
|
44
|
-
kind: PredicateKind.SIGNAL,
|
|
45
|
-
name: success.signal,
|
|
46
|
-
};
|
|
47
|
-
if (success.signalData !== undefined)
|
|
48
|
-
signal.dataMatches = success.signalData;
|
|
49
|
-
if (success.signalCount !== undefined) {
|
|
50
|
-
signal.count = success.signalCount;
|
|
51
|
-
// Same post-settle reasoning as net.count: the success waiter is wait-until-true, so an exact
|
|
52
|
-
// count is transiently satisfied the instant the FIRST matching signal fires — before a
|
|
53
|
-
// double-fire's duplicate arrives. Gating on `settled` forces the count to be read only after
|
|
54
|
-
// the app goes quiet, by which point the duplicate IS counted and the over-count fails.
|
|
55
|
-
parts.push({ kind: PredicateKind.SETTLED });
|
|
56
|
-
}
|
|
57
|
-
parts.push(signal);
|
|
58
|
-
}
|
|
59
|
-
if (success.route !== undefined) {
|
|
60
|
-
// The mirror of `predicateToExpect`'s ROUTE case. Replay evaluates predicates, so carrying the
|
|
61
|
-
// route back here is the whole of what makes a recorded navigation able to fail.
|
|
62
|
-
const route = {
|
|
63
|
-
kind: PredicateKind.ROUTE,
|
|
64
|
-
};
|
|
65
|
-
if (success.route.pathname !== undefined)
|
|
66
|
-
route.pathname = success.route.pathname;
|
|
67
|
-
if (success.route.contains !== undefined)
|
|
68
|
-
route.contains = success.route.contains;
|
|
69
|
-
return route;
|
|
70
|
-
}
|
|
71
|
-
if (success.net !== undefined) {
|
|
72
|
-
const net = { kind: PredicateKind.NET };
|
|
73
|
-
if (success.net.method !== undefined)
|
|
74
|
-
net.method = success.net.method;
|
|
75
|
-
if (success.net.urlContains !== undefined)
|
|
76
|
-
net.urlContains = success.net.urlContains;
|
|
77
|
-
if (success.net.status !== undefined)
|
|
78
|
-
net.status = success.net.status;
|
|
79
|
-
if (success.net.count !== undefined) {
|
|
80
|
-
net.count = success.net.count;
|
|
81
|
-
// A cardinality assertion is inherently POST-SETTLE. The success waiter is wait-until-true, so an
|
|
82
|
-
// exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
|
|
83
|
-
// before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
|
|
84
|
-
// only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
|
|
85
|
-
parts.push({ kind: PredicateKind.SETTLED });
|
|
86
|
-
}
|
|
87
|
-
parts.push(net);
|
|
88
|
-
}
|
|
89
|
-
if (success.console !== undefined) {
|
|
90
|
-
const con = {
|
|
91
|
-
kind: PredicateKind.CONSOLE,
|
|
92
|
-
};
|
|
93
|
-
if (success.console.level !== undefined)
|
|
94
|
-
con.level = success.console.level;
|
|
95
|
-
if (success.console.absent !== undefined) {
|
|
96
|
-
con.absent = success.console.absent;
|
|
97
|
-
// An `absent` assertion is post-settle, same as net.count: a wait-until-true waiter sees "no
|
|
98
|
-
// error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
|
|
99
|
-
// the console is read only after the page quiets, by which point any error is in the buffer.
|
|
100
|
-
if (success.console.absent)
|
|
101
|
-
parts.push({ kind: PredicateKind.SETTLED });
|
|
102
|
-
}
|
|
103
|
-
parts.push(con);
|
|
104
|
-
}
|
|
105
|
-
const state = success.state;
|
|
106
|
-
if (state !== undefined) {
|
|
107
|
-
const part = {
|
|
108
|
-
kind: PredicateKind.STATE,
|
|
109
|
-
path: state.path,
|
|
110
|
-
};
|
|
111
|
-
if (state.store !== undefined)
|
|
112
|
-
part.store = state.store;
|
|
113
|
-
if (state.equals !== undefined)
|
|
114
|
-
part.equals = state.equals;
|
|
115
|
-
// `hold` = an INVARIANT ("this state must still hold after the action settles"), vs the default
|
|
116
|
-
// wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
|
|
117
|
-
// value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
|
|
118
|
-
// on `settled` so the read happens after the page quiets, by which point the leak has landed.
|
|
119
|
-
if (true === state.hold)
|
|
120
|
-
parts.push({ kind: PredicateKind.SETTLED });
|
|
121
|
-
parts.push(part);
|
|
122
|
-
}
|
|
123
|
-
const element = success.element;
|
|
124
|
-
if (element !== undefined) {
|
|
125
|
-
const testid = element.testid;
|
|
126
|
-
// A dynamic-marked testid is NOT asserted as a success condition (presence-only).
|
|
127
|
-
if (testid === undefined || !dynamic.has(testid)) {
|
|
128
|
-
const query = {};
|
|
129
|
-
if (testid !== undefined)
|
|
130
|
-
query['testid'] = testid;
|
|
131
|
-
if (element.role !== undefined)
|
|
132
|
-
query['role'] = element.role;
|
|
133
|
-
if (element.name !== undefined)
|
|
134
|
-
query['name'] = element.name;
|
|
135
|
-
if (Object.keys(query).length > 0)
|
|
136
|
-
parts.push({ kind: PredicateKind.ELEMENT, query });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const text = success.text;
|
|
140
|
-
if (text !== undefined) {
|
|
141
|
-
const part = {
|
|
142
|
-
kind: PredicateKind.TEXT,
|
|
143
|
-
contains: text.contains,
|
|
144
|
-
};
|
|
145
|
-
if (text.scope !== undefined)
|
|
146
|
-
part.scope = text.scope;
|
|
147
|
-
if (text.visible !== undefined)
|
|
148
|
-
part.visible = text.visible;
|
|
149
|
-
if (true === text.absent) {
|
|
150
|
-
part.absent = true;
|
|
151
|
-
// Same post-settle reasoning as `console.absent` and `state.hold`: a wait-until-true waiter
|
|
152
|
-
// reads "not there yet" on the first poll and passes BEFORE the text it is meant to see
|
|
153
|
-
// disappear has even been rendered. Gate on `settled` so the read happens after the page
|
|
154
|
-
// quiets, by which point a text that was going to appear has.
|
|
155
|
-
parts.push({ kind: PredicateKind.SETTLED });
|
|
156
|
-
}
|
|
157
|
-
parts.push(part);
|
|
158
|
-
}
|
|
159
|
-
const [first] = parts;
|
|
160
|
-
if (0 === parts.length)
|
|
71
|
+
if (success === undefined)
|
|
161
72
|
return undefined;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
73
|
+
const predicate = 'kind' in success ? success : flowExpectToPredicate(success);
|
|
74
|
+
if (predicate === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
return withoutClauses(predicate, (clause) => PredicateKind.ELEMENT === clause.kind &&
|
|
77
|
+
clause.query.testid !== undefined &&
|
|
78
|
+
dynamic.has(clause.query.testid));
|
|
165
79
|
}
|
|
166
80
|
/**
|
|
167
81
|
* Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
|
|
@@ -170,8 +84,6 @@ export function successToPredicate(success, dynamic) {
|
|
|
170
84
|
* the injected timeout. Never reads the wall clock.
|
|
171
85
|
*/
|
|
172
86
|
export async function assertSuccess(session, success, dynamic, waitForSignal, timeoutMs, since = 0) {
|
|
173
|
-
if (success === undefined)
|
|
174
|
-
return { pass: true };
|
|
175
87
|
const predicate = successToPredicate(success, dynamic);
|
|
176
88
|
if (predicate === undefined)
|
|
177
89
|
return { pass: true };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { flowAuthor } from './flow-author.js';
|
|
1
2
|
import { FLOW_MUTATE_TOOL } from './flow-mutate-tools.js';
|
|
2
3
|
import { seedSchema } from '../../surface/tools/args/numeric-bounds.js';
|
|
3
4
|
import { z } from 'zod';
|
|
@@ -217,7 +218,12 @@ export const FLOW_TOOLS = [
|
|
|
217
218
|
if (emptyRefusal !== undefined)
|
|
218
219
|
return Promise.resolve(emptyRefusal);
|
|
219
220
|
const { flows, root } = flowsForSession(deps, projectId);
|
|
220
|
-
const
|
|
221
|
+
const author = flowAuthor();
|
|
222
|
+
const toSave = {
|
|
223
|
+
...program,
|
|
224
|
+
...(saveAs === undefined ? {} : { name: saveAs }),
|
|
225
|
+
...(author === undefined ? {} : { author }),
|
|
226
|
+
};
|
|
221
227
|
return flows.save(toSave, annotations, projectId).then(async (res) => {
|
|
222
228
|
if (!res.ok)
|
|
223
229
|
return { error: flowErrorMessage(res.code, res.detail), code: res.code };
|
|
@@ -382,6 +388,10 @@ export const FLOW_TOOLS = [
|
|
|
382
388
|
.boolean()
|
|
383
389
|
.optional()
|
|
384
390
|
.describe('Set true to allow destructive controls during this replay only.'),
|
|
391
|
+
from: z
|
|
392
|
+
.union([z.number().int().nonnegative(), z.string().min(1)])
|
|
393
|
+
.optional()
|
|
394
|
+
.describe('Resume at this step (index or step id). Earlier steps re-run as unchecked, unreported setup; a setup step marked effect:"commits" refuses the resume.'),
|
|
385
395
|
sweep: z
|
|
386
396
|
.boolean()
|
|
387
397
|
.optional()
|
|
@@ -636,9 +646,7 @@ export const FLOW_TOOLS = [
|
|
|
636
646
|
return flow === undefined ? { replay } : { replay, flow };
|
|
637
647
|
}));
|
|
638
648
|
const timed = outcomes.map((o, i) => ({
|
|
639
|
-
replay:
|
|
640
|
-
? o.value.replay
|
|
641
|
-
: leaseFailureReplay(requested[i] ?? '', o.error),
|
|
649
|
+
...(parallelRuns[i] ?? { replay: leaseFailureReplay(requested[i] ?? '', o.error) }),
|
|
642
650
|
durationMs: o.ok && o.value !== undefined ? o.value.durationMs : 0,
|
|
643
651
|
}));
|
|
644
652
|
const flaky = await recordSuiteFlakes(deps.fs, rootForProjectId(deps, projectId), parallelRuns);
|
|
@@ -671,7 +679,11 @@ export const FLOW_TOOLS = [
|
|
|
671
679
|
.catch(() => null);
|
|
672
680
|
const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
|
|
673
681
|
runs.push(flow === undefined ? { replay } : { replay, flow });
|
|
674
|
-
timed.push({
|
|
682
|
+
timed.push({
|
|
683
|
+
replay,
|
|
684
|
+
durationMs: deps.now() - start,
|
|
685
|
+
...(flow === undefined ? {} : { flow }),
|
|
686
|
+
});
|
|
675
687
|
}
|
|
676
688
|
// The flake ledger the CLI gate already keeps — see FlakeStore. It was only ever written by
|
|
677
689
|
// `reticle flow` on the command line, so an AGENT running this tool a hundred times learned
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type ProjectId } from '@reticlehq/core';
|
|
2
|
-
export { REDACTED_FILL } from './fields/flow-secret-field.js';
|
|
1
|
+
import { type Predicate, type ProjectId } from '@reticlehq/core';
|
|
2
|
+
export { REDACTED_FILL, secretEnvKey } from './fields/flow-secret-field.js';
|
|
3
3
|
import { type FlowResult } from './flow-result.js';
|
|
4
4
|
export type { FlowResult } from './flow-result.js';
|
|
5
|
-
import type { FlowAnchor,
|
|
5
|
+
import type { FlowAnchor, FlowFile, FlowStep, HealChange, InstrumentationGap } from '@reticlehq/core';
|
|
6
6
|
import type { CompiledProgram, RecordedStep } from './recording/tape/recordings.js';
|
|
7
7
|
import type { FileSystemPort } from '../../memory/project/fs/fs-port.js';
|
|
8
8
|
import type { Clock } from '../../machine/clock.js';
|
|
@@ -49,9 +49,9 @@ interface SaveSummary {
|
|
|
49
49
|
* the same bytes as before.
|
|
50
50
|
*/
|
|
51
51
|
export interface FlowAnnotations {
|
|
52
|
-
stepExpect: Map<number,
|
|
52
|
+
stepExpect: Map<number, Predicate>;
|
|
53
53
|
dynamic: string[];
|
|
54
|
-
success?:
|
|
54
|
+
success?: Predicate;
|
|
55
55
|
/** The flow's declared business goal (intent annotation). */
|
|
56
56
|
intent?: string;
|
|
57
57
|
}
|
|
@@ -65,6 +65,15 @@ export declare class FlowStore {
|
|
|
65
65
|
* the flow before writing. Omitting `annotations` reproduces the same bytes.
|
|
66
66
|
*/
|
|
67
67
|
save(program: CompiledProgram, annotations?: FlowAnnotations, projectId?: ProjectId): Promise<FlowResult<SaveSummary>>;
|
|
68
|
+
/**
|
|
69
|
+
* Save a flow the session drove, folded into any saved copy of the same journey.
|
|
70
|
+
*
|
|
71
|
+
* Every session saves its own drive, so one journey arrived as a near-copy per session. Flows
|
|
72
|
+
* under `prefix` (the auto-saved ones — a hand-named flow is never merged or removed) that start
|
|
73
|
+
* on the same page and take the same steps become ONE file: the oldest keeps its name, the others
|
|
74
|
+
* fill its gaps and are removed. The same scan collapses copies saved before this existed.
|
|
75
|
+
*/
|
|
76
|
+
saveJourney(program: CompiledProgram, projectId: ProjectId | undefined, prefix: string): Promise<FlowResult<SaveSummary>>;
|
|
68
77
|
/**
|
|
69
78
|
* Persist an already-anchored FlowFile captured in-page (no recompilation). The
|
|
70
79
|
* browser resolved every semantic anchor at capture time; here we only validate the name +
|
|
@@ -75,6 +84,10 @@ export declare class FlowStore {
|
|
|
75
84
|
recordLearned(name: string, learned: NonNullable<FlowFile['learned']>, projectId?: ProjectId): Promise<FlowResult<{
|
|
76
85
|
name: string;
|
|
77
86
|
}>>;
|
|
87
|
+
/** Give sourceless steps the files a clean replay resolved them to, merged onto the file as it is now. */
|
|
88
|
+
recordSources(name: string, sources: ReadonlyMap<string, NonNullable<FlowStep['source']>>, projectId?: ProjectId): Promise<FlowResult<{
|
|
89
|
+
name: string;
|
|
90
|
+
}>>;
|
|
78
91
|
/**
|
|
79
92
|
* Apply confident testid rebinds to an on-disk flow (the `reticle_flow_heal` apply path),
|
|
80
93
|
* rewriting ONLY the named steps' anchors and preserving every other field. Loading, the name
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { asFlowName, asProjectId } from '@reticlehq/core';
|
|
1
|
+
import { asFlowName, asProjectId, } from '@reticlehq/core';
|
|
2
2
|
import { REDACTED_FILL } from './fields/flow-secret-field.js';
|
|
3
|
-
export { REDACTED_FILL } from './fields/flow-secret-field.js';
|
|
3
|
+
export { REDACTED_FILL, secretEnvKey } from './fields/flow-secret-field.js';
|
|
4
4
|
import { safeProjectId } from './flow-result.js';
|
|
5
5
|
import { changeInPlace } from './narrow-write.js';
|
|
6
|
-
import { AnchorKind, DEGRADED_ANCHOR_ROLE, FLOW_FILE_VERSION, FlowErrorCode, FlowFileSchema, FlowStepTool, QueryBy, defaultIsSensitiveKey, } from '@reticlehq/core';
|
|
6
|
+
import { AnchorKind, DEGRADED_ANCHOR_ROLE, FLOW_FILE_VERSION, flowFileVersionFor, FlowErrorCode, FlowFileSchema, FlowStepTool, QueryBy, defaultIsSensitiveKey, } from '@reticlehq/core';
|
|
7
7
|
import { ReticleTool } from '@reticlehq/core';
|
|
8
8
|
import { asRecord, asString } from '@reticlehq/core';
|
|
9
9
|
import { applyHealChanges } from './heal.js';
|
|
10
|
+
import { withLearnedSources } from './learned-sources.js';
|
|
10
11
|
import { flowIntentGap, linkFlowIntent } from './flow-intent.js';
|
|
12
|
+
import { journeyFingerprint, journeyName, mergeJourney, routeClaims } from './flow-journey.js';
|
|
11
13
|
import { IntentStore } from '../../memory/intent/intent-store.js';
|
|
12
14
|
import { flowDir, flowParentDir, flowPath, reticleDirPaths, isValidFlowName, } from '../../memory/project/dir/reticle-dir.js';
|
|
13
15
|
import { describeFlowZodFailure, parseFlowFileText } from './flow-expect-grammar.js';
|
|
@@ -163,6 +165,14 @@ function buildStep(tool, anchor, action, args, degraded) {
|
|
|
163
165
|
* NEVER silently dropped. ACT_SEQUENCE recurses over its sub-steps.
|
|
164
166
|
*/
|
|
165
167
|
export function recordedStepToFlowStep(step) {
|
|
168
|
+
const out = anchoredStep(step);
|
|
169
|
+
if (step.page !== undefined)
|
|
170
|
+
out.page = step.page;
|
|
171
|
+
if (step.endPage !== undefined && step.endPage !== step.page)
|
|
172
|
+
out.endPage = step.endPage;
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
function anchoredStep(step) {
|
|
166
176
|
if (step.invoke !== undefined) {
|
|
167
177
|
// An invocation drives nothing: no action, no args, and an anchor only because every step
|
|
168
178
|
// carries one. Falling through to the action path below would give it an `action` and a
|
|
@@ -236,7 +246,8 @@ export class FlowStore {
|
|
|
236
246
|
* of them produces byte-identical on-disk content (locked by the byte-stability tests).
|
|
237
247
|
*/
|
|
238
248
|
#serialize(flow) {
|
|
239
|
-
|
|
249
|
+
const stamped = { ...flow, version: flowFileVersionFor(flow) };
|
|
250
|
+
return `${JSON.stringify(stamped, null, JSON_INDENT)}\n`;
|
|
240
251
|
}
|
|
241
252
|
/**
|
|
242
253
|
* Register the flow's business goal in the intent ledger and stamp the row's id onto the flow.
|
|
@@ -266,16 +277,8 @@ export class FlowStore {
|
|
|
266
277
|
...(gap === undefined ? {} : { intentGap: gap }),
|
|
267
278
|
};
|
|
268
279
|
}
|
|
269
|
-
/**
|
|
270
|
-
|
|
271
|
-
* Optionally fold structured annotations (per-step expect, dynamic[], success) onto
|
|
272
|
-
* the flow before writing. Omitting `annotations` reproduces the same bytes.
|
|
273
|
-
*/
|
|
274
|
-
async save(program, annotations, projectId) {
|
|
275
|
-
if (!isValidFlowName(program.name)) {
|
|
276
|
-
return { ok: false, code: FlowErrorCode.INVALID_NAME };
|
|
277
|
-
}
|
|
278
|
-
const pid = safeProjectId(projectId);
|
|
280
|
+
/** The on-disk flow a compiled recording becomes, before anything is written. */
|
|
281
|
+
#build(program, annotations, pid) {
|
|
279
282
|
const steps = program.steps.map(recordedStepToFlowStep);
|
|
280
283
|
const base = {
|
|
281
284
|
version: FLOW_FILE_VERSION,
|
|
@@ -294,11 +297,85 @@ export class FlowStore {
|
|
|
294
297
|
*/
|
|
295
298
|
...(program.startPath === undefined ? {} : { startPath: program.startPath }),
|
|
296
299
|
};
|
|
297
|
-
const
|
|
300
|
+
const intent = program.steps.find((step) => step.intent !== undefined)?.intent;
|
|
301
|
+
const annotated = withAnnotations(base, annotations);
|
|
302
|
+
return {
|
|
303
|
+
...annotated,
|
|
304
|
+
// The action's own intent, when nobody annotated the flow — see RecordedStep.intent.
|
|
305
|
+
...(annotated.intent === undefined && intent !== undefined ? { intent } : {}),
|
|
306
|
+
...routeClaims(annotated.startPath, annotated.steps),
|
|
307
|
+
...(program.author === undefined ? {} : { author: program.author }),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Convert a CompiledProgram (testid-normalized) into an anchored, on-disk flow + write it.
|
|
312
|
+
* Optionally fold structured annotations (per-step expect, dynamic[], success) onto
|
|
313
|
+
* the flow before writing. Omitting `annotations` reproduces the same bytes.
|
|
314
|
+
*/
|
|
315
|
+
async save(program, annotations, projectId) {
|
|
316
|
+
if (!isValidFlowName(program.name)) {
|
|
317
|
+
return { ok: false, code: FlowErrorCode.INVALID_NAME };
|
|
318
|
+
}
|
|
319
|
+
const pid = safeProjectId(projectId);
|
|
320
|
+
const base = this.#build(program, annotations, pid);
|
|
321
|
+
const flow = await this.#linkIntent(base);
|
|
298
322
|
await this.#fs.mkdir(flowParentDir(this.#root, program.name, pid));
|
|
299
323
|
await this.#fs.writeFile(flowPath(this.#root, program.name, pid), this.#serialize(flow));
|
|
300
324
|
return { ok: true, value: this.#summary(flow) };
|
|
301
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Save a flow the session drove, folded into any saved copy of the same journey.
|
|
328
|
+
*
|
|
329
|
+
* Every session saves its own drive, so one journey arrived as a near-copy per session. Flows
|
|
330
|
+
* under `prefix` (the auto-saved ones — a hand-named flow is never merged or removed) that start
|
|
331
|
+
* on the same page and take the same steps become ONE file: the oldest keeps its name, the others
|
|
332
|
+
* fill its gaps and are removed. The same scan collapses copies saved before this existed.
|
|
333
|
+
*/
|
|
334
|
+
async saveJourney(program, projectId, prefix) {
|
|
335
|
+
if (!isValidFlowName(program.name))
|
|
336
|
+
return { ok: false, code: FlowErrorCode.INVALID_NAME };
|
|
337
|
+
const pid = safeProjectId(projectId);
|
|
338
|
+
const fresh = this.#build(program, undefined, pid);
|
|
339
|
+
const saved = [];
|
|
340
|
+
for (const name of (await this.list(pid)).filter((n) => n.startsWith(prefix))) {
|
|
341
|
+
const loaded = await this.load(name, pid);
|
|
342
|
+
if (loaded.ok)
|
|
343
|
+
saved.push(loaded.value);
|
|
344
|
+
}
|
|
345
|
+
const byPrint = new Map();
|
|
346
|
+
for (const flow of saved) {
|
|
347
|
+
const print = journeyFingerprint(flow);
|
|
348
|
+
byPrint.set(print, [...(byPrint.get(print) ?? []), flow]);
|
|
349
|
+
}
|
|
350
|
+
const print = journeyFingerprint(fresh);
|
|
351
|
+
const taken = new Map(saved.map((flow) => [flow.name, journeyFingerprint(flow)]));
|
|
352
|
+
const named = { ...fresh, name: journeyName(fresh.name, print, taken) };
|
|
353
|
+
byPrint.set(print, [...(byPrint.get(print) ?? []), named]);
|
|
354
|
+
let result = named;
|
|
355
|
+
for (const [groupPrint, group] of byPrint) {
|
|
356
|
+
if (group.length < 2 && groupPrint !== print)
|
|
357
|
+
continue;
|
|
358
|
+
const [kept, ...rest] = [...group].sort((a, b) => a.createdAt - b.createdAt);
|
|
359
|
+
if (kept === undefined)
|
|
360
|
+
continue;
|
|
361
|
+
const merged = await this.#linkIntent(rest.reduce(mergeJourney, kept));
|
|
362
|
+
await this.#writeInPlace(merged, pid);
|
|
363
|
+
for (const dropped of rest) {
|
|
364
|
+
if (dropped.name !== merged.name && dropped !== named)
|
|
365
|
+
await this.remove(dropped.name, pid);
|
|
366
|
+
}
|
|
367
|
+
if (groupPrint === print)
|
|
368
|
+
result = merged;
|
|
369
|
+
}
|
|
370
|
+
return { ok: true, value: this.#summary(result) };
|
|
371
|
+
}
|
|
372
|
+
/** Write a flow where it already lives, or into its project's directory when it is new. */
|
|
373
|
+
async #writeInPlace(flow, pid) {
|
|
374
|
+
const name = asFlowName(flow.name);
|
|
375
|
+
const path = (await this.#resolveReadPath(name, pid)) ?? flowPath(this.#root, name, pid);
|
|
376
|
+
await this.#fs.mkdir(flowParentDir(this.#root, name, pid));
|
|
377
|
+
await this.#fs.writeFile(path, this.#serialize(flow));
|
|
378
|
+
}
|
|
302
379
|
/**
|
|
303
380
|
* Persist an already-anchored FlowFile captured in-page (no recompilation). The
|
|
304
381
|
* browser resolved every semantic anchor at capture time; here we only validate the name +
|
|
@@ -351,6 +428,13 @@ export class FlowStore {
|
|
|
351
428
|
value: { name },
|
|
352
429
|
}));
|
|
353
430
|
}
|
|
431
|
+
/** Give sourceless steps the files a clean replay resolved them to, merged onto the file as it is now. */
|
|
432
|
+
async recordSources(name, sources, projectId) {
|
|
433
|
+
return await this.#changeInPlace(name, projectId, (flow) => ({
|
|
434
|
+
next: withLearnedSources(flow, sources) ?? flow,
|
|
435
|
+
value: { name },
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
354
438
|
/**
|
|
355
439
|
* Apply confident testid rebinds to an on-disk flow (the `reticle_flow_heal` apply path),
|
|
356
440
|
* rewriting ONLY the named steps' anchors and preserving every other field. Loading, the name
|
|
@@ -362,7 +446,8 @@ export class FlowStore {
|
|
|
362
446
|
*/
|
|
363
447
|
async heal(name, changes, projectId) {
|
|
364
448
|
return await this.#changeInPlace(name, projectId, (flow) => {
|
|
365
|
-
|
|
449
|
+
// The store's injected clock, so a written flow records when the rebind actually happened.
|
|
450
|
+
const { flow: next, applied } = applyHealChanges(flow, changes, () => this.#clock.now());
|
|
366
451
|
return { next, value: { name, changed: applied } };
|
|
367
452
|
});
|
|
368
453
|
}
|
|
@@ -25,7 +25,15 @@ export declare function confidenceFor(from: string, to: string): number;
|
|
|
25
25
|
* skipped (idempotent / defensive), never throwing. Shared by the on-disk writer (FlowStore.heal)
|
|
26
26
|
* and the in-memory pre-write verification in reticle_flow_heal, so both rewrite identically.
|
|
27
27
|
*/
|
|
28
|
-
export declare function applyHealChanges(flow: FlowFile, changes: HealChange[]
|
|
28
|
+
export declare function applyHealChanges(flow: FlowFile, changes: HealChange[],
|
|
29
|
+
/**
|
|
30
|
+
* When the rebind happened. Injected rather than read here, because this function is pure and a
|
|
31
|
+
* clock inside it would make the flow it returns depend on when a test ran.
|
|
32
|
+
*
|
|
33
|
+
* Optional so every existing caller keeps working; without it the step still records WHICH anchor
|
|
34
|
+
* was replaced, which is the half a reader actually acts on.
|
|
35
|
+
*/
|
|
36
|
+
now?: () => number): {
|
|
29
37
|
flow: FlowFile;
|
|
30
38
|
applied: HealChange[];
|
|
31
39
|
};
|
|
@@ -38,7 +38,15 @@ export function confidenceFor(from, to) {
|
|
|
38
38
|
* skipped (idempotent / defensive), never throwing. Shared by the on-disk writer (FlowStore.heal)
|
|
39
39
|
* and the in-memory pre-write verification in reticle_flow_heal, so both rewrite identically.
|
|
40
40
|
*/
|
|
41
|
-
export function applyHealChanges(flow, changes
|
|
41
|
+
export function applyHealChanges(flow, changes,
|
|
42
|
+
/**
|
|
43
|
+
* When the rebind happened. Injected rather than read here, because this function is pure and a
|
|
44
|
+
* clock inside it would make the flow it returns depend on when a test ran.
|
|
45
|
+
*
|
|
46
|
+
* Optional so every existing caller keeps working; without it the step still records WHICH anchor
|
|
47
|
+
* was replaced, which is the half a reader actually acts on.
|
|
48
|
+
*/
|
|
49
|
+
now) {
|
|
42
50
|
const byStep = new Map();
|
|
43
51
|
for (const change of changes)
|
|
44
52
|
byStep.set(change.step, change);
|
|
@@ -51,7 +59,21 @@ export function applyHealChanges(flow, changes) {
|
|
|
51
59
|
return step;
|
|
52
60
|
}
|
|
53
61
|
applied.push(change);
|
|
54
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
...step,
|
|
64
|
+
anchor: { kind: AnchorKind.TESTID, value: change.to },
|
|
65
|
+
/*
|
|
66
|
+
* `from` is the ORIGINAL recorded anchor, kept across later heals.
|
|
67
|
+
*
|
|
68
|
+
* A step healed twice has an intermediate name nobody ever chose, so overwriting with the
|
|
69
|
+
* newest would throw away the only anchor a reader wants — the one that was recorded by
|
|
70
|
+
* driving. The timestamp is the LATEST heal, because that is when this anchor arrived.
|
|
71
|
+
*/
|
|
72
|
+
healed: {
|
|
73
|
+
from: step.healed?.from ?? change.from,
|
|
74
|
+
at: now?.() ?? step.healed?.at ?? 0,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
55
77
|
});
|
|
56
78
|
return { flow: { ...flow, steps }, applied };
|
|
57
79
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learn a flow step's source file from the element its anchor resolved to on a replay.
|
|
3
|
+
*
|
|
4
|
+
* A step recorded before the build stamped the app names no file, so a scoped re-verify cannot say
|
|
5
|
+
* which edits it covers. Every replay already asks the page for each anchor's element, and that
|
|
6
|
+
* element carries its stamp now: this records the answer at the one place every resolution passes
|
|
7
|
+
* through (the session's QUERY), rather than threading it through each step runner.
|
|
8
|
+
*/
|
|
9
|
+
import { type FlowFile, type FlowStep } from '@reticlehq/core';
|
|
10
|
+
import type { FlowReplaySession } from './flow-replay-types.js';
|
|
11
|
+
type StepSource = NonNullable<FlowStep['source']>;
|
|
12
|
+
/** `src/Pay.tsx:12:4` or `src/Pay.tsx:12` → `{file, line}`; anything else is not a location. */
|
|
13
|
+
export declare function parseCompactSource(value: string): StepSource | undefined;
|
|
14
|
+
/** Wrap a replay session so every anchor QUERY that resolves to one stamped element is remembered. */
|
|
15
|
+
export declare function recordingSources(inner: FlowReplaySession): {
|
|
16
|
+
session: FlowReplaySession;
|
|
17
|
+
sources: ReadonlyMap<string, StepSource>;
|
|
18
|
+
};
|
|
19
|
+
/** The flow with every sourceless step given the file its anchor resolved to; undefined if none was. */
|
|
20
|
+
export declare function withLearnedSources(flow: FlowFile, sources: ReadonlyMap<string, StepSource>): FlowFile | undefined;
|
|
21
|
+
export {};
|