@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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FlowAnchor } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* What the anchor CALLS the field it points at.
|
|
4
|
+
*
|
|
5
|
+
* Every anchor kind names its target differently and all of them can name a password: a testid
|
|
6
|
+
* (auth-password), an accessible name (role=textbox, name="Password"), a signal. Checking only the
|
|
7
|
+
* testid variant would redact the app that uses test ids and quietly leak the one that does not —
|
|
8
|
+
* and an app without test ids is exactly the app whose flows were recorded by role.
|
|
9
|
+
*
|
|
10
|
+
* Save and replay MUST share this. Redaction keys off the field name; substitution looks up
|
|
11
|
+
* `RETICLE_SECRET_<FIELD>` from the same string. Two copies would redact `Password` and then fail
|
|
12
|
+
* to find the env var because the runner passed a role or a label instead.
|
|
13
|
+
*/
|
|
14
|
+
export declare function anchorFieldName(anchor: FlowAnchor): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AnchorKind } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* What the anchor CALLS the field it points at.
|
|
4
|
+
*
|
|
5
|
+
* Every anchor kind names its target differently and all of them can name a password: a testid
|
|
6
|
+
* (auth-password), an accessible name (role=textbox, name="Password"), a signal. Checking only the
|
|
7
|
+
* testid variant would redact the app that uses test ids and quietly leak the one that does not —
|
|
8
|
+
* and an app without test ids is exactly the app whose flows were recorded by role.
|
|
9
|
+
*
|
|
10
|
+
* Save and replay MUST share this. Redaction keys off the field name; substitution looks up
|
|
11
|
+
* `RETICLE_SECRET_<FIELD>` from the same string. Two copies would redact `Password` and then fail
|
|
12
|
+
* to find the env var because the runner passed a role or a label instead.
|
|
13
|
+
*/
|
|
14
|
+
export function anchorFieldName(anchor) {
|
|
15
|
+
if (AnchorKind.TESTID === anchor.kind)
|
|
16
|
+
return anchor.value;
|
|
17
|
+
if (AnchorKind.ROLE === anchor.kind)
|
|
18
|
+
return anchor.name;
|
|
19
|
+
if (AnchorKind.SIGNAL === anchor.kind)
|
|
20
|
+
return anchor.name;
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
@@ -11,6 +11,7 @@ import type { FlowReplaySession, Sleep } from './flow-replay.js';
|
|
|
11
11
|
export declare function runRoleStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
|
|
12
12
|
kind: typeof AnchorKind.ROLE;
|
|
13
13
|
}>, confirmDangerous: boolean, sleep: Sleep): Promise<FlowStepResult>;
|
|
14
|
+
export declare function replayDestructiveActionHint(rawError: string): string;
|
|
14
15
|
/** Run one component-anchored step: re-resolve via QUERY by:'component', ACT on the live ref, else drift. */
|
|
15
16
|
export declare function runComponentStep(session: FlowReplaySession, step: FlowStep, index: number, anchor: Extract<FlowAnchor, {
|
|
16
17
|
kind: typeof AnchorKind.COMPONENT;
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
|
|
10
10
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
11
11
|
import { replayActionArgs } from './replay.js';
|
|
12
|
+
import { isStaleRefError } from '../tools/act-sequence-retry.js';
|
|
13
|
+
import { waitForReaction } from '../tools/react-grace.js';
|
|
14
|
+
import { anchorFieldName } from './flows.js';
|
|
12
15
|
import { anchorLabel, componentLabel, componentQueryArgs, resolveQuery, testidDrift, } from './flow-replay.js';
|
|
13
16
|
import { nearestRoleName } from './role-anchor-nearest.js';
|
|
14
17
|
import { roleDriftReason } from './role-drift-reason.js';
|
|
@@ -81,30 +84,98 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
81
84
|
},
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
|
-
return await actOnResolvedRef(session, step, index, label, ref, confirmDangerous)
|
|
87
|
+
return await actOnResolvedRef(session, step, index, label, ref, confirmDangerous, anchorFieldName(anchor),
|
|
88
|
+
// The retry's whole point: run the SAME locator against the DOM as it is now.
|
|
89
|
+
async () => (await resolveQuery(session, roleQueryArgs(anchor), sleep)).refs[0]);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `assertActionAllowed`'s refusal — "potentially destructive action blocked; retry with
|
|
93
|
+
* args.confirmDangerous=true" — is written for a LIVE call, where retrying with that arg works. It
|
|
94
|
+
* does not during replay: `replayActionArgs` deliberately strips any `confirmDangerous` a step's own
|
|
95
|
+
* args carry and restores it only from the ONE call-level boolean this run was given, because a
|
|
96
|
+
* destructive confirmation has to be a decision made for THIS run, never a value saved into the
|
|
97
|
+
* recording (#94). A caller who hand-edits the flow file's step args to add it gets it silently
|
|
98
|
+
* deleted, replays again, and reads the identical refusal — which reads as though nothing they tried
|
|
99
|
+
* worked, when what happened is they tried the one thing that cannot work here.
|
|
100
|
+
*
|
|
101
|
+
* Appends the path that does, rather than replacing the message: an agent that has already learned
|
|
102
|
+
* to recognise the raw refusal still finds it, plus the fix for this specific caller.
|
|
103
|
+
*/
|
|
104
|
+
const DESTRUCTIVE_ACTION_PATTERN = /potentially destructive (?:\w+ )*(?:action|tool) blocked/i;
|
|
105
|
+
export function replayDestructiveActionHint(rawError) {
|
|
106
|
+
if (!DESTRUCTIVE_ACTION_PATTERN.test(rawError))
|
|
107
|
+
return rawError;
|
|
108
|
+
return (`${rawError} — this is a flow REPLAY: a step's own args.confirmDangerous is stripped before ` +
|
|
109
|
+
'dispatch and never persists, so editing the flow file does nothing. Pass it at the call level ' +
|
|
110
|
+
`instead: ${ReticleTool.FLOW_REPLAY} { confirmDangerous: true } acknowledges every flagged step ` +
|
|
111
|
+
// `reticle_verify{action:"flows"}` (the batch suite runner) has no such argument today — naming
|
|
112
|
+
// it here would repeat the exact defect this message exists to fix, so the honest statement is
|
|
113
|
+
// that a flow with a flagged step currently has to be replayed on its own to pass it.
|
|
114
|
+
'for this run. The batch reticle_verify{action:"flows"} has no equivalent option yet, so a flow ' +
|
|
115
|
+
'with a flagged step has to be replayed on its own to acknowledge it.');
|
|
85
116
|
}
|
|
86
117
|
/**
|
|
87
118
|
* Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
|
|
88
119
|
* including the action window, whose open/close must not depend on which anchor found the element.
|
|
89
120
|
*/
|
|
90
|
-
async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
121
|
+
async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
|
|
122
|
+
/**
|
|
123
|
+
* The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
|
|
124
|
+
* redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
|
|
125
|
+
* and looked up at replay under no name at all, so the flow types the placeholder into the form.
|
|
126
|
+
*/
|
|
127
|
+
field,
|
|
128
|
+
/**
|
|
129
|
+
* Resolve the anchor again. Supplied by the caller because only it knows how this step's anchor is
|
|
130
|
+
* found, and the retry below is worthless without it: re-dispatching the SAME dead ref fails
|
|
131
|
+
* identically. What changed between the two attempts is the DOM, so the locator has to be run
|
|
132
|
+
* against it again.
|
|
133
|
+
*/
|
|
134
|
+
reresolve) {
|
|
135
|
+
const dispatch = async (at) => {
|
|
136
|
+
session.beginAction?.(ReticleTool.FLOW_REPLAY, { ref: at, action: step.action ?? '' });
|
|
137
|
+
try {
|
|
138
|
+
const r = await session.command(ReticleCommand.ACT, {
|
|
139
|
+
ref: at,
|
|
140
|
+
action: step.action ?? '',
|
|
141
|
+
args: replayActionArgs(step.args, confirmDangerous, field),
|
|
142
|
+
});
|
|
143
|
+
return { ok: r.ok, error: r.error };
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
session.finishAction?.();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
let act = await dispatch(ref);
|
|
150
|
+
/*
|
|
151
|
+
* One retry, and only for staleness.
|
|
152
|
+
*
|
|
153
|
+
* A saved flow of `fill` then `click` failed with "ref 'eNNNN' no longer resolves to an element",
|
|
154
|
+
* a different ref each attempt: the fill re-rendered the page between the click step resolving its
|
|
155
|
+
* element and dispatching at it. `flow_heal` answered `unhealable` and was right — the locator was
|
|
156
|
+
* correct, only the timing was wrong — and the flow format has no way to express a wait, so there
|
|
157
|
+
* was nothing the user could do.
|
|
158
|
+
*
|
|
159
|
+
* `act_sequence` already solved this race for the same reason; the predicate and the grace period
|
|
160
|
+
* come from there rather than being written twice. A step that failed for any OTHER reason is
|
|
161
|
+
* never retried: a replay that quietly repeats actions turns one click into two, which is worse
|
|
162
|
+
* than the failure it papers over.
|
|
163
|
+
*/
|
|
164
|
+
if (!act.ok && isStaleRefError(act.error) && reresolve !== undefined) {
|
|
165
|
+
await waitForReaction(session, 0, STALE_REF_GRACE_MS, {
|
|
166
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
98
167
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
168
|
+
const fresh = await reresolve();
|
|
169
|
+
if (fresh !== undefined)
|
|
170
|
+
act = await dispatch(fresh);
|
|
102
171
|
}
|
|
103
172
|
const result = { step: index, tool: step.tool, anchor: label, ok: act.ok };
|
|
104
173
|
if (!act.ok)
|
|
105
|
-
result.error = act.error ?? 'command failed';
|
|
174
|
+
result.error = replayDestructiveActionHint(act.error ?? 'command failed');
|
|
106
175
|
return result;
|
|
107
176
|
}
|
|
177
|
+
/** How long to let the app finish re-rendering before the one retry. Matches the sequence path. */
|
|
178
|
+
const STALE_REF_GRACE_MS = 400;
|
|
108
179
|
/** Run one component-anchored step: re-resolve via QUERY by:'component', ACT on the live ref, else drift. */
|
|
109
180
|
export async function runComponentStep(session, step, index, anchor, confirmDangerous, sleep) {
|
|
110
181
|
const label = componentLabel(anchor);
|
|
@@ -124,25 +195,9 @@ export async function runComponentStep(session, step, index, anchor, confirmDang
|
|
|
124
195
|
};
|
|
125
196
|
}
|
|
126
197
|
const ref = refs[0] ?? '';
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
session
|
|
130
|
-
let act;
|
|
131
|
-
try {
|
|
132
|
-
act = await session.command(ReticleCommand.ACT, {
|
|
133
|
-
ref,
|
|
134
|
-
action: step.action ?? '',
|
|
135
|
-
args: replayActionArgs(step.args, confirmDangerous),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
139
|
-
// Close on every exit so a throwing step cannot leak the window onto the next step's events.
|
|
140
|
-
session.finishAction?.();
|
|
141
|
-
}
|
|
142
|
-
const result = { step: index, tool: step.tool, anchor: label, ok: act.ok };
|
|
143
|
-
if (!act.ok)
|
|
144
|
-
result.error = act.error ?? 'command failed';
|
|
145
|
-
return result;
|
|
198
|
+
// Shared with the role path so both get the same action window AND the same stale-ref retry —
|
|
199
|
+
// this used to dispatch inline, which is how one anchor kind could gain a fix the other lacked.
|
|
200
|
+
return await actOnResolvedRef(session, step, index, label, ref, confirmDangerous, anchorFieldName(anchor), async () => (await resolveQuery(session, componentQueryArgs(anchor), sleep)).refs[0]);
|
|
146
201
|
}
|
|
147
202
|
/**
|
|
148
203
|
* DEGRADED_ANCHOR_ROLE is a MARKER ("no anchor could be determined"), never a locator.
|
|
@@ -235,7 +290,9 @@ export async function runSequenceStep(session, step, index, subs, confirmDangero
|
|
|
235
290
|
live.push({
|
|
236
291
|
ref,
|
|
237
292
|
action: sub.action ?? '',
|
|
238
|
-
|
|
293
|
+
// Each sub-step carries its OWN anchor, so each gets its own field name. A sequence that ends
|
|
294
|
+
// in a login is the shape this was reported on, and the sub-step is where the fill lives.
|
|
295
|
+
args: replayActionArgs(sub.args, confirmDangerous, anchorFieldName(sub.anchor)),
|
|
239
296
|
});
|
|
240
297
|
}
|
|
241
298
|
session.beginAction?.(ReticleTool.FLOW_REPLAY, { steps: live.length });
|
|
@@ -253,6 +310,6 @@ export async function runSequenceStep(session, step, index, subs, confirmDangero
|
|
|
253
310
|
ok: act.ok,
|
|
254
311
|
};
|
|
255
312
|
if (!act.ok)
|
|
256
|
-
result.error = act.error ?? 'command failed';
|
|
313
|
+
result.error = replayDestructiveActionHint(act.error ?? 'command failed');
|
|
257
314
|
return result;
|
|
258
315
|
}
|
|
@@ -32,6 +32,8 @@ export function successLabel(success) {
|
|
|
32
32
|
}
|
|
33
33
|
if (success.state !== undefined)
|
|
34
34
|
return `state:${success.state.path}`;
|
|
35
|
+
if (success.text !== undefined)
|
|
36
|
+
return `text:${success.text.contains}`;
|
|
35
37
|
return success.element?.testid ?? success.element?.name ?? success.element?.role ?? 'success';
|
|
36
38
|
}
|
|
37
39
|
/** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
|
|
@@ -122,6 +124,26 @@ export function successToPredicate(success, dynamic) {
|
|
|
122
124
|
parts.push({ kind: PredicateKind.ELEMENT, query });
|
|
123
125
|
}
|
|
124
126
|
}
|
|
127
|
+
const text = success.text;
|
|
128
|
+
if (text !== undefined) {
|
|
129
|
+
const part = {
|
|
130
|
+
kind: PredicateKind.TEXT,
|
|
131
|
+
contains: text.contains,
|
|
132
|
+
};
|
|
133
|
+
if (text.scope !== undefined)
|
|
134
|
+
part.scope = text.scope;
|
|
135
|
+
if (text.visible !== undefined)
|
|
136
|
+
part.visible = text.visible;
|
|
137
|
+
if (true === text.absent) {
|
|
138
|
+
part.absent = true;
|
|
139
|
+
// Same post-settle reasoning as `console.absent` and `state.hold`: a wait-until-true waiter
|
|
140
|
+
// reads "not there yet" on the first poll and passes BEFORE the text it is meant to see
|
|
141
|
+
// disappear has even been rendered. Gate on `settled` so the read happens after the page
|
|
142
|
+
// quiets, by which point a text that was going to appear has.
|
|
143
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
144
|
+
}
|
|
145
|
+
parts.push(part);
|
|
146
|
+
}
|
|
125
147
|
const [first] = parts;
|
|
126
148
|
if (0 === parts.length)
|
|
127
149
|
return undefined;
|
package/dist/flows/flow-tools.js
CHANGED
|
@@ -14,6 +14,7 @@ import { homedir } from 'node:os';
|
|
|
14
14
|
import { resolveProjectCloud } from '../cloud/cloud-config.js';
|
|
15
15
|
import { buildSuiteVerdict } from './decision.js';
|
|
16
16
|
import { classifyFlowAssertions } from './flow-classify.js';
|
|
17
|
+
import { recordingBacktrackWarning } from './recording-backtrack.js';
|
|
17
18
|
import { isValidFlowName, flowPath } from '../project/reticle-dir.js';
|
|
18
19
|
import {} from './flows.js';
|
|
19
20
|
import { flowsForSession } from './flow-store-for-session.js';
|
|
@@ -84,7 +85,7 @@ const INTENT_GAP_FIELD = z
|
|
|
84
85
|
export const FLOW_TOOLS = [
|
|
85
86
|
{
|
|
86
87
|
name: ReticleTool.FLOW_SAVE,
|
|
87
|
-
description: 'Persist the last/active recording (by name) as a git-checked, anchor-resolved flow at .reticle/flows/<name>.json. Each step is bound to a SEMANTIC anchor (testid/role/signal), never a volatile ref; steps without a resolvable testid are kept with degraded:true (a "add a data-testid here" marker) rather than dropped. Returns { name, stepCount, degraded, empty, assertions } — `assertions.grade` is asserted | presence-only | assertion-free: a flow that only acts (or only checks element presence) will pass even if the feature breaks, so when grade is not "asserted" follow assertions.warning and add a consequence assertion via reticle_annotate (assert-signal / assert-net / success-state).',
|
|
88
|
+
description: 'Persist the last/active recording (by name) as a git-checked, anchor-resolved flow at .reticle/flows/<name>.json. Each step is bound to a SEMANTIC anchor (testid/role/signal), never a volatile ref; steps without a resolvable testid are kept with degraded:true (a "add a data-testid here" marker) rather than dropped. Returns { name, stepCount, degraded, empty, assertions, warning? } — `assertions.grade` is asserted | presence-only | assertion-free: a flow that only acts (or only checks element presence) will pass even if the feature breaks, so when grade is not "asserted" follow assertions.warning and add a consequence assertion via reticle_annotate (assert-signal / assert-net / success-state). `warning` is present when the recording left a page and returned to it: replay has no navigation steps, so the next click will look for a control on a page the tab is no longer on.',
|
|
88
89
|
inputSchema: {
|
|
89
90
|
flow: z
|
|
90
91
|
.string()
|
|
@@ -128,6 +129,10 @@ export const FLOW_TOOLS = [
|
|
|
128
129
|
})
|
|
129
130
|
.optional(),
|
|
130
131
|
intentGap: INTENT_GAP_FIELD,
|
|
132
|
+
warning: z
|
|
133
|
+
.string()
|
|
134
|
+
.optional()
|
|
135
|
+
.describe('Present when the recording left a page and returned to it. Replay has no navigation steps, so the next click will look for a control on a page the tab is no longer on.'),
|
|
131
136
|
error: z.string().optional(),
|
|
132
137
|
code: z.string().optional(),
|
|
133
138
|
},
|
|
@@ -189,7 +194,7 @@ export const FLOW_TOOLS = [
|
|
|
189
194
|
const toSave = saveAs === undefined ? program : { ...program, name: saveAs };
|
|
190
195
|
return flows.save(toSave, annotations, projectId).then(async (res) => {
|
|
191
196
|
if (!res.ok)
|
|
192
|
-
return { error: flowErrorMessage(res.code), code: res.code };
|
|
197
|
+
return { error: flowErrorMessage(res.code, res.detail), code: res.code };
|
|
193
198
|
deps.annotations.clear(name);
|
|
194
199
|
// Grade the saved flow's assertions so the agent learns immediately if it just saved a flow
|
|
195
200
|
// that asserts nothing observable (passes even when the feature is broken).
|
|
@@ -199,13 +204,19 @@ export const FLOW_TOOLS = [
|
|
|
199
204
|
// back from a save that succeeded, so the guard is a type narrowing rather than a doubt.
|
|
200
205
|
const saved = res.value.name;
|
|
201
206
|
const path = isValidFlowName(saved) ? flowPath(root, saved, projectId) : undefined;
|
|
207
|
+
const backtrack = recordingBacktrackWarning(program.routes ?? []);
|
|
202
208
|
return loaded.ok
|
|
203
209
|
? {
|
|
204
210
|
...res.value,
|
|
205
211
|
...(path === undefined ? {} : { path }),
|
|
206
212
|
assertions: classifyFlowAssertions(loaded.value),
|
|
213
|
+
...(backtrack === undefined ? {} : { warning: backtrack }),
|
|
207
214
|
}
|
|
208
|
-
: {
|
|
215
|
+
: {
|
|
216
|
+
...res.value,
|
|
217
|
+
...(path === undefined ? {} : { path }),
|
|
218
|
+
...(backtrack === undefined ? {} : { warning: backtrack }),
|
|
219
|
+
};
|
|
209
220
|
});
|
|
210
221
|
},
|
|
211
222
|
},
|
|
@@ -246,7 +257,7 @@ export const FLOW_TOOLS = [
|
|
|
246
257
|
},
|
|
247
258
|
{
|
|
248
259
|
name: ReticleTool.FLOW_LOAD,
|
|
249
|
-
description: 'Read + validate a saved flow by flowName from .reticle/flows/<flowName>.json. Returns the FlowFile (version, flowName, createdAt, anchored steps) or a structured { error, code }.',
|
|
260
|
+
description: 'Read + validate a saved flow by flowName from .reticle/flows/<flowName>.json. Returns the FlowFile (version, flowName, createdAt, anchored steps) or a structured { error, code }. Step `expect` (and flow `success`) accepts the same allOf grammar as reticle_act_and_wait; a parse failure names the step and key rather than calling the file malformed.',
|
|
250
261
|
inputSchema: {
|
|
251
262
|
flow: z
|
|
252
263
|
.string()
|
|
@@ -275,7 +286,7 @@ export const FLOW_TOOLS = [
|
|
|
275
286
|
.load(asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '', projectId)
|
|
276
287
|
.then((res) => {
|
|
277
288
|
if (!res.ok)
|
|
278
|
-
return { error: flowErrorMessage(res.code), code: res.code };
|
|
289
|
+
return { error: flowErrorMessage(res.code, res.detail), code: res.code };
|
|
279
290
|
const { name, ...rest } = res.value;
|
|
280
291
|
return { flowName: name, ...rest };
|
|
281
292
|
});
|
|
@@ -310,7 +321,7 @@ export const FLOW_TOOLS = [
|
|
|
310
321
|
.remove(asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '', projectId)
|
|
311
322
|
.then((res) => {
|
|
312
323
|
if (!res.ok)
|
|
313
|
-
return { error: flowErrorMessage(res.code), code: res.code };
|
|
324
|
+
return { error: flowErrorMessage(res.code, res.detail), code: res.code };
|
|
314
325
|
return { deleted: true };
|
|
315
326
|
});
|
|
316
327
|
},
|
|
@@ -324,7 +335,10 @@ export const FLOW_TOOLS = [
|
|
|
324
335
|
'Returns { name, status: ok|drift|error, steps:[...] }; missing/malformed files and action ' +
|
|
325
336
|
'failures are status:error with a structured code (distinct from contract-changed drift). ' +
|
|
326
337
|
'An ok replay of a flow that asserts no consequence also carries unverifiable:{reason} — it ' +
|
|
327
|
-
'would read ok even if the feature were broken, so do NOT treat that ok as proof.'
|
|
338
|
+
'would read ok even if the feature were broken, so do NOT treat that ok as proof. ' +
|
|
339
|
+
'A run that stops early carries halted:{atStep, notAttempted}: `steps` is SHORTER than the ' +
|
|
340
|
+
'flow because replay stopped, not because a step was skipped — do not read a missing step as ' +
|
|
341
|
+
'an action that failed to dispatch.',
|
|
328
342
|
inputSchema: {
|
|
329
343
|
flow: z
|
|
330
344
|
.string()
|
|
@@ -354,6 +368,11 @@ export const FLOW_TOOLS = [
|
|
|
354
368
|
.optional()
|
|
355
369
|
.describe('Push-default deviation report over the replay segments: ranked deviations vs the learned envelope + a nominal count, or a fall-back note below 3 runs.'),
|
|
356
370
|
error: z.object({ code: z.string(), message: z.string() }).optional(),
|
|
371
|
+
// Same rule as `unverifiable` below, and the same failure if it is left out.
|
|
372
|
+
halted: z
|
|
373
|
+
.object({ atStep: z.number(), notAttempted: z.number() })
|
|
374
|
+
.optional()
|
|
375
|
+
.describe('Set when the replay STOPPED at a failing step: `steps` is shorter than the flow, and this says where it stopped and how many steps were never attempted. Absent means every step ran.'),
|
|
357
376
|
// Declared here or a validating profile strips it -- the same way `name` was stripped once.
|
|
358
377
|
// A field the handler sets and the schema omits arrives as nothing, silently.
|
|
359
378
|
unverifiable: z
|
|
@@ -566,7 +585,7 @@ export const FLOW_TOOLS = [
|
|
|
566
585
|
// it — and the pair that disagreed were the two ways to save the same thing.
|
|
567
586
|
const res = await flowsForSession(deps, session.projectId).flows.saveFlow(flow, session.projectId);
|
|
568
587
|
if (!res.ok)
|
|
569
|
-
return { error: flowErrorMessage(res.code), code: res.code };
|
|
588
|
+
return { error: flowErrorMessage(res.code, res.detail), code: res.code };
|
|
570
589
|
// If logged in to Reticle, mirror the saved flow to the team's regression suite. Best-effort
|
|
571
590
|
// and non-blocking: the flow is already on disk, so a sync failure never fails the save.
|
|
572
591
|
void syncSavedFlowToCloud(deps, flow, session.projectId);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FlowFile } from '@reticlehq/core';
|
|
2
|
+
import type { ToolDeps } from '../tools/tools.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turn a recorded upload's `path` into the bytes the browser can actually take, once, before replay
|
|
5
|
+
* runs a single step.
|
|
6
|
+
*
|
|
7
|
+
* The recorder and the replayer disagreed about what an upload step IS, and the disagreement was
|
|
8
|
+
* total: `reticle_record` writes `{"action":"upload","args":{"path":"test-fixtures/pipe.step"}}` —
|
|
9
|
+
* the only form the live `reticle_act` accepts, and the form that works interactively — while replay
|
|
10
|
+
* dispatched it straight at the browser, which refused with "upload does not read path, so it would
|
|
11
|
+
* be dropped". A recorded upload could therefore never replay as recorded. The capability was there
|
|
12
|
+
* the whole time: an agent who hand-patched the flow JSON to `{name, content, type}` got a real
|
|
13
|
+
* upload, a real `POST /api/v0/files -> 200`, and the app went on to perceive the file.
|
|
14
|
+
*
|
|
15
|
+
* Resolved HERE rather than at each of the four dispatch sites inside replay, and rather than at
|
|
16
|
+
* save time: the flow file stays small and legible, the fixture stays on disk where it can be
|
|
17
|
+
* reviewed and updated, and a flow that names a file nobody checked in fails saying so instead of
|
|
18
|
+
* carrying a stale copy of it.
|
|
19
|
+
*
|
|
20
|
+
* `rewriteUploadArgs` is the same function the live path uses, so the two cannot drift again — which
|
|
21
|
+
* is the whole reason this defect existed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveFlowUploads(deps: Pick<ToolDeps, 'fs' | 'reticleRoot'>, flow: FlowFile): Promise<FlowFile>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ActionType } from '@reticlehq/core';
|
|
2
|
+
import { rewriteUploadArgs } from '../tools/real-input-attempt.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turn a recorded upload's `path` into the bytes the browser can actually take, once, before replay
|
|
5
|
+
* runs a single step.
|
|
6
|
+
*
|
|
7
|
+
* The recorder and the replayer disagreed about what an upload step IS, and the disagreement was
|
|
8
|
+
* total: `reticle_record` writes `{"action":"upload","args":{"path":"test-fixtures/pipe.step"}}` —
|
|
9
|
+
* the only form the live `reticle_act` accepts, and the form that works interactively — while replay
|
|
10
|
+
* dispatched it straight at the browser, which refused with "upload does not read path, so it would
|
|
11
|
+
* be dropped". A recorded upload could therefore never replay as recorded. The capability was there
|
|
12
|
+
* the whole time: an agent who hand-patched the flow JSON to `{name, content, type}` got a real
|
|
13
|
+
* upload, a real `POST /api/v0/files -> 200`, and the app went on to perceive the file.
|
|
14
|
+
*
|
|
15
|
+
* Resolved HERE rather than at each of the four dispatch sites inside replay, and rather than at
|
|
16
|
+
* save time: the flow file stays small and legible, the fixture stays on disk where it can be
|
|
17
|
+
* reviewed and updated, and a flow that names a file nobody checked in fails saying so instead of
|
|
18
|
+
* carrying a stale copy of it.
|
|
19
|
+
*
|
|
20
|
+
* `rewriteUploadArgs` is the same function the live path uses, so the two cannot drift again — which
|
|
21
|
+
* is the whole reason this defect existed.
|
|
22
|
+
*/
|
|
23
|
+
export async function resolveFlowUploads(deps, flow) {
|
|
24
|
+
const resolveStep = async (step) => {
|
|
25
|
+
const nested = step.steps;
|
|
26
|
+
const steps = nested === undefined ? undefined : await Promise.all(nested.map(resolveStep));
|
|
27
|
+
const isUpload = step.action === ActionType.UPLOAD && step.args?.['path'] !== undefined;
|
|
28
|
+
const args = isUpload
|
|
29
|
+
? await rewriteUploadArgs(deps, ActionType.UPLOAD, step.args ?? {})
|
|
30
|
+
: step.args;
|
|
31
|
+
return {
|
|
32
|
+
...step,
|
|
33
|
+
...(args === undefined ? {} : { args }),
|
|
34
|
+
...(steps === undefined ? {} : { steps }),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
// Untouched flows are returned as-is so the common journey allocates nothing and the on-disk file
|
|
38
|
+
// and the replayed one stay the same object to compare.
|
|
39
|
+
if (!hasUpload(flow.steps))
|
|
40
|
+
return flow;
|
|
41
|
+
return { ...flow, steps: await Promise.all(flow.steps.map(resolveStep)) };
|
|
42
|
+
}
|
|
43
|
+
function hasUpload(steps) {
|
|
44
|
+
return steps.some((s) => (s.action === ActionType.UPLOAD && s.args?.['path'] !== undefined) ||
|
|
45
|
+
(s.steps !== undefined && hasUpload(s.steps)));
|
|
46
|
+
}
|
package/dist/flows/flows.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type FlowResult<T> = {
|
|
|
13
13
|
} | {
|
|
14
14
|
ok: false;
|
|
15
15
|
code: FlowErrorCode;
|
|
16
|
+
detail?: string;
|
|
16
17
|
};
|
|
17
18
|
/** Pick the on-disk anchor for a normalized step: testid > component(auto) > degraded role. */
|
|
18
19
|
export declare function anchorForStep(args: Record<string, unknown>): {
|
|
@@ -27,6 +28,20 @@ export declare function anchorForStep(args: Record<string, unknown>): {
|
|
|
27
28
|
* reader what to do: the value belongs in the environment, not in a file they are about to commit.
|
|
28
29
|
*/
|
|
29
30
|
export declare const REDACTED_FILL = "<redacted: supply at replay>";
|
|
31
|
+
/**
|
|
32
|
+
* What the anchor CALLS the field it points at.
|
|
33
|
+
*
|
|
34
|
+
* Every anchor kind names its target differently and all of them can name a password: a testid
|
|
35
|
+
* (auth-password), an accessible name (role=textbox, name="Password"), a signal. Checking only the
|
|
36
|
+
* testid variant would redact the app that uses test ids and quietly leak the one that does not —
|
|
37
|
+
* and an app without test ids is exactly the app whose flows were recorded by role.
|
|
38
|
+
*
|
|
39
|
+
* Exported because REPLAY has to agree with redaction about what the field is called. It did not:
|
|
40
|
+
* the replay paths took a `field` argument that only the testid caller passed, so a role-anchored
|
|
41
|
+
* secret was redacted at save under one name and looked up at replay under none — the flow typed
|
|
42
|
+
* the placeholder into the form. One function decides it for both sides, or the two drift again.
|
|
43
|
+
*/
|
|
44
|
+
export declare function anchorFieldName(anchor: FlowAnchor): string | undefined;
|
|
30
45
|
/**
|
|
31
46
|
* Pure: map one normalized RecordedStep → FlowStep with a semantic anchor (+ degraded marker).
|
|
32
47
|
* A ref-only (stable:false) step is recorded with a best-effort anchor and degraded:true —
|
package/dist/flows/flows.js
CHANGED
|
@@ -6,6 +6,7 @@ import { applyHealChanges } from './heal.js';
|
|
|
6
6
|
import { flowIntentGap, linkFlowIntent } from './flow-intent.js';
|
|
7
7
|
import { IntentStore } from '../intent/intent-store.js';
|
|
8
8
|
import { flowDir, flowPath, reticleDirPaths, isValidFlowName } from '../project/reticle-dir.js';
|
|
9
|
+
import { describeFlowZodFailure, parseFlowFileText } from './flow-expect-grammar.js';
|
|
9
10
|
/**
|
|
10
11
|
* A projectId only scopes storage when it's a safe single path segment (it's stamped from the
|
|
11
12
|
* session's HELLO, but the store defends the disk boundary itself). An unsafe/absent value collapses
|
|
@@ -540,8 +541,13 @@ function redactSecretFill(anchor, args) {
|
|
|
540
541
|
* (auth-password), an accessible name (role=textbox, name="Password"), a signal. Checking only the
|
|
541
542
|
* testid variant would redact the app that uses test ids and quietly leak the one that does not —
|
|
542
543
|
* and an app without test ids is exactly the app whose flows were recorded by role.
|
|
544
|
+
*
|
|
545
|
+
* Exported because REPLAY has to agree with redaction about what the field is called. It did not:
|
|
546
|
+
* the replay paths took a `field` argument that only the testid caller passed, so a role-anchored
|
|
547
|
+
* secret was redacted at save under one name and looked up at replay under none — the flow typed
|
|
548
|
+
* the placeholder into the form. One function decides it for both sides, or the two drift again.
|
|
543
549
|
*/
|
|
544
|
-
function anchorFieldName(anchor) {
|
|
550
|
+
export function anchorFieldName(anchor) {
|
|
545
551
|
if (AnchorKind.TESTID === anchor.kind)
|
|
546
552
|
return anchor.value;
|
|
547
553
|
if (AnchorKind.ROLE === anchor.kind)
|
|
@@ -700,8 +706,15 @@ export class FlowStore {
|
|
|
700
706
|
// per-project subdir. Both come from the same `pid`, so on-disk location and content always agree.
|
|
701
707
|
const stamped = pid === undefined ? flow : { ...flow, projectId: pid };
|
|
702
708
|
const parsed = FlowFileSchema.safeParse(stamped);
|
|
703
|
-
if (!parsed.success)
|
|
704
|
-
|
|
709
|
+
if (!parsed.success) {
|
|
710
|
+
// Named, not bare: the load path already says which step and key it choked on, and a save
|
|
711
|
+
// that refuses in silence sends the caller to the file to guess at what a load would tell it.
|
|
712
|
+
return {
|
|
713
|
+
ok: false,
|
|
714
|
+
code: FlowErrorCode.PARSE_FAILED,
|
|
715
|
+
detail: describeFlowZodFailure(parsed.error),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
705
718
|
const valid = await this.#linkIntent(parsed.data);
|
|
706
719
|
await this.#fs.mkdir(flowDir(this.#root, pid));
|
|
707
720
|
await this.#fs.writeFile(flowPath(this.#root, asFlowName(valid.name), pid), this.#serialize(valid));
|
|
@@ -837,17 +850,7 @@ export class FlowStore {
|
|
|
837
850
|
code: this.#fs.isNotFound(error) ? FlowErrorCode.NOT_FOUND : FlowErrorCode.PARSE_FAILED,
|
|
838
851
|
};
|
|
839
852
|
}
|
|
840
|
-
|
|
841
|
-
try {
|
|
842
|
-
parsed = JSON.parse(text);
|
|
843
|
-
}
|
|
844
|
-
catch {
|
|
845
|
-
return { ok: false, code: FlowErrorCode.PARSE_FAILED };
|
|
846
|
-
}
|
|
847
|
-
const result = FlowFileSchema.safeParse(parsed);
|
|
848
|
-
if (!result.success)
|
|
849
|
-
return { ok: false, code: FlowErrorCode.PARSE_FAILED };
|
|
850
|
-
return { ok: true, value: result.data };
|
|
853
|
+
return parseFlowFileText(text);
|
|
851
854
|
}
|
|
852
855
|
/**
|
|
853
856
|
* Delete a flow's file so a renamed/obsolete flow stops lingering in the replay list. Resolves the
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
/** Parse `git diff --name-only` output into a clean file list. Pure; exported for testing. */
|
|
2
2
|
export declare function parseGitFiles(output: string): string[];
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The outcome of asking git what changed — files, and whether the question could be answered.
|
|
5
|
+
*
|
|
6
|
+
* `files: []` used to mean both "the tree is clean" and "git could not tell us", and those are
|
|
7
|
+
* opposite answers to the only question `reticle_affected` and `reticle_verify_change` exist to ask.
|
|
8
|
+
* A caller that cannot distinguish them reports "nothing to re-verify" over a mistyped ref, a
|
|
9
|
+
* shallow clone with no history, or a daemon outside the repository — and the agent ships.
|
|
7
10
|
*/
|
|
8
|
-
export
|
|
11
|
+
export interface ChangedFiles {
|
|
12
|
+
files: string[];
|
|
13
|
+
/** True when git could not answer. `files` is then empty because nothing was learned. */
|
|
14
|
+
failed: boolean;
|
|
15
|
+
/** Why, when it failed — for the caller to surface, never to swallow. */
|
|
16
|
+
reason?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Files changed since a git ref (`git diff --name-only <ref>`), so `reticle gate --since main` works
|
|
20
|
+
* from the real diff instead of hand-listed files.
|
|
21
|
+
*
|
|
22
|
+
* Never throws, and never pretends. The CLI gate may still degrade to "no changes" and pass rather
|
|
23
|
+
* than crash CI — that reasoning was right and is unchanged — but it now has to opt into doing so by
|
|
24
|
+
* ignoring `failed`, instead of being handed a clean-looking empty list. The MCP tools read the flag
|
|
25
|
+
* and say they could not tell.
|
|
26
|
+
*/
|
|
27
|
+
export declare function changedFilesSince(ref: string, cwd: string): Promise<ChangedFiles>;
|
|
@@ -9,16 +9,42 @@ export function parseGitFiles(output) {
|
|
|
9
9
|
.filter((line) => line.length > 0);
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* A ref beginning with `-` is read by git as an OPTION, not a ref.
|
|
13
|
+
*
|
|
14
|
+
* `execFile` runs no shell, so this is argument injection rather than command injection — but
|
|
15
|
+
* `--output=<path>` in the ref position is enough to make git write somewhere it was not asked to.
|
|
16
|
+
* Refused rather than escaped: `--end-of-options` is not supported by every git version we might
|
|
17
|
+
* meet, and a ref that starts with a dash is not a thing a caller legitimately wants.
|
|
18
|
+
*/
|
|
19
|
+
function refIsOptionLike(ref) {
|
|
20
|
+
return ref.startsWith('-');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Files changed since a git ref (`git diff --name-only <ref>`), so `reticle gate --since main` works
|
|
24
|
+
* from the real diff instead of hand-listed files.
|
|
25
|
+
*
|
|
26
|
+
* Never throws, and never pretends. The CLI gate may still degrade to "no changes" and pass rather
|
|
27
|
+
* than crash CI — that reasoning was right and is unchanged — but it now has to opt into doing so by
|
|
28
|
+
* ignoring `failed`, instead of being handed a clean-looking empty list. The MCP tools read the flag
|
|
29
|
+
* and say they could not tell.
|
|
15
30
|
*/
|
|
16
31
|
export async function changedFilesSince(ref, cwd) {
|
|
32
|
+
if (refIsOptionLike(ref)) {
|
|
33
|
+
return {
|
|
34
|
+
files: [],
|
|
35
|
+
failed: true,
|
|
36
|
+
reason: `refusing ref "${ref}": a leading dash makes git read it as an option, not a ref`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
17
39
|
try {
|
|
18
40
|
const { stdout } = await run('git', ['diff', '--name-only', ref], { cwd });
|
|
19
|
-
return parseGitFiles(stdout);
|
|
41
|
+
return { files: parseGitFiles(stdout), failed: false };
|
|
20
42
|
}
|
|
21
|
-
catch {
|
|
22
|
-
return
|
|
43
|
+
catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
files: [],
|
|
46
|
+
failed: true,
|
|
47
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
48
|
+
};
|
|
23
49
|
}
|
|
24
50
|
}
|
package/dist/flows/heal-run.js
CHANGED
|
@@ -36,8 +36,8 @@ export async function healFlow(deps, args) {
|
|
|
36
36
|
applied: false,
|
|
37
37
|
proposals: [],
|
|
38
38
|
changed: [],
|
|
39
|
-
message: flowErrorMessage(loaded.code),
|
|
40
|
-
error: { code: loaded.code, message: flowErrorMessage(loaded.code) },
|
|
39
|
+
message: flowErrorMessage(loaded.code, loaded.detail),
|
|
40
|
+
error: { code: loaded.code, message: flowErrorMessage(loaded.code, loaded.detail) },
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
@@ -37,7 +37,8 @@ export declare function predicateToExpect(predicate: Predicate): FlowExpect | un
|
|
|
37
37
|
* `reticle_verify` then correctly called it `unverifiable` — the record → save → verify path
|
|
38
38
|
* completing all the way to a run that could never be a pass. Found by driving it.
|
|
39
39
|
*
|
|
40
|
-
* The list below is exactly what `successToPredicate` reads,
|
|
41
|
-
*
|
|
40
|
+
* The list below is exactly what `successToPredicate` reads, including an element located by
|
|
41
|
+
* role or name. A testid is also asserted directly against the DOM by the step runner. Anything
|
|
42
|
+
* it cannot compile is still dropped.
|
|
42
43
|
*/
|
|
43
44
|
export declare function enforcedOnReplay(expect: FlowExpect | undefined): FlowExpect | undefined;
|