@reticlehq/server 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +6 -2
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +24 -4
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +64 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.d.ts +1 -1
- package/dist/command/cli/cloud-login.js +27 -8
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +21 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +79 -11
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
- package/dist/command/setup/bringup/dev-server-wait.js +1 -1
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +91 -9
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +86 -45
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +28 -1
- package/dist/command/setup/run-setup.js +103 -15
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +17 -8
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +10 -0
- package/dist/features/harness/fill-values.js +50 -11
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.d.ts +12 -0
- package/dist/features/harness/openai-driver.js +21 -12
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/features/license/license.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -19
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +77 -3
- package/dist/memory/cloud/sync-cycle.js +325 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/intent/intent-tools.js +8 -3
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
- package/dist/memory/project/artifact-root-resolver.js +46 -19
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +12 -3
- package/dist/portal/bridge/bridge.js +49 -3
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/input/viewport-tools.js +16 -6
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +10 -8
- package/dist/portal/session/session.js +24 -32
- package/dist/surface/http-server.d.ts +3 -2
- package/dist/surface/http-server.js +8 -5
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +28 -52
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/error-recovery.js +5 -6
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +12 -7
- package/dist/surface/tools/invoke-tool.js +12 -8
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tool-kit.d.ts +10 -0
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +1 -5
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -4
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +20 -8
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +22 -7
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +15 -5
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +2 -2
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +4 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +12 -10
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -27,7 +27,9 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
27
27
|
const total = Math.max(plan.planned, steps.length);
|
|
28
28
|
const declaring = steps.filter((step) => step.declared);
|
|
29
29
|
const declared = declaring.length;
|
|
30
|
-
|
|
30
|
+
// `held: false` on an undeclared step means it could not be performed at all — the journey broke
|
|
31
|
+
// there whether or not anything was declared about it.
|
|
32
|
+
const missed = steps.find((step) => (step.declared ? true !== step.held : false === step.held));
|
|
31
33
|
// A miss decides the grade before coverage does. How much of the plan declared nothing is
|
|
32
34
|
// context on a red, never a softening of it.
|
|
33
35
|
if (missed !== undefined) {
|
|
@@ -39,6 +41,17 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
39
41
|
because: `a declared consequence did not hold — observed ${observed}`,
|
|
40
42
|
};
|
|
41
43
|
}
|
|
44
|
+
// Same rule as a single action: an observed contradiction outranks a consequence that held.
|
|
45
|
+
const contradictedAt = steps.findIndex((step) => (step.contradicted ?? []).length > 0);
|
|
46
|
+
if (contradictedAt >= 0) {
|
|
47
|
+
const kinds = (steps[contradictedAt]?.contradicted ?? []).join(', ');
|
|
48
|
+
return {
|
|
49
|
+
verified: Verified.NO,
|
|
50
|
+
declared,
|
|
51
|
+
total,
|
|
52
|
+
because: `step ${String(contradictedAt + 1)}: channels disagree about this action (${kinds}) even though it went on`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
42
55
|
/*
|
|
43
56
|
* `unknown` rather than `no-fault`, and the distinction is load-bearing.
|
|
44
57
|
*
|
|
@@ -68,6 +81,24 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
68
81
|
: `nothing was dispatched, so none of the ${String(total)} step(s) ran — read the per-step \`error\` for the one that was refused, and fix that before adding an \`expect\``,
|
|
69
82
|
};
|
|
70
83
|
}
|
|
84
|
+
// A journey is proved at its END. Declaring only early steps checks that it started, and a yes
|
|
85
|
+
// there was the one a single act_and_wait on the last step would never have given.
|
|
86
|
+
if (steps.length < total) {
|
|
87
|
+
return {
|
|
88
|
+
verified: Verified.UNKNOWN,
|
|
89
|
+
declared,
|
|
90
|
+
total,
|
|
91
|
+
because: `only ${String(steps.length)} of ${String(total)} steps ran, so the journey never reached its end`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (true !== steps.at(-1)?.declared) {
|
|
95
|
+
return {
|
|
96
|
+
verified: Verified.UNKNOWN,
|
|
97
|
+
declared,
|
|
98
|
+
total,
|
|
99
|
+
because: `the last step, where the journey ends, declared nothing — give it an \`expect\` naming the end state (${String(declared)} of ${String(total)} steps declared one)`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
71
102
|
return {
|
|
72
103
|
verified: Verified.YES,
|
|
73
104
|
declared,
|
|
@@ -28,6 +28,13 @@ interface CapsuleSaveInputs {
|
|
|
28
28
|
*/
|
|
29
29
|
root?: string | undefined;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* What the act claimed would happen, in words: `route /settings`, `POST /v1/pay → 200`,
|
|
33
|
+
* `element "Issues"`. Read off the predicate the agent declared, because the causal links only name
|
|
34
|
+
* signals and requests: a failed route, element or text claim used to be filed as "declared
|
|
35
|
+
* consequence", which tells whoever reads the capsule nothing about what was expected.
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeExpected(predicate: unknown): string | undefined;
|
|
31
38
|
/** Returns the capsule id when one was written, or undefined when there was nothing to save. */
|
|
32
39
|
export declare function saveFailedAssertCapsule(inputs: CapsuleSaveInputs): Promise<string | undefined>;
|
|
33
40
|
export {};
|
|
@@ -14,6 +14,50 @@ import { CapsuleStore, capsuleFingerprint, capsuleId, CAPSULE_VERSION, } from '.
|
|
|
14
14
|
import { ReticleTool } from '@reticlehq/core';
|
|
15
15
|
import { asRecord, asString } from '@reticlehq/core';
|
|
16
16
|
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
17
|
+
/**
|
|
18
|
+
* What the act claimed would happen, in words: `route /settings`, `POST /v1/pay → 200`,
|
|
19
|
+
* `element "Issues"`. Read off the predicate the agent declared, because the causal links only name
|
|
20
|
+
* signals and requests: a failed route, element or text claim used to be filed as "declared
|
|
21
|
+
* consequence", which tells whoever reads the capsule nothing about what was expected.
|
|
22
|
+
*/
|
|
23
|
+
export function describeExpected(predicate) {
|
|
24
|
+
const p = asRecord(predicate);
|
|
25
|
+
const kind = asString(p['kind']);
|
|
26
|
+
if (kind === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
const all = (key, joiner) => {
|
|
29
|
+
const parts = (Array.isArray(p[key]) ? p[key] : [])
|
|
30
|
+
.map(describeExpected)
|
|
31
|
+
.filter((d) => d !== undefined);
|
|
32
|
+
return 0 === parts.length ? undefined : parts.join(joiner);
|
|
33
|
+
};
|
|
34
|
+
const q = asRecord(p['query']);
|
|
35
|
+
const named = asString(p['name']) ?? asString(q['name']) ?? asString(q['value']) ?? asString(q['testid']);
|
|
36
|
+
switch (kind) {
|
|
37
|
+
case 'route':
|
|
38
|
+
return `route ${asString(p['pathname']) ?? asString(p['contains']) ?? '?'}`;
|
|
39
|
+
case 'net': {
|
|
40
|
+
const status = 'number' === typeof p['status'] ? ` → ${String(p['status'])}` : '';
|
|
41
|
+
return `${asString(p['method']) ?? 'a'} ${asString(p['urlContains']) ?? 'request'}${status}`;
|
|
42
|
+
}
|
|
43
|
+
case 'signal':
|
|
44
|
+
return `signal ${asString(p['name']) ?? '?'}`;
|
|
45
|
+
case 'element':
|
|
46
|
+
return `${true === p['absent'] ? 'no ' : ''}element ${named === undefined ? '?' : `"${named}"`}`;
|
|
47
|
+
case 'text':
|
|
48
|
+
return `${true === p['absent'] ? 'no ' : ''}text "${asString(p['contains']) ?? '?'}"`;
|
|
49
|
+
case 'allOf':
|
|
50
|
+
return all('predicates', ' AND ');
|
|
51
|
+
case 'anyOf':
|
|
52
|
+
return all('predicates', ' OR ');
|
|
53
|
+
case 'not': {
|
|
54
|
+
const inner = describeExpected(p['predicate']);
|
|
55
|
+
return inner === undefined ? undefined : `not ${inner}`;
|
|
56
|
+
}
|
|
57
|
+
default:
|
|
58
|
+
return kind;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
17
61
|
/** Returns the capsule id when one was written, or undefined when there was nothing to save. */
|
|
18
62
|
export async function saveFailedAssertCapsule(inputs) {
|
|
19
63
|
const { deps, verdict, capsule, links, args, actResult, actedSource, root: given } = inputs;
|
|
@@ -47,7 +91,8 @@ export async function saveFailedAssertCapsule(inputs) {
|
|
|
47
91
|
const body = {
|
|
48
92
|
version: CAPSULE_VERSION,
|
|
49
93
|
origin: 'failed-assert',
|
|
50
|
-
expected:
|
|
94
|
+
expected: describeExpected(args['until'] ?? args['predicate']) ??
|
|
95
|
+
(expectedText.length > 0 ? expectedText : 'declared consequence'),
|
|
51
96
|
observed: capsule.firstDivergence?.observed ?? verdict.failureReason ?? 'not observed',
|
|
52
97
|
steps: [
|
|
53
98
|
{
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
import { timeoutMsSchema } from './args/numeric-bounds.js';
|
|
14
|
-
import { compileSequenceStep } from '../../language/flows/replay.js';
|
|
14
|
+
import { compileSequenceStep, pathOf } from '../../language/flows/replay.js';
|
|
15
15
|
import { sequenceStepArgs } from './act/act-preflight.js';
|
|
16
16
|
import { ReticleTool } from '@reticlehq/core';
|
|
17
17
|
import { healthEnvelope } from '../../portal/session/session-health.js';
|
|
18
18
|
import { pausedShortCircuit, pausedOutputShape, withControl, } from '../../portal/session/control-envelope.js';
|
|
19
|
-
import { asRecord } from '@reticlehq/core';
|
|
19
|
+
import { asRecord, isAbsenceDerived, isAdvisory } from '@reticlehq/core';
|
|
20
20
|
import { sessionIdFromArgs } from './tools-helpers.js';
|
|
21
21
|
import { describeStepResult, runStepWithStaleRetry } from './act/act-sequence-retry.js';
|
|
22
22
|
import { assertSequenceSteps } from './act/act-preflight.js';
|
|
@@ -59,10 +59,9 @@ function effectOf(session, since) {
|
|
|
59
59
|
const BURST_MAX_GAP_MS = 500;
|
|
60
60
|
export const ACT_SEQUENCE_TOOL = {
|
|
61
61
|
name: ReticleTool.ACT_SEQUENCE,
|
|
62
|
-
// The example is required for a core tool, and this one carries weight: the
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// exact shape an agent otherwise spends three round trips on.
|
|
62
|
+
// The example is required for a core tool, and this one carries weight: the loop it replaces is
|
|
63
|
+
// a login form driven as three separate reticle_act calls. Showing fill -> fill -> click is showing
|
|
64
|
+
// the exact shape an agent otherwise spends three round trips on.
|
|
66
65
|
example: {
|
|
67
66
|
steps: [
|
|
68
67
|
{ ref: 'e12', action: 'fill', args: { value: 'a@b.com' } },
|
|
@@ -133,6 +132,7 @@ export const ACT_SEQUENCE_TOOL = {
|
|
|
133
132
|
if (paused !== undefined)
|
|
134
133
|
return paused;
|
|
135
134
|
const since = session.elapsed();
|
|
135
|
+
const pageBefore = pathOf(session.url);
|
|
136
136
|
session.beginAction(ReticleTool.ACT_SEQUENCE, asRecord(args));
|
|
137
137
|
try {
|
|
138
138
|
const inputSteps = Array.isArray(args['steps']) ? args['steps'] : [];
|
|
@@ -230,8 +230,12 @@ export const ACT_SEQUENCE_TOOL = {
|
|
|
230
230
|
* satisfy step four's assertion, which is a false green built out of correct parts.
|
|
231
231
|
*/
|
|
232
232
|
const parsed = PredicateSchema.safeParse(step['expect']);
|
|
233
|
+
const contradicted = (described.contradictions ?? [])
|
|
234
|
+
.map((c) => c.kind)
|
|
235
|
+
.filter((kind) => !isAdvisory(kind) && !isAbsenceDerived(kind));
|
|
236
|
+
const disagreement = 0 === contradicted.length ? {} : { contradicted };
|
|
233
237
|
if (!parsed.success) {
|
|
234
|
-
expectations.push({ declared: false });
|
|
238
|
+
expectations.push({ declared: false, ...disagreement });
|
|
235
239
|
stepResults.push(described);
|
|
236
240
|
}
|
|
237
241
|
else {
|
|
@@ -240,6 +244,7 @@ export const ACT_SEQUENCE_TOOL = {
|
|
|
240
244
|
expectations.push({
|
|
241
245
|
declared: true,
|
|
242
246
|
held,
|
|
247
|
+
...disagreement,
|
|
243
248
|
...(verdict.observed === undefined ? {} : { observed: verdict.observed }),
|
|
244
249
|
...(verdict.expected === undefined ? {} : { expected: verdict.expected }),
|
|
245
250
|
});
|
|
@@ -287,7 +292,19 @@ export const ACT_SEQUENCE_TOOL = {
|
|
|
287
292
|
// Same as captureAct: no `active()` gate, so a sequence driven with nothing open still lands
|
|
288
293
|
// in the ambient tape. A stalled plan is still excluded — those steps never ran.
|
|
289
294
|
if (stalledAt === undefined) {
|
|
290
|
-
|
|
295
|
+
// A sub-step keeps its `expect` only if it held — same rule as act_and_wait, so a flow
|
|
296
|
+
// never replays an expectation that was never once observed to hold.
|
|
297
|
+
const proved = inputSteps.map((raw, i) => {
|
|
298
|
+
const step = asRecord(raw);
|
|
299
|
+
const { expect: _expect, ...rest } = step;
|
|
300
|
+
return true === expectations[i]?.held ? step : rest;
|
|
301
|
+
});
|
|
302
|
+
const recorded = compileSequenceStep({ ...args, steps: proved }, {
|
|
303
|
+
count: inputSteps.length,
|
|
304
|
+
steps: stepResults,
|
|
305
|
+
});
|
|
306
|
+
deps.recordings.capture(pageBefore === undefined ? recorded : { ...recorded, page: pageBefore });
|
|
307
|
+
deps.recordings.markEnded(pathOf(session.url));
|
|
291
308
|
}
|
|
292
309
|
/*
|
|
293
310
|
* The grade, and the coverage it is never allowed to hide.
|
|
@@ -3,23 +3,8 @@ import { aliasParam } from './args/alias-args.js';
|
|
|
3
3
|
import { resolveSessionWithin } from '../../portal/session/timing/resolve-within.js';
|
|
4
4
|
import { WALL_CLOCK } from '../../portal/session/timing/wall-clock.js';
|
|
5
5
|
import { timeoutMsSchema } from './args/numeric-bounds.js';
|
|
6
|
-
import { captureAct } from '../../language/flows/replay.js';
|
|
7
|
-
|
|
8
|
-
* The pathname of a session's live url, or undefined when it has none yet.
|
|
9
|
-
*
|
|
10
|
-
* Pathname rather than the whole url, because that is what `startPath` is compared against and what
|
|
11
|
-
* a hash-routed app makes meaningless in the document location — see routeOfEvent.
|
|
12
|
-
*/
|
|
13
|
-
function pathOf(url) {
|
|
14
|
-
if (url === undefined)
|
|
15
|
-
return undefined;
|
|
16
|
-
try {
|
|
17
|
-
return new URL(url).pathname;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
6
|
+
import { captureAct, captureVerdictedAct, pathOf } from '../../language/flows/replay.js';
|
|
7
|
+
import { durableArg, durableOutput, reloadAndRecheck, withDurability } from './act/durable.js';
|
|
23
8
|
import { ActionType, ActionWarning, ReticleCommand, CaptureLoss, DEFAULT_ASSERT_TIMEOUT_MS, InputMode, Verified, VerifiedReason, PredicateKind, } from '@reticlehq/core';
|
|
24
9
|
import { leanActResult, mutatedWithin } from './act/act-view.js';
|
|
25
10
|
import { ReticleTool } from '@reticlehq/core';
|
|
@@ -37,8 +22,9 @@ import { intentDebt, openSessionIntents } from '../../memory/intent/open-intents
|
|
|
37
22
|
import { dischargeInlineIntent, inlineVerdictId, linkInlineIntent, } from '../../memory/intent/inline-intent.js';
|
|
38
23
|
import { declaresState } from '@reticlehq/engine/question/predicate/predicate-asks.js';
|
|
39
24
|
import { isStateUnwatched } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
40
|
-
import { inFlightRequestLabels, repeatedRequestLabels
|
|
41
|
-
import {
|
|
25
|
+
import { inFlightRequestLabels, repeatedRequestLabels } from './act/settle-in-flight.js';
|
|
26
|
+
import { finishAfterMatch } from './act/after-match.js';
|
|
27
|
+
import { noteProved } from './act/proved-controls.js';
|
|
42
28
|
import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
|
|
43
29
|
import { honestyForVerdict } from '@reticlehq/engine/evidence/honesty.js';
|
|
44
30
|
import { declaredExpectations, declaresBodyIndependentChannel, } from '@reticlehq/engine/question/declared.js';
|
|
@@ -76,8 +62,8 @@ import { readAlreadyTrue } from './act/already-true.js';
|
|
|
76
62
|
/**
|
|
77
63
|
* The action vocabulary, derived from ActionType — never retyped.
|
|
78
64
|
*
|
|
79
|
-
* The description used to
|
|
80
|
-
*
|
|
65
|
+
* The description used to omit callable actions such as blur, upload and drag,
|
|
66
|
+
* because a hand-copied list drifts the moment
|
|
81
67
|
* someone adds an arm. Deriving both the schema and the prose from the enum makes that impossible.
|
|
82
68
|
*
|
|
83
69
|
* The handler already refused an unknown action with a good message; the schema now refuses it
|
|
@@ -254,6 +240,7 @@ export const ACT_TOOLS = [
|
|
|
254
240
|
});
|
|
255
241
|
}
|
|
256
242
|
finally {
|
|
243
|
+
deps.recordings.markEnded(pathOf(session.url));
|
|
257
244
|
// Close the window on every exit (settle or throw), recording the action + settle outcome.
|
|
258
245
|
session.finishAction(undefined, settledOutcome, true === settledOutcome ? session.elapsed() - since : undefined);
|
|
259
246
|
}
|
|
@@ -286,7 +273,7 @@ export const ACT_TOOLS = [
|
|
|
286
273
|
.record(z.unknown())
|
|
287
274
|
.optional()
|
|
288
275
|
.describe('Action-specific arguments: { value } for fill/select, { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt), { 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.'),
|
|
289
|
-
predicate: PredicateSchema.optional().describe('Alias for `until
|
|
276
|
+
predicate: PredicateSchema.optional().describe('Alias for `until`.'),
|
|
290
277
|
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" }] }.'),
|
|
291
278
|
timeout_ms: timeoutMsSchema
|
|
292
279
|
.optional()
|
|
@@ -296,9 +283,11 @@ export const ACT_TOOLS = [
|
|
|
296
283
|
.optional()
|
|
297
284
|
.describe('Throw if the tab is throttled. Default: false.'),
|
|
298
285
|
intent: intentArg,
|
|
286
|
+
durable: durableArg,
|
|
299
287
|
...sessionIdShape,
|
|
300
288
|
},
|
|
301
289
|
outputSchema: {
|
|
290
|
+
durable: durableOutput,
|
|
302
291
|
effect: z
|
|
303
292
|
.unknown()
|
|
304
293
|
.describe('The reticle_act result (dispatched, settled, inputMode, etc.).'),
|
|
@@ -433,6 +422,9 @@ export const ACT_TOOLS = [
|
|
|
433
422
|
const { alreadyTrue, alreadyTrueHiddenMatch, alreadyTrueEvidence, baselines } = await readAlreadyTrue(session, until, since);
|
|
434
423
|
// Same as the ACT handler: the route this step RAN on, before the action can move the app.
|
|
435
424
|
const routeBeforeWait = pathOf(session.url);
|
|
425
|
+
// Recorded in `finally`, once the verdict is known — see captureVerdictedAct.
|
|
426
|
+
let dispatched;
|
|
427
|
+
let recordedVerdict;
|
|
436
428
|
try {
|
|
437
429
|
// actCommand is the single interception point for upload+path rewrite.
|
|
438
430
|
//
|
|
@@ -450,7 +442,7 @@ export const ACT_TOOLS = [
|
|
|
450
442
|
if (actResult !== null) {
|
|
451
443
|
if (!actResult.ok)
|
|
452
444
|
throw new Error(actResult.error ?? 'act failed');
|
|
453
|
-
|
|
445
|
+
dispatched = { result: actResult.result };
|
|
454
446
|
// Dispatched — now this act owns the cursor and the effect. Marking its OWN measurement
|
|
455
447
|
// also stops the spread below from inheriting an earlier reticle_act's action and
|
|
456
448
|
// mutation count.
|
|
@@ -476,34 +468,12 @@ export const ACT_TOOLS = [
|
|
|
476
468
|
since = 0;
|
|
477
469
|
session = followed.session;
|
|
478
470
|
verdict = followed.verdict;
|
|
479
|
-
//
|
|
480
|
-
// while the write is still in flight — so the verdict was taken over a window the app had not
|
|
481
|
-
// finished, and came back `unknown / unsettled` asking the CALLER to re-check. A login that
|
|
482
|
-
// genuinely succeeded returned at 492ms of an 8000ms budget with every corroborating channel
|
|
483
|
-
// agreeing. Spend the rest of the budget the caller already granted.
|
|
484
|
-
//
|
|
485
|
-
// This decides nothing: it waits for the answer instead of guessing at it. A response that
|
|
486
|
-
// arrives as a FAILURE still contradicts — and now arrives INSIDE the window, where the
|
|
487
|
-
// detector can see it at all — while a request still open when the budget genuinely runs out
|
|
488
|
-
// reports unsettled exactly as before, an honest limit rather than an early exit. Costs
|
|
489
|
-
// nothing on the common path, where no request is in flight.
|
|
471
|
+
// After a match, let the app finish before the window closes — see act/after-match.ts.
|
|
490
472
|
if (verdict.pass && timeout > 0) {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
await waitForInFlight(session, since, spent(), sleep);
|
|
496
|
-
// Waiting for the response moved the hazard rather than removing it: the response now lands
|
|
497
|
-
// inside the window BY DESIGN, and the app's re-render happens a task or two later. Close
|
|
498
|
-
// the window in that gap and every channel agrees the app took a successful write and did
|
|
499
|
-
// nothing — `response-ignored`, i.e. `verified:"no"` on a correct app, produced entirely by
|
|
500
|
-
// where we stopped looking. A false accusation is the more damaging direction of error for
|
|
501
|
-
// a verification tool: it sends someone to fix code that is not broken.
|
|
502
|
-
//
|
|
503
|
-
// So the response is not the end of the window; the app's REACTION to it is. Paid only in
|
|
504
|
-
// the shape that would otherwise be accused — a successful mutating write with nothing
|
|
505
|
-
// moved after it — and short enough that a genuinely dropped response is still reported.
|
|
506
|
-
await waitForReaction(session, since, spent(), sleep);
|
|
473
|
+
const left = () => timeout - (session.elapsed() - predicateStarted);
|
|
474
|
+
const reverted = await finishAfterMatch(session, until, since, baselines, left);
|
|
475
|
+
if (reverted !== undefined)
|
|
476
|
+
verdict = { pass: false, failureReason: reverted, observed: reverted };
|
|
507
477
|
}
|
|
508
478
|
const r = asRecord(actResult?.result);
|
|
509
479
|
if ('boolean' === typeof r['settled'])
|
|
@@ -650,7 +620,7 @@ export const ACT_TOOLS = [
|
|
|
650
620
|
const outcomePending = acceptedWriteLabels(windowEvents);
|
|
651
621
|
const outcomeUnread = unreadWriteLabels(windowEvents);
|
|
652
622
|
const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
|
|
653
|
-
const decision = decideVerified({
|
|
623
|
+
const { decision, durable } = await withDurability(args['durable'], until, decideVerified({
|
|
654
624
|
pass: verdict.pass,
|
|
655
625
|
// Threaded rather than looked up: decideVerified is pure and has no session.
|
|
656
626
|
...sessionVerdictFacts(session),
|
|
@@ -690,7 +660,9 @@ export const ACT_TOOLS = [
|
|
|
690
660
|
repeated: repeatedRequestLabels(windowEvents),
|
|
691
661
|
},
|
|
692
662
|
...(namedNetIsInFlight(until, stillInFlight) ? { namedRequestInFlight: true } : {}),
|
|
693
|
-
});
|
|
663
|
+
}), (part) => reloadAndRecheck(deps, session, part, timeout));
|
|
664
|
+
recordedVerdict = String(decision.verified);
|
|
665
|
+
noteProved(session, recordedVerdict, actResult?.result);
|
|
694
666
|
// Computed once: the verdict block reports it, and the instrumentation gaps are a second
|
|
695
667
|
// reading of the same evidence rather than a new observation.
|
|
696
668
|
const actionSummary = causalSummary(windowEvents, {
|
|
@@ -818,6 +790,7 @@ export const ACT_TOOLS = [
|
|
|
818
790
|
// agent cannot miss it by not asking. Omitted entirely when clean, so a healthy action
|
|
819
791
|
// pays nothing.
|
|
820
792
|
...(contradictions.length > 0 ? { contradictions } : {}),
|
|
793
|
+
...(durable === undefined ? {} : { durable }),
|
|
821
794
|
honesty: honestyForVerdict(String(decision.verified), honesty),
|
|
822
795
|
// Without its summary: it is byte-identical to `summary` two fields up. See wireCapsule.
|
|
823
796
|
...(capsule === undefined ? {} : { capsule: wireCapsule(capsule) }),
|
|
@@ -828,6 +801,9 @@ export const ACT_TOOLS = [
|
|
|
828
801
|
});
|
|
829
802
|
}
|
|
830
803
|
finally {
|
|
804
|
+
if (dispatched !== undefined)
|
|
805
|
+
captureVerdictedAct(deps.recordings, args, dispatched.result, routeBeforeWait, recordedVerdict);
|
|
806
|
+
deps.recordings.markEnded(pathOf(currentOf(deps.sessions, session).url));
|
|
831
807
|
acted.finishAction(verdictEffect, settledOutcome, true === settledOutcome ? acted.elapsed() - actedSince : undefined);
|
|
832
808
|
}
|
|
833
809
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Pure: no IO, no clock.
|
|
12
12
|
*/
|
|
13
|
-
import { isConsequenceKind, isPresenceKind, PredicateKind } from '@reticlehq/core';
|
|
13
|
+
import { compareSourceClauses, isConsequenceKind, isPresenceKind, PredicateKind, } from '@reticlehq/core';
|
|
14
14
|
import { HonestyGrade, strongerGrade, weakerGrade } from '@reticlehq/engine/evidence/honesty.js';
|
|
15
15
|
export const PRESENCE_ONLY_ADVICE = 'This predicate only checks element/text presence, not an observable consequence. A locator healed to the wrong element (or a stale render) can satisfy it while the feature is broken. Prefer a { signal } or { net } assertion (or allOf it with one) so green means the feature actually worked.';
|
|
16
16
|
function walk(predicate, negated = false) {
|
|
@@ -27,6 +27,14 @@ function walk(predicate, negated = false) {
|
|
|
27
27
|
// never matched anything in the first place. Negation makes a presence check weaker, not stronger.
|
|
28
28
|
if (PredicateKind.NOT === predicate.kind)
|
|
29
29
|
return walk(predicate.predicate, true);
|
|
30
|
+
// A comparison grades as what its sides read: with a net/signal/state side it relates the page to
|
|
31
|
+
// the app's own record, which a wrong locator cannot fake; text against text is two readings of
|
|
32
|
+
// the same DOM. Its text side is always a content check, so it never counts as bare presence
|
|
33
|
+
// unless negated.
|
|
34
|
+
if (PredicateKind.COMPARE === predicate.kind) {
|
|
35
|
+
const consequence = compareSourceClauses(predicate).some((c) => isConsequenceKind(c.kind));
|
|
36
|
+
return { consequence, presence: !consequence && negated };
|
|
37
|
+
}
|
|
30
38
|
// Leaf: classify against the single source of truth in core. Kinds that are neither consequence
|
|
31
39
|
// nor presence (route/console/settled/animation) correctly return false for both.
|
|
32
40
|
return {
|
|
@@ -109,6 +117,9 @@ export function gradeOfPredicate(predicate) {
|
|
|
109
117
|
// verdict that proved only presence report `signal`, and a `minGrade: net` gate would trust it.
|
|
110
118
|
case PredicateKind.ANY_OF:
|
|
111
119
|
return combine(predicate.predicates, weakerGrade);
|
|
120
|
+
// Both sides had to be read and agree, so it proves as much as its stronger side.
|
|
121
|
+
case PredicateKind.COMPARE:
|
|
122
|
+
return combine(compareSourceClauses(predicate), strongerGrade);
|
|
112
123
|
// Everything else, `not` included: an absence claim is satisfied trivially by a locator that
|
|
113
124
|
// never matched anything, which is the argument `walk` already makes about negation.
|
|
114
125
|
default:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CaptureLoss,
|
|
1
|
+
import { CaptureLoss, channelsReadBy, PredicateKind } from '@reticlehq/core';
|
|
2
2
|
import { sessionVerdictFacts } from '../../../portal/session/session-verdict-facts.js';
|
|
3
3
|
import { gapsForAction } from '@reticlehq/engine/evidence/instrumentation-gaps.js';
|
|
4
4
|
import { noteSessionGaps } from '@reticlehq/engine/evidence/gap-ledger.js';
|
|
@@ -148,7 +148,7 @@ changeUndeclared) {
|
|
|
148
148
|
// first clause, and one this implementation could not run until the page started declaring.
|
|
149
149
|
// Both halves are conditional on purpose: an SDK too old to declare sends nothing, and
|
|
150
150
|
// treating that silence as an empty set would refuse every claim from every older page.
|
|
151
|
-
channelsRead:
|
|
151
|
+
channelsRead: channelsReadBy(predicate),
|
|
152
152
|
...(session.channels === undefined ? {} : { channelsObservable: session.channels }),
|
|
153
153
|
// Threaded rather than looked up: decideVerified is pure and has no session.
|
|
154
154
|
...sessionVerdictFacts(session),
|
|
@@ -13,7 +13,7 @@ export const BROWSER_TOOLS = [
|
|
|
13
13
|
{
|
|
14
14
|
name: ReticleTool.NAVIGATE,
|
|
15
15
|
example: { url: '/settings' },
|
|
16
|
-
description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the page
|
|
16
|
+
description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the old page cannot report on it. The daemon then waits up to `timeout_ms` (default 5000) for the SDK to reconnect. `confirmed:true` with a `sessionId` means the requested page arrived and you can act immediately. `confirmed:false` with `landedOn` means the navigation arrived but the app landed at another URL, such as an authentication or route-guard redirect; use `landedOn` to understand where the browser actually ended up. `confirmed:false` without `landedOn` means the target was not observed within the wait window; a slow app may still be on its way, so increase `timeout_ms` when appropriate.',
|
|
17
17
|
inputSchema: {
|
|
18
18
|
url: z.string().optional().describe('The URL to navigate to. Omit when using reload.'),
|
|
19
19
|
reload: z
|
|
@@ -38,6 +38,10 @@ export const BROWSER_TOOLS = [
|
|
|
38
38
|
confirmed: z.boolean().optional(),
|
|
39
39
|
/** The session the SDK reconnected as, when arrival was confirmed — it is a NEW id. */
|
|
40
40
|
sessionId: z.string().optional(),
|
|
41
|
+
/**
|
|
42
|
+
* The URL where the navigated document landed when it differs from the requested target.
|
|
43
|
+
*/
|
|
44
|
+
landedOn: z.string().optional(),
|
|
41
45
|
/**
|
|
42
46
|
* On confirmed:false — the budget (ms) that expired without the page coming back. Says which
|
|
43
47
|
* of "Reticle stopped waiting" and "the page never came back" you are looking at.
|
|
@@ -58,6 +62,7 @@ export const BROWSER_TOOLS = [
|
|
|
58
62
|
// back past this point. Reported from the field as an assertion whose clauses all passed
|
|
59
63
|
// coming back `contradicted` by hundreds of 500s against resources that were already gone.
|
|
60
64
|
before.lastAct.markNavigated(before.elapsed());
|
|
65
|
+
deps.recordings.markNavigated();
|
|
61
66
|
await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
|
|
62
67
|
hard: true === args['hard'],
|
|
63
68
|
});
|
|
@@ -96,6 +101,7 @@ export const BROWSER_TOOLS = [
|
|
|
96
101
|
// document just as thoroughly. `beginAction` attributes events to this action; it does not move
|
|
97
102
|
// the window a later assert judges over, and those are two different jobs.
|
|
98
103
|
session.lastAct.markNavigated(session.elapsed());
|
|
104
|
+
deps.recordings.markNavigated();
|
|
99
105
|
try {
|
|
100
106
|
const result = (await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.NAVIGATE, { url }));
|
|
101
107
|
// `ok` is the browser accepting the instruction, not the page arriving — see navigate-result.
|
|
@@ -104,7 +110,13 @@ export const BROWSER_TOOLS = [
|
|
|
104
110
|
// reticle_sessions itself. A refusal skips the wait: there is nothing to wait FOR.
|
|
105
111
|
const timeoutMs = asNumber(args['timeout_ms']) ?? ARRIVAL_TIMEOUT_MS;
|
|
106
112
|
const arrival = true === result.ok
|
|
107
|
-
? await awaitArrival(deps.sessions, target, {
|
|
113
|
+
? await awaitArrival(deps.sessions, target, {
|
|
114
|
+
// Keep the original Session object and URL so arrival detection can distinguish
|
|
115
|
+
// an in-progress navigation from a document that arrived somewhere else.
|
|
116
|
+
navigatedSession: session,
|
|
117
|
+
navigatedFrom: session.url,
|
|
118
|
+
priorIds,
|
|
119
|
+
}, timeoutMs)
|
|
108
120
|
: null;
|
|
109
121
|
return navigateResult(result, arrival, timeoutMs);
|
|
110
122
|
}
|
|
@@ -10,6 +10,7 @@ import { ReticleTool } from '@reticlehq/core';
|
|
|
10
10
|
import { asString } from '@reticlehq/core';
|
|
11
11
|
import { exercisedCount } from './gaps/coverage-identity.js';
|
|
12
12
|
import { commandOrThrow, sessionIdShape } from './tool-kit.js';
|
|
13
|
+
import { foldAppCoverage } from '../../features/exhaust/app-coverage.js';
|
|
13
14
|
/**
|
|
14
15
|
* `reticle_coverage` — which interactive controls this session has driven, and which it has not.
|
|
15
16
|
*
|
|
@@ -69,8 +70,17 @@ export function buildCoverageTools(toolNames) {
|
|
|
69
70
|
name: ReticleTool.COVERAGE,
|
|
70
71
|
description: 'Which interactive controls you have driven this session, and which you have NOT. Returns { total, exercised, untouched:[{ref,label}], alsoDroveGone? } over the controls currently on the page. Use it to decide whether verification is finished: an untouched list that still holds the controls your change affects means you are not done. This is about what you did not TOUCH — distinct from the `coverage` field on an action result, which reports what the layer could not SEE.',
|
|
71
72
|
example: {},
|
|
72
|
-
inputSchema: {
|
|
73
|
+
inputSchema: {
|
|
74
|
+
app: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Also fold into the app-wide ledger; returns `app.levels`.'),
|
|
78
|
+
...sessionIdShape,
|
|
79
|
+
},
|
|
73
80
|
outputSchema: {
|
|
81
|
+
// The app-wide ledger — see features/exhaust/ledger.ts. Loose: run-only, and the levels
|
|
82
|
+
// carry their own shape.
|
|
83
|
+
app: z.record(z.unknown()).optional(),
|
|
74
84
|
total: z.number(),
|
|
75
85
|
exercised: z.number(),
|
|
76
86
|
untouched: z.array(z.object({ ref: z.string(), label: z.string() })),
|
|
@@ -171,6 +181,22 @@ export function buildCoverageTools(toolNames) {
|
|
|
171
181
|
...(regressed === undefined ? {} : { observabilityRegressed: regressed }),
|
|
172
182
|
featureUse,
|
|
173
183
|
toolHitRate,
|
|
184
|
+
...(true === args['app']
|
|
185
|
+
? {
|
|
186
|
+
app: await foldAppCoverage({
|
|
187
|
+
fs: deps.fs,
|
|
188
|
+
reticleRoot: sessionRoot(deps, sessionId),
|
|
189
|
+
seen: parseControls(tree).map((c) => c.label),
|
|
190
|
+
session,
|
|
191
|
+
now: deps.now,
|
|
192
|
+
...(deps.realInput?.takeCodeCoverage === undefined
|
|
193
|
+
? {}
|
|
194
|
+
: {
|
|
195
|
+
takeCode: (url) => deps.realInput?.takeCodeCoverage?.(url) ?? Promise.resolve(undefined),
|
|
196
|
+
}),
|
|
197
|
+
}),
|
|
198
|
+
}
|
|
199
|
+
: {}),
|
|
174
200
|
};
|
|
175
201
|
},
|
|
176
202
|
},
|
|
@@ -29,6 +29,17 @@ function unknownParamsError(toolName, unknown) {
|
|
|
29
29
|
const TOOLS_LIST_ACTION = 'list';
|
|
30
30
|
/** What to write instead — the one parameter reticle_tools declares, and the no-argument form. */
|
|
31
31
|
const TOOLS_ARG_HINT = 'name the tools you want in `names`: reticle_tools { names: ["reticle_act_and_wait"] } — or call it with no arguments for the full catalog';
|
|
32
|
+
/**
|
|
33
|
+
* The refusal for arguments of the wrong type, naming each offending key and what it declared.
|
|
34
|
+
* `path` is joined for a nested value (`intents.0.id`); a missing required key reads as the key.
|
|
35
|
+
*/
|
|
36
|
+
function invalidParamsError(toolName, issues) {
|
|
37
|
+
const named = issues.map((issue) => {
|
|
38
|
+
const where = 0 === issue.path.length ? '(arguments)' : issue.path.join('.');
|
|
39
|
+
return `${where} (${issue.message})`;
|
|
40
|
+
});
|
|
41
|
+
return `invalid ${1 === named.length ? 'parameter' : 'parameters'} for ${toolName}: ${named.join('; ')} — NOT applied, so any result would be an answer to a different question`;
|
|
42
|
+
}
|
|
32
43
|
/** The keys not declared by `shape`, in call order. */
|
|
33
44
|
function unknownKeys(args, shape) {
|
|
34
45
|
const declared = new Set(Object.keys(shape));
|
|
@@ -263,8 +274,24 @@ callable) {
|
|
|
263
274
|
hint: 'fix the arguments and call reticle_run again',
|
|
264
275
|
};
|
|
265
276
|
}
|
|
277
|
+
// Keys are not enough: the TYPES are the other half of what a direct call's SDK validation
|
|
278
|
+
// refuses. Unchecked here, `reticle_viewport { width: "1440" }` from a client that stringifies
|
|
279
|
+
// numbers reached the handler, which read a non-number as missing and set the page to 64x64,
|
|
280
|
+
// and `reticle_intent { action: "declare", intents: "[...]" }` stored nothing and answered
|
|
281
|
+
// `{ intents: [] }` (#1118). The parsed value goes on, as it does on the direct path, so a
|
|
282
|
+
// schema's defaults and transforms apply the same way through either door.
|
|
283
|
+
const typed = z.object(target.inputSchema).safeParse(callArgs);
|
|
284
|
+
if (!typed.success) {
|
|
285
|
+
return {
|
|
286
|
+
error: invalidParamsError(name, typed.error.issues),
|
|
287
|
+
tool: name,
|
|
288
|
+
params: paramInfo(target.inputSchema),
|
|
289
|
+
...(target.example === undefined ? {} : { example: target.example }),
|
|
290
|
+
hint: 'fix the arguments and call reticle_run again',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
266
293
|
try {
|
|
267
|
-
return await runTool(target, deps,
|
|
294
|
+
return await runTool(target, deps, typed.data);
|
|
268
295
|
}
|
|
269
296
|
catch (error) {
|
|
270
297
|
// The SAME payload the direct tool path builds — recovery included. Answering every failure
|
|
@@ -272,7 +299,7 @@ callable) {
|
|
|
272
299
|
// is reached through here: a stale ref, a paused session, a missing pairing token all came
|
|
273
300
|
// back as the agent's arguments being wrong, which is advice that spends the retry.
|
|
274
301
|
const message = error instanceof Error ? error.message : String(error);
|
|
275
|
-
return { ...takeVersionSkewOnto(buildErrorPayload(message)), tool: name };
|
|
302
|
+
return { ...takeVersionSkewOnto(buildErrorPayload(message), deps.peerSkew), tool: name };
|
|
276
303
|
}
|
|
277
304
|
},
|
|
278
305
|
};
|