@reticlehq/server 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +93 -111
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -14
- package/dist/bridge/bridge.js +248 -165
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +8 -7
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +39 -1
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +17 -1
- package/dist/cli/cli-parse.js +238 -12
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +2 -1
- package/dist/cli/cli-verify.js +5 -4
- package/dist/cli/cloud-cli.js +428 -253
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.d.ts +2 -1
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +12 -1
- package/dist/cli/doctor-rows.js +12 -1
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +174 -0
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +167 -186
- package/dist/cloud/cloud-config.d.ts +24 -2
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +16 -5
- package/dist/cloud/cloud-sync.js +64 -1
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +12 -13
- package/dist/crawl/crawl.js +25 -3
- package/dist/daemon/daemon-resolve.d.ts +122 -0
- package/dist/daemon/daemon-resolve.js +187 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/dev/stale-issue-guard.d.ts +0 -2
- package/dist/dev/stale-issue-guard.js +1 -1
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +59 -0
- package/dist/events/contradictions.js +253 -27
- package/dist/events/declared.d.ts +17 -0
- package/dist/events/declared.js +43 -1
- package/dist/events/echo-mismatch.js +65 -1
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +13 -0
- package/dist/events/predicate-asks.js +22 -0
- package/dist/events/predicate-eval.js +117 -27
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +43 -3
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate-schema.d.ts +44 -0
- package/dist/events/predicate-schema.js +126 -23
- package/dist/events/predicate.d.ts +20 -0
- package/dist/events/predicate.js +77 -6
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +6 -8
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +263 -23
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-success.js +15 -3
- package/dist/flows/flow-tools.js +87 -33
- package/dist/flows/flows.d.ts +14 -1
- package/dist/flows/flows.js +502 -24
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +36 -15
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -3
- package/dist/honesty/blind-spots.d.ts +51 -15
- package/dist/honesty/blind-spots.js +64 -4
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +39 -6
- package/dist/honesty/instrumentation-gaps.js +88 -5
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +16 -0
- package/dist/honesty/verified.js +84 -5
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +13 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +13 -4
- package/dist/impact/impact-store.js +44 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +30 -6
- package/dist/init/agent-rules.js +54 -13
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -3
- package/dist/init/capabilities.js +23 -3
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +27 -28
- package/dist/init/confirm.js +51 -11
- package/dist/init/cra.d.ts +34 -5
- package/dist/init/cra.js +96 -25
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +50 -0
- package/dist/init/dev-script.js +66 -0
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +17 -6
- package/dist/init/mcp.js +27 -11
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +147 -37
- package/dist/init/plan.d.ts +30 -37
- package/dist/init/plan.js +50 -60
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +32 -35
- package/dist/init/run.js +151 -202
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +90 -31
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.d.ts +2 -0
- package/dist/input/real-input.js +28 -7
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +37 -0
- package/dist/intent/inline-intent.js +193 -0
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +49 -0
- package/dist/intent/intent-store.js +133 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +143 -0
- package/dist/intent/open-intents.d.ts +42 -0
- package/dist/intent/open-intents.js +40 -0
- package/dist/journal/envelope.d.ts +26 -26
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +83 -0
- package/dist/mcp/attach-memory.js +185 -0
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -8
- package/dist/mcp/mcp-proxy.js +64 -62
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +52 -24
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +16 -0
- package/dist/mcp/tool-catalog-cache.js +22 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +75 -5
- package/dist/pool/browser-pool.js +144 -5
- package/dist/pool/playwright-launcher.js +12 -1
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.js +6 -1
- package/dist/project/project-tools.js +6 -35
- package/dist/project/reticle-dir.d.ts +17 -2
- package/dist/project/reticle-dir.js +22 -4
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +24 -3
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +33 -0
- package/dist/session/no-session-diagnosis.js +82 -24
- package/dist/session/no-session-next-action.d.ts +12 -0
- package/dist/session/no-session-next-action.js +77 -6
- package/dist/session/no-session-watch.d.ts +18 -6
- package/dist/session/no-session-watch.js +125 -75
- package/dist/session/output-budget.js +4 -1
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +111 -4
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.d.ts +37 -0
- package/dist/session/session-successor.js +113 -0
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +53 -3
- package/dist/session/session.js +162 -9
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +60 -0
- package/dist/setup/dev-server-wait.js +65 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +187 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/automation-hint.d.ts +2 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback-outbox.d.ts +2 -1
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -9
- package/dist/telemetry/project-profile.js +12 -7
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/telemetry/verification-of.js +2 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +196 -204
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +20 -3
- package/dist/tools/assert-verdict.js +58 -20
- package/dist/tools/browser-tools.js +6 -22
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +123 -69
- package/dist/tools/dynamic-tools.js +53 -13
- package/dist/tools/error-recovery.d.ts +25 -14
- package/dist/tools/error-recovery.js +78 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +121 -11
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +35 -5
- package/dist/tools/lease-tools.js +191 -13
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +109 -65
- package/dist/tools/read-tools.js +31 -8
- package/dist/tools/real-input-attempt.d.ts +32 -0
- package/dist/tools/real-input-attempt.js +212 -2
- package/dist/tools/reconcile-tools.js +22 -8
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +24 -0
- package/dist/tools/tool-kit.js +21 -0
- package/dist/tools/tool-names.d.ts +5 -0
- package/dist/tools/tool-names.js +5 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +78 -12
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +25 -10
- package/dist/update/update-nudge.js +32 -3
- package/dist/update/updater.d.ts +1 -1
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/README.md +2 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli/verify.mdx +4 -5
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +2 -2
- package/docs/gates.md +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +4 -3
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { InstrumentationGapKind, instrumentationGap, } from '@reticlehq/core';
|
|
2
|
+
import { IntentStore } from '../intent/intent-store.js';
|
|
3
|
+
import { classifyFlowAssertions } from './flow-classify.js';
|
|
4
|
+
/**
|
|
5
|
+
* The link between a saved flow and the intent ledger — what the flow is FOR.
|
|
6
|
+
*
|
|
7
|
+
* A replay used to report only in the language of the DOM: step 3 drifted, this assertion failed.
|
|
8
|
+
* True, actionable, and illegible — the reader still had to reconstruct what stopped working. The
|
|
9
|
+
* ledger in `.reticle/intent.json` already holds the other half (prose statements, declared → bound
|
|
10
|
+
* → proved, and the verdict that discharged each one), so a flow does not need its own notion of
|
|
11
|
+
* intent. It needs an id.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here derives an intent from step names. A flow that declares none reports that it declares
|
|
14
|
+
* none — a guessed intent puts words in the product owner's mouth and an agent will act on them,
|
|
15
|
+
* which is strictly worse than the honest absence.
|
|
16
|
+
*/
|
|
17
|
+
/** Namespaced so a flow-declared intent never collides with one an agent declared by hand. */
|
|
18
|
+
const FLOW_INTENT_ID_PREFIX = 'flow:';
|
|
19
|
+
const FLOW_VERDICT_ID_PREFIX = 'flow_replay:';
|
|
20
|
+
const ID_SEPARATOR = ':';
|
|
21
|
+
/** The ledger id a flow's own prose goal is declared under. */
|
|
22
|
+
export function flowIntentId(flowName) {
|
|
23
|
+
return `${FLOW_INTENT_ID_PREFIX}${flowName}`;
|
|
24
|
+
}
|
|
25
|
+
/** The verdict that a replay of this flow, at this time, constitutes — what `provenBy` records. */
|
|
26
|
+
export function flowReplayVerdictId(flowName, at) {
|
|
27
|
+
return `${FLOW_VERDICT_ID_PREFIX}${flowName}${ID_SEPARATOR}${String(at)}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Which ledger row this flow answers to, or nothing.
|
|
31
|
+
*
|
|
32
|
+
* An explicit `intentId` wins, so a flow can discharge an intent that was declared before it existed.
|
|
33
|
+
* Otherwise the flow's own prose implies one. A flow with neither has no intent, and says so.
|
|
34
|
+
*/
|
|
35
|
+
function intentIdOf(flow) {
|
|
36
|
+
if (flow.intentId !== undefined)
|
|
37
|
+
return flow.intentId;
|
|
38
|
+
// TRIMMED, not merely defined. `intent: " "` satisfied `!== undefined` and silenced the gap, so
|
|
39
|
+
// a flow whose stated goal is whitespace looked identical on disk to one somebody thought about.
|
|
40
|
+
//
|
|
41
|
+
// The worse direction of the two: this gap is the ONLY thing that would ever tell anyone the flow
|
|
42
|
+
// has no goal, so suppressing it wrongly means the flow replays for months and the day it goes red
|
|
43
|
+
// the report names the broken step and nothing else — the precise failure it was written to
|
|
44
|
+
// prevent. Found by probing the seam rather than reading it.
|
|
45
|
+
return undefined === flow.intent || '' === flow.intent.trim()
|
|
46
|
+
? undefined
|
|
47
|
+
: flowIntentId(flow.name);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Promote a flow's prose goal into the ledger and stamp the id back onto the flow.
|
|
51
|
+
*
|
|
52
|
+
* The binding is attached only when the flow asserts an observable consequence, because that is the
|
|
53
|
+
* only case where replaying it green proves anything. A flow that asserts nothing leaves its intent
|
|
54
|
+
* `declared` — and `dischargeIntent` refuses to prove an unbound intent, so an assertion-free flow
|
|
55
|
+
* can never discharge one. That is the whole guard, and it is the ledger's own rule, not a new one.
|
|
56
|
+
*
|
|
57
|
+
* Returns the flow unchanged (and writes nothing) when there is no intent, so a flow file saved
|
|
58
|
+
* before any of this existed keeps its exact bytes.
|
|
59
|
+
*/
|
|
60
|
+
export async function linkFlowIntent(store, flow) {
|
|
61
|
+
const id = intentIdOf(flow);
|
|
62
|
+
if (id === undefined)
|
|
63
|
+
return flow;
|
|
64
|
+
if (flow.intent !== undefined) {
|
|
65
|
+
await store.declare([{ id, statement: flow.intent, surface: { flow: flow.name } }]);
|
|
66
|
+
}
|
|
67
|
+
if (classifyFlowAssertions(flow).hasConsequenceAssertion) {
|
|
68
|
+
await store.bind(id, { flow: flow.name });
|
|
69
|
+
}
|
|
70
|
+
return { ...flow, intentId: id };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The nudge a flow saved without an intent earns, or nothing.
|
|
74
|
+
*
|
|
75
|
+
* Every save path routes through here, so the three of them cannot drift into three different ways
|
|
76
|
+
* of saying the same thing. It says it in the vocabulary the instrumentation gaps already use — what
|
|
77
|
+
* is missing, what it costs, and the one change that closes it — because "nobody said what this is
|
|
78
|
+
* for" is the same finding `undeclared-change` makes about an edit, one artifact later.
|
|
79
|
+
*
|
|
80
|
+
* Deliberately generic. It names no step, no assertion and not even the flow, for the reason this
|
|
81
|
+
* file already gives twice: anything specific enough to identify the goal is specific enough to be
|
|
82
|
+
* read AS the goal. It also never fails the save — a flow with no intent is still far better than no
|
|
83
|
+
* flow, and a verification tool that refuses to record work is one people route around.
|
|
84
|
+
*/
|
|
85
|
+
export function flowIntentGap(flow) {
|
|
86
|
+
if (intentIdOf(flow) !== undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
return instrumentationGap(InstrumentationGapKind.NO_FLOW_INTENT, 'this flow declares no intent, so nothing on disk says what it is meant to prove', 'it will replay for months, and the day it goes red the report can only name the step that broke — not the thing that stopped being true — so whoever reads it has to reconstruct the goal from the steps before they can tell a real regression from a moved button');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* What the ledger currently says this flow is for, or undefined when nothing declared it.
|
|
92
|
+
*
|
|
93
|
+
* Read from the ledger rather than from the flow file's own copy: the ledger is where an amendment
|
|
94
|
+
* lands, and a report that quotes the stale copy would be the two-vocabularies problem all over
|
|
95
|
+
* again — one sentence in review, a different one in the failure message.
|
|
96
|
+
*/
|
|
97
|
+
export async function flowIntentStatement(store, flow) {
|
|
98
|
+
const id = intentIdOf(flow);
|
|
99
|
+
if (id === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
const found = (await store.read()).find((intent) => intent.id === id);
|
|
102
|
+
return found?.statement ?? flow.intent;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Record that this replay proved the flow's intent. False when there was nothing to prove.
|
|
106
|
+
*
|
|
107
|
+
* Best-effort by construction: `IntentStore.discharge` returns false rather than throwing on an
|
|
108
|
+
* unknown or unbound id, because discharge runs off the back of a verdict and must never be the
|
|
109
|
+
* reason one fails to return.
|
|
110
|
+
*/
|
|
111
|
+
export async function dischargeFlowIntent(store, flow, proof) {
|
|
112
|
+
const id = intentIdOf(flow);
|
|
113
|
+
if (id === undefined)
|
|
114
|
+
return false;
|
|
115
|
+
try {
|
|
116
|
+
return await store.discharge(id, proof);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// A ledger that cannot be written is a small problem; a verdict that never returns because of
|
|
120
|
+
// one is a large problem. The replay already succeeded — the proof is simply not recorded.
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the team already knows about the flow being verified.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a replay should ask at all
|
|
5
|
+
*
|
|
6
|
+
* Shared memory is written on every verification and, measured across this project's whole corpus,
|
|
7
|
+
* read essentially never: every subject on the dashboard showed `fetches: 0` and `readers: []`. Not
|
|
8
|
+
* because the data is useless — because consulting it is a SEPARATE act an agent has to remember to
|
|
9
|
+
* perform. Knowledge that must be deliberately fetched is a wiki, and wikis are not read.
|
|
10
|
+
*
|
|
11
|
+
* So verification asks on the agent's behalf. Replaying `checkout` returns what the project knows
|
|
12
|
+
* about checkout beside the verdict, which is the difference between memory the platform stores and
|
|
13
|
+
* memory the platform USES — and it is the only way the read counts on the coverage map ever become
|
|
14
|
+
* a real signal rather than a number nobody moves.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this file is pure
|
|
17
|
+
*
|
|
18
|
+
* The fetch is the caller's. What a replay is ENTITLED to ask for — which subject, how much of it —
|
|
19
|
+
* is a rule worth testing without a network, and the rule is where the mistakes live.
|
|
20
|
+
*/
|
|
21
|
+
import type { FlowFile } from '@reticlehq/core';
|
|
22
|
+
/**
|
|
23
|
+
* How many records travel back with a verdict.
|
|
24
|
+
*
|
|
25
|
+
* Small on purpose. This rides on every replay, and a verdict that arrives with fifty statements
|
|
26
|
+
* attached is one whose actual result is now below the fold. The subject page is one call away for
|
|
27
|
+
* an agent that wants the rest.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MEMORY_CONSULT_LIMIT = 5;
|
|
30
|
+
/**
|
|
31
|
+
* The subject a replay should ask about.
|
|
32
|
+
*
|
|
33
|
+
* The FLOW NAME, because a flow is a feature and that is the axis the store shards on — replaying
|
|
34
|
+
* `checkout` should surface what the team knows about checkout, not about the route it happens to
|
|
35
|
+
* start on. A flow whose name says nothing gets nothing: asking for `unsorted` would return the
|
|
36
|
+
* project's junk drawer on every verdict, which is worse than silence because it looks like an
|
|
37
|
+
* answer.
|
|
38
|
+
*/
|
|
39
|
+
export declare function consultSubjectFor(flow: FlowFile): string | undefined;
|
|
40
|
+
/** One thing the project knows, as it travels back with a verdict. */
|
|
41
|
+
export interface ConsultedMemory {
|
|
42
|
+
statement: string;
|
|
43
|
+
status: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Trim what came back to what is worth carrying.
|
|
47
|
+
*
|
|
48
|
+
* PROVED records first: a statement something has actually verified is stronger evidence about the
|
|
49
|
+
* feature than one somebody proposed and never checked. Beyond that the order is the server's, and
|
|
50
|
+
* anything without a statement is dropped rather than rendered as an empty line.
|
|
51
|
+
*/
|
|
52
|
+
export declare function selectConsulted(entries: readonly {
|
|
53
|
+
statement?: unknown;
|
|
54
|
+
status?: unknown;
|
|
55
|
+
}[], limit?: number): ConsultedMemory[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many records travel back with a verdict.
|
|
3
|
+
*
|
|
4
|
+
* Small on purpose. This rides on every replay, and a verdict that arrives with fifty statements
|
|
5
|
+
* attached is one whose actual result is now below the fold. The subject page is one call away for
|
|
6
|
+
* an agent that wants the rest.
|
|
7
|
+
*/
|
|
8
|
+
export const MEMORY_CONSULT_LIMIT = 5;
|
|
9
|
+
/**
|
|
10
|
+
* The subject a replay should ask about.
|
|
11
|
+
*
|
|
12
|
+
* The FLOW NAME, because a flow is a feature and that is the axis the store shards on — replaying
|
|
13
|
+
* `checkout` should surface what the team knows about checkout, not about the route it happens to
|
|
14
|
+
* start on. A flow whose name says nothing gets nothing: asking for `unsorted` would return the
|
|
15
|
+
* project's junk drawer on every verdict, which is worse than silence because it looks like an
|
|
16
|
+
* answer.
|
|
17
|
+
*/
|
|
18
|
+
export function consultSubjectFor(flow) {
|
|
19
|
+
const name = flow.name;
|
|
20
|
+
if ('string' !== typeof name || 0 === name.trim().length)
|
|
21
|
+
return undefined;
|
|
22
|
+
return name.trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Trim what came back to what is worth carrying.
|
|
26
|
+
*
|
|
27
|
+
* PROVED records first: a statement something has actually verified is stronger evidence about the
|
|
28
|
+
* feature than one somebody proposed and never checked. Beyond that the order is the server's, and
|
|
29
|
+
* anything without a statement is dropped rather than rendered as an empty line.
|
|
30
|
+
*/
|
|
31
|
+
export function selectConsulted(entries, limit = MEMORY_CONSULT_LIMIT) {
|
|
32
|
+
const usable = entries.flatMap((e) => 'string' === typeof e.statement && e.statement.trim().length > 0
|
|
33
|
+
? [{ statement: e.statement, status: 'string' === typeof e.status ? e.status : 'unknown' }]
|
|
34
|
+
: []);
|
|
35
|
+
const proved = usable.filter((e) => 'proved' === e.status);
|
|
36
|
+
const rest = usable.filter((e) => 'proved' !== e.status);
|
|
37
|
+
return [...proved, ...rest].slice(0, limit);
|
|
38
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { FlowErrorCode, type FlowFile, type FlowReplayResult, type ReticleEvent } from '@reticlehq/core';
|
|
1
|
+
import { FlowErrorCode, type CommandResult, type FlowFile, type FlowReplayResult, type ReticleEvent } from '@reticlehq/core';
|
|
2
|
+
import type { ArrivalClock } from '../tools/navigate-arrival.js';
|
|
3
|
+
import type { SessionManager } from '../session/session-manager.js';
|
|
4
|
+
import type { Session } from '../session/session.js';
|
|
2
5
|
import type { ToolDeps } from '../tools/tools.js';
|
|
3
6
|
export declare function latestRecordedFlow(events: ReticleEvent[]): {
|
|
4
7
|
name: string;
|
|
@@ -6,18 +9,25 @@ export declare function latestRecordedFlow(events: ReticleEvent[]): {
|
|
|
6
9
|
} | undefined;
|
|
7
10
|
/** Map a structured FlowErrorCode to a legible one-line message for the agent. */
|
|
8
11
|
export declare function flowErrorMessage(code: FlowErrorCode): string;
|
|
12
|
+
/** The slice of a session the start-path logic reads: the live URL plus the event buffer. */
|
|
13
|
+
interface StartPathSession {
|
|
14
|
+
url?: string;
|
|
15
|
+
eventsSince(cursor: number): ReticleEvent[];
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
18
|
* When a flow records the page its journey started on (`startPath`) and the tab is currently on a
|
|
11
19
|
* different route, step 1 drifts for a reason that has nothing to do with the app regressing — the
|
|
12
20
|
* anchor simply isn't on this page yet. Detect that so the decision says "navigate there first"
|
|
13
21
|
* instead of a mystifying "a step no longer matches". Returns undefined when the routes agree or the
|
|
14
|
-
* current route is unobservable
|
|
15
|
-
*
|
|
16
|
-
*
|
|
22
|
+
* current route is unobservable — never a false alarm. Replay normally never gets here on a
|
|
23
|
+
* wrong page (arriveAtStartPath navigates before step 1); this is the fallback for when that
|
|
24
|
+
* navigation was refused or the SDK never reconnected in the window.
|
|
17
25
|
*/
|
|
18
|
-
export declare function startPathMismatchHint(flow: FlowFile, session:
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
export declare function startPathMismatchHint(flow: FlowFile, session: StartPathSession): string | undefined;
|
|
27
|
+
export declare function arriveAtStartPath(sessions: SessionManager, session: StartPathSession & {
|
|
28
|
+
id: string;
|
|
29
|
+
command(name: string, args?: Record<string, unknown>): Promise<CommandResult>;
|
|
30
|
+
}, flow: FlowFile, timeoutMs?: number, clock?: ArrivalClock): Promise<Session | undefined>;
|
|
21
31
|
/**
|
|
22
32
|
* Replay one named flow end to end: load → re-resolve+run each step → assert the success oracle →
|
|
23
33
|
* status + decision. Shared by reticle_flow_replay (single flow) and reticle_flow_verify (whole suite) so
|
|
@@ -30,3 +40,4 @@ export declare function replayNamedFlow(deps: ToolDeps, args: Record<string, unk
|
|
|
30
40
|
* documented to work headless), so a missing/unknown session degrades to the global/legacy store.
|
|
31
41
|
*/
|
|
32
42
|
export declare function sessionProjectId(deps: ToolDeps, sessionId: string | undefined): string | undefined;
|
|
43
|
+
export {};
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, RunKind, RunStatus, } from '@reticlehq/core';
|
|
2
|
-
import { asString } from '../tools/tools-helpers.js';
|
|
1
|
+
import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, ReticleCommand, RunKind, RunStatus, } from '@reticlehq/core';
|
|
2
|
+
import { asRecord, asString } from '../tools/tools-helpers.js';
|
|
3
|
+
import { carryReticleIdentity } from '../tools/lease-tools.js';
|
|
3
4
|
import { replayFlow } from './flow-replay.js';
|
|
5
|
+
import { anchorQueryArgs } from './flow-step-runners.js';
|
|
6
|
+
import { queryRefs } from './replay.js';
|
|
4
7
|
import { assertSuccess, dynamicTestids, successLabel, SUCCESS_STEP_TOOL } from './flow-success.js';
|
|
5
8
|
import { buildDecision, unverifiableReason } from './decision.js';
|
|
9
|
+
import { classifyFlowAssertions } from './flow-classify.js';
|
|
10
|
+
import { dischargeFlowIntent, flowIntentStatement, flowReplayVerdictId } from './flow-intent.js';
|
|
11
|
+
import { IntentStore } from '../intent/intent-store.js';
|
|
12
|
+
import { sessionRoot } from '../project/session-root.js';
|
|
6
13
|
import { waitForPredicate } from '../events/predicate.js';
|
|
7
14
|
import { computeSegments } from '../journal/rollups.js';
|
|
8
15
|
import { AssertionTiersStore } from './assertion-tiers-store.js';
|
|
@@ -10,9 +17,11 @@ import { toFlowSources } from './flow-sources.js';
|
|
|
10
17
|
import { reportAndAccumulate } from '../journal/deviation-service.js';
|
|
11
18
|
import { EnvelopeStore } from '../journal/envelope-store.js';
|
|
12
19
|
import { homedir } from 'node:os';
|
|
13
|
-
import { syncRunRecordToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
20
|
+
import { cloudFetch, syncRunRecordToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
14
21
|
import { resolveProjectCloud } from '../cloud/cloud-config.js';
|
|
22
|
+
import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
|
|
15
23
|
import { log } from '../log.js';
|
|
24
|
+
import { flowsForSession } from './flow-store-for-session.js';
|
|
16
25
|
export function latestRecordedFlow(events) {
|
|
17
26
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
18
27
|
const event = events[i];
|
|
@@ -50,10 +59,12 @@ function replayToRunStatus(status) {
|
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
52
61
|
* Append a flow-replay outcome to .reticle/project.json (never throws into replay) and, when logged in,
|
|
53
|
-
* best-effort mirror it to Reticle
|
|
62
|
+
* best-effort mirror it to Reticle so the team's server-side regression history stays current. The
|
|
54
63
|
* cloud push is fire-and-forget: not logged in → skipped, a network failure is logged and swallowed.
|
|
55
64
|
*/
|
|
56
|
-
async function recordReplayRun(deps, name, status, driftSteps, durationMs, projectId
|
|
65
|
+
async function recordReplayRun(deps, name, status, driftSteps, durationMs, projectId,
|
|
66
|
+
/** The APP's `.reticle`, resolved from the session — never the daemon's own. */
|
|
67
|
+
recordRoot) {
|
|
57
68
|
const runStatus = replayToRunStatus(status);
|
|
58
69
|
await deps.project.recordRun({
|
|
59
70
|
kind: RunKind.FLOW_REPLAY,
|
|
@@ -63,35 +74,223 @@ async function recordReplayRun(deps, name, status, driftSteps, durationMs, proje
|
|
|
63
74
|
durationMs,
|
|
64
75
|
});
|
|
65
76
|
// Per-project cloud: push memory outcomes only when cloud is attached AND memory sync is enabled.
|
|
66
|
-
|
|
77
|
+
// Rooted at the APP, not the daemon — see consultProjectMemory. This call had the same defect and
|
|
78
|
+
// it was worse here: a replay outcome that should have reached the dashboard silently did not,
|
|
79
|
+
// because the daemon's own directory has no link file.
|
|
80
|
+
const cloud = await resolveProjectCloud(deps.fs, recordRoot, homedir(), process.env);
|
|
67
81
|
if (null === cloud.config || !cloud.policy.memory)
|
|
68
82
|
return; // not attached / memory disabled → local only
|
|
69
|
-
const result = await syncRunRecordToCloud({ kind: RunKind.FLOW_REPLAY, name, status: runStatus, at: deps.now(), durationMs }, projectId, cloud.config,
|
|
83
|
+
const result = await syncRunRecordToCloud({ kind: RunKind.FLOW_REPLAY, name, status: runStatus, at: deps.now(), durationMs }, projectId, cloud.config, cloudFetch);
|
|
70
84
|
if (result.outcome !== SyncOutcome.SYNCED) {
|
|
71
85
|
log('cloud-run-record-sync-failed', { flow: name, status: result.status, error: result.error });
|
|
72
86
|
}
|
|
73
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The route the tab is on now: the last observed route.change, falling back to the pathname of the
|
|
90
|
+
* session's own URL. The fallback matters — a tab that hard-loaded its page emits no route event
|
|
91
|
+
* (the route observer only sees pushState/replaceState/popstate), but the HELLO url still names
|
|
92
|
+
* where it sits, and without it a wrong-page replay reported plain drift with no hint at all.
|
|
93
|
+
*/
|
|
94
|
+
function currentPathOf(session) {
|
|
95
|
+
const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
|
|
96
|
+
const data = routes.at(-1)?.data ?? {};
|
|
97
|
+
// pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE. Reading
|
|
98
|
+
// the pathname alone made both sides `/` on a hash router — always "same path", so the hint never
|
|
99
|
+
// fired however far the tab had drifted, on the router desktop renderers use by default.
|
|
100
|
+
const observed = asString(data['pathname']) ?? asString(data['to']);
|
|
101
|
+
if (observed !== undefined)
|
|
102
|
+
return `${observed}${asString(data['hash']) ?? ''}`;
|
|
103
|
+
if (session.url === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
try {
|
|
106
|
+
const parsed = new URL(session.url);
|
|
107
|
+
return `${parsed.pathname}${parsed.hash}`;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Pathname equality up to a trailing slash — a router normalising one must not read as "elsewhere". */
|
|
114
|
+
function samePath(a, b) {
|
|
115
|
+
return a.replace(/\/$/, '') === b.replace(/\/$/, '');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Ask the project what it already knows about this flow.
|
|
119
|
+
*
|
|
120
|
+
* Best-effort in the strongest sense: an unlinked project, a disabled memory policy, an offline
|
|
121
|
+
* laptop and a server that answers nonsense all reach the same place — the verdict returns without
|
|
122
|
+
* a memory block. A verification that FAILED because the knowledge lookup failed would be a worse
|
|
123
|
+
* product than one that never looked, and this is the path every replay takes.
|
|
124
|
+
*
|
|
125
|
+
* The read is also what makes the coverage map's fetch counts mean anything: they were zero across
|
|
126
|
+
* the entire corpus, not because memory is useless but because consulting it was a separate act
|
|
127
|
+
* nobody performed. Now the platform performs it.
|
|
128
|
+
*/
|
|
129
|
+
async function consultProjectMemory(deps, flow, root) {
|
|
130
|
+
const subject = consultSubjectFor(flow);
|
|
131
|
+
if (subject === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
try {
|
|
134
|
+
// The APP's root, not the daemon's. `deps.reticleRoot` is wherever the daemon was launched,
|
|
135
|
+
// which for a user-scoped MCP registration is almost never the project being verified — so the
|
|
136
|
+
// link file it reads is the wrong one, or absent, and every project silently reads as
|
|
137
|
+
// "not attached". Same class of bug as the flow store resolving per daemon instead of per
|
|
138
|
+
// session, and it is invisible: the feature simply never appears.
|
|
139
|
+
const cloud = await resolveProjectCloud(deps.fs, root, homedir(), process.env);
|
|
140
|
+
if (null === cloud.config || !cloud.policy.memory)
|
|
141
|
+
return undefined;
|
|
142
|
+
const url = `${cloud.config.url}/v1/memory?subject=${encodeURIComponent(subject)}`;
|
|
143
|
+
const res = await cloudFetch(url, {
|
|
144
|
+
method: 'GET',
|
|
145
|
+
headers: { authorization: `Bearer ${cloud.config.apiKey}` },
|
|
146
|
+
});
|
|
147
|
+
if (200 !== res.status)
|
|
148
|
+
return undefined;
|
|
149
|
+
// `cloudFetch` hands back a real `Response`, so the body is a METHOD. Reading `res.json` as a
|
|
150
|
+
// property yields the function itself, `.entries` on it is undefined, and the whole feature
|
|
151
|
+
// fails silently to "the project knows nothing" — which is indistinguishable from the honest
|
|
152
|
+
// empty case and is why this took a live drive to notice at all.
|
|
153
|
+
const body = (await res.json());
|
|
154
|
+
const entries = body?.entries;
|
|
155
|
+
if (!Array.isArray(entries))
|
|
156
|
+
return undefined;
|
|
157
|
+
const picked = selectConsulted(entries);
|
|
158
|
+
return 0 === picked.length ? undefined : picked;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// See the note above: never the reason a verdict fails to return.
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
74
165
|
/**
|
|
75
166
|
* When a flow records the page its journey started on (`startPath`) and the tab is currently on a
|
|
76
167
|
* different route, step 1 drifts for a reason that has nothing to do with the app regressing — the
|
|
77
168
|
* anchor simply isn't on this page yet. Detect that so the decision says "navigate there first"
|
|
78
169
|
* instead of a mystifying "a step no longer matches". Returns undefined when the routes agree or the
|
|
79
|
-
* current route is unobservable
|
|
80
|
-
*
|
|
81
|
-
*
|
|
170
|
+
* current route is unobservable — never a false alarm. Replay normally never gets here on a
|
|
171
|
+
* wrong page (arriveAtStartPath navigates before step 1); this is the fallback for when that
|
|
172
|
+
* navigation was refused or the SDK never reconnected in the window.
|
|
82
173
|
*/
|
|
83
174
|
export function startPathMismatchHint(flow, session) {
|
|
84
175
|
const startPath = flow.startPath;
|
|
85
176
|
if (startPath === undefined || 0 === startPath.length)
|
|
86
177
|
return undefined;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const data = last?.data ?? {};
|
|
90
|
-
const current = asString(data['pathname']) ?? asString(data['to']);
|
|
91
|
-
if (current === undefined || current === startPath)
|
|
178
|
+
const current = currentPathOf(session);
|
|
179
|
+
if (current === undefined || samePath(current, startPath))
|
|
92
180
|
return undefined;
|
|
93
181
|
return `this flow's journey starts on ${startPath} but the tab is on ${current} — navigate there (reticle_navigate { url: "${startPath}" }), then replay`;
|
|
94
182
|
}
|
|
183
|
+
/** How long replay waits for the SDK to reconnect on the start page before falling back to the hint. */
|
|
184
|
+
const START_PATH_ARRIVAL_TIMEOUT_MS = 5_000;
|
|
185
|
+
const START_PATH_POLL_MS = 100;
|
|
186
|
+
const REAL_ARRIVAL_CLOCK = {
|
|
187
|
+
now: () => Date.now(),
|
|
188
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* The session `oldId` denotes right now — itself, or the tombstone successor `resolve` rebinds to
|
|
192
|
+
* after a full-document navigation — but only once it actually sits on `target`. Undefined while
|
|
193
|
+
* the tab is still travelling (or in the gap between teardown and the successor's HELLO, when
|
|
194
|
+
* `resolve` throws). Keyed to the navigated tab's own identity on purpose: matching "any session at
|
|
195
|
+
* the target page" would happily hand replay an unrelated tab that was already sitting there.
|
|
196
|
+
*/
|
|
197
|
+
function arrivedSuccessor(sessions, oldId, target) {
|
|
198
|
+
try {
|
|
199
|
+
const candidate = sessions.resolve(oldId);
|
|
200
|
+
const path = currentPathOf(candidate);
|
|
201
|
+
return path !== undefined && samePath(path, target) ? candidate : undefined;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Replay's half of the FlowFile contract: navigate to the flow's `startPath` before step 1.
|
|
209
|
+
*
|
|
210
|
+
* A full-page load tears down the session socket, so the navigation must happen here — before any
|
|
211
|
+
* step runs — and the replay continues on the session the SDK reconnects as (found via the same
|
|
212
|
+
* tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
|
|
213
|
+
* when the flow carries no startPath, the tab is already there, the current route is unobservable,
|
|
214
|
+
* the navigation is refused, or the SDK never reconnects in the window, it returns undefined and
|
|
215
|
+
* replay proceeds on the connected session as before — with startPathMismatchHint turning any
|
|
216
|
+
* resulting drift into an actionable next move rather than a mystifying one.
|
|
217
|
+
*/
|
|
218
|
+
/**
|
|
219
|
+
* Can step 1 start from where the tab already is?
|
|
220
|
+
*
|
|
221
|
+
* `startPathMismatchHint` states the premise the navigation below exists to serve: on the wrong
|
|
222
|
+
* route "the anchor simply isn't on this page yet". That is the condition worth a page load — and
|
|
223
|
+
* it was never the condition checked. `arriveAtStartPath` fired on a route mismatch ALONE, so a
|
|
224
|
+
* flow whose first anchor lives somewhere persistent (a sidebar, a header, a nav rail — present on
|
|
225
|
+
* every route) was navigated for a problem it did not have.
|
|
226
|
+
*
|
|
227
|
+
* A navigation is a full page load, which tears the session down and brings the app back in its
|
|
228
|
+
* cold state. For the ordinary case of a flow recorded after signing in, that cold state is the
|
|
229
|
+
* LOGIN SCREEN: step 1 then reports its anchor missing and names the component that holds it, which
|
|
230
|
+
* is a correct sentence about a file that is completely fine. Found by the benchmark, on two flows
|
|
231
|
+
* with identical steps where the first replayed clean and the second — run after it, with the tab
|
|
232
|
+
* drifted off `/` — did not.
|
|
233
|
+
*
|
|
234
|
+
* So the question is asked directly. A best-effort step that can leave the caller WORSE off than
|
|
235
|
+
* skipping it is not best-effort; checking first is what makes the description true.
|
|
236
|
+
*
|
|
237
|
+
* Unresolvable either way (no anchor we can query, a failed query) reads as "cannot tell", and the
|
|
238
|
+
* navigation goes ahead — the pre-existing behaviour, and the safe direction for a check whose whole
|
|
239
|
+
* job is to avoid making things worse.
|
|
240
|
+
*/
|
|
241
|
+
async function firstStepResolvesHere(session, flow) {
|
|
242
|
+
const first = flow.steps?.[0];
|
|
243
|
+
if (first === undefined)
|
|
244
|
+
return false;
|
|
245
|
+
const args = anchorQueryArgs(first.anchor);
|
|
246
|
+
if (null === args)
|
|
247
|
+
return false;
|
|
248
|
+
try {
|
|
249
|
+
return 0 < queryRefs(await session.command(ReticleCommand.QUERY, args)).length;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export async function arriveAtStartPath(sessions, session, flow, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK) {
|
|
256
|
+
const target = flow.startPath;
|
|
257
|
+
if (target === undefined || 0 === target.length)
|
|
258
|
+
return undefined;
|
|
259
|
+
const current = currentPathOf(session);
|
|
260
|
+
if (current === undefined || samePath(current, target))
|
|
261
|
+
return undefined;
|
|
262
|
+
// The route differs — but that only matters if it stops the flow starting. See above.
|
|
263
|
+
if (await firstStepResolvesHere(session, flow))
|
|
264
|
+
return undefined;
|
|
265
|
+
let destination;
|
|
266
|
+
try {
|
|
267
|
+
// startPath is a pathname (a host belongs to the machine, not the journey) — resolve it
|
|
268
|
+
// against the tab's own URL to get something the browser can be sent to.
|
|
269
|
+
destination = new URL(target, session.url).toString();
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined; // no usable base URL — nowhere to navigate from
|
|
273
|
+
}
|
|
274
|
+
// A leased tab is addressed by URL params, so navigating without them would strand the lease.
|
|
275
|
+
const url = carryReticleIdentity(session.url, destination);
|
|
276
|
+
try {
|
|
277
|
+
const outcome = await session.command(ReticleCommand.NAVIGATE, { url });
|
|
278
|
+
if (!outcome.ok || true !== asRecord(outcome.result)['ok'])
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
const deadline = clock.now() + timeoutMs;
|
|
285
|
+
for (;;) {
|
|
286
|
+
const arrived = arrivedSuccessor(sessions, session.id, target);
|
|
287
|
+
if (arrived !== undefined)
|
|
288
|
+
return arrived;
|
|
289
|
+
if (clock.now() >= deadline)
|
|
290
|
+
return undefined;
|
|
291
|
+
await clock.sleep(START_PATH_POLL_MS);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
95
294
|
/**
|
|
96
295
|
* Replay one named flow end to end: load → re-resolve+run each step → assert the success oracle →
|
|
97
296
|
* status + decision. Shared by reticle_flow_replay (single flow) and reticle_flow_verify (whole suite) so
|
|
@@ -110,9 +309,11 @@ export async function replayNamedFlow(deps, args) {
|
|
|
110
309
|
catch {
|
|
111
310
|
projectId = undefined;
|
|
112
311
|
}
|
|
113
|
-
|
|
312
|
+
// The app's store, not the daemon's: this load answering `flow_not_found` for a flow plainly on
|
|
313
|
+
// disk is what made replay unusable from a daemon started outside the project.
|
|
314
|
+
const loaded = await flowsForSession(deps, projectId).flows.load(name, projectId);
|
|
114
315
|
if (!loaded.ok) {
|
|
115
|
-
await recordReplayRun(deps, name, ReplayStatus.ERROR, 0, deps.now() - startedAt, projectId);
|
|
316
|
+
await recordReplayRun(deps, name, ReplayStatus.ERROR, 0, deps.now() - startedAt, projectId, sessionRoot(deps, asString(args['sessionId'])));
|
|
116
317
|
return {
|
|
117
318
|
name,
|
|
118
319
|
status: ReplayStatus.ERROR,
|
|
@@ -120,9 +321,22 @@ export async function replayNamedFlow(deps, args) {
|
|
|
120
321
|
error: { code: loaded.code, message: flowErrorMessage(loaded.code) },
|
|
121
322
|
};
|
|
122
323
|
}
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
324
|
+
// What this flow is FOR, from the shared ledger — so a failure can report the business outcome
|
|
325
|
+
// that stopped being true before the step that stopped being green. Undefined when nothing
|
|
326
|
+
// declared one, which the decision then says plainly rather than inventing a goal from step names.
|
|
327
|
+
/*
|
|
328
|
+
* The APP's `.reticle`, resolved once. Everything below that reaches the project's own files —
|
|
329
|
+
* the intent ledger, the cloud link, the memory consultation — takes THIS, not `deps.reticleRoot`,
|
|
330
|
+
* which is wherever the daemon happened to be launched.
|
|
331
|
+
*/
|
|
332
|
+
const replayRoot = sessionRoot(deps, asString(args['sessionId']));
|
|
333
|
+
const intents = new IntentStore(deps.fs, replayRoot, { now: deps.now });
|
|
334
|
+
const intentSaid = await flowIntentStatement(intents, loaded.value);
|
|
335
|
+
const connected = deps.sessions.resolve(asString(args['sessionId']));
|
|
336
|
+
// The FlowFile contract: replay navigates to the flow's startPath before step 1, and the steps run
|
|
337
|
+
// on the session the SDK reconnects as. When arrival can't be confirmed, replay proceeds on the
|
|
338
|
+
// connected session — and the hint below turns the wrong-page drift into an actionable next move.
|
|
339
|
+
const session = (await arriveAtStartPath(deps.sessions, connected, loaded.value)) ?? connected;
|
|
126
340
|
const startPathHint = startPathMismatchHint(loaded.value, session);
|
|
127
341
|
// Floor the success oracle at the start of THIS replay so a stale signal from a prior run
|
|
128
342
|
// in the same session can't fake a pass.
|
|
@@ -146,7 +360,7 @@ export async function replayNamedFlow(deps, args) {
|
|
|
146
360
|
const driftSteps = steps.filter((s) => s.drift !== undefined).length;
|
|
147
361
|
const allOk = steps.every((s) => s.ok);
|
|
148
362
|
const status = driftSteps > 0 ? ReplayStatus.DRIFT : allOk ? ReplayStatus.OK : ReplayStatus.ERROR;
|
|
149
|
-
await recordReplayRun(deps, name, status, driftSteps, deps.now() - startedAt, projectId);
|
|
363
|
+
await recordReplayRun(deps, name, status, driftSteps, deps.now() - startedAt, projectId, replayRoot);
|
|
150
364
|
// Anti-reward-hacking baseline: record what this flow asserted ONLY when it passed clean. A
|
|
151
365
|
// failing run must never become the baseline a later weakening is measured against.
|
|
152
366
|
if (status === ReplayStatus.OK) {
|
|
@@ -156,9 +370,31 @@ export async function replayNamedFlow(deps, args) {
|
|
|
156
370
|
})),
|
|
157
371
|
// Record what this flow COVERS so a later deletion can be scoped to the files that changed.
|
|
158
372
|
toFlowSources([{ name, steps: loaded.value.steps }])[0]?.sources ?? []);
|
|
373
|
+
// The ledger learns what this run proved. Only a flow that COULD have gone red discharges: an
|
|
374
|
+
// assertion-free flow is never bound, and `dischargeIntent` refuses an unbound intent, so the
|
|
375
|
+
// guard here is the cheap half of a rule the ledger already enforces. `grade` is what makes a
|
|
376
|
+
// later weakening visible — an intent re-proved by a weaker flow says so in the git diff.
|
|
377
|
+
if (unverifiableReason(loaded.value) === undefined) {
|
|
378
|
+
const provedAt = deps.now();
|
|
379
|
+
await dischargeFlowIntent(intents, loaded.value, {
|
|
380
|
+
verdictId: flowReplayVerdictId(name, provedAt),
|
|
381
|
+
grade: classifyFlowAssertions(loaded.value).grade,
|
|
382
|
+
at: provedAt,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
159
385
|
}
|
|
160
386
|
// Push-default: the deviation report over this drive's segments, learned across runs. Best-effort.
|
|
161
387
|
const deviation = await computeReplayDeviation(deps, session, replayFloor);
|
|
388
|
+
/*
|
|
389
|
+
* What the team already knows about this flow, fetched on the agent's behalf.
|
|
390
|
+
*
|
|
391
|
+
* Attached to the FAILING path as well as the clean one, deliberately: a drift is exactly the
|
|
392
|
+
* moment somebody needs to know what this feature is supposed to do and who established it. A
|
|
393
|
+
* knowledge base you only see when everything is already fine is decoration.
|
|
394
|
+
*/
|
|
395
|
+
// No projectId argument: the API key is already bound to one project server-side, so passing a
|
|
396
|
+
// second opinion about which project this is would only create a way for the two to disagree.
|
|
397
|
+
const knows = await consultProjectMemory(deps, loaded.value, replayRoot);
|
|
162
398
|
const failed = steps.find((step) => !step.ok && step.drift === undefined);
|
|
163
399
|
if (failed !== undefined) {
|
|
164
400
|
const errored = {
|
|
@@ -167,13 +403,17 @@ export async function replayNamedFlow(deps, args) {
|
|
|
167
403
|
steps,
|
|
168
404
|
error: { code: ReplayStatus.ERROR, message: failed.error ?? 'flow action failed' },
|
|
169
405
|
};
|
|
170
|
-
errored.decision = buildDecision(errored, loaded.value);
|
|
406
|
+
errored.decision = buildDecision(errored, loaded.value, intentSaid);
|
|
171
407
|
applyStartPathHint(errored, startPathHint);
|
|
172
408
|
if (deviation !== undefined)
|
|
173
409
|
errored.deviation = deviation;
|
|
410
|
+
if (knows !== undefined)
|
|
411
|
+
errored.knows = knows;
|
|
174
412
|
return errored;
|
|
175
413
|
}
|
|
176
414
|
const result = { name, status, steps };
|
|
415
|
+
if (knows !== undefined)
|
|
416
|
+
result.knows = knows;
|
|
177
417
|
// A green that cannot go red is not a pass. `reticle_flow_verify` already refuses to count these,
|
|
178
418
|
// via this same function -- a single-flow caller saw a bare `ok` and had no way to learn the flow
|
|
179
419
|
// asserts nothing. Derived from the same helper on purpose: two copies of this judgement would
|
|
@@ -182,7 +422,7 @@ export async function replayNamedFlow(deps, args) {
|
|
|
182
422
|
if (cannotFail !== undefined)
|
|
183
423
|
result.unverifiable = { reason: cannotFail };
|
|
184
424
|
if (status !== ReplayStatus.OK)
|
|
185
|
-
result.decision = buildDecision(result, loaded.value);
|
|
425
|
+
result.decision = buildDecision(result, loaded.value, intentSaid);
|
|
186
426
|
applyStartPathHint(result, startPathHint);
|
|
187
427
|
if (deviation !== undefined)
|
|
188
428
|
result.deviation = deviation;
|