@reticlehq/server 2.13.0 → 2.14.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 +8 -18
- package/bin/reticle.js +3 -0
- package/dist/bridge/auth-failure-reason.js +12 -1
- package/dist/bridge/bridge-security.d.ts +8 -0
- package/dist/bridge/bridge-security.js +6 -1
- package/dist/bridge/bridge.d.ts +3 -0
- package/dist/bridge/bridge.js +23 -1
- package/dist/bridge/bridge.test-harness.d.ts +10 -1
- package/dist/bridge/bridge.test-harness.js +7 -1
- package/dist/bridge/pairing-token.d.ts +17 -0
- package/dist/bridge/pairing-token.js +26 -0
- package/dist/bridge/token-auth.d.ts +7 -0
- package/dist/bridge/token-auth.js +7 -1
- package/dist/capsule/capsule-store.d.ts +4 -4
- package/dist/capsule/capsule.d.ts +1 -1
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +21 -1
- package/dist/capsule/causal-summary.js +8 -6
- package/dist/capsule/divergence.d.ts +1 -1
- package/dist/capsule/divergence.js +26 -10
- package/dist/chromium-launch-options.d.ts +2 -1
- package/dist/cli/adhoc-verdict.d.ts +58 -0
- package/dist/cli/adhoc-verdict.js +115 -0
- package/dist/cli/chromium-hint.d.ts +18 -4
- package/dist/cli/chromium-hint.js +54 -6
- package/dist/cli/cli-doctor.js +1 -2
- package/dist/cli/cli-flow-commands.d.ts +16 -2
- package/dist/cli/cli-flow-commands.js +28 -9
- package/dist/cli/cli-parse-feedback.js +1 -1
- package/dist/cli/cli-parse.d.ts +2 -1
- package/dist/cli/cli-parse.js +38 -0
- package/dist/cli/cli-port.d.ts +5 -13
- package/dist/cli/cli-port.js +5 -38
- package/dist/cli/cli-update-commands.js +4 -4
- package/dist/cli/cli-verify.d.ts +24 -7
- package/dist/cli/cli-verify.js +87 -13
- package/dist/cli/cloud-keystore.d.ts +2 -1
- package/dist/cli/cloud-login.d.ts +2 -1
- package/dist/cli/cloud-session.d.ts +2 -1
- package/dist/cli/cloud-session.js +1 -1
- package/dist/cli/daemon-start-options.d.ts +2 -1
- package/dist/cli/device-flow.d.ts +2 -2
- package/dist/cli/doctor-daemon-line.d.ts +2 -1
- package/dist/cli/doctor-project-line.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +2 -1
- package/dist/cli/drive-attach.d.ts +4 -3
- package/dist/cli/gate-hook-message.d.ts +2 -1
- package/dist/cli/status-next-action.js +10 -11
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +21 -9
- package/dist/cloud/account-state.d.ts +20 -0
- package/dist/cloud/account-state.js +65 -0
- package/dist/cloud/cloud-sync.d.ts +46 -3
- package/dist/cloud/cloud-sync.js +116 -0
- package/dist/cloud/sync-cycle.d.ts +5 -4
- package/dist/cloud/sync-cycle.js +1 -1
- package/dist/cloud/sync-daemon.d.ts +3 -19
- package/dist/cloud/sync-daemon.js +2 -2
- package/dist/crawl/crawl-tools.js +1 -1
- package/dist/crawl/crawl.d.ts +5 -5
- package/dist/crawl/crawl.js +138 -4
- package/dist/daemon/daemon-resilience.d.ts +3 -2
- package/dist/daemon/daemon-resilience.js +43 -4
- package/dist/daemon/daemon-resolve.d.ts +0 -9
- package/dist/daemon/daemon-resolve.js +2 -2
- package/dist/daemon/dev-servers.d.ts +1 -1
- package/dist/daemon/dev-servers.js +1 -1
- package/dist/daemon/loopback-alias.d.ts +2 -1
- package/dist/daemon/startup-failure.d.ts +11 -0
- package/dist/daemon/startup-failure.js +46 -0
- package/dist/events/contradictions.d.ts +10 -16
- package/dist/events/contradictions.js +149 -17
- package/dist/events/declared.d.ts +13 -1
- package/dist/events/declared.js +59 -3
- package/dist/events/echo-mismatch.js +75 -6
- package/dist/events/lineage.d.ts +52 -0
- package/dist/events/lineage.js +122 -0
- package/dist/events/net-evidence.d.ts +21 -0
- package/dist/events/net-evidence.js +81 -0
- package/dist/events/predicate-console.d.ts +13 -0
- package/dist/events/predicate-console.js +138 -0
- package/dist/events/predicate-element.d.ts +13 -0
- package/dist/events/predicate-element.js +214 -0
- package/dist/events/predicate-eval.d.ts +15 -3
- package/dist/events/predicate-eval.js +128 -110
- package/dist/events/predicate-parse.js +34 -1
- package/dist/events/predicate-request-body.d.ts +50 -0
- package/dist/events/predicate-request-body.js +120 -0
- package/dist/events/predicate-route.d.ts +21 -1
- package/dist/events/predicate-route.js +149 -21
- package/dist/events/predicate-schema.d.ts +8 -1
- package/dist/events/predicate-schema.js +34 -0
- package/dist/events/predicate.d.ts +2 -0
- package/dist/events/predicate.js +52 -159
- package/dist/events/split-text-miss.d.ts +6 -4
- package/dist/events/split-text-miss.js +12 -5
- package/dist/flows/affected-tools.js +22 -9
- package/dist/flows/annotate.d.ts +2 -1
- package/dist/flows/annotate.js +19 -1
- package/dist/flows/coverage.d.ts +2 -2
- package/dist/flows/flake.d.ts +3 -3
- package/dist/flows/flow-expect-grammar.d.ts +33 -0
- package/dist/flows/flow-expect-grammar.js +155 -0
- package/dist/flows/flow-replay-run.d.ts +1 -1
- package/dist/flows/flow-replay-run.js +28 -15
- package/dist/flows/flow-replay.d.ts +10 -0
- package/dist/flows/flow-replay.js +57 -22
- package/dist/flows/flow-secret-field.d.ts +14 -0
- package/dist/flows/flow-secret-field.js +22 -0
- package/dist/flows/flow-step-runners.d.ts +1 -0
- package/dist/flows/flow-step-runners.js +91 -34
- package/dist/flows/flow-success.js +22 -0
- package/dist/flows/flow-tools.js +27 -8
- package/dist/flows/flow-upload-resolve.d.ts +23 -0
- package/dist/flows/flow-upload-resolve.js +46 -0
- package/dist/flows/flows.d.ts +15 -0
- package/dist/flows/flows.js +17 -14
- package/dist/flows/git-changed.d.ts +23 -4
- package/dist/flows/git-changed.js +32 -6
- package/dist/flows/heal-run.js +2 -2
- package/dist/flows/predicate-to-expect.d.ts +3 -2
- package/dist/flows/predicate-to-expect.js +11 -5
- package/dist/flows/recording-backtrack.d.ts +24 -0
- package/dist/flows/recording-backtrack.js +73 -0
- package/dist/flows/recordings.d.ts +6 -0
- package/dist/flows/replay-halt.d.ts +26 -0
- package/dist/flows/replay-halt.js +30 -0
- package/dist/flows/replay.d.ts +0 -8
- package/dist/flows/replay.js +15 -3
- package/dist/flows/suite-flakes.d.ts +2 -1
- package/dist/flows/verify-change-tools.js +16 -4
- package/dist/honesty/already-true.d.ts +14 -0
- package/dist/honesty/already-true.js +47 -4
- package/dist/honesty/blind-spots.d.ts +3 -1
- package/dist/honesty/blind-spots.js +24 -1
- package/dist/honesty/body-capture-remedy.d.ts +42 -0
- package/dist/honesty/body-capture-remedy.js +95 -0
- package/dist/honesty/feature-capture.d.ts +3 -2
- package/dist/honesty/feature-capture.js +1 -1
- package/dist/honesty/gap-ledger.d.ts +1 -1
- package/dist/honesty/gap-ledger.js +1 -1
- package/dist/honesty/instrumentation-gaps.d.ts +8 -1
- package/dist/honesty/instrumentation-gaps.js +12 -2
- package/dist/honesty/observability.d.ts +4 -3
- package/dist/honesty/tool-hit-rate.d.ts +3 -2
- package/dist/honesty/uncaptured-bodies.d.ts +3 -1
- package/dist/honesty/uncaptured-bodies.js +6 -2
- package/dist/honesty/unsettled.d.ts +9 -0
- package/dist/honesty/unsettled.js +45 -0
- package/dist/honesty/verified.d.ts +32 -1
- package/dist/honesty/verified.js +37 -10
- package/dist/impact/impact-store.d.ts +5 -14
- package/dist/impact/impact-store.js +49 -6
- package/dist/index.js +1 -1
- package/dist/input/network-detail.d.ts +26 -1
- package/dist/input/network-detail.js +44 -12
- package/dist/input/network-mock-tools.js +24 -17
- package/dist/input/real-input.js +1 -1
- package/dist/input/viewport-tools.js +25 -13
- package/dist/intent/inline-intent.d.ts +0 -10
- package/dist/intent/inline-intent.js +1 -1
- package/dist/intent/intent-shard-store.d.ts +3 -2
- package/dist/intent/intent-shard-store.js +1 -1
- package/dist/intent/intent-shard.d.ts +14 -14
- package/dist/intent/intent-shard.js +4 -4
- package/dist/intent/intent-store.d.ts +1 -1
- package/dist/intent/intent-store.js +1 -1
- package/dist/intent/intent-subject.d.ts +2 -1
- package/dist/intent/open-intents.d.ts +3 -2
- package/dist/license/license.d.ts +4 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/attach-memory.d.ts +2 -1
- package/dist/mcp/client-identity.d.ts +2 -1
- package/dist/mcp/mcp-outage.d.ts +1 -1
- package/dist/mcp/mcp-outage.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +3 -1
- package/dist/mcp/mcp-proxy.js +32 -7
- package/dist/mcp/mcp.js +3 -1
- package/dist/mcp/proxy-handshake.d.ts +2 -1
- package/dist/mcp/proxy-idle-exit.d.ts +45 -0
- package/dist/mcp/proxy-idle-exit.js +94 -0
- package/dist/mcp/server-instructions.d.ts +2 -1
- package/dist/memory/project-memory.d.ts +2 -3
- package/dist/memory/project-memory.js +1 -1
- package/dist/oracles/propose-consequences.js +5 -5
- package/dist/oracles/self-instrument.d.ts +2 -2
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/platform.d.ts +3 -5
- package/dist/platform.js +3 -5
- package/dist/pool/browser-pool.d.ts +92 -0
- package/dist/pool/browser-pool.js +119 -5
- package/dist/pool/playwright-launcher.js +29 -2
- package/dist/pool/storage-seed.d.ts +23 -0
- package/dist/pool/storage-seed.js +198 -0
- package/dist/project/artifact-root.d.ts +2 -1
- package/dist/project/memory-fs.d.ts +2 -1
- package/dist/project/reticle-dir.js +1 -1
- package/dist/project/session-root.d.ts +8 -0
- package/dist/project/session-root.js +9 -2
- package/dist/runs/reticle-runner.d.ts +14 -1
- package/dist/runs/reticle-runner.js +39 -2
- package/dist/session/attachment-history.d.ts +2 -1
- package/dist/session/fake-session.d.ts +37 -0
- package/dist/session/fake-session.js +81 -0
- package/dist/session/lease-visibility.d.ts +2 -1
- package/dist/session/live-control-tools.js +6 -0
- package/dist/session/no-session-diagnosis.d.ts +17 -1
- package/dist/session/no-session-diagnosis.js +32 -6
- package/dist/session/no-session-next-action.d.ts +14 -0
- package/dist/session/no-session-next-action.js +18 -0
- package/dist/session/no-session-watch.js +46 -3
- package/dist/session/registered-projects.d.ts +54 -0
- package/dist/session/registered-projects.js +73 -0
- package/dist/session/review-store.d.ts +2 -1
- package/dist/session/session-health.d.ts +62 -1
- package/dist/session/session-health.js +82 -2
- package/dist/session/session-manager.d.ts +11 -1
- package/dist/session/session-manager.js +18 -3
- package/dist/session/session-replaced.d.ts +24 -0
- package/dist/session/session-replaced.js +29 -0
- package/dist/session/session-successor.js +20 -4
- package/dist/session/session.d.ts +13 -5
- package/dist/session/session.js +31 -23
- package/dist/setup/agent-configs.d.ts +3 -2
- package/dist/setup/agent-writer.d.ts +3 -2
- package/dist/setup/approval-migration.d.ts +2 -1
- package/dist/setup/approval-migration.js +1 -1
- package/dist/setup/auto-approve.d.ts +11 -19
- package/dist/setup/auto-approve.js +15 -12
- package/dist/{init → setup}/confirm.js +1 -1
- package/dist/setup/desktop-shape.d.ts +3 -2
- package/dist/setup/dev-server-wait.d.ts +8 -0
- package/dist/setup/dev-server-wait.js +3 -1
- package/dist/setup/drive-agent.d.ts +2 -3
- package/dist/setup/drive-agent.js +1 -1
- package/dist/setup/drive-plan.d.ts +3 -2
- package/dist/setup/ensure-daemon.d.ts +2 -1
- package/dist/setup/existing-app.d.ts +12 -0
- package/dist/setup/existing-app.js +13 -0
- package/dist/setup/init-host.d.ts +11 -0
- package/dist/setup/init-host.js +26 -0
- package/dist/setup/init-runtime.d.ts +4 -10
- package/dist/setup/init-runtime.js +23 -16
- package/dist/setup/license-key.d.ts +2 -1
- package/dist/setup/listeners.d.ts +2 -1
- package/dist/setup/loopback-probe-urls.d.ts +18 -0
- package/dist/setup/loopback-probe-urls.js +52 -0
- package/dist/setup/node-effects.d.ts +3 -0
- package/dist/setup/node-effects.js +26 -11
- package/dist/setup/page-probe.d.ts +5 -0
- package/dist/setup/relaunch.d.ts +2 -1
- package/dist/setup/run-setup.d.ts +5 -1
- package/dist/setup/run-setup.js +155 -48
- package/dist/setup/setup-command.d.ts +3 -2
- package/dist/setup/setup-command.js +8 -0
- package/dist/setup/setup-options.d.ts +5 -3
- package/dist/setup/setup-options.js +3 -2
- package/dist/setup/wait-progress.d.ts +28 -0
- package/dist/setup/wait-progress.js +43 -0
- package/dist/telemetry/app-instrumented.d.ts +2 -1
- package/dist/telemetry/app-instrumented.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +1 -1
- package/dist/telemetry/automation-hint.js +1 -1
- package/dist/telemetry/cli-telemetry.js +1 -1
- package/dist/telemetry/connect-failure.d.ts +1 -1
- package/dist/telemetry/connect-failure.js +1 -1
- package/dist/telemetry/crash-cause.d.ts +3 -2
- package/dist/telemetry/crash-cause.js +1 -1
- package/dist/telemetry/daemon-telemetry.d.ts +3 -2
- package/dist/telemetry/daemon-telemetry.js +1 -1
- package/dist/telemetry/error-class.d.ts +3 -2
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-cli.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +3 -2
- package/dist/telemetry/feedback-context.js +5 -4
- package/dist/telemetry/feedback-local.d.ts +3 -2
- package/dist/telemetry/feedback.d.ts +4 -3
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/git-facts.d.ts +2 -2
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/identify.d.ts +1 -1
- package/dist/telemetry/identify.js +1 -1
- package/dist/telemetry/init-telemetry.d.ts +2 -14
- package/dist/telemetry/init-telemetry.js +4 -13
- package/dist/telemetry/install-source.d.ts +7 -13
- package/dist/telemetry/install-source.js +7 -29
- package/dist/telemetry/license-activation.d.ts +3 -2
- package/dist/telemetry/license-activation.js +1 -1
- package/dist/telemetry/machine-snapshot.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +1 -1
- package/dist/telemetry/project-profile.d.ts +0 -14
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/proxy-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +1 -17
- package/dist/telemetry/telemetry.js +4 -4
- package/dist/telemetry/tool-refused.d.ts +1 -1
- package/dist/telemetry/tool-refused.js +1 -1
- package/dist/tools/act-observation.d.ts +3 -2
- package/dist/tools/act-preflight.js +8 -3
- package/dist/tools/act-target.d.ts +4 -0
- package/dist/tools/act-target.js +14 -5
- package/dist/tools/act-tools.d.ts +0 -2
- package/dist/tools/act-tools.js +45 -15
- package/dist/tools/assert-source.d.ts +30 -0
- package/dist/tools/assert-source.js +21 -0
- package/dist/tools/assert-verdict.d.ts +1 -1
- package/dist/tools/assert-verdict.js +14 -4
- package/dist/tools/browser-tools.js +28 -8
- package/dist/tools/complete-snapshot.d.ts +3 -2
- package/dist/tools/dynamic-tools.js +8 -0
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +23 -0
- package/dist/tools/feedback-tools.js +1 -1
- package/dist/tools/invoke-tool.js +2 -0
- package/dist/tools/lease-tools.d.ts +48 -20
- package/dist/tools/lease-tools.js +311 -76
- package/dist/tools/lineage-tools.d.ts +14 -0
- package/dist/tools/lineage-tools.js +64 -0
- package/dist/tools/merge-tools.js +1 -3
- package/dist/tools/navigate-arrival.d.ts +45 -1
- package/dist/tools/navigate-arrival.js +39 -6
- package/dist/tools/navigate-result.d.ts +8 -1
- package/dist/tools/navigate-result.js +14 -4
- package/dist/tools/numeric-bounds.d.ts +0 -11
- package/dist/tools/numeric-bounds.js +4 -4
- package/dist/tools/observe-tools.js +22 -26
- package/dist/tools/observed-nothing.d.ts +9 -0
- package/dist/tools/observed-nothing.js +2 -1
- package/dist/tools/read-tools.js +12 -17
- package/dist/tools/real-input-attempt.d.ts +14 -6
- package/dist/tools/real-input-attempt.js +75 -18
- package/dist/tools/reconcile-tools.js +2 -1
- package/dist/tools/reload-result.d.ts +6 -1
- package/dist/tools/reload-result.js +7 -2
- package/dist/tools/resolve-target.d.ts +7 -1
- package/dist/tools/resolve-target.js +72 -9
- package/dist/tools/settle-in-flight.d.ts +1 -1
- package/dist/tools/settle-in-flight.js +9 -2
- package/dist/tools/snapshot-delta.d.ts +24 -1
- package/dist/tools/snapshot-delta.js +66 -7
- package/dist/tools/tool-names.d.ts +1 -0
- package/dist/tools/tool-names.js +1 -0
- package/dist/tools/tool-surface.js +20 -2
- package/dist/tools/tools.js +120 -11
- package/dist/tools/verify-next-baton.d.ts +2 -1
- package/dist/update/sdk-sync.d.ts +1 -1
- package/dist/update/sdk-sync.js +1 -1
- package/dist/update/updater.js +1 -1
- package/dist/version/sdk-fix.d.ts +2 -1
- package/dist/version/version-skew.d.ts +0 -8
- package/dist/version/version-skew.js +1 -1
- package/docs/README.md +8 -6
- package/docs/actions.mdx +1 -1
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +1 -1
- package/docs/cli/mcp.mdx +3 -1
- package/docs/cli/watch.mdx +1 -1
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +8 -3
- package/docs/faq.mdx +9 -0
- package/docs/first-drive.md +136 -0
- package/docs/frameworks.mdx +29 -7
- package/docs/gate-plan.md +2 -2
- package/docs/gates.md +19 -11
- package/docs/getting-started.md +7 -1
- package/docs/http-transport.md +164 -0
- package/docs/index.mdx +5 -3
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +15 -3
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +2 -0
- package/docs/system-map.md +2 -2
- package/docs/telemetry-contract.md +4 -4
- package/docs/telemetry-events.mdx +1 -1
- package/docs/telemetry.md +3 -3
- package/docs/tools-navigate.mdx +14 -6
- package/docs/usage.md +4 -4
- package/package.json +7 -4
- package/dist/init/agent-rules.d.ts +0 -116
- package/dist/init/agent-rules.js +0 -279
- package/dist/init/app-choice.d.ts +0 -20
- package/dist/init/app-choice.js +0 -25
- package/dist/init/astro-host.d.ts +0 -21
- package/dist/init/astro-host.js +0 -27
- package/dist/init/astro-patch.d.ts +0 -33
- package/dist/init/astro-patch.js +0 -300
- package/dist/init/capabilities.d.ts +0 -40
- package/dist/init/capabilities.js +0 -150
- package/dist/init/closing-hint.d.ts +0 -36
- package/dist/init/closing-hint.js +0 -120
- package/dist/init/connect-steps.d.ts +0 -2
- package/dist/init/connect-steps.js +0 -50
- package/dist/init/cra.d.ts +0 -79
- package/dist/init/cra.js +0 -178
- package/dist/init/csp-check.d.ts +0 -38
- package/dist/init/csp-check.js +0 -107
- package/dist/init/csp-doctor.d.ts +0 -32
- package/dist/init/csp-doctor.js +0 -54
- package/dist/init/desktop-doctor.d.ts +0 -41
- package/dist/init/desktop-doctor.js +0 -198
- package/dist/init/detect.d.ts +0 -96
- package/dist/init/detect.js +0 -209
- package/dist/init/dev-script.d.ts +0 -50
- package/dist/init/dev-script.js +0 -66
- package/dist/init/format-generated.d.ts +0 -18
- package/dist/init/format-generated.js +0 -39
- package/dist/init/install-hint.d.ts +0 -9
- package/dist/init/install-hint.js +0 -29
- package/dist/init/mcp-clients.d.ts +0 -89
- package/dist/init/mcp-clients.js +0 -299
- package/dist/init/mcp-registered.d.ts +0 -12
- package/dist/init/mcp-registered.js +0 -14
- package/dist/init/mcp.d.ts +0 -89
- package/dist/init/mcp.js +0 -129
- package/dist/init/next-patch.d.ts +0 -39
- package/dist/init/next-patch.js +0 -196
- package/dist/init/node-io.d.ts +0 -7
- package/dist/init/node-io.js +0 -117
- package/dist/init/non-js-project.d.ts +0 -27
- package/dist/init/non-js-project.js +0 -84
- package/dist/init/patch-kind.d.ts +0 -20
- package/dist/init/patch-kind.js +0 -10
- package/dist/init/plan-framework.d.ts +0 -64
- package/dist/init/plan-framework.js +0 -565
- package/dist/init/plan.d.ts +0 -242
- package/dist/init/plan.js +0 -703
- package/dist/init/preflight.d.ts +0 -28
- package/dist/init/preflight.js +0 -38
- package/dist/init/project-id.d.ts +0 -12
- package/dist/init/project-id.js +0 -25
- package/dist/init/refresh-rules.d.ts +0 -39
- package/dist/init/refresh-rules.js +0 -51
- package/dist/init/run.d.ts +0 -117
- package/dist/init/run.js +0 -806
- package/dist/init/slash-command.d.ts +0 -27
- package/dist/init/slash-command.js +0 -132
- package/dist/init/snippets.d.ts +0 -134
- package/dist/init/snippets.js +0 -572
- package/dist/init/vite-config.d.ts +0 -18
- package/dist/init/vite-config.js +0 -114
- package/dist/init/windows-quote.d.ts +0 -38
- package/dist/init/windows-quote.js +0 -70
- package/dist/init/workspace-apps.d.ts +0 -30
- package/dist/init/workspace-apps.js +0 -178
- package/dist/project/remember-project.d.ts +0 -24
- package/dist/project/remember-project.js +0 -40
- /package/dist/{init → setup}/confirm.d.ts +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
3
3
|
import { loadNamedFlows, resolveChangedFiles } from '../cli/cli-flow-commands.js';
|
|
4
|
+
import { sessionRoot, sessionProjectId } from '../project/session-root.js';
|
|
4
5
|
import { affectedSavedFlows } from './flow-sources.js';
|
|
5
6
|
/**
|
|
6
7
|
* `reticle_affected` — which saved flows must re-verify for a set of changed files.
|
|
@@ -43,20 +44,32 @@ export const AFFECTED_TOOLS = [
|
|
|
43
44
|
? args['files'].filter((f) => 'string' === typeof f)
|
|
44
45
|
: [];
|
|
45
46
|
const since = 'string' === typeof args['since'] ? args['since'] : undefined;
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// Same address as verify_change, resolved the same way: `deps.reticleRoot` is the configured
|
|
48
|
+
// project directory (the daemon's cwd is not), and the session names which project's flows.
|
|
49
|
+
const changed = await resolveChangedFiles(files, since, deps.reticleRoot);
|
|
50
|
+
const changedFiles = changed.files;
|
|
51
|
+
// No `sessionId` on this tool's surface, and it does not need one: passing `undefined`
|
|
52
|
+
// resolves the single connected session, and falls back exactly as before when it cannot.
|
|
53
|
+
// Adding an argument here would be a surface change to fix an addressing bug.
|
|
54
|
+
const flows = await loadNamedFlows(deps.fs, sessionRoot(deps, undefined), sessionProjectId(deps, undefined));
|
|
48
55
|
const result = affectedSavedFlows(flows, changedFiles);
|
|
49
56
|
// Three different situations produced the same empty answer: no files changed, no flows saved,
|
|
50
57
|
// and a git ref that resolved to nothing. Only the first means "nothing to re-verify"; the
|
|
51
58
|
// others mean the question could not be answered, and re-running nothing on that basis is how a
|
|
52
59
|
// regression ships. Say which one it is.
|
|
53
|
-
const why =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const why =
|
|
61
|
+
// A git failure OUTRANKS every other explanation: the others describe a question that was
|
|
62
|
+
// answered, and this one is a question that was not. Reporting "nothing changed" over a
|
|
63
|
+
// mistyped ref or a shallow clone is how a regression ships past the tool meant to catch it.
|
|
64
|
+
changed.failed
|
|
65
|
+
? `could not read the diff for \`${String(since)}\`, so NOTHING was compared and this result proves nothing: ${changed.reason ?? 'git failed'}`
|
|
66
|
+
: 0 === flows.length
|
|
67
|
+
? 'no saved flows exist yet, so nothing COULD be affected — record one with reticle_record then reticle_flow_save'
|
|
68
|
+
: 0 === changedFiles.length
|
|
69
|
+
? since === undefined && 0 === files.length
|
|
70
|
+
? 'no files were given and no `since` ref was passed, so nothing was compared — pass the files you edited, or a git ref'
|
|
71
|
+
: 'nothing changed against that input, so no saved flow needs re-verifying'
|
|
72
|
+
: undefined;
|
|
60
73
|
return {
|
|
61
74
|
changedFiles,
|
|
62
75
|
...result,
|
package/dist/flows/annotate.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { type Annotation, type AnnotateOutcome } from '@reticlehq/core';
|
|
|
7
7
|
* assert-signal → step.expect.signal (+ signalData) on the LAST step (needs ≥1 step)
|
|
8
8
|
* assert-visible → step.expect.element.testid on the LAST step (needs ≥1 step)
|
|
9
9
|
* mark-dynamic → flow.dynamic[] += testid flow-level, allowed with 0 steps
|
|
10
|
-
* success-state → flow.success = { signal |
|
|
10
|
+
* success-state → flow.success = { signal | state | net | console | element | text } flow-level,
|
|
11
|
+
* resolved in that precedence order (a consequence beats a presence check)
|
|
11
12
|
*
|
|
12
13
|
* FIRST CUT: only the four structured kinds above. Free natural-language annotation → predicate
|
|
13
14
|
* compilation is explicitly FUTURE — an NL string never reaches here (AnnotationSchema
|
package/dist/flows/annotate.js
CHANGED
|
@@ -7,7 +7,8 @@ import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICA
|
|
|
7
7
|
* assert-signal → step.expect.signal (+ signalData) on the LAST step (needs ≥1 step)
|
|
8
8
|
* assert-visible → step.expect.element.testid on the LAST step (needs ≥1 step)
|
|
9
9
|
* mark-dynamic → flow.dynamic[] += testid flow-level, allowed with 0 steps
|
|
10
|
-
* success-state → flow.success = { signal |
|
|
10
|
+
* success-state → flow.success = { signal | state | net | console | element | text } flow-level,
|
|
11
|
+
* resolved in that precedence order (a consequence beats a presence check)
|
|
11
12
|
*
|
|
12
13
|
* FIRST CUT: only the four structured kinds above. Free natural-language annotation → predicate
|
|
13
14
|
* compilation is explicitly FUTURE — an NL string never reaches here (AnnotationSchema
|
|
@@ -73,6 +74,19 @@ export function compileAnnotation(a, stepCount) {
|
|
|
73
74
|
if (a.testid !== undefined) {
|
|
74
75
|
return flowSuccess(a, { element: { testid: a.testid } });
|
|
75
76
|
}
|
|
77
|
+
// Last, and above only MISSING_FIELD. An element is a locator and text is content; where a
|
|
78
|
+
// caller offered both, the locator is the more stable anchor. But text beats nothing at all,
|
|
79
|
+
// which is what an app whose only observable is a rendered value used to get (#811).
|
|
80
|
+
if (a.text !== undefined) {
|
|
81
|
+
const text = { contains: a.text.contains };
|
|
82
|
+
if (a.text.scope !== undefined)
|
|
83
|
+
text.scope = a.text.scope;
|
|
84
|
+
if (a.text.absent !== undefined)
|
|
85
|
+
text.absent = a.text.absent;
|
|
86
|
+
if (a.text.visible !== undefined)
|
|
87
|
+
text.visible = a.text.visible;
|
|
88
|
+
return flowSuccess(a, { text });
|
|
89
|
+
}
|
|
76
90
|
return { result: { ok: false, code: AnnotationErrorCode.MISSING_FIELD } };
|
|
77
91
|
}
|
|
78
92
|
case AnnotationKind.INTENT:
|
|
@@ -153,6 +167,10 @@ export function describeCompiled(a) {
|
|
|
153
167
|
const level = a.console.level ?? 'error';
|
|
154
168
|
return `${COMPILED_PREDICATE_PREFIX} succeed when ${true === a.console.absent ? `no console.${level}` : `console.${level}`}`;
|
|
155
169
|
}
|
|
170
|
+
if (a.testid === undefined && a.text !== undefined) {
|
|
171
|
+
const where = a.text.scope === undefined ? '' : ` in ${a.text.scope}`;
|
|
172
|
+
return `${COMPILED_PREDICATE_PREFIX} succeed when ${true === a.text.absent ? 'text is gone:' : 'text shows:'} ${JSON.stringify(a.text.contains)}${where}`;
|
|
173
|
+
}
|
|
156
174
|
return `${COMPILED_PREDICATE_PREFIX} succeed when ${a.testid ?? ''} visible`;
|
|
157
175
|
case AnnotationKind.INTENT:
|
|
158
176
|
return `${COMPILED_PREDICATE_PREFIX} intent: ${a.text}`;
|
package/dist/flows/coverage.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ interface DeclaredSurface {
|
|
|
9
9
|
signals: readonly string[];
|
|
10
10
|
flows: readonly string[];
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
interface CoverageDimension {
|
|
13
13
|
total: number;
|
|
14
14
|
covered: number;
|
|
15
15
|
/**
|
|
@@ -34,7 +34,7 @@ export interface Coverage {
|
|
|
34
34
|
/** The gate's outcome when the project has recorded no flows at all — not a pass, and not a failure. */
|
|
35
35
|
export declare const NO_FLOWS = "no_flows";
|
|
36
36
|
/** The gate's flow-coverage line. `pct` and `outcome` are mutually exclusive by construction. */
|
|
37
|
-
|
|
37
|
+
interface FlowCoverageReport {
|
|
38
38
|
covered: number;
|
|
39
39
|
total: number;
|
|
40
40
|
pct?: number;
|
package/dist/flows/flake.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ declare const FlakeRecordSchema: z.ZodObject<{
|
|
|
15
15
|
runs: number;
|
|
16
16
|
fails: number;
|
|
17
17
|
}>;
|
|
18
|
-
|
|
18
|
+
type FlakeRecord = z.infer<typeof FlakeRecordSchema>;
|
|
19
19
|
/** Per-flow map (flow name → record). */
|
|
20
20
|
export type FlakeLedger = Record<string, FlakeRecord>;
|
|
21
21
|
export declare const FlakeFileSchema: z.ZodObject<{
|
|
@@ -31,17 +31,17 @@ export declare const FlakeFileSchema: z.ZodObject<{
|
|
|
31
31
|
fails: number;
|
|
32
32
|
}>>;
|
|
33
33
|
}, "strip", z.ZodTypeAny, {
|
|
34
|
-
version: 1;
|
|
35
34
|
flows: Record<string, {
|
|
36
35
|
runs: number;
|
|
37
36
|
fails: number;
|
|
38
37
|
}>;
|
|
39
|
-
}, {
|
|
40
38
|
version: 1;
|
|
39
|
+
}, {
|
|
41
40
|
flows: Record<string, {
|
|
42
41
|
runs: number;
|
|
43
42
|
fails: number;
|
|
44
43
|
}>;
|
|
44
|
+
version: 1;
|
|
45
45
|
}>;
|
|
46
46
|
export declare function emptyRecord(): FlakeRecord;
|
|
47
47
|
/** Accrue one unchanged-code replay outcome. */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accept the assertion grammar a drive already uses, in a saved flow file.
|
|
3
|
+
*
|
|
4
|
+
* FlowExpect is a flat object (`signal` is a name string; `net`/`state` sit beside it). Agents write
|
|
5
|
+
* the `act_and_wait` shape instead — `{ kind: "allOf", predicates: [...] }`, or sibling channels
|
|
6
|
+
* with `signal: { name, count }` — and replay answered `flow_parse_failed` / "malformed". That sent
|
|
7
|
+
* people looking for a JSON syntax error in a file they had just written, and the workaround was to
|
|
8
|
+
* drop every channel but one.
|
|
9
|
+
*
|
|
10
|
+
* Coercion happens at load: the on-disk schema does not move, FLOW_FILE_VERSION stays 1, and a
|
|
11
|
+
* re-save writes the canonical shape. A kind a saved flow cannot enforce is refused, not stripped
|
|
12
|
+
* to an empty expect that would grade asserted-while-unchecked.
|
|
13
|
+
*/
|
|
14
|
+
import { type FlowFile } from '@reticlehq/core';
|
|
15
|
+
import type { ZodError } from 'zod';
|
|
16
|
+
import type { FlowResult } from './flows.js';
|
|
17
|
+
export declare const FlowParseNote: {
|
|
18
|
+
readonly NOT_JSON: "flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save";
|
|
19
|
+
readonly MALFORMED: "flow file is malformed — fix or regenerate it with reticle_flow_save";
|
|
20
|
+
readonly UNSUPPORTED_SHAPE: "valid JSON, unsupported expect shape";
|
|
21
|
+
readonly UNENFORCED: "valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)";
|
|
22
|
+
};
|
|
23
|
+
export type CoerceExpectResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
value: unknown;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
detail: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function coerceFlowExpect(raw: unknown): CoerceExpectResult;
|
|
31
|
+
export declare function coerceFlowFileExpects(raw: unknown): CoerceExpectResult;
|
|
32
|
+
export declare function describeFlowZodFailure(error: ZodError): string;
|
|
33
|
+
export declare function parseFlowFileText(text: string): FlowResult<FlowFile>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accept the assertion grammar a drive already uses, in a saved flow file.
|
|
3
|
+
*
|
|
4
|
+
* FlowExpect is a flat object (`signal` is a name string; `net`/`state` sit beside it). Agents write
|
|
5
|
+
* the `act_and_wait` shape instead — `{ kind: "allOf", predicates: [...] }`, or sibling channels
|
|
6
|
+
* with `signal: { name, count }` — and replay answered `flow_parse_failed` / "malformed". That sent
|
|
7
|
+
* people looking for a JSON syntax error in a file they had just written, and the workaround was to
|
|
8
|
+
* drop every channel but one.
|
|
9
|
+
*
|
|
10
|
+
* Coercion happens at load: the on-disk schema does not move, FLOW_FILE_VERSION stays 1, and a
|
|
11
|
+
* re-save writes the canonical shape. A kind a saved flow cannot enforce is refused, not stripped
|
|
12
|
+
* to an empty expect that would grade asserted-while-unchecked.
|
|
13
|
+
*/
|
|
14
|
+
import { FlowErrorCode, FlowFileSchema } from '@reticlehq/core';
|
|
15
|
+
import { PredicateSchema } from '../events/predicate.js';
|
|
16
|
+
import { predicateToExpect } from './predicate-to-expect.js';
|
|
17
|
+
export const FlowParseNote = {
|
|
18
|
+
NOT_JSON: 'flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save',
|
|
19
|
+
MALFORMED: 'flow file is malformed — fix or regenerate it with reticle_flow_save',
|
|
20
|
+
UNSUPPORTED_SHAPE: 'valid JSON, unsupported expect shape',
|
|
21
|
+
UNENFORCED: 'valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)',
|
|
22
|
+
};
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return 'object' === typeof value && null !== value && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Flatten `signal: { name, count }` into the on-disk fields. Other channels already match FlowExpect.
|
|
28
|
+
* Leave a signal object with no name alone so the schema failure can name the key.
|
|
29
|
+
*/
|
|
30
|
+
function flattenSignalObject(raw) {
|
|
31
|
+
const signal = raw['signal'];
|
|
32
|
+
if (!isRecord(signal))
|
|
33
|
+
return raw;
|
|
34
|
+
const name = signal['name'];
|
|
35
|
+
if ('string' !== typeof name)
|
|
36
|
+
return raw;
|
|
37
|
+
const out = { ...raw, signal: name };
|
|
38
|
+
if ('number' === typeof signal['count'])
|
|
39
|
+
out['signalCount'] = signal['count'];
|
|
40
|
+
if (undefined !== signal['dataMatches'])
|
|
41
|
+
out['signalData'] = signal['dataMatches'];
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
export function coerceFlowExpect(raw) {
|
|
45
|
+
if (isRecord(raw) && 'kind' in raw) {
|
|
46
|
+
const parsed = PredicateSchema.safeParse(raw);
|
|
47
|
+
if (!parsed.success) {
|
|
48
|
+
return { ok: false, detail: describePredicateExpectFailure(parsed.error) };
|
|
49
|
+
}
|
|
50
|
+
const expect = predicateToExpect(parsed.data);
|
|
51
|
+
if (undefined === expect)
|
|
52
|
+
return { ok: false, detail: FlowParseNote.UNENFORCED };
|
|
53
|
+
return { ok: true, value: expect };
|
|
54
|
+
}
|
|
55
|
+
if (!isRecord(raw))
|
|
56
|
+
return { ok: true, value: raw };
|
|
57
|
+
return { ok: true, value: flattenSignalObject(raw) };
|
|
58
|
+
}
|
|
59
|
+
function describePredicateExpectFailure(error) {
|
|
60
|
+
const issue = error.issues[0];
|
|
61
|
+
if (undefined === issue)
|
|
62
|
+
return FlowParseNote.UNSUPPORTED_SHAPE;
|
|
63
|
+
const key = issue.path[0];
|
|
64
|
+
if ('string' === typeof key || 'number' === typeof key) {
|
|
65
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} key "${String(key)}"`;
|
|
66
|
+
}
|
|
67
|
+
return FlowParseNote.UNSUPPORTED_SHAPE;
|
|
68
|
+
}
|
|
69
|
+
export function coerceFlowFileExpects(raw) {
|
|
70
|
+
if (!isRecord(raw))
|
|
71
|
+
return { ok: true, value: raw };
|
|
72
|
+
const next = { ...raw };
|
|
73
|
+
const stepsIn = next['steps'];
|
|
74
|
+
if (Array.isArray(stepsIn)) {
|
|
75
|
+
const steps = [];
|
|
76
|
+
for (let i = 0; i < stepsIn.length; i++) {
|
|
77
|
+
const step = stepsIn[i];
|
|
78
|
+
if (!isRecord(step) || undefined === step['expect']) {
|
|
79
|
+
steps.push(step);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const coerced = coerceFlowExpect(step['expect']);
|
|
83
|
+
if (!coerced.ok) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
detail: `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(i)}: ${coerced.detail}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
steps.push({ ...step, expect: coerced.value });
|
|
90
|
+
}
|
|
91
|
+
next['steps'] = steps;
|
|
92
|
+
}
|
|
93
|
+
if (undefined !== next['success']) {
|
|
94
|
+
const coerced = coerceFlowExpect(next['success']);
|
|
95
|
+
if (!coerced.ok) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
detail: `${FlowParseNote.UNSUPPORTED_SHAPE} at success: ${coerced.detail}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
next['success'] = coerced.value;
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, value: next };
|
|
104
|
+
}
|
|
105
|
+
export function describeFlowZodFailure(error) {
|
|
106
|
+
const issue = error.issues[0];
|
|
107
|
+
if (undefined === issue)
|
|
108
|
+
return FlowParseNote.MALFORMED;
|
|
109
|
+
const path = issue.path;
|
|
110
|
+
// A strict object reports an unrecognized key as `unrecognized_keys` on the OBJECT's path, with
|
|
111
|
+
// the offending names in `keys` — not as an issue whose path ends in the key. Reading only the
|
|
112
|
+
// path shape reported the one failure this exists to name as a bare "malformed", which sends the
|
|
113
|
+
// author back to the file to find a typo we had already identified.
|
|
114
|
+
if ('unrecognized_keys' === issue.code) {
|
|
115
|
+
const named = issue.keys.map((k) => `"${k}"`).join(', ');
|
|
116
|
+
if ('steps' === path[0] && 'number' === typeof path[1] && 'expect' === path[2]) {
|
|
117
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(path[1])} key ${named}`;
|
|
118
|
+
}
|
|
119
|
+
if ('success' === path[0])
|
|
120
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} at success key ${named}`;
|
|
121
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} — unrecognized key ${named}`;
|
|
122
|
+
}
|
|
123
|
+
if ('steps' === path[0] &&
|
|
124
|
+
'number' === typeof path[1] &&
|
|
125
|
+
'expect' === path[2] &&
|
|
126
|
+
'string' === typeof path[3]) {
|
|
127
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(path[1])} key "${path[3]}"`;
|
|
128
|
+
}
|
|
129
|
+
if ('success' === path[0] && 'string' === typeof path[1]) {
|
|
130
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} at success key "${path[1]}"`;
|
|
131
|
+
}
|
|
132
|
+
return FlowParseNote.MALFORMED;
|
|
133
|
+
}
|
|
134
|
+
export function parseFlowFileText(text) {
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = JSON.parse(text);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: FlowParseNote.NOT_JSON };
|
|
141
|
+
}
|
|
142
|
+
const coerced = coerceFlowFileExpects(parsed);
|
|
143
|
+
if (!coerced.ok) {
|
|
144
|
+
return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: coerced.detail };
|
|
145
|
+
}
|
|
146
|
+
const result = FlowFileSchema.safeParse(coerced.value);
|
|
147
|
+
if (!result.success) {
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
code: FlowErrorCode.PARSE_FAILED,
|
|
151
|
+
detail: describeFlowZodFailure(result.error),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return { ok: true, value: result.data };
|
|
155
|
+
}
|
|
@@ -8,7 +8,7 @@ export declare function latestRecordedFlow(events: ReticleEvent[]): {
|
|
|
8
8
|
flow: import('@reticlehq/core').FlowFile;
|
|
9
9
|
} | undefined;
|
|
10
10
|
/** Map a structured FlowErrorCode to a legible one-line message for the agent. */
|
|
11
|
-
export declare function flowErrorMessage(code: FlowErrorCode): string;
|
|
11
|
+
export declare function flowErrorMessage(code: FlowErrorCode, detail?: string): string;
|
|
12
12
|
/** The slice of a session the start-path logic reads: the live URL plus the event buffer. */
|
|
13
13
|
interface StartPathSession {
|
|
14
14
|
url?: string;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { resolveFlowUploads } from './flow-upload-resolve.js';
|
|
1
2
|
import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, ReticleCommand, RunKind, RunStatus, } from '@reticlehq/core';
|
|
3
|
+
import { haltedFrom } from './replay-halt.js';
|
|
2
4
|
import { asRecord, asString } from '../tools/tools-helpers.js';
|
|
5
|
+
import { routeOfEvent, routeOfUrl } from '../events/predicate-route.js';
|
|
3
6
|
import { carryReticleIdentity } from '../tools/lease-tools.js';
|
|
4
7
|
import { replayFlow } from './flow-replay.js';
|
|
5
8
|
import { anchorQueryArgs } from './flow-step-runners.js';
|
|
@@ -22,6 +25,7 @@ import { resolveProjectCloud } from '../cloud/cloud-config.js';
|
|
|
22
25
|
import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
|
|
23
26
|
import { log } from '../log.js';
|
|
24
27
|
import { flowsForSession } from './flow-store-for-session.js';
|
|
28
|
+
import { FlowParseNote } from './flow-expect-grammar.js';
|
|
25
29
|
export function latestRecordedFlow(events) {
|
|
26
30
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
27
31
|
const event = events[i];
|
|
@@ -34,14 +38,16 @@ export function latestRecordedFlow(events) {
|
|
|
34
38
|
return undefined;
|
|
35
39
|
}
|
|
36
40
|
/** Map a structured FlowErrorCode to a legible one-line message for the agent. */
|
|
37
|
-
export function flowErrorMessage(code) {
|
|
41
|
+
export function flowErrorMessage(code, detail) {
|
|
42
|
+
if (FlowErrorCode.PARSE_FAILED === code && undefined !== detail)
|
|
43
|
+
return detail;
|
|
38
44
|
switch (code) {
|
|
39
45
|
case FlowErrorCode.INVALID_NAME:
|
|
40
46
|
return 'invalid flow name — use a single safe segment (letters/digits/-/_), no path separators';
|
|
41
47
|
case FlowErrorCode.NOT_FOUND:
|
|
42
48
|
return 'no such flow on disk — run reticle_flow{action:"list"} to see saved flows';
|
|
43
49
|
case FlowErrorCode.PARSE_FAILED:
|
|
44
|
-
return
|
|
50
|
+
return FlowParseNote.MALFORMED;
|
|
45
51
|
case FlowErrorCode.NO_RECORDING:
|
|
46
52
|
return 'no compiled recording by that name — record one (reticle_record{action:"start"|"stop"}) first';
|
|
47
53
|
}
|
|
@@ -93,22 +99,17 @@ recordRoot) {
|
|
|
93
99
|
*/
|
|
94
100
|
function currentPathOf(session) {
|
|
95
101
|
const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
|
|
96
|
-
const
|
|
102
|
+
const last = routes.at(-1);
|
|
97
103
|
// pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE. Reading
|
|
98
104
|
// the pathname alone made both sides `/` on a hash router — always "same path", so the hint never
|
|
99
105
|
// fired however far the tab had drifted, on the router desktop renderers use by default.
|
|
100
|
-
const observed =
|
|
106
|
+
const observed = last === undefined ? undefined : routeOfEvent(last);
|
|
101
107
|
if (observed !== undefined)
|
|
102
|
-
return `${observed}${
|
|
108
|
+
return `${observed.docPath}${observed.hash}`;
|
|
103
109
|
if (session.url === undefined)
|
|
104
110
|
return undefined;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return `${parsed.pathname}${parsed.hash}`;
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
111
|
+
const fromUrl = routeOfUrl(session.url);
|
|
112
|
+
return fromUrl === undefined ? undefined : `${fromUrl.docPath}${fromUrl.hash}`;
|
|
112
113
|
}
|
|
113
114
|
/** Pathname equality up to a trailing slash — a router normalising one must not read as "elsewhere". */
|
|
114
115
|
function samePath(a, b) {
|
|
@@ -318,7 +319,7 @@ export async function replayNamedFlow(deps, args) {
|
|
|
318
319
|
name,
|
|
319
320
|
status: ReplayStatus.ERROR,
|
|
320
321
|
steps: [],
|
|
321
|
-
error: { code: loaded.code, message: flowErrorMessage(loaded.code) },
|
|
322
|
+
error: { code: loaded.code, message: flowErrorMessage(loaded.code, loaded.detail) },
|
|
322
323
|
};
|
|
323
324
|
}
|
|
324
325
|
// What this flow is FOR, from the shared ledger — so a failure can report the business outcome
|
|
@@ -341,13 +342,23 @@ export async function replayNamedFlow(deps, args) {
|
|
|
341
342
|
// Floor the success oracle at the start of THIS replay so a stale signal from a prior run
|
|
342
343
|
// in the same session can't fake a pass.
|
|
343
344
|
const replayFloor = session.elapsed();
|
|
344
|
-
|
|
345
|
+
// A recorded upload names a path on disk; the browser can only take bytes. Resolved once, before
|
|
346
|
+
// step 1, through the same helper the live `reticle_act` uses. See resolveFlowUploads.
|
|
347
|
+
const replayable = await resolveFlowUploads(deps, loaded.value);
|
|
348
|
+
const steps = await replayFlow(session, replayable, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous']);
|
|
349
|
+
// Computed HERE, before the synthetic success row is appended below: once that row is pushed,
|
|
350
|
+
// `steps.length` no longer counts only the flow's own steps and the arithmetic is wrong.
|
|
351
|
+
const halted = haltedFrom(steps, loaded.value.steps.length);
|
|
345
352
|
// "green means intent satisfied": when every step ran clean, assert the flow's success
|
|
346
353
|
// end-condition as a real consequence. A signal/net success that never fires FAILS the replay
|
|
347
354
|
// even though all locators resolved — the regression a healed-but-wrong locator ships green.
|
|
348
355
|
const stepsClean = steps.length > 0 && steps.every((s) => s.ok && s.drift === undefined);
|
|
349
356
|
if (stepsClean && loaded.value.success !== undefined) {
|
|
350
|
-
const verdict = await assertSuccess(session, loaded.value.success, dynamicTestids(loaded.value), waitForPredicate,
|
|
357
|
+
const verdict = await assertSuccess(session, loaded.value.success, dynamicTestids(loaded.value), waitForPredicate,
|
|
358
|
+
// The flow's own declaration, not the built-in floor: an app slow enough to need a longer
|
|
359
|
+
// step wait is slow enough that its OUTCOME lands late too, and greening every step only to
|
|
360
|
+
// fail the success oracle at 4s is the same false red one layer down.
|
|
361
|
+
loaded.value.signalTimeoutMs ?? FLOW_SIGNAL_TIMEOUT_MS, replayFloor);
|
|
351
362
|
const row = {
|
|
352
363
|
step: steps.length,
|
|
353
364
|
tool: SUCCESS_STEP_TOOL,
|
|
@@ -412,6 +423,8 @@ export async function replayNamedFlow(deps, args) {
|
|
|
412
423
|
return errored;
|
|
413
424
|
}
|
|
414
425
|
const result = { name, status, steps };
|
|
426
|
+
if (halted !== undefined)
|
|
427
|
+
result.halted = halted;
|
|
415
428
|
if (knows !== undefined)
|
|
416
429
|
result.knows = knows;
|
|
417
430
|
// A green that cannot go red is not a pass. `reticle_flow_verify` already refuses to count these,
|
|
@@ -49,6 +49,16 @@ export type Sleep = (ms: number) => Promise<void>;
|
|
|
49
49
|
export declare function nearestIsAmbiguous(missing: string, present: string[]): boolean;
|
|
50
50
|
/** Build the legible-drift record for a testid anchor that resolved to zero live elements. */
|
|
51
51
|
export declare function testidDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
|
|
52
|
+
/**
|
|
53
|
+
* Build the drift for a step whose `expect.element` testid was absent after the action ran.
|
|
54
|
+
*
|
|
55
|
+
* Deliberately not `testidDrift`: that one means "this step's anchor is gone", and reusing it here
|
|
56
|
+
* put the assertion's target in the result's `anchor` field — the field documented as the value the
|
|
57
|
+
* step is bound to. A caller then reads `testid_not_found` naming something that was never the
|
|
58
|
+
* step's locator and goes hunting for a rename, while the truth is the step ran and its
|
|
59
|
+
* consequence did not hold.
|
|
60
|
+
*/
|
|
61
|
+
export declare function expectElementDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
|
|
52
62
|
export declare function resolveQuery(session: FlowReplaySession, queryArgs: Record<string, unknown>, sleep: Sleep, now?: () => number): Promise<{
|
|
53
63
|
refs: string[];
|
|
54
64
|
hint?: QueryEmptyHint;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { span } from '../trace.js';
|
|
2
|
-
import {
|
|
2
|
+
import { routeOfEvent } from '../events/predicate-route.js';
|
|
3
3
|
import { AnchorKind, DriftReason, EventType, ReticleCommand, QueryBy, PredicateKind, } from '@reticlehq/core';
|
|
4
4
|
import { asRecord, asString } from '../tools/tools-helpers.js';
|
|
5
5
|
import { replayActionArgs, ambiguousTestidNote, queryRefs } from './replay.js';
|
|
6
|
+
import { anchorFieldName } from './flow-secret-field.js';
|
|
6
7
|
import { degradedStepResult, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
|
|
7
8
|
import { successToPredicate } from './flow-success.js';
|
|
8
9
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
@@ -138,6 +139,27 @@ export function testidDrift(value, hint) {
|
|
|
138
139
|
drift.ambiguous = true;
|
|
139
140
|
return drift;
|
|
140
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Build the drift for a step whose `expect.element` testid was absent after the action ran.
|
|
144
|
+
*
|
|
145
|
+
* Deliberately not `testidDrift`: that one means "this step's anchor is gone", and reusing it here
|
|
146
|
+
* put the assertion's target in the result's `anchor` field — the field documented as the value the
|
|
147
|
+
* step is bound to. A caller then reads `testid_not_found` naming something that was never the
|
|
148
|
+
* step's locator and goes hunting for a rename, while the truth is the step ran and its
|
|
149
|
+
* consequence did not hold.
|
|
150
|
+
*/
|
|
151
|
+
export function expectElementDrift(value, hint) {
|
|
152
|
+
const present = hint?.presentTestids ?? [];
|
|
153
|
+
const drift = {
|
|
154
|
+
reasonKind: DriftReason.EXPECT_ELEMENT_NOT_FOUND,
|
|
155
|
+
reason: `expect.element testid "${value}" not present after the action`,
|
|
156
|
+
anchor: value,
|
|
157
|
+
nearest: nearestTestid(value, present),
|
|
158
|
+
};
|
|
159
|
+
if (nearestIsAmbiguous(value, present))
|
|
160
|
+
drift.ambiguous = true;
|
|
161
|
+
return drift;
|
|
162
|
+
}
|
|
141
163
|
/**
|
|
142
164
|
* Re-resolve any QUERY against the live DOM, tolerating an in-flight render: QUERY, and while it
|
|
143
165
|
* returns zero refs, sleep and retry up to ANCHOR_SETTLE_ATTEMPTS. Returns as soon as refs appear,
|
|
@@ -198,15 +220,13 @@ function currentRoute(session) {
|
|
|
198
220
|
const last = routes.at(-1);
|
|
199
221
|
if (last === undefined)
|
|
200
222
|
return undefined;
|
|
201
|
-
const data = last.data ?? {};
|
|
202
223
|
// The ROUTER's path. This field answers "which page did this step run on", and the document
|
|
203
224
|
// pathname is `/` on every page of a hash-routed app — so a whole desktop replay reported `/` for
|
|
204
|
-
// every step. Sixth place the same reading was wrong; see
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
225
|
+
// every step. Sixth place the same reading was wrong; see routeOfEvent.
|
|
226
|
+
const parts = routeOfEvent(last);
|
|
227
|
+
if (parts === undefined || 0 === parts.docPath.length)
|
|
207
228
|
return undefined;
|
|
208
|
-
|
|
209
|
-
return routed.length > 0 ? routed : undefined;
|
|
229
|
+
return parts.routePath.length > 0 ? parts.routePath : undefined;
|
|
210
230
|
}
|
|
211
231
|
/** Pathname only (drop origin + query) so a net URL stays terse in the journey. */
|
|
212
232
|
function trimUrl(url) {
|
|
@@ -226,10 +246,9 @@ function summarizeConsequence(events) {
|
|
|
226
246
|
const parts = [];
|
|
227
247
|
const lastRoute = events.filter((e) => e.type === EventType.ROUTE_CHANGE).at(-1);
|
|
228
248
|
if (lastRoute !== undefined) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
parts.push(`→ ${to}`);
|
|
249
|
+
const routed = routeOfEvent(lastRoute);
|
|
250
|
+
if (routed !== undefined && routed.routePath.length > 0)
|
|
251
|
+
parts.push(`→ ${routed.routePath}`);
|
|
233
252
|
}
|
|
234
253
|
const signals = new Set();
|
|
235
254
|
for (const e of events) {
|
|
@@ -301,9 +320,11 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
301
320
|
act = await session.command(ReticleCommand.ACT, {
|
|
302
321
|
ref,
|
|
303
322
|
action: step.action ?? '',
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
|
|
323
|
+
// The field this step types into — from the anchor, so a redacted fill can be supplied from
|
|
324
|
+
// RETICLE_SECRET_<FIELD> without the flow carrying the secret. The testid runner used to pass
|
|
325
|
+
// the testid string here and the other two runners passed nothing, so a role-anchored login
|
|
326
|
+
// typed the literal placeholder.
|
|
327
|
+
args: replayActionArgs(step.args, confirmDangerous, anchorFieldName(step.anchor)),
|
|
307
328
|
});
|
|
308
329
|
}
|
|
309
330
|
finally {
|
|
@@ -326,9 +347,12 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
326
347
|
return {
|
|
327
348
|
step: index,
|
|
328
349
|
tool: step.tool,
|
|
329
|
-
anchor
|
|
350
|
+
// The step's OWN anchor, not the expectation's target. Replay stops at the first drift, so
|
|
351
|
+
// this result is the only thing the caller sees about why the run ended — naming the
|
|
352
|
+
// assertion here reads as "step N's locator drifted" and hides that the action did run.
|
|
353
|
+
anchor: value,
|
|
330
354
|
ok: false,
|
|
331
|
-
drift:
|
|
355
|
+
drift: expectElementDrift(expectTestid, expectRefs.hint),
|
|
332
356
|
};
|
|
333
357
|
}
|
|
334
358
|
}
|
|
@@ -350,10 +374,13 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
350
374
|
* nothing was looking.
|
|
351
375
|
*/
|
|
352
376
|
async function assertStepExpect(session, expect, dynamic, waitForSignal, timeoutMs, since) {
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
const
|
|
377
|
+
// A testid is already asserted against the live DOM by the step runner. A role/name locator is
|
|
378
|
+
// not that path — stripping every element made a recorded `until` by button name a no-op, so a
|
|
379
|
+
// flow that proved the control at capture time could not go red when it was gone.
|
|
380
|
+
const consequences = { ...expect };
|
|
381
|
+
if (undefined !== consequences.element?.testid) {
|
|
382
|
+
delete consequences.element;
|
|
383
|
+
}
|
|
357
384
|
const predicate = successToPredicate(consequences, dynamic);
|
|
358
385
|
if (predicate === undefined)
|
|
359
386
|
return undefined;
|
|
@@ -379,6 +406,9 @@ function expectLabel(expect) {
|
|
|
379
406
|
return `state:${expect.state.path}`;
|
|
380
407
|
if (expect.console !== undefined)
|
|
381
408
|
return `console:${expect.console.level ?? '*'}`;
|
|
409
|
+
if (undefined !== expect.element) {
|
|
410
|
+
return expect.element.testid ?? expect.element.name ?? expect.element.role ?? 'element';
|
|
411
|
+
}
|
|
382
412
|
return 'expect';
|
|
383
413
|
}
|
|
384
414
|
/** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
|
|
@@ -423,6 +453,11 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
423
453
|
// Floor for signal steps: signals that fire during THIS replay, never a prior flow/run in the same
|
|
424
454
|
// session. Captured once, before any step, so a back-to-back suite verify cannot cross-satisfy.
|
|
425
455
|
const replayFloor = session.elapsed();
|
|
456
|
+
// How long a step waits for its consequence: the step's own declaration, else the flow's, else the
|
|
457
|
+
// caller's default. Resolved per step rather than once, because one slow step in an otherwise fast
|
|
458
|
+
// journey is the common shape — a file import, a model-backed endpoint — and making the whole flow
|
|
459
|
+
// wait for the slowest step would trade a false red for a slow suite. See FlowStep.timeoutMs.
|
|
460
|
+
const waitFor = (step) => step.timeoutMs ?? flow.signalTimeoutMs ?? signalTimeoutMs;
|
|
426
461
|
let index = 0;
|
|
427
462
|
for (const step of flow.steps) {
|
|
428
463
|
const label = anchorLabel(step.anchor);
|
|
@@ -443,7 +478,7 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
443
478
|
return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep);
|
|
444
479
|
}
|
|
445
480
|
if (step.anchor.kind === AnchorKind.SIGNAL) {
|
|
446
|
-
return runSignalStep(session, step, index, label, waitForSignal,
|
|
481
|
+
return runSignalStep(session, step, index, label, waitForSignal, waitFor(step), replayFloor);
|
|
447
482
|
}
|
|
448
483
|
if (step.anchor.kind === AnchorKind.COMPONENT) {
|
|
449
484
|
return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
@@ -459,7 +494,7 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
459
494
|
// console and store truth alike — deterministically, in the same cheap replay loop, with no LLM.
|
|
460
495
|
const stepExpect = step.expect;
|
|
461
496
|
if (result.ok && result.drift === undefined && stepExpect !== undefined) {
|
|
462
|
-
const expectDrift = await assertStepExpect(session, stepExpect, dynamic, waitForSignal,
|
|
497
|
+
const expectDrift = await assertStepExpect(session, stepExpect, dynamic, waitForSignal, waitFor(step), cursorBefore);
|
|
463
498
|
if (expectDrift !== undefined) {
|
|
464
499
|
result.ok = false;
|
|
465
500
|
result.drift = expectDrift;
|