@reticlehq/server 3.9.0 → 3.10.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 +13 -12
- 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 -187
- 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/cli-doctor.js +1 -1
- package/dist/command/cli/cli-flow-commands.js +2 -9
- package/dist/command/cli/cli-kill.js +1 -1
- 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 +17 -15
- package/dist/command/cli/cli-parse.js +44 -27
- 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 +58 -29
- package/dist/command/cli/cli-verify.d.ts +11 -2
- package/dist/command/cli/cli-verify.js +27 -8
- 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/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor-repairs.js +1 -1
- 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/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
- package/dist/command/cli/ports/port-holder.d.ts +9 -0
- package/dist/command/cli/ports/port-holder.js +27 -3
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +22 -0
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +7 -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 +46 -41
- 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 +12 -1
- package/dist/command/daemon/daemon.js +35 -4
- package/dist/command/daemon/lifetime/death-cause.js +21 -2
- package/dist/command/daemon/lifetime/heartbeat.js +7 -2
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- 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-runtime.js +43 -2
- 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 +6 -3
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/version-skew.js +1 -1
- 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/visual/visual-tools.js +66 -19
- package/dist/index.js +7 -4
- 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/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- 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 +44 -0
- 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 +19 -3
- package/dist/language/flows/recording/tape/recordings.js +71 -5
- package/dist/language/flows/replay.d.ts +19 -5
- package/dist/language/flows/replay.js +99 -7
- 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/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/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/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 +28 -9
- 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 +22 -20
- package/dist/portal/input/real-input.js +41 -31
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +75 -18
- package/dist/portal/pool/browser-pool.d.ts +11 -29
- package/dist/portal/pool/browser-pool.js +35 -17
- package/dist/portal/pool/context-permissions.d.ts +17 -3
- package/dist/portal/pool/context-permissions.js +36 -5
- 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 +44 -17
- package/dist/portal/pool/lease-page-error.js +147 -5
- package/dist/portal/pool/page-error-journal.d.ts +7 -1
- package/dist/portal/pool/page-error-journal.js +27 -0
- package/dist/portal/pool/playwright-launcher.js +51 -8
- package/dist/portal/pool/pool-contract.d.ts +27 -1
- package/dist/portal/pool/pool-lease.d.ts +36 -1
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +23 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
- package/dist/portal/session/dev-server/dev-command.js +61 -15
- 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 +67 -25
- package/dist/portal/session/no-session-next-action.d.ts +2 -0
- package/dist/portal/session/no-session-next-action.js +12 -3
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +38 -3
- 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 +174 -63
- package/dist/start-wiring.d.ts +2 -1
- package/dist/start-wiring.js +2 -1
- package/dist/surface/mcp/mcp.js +3 -2
- 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 +1 -1
- 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/after-match.d.ts +4 -1
- package/dist/surface/tools/act/after-match.js +8 -1
- 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 +15 -1
- package/dist/surface/tools/act/resolve-target.js +24 -1
- package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
- package/dist/surface/tools/act/sequence-grade.js +37 -26
- 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 +78 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +74 -86
- 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 +16 -37
- package/dist/surface/tools/browser-tools.js +12 -6
- 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 +9 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +91 -21
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +3 -2
- 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 +35 -1
- package/dist/surface/tools/lease-hint.js +42 -0
- package/dist/surface/tools/lease-readiness.d.ts +26 -0
- package/dist/surface/tools/lease-readiness.js +79 -0
- package/dist/surface/tools/lease-seed.d.ts +14 -1
- package/dist/surface/tools/lease-seed.js +35 -3
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +62 -110
- 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/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +6 -0
- package/dist/surface/tools/tool-kit.js +2 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +28 -5
- 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 +1 -0
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +8 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- 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 +1 -1
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/init.mdx +2 -2
- package/docs/cli/license.mdx +9 -7
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- 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 +33 -23
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +2 -2
- package/docs/environment.mdx +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 +1 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +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 +2 -2
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +30 -1
- package/docs/usage.md +17 -14
- package/package.json +11 -14
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -13,7 +13,7 @@ import { aliasParam } from './args/alias-args.js';
|
|
|
13
13
|
import { resolveSessionWithin } from '../../portal/session/timing/resolve-within.js';
|
|
14
14
|
import { WALL_CLOCK } from '../../portal/session/timing/wall-clock.js';
|
|
15
15
|
import { timeoutMsSchema } from './args/numeric-bounds.js';
|
|
16
|
-
import { captureAct, captureVerdictedAct,
|
|
16
|
+
import { captureAct, captureVerdictedAct, pageOf, wroteSince } from '../../language/flows/replay.js';
|
|
17
17
|
import { durableArg, durableOutput, reloadAndRecheck, withDurability } from './act/durable.js';
|
|
18
18
|
import { ActionType, ActionWarning, ReticleCommand, CaptureLoss, DEFAULT_ASSERT_TIMEOUT_MS, InputMode, Verified, VerifiedReason, PredicateKind, checkIntent, } from '@reticlehq/core';
|
|
19
19
|
import { leanActResult, mutatedWithin } from './act/act-view.js';
|
|
@@ -34,19 +34,19 @@ import { declaresState } from '@reticlehq/engine/question/predicate/predicate-as
|
|
|
34
34
|
import { isStateUnwatched } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
35
35
|
import { hiddenMatchNote } from '@reticlehq/engine/evidence/already-true.js';
|
|
36
36
|
import { inFlightRequestLabels, repeatedRequestLabels } from './act/settle-in-flight.js';
|
|
37
|
-
import { awaitShownMatch, finishAfterMatch } from './act/after-match.js';
|
|
37
|
+
import { awaitShownMatch, evaluateAfterAct, finishAfterMatch } from './act/after-match.js';
|
|
38
38
|
import { noteProved } from './act/proved-controls.js';
|
|
39
|
-
import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
|
|
39
|
+
import { decideVerified, withDeclinedDialog } from '@reticlehq/engine/evidence/verified.js';
|
|
40
40
|
import { buildHonestyBlock, honestyForVerdict } from '@reticlehq/engine/evidence/honesty.js';
|
|
41
|
-
import { declaredExpectations, declaresBodyIndependentChannel, } from '@reticlehq/engine/question/declared.js';
|
|
41
|
+
import { declaredExpectations, declaresBodyIndependentChannel, namedReactions, provedConsequences, } from '@reticlehq/engine/question/declared.js';
|
|
42
42
|
import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
|
|
43
43
|
import { saveFailedAssertCapsule } from './act-capsule.js';
|
|
44
44
|
import { blastRadius, buildDivergenceCapsule, wireCapsule } from '../../judgement/capsule/capsule.js';
|
|
45
45
|
import { predicateToExpectedLinks } from '@reticlehq/engine/question/predicate/predicate-to-links.js';
|
|
46
|
-
import { absenceBlindSpotNote, buildCoverageStatement, blindSpotsFromState, transportGapNote, Coverage, impeachesCapture, } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
46
|
+
import { absenceBlindSpotNote, buildCoverageStatement, canvasPaintSpots, blindSpotsFromState, failingClauseReadsLostChannel, restsOnCompleteWindow, transportGapNote, Coverage, impeachesCapture, } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
47
47
|
import { acceptedWriteLabels } from '@reticlehq/engine/evidence/accepted-write.js';
|
|
48
48
|
import { unreadWriteLabels, unreadWriteReason } from '@reticlehq/engine/evidence/unread-outcome.js';
|
|
49
|
-
import {
|
|
49
|
+
import { waitForPredicate, provenExpectedLinks, PredicateSchema, } from '@reticlehq/engine/question/predicate/predicate.js';
|
|
50
50
|
import { sessionVerdictFacts } from '../../portal/session/session-verdict-facts.js';
|
|
51
51
|
import { currentOf, healthEnvelope, refuseIfThrottled } from '../../portal/session/session-health.js';
|
|
52
52
|
import { pausedShortCircuit, pausedOutputShape, withControl, PAUSED_NO_VERDICT, } from '../../portal/session/control-envelope.js';
|
|
@@ -54,14 +54,15 @@ import { asNumber, asRecord, asString } from '@reticlehq/core';
|
|
|
54
54
|
import { sourceOf } from './tools-helpers.js';
|
|
55
55
|
import { confirmedByDeclaration } from './act/act-danger.js';
|
|
56
56
|
import { dispatchAct, preflightAct } from './act/act-preflight.js';
|
|
57
|
-
import {
|
|
57
|
+
import { actInconclusive, checkNet, followLostObservation, NAVIGATION_FOLLOW_GRACE_MS, } from './act/act-observation.js';
|
|
58
58
|
import { intentArg, sessionIdShape } from './tool-kit.js';
|
|
59
|
+
import { actCommand } from './act-command.js';
|
|
60
|
+
import { tryRealInput } from './real-input-attempt.js';
|
|
59
61
|
import { verdictIntentGap } from '@reticlehq/engine/evidence/verdict-intent-gap.js';
|
|
60
62
|
import { asActionType, gradeOf } from './act/act-helpers.js';
|
|
61
63
|
import { resolveActTarget } from './act/act-target.js';
|
|
64
|
+
import { hiddenTargetField } from './act/resolve-target.js';
|
|
62
65
|
import { screenRequestFindings, withAdvisories } from './act/screen-request.js';
|
|
63
|
-
import { tryRealInput, rewriteUploadArgs, HOVER_NEEDS_POINTER_MSG } from './real-input-attempt.js';
|
|
64
|
-
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
65
66
|
import { gradeOfPredicate } from './assert/assert-grade.js';
|
|
66
67
|
import { readAlreadyTrue } from './act/already-true.js';
|
|
67
68
|
import { engineBlindSpotsField, noteSourceSeen } from '../../portal/session/recorded-gaps.js';
|
|
@@ -71,45 +72,11 @@ import { engineBlindSpotsField, noteSourceSeen } from '../../portal/session/reco
|
|
|
71
72
|
*/
|
|
72
73
|
const ACTION_TYPE_VALUES = Object.values(ActionType);
|
|
73
74
|
const actionTypeEnum = z.enum(ACTION_TYPE_VALUES);
|
|
74
|
-
/**
|
|
75
|
-
* Single dispatch point for every ACT and ACT_SEQUENCE command.
|
|
76
|
-
*
|
|
77
|
-
* reticle_act, act_and_wait and act_sequence rewrite uploads here, once. The path resolves against
|
|
78
|
-
* the session's project, not the daemon's folder, so a worktree uploads its own fixture (#1769).
|
|
79
|
-
*
|
|
80
|
-
* captureAct is called AFTER this, so the flow-recording captures the pre-rewrite args (the path
|
|
81
|
-
* the agent actually wrote) rather than the base64 blob — replay would otherwise send 750 KiB of
|
|
82
|
-
* base64 to the browser as if it were an inline content call.
|
|
83
|
-
*/
|
|
84
|
-
export async function actCommand(deps, session, actArgs, timeoutMs) {
|
|
85
|
-
const rewritten = await rewriteUploadArgs({ fs: deps.fs, reticleRoot: sessionRoot(deps, session.id) }, 'string' === typeof actArgs['action'] ? actArgs['action'] : '', asRecord(actArgs['args']));
|
|
86
|
-
// Sequence and act_and_wait dispatch through here without the ACT handler's tryRealInput.
|
|
87
|
-
// A synthetic hover reports dispatched/settled while CSS :hover never applies — drive a real
|
|
88
|
-
// pointer when one is available, otherwise refuse rather than lie. A press on a lease takes its
|
|
89
|
-
// real keyboard, as reticle_act does: a synthetic Tab moves no focus.
|
|
90
|
-
const hover = ActionType.HOVER === actArgs['action'];
|
|
91
|
-
if (hover || (ActionType.PRESS === actArgs['action'] && deps.realInput === undefined)) {
|
|
92
|
-
const ref = asString(actArgs['ref']);
|
|
93
|
-
if (hover && ref === undefined)
|
|
94
|
-
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
95
|
-
const action = hover ? ActionType.HOVER : ActionType.PRESS;
|
|
96
|
-
const real = await tryRealInput(deps, session, ref ?? '', action, actArgs);
|
|
97
|
-
if (hover && real.result === undefined)
|
|
98
|
-
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
99
|
-
if (real.result !== undefined) {
|
|
100
|
-
const result = { dispatched: true, settled: real.settled, ...asRecord(real.result) };
|
|
101
|
-
return { kind: 'command_result', id: action, ok: true, result };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const bridgeArgs = { ...actArgs, args: rewritten };
|
|
105
|
-
// What the acted element shows (test id, source) is noted in `Session.command`, Harness acts too.
|
|
106
|
-
return session.command(ReticleCommand.ACT, bridgeArgs, timeoutMs);
|
|
107
|
-
}
|
|
108
75
|
export const ACT_TOOLS = [
|
|
109
76
|
{
|
|
110
77
|
name: ReticleTool.ACT,
|
|
111
78
|
example: { ref: 'e42', action: 'fill', args: { value: 'hello' } },
|
|
112
|
-
description: 'Act WITHOUT checking the result — if the action is supposed to cause something, use reticle_act_and_wait { until } instead and get the verdict in the same call. This tool returns immediately with a `since` cursor; observe the reaction yourself with reticle_observe. Use it for actions with no observable consequence to assert (focus, hover, scrollIntoView, a fill you are about to submit). One action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. Fields at their uninformative default are OMITTED so a clean action collapses to its consequence: an absent dispatched/targetMatched/visible/enabled means true, an absent occluded/scrolledIntoView/valueChanged/defaultPrevented means false, an absent focusMoved/occludedBy means null. occluded=true means the click point is covered by another element (a real user could not click it) — synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. effect.alreadyAtValue=true (check/uncheck only) means the box already read as the requested state, so NOTHING was dispatched and the app was never told — the DOM property is not evidence the application holds that value, so assert the app\'s own state (a signal, a request, a derived control), not the box. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured — pass args.native:true to force a trusted native click (file pickers, clipboard). args.holdMs keeps the pointer DOWN for that long between mousedown and mouseup — the only way to drive a hold-to-confirm control (hold-to-delete, hold-to-record, long-press); effect.heldMs reports the hold actually achieved, which a throttled background tab can stretch. args.confirmDangerous is a permission gate and NOT a duration: it allows a destructive control, it does not hold one. inputModeReason explains any real→synthetic choice so it is never silent. On a leased tab a press uses the real keyboard: Tab moves focus, and a press with a ref focuses that element first, so Enter or Space presses it as a person would. Full model (real-input, throttled tabs, `reticle drive`): node_modules/@reticlehq/server/docs/usage.md §18.',
|
|
79
|
+
description: 'Act WITHOUT checking the result — if the action is supposed to cause something, use reticle_act_and_wait { until } instead and get the verdict in the same call. This tool returns immediately with a `since` cursor; observe the reaction yourself with reticle_observe. Use it for actions with no observable consequence to assert (focus, hover, scrollIntoView, a fill you are about to submit). One action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView|insertText. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. Fields at their uninformative default are OMITTED so a clean action collapses to its consequence: an absent dispatched/targetMatched/visible/enabled means true, an absent occluded/scrolledIntoView/valueChanged/defaultPrevented means false, an absent focusMoved/occludedBy means null. occluded=true means the click point is covered by another element (a real user could not click it) — synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. effect.alreadyAtValue=true (check/uncheck only) means the box already read as the requested state, so NOTHING was dispatched and the app was never told — the DOM property is not evidence the application holds that value, so assert the app\'s own state (a signal, a request, a derived control), not the box. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured — pass args.native:true to force a trusted native click (file pickers, clipboard). args.holdMs keeps the pointer DOWN for that long between mousedown and mouseup — the only way to drive a hold-to-confirm control (hold-to-delete, hold-to-record, long-press); effect.heldMs reports the hold actually achieved, which a throttled background tab can stretch. args.confirmDangerous is a permission gate and NOT a duration: it allows a destructive control, it does not hold one. inputModeReason explains any real→synthetic choice so it is never silent. For insertText, defaultPrevented=true with valueChanged=true is the success path on an editor that owns its input: it cancelled beforeinput and applied the text, so it is NOT a refusal. insertText refuses with an error when the surface is not contenteditable — an EditContext editor (Monaco, CodeMirror 6) is refused too, because a scripted update fires no textupdate for its model — or when the native command exists and fails. On a leased tab a press uses the real keyboard: Tab moves focus, and a press with a ref focuses that element first, so Enter or Space presses it as a person would. Full model (real-input, throttled tabs, `reticle drive`): node_modules/@reticlehq/server/docs/usage.md §18.',
|
|
113
80
|
inputSchema: {
|
|
114
81
|
ref: z
|
|
115
82
|
.string()
|
|
@@ -123,7 +90,7 @@ export const ACT_TOOLS = [
|
|
|
123
90
|
args: z
|
|
124
91
|
.record(z.string(), z.unknown())
|
|
125
92
|
.optional()
|
|
126
|
-
.describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt — a Cmd+K) or a click/dblclick (Shift+click range select), { button: "right" | "middle" } for a click (right sends contextmenu), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { native: true } to force a trusted native click, { holdMs: N } to keep the pointer or the key DOWN for N ms (hold-to-confirm controls; effect.heldMs reports what was achieved), { confirmDangerous: true } to allow a potentially destructive control — a permission gate, NOT a duration. For press: { keys: ["Control","k"] } holds several keys down TOGETHER (pressed in order, released in reverse) and { code } names the physical key — both stay on the in-page dispatcher, the only path that can express them. For upload: { path } is a path on disk (absolute or relative to the project root; the daemon reads the file and delivers real bytes to the file picker — this is the way to verify document-ingestion flows); or { name, content?, type? } to supply inline bytes directly.'),
|
|
93
|
+
.describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press/insertText (the key NAME, e.g. Escape or Tab, for press/type — or the text to insert for insertText), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt — a Cmd+K) or a click/dblclick (Shift+click range select), { button: "right" | "middle" } for a click (right sends contextmenu), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { at: { x, y } } for click/dblclick/hover/tap/drag to act at a point of the element as fractions 0..1 of its box (a canvas has no child elements), { to: { x, y } } for drag to end at a point of the drop target (or of the source), { native: true } to force a trusted native click, { holdMs: N } to keep the pointer or the key DOWN for N ms (hold-to-confirm controls; effect.heldMs reports what was achieved), { confirmDangerous: true } to allow a potentially destructive control — a permission gate, NOT a duration. For press: { keys: ["Control","k"] } holds several keys down TOGETHER (pressed in order, released in reverse) and { code } names the physical key — both stay on the in-page dispatcher, the only path that can express them. For upload: { path } is a path on disk (absolute or relative to the project root; the daemon reads the file and delivers real bytes to the file picker — this is the way to verify document-ingestion flows); or { name, content?, type? } to supply inline bytes directly.'),
|
|
127
94
|
refuseWhenThrottled: z
|
|
128
95
|
.boolean()
|
|
129
96
|
.optional()
|
|
@@ -160,8 +127,9 @@ export const ACT_TOOLS = [
|
|
|
160
127
|
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
161
128
|
// Live-control: refuse to drive the page while the human has paused us (before any work).
|
|
162
129
|
const paused = pausedShortCircuit(session);
|
|
163
|
-
if (paused !== undefined)
|
|
130
|
+
if (paused !== undefined) {
|
|
164
131
|
return paused;
|
|
132
|
+
}
|
|
165
133
|
refuseIfThrottled(session, args['refuseWhenThrottled']);
|
|
166
134
|
// Where this step RUNS, read before the action — not after.
|
|
167
135
|
//
|
|
@@ -169,12 +137,13 @@ export const ACT_TOOLS = [
|
|
|
169
137
|
// ambient tape cuts journeys on this value, and stamping the destination put the sign-in click
|
|
170
138
|
// at the head of the page it navigated TO: 4 of 11 auto-captured flows began with
|
|
171
139
|
// `login-submit` on `/deployments`, which is the exact flow the segmentation exists to prevent.
|
|
172
|
-
const routeBefore =
|
|
140
|
+
const routeBefore = pageOf(session.url);
|
|
173
141
|
// Resolve `target` to a ref BEFORE the action window opens, so the lookup is not attributed to
|
|
174
142
|
// the act and cannot be mistaken for something the action caused.
|
|
175
143
|
const targetRef = await resolveActTarget(session, args);
|
|
176
|
-
if ('error' === targetRef.kind)
|
|
144
|
+
if ('error' === targetRef.kind) {
|
|
177
145
|
throw new Error(targetRef.message);
|
|
146
|
+
}
|
|
178
147
|
const since = session.elapsed();
|
|
179
148
|
// The act cursor + effect are marked only once the action has actually DISPATCHED (below, on
|
|
180
149
|
// each success path) — a refused act must leave nothing behind for the next observe to judge.
|
|
@@ -204,23 +173,23 @@ export const ACT_TOOLS = [
|
|
|
204
173
|
settled: real.settled,
|
|
205
174
|
settleReason: null,
|
|
206
175
|
result: leanActResult(real.result),
|
|
176
|
+
...hiddenTargetField(targetRef),
|
|
207
177
|
...healthEnvelope(session),
|
|
208
178
|
});
|
|
209
179
|
}
|
|
210
180
|
// actCommand is the single interception point: it rewrites upload+path args to real bytes
|
|
211
181
|
// before any ACT command crosses the bridge, covering this call site and all others.
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
args: args['args'] ?? {},
|
|
216
|
-
});
|
|
217
|
-
if (!result.ok)
|
|
182
|
+
const plain = { ref: targetRef.ref, action: args['action'], args: args['args'] ?? {} };
|
|
183
|
+
const result = await actCommand(deps, session, ReticleCommand.ACT, plain, undefined, true);
|
|
184
|
+
if (!result.ok) {
|
|
218
185
|
throw new Error(result.error ?? 'act failed');
|
|
186
|
+
}
|
|
219
187
|
captureAct(deps.recordings, args, result.result, routeBefore, session.id);
|
|
220
188
|
// lift dispatch/settle status to the envelope (a settle timeout is NOT a failure).
|
|
221
189
|
const r = asRecord(result.result);
|
|
222
|
-
if ('boolean' === typeof r['settled'])
|
|
190
|
+
if ('boolean' === typeof r['settled']) {
|
|
223
191
|
settledOutcome = r['settled'];
|
|
192
|
+
}
|
|
224
193
|
// Keep what only this call measured, so the observe that judges this window can ask whether
|
|
225
194
|
// anything happened INSIDE the target — the one fact that separates a dead control from a
|
|
226
195
|
// page that was merely busy with something else.
|
|
@@ -234,12 +203,13 @@ export const ACT_TOOLS = [
|
|
|
234
203
|
settled: r['settled'] ?? null,
|
|
235
204
|
settleReason: r['settleReason'] ?? null,
|
|
236
205
|
result: leanActResult(result.result),
|
|
206
|
+
...hiddenTargetField(targetRef),
|
|
237
207
|
...(true === real.fellBack ? { warning: ActionWarning.REAL_INPUT_FELL_BACK } : {}),
|
|
238
208
|
...healthEnvelope(session),
|
|
239
209
|
});
|
|
240
210
|
}
|
|
241
211
|
finally {
|
|
242
|
-
deps.recordings.markEnded(
|
|
212
|
+
deps.recordings.markEnded(pageOf(session.url), session.id);
|
|
243
213
|
// Close the window on every exit (settle or throw), recording the action + settle outcome.
|
|
244
214
|
session.finishAction(undefined, settledOutcome, true === settledOutcome ? session.elapsed() - since : undefined);
|
|
245
215
|
}
|
|
@@ -271,7 +241,7 @@ export const ACT_TOOLS = [
|
|
|
271
241
|
args: z
|
|
272
242
|
.record(z.string(), z.unknown())
|
|
273
243
|
.optional()
|
|
274
|
-
.describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut or a click (Meta/Control/Shift/Alt), { button: "right" | "middle" } for a click, { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { confirmDangerous: true } for a potentially destructive control. For upload: { path } is a path on disk (absolute or relative to project root; daemon reads real bytes) or { name, content?, type? } for inline bytes.'),
|
|
244
|
+
.describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press/insertText (the key NAME, e.g. Escape or Tab, for press/type — or the text to insert for insertText), { modifiers: ["Meta","Shift"] } for a press shortcut or a click (Meta/Control/Shift/Alt), { button: "right" | "middle" } for a click, { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { at: { x, y } } for click/dblclick/hover/tap/drag to act at a point of the element as fractions 0..1 of its box (a canvas has no child elements), { to: { x, y } } for drag to end at a point of the drop target (or of the source), { confirmDangerous: true } for a potentially destructive control. For upload: { path } is a path on disk (absolute or relative to project root; daemon reads real bytes) or { name, content?, type? } for inline bytes.'),
|
|
275
245
|
predicate: PredicateSchema.optional().describe('Alias for `until`.'),
|
|
276
246
|
until: PredicateSchema.optional().describe('Predicate to wait for after the action completes (same shape as reticle_assert). OMIT to wait for the page to SETTLE — network + DOM idle — the deterministic default instead of a sleep. To assert a consequence AND settle, allOf them: { kind: "allOf", predicates: [<your predicate>, { kind: "settled" }] }.'),
|
|
277
247
|
timeout_ms: timeoutMsSchema
|
|
@@ -287,6 +257,10 @@ export const ACT_TOOLS = [
|
|
|
287
257
|
},
|
|
288
258
|
outputSchema: {
|
|
289
259
|
durable: durableOutput,
|
|
260
|
+
budgetExtendedMs: z
|
|
261
|
+
.number()
|
|
262
|
+
.optional()
|
|
263
|
+
.describe("The re-ask on the successor ran on the caller's grace: a budget extension ON PURPOSE, taken only when the departure evidence showed the page navigating. Absent for every verdict that needed no follow."),
|
|
290
264
|
effect: z
|
|
291
265
|
.unknown()
|
|
292
266
|
.optional()
|
|
@@ -401,8 +375,9 @@ export const ACT_TOOLS = [
|
|
|
401
375
|
// BEFORE the action. A body clause this session cannot answer would fail whatever the app
|
|
402
376
|
// did, and on a drive that mutates state the action is not always repeatable. See #801(C).
|
|
403
377
|
const bodyRefusal = bodyClauseRefusal(until, session);
|
|
404
|
-
if (bodyRefusal !== undefined)
|
|
378
|
+
if (bodyRefusal !== undefined) {
|
|
405
379
|
throw new Error(bodyRefusal);
|
|
380
|
+
}
|
|
406
381
|
const timeout = asNumber(args['timeout_ms']) ?? DEFAULT_ASSERT_TIMEOUT_MS;
|
|
407
382
|
// An intent declared here lands in the ledger BEFORE the verdict is drawn, which is what makes
|
|
408
383
|
// the undeclared-change gap silent on THIS verdict rather than the next one: it reads the
|
|
@@ -415,8 +390,9 @@ export const ACT_TOOLS = [
|
|
|
415
390
|
// Resolve `target` to a ref BEFORE the action window opens, so the lookup is not attributed to
|
|
416
391
|
// the act and cannot be mistaken for something the action caused.
|
|
417
392
|
const resolved = await resolveActTarget(session, args);
|
|
418
|
-
if ('error' === resolved.kind)
|
|
393
|
+
if ('error' === resolved.kind) {
|
|
419
394
|
throw new Error(resolved.message);
|
|
395
|
+
}
|
|
420
396
|
let since = session.elapsed();
|
|
421
397
|
const actedSince = since;
|
|
422
398
|
// The cursor + effect are marked after the act dispatches (below) — a refused act leaves none.
|
|
@@ -431,7 +407,7 @@ export const ACT_TOOLS = [
|
|
|
431
407
|
// Was the consequence ALREADY TRUE, and what did that reading say? See act/already-true.ts.
|
|
432
408
|
const { alreadyTrue, alreadyTrueHiddenMatch, alreadyTrueEvidence, baselines } = await readAlreadyTrue(session, until, since);
|
|
433
409
|
// Same as the ACT handler: the route this step RAN on, before the action can move the app.
|
|
434
|
-
const routeBeforeWait =
|
|
410
|
+
const routeBeforeWait = pageOf(session.url);
|
|
435
411
|
// Recorded in `finally`, once the verdict is known — see captureVerdictedAct. `held`: the
|
|
436
412
|
// action made the declared claim hold.
|
|
437
413
|
let dispatched;
|
|
@@ -445,14 +421,15 @@ export const ACT_TOOLS = [
|
|
|
445
421
|
// returns null instead (see act-preflight.ts for why a write is never re-sent), and the
|
|
446
422
|
// successor block below then asks whether the consequence held on the NEW document — a
|
|
447
423
|
// green if it did, an honest observation_lost if it did not.
|
|
448
|
-
const actResult = await dispatchAct(() => actCommand(deps, session, {
|
|
424
|
+
const actResult = await dispatchAct(() => actCommand(deps, session, ReticleCommand.ACT, {
|
|
449
425
|
ref: resolved.ref,
|
|
450
426
|
action: args['action'],
|
|
451
427
|
args: confirmedByDeclaration(asRecord(args['args']), args['target'], untilNamed),
|
|
452
428
|
}));
|
|
453
429
|
if (actResult !== null) {
|
|
454
|
-
if (!actResult.ok)
|
|
430
|
+
if (!actResult.ok) {
|
|
455
431
|
throw new Error(actResult.error ?? 'act failed');
|
|
432
|
+
}
|
|
456
433
|
dispatched = { result: actResult.result };
|
|
457
434
|
// Dispatched — now this act owns the cursor and the effect. Marking its OWN measurement
|
|
458
435
|
// also stops the spread below from inheriting an earlier reticle_act's action and
|
|
@@ -463,9 +440,7 @@ export const ACT_TOOLS = [
|
|
|
463
440
|
const predicateStarted = session.elapsed();
|
|
464
441
|
let verdict = null === actResult
|
|
465
442
|
? { pass: false, observationLost: true }
|
|
466
|
-
: timeout
|
|
467
|
-
? await waitForPredicate(session, until, timeout, since, baselines)
|
|
468
|
-
: await evaluatePredicate(session, until, since, true, baselines);
|
|
443
|
+
: await evaluateAfterAct(session, until, timeout, since, baselines);
|
|
469
444
|
// The SDK may have gone away mid-act — see act-observation.ts.
|
|
470
445
|
const followed = await followLostObservation({
|
|
471
446
|
sessions: deps.sessions,
|
|
@@ -473,23 +448,28 @@ export const ACT_TOOLS = [
|
|
|
473
448
|
verdict,
|
|
474
449
|
timeout,
|
|
475
450
|
predicateStarted,
|
|
451
|
+
since,
|
|
452
|
+
failedFollowGraceMs: NAVIGATION_FOLLOW_GRACE_MS,
|
|
476
453
|
reevaluate: (next, budget) => waitForPredicate(next, until, budget, 0, baselines),
|
|
477
454
|
});
|
|
478
|
-
if (followed.followed)
|
|
455
|
+
if (followed.followed) {
|
|
479
456
|
since = 0;
|
|
457
|
+
}
|
|
480
458
|
session = followed.session;
|
|
481
459
|
verdict = followed.verdict;
|
|
482
460
|
// After a match, let the app finish before the window closes — see act/after-match.ts.
|
|
483
461
|
if (verdict.pass && timeout > 0) {
|
|
484
462
|
const left = () => timeout - (session.elapsed() - predicateStarted);
|
|
485
463
|
const reverted = await finishAfterMatch(session, until, since, baselines, left);
|
|
486
|
-
if (reverted !== undefined)
|
|
464
|
+
if (reverted !== undefined) {
|
|
487
465
|
verdict = { pass: false, failureReason: reverted, observed: reverted };
|
|
466
|
+
}
|
|
488
467
|
verdict = await awaitShownMatch(session, until, since, baselines, verdict, left);
|
|
489
468
|
}
|
|
490
469
|
const r = asRecord(actResult?.result);
|
|
491
|
-
if ('boolean' === typeof r['settled'])
|
|
470
|
+
if ('boolean' === typeof r['settled']) {
|
|
492
471
|
settledOutcome = r['settled'];
|
|
472
|
+
}
|
|
493
473
|
// Where the acted element is written, captured at act time even if the action unmounted it.
|
|
494
474
|
const actedSource = sourceOf(r['source']);
|
|
495
475
|
// Remembered so a LATER assertion can name a file with no element to point at, and so the
|
|
@@ -507,6 +487,9 @@ export const ACT_TOOLS = [
|
|
|
507
487
|
// Read before the capsule, which needs it: a window the buffer trimmed cannot state an
|
|
508
488
|
// absolute about what did NOT happen. See CausalSummary.truncated.
|
|
509
489
|
const bufferLost = session.lostSince(since);
|
|
490
|
+
const impeachForLoss = bufferLost &&
|
|
491
|
+
(restsOnCompleteWindow(until) ||
|
|
492
|
+
(false === verdict.pass && failingClauseReadsLostChannel(until, windowEvents)));
|
|
510
493
|
const capsule = verdict.pass
|
|
511
494
|
? undefined
|
|
512
495
|
: buildDivergenceCapsule(links, windowEvents, bufferLost);
|
|
@@ -528,11 +511,13 @@ export const ACT_TOOLS = [
|
|
|
528
511
|
// stronger than this block. Grade from the strongest asserted consequence; integrity from evictions.
|
|
529
512
|
// Coverage: cross-origin frames / other blind spots the SDK reported during this window mean the
|
|
530
513
|
// verdict didn't see everything — say so, never imply full coverage.
|
|
531
|
-
const
|
|
514
|
+
const prior = since > 0 ? (await session.queryEvents({ since: 0 })).filter((e) => e.t < since) : [];
|
|
515
|
+
const canvas = canvasPaintSpots(windowEvents, { actionSince: since, prior });
|
|
516
|
+
const spots = [...blindSpotsFromState(session.blindSpots(), session.runtime), ...canvas];
|
|
532
517
|
// The window's own counts, so sampling before it is not described as this window's (#1414).
|
|
533
518
|
const windowSpots = blindSpotsFromState(session.blindSpots(since), session.runtime);
|
|
534
519
|
const coverage = buildCoverageStatement(spots, windowSpots);
|
|
535
|
-
const absenceBlindSpot = absenceBlindSpotNote(until, spots);
|
|
520
|
+
const absenceBlindSpot = absenceBlindSpotNote(until, spots, windowEvents);
|
|
536
521
|
// A green resting on hidden matches says the node exists, not that it shows (#1408).
|
|
537
522
|
const hiddenMatch = hiddenMatchNote(until, verdict.evidence);
|
|
538
523
|
// Nothing subscribed ⇒ the state channel is dark, and the summary must say so rather than
|
|
@@ -567,13 +552,7 @@ export const ACT_TOOLS = [
|
|
|
567
552
|
const honesty = buildHonestyBlock({
|
|
568
553
|
grade,
|
|
569
554
|
attribution: 'window',
|
|
570
|
-
|
|
571
|
-
// the action ran", which was the previous rule and which is true on essentially every live
|
|
572
|
-
// page. Age eviction retires everything past 60s on every push, and the churn floor is
|
|
573
|
-
// sacrificed on purpose; neither costs this verdict a single event it needed. In the
|
|
574
|
-
// field this made `unclean_capture` the dominant cause of `unknown`, reported for
|
|
575
|
-
// evictions that happened outside the window they impeached.
|
|
576
|
-
truncated: bufferLost,
|
|
555
|
+
truncated: impeachForLoss,
|
|
577
556
|
coveragePartial: Coverage.PARTIAL === coverage.coverage,
|
|
578
557
|
...(coverage.note === undefined ? {} : { coverageNote: coverage.note }),
|
|
579
558
|
// Settlement no longer vetoes a declared consequence that held, so the fact is carried
|
|
@@ -609,8 +588,8 @@ export const ACT_TOOLS = [
|
|
|
609
588
|
// flagship path, and nothing said so: the finding was simply never produced.
|
|
610
589
|
//
|
|
611
590
|
// Learning material only. Attribution is unchanged — findings still come exclusively from
|
|
612
|
-
// `windowEvents`, exactly as on the assert path.
|
|
613
|
-
|
|
591
|
+
// `windowEvents`, exactly as on the assert path. It is read above, before coverage, because the
|
|
592
|
+
// canvas-paint spot (#1525) learns the last canvas digest from it too.
|
|
614
593
|
//
|
|
615
594
|
// `actionSince` is the window's own floor here, which is the tightest attribution there is —
|
|
616
595
|
// and it is what lets `duplicate-request` claim "one user action was performed" and mean it.
|
|
@@ -625,9 +604,10 @@ export const ACT_TOOLS = [
|
|
|
625
604
|
namedNetUrls: declared.netUrls,
|
|
626
605
|
repeatableNetUrls: declared.repeatableNetUrls,
|
|
627
606
|
// A consequence that was already true before the action proves nothing about it, so it is
|
|
628
|
-
// not evidence the destination rendered
|
|
629
|
-
|
|
607
|
+
// not evidence the destination rendered or the route moved — `alreadyTrue` decides that, once.
|
|
608
|
+
...provedConsequences(declared, verdict.pass && !alreadyTrue),
|
|
630
609
|
...session.lastAct.effect(),
|
|
610
|
+
...namedReactions(declared),
|
|
631
611
|
currentDocumentId: session.currentDocumentId,
|
|
632
612
|
currentEditEpoch: session.currentEditEpoch,
|
|
633
613
|
appOrigin: session.url,
|
|
@@ -645,8 +625,8 @@ export const ACT_TOOLS = [
|
|
|
645
625
|
});
|
|
646
626
|
const outcomeUnread = unreadWriteLabels(windowEvents);
|
|
647
627
|
const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
|
|
648
|
-
// A
|
|
649
|
-
const inconclusive =
|
|
628
|
+
// A timed-out read says why; a disabled or unreachable target cannot cause anything.
|
|
629
|
+
const inconclusive = actInconclusive(verdict, actResult?.result, session);
|
|
650
630
|
const { decision, durable } = await withDurability(args['durable'], until, decideVerified({
|
|
651
631
|
pass: verdict.pass,
|
|
652
632
|
// Threaded rather than looked up: decideVerified is pure and has no session.
|
|
@@ -686,10 +666,11 @@ export const ACT_TOOLS = [
|
|
|
686
666
|
unsettled: {
|
|
687
667
|
waitedFor: describeWaitTarget(until),
|
|
688
668
|
stillInFlight,
|
|
689
|
-
// The retry loop that leaves nothing outstanding — see repeatedRequestLabels.
|
|
690
669
|
repeated: repeatedRequestLabels(windowEvents),
|
|
691
670
|
},
|
|
692
671
|
...(namedNetIsInFlight(until, stillInFlight) ? { namedRequestInFlight: true } : {}),
|
|
672
|
+
...(impeachForLoss && false === verdict.pass ? { bufferLostEvidence: true } : {}),
|
|
673
|
+
...withDeclinedDialog(windowEvents),
|
|
693
674
|
}), (part) => reloadAndRecheck(deps, session, part, timeout));
|
|
694
675
|
noteProved(session, String(decision.verified), actResult?.result);
|
|
695
676
|
// Computed once: the verdict block reports it, and the instrumentation gaps are a second
|
|
@@ -785,6 +766,7 @@ export const ACT_TOOLS = [
|
|
|
785
766
|
// An unobserved act has no effect to report, and inventing an empty one would read as
|
|
786
767
|
// "the page did nothing" — a claim about the app, from a call that never saw it.
|
|
787
768
|
...(null === actResult ? {} : { effect: leanActResult(actResult.result) }),
|
|
769
|
+
...hiddenTargetField(resolved),
|
|
788
770
|
verdict,
|
|
789
771
|
// Promoted out of `effect` on red only. On green nobody needs it and it is noise; on red it
|
|
790
772
|
// is the first thing the agent wants, and burying a file:line inside the effect block is
|
|
@@ -827,15 +809,21 @@ export const ACT_TOOLS = [
|
|
|
827
809
|
// Without its summary: it is byte-identical to `summary` two fields up. See wireCapsule.
|
|
828
810
|
...(capsule === undefined ? {} : { capsule: wireCapsule(capsule) }),
|
|
829
811
|
...(0 === greenRadius.length ? {} : { blastRadius: greenRadius }),
|
|
812
|
+
// The re-ask on the successor ran on the caller's grace — a budget extension on purpose,
|
|
813
|
+
// declared so the agent can see the extra time it was given (#2009).
|
|
814
|
+
...(followed.budgetExtendedMs === undefined
|
|
815
|
+
? {}
|
|
816
|
+
: { budgetExtendedMs: followed.budgetExtendedMs }),
|
|
830
817
|
since,
|
|
831
818
|
...(session.id === actedSessionId ? {} : { sessionId: session.id }),
|
|
832
819
|
...healthEnvelope(currentOf(deps.sessions, session)),
|
|
833
820
|
});
|
|
834
821
|
}
|
|
835
822
|
finally {
|
|
836
|
-
if (dispatched !== undefined)
|
|
823
|
+
if (dispatched !== undefined) {
|
|
837
824
|
captureVerdictedAct(deps.recordings, args, dispatched.result, routeBeforeWait, held, session.id, wroteSince(acted, actedSince));
|
|
838
|
-
|
|
825
|
+
}
|
|
826
|
+
deps.recordings.markEnded(pageOf(currentOf(deps.sessions, session).url), session.id);
|
|
839
827
|
acted.finishAction(verdictEffect, settledOutcome, true === settledOutcome ? acted.elapsed() - actedSince : undefined);
|
|
840
828
|
}
|
|
841
829
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server restart`) and retry before trusting a fix to Reticle itself.";
|
|
1
|
+
export declare const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server serve --restart`) and retry before trusting a fix to Reticle itself.";
|
|
2
2
|
/** Newest `.js` mtime under `dir`, or 0 when it cannot be read. */
|
|
3
3
|
export declare function newestJsMtime(dir: string): number;
|
|
4
4
|
/** A watcher over one directory, started now. Exported for tests; the daemon uses the default. */
|
|
@@ -14,7 +14,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
/** How often the walk may run. A few hundred stats, so cheap, but not on every call. */
|
|
16
16
|
const CHECK_EVERY_MS = 30_000;
|
|
17
|
-
export const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server restart`) and retry before trusting a fix to Reticle itself.";
|
|
17
|
+
export const STALE_BUILD_NOTICE = "Reticle's code on disk changed after this daemon started, so it is still running the old build. Restart it (`npx @reticlehq/server serve --restart`) and retry before trusting a fix to Reticle itself.";
|
|
18
18
|
/** `dist/` of this package: this file sits at dist/surface/tools/agent/. */
|
|
19
19
|
const DIST_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
20
20
|
/** Newest `.js` mtime under `dir`, or 0 when it cannot be read. */
|
|
@@ -8,40 +8,19 @@ import { hiddenMatchNote } from '@reticlehq/engine/evidence/already-true.js';
|
|
|
8
8
|
import { provenExpectedLinks, } from '@reticlehq/engine/question/predicate/predicate.js';
|
|
9
9
|
import { findContradictions, } from '@reticlehq/engine/disagreement/contradictions.js';
|
|
10
10
|
import { crashedRuleNotes } from '@reticlehq/engine/disagreement/contradiction-folds.js';
|
|
11
|
-
import { declaredExpectations, declaresBodyIndependentChannel, } from '@reticlehq/engine/question/declared.js';
|
|
12
|
-
import { absenceBlindSpotNote, blindSpotsFromState, buildCoverageStatement, restsOnCompleteWindow, Coverage, impeachesCapture, transportGapNote, } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
11
|
+
import { declaredExpectations, declaresBodyIndependentChannel, provedConsequences, } from '@reticlehq/engine/question/declared.js';
|
|
12
|
+
import { absenceBlindSpotNote, blindSpotsFromState, buildCoverageStatement, failingClauseReadsLostChannel, restsOnCompleteWindow, Coverage, impeachesCapture, transportGapNote, } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
13
13
|
import { buildHonestyBlock } from '@reticlehq/engine/evidence/honesty.js';
|
|
14
14
|
import { acceptedWriteLabels, hasAcceptedWrite, } from '@reticlehq/engine/evidence/accepted-write.js';
|
|
15
15
|
import { unreadWriteLabels, unreadWriteReason } from '@reticlehq/engine/evidence/unread-outcome.js';
|
|
16
|
-
import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
|
|
16
|
+
import { decideVerified, withDeclinedDialog } from '@reticlehq/engine/evidence/verified.js';
|
|
17
17
|
import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
|
|
18
18
|
import { inFlightRequestLabels, repeatedRequestLabels, } from '../act/settle-in-flight.js';
|
|
19
19
|
import { gradeOfPredicate } from './assert-grade.js';
|
|
20
20
|
import { assertSource, stampedSourceIn } from './assert-source.js';
|
|
21
21
|
import { VerdictAttribution, verdictAttributionOf } from '@reticlehq/core';
|
|
22
22
|
import { noteSourceSeen } from '../../../portal/session/recorded-gaps.js';
|
|
23
|
-
import {
|
|
24
|
-
// Unanchored: the engine prefixes the reason with "the page did not answer:".
|
|
25
|
-
const COMMAND_TIMEOUT = /command '[^']*' timed out after (\d+)ms/;
|
|
26
|
-
/**
|
|
27
|
-
* The cause and the one fix for a read that came back `unknown`, in place of a bare fact (#1990).
|
|
28
|
-
* A page whose SDK is behind this daemon may not serve `match` at all, so the fix is the same
|
|
29
|
-
* SDK-behind sentence the listing shows. On a current page the read ran out of the assertion's own
|
|
30
|
-
* budget, and the fix is more of it.
|
|
31
|
-
*/
|
|
32
|
-
function diagnoseTimeout(reason, session) {
|
|
33
|
-
const behind = sdkBehindNote(session);
|
|
34
|
-
if (behind !== undefined) {
|
|
35
|
-
return `${reason} ${behind}`;
|
|
36
|
-
}
|
|
37
|
-
const granted = COMMAND_TIMEOUT.exec(reason)?.[1];
|
|
38
|
-
if (granted === undefined) {
|
|
39
|
-
return reason;
|
|
40
|
-
}
|
|
41
|
-
return (`${reason} This read was bounded by what the assertion's budget had left ` +
|
|
42
|
-
`(${granted}ms): the budget ran out while the page was still working. ` +
|
|
43
|
-
'Give the assertion more time with `timeout_ms`.');
|
|
44
|
-
}
|
|
23
|
+
import { diagnoseTimeout } from '../act/act-observation.js';
|
|
45
24
|
/**
|
|
46
25
|
* The honesty verdict for a plain `reticle_assert`.
|
|
47
26
|
*
|
|
@@ -84,7 +63,6 @@ changeUndeclared) {
|
|
|
84
63
|
// The window's own counts, so sampling before it is not described as this window's (#1414).
|
|
85
64
|
const windowSpots = blindSpotsFromState(session.blindSpots(since), session.runtime);
|
|
86
65
|
const statement = buildCoverageStatement(spots, windowSpots);
|
|
87
|
-
const absenceBlindSpot = absenceBlindSpotNote(predicate, spots);
|
|
88
66
|
// A green resting on hidden matches says the node exists, not that it shows (#1408).
|
|
89
67
|
const hiddenMatch = hiddenMatchNote(predicate, evidence);
|
|
90
68
|
// Omitted entirely when coverage is full, so an intact page pays nothing and the field's PRESENCE
|
|
@@ -96,6 +74,8 @@ changeUndeclared) {
|
|
|
96
74
|
}
|
|
97
75
|
: {};
|
|
98
76
|
const windowEvents = await session.queryEvents({ since });
|
|
77
|
+
// The window lets a target the page was seen to remove escape the virtualized blind spot.
|
|
78
|
+
const absenceBlindSpot = absenceBlindSpotNote(predicate, spots, windowEvents);
|
|
99
79
|
// Everything before the window, passed as LEARNING material only. A scale error disagrees with a
|
|
100
80
|
// value the API stated earlier in the session, which an action-scoped window can never contain —
|
|
101
81
|
// measured live, `observe` over a wide window reported `unit-mismatch` while `assert` on the same
|
|
@@ -120,7 +100,7 @@ changeUndeclared) {
|
|
|
120
100
|
expectedFailures: declared.netFailures,
|
|
121
101
|
namedNetUrls: declared.netUrls,
|
|
122
102
|
repeatableNetUrls: declared.repeatableNetUrls,
|
|
123
|
-
|
|
103
|
+
...provedConsequences(declared, pass),
|
|
124
104
|
...(actCursor !== undefined && actCursor >= since ? { actionSince: actCursor } : {}),
|
|
125
105
|
});
|
|
126
106
|
// Only a spot that IMPEACHES the capture downgrades a general verdict. Structural boundaries are
|
|
@@ -162,10 +142,7 @@ changeUndeclared) {
|
|
|
162
142
|
const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
|
|
163
143
|
const rawInconclusive = inconclusive ?? (!pass ? session.preconditionFailure?.() : undefined);
|
|
164
144
|
// A timed-out page read is the one `unknown` whose cause and fix are knowable here (#1990).
|
|
165
|
-
|
|
166
|
-
if (rawInconclusive !== undefined && COMMAND_TIMEOUT.test(rawInconclusive)) {
|
|
167
|
-
effectiveInconclusive = diagnoseTimeout(rawInconclusive, session);
|
|
168
|
-
}
|
|
145
|
+
const effectiveInconclusive = diagnoseTimeout(rawInconclusive, session);
|
|
169
146
|
/**
|
|
170
147
|
* Does a green here rest on the window having been COMPLETE?
|
|
171
148
|
*
|
|
@@ -180,13 +157,13 @@ changeUndeclared) {
|
|
|
180
157
|
/**
|
|
181
158
|
* The buffer evicted scarce evidence from this window.
|
|
182
159
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* its cursor is the action's own.
|
|
160
|
+
* Two cases. (1) An ABSENCE claim needs a complete window; things dropped are the disproof it
|
|
161
|
+
* cannot see. (2) A FAILING positive `allOf` whose NET/SIGNAL/LOG child has no evidence left in
|
|
162
|
+
* the window — the child might have failed because the buffer dropped the request, not because
|
|
163
|
+
* the app never made it (#1231). Only the second reads the events; the first is structural.
|
|
188
164
|
*/
|
|
189
|
-
const bufferLost = session.lostSince(since) &&
|
|
165
|
+
const bufferLost = session.lostSince(since) &&
|
|
166
|
+
(restsOnComplete || (false === pass && failingClauseReadsLostChannel(predicate, windowEvents)));
|
|
190
167
|
/** The durable ledger refused writes, on a query path that read from it. Same rule, other store. */
|
|
191
168
|
const ledgerLost = ledgerClosed && restsOnComplete;
|
|
192
169
|
const decision = decideVerified({
|
|
@@ -209,6 +186,8 @@ changeUndeclared) {
|
|
|
209
186
|
...(absenceBlindSpot === undefined ? {} : { absenceBlindSpot }),
|
|
210
187
|
...(hiddenMatch === undefined ? {} : { hiddenMatch }),
|
|
211
188
|
...(namedNetIsInFlight(predicate, stillInFlight) ? { namedRequestInFlight: true } : {}),
|
|
189
|
+
...(bufferLost && false === pass ? { bufferLostEvidence: true } : {}),
|
|
190
|
+
...withDeclinedDialog(windowEvents),
|
|
212
191
|
honesty: buildHonestyBlock({
|
|
213
192
|
grade: gradeOfPredicate(predicate),
|
|
214
193
|
attribution: 'window',
|
|
@@ -2,7 +2,7 @@ import { carryReticleIdentity, LEASE_ACQUIRE_TOOL } from './lease-tools.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { navigateResult } from './act/navigation/navigate-result.js';
|
|
4
4
|
import { absoluteTarget, awaitArrival, idsOpenAtDispatch, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
|
|
5
|
-
import { reloadResult } from './act/navigation/reload-result.js';
|
|
5
|
+
import { HardReload, hardReloadOf, reloadResult } from './act/navigation/reload-result.js';
|
|
6
6
|
import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS, } from '../../portal/session/session-reconnect.js';
|
|
7
7
|
import { NO_SESSION_CONNECTED_ERROR, ReticleCommand, SESSION_STARTED_NOTE, SessionStartedVia, relativeUrlNoSession, } from '@reticlehq/core';
|
|
8
8
|
import { ReticleTool } from '@reticlehq/core';
|
|
@@ -106,7 +106,7 @@ export const BROWSER_TOOLS = [
|
|
|
106
106
|
hard: z
|
|
107
107
|
.boolean()
|
|
108
108
|
.optional()
|
|
109
|
-
.describe('With reload:true, bypass the browser cache (Cmd+Shift+R). Default: false.'),
|
|
109
|
+
.describe('With reload:true, bypass the browser cache (Cmd+Shift+R) on a leased tab. On a tab Reticle does not drive, only the document is fetched again; scripts and modules can still come from the cache. Default: false.'),
|
|
110
110
|
timeout_ms: timeoutMsSchema
|
|
111
111
|
.optional()
|
|
112
112
|
.describe('How long to wait for the page to come back (the SDK reconnecting at the new URL, or under the same id on a reload) before answering confirmed:false. Default 5000. 0 looks once without waiting. An SPA reattaching under HMR has been measured at 30-60s — say so here instead of polling reticle_sessions.'),
|
|
@@ -133,6 +133,8 @@ export const BROWSER_TOOLS = [
|
|
|
133
133
|
/** Present only when no session was connected and this call opened one on the URL. */
|
|
134
134
|
sessionStarted: SessionStartedSchema.optional(),
|
|
135
135
|
note: z.string().optional(),
|
|
136
|
+
/** On a `hard` reload: whether the browser ignored its cache, or only the document was fetched again. */
|
|
137
|
+
hardReload: z.nativeEnum(HardReload).optional(),
|
|
136
138
|
},
|
|
137
139
|
handler: async (deps, args) => {
|
|
138
140
|
// reload:true is the absorbed reticle_refresh — same command, one fewer advertised tool.
|
|
@@ -148,9 +150,13 @@ export const BROWSER_TOOLS = [
|
|
|
148
150
|
// coming back `contradicted` by hundreds of 500s against resources that were already gone.
|
|
149
151
|
before.lastAct.markNavigated(before.elapsed());
|
|
150
152
|
deps.recordings.markNavigated();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
const hard = true === args['hard'];
|
|
154
|
+
// A leased tab reloads through the browser with the cache ignored. The page itself can only
|
|
155
|
+
// re-fetch its document: scripts and modules still come from the cache (#1785).
|
|
156
|
+
const throughBrowser = hard && true === (await deps.pool?.hardReloadLease(before.id));
|
|
157
|
+
if (!throughBrowser) {
|
|
158
|
+
await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, { hard });
|
|
159
|
+
}
|
|
154
160
|
// WAIT for the page to come back, rather than telling the agent to. The id survives the
|
|
155
161
|
// reload, but the seconds between dispatch and the new HELLO are seconds in which every call
|
|
156
162
|
// lands in the old, disconnected session — measured as reticle_run failing 5 of 5 on a page
|
|
@@ -165,7 +171,7 @@ export const BROWSER_TOOLS = [
|
|
|
165
171
|
// Not a bare `{ ok: true }`. The URL branch below already discloses that `ok` means
|
|
166
172
|
// DISPATCHED — the reload branch had identical semantics and said nothing, on the path most
|
|
167
173
|
// likely to need it. See reload-result.
|
|
168
|
-
return reloadResult(back, timeoutMs);
|
|
174
|
+
return reloadResult(back, timeoutMs, hardReloadOf(hard, throughBrowser));
|
|
169
175
|
}
|
|
170
176
|
const requested = asString(args['url']);
|
|
171
177
|
if (requested === undefined || 0 === requested.length) {
|