@reticlehq/server 3.8.2 → 3.10.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 +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/SKILL.md +13 -12
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -160
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +46 -5
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -24
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +47 -4
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +22 -17
- package/dist/command/cli/cli-parse.js +54 -32
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +61 -31
- package/dist/command/cli/cli-verify.d.ts +17 -3
- package/dist/command/cli/cli-verify.js +59 -21
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +11 -0
- package/dist/command/cli/ports/port-holder.js +36 -4
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +2 -0
- package/dist/command/cli/status/status-lines.js +72 -17
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +24 -6
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +112 -70
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +22 -4
- package/dist/command/daemon/daemon.js +52 -10
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +149 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +11 -15
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +63 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +36 -15
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +14 -3
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +67 -99
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -7
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +51 -5
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
- package/dist/language/flows/recording/tape/recordings.js +112 -49
- package/dist/language/flows/replay.d.ts +36 -12
- package/dist/language/flows/replay.js +193 -50
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +30 -10
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +35 -18
- package/dist/portal/input/real-input.js +99 -33
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +81 -21
- package/dist/portal/pool/browser-pool.d.ts +16 -30
- package/dist/portal/pool/browser-pool.js +64 -52
- package/dist/portal/pool/context-permissions.d.ts +34 -17
- package/dist/portal/pool/context-permissions.js +94 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +56 -0
- package/dist/portal/pool/lease-page-error.js +160 -0
- package/dist/portal/pool/page-error-journal.d.ts +63 -0
- package/dist/portal/pool/page-error-journal.js +208 -0
- package/dist/portal/pool/playwright-launcher.js +71 -15
- package/dist/portal/pool/pool-contract.d.ts +49 -1
- package/dist/portal/pool/pool-lease.d.ts +70 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +91 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
- package/dist/portal/session/dev-server/dev-command.js +110 -16
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +76 -27
- package/dist/portal/session/no-session-next-action.d.ts +14 -0
- package/dist/portal/session/no-session-next-action.js +56 -7
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +41 -4
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +211 -81
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +30 -2
- package/dist/start-wiring.js +74 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +8 -5
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +38 -18
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +10 -0
- package/dist/surface/tools/act/after-match.js +40 -0
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +17 -1
- package/dist/surface/tools/act/resolve-target.js +59 -9
- package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
- package/dist/surface/tools/act/sequence-grade.js +45 -27
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +81 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +83 -95
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +18 -12
- package/dist/surface/tools/browser-tools.js +87 -13
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +11 -3
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +131 -40
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +29 -21
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +42 -1
- package/dist/surface/tools/lease-hint.js +52 -3
- package/dist/surface/tools/lease-readiness.d.ts +47 -0
- package/dist/surface/tools/lease-readiness.js +126 -0
- package/dist/surface/tools/lease-seed.d.ts +16 -1
- package/dist/surface/tools/lease-seed.js +37 -4
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +104 -124
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +18 -0
- package/dist/surface/tools/tool-kit.js +9 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +54 -18
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +17 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +15 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/architecture.md +3 -6
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +13 -12
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +34 -24
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +17 -22
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +6 -4
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +18 -14
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -36,14 +36,104 @@ function strayKeysRefusal(tool, action, stray, actions) {
|
|
|
36
36
|
/** The union of member input shapes, every field optional so one schema serves every action. */
|
|
37
37
|
function unionShape(actions) {
|
|
38
38
|
const shape = {};
|
|
39
|
+
const contested = contestedKeys(actions);
|
|
39
40
|
for (const tool of Object.values(actions)) {
|
|
40
41
|
for (const [key, schema] of Object.entries(tool.inputSchema)) {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (key in shape) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// First definition wins, unless members accept different values for it: see contestedKeys.
|
|
46
|
+
if (contested.has(key)) {
|
|
47
|
+
shape[key] = eitherBound(key, actions);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
shape[key] = schema.isOptional() ? schema : schema.optional();
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
return shape;
|
|
46
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* What a field accepts, without its description: two members that word one field differently
|
|
58
|
+
* still accept the same values.
|
|
59
|
+
*/
|
|
60
|
+
function boundOf(schema) {
|
|
61
|
+
try {
|
|
62
|
+
const json = { ...z.toJSONSchema(schema, { unrepresentable: 'any' }) };
|
|
63
|
+
delete json['description'];
|
|
64
|
+
return JSON.stringify(json);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The fields two members accept different values for (#2144).
|
|
72
|
+
*
|
|
73
|
+
* `reticle_assert { action: "now" }` caps `timeout_ms` at one request, and `action: "wait"` takes up
|
|
74
|
+
* to 600000 and answers `resume_ms` for the rest. With the first definition winning, the merged tool
|
|
75
|
+
* kept the cap of "now", and refused every wait longer than one request: `reticle verify --expect
|
|
76
|
+
* --timeout 120000` printed `verified: unknown`.
|
|
77
|
+
*/
|
|
78
|
+
function contestedKeys(actions) {
|
|
79
|
+
const seen = new Map();
|
|
80
|
+
const contested = new Set();
|
|
81
|
+
for (const tool of Object.values(actions)) {
|
|
82
|
+
for (const [key, schema] of Object.entries(tool.inputSchema)) {
|
|
83
|
+
const bound = boundOf(schema);
|
|
84
|
+
if (!seen.has(key)) {
|
|
85
|
+
seen.set(key, bound);
|
|
86
|
+
}
|
|
87
|
+
else if (seen.get(key) !== bound) {
|
|
88
|
+
contested.add(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return contested;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A contested field accepts what any member accepts. The dispatch then checks the value against the
|
|
96
|
+
* chosen action's own schema (`contestedRefusal`), so no action gets a value it would refuse.
|
|
97
|
+
*/
|
|
98
|
+
function eitherBound(key, actions) {
|
|
99
|
+
const owners = Object.entries(actions).filter(([, tool]) => key in tool.inputSchema);
|
|
100
|
+
const schemas = owners.flatMap(([, tool]) => {
|
|
101
|
+
const schema = tool.inputSchema[key];
|
|
102
|
+
return schema === undefined ? [] : [schema];
|
|
103
|
+
});
|
|
104
|
+
const described = owners
|
|
105
|
+
.map(([action, tool]) => {
|
|
106
|
+
const said = tool.inputSchema[key]?.description;
|
|
107
|
+
return said === undefined ? undefined : `For action "${action}": ${said}`;
|
|
108
|
+
})
|
|
109
|
+
.filter((said) => said !== undefined)
|
|
110
|
+
.join(' ');
|
|
111
|
+
const [first, second, ...rest] = schemas;
|
|
112
|
+
if (first === undefined || second === undefined) {
|
|
113
|
+
throw new Error(`mergeTools: contested key '${key}' has fewer than two schemas`);
|
|
114
|
+
}
|
|
115
|
+
const either = z.union([first, second, ...rest]).optional();
|
|
116
|
+
return 0 === described.length ? either : either.describe(described);
|
|
117
|
+
}
|
|
118
|
+
/** The refusal for a contested field whose value the chosen action does not accept. */
|
|
119
|
+
function contestedRefusal(tool, action, args, chosen, contested) {
|
|
120
|
+
const named = [];
|
|
121
|
+
for (const key of contested) {
|
|
122
|
+
const schema = chosen.inputSchema[key];
|
|
123
|
+
if (schema === undefined || args[key] === undefined) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const checked = schema.safeParse(args[key]);
|
|
127
|
+
if (!checked.success) {
|
|
128
|
+
named.push(`${key} (${checked.error.issues.map((issue) => issue.message).join('; ')})`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (0 === named.length) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
return (`invalid ${1 === named.length ? 'parameter' : 'parameters'} for ${tool} { action: "${action}" }: ` +
|
|
135
|
+
`${named.join('; ')} — NOT applied, so any result would be an answer to a different question`);
|
|
136
|
+
}
|
|
47
137
|
/**
|
|
48
138
|
* A member that declares `action` itself cannot be merged, and the failure was SILENT.
|
|
49
139
|
*
|
|
@@ -120,6 +210,7 @@ export function mergeTools(spec) {
|
|
|
120
210
|
if (spec.defaultAction !== undefined && !(spec.defaultAction in spec.actions)) {
|
|
121
211
|
throw new Error(`mergeTools(${spec.name}): defaultAction '${spec.defaultAction}' is not one of its actions`);
|
|
122
212
|
}
|
|
213
|
+
const contested = contestedKeys(spec.actions);
|
|
123
214
|
return {
|
|
124
215
|
name: spec.name,
|
|
125
216
|
description: spec.description,
|
|
@@ -148,6 +239,12 @@ export function mergeTools(spec) {
|
|
|
148
239
|
if (0 < stray.length && action !== undefined) {
|
|
149
240
|
return Promise.reject(new Error(strayKeysRefusal(spec.name, action, stray, spec.actions)));
|
|
150
241
|
}
|
|
242
|
+
const outOfBounds = action === undefined
|
|
243
|
+
? undefined
|
|
244
|
+
: contestedRefusal(spec.name, action, args, chosen, contested);
|
|
245
|
+
if (outOfBounds !== undefined) {
|
|
246
|
+
return Promise.reject(new Error(outOfBounds));
|
|
247
|
+
}
|
|
151
248
|
return chosen.handler(deps, args, call);
|
|
152
249
|
},
|
|
153
250
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where a merged or retired tool's old name went.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Many names exported from `ReticleTool` are no longer tools: `reticle_record_start` became
|
|
5
5
|
* `reticle_record { action: "start" }`, `reticle_diff` became `reticle_baseline { action: "diff" }`,
|
|
6
6
|
* and three were retired outright. Confirmed live in a field sweep, they all answered `unknown tool`
|
|
7
7
|
* — which is the wrong answer, because the capability exists and only moved. An agent trained on an
|
|
@@ -23,6 +23,10 @@ const RETIRED_NOTE = {
|
|
|
23
23
|
tool: ReticleTool.PROJECT,
|
|
24
24
|
note: 'run outcomes are persisted automatically by reticle_flow_replay; read them with reticle_project',
|
|
25
25
|
},
|
|
26
|
+
[ReticleTool.EXPLORE]: {
|
|
27
|
+
tool: ReticleTool.LOOK,
|
|
28
|
+
note: 'list the controls with reticle_look { action: "page", mode: "interactive" } (reticle_snapshot { mode: "interactive" }), and read console errors with reticle_observe { action: "console" } (reticle_console)',
|
|
29
|
+
},
|
|
26
30
|
[ReticleTool.WAIT_READY]: {
|
|
27
31
|
tool: ReticleTool.SESSIONS,
|
|
28
32
|
note: 'waiting is implicit — the first live call already blocks until the session is ready',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { checkWords, runIdOfCall } from '../../judgement/runs/drive-run.js';
|
|
6
6
|
import { noteEmptyRead } from './gaps/observed-nothing.js';
|
|
7
|
+
import { withPreAttach } from './read/console-pre-attach.js';
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import { aliasParam } from './args/alias-args.js';
|
|
9
10
|
import { CONSOLE_ATTACH_NOTE, CONSOLE_LEVELS, ReticleCommand, DEFAULT_ASSERT_TIMEOUT_MS, PredicateKind, Verified, checkIntent, } from '@reticlehq/core';
|
|
@@ -450,13 +451,15 @@ export const OBSERVE_TOOLS = [
|
|
|
450
451
|
// Two nudges, both only on a GREEN verdict (a failing one is moot): a presence-only assertion
|
|
451
452
|
// a wrong element can fake, and an assertion pinned to a status Reticle derived rather than one
|
|
452
453
|
// a server sent. Neither blocks — the assertion still passes; they steer the next one.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
454
|
+
let advice = {};
|
|
455
|
+
if (verdict.pass) {
|
|
456
|
+
if (isPresenceOnlyAssertion(predicate)) {
|
|
457
|
+
advice = { advice: PRESENCE_ONLY_ADVICE };
|
|
458
|
+
}
|
|
459
|
+
else if (assertsDerivedIpcStatus(predicate)) {
|
|
460
|
+
advice = { advice: DERIVED_IPC_STATUS_ADVICE };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
460
463
|
// Asked of every verdict drawn after an observed edit, not once per edit — see
|
|
461
464
|
// isChangeUndeclared for why repeating it is disclosure rather than nagging.
|
|
462
465
|
// Read once: the undeclared-change check and the engine's claim reading share it.
|
|
@@ -609,7 +612,10 @@ export const OBSERVE_TOOLS = [
|
|
|
609
612
|
* `true` and `false` keep their exact old meanings, so a caller that already passes one is
|
|
610
613
|
* unaffected. Only the omitted case moves.
|
|
611
614
|
*/
|
|
612
|
-
|
|
615
|
+
let bodyMode = 'auto';
|
|
616
|
+
if ('boolean' === typeof args['bodies']) {
|
|
617
|
+
bodyMode = args['bodies'] ? 'all' : 'none';
|
|
618
|
+
}
|
|
613
619
|
// A filter names specific calls, so under `auto` their bodies are the thing being asked about.
|
|
614
620
|
const named = method !== undefined || urlContains !== undefined || status !== undefined;
|
|
615
621
|
const buffer = bufferEnvelope(session);
|
|
@@ -729,7 +735,7 @@ export const OBSERVE_TOOLS = [
|
|
|
729
735
|
}
|
|
730
736
|
// Default the cap so an omitted `limit` can't dump a whole flooded session (since defaults to 0).
|
|
731
737
|
const { events: budgeted, droppedOldest } = applyEventBudget(matched, limit ?? DEFAULT_QUERY_LIMIT);
|
|
732
|
-
const logs = budgeted.map(projectConsoleLog);
|
|
738
|
+
const logs = budgeted.map((e) => withPreAttach(projectConsoleLog(e), e));
|
|
733
739
|
// Say the look HAPPENED when it found nothing. A quiet page and a dead console observer both
|
|
734
740
|
// produced `{ logs: [] }`, and "no console errors" is the claim agents lean on most — see
|
|
735
741
|
// observed-nothing.ts, whose own header names this exact case and which every other array-
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Say on a console line that the page raised it before the SDK attached (#1540).
|
|
3
|
+
*
|
|
4
|
+
* The build plugin's inline buffer holds what an app raised while nothing listened: an entry module
|
|
5
|
+
* that 404'd or threw, a rejection, a `console.error` during start-up. The SDK reports it when it
|
|
6
|
+
* attaches, marked `preAttach`. Without the mark on the line the agent reads, a reported error looks
|
|
7
|
+
* like one the last action caused, and the reason a page is blank is not read as a load failure.
|
|
8
|
+
*
|
|
9
|
+
* Here and not in the engine's `projectConsoleLog`: the engine does not take new names from core.
|
|
10
|
+
*/
|
|
11
|
+
import { type ReticleEvent } from '@reticlehq/core';
|
|
12
|
+
export interface PreAttachMark {
|
|
13
|
+
/** The page raised it before the SDK attached. */
|
|
14
|
+
preAttach?: true;
|
|
15
|
+
/** How many more pre-attach errors the full buffer did not keep — loss, declared. */
|
|
16
|
+
droppedPreAttach?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The console line, with the pre-attach mark and the declared loss when the event carries them. */
|
|
19
|
+
export declare function withPreAttach<T extends object>(view: T, e: ReticleEvent): T & PreAttachMark;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Say on a console line that the page raised it before the SDK attached (#1540).
|
|
3
|
+
*
|
|
4
|
+
* The build plugin's inline buffer holds what an app raised while nothing listened: an entry module
|
|
5
|
+
* that 404'd or threw, a rejection, a `console.error` during start-up. The SDK reports it when it
|
|
6
|
+
* attaches, marked `preAttach`. Without the mark on the line the agent reads, a reported error looks
|
|
7
|
+
* like one the last action caused, and the reason a page is blank is not read as a load failure.
|
|
8
|
+
*
|
|
9
|
+
* Here and not in the engine's `projectConsoleLog`: the engine does not take new names from core.
|
|
10
|
+
*/
|
|
11
|
+
import { CONSOLE_EVENT, asNumber } from '@reticlehq/core';
|
|
12
|
+
/** The console line, with the pre-attach mark and the declared loss when the event carries them. */
|
|
13
|
+
export function withPreAttach(view, e) {
|
|
14
|
+
const mark = {};
|
|
15
|
+
if (true === e.data[CONSOLE_EVENT.PRE_ATTACH]) {
|
|
16
|
+
mark.preAttach = true;
|
|
17
|
+
}
|
|
18
|
+
const dropped = asNumber(e.data[CONSOLE_EVENT.DROPPED_PRE_ATTACH]);
|
|
19
|
+
if (dropped !== undefined && dropped > 0) {
|
|
20
|
+
mark.droppedPreAttach = dropped;
|
|
21
|
+
}
|
|
22
|
+
return { ...view, ...mark };
|
|
23
|
+
}
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Read / record / replay tools — baselines + diff, recordings + replay, narrate, clock, state,
|
|
3
3
|
* explore. Split out of tools.ts; assembled back via...READ_TOOLS.
|
|
4
4
|
*/
|
|
5
|
-
import { UNKEPT_EXPECT } from '../../language/flows/replay.js';
|
|
5
|
+
import { UNKEPT_EXPECT, pageOf } from '../../language/flows/replay.js';
|
|
6
6
|
import { resolveAnnotateTarget } from '../../language/flows/annotate-notes/annotate-target.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { EventType, ReticleCommand, REPLAY_PROGRAM_VERSION, SnapshotMode, StorageArea, } from '@reticlehq/core';
|
|
9
9
|
import { ReticleTool } from '@reticlehq/core';
|
|
10
10
|
import { advanceMsSchema, depthSchema } from './args/numeric-bounds.js';
|
|
11
|
+
import { StepFramesSchema } from '../../portal/pool/determinism.js';
|
|
12
|
+
const NOT_DETERMINISTIC = 'stepFrames needs a deterministic lease. Acquire one with reticle_lease { action: "acquire", url, deterministic: {} }, then pass its sessionId.';
|
|
11
13
|
import { aliasParam, aliasUsed } from './args/alias-args.js';
|
|
12
14
|
/** `name` for `recordingName`: the spelling every other naming argument uses. */
|
|
13
15
|
const RECORDING_NAME_ALIAS = z.string().optional().describe('Alias for `recordingName`.');
|
|
@@ -22,25 +24,6 @@ import { buildReactionReport, summarizeReaction } from '@reticlehq/engine/questi
|
|
|
22
24
|
import { asNumber, asString, parseInteractive } from '@reticlehq/core';
|
|
23
25
|
import { sessionIdShape, commandOrThrow, snapshotTree } from './tool-kit.js';
|
|
24
26
|
import { bufferEnvelope } from '../../portal/session/session-health.js';
|
|
25
|
-
import { navigablePath, routeOfUrl } from '@reticlehq/engine/question/predicate/predicate-route.js';
|
|
26
|
-
/** The route part of a session URL. A host belongs to the machine, not to the journey. */
|
|
27
|
-
/**
|
|
28
|
-
* The page a recording started on, as a NAVIGABLE path: pathname + query + hash.
|
|
29
|
-
*
|
|
30
|
-
* The hash matters and the pathname alone is not enough. Under a hash router every page has the
|
|
31
|
-
* document pathname `/`, so a recording made on `#/posts/12` stored `/`, the replay's start-path
|
|
32
|
-
* comparison saw `/` on both sides and never warned about drift, and the navigate it suggests
|
|
33
|
-
* pointed at the default route rather than the recorded one.
|
|
34
|
-
*/
|
|
35
|
-
function pathnameOf(url) {
|
|
36
|
-
if (url === undefined)
|
|
37
|
-
return undefined;
|
|
38
|
-
const parts = routeOfUrl(url);
|
|
39
|
-
// The query too, minus Reticle's own params: `/products?scope=TOPWEAR` and `/products` are different
|
|
40
|
-
// pages, and replay compares the query when the flow recorded one (#1059), so dropping it here
|
|
41
|
-
// started the replay on the unfiltered page (#1411).
|
|
42
|
-
return parts === undefined ? undefined : navigablePath(parts);
|
|
43
|
-
}
|
|
44
27
|
/** What record-stop says when the span crossed a full-page load (#1411). */
|
|
45
28
|
const RECORDING_CROSSED_RELOAD = 'The recording crossed a full-page load: the page it started on unloaded, so the reaction report and the proposed consequences cover only the page that loaded after it.';
|
|
46
29
|
/**
|
|
@@ -161,7 +144,7 @@ export const READ_TOOLS = [
|
|
|
161
144
|
recordingName: z
|
|
162
145
|
.string()
|
|
163
146
|
.optional()
|
|
164
|
-
.describe('Identifier for this recording. Pass the same name to reticle_record{action:"stop"} and
|
|
147
|
+
.describe('Identifier for this recording. Pass the same name to reticle_record{action:"stop"} and reticle_record{action:"replay"}.'),
|
|
165
148
|
name: RECORDING_NAME_ALIAS,
|
|
166
149
|
...sessionIdShape,
|
|
167
150
|
},
|
|
@@ -176,9 +159,9 @@ export const READ_TOOLS = [
|
|
|
176
159
|
const name = asString(aliasParam(args, 'recordingName', ['name'])['recordingName']) ?? 'default';
|
|
177
160
|
const cursor = session.elapsed();
|
|
178
161
|
// Where the journey begins, so a saved flow can navigate here before step 1 instead of
|
|
179
|
-
// replaying from wherever the page happens to be.
|
|
180
|
-
//
|
|
181
|
-
deps.recordings.start(name, cursor,
|
|
162
|
+
// replaying from wherever the page happens to be. No host or port: they belong to the machine
|
|
163
|
+
// that recorded it, not to the journey. The app's query and hash stay (see pageOf).
|
|
164
|
+
deps.recordings.start(name, cursor, pageOf(session.url), session.id, session.currentDocumentId);
|
|
182
165
|
return Promise.resolve({
|
|
183
166
|
recordingName: name,
|
|
184
167
|
since: cursor,
|
|
@@ -339,7 +322,7 @@ export const READ_TOOLS = [
|
|
|
339
322
|
},
|
|
340
323
|
{
|
|
341
324
|
name: ReticleTool.CLOCK,
|
|
342
|
-
description: 'Control a fake clock: { freeze:true } to freeze time, { advanceMs:N } to fast-forward timers (toasts, debounces, auto-dismiss), { reset:true } to restore. Lets you test time-gated UI deterministically.',
|
|
325
|
+
description: 'Control a fake clock: { freeze:true } to freeze time, { advanceMs:N } to fast-forward timers (toasts, debounces, auto-dismiss), { reset:true } to restore. Lets you test time-gated UI deterministically. On a lease acquired with `deterministic`, { stepFrames:N } moves its clock N frames of 16.67 ms: due timers run, then the requestAnimationFrame callbacks, once each frame.',
|
|
343
326
|
inputSchema: {
|
|
344
327
|
freeze: z
|
|
345
328
|
.boolean()
|
|
@@ -349,6 +332,7 @@ export const READ_TOOLS = [
|
|
|
349
332
|
.optional()
|
|
350
333
|
.describe('Fast-forward time by this many milliseconds — triggers debounces, toasts, auto-dismiss timers.'),
|
|
351
334
|
reset: z.boolean().optional().describe('Restore the real clock.'),
|
|
335
|
+
stepFrames: StepFramesSchema.optional().describe('Step a deterministic lease by this many frames (60 each second). Only for a lease acquired with `deterministic`.'),
|
|
352
336
|
...sessionIdShape,
|
|
353
337
|
},
|
|
354
338
|
// The command returns `{ frozen }`. It used to declare `{ ok, elapsed }` — neither of which any
|
|
@@ -356,12 +340,31 @@ export const READ_TOOLS = [
|
|
|
356
340
|
// successful freeze and a failed one both validated to `{}` and became indistinguishable.
|
|
357
341
|
outputSchema: {
|
|
358
342
|
frozen: z.boolean().optional(),
|
|
343
|
+
frame: z
|
|
344
|
+
.number()
|
|
345
|
+
.optional()
|
|
346
|
+
.describe('After stepFrames: the frames stepped since the page loaded.'),
|
|
347
|
+
nowMs: z
|
|
348
|
+
.number()
|
|
349
|
+
.optional()
|
|
350
|
+
.describe("After stepFrames: the page's performance.now(), in virtual milliseconds."),
|
|
351
|
+
},
|
|
352
|
+
handler: async (deps, args) => {
|
|
353
|
+
if (args['stepFrames'] !== undefined) {
|
|
354
|
+
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
355
|
+
const frames = StepFramesSchema.parse(args['stepFrames']);
|
|
356
|
+
const stepped = await deps.pool?.stepLease(session.id, frames);
|
|
357
|
+
if (stepped === undefined) {
|
|
358
|
+
throw new Error(NOT_DETERMINISTIC);
|
|
359
|
+
}
|
|
360
|
+
return { frozen: true, ...stepped };
|
|
361
|
+
}
|
|
362
|
+
return commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.CLOCK, {
|
|
363
|
+
freeze: args['freeze'],
|
|
364
|
+
advanceMs: args['advanceMs'],
|
|
365
|
+
reset: args['reset'],
|
|
366
|
+
});
|
|
359
367
|
},
|
|
360
|
-
handler: (deps, args) => commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.CLOCK, {
|
|
361
|
-
freeze: args['freeze'],
|
|
362
|
-
advanceMs: args['advanceMs'],
|
|
363
|
-
reset: args['reset'],
|
|
364
|
-
}),
|
|
365
368
|
},
|
|
366
369
|
{
|
|
367
370
|
name: ReticleTool.STATE,
|
|
@@ -62,13 +62,5 @@ export declare const HOVER_OFF_VIEWPORT_MSG = "cannot hover: the target is outsi
|
|
|
62
62
|
* or throws.
|
|
63
63
|
*/
|
|
64
64
|
export declare function tryRealInput(deps: ToolDeps, session: Session, ref: string, action: ActionType, args: Record<string, unknown>): Promise<RealActResult>;
|
|
65
|
-
/**
|
|
66
|
-
* The session a flow replay runs on, with a press sent to the lease's real keyboard.
|
|
67
|
-
*
|
|
68
|
-
* Replay dispatches its steps itself, not through `reticle_act`, so a recorded Tab replayed as a
|
|
69
|
-
* synthetic keydown that moves no focus, and a keyboard flow that passed live drifted on replay.
|
|
70
|
-
* Every other command, and a press this lease cannot drive for real, goes to the page as before.
|
|
71
|
-
* A Proxy, not a copy: the session keeps private state its own methods read.
|
|
72
|
-
*/
|
|
73
65
|
export declare function withLeaseKeyboard(deps: ToolDeps, session: Session): Session;
|
|
74
66
|
export {};
|
|
@@ -19,14 +19,15 @@
|
|
|
19
19
|
* the base64 4/3 inflation factor applied, so the encoded payload always fits in one WebSocket
|
|
20
20
|
* frame.
|
|
21
21
|
*/
|
|
22
|
-
import { ActionType, clickInputProblem, DriveErrorCode, ElementState, INSPECT_IN_VIEWPORT_FIELD, INSPECT_REVEAL_ARG, InputMode, InputModeReason, ReticleCommand, TRANSPORT_LIMITS, } from '@reticlehq/core';
|
|
23
|
-
import {
|
|
22
|
+
import { ACT_POINT_ARG, ActionType, actPointProblem, boxFractionFromArgs, clickInputProblem, DRAG_TO_POINT_ARG, DriveErrorCode, ElementState, INSPECT_IN_VIEWPORT_FIELD, INSPECT_REVEAL_ARG, InputMode, InputModeReason, ReticleCommand, TRANSPORT_LIMITS, } from '@reticlehq/core';
|
|
23
|
+
import { boxPoint, DriveError, isRealInputAction, unspellablePressReason, } from '../../portal/input/real-input.js';
|
|
24
24
|
import { assertDragNotDestructive, assertNotDestructive } from './act/act-danger.js';
|
|
25
25
|
import { assertNativeAllowed } from '../../portal/production/production-drive.js';
|
|
26
26
|
import { NATIVE_INPUT_ARG } from '@reticlehq/core';
|
|
27
27
|
import { asNumber, asRecord, asString } from '@reticlehq/core';
|
|
28
28
|
import { commandOrThrow } from './tool-kit.js';
|
|
29
29
|
import { asBox } from './act/act-helpers.js';
|
|
30
|
+
import { isStaleRefError } from './act/act-sequence-retry.js';
|
|
30
31
|
import { isAbsolute, join, relative, extname, basename, resolve } from 'node:path';
|
|
31
32
|
import { homedir } from 'node:os';
|
|
32
33
|
import { RETICLE_CONFIG_BASENAME } from '../../command/cli/ports/resolve/cli-port.js';
|
|
@@ -269,6 +270,15 @@ function performArgsOf(inner) {
|
|
|
269
270
|
if (holdMs !== undefined) {
|
|
270
271
|
performArgs.holdMs = holdMs;
|
|
271
272
|
}
|
|
273
|
+
// A point inside the target: a canvas has no child elements, so its centre is not enough.
|
|
274
|
+
const at = boxFractionFromArgs(inner, ACT_POINT_ARG);
|
|
275
|
+
if (at !== undefined) {
|
|
276
|
+
performArgs.at = at;
|
|
277
|
+
}
|
|
278
|
+
const to = boxFractionFromArgs(inner, DRAG_TO_POINT_ARG);
|
|
279
|
+
if (to !== undefined) {
|
|
280
|
+
performArgs.to = to;
|
|
281
|
+
}
|
|
272
282
|
// `keys` and `code` are read but not forwarded: both are routed away by the caller — a multi-key
|
|
273
283
|
// sequence and an explicit physical code have no driver spelling — so carrying them into the
|
|
274
284
|
// provider would only invite it to guess at a gesture the caller did not ask for.
|
|
@@ -284,7 +294,11 @@ const ANCHOR_FIELDS = [
|
|
|
284
294
|
'name',
|
|
285
295
|
'nth',
|
|
286
296
|
'of',
|
|
297
|
+
'componentNth',
|
|
298
|
+
'componentOf',
|
|
299
|
+
'ownText',
|
|
287
300
|
'secret',
|
|
301
|
+
'within',
|
|
288
302
|
];
|
|
289
303
|
/**
|
|
290
304
|
* Press with a leased page's real keyboard, or undefined when this session is not such a lease.
|
|
@@ -298,6 +312,31 @@ const ANCHOR_FIELDS = [
|
|
|
298
312
|
* keeps the in-page press, with the reason: a person's keyboard could not reach it either.
|
|
299
313
|
* ponytail: lease only; the drive provider keeps its documented element-press rule.
|
|
300
314
|
*/
|
|
315
|
+
/**
|
|
316
|
+
* Put focus on a press's target, as a person does before they press a key in a field.
|
|
317
|
+
*
|
|
318
|
+
* A real keyboard cannot aim at an element, but focus can: focus the element, confirm it holds
|
|
319
|
+
* focus, and the real key then lands there, with every default action a browser gives a trusted
|
|
320
|
+
* key (an Enter in a text field submits its form). Returns what the focused element is called, so
|
|
321
|
+
* the saved step can find it again (the real keyboard reports no element of its own), or
|
|
322
|
+
* `undefined` when the element did not take focus. No ref: nothing to focus, nothing to name.
|
|
323
|
+
*/
|
|
324
|
+
async function focusForPress(deps, session, ref) {
|
|
325
|
+
if (0 === ref.length) {
|
|
326
|
+
return {};
|
|
327
|
+
}
|
|
328
|
+
const focus = asRecord(await commandOrThrow(deps, session.id, ReticleCommand.ACT, {
|
|
329
|
+
ref,
|
|
330
|
+
action: ActionType.FOCUS,
|
|
331
|
+
args: {},
|
|
332
|
+
}));
|
|
333
|
+
const inspected = asRecord(await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, { ref }));
|
|
334
|
+
const states = Array.isArray(inspected['states']) ? inspected['states'] : [];
|
|
335
|
+
if (!states.includes(ElementState.FOCUSED)) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
return Object.fromEntries(ANCHOR_FIELDS.flatMap((k) => (k in focus ? [[k, focus[k]]] : [])));
|
|
339
|
+
}
|
|
301
340
|
async function pressOnLease(deps, session, ref, inner) {
|
|
302
341
|
const pool = deps.pool;
|
|
303
342
|
if (pool === undefined || !pool.hasKeyboard(session.id)) {
|
|
@@ -307,21 +346,9 @@ async function pressOnLease(deps, session, ref, inner) {
|
|
|
307
346
|
if (unspellable !== undefined) {
|
|
308
347
|
return synthetic(unspellable);
|
|
309
348
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (0 < ref.length) {
|
|
314
|
-
const focus = asRecord(await commandOrThrow(deps, session.id, ReticleCommand.ACT, {
|
|
315
|
-
ref,
|
|
316
|
-
action: ActionType.FOCUS,
|
|
317
|
-
args: {},
|
|
318
|
-
}));
|
|
319
|
-
anchor = Object.fromEntries(ANCHOR_FIELDS.flatMap((k) => (k in focus ? [[k, focus[k]]] : [])));
|
|
320
|
-
const inspected = asRecord(await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, { ref }));
|
|
321
|
-
const states = Array.isArray(inspected['states']) ? inspected['states'] : [];
|
|
322
|
-
if (!states.includes(ElementState.FOCUSED)) {
|
|
323
|
-
return synthetic(InputModeReason.TARGET_NOT_FOCUSABLE);
|
|
324
|
-
}
|
|
349
|
+
const anchor = await focusForPress(deps, session, ref);
|
|
350
|
+
if (anchor === undefined) {
|
|
351
|
+
return synthetic(InputModeReason.TARGET_NOT_FOCUSABLE);
|
|
325
352
|
}
|
|
326
353
|
const performed = await pool.pressLease(session.id, performArgsOf(inner));
|
|
327
354
|
if (performed?.performed !== true) {
|
|
@@ -403,20 +430,21 @@ async function hoverForReal(deps, session, ref, inner, provider) {
|
|
|
403
430
|
if (!inView) {
|
|
404
431
|
throw new Error(HOVER_OFF_VIEWPORT_MSG);
|
|
405
432
|
}
|
|
433
|
+
const at = boxFractionFromArgs(inner, ACT_POINT_ARG);
|
|
406
434
|
if (providerReady && provider !== undefined) {
|
|
407
435
|
try {
|
|
408
|
-
const performed = await provider.perform(session.url, ActionType.HOVER, box, {});
|
|
436
|
+
const performed = await provider.perform(session.url, ActionType.HOVER, box, at === undefined ? {} : { at });
|
|
409
437
|
// Hover always has a resolved box, so there is always a center to report: the provider's own
|
|
410
438
|
// reading, or the box's if it reported none. Both are the same point, not a fabricated one.
|
|
411
439
|
if (performed.performed) {
|
|
412
|
-
return hoverSucceeded(performed.center ??
|
|
440
|
+
return hoverSucceeded(performed.center ?? boxPoint(box, at), scrolled);
|
|
413
441
|
}
|
|
414
442
|
}
|
|
415
443
|
catch {
|
|
416
444
|
// A provider error used to fall back to synthetic dispatch. For hover that is a lie.
|
|
417
445
|
}
|
|
418
446
|
}
|
|
419
|
-
const { cx, cy } =
|
|
447
|
+
const { cx, cy } = boxPoint(box, at);
|
|
420
448
|
if (true === (await deps.pool?.hoverLease(session.id, cx, cy))) {
|
|
421
449
|
return hoverSucceeded({ cx, cy }, scrolled);
|
|
422
450
|
}
|
|
@@ -436,6 +464,11 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
436
464
|
const provider = deps.realInput;
|
|
437
465
|
const inner = asRecord(args['args']);
|
|
438
466
|
const askedForNative = true === inner[NATIVE_INPUT_ARG];
|
|
467
|
+
// The page refuses the same point on the synthetic path, with the same sentence.
|
|
468
|
+
const pointProblem = actPointProblem(action, inner);
|
|
469
|
+
if (pointProblem !== undefined) {
|
|
470
|
+
throw new Error(`cannot ${action}: ${pointProblem}`);
|
|
471
|
+
}
|
|
439
472
|
if (action === ActionType.HOVER) {
|
|
440
473
|
return hoverForReal(deps, session, ref, inner, provider);
|
|
441
474
|
}
|
|
@@ -460,8 +493,10 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
460
493
|
}
|
|
461
494
|
// Two `press`es this module will NOT drive, and neither is a fallback — the reason says which
|
|
462
495
|
// one was asked for, instead of a different gesture being sent. See `unspellablePressReason`.
|
|
496
|
+
// A press aimed at an element is focused first (focusForPress), as on a lease; only a chord and
|
|
497
|
+
// an explicit key code stay in the page.
|
|
463
498
|
if (action === ActionType.PRESS) {
|
|
464
|
-
const unspellable = unspellablePressReason(
|
|
499
|
+
const unspellable = unspellablePressReason('', inner);
|
|
465
500
|
if (unspellable !== undefined) {
|
|
466
501
|
return synthetic(unspellable);
|
|
467
502
|
}
|
|
@@ -500,8 +535,12 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
500
535
|
return synthetic(InputModeReason.PAGE_NOT_CORRELATED);
|
|
501
536
|
}
|
|
502
537
|
const performArgs = performArgsOf(inner);
|
|
503
|
-
// A
|
|
538
|
+
// A key addresses focus, not a point: focus the target (if any), then skip INSPECT and the box.
|
|
504
539
|
if (action === ActionType.PRESS) {
|
|
540
|
+
const anchor = await focusForPress(deps, session, ref);
|
|
541
|
+
if (anchor === undefined) {
|
|
542
|
+
return synthetic(InputModeReason.TARGET_NOT_FOCUSABLE);
|
|
543
|
+
}
|
|
505
544
|
try {
|
|
506
545
|
const performed = await provider.perform(session.url, action, PRESS_HAS_NO_BOX, performArgs);
|
|
507
546
|
if (!performed.performed) {
|
|
@@ -516,6 +555,7 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
516
555
|
// `holdMs` to — an agent checking for an armed hold-to-confirm control read `undefined`.
|
|
517
556
|
return {
|
|
518
557
|
result: {
|
|
558
|
+
...anchor,
|
|
519
559
|
performed: true,
|
|
520
560
|
action,
|
|
521
561
|
inputMode: performed.inputMode,
|
|
@@ -557,8 +597,12 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
557
597
|
return synthetic(InputModeReason.ELEMENT_NOT_LOCATABLE);
|
|
558
598
|
}
|
|
559
599
|
let toBox;
|
|
560
|
-
|
|
561
|
-
|
|
600
|
+
const toRef = asString(inner['toRef']);
|
|
601
|
+
// A drag that names only its end point moves inside the source, such as a stroke on one canvas.
|
|
602
|
+
if (action === ActionType.DRAG && toRef === undefined && performArgs.to !== undefined) {
|
|
603
|
+
toBox = box;
|
|
604
|
+
}
|
|
605
|
+
else if (action === ActionType.DRAG) {
|
|
562
606
|
if (toRef === undefined) {
|
|
563
607
|
return synthetic(InputModeReason.DRAG_TARGET_UNRESOLVED);
|
|
564
608
|
}
|
|
@@ -614,18 +658,89 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
614
658
|
}
|
|
615
659
|
}
|
|
616
660
|
/**
|
|
617
|
-
* The session a flow replay runs on, with a press sent to the
|
|
661
|
+
* The session a flow replay runs on, with a press sent to the real keyboard, as `reticle_act` does.
|
|
618
662
|
*
|
|
619
663
|
* Replay dispatches its steps itself, not through `reticle_act`, so a recorded Tab replayed as a
|
|
620
664
|
* synthetic keydown that moves no focus, and a keyboard flow that passed live drifted on replay.
|
|
621
|
-
*
|
|
622
|
-
*
|
|
665
|
+
* The real keyboard is a lease's, or the driven browser's when a real-input provider is configured:
|
|
666
|
+
* that is `reticle verify`'s own page, where a synthetic Enter submitted no form, so a recorded
|
|
667
|
+
* "fill, press Enter" journey failed on a working app. Every other command, and a press that cannot
|
|
668
|
+
* be driven for real, goes to the page as before. A Proxy, not a copy: the session keeps private
|
|
669
|
+
* state its own methods read.
|
|
670
|
+
*/
|
|
671
|
+
/**
|
|
672
|
+
* A sequence with a press in it, run as the page and the keyboard would run it.
|
|
673
|
+
*
|
|
674
|
+
* Replay sends a recorded sequence as one in-page command, so a press inside it stayed a synthetic
|
|
675
|
+
* key event: a recorded "fill, press Enter" sequence submitted no form and failed on a working app.
|
|
676
|
+
* The other sub-steps still go to the page together, in order; each press goes to the real keyboard
|
|
677
|
+
* (focus, then the key), and to the page as before when the real keyboard cannot take it. The
|
|
678
|
+
* first failure is returned as the page returned it, so a stale-ref retry still finds its step.
|
|
623
679
|
*/
|
|
680
|
+
async function sequenceWithRealPresses(deps, session, steps, timeoutMs) {
|
|
681
|
+
const share = (n) => timeoutMs === undefined ? undefined : Math.ceil((timeoutMs * n) / Math.max(1, steps.length));
|
|
682
|
+
let last = {
|
|
683
|
+
kind: 'command_result',
|
|
684
|
+
id: ReticleCommand.ACT_SEQUENCE,
|
|
685
|
+
ok: true,
|
|
686
|
+
result: {},
|
|
687
|
+
};
|
|
688
|
+
let chunk = [];
|
|
689
|
+
const flush = async () => {
|
|
690
|
+
if (0 === chunk.length) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
last = await session.command(ReticleCommand.ACT_SEQUENCE, { steps: chunk }, share(chunk.length));
|
|
694
|
+
chunk = [];
|
|
695
|
+
return last.ok;
|
|
696
|
+
};
|
|
697
|
+
for (const sub of steps) {
|
|
698
|
+
const step = asRecord(sub);
|
|
699
|
+
if (ActionType.PRESS !== step['action']) {
|
|
700
|
+
chunk.push(sub);
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (!(await flush())) {
|
|
704
|
+
return last;
|
|
705
|
+
}
|
|
706
|
+
// A click before this press can load a new page, and then the press's ref is stale. Answered as
|
|
707
|
+
// the page answers a stale ref, so the caller's retry finds the press again on the new page.
|
|
708
|
+
let real;
|
|
709
|
+
try {
|
|
710
|
+
real = await tryRealInput(deps, session, asString(step['ref']) ?? '', ActionType.PRESS, step);
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
714
|
+
if (!isStaleRefError(message)) {
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
return { kind: 'command_result', id: ActionType.PRESS, ok: false, error: message };
|
|
718
|
+
}
|
|
719
|
+
if (real.result === undefined) {
|
|
720
|
+
chunk.push(sub);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
last = {
|
|
724
|
+
kind: 'command_result',
|
|
725
|
+
id: ActionType.PRESS,
|
|
726
|
+
ok: true,
|
|
727
|
+
result: { dispatched: true, settled: real.settled, ...asRecord(real.result) },
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
await flush();
|
|
731
|
+
return last;
|
|
732
|
+
}
|
|
624
733
|
export function withLeaseKeyboard(deps, session) {
|
|
625
|
-
if (deps.realInput
|
|
734
|
+
if (deps.realInput === undefined && deps.pool?.hasKeyboard(session.id) !== true) {
|
|
626
735
|
return session;
|
|
627
736
|
}
|
|
628
737
|
const command = async (name, args = {}, timeoutMs) => {
|
|
738
|
+
const steps = args['steps'];
|
|
739
|
+
if (ReticleCommand.ACT_SEQUENCE === name &&
|
|
740
|
+
Array.isArray(steps) &&
|
|
741
|
+
steps.some((sub) => ActionType.PRESS === asRecord(sub)['action'])) {
|
|
742
|
+
return await sequenceWithRealPresses(deps, session, steps, timeoutMs);
|
|
743
|
+
}
|
|
629
744
|
if (ReticleCommand.ACT === name && ActionType.PRESS === args['action']) {
|
|
630
745
|
const ref = asString(args['ref']) ?? '';
|
|
631
746
|
const real = await tryRealInput(deps, session, ref, ActionType.PRESS, args);
|