@reticlehq/server 2.12.0 → 2.13.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 +67 -109
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -1
- package/dist/bridge/bridge.js +247 -164
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +239 -22
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +30 -12
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +3 -1
- package/dist/init/dev-script.js +8 -5
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +22 -1
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +61 -3
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +68 -0
- package/dist/setup/dev-server-wait.js +67 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +203 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +56 -8
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +18 -0
- package/dist/update/update-nudge.js +20 -0
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared bounds for numeric TOOL INPUTS.
|
|
3
|
+
*
|
|
4
|
+
* An unknown parameter is refused with "They were NOT applied — a result computed without them
|
|
5
|
+
* would look like an answer." A known parameter carrying a value the code cannot honour is the
|
|
6
|
+
* same failure: `reticle_state { depth: -5 }` used to return the full unscoped store, because
|
|
7
|
+
* `capDepth` treats a negative budget as "no cap". The agent cannot tell a bound it asked for
|
|
8
|
+
* from a bound that was dropped.
|
|
9
|
+
*
|
|
10
|
+
* A single rule for every number would be wrong (`since: 0` is a real cursor; `timeout_ms: 0`
|
|
11
|
+
* on assert means evaluate now; `threshold` is a 0–1 ratio). Each helper below is one policy,
|
|
12
|
+
* with the sentence for why that bound and not another.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
/**
|
|
16
|
+
* Hard cap on "return the first N". The ring buffer holds this many events; asking for a billion
|
|
17
|
+
* cannot be honoured from memory we do not keep, and would look like a complete listing.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MAX_RESULT_COUNT: 2000;
|
|
20
|
+
/**
|
|
21
|
+
* Hard cap on a numeric millisecond argument that does NOT block the caller's request.
|
|
22
|
+
* `timeout_ms: 0` stays legal — it means evaluate once, do not wait.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_TIMEOUT_MS = 120000;
|
|
25
|
+
export declare const MAX_BLOCKING_WAIT_MS: number;
|
|
26
|
+
/**
|
|
27
|
+
* `capDepth` at this many levels is already past any store an agent can read.
|
|
28
|
+
*
|
|
29
|
+
* Not lower: `numeric-args-are-bounded.test.ts` pins depth 50 as legal, because a large depth is a
|
|
30
|
+
* SCOPE choice and not an error. A ceiling that refuses it would turn a working call into a refusal
|
|
31
|
+
* in the name of bounding one that never worked.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MAX_STATE_DEPTH = 256;
|
|
34
|
+
/** A crawl that clicks forever is not a bound. */
|
|
35
|
+
export declare const MAX_CRAWL_STEPS = 500;
|
|
36
|
+
/** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
|
|
37
|
+
export declare const MAX_SCROLLS = 200;
|
|
38
|
+
/** Fake-clock jump. One day of timers is a test; a billion ms is not. */
|
|
39
|
+
export declare const MAX_ADVANCE_MS = 86400000;
|
|
40
|
+
/** Presenter idle window. One hour is a slow app; larger values never fire. */
|
|
41
|
+
export declare const MAX_IDLE_MS = 3600000;
|
|
42
|
+
/** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
|
|
43
|
+
export declare const MIN_VIEWPORT_PX = 64;
|
|
44
|
+
export declare const MAX_VIEWPORT_PX = 10000;
|
|
45
|
+
/**
|
|
46
|
+
* A page cursor. 0 is the start of the buffer, so `.positive()` would refuse a legitimate first
|
|
47
|
+
* page. Negative is not a cursor.
|
|
48
|
+
*/
|
|
49
|
+
export declare const cursorSchema: z.ZodNumber;
|
|
50
|
+
/**
|
|
51
|
+
* A wait budget in ms. 0 means evaluate now (documented on assert). Negative cannot be honoured.
|
|
52
|
+
*/
|
|
53
|
+
export declare const timeoutMsSchema: z.ZodNumber;
|
|
54
|
+
/**
|
|
55
|
+
* A count / cap. 0 means "return none", which is a real request. Negative is not.
|
|
56
|
+
* Capped at the ring-buffer size so a `limit: 1e9` cannot pretend the buffer is larger than it is.
|
|
57
|
+
*/
|
|
58
|
+
export declare const countSchema: z.ZodNumber;
|
|
59
|
+
/**
|
|
60
|
+
* Store projection depth. Negative used to mean "no cap" inside `capDepth`, which is how
|
|
61
|
+
* `depth: -5` silently returned the full store — that is the defect this bound closes.
|
|
62
|
+
*
|
|
63
|
+
* 0 stays REFUSED, which is the policy already on the surface (a zero-level read is not a read).
|
|
64
|
+
* `capDepth(v, 0)` does return a size marker, so accepting it would be defensible — but that is a
|
|
65
|
+
* change to what the tool answers, not a bound on what it cannot honour, and it belongs in its own
|
|
66
|
+
* change with its own reason.
|
|
67
|
+
*/
|
|
68
|
+
export declare const depthSchema: z.ZodNumber;
|
|
69
|
+
/**
|
|
70
|
+
* Pixel-diff ratios (`threshold`, `maxRatio`). pixelmatch's threshold and our pass ratio are both
|
|
71
|
+
* 0..1; `.int()` would refuse 0.01.
|
|
72
|
+
*/
|
|
73
|
+
export declare const ratioSchema: z.ZodNumber;
|
|
74
|
+
/** HTTP status on a mock or a filter. */
|
|
75
|
+
export declare const httpStatusSchema: z.ZodNumber;
|
|
76
|
+
/** Viewport CSS px. `.int()` alone still accepted 5 and 999999, then the handler silently clamped. */
|
|
77
|
+
export declare const viewportPxSchema: z.ZodNumber;
|
|
78
|
+
/** Crawl / scroll step budgets. 0 is a documented no-op on crawl ("raise it to crawl at all"). */
|
|
79
|
+
export declare const stepCountSchema: z.ZodNumber;
|
|
80
|
+
export declare const scrollCountSchema: z.ZodNumber;
|
|
81
|
+
export declare const advanceMsSchema: z.ZodNumber;
|
|
82
|
+
export declare const idleMsSchema: z.ZodNumber;
|
|
83
|
+
/** Delay before a mocked response. 0 is immediate. */
|
|
84
|
+
export declare const delayMsSchema: z.ZodNumber;
|
|
85
|
+
/**
|
|
86
|
+
* Observe lookback. Must be > 0 — a non-positive window used to mint a FUTURE cursor, so the
|
|
87
|
+
* tool returned zero events and looked like a quiet page. Capped so `window_ms: 1e12` cannot hang.
|
|
88
|
+
*/
|
|
89
|
+
export declare const windowMsSchema: z.ZodNumber;
|
|
90
|
+
/**
|
|
91
|
+
* Parallelism for suite replay. 0 (and omit) means sequential. Capped at the session/pool ceiling;
|
|
92
|
+
* asking for a thousand workers cannot be honoured.
|
|
93
|
+
*/
|
|
94
|
+
export declare const workerCountSchema: z.ZodNumber;
|
|
95
|
+
/**
|
|
96
|
+
* A row index / list length in a virtualized table. Those lists are large on purpose; the ring
|
|
97
|
+
* buffer's 2000 is the wrong ceiling. A billion-row claim is not a list, it is a hang.
|
|
98
|
+
*/
|
|
99
|
+
export declare const MAX_LIST_INDEX = 1000000;
|
|
100
|
+
export declare const listIndexSchema: z.ZodNumber;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared bounds for numeric TOOL INPUTS.
|
|
3
|
+
*
|
|
4
|
+
* An unknown parameter is refused with "They were NOT applied — a result computed without them
|
|
5
|
+
* would look like an answer." A known parameter carrying a value the code cannot honour is the
|
|
6
|
+
* same failure: `reticle_state { depth: -5 }` used to return the full unscoped store, because
|
|
7
|
+
* `capDepth` treats a negative budget as "no cap". The agent cannot tell a bound it asked for
|
|
8
|
+
* from a bound that was dropped.
|
|
9
|
+
*
|
|
10
|
+
* A single rule for every number would be wrong (`since: 0` is a real cursor; `timeout_ms: 0`
|
|
11
|
+
* on assert means evaluate now; `threshold` is a 0–1 ratio). Each helper below is one policy,
|
|
12
|
+
* with the sentence for why that bound and not another.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { RING_BUFFER_DEFAULTS, TRANSPORT_LIMITS } from '@reticlehq/core';
|
|
16
|
+
/**
|
|
17
|
+
* Hard cap on "return the first N". The ring buffer holds this many events; asking for a billion
|
|
18
|
+
* cannot be honoured from memory we do not keep, and would look like a complete listing.
|
|
19
|
+
*/
|
|
20
|
+
export const MAX_RESULT_COUNT = RING_BUFFER_DEFAULTS.MAX_EVENTS;
|
|
21
|
+
/**
|
|
22
|
+
* Hard cap on a numeric millisecond argument that does NOT block the caller's request.
|
|
23
|
+
* `timeout_ms: 0` stays legal — it means evaluate once, do not wait.
|
|
24
|
+
*/
|
|
25
|
+
export const MAX_TIMEOUT_MS = 120_000;
|
|
26
|
+
/**
|
|
27
|
+
* Hard cap on a wait the CALLER BLOCKS ON, which is a different ceiling from the one above.
|
|
28
|
+
*
|
|
29
|
+
* A blocking wait is bounded by the client's patience, not by ours. The MCP SDK's default request
|
|
30
|
+
* timeout is 60s and clients configure it lower; we advertised 120s. A caller who believed the
|
|
31
|
+
* advertised bound and asked for 90s got a TRANSPORT error at 60s — not a Reticle verdict, not a
|
|
32
|
+
* near-miss diagnosis, nothing to act on. The wait was honoured right up to the point where the
|
|
33
|
+
* only thing that could report it had gone.
|
|
34
|
+
*
|
|
35
|
+
* So the ceiling is set below the SDK default rather than at it: the margin is what lets Reticle's
|
|
36
|
+
* own "timed out, here is the near miss" answer beat the client's abort. A refused argument is a
|
|
37
|
+
* bad ceiling costing one round trip; an accepted one that cannot be delivered costs the drive.
|
|
38
|
+
*
|
|
39
|
+
* This does not make long waits possible, and is not meant to — it makes the ADVERTISED bound one
|
|
40
|
+
* that can actually be honoured. A caller that genuinely needs to outlast this polls: several short
|
|
41
|
+
* waits, each of which returns a verdict. See #601 for the bounded-wait cursor that would let one
|
|
42
|
+
* call do it properly.
|
|
43
|
+
*/
|
|
44
|
+
const MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
45
|
+
export const MAX_BLOCKING_WAIT_MS = MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS - 5_000;
|
|
46
|
+
/**
|
|
47
|
+
* `capDepth` at this many levels is already past any store an agent can read.
|
|
48
|
+
*
|
|
49
|
+
* Not lower: `numeric-args-are-bounded.test.ts` pins depth 50 as legal, because a large depth is a
|
|
50
|
+
* SCOPE choice and not an error. A ceiling that refuses it would turn a working call into a refusal
|
|
51
|
+
* in the name of bounding one that never worked.
|
|
52
|
+
*/
|
|
53
|
+
export const MAX_STATE_DEPTH = 256;
|
|
54
|
+
/** A crawl that clicks forever is not a bound. */
|
|
55
|
+
export const MAX_CRAWL_STEPS = 500;
|
|
56
|
+
/** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
|
|
57
|
+
export const MAX_SCROLLS = 200;
|
|
58
|
+
/** Fake-clock jump. One day of timers is a test; a billion ms is not. */
|
|
59
|
+
export const MAX_ADVANCE_MS = 86_400_000;
|
|
60
|
+
/** Presenter idle window. One hour is a slow app; larger values never fire. */
|
|
61
|
+
export const MAX_IDLE_MS = 3_600_000;
|
|
62
|
+
/** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
|
|
63
|
+
export const MIN_VIEWPORT_PX = 64;
|
|
64
|
+
export const MAX_VIEWPORT_PX = 10_000;
|
|
65
|
+
/**
|
|
66
|
+
* A page cursor. 0 is the start of the buffer, so `.positive()` would refuse a legitimate first
|
|
67
|
+
* page. Negative is not a cursor.
|
|
68
|
+
*/
|
|
69
|
+
export const cursorSchema = z.number().finite().int().nonnegative();
|
|
70
|
+
/**
|
|
71
|
+
* A wait budget in ms. 0 means evaluate now (documented on assert). Negative cannot be honoured.
|
|
72
|
+
*/
|
|
73
|
+
export const timeoutMsSchema = z.number().finite().int().nonnegative().max(MAX_BLOCKING_WAIT_MS);
|
|
74
|
+
/**
|
|
75
|
+
* A count / cap. 0 means "return none", which is a real request. Negative is not.
|
|
76
|
+
* Capped at the ring-buffer size so a `limit: 1e9` cannot pretend the buffer is larger than it is.
|
|
77
|
+
*/
|
|
78
|
+
export const countSchema = z.number().finite().int().nonnegative().max(MAX_RESULT_COUNT);
|
|
79
|
+
/**
|
|
80
|
+
* Store projection depth. Negative used to mean "no cap" inside `capDepth`, which is how
|
|
81
|
+
* `depth: -5` silently returned the full store — that is the defect this bound closes.
|
|
82
|
+
*
|
|
83
|
+
* 0 stays REFUSED, which is the policy already on the surface (a zero-level read is not a read).
|
|
84
|
+
* `capDepth(v, 0)` does return a size marker, so accepting it would be defensible — but that is a
|
|
85
|
+
* change to what the tool answers, not a bound on what it cannot honour, and it belongs in its own
|
|
86
|
+
* change with its own reason.
|
|
87
|
+
*/
|
|
88
|
+
export const depthSchema = z.number().finite().int().positive().max(MAX_STATE_DEPTH);
|
|
89
|
+
/**
|
|
90
|
+
* Pixel-diff ratios (`threshold`, `maxRatio`). pixelmatch's threshold and our pass ratio are both
|
|
91
|
+
* 0..1; `.int()` would refuse 0.01.
|
|
92
|
+
*/
|
|
93
|
+
export const ratioSchema = z.number().finite().min(0).max(1);
|
|
94
|
+
/** HTTP status on a mock or a filter. */
|
|
95
|
+
export const httpStatusSchema = z.number().finite().int().min(100).max(599);
|
|
96
|
+
/** Viewport CSS px. `.int()` alone still accepted 5 and 999999, then the handler silently clamped. */
|
|
97
|
+
export const viewportPxSchema = z.number().finite().int().min(MIN_VIEWPORT_PX).max(MAX_VIEWPORT_PX);
|
|
98
|
+
/** Crawl / scroll step budgets. 0 is a documented no-op on crawl ("raise it to crawl at all"). */
|
|
99
|
+
export const stepCountSchema = z.number().finite().int().nonnegative().max(MAX_CRAWL_STEPS);
|
|
100
|
+
export const scrollCountSchema = z.number().finite().int().nonnegative().max(MAX_SCROLLS);
|
|
101
|
+
export const advanceMsSchema = z.number().finite().int().nonnegative().max(MAX_ADVANCE_MS);
|
|
102
|
+
export const idleMsSchema = z.number().finite().int().nonnegative().max(MAX_IDLE_MS);
|
|
103
|
+
/** Delay before a mocked response. 0 is immediate. */
|
|
104
|
+
export const delayMsSchema = z.number().finite().int().nonnegative().max(MAX_TIMEOUT_MS);
|
|
105
|
+
/**
|
|
106
|
+
* Observe lookback. Must be > 0 — a non-positive window used to mint a FUTURE cursor, so the
|
|
107
|
+
* tool returned zero events and looked like a quiet page. Capped so `window_ms: 1e12` cannot hang.
|
|
108
|
+
*/
|
|
109
|
+
export const windowMsSchema = z.number().finite().int().positive().max(MAX_TIMEOUT_MS);
|
|
110
|
+
/**
|
|
111
|
+
* Parallelism for suite replay. 0 (and omit) means sequential. Capped at the session/pool ceiling;
|
|
112
|
+
* asking for a thousand workers cannot be honoured.
|
|
113
|
+
*/
|
|
114
|
+
export const workerCountSchema = z
|
|
115
|
+
.number()
|
|
116
|
+
.finite()
|
|
117
|
+
.int()
|
|
118
|
+
.nonnegative()
|
|
119
|
+
.max(TRANSPORT_LIMITS.MAX_SESSIONS);
|
|
120
|
+
/**
|
|
121
|
+
* A row index / list length in a virtualized table. Those lists are large on purpose; the ring
|
|
122
|
+
* buffer's 2000 is the wrong ceiling. A billion-row claim is not a list, it is a hang.
|
|
123
|
+
*/
|
|
124
|
+
export const MAX_LIST_INDEX = 1_000_000;
|
|
125
|
+
export const listIndexSchema = z.number().finite().int().nonnegative().max(MAX_LIST_INDEX);
|
|
@@ -7,11 +7,14 @@ import { z } from 'zod';
|
|
|
7
7
|
import { aliasParam } from './alias-args.js';
|
|
8
8
|
import { CONSOLE_LEVELS, ReticleCommand, DEFAULT_ASSERT_TIMEOUT_MS, PredicateKind, Verified, } from '@reticlehq/core';
|
|
9
9
|
import { ReticleTool } from './tool-names.js';
|
|
10
|
+
import { countSchema, cursorSchema, httpStatusSchema, timeoutMsSchema, windowMsSchema, } from './numeric-bounds.js';
|
|
10
11
|
import { buildReactionReport } from '../events/reaction.js';
|
|
11
12
|
import { findContradictions } from '../events/contradictions.js';
|
|
12
13
|
import { evaluatePredicate, waitForPredicate, PredicateSchema } from '../events/predicate.js';
|
|
14
|
+
import { resolveSessionWithin } from '../session/resolve-within.js';
|
|
15
|
+
import { WALL_CLOCK } from '../session/wall-clock.js';
|
|
13
16
|
import { parsePredicate } from '../events/predicate-parse.js';
|
|
14
|
-
import { matchNet, matchConsole, isConsoleEvent, eventMatchesFilters, netEmptyHint, consoleEmptyHint, reconcileNet, projectNetCall, projectConsoleLog, } from '../events/event-filters.js';
|
|
17
|
+
import { matchNet, matchConsole, isConsoleEvent, eventMatchesFilters, netEmptyHint, consoleEmptyHint, reconcileNet, projectNetCall, projectConsoleLog, withoutUrlRaw, } from '../events/event-filters.js';
|
|
15
18
|
import { applyEventBudget, costHint, withSizeCost, DEFAULT_QUERY_LIMIT, } from '../session/output-budget.js';
|
|
16
19
|
import { annotateStarvedFailure, healthEnvelope, bufferEnvelope, } from '../session/session-health.js';
|
|
17
20
|
import { assertsDerivedIpcStatus, DERIVED_IPC_STATUS_ADVICE, isPresenceOnlyAssertion, PRESENCE_ONLY_ADVICE, } from './assert-grade.js';
|
|
@@ -83,21 +86,17 @@ export const OBSERVE_TOOLS = [
|
|
|
83
86
|
example: { since: 0 },
|
|
84
87
|
description: 'Return the timeline of everything the app did in a window (DOM/network/route/console/animation/signal), with a summary. Use after an action. Pass `max_events` to cap the timeline to the most recent N (older events are dropped and counted in cost.droppedOldest). Every result carries a `cost:{events,bytes}` hint so you can self-budget your next call.',
|
|
85
88
|
inputSchema: {
|
|
86
|
-
window_ms:
|
|
87
|
-
.number()
|
|
88
|
-
.positive()
|
|
89
|
+
window_ms: windowMsSchema
|
|
89
90
|
// A non-positive window silently produced a FUTURE cursor, so the tool returned zero events
|
|
90
91
|
// and echoed the nonsense value back — indistinguishable from a genuinely quiet page. An
|
|
91
92
|
// agent that fumbles this argument gets a clean "nothing happened" instead of being told the
|
|
92
93
|
// question was malformed, which is the exact confusion this layer exists to refuse.
|
|
93
94
|
.optional()
|
|
94
95
|
.describe('Time window to look back, in ms (must be > 0). Default: 2000. Ignored when `since` is provided.'),
|
|
95
|
-
since:
|
|
96
|
-
.number()
|
|
96
|
+
since: cursorSchema
|
|
97
97
|
.optional()
|
|
98
98
|
.describe('Cursor from a prior reticle_act or reticle_observe call. Scopes the event window to exactly that span.'),
|
|
99
|
-
until:
|
|
100
|
-
.number()
|
|
99
|
+
until: cursorSchema
|
|
101
100
|
.optional()
|
|
102
101
|
.describe('Upper cursor bound. With `since`, returns the span "between action A and B".'),
|
|
103
102
|
actionId: z
|
|
@@ -110,9 +109,7 @@ export const OBSERVE_TOOLS = [
|
|
|
110
109
|
.describe('Event type allowlist — the cheapest way to shrink a large timeline. Use a bucket name — ' +
|
|
111
110
|
'dom | net | route | console | animation | signal | perf | state | storage — or a raw ' +
|
|
112
111
|
'type (e.g. "net.request"). Omit to return all types.'),
|
|
113
|
-
max_events:
|
|
114
|
-
.number()
|
|
115
|
-
.nonnegative()
|
|
112
|
+
max_events: countSchema
|
|
116
113
|
.optional()
|
|
117
114
|
.describe('Cap the timeline to the most recent N events. Older events are counted in cost.droppedOldest.'),
|
|
118
115
|
...sessionIdShape,
|
|
@@ -187,12 +184,13 @@ export const OBSERVE_TOOLS = [
|
|
|
187
184
|
const contradictions = findContradictions(filtered, {
|
|
188
185
|
currentDocumentId: session.currentDocumentId,
|
|
189
186
|
currentEditEpoch: session.currentEditEpoch,
|
|
187
|
+
appOrigin: session.url,
|
|
190
188
|
...(judgingTheAct ? { ...session.lastAct.effect(), actionSince: actCursor } : {}),
|
|
191
189
|
});
|
|
192
190
|
// carry session health — a throttled tab means the observed timeline may be incomplete.
|
|
193
191
|
return withControl(session, {
|
|
194
192
|
...report,
|
|
195
|
-
events: report.events.map(withoutConstantSessionId),
|
|
193
|
+
events: report.events.map((e) => withoutUrlRaw(withoutConstantSessionId(e))),
|
|
196
194
|
...(contradictions.length > 0 ? { contradictions } : {}),
|
|
197
195
|
cost: costHint(report, budgeted.length, droppedOldest),
|
|
198
196
|
...healthEnvelope(session),
|
|
@@ -208,9 +206,10 @@ export const OBSERVE_TOOLS = [
|
|
|
208
206
|
predicate: PredicateSchema.optional().describe('Predicate to wait for: { signal }, { net }, { element }, { kind: "net", ok: false } (assert on the OUTCOME — the honest field for IPC, which has no status code), { kind: "state", store, path, equals } (assert a registered store\'s value directly — the source of truth no DOM read can reach; equals takes a literal or { $gte | $contains | $length } pattern), { kind: "settled", quietMs } (deterministic network + DOM idle — prefer this over a fixed sleep), or a combination via allOf/anyOf.'),
|
|
209
207
|
// Same concept, the neighbouring tool's name. See alias-args.ts.
|
|
210
208
|
until: PredicateSchema.optional().describe("Alias for `predicate` (act_and_wait's name)."),
|
|
211
|
-
timeout_ms:
|
|
212
|
-
|
|
213
|
-
.
|
|
209
|
+
timeout_ms: timeoutMsSchema
|
|
210
|
+
.optional()
|
|
211
|
+
.describe('Maximum wait in milliseconds. Default: 4000. Capped at 55000: your MCP client aborts the request before a longer wait can return, so a bound above this would be advertised and not deliverable. To outlast it, poll — several short waits, each of which returns a verdict.'),
|
|
212
|
+
since: cursorSchema
|
|
214
213
|
.optional()
|
|
215
214
|
.describe('Cursor from a prior reticle_act — scopes the wait to events after that act.'),
|
|
216
215
|
...sessionIdShape,
|
|
@@ -223,6 +222,10 @@ export const OBSERVE_TOOLS = [
|
|
|
223
222
|
.boolean()
|
|
224
223
|
.optional()
|
|
225
224
|
.describe('The tab disconnected mid-wait, so this was never observed — the verdict is UNKNOWN, not a failure of the app.'),
|
|
225
|
+
inconclusive: z
|
|
226
|
+
.string()
|
|
227
|
+
.optional()
|
|
228
|
+
.describe('The wait could not be graded as a product failure — e.g. the tab is throttled and may never have rendered. The verdict is UNKNOWN, not "the UI is absent".'),
|
|
226
229
|
session: z
|
|
227
230
|
.object({ lastSeenMs: z.number(), throttled: z.boolean(), focused: z.boolean() })
|
|
228
231
|
.optional(),
|
|
@@ -245,12 +248,14 @@ export const OBSERVE_TOOLS = [
|
|
|
245
248
|
.describe('Where the code behind this verdict lives, as `file:line`. For an element/text assertion it is the matched element itself; for a FAILING signal/net/state assertion — which has no element to point at — it is the control last acted on, where the handler that should have fired lives. OMITTED when neither is known: this tool never borrows an unrelated location.'),
|
|
246
249
|
},
|
|
247
250
|
handler: async (deps, args) => {
|
|
248
|
-
const
|
|
251
|
+
const waitBudget = asNumber(args['timeout_ms']) ?? DEFAULT_ASSERT_TIMEOUT_MS;
|
|
252
|
+
// Spend the budget waiting for the APP as well as for the predicate. See resolve-within.
|
|
253
|
+
const session = await resolveSessionWithin(deps.sessions, asString(args['sessionId']), waitBudget, WALL_CLOCK);
|
|
249
254
|
// `until` is act_and_wait's name for this — see alias-args.ts.
|
|
250
255
|
const predicate = parsePredicate(aliasParam(args, 'predicate', ['until'])['predicate']);
|
|
251
256
|
// Honesty: explicit since wins; else default to the last act's cursor; else the whole buffer.
|
|
252
257
|
const since = asNumber(args['since']) ?? session.lastAct.cursor() ?? 0;
|
|
253
|
-
const verdict = await waitForPredicate(session, predicate,
|
|
258
|
+
const verdict = await waitForPredicate(session, predicate, waitBudget, since);
|
|
254
259
|
// match reticle_assert — wrap with control + session health (throttle matters most while blocking)
|
|
255
260
|
// and the buffer envelope, so a verdict reached over an evicted window says so.
|
|
256
261
|
return withControl(session, {
|
|
@@ -281,12 +286,10 @@ export const OBSERVE_TOOLS = [
|
|
|
281
286
|
'{ settled } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
|
|
282
287
|
'over element/text presence.'),
|
|
283
288
|
until: PredicateSchema.optional().describe("Alias for `predicate` (act_and_wait's name)."),
|
|
284
|
-
timeout_ms:
|
|
285
|
-
.number()
|
|
289
|
+
timeout_ms: timeoutMsSchema
|
|
286
290
|
.optional()
|
|
287
|
-
.describe('If > 0, wait up to this many milliseconds before failing. Default: 0 (evaluate once).'),
|
|
288
|
-
since:
|
|
289
|
-
.number()
|
|
291
|
+
.describe('If > 0, wait up to this many milliseconds before failing. Default: 0 (evaluate once). Capped at 55000: your MCP client aborts the request before a longer wait can return, so a bound above this would be advertised and not deliverable. To outlast it, poll — several short waits, each of which returns a verdict.'),
|
|
292
|
+
since: cursorSchema
|
|
290
293
|
.optional()
|
|
291
294
|
.describe('Cursor from a prior reticle_act — scopes the assertion to events after that act.'),
|
|
292
295
|
intent: intentArg,
|
|
@@ -300,6 +303,10 @@ export const OBSERVE_TOOLS = [
|
|
|
300
303
|
.boolean()
|
|
301
304
|
.optional()
|
|
302
305
|
.describe('The tab disconnected mid-wait, so this was never observed — the verdict is UNKNOWN, not a failure of the app.'),
|
|
306
|
+
inconclusive: z
|
|
307
|
+
.string()
|
|
308
|
+
.optional()
|
|
309
|
+
.describe('The assertion could not be graded as a product failure — e.g. the tab is throttled and may never have rendered. The verdict is UNKNOWN, not "the UI is absent".'),
|
|
303
310
|
advice: z
|
|
304
311
|
.string()
|
|
305
312
|
.optional()
|
|
@@ -338,10 +345,11 @@ export const OBSERVE_TOOLS = [
|
|
|
338
345
|
.describe('What the APP did not tell Reticle, and the one change that would fix it — each entry is { kind, missing, cost, fix, source?, ref? }. Reported ONLY where an absence made THIS verdict weaker: a failing assertion with no source to point at, or a state assertion against an app that registers no store. Never a survey of the page, so an entry is always work worth doing now. OMITTED when the app told Reticle everything it needed.'),
|
|
339
346
|
},
|
|
340
347
|
handler: async (deps, args) => {
|
|
341
|
-
const
|
|
348
|
+
const timeout = asNumber(args['timeout_ms']) ?? 0;
|
|
349
|
+
// Spend the budget waiting for the APP as well as for the predicate. See resolve-within.
|
|
350
|
+
const session = await resolveSessionWithin(deps.sessions, asString(args['sessionId']), timeout, WALL_CLOCK);
|
|
342
351
|
// `until` is act_and_wait's name for this — see alias-args.ts.
|
|
343
352
|
const predicate = parsePredicate(aliasParam(args, 'predicate', ['until'])['predicate']);
|
|
344
|
-
const timeout = asNumber(args['timeout_ms']) ?? 0;
|
|
345
353
|
// Honesty: explicit since wins; else default to the last act's cursor; else the whole buffer.
|
|
346
354
|
const since = asNumber(args['since']) ?? session.lastAct.cursor() ?? 0;
|
|
347
355
|
// Declared BEFORE the verdict, so the undeclared-change read below finds it open and stays
|
|
@@ -375,7 +383,14 @@ export const OBSERVE_TOOLS = [
|
|
|
375
383
|
verdictId: inlineVerdictId(ReticleTool.ASSERT, deps.now()),
|
|
376
384
|
grade: gradeOfPredicate(predicate),
|
|
377
385
|
at: deps.now(),
|
|
378
|
-
}
|
|
386
|
+
},
|
|
387
|
+
/*
|
|
388
|
+
* An assertion has no element of its own — it observes, it does not act. The file it names
|
|
389
|
+
* is the one the LAST action touched, which is the code path that produced the state being
|
|
390
|
+
* asserted about. Already remembered on the session for exactly this reason: an assertion
|
|
391
|
+
* whose failure has nothing to point at still needs to name a file.
|
|
392
|
+
*/
|
|
393
|
+
session.lastAct.source());
|
|
379
394
|
}
|
|
380
395
|
// Journal the verdict so a LATER turn can read what this one proved. A verdict that lives only
|
|
381
396
|
// in the response lives only in the agent's context window, which is the copy a compaction
|
|
@@ -402,12 +417,10 @@ export const OBSERVE_TOOLS = [
|
|
|
402
417
|
example: { urlContains: '/api/todos', method: 'POST' },
|
|
403
418
|
description: 'Filtered list of network calls. Fast path for "did POST /x return 200?". A zero-match filter returns a `hint` { totalInWindow, present[] } of the calls that DID fire, so a miss is diagnosable. Desktop IPC (`ipc://`) has no status code — the 200/500 there is derived, so filter on `ok` for those.',
|
|
404
419
|
inputSchema: {
|
|
405
|
-
since:
|
|
406
|
-
.number()
|
|
420
|
+
since: cursorSchema
|
|
407
421
|
.optional()
|
|
408
422
|
.describe('Cursor from a prior reticle_act — scopes the query to requests fired after that act.'),
|
|
409
|
-
until:
|
|
410
|
-
.number()
|
|
423
|
+
until: cursorSchema
|
|
411
424
|
.optional()
|
|
412
425
|
.describe('Upper cursor bound — with `since`, the span between two acts.'),
|
|
413
426
|
actionId: z
|
|
@@ -419,14 +432,12 @@ export const OBSERVE_TOOLS = [
|
|
|
419
432
|
.optional()
|
|
420
433
|
.describe('HTTP method filter: GET | POST | PUT | DELETE | PATCH etc.'),
|
|
421
434
|
urlContains: z.string().optional().describe('Substring that the request URL must contain.'),
|
|
422
|
-
status:
|
|
435
|
+
status: httpStatusSchema.optional().describe('HTTP status code filter (e.g. 200, 404, 500).'),
|
|
423
436
|
ok: z
|
|
424
437
|
.boolean()
|
|
425
438
|
.optional()
|
|
426
439
|
.describe('Outcome filter: false keeps only calls that FAILED, true only those that succeeded. The filter to use for desktop IPC (`ipc://`), whose status code is derived. A still-pending call matches neither.'),
|
|
427
|
-
limit:
|
|
428
|
-
.number()
|
|
429
|
-
.nonnegative()
|
|
440
|
+
limit: countSchema
|
|
430
441
|
.optional()
|
|
431
442
|
.describe('Keep only the most recent N matching calls (older are dropped and counted in droppedOldest) — cuts tokens on a wide window. Defaults to 200 when omitted; pass a higher number for more, or scope with since/until.'),
|
|
432
443
|
bodies: z
|
|
@@ -494,21 +505,17 @@ export const OBSERVE_TOOLS = [
|
|
|
494
505
|
level: consoleLevelEnum
|
|
495
506
|
.optional()
|
|
496
507
|
.describe(`Log level filter: ${CONSOLE_LEVEL_LIST}. Omit to return all levels.`),
|
|
497
|
-
since:
|
|
498
|
-
.number()
|
|
508
|
+
since: cursorSchema
|
|
499
509
|
.optional()
|
|
500
510
|
.describe('Cursor from a prior reticle_act — scopes the query to log entries after that act.'),
|
|
501
|
-
until:
|
|
502
|
-
.number()
|
|
511
|
+
until: cursorSchema
|
|
503
512
|
.optional()
|
|
504
513
|
.describe('Upper cursor bound — with `since`, the span between two acts.'),
|
|
505
514
|
actionId: z
|
|
506
515
|
.string()
|
|
507
516
|
.optional()
|
|
508
517
|
.describe('Keep only log entries attributed to this action — "what did action N log".'),
|
|
509
|
-
limit:
|
|
510
|
-
.number()
|
|
511
|
-
.nonnegative()
|
|
518
|
+
limit: countSchema
|
|
512
519
|
.optional()
|
|
513
520
|
.describe('Keep only the most recent N matching entries (older are dropped and counted in droppedOldest) — cuts tokens when a page spams the console. Defaults to 200 when omitted; pass a higher number for more, or scope with since/until.'),
|
|
514
521
|
...sessionIdShape,
|
package/dist/tools/read-tools.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveAnnotateTarget } from '../flows/annotate-target.js';
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { EventType, ReticleCommand, REPLAY_PROGRAM_VERSION, SnapshotMode, StorageArea, } from '@reticlehq/core';
|
|
8
8
|
import { ReticleTool } from './tool-names.js';
|
|
9
|
+
import { advanceMsSchema, depthSchema } from './numeric-bounds.js';
|
|
9
10
|
import { proposeConsequences } from '../oracles/propose-consequences.js';
|
|
10
11
|
import { replayProgram } from '../flows/replay.js';
|
|
11
12
|
import { diffLines } from '../project/baselines.js';
|
|
@@ -15,6 +16,26 @@ import { buildReactionReport, summarizeReaction } from '../events/reaction.js';
|
|
|
15
16
|
import { asString, asNumber, parseInteractive } from './tools-helpers.js';
|
|
16
17
|
import { sessionIdShape, commandOrThrow, snapshotTree } from './tool-kit.js';
|
|
17
18
|
import { bufferEnvelope } from '../session/session-health.js';
|
|
19
|
+
/** The route part of a session URL. A host belongs to the machine, not to the journey. */
|
|
20
|
+
/**
|
|
21
|
+
* The page a recording started on, as a NAVIGABLE path: pathname + hash.
|
|
22
|
+
*
|
|
23
|
+
* The hash matters and the pathname alone is not enough. Under a hash router every page has the
|
|
24
|
+
* document pathname `/`, so a recording made on `#/posts/12` stored `/`, the replay's start-path
|
|
25
|
+
* comparison saw `/` on both sides and never warned about drift, and the navigate it suggests
|
|
26
|
+
* pointed at the default route rather than the recorded one.
|
|
27
|
+
*/
|
|
28
|
+
function pathnameOf(url) {
|
|
29
|
+
if (url === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const parsed = new URL(url);
|
|
33
|
+
return `${parsed.pathname}${parsed.hash}`;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
/**
|
|
19
40
|
* What record-stop says about a step that compiled to no anchor at all.
|
|
20
41
|
*
|
|
@@ -141,7 +162,10 @@ export const READ_TOOLS = [
|
|
|
141
162
|
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
142
163
|
const name = asString(args['recordingName']) ?? 'default';
|
|
143
164
|
const cursor = session.elapsed();
|
|
144
|
-
|
|
165
|
+
// Where the journey begins, so a saved flow can navigate here before step 1 instead of
|
|
166
|
+
// replaying from wherever the page happens to be. Pathname only: a host or port belongs to
|
|
167
|
+
// the machine that recorded it, not to the journey.
|
|
168
|
+
deps.recordings.start(name, cursor, pathnameOf(session.url));
|
|
145
169
|
return Promise.resolve({ recordingName: name, since: cursor });
|
|
146
170
|
},
|
|
147
171
|
},
|
|
@@ -181,6 +205,7 @@ export const READ_TOOLS = [
|
|
|
181
205
|
name,
|
|
182
206
|
version: REPLAY_PROGRAM_VERSION,
|
|
183
207
|
steps: rec.steps,
|
|
208
|
+
...(rec.startPath === undefined ? {} : { startPath: rec.startPath }),
|
|
184
209
|
};
|
|
185
210
|
deps.recordings.saveCompiled(program);
|
|
186
211
|
const unstable = rec.steps.filter((s) => !s.stable).length;
|
|
@@ -282,8 +307,7 @@ export const READ_TOOLS = [
|
|
|
282
307
|
.boolean()
|
|
283
308
|
.optional()
|
|
284
309
|
.describe('Freeze the fake clock. Time stops advancing until advanceMs or reset.'),
|
|
285
|
-
advanceMs:
|
|
286
|
-
.number()
|
|
310
|
+
advanceMs: advanceMsSchema
|
|
287
311
|
.optional()
|
|
288
312
|
.describe('Fast-forward time by this many milliseconds — triggers debounces, toasts, auto-dismiss timers.'),
|
|
289
313
|
reset: z.boolean().optional().describe('Restore the real clock.'),
|
|
@@ -318,10 +342,7 @@ export const READ_TOOLS = [
|
|
|
318
342
|
.string()
|
|
319
343
|
.optional()
|
|
320
344
|
.describe("Dot-path into the store (e.g. 'captionCache.v3'). Numeric array indices are supported."),
|
|
321
|
-
depth:
|
|
322
|
-
.number()
|
|
323
|
-
.int()
|
|
324
|
-
.positive()
|
|
345
|
+
depth: depthSchema
|
|
325
346
|
.optional()
|
|
326
347
|
.describe('Collapse anything deeper than N levels to a size marker — avoids huge outputs for large stores. A whole number of levels, 1 or more.'),
|
|
327
348
|
...sessionIdShape,
|
|
@@ -40,11 +40,21 @@ interface RealActResult {
|
|
|
40
40
|
/** Why we went synthetic despite a configured provider (field bug #2: never a silent fallback). */
|
|
41
41
|
reason?: InputModeReason;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Hover without a native pointer is a false success: synthetic mouseover reports dispatched and
|
|
45
|
+
* settled while CSS `:hover` never applies. Same refusal shape as contenteditable — name the gap
|
|
46
|
+
* rather than pretend the action ran.
|
|
47
|
+
*/
|
|
48
|
+
export declare const HOVER_NEEDS_POINTER_MSG = "cannot hover without a real pointer \u2014 CSS :hover only applies to a native mouse move, never to a synthetic mouseover";
|
|
43
49
|
/**
|
|
44
50
|
* Attempt to drive a pointer action via native input. Returns a synthetic outcome (with a
|
|
45
51
|
* `reason` when a provider is configured) whenever the synthetic path should run — no matching
|
|
46
52
|
* page, unresolvable box, declined, etc. A throw inside the provider becomes a synthetic fallback
|
|
47
53
|
* flagged with `fellBack`. `result` is defined only on a real success.
|
|
54
|
+
*
|
|
55
|
+
* Hover is the exception: it never returns synthetic. CSS `:hover` is not applied by
|
|
56
|
+
* `dispatchEvent`, so a synthetic success is the defect. The call either drives a real pointer
|
|
57
|
+
* or throws.
|
|
48
58
|
*/
|
|
49
59
|
export declare function tryRealInput(deps: ToolDeps, session: Session, ref: string, action: ActionType, args: Record<string, unknown>): Promise<RealActResult>;
|
|
50
60
|
export {};
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* fits in one WebSocket frame.
|
|
20
20
|
*/
|
|
21
21
|
import { ActionType, InputModeReason, ReticleCommand, TRANSPORT_LIMITS } from '@reticlehq/core';
|
|
22
|
-
import { isPointerAction } from '../input/real-input.js';
|
|
22
|
+
import { boxCenter, isPointerAction } from '../input/real-input.js';
|
|
23
23
|
import { assertDragNotDestructive, assertNotDestructive } from './act-danger.js';
|
|
24
24
|
import { NATIVE_INPUT_ARG } from '@reticlehq/core';
|
|
25
25
|
import { asString, asRecord } from './tools-helpers.js';
|
|
@@ -171,16 +171,66 @@ function synthetic(reason) {
|
|
|
171
171
|
? { result: undefined, settled: false }
|
|
172
172
|
: { result: undefined, settled: false, reason };
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Hover without a native pointer is a false success: synthetic mouseover reports dispatched and
|
|
176
|
+
* settled while CSS `:hover` never applies. Same refusal shape as contenteditable — name the gap
|
|
177
|
+
* rather than pretend the action ran.
|
|
178
|
+
*/
|
|
179
|
+
export const HOVER_NEEDS_POINTER_MSG = 'cannot hover without a real pointer — CSS :hover only applies to a native mouse move, never to a synthetic mouseover';
|
|
180
|
+
function hoverSucceeded(center) {
|
|
181
|
+
return { result: { performed: true, center, action: ActionType.HOVER }, settled: true };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Drive hover through a real pointer (CDP provider, then a leased Playwright page), or refuse.
|
|
185
|
+
*
|
|
186
|
+
* A leased tab already owns Chromium — the pool can move the mouse even when `reticle drive` /
|
|
187
|
+
* `RETICLE_CDP_URL` never configured a provider. Falling through to synthetic dispatch is the
|
|
188
|
+
* bug: the tool reports done and the styles never ran.
|
|
189
|
+
*/
|
|
190
|
+
async function hoverForReal(deps, session, ref, inner, provider) {
|
|
191
|
+
const providerReady = provider !== undefined && (await provider.isAvailableFor(session.url));
|
|
192
|
+
if (!providerReady && deps.pool === undefined) {
|
|
193
|
+
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
194
|
+
}
|
|
195
|
+
const inspected = await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, { ref });
|
|
196
|
+
assertNotDestructive(ActionType.HOVER, inner, inspected);
|
|
197
|
+
const box = asBox(inspected);
|
|
198
|
+
if (box === undefined) {
|
|
199
|
+
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
200
|
+
}
|
|
201
|
+
if (providerReady && provider !== undefined) {
|
|
202
|
+
try {
|
|
203
|
+
const performed = await provider.perform(session.url, ActionType.HOVER, box, {});
|
|
204
|
+
if (performed.performed)
|
|
205
|
+
return hoverSucceeded(performed.center);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// A provider error used to fall back to synthetic dispatch. For hover that is a lie.
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const { cx, cy } = boxCenter(box);
|
|
212
|
+
if (true === (await deps.pool?.hoverLease(session.id, cx, cy))) {
|
|
213
|
+
return hoverSucceeded({ cx, cy });
|
|
214
|
+
}
|
|
215
|
+
throw new Error(HOVER_NEEDS_POINTER_MSG);
|
|
216
|
+
}
|
|
174
217
|
/**
|
|
175
218
|
* Attempt to drive a pointer action via native input. Returns a synthetic outcome (with a
|
|
176
219
|
* `reason` when a provider is configured) whenever the synthetic path should run — no matching
|
|
177
220
|
* page, unresolvable box, declined, etc. A throw inside the provider becomes a synthetic fallback
|
|
178
221
|
* flagged with `fellBack`. `result` is defined only on a real success.
|
|
222
|
+
*
|
|
223
|
+
* Hover is the exception: it never returns synthetic. CSS `:hover` is not applied by
|
|
224
|
+
* `dispatchEvent`, so a synthetic success is the defect. The call either drives a real pointer
|
|
225
|
+
* or throws.
|
|
179
226
|
*/
|
|
180
227
|
export async function tryRealInput(deps, session, ref, action, args) {
|
|
181
228
|
const provider = deps.realInput;
|
|
182
229
|
const inner = asRecord(args['args']);
|
|
183
230
|
const askedForNative = true === inner[NATIVE_INPUT_ARG];
|
|
231
|
+
if (action === ActionType.HOVER) {
|
|
232
|
+
return hoverForReal(deps, session, ref, inner, provider);
|
|
233
|
+
}
|
|
184
234
|
if (provider === undefined) {
|
|
185
235
|
// Silent by default: with no provider EVERY action is synthetic, and a reason on all of them is
|
|
186
236
|
// noise on the most-used tool in the product. But an agent that passed native:true asked a
|
|
@@ -197,6 +247,12 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
197
247
|
if ((action === ActionType.CLICK || action === ActionType.DBLCLICK) && !askedForNative) {
|
|
198
248
|
return synthetic(InputModeReason.SYNTHETIC_CLICK_PREFERRED);
|
|
199
249
|
}
|
|
250
|
+
// Under version skew CDP is unusable while DOM tools still work (#688). Surface the skew sentence
|
|
251
|
+
// rather than "page not correlated" / a silent provider-error fallback — those send the agent
|
|
252
|
+
// hunting a dead context that is not dead.
|
|
253
|
+
if (session.versionSkew !== undefined) {
|
|
254
|
+
throw new Error(session.versionSkew);
|
|
255
|
+
}
|
|
200
256
|
if (!(await provider.isAvailableFor(session.url)))
|
|
201
257
|
return synthetic(InputModeReason.PAGE_NOT_CORRELATED);
|
|
202
258
|
const inspected = await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, { ref });
|
|
@@ -235,6 +291,8 @@ export async function tryRealInput(deps, session, ref, action, args) {
|
|
|
235
291
|
return { result: { performed: true, center: performed.center, action }, settled: true };
|
|
236
292
|
}
|
|
237
293
|
catch {
|
|
294
|
+
if (session.versionSkew !== undefined)
|
|
295
|
+
throw new Error(session.versionSkew);
|
|
238
296
|
return {
|
|
239
297
|
result: undefined,
|
|
240
298
|
settled: false,
|