@reticlehq/server 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +105 -0
- package/README.md +17 -0
- package/dist/bridge-security.d.ts +23 -0
- package/dist/bridge-security.d.ts.map +1 -0
- package/dist/bridge-security.js +44 -0
- package/dist/bridge-security.js.map +1 -0
- package/dist/bridge.d.ts +42 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +281 -0
- package/dist/bridge.js.map +1 -0
- package/dist/bridge.test-harness.d.ts +42 -0
- package/dist/bridge.test-harness.d.ts.map +1 -0
- package/dist/bridge.test-harness.js +192 -0
- package/dist/bridge.test-harness.js.map +1 -0
- package/dist/cli-launch.d.ts +53 -0
- package/dist/cli-launch.d.ts.map +1 -0
- package/dist/cli-launch.js +109 -0
- package/dist/cli-launch.js.map +1 -0
- package/dist/cli-parse.d.ts +74 -0
- package/dist/cli-parse.d.ts.map +1 -0
- package/dist/cli-parse.js +321 -0
- package/dist/cli-parse.js.map +1 -0
- package/dist/cli-port.d.ts +26 -0
- package/dist/cli-port.d.ts.map +1 -0
- package/dist/cli-port.js +57 -0
- package/dist/cli-port.js.map +1 -0
- package/dist/cli-verify.d.ts +46 -0
- package/dist/cli-verify.d.ts.map +1 -0
- package/dist/cli-verify.js +181 -0
- package/dist/cli-verify.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +337 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud/cloud-sync.d.ts +50 -0
- package/dist/cloud/cloud-sync.d.ts.map +1 -0
- package/dist/cloud/cloud-sync.js +47 -0
- package/dist/cloud/cloud-sync.js.map +1 -0
- package/dist/crawl/crawl-tools.d.ts +8 -0
- package/dist/crawl/crawl-tools.d.ts.map +1 -0
- package/dist/crawl/crawl-tools.js +61 -0
- package/dist/crawl/crawl-tools.js.map +1 -0
- package/dist/crawl/crawl.d.ts +45 -0
- package/dist/crawl/crawl.d.ts.map +1 -0
- package/dist/crawl/crawl.js +106 -0
- package/dist/crawl/crawl.js.map +1 -0
- package/dist/daemon-resilience.d.ts +17 -0
- package/dist/daemon-resilience.d.ts.map +1 -0
- package/dist/daemon-resilience.js +23 -0
- package/dist/daemon-resilience.js.map +1 -0
- package/dist/daemon.d.ts +26 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +122 -0
- package/dist/daemon.js.map +1 -0
- package/dist/domain/domain-model.d.ts +64 -0
- package/dist/domain/domain-model.d.ts.map +1 -0
- package/dist/domain/domain-model.js +127 -0
- package/dist/domain/domain-model.js.map +1 -0
- package/dist/domain/domain-tools.d.ts +9 -0
- package/dist/domain/domain-tools.d.ts.map +1 -0
- package/dist/domain/domain-tools.js +66 -0
- package/dist/domain/domain-tools.js.map +1 -0
- package/dist/domain/flow-risk.d.ts +34 -0
- package/dist/domain/flow-risk.d.ts.map +1 -0
- package/dist/domain/flow-risk.js +81 -0
- package/dist/domain/flow-risk.js.map +1 -0
- package/dist/ee/audit-log.d.ts +15 -0
- package/dist/ee/audit-log.d.ts.map +1 -0
- package/dist/ee/audit-log.js +12 -0
- package/dist/ee/audit-log.js.map +1 -0
- package/dist/events/event-filters.d.ts +65 -0
- package/dist/events/event-filters.d.ts.map +1 -0
- package/dist/events/event-filters.js +98 -0
- package/dist/events/event-filters.js.map +1 -0
- package/dist/events/predicate-eval.d.ts +91 -0
- package/dist/events/predicate-eval.d.ts.map +1 -0
- package/dist/events/predicate-eval.js +299 -0
- package/dist/events/predicate-eval.js.map +1 -0
- package/dist/events/predicate.d.ts +19 -0
- package/dist/events/predicate.d.ts.map +1 -0
- package/dist/events/predicate.js +188 -0
- package/dist/events/predicate.js.map +1 -0
- package/dist/events/reaction.d.ts +34 -0
- package/dist/events/reaction.d.ts.map +1 -0
- package/dist/events/reaction.js +60 -0
- package/dist/events/reaction.js.map +1 -0
- package/dist/events/ring-buffer.d.ts +32 -0
- package/dist/events/ring-buffer.d.ts.map +1 -0
- package/dist/events/ring-buffer.js +75 -0
- package/dist/events/ring-buffer.js.map +1 -0
- package/dist/flows/annotate-tools.d.ts +16 -0
- package/dist/flows/annotate-tools.d.ts.map +1 -0
- package/dist/flows/annotate-tools.js +136 -0
- package/dist/flows/annotate-tools.js.map +1 -0
- package/dist/flows/annotate.d.ts +22 -0
- package/dist/flows/annotate.d.ts.map +1 -0
- package/dist/flows/annotate.js +134 -0
- package/dist/flows/annotate.js.map +1 -0
- package/dist/flows/annotation-store.d.ts +23 -0
- package/dist/flows/annotation-store.d.ts.map +1 -0
- package/dist/flows/annotation-store.js +51 -0
- package/dist/flows/annotation-store.js.map +1 -0
- package/dist/flows/decision.d.ts +13 -0
- package/dist/flows/decision.d.ts.map +1 -0
- package/dist/flows/decision.js +111 -0
- package/dist/flows/decision.js.map +1 -0
- package/dist/flows/flow-classify.d.ts +52 -0
- package/dist/flows/flow-classify.d.ts.map +1 -0
- package/dist/flows/flow-classify.js +99 -0
- package/dist/flows/flow-classify.js.map +1 -0
- package/dist/flows/flow-replay-run.d.ts +15 -0
- package/dist/flows/flow-replay-run.d.ts.map +1 -0
- package/dist/flows/flow-replay-run.js +120 -0
- package/dist/flows/flow-replay-run.js.map +1 -0
- package/dist/flows/flow-replay.d.ts +49 -0
- package/dist/flows/flow-replay.d.ts.map +1 -0
- package/dist/flows/flow-replay.js +391 -0
- package/dist/flows/flow-replay.js.map +1 -0
- package/dist/flows/flow-report.d.ts +23 -0
- package/dist/flows/flow-report.d.ts.map +1 -0
- package/dist/flows/flow-report.js +106 -0
- package/dist/flows/flow-report.js.map +1 -0
- package/dist/flows/flow-scope.d.ts +25 -0
- package/dist/flows/flow-scope.d.ts.map +1 -0
- package/dist/flows/flow-scope.js +32 -0
- package/dist/flows/flow-scope.js.map +1 -0
- package/dist/flows/flow-success.d.ts +33 -0
- package/dist/flows/flow-success.d.ts.map +1 -0
- package/dist/flows/flow-success.js +131 -0
- package/dist/flows/flow-success.js.map +1 -0
- package/dist/flows/flow-tools.d.ts +4 -0
- package/dist/flows/flow-tools.d.ts.map +1 -0
- package/dist/flows/flow-tools.js +460 -0
- package/dist/flows/flow-tools.js.map +1 -0
- package/dist/flows/flows.d.ts +86 -0
- package/dist/flows/flows.d.ts.map +1 -0
- package/dist/flows/flows.js +319 -0
- package/dist/flows/flows.js.map +1 -0
- package/dist/flows/heal.d.ts +43 -0
- package/dist/flows/heal.d.ts.map +1 -0
- package/dist/flows/heal.js +98 -0
- package/dist/flows/heal.js.map +1 -0
- package/dist/flows/recordings.d.ts +46 -0
- package/dist/flows/recordings.d.ts.map +1 -0
- package/dist/flows/recordings.js +43 -0
- package/dist/flows/recordings.js.map +1 -0
- package/dist/flows/replay.d.ts +23 -0
- package/dist/flows/replay.d.ts.map +1 -0
- package/dist/flows/replay.js +160 -0
- package/dist/flows/replay.js.map +1 -0
- package/dist/http-server.d.ts +39 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +145 -0
- package/dist/http-server.js.map +1 -0
- package/dist/idle-shutdown.d.ts +34 -0
- package/dist/idle-shutdown.d.ts.map +1 -0
- package/dist/idle-shutdown.js +68 -0
- package/dist/idle-shutdown.js.map +1 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/dist/init/cursor.d.ts +25 -0
- package/dist/init/cursor.d.ts.map +1 -0
- package/dist/init/cursor.js +49 -0
- package/dist/init/cursor.js.map +1 -0
- package/dist/init/detect.d.ts +49 -0
- package/dist/init/detect.d.ts.map +1 -0
- package/dist/init/detect.js +84 -0
- package/dist/init/detect.js.map +1 -0
- package/dist/init/mcp.d.ts +40 -0
- package/dist/init/mcp.d.ts.map +1 -0
- package/dist/init/mcp.js +55 -0
- package/dist/init/mcp.js.map +1 -0
- package/dist/init/node-io.d.ts +8 -0
- package/dist/init/node-io.d.ts.map +1 -0
- package/dist/init/node-io.js +57 -0
- package/dist/init/node-io.js.map +1 -0
- package/dist/init/plan.d.ts +72 -0
- package/dist/init/plan.d.ts.map +1 -0
- package/dist/init/plan.js +280 -0
- package/dist/init/plan.js.map +1 -0
- package/dist/init/project-id.d.ts +17 -0
- package/dist/init/project-id.d.ts.map +1 -0
- package/dist/init/project-id.js +38 -0
- package/dist/init/project-id.js.map +1 -0
- package/dist/init/run.d.ts +42 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +171 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/snippets.d.ts +33 -0
- package/dist/init/snippets.d.ts.map +1 -0
- package/dist/init/snippets.js +117 -0
- package/dist/init/snippets.js.map +1 -0
- package/dist/init/vite-config.d.ts +25 -0
- package/dist/init/vite-config.d.ts.map +1 -0
- package/dist/init/vite-config.js +45 -0
- package/dist/init/vite-config.js.map +1 -0
- package/dist/input/network-mock-tools.d.ts +3 -0
- package/dist/input/network-mock-tools.d.ts.map +1 -0
- package/dist/input/network-mock-tools.js +94 -0
- package/dist/input/network-mock-tools.js.map +1 -0
- package/dist/input/network-mock.d.ts +55 -0
- package/dist/input/network-mock.d.ts.map +1 -0
- package/dist/input/network-mock.js +62 -0
- package/dist/input/network-mock.js.map +1 -0
- package/dist/input/real-input.d.ts +174 -0
- package/dist/input/real-input.d.ts.map +1 -0
- package/dist/input/real-input.js +289 -0
- package/dist/input/real-input.js.map +1 -0
- package/dist/input/scroll-find.d.ts +41 -0
- package/dist/input/scroll-find.d.ts.map +1 -0
- package/dist/input/scroll-find.js +59 -0
- package/dist/input/scroll-find.js.map +1 -0
- package/dist/input/scroll-tools.d.ts +8 -0
- package/dist/input/scroll-tools.d.ts.map +1 -0
- package/dist/input/scroll-tools.js +68 -0
- package/dist/input/scroll-tools.js.map +1 -0
- package/dist/input/viewport-tools.d.ts +3 -0
- package/dist/input/viewport-tools.d.ts.map +1 -0
- package/dist/input/viewport-tools.js +65 -0
- package/dist/input/viewport-tools.js.map +1 -0
- package/dist/license/license.d.ts +108 -0
- package/dist/license/license.d.ts.map +1 -0
- package/dist/license/license.js +203 -0
- package/dist/license/license.js.map +1 -0
- package/dist/log.d.ts +6 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +9 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp-proxy.d.ts +16 -0
- package/dist/mcp-proxy.d.ts.map +1 -0
- package/dist/mcp-proxy.js +168 -0
- package/dist/mcp-proxy.js.map +1 -0
- package/dist/mcp.d.ts +14 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +101 -0
- package/dist/mcp.js.map +1 -0
- package/dist/pairing-token.d.ts +28 -0
- package/dist/pairing-token.d.ts.map +1 -0
- package/dist/pairing-token.js +62 -0
- package/dist/pairing-token.js.map +1 -0
- package/dist/pool/browser-pool.d.ts +90 -0
- package/dist/pool/browser-pool.d.ts.map +1 -0
- package/dist/pool/browser-pool.js +221 -0
- package/dist/pool/browser-pool.js.map +1 -0
- package/dist/pool/lease-reaper.d.ts +16 -0
- package/dist/pool/lease-reaper.d.ts.map +1 -0
- package/dist/pool/lease-reaper.js +41 -0
- package/dist/pool/lease-reaper.js.map +1 -0
- package/dist/pool/playwright-launcher.d.ts +19 -0
- package/dist/pool/playwright-launcher.d.ts.map +1 -0
- package/dist/pool/playwright-launcher.js +51 -0
- package/dist/pool/playwright-launcher.js.map +1 -0
- package/dist/project/baselines.d.ts +22 -0
- package/dist/project/baselines.d.ts.map +1 -0
- package/dist/project/baselines.js +45 -0
- package/dist/project/baselines.js.map +1 -0
- package/dist/project/fs-port.d.ts +27 -0
- package/dist/project/fs-port.d.ts.map +1 -0
- package/dist/project/fs-port.js +32 -0
- package/dist/project/fs-port.js.map +1 -0
- package/dist/project/project-store.d.ts +32 -0
- package/dist/project/project-store.d.ts.map +1 -0
- package/dist/project/project-store.js +127 -0
- package/dist/project/project-store.js.map +1 -0
- package/dist/project/project-tools.d.ts +9 -0
- package/dist/project/project-tools.d.ts.map +1 -0
- package/dist/project/project-tools.js +118 -0
- package/dist/project/project-tools.js.map +1 -0
- package/dist/project/reticle-dir.d.ts +61 -0
- package/dist/project/reticle-dir.d.ts.map +1 -0
- package/dist/project/reticle-dir.js +144 -0
- package/dist/project/reticle-dir.js.map +1 -0
- package/dist/runs/build-verification-run.d.ts +30 -0
- package/dist/runs/build-verification-run.d.ts.map +1 -0
- package/dist/runs/build-verification-run.js +71 -0
- package/dist/runs/build-verification-run.js.map +1 -0
- package/dist/runs/profile-redact.d.ts +16 -0
- package/dist/runs/profile-redact.d.ts.map +1 -0
- package/dist/runs/profile-redact.js +32 -0
- package/dist/runs/profile-redact.js.map +1 -0
- package/dist/runs/render-report.d.ts +9 -0
- package/dist/runs/render-report.d.ts.map +1 -0
- package/dist/runs/render-report.js +70 -0
- package/dist/runs/render-report.js.map +1 -0
- package/dist/runs/repair-prompt.d.ts +15 -0
- package/dist/runs/repair-prompt.d.ts.map +1 -0
- package/dist/runs/repair-prompt.js +59 -0
- package/dist/runs/repair-prompt.js.map +1 -0
- package/dist/runs/replay-mapping.d.ts +12 -0
- package/dist/runs/replay-mapping.d.ts.map +1 -0
- package/dist/runs/replay-mapping.js +35 -0
- package/dist/runs/replay-mapping.js.map +1 -0
- package/dist/runs/reticle-runner.d.ts +48 -0
- package/dist/runs/reticle-runner.d.ts.map +1 -0
- package/dist/runs/reticle-runner.js +56 -0
- package/dist/runs/reticle-runner.js.map +1 -0
- package/dist/runs/risk-classify.d.ts +27 -0
- package/dist/runs/risk-classify.d.ts.map +1 -0
- package/dist/runs/risk-classify.js +76 -0
- package/dist/runs/risk-classify.js.map +1 -0
- package/dist/runs/run-store.d.ts +40 -0
- package/dist/runs/run-store.d.ts.map +1 -0
- package/dist/runs/run-store.js +118 -0
- package/dist/runs/run-store.js.map +1 -0
- package/dist/runs/run-tools.d.ts +10 -0
- package/dist/runs/run-tools.d.ts.map +1 -0
- package/dist/runs/run-tools.js +69 -0
- package/dist/runs/run-tools.js.map +1 -0
- package/dist/runs/runner-port.d.ts +14 -0
- package/dist/runs/runner-port.d.ts.map +1 -0
- package/dist/runs/runner-port.js +23 -0
- package/dist/runs/runner-port.js.map +1 -0
- package/dist/runs/verify-http.d.ts +34 -0
- package/dist/runs/verify-http.d.ts.map +1 -0
- package/dist/runs/verify-http.js +102 -0
- package/dist/runs/verify-http.js.map +1 -0
- package/dist/runs/verify-server.d.ts +26 -0
- package/dist/runs/verify-server.d.ts.map +1 -0
- package/dist/runs/verify-server.js +99 -0
- package/dist/runs/verify-server.js.map +1 -0
- package/dist/server-version.d.ts +3 -0
- package/dist/server-version.d.ts.map +1 -0
- package/dist/server-version.js +5 -0
- package/dist/server-version.js.map +1 -0
- package/dist/session/control-envelope.d.ts +49 -0
- package/dist/session/control-envelope.d.ts.map +1 -0
- package/dist/session/control-envelope.js +44 -0
- package/dist/session/control-envelope.js.map +1 -0
- package/dist/session/live-control-tools.d.ts +16 -0
- package/dist/session/live-control-tools.d.ts.map +1 -0
- package/dist/session/live-control-tools.js +204 -0
- package/dist/session/live-control-tools.js.map +1 -0
- package/dist/session/output-budget.d.ts +41 -0
- package/dist/session/output-budget.d.ts.map +1 -0
- package/dist/session/output-budget.js +54 -0
- package/dist/session/output-budget.js.map +1 -0
- package/dist/session/presenter-args.d.ts +8 -0
- package/dist/session/presenter-args.d.ts.map +1 -0
- package/dist/session/presenter-args.js +15 -0
- package/dist/session/presenter-args.js.map +1 -0
- package/dist/session/review-store.d.ts +40 -0
- package/dist/session/review-store.d.ts.map +1 -0
- package/dist/session/review-store.js +61 -0
- package/dist/session/review-store.js.map +1 -0
- package/dist/session/session-health.d.ts +22 -0
- package/dist/session/session-health.d.ts.map +1 -0
- package/dist/session/session-health.js +26 -0
- package/dist/session/session-health.js.map +1 -0
- package/dist/session/session-manager.d.ts +43 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +155 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/session-readiness.d.ts +35 -0
- package/dist/session/session-readiness.d.ts.map +1 -0
- package/dist/session/session-readiness.js +32 -0
- package/dist/session/session-readiness.js.map +1 -0
- package/dist/session/session-reaper.d.ts +37 -0
- package/dist/session/session-reaper.d.ts.map +1 -0
- package/dist/session/session-reaper.js +94 -0
- package/dist/session/session-reaper.js.map +1 -0
- package/dist/session/session-recommendation.d.ts +18 -0
- package/dist/session/session-recommendation.d.ts.map +1 -0
- package/dist/session/session-recommendation.js +14 -0
- package/dist/session/session-recommendation.js.map +1 -0
- package/dist/session/session-tools.d.ts +9 -0
- package/dist/session/session-tools.d.ts.map +1 -0
- package/dist/session/session-tools.js +43 -0
- package/dist/session/session-tools.js.map +1 -0
- package/dist/session/session.d.ts +166 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +408 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/state-select.d.ts +8 -0
- package/dist/session/state-select.d.ts.map +1 -0
- package/dist/session/state-select.js +8 -0
- package/dist/session/state-select.js.map +1 -0
- package/dist/token-auth.d.ts +24 -0
- package/dist/token-auth.d.ts.map +1 -0
- package/dist/token-auth.js +71 -0
- package/dist/token-auth.js.map +1 -0
- package/dist/tools/act-tools.d.ts +3 -0
- package/dist/tools/act-tools.d.ts.map +1 -0
- package/dist/tools/act-tools.js +327 -0
- package/dist/tools/act-tools.js.map +1 -0
- package/dist/tools/act-view.d.ts +19 -0
- package/dist/tools/act-view.d.ts.map +1 -0
- package/dist/tools/act-view.js +46 -0
- package/dist/tools/act-view.js.map +1 -0
- package/dist/tools/assert-grade.d.ts +21 -0
- package/dist/tools/assert-grade.d.ts.map +1 -0
- package/dist/tools/assert-grade.js +52 -0
- package/dist/tools/assert-grade.js.map +1 -0
- package/dist/tools/browser-tools.d.ts +3 -0
- package/dist/tools/browser-tools.d.ts.map +1 -0
- package/dist/tools/browser-tools.js +52 -0
- package/dist/tools/browser-tools.js.map +1 -0
- package/dist/tools/contract-tools.d.ts +8 -0
- package/dist/tools/contract-tools.d.ts.map +1 -0
- package/dist/tools/contract-tools.js +62 -0
- package/dist/tools/contract-tools.js.map +1 -0
- package/dist/tools/dynamic-tools.d.ts +7 -0
- package/dist/tools/dynamic-tools.d.ts.map +1 -0
- package/dist/tools/dynamic-tools.js +103 -0
- package/dist/tools/dynamic-tools.js.map +1 -0
- package/dist/tools/error-recovery.d.ts +31 -0
- package/dist/tools/error-recovery.d.ts.map +1 -0
- package/dist/tools/error-recovery.js +52 -0
- package/dist/tools/error-recovery.js.map +1 -0
- package/dist/tools/invoke-tool.d.ts +23 -0
- package/dist/tools/invoke-tool.d.ts.map +1 -0
- package/dist/tools/invoke-tool.js +99 -0
- package/dist/tools/invoke-tool.js.map +1 -0
- package/dist/tools/lease-tools.d.ts +31 -0
- package/dist/tools/lease-tools.d.ts.map +1 -0
- package/dist/tools/lease-tools.js +164 -0
- package/dist/tools/lease-tools.js.map +1 -0
- package/dist/tools/observe-tools.d.ts +3 -0
- package/dist/tools/observe-tools.d.ts.map +1 -0
- package/dist/tools/observe-tools.js +259 -0
- package/dist/tools/observe-tools.js.map +1 -0
- package/dist/tools/profiles.d.ts +33 -0
- package/dist/tools/profiles.d.ts.map +1 -0
- package/dist/tools/profiles.js +105 -0
- package/dist/tools/profiles.js.map +1 -0
- package/dist/tools/query-paginate.d.ts +15 -0
- package/dist/tools/query-paginate.d.ts.map +1 -0
- package/dist/tools/query-paginate.js +31 -0
- package/dist/tools/query-paginate.js.map +1 -0
- package/dist/tools/read-tools.d.ts +3 -0
- package/dist/tools/read-tools.d.ts.map +1 -0
- package/dist/tools/read-tools.js +329 -0
- package/dist/tools/read-tools.js.map +1 -0
- package/dist/tools/snapshot-delta.d.ts +59 -0
- package/dist/tools/snapshot-delta.d.ts.map +1 -0
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/snapshot-delta.js.map +1 -0
- package/dist/tools/tool-invoker.d.ts +15 -0
- package/dist/tools/tool-invoker.d.ts.map +1 -0
- package/dist/tools/tool-invoker.js +20 -0
- package/dist/tools/tool-invoker.js.map +1 -0
- package/dist/tools/tool-kit.d.ts +60 -0
- package/dist/tools/tool-kit.d.ts.map +1 -0
- package/dist/tools/tool-kit.js +33 -0
- package/dist/tools/tool-kit.js.map +1 -0
- package/dist/tools/tool-names.d.ts +89 -0
- package/dist/tools/tool-names.d.ts.map +1 -0
- package/dist/tools/tool-names.js +88 -0
- package/dist/tools/tool-names.js.map +1 -0
- package/dist/tools/tools-helpers.d.ts +12 -0
- package/dist/tools/tools-helpers.d.ts.map +1 -0
- package/dist/tools/tools-helpers.js +22 -0
- package/dist/tools/tools-helpers.js.map +1 -0
- package/dist/tools/tools.d.ts +4 -0
- package/dist/tools/tools.d.ts.map +1 -0
- package/dist/tools/tools.js +268 -0
- package/dist/tools/tools.js.map +1 -0
- package/dist/update/update-checker.d.ts +20 -0
- package/dist/update/update-checker.d.ts.map +1 -0
- package/dist/update/update-checker.js +94 -0
- package/dist/update/update-checker.js.map +1 -0
- package/dist/update/update-tools.d.ts +3 -0
- package/dist/update/update-tools.d.ts.map +1 -0
- package/dist/update/update-tools.js +94 -0
- package/dist/update/update-tools.js.map +1 -0
- package/dist/update/updater.d.ts +31 -0
- package/dist/update/updater.d.ts.map +1 -0
- package/dist/update/updater.js +120 -0
- package/dist/update/updater.js.map +1 -0
- package/dist/visual/visual-diff.d.ts +46 -0
- package/dist/visual/visual-diff.d.ts.map +1 -0
- package/dist/visual/visual-diff.js +100 -0
- package/dist/visual/visual-diff.js.map +1 -0
- package/dist/visual/visual-store.d.ts +22 -0
- package/dist/visual/visual-store.d.ts.map +1 -0
- package/dist/visual/visual-store.js +60 -0
- package/dist/visual/visual-store.js.map +1 -0
- package/dist/visual/visual-tools.d.ts +9 -0
- package/dist/visual/visual-tools.d.ts.map +1 -0
- package/dist/visual/visual-tools.js +182 -0
- package/dist/visual/visual-tools.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate a flow's `success` end-condition — "green means intent satisfied".
|
|
3
|
+
*
|
|
4
|
+
* This is the one piece replay does NOT do per-step: turn a flow's success FlowExpect into a
|
|
5
|
+
* Predicate and assert it with the SAME waitForPredicate engine the tools/replay use. A signal/net
|
|
6
|
+
* success is a real CONSEQUENCE — a locator healed to the wrong element cannot fake it, so it
|
|
7
|
+
* catches the regression self-healing tools ship green (mabl/qate.ai). The dynamic skip-set is
|
|
8
|
+
* honored exactly as in replay: a success bound to a dynamic (LLM-output) testid is presence-only,
|
|
9
|
+
* never asserted, so the skip is symmetric across the step layer and the success layer.
|
|
10
|
+
*
|
|
11
|
+
* Lives in reticle-server (alongside the predicate engine) so BOTH the live MCP `reticle_flow_replay`
|
|
12
|
+
* tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
|
|
13
|
+
* Pure: no IO, no clock.
|
|
14
|
+
*/
|
|
15
|
+
import { type FlowExpect, type FlowFile } from '@reticlehq/core';
|
|
16
|
+
import type { EvalResult, Predicate } from '../events/predicate.js';
|
|
17
|
+
import type { FlowReplaySession, WaitForSignal } from './flow-replay.js';
|
|
18
|
+
/** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
|
|
19
|
+
export declare function dynamicTestids(flow: FlowFile): Set<string>;
|
|
20
|
+
/** The synthetic step.tool a replay appends when it asserts the flow's success oracle. */
|
|
21
|
+
export declare const SUCCESS_STEP_TOOL = "success";
|
|
22
|
+
/** A short human label for the success end-condition, for result rows. */
|
|
23
|
+
export declare function successLabel(success: FlowExpect): string;
|
|
24
|
+
/** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
|
|
25
|
+
export declare function successToPredicate(success: FlowExpect, dynamic: ReadonlySet<string>): Predicate | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
|
|
28
|
+
* real waitForPredicate in production, a fake in unit tests). Passes when: no success was declared,
|
|
29
|
+
* OR every success field was dynamic-skipped (vacuously met), OR the compiled predicate held within
|
|
30
|
+
* the injected timeout. Never reads the wall clock.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertSuccess(session: FlowReplaySession, success: FlowExpect | undefined, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since?: number): Promise<EvalResult>;
|
|
33
|
+
//# sourceMappingURL=flow-success.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-success.d.ts","sourceRoot":"","sources":["../../src/flows/flow-success.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAc,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEzE,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAM1D;AAED,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAQxD;AAED,qGAAqG;AACrG,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,SAAS,GAAG,SAAS,CAsEvB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,UAAU,GAAG,SAAS,EAC/B,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,SAAI,GACR,OAAO,CAAC,UAAU,CAAC,CAOrB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate a flow's `success` end-condition — "green means intent satisfied".
|
|
3
|
+
*
|
|
4
|
+
* This is the one piece replay does NOT do per-step: turn a flow's success FlowExpect into a
|
|
5
|
+
* Predicate and assert it with the SAME waitForPredicate engine the tools/replay use. A signal/net
|
|
6
|
+
* success is a real CONSEQUENCE — a locator healed to the wrong element cannot fake it, so it
|
|
7
|
+
* catches the regression self-healing tools ship green (mabl/qate.ai). The dynamic skip-set is
|
|
8
|
+
* honored exactly as in replay: a success bound to a dynamic (LLM-output) testid is presence-only,
|
|
9
|
+
* never asserted, so the skip is symmetric across the step layer and the success layer.
|
|
10
|
+
*
|
|
11
|
+
* Lives in reticle-server (alongside the predicate engine) so BOTH the live MCP `reticle_flow_replay`
|
|
12
|
+
* tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
|
|
13
|
+
* Pure: no IO, no clock.
|
|
14
|
+
*/
|
|
15
|
+
import { AnchorKind } from '@reticlehq/core';
|
|
16
|
+
/** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
|
|
17
|
+
export function dynamicTestids(flow) {
|
|
18
|
+
return new Set((flow.dynamic ?? [])
|
|
19
|
+
.filter((a) => a.kind === AnchorKind.TESTID)
|
|
20
|
+
.map((a) => (a.kind === AnchorKind.TESTID ? a.value : '')));
|
|
21
|
+
}
|
|
22
|
+
/** The synthetic step.tool a replay appends when it asserts the flow's success oracle. */
|
|
23
|
+
export const SUCCESS_STEP_TOOL = 'success';
|
|
24
|
+
/** A short human label for the success end-condition, for result rows. */
|
|
25
|
+
export function successLabel(success) {
|
|
26
|
+
if (success.signal !== undefined)
|
|
27
|
+
return success.signal;
|
|
28
|
+
if (success.net !== undefined)
|
|
29
|
+
return success.net.urlContains ?? success.net.method ?? 'net';
|
|
30
|
+
if (success.console !== undefined) {
|
|
31
|
+
return `console:${success.console.absent === true ? 'clean' : (success.console.level ?? 'error')}`;
|
|
32
|
+
}
|
|
33
|
+
if (success.state !== undefined)
|
|
34
|
+
return `state:${success.state.path}`;
|
|
35
|
+
return success.element?.testid ?? success.element?.name ?? success.element?.role ?? 'success';
|
|
36
|
+
}
|
|
37
|
+
/** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
|
|
38
|
+
export function successToPredicate(success, dynamic) {
|
|
39
|
+
const parts = [];
|
|
40
|
+
if (success.signal !== undefined) {
|
|
41
|
+
parts.push(success.signalData !== undefined
|
|
42
|
+
? { kind: 'signal', name: success.signal, dataMatches: success.signalData }
|
|
43
|
+
: { kind: 'signal', name: success.signal });
|
|
44
|
+
}
|
|
45
|
+
if (success.net !== undefined) {
|
|
46
|
+
const net = { kind: 'net' };
|
|
47
|
+
if (success.net.method !== undefined)
|
|
48
|
+
net.method = success.net.method;
|
|
49
|
+
if (success.net.urlContains !== undefined)
|
|
50
|
+
net.urlContains = success.net.urlContains;
|
|
51
|
+
if (success.net.status !== undefined)
|
|
52
|
+
net.status = success.net.status;
|
|
53
|
+
if (success.net.count !== undefined) {
|
|
54
|
+
net.count = success.net.count;
|
|
55
|
+
// A cardinality assertion is inherently POST-SETTLE. The success waiter is wait-until-true, so an
|
|
56
|
+
// exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
|
|
57
|
+
// before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
|
|
58
|
+
// only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
|
|
59
|
+
parts.push({ kind: 'settled' });
|
|
60
|
+
}
|
|
61
|
+
parts.push(net);
|
|
62
|
+
}
|
|
63
|
+
if (success.console !== undefined) {
|
|
64
|
+
const con = { kind: 'console' };
|
|
65
|
+
if (success.console.level !== undefined)
|
|
66
|
+
con.level = success.console.level;
|
|
67
|
+
if (success.console.absent !== undefined) {
|
|
68
|
+
con.absent = success.console.absent;
|
|
69
|
+
// An `absent` assertion is post-settle, same as net.count: a wait-until-true waiter sees "no
|
|
70
|
+
// error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
|
|
71
|
+
// the console is read only after the page quiets, by which point any error is in the buffer.
|
|
72
|
+
if (success.console.absent)
|
|
73
|
+
parts.push({ kind: 'settled' });
|
|
74
|
+
}
|
|
75
|
+
parts.push(con);
|
|
76
|
+
}
|
|
77
|
+
const state = success.state;
|
|
78
|
+
if (state !== undefined) {
|
|
79
|
+
const part = { kind: 'state', path: state.path };
|
|
80
|
+
if (state.store !== undefined)
|
|
81
|
+
part.store = state.store;
|
|
82
|
+
if (state.equals !== undefined)
|
|
83
|
+
part.equals = state.equals;
|
|
84
|
+
// `hold` = an INVARIANT ("this state must still hold after the action settles"), vs the default
|
|
85
|
+
// wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
|
|
86
|
+
// value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
|
|
87
|
+
// on `settled` so the read happens after the page quiets, by which point the leak has landed.
|
|
88
|
+
if (state.hold === true)
|
|
89
|
+
parts.push({ kind: 'settled' });
|
|
90
|
+
parts.push(part);
|
|
91
|
+
}
|
|
92
|
+
const element = success.element;
|
|
93
|
+
if (element !== undefined) {
|
|
94
|
+
const testid = element.testid;
|
|
95
|
+
// A dynamic-marked testid is NOT asserted as a success condition (presence-only).
|
|
96
|
+
if (testid === undefined || !dynamic.has(testid)) {
|
|
97
|
+
const query = {};
|
|
98
|
+
if (testid !== undefined)
|
|
99
|
+
query['testid'] = testid;
|
|
100
|
+
if (element.role !== undefined)
|
|
101
|
+
query['role'] = element.role;
|
|
102
|
+
if (element.name !== undefined)
|
|
103
|
+
query['name'] = element.name;
|
|
104
|
+
if (Object.keys(query).length > 0)
|
|
105
|
+
parts.push({ kind: 'element', query });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const [first] = parts;
|
|
109
|
+
if (parts.length === 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (parts.length === 1 && first !== undefined)
|
|
112
|
+
return first;
|
|
113
|
+
return { kind: 'allOf', predicates: parts };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
|
|
117
|
+
* real waitForPredicate in production, a fake in unit tests). Passes when: no success was declared,
|
|
118
|
+
* OR every success field was dynamic-skipped (vacuously met), OR the compiled predicate held within
|
|
119
|
+
* the injected timeout. Never reads the wall clock.
|
|
120
|
+
*/
|
|
121
|
+
export async function assertSuccess(session, success, dynamic, waitForSignal, timeoutMs, since = 0) {
|
|
122
|
+
if (success === undefined)
|
|
123
|
+
return { pass: true };
|
|
124
|
+
const predicate = successToPredicate(success, dynamic);
|
|
125
|
+
if (predicate === undefined)
|
|
126
|
+
return { pass: true };
|
|
127
|
+
// `since` floors the window at the start of THIS replay so a success signal left in the buffer by
|
|
128
|
+
// a prior replay/run (or, in heal, by the pre-heal drift replay) cannot fake a pass.
|
|
129
|
+
return waitForSignal(session, predicate, timeoutMs, since);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=flow-success.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-success.js","sourceRoot":"","sources":["../../src/flows/flow-success.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-tools.d.ts","sourceRoot":"","sources":["../../src/flows/flow-tools.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA6BvD,eAAO,MAAM,UAAU,EAAE,OAAO,EAoT/B,CAAC"}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, HEAL_CONFIDENCE_MIN, HealStatus, RecordedSaveError, ReplayStatus, } from '@reticlehq/core';
|
|
3
|
+
import { ReticleTool } from '../tools/tool-names.js';
|
|
4
|
+
import { asString } from '../tools/tools-helpers.js';
|
|
5
|
+
import { log } from '../log.js';
|
|
6
|
+
import { resolveCloudConfig, syncFlowToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
7
|
+
import { replayFlow } from './flow-replay.js';
|
|
8
|
+
import { buildSuiteVerdict } from './decision.js';
|
|
9
|
+
import { classifyFlowAssertions } from './flow-classify.js';
|
|
10
|
+
import { assertSuccess, dynamicTestids, successLabel } from './flow-success.js';
|
|
11
|
+
import { flowPath } from '../project/reticle-dir.js';
|
|
12
|
+
import { applyHealChanges, collectProposals } from './heal.js';
|
|
13
|
+
import { waitForPredicate } from '../events/predicate.js';
|
|
14
|
+
import { replayNamedFlow, flowErrorMessage, latestRecordedFlow } from './flow-replay-run.js';
|
|
15
|
+
export { replayNamedFlow } from './flow-replay-run.js';
|
|
16
|
+
/**
|
|
17
|
+
* The connecting session's project, or undefined when no browser is attached. Flow tools use it to
|
|
18
|
+
* scope storage to the current app on a shared daemon; resolving must NOT throw here (list/load are
|
|
19
|
+
* documented to work headless), so a missing/unknown session degrades to the global/legacy store.
|
|
20
|
+
*/
|
|
21
|
+
function sessionProjectId(deps, sessionId) {
|
|
22
|
+
try {
|
|
23
|
+
return deps.sessions.resolve(sessionId).projectId;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Best-effort mirror of a just-saved flow to Reticle Cloud (only when logged in — both cloud env vars
|
|
31
|
+
* set). Fire-and-forget: resolves the config, POSTs via the platform fetch, and logs the outcome. Any
|
|
32
|
+
* failure is swallowed so a network hiccup never affects the local save.
|
|
33
|
+
*/
|
|
34
|
+
async function syncSavedFlowToCloud(flow, projectId) {
|
|
35
|
+
const config = resolveCloudConfig(process.env);
|
|
36
|
+
if (config === null)
|
|
37
|
+
return; // not logged in → stays local
|
|
38
|
+
const result = await syncFlowToCloud(flow, config, projectId, (url, init) => fetch(url, init));
|
|
39
|
+
if (result.outcome !== SyncOutcome.SYNCED) {
|
|
40
|
+
log('cloud-flow-sync-failed', { flow: flow.name, status: result.status, error: result.error });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const FLOW_TOOLS = [
|
|
44
|
+
{
|
|
45
|
+
name: ReticleTool.FLOW_SAVE,
|
|
46
|
+
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).',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
flowName: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe('Name for the flow file (saved to .reticle/flows/<flowName>.json). Use again in reticle_flow_load/reticle_flow_replay.'),
|
|
51
|
+
sessionId: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Active session ID — scopes the saved flow to that app.'),
|
|
55
|
+
},
|
|
56
|
+
// Schema MUST match what the handler actually returns on BOTH paths: success
|
|
57
|
+
// { name, stepCount, degraded, empty, assertions? } and error { error, code }. The prior schema
|
|
58
|
+
// declared { saved, path } — fields the handler never returns — so a schema-validating MCP
|
|
59
|
+
// client rejected every reticle_flow_save result ("expected boolean"). Unit tests call the handler
|
|
60
|
+
// directly and bypass MCP output validation, so only a live MCP run caught it (cf. the same
|
|
61
|
+
// class of bug fixed for FLOW_LIST above).
|
|
62
|
+
outputSchema: {
|
|
63
|
+
name: z.string().optional(),
|
|
64
|
+
stepCount: z.number().optional(),
|
|
65
|
+
degraded: z.number().optional(),
|
|
66
|
+
empty: z.boolean().optional(),
|
|
67
|
+
assertions: z
|
|
68
|
+
.object({
|
|
69
|
+
grade: z.string().describe('asserted | presence-only | assertion-free'),
|
|
70
|
+
hasConsequenceAssertion: z.boolean(),
|
|
71
|
+
totalSteps: z.number(),
|
|
72
|
+
consequenceSteps: z.number(),
|
|
73
|
+
weakSteps: z.number(),
|
|
74
|
+
warning: z.string().optional(),
|
|
75
|
+
})
|
|
76
|
+
.optional(),
|
|
77
|
+
error: z.string().optional(),
|
|
78
|
+
code: z.string().optional(),
|
|
79
|
+
},
|
|
80
|
+
handler: (deps, args) => {
|
|
81
|
+
const name = asString(args['flowName']) ?? '';
|
|
82
|
+
const program = deps.recordings.getCompiled(name);
|
|
83
|
+
if (program === undefined) {
|
|
84
|
+
return Promise.resolve({
|
|
85
|
+
error: flowErrorMessage(FlowErrorCode.NO_RECORDING),
|
|
86
|
+
code: FlowErrorCode.NO_RECORDING,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// fold any structured annotations (expect/dynamic/success/intent) onto the saved flow.
|
|
90
|
+
const success = deps.annotations.success(name);
|
|
91
|
+
const intent = deps.annotations.intent(name);
|
|
92
|
+
const annotations = {
|
|
93
|
+
stepExpect: deps.annotations.stepExpect(name),
|
|
94
|
+
dynamic: deps.annotations.dynamic(name),
|
|
95
|
+
...(success !== undefined ? { success } : {}),
|
|
96
|
+
...(intent !== undefined ? { intent } : {}),
|
|
97
|
+
};
|
|
98
|
+
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
99
|
+
return deps.flows.save(program, annotations, projectId).then(async (res) => {
|
|
100
|
+
if (!res.ok)
|
|
101
|
+
return { error: flowErrorMessage(res.code), code: res.code };
|
|
102
|
+
deps.annotations.clear(name);
|
|
103
|
+
// Grade the saved flow's assertions so the agent learns immediately if it just saved a flow
|
|
104
|
+
// that asserts nothing observable (passes even when the feature is broken).
|
|
105
|
+
const loaded = await deps.flows.load(res.value.name, projectId);
|
|
106
|
+
return loaded.ok
|
|
107
|
+
? { ...res.value, assertions: classifyFlowAssertions(loaded.value) }
|
|
108
|
+
: res.value;
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: ReticleTool.FLOW_LIST,
|
|
114
|
+
description: 'List saved flow names under .reticle/flows (a fresh agent learns the demonstrated journeys ' +
|
|
115
|
+
'without a browser). Scoped to the connected app: with a session it lists that project’s ' +
|
|
116
|
+
'flows plus legacy untagged ones; with no browser it lists every flow in the repo.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
sessionId: z
|
|
119
|
+
.string()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe('Active session ID — scopes the list to that app. Omit to list every saved flow.'),
|
|
122
|
+
},
|
|
123
|
+
outputSchema: {
|
|
124
|
+
flows: z.array(z.object({ name: z.string(), path: z.string(), createdAt: z.number().optional() })),
|
|
125
|
+
},
|
|
126
|
+
// Return {name, path} objects to MATCH the declared outputSchema. Returning bare name strings
|
|
127
|
+
// (the prior bug) made schema-validating MCP clients reject the result ("expected object,
|
|
128
|
+
// received string") — caught driving the live demo.
|
|
129
|
+
handler: (deps, args) => {
|
|
130
|
+
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
131
|
+
return deps.flows.list(projectId).then((names) => ({
|
|
132
|
+
flows: names.map((name) => ({ name, path: flowPath(deps.reticleRoot, name, projectId) })),
|
|
133
|
+
}));
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: ReticleTool.FLOW_LOAD,
|
|
138
|
+
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 }.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
flowName: z
|
|
141
|
+
.string()
|
|
142
|
+
.describe('Flow file name (without .json extension) from reticle_flow_list.'),
|
|
143
|
+
sessionId: z
|
|
144
|
+
.string()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe('Active session ID — resolves the flow within that app’s scope.'),
|
|
147
|
+
},
|
|
148
|
+
outputSchema: {
|
|
149
|
+
flowName: z.string(),
|
|
150
|
+
steps: z.array(z.unknown()),
|
|
151
|
+
createdAt: z.number().optional(),
|
|
152
|
+
},
|
|
153
|
+
handler: (deps, args) => {
|
|
154
|
+
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
155
|
+
return deps.flows.load(asString(args['flowName']) ?? '', projectId).then((res) => {
|
|
156
|
+
if (!res.ok)
|
|
157
|
+
return { error: flowErrorMessage(res.code), code: res.code };
|
|
158
|
+
const { name, ...rest } = res.value;
|
|
159
|
+
return { flowName: name, ...rest };
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: ReticleTool.FLOW_REPLAY,
|
|
165
|
+
description: "Replay a git-checked flow from .reticle/flows/<name>.json. RE-RESOLVES each step's semantic " +
|
|
166
|
+
'anchor (testid via reticle_query; signal via predicate) against the LIVE DOM — never reuses a ' +
|
|
167
|
+
'stale ref. On an anchor MISS returns legible DRIFT { step, anchor, drift:{ reasonKind, reason, ' +
|
|
168
|
+
'nearest } } (the closest surviving testid) and stops — the "whose fault is it" contract. ' +
|
|
169
|
+
'Returns { name, status: ok|drift|error, steps:[...] }; missing/malformed files and action ' +
|
|
170
|
+
'failures are status:error with a structured code (distinct from contract-changed drift).',
|
|
171
|
+
inputSchema: {
|
|
172
|
+
flowName: z
|
|
173
|
+
.string()
|
|
174
|
+
.describe('Flow file name (without .json extension) from reticle_flow_list.'),
|
|
175
|
+
confirmDangerous: z
|
|
176
|
+
.boolean()
|
|
177
|
+
.optional()
|
|
178
|
+
.describe('Set true to allow destructive controls during this replay only.'),
|
|
179
|
+
sessionId: z
|
|
180
|
+
.string()
|
|
181
|
+
.optional()
|
|
182
|
+
.describe('Active session ID from reticle_sessions. Omit when only one browser session is open.'),
|
|
183
|
+
},
|
|
184
|
+
outputSchema: {
|
|
185
|
+
status: z.string().describe('ok | drift | error'),
|
|
186
|
+
steps: z.array(z.unknown()),
|
|
187
|
+
proposals: z.array(z.unknown()).optional(),
|
|
188
|
+
error: z.object({ code: z.string(), message: z.string() }).optional(),
|
|
189
|
+
decision: z
|
|
190
|
+
.object({
|
|
191
|
+
verdict: z.string(),
|
|
192
|
+
summary: z.string(),
|
|
193
|
+
whatChanged: z.string().optional(),
|
|
194
|
+
whereInSource: z.string().optional(),
|
|
195
|
+
suggestedFix: z.string().optional(),
|
|
196
|
+
nextAction: z.string(),
|
|
197
|
+
})
|
|
198
|
+
.optional()
|
|
199
|
+
.describe('Autonomy envelope: verdict + what changed + where + fix + next action.'),
|
|
200
|
+
},
|
|
201
|
+
// The decision envelope is attached by replayNamedFlow only on drift/fail (clean pass stays
|
|
202
|
+
// token-flat). Single-flow replay and whole-suite verify share that one implementation.
|
|
203
|
+
handler: (deps, args) => replayNamedFlow(deps, args),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: ReticleTool.FLOW_VERIFY,
|
|
207
|
+
description: 'Replay EVERY saved flow (or a given subset) and return ONE consolidated suite verdict — the ' +
|
|
208
|
+
'autonomous regression check to run after a build/change. Deterministic (no LLM per flow). ' +
|
|
209
|
+
'Returns { status: pass|fail, total, passed, failed, summary, failures:[{ flow, verdict, ' +
|
|
210
|
+
'whatChanged, whereInSource, nextAction }] } — passing flows are counted, only failures carry ' +
|
|
211
|
+
'detail (the actionable fix). One call replaces N hand-driven replays.',
|
|
212
|
+
inputSchema: {
|
|
213
|
+
names: z
|
|
214
|
+
.array(z.string())
|
|
215
|
+
.optional()
|
|
216
|
+
.describe('Flow names to verify. Omit to verify every saved flow.'),
|
|
217
|
+
sessionId: z
|
|
218
|
+
.string()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe('Active session ID from reticle_sessions. Omit when only one browser session is open.'),
|
|
221
|
+
},
|
|
222
|
+
outputSchema: {
|
|
223
|
+
status: z.string().describe('pass | fail'),
|
|
224
|
+
total: z.number(),
|
|
225
|
+
passed: z.number(),
|
|
226
|
+
failed: z.number(),
|
|
227
|
+
summary: z.string(),
|
|
228
|
+
failures: z.array(z.unknown()),
|
|
229
|
+
},
|
|
230
|
+
handler: async (deps, args) => {
|
|
231
|
+
const sessionId = asString(args['sessionId']);
|
|
232
|
+
// "Replay all" means all of THIS app's flows (+ legacy), not every project's on a shared daemon.
|
|
233
|
+
const requested = Array.isArray(args['names'])
|
|
234
|
+
? args['names'].filter((n) => typeof n === 'string')
|
|
235
|
+
: await deps.flows.list(sessionProjectId(deps, sessionId));
|
|
236
|
+
const runs = [];
|
|
237
|
+
// Sequential: each flow replays against the same live session; parallel would race the DOM.
|
|
238
|
+
for (const flowName of requested) {
|
|
239
|
+
const replay = await replayNamedFlow(deps, { flowName, sessionId });
|
|
240
|
+
runs.push({ replay });
|
|
241
|
+
}
|
|
242
|
+
return buildSuiteVerdict(runs);
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: ReticleTool.FLOW_SAVE_RECORDED,
|
|
247
|
+
description: 'Persist the HUMAN-recorded flow from the live tab. The recorder toolbar compiles the ' +
|
|
248
|
+
"human's real clicks/inputs into a semantically anchored FlowFile in-page and emits it; this " +
|
|
249
|
+
'tool reads the LATEST recorded-flow from the session and writes it to .reticle/flows/<name>.json ' +
|
|
250
|
+
'(no recompilation — the browser already resolved every anchor). Pass `name` to override the ' +
|
|
251
|
+
'recorded name. Returns { name, stepCount, degraded, empty } or { error, code } (code ' +
|
|
252
|
+
'flow_no_recorded when no recording is present).',
|
|
253
|
+
inputSchema: {
|
|
254
|
+
flowName: z
|
|
255
|
+
.string()
|
|
256
|
+
.optional()
|
|
257
|
+
.describe('Override the flow name embedded in the recorded flow. Omit to use the recorder-assigned name.'),
|
|
258
|
+
...{
|
|
259
|
+
sessionId: z
|
|
260
|
+
.string()
|
|
261
|
+
.optional()
|
|
262
|
+
.describe('Active session ID from reticle_sessions. Omit when only one browser session is open.'),
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
// Match the handler's actual return (SaveSummary { name, stepCount, degraded, empty } or
|
|
266
|
+
// { error, code }) — the prior `flowName` key silently stripped the real `name`/`empty` fields.
|
|
267
|
+
outputSchema: {
|
|
268
|
+
name: z.string().optional(),
|
|
269
|
+
stepCount: z.number().optional(),
|
|
270
|
+
degraded: z.number().optional(),
|
|
271
|
+
empty: z.boolean().optional(),
|
|
272
|
+
error: z.string().optional(),
|
|
273
|
+
code: z.string().optional(),
|
|
274
|
+
},
|
|
275
|
+
handler: async (deps, args) => {
|
|
276
|
+
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
277
|
+
const recorded = latestRecordedFlow(session.eventsSince(0));
|
|
278
|
+
if (recorded === undefined) {
|
|
279
|
+
return {
|
|
280
|
+
error: 'no human recording on this tab — start the recorder toolbar and click Stop first',
|
|
281
|
+
code: RecordedSaveError.NO_RECORDED_FLOW,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const override = asString(args['flowName']);
|
|
285
|
+
const flow = override !== undefined ? { ...recorded.flow, name: override } : recorded.flow;
|
|
286
|
+
// The store stamps the project into the file AND routes it to the per-project subdir (a shared
|
|
287
|
+
// daemon serves many apps), so location and content agree from one source of truth.
|
|
288
|
+
const res = await deps.flows.saveFlow(flow, session.projectId);
|
|
289
|
+
if (!res.ok)
|
|
290
|
+
return { error: flowErrorMessage(res.code), code: res.code };
|
|
291
|
+
// If logged into Reticle Cloud, mirror the saved flow to the team's regression suite. Best-effort
|
|
292
|
+
// and non-blocking: the flow is already on disk, so a sync failure never fails the save.
|
|
293
|
+
void syncSavedFlowToCloud(flow, session.projectId);
|
|
294
|
+
const { name, ...rest } = res.value;
|
|
295
|
+
return { flowName: name, ...rest };
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: ReticleTool.FLOW_HEAL,
|
|
300
|
+
description: 'Self-healing replay. Re-runs reticle_flow_replay; on testid DRIFT computes confidence-scored ' +
|
|
301
|
+
'nearest-match rebind PROPOSALS. With apply:false (default) returns the proposed diff WITHOUT ' +
|
|
302
|
+
'writing. With apply:true, writes the confident rebind(s) back into .reticle/flows/<name>.json and ' +
|
|
303
|
+
'returns what changed — never silently. Before writing, apply re-replays the healed flow and ' +
|
|
304
|
+
're-asserts its success consequence: if the rebound locator resolves but the consequence no ' +
|
|
305
|
+
'longer fires, the write is REFUSED (status:consequence_broken) — it heals the locator, never ' +
|
|
306
|
+
'the intent. A drift with no proposal above the confidence floor is status:unhealable (file ' +
|
|
307
|
+
'untouched). Returns { name, status: healed|drift|unhealable|consequence_broken|' +
|
|
308
|
+
'nothing_to_heal|error, applied, proposals[], changed[], message }.',
|
|
309
|
+
inputSchema: {
|
|
310
|
+
flowName: z.string().describe('Flow file name to heal (from reticle_flow_list).'),
|
|
311
|
+
apply: z.boolean().optional(),
|
|
312
|
+
confirmDangerous: z
|
|
313
|
+
.boolean()
|
|
314
|
+
.optional()
|
|
315
|
+
.describe('Set true to allow destructive controls during this heal replay only.'),
|
|
316
|
+
sessionId: z
|
|
317
|
+
.string()
|
|
318
|
+
.optional()
|
|
319
|
+
.describe('Active session ID from reticle_sessions. Omit when only one browser session is open.'),
|
|
320
|
+
},
|
|
321
|
+
outputSchema: {
|
|
322
|
+
flowName: z.string(),
|
|
323
|
+
status: z.string(),
|
|
324
|
+
applied: z.boolean(),
|
|
325
|
+
proposals: z.array(z.unknown()),
|
|
326
|
+
changed: z.array(z.unknown()),
|
|
327
|
+
message: z.string(),
|
|
328
|
+
error: z.object({ code: z.string(), message: z.string() }).optional(),
|
|
329
|
+
},
|
|
330
|
+
handler: (deps, args) => healFlow(deps, args).then(({ name, ...rest }) => ({ flowName: name, ...rest })),
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
const HEAL_MESSAGES = {
|
|
334
|
+
NOTHING: 'nothing to heal — every anchor resolved on replay',
|
|
335
|
+
HEALED: "rewrote drifted testid anchors to their nearest surviving match and re-verified the flow's success consequence still fires",
|
|
336
|
+
DRIFT_DRY: 'confident rebind(s) proposed — re-run with apply:true to write them to disk',
|
|
337
|
+
UNHEALABLE: `drift found, but no nearest match cleared the confidence floor (HEAL_CONFIDENCE_MIN=${HEAL_CONFIDENCE_MIN}); file left untouched — add a data-testid or fix the flow by hand`,
|
|
338
|
+
HEALED_UNVERIFIED: 'rewrote drifted testid anchors — but this flow declares no success consequence, so the rebind resolves a locator without proving the intent still holds. Add a success-state assertion (reticle_annotate) so future heals can be verified.',
|
|
339
|
+
CONSEQUENCE_BROKEN: 'rebind resolves the drifted locator to a surviving element, but the healed flow no longer satisfies its success consequence — refusing to write (a heal that loses the intent would ship a green-but-dead test). Fix by hand and verify',
|
|
340
|
+
};
|
|
341
|
+
function toChange(proposal) {
|
|
342
|
+
return { step: proposal.step, from: proposal.from, to: proposal.to };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Self-heal handler: load → replay → collect confident proposals → (apply ? write : dry).
|
|
346
|
+
* Never silently rewrites: only proposals that cleared HEAL_CONFIDENCE_MIN are eligible, and only
|
|
347
|
+
* when apply:true. A heal disk failure maps back to status:error.
|
|
348
|
+
*/
|
|
349
|
+
async function healFlow(deps, args) {
|
|
350
|
+
const name = asString(args['flowName']) ?? '';
|
|
351
|
+
const apply = args['apply'] === true;
|
|
352
|
+
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
353
|
+
const loaded = await deps.flows.load(name, projectId);
|
|
354
|
+
if (!loaded.ok) {
|
|
355
|
+
return {
|
|
356
|
+
name,
|
|
357
|
+
status: HealStatus.ERROR,
|
|
358
|
+
applied: false,
|
|
359
|
+
proposals: [],
|
|
360
|
+
changed: [],
|
|
361
|
+
message: flowErrorMessage(loaded.code),
|
|
362
|
+
error: { code: loaded.code, message: flowErrorMessage(loaded.code) },
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
366
|
+
const steps = await replayFlow(session, loaded.value, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, args['confirmDangerous'] === true);
|
|
367
|
+
const drifted = steps.some((s) => s.drift !== undefined);
|
|
368
|
+
const failed = steps.find((s) => !s.ok && s.drift === undefined);
|
|
369
|
+
if (failed !== undefined) {
|
|
370
|
+
const message = failed.error ?? 'flow replay failed before an anchor could be healed';
|
|
371
|
+
return {
|
|
372
|
+
name,
|
|
373
|
+
status: HealStatus.ERROR,
|
|
374
|
+
applied: false,
|
|
375
|
+
proposals: [],
|
|
376
|
+
changed: [],
|
|
377
|
+
message,
|
|
378
|
+
error: { code: ReplayStatus.ERROR, message },
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (!drifted) {
|
|
382
|
+
return {
|
|
383
|
+
name,
|
|
384
|
+
status: HealStatus.NOTHING_TO_HEAL,
|
|
385
|
+
applied: false,
|
|
386
|
+
proposals: [],
|
|
387
|
+
changed: [],
|
|
388
|
+
message: HEAL_MESSAGES.NOTHING,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const proposals = collectProposals(steps);
|
|
392
|
+
if (proposals.length === 0) {
|
|
393
|
+
return {
|
|
394
|
+
name,
|
|
395
|
+
status: HealStatus.UNHEALABLE,
|
|
396
|
+
applied: false,
|
|
397
|
+
proposals: [],
|
|
398
|
+
changed: [],
|
|
399
|
+
message: HEAL_MESSAGES.UNHEALABLE,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (!apply) {
|
|
403
|
+
return {
|
|
404
|
+
name,
|
|
405
|
+
status: HealStatus.DRIFT,
|
|
406
|
+
applied: false,
|
|
407
|
+
proposals,
|
|
408
|
+
changed: [],
|
|
409
|
+
message: HEAL_MESSAGES.DRIFT_DRY,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
// Heal-the-locator-never-the-intent invariant: "heal the locator, never the intent." Before persisting, verify the rebind on a
|
|
413
|
+
// healed in-memory copy: a rebound testid can resolve to a real but WRONG element that no longer
|
|
414
|
+
// triggers the flow's success consequence (e.g. a look-alike control). Persisting that would ship
|
|
415
|
+
// a green flow that tests nothing. Re-replay the healed flow and assert its success; refuse the
|
|
416
|
+
// write if the consequence no longer fires. Flows with no declared success can't be verified — we
|
|
417
|
+
// still heal them but say so loudly so the gap is visible.
|
|
418
|
+
const { flow: healed } = applyHealChanges(loaded.value, proposals.map(toChange));
|
|
419
|
+
if (healed.success !== undefined) {
|
|
420
|
+
// Floor the success oracle at the start of the VERIFY replay so the success signal emitted by the
|
|
421
|
+
// earlier drift replay's prefix (this same heal call) cannot fake the verification.
|
|
422
|
+
const verifyFloor = session.elapsed();
|
|
423
|
+
const verifySteps = await replayFlow(session, healed, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, args['confirmDangerous'] === true);
|
|
424
|
+
const verifyClean = verifySteps.length > 0 && verifySteps.every((s) => s.ok && s.drift === undefined);
|
|
425
|
+
const verdict = verifyClean
|
|
426
|
+
? await assertSuccess(session, healed.success, dynamicTestids(healed), waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, verifyFloor)
|
|
427
|
+
: { pass: false, failureReason: 'healed flow did not replay cleanly' };
|
|
428
|
+
if (!verdict.pass) {
|
|
429
|
+
return {
|
|
430
|
+
name,
|
|
431
|
+
status: HealStatus.CONSEQUENCE_BROKEN,
|
|
432
|
+
applied: false,
|
|
433
|
+
proposals,
|
|
434
|
+
changed: [],
|
|
435
|
+
message: `${HEAL_MESSAGES.CONSEQUENCE_BROKEN} (${successLabel(healed.success)}: ${verdict.failureReason ?? 'not satisfied'})`,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const written = await deps.flows.heal(name, proposals.map(toChange), projectId);
|
|
440
|
+
if (!written.ok) {
|
|
441
|
+
return {
|
|
442
|
+
name,
|
|
443
|
+
status: HealStatus.ERROR,
|
|
444
|
+
applied: false,
|
|
445
|
+
proposals,
|
|
446
|
+
changed: [],
|
|
447
|
+
message: flowErrorMessage(written.code),
|
|
448
|
+
error: { code: written.code, message: flowErrorMessage(written.code) },
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
name,
|
|
453
|
+
status: HealStatus.HEALED,
|
|
454
|
+
applied: written.value.changed.length > 0,
|
|
455
|
+
proposals,
|
|
456
|
+
changed: written.value.changed,
|
|
457
|
+
message: loaded.value.success !== undefined ? HEAL_MESSAGES.HEALED : HEAL_MESSAGES.HEALED_UNVERIFIED,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
//# sourceMappingURL=flow-tools.js.map
|