@reticlehq/server 2.11.0 → 2.13.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/SKILL.md +93 -111
- 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 -14
- package/dist/bridge/bridge.js +248 -165
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- 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 +8 -7
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +39 -1
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -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 +17 -1
- package/dist/cli/cli-parse.js +238 -12
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +2 -1
- package/dist/cli/cli-verify.js +5 -4
- package/dist/cli/cloud-cli.js +428 -253
- 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.d.ts +2 -1
- 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 +12 -1
- package/dist/cli/doctor-rows.js +12 -1
- 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/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +174 -0
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -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 +167 -186
- package/dist/cloud/cloud-config.d.ts +24 -2
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +16 -5
- package/dist/cloud/cloud-sync.js +64 -1
- 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 +12 -13
- package/dist/crawl/crawl.js +25 -3
- package/dist/daemon/daemon-resolve.d.ts +122 -0
- package/dist/daemon/daemon-resolve.js +187 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/dev/stale-issue-guard.d.ts +0 -2
- package/dist/dev/stale-issue-guard.js +1 -1
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +59 -0
- package/dist/events/contradictions.js +253 -27
- package/dist/events/declared.d.ts +17 -0
- package/dist/events/declared.js +43 -1
- package/dist/events/echo-mismatch.js +65 -1
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +13 -0
- package/dist/events/predicate-asks.js +22 -0
- package/dist/events/predicate-eval.js +117 -27
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +43 -3
- 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-schema.d.ts +44 -0
- package/dist/events/predicate-schema.js +126 -23
- package/dist/events/predicate.d.ts +20 -0
- package/dist/events/predicate.js +77 -6
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +6 -8
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- 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 +263 -23
- 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-success.js +15 -3
- package/dist/flows/flow-tools.js +87 -33
- package/dist/flows/flows.d.ts +14 -1
- package/dist/flows/flows.js +502 -24
- 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 +36 -15
- 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/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -3
- package/dist/honesty/blind-spots.d.ts +51 -15
- package/dist/honesty/blind-spots.js +64 -4
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +39 -6
- package/dist/honesty/instrumentation-gaps.js +88 -5
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +16 -0
- package/dist/honesty/verified.js +84 -5
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +13 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +13 -4
- package/dist/impact/impact-store.js +44 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +30 -6
- package/dist/init/agent-rules.js +54 -13
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -3
- package/dist/init/capabilities.js +23 -3
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +27 -28
- package/dist/init/confirm.js +51 -11
- package/dist/init/cra.d.ts +34 -5
- package/dist/init/cra.js +96 -25
- 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 +50 -0
- package/dist/init/dev-script.js +66 -0
- 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 +17 -6
- package/dist/init/mcp.js +27 -11
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +147 -37
- package/dist/init/plan.d.ts +30 -37
- package/dist/init/plan.js +50 -60
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +32 -35
- package/dist/init/run.js +151 -202
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +90 -31
- 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.d.ts +2 -0
- package/dist/input/real-input.js +28 -7
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +37 -0
- package/dist/intent/inline-intent.js +193 -0
- 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 +49 -0
- package/dist/intent/intent-store.js +133 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +143 -0
- package/dist/intent/open-intents.d.ts +42 -0
- package/dist/intent/open-intents.js +40 -0
- package/dist/journal/envelope.d.ts +26 -26
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -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 +83 -0
- package/dist/mcp/attach-memory.js +185 -0
- 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 -8
- package/dist/mcp/mcp-proxy.js +64 -62
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +52 -24
- 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/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +16 -0
- package/dist/mcp/tool-catalog-cache.js +22 -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/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +75 -5
- package/dist/pool/browser-pool.js +144 -5
- package/dist/pool/playwright-launcher.js +12 -1
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.js +6 -1
- package/dist/project/project-tools.js +6 -35
- package/dist/project/reticle-dir.d.ts +17 -2
- package/dist/project/reticle-dir.js +22 -4
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- 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 +24 -3
- 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/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +33 -0
- package/dist/session/no-session-diagnosis.js +82 -24
- package/dist/session/no-session-next-action.d.ts +12 -0
- package/dist/session/no-session-next-action.js +77 -6
- package/dist/session/no-session-watch.d.ts +18 -6
- package/dist/session/no-session-watch.js +125 -75
- package/dist/session/output-budget.js +4 -1
- 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-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -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 +111 -4
- 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.d.ts +37 -0
- package/dist/session/session-successor.js +113 -0
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +53 -3
- package/dist/session/session.js +162 -9
- 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 +60 -0
- package/dist/setup/dev-server-wait.js +65 -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 +187 -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/automation-hint.d.ts +2 -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-outbox.d.ts +2 -1
- 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 -9
- package/dist/telemetry/project-profile.js +12 -7
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- 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/telemetry/verification-of.js +2 -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 +196 -204
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +20 -3
- package/dist/tools/assert-verdict.js +58 -20
- package/dist/tools/browser-tools.js +6 -22
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +123 -69
- package/dist/tools/dynamic-tools.js +53 -13
- package/dist/tools/error-recovery.d.ts +25 -14
- package/dist/tools/error-recovery.js +78 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +121 -11
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +35 -5
- package/dist/tools/lease-tools.js +191 -13
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +109 -65
- package/dist/tools/read-tools.js +31 -8
- package/dist/tools/real-input-attempt.d.ts +32 -0
- package/dist/tools/real-input-attempt.js +212 -2
- package/dist/tools/reconcile-tools.js +22 -8
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +24 -0
- package/dist/tools/tool-kit.js +21 -0
- package/dist/tools/tool-names.d.ts +5 -0
- package/dist/tools/tool-names.js +5 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- 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 +78 -12
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- 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 +25 -10
- package/dist/update/update-nudge.js +32 -3
- package/dist/update/updater.d.ts +1 -1
- 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/README.md +2 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli/verify.mdx +4 -5
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +2 -2
- package/docs/gates.md +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/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +4 -3
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventType } from '@reticlehq/core';
|
|
1
|
+
import { EventType, URL_RAW, urlForMatch } from '@reticlehq/core';
|
|
2
2
|
import { asString, asNumber } from '../tools/tools-helpers.js';
|
|
3
3
|
import { mergeNetworkDetail } from '../input/network-detail.js';
|
|
4
4
|
/**
|
|
@@ -55,7 +55,7 @@ export function matchNet(e, method, urlContains, status, ok) {
|
|
|
55
55
|
if (method !== undefined && asString(d['method'])?.toUpperCase() !== method.toUpperCase()) {
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
|
-
if (urlContains !== undefined && !(
|
|
58
|
+
if (urlContains !== undefined && !urlForMatch(d).includes(urlContains)) {
|
|
59
59
|
return false;
|
|
60
60
|
}
|
|
61
61
|
if (status !== undefined && asNumber(d['status']) !== status)
|
|
@@ -86,6 +86,23 @@ export function reconcileNet(events) {
|
|
|
86
86
|
.map((e) => ({ ...e, data: { ...e.data, status: 'pending', pending: true } }));
|
|
87
87
|
return [...completed, ...unresolved].sort((a, b) => a.t - b.t);
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Strip the grader-only raw URL before an event is rendered to the agent.
|
|
91
|
+
*
|
|
92
|
+
* `urlRaw` exists so `urlContains` can match a path redaction rewrote. It is a secret-bearing field
|
|
93
|
+
* by construction (the whole reason `url` was redacted) and must never appear in observe, evidence,
|
|
94
|
+
* or a network listing.
|
|
95
|
+
*/
|
|
96
|
+
export function withoutUrlRaw(event) {
|
|
97
|
+
if (typeof event !== 'object' || null === event)
|
|
98
|
+
return event;
|
|
99
|
+
const rec = event;
|
|
100
|
+
const data = rec['data'];
|
|
101
|
+
if (typeof data !== 'object' || null === data || !(URL_RAW in data))
|
|
102
|
+
return event;
|
|
103
|
+
const { [URL_RAW]: _raw, ...rest } = data;
|
|
104
|
+
return { ...rec, data: rest };
|
|
105
|
+
}
|
|
89
106
|
export function projectNetCall(e, includeBodies = true) {
|
|
90
107
|
const status = e.data['status'];
|
|
91
108
|
const ms = asNumber(e.data['durationMs']);
|
|
@@ -10,3 +10,15 @@
|
|
|
10
10
|
* not do what you thought) versus things happened but not that one (you named it wrong).
|
|
11
11
|
*/
|
|
12
12
|
export declare function describeObserved(noun: string, seen: readonly string[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* The third answer, and the one this file existed to make room for: things happened, and they belong
|
|
15
|
+
* to a document that has since been replaced.
|
|
16
|
+
*
|
|
17
|
+
* `describeObserved` already separates "nothing happened" from "things happened but not that one",
|
|
18
|
+
* because those need different fixes. A window whose evidence was discarded as superseded is a third
|
|
19
|
+
* case needing a third fix, and it is the one that reads most like the first: everything that could
|
|
20
|
+
* have answered the question is gone, so a bare "nothing happened in this window" is available and
|
|
21
|
+
* wrong. The remedy is in the sentence, because an agent that cannot act on a caveat learns to skip
|
|
22
|
+
* it — here the remedy is to drive the action again against the page now on screen.
|
|
23
|
+
*/
|
|
24
|
+
export declare function describeSuperseded(noun: string, count: number): string;
|
|
@@ -19,3 +19,20 @@ export function describeObserved(noun, seen) {
|
|
|
19
19
|
const rest = unique.length - shown.length;
|
|
20
20
|
return `${noun} seen in this window: ${shown.join(', ')}${rest > 0 ? `, and ${String(rest)} more` : ''}`;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The third answer, and the one this file existed to make room for: things happened, and they belong
|
|
24
|
+
* to a document that has since been replaced.
|
|
25
|
+
*
|
|
26
|
+
* `describeObserved` already separates "nothing happened" from "things happened but not that one",
|
|
27
|
+
* because those need different fixes. A window whose evidence was discarded as superseded is a third
|
|
28
|
+
* case needing a third fix, and it is the one that reads most like the first: everything that could
|
|
29
|
+
* have answered the question is gone, so a bare "nothing happened in this window" is available and
|
|
30
|
+
* wrong. The remedy is in the sentence, because an agent that cannot act on a caveat learns to skip
|
|
31
|
+
* it — here the remedy is to drive the action again against the page now on screen.
|
|
32
|
+
*/
|
|
33
|
+
export function describeSuperseded(noun, count) {
|
|
34
|
+
return (`no ${noun} belong to the document currently on screen: ${String(count)} ${noun} were observed ` +
|
|
35
|
+
`and every one of them belongs to a document that has since been replaced (the page reloaded or ` +
|
|
36
|
+
`navigated). This says nothing about the app — the evidence went away with the page that produced ` +
|
|
37
|
+
`it. Re-drive the action against the page now on screen`);
|
|
38
|
+
}
|
|
@@ -15,3 +15,16 @@ import type { Predicate } from './predicate-schema.js';
|
|
|
15
15
|
* that reads as the subject of an unrelated test is a half-hour somebody loses later.
|
|
16
16
|
*/
|
|
17
17
|
export declare function declaresState(predicate: Predicate): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Does the caller's predicate judge the DOM — an `element` or `text` clause anywhere in the tree?
|
|
20
|
+
*
|
|
21
|
+
* The question behind it is where a verdict is allowed to point. A DOM predicate has its own locus:
|
|
22
|
+
* the element it matched, whose `file:line` the descriptor already carries. A `signal`/`net`/`state`
|
|
23
|
+
* failure has none — there is no node for "the request was never made" — and that is the one case
|
|
24
|
+
* where the verdict borrows the last driven control's line, because the handler that should have
|
|
25
|
+
* fired lives there.
|
|
26
|
+
*
|
|
27
|
+
* Walks composites for the same reason `declaresState` does: `allOf[{element}, {signal}]` looked at
|
|
28
|
+
* the DOM exactly as much as a bare `{element}` did.
|
|
29
|
+
*/
|
|
30
|
+
export declare function declaresDom(predicate: Predicate): boolean;
|
|
@@ -24,3 +24,25 @@ export function declaresState(predicate) {
|
|
|
24
24
|
return declaresState(predicate.predicate);
|
|
25
25
|
return false;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Does the caller's predicate judge the DOM — an `element` or `text` clause anywhere in the tree?
|
|
29
|
+
*
|
|
30
|
+
* The question behind it is where a verdict is allowed to point. A DOM predicate has its own locus:
|
|
31
|
+
* the element it matched, whose `file:line` the descriptor already carries. A `signal`/`net`/`state`
|
|
32
|
+
* failure has none — there is no node for "the request was never made" — and that is the one case
|
|
33
|
+
* where the verdict borrows the last driven control's line, because the handler that should have
|
|
34
|
+
* fired lives there.
|
|
35
|
+
*
|
|
36
|
+
* Walks composites for the same reason `declaresState` does: `allOf[{element}, {signal}]` looked at
|
|
37
|
+
* the DOM exactly as much as a bare `{element}` did.
|
|
38
|
+
*/
|
|
39
|
+
export function declaresDom(predicate) {
|
|
40
|
+
if (predicate.kind === PredicateKind.ELEMENT || predicate.kind === PredicateKind.TEXT)
|
|
41
|
+
return true;
|
|
42
|
+
if (predicate.kind === PredicateKind.ALL_OF || predicate.kind === PredicateKind.ANY_OF) {
|
|
43
|
+
return predicate.predicates.some(declaresDom);
|
|
44
|
+
}
|
|
45
|
+
if (predicate.kind === PredicateKind.NOT)
|
|
46
|
+
return declaresDom(predicate.predicate);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { EventType, PredicateKind, StreamDirection, isDevToolingUrl, } from '@reticlehq/core';
|
|
1
|
+
import { EventType, PredicateKind, StreamDirection, isDevToolingUrl, urlForMatch, REDACTED_VALUE, } from '@reticlehq/core';
|
|
2
2
|
import { describeObserved } from './observed-in-window.js';
|
|
3
|
+
import { withoutUrlRaw } from './event-filters.js';
|
|
3
4
|
// The predicate SHAPE — the discriminated union, its aliases and its zod schema — lives in
|
|
4
5
|
// predicate-schema.ts. Re-exported here so every existing importer of this module is unaffected:
|
|
5
6
|
// the two halves are one public surface, split only because the file outgrew the size backstop.
|
|
@@ -140,6 +141,26 @@ function describeCall(e) {
|
|
|
140
141
|
const status = num(e.data['status']);
|
|
141
142
|
return status === undefined ? head : `${head} → ${String(status)}`;
|
|
142
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* What a miss should say when the displayed URL was redacted. Matching uses `urlRaw` when present;
|
|
146
|
+
* an older SDK has no copy, and this is the sentence both reporters asked for.
|
|
147
|
+
*/
|
|
148
|
+
const REDACTED_PATH_HINT = 'this path segment was redacted — the literal you matched may be here, try bodyContains';
|
|
149
|
+
function observedNetCalls(events, urlContains) {
|
|
150
|
+
const calls = events.filter((e) => e.type === EventType.NET_REQUEST);
|
|
151
|
+
const base = describeObserved('calls', calls.map(describeCall));
|
|
152
|
+
if (urlContains === undefined)
|
|
153
|
+
return base;
|
|
154
|
+
const encoded = encodeURIComponent(REDACTED_VALUE);
|
|
155
|
+
const redacted = calls.some((e) => {
|
|
156
|
+
const url = str(e.data['url']) ?? '';
|
|
157
|
+
return url.includes(REDACTED_VALUE) || url.includes(encoded);
|
|
158
|
+
});
|
|
159
|
+
return redacted ? `${base}; ${REDACTED_PATH_HINT}` : base;
|
|
160
|
+
}
|
|
161
|
+
function netEvidence(data) {
|
|
162
|
+
return withoutUrlRaw({ data }).data;
|
|
163
|
+
}
|
|
143
164
|
/**
|
|
144
165
|
* How much of a response body a failure may quote. Enough to see the value that differed on the
|
|
145
166
|
* bodies this field is used against (a JSON answer), and not a whole payload in every verdict.
|
|
@@ -160,6 +181,35 @@ function describeNetFilter(p) {
|
|
|
160
181
|
const { kind: _kind, count: _count, since: _since, ...filter } = p;
|
|
161
182
|
return JSON.stringify(filter);
|
|
162
183
|
}
|
|
184
|
+
/** Same, for a signal: the matcher as applied, with the cardinality and the floor taken out. */
|
|
185
|
+
function describeSignalFilter(p) {
|
|
186
|
+
const { kind: _kind, count: _count, since: _since, ...filter } = p;
|
|
187
|
+
return JSON.stringify(filter);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Exact cardinality, once, for every channel that can count its matches.
|
|
191
|
+
*
|
|
192
|
+
* `net` and `signal` are the same assertion over different evidence — "this happened EXACTLY n
|
|
193
|
+
* times" — and the interesting half is the same on both: an over-count is `decided`, because a
|
|
194
|
+
* window only accumulates and a count cannot come back down, so waiting out the rest of the budget
|
|
195
|
+
* to report a double-fire buys nothing but latency.
|
|
196
|
+
*
|
|
197
|
+
* `noun` names the population in prose ("network call(s)", "signal(s)"), `filter` is the matcher as
|
|
198
|
+
* applied, and `assertion` is the oracle that judged it.
|
|
199
|
+
*/
|
|
200
|
+
function evalExactCount(args) {
|
|
201
|
+
const { matched, want, noun, filter, assertion } = args;
|
|
202
|
+
if (matched === want)
|
|
203
|
+
return { pass: true, evidence: { matched } };
|
|
204
|
+
return {
|
|
205
|
+
pass: false,
|
|
206
|
+
...(matched > want ? { decided: true } : {}),
|
|
207
|
+
failureReason: `expected ${String(want)} ${noun} matching ${filter}, saw ${String(matched)}`,
|
|
208
|
+
observed: `${String(matched)} matching ${noun}`,
|
|
209
|
+
expected: `exactly ${String(want)} matching ${filter}`,
|
|
210
|
+
assertion,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
163
213
|
/**
|
|
164
214
|
* URL suffixes only the DOCUMENT fetches, which the network observer therefore never records.
|
|
165
215
|
*
|
|
@@ -285,6 +335,12 @@ export function evalNet(events, p) {
|
|
|
285
335
|
* a UI wiring bug that did not exist, when the defect was the value the server answered with.
|
|
286
336
|
*/
|
|
287
337
|
let bodyMismatch;
|
|
338
|
+
/**
|
|
339
|
+
* The prefix of a TRUNCATED body the needle was not found in. Held apart from `bodyMismatch`
|
|
340
|
+
* because the two are different verdicts: a full body without the needle decides the assertion,
|
|
341
|
+
* a truncated one cannot (#614).
|
|
342
|
+
*/
|
|
343
|
+
let truncatedBody;
|
|
288
344
|
const matches = events.filter((e) => {
|
|
289
345
|
if (e.type !== EventType.NET_REQUEST || e.t < since)
|
|
290
346
|
return false;
|
|
@@ -292,7 +348,7 @@ export function evalNet(events, p) {
|
|
|
292
348
|
if (p.method !== undefined && str(d['method'])?.toUpperCase() !== p.method.toUpperCase()) {
|
|
293
349
|
return false;
|
|
294
350
|
}
|
|
295
|
-
if (p.urlContains !== undefined && !(
|
|
351
|
+
if (p.urlContains !== undefined && !urlForMatch(d).includes(p.urlContains)) {
|
|
296
352
|
return false;
|
|
297
353
|
}
|
|
298
354
|
if (p.status !== undefined) {
|
|
@@ -321,6 +377,15 @@ export function evalNet(events, p) {
|
|
|
321
377
|
return false;
|
|
322
378
|
}
|
|
323
379
|
if (!response.includes(p.bodyContains)) {
|
|
380
|
+
// A needle missing from a body we only hold the FIRST N BYTES of is undecidable, not
|
|
381
|
+
// absent: the rest of the response was never recorded, so nothing here can say whether it
|
|
382
|
+
// was in there (#614). Grading it `pass: false` with "the response value is what differed"
|
|
383
|
+
// is the inversion the honesty rules exist to prevent — an unknown reported as decided,
|
|
384
|
+
// against a response that was very likely correct.
|
|
385
|
+
if (true === d['responseBodyTruncated']) {
|
|
386
|
+
truncatedBody ??= response;
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
324
389
|
bodyMismatch ??= response;
|
|
325
390
|
return false;
|
|
326
391
|
}
|
|
@@ -348,6 +413,18 @@ export function evalNet(events, p) {
|
|
|
348
413
|
assertion: 'net.bodyContains',
|
|
349
414
|
};
|
|
350
415
|
}
|
|
416
|
+
// Ranked ABOVE the mismatch branch: when both a truncated and a full body missed the needle,
|
|
417
|
+
// the honest verdict is the undecidable one. Deciding on the full body would report a failure
|
|
418
|
+
// the truncated call may well contradict.
|
|
419
|
+
if (truncatedBody !== undefined && 0 === matches.length) {
|
|
420
|
+
return {
|
|
421
|
+
pass: false,
|
|
422
|
+
inconclusive: `a call matching ${describeNetFilter(p)} was answered with a body that was TRUNCATED before it was recorded, and ${JSON.stringify(p.bodyContains)} is not in the part that was kept — so this is undecidable, not a failure. Raise the capture cap or assert on something inside the recorded prefix`,
|
|
423
|
+
observed: `the first ${String(truncatedBody.length)} characters of a truncated response body ${JSON.stringify(clipBody(truncatedBody))}`,
|
|
424
|
+
expected: `a response body containing ${JSON.stringify(p.bodyContains)}`,
|
|
425
|
+
assertion: 'net.bodyContains',
|
|
426
|
+
};
|
|
427
|
+
}
|
|
351
428
|
if (bodyMismatch !== undefined && 0 === matches.length) {
|
|
352
429
|
// The call is there and its body is there; only the VALUE differs. Counting it as zero matches
|
|
353
430
|
// points at the wiring, which is the one place the defect is not.
|
|
@@ -363,26 +440,24 @@ export function evalNet(events, p) {
|
|
|
363
440
|
// useEffect-double-fire / retry-storm regression class, where the request DID fire (presence passes)
|
|
364
441
|
// but fired the WRONG number of times. Without `count`, the matcher is presence-only (≥1).
|
|
365
442
|
if (p.count !== undefined) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
assertion: 'net.count',
|
|
385
|
-
};
|
|
443
|
+
if (matches.length !== p.count &&
|
|
444
|
+
0 === matches.length &&
|
|
445
|
+
targetsUnobservedChannel(p) &&
|
|
446
|
+
!sawSubresources) {
|
|
447
|
+
return {
|
|
448
|
+
pass: false,
|
|
449
|
+
failureReason: unobservedChannelReason(p),
|
|
450
|
+
inconclusive: unobservedChannelReason(p),
|
|
451
|
+
assertion: 'net.count',
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return evalExactCount({
|
|
455
|
+
matched: matches.length,
|
|
456
|
+
want: p.count,
|
|
457
|
+
noun: 'network call(s)',
|
|
458
|
+
filter: describeNetFilter(p),
|
|
459
|
+
assertion: 'net.count',
|
|
460
|
+
});
|
|
386
461
|
}
|
|
387
462
|
const hit = matches[0];
|
|
388
463
|
if (hit === undefined && targetsUnobservedChannel(p) && !sawSubresources) {
|
|
@@ -391,19 +466,19 @@ export function evalNet(events, p) {
|
|
|
391
466
|
pass: false,
|
|
392
467
|
failureReason: reason,
|
|
393
468
|
inconclusive: reason,
|
|
394
|
-
observed:
|
|
469
|
+
observed: observedNetCalls(events, p.urlContains),
|
|
395
470
|
expected: `at least one call matching ${describeNetFilter(p)}`,
|
|
396
471
|
assertion: 'net.unobserved-channel',
|
|
397
472
|
};
|
|
398
473
|
}
|
|
399
474
|
return hit !== undefined
|
|
400
|
-
? { pass: true, evidence: hit.data }
|
|
475
|
+
? { pass: true, evidence: netEvidence(hit.data) }
|
|
401
476
|
: {
|
|
402
477
|
pass: false,
|
|
403
478
|
failureReason: `no network call matched ${JSON.stringify(p)}`,
|
|
404
479
|
// Same reasoning as the signal miss: "no matching call" cannot be told apart from "the app
|
|
405
480
|
// made no calls at all", and those need different fixes.
|
|
406
|
-
observed:
|
|
481
|
+
observed: observedNetCalls(events, p.urlContains),
|
|
407
482
|
expected: `at least one call matching ${JSON.stringify(p)}`,
|
|
408
483
|
assertion: 'net.present',
|
|
409
484
|
};
|
|
@@ -527,7 +602,7 @@ export function evalAnimation(events, p) {
|
|
|
527
602
|
};
|
|
528
603
|
}
|
|
529
604
|
export function evalSignal(events, p) {
|
|
530
|
-
const
|
|
605
|
+
const isMatch = (e) => {
|
|
531
606
|
if (e.type !== EventType.SIGNAL)
|
|
532
607
|
return false;
|
|
533
608
|
if (p.name !== undefined && str(e.data['name']) !== p.name)
|
|
@@ -538,7 +613,22 @@ export function evalSignal(events, p) {
|
|
|
538
613
|
return false;
|
|
539
614
|
}
|
|
540
615
|
return true;
|
|
541
|
-
}
|
|
616
|
+
};
|
|
617
|
+
// `count` (exact) turns presence into a cardinality assertion, exactly as it does on `net`. The
|
|
618
|
+
// double-fire is invisible to every state-only oracle — a handler wired twice leaves the store in
|
|
619
|
+
// the right shape and fires the signal twice — and so is the wrong-name fire, where the intended
|
|
620
|
+
// signal fires once beside a mistyped sibling and a presence check cannot say which is which.
|
|
621
|
+
// Counting only what the MATCHER matched is what separates them. Omit = presence (≥1).
|
|
622
|
+
if (p.count !== undefined) {
|
|
623
|
+
return evalExactCount({
|
|
624
|
+
matched: events.filter(isMatch).length,
|
|
625
|
+
want: p.count,
|
|
626
|
+
noun: 'signal(s)',
|
|
627
|
+
filter: describeSignalFilter(p),
|
|
628
|
+
assertion: 'signal.count',
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
const hit = events.find(isMatch);
|
|
542
632
|
if (hit !== undefined)
|
|
543
633
|
return { pass: true, evidence: hit.data };
|
|
544
634
|
// Near-miss: show signals that fired with the same name (so the agent sees the real data).
|
|
@@ -14,10 +14,4 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
import { PredicateSchema } from './predicate-eval.js';
|
|
17
|
-
/**
|
|
18
|
-
* Parse a predicate, or throw an Error whose message is a sentence.
|
|
19
|
-
*
|
|
20
|
-
* Deliberately still THROWS: every call site already handles a throw, and turning this into a
|
|
21
|
-
* result type would mean touching three handlers to gain nothing the message does not already say.
|
|
22
|
-
*/
|
|
23
17
|
export declare function parsePredicate(input: unknown): z.infer<typeof PredicateSchema>;
|
|
@@ -67,7 +67,35 @@ function describeIssue(issue) {
|
|
|
67
67
|
* Deliberately still THROWS: every call site already handles a throw, and turning this into a
|
|
68
68
|
* result type would mean touching three handlers to gain nothing the message does not already say.
|
|
69
69
|
*/
|
|
70
|
+
/**
|
|
71
|
+
* The placeholder `verify_next` puts in the `until` it suggests, sent back unchanged.
|
|
72
|
+
*
|
|
73
|
+
* The nudge hands the agent a ready-made `act_and_wait` call with `ref` and `action` filled in from
|
|
74
|
+
* the act that dispatched, and leaves the consequence blank on purpose: naming it is the one part
|
|
75
|
+
* only the agent can know, and guessing would be Reticle inventing the assertion.
|
|
76
|
+
*
|
|
77
|
+
* Sent verbatim it parses as a perfectly valid text predicate that simply never matches, so the
|
|
78
|
+
* verdict came back `verified:"no"` / "the declared consequence did not hold" — blaming the app for
|
|
79
|
+
* a placeholder the agent forgot to replace, and sending it to hunt a defect that does not exist.
|
|
80
|
+
* Measured on a live app before this guard: a confident, wrong, actionable-looking answer, which is
|
|
81
|
+
* the exact failure the rest of this file exists to avoid.
|
|
82
|
+
*/
|
|
83
|
+
const UNFILLED_PLACEHOLDER = /^<name the consequence/i;
|
|
84
|
+
function placeholderValue(input) {
|
|
85
|
+
if ('object' !== typeof input || null === input)
|
|
86
|
+
return undefined;
|
|
87
|
+
const value = input['value'];
|
|
88
|
+
return 'string' === typeof value && UNFILLED_PLACEHOLDER.test(value) ? value : undefined;
|
|
89
|
+
}
|
|
70
90
|
export function parsePredicate(input) {
|
|
91
|
+
const unfilled = placeholderValue(input);
|
|
92
|
+
if (unfilled !== undefined) {
|
|
93
|
+
throw new Error(`that predicate still carries the placeholder from verify_next ("${unfilled}"). Nothing ran, ` +
|
|
94
|
+
'and no verdict was produced — which is deliberate: as written it would have failed and ' +
|
|
95
|
+
'blamed the app for a value you had not filled in yet. Replace it with the consequence ' +
|
|
96
|
+
'this action actually causes, and prefer one the action CHANGES: a signal, a request, a ' +
|
|
97
|
+
'route, or store state. Text on screen that was already there proves nothing.');
|
|
98
|
+
}
|
|
71
99
|
const parsed = PredicateSchema.safeParse(input);
|
|
72
100
|
if (parsed.success)
|
|
73
101
|
return parsed.data;
|
|
@@ -78,7 +106,7 @@ export function parsePredicate(input) {
|
|
|
78
106
|
// back is how the zod array became unreadable in the first place.
|
|
79
107
|
const issues = parsed.error.issues.slice(0, 3).map(describeIssue).join('; ');
|
|
80
108
|
throw new Error(`that predicate did not parse (kind "${kind}"): ${issues}. Nothing ran — the predicate was ` +
|
|
81
|
-
`not evaluated, so no verdict was produced. ${accepted(kind)} ` +
|
|
109
|
+
`not evaluated, so no verdict was produced. ${accepted(kind, parsed.error.issues)} ` +
|
|
82
110
|
`A valid ${kind} predicate looks like: ${exampleFor(kind)}`);
|
|
83
111
|
}
|
|
84
112
|
/**
|
|
@@ -88,12 +116,24 @@ export function parsePredicate(input) {
|
|
|
88
116
|
* round trip on the one call path that produces verdicts. Naming the accepted fields — or, when the
|
|
89
117
|
* kind itself is the mistake, the accepted kinds — makes the retry informed instead.
|
|
90
118
|
*/
|
|
91
|
-
function accepted(kind) {
|
|
119
|
+
function accepted(kind, issues) {
|
|
92
120
|
const fields = predicateFieldsFor(kind);
|
|
93
121
|
if (0 < fields.length) {
|
|
94
122
|
// A field whose value is an object is the one an agent cannot guess from its name alone, so
|
|
95
123
|
// expand it in the same breath rather than making the shape a second round trip.
|
|
96
|
-
|
|
124
|
+
//
|
|
125
|
+
// Only the field the rejection actually points at, though. Expanding every object-valued field
|
|
126
|
+
// of the kind makes the sentence longer the more the schema grows, and buries the one clause
|
|
127
|
+
// that answers the question asked — the same argument this file already makes for showing one
|
|
128
|
+
// example per kind instead of a generic one. `element` is the only kind with an object-valued
|
|
129
|
+
// field today, so the two agree except when the mistake is somewhere else entirely, which is
|
|
130
|
+
// the case pinned in the tests.
|
|
131
|
+
const all = predicateNestedFieldsFor(kind);
|
|
132
|
+
const blamed = new Set(issues
|
|
133
|
+
.map((issue) => issue.path[0])
|
|
134
|
+
.filter((field) => 'string' === typeof field));
|
|
135
|
+
const nested = Object.entries(all)
|
|
136
|
+
.filter(([field]) => blamed.has(field))
|
|
97
137
|
.map(([field, keys]) => ` ${field} accepts: ${keys.join(', ')}.`)
|
|
98
138
|
.join('');
|
|
99
139
|
return `${kind} accepts: ${fields.join(', ')}.${nested}`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reason this predicate cannot be evaluated, or undefined when it can.
|
|
3
|
+
*
|
|
4
|
+
* Only checks what is knowable WITHOUT the page — a locator that names fields the resolver drops is
|
|
5
|
+
* wrong whatever the DOM contains, which is what makes it safe to refuse before acting. Anything
|
|
6
|
+
* that depends on what is actually rendered is still decided after the action, where it belongs.
|
|
7
|
+
*/
|
|
8
|
+
export declare function unevaluablePredicateReason(predicate: unknown): string | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { residualQueryChecks } from './predicate-schema.js';
|
|
2
|
+
/** Composite predicates nest; the unusable one can be at any depth. */
|
|
3
|
+
const NESTED_KEYS = ['predicates', 'predicate'];
|
|
4
|
+
const isRecord = (v) => 'object' === typeof v && null !== v;
|
|
5
|
+
/** Every element query in a predicate tree, in the order a reader would meet them. */
|
|
6
|
+
function elementQueries(predicate, found = []) {
|
|
7
|
+
if (!isRecord(predicate))
|
|
8
|
+
return found;
|
|
9
|
+
if (isRecord(predicate['query']))
|
|
10
|
+
found.push(predicate['query']);
|
|
11
|
+
for (const key of NESTED_KEYS) {
|
|
12
|
+
const nested = predicate[key];
|
|
13
|
+
if (Array.isArray(nested))
|
|
14
|
+
for (const child of nested)
|
|
15
|
+
elementQueries(child, found);
|
|
16
|
+
else if (isRecord(nested))
|
|
17
|
+
elementQueries(nested, found);
|
|
18
|
+
}
|
|
19
|
+
return found;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The reason this predicate cannot be evaluated, or undefined when it can.
|
|
23
|
+
*
|
|
24
|
+
* Only checks what is knowable WITHOUT the page — a locator that names fields the resolver drops is
|
|
25
|
+
* wrong whatever the DOM contains, which is what makes it safe to refuse before acting. Anything
|
|
26
|
+
* that depends on what is actually rendered is still decided after the action, where it belongs.
|
|
27
|
+
*/
|
|
28
|
+
export function unevaluablePredicateReason(predicate) {
|
|
29
|
+
for (const query of elementQueries(predicate)) {
|
|
30
|
+
const residual = residualQueryChecks(query);
|
|
31
|
+
if (0 === residual.unusable.length)
|
|
32
|
+
continue;
|
|
33
|
+
return (`the element locator ignores ${residual.unusable.map((f) => `\`${f}\``).join(', ')} ` +
|
|
34
|
+
`in ${JSON.stringify(query)} — it resolves by the first of by+value, component/source, role, ` +
|
|
35
|
+
'text, label, placeholder, testid, alt that is present, and nothing here can check the rest. ' +
|
|
36
|
+
'Assert them one locator at a time, or move the extra field into the locator. ' +
|
|
37
|
+
'Nothing was acted on: this predicate could never have been evaluated, so refusing it costs ' +
|
|
38
|
+
'you nothing and spending the action on it would have cost you the verdict.');
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
@@ -17,6 +17,20 @@ export declare const RouteDecidedBy: {
|
|
|
17
17
|
readonly CHANGE: "route-change";
|
|
18
18
|
readonly CURRENT: "current-route";
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* The path the router is actually on.
|
|
22
|
+
*
|
|
23
|
+
* Under a hash router the document pathname is `/` on every page and the whole route lives in the
|
|
24
|
+
* fragment — which is why `contains` below matches the full route. `pathname` was left comparing the
|
|
25
|
+
* document path, so `{ pathname: '/posts/12/show' }` could never pass on a HashRouter app, the
|
|
26
|
+
* standard router for a packaged Electron/Tauri renderer. That is a guaranteed false red: Reticle
|
|
27
|
+
* telling a user their working app is broken.
|
|
28
|
+
*
|
|
29
|
+
* The fragment WINS rather than being accepted alongside the document path. Honouring both would
|
|
30
|
+
* make `{ pathname: '/' }` trivially true on every hash-routed page — a green that says nothing
|
|
31
|
+
* about where the app is, which is the failure this predicate exists to prevent.
|
|
32
|
+
*/
|
|
33
|
+
export declare function routePathOf(pathname: string, hash: string): string;
|
|
20
34
|
export declare function evalRoute(events: ReticleEvent[], p: Extract<Predicate, {
|
|
21
35
|
kind: typeof PredicateKind.ROUTE;
|
|
22
36
|
}>,
|
|
@@ -17,6 +17,26 @@ export const RouteDecidedBy = {
|
|
|
17
17
|
CHANGE: 'route-change',
|
|
18
18
|
CURRENT: 'current-route',
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* The path the router is actually on.
|
|
22
|
+
*
|
|
23
|
+
* Under a hash router the document pathname is `/` on every page and the whole route lives in the
|
|
24
|
+
* fragment — which is why `contains` below matches the full route. `pathname` was left comparing the
|
|
25
|
+
* document path, so `{ pathname: '/posts/12/show' }` could never pass on a HashRouter app, the
|
|
26
|
+
* standard router for a packaged Electron/Tauri renderer. That is a guaranteed false red: Reticle
|
|
27
|
+
* telling a user their working app is broken.
|
|
28
|
+
*
|
|
29
|
+
* The fragment WINS rather than being accepted alongside the document path. Honouring both would
|
|
30
|
+
* make `{ pathname: '/' }` trivially true on every hash-routed page — a green that says nothing
|
|
31
|
+
* about where the app is, which is the failure this predicate exists to prevent.
|
|
32
|
+
*/
|
|
33
|
+
export function routePathOf(pathname, hash) {
|
|
34
|
+
if (!hash.startsWith('#/'))
|
|
35
|
+
return pathname;
|
|
36
|
+
const withoutHash = hash.slice(1);
|
|
37
|
+
const query = withoutHash.indexOf('?');
|
|
38
|
+
return -1 === query ? withoutHash : withoutHash.slice(0, query);
|
|
39
|
+
}
|
|
20
40
|
/**
|
|
21
41
|
* The session's live URL, split the way a route-change event already carries it.
|
|
22
42
|
*
|
|
@@ -30,6 +50,7 @@ function readCurrentRoute(url) {
|
|
|
30
50
|
const hash = parsed?.hash ?? '';
|
|
31
51
|
return {
|
|
32
52
|
pathname,
|
|
53
|
+
routePath: routePathOf(pathname, hash),
|
|
33
54
|
full: `${pathname}${search}${hash}`,
|
|
34
55
|
decidedBy: RouteDecidedBy.CURRENT,
|
|
35
56
|
data: { pathname, search, hash, url, decidedBy: RouteDecidedBy.CURRENT },
|
|
@@ -55,12 +76,17 @@ currentUrl) {
|
|
|
55
76
|
const routes = events.filter((e) => e.type === EventType.ROUTE_CHANGE);
|
|
56
77
|
const last = routes.at(-1);
|
|
57
78
|
const reading = last !== undefined
|
|
58
|
-
? {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
? (() => {
|
|
80
|
+
const changedPath = str(last.data['pathname']) ?? str(last.data['to']) ?? '';
|
|
81
|
+
const changedHash = str(last.data['hash']) ?? '';
|
|
82
|
+
return {
|
|
83
|
+
pathname: changedPath,
|
|
84
|
+
routePath: routePathOf(changedPath, changedHash),
|
|
85
|
+
full: `${changedPath}${str(last.data['search']) ?? ''}${changedHash}`,
|
|
86
|
+
decidedBy: RouteDecidedBy.CHANGE,
|
|
87
|
+
data: { ...last.data, decidedBy: RouteDecidedBy.CHANGE },
|
|
88
|
+
};
|
|
89
|
+
})()
|
|
64
90
|
: currentUrl === undefined || 0 === currentUrl.length
|
|
65
91
|
? undefined
|
|
66
92
|
: readCurrentRoute(currentUrl);
|
|
@@ -73,11 +99,13 @@ currentUrl) {
|
|
|
73
99
|
assertion: 'route.changed',
|
|
74
100
|
};
|
|
75
101
|
}
|
|
76
|
-
|
|
102
|
+
// The ROUTER's path, not the document's — see routePathOf.
|
|
103
|
+
const pathname = reading.routePath;
|
|
77
104
|
if (p.pathname !== undefined && pathname !== p.pathname) {
|
|
78
105
|
return {
|
|
79
106
|
pass: false,
|
|
80
|
-
|
|
107
|
+
// No ` is` after the clause: it read "route changed to is '/'".
|
|
108
|
+
failureReason: `${describeRouteSource(reading)} '${pathname}', expected '${p.pathname}'`,
|
|
81
109
|
observed: `${describeRouteSource(reading)} '${pathname}'`,
|
|
82
110
|
expected: `route '${p.pathname}'`,
|
|
83
111
|
assertion: 'route.pathname',
|
|
@@ -56,6 +56,11 @@ export type Predicate = {
|
|
|
56
56
|
kind: typeof PredicateKind.SIGNAL;
|
|
57
57
|
name?: string;
|
|
58
58
|
dataMatches?: Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Exact number of matching signals in the window — the same cardinality assertion `net`
|
|
61
|
+
* carries, on the channel the app itself speaks. Omit for presence (≥1).
|
|
62
|
+
*/
|
|
63
|
+
count?: number;
|
|
59
64
|
since?: number;
|
|
60
65
|
} | {
|
|
61
66
|
kind: typeof PredicateKind.STATE;
|
|
@@ -103,6 +108,45 @@ export declare const PredicateSchema: z.ZodType<Predicate>;
|
|
|
103
108
|
* wall. Empty for a kind that is not in the union.
|
|
104
109
|
*/
|
|
105
110
|
export declare function predicateFieldsFor(kind: string): readonly string[];
|
|
111
|
+
/**
|
|
112
|
+
* Is this tool parameter ACTUALLY the predicate union?
|
|
113
|
+
*
|
|
114
|
+
* Not a name check. `until` is overloaded on this surface — the act/assert family means a predicate
|
|
115
|
+
* by it, while reticle_observe / _network / _console mean a NUMBER, an upper cursor bound — so any
|
|
116
|
+
* caller keying on the word would treat a numeric parameter as a predicate and describe it to the
|
|
117
|
+
* agent as an object. Keying on the schema cannot make that mistake.
|
|
118
|
+
*
|
|
119
|
+
* Lives beside the schema because two callers now ask the question: the lean tool surface, which
|
|
120
|
+
* compacts these parameters, and `reticle_tools`, which spells their grammar out on request. Two
|
|
121
|
+
* spellings of "is this a predicate" is one chance for the surface and the grammar to disagree.
|
|
122
|
+
*/
|
|
123
|
+
export declare function isPredicateParam(schema: z.ZodTypeAny): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Every kind's fields, in one block an agent can write a predicate from.
|
|
126
|
+
*
|
|
127
|
+
* The tool surface advertises the KIND list and points at `reticle_tools` for the fields, because
|
|
128
|
+
* inlining the 12-variant recursive union in the declared JSON Schema costs thousands of characters
|
|
129
|
+
* per predicate parameter, re-sent every turn, to describe a grammar most calls use one variant of.
|
|
130
|
+
* That trade is right — but the pointer has to land somewhere, and it landed on a parameter
|
|
131
|
+
* description reading "same shape as reticle_assert". So the grammar was reachable from nothing, and
|
|
132
|
+
* an agent that could not find `route`'s fields fell back to a `text` check where a route check was
|
|
133
|
+
* meant: the weaker oracle, which is the expensive half of an undiscoverable grammar.
|
|
134
|
+
*
|
|
135
|
+
* Derived from the schema, like `predicateFieldsFor` and for the same reason: a hand-written grammar
|
|
136
|
+
* that drifts is worse than none, because the agent trusts it and retries into the same wall.
|
|
137
|
+
*/
|
|
138
|
+
export declare function predicateGrammar(): Readonly<Record<string, string>>;
|
|
139
|
+
/**
|
|
140
|
+
* The keys of a field that is an object, once its wrappers are peeled. Empty for anything else.
|
|
141
|
+
*
|
|
142
|
+
* Exported so the wrapper handling is asserted against THIS function rather than through the
|
|
143
|
+
* rendered sentence. Reached only that way, a wrapper it fails to peel returns `[]`, the old
|
|
144
|
+
* message prints unchanged, and nothing reddens — a regression with no symptom.
|
|
145
|
+
*
|
|
146
|
+
* No top-level predicate field is currently declared behind a wrapper, so the real schema exercises
|
|
147
|
+
* none of these paths; that is exactly why they are worth a case each.
|
|
148
|
+
*/
|
|
149
|
+
export declare function nestedKeysOf(schema: z.ZodTypeAny | undefined): readonly string[];
|
|
106
150
|
/**
|
|
107
151
|
* One level into the fields that are themselves objects — `{ query: ['by', 'value', ...] }`.
|
|
108
152
|
*
|