@reticlehq/server 3.7.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +59 -68
- package/bin/reticle.js +9 -2
- package/dist/command/cli/adhoc-suite.js +1 -1
- package/dist/command/cli/adhoc-verdict.d.ts +12 -0
- package/dist/command/cli/adhoc-verdict.js +62 -4
- package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
- package/dist/command/cli/answers/gate-hook-message.js +5 -1
- package/dist/command/cli/auth/cloud-session.d.ts +11 -19
- package/dist/command/cli/auth/cloud-session.js +23 -0
- package/dist/command/cli/auth/device-flow.d.ts +3 -37
- package/dist/command/cli/cli-doctor.js +66 -21
- package/dist/command/cli/cli-flow-commands.js +53 -11
- package/dist/command/cli/cli-kill.d.ts +8 -0
- package/dist/command/cli/cli-kill.js +43 -3
- package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
- package/dist/command/cli/cli-parse-feedback.js +13 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
- package/dist/command/cli/cli-parse-grammar.js +1 -6
- package/dist/command/cli/cli-parse-serve.d.ts +24 -0
- package/dist/command/cli/cli-parse-serve.js +124 -0
- package/dist/command/cli/cli-parse.d.ts +12 -11
- package/dist/command/cli/cli-parse.js +100 -123
- package/dist/command/cli/cli-usage.d.ts +0 -11
- package/dist/command/cli/cli-usage.js +43 -16
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +29 -14
- package/dist/command/cli/cloud-cli.js +27 -9
- package/dist/command/cli/cloud-kit.js +3 -3
- package/dist/command/cli/cloud-link.d.ts +1 -20
- package/dist/command/cli/cloud-link.js +2 -0
- package/dist/command/cli/cloud-login.d.ts +6 -2
- package/dist/command/cli/cloud-login.js +13 -41
- package/dist/command/cli/config/checkout.d.ts +10 -0
- package/dist/command/cli/config/checkout.js +78 -0
- package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
- package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
- 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/drive/drive-attach.d.ts +5 -0
- package/dist/command/cli/drive/drive-attach.js +21 -1
- package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
- package/dist/command/cli/hook/codex-transcript.js +224 -0
- package/dist/command/cli/hook/episode-structure.d.ts +24 -0
- package/dist/command/cli/hook/episode-structure.js +138 -0
- package/dist/command/cli/hook/hook-command.d.ts +9 -0
- package/dist/command/cli/hook/hook-command.js +211 -0
- package/dist/command/cli/hook/hook-fast.d.ts +31 -0
- package/dist/command/cli/hook/hook-fast.js +136 -0
- package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
- package/dist/command/cli/hook/prompt-hook.js +199 -0
- package/dist/command/cli/hook/tool-hook.d.ts +21 -0
- package/dist/command/cli/hook/tool-hook.js +259 -0
- package/dist/command/cli/launch/cli-launch.d.ts +2 -0
- package/dist/command/cli/launch/cli-launch.js +22 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
- package/dist/command/cli/mcp-command.d.ts +9 -0
- package/dist/command/cli/mcp-command.js +127 -41
- package/dist/command/cli/open-navigate.d.ts +43 -0
- package/dist/command/cli/open-navigate.js +85 -0
- package/dist/command/cli/ports/port-holder.d.ts +16 -4
- package/dist/command/cli/ports/port-holder.js +58 -12
- package/dist/command/cli/ports/resolve/cli-port.js +18 -2
- package/dist/command/cli/project-name.d.ts +1 -7
- package/dist/command/cli/project-name.js +1 -0
- package/dist/command/cli/report-command.d.ts +8 -2
- package/dist/command/cli/report-command.js +95 -18
- package/dist/command/cli/status-fields.d.ts +9 -2
- package/dist/command/cli/status-fields.js +148 -4
- package/dist/command/cli/try-command.d.ts +3 -4
- package/dist/command/cli/try-command.js +6 -11
- package/dist/command/cli/verify-results.d.ts +4 -1
- package/dist/command/cli/verify-results.js +3 -1
- package/dist/command/cli.d.ts +1 -7
- package/dist/command/cli.js +79 -160
- package/dist/command/daemon/daemon-resilience.d.ts +6 -1
- package/dist/command/daemon/daemon-resilience.js +31 -18
- package/dist/command/daemon/daemon.d.ts +11 -0
- package/dist/command/daemon/daemon.js +70 -2
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
- package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
- package/dist/command/daemon/wake-decision.js +4 -2
- package/dist/command/daemon/win32-escape.d.ts +32 -0
- package/dist/command/daemon/win32-escape.js +119 -0
- package/dist/command/setup/agent-configs.d.ts +9 -0
- package/dist/command/setup/agent-configs.js +9 -0
- package/dist/command/setup/auto-approve.d.ts +10 -3
- package/dist/command/setup/auto-approve.js +51 -3
- package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
- package/dist/command/setup/bringup/dev-server-wait.js +5 -1
- package/dist/command/setup/bringup/ensure-daemon.js +4 -7
- package/dist/command/setup/init/init-host.js +6 -0
- package/dist/command/setup/init/init-port.js +5 -4
- package/dist/command/setup/init/init-runtime.js +7 -1
- package/dist/command/setup/node-effects.d.ts +8 -0
- package/dist/command/setup/node-effects.js +36 -4
- package/dist/command/setup/probe/dev-port.d.ts +26 -0
- package/dist/command/setup/probe/dev-port.js +117 -0
- package/dist/command/setup/probe/page-probe.d.ts +1 -1
- package/dist/command/setup/probe/page-probe.js +11 -3
- package/dist/command/setup/remaining-steps.d.ts +0 -7
- package/dist/command/setup/remaining-steps.js +3 -2
- package/dist/command/setup/run-setup.d.ts +11 -0
- package/dist/command/setup/run-setup.js +33 -5
- package/dist/command/setup/setup-command.d.ts +2 -0
- package/dist/command/setup/setup-command.js +22 -4
- package/dist/command/update/updater.d.ts +1 -6
- package/dist/command/update/updater.js +4 -5
- package/dist/command/update/version-seen.d.ts +24 -0
- package/dist/command/update/version-seen.js +65 -0
- package/dist/command/version/peer-announce.d.ts +9 -1
- package/dist/command/version/peer-announce.js +25 -2
- package/dist/command/version/sdk-fix.d.ts +5 -6
- package/dist/command/version/sdk-fix.js +17 -32
- package/dist/command/version/version-skew.d.ts +16 -1
- package/dist/command/version/version-skew.js +84 -10
- package/dist/features/crawl/crawl-tools.js +2 -2
- package/dist/features/crawl/crawl.js +2 -0
- package/dist/features/exhaust/ledger.d.ts +15 -96
- package/dist/features/exhaust/ledger.js +3 -3
- package/dist/features/harness/drive-runs.d.ts +28 -4
- package/dist/features/harness/drive-runs.js +99 -7
- package/dist/features/harness/drivers.d.ts +1 -1
- package/dist/features/harness/drivers.js +1 -1
- package/dist/features/harness/harness.d.ts +5 -0
- package/dist/features/harness/harness.js +24 -12
- package/dist/features/harness/platform/platform-drives.d.ts +7 -1
- package/dist/features/harness/platform/platform-drives.js +12 -7
- package/dist/features/harness/platform/refusal.d.ts +1 -1
- package/dist/features/harness/platform/refusal.js +4 -4
- package/dist/features/harness/platform/remote-drive.js +70 -28
- package/dist/features/harness/platform/server-driver.js +22 -1
- package/dist/features/harness/platform-config.d.ts +1 -10
- package/dist/features/harness/platform-config.js +0 -20
- package/dist/features/license/license.d.ts +2 -14
- package/dist/features/visual/step-shots.d.ts +33 -1
- package/dist/features/visual/step-shots.js +155 -1
- package/dist/features/visual/visual-tools.js +12 -4
- package/dist/hooks/coding-agents.d.ts +6 -0
- package/dist/hooks/coding-agents.js +24 -3
- package/dist/hooks/episode-stamp.d.ts +32 -0
- package/dist/hooks/episode-stamp.js +61 -0
- package/dist/hooks/hook-commands.js +19 -9
- package/dist/index.d.ts +5 -1
- package/dist/index.js +66 -24
- package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
- package/dist/judgement/app-graph/journal-steps.js +92 -0
- package/dist/judgement/capsule/capsule-store.d.ts +2 -39
- package/dist/judgement/domain/domain-model.js +1 -10
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
- package/dist/judgement/runs/artifact/run-store.js +153 -28
- package/dist/judgement/runs/default-run-id.d.ts +3 -1
- package/dist/judgement/runs/default-run-id.js +15 -3
- package/dist/judgement/runs/drive-run.d.ts +3 -3
- package/dist/judgement/runs/drive-run.js +11 -7
- package/dist/judgement/runs/run-ledger.d.ts +21 -0
- package/dist/judgement/runs/run-ledger.js +143 -0
- package/dist/judgement/runs/runner-port.d.ts +1 -1
- package/dist/judgement/runs/runner-port.js +8 -2
- package/dist/judgement/runs/verification-sync.d.ts +4 -3
- package/dist/judgement/runs/verification-sync.js +27 -5
- package/dist/language/flows/affected-tools.js +2 -1
- package/dist/language/flows/annotate-notes/annotate.js +8 -3
- package/dist/language/flows/annotate-tools.js +1 -1
- package/dist/language/flows/change/affected.d.ts +8 -0
- package/dist/language/flows/change/affected.js +20 -7
- package/dist/language/flows/decision.js +19 -4
- package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
- package/dist/language/flows/fields/flow-secret-field.js +67 -5
- package/dist/language/flows/flow-anchor.d.ts +5 -0
- package/dist/language/flows/flow-anchor.js +4 -0
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +9 -0
- package/dist/language/flows/flow-expect-grammar.js +15 -10
- package/dist/language/flows/flow-intent.js +6 -1
- package/dist/language/flows/flow-journey.d.ts +8 -0
- package/dist/language/flows/flow-journey.js +13 -0
- package/dist/language/flows/flow-lease.d.ts +27 -0
- package/dist/language/flows/flow-lease.js +59 -0
- package/dist/language/flows/flow-replay-run.d.ts +1 -87
- package/dist/language/flows/flow-replay-run.js +111 -310
- package/dist/language/flows/flow-replay.js +21 -19
- package/dist/language/flows/flow-replayable.d.ts +38 -0
- package/dist/language/flows/flow-replayable.js +179 -0
- package/dist/language/flows/flow-start-path.d.ts +108 -0
- package/dist/language/flows/flow-start-path.js +313 -0
- package/dist/language/flows/flow-step-runners.d.ts +23 -2
- package/dist/language/flows/flow-step-runners.js +191 -32
- package/dist/language/flows/flow-store-for-session.d.ts +8 -1
- package/dist/language/flows/flow-store-for-session.js +6 -0
- package/dist/language/flows/flow-tools.d.ts +2 -23
- package/dist/language/flows/flow-tools.js +63 -113
- package/dist/language/flows/flows.d.ts +4 -6
- package/dist/language/flows/flows.js +90 -15
- package/dist/language/flows/heal-run.d.ts +10 -4
- package/dist/language/flows/heal-run.js +26 -9
- package/dist/language/flows/recording/flake.d.ts +3 -27
- package/dist/language/flows/recording/flake.js +1 -1
- package/dist/language/flows/recording/recording-backtrack.js +1 -4
- package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
- package/dist/language/flows/recording/tape/recordings.js +9 -3
- package/dist/language/flows/replay.d.ts +25 -5
- package/dist/language/flows/replay.js +83 -16
- package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
- package/dist/language/flows/suite/coverage.d.ts +9 -0
- package/dist/language/flows/suite/coverage.js +12 -1
- package/dist/language/flows/suite-selection.d.ts +25 -0
- package/dist/language/flows/suite-selection.js +41 -9
- package/dist/machine/git-tracks.d.ts +6 -0
- package/dist/machine/git-tracks.js +36 -0
- package/dist/memory/cloud/background-sync.d.ts +25 -0
- package/dist/memory/cloud/background-sync.js +79 -0
- package/dist/memory/cloud/cloud-config.d.ts +12 -1
- package/dist/memory/cloud/cloud-config.js +39 -13
- package/dist/memory/cloud/cloud-sync.d.ts +3 -24
- package/dist/memory/cloud/cloud-sync.js +5 -2
- package/dist/memory/cloud/harness-config.d.ts +0 -7
- package/dist/memory/cloud/memory-scope.d.ts +19 -0
- package/dist/memory/cloud/memory-scope.js +48 -0
- package/dist/memory/cloud/sync-answer.d.ts +43 -0
- package/dist/memory/cloud/sync-answer.js +42 -0
- package/dist/memory/cloud/sync-cycle.d.ts +40 -10
- package/dist/memory/cloud/sync-cycle.js +97 -144
- package/dist/memory/cloud/sync-daemon.d.ts +50 -3
- package/dist/memory/cloud/sync-daemon.js +473 -83
- package/dist/memory/cloud/sync-describe.d.ts +28 -0
- package/dist/memory/cloud/sync-describe.js +92 -0
- package/dist/memory/cloud/sync-disk.d.ts +9 -1
- package/dist/memory/cloud/sync-disk.js +137 -33
- package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
- package/dist/memory/cloud/sync-flow-versions.js +42 -0
- package/dist/memory/impact/impact-store.js +32 -15
- package/dist/memory/intent/intent-shard.d.ts +41 -266
- package/dist/memory/intent/intent-tools.js +20 -61
- package/dist/memory/intent/record-request.d.ts +27 -0
- package/dist/memory/intent/record-request.js +88 -0
- package/dist/memory/intent/settled-words.js +9 -1
- package/dist/memory/journal/drive-flow.d.ts +3 -0
- package/dist/memory/journal/drive-flow.js +11 -41
- package/dist/memory/journal/envelope-store.js +1 -1
- package/dist/memory/journal/envelope.d.ts +6 -154
- package/dist/memory/journal/episode-log.d.ts +107 -0
- package/dist/memory/journal/episode-log.js +302 -0
- package/dist/memory/journal/journal-recorder.d.ts +7 -0
- package/dist/memory/journal/journal-recorder.js +22 -9
- package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
- package/dist/memory/journal/on-disk/ambient-file.js +1 -1
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
- package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
- package/dist/memory/journal/self-observation.js +3 -1
- package/dist/memory/journal/session-end.js +23 -9
- package/dist/memory/journal/session-journal.js +39 -20
- package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
- package/dist/memory/project/artifact-root-resolver.js +68 -23
- package/dist/memory/project/artifact-root.d.ts +17 -3
- package/dist/memory/project/artifact-root.js +47 -9
- package/dist/memory/project/fs/fs-port.d.ts +5 -0
- package/dist/memory/project/fs/fs-port.js +13 -0
- package/dist/memory/project/learned-routes.d.ts +3 -4
- package/dist/memory/project/learned-routes.js +3 -11
- package/dist/memory/project/link-directory.js +5 -2
- package/dist/memory/project/root-claims.d.ts +3 -1
- package/dist/memory/project/root-claims.js +41 -7
- package/dist/memory/project/session-root.d.ts +1 -1
- package/dist/memory/project/session-root.js +1 -15
- package/dist/memory/project/sync-status.js +5 -6
- package/dist/memory/recall/memory-tools.js +2 -2
- package/dist/memory/recall/platform-memory.d.ts +3 -1
- package/dist/memory/recall/platform-memory.js +9 -1
- package/dist/memory/recall/project-memory.js +15 -19
- package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
- package/dist/portal/bridge/auth-failure-reason.js +19 -0
- package/dist/portal/bridge/bridge.d.ts +19 -4
- package/dist/portal/bridge/bridge.js +150 -177
- package/dist/portal/bridge/harness-request.d.ts +15 -0
- package/dist/portal/bridge/harness-request.js +19 -0
- package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
- package/dist/portal/bridge/origin/origin-follow.js +110 -0
- package/dist/portal/bridge/pairing-token.d.ts +6 -0
- package/dist/portal/bridge/pairing-token.js +47 -16
- package/dist/portal/bridge/protocol-skew.d.ts +27 -2
- package/dist/portal/bridge/protocol-skew.js +66 -2
- package/dist/portal/bridge/session-nonces.d.ts +46 -0
- package/dist/portal/bridge/session-nonces.js +72 -0
- package/dist/portal/input/viewport-tools.d.ts +31 -0
- package/dist/portal/input/viewport-tools.js +106 -15
- package/dist/portal/pool/bounded-close.d.ts +23 -0
- package/dist/portal/pool/bounded-close.js +38 -0
- package/dist/portal/pool/browser-pool.d.ts +44 -39
- package/dist/portal/pool/browser-pool.js +247 -95
- package/dist/portal/pool/context-permissions.d.ts +9 -1
- package/dist/portal/pool/context-permissions.js +26 -0
- package/dist/portal/pool/lease-emulation.d.ts +37 -0
- package/dist/portal/pool/lease-emulation.js +29 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +74 -7
- package/dist/portal/pool/pool-contract.d.ts +23 -1
- package/dist/portal/pool/pool-lease.d.ts +128 -0
- package/dist/portal/pool/pool-lease.js +49 -0
- package/dist/portal/pool/zero-install.d.ts +8 -1
- package/dist/portal/pool/zero-install.js +24 -2
- package/dist/portal/production/production-activation.d.ts +136 -0
- package/dist/portal/production/production-activation.js +173 -0
- package/dist/portal/production/production-drive.d.ts +52 -0
- package/dist/portal/production/production-drive.js +301 -0
- package/dist/portal/production/production-hello.d.ts +23 -0
- package/dist/portal/production/production-hello.js +23 -0
- package/dist/portal/realm/web-realm.js +42 -15
- package/dist/portal/session/control-envelope.d.ts +1 -1
- package/dist/portal/session/dev-server/served-document.d.ts +0 -5
- package/dist/portal/session/dev-server/served-document.js +6 -8
- package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
- package/dist/portal/session/first-run-wiring.d.ts +3 -36
- package/dist/portal/session/first-run-wiring.js +10 -103
- package/dist/portal/session/live-control-tools.js +61 -19
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +40 -15
- package/dist/portal/session/no-session-watch.d.ts +7 -0
- package/dist/portal/session/no-session-watch.js +67 -23
- package/dist/portal/session/output-budget.d.ts +10 -0
- package/dist/portal/session/output-budget.js +16 -0
- package/dist/portal/session/presence/session-lease.d.ts +2 -0
- package/dist/portal/session/presence/session-lease.js +8 -0
- package/dist/portal/session/recorded-gaps.d.ts +15 -11
- package/dist/portal/session/recorded-gaps.js +39 -18
- package/dist/portal/session/serving-directory.d.ts +24 -2
- package/dist/portal/session/serving-directory.js +93 -5
- package/dist/portal/session/session-info.d.ts +26 -0
- package/dist/portal/session/session-info.js +20 -0
- package/dist/portal/session/session-manager.d.ts +35 -3
- package/dist/portal/session/session-manager.js +69 -14
- package/dist/portal/session/session-successor.js +1 -9
- package/dist/portal/session/session-tools.js +22 -4
- package/dist/portal/session/session.d.ts +10 -10
- package/dist/portal/session/session.js +37 -29
- package/dist/portal/session/zero-install-first-call.d.ts +7 -2
- package/dist/portal/session/zero-install-first-call.js +43 -20
- package/dist/start-wiring.d.ts +2 -2
- package/dist/start-wiring.js +17 -3
- package/dist/status-payload.d.ts +11 -1
- package/dist/status-payload.js +7 -1
- package/dist/surface/agent-presence-grace.d.ts +11 -0
- package/dist/surface/agent-presence-grace.js +34 -0
- package/dist/surface/http-server.d.ts +12 -1
- package/dist/surface/http-server.js +64 -15
- package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
- package/dist/surface/mcp/faults/mcp-outage.js +19 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
- package/dist/surface/mcp/mcp-proxy.js +194 -267
- package/dist/surface/mcp/mcp.d.ts +20 -3
- package/dist/surface/mcp/mcp.js +88 -25
- package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
- package/dist/surface/mcp/proxy/drive-calls.js +114 -5
- package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
- package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
- package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
- package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
- package/dist/surface/mcp/server-instructions.js +35 -16
- package/dist/surface/mcp/shared-params.d.ts +8 -0
- package/dist/surface/mcp/shared-params.js +15 -1
- package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
- package/dist/surface/mcp/sse-frame-parser.js +18 -0
- package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
- package/dist/surface/mcp/surface-vocabulary.js +13 -0
- package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
- package/dist/surface/mcp/tool-catalog-cache.js +14 -4
- package/dist/surface/tools/account-tool.d.ts +4 -3
- package/dist/surface/tools/account-tool.js +20 -31
- package/dist/surface/tools/act/act-danger.d.ts +2 -0
- package/dist/surface/tools/act/act-danger.js +41 -14
- package/dist/surface/tools/act/act-observation.d.ts +11 -0
- package/dist/surface/tools/act/act-observation.js +33 -0
- package/dist/surface/tools/act/act-preflight.js +10 -3
- package/dist/surface/tools/act/act-sequence-retry.js +2 -1
- package/dist/surface/tools/act/act-target.js +19 -5
- package/dist/surface/tools/act/durable.js +1 -1
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
- package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
- package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
- package/dist/surface/tools/act/unsettled-find.js +71 -0
- package/dist/surface/tools/act-sequence-tool.js +53 -24
- package/dist/surface/tools/act-tools.d.ts +2 -3
- package/dist/surface/tools/act-tools.js +49 -47
- package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
- package/dist/surface/tools/agent/for-the-agent.js +41 -3
- package/dist/surface/tools/assert/assert-grade.js +8 -3
- package/dist/surface/tools/assert/assert-verdict.js +4 -2
- package/dist/surface/tools/browser-tools.js +7 -4
- package/dist/surface/tools/chat-drives.js +50 -25
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.d.ts +8 -0
- package/dist/surface/tools/dynamic-tools.js +89 -34
- package/dist/surface/tools/empty-session-list.d.ts +18 -25
- package/dist/surface/tools/empty-session-list.js +67 -38
- package/dist/surface/tools/error-recovery.d.ts +17 -7
- package/dist/surface/tools/error-recovery.js +53 -20
- package/dist/surface/tools/explore-tools.d.ts +10 -0
- package/dist/surface/tools/explore-tools.js +72 -19
- package/dist/surface/tools/feedback-tools.js +23 -2
- package/dist/surface/tools/gap-novelty.d.ts +6 -0
- package/dist/surface/tools/gap-novelty.js +9 -0
- package/dist/surface/tools/gaps/capability-absences.js +4 -3
- package/dist/surface/tools/harness-explore.d.ts +1 -1
- package/dist/surface/tools/harness-explore.js +6 -4
- package/dist/surface/tools/harness-gate.d.ts +43 -21
- package/dist/surface/tools/harness-gate.js +55 -81
- package/dist/surface/tools/harness-script.d.ts +2 -0
- package/dist/surface/tools/harness-script.js +11 -6
- package/dist/surface/tools/harness-toolset.js +3 -4
- package/dist/surface/tools/invoke-tool.d.ts +0 -8
- package/dist/surface/tools/invoke-tool.js +164 -178
- package/dist/surface/tools/lease-hint.d.ts +9 -0
- package/dist/surface/tools/lease-hint.js +22 -3
- package/dist/surface/tools/lease-permissions.d.ts +1 -1
- package/dist/surface/tools/lease-readiness.d.ts +55 -0
- package/dist/surface/tools/lease-readiness.js +150 -2
- package/dist/surface/tools/lease-tools.d.ts +3 -75
- package/dist/surface/tools/lease-tools.js +124 -188
- package/dist/surface/tools/map-tools.d.ts +20 -0
- package/dist/surface/tools/map-tools.js +145 -0
- package/dist/surface/tools/merge-tools.js +11 -7
- package/dist/surface/tools/next-step.js +6 -3
- package/dist/surface/tools/observe-tools.js +46 -35
- package/dist/surface/tools/platform-moment.d.ts +3 -12
- package/dist/surface/tools/platform-moment.js +12 -24
- package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
- package/dist/surface/tools/read/complete-snapshot.js +13 -2
- package/dist/surface/tools/read/query-paginate.js +6 -0
- package/dist/surface/tools/read/query-shape.d.ts +9 -0
- package/dist/surface/tools/read/query-shape.js +15 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
- package/dist/surface/tools/read-tools.js +5 -5
- package/dist/surface/tools/real-input-attempt.d.ts +11 -0
- package/dist/surface/tools/real-input-attempt.js +206 -38
- package/dist/surface/tools/reconcile-tools.js +21 -8
- package/dist/surface/tools/reticle-result.d.ts +3 -0
- package/dist/surface/tools/reticle-result.js +8 -1
- package/dist/surface/tools/run-report.d.ts +22 -0
- package/dist/surface/tools/run-report.js +141 -0
- package/dist/surface/tools/session-ports.d.ts +57 -0
- package/dist/surface/tools/session-ports.js +102 -0
- package/dist/surface/tools/tool-kit.d.ts +58 -4
- package/dist/surface/tools/tool-kit.js +140 -6
- package/dist/surface/tools/tool-surface.d.ts +2 -0
- package/dist/surface/tools/tool-surface.js +45 -0
- package/dist/surface/tools/tools.js +49 -87
- package/dist/telemetry/automation-hint.d.ts +5 -0
- package/dist/telemetry/automation-hint.js +7 -0
- package/dist/telemetry/cli-telemetry.d.ts +22 -0
- package/dist/telemetry/cli-telemetry.js +80 -11
- package/dist/telemetry/daemon-telemetry.d.ts +3 -1
- package/dist/telemetry/daemon-telemetry.js +9 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +8 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/feedback.js +51 -4
- package/dist/telemetry/harness-run.d.ts +3 -3
- package/dist/telemetry/harness-run.js +18 -7
- package/dist/telemetry/hud-events.d.ts +1 -1
- package/dist/telemetry/hud-metrics.d.ts +6 -3
- package/dist/telemetry/hud-metrics.js +20 -8
- package/dist/telemetry/install-source.js +1 -1
- package/dist/telemetry/mcp-connection.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +21 -2
- package/dist/telemetry/onboarding-firsts.d.ts +6 -1
- package/dist/telemetry/onboarding-firsts.js +41 -1
- package/dist/telemetry/proxy-telemetry.d.ts +42 -0
- package/dist/telemetry/proxy-telemetry.js +70 -1
- package/dist/telemetry/session-metrics.d.ts +1 -3
- package/dist/telemetry/session-metrics.js +16 -12
- package/dist/telemetry/telemetry.d.ts +9 -1
- package/dist/telemetry/telemetry.js +16 -1
- package/dist/telemetry/verification-of.d.ts +3 -1
- package/dist/telemetry/verification-of.js +7 -1
- package/dist/wire-journal.js +12 -2
- package/docs/README.md +4 -2
- package/docs/actions.mdx +7 -6
- package/docs/adapters.md +2 -1
- package/docs/agent-cheatsheet.md +19 -5
- package/docs/architecture.md +13 -10
- package/docs/autodrive.md +6 -5
- package/docs/benchmarks.md +2 -2
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/affected.mdx +1 -1
- package/docs/cli/cloud.mdx +6 -6
- package/docs/cli/doctor.mdx +3 -1
- package/docs/cli/feedback.mdx +11 -7
- package/docs/cli/gate.mdx +1 -1
- package/docs/cli/help.mdx +1 -1
- package/docs/cli/init.mdx +20 -16
- package/docs/cli/mcp.mdx +5 -2
- package/docs/cli/open.mdx +5 -2
- package/docs/cli/report.mdx +1 -1
- package/docs/cli/restart.mdx +1 -1
- package/docs/cli/rollback.mdx +4 -4
- package/docs/cli/serve.mdx +7 -5
- package/docs/cli/setup.mdx +1 -1
- package/docs/cli/status.mdx +6 -2
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/telemetry.mdx +5 -1
- package/docs/cli/try.mdx +8 -8
- package/docs/cli/update.mdx +8 -5
- package/docs/cli/verify.mdx +12 -5
- package/docs/cli/version.mdx +1 -1
- package/docs/cli.mdx +10 -6
- package/docs/debugging.md +1 -1
- package/docs/deploy-checks.md +8 -6
- package/docs/desktop.mdx +14 -18
- package/docs/enterprise.md +6 -6
- package/docs/environment.mdx +5 -3
- package/docs/faq.mdx +9 -6
- package/docs/first-drive.md +19 -9
- package/docs/flows.md +3 -4
- package/docs/for-agents.md +1 -1
- package/docs/frameworks.mdx +34 -11
- package/docs/getting-started.md +46 -29
- package/docs/http-transport.md +2 -2
- package/docs/human-control.md +8 -8
- package/docs/index.mdx +2 -2
- package/docs/install-agentic.mdx +18 -19
- package/docs/install-manual.mdx +8 -4
- package/docs/integration-patterns.md +5 -5
- package/docs/multi-agent-testing.md +7 -5
- package/docs/packages/browser.mdx +34 -2
- package/docs/packages/core.mdx +2 -1
- package/docs/packages/electron.mdx +15 -3
- package/docs/packages/eslint-plugin.mdx +1 -1
- package/docs/packages/next.mdx +17 -5
- package/docs/packages/react.mdx +8 -1
- package/docs/packages/server.mdx +3 -3
- package/docs/packages/tauri.mdx +3 -3
- package/docs/packages/vite-plugin.mdx +1 -1
- package/docs/packages.mdx +19 -6
- package/docs/platform-integration.md +12 -4
- package/docs/predicates.mdx +18 -7
- package/docs/production-mode.md +108 -0
- package/docs/prompt-capture.md +99 -0
- package/docs/quickstart.mdx +14 -12
- package/docs/recipes.mdx +9 -8
- package/docs/required-checks.md +3 -2
- package/docs/skill-file.mdx +4 -3
- package/docs/skill-setup.md +2 -2
- package/docs/skill.md +62 -70
- package/docs/state-files.md +1 -0
- package/docs/state-management.mdx +11 -9
- package/docs/system-map.md +14 -13
- package/docs/telemetry.md +32 -24
- package/docs/testing.md +9 -5
- package/docs/tools/act-and-wait.mdx +17 -13
- package/docs/tools/act-sequence.mdx +40 -66
- package/docs/tools/act.mdx +7 -5
- package/docs/tools/assert.mdx +1 -1
- package/docs/tools/look.mdx +1 -1
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/network.mdx +13 -11
- package/docs/tools/overview.mdx +3 -3
- package/docs/tools/query.mdx +3 -3
- package/docs/tools/session-and-feedback.mdx +9 -6
- package/docs/tools/sessions.mdx +7 -0
- package/docs/tools/snapshot.mdx +9 -7
- package/docs/tools/tools-and-run.mdx +11 -10
- package/docs/tools/verify.mdx +1 -1
- package/docs/troubleshooting.mdx +21 -9
- package/docs/usage.md +58 -43
- package/docs/use-cases.mdx +2 -2
- package/docs/vs-chrome-devtools-mcp.mdx +5 -5
- package/docs/vs-playwright-mcp.mdx +5 -4
- package/docs/what-is-recorded.md +12 -7
- package/docs/what-is-reticle.mdx +2 -2
- package/docs/why-reticle.mdx +1 -1
- package/package.json +9 -12
- package/dist/command/dev/stale-issue-guard.d.ts +0 -34
- package/dist/command/dev/stale-issue-guard.js +0 -69
|
@@ -37,3 +37,18 @@ export function normalizeQueryArgs(args) {
|
|
|
37
37
|
}
|
|
38
38
|
return args;
|
|
39
39
|
}
|
|
40
|
+
/** How the refusal of an empty find opens: the error table recognizes it by these words. */
|
|
41
|
+
export const EMPTY_FIND_OPENING = 'Nothing was searched: this find names nothing to find.';
|
|
42
|
+
/**
|
|
43
|
+
* The error for a query that names nothing to find, or `undefined` when it names something.
|
|
44
|
+
*
|
|
45
|
+
* Sent to the page, an empty query answered `count: 0`, which reads as "nothing here matches" about
|
|
46
|
+
* a question nobody asked. A container asked for itself (`self`) needs no other field.
|
|
47
|
+
*/
|
|
48
|
+
export function emptyQueryRefusal(args) {
|
|
49
|
+
if (true === args['self'] || 'string' === typeof args['by'] || 'string' === typeof args['value'])
|
|
50
|
+
return undefined;
|
|
51
|
+
return new Error(`${EMPTY_FIND_OPENING} Pass one of testid, text, role ` +
|
|
52
|
+
'(with name), label, placeholder or alt, e.g. { testid: "submit" } or { role: "button", ' +
|
|
53
|
+
'name: "Save" }, or `by` with `value`.');
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function noteUnmountedRoot(result: unknown, mode: string, session?: unknown, scope?: string): unknown;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EventType, SnapshotMode, asNumber, asRecord, asString } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* How much DOM there is under the mount container, when the tree is empty and nothing was skipped.
|
|
4
|
+
*
|
|
5
|
+
* A handful of elements is a container with nothing in it. React's own root div counts as one, and a
|
|
6
|
+
* wrapper or two is ordinary, so this is deliberately not `=== 0`.
|
|
7
|
+
*/
|
|
8
|
+
const UNMOUNTED_ROOT_MAX_ELEMENTS = 3;
|
|
9
|
+
function crashEvidence(session) {
|
|
10
|
+
const since = asRecord(session)['eventsSince'];
|
|
11
|
+
if ('function' !== typeof since)
|
|
12
|
+
return undefined;
|
|
13
|
+
let events;
|
|
14
|
+
try {
|
|
15
|
+
events = since.call(session, 0);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (!Array.isArray(events))
|
|
21
|
+
return undefined;
|
|
22
|
+
const errors = events.filter((e) => {
|
|
23
|
+
const type = asString(asRecord(e)['type']);
|
|
24
|
+
return EventType.CONSOLE_ERROR === type || EventType.ERROR_UNCAUGHT === type;
|
|
25
|
+
});
|
|
26
|
+
if (0 === errors.length)
|
|
27
|
+
return undefined;
|
|
28
|
+
const first = errors
|
|
29
|
+
.map((e) => asString(asRecord(asRecord(e)['data'])['message']))
|
|
30
|
+
.find((m) => undefined !== m && '' !== m);
|
|
31
|
+
return { count: errors.length, first: first ?? '' };
|
|
32
|
+
}
|
|
33
|
+
export function noteUnmountedRoot(result, mode, session, scope) {
|
|
34
|
+
// A scope narrows the walk to one element, so a near-empty DOM under it says nothing about the app.
|
|
35
|
+
if (SnapshotMode.STATUS === mode || (scope ?? '') !== '')
|
|
36
|
+
return result;
|
|
37
|
+
const row = asRecord(result);
|
|
38
|
+
if (0 !== asNumber(row['nodes']))
|
|
39
|
+
return result;
|
|
40
|
+
if (row['note'] !== undefined)
|
|
41
|
+
return result;
|
|
42
|
+
const elements = asNumber(row['domElements']);
|
|
43
|
+
if (elements === undefined || elements > UNMOUNTED_ROOT_MAX_ELEMENTS)
|
|
44
|
+
return result;
|
|
45
|
+
const mount = `the tree is empty because there is almost nothing in the DOM: ${String(elements)} ` +
|
|
46
|
+
`element(s) under this scope. That is a mount container with nothing rendered into it, not a ` +
|
|
47
|
+
`page whose contents were skipped — so the app is UNMOUNTED rather than slow, and waiting ` +
|
|
48
|
+
`will not change it. `;
|
|
49
|
+
// An uncaught error beside an empty mount container is the difference between "has not started"
|
|
50
|
+
// and "started and died", which are the two readings of this count and want opposite next moves.
|
|
51
|
+
// Nothing here is framework-specific, and deliberately so: the condition is a known mount
|
|
52
|
+
// container that holds nothing with an uncaught error beside it, which is as true of a Vue or
|
|
53
|
+
// Svelte root as a React one.
|
|
54
|
+
const crash = crashEvidence(session);
|
|
55
|
+
if (crash !== undefined) {
|
|
56
|
+
return {
|
|
57
|
+
...row,
|
|
58
|
+
note: mount +
|
|
59
|
+
`The app did not merely fail to start — it CRASHED: ${String(crash.count)} uncaught ` +
|
|
60
|
+
`error(s) were logged in this session` +
|
|
61
|
+
('' === crash.first ? '' : `, the first: "${crash.first}"`) +
|
|
62
|
+
`. Read reticle_console for the rest, and check anything that runs inside a framework ` +
|
|
63
|
+
`commit phase. Loading the app again will reproduce it rather than fix it.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
...row,
|
|
68
|
+
note: mount +
|
|
69
|
+
`An app that was rendering and then stopped has usually thrown: read ` +
|
|
70
|
+
`reticle_console for an uncaught error, and check anything that runs inside a framework ` +
|
|
71
|
+
`commit phase. If the app has genuinely not started yet, load it and snapshot again.`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -199,7 +199,7 @@ export const READ_TOOLS = [
|
|
|
199
199
|
},
|
|
200
200
|
outputSchema: {
|
|
201
201
|
recordingName: z.string(),
|
|
202
|
-
program: z.unknown(),
|
|
202
|
+
program: z.unknown().optional(),
|
|
203
203
|
warning: z.string().optional(),
|
|
204
204
|
proposedConsequences: z
|
|
205
205
|
.array(z.unknown())
|
|
@@ -386,7 +386,7 @@ export const READ_TOOLS = [
|
|
|
386
386
|
...sessionIdShape,
|
|
387
387
|
},
|
|
388
388
|
outputSchema: {
|
|
389
|
-
stores: z.record(z.unknown()).optional(),
|
|
389
|
+
stores: z.record(z.string(), z.unknown()).optional(),
|
|
390
390
|
storeNames: z.array(z.string()).optional(),
|
|
391
391
|
found: z.boolean().optional(),
|
|
392
392
|
value: z.unknown().optional(),
|
|
@@ -598,9 +598,9 @@ export const READ_TOOLS = [
|
|
|
598
598
|
...sessionIdShape,
|
|
599
599
|
},
|
|
600
600
|
outputSchema: {
|
|
601
|
-
local: z.record(z.string()).optional(),
|
|
602
|
-
session: z.record(z.string()).optional(),
|
|
603
|
-
cookies: z.record(z.string()).optional(),
|
|
601
|
+
local: z.record(z.string(), z.string()).optional(),
|
|
602
|
+
session: z.record(z.string(), z.string()).optional(),
|
|
603
|
+
cookies: z.record(z.string(), z.string()).optional(),
|
|
604
604
|
area: z.string().optional(),
|
|
605
605
|
key: z.string().optional(),
|
|
606
606
|
value: z.string().optional(),
|
|
@@ -49,6 +49,8 @@ interface RealActResult {
|
|
|
49
49
|
* rather than pretend the action ran.
|
|
50
50
|
*/
|
|
51
51
|
export declare const HOVER_NEEDS_POINTER_MSG = "cannot hover without a real pointer \u2014 CSS :hover only applies to a native mouse move, never to a synthetic mouseover";
|
|
52
|
+
/** A hover target the page could not bring into the viewport. The pointer would miss it. */
|
|
53
|
+
export declare const HOVER_OFF_VIEWPORT_MSG = "cannot hover: the target is outside the viewport and could not be scrolled into view, so a real pointer would miss it";
|
|
52
54
|
/**
|
|
53
55
|
* Attempt to drive a pointer action via native input. Returns a synthetic outcome (with a
|
|
54
56
|
* `reason` when a provider is configured) whenever the synthetic path should run — no matching
|
|
@@ -60,4 +62,13 @@ export declare const HOVER_NEEDS_POINTER_MSG = "cannot hover without a real poin
|
|
|
60
62
|
* or throws.
|
|
61
63
|
*/
|
|
62
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
|
+
export declare function withLeaseKeyboard(deps: ToolDeps, session: Session): Session;
|
|
63
74
|
export {};
|
|
@@ -19,9 +19,10 @@
|
|
|
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, DriveErrorCode, InputModeReason, ReticleCommand, TRANSPORT_LIMITS, } from '@reticlehq/core';
|
|
22
|
+
import { ActionType, DriveErrorCode, ElementState, INSPECT_IN_VIEWPORT_FIELD, INSPECT_REVEAL_ARG, InputMode, InputModeReason, ReticleCommand, TRANSPORT_LIMITS, } from '@reticlehq/core';
|
|
23
23
|
import { boxCenter, DriveError, isRealInputAction, unspellablePressReason, } from '../../portal/input/real-input.js';
|
|
24
24
|
import { assertDragNotDestructive, assertNotDestructive } from './act/act-danger.js';
|
|
25
|
+
import { assertNativeAllowed } from '../../portal/production/production-drive.js';
|
|
25
26
|
import { NATIVE_INPUT_ARG } from '@reticlehq/core';
|
|
26
27
|
import { asNumber, asRecord, asString } from '@reticlehq/core';
|
|
27
28
|
import { commandOrThrow } from './tool-kit.js';
|
|
@@ -162,7 +163,10 @@ async function resolveUploadPath(rawPath, projectRoot, fs, uploadRoots, home) {
|
|
|
162
163
|
const real = await fs.realpath(abs).catch(() => {
|
|
163
164
|
throw new Error(`upload path '${rawPath}' could not be read: file not found or not accessible.`);
|
|
164
165
|
});
|
|
165
|
-
|
|
166
|
+
// The roots go through realpath too, or one path compares under two spellings: a Windows 8.3
|
|
167
|
+
// short name (`RUNNER~1`) against the long name realpath returns, or macOS `/tmp` against
|
|
168
|
+
// `/private/tmp`. A root that cannot be resolved keeps the spelling it was given.
|
|
169
|
+
const allowed = await Promise.all([projectRoot, ...uploadRoots].map((root) => fs.realpath(root).catch(() => root)));
|
|
166
170
|
if (!allowed.some((root) => isInsideRoot(real, root))) {
|
|
167
171
|
const listed = allowed.map((r) => `'${r}'`).join(', ');
|
|
168
172
|
throw new Error(`upload path '${rawPath}' resolves to '${real}', which is outside every allowed upload root ` +
|
|
@@ -228,6 +232,94 @@ function synthetic(reason) {
|
|
|
228
232
|
? { result: undefined, settled: false }
|
|
229
233
|
: { result: undefined, settled: false, reason };
|
|
230
234
|
}
|
|
235
|
+
/** The act's args, as the real-input driver reads them. */
|
|
236
|
+
function performArgsOf(inner) {
|
|
237
|
+
const performArgs = {};
|
|
238
|
+
const value = asString(inner['value']);
|
|
239
|
+
if (value !== undefined)
|
|
240
|
+
performArgs.value = value;
|
|
241
|
+
const text = asString(inner['text']);
|
|
242
|
+
if (text !== undefined)
|
|
243
|
+
performArgs.text = text;
|
|
244
|
+
const key = asString(inner['key']);
|
|
245
|
+
if (key !== undefined)
|
|
246
|
+
performArgs.key = key;
|
|
247
|
+
const modifiers = inner['modifiers'];
|
|
248
|
+
if (Array.isArray(modifiers)) {
|
|
249
|
+
performArgs.modifiers = modifiers.filter((m) => 'string' === typeof m);
|
|
250
|
+
}
|
|
251
|
+
// A held key is the whole point of a hold-to-confirm control, and the synthetic path has honoured
|
|
252
|
+
// `holdMs` all along. Not forwarding it made the real keyboard the WEAKER of the two paths: it
|
|
253
|
+
// drove the key instantly and reported a hold that never held.
|
|
254
|
+
const holdMs = asNumber(inner['holdMs']);
|
|
255
|
+
if (holdMs !== undefined)
|
|
256
|
+
performArgs.holdMs = holdMs;
|
|
257
|
+
// `keys` and `code` are read but not forwarded: both are routed away by the caller — a multi-key
|
|
258
|
+
// sequence and an explicit physical code have no driver spelling — so carrying them into the
|
|
259
|
+
// provider would only invite it to guess at a gesture the caller did not ask for.
|
|
260
|
+
return performArgs;
|
|
261
|
+
}
|
|
262
|
+
/** The fields an act result names its element by, which the flow recorder anchors a step on. */
|
|
263
|
+
const ANCHOR_FIELDS = [
|
|
264
|
+
'testid',
|
|
265
|
+
'testidOf',
|
|
266
|
+
'component',
|
|
267
|
+
'source',
|
|
268
|
+
'role',
|
|
269
|
+
'name',
|
|
270
|
+
'nth',
|
|
271
|
+
'of',
|
|
272
|
+
'secret',
|
|
273
|
+
];
|
|
274
|
+
/**
|
|
275
|
+
* Press with a leased page's real keyboard, or undefined when this session is not such a lease.
|
|
276
|
+
*
|
|
277
|
+
* A lease is Reticle's own Playwright page, so it has a real keyboard with no `reticle drive`
|
|
278
|
+
* provider configured. Without this, Tab moved no focus and Enter or Space on a focused button did
|
|
279
|
+
* nothing, so a keyboard-only journey could be audited and never driven.
|
|
280
|
+
*
|
|
281
|
+
* A press that names an element focuses it first, inside the caller's `beginAction` window, the
|
|
282
|
+
* way a person tabs to a control and then presses the key. An element that does not take focus
|
|
283
|
+
* keeps the in-page press, with the reason: a person's keyboard could not reach it either.
|
|
284
|
+
* ponytail: lease only; the drive provider keeps its documented element-press rule.
|
|
285
|
+
*/
|
|
286
|
+
async function pressOnLease(deps, session, ref, inner) {
|
|
287
|
+
const pool = deps.pool;
|
|
288
|
+
if (pool === undefined || !pool.hasKeyboard(session.id))
|
|
289
|
+
return undefined;
|
|
290
|
+
const unspellable = unspellablePressReason('', inner);
|
|
291
|
+
if (unspellable !== undefined)
|
|
292
|
+
return synthetic(unspellable);
|
|
293
|
+
// What the focused element is called, from the focus act's own report, so the saved step can
|
|
294
|
+
// find it again: the real keyboard reports no element of its own.
|
|
295
|
+
let anchor = {};
|
|
296
|
+
if (0 < ref.length) {
|
|
297
|
+
const focus = asRecord(await commandOrThrow(deps, session.id, ReticleCommand.ACT, {
|
|
298
|
+
ref,
|
|
299
|
+
action: ActionType.FOCUS,
|
|
300
|
+
args: {},
|
|
301
|
+
}));
|
|
302
|
+
anchor = Object.fromEntries(ANCHOR_FIELDS.flatMap((k) => (k in focus ? [[k, focus[k]]] : [])));
|
|
303
|
+
const inspected = asRecord(await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, { ref }));
|
|
304
|
+
const states = Array.isArray(inspected['states']) ? inspected['states'] : [];
|
|
305
|
+
if (!states.includes(ElementState.FOCUSED)) {
|
|
306
|
+
return synthetic(InputModeReason.TARGET_NOT_FOCUSABLE);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const performed = await pool.pressLease(session.id, performArgsOf(inner));
|
|
310
|
+
if (performed?.performed !== true)
|
|
311
|
+
return undefined;
|
|
312
|
+
return {
|
|
313
|
+
result: {
|
|
314
|
+
...anchor,
|
|
315
|
+
performed: true,
|
|
316
|
+
action: ActionType.PRESS,
|
|
317
|
+
inputMode: InputMode.REAL,
|
|
318
|
+
...(performed.heldMs !== undefined ? { heldMs: performed.heldMs } : {}),
|
|
319
|
+
},
|
|
320
|
+
settled: true,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
231
323
|
/**
|
|
232
324
|
* Filler for the provider signature, which demands a box on every action.
|
|
233
325
|
*
|
|
@@ -241,8 +333,36 @@ const PRESS_HAS_NO_BOX = { x: 0, y: 0, width: 0, height: 0 };
|
|
|
241
333
|
* rather than pretend the action ran.
|
|
242
334
|
*/
|
|
243
335
|
export const HOVER_NEEDS_POINTER_MSG = 'cannot hover without a real pointer — CSS :hover only applies to a native mouse move, never to a synthetic mouseover';
|
|
244
|
-
|
|
245
|
-
|
|
336
|
+
/** A hover target the page could not bring into the viewport. The pointer would miss it. */
|
|
337
|
+
export const HOVER_OFF_VIEWPORT_MSG = 'cannot hover: the target is outside the viewport and could not be scrolled into view, so a real pointer would miss it';
|
|
338
|
+
/**
|
|
339
|
+
* INSPECT a ref for a real gesture, asking the page to scroll it into view first (#1711).
|
|
340
|
+
*
|
|
341
|
+
* A real gesture aims at coordinates. An element above or below the viewport has a box off the page,
|
|
342
|
+
* so the pointer lands on nothing while the provider reports `performed: true`. The synthetic path
|
|
343
|
+
* scrolls the target in first; this gives the real path the same step in the same round trip.
|
|
344
|
+
*/
|
|
345
|
+
async function inspectRevealed(deps, session, ref) {
|
|
346
|
+
const inspected = await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, {
|
|
347
|
+
ref,
|
|
348
|
+
[INSPECT_REVEAL_ARG]: true,
|
|
349
|
+
});
|
|
350
|
+
const record = asRecord(inspected);
|
|
351
|
+
// Read the page's own measurement, taken after the scroll. `states` is no substitute: INSPECT
|
|
352
|
+
// builds it with `describe()`, which never adds `inViewport`, so every element read as out.
|
|
353
|
+
const inView = false !== record[INSPECT_IN_VIEWPORT_FIELD];
|
|
354
|
+
return { inspected, inView, scrolled: true === record['scrolledIntoView'] };
|
|
355
|
+
}
|
|
356
|
+
function hoverSucceeded(center, scrolled) {
|
|
357
|
+
return {
|
|
358
|
+
result: {
|
|
359
|
+
performed: true,
|
|
360
|
+
center,
|
|
361
|
+
action: ActionType.HOVER,
|
|
362
|
+
...(scrolled ? { scrolledIntoView: true } : {}),
|
|
363
|
+
},
|
|
364
|
+
settled: true,
|
|
365
|
+
};
|
|
246
366
|
}
|
|
247
367
|
/**
|
|
248
368
|
* Drive hover through a real pointer (CDP provider, then a leased Playwright page), or refuse.
|
|
@@ -256,19 +376,21 @@ async function hoverForReal(deps, session, ref, inner, provider) {
|
|
|
256
376
|
if (!providerReady && deps.pool === undefined) {
|
|
257
377
|
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
258
378
|
}
|
|
259
|
-
const inspected = await
|
|
379
|
+
const { inspected, inView, scrolled } = await inspectRevealed(deps, session, ref);
|
|
260
380
|
assertNotDestructive(ActionType.HOVER, inner, inspected);
|
|
261
381
|
const box = asBox(inspected);
|
|
262
382
|
if (box === undefined) {
|
|
263
383
|
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
264
384
|
}
|
|
385
|
+
if (!inView)
|
|
386
|
+
throw new Error(HOVER_OFF_VIEWPORT_MSG);
|
|
265
387
|
if (providerReady && provider !== undefined) {
|
|
266
388
|
try {
|
|
267
389
|
const performed = await provider.perform(session.url, ActionType.HOVER, box, {});
|
|
268
390
|
// Hover always has a resolved box, so there is always a center to report: the provider's own
|
|
269
391
|
// reading, or the box's if it reported none. Both are the same point, not a fabricated one.
|
|
270
392
|
if (performed.performed)
|
|
271
|
-
return hoverSucceeded(performed.center ?? boxCenter(box));
|
|
393
|
+
return hoverSucceeded(performed.center ?? boxCenter(box), scrolled);
|
|
272
394
|
}
|
|
273
395
|
catch {
|
|
274
396
|
// A provider error used to fall back to synthetic dispatch. For hover that is a lie.
|
|
@@ -276,7 +398,7 @@ async function hoverForReal(deps, session, ref, inner, provider) {
|
|
|
276
398
|
}
|
|
277
399
|
const { cx, cy } = boxCenter(box);
|
|
278
400
|
if (true === (await deps.pool?.hoverLease(session.id, cx, cy))) {
|
|
279
|
-
return hoverSucceeded({ cx, cy });
|
|
401
|
+
return hoverSucceeded({ cx, cy }, scrolled);
|
|
280
402
|
}
|
|
281
403
|
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
282
404
|
}
|
|
@@ -297,7 +419,18 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
297
419
|
if (action === ActionType.HOVER) {
|
|
298
420
|
return hoverForReal(deps, session, ref, inner, provider);
|
|
299
421
|
}
|
|
422
|
+
// The production policy judges a command by the control it names, when the daemon dispatches
|
|
423
|
+
// it. A native key goes to whatever holds focus, so the policy cannot see what it presses. On a
|
|
424
|
+
// production drive a press runs in the page, where the daemon judges it.
|
|
425
|
+
if (action === ActionType.PRESS && session.production !== undefined) {
|
|
426
|
+
return synthetic(InputModeReason.PRODUCTION_PRESS_IN_PAGE);
|
|
427
|
+
}
|
|
300
428
|
if (provider === undefined) {
|
|
429
|
+
if (action === ActionType.PRESS) {
|
|
430
|
+
const leased = await pressOnLease(deps, session, ref, inner);
|
|
431
|
+
if (leased !== undefined)
|
|
432
|
+
return leased;
|
|
433
|
+
}
|
|
301
434
|
// Silent by default: with no provider EVERY action is synthetic, and a reason on all of them is
|
|
302
435
|
// noise on the most-used tool in the product. But an agent that passed native:true asked a
|
|
303
436
|
// question and got the opposite answer — reported from the field as a silent downgrade that cost
|
|
@@ -335,29 +468,7 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
335
468
|
}
|
|
336
469
|
if (!(await provider.isAvailableFor(session.url)))
|
|
337
470
|
return synthetic(InputModeReason.PAGE_NOT_CORRELATED);
|
|
338
|
-
const performArgs =
|
|
339
|
-
const value = asString(inner['value']);
|
|
340
|
-
if (value !== undefined)
|
|
341
|
-
performArgs.value = value;
|
|
342
|
-
const text = asString(inner['text']);
|
|
343
|
-
if (text !== undefined)
|
|
344
|
-
performArgs.text = text;
|
|
345
|
-
const key = asString(inner['key']);
|
|
346
|
-
if (key !== undefined)
|
|
347
|
-
performArgs.key = key;
|
|
348
|
-
const modifiers = inner['modifiers'];
|
|
349
|
-
if (Array.isArray(modifiers)) {
|
|
350
|
-
performArgs.modifiers = modifiers.filter((m) => 'string' === typeof m);
|
|
351
|
-
}
|
|
352
|
-
// A held key is the whole point of a hold-to-confirm control, and the synthetic path has honoured
|
|
353
|
-
// `holdMs` all along. Not forwarding it made the real keyboard the WEAKER of the two paths: it
|
|
354
|
-
// drove the key instantly and reported a hold that never held.
|
|
355
|
-
const holdMs = asNumber(inner['holdMs']);
|
|
356
|
-
if (holdMs !== undefined)
|
|
357
|
-
performArgs.holdMs = holdMs;
|
|
358
|
-
// `keys` and `code` are read but not forwarded: both are routed away above — a multi-key sequence
|
|
359
|
-
// and an explicit physical code have no driver spelling — so carrying them into the provider would
|
|
360
|
-
// only invite it to guess at a gesture the caller did not ask for.
|
|
471
|
+
const performArgs = performArgsOf(inner);
|
|
361
472
|
// A document key has no element of its own: skip INSPECT and the box entirely.
|
|
362
473
|
if (action === ActionType.PRESS) {
|
|
363
474
|
try {
|
|
@@ -402,25 +513,41 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
402
513
|
};
|
|
403
514
|
}
|
|
404
515
|
}
|
|
405
|
-
const
|
|
516
|
+
const source = await inspectRevealed(deps, session, ref);
|
|
517
|
+
const { inspected } = source;
|
|
518
|
+
let { scrolled } = source;
|
|
406
519
|
assertNotDestructive(action, inner, inspected);
|
|
407
|
-
|
|
408
|
-
|
|
520
|
+
await assertNativeAllowed(session.production, action, inspected);
|
|
521
|
+
let box = asBox(inspected);
|
|
522
|
+
// A box outside the viewport after the reveal is a gesture that would land on nothing. The
|
|
523
|
+
// synthetic path scrolls and hit-tests on its own, so it is the honest answer.
|
|
524
|
+
if (box === undefined || !source.inView)
|
|
409
525
|
return synthetic(InputModeReason.ELEMENT_NOT_LOCATABLE);
|
|
410
526
|
let toBox;
|
|
411
527
|
if (action === ActionType.DRAG) {
|
|
412
528
|
const toRef = asString(inner['toRef']);
|
|
413
529
|
if (toRef === undefined)
|
|
414
530
|
return synthetic(InputModeReason.DRAG_TARGET_UNRESOLVED);
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
});
|
|
531
|
+
const target = await inspectRevealed(deps, session, toRef);
|
|
532
|
+
const targetInspected = target.inspected;
|
|
418
533
|
// A drag is judged on BOTH ends: dropping onto "Trash" is destructive however innocent the
|
|
419
534
|
// thing being dragged looks.
|
|
420
535
|
assertDragNotDestructive(inner, inspected, targetInspected);
|
|
421
536
|
toBox = asBox(targetInspected);
|
|
422
|
-
if (toBox === undefined)
|
|
537
|
+
if (toBox === undefined || !target.inView) {
|
|
423
538
|
return synthetic(InputModeReason.DRAG_TARGET_UNRESOLVED);
|
|
539
|
+
}
|
|
540
|
+
// Scrolling the drop target in can carry the source out, so read the source again. Only when
|
|
541
|
+
// the page moved. If the page has to scroll AGAIN to show the source, the two ends do not fit on
|
|
542
|
+
// one screen, and a real drag between them cannot land.
|
|
543
|
+
if (target.scrolled) {
|
|
544
|
+
scrolled = true;
|
|
545
|
+
const again = await inspectRevealed(deps, session, ref);
|
|
546
|
+
box = asBox(again.inspected);
|
|
547
|
+
if (box === undefined || !again.inView || again.scrolled) {
|
|
548
|
+
return synthetic(InputModeReason.ELEMENT_NOT_LOCATABLE);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
424
551
|
}
|
|
425
552
|
if (toBox !== undefined)
|
|
426
553
|
performArgs.toBox = toBox;
|
|
@@ -428,7 +555,15 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
428
555
|
const performed = await provider.perform(session.url, action, box, performArgs);
|
|
429
556
|
if (!performed.performed)
|
|
430
557
|
return synthetic(InputModeReason.PROVIDER_DECLINED);
|
|
431
|
-
return {
|
|
558
|
+
return {
|
|
559
|
+
result: {
|
|
560
|
+
performed: true,
|
|
561
|
+
center: performed.center,
|
|
562
|
+
action,
|
|
563
|
+
...(scrolled ? { scrolledIntoView: true } : {}),
|
|
564
|
+
},
|
|
565
|
+
settled: true,
|
|
566
|
+
};
|
|
432
567
|
}
|
|
433
568
|
catch {
|
|
434
569
|
if (session.versionSkew !== undefined)
|
|
@@ -441,3 +576,36 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
441
576
|
};
|
|
442
577
|
}
|
|
443
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* The session a flow replay runs on, with a press sent to the lease's real keyboard.
|
|
581
|
+
*
|
|
582
|
+
* Replay dispatches its steps itself, not through `reticle_act`, so a recorded Tab replayed as a
|
|
583
|
+
* synthetic keydown that moves no focus, and a keyboard flow that passed live drifted on replay.
|
|
584
|
+
* Every other command, and a press this lease cannot drive for real, goes to the page as before.
|
|
585
|
+
* A Proxy, not a copy: the session keeps private state its own methods read.
|
|
586
|
+
*/
|
|
587
|
+
export function withLeaseKeyboard(deps, session) {
|
|
588
|
+
if (deps.realInput !== undefined || deps.pool?.hasKeyboard(session.id) !== true)
|
|
589
|
+
return session;
|
|
590
|
+
const command = async (name, args = {}, timeoutMs) => {
|
|
591
|
+
if (ReticleCommand.ACT === name && ActionType.PRESS === args['action']) {
|
|
592
|
+
const ref = asString(args['ref']) ?? '';
|
|
593
|
+
const real = await tryRealInput(deps, session, ref, ActionType.PRESS, args);
|
|
594
|
+
if (real.result !== undefined) {
|
|
595
|
+
const result = { dispatched: true, settled: real.settled, ...asRecord(real.result) };
|
|
596
|
+
return { kind: 'command_result', id: ActionType.PRESS, ok: true, result };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return session.command(name, args, timeoutMs);
|
|
600
|
+
};
|
|
601
|
+
return new Proxy(session, {
|
|
602
|
+
get(target, key) {
|
|
603
|
+
if ('command' === key)
|
|
604
|
+
return command;
|
|
605
|
+
const value = Reflect.get(target, key, target);
|
|
606
|
+
return 'function' === typeof value
|
|
607
|
+
? value.bind(target)
|
|
608
|
+
: value;
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bodyCaptureRemedy } from '@reticlehq/engine/evidence/body-capture-remedy.js';
|
|
2
|
+
import { sharedSkipReason } from '@reticlehq/engine/question/predicate/predicate-eval.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { witnessDisagreement, WitnessOutcome, } from '@reticlehq/engine/disagreement/witness-disagreement.js';
|
|
4
5
|
/** How long to wait on the outside observer. Short: it is a read model, not the app under test. */
|
|
@@ -32,6 +33,7 @@ async function capturedBodies(session, since, urlContains) {
|
|
|
32
33
|
let withBody = 0;
|
|
33
34
|
let total = 0;
|
|
34
35
|
let truncated = 0;
|
|
36
|
+
const skipReasons = [];
|
|
35
37
|
for (const event of events) {
|
|
36
38
|
if (event.type !== EventType.NET_REQUEST)
|
|
37
39
|
continue;
|
|
@@ -40,8 +42,10 @@ async function capturedBodies(session, since, urlContains) {
|
|
|
40
42
|
continue;
|
|
41
43
|
total += 1;
|
|
42
44
|
const raw = event.data['responseBody'];
|
|
43
|
-
if (typeof raw !== 'string' || 0 === raw.length)
|
|
45
|
+
if (typeof raw !== 'string' || 0 === raw.length) {
|
|
46
|
+
skipReasons.push(event.data['responseBodyReason']);
|
|
44
47
|
continue;
|
|
48
|
+
}
|
|
45
49
|
// A capped body is not an absent one. Recover the records that closed before the cut rather than
|
|
46
50
|
// reporting nothing about the page with the most data on it.
|
|
47
51
|
const salvaged = salvageJson(raw);
|
|
@@ -52,7 +56,7 @@ async function capturedBodies(session, since, urlContains) {
|
|
|
52
56
|
if (salvaged.partial)
|
|
53
57
|
truncated += 1;
|
|
54
58
|
}
|
|
55
|
-
return { bodies, withBody, total, truncated };
|
|
59
|
+
return { bodies, withBody, total, truncated, skipReasons };
|
|
56
60
|
}
|
|
57
61
|
export const RECONCILE_TOOLS = [
|
|
58
62
|
{
|
|
@@ -68,6 +72,10 @@ export const RECONCILE_TOOLS = [
|
|
|
68
72
|
.string()
|
|
69
73
|
.optional()
|
|
70
74
|
.describe('Only compare responses from URLs containing this, e.g. "/api/v1/settlements".'),
|
|
75
|
+
scope: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('A ref or CSS selector, e.g. "#status". Compare only against that subtree. Use it when the whole page is too large to read completely (a card of large native selects) and the API feeds one region. A scope that matches no element is refused, not read as an empty page.'),
|
|
71
79
|
witness: z
|
|
72
80
|
.object({
|
|
73
81
|
url: z.string().url(),
|
|
@@ -93,7 +101,7 @@ export const RECONCILE_TOOLS = [
|
|
|
93
101
|
handler: async (deps, args) => {
|
|
94
102
|
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
95
103
|
const since = asNumber(args['since']) ?? 0;
|
|
96
|
-
const { bodies, withBody, total, truncated } = await capturedBodies(session, since, asString(args['urlContains']));
|
|
104
|
+
const { bodies, withBody, total, truncated, skipReasons } = await capturedBodies(session, since, asString(args['urlContains']));
|
|
97
105
|
// An empty result over data that was never read is the exact false green this tool exists to
|
|
98
106
|
// prevent, so the two cases are reported differently and neither is silent.
|
|
99
107
|
if (0 === bodies.length) {
|
|
@@ -102,16 +110,19 @@ export const RECONCILE_TOOLS = [
|
|
|
102
110
|
compared: 0,
|
|
103
111
|
note: 0 === total
|
|
104
112
|
? 'no responses in this window matched — nothing was compared'
|
|
105
|
-
: `${String(total)} response(s) matched but NONE carried a recorded body, so nothing was compared. ${bodyCaptureRemedy(session.sdkVersion)}`,
|
|
113
|
+
: `${String(total)} response(s) matched but NONE carried a recorded body, so nothing was compared. ${bodyCaptureRemedy(session.sdkVersion, sharedSkipReason(skipReasons))}`,
|
|
106
114
|
});
|
|
107
115
|
}
|
|
108
116
|
// The whole page, INCLUDING whatever the snapshot's node cap cut off. A comparison against a
|
|
109
117
|
// prefix of the page can miss the row that disagrees and can invent one that does not — the
|
|
110
118
|
// value it was looking for may simply sit past the cut.
|
|
119
|
+
// A caller-given `scope` narrows the read to one subtree (reticle#1545); every cut branch under
|
|
120
|
+
// it is still finished, so a clean result is a claim about that whole subtree.
|
|
121
|
+
const scopeArg = asString(args['scope']);
|
|
111
122
|
const page = await readCompleteTree((scope) => commandOrThrow(deps, session.id, ReticleCommand.SNAPSHOT, {
|
|
112
123
|
mode: SnapshotMode.FULL,
|
|
113
124
|
...(scope === undefined ? {} : { scope, includeRoot: true }),
|
|
114
|
-
}));
|
|
125
|
+
}), scopeArg);
|
|
115
126
|
const mismatches = reconcile(bodies, page.tree);
|
|
116
127
|
/*
|
|
117
128
|
* Consult the observer outside the app, when one was named.
|
|
@@ -185,8 +196,9 @@ export const RECONCILE_TOOLS = [
|
|
|
185
196
|
if (0 === mismatches.length && !page.complete && witness?.kind !== WitnessOutcome.DISAGREES) {
|
|
186
197
|
throw new Error(`the page could not be read completely — ${page.incompleteBecause ?? 'the snapshot was cut'}. ` +
|
|
187
198
|
'Nothing was found to disagree with the API, but that is not the same as nothing disagreeing: ' +
|
|
188
|
-
'the record that does could be in the part that was never read. Narrow the
|
|
189
|
-
'
|
|
199
|
+
'the record that does could be in the part that was never read. Narrow the read: pass `scope` ' +
|
|
200
|
+
'(a ref or CSS selector for the region the API feeds), close overlays, or navigate to the view ' +
|
|
201
|
+
'holding these records, then re-run.');
|
|
190
202
|
}
|
|
191
203
|
// A truncated body is declared whether or not anything was found in it: "no mismatches" over a
|
|
192
204
|
// partially-read payload is a weaker statement than over a whole one, and the difference is
|
|
@@ -197,7 +209,8 @@ export const RECONCILE_TOOLS = [
|
|
|
197
209
|
}
|
|
198
210
|
: 0 === mismatches.length
|
|
199
211
|
? {
|
|
200
|
-
note: `compared ${String(withBody)} response(s); nothing on screen contradicts them
|
|
212
|
+
note: `compared ${String(withBody)} response(s); nothing on screen contradicts them` +
|
|
213
|
+
(scopeArg === undefined ? '' : ` inside \`${scopeArg}\``),
|
|
201
214
|
}
|
|
202
215
|
: {};
|
|
203
216
|
return withControl(session, {
|
|
@@ -26,6 +26,7 @@ export interface ResultFacts {
|
|
|
26
26
|
/** From `platformLine`. */
|
|
27
27
|
platform: string;
|
|
28
28
|
}
|
|
29
|
+
export declare const seconds: (ms: number) => string;
|
|
29
30
|
/**
|
|
30
31
|
* Each fact once. The headline carries the check count, so the metrics do not repeat it; with one
|
|
31
32
|
* check the headline is the whole tally, so the per-word count line is left out.
|
|
@@ -40,6 +41,8 @@ export declare function retitleResult(block: string, verified: Verified, because
|
|
|
40
41
|
export declare const KEY_REFUSED_HEAD = "Not on the platform: ";
|
|
41
42
|
/** The line for a key refused for no reason the platform named. */
|
|
42
43
|
export declare const KEY_REFUSED_LINE: string;
|
|
44
|
+
/** Linked to the platform: a dashboard, a link file or a key, refused or not. */
|
|
45
|
+
export declare function linkedToPlatform(root: string, env: Record<string, string | undefined>): boolean;
|
|
43
46
|
/** The platform line: where the run lives when linked, what linking adds when not. One line. */
|
|
44
47
|
export declare function platformLine(root: string, env: Record<string, string | undefined>, runId?: string): string;
|
|
45
48
|
/**
|
|
@@ -47,7 +47,7 @@ const Said = {
|
|
|
47
47
|
PITCH: 'Kept on this machine only. The Reticle platform adds run history, your team and Reticle Harness',
|
|
48
48
|
};
|
|
49
49
|
const plural = (n, word) => `${String(n)} ${word}${1 === n ? '' : 's'}`;
|
|
50
|
-
const seconds = (ms) => ms < 60_000
|
|
50
|
+
export const seconds = (ms) => ms < 60_000
|
|
51
51
|
? `${(ms / 1000).toFixed(1)}s`
|
|
52
52
|
: `${String(Math.floor(ms / 60_000))}m ${String(Math.round((ms % 60_000) / 1000))}s`;
|
|
53
53
|
function headline(f) {
|
|
@@ -126,6 +126,13 @@ export const KEY_REFUSED_HEAD = 'Not on the platform: ';
|
|
|
126
126
|
const keyRefusedLine = (why) => `${KEY_REFUSED_HEAD}${why.replace(/\.$/, '')}. Mint a new key in Settings → API keys; the run is kept on this machine`;
|
|
127
127
|
/** The line for a key refused for no reason the platform named. */
|
|
128
128
|
export const KEY_REFUSED_LINE = keyRefusedLine(KEY_REFUSED);
|
|
129
|
+
/** Linked to the platform: a dashboard, a link file or a key, refused or not. */
|
|
130
|
+
export function linkedToPlatform(root, env) {
|
|
131
|
+
return (keyRefusalAtLastSync(root) !== undefined ||
|
|
132
|
+
readDashboardUrl(root) !== undefined ||
|
|
133
|
+
existsSync(join(root, ReticleDir.CLOUD_LINK_FILE)) ||
|
|
134
|
+
apiKeyFrom(env) !== undefined);
|
|
135
|
+
}
|
|
129
136
|
/** The platform line: where the run lives when linked, what linking adds when not. One line. */
|
|
130
137
|
export function platformLine(root, env, runId) {
|
|
131
138
|
// Before any link: a run the platform will refuse is not "saved" and not "on the dashboard".
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The end-of-run report: the run ledger as the agent shows it to the user.
|
|
3
|
+
*
|
|
4
|
+
* Added to the answer of the call that ends an agent's run: a session end, a yield, or a released
|
|
5
|
+
* tab. A run that ended by releasing its tab got no word about the run before this, and its agent
|
|
6
|
+
* wrote its own summary. The report is a schema'd object, the markdown built from it, and one
|
|
7
|
+
* instruction. The cloud line is said only when the project is not linked, and it says what it is.
|
|
8
|
+
*/
|
|
9
|
+
import { RUN_REPORT_FIELD, type RunReport } from '@reticlehq/core';
|
|
10
|
+
import type { RunLedger } from '../../judgement/runs/run-ledger.js';
|
|
11
|
+
/** The report as markdown: the verdict, each claim with its evidence, then what the run left. */
|
|
12
|
+
export declare function renderRunReport(report: RunReport): string;
|
|
13
|
+
/** The calls that end an agent's run, by their own name or as an action of a merged tool. */
|
|
14
|
+
export declare function endsRun(tool: string, args: Record<string, unknown>): boolean;
|
|
15
|
+
/** What an answer says the run saved or read. An answer that refused counts for nothing. */
|
|
16
|
+
export declare function noteToolResult(ledger: RunLedger, tool: string, args: Record<string, unknown>, result: unknown): void;
|
|
17
|
+
/** The report as an answer field, or no field when the ledger holds no run. Empties the ledger. */
|
|
18
|
+
export declare function runReportField(ledger: RunLedger, root: string | undefined, env: Record<string, string | undefined>, now: number): Partial<Record<typeof RUN_REPORT_FIELD, {
|
|
19
|
+
report: RunReport;
|
|
20
|
+
markdown: string;
|
|
21
|
+
instruction: string;
|
|
22
|
+
}>>;
|