@reticlehq/server 2.13.1 → 2.14.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 +8 -18
- package/bin/reticle.js +3 -0
- package/dist/bridge/auth-failure-reason.js +12 -1
- package/dist/bridge/bridge-security.d.ts +8 -0
- package/dist/bridge/bridge-security.js +6 -1
- package/dist/bridge/bridge.d.ts +3 -0
- package/dist/bridge/bridge.js +23 -1
- package/dist/bridge/bridge.test-harness.d.ts +10 -1
- package/dist/bridge/bridge.test-harness.js +7 -1
- package/dist/bridge/pairing-token.d.ts +17 -0
- package/dist/bridge/pairing-token.js +26 -0
- package/dist/bridge/token-auth.d.ts +7 -0
- package/dist/bridge/token-auth.js +7 -1
- package/dist/capsule/capsule-store.d.ts +4 -4
- package/dist/capsule/capsule.d.ts +1 -1
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +21 -1
- package/dist/capsule/causal-summary.js +8 -6
- package/dist/capsule/divergence.d.ts +1 -1
- package/dist/capsule/divergence.js +26 -10
- package/dist/chromium-launch-options.d.ts +2 -1
- package/dist/cli/adhoc-verdict.d.ts +58 -0
- package/dist/cli/adhoc-verdict.js +115 -0
- package/dist/cli/chromium-hint.d.ts +18 -4
- package/dist/cli/chromium-hint.js +54 -6
- package/dist/cli/cli-doctor.js +1 -2
- package/dist/cli/cli-flow-commands.d.ts +16 -2
- package/dist/cli/cli-flow-commands.js +28 -9
- package/dist/cli/cli-parse-feedback.js +1 -1
- package/dist/cli/cli-parse.d.ts +2 -1
- package/dist/cli/cli-parse.js +38 -0
- package/dist/cli/cli-port.d.ts +5 -13
- package/dist/cli/cli-port.js +5 -38
- package/dist/cli/cli-update-commands.js +4 -4
- package/dist/cli/cli-verify.d.ts +24 -7
- package/dist/cli/cli-verify.js +87 -13
- package/dist/cli/cloud-keystore.d.ts +2 -1
- package/dist/cli/cloud-login.d.ts +2 -1
- package/dist/cli/cloud-session.d.ts +2 -1
- package/dist/cli/cloud-session.js +1 -1
- package/dist/cli/daemon-start-options.d.ts +2 -1
- package/dist/cli/device-flow.d.ts +2 -2
- package/dist/cli/doctor-daemon-line.d.ts +2 -1
- package/dist/cli/doctor-project-line.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +2 -1
- package/dist/cli/drive-attach.d.ts +4 -3
- package/dist/cli/gate-hook-message.d.ts +2 -1
- package/dist/cli/status-next-action.js +10 -11
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +21 -9
- package/dist/cloud/account-state.d.ts +20 -0
- package/dist/cloud/account-state.js +65 -0
- package/dist/cloud/cloud-sync.d.ts +46 -3
- package/dist/cloud/cloud-sync.js +116 -0
- package/dist/cloud/sync-cycle.d.ts +5 -4
- package/dist/cloud/sync-cycle.js +1 -1
- package/dist/cloud/sync-daemon.d.ts +3 -19
- package/dist/cloud/sync-daemon.js +2 -2
- package/dist/crawl/crawl-tools.js +1 -1
- package/dist/crawl/crawl.d.ts +5 -5
- package/dist/crawl/crawl.js +138 -4
- package/dist/daemon/daemon-resilience.d.ts +3 -2
- package/dist/daemon/daemon-resilience.js +43 -4
- package/dist/daemon/daemon-resolve.d.ts +0 -9
- package/dist/daemon/daemon-resolve.js +2 -2
- package/dist/daemon/dev-servers.d.ts +1 -1
- package/dist/daemon/dev-servers.js +1 -1
- package/dist/daemon/loopback-alias.d.ts +2 -1
- package/dist/daemon/startup-failure.d.ts +11 -0
- package/dist/daemon/startup-failure.js +46 -0
- package/dist/events/contradictions.d.ts +10 -16
- package/dist/events/contradictions.js +149 -17
- package/dist/events/declared.d.ts +13 -1
- package/dist/events/declared.js +59 -3
- package/dist/events/echo-mismatch.js +75 -6
- package/dist/events/lineage.d.ts +52 -0
- package/dist/events/lineage.js +122 -0
- package/dist/events/net-evidence.d.ts +21 -0
- package/dist/events/net-evidence.js +81 -0
- package/dist/events/predicate-console.d.ts +13 -0
- package/dist/events/predicate-console.js +138 -0
- package/dist/events/predicate-element.d.ts +13 -0
- package/dist/events/predicate-element.js +214 -0
- package/dist/events/predicate-eval.d.ts +15 -3
- package/dist/events/predicate-eval.js +128 -110
- package/dist/events/predicate-parse.js +34 -1
- package/dist/events/predicate-request-body.d.ts +50 -0
- package/dist/events/predicate-request-body.js +120 -0
- package/dist/events/predicate-route.d.ts +21 -1
- package/dist/events/predicate-route.js +149 -21
- package/dist/events/predicate-schema.d.ts +8 -1
- package/dist/events/predicate-schema.js +34 -0
- package/dist/events/predicate.d.ts +2 -0
- package/dist/events/predicate.js +52 -159
- package/dist/events/split-text-miss.d.ts +6 -4
- package/dist/events/split-text-miss.js +12 -5
- package/dist/flows/affected-tools.js +22 -9
- package/dist/flows/annotate.d.ts +2 -1
- package/dist/flows/annotate.js +19 -1
- package/dist/flows/coverage.d.ts +2 -2
- package/dist/flows/flake.d.ts +3 -3
- package/dist/flows/flow-expect-grammar.d.ts +33 -0
- package/dist/flows/flow-expect-grammar.js +155 -0
- package/dist/flows/flow-replay-run.d.ts +1 -1
- package/dist/flows/flow-replay-run.js +28 -15
- package/dist/flows/flow-replay.d.ts +10 -0
- package/dist/flows/flow-replay.js +57 -22
- package/dist/flows/flow-secret-field.d.ts +14 -0
- package/dist/flows/flow-secret-field.js +22 -0
- package/dist/flows/flow-step-runners.d.ts +1 -0
- package/dist/flows/flow-step-runners.js +91 -34
- package/dist/flows/flow-success.js +22 -0
- package/dist/flows/flow-tools.js +27 -8
- package/dist/flows/flow-upload-resolve.d.ts +23 -0
- package/dist/flows/flow-upload-resolve.js +46 -0
- package/dist/flows/flows.d.ts +15 -0
- package/dist/flows/flows.js +17 -14
- package/dist/flows/git-changed.d.ts +23 -4
- package/dist/flows/git-changed.js +32 -6
- package/dist/flows/heal-run.js +2 -2
- package/dist/flows/predicate-to-expect.d.ts +3 -2
- package/dist/flows/predicate-to-expect.js +11 -5
- package/dist/flows/recording-backtrack.d.ts +24 -0
- package/dist/flows/recording-backtrack.js +73 -0
- package/dist/flows/recordings.d.ts +6 -0
- package/dist/flows/replay-halt.d.ts +26 -0
- package/dist/flows/replay-halt.js +30 -0
- package/dist/flows/replay.d.ts +0 -8
- package/dist/flows/replay.js +15 -3
- package/dist/flows/suite-flakes.d.ts +2 -1
- package/dist/flows/verify-change-tools.js +16 -4
- package/dist/honesty/already-true.d.ts +14 -0
- package/dist/honesty/already-true.js +47 -4
- package/dist/honesty/blind-spots.d.ts +3 -1
- package/dist/honesty/blind-spots.js +24 -1
- package/dist/honesty/body-capture-remedy.d.ts +42 -0
- package/dist/honesty/body-capture-remedy.js +95 -0
- package/dist/honesty/feature-capture.d.ts +3 -2
- package/dist/honesty/feature-capture.js +1 -1
- package/dist/honesty/gap-ledger.d.ts +1 -1
- package/dist/honesty/gap-ledger.js +1 -1
- package/dist/honesty/instrumentation-gaps.d.ts +8 -1
- package/dist/honesty/instrumentation-gaps.js +12 -2
- package/dist/honesty/observability.d.ts +4 -3
- package/dist/honesty/tool-hit-rate.d.ts +3 -2
- package/dist/honesty/uncaptured-bodies.d.ts +3 -1
- package/dist/honesty/uncaptured-bodies.js +6 -2
- package/dist/honesty/unsettled.d.ts +9 -0
- package/dist/honesty/unsettled.js +45 -0
- package/dist/honesty/verified.d.ts +32 -1
- package/dist/honesty/verified.js +37 -10
- package/dist/impact/impact-store.d.ts +5 -14
- package/dist/impact/impact-store.js +49 -6
- package/dist/index.js +1 -1
- package/dist/input/network-detail.d.ts +26 -1
- package/dist/input/network-detail.js +44 -12
- package/dist/input/network-mock-tools.js +24 -17
- package/dist/input/real-input.js +1 -1
- package/dist/input/viewport-tools.js +25 -13
- package/dist/intent/inline-intent.d.ts +0 -10
- package/dist/intent/inline-intent.js +1 -1
- package/dist/intent/intent-shard-store.d.ts +3 -2
- package/dist/intent/intent-shard-store.js +1 -1
- package/dist/intent/intent-shard.d.ts +14 -14
- package/dist/intent/intent-shard.js +4 -4
- package/dist/intent/intent-store.d.ts +1 -1
- package/dist/intent/intent-store.js +1 -1
- package/dist/intent/intent-subject.d.ts +2 -1
- package/dist/intent/open-intents.d.ts +3 -2
- package/dist/license/license.d.ts +4 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/attach-memory.d.ts +2 -1
- package/dist/mcp/client-identity.d.ts +2 -1
- package/dist/mcp/mcp-outage.d.ts +1 -1
- package/dist/mcp/mcp-outage.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +3 -1
- package/dist/mcp/mcp-proxy.js +32 -7
- package/dist/mcp/mcp.js +3 -1
- package/dist/mcp/proxy-handshake.d.ts +2 -1
- package/dist/mcp/proxy-idle-exit.d.ts +45 -0
- package/dist/mcp/proxy-idle-exit.js +94 -0
- package/dist/mcp/server-instructions.d.ts +2 -1
- package/dist/memory/project-memory.d.ts +2 -3
- package/dist/memory/project-memory.js +1 -1
- package/dist/oracles/propose-consequences.js +5 -5
- package/dist/oracles/self-instrument.d.ts +2 -2
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/platform.d.ts +3 -5
- package/dist/platform.js +3 -5
- package/dist/pool/browser-pool.d.ts +92 -0
- package/dist/pool/browser-pool.js +119 -5
- package/dist/pool/playwright-launcher.js +29 -2
- package/dist/pool/storage-seed.d.ts +23 -0
- package/dist/pool/storage-seed.js +198 -0
- package/dist/project/artifact-root.d.ts +2 -1
- package/dist/project/memory-fs.d.ts +2 -1
- package/dist/project/reticle-dir.js +1 -1
- package/dist/project/session-root.d.ts +8 -0
- package/dist/project/session-root.js +9 -2
- package/dist/runs/reticle-runner.d.ts +14 -1
- package/dist/runs/reticle-runner.js +39 -2
- package/dist/session/attachment-history.d.ts +2 -1
- package/dist/session/fake-session.d.ts +37 -0
- package/dist/session/fake-session.js +81 -0
- package/dist/session/lease-visibility.d.ts +2 -1
- package/dist/session/live-control-tools.js +6 -0
- package/dist/session/no-session-diagnosis.d.ts +17 -1
- package/dist/session/no-session-diagnosis.js +32 -6
- package/dist/session/no-session-next-action.d.ts +14 -0
- package/dist/session/no-session-next-action.js +18 -0
- package/dist/session/no-session-watch.js +46 -3
- package/dist/session/registered-projects.d.ts +54 -0
- package/dist/session/registered-projects.js +73 -0
- package/dist/session/review-store.d.ts +2 -1
- package/dist/session/session-health.d.ts +62 -1
- package/dist/session/session-health.js +82 -2
- package/dist/session/session-manager.d.ts +11 -1
- package/dist/session/session-manager.js +18 -3
- package/dist/session/session-replaced.d.ts +24 -0
- package/dist/session/session-replaced.js +29 -0
- package/dist/session/session-successor.js +20 -4
- package/dist/session/session.d.ts +13 -5
- package/dist/session/session.js +31 -23
- package/dist/setup/agent-configs.d.ts +3 -2
- package/dist/setup/agent-writer.d.ts +3 -2
- package/dist/setup/approval-migration.d.ts +2 -1
- package/dist/setup/approval-migration.js +1 -1
- package/dist/setup/auto-approve.d.ts +11 -19
- package/dist/setup/auto-approve.js +15 -12
- package/dist/{init → setup}/confirm.js +1 -1
- package/dist/setup/desktop-shape.d.ts +3 -2
- package/dist/setup/drive-agent.d.ts +2 -3
- package/dist/setup/drive-agent.js +1 -1
- package/dist/setup/drive-plan.d.ts +3 -2
- package/dist/setup/ensure-daemon.d.ts +2 -1
- package/dist/setup/existing-app.d.ts +12 -0
- package/dist/setup/existing-app.js +13 -0
- package/dist/setup/init-host.d.ts +11 -0
- package/dist/setup/init-host.js +26 -0
- package/dist/setup/init-runtime.d.ts +4 -10
- package/dist/setup/init-runtime.js +23 -16
- package/dist/setup/license-key.d.ts +2 -1
- package/dist/setup/listeners.d.ts +2 -1
- package/dist/setup/loopback-probe-urls.d.ts +18 -0
- package/dist/setup/loopback-probe-urls.js +52 -0
- package/dist/setup/node-effects.d.ts +3 -0
- package/dist/setup/node-effects.js +26 -11
- package/dist/setup/page-probe.d.ts +5 -0
- package/dist/setup/relaunch.d.ts +2 -1
- package/dist/setup/run-setup.d.ts +5 -1
- package/dist/setup/run-setup.js +141 -50
- package/dist/setup/setup-command.d.ts +3 -2
- package/dist/setup/setup-command.js +8 -0
- package/dist/setup/setup-options.d.ts +5 -3
- package/dist/setup/setup-options.js +3 -2
- package/dist/setup/wait-progress.d.ts +28 -0
- package/dist/setup/wait-progress.js +43 -0
- package/dist/telemetry/app-instrumented.d.ts +2 -1
- package/dist/telemetry/app-instrumented.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +1 -1
- package/dist/telemetry/automation-hint.js +1 -1
- package/dist/telemetry/cli-telemetry.js +1 -1
- package/dist/telemetry/connect-failure.d.ts +1 -1
- package/dist/telemetry/connect-failure.js +1 -1
- package/dist/telemetry/crash-cause.d.ts +3 -2
- package/dist/telemetry/crash-cause.js +1 -1
- package/dist/telemetry/daemon-telemetry.d.ts +3 -2
- package/dist/telemetry/daemon-telemetry.js +1 -1
- package/dist/telemetry/error-class.d.ts +3 -2
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-cli.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +3 -2
- package/dist/telemetry/feedback-context.js +5 -4
- package/dist/telemetry/feedback-local.d.ts +3 -2
- package/dist/telemetry/feedback.d.ts +4 -3
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/git-facts.d.ts +2 -2
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/identify.d.ts +1 -1
- package/dist/telemetry/identify.js +1 -1
- package/dist/telemetry/init-telemetry.d.ts +2 -14
- package/dist/telemetry/init-telemetry.js +4 -13
- package/dist/telemetry/install-source.d.ts +7 -13
- package/dist/telemetry/install-source.js +7 -29
- package/dist/telemetry/license-activation.d.ts +3 -2
- package/dist/telemetry/license-activation.js +1 -1
- package/dist/telemetry/machine-snapshot.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +1 -1
- package/dist/telemetry/project-profile.d.ts +0 -14
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/proxy-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +1 -17
- package/dist/telemetry/telemetry.js +4 -4
- package/dist/telemetry/tool-refused.d.ts +1 -1
- package/dist/telemetry/tool-refused.js +1 -1
- package/dist/tools/act-observation.d.ts +3 -2
- package/dist/tools/act-preflight.js +8 -3
- package/dist/tools/act-target.d.ts +4 -0
- package/dist/tools/act-target.js +14 -5
- package/dist/tools/act-tools.d.ts +0 -2
- package/dist/tools/act-tools.js +45 -15
- package/dist/tools/assert-source.d.ts +30 -0
- package/dist/tools/assert-source.js +21 -0
- package/dist/tools/assert-verdict.d.ts +1 -1
- package/dist/tools/assert-verdict.js +14 -4
- package/dist/tools/browser-tools.js +28 -8
- package/dist/tools/complete-snapshot.d.ts +3 -2
- package/dist/tools/dynamic-tools.js +8 -0
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +23 -0
- package/dist/tools/feedback-tools.js +1 -1
- package/dist/tools/invoke-tool.js +2 -0
- package/dist/tools/lease-tools.d.ts +48 -20
- package/dist/tools/lease-tools.js +311 -76
- package/dist/tools/lineage-tools.d.ts +14 -0
- package/dist/tools/lineage-tools.js +64 -0
- package/dist/tools/merge-tools.js +1 -3
- package/dist/tools/navigate-arrival.d.ts +45 -1
- package/dist/tools/navigate-arrival.js +39 -6
- package/dist/tools/navigate-result.d.ts +8 -1
- package/dist/tools/navigate-result.js +14 -4
- package/dist/tools/numeric-bounds.d.ts +0 -11
- package/dist/tools/numeric-bounds.js +4 -4
- package/dist/tools/observe-tools.js +22 -26
- package/dist/tools/observed-nothing.d.ts +9 -0
- package/dist/tools/observed-nothing.js +2 -1
- package/dist/tools/read-tools.js +12 -17
- package/dist/tools/real-input-attempt.d.ts +14 -6
- package/dist/tools/real-input-attempt.js +75 -18
- package/dist/tools/reconcile-tools.js +2 -1
- package/dist/tools/reload-result.d.ts +6 -1
- package/dist/tools/reload-result.js +7 -2
- package/dist/tools/resolve-target.d.ts +7 -1
- package/dist/tools/resolve-target.js +72 -9
- package/dist/tools/settle-in-flight.d.ts +1 -1
- package/dist/tools/settle-in-flight.js +9 -2
- package/dist/tools/snapshot-delta.d.ts +24 -1
- package/dist/tools/snapshot-delta.js +66 -7
- package/dist/tools/tool-names.d.ts +1 -0
- package/dist/tools/tool-names.js +1 -0
- package/dist/tools/tool-surface.js +20 -2
- package/dist/tools/tools.js +120 -11
- package/dist/tools/verify-next-baton.d.ts +2 -1
- package/dist/update/sdk-sync.d.ts +1 -1
- package/dist/update/sdk-sync.js +1 -1
- package/dist/update/updater.js +1 -1
- package/dist/version/sdk-fix.d.ts +2 -1
- package/dist/version/version-skew.d.ts +0 -8
- package/dist/version/version-skew.js +1 -1
- package/docs/README.md +8 -6
- package/docs/actions.mdx +1 -1
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +1 -1
- package/docs/cli/mcp.mdx +3 -1
- package/docs/cli/watch.mdx +1 -1
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +8 -3
- package/docs/faq.mdx +9 -0
- package/docs/first-drive.md +136 -0
- package/docs/frameworks.mdx +29 -7
- package/docs/gate-plan.md +2 -2
- package/docs/gates.md +19 -11
- package/docs/getting-started.md +7 -1
- package/docs/http-transport.md +164 -0
- package/docs/index.mdx +5 -3
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +15 -3
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +2 -0
- package/docs/system-map.md +2 -2
- package/docs/telemetry-contract.md +4 -4
- package/docs/telemetry-events.mdx +1 -1
- package/docs/telemetry.md +3 -3
- package/docs/tools-navigate.mdx +14 -6
- package/docs/usage.md +4 -4
- package/package.json +7 -4
- package/dist/init/agent-rules.d.ts +0 -116
- package/dist/init/agent-rules.js +0 -279
- package/dist/init/app-choice.d.ts +0 -20
- package/dist/init/app-choice.js +0 -25
- package/dist/init/astro-host.d.ts +0 -21
- package/dist/init/astro-host.js +0 -27
- package/dist/init/astro-patch.d.ts +0 -33
- package/dist/init/astro-patch.js +0 -300
- package/dist/init/capabilities.d.ts +0 -40
- package/dist/init/capabilities.js +0 -150
- package/dist/init/closing-hint.d.ts +0 -36
- package/dist/init/closing-hint.js +0 -120
- package/dist/init/connect-steps.d.ts +0 -2
- package/dist/init/connect-steps.js +0 -50
- package/dist/init/cra.d.ts +0 -79
- package/dist/init/cra.js +0 -178
- package/dist/init/csp-check.d.ts +0 -38
- package/dist/init/csp-check.js +0 -107
- package/dist/init/csp-doctor.d.ts +0 -32
- package/dist/init/csp-doctor.js +0 -54
- package/dist/init/desktop-doctor.d.ts +0 -41
- package/dist/init/desktop-doctor.js +0 -198
- package/dist/init/detect.d.ts +0 -96
- package/dist/init/detect.js +0 -209
- package/dist/init/dev-script.d.ts +0 -50
- package/dist/init/dev-script.js +0 -66
- package/dist/init/format-generated.d.ts +0 -18
- package/dist/init/format-generated.js +0 -39
- package/dist/init/install-hint.d.ts +0 -9
- package/dist/init/install-hint.js +0 -29
- package/dist/init/mcp-clients.d.ts +0 -89
- package/dist/init/mcp-clients.js +0 -299
- package/dist/init/mcp-registered.d.ts +0 -12
- package/dist/init/mcp-registered.js +0 -14
- package/dist/init/mcp.d.ts +0 -89
- package/dist/init/mcp.js +0 -129
- package/dist/init/next-patch.d.ts +0 -39
- package/dist/init/next-patch.js +0 -196
- package/dist/init/node-io.d.ts +0 -7
- package/dist/init/node-io.js +0 -117
- package/dist/init/non-js-project.d.ts +0 -27
- package/dist/init/non-js-project.js +0 -84
- package/dist/init/patch-kind.d.ts +0 -20
- package/dist/init/patch-kind.js +0 -10
- package/dist/init/plan-framework.d.ts +0 -64
- package/dist/init/plan-framework.js +0 -565
- package/dist/init/plan.d.ts +0 -242
- package/dist/init/plan.js +0 -703
- package/dist/init/preflight.d.ts +0 -28
- package/dist/init/preflight.js +0 -38
- package/dist/init/project-id.d.ts +0 -12
- package/dist/init/project-id.js +0 -25
- package/dist/init/refresh-rules.d.ts +0 -39
- package/dist/init/refresh-rules.js +0 -51
- package/dist/init/run.d.ts +0 -117
- package/dist/init/run.js +0 -806
- package/dist/init/slash-command.d.ts +0 -27
- package/dist/init/slash-command.js +0 -132
- package/dist/init/snippets.d.ts +0 -134
- package/dist/init/snippets.js +0 -572
- package/dist/init/vite-config.d.ts +0 -18
- package/dist/init/vite-config.js +0 -114
- package/dist/init/windows-quote.d.ts +0 -38
- package/dist/init/windows-quote.js +0 -70
- package/dist/init/workspace-apps.d.ts +0 -30
- package/dist/init/workspace-apps.js +0 -178
- package/dist/project/remember-project.d.ts +0 -24
- package/dist/project/remember-project.js +0 -40
- /package/dist/{init → setup}/confirm.d.ts +0 -0
|
@@ -10,9 +10,18 @@
|
|
|
10
10
|
* Bounded and best-effort by construction: a navigation to a page that is not instrumented, or not
|
|
11
11
|
* there at all, must still return promptly with `confirmed:false` rather than hanging. The clock and
|
|
12
12
|
* the sleep are injected so this is testable without waiting on a real one.
|
|
13
|
+
*
|
|
14
|
+
* The bound is the CALLER'S to set. It was a fixed 5s that `reticle_navigate` did not expose, and a
|
|
15
|
+
* Nuxt SPA reattaching under HMR was measured coming back in 30–60s — so every navigation to it
|
|
16
|
+
* answered `confirmed:false` while the app was still on its way, and nothing the agent could pass
|
|
17
|
+
* would make the daemon wait. `assert` / `wait_for` / `act_and_wait` all spend the caller's
|
|
18
|
+
* `timeout_ms` (resolve-within.ts); this is the same budget applied to the same wait.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The default when the caller gives no `timeout_ms`: long enough for a dev server to serve a page
|
|
22
|
+
* and the SDK to dial back; short enough to not hang. A caller who knows the app is slower says so.
|
|
13
23
|
*/
|
|
14
|
-
|
|
15
|
-
const ARRIVAL_TIMEOUT_MS = 5_000;
|
|
24
|
+
export const ARRIVAL_TIMEOUT_MS = 5_000;
|
|
16
25
|
const POLL_MS = 100;
|
|
17
26
|
/**
|
|
18
27
|
* Compare only origin + pathname.
|
|
@@ -32,12 +41,36 @@ function samePage(a, b) {
|
|
|
32
41
|
return false;
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The sessions already on `target` — the set `ArrivalScope.priorIds` is built from, sampled before
|
|
46
|
+
* the navigation is dispatched.
|
|
47
|
+
*
|
|
48
|
+
* Shares `samePage` with the arrival scan on purpose: "was it already there" and "is it there now"
|
|
49
|
+
* are the same question asked at two moments, and two spellings of it would eventually disagree.
|
|
50
|
+
*/
|
|
51
|
+
export function idsAtTarget(sessions, target) {
|
|
52
|
+
const ids = new Set();
|
|
36
53
|
for (const s of sessions.all()) {
|
|
37
54
|
if (samePage(s.url, target))
|
|
55
|
+
ids.add(s.id);
|
|
56
|
+
}
|
|
57
|
+
return ids;
|
|
58
|
+
}
|
|
59
|
+
function findArrival(sessions, target, scope) {
|
|
60
|
+
let arrived = null;
|
|
61
|
+
for (const s of sessions.all()) {
|
|
62
|
+
if (!samePage(s.url, target))
|
|
63
|
+
continue;
|
|
64
|
+
// The tab we drove is the answer whenever it is there, not a tie broken by scan order.
|
|
65
|
+
if (s.id === scope.navigatedId)
|
|
38
66
|
return { sessionId: s.id };
|
|
67
|
+
if (scope.priorIds.has(s.id))
|
|
68
|
+
continue;
|
|
69
|
+
// Keep looking rather than returning: the driven session may still be ahead of us.
|
|
70
|
+
if (null === arrived)
|
|
71
|
+
arrived = { sessionId: s.id };
|
|
39
72
|
}
|
|
40
|
-
return
|
|
73
|
+
return arrived;
|
|
41
74
|
}
|
|
42
75
|
const REAL_CLOCK = {
|
|
43
76
|
now: () => Date.now(),
|
|
@@ -47,10 +80,10 @@ const REAL_CLOCK = {
|
|
|
47
80
|
* Poll until a session is present at `target`, or the budget runs out. Returns `null` on timeout —
|
|
48
81
|
* never throws, because a failure to confirm is a legitimate answer, not an error.
|
|
49
82
|
*/
|
|
50
|
-
export async function awaitArrival(sessions, target, timeoutMs = ARRIVAL_TIMEOUT_MS, clock = REAL_CLOCK) {
|
|
83
|
+
export async function awaitArrival(sessions, target, scope, timeoutMs = ARRIVAL_TIMEOUT_MS, clock = REAL_CLOCK) {
|
|
51
84
|
const deadline = clock.now() + timeoutMs;
|
|
52
85
|
for (;;) {
|
|
53
|
-
const found = findArrival(sessions, target);
|
|
86
|
+
const found = findArrival(sessions, target, scope);
|
|
54
87
|
if (found !== null)
|
|
55
88
|
return found;
|
|
56
89
|
if (clock.now() >= deadline)
|
|
@@ -28,4 +28,11 @@ export declare function navigateResult(result: {
|
|
|
28
28
|
* one place that CAN answer this — the SDK reconnects to it — so making it answer removes a
|
|
29
29
|
* `reticle_sessions` poll from every single navigation.
|
|
30
30
|
*/
|
|
31
|
-
arrival
|
|
31
|
+
arrival: NavigateArrival | null,
|
|
32
|
+
/**
|
|
33
|
+
* The budget the daemon spent waiting for arrival, in ms. Reported on `confirmed:false` so the
|
|
34
|
+
* agent can tell "Reticle stopped waiting" from "the page never came back": the first is fixed by
|
|
35
|
+
* asking for a longer `timeout_ms`, the second is not. Without it the two read identically, and a
|
|
36
|
+
* slow SPA (30–60s to reattach under HMR) looked like a failed navigation on every call.
|
|
37
|
+
*/
|
|
38
|
+
waitedMs: number): Record<string, unknown>;
|
|
@@ -8,7 +8,14 @@ export function navigateResult(result,
|
|
|
8
8
|
* one place that CAN answer this — the SDK reconnects to it — so making it answer removes a
|
|
9
9
|
* `reticle_sessions` poll from every single navigation.
|
|
10
10
|
*/
|
|
11
|
-
arrival
|
|
11
|
+
arrival,
|
|
12
|
+
/**
|
|
13
|
+
* The budget the daemon spent waiting for arrival, in ms. Reported on `confirmed:false` so the
|
|
14
|
+
* agent can tell "Reticle stopped waiting" from "the page never came back": the first is fixed by
|
|
15
|
+
* asking for a longer `timeout_ms`, the second is not. Without it the two read identically, and a
|
|
16
|
+
* slow SPA (30–60s to reattach under HMR) looked like a failed navigation on every call.
|
|
17
|
+
*/
|
|
18
|
+
waitedMs) {
|
|
12
19
|
const ok = true === result.ok;
|
|
13
20
|
const base = {
|
|
14
21
|
ok,
|
|
@@ -30,9 +37,12 @@ arrival = null) {
|
|
|
30
37
|
return {
|
|
31
38
|
...base,
|
|
32
39
|
confirmed: false,
|
|
40
|
+
waitedMs,
|
|
33
41
|
note: 'ok means the navigation was DISPATCHED, not that the page arrived. The SDK is torn down by ' +
|
|
34
|
-
'the navigation itself, so nothing here can see the new document
|
|
35
|
-
|
|
36
|
-
'
|
|
42
|
+
'the navigation itself, so nothing here can see the new document, and no session reconnected ' +
|
|
43
|
+
`at the new URL within ${waitedMs}ms. A slow app may still be on its way (an SPA reattaching ` +
|
|
44
|
+
'under HMR can take 30-60s): navigate again with a larger timeout_ms, or call reticle_sessions ' +
|
|
45
|
+
'to confirm a session reconnected before acting; if none appears, the page did not load or is ' +
|
|
46
|
+
'not instrumented.',
|
|
37
47
|
};
|
|
38
48
|
}
|
|
@@ -31,14 +31,8 @@ export declare const MAX_BLOCKING_WAIT_MS: number;
|
|
|
31
31
|
* in the name of bounding one that never worked.
|
|
32
32
|
*/
|
|
33
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
34
|
/** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
|
|
37
35
|
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
36
|
/** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
|
|
43
37
|
export declare const MIN_VIEWPORT_PX = 64;
|
|
44
38
|
export declare const MAX_VIEWPORT_PX = 10000;
|
|
@@ -92,9 +86,4 @@ export declare const windowMsSchema: z.ZodNumber;
|
|
|
92
86
|
* asking for a thousand workers cannot be honoured.
|
|
93
87
|
*/
|
|
94
88
|
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
89
|
export declare const listIndexSchema: z.ZodNumber;
|
|
@@ -52,13 +52,13 @@ export const MAX_BLOCKING_WAIT_MS = MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS - 5_000;
|
|
|
52
52
|
*/
|
|
53
53
|
export const MAX_STATE_DEPTH = 256;
|
|
54
54
|
/** A crawl that clicks forever is not a bound. */
|
|
55
|
-
|
|
55
|
+
const MAX_CRAWL_STEPS = 500;
|
|
56
56
|
/** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
|
|
57
57
|
export const MAX_SCROLLS = 200;
|
|
58
58
|
/** Fake-clock jump. One day of timers is a test; a billion ms is not. */
|
|
59
|
-
|
|
59
|
+
const MAX_ADVANCE_MS = 86_400_000;
|
|
60
60
|
/** Presenter idle window. One hour is a slow app; larger values never fire. */
|
|
61
|
-
|
|
61
|
+
const MAX_IDLE_MS = 3_600_000;
|
|
62
62
|
/** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
|
|
63
63
|
export const MIN_VIEWPORT_PX = 64;
|
|
64
64
|
export const MAX_VIEWPORT_PX = 10_000;
|
|
@@ -121,5 +121,5 @@ export const workerCountSchema = z
|
|
|
121
121
|
* A row index / list length in a virtualized table. Those lists are large on purpose; the ring
|
|
122
122
|
* buffer's 2000 is the wrong ceiling. A billion-row claim is not a list, it is a hang.
|
|
123
123
|
*/
|
|
124
|
-
|
|
124
|
+
const MAX_LIST_INDEX = 1_000_000;
|
|
125
125
|
export const listIndexSchema = z.number().finite().int().nonnegative().max(MAX_LIST_INDEX);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { noteEmptyRead } from './observed-nothing.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { aliasParam } from './alias-args.js';
|
|
8
|
-
import { CONSOLE_LEVELS, ReticleCommand, DEFAULT_ASSERT_TIMEOUT_MS, PredicateKind, Verified, } from '@reticlehq/core';
|
|
8
|
+
import { CONSOLE_ATTACH_NOTE, CONSOLE_LEVELS, ReticleCommand, DEFAULT_ASSERT_TIMEOUT_MS, PredicateKind, Verified, } from '@reticlehq/core';
|
|
9
9
|
import { ReticleTool } from './tool-names.js';
|
|
10
10
|
import { countSchema, cursorSchema, httpStatusSchema, timeoutMsSchema, windowMsSchema, } from './numeric-bounds.js';
|
|
11
11
|
import { buildReactionReport } from '../events/reaction.js';
|
|
@@ -19,11 +19,12 @@ import { applyEventBudget, costHint, withSizeCost, DEFAULT_QUERY_LIMIT, } from '
|
|
|
19
19
|
import { annotateStarvedFailure, healthEnvelope, bufferEnvelope, } from '../session/session-health.js';
|
|
20
20
|
import { assertsDerivedIpcStatus, DERIVED_IPC_STATUS_ADVICE, isPresenceOnlyAssertion, PRESENCE_ONLY_ADVICE, } from './assert-grade.js';
|
|
21
21
|
import { assertVerdict } from './assert-verdict.js';
|
|
22
|
-
import {
|
|
22
|
+
import { assertionSource } from './assert-source.js';
|
|
23
23
|
import { isChangeUndeclared } from '../honesty/undeclared-change.js';
|
|
24
24
|
import { openSessionIntents } from '../intent/open-intents.js';
|
|
25
25
|
import { dischargeInlineIntent, inlineVerdictId, linkInlineIntent, } from '../intent/inline-intent.js';
|
|
26
26
|
import { bodiesNotCaptured } from '../honesty/uncaptured-bodies.js';
|
|
27
|
+
import { bodyClauseRefusal } from '../honesty/body-capture-remedy.js';
|
|
27
28
|
import { withControl } from '../session/control-envelope.js';
|
|
28
29
|
import { asString, asNumber, asRecord } from './tools-helpers.js';
|
|
29
30
|
import { intentArg, sessionIdShape, commandOrThrow } from './tool-kit.js';
|
|
@@ -38,23 +39,6 @@ const bufferOutputShape = {
|
|
|
38
39
|
.optional()
|
|
39
40
|
.describe('Present only when the event buffer evicted events — a negative result may then be a false negative.'),
|
|
40
41
|
};
|
|
41
|
-
/**
|
|
42
|
-
* The file:line an assertion may report — see `assertSource`.
|
|
43
|
-
*
|
|
44
|
-
* Neither `reticle_assert` nor `reticle_wait_for` drives anything, so the last act's source is about
|
|
45
|
-
* some earlier action and not about this verdict. The pointer comes from the assertion's own matched
|
|
46
|
-
* evidence; the last driven control is borrowed only for a RED whose predicate has no DOM clause at
|
|
47
|
-
* all, which is the failure that genuinely has no element to point at.
|
|
48
|
-
*/
|
|
49
|
-
function assertionSource(session, predicate, verdict) {
|
|
50
|
-
const source = assertSource({
|
|
51
|
-
predicate,
|
|
52
|
-
evidence: verdict.evidence,
|
|
53
|
-
pass: verdict.pass,
|
|
54
|
-
lastActSource: session.lastAct.source(),
|
|
55
|
-
});
|
|
56
|
-
return source === undefined ? {} : { source };
|
|
57
|
-
}
|
|
58
42
|
/**
|
|
59
43
|
* Drop `sessionId` from an event in a response the caller scoped to ONE session.
|
|
60
44
|
*
|
|
@@ -78,7 +62,6 @@ function withoutConstantSessionId(event) {
|
|
|
78
62
|
* it (it reports which levels are present), but on a quiet page the answer is identical to a genuine
|
|
79
63
|
* all-clear. Refusing the value outright is the only reading with no ambiguity.
|
|
80
64
|
*/
|
|
81
|
-
const CONSOLE_LEVEL_LIST = CONSOLE_LEVELS.join(' | ');
|
|
82
65
|
const consoleLevelEnum = z.enum(CONSOLE_LEVELS);
|
|
83
66
|
export const OBSERVE_TOOLS = [
|
|
84
67
|
{
|
|
@@ -253,6 +236,11 @@ export const OBSERVE_TOOLS = [
|
|
|
253
236
|
const session = await resolveSessionWithin(deps.sessions, asString(args['sessionId']), waitBudget, WALL_CLOCK);
|
|
254
237
|
// `until` is act_and_wait's name for this — see alias-args.ts.
|
|
255
238
|
const predicate = parsePredicate(aliasParam(args, 'predicate', ['until'])['predicate']);
|
|
239
|
+
// Refused up front rather than waited out: a body clause this session cannot answer would
|
|
240
|
+
// burn the whole timeout to report something knowable now. See #801(C).
|
|
241
|
+
const bodyRefusal = bodyClauseRefusal(predicate, session);
|
|
242
|
+
if (bodyRefusal !== undefined)
|
|
243
|
+
throw new Error(bodyRefusal);
|
|
256
244
|
// Honesty: explicit since wins; else default to the last act's cursor; else the whole buffer.
|
|
257
245
|
const since = asNumber(args['since']) ?? session.lastAct.cursor() ?? 0;
|
|
258
246
|
const verdict = await waitForPredicate(session, predicate, waitBudget, since);
|
|
@@ -280,7 +268,7 @@ export const OBSERVE_TOOLS = [
|
|
|
280
268
|
// is the most common thing an agent wants to assert. A field report reached us from an agent
|
|
281
269
|
// that guessed `urlContains` on route (net's spelling) and got unrecognized_keys.
|
|
282
270
|
'Predicate to evaluate. Kinds: { signal, name|dataMatches|count } ' +
|
|
283
|
-
'{ net, urlContains|method|status|count|bodyContains } ' +
|
|
271
|
+
'{ net, urlContains|method|status|count|bodyContains|requestBodyContains|requestBodyMatches } ' +
|
|
284
272
|
'{ state, path|equals } { route, pathname (exact) | contains (path+query+hash) } ' +
|
|
285
273
|
'{ element, testid|role|text } { text } { console, level|contains|absent } { animation, name } ' +
|
|
286
274
|
'{ settled } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
|
|
@@ -350,6 +338,11 @@ export const OBSERVE_TOOLS = [
|
|
|
350
338
|
const session = await resolveSessionWithin(deps.sessions, asString(args['sessionId']), timeout, WALL_CLOCK);
|
|
351
339
|
// `until` is act_and_wait's name for this — see alias-args.ts.
|
|
352
340
|
const predicate = parsePredicate(aliasParam(args, 'predicate', ['until'])['predicate']);
|
|
341
|
+
// Refused up front rather than waited out: a body clause this session cannot answer would
|
|
342
|
+
// burn the whole timeout to report something knowable now. See #801(C).
|
|
343
|
+
const bodyRefusal = bodyClauseRefusal(predicate, session);
|
|
344
|
+
if (bodyRefusal !== undefined)
|
|
345
|
+
throw new Error(bodyRefusal);
|
|
353
346
|
// Honesty: explicit since wins; else default to the last act's cursor; else the whole buffer.
|
|
354
347
|
const since = asNumber(args['since']) ?? session.lastAct.cursor() ?? 0;
|
|
355
348
|
// Declared BEFORE the verdict, so the undeclared-change read below finds it open and stays
|
|
@@ -492,7 +485,7 @@ export const OBSERVE_TOOLS = [
|
|
|
492
485
|
return withSizeCost(noteEmptyRead({
|
|
493
486
|
calls,
|
|
494
487
|
...(droppedOldest > 0 ? { total: matched.length, droppedOldest } : {}),
|
|
495
|
-
...(bodies ? bodiesNotCaptured(calls) : {}),
|
|
488
|
+
...(bodies ? bodiesNotCaptured(calls, session.sdkVersion) : {}),
|
|
496
489
|
...buffer,
|
|
497
490
|
}, 'calls', { noun: 'network calls' }));
|
|
498
491
|
},
|
|
@@ -502,9 +495,7 @@ export const OBSERVE_TOOLS = [
|
|
|
502
495
|
example: { level: 'error' },
|
|
503
496
|
description: 'Console/error log. Fast path for "were there any errors during this flow?". When a level filter matches nothing, returns a `hint` { totalInWindow, byLevel } so 0 errors is distinguishable from a silent page.',
|
|
504
497
|
inputSchema: {
|
|
505
|
-
level: consoleLevelEnum
|
|
506
|
-
.optional()
|
|
507
|
-
.describe(`Log level filter: ${CONSOLE_LEVEL_LIST}. Omit to return all levels.`),
|
|
498
|
+
level: consoleLevelEnum.optional().describe('Log level filter. Omit to return all levels.'),
|
|
508
499
|
since: cursorSchema
|
|
509
500
|
.optional()
|
|
510
501
|
.describe('Cursor from a prior reticle_act — scopes the query to log entries after that act.'),
|
|
@@ -556,7 +547,12 @@ export const OBSERVE_TOOLS = [
|
|
|
556
547
|
// returning read here was already wired to.
|
|
557
548
|
return withSizeCost(noteEmptyRead(droppedOldest > 0
|
|
558
549
|
? { logs, total: matched.length, droppedOldest, ...buffer }
|
|
559
|
-
: { logs, ...buffer }, 'logs',
|
|
550
|
+
: { logs, ...buffer }, 'logs',
|
|
551
|
+
// Same gate as the predicate path, for the same reason and at the same price: the blind
|
|
552
|
+
// stretch is between page load and attach, so only a window that starts at attach can
|
|
553
|
+
// contain it. A read since an action already began after the channel was live, and
|
|
554
|
+
// stapling 369 bytes onto every quiet read would be paying for a caveat that is not true.
|
|
555
|
+
{ noun: 'console lines', ...(0 === since ? { caveat: CONSOLE_ATTACH_NOTE } : {}) }));
|
|
560
556
|
},
|
|
561
557
|
},
|
|
562
558
|
{
|
|
@@ -15,6 +15,15 @@ interface EmptyReadContext {
|
|
|
15
15
|
windowMs?: number;
|
|
16
16
|
/** What was being counted, in the agent's words: "console lines", "network calls". */
|
|
17
17
|
noun: string;
|
|
18
|
+
/**
|
|
19
|
+
* What this channel could NOT have seen, when it has a structural blind spot.
|
|
20
|
+
*
|
|
21
|
+
* "The observation ran and found none" is only the whole truth for a channel that was watching
|
|
22
|
+
* the entire window. The console is not one: it does not exist until the SDK patches it, so a
|
|
23
|
+
* clean read cannot speak for what the page logged before that. Saying so here is the difference
|
|
24
|
+
* between a result and a confident false green — omitted for every channel that has no such gap.
|
|
25
|
+
*/
|
|
26
|
+
caveat?: string;
|
|
18
27
|
}
|
|
19
28
|
/**
|
|
20
29
|
* Add `observed: true` and a note when `key` holds an EMPTY array. Returns the result untouched
|
|
@@ -28,6 +28,7 @@ export function noteEmptyRead(result, key, context) {
|
|
|
28
28
|
...result,
|
|
29
29
|
// The distinction the field sweep could not make: this is a RESULT, not a failure to look.
|
|
30
30
|
observed: true,
|
|
31
|
-
note: `no ${context.noun}${window}; the observation ran and found none, which is a result, not a missing reading
|
|
31
|
+
note: `no ${context.noun}${window}; the observation ran and found none, which is a result, not a missing reading` +
|
|
32
|
+
(context.caveat === undefined ? '' : ` — ${context.caveat}`),
|
|
32
33
|
};
|
|
33
34
|
}
|
package/dist/tools/read-tools.js
CHANGED
|
@@ -8,6 +8,7 @@ import { EventType, ReticleCommand, REPLAY_PROGRAM_VERSION, SnapshotMode, Storag
|
|
|
8
8
|
import { ReticleTool } from './tool-names.js';
|
|
9
9
|
import { advanceMsSchema, depthSchema } from './numeric-bounds.js';
|
|
10
10
|
import { proposeConsequences } from '../oracles/propose-consequences.js';
|
|
11
|
+
import { recordingBacktrackWarning, routesFromRecording } from '../flows/recording-backtrack.js';
|
|
11
12
|
import { replayProgram } from '../flows/replay.js';
|
|
12
13
|
import { diffLines } from '../project/baselines.js';
|
|
13
14
|
import { selectPath, capDepth, projectComponentState } from '../session/state-select.js';
|
|
@@ -16,6 +17,7 @@ import { buildReactionReport, summarizeReaction } from '../events/reaction.js';
|
|
|
16
17
|
import { asString, asNumber, parseInteractive } from './tools-helpers.js';
|
|
17
18
|
import { sessionIdShape, commandOrThrow, snapshotTree } from './tool-kit.js';
|
|
18
19
|
import { bufferEnvelope } from '../session/session-health.js';
|
|
20
|
+
import { routeOfUrl } from '../events/predicate-route.js';
|
|
19
21
|
/** The route part of a session URL. A host belongs to the machine, not to the journey. */
|
|
20
22
|
/**
|
|
21
23
|
* The page a recording started on, as a NAVIGABLE path: pathname + hash.
|
|
@@ -28,13 +30,8 @@ import { bufferEnvelope } from '../session/session-health.js';
|
|
|
28
30
|
function pathnameOf(url) {
|
|
29
31
|
if (url === undefined)
|
|
30
32
|
return undefined;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return `${parsed.pathname}${parsed.hash}`;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
33
|
+
const parts = routeOfUrl(url);
|
|
34
|
+
return parts === undefined ? undefined : `${parts.docPath}${parts.hash}`;
|
|
38
35
|
}
|
|
39
36
|
/**
|
|
40
37
|
* What record-stop says about a step that compiled to no anchor at all.
|
|
@@ -58,7 +55,6 @@ function unanchoredWarning(count) {
|
|
|
58
55
|
}
|
|
59
56
|
/** Severities the presenter HUD can render. */
|
|
60
57
|
const HUD_LEVELS = ['info', 'warn', 'error'];
|
|
61
|
-
const HUD_LEVEL_LIST = HUD_LEVELS.join(' | ');
|
|
62
58
|
const hudLevelEnum = z.enum(HUD_LEVELS);
|
|
63
59
|
/**
|
|
64
60
|
* How deep an unscoped `reticle_state` read goes before collapsing to size markers.
|
|
@@ -171,7 +167,7 @@ export const READ_TOOLS = [
|
|
|
171
167
|
},
|
|
172
168
|
{
|
|
173
169
|
name: ReticleTool.RECORD_STOP,
|
|
174
|
-
description: 'Stop the recording identified by `recordingName` and return both the reaction report for the span and a compiled, replayable { program: { version, steps:[{tool,args,stable}] } } of the agent acts captured during it.',
|
|
170
|
+
description: 'Stop the recording identified by `recordingName` and return both the reaction report for the span and a compiled, replayable { program: { version, steps:[{tool,args,stable}] } } of the agent acts captured during it. `warning` is present when the window left a page and returned to it — replay has no navigation steps, so the next click will look for a control on a page the tab is no longer on.',
|
|
175
171
|
inputSchema: {
|
|
176
172
|
recordingName: z
|
|
177
173
|
.string()
|
|
@@ -201,11 +197,13 @@ export const READ_TOOLS = [
|
|
|
201
197
|
: `no active recording named '${name}'; in progress: ${active.map((r) => `'${r}'`).join(', ')}`);
|
|
202
198
|
}
|
|
203
199
|
const events = session.eventsSince(rec.cursor);
|
|
200
|
+
const routes = routesFromRecording(rec.startPath, events);
|
|
204
201
|
const program = {
|
|
205
202
|
name,
|
|
206
203
|
version: REPLAY_PROGRAM_VERSION,
|
|
207
204
|
steps: rec.steps,
|
|
208
205
|
...(rec.startPath === undefined ? {} : { startPath: rec.startPath }),
|
|
206
|
+
...(0 === routes.length ? {} : { routes }),
|
|
209
207
|
};
|
|
210
208
|
deps.recordings.saveCompiled(program);
|
|
211
209
|
const unstable = rec.steps.filter((s) => !s.stable).length;
|
|
@@ -224,15 +222,14 @@ export const READ_TOOLS = [
|
|
|
224
222
|
timeline_omitted: `${String(events.length)} event(s) were recorded and are not included here. Call reticle_observe { since: ${String(rec.cursor)} } for the raw timeline.`,
|
|
225
223
|
}
|
|
226
224
|
: {};
|
|
225
|
+
const unanchored = 0 < unstable ? unanchoredWarning(unstable) : undefined;
|
|
226
|
+
const backtrack = recordingBacktrackWarning(routes);
|
|
227
|
+
const warning = [unanchored, backtrack].filter((part) => part !== undefined);
|
|
227
228
|
const body = {
|
|
228
229
|
recordingName: name,
|
|
229
230
|
program,
|
|
230
231
|
...timeline,
|
|
231
|
-
...(
|
|
232
|
-
? {
|
|
233
|
-
warning: unanchoredWarning(unstable),
|
|
234
|
-
}
|
|
235
|
-
: {}),
|
|
232
|
+
...(0 === warning.length ? {} : { warning: warning.join(' ') }),
|
|
236
233
|
...(proposedConsequences.length > 0 ? { proposedConsequences } : {}),
|
|
237
234
|
...digest,
|
|
238
235
|
};
|
|
@@ -285,9 +282,7 @@ export const READ_TOOLS = [
|
|
|
285
282
|
.describe('Short sentence describing your next action, shown on the presenter HUD for the developer watching.'),
|
|
286
283
|
// Derived, like every other advertised vocabulary here: a free string let a typo through to
|
|
287
284
|
// a HUD that then rendered an unknown severity.
|
|
288
|
-
level: hudLevelEnum
|
|
289
|
-
.optional()
|
|
290
|
-
.describe(`Display severity: ${HUD_LEVEL_LIST}. Default: info.`),
|
|
285
|
+
level: hudLevelEnum.optional().describe('Display severity. Default: info.'),
|
|
291
286
|
...sessionIdShape,
|
|
292
287
|
},
|
|
293
288
|
outputSchema: { ok: z.boolean() },
|
|
@@ -12,15 +12,21 @@
|
|
|
12
12
|
* bridge, so the browser side sees a normal `{ content, name, type }` call and `assertUploadArgs`
|
|
13
13
|
* keeps its invariant (no fabricated bytes, no silently-dropped keys).
|
|
14
14
|
*
|
|
15
|
-
* Trust boundary: scoped to the project root (one level above `deps.reticleRoot`)
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* fits in one WebSocket
|
|
15
|
+
* Trust boundary: scoped to the project root (one level above `deps.reticleRoot`) plus any
|
|
16
|
+
* opt-in `uploadRoots` declared in `.reticle.json`, resolved through `realpath` so symlinks
|
|
17
|
+
* cannot escape an allowed root. Sensitive files (.env*, .git/, *.pem, id_*, .npmrc) are denied
|
|
18
|
+
* with a message that says why. The cap is derived from the bridge's own MAX_MESSAGE_BYTES with
|
|
19
|
+
* the base64 4/3 inflation factor applied, so the encoded payload always fits in one WebSocket
|
|
20
|
+
* frame.
|
|
20
21
|
*/
|
|
21
22
|
import { ActionType, InputModeReason } from '@reticlehq/core';
|
|
22
23
|
import type { Session } from '../session/session.js';
|
|
23
24
|
import { type ToolDeps } from './tool-kit.js';
|
|
25
|
+
/**
|
|
26
|
+
* Expand a leading `~` against the caller's home directory. Absolute and relative paths pass
|
|
27
|
+
* through unchanged. `home` is injected so unit tests do not depend on the machine's real home.
|
|
28
|
+
*/
|
|
29
|
+
export declare function expandUserPath(rawPath: string, home: string): string;
|
|
24
30
|
/**
|
|
25
31
|
* If the action is `upload` AND the inner args carry `path`, read the file from disk and rewrite
|
|
26
32
|
* the args to `{ content, name, type, __base64: true }` before the ACT command reaches the browser.
|
|
@@ -30,7 +36,9 @@ import { type ToolDeps } from './tool-kit.js';
|
|
|
30
36
|
* This is the SINGLE interception point — called from session.command() so flow replay, crawl,
|
|
31
37
|
* and every other ACT dispatch site are covered without per-site wiring.
|
|
32
38
|
*/
|
|
33
|
-
export declare function rewriteUploadArgs(deps: Pick<ToolDeps, 'fs' | 'reticleRoot'>, action: string, innerArgs: Record<string, unknown
|
|
39
|
+
export declare function rewriteUploadArgs(deps: Pick<ToolDeps, 'fs' | 'reticleRoot'>, action: string, innerArgs: Record<string, unknown>,
|
|
40
|
+
/** Injected for tests; production uses the process home directory. */
|
|
41
|
+
home?: string): Promise<Record<string, unknown>>;
|
|
34
42
|
interface RealActResult {
|
|
35
43
|
/** Defined only on a successful native action; `undefined` means the synthetic path runs. */
|
|
36
44
|
result: unknown;
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* bridge, so the browser side sees a normal `{ content, name, type }` call and `assertUploadArgs`
|
|
13
13
|
* keeps its invariant (no fabricated bytes, no silently-dropped keys).
|
|
14
14
|
*
|
|
15
|
-
* Trust boundary: scoped to the project root (one level above `deps.reticleRoot`)
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* fits in one WebSocket
|
|
15
|
+
* Trust boundary: scoped to the project root (one level above `deps.reticleRoot`) plus any
|
|
16
|
+
* opt-in `uploadRoots` declared in `.reticle.json`, resolved through `realpath` so symlinks
|
|
17
|
+
* cannot escape an allowed root. Sensitive files (.env*, .git/, *.pem, id_*, .npmrc) are denied
|
|
18
|
+
* with a message that says why. The cap is derived from the bridge's own MAX_MESSAGE_BYTES with
|
|
19
|
+
* the base64 4/3 inflation factor applied, so the encoded payload always fits in one WebSocket
|
|
20
|
+
* frame.
|
|
20
21
|
*/
|
|
21
22
|
import { ActionType, InputModeReason, ReticleCommand, TRANSPORT_LIMITS } from '@reticlehq/core';
|
|
22
23
|
import { boxCenter, isPointerAction } from '../input/real-input.js';
|
|
@@ -25,7 +26,9 @@ import { NATIVE_INPUT_ARG } from '@reticlehq/core';
|
|
|
25
26
|
import { asString, asRecord } from './tools-helpers.js';
|
|
26
27
|
import { commandOrThrow } from './tool-kit.js';
|
|
27
28
|
import { asBox } from './act-helpers.js';
|
|
28
|
-
import { isAbsolute, join, relative, extname, basename } from 'node:path';
|
|
29
|
+
import { isAbsolute, join, relative, extname, basename, resolve } from 'node:path';
|
|
30
|
+
import { homedir } from 'node:os';
|
|
31
|
+
import { RETICLE_CONFIG_BASENAME } from '../cli/cli-port.js';
|
|
29
32
|
/**
|
|
30
33
|
* Minimal extension → MIME-type table for the file types agents most commonly upload.
|
|
31
34
|
* Falls back to `application/octet-stream` for anything not listed here — the browser and
|
|
@@ -92,28 +95,79 @@ function isDeniedPath(resolvedPath) {
|
|
|
92
95
|
* that configures the socket so they can never drift.
|
|
93
96
|
*/
|
|
94
97
|
const UPLOAD_MAX_BYTES = Math.floor((TRANSPORT_LIMITS.MAX_MESSAGE_BYTES / (4 / 3)) * 0.75);
|
|
98
|
+
/**
|
|
99
|
+
* Expand a leading `~` against the caller's home directory. Absolute and relative paths pass
|
|
100
|
+
* through unchanged. `home` is injected so unit tests do not depend on the machine's real home.
|
|
101
|
+
*/
|
|
102
|
+
export function expandUserPath(rawPath, home) {
|
|
103
|
+
if ('~' === rawPath)
|
|
104
|
+
return home;
|
|
105
|
+
if (rawPath.startsWith('~/') || rawPath.startsWith('~\\'))
|
|
106
|
+
return join(home, rawPath.slice(2));
|
|
107
|
+
return rawPath;
|
|
108
|
+
}
|
|
109
|
+
/** True when `real` is inside `root` after both have been realpath'd / resolved. */
|
|
110
|
+
function isInsideRoot(real, root) {
|
|
111
|
+
const rel = relative(root, real);
|
|
112
|
+
return !rel.startsWith('..') && !isAbsolute(rel);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Opt-in extra upload directories from `.reticle.json` (`uploadRoots`). Absent, empty, or unreadable
|
|
116
|
+
* config means project-root only — the previous default, kept on purpose.
|
|
117
|
+
*/
|
|
118
|
+
async function readUploadRoots(projectRoot, fs, home) {
|
|
119
|
+
const configPath = join(projectRoot, RETICLE_CONFIG_BASENAME);
|
|
120
|
+
let raw;
|
|
121
|
+
try {
|
|
122
|
+
raw = await fs.readFile(configPath);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = JSON.parse(raw);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
if ('object' !== typeof parsed || null === parsed || Array.isArray(parsed))
|
|
135
|
+
return [];
|
|
136
|
+
const listed = parsed['uploadRoots'];
|
|
137
|
+
if (!Array.isArray(listed))
|
|
138
|
+
return [];
|
|
139
|
+
const roots = [];
|
|
140
|
+
for (const entry of listed) {
|
|
141
|
+
if ('string' !== typeof entry || 0 === entry.trim().length)
|
|
142
|
+
continue;
|
|
143
|
+
roots.push(resolve(expandUserPath(entry.trim(), home)));
|
|
144
|
+
}
|
|
145
|
+
return roots;
|
|
146
|
+
}
|
|
95
147
|
/**
|
|
96
148
|
* Resolve and validate a caller-supplied upload path.
|
|
97
149
|
*
|
|
98
|
-
* 1.
|
|
150
|
+
* 1. Expand a leading `~`, then resolve to absolute (join against project root for relative paths).
|
|
99
151
|
* 2. Call `realpath` to follow symlinks — `relative()` is lexical and a symlink inside the tree
|
|
100
152
|
* can point outside it; realpath is the only reliable check.
|
|
101
|
-
* 3. Confirm the real path is within the project root.
|
|
153
|
+
* 3. Confirm the real path is within the project root or an opt-in `uploadRoots` entry.
|
|
102
154
|
* 4. Confirm the path does not match the sensitive-file deny-list.
|
|
103
155
|
*
|
|
104
156
|
* Returns the resolved real path on success. Throws with a user-readable message on any violation.
|
|
105
157
|
*/
|
|
106
|
-
async function resolveUploadPath(rawPath, projectRoot, fs) {
|
|
107
|
-
const
|
|
158
|
+
async function resolveUploadPath(rawPath, projectRoot, fs, uploadRoots, home) {
|
|
159
|
+
const expanded = expandUserPath(rawPath, home);
|
|
160
|
+
const abs = isAbsolute(expanded) ? expanded : join(projectRoot, expanded);
|
|
108
161
|
// realpath resolves symlinks; it also rejects ENOENT, so we get a clear missing-file error.
|
|
109
162
|
const real = await fs.realpath(abs).catch(() => {
|
|
110
163
|
throw new Error(`upload path '${rawPath}' could not be read: file not found or not accessible.`);
|
|
111
164
|
});
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
165
|
+
const allowed = [projectRoot, ...uploadRoots];
|
|
166
|
+
if (!allowed.some((root) => isInsideRoot(real, root))) {
|
|
167
|
+
const listed = allowed.map((r) => `'${r}'`).join(', ');
|
|
168
|
+
throw new Error(`upload path '${rawPath}' resolves to '${real}', which is outside every allowed upload root ` +
|
|
169
|
+
`(${listed}). Paths must sit under the project directory or an entry in ` +
|
|
170
|
+
`\`.reticle.json\`'s \`uploadRoots\` (e.g. ["~/Downloads"]).`);
|
|
117
171
|
}
|
|
118
172
|
if (isDeniedPath(real)) {
|
|
119
173
|
throw new Error(`upload path '${rawPath}' matches a sensitive-file pattern and cannot be read by the daemon. ` +
|
|
@@ -130,16 +184,19 @@ async function resolveUploadPath(rawPath, projectRoot, fs) {
|
|
|
130
184
|
* This is the SINGLE interception point — called from session.command() so flow replay, crawl,
|
|
131
185
|
* and every other ACT dispatch site are covered without per-site wiring.
|
|
132
186
|
*/
|
|
133
|
-
export async function rewriteUploadArgs(deps, action, innerArgs
|
|
187
|
+
export async function rewriteUploadArgs(deps, action, innerArgs,
|
|
188
|
+
/** Injected for tests; production uses the process home directory. */
|
|
189
|
+
home = homedir()) {
|
|
134
190
|
if (action !== ActionType.UPLOAD)
|
|
135
191
|
return innerArgs;
|
|
136
192
|
const rawPath = asString(innerArgs['path']);
|
|
137
193
|
if (rawPath === undefined)
|
|
138
194
|
return innerArgs; // no path → browser handles as inline upload
|
|
139
195
|
// Project root is one level above .reticle/
|
|
140
|
-
const projectRoot = join(deps.reticleRoot, '..');
|
|
196
|
+
const projectRoot = resolve(join(deps.reticleRoot, '..'));
|
|
197
|
+
const uploadRoots = await readUploadRoots(projectRoot, deps.fs, home);
|
|
141
198
|
// Blocker 4 fix: stat FIRST, before allocating read buffer, so a huge file fails fast.
|
|
142
|
-
const realPath = await resolveUploadPath(rawPath, projectRoot, deps.fs);
|
|
199
|
+
const realPath = await resolveUploadPath(rawPath, projectRoot, deps.fs, uploadRoots, home);
|
|
143
200
|
const fileStat = await deps.fs.stat(realPath).catch(() => {
|
|
144
201
|
throw new Error(`upload path '${rawPath}' could not be read: file not found or not accessible.`);
|
|
145
202
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bodyCaptureRemedy } from '../honesty/body-capture-remedy.js';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { EventType, ReticleCommand, SnapshotMode, urlForMatch } from '@reticlehq/core';
|
|
3
4
|
import { ReticleTool } from './tool-names.js';
|
|
@@ -86,7 +87,7 @@ export const RECONCILE_TOOLS = [
|
|
|
86
87
|
compared: 0,
|
|
87
88
|
note: 0 === total
|
|
88
89
|
? 'no responses in this window matched — nothing was compared'
|
|
89
|
-
: `${String(total)} response(s) matched but NONE carried a recorded body, so nothing was compared.
|
|
90
|
+
: `${String(total)} response(s) matched but NONE carried a recorded body, so nothing was compared. ${bodyCaptureRemedy(session.sdkVersion)}`,
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
93
|
// The whole page, INCLUDING whatever the snapshot's node cap cut off. A comparison against a
|
|
@@ -11,4 +11,9 @@
|
|
|
11
11
|
* remembers the id in sessionStorage so a reload rejoins the SAME session instead of appearing as a
|
|
12
12
|
* new one. This is the remaining half: saying that the page is not back yet.
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* `waitedMs` is the budget that expired, reported on `confirmed:false` for the same reason the URL
|
|
16
|
+
* branch reports it (navigate-result.ts): so "Reticle stopped waiting" is distinguishable from "the
|
|
17
|
+
* page never came back", and the fix — a larger `timeout_ms` — is visible in the result.
|
|
18
|
+
*/
|
|
19
|
+
export declare function reloadResult(reconnected: boolean, waitedMs: number): Record<string, unknown>;
|
|
@@ -22,8 +22,13 @@ const RELOAD_NOTE = 'ok means the reload was DISPATCHED, not that the page came
|
|
|
22
22
|
*/
|
|
23
23
|
const RELOAD_RECONNECTED_NOTE = 'the page came back and re-announced itself under the same session id, so this session is live ' +
|
|
24
24
|
'again — no re-selection needed. Anything captured before the reload is gone with the old document.';
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* `waitedMs` is the budget that expired, reported on `confirmed:false` for the same reason the URL
|
|
27
|
+
* branch reports it (navigate-result.ts): so "Reticle stopped waiting" is distinguishable from "the
|
|
28
|
+
* page never came back", and the fix — a larger `timeout_ms` — is visible in the result.
|
|
29
|
+
*/
|
|
30
|
+
export function reloadResult(reconnected, waitedMs) {
|
|
26
31
|
return reconnected
|
|
27
32
|
? { ok: true, confirmed: true, note: RELOAD_RECONNECTED_NOTE }
|
|
28
|
-
: { ok: true, confirmed: false, note: RELOAD_NOTE };
|
|
33
|
+
: { ok: true, confirmed: false, waitedMs, note: RELOAD_NOTE };
|
|
29
34
|
}
|