@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
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* The browser is injected via a `Launcher` so all lifecycle logic is testable without real Chromium;
|
|
10
10
|
* the thin Playwright adapter that satisfies these interfaces lives separately.
|
|
11
11
|
*/
|
|
12
|
-
/** The minimal page surface the pool drives. Real Playwright `Page` satisfies this. */
|
|
13
12
|
export interface PooledPage {
|
|
14
13
|
goto(url: string, opts?: {
|
|
15
14
|
timeoutMs?: number;
|
|
@@ -17,6 +16,27 @@ export interface PooledPage {
|
|
|
17
16
|
close(): Promise<void>;
|
|
18
17
|
/** Fires when THIS page's renderer crashes — lets the pool reclaim just this lease, not the fleet. */
|
|
19
18
|
onCrash(handler: () => void): void;
|
|
19
|
+
/**
|
|
20
|
+
* Fires for each console message the page logs. OPTIONAL: a fake that does not implement it makes
|
|
21
|
+
* the pool record nothing, which is the correct degradation — an absent dial address must read as
|
|
22
|
+
* "the page said nothing", never as "the page dialled correctly".
|
|
23
|
+
*/
|
|
24
|
+
onConsole?(handler: (text: string) => void): void;
|
|
25
|
+
/**
|
|
26
|
+
* Capture the page as a PNG. OPTIONAL, like `onConsole`: a fake that does not implement it makes
|
|
27
|
+
* the pool report "no provider", which is the correct degradation — an absent screenshotter must
|
|
28
|
+
* read as "this context cannot be captured", never as a blank image that a visual diff would then
|
|
29
|
+
* compare against and pass.
|
|
30
|
+
*/
|
|
31
|
+
screenshot?(opts?: {
|
|
32
|
+
fullPage?: boolean;
|
|
33
|
+
}): Promise<Uint8Array>;
|
|
34
|
+
/**
|
|
35
|
+
* Move the real pointer to (x, y) so CSS `:hover` applies. OPTIONAL, like `screenshot`: a fake
|
|
36
|
+
* that does not implement it makes hover refuse rather than dispatch a synthetic mouseover that
|
|
37
|
+
* reports dispatched/settled while the styles never ran.
|
|
38
|
+
*/
|
|
39
|
+
hover?(x: number, y: number): Promise<void>;
|
|
20
40
|
}
|
|
21
41
|
/** An isolated browsing context (cookies/storage). Real Playwright `BrowserContext` satisfies this. */
|
|
22
42
|
export interface PooledContext {
|
|
@@ -69,6 +89,16 @@ export declare class BrowserPool {
|
|
|
69
89
|
queuedCount(): number;
|
|
70
90
|
/** The sessionIds of every currently leased context — used to report/group leased sessions. */
|
|
71
91
|
leasedSessionIds(): string[];
|
|
92
|
+
/**
|
|
93
|
+
* The public session id of an active lease on this origin, if we already hold one.
|
|
94
|
+
*
|
|
95
|
+
* The lease TOOL reuses that id instead of minting a second context: a second acquire on the same
|
|
96
|
+
* origin used to leave both tabs connected and poison default session resolution (#600). The pool's
|
|
97
|
+
* own `acquire` still mints — the parallel suite needs isolation on purpose.
|
|
98
|
+
*
|
|
99
|
+
* Prefers the alias the agent was given, when the app registered under its own name.
|
|
100
|
+
*/
|
|
101
|
+
leaseIdOnOrigin(origin: string): string | undefined;
|
|
72
102
|
/**
|
|
73
103
|
* Release a lease by its sessionId (closes the context, frees the slot). No-op if unknown.
|
|
74
104
|
*
|
|
@@ -96,16 +126,50 @@ export declare class BrowserPool {
|
|
|
96
126
|
* Reclaim leases untouched for longer than the TTL — a hung/crashed agent never holds a slot
|
|
97
127
|
* forever. Returns the released sessionIds. This is the fault-tolerance backstop for the pool.
|
|
98
128
|
*/
|
|
129
|
+
/**
|
|
130
|
+
* Capture a leased page, or undefined when this session is not a lease or cannot be captured.
|
|
131
|
+
*
|
|
132
|
+
* A lease IS a real browser page, so the pixels were always available — the visual tools simply had
|
|
133
|
+
* no route to them and answered "no provider" for every context an agent can actually get. That
|
|
134
|
+
* made visual regression impossible on exactly the isolated contexts the pool exists to hand out.
|
|
135
|
+
*
|
|
136
|
+
* Touches the lease like any other tool call, so capturing keeps it alive rather than letting the
|
|
137
|
+
* reaper take it out from under a diff.
|
|
138
|
+
*/
|
|
139
|
+
screenshotLease(sessionId: string, opts?: {
|
|
140
|
+
fullPage?: boolean;
|
|
141
|
+
}): Promise<Uint8Array | undefined>;
|
|
142
|
+
/**
|
|
143
|
+
* Move the real pointer on a leased page, or false when this session is not a lease or cannot
|
|
144
|
+
* hover. Same optional-capability contract as screenshotLease: absence must read as "could not
|
|
145
|
+
* hover", never as a successful dispatch of a synthetic mouseover.
|
|
146
|
+
*
|
|
147
|
+
* Touches the lease like any other tool call, so hovering keeps it alive.
|
|
148
|
+
*/
|
|
149
|
+
hoverLease(sessionId: string, x: number, y: number): Promise<boolean>;
|
|
99
150
|
sweepExpired(): Promise<string[]>;
|
|
100
151
|
/**
|
|
101
152
|
* How many leases this pool has reclaimed for going stale.
|
|
102
153
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* because nothing here knows WHICH session the caller just lost.
|
|
154
|
+
* Kept for reporting. NOT a basis for diagnosis: it is a lifetime total that never resets, so
|
|
155
|
+
* `count > 0` latches true forever after the first reap. Ask `wasReapedLease` instead, which
|
|
156
|
+
* answers about a specific session.
|
|
107
157
|
*/
|
|
108
158
|
reapedLeaseCount(): number;
|
|
159
|
+
/**
|
|
160
|
+
* Did THIS session id belong to a lease this pool aged out?
|
|
161
|
+
*
|
|
162
|
+
* The question the no-session diagnosis actually has. It used to ask `reapedLeaseCount() > 0`,
|
|
163
|
+
* which meant that once any lease had ever aged out, every closed human tab for the rest of the
|
|
164
|
+
* daemon's life was reported as an expired lease — and the recovery that followed
|
|
165
|
+
* (`reticle_lease {acquire}`) would open an unauthenticated headless context and lose the app
|
|
166
|
+
* session (#611).
|
|
167
|
+
*
|
|
168
|
+
* Only the last {@link MAX_REMEMBERED_REAPED_LEASES} are remembered, so this answers `false` for
|
|
169
|
+
* a lease reaped long enough ago that thousands have followed. That direction is the safe one:
|
|
170
|
+
* it falls back to the generic "the tab was closed" wording rather than inventing a lease.
|
|
171
|
+
*/
|
|
172
|
+
wasReapedLease(sessionId: string): boolean;
|
|
109
173
|
/**
|
|
110
174
|
* Lease an isolated context, navigate it to `url`, and return a handle. If the pool is at capacity,
|
|
111
175
|
* waits FIFO until a slot frees (or until `signal` aborts, if provided).
|
|
@@ -114,6 +178,12 @@ export declare class BrowserPool {
|
|
|
114
178
|
signal?: AbortSignal;
|
|
115
179
|
sessionId?: string;
|
|
116
180
|
}): Promise<Lease>;
|
|
181
|
+
/**
|
|
182
|
+
* The websocket address this lease's page reported it could not reach, if it reported one.
|
|
183
|
+
*
|
|
184
|
+
* Undefined means the page said nothing — never that it dialled the right place.
|
|
185
|
+
*/
|
|
186
|
+
dialFailureUrl(sessionId: string): string | undefined;
|
|
117
187
|
/** Close every context and the browser. Pending waiters are rejected (the pool is terminal now). */
|
|
118
188
|
shutdown(): Promise<void>;
|
|
119
189
|
}
|
|
@@ -9,10 +9,17 @@
|
|
|
9
9
|
* The browser is injected via a `Launcher` so all lifecycle logic is testable without real Chromium;
|
|
10
10
|
* the thin Playwright adapter that satisfies these interfaces lives separately.
|
|
11
11
|
*/
|
|
12
|
+
/** The minimal page surface the pool drives. Real Playwright `Page` satisfies this. */
|
|
13
|
+
import { unreachableUrlIn } from '@reticlehq/core';
|
|
12
14
|
/** Default lease time-to-live: a lease untouched for this long is presumed orphaned and reclaimed. */
|
|
13
15
|
export const DEFAULT_LEASE_TTL_MS = 5 * 60_000;
|
|
14
16
|
/** Default cap on how long a lease's initial navigation may take before it fails (frees the slot). */
|
|
15
17
|
const DEFAULT_NAV_TIMEOUT_MS = 30_000;
|
|
18
|
+
/**
|
|
19
|
+
* How many reaped lease ids to remember. Only the most recent departure is ever asked about, so
|
|
20
|
+
* this is slack for a burst sweep, not a history.
|
|
21
|
+
*/
|
|
22
|
+
const MAX_REMEMBERED_REAPED_LEASES = 64;
|
|
16
23
|
/**
|
|
17
24
|
* Owns a single browser and leases isolated contexts out of it, capped at `maxContexts`.
|
|
18
25
|
* Not exported as a singleton — the daemon owns one instance.
|
|
@@ -29,6 +36,18 @@ export class BrowserPool {
|
|
|
29
36
|
#closed = false;
|
|
30
37
|
/** Leases reclaimed for going stale — see reapedLeaseCount(). */
|
|
31
38
|
#reapedLeases = 0;
|
|
39
|
+
/**
|
|
40
|
+
* The session ids of recently reaped leases, most recent last.
|
|
41
|
+
*
|
|
42
|
+
* The count above answers "has anything ever aged out", which is not a
|
|
43
|
+
* question any caller actually has: it latches true for the life of the
|
|
44
|
+
* daemon (#611). The ids answer the question the no-session diagnosis is
|
|
45
|
+
* really asking — was THIS session a lease that aged out — so a closed
|
|
46
|
+
* human tab is no longer blamed on an expired lease forever after.
|
|
47
|
+
*
|
|
48
|
+
* Bounded because a long-lived daemon reaps unboundedly many.
|
|
49
|
+
*/
|
|
50
|
+
#reapedLeaseIds = [];
|
|
32
51
|
#active = new Map();
|
|
33
52
|
/** Registered-session-id → lease id, for apps that keep their own session name. See `alias`. */
|
|
34
53
|
#aliases = new Map();
|
|
@@ -71,6 +90,34 @@ export class BrowserPool {
|
|
|
71
90
|
leasedSessionIds() {
|
|
72
91
|
return [...this.#active.keys()];
|
|
73
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* The public session id of an active lease on this origin, if we already hold one.
|
|
95
|
+
*
|
|
96
|
+
* The lease TOOL reuses that id instead of minting a second context: a second acquire on the same
|
|
97
|
+
* origin used to leave both tabs connected and poison default session resolution (#600). The pool's
|
|
98
|
+
* own `acquire` still mints — the parallel suite needs isolation on purpose.
|
|
99
|
+
*
|
|
100
|
+
* Prefers the alias the agent was given, when the app registered under its own name.
|
|
101
|
+
*/
|
|
102
|
+
leaseIdOnOrigin(origin) {
|
|
103
|
+
for (const [leaseId, lease] of this.#active) {
|
|
104
|
+
let leaseOrigin;
|
|
105
|
+
try {
|
|
106
|
+
leaseOrigin = new URL(lease.url).origin;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (leaseOrigin !== origin)
|
|
112
|
+
continue;
|
|
113
|
+
for (const [alias, id] of this.#aliases) {
|
|
114
|
+
if (id === leaseId)
|
|
115
|
+
return alias;
|
|
116
|
+
}
|
|
117
|
+
return leaseId;
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
74
121
|
/**
|
|
75
122
|
* Release a lease by its sessionId (closes the context, frees the slot). No-op if unknown.
|
|
76
123
|
*
|
|
@@ -114,6 +161,50 @@ export class BrowserPool {
|
|
|
114
161
|
* Reclaim leases untouched for longer than the TTL — a hung/crashed agent never holds a slot
|
|
115
162
|
* forever. Returns the released sessionIds. This is the fault-tolerance backstop for the pool.
|
|
116
163
|
*/
|
|
164
|
+
/**
|
|
165
|
+
* Capture a leased page, or undefined when this session is not a lease or cannot be captured.
|
|
166
|
+
*
|
|
167
|
+
* A lease IS a real browser page, so the pixels were always available — the visual tools simply had
|
|
168
|
+
* no route to them and answered "no provider" for every context an agent can actually get. That
|
|
169
|
+
* made visual regression impossible on exactly the isolated contexts the pool exists to hand out.
|
|
170
|
+
*
|
|
171
|
+
* Touches the lease like any other tool call, so capturing keeps it alive rather than letting the
|
|
172
|
+
* reaper take it out from under a diff.
|
|
173
|
+
*/
|
|
174
|
+
async screenshotLease(sessionId, opts = {}) {
|
|
175
|
+
const lease = this.#active.get(sessionId);
|
|
176
|
+
if (lease === undefined || lease.page.screenshot === undefined)
|
|
177
|
+
return undefined;
|
|
178
|
+
this.touch(sessionId);
|
|
179
|
+
try {
|
|
180
|
+
return await lease.page.screenshot(opts);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// A capture that fails is not a broken lease. Report "could not capture" and leave the lease
|
|
184
|
+
// usable — the alternative is losing a working context to one bad frame.
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Move the real pointer on a leased page, or false when this session is not a lease or cannot
|
|
190
|
+
* hover. Same optional-capability contract as screenshotLease: absence must read as "could not
|
|
191
|
+
* hover", never as a successful dispatch of a synthetic mouseover.
|
|
192
|
+
*
|
|
193
|
+
* Touches the lease like any other tool call, so hovering keeps it alive.
|
|
194
|
+
*/
|
|
195
|
+
async hoverLease(sessionId, x, y) {
|
|
196
|
+
const lease = this.#active.get(sessionId);
|
|
197
|
+
if (lease === undefined || lease.page.hover === undefined)
|
|
198
|
+
return false;
|
|
199
|
+
this.touch(sessionId);
|
|
200
|
+
try {
|
|
201
|
+
await lease.page.hover(x, y);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
117
208
|
async sweepExpired() {
|
|
118
209
|
const now = this.#now();
|
|
119
210
|
const expired = [...this.#active.entries()]
|
|
@@ -122,19 +213,39 @@ export class BrowserPool {
|
|
|
122
213
|
for (const sessionId of expired)
|
|
123
214
|
await this.#release(sessionId);
|
|
124
215
|
this.#reapedLeases += expired.length;
|
|
216
|
+
for (const sessionId of expired) {
|
|
217
|
+
this.#reapedLeaseIds.push(sessionId);
|
|
218
|
+
if (this.#reapedLeaseIds.length > MAX_REMEMBERED_REAPED_LEASES)
|
|
219
|
+
this.#reapedLeaseIds.shift();
|
|
220
|
+
}
|
|
125
221
|
return expired;
|
|
126
222
|
}
|
|
127
223
|
/**
|
|
128
224
|
* How many leases this pool has reclaimed for going stale.
|
|
129
225
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* because nothing here knows WHICH session the caller just lost.
|
|
226
|
+
* Kept for reporting. NOT a basis for diagnosis: it is a lifetime total that never resets, so
|
|
227
|
+
* `count > 0` latches true forever after the first reap. Ask `wasReapedLease` instead, which
|
|
228
|
+
* answers about a specific session.
|
|
134
229
|
*/
|
|
135
230
|
reapedLeaseCount() {
|
|
136
231
|
return this.#reapedLeases;
|
|
137
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Did THIS session id belong to a lease this pool aged out?
|
|
235
|
+
*
|
|
236
|
+
* The question the no-session diagnosis actually has. It used to ask `reapedLeaseCount() > 0`,
|
|
237
|
+
* which meant that once any lease had ever aged out, every closed human tab for the rest of the
|
|
238
|
+
* daemon's life was reported as an expired lease — and the recovery that followed
|
|
239
|
+
* (`reticle_lease {acquire}`) would open an unauthenticated headless context and lose the app
|
|
240
|
+
* session (#611).
|
|
241
|
+
*
|
|
242
|
+
* Only the last {@link MAX_REMEMBERED_REAPED_LEASES} are remembered, so this answers `false` for
|
|
243
|
+
* a lease reaped long enough ago that thousands have followed. That direction is the safe one:
|
|
244
|
+
* it falls back to the generic "the tab was closed" wording rather than inventing a lease.
|
|
245
|
+
*/
|
|
246
|
+
wasReapedLease(sessionId) {
|
|
247
|
+
return this.#reapedLeaseIds.includes(sessionId);
|
|
248
|
+
}
|
|
138
249
|
/**
|
|
139
250
|
* Lease an isolated context, navigate it to `url`, and return a handle. If the pool is at capacity,
|
|
140
251
|
* waits FIFO until a slot frees (or until `signal` aborts, if provided).
|
|
@@ -147,6 +258,7 @@ export class BrowserPool {
|
|
|
147
258
|
await this.#waitForSlot(opts.signal);
|
|
148
259
|
const sessionId = opts.sessionId ?? this.#genId();
|
|
149
260
|
let context;
|
|
261
|
+
let pending;
|
|
150
262
|
try {
|
|
151
263
|
const browser = await this.#ensureBrowser();
|
|
152
264
|
context = await browser.newContext();
|
|
@@ -156,6 +268,19 @@ export class BrowserPool {
|
|
|
156
268
|
page.onCrash(() => {
|
|
157
269
|
void this.#release(sessionId);
|
|
158
270
|
});
|
|
271
|
+
// Listen BEFORE goto: the SDK's connect attempt races the navigation's own resolution, and a
|
|
272
|
+
// listener attached afterwards misses the first failures — the only ones logged before the
|
|
273
|
+
// retry backoff stretches out.
|
|
274
|
+
page.onConsole?.((text) => {
|
|
275
|
+
const dialled = unreachableUrlIn(text);
|
|
276
|
+
if (dialled === undefined)
|
|
277
|
+
return;
|
|
278
|
+
const active = this.#active.get(sessionId);
|
|
279
|
+
if (active !== undefined)
|
|
280
|
+
active.dialFailureUrl = dialled;
|
|
281
|
+
else
|
|
282
|
+
pending = dialled; // logged during goto, before the lease is registered below
|
|
283
|
+
});
|
|
159
284
|
await page.goto(url, { timeoutMs: this.#navTimeout });
|
|
160
285
|
// The browser can crash WHILE goto is resolving; #onCrash then clears #active and zeroes
|
|
161
286
|
// #occupied. Registering the lease now would resurrect a dead entry against a crashed browser with
|
|
@@ -163,7 +288,13 @@ export class BrowserPool {
|
|
|
163
288
|
// no longer the live browser, bail — the catch below closes the context and returns the slot.
|
|
164
289
|
if (this.#browser !== browser)
|
|
165
290
|
throw new Error('browser crashed during navigation');
|
|
166
|
-
this.#active.set(sessionId, {
|
|
291
|
+
this.#active.set(sessionId, {
|
|
292
|
+
context,
|
|
293
|
+
page,
|
|
294
|
+
url,
|
|
295
|
+
touchedAt: this.#now(),
|
|
296
|
+
...(pending === undefined ? {} : { dialFailureUrl: pending }),
|
|
297
|
+
});
|
|
167
298
|
return {
|
|
168
299
|
sessionId,
|
|
169
300
|
url,
|
|
@@ -179,6 +310,14 @@ export class BrowserPool {
|
|
|
179
310
|
throw err;
|
|
180
311
|
}
|
|
181
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* The websocket address this lease's page reported it could not reach, if it reported one.
|
|
315
|
+
*
|
|
316
|
+
* Undefined means the page said nothing — never that it dialled the right place.
|
|
317
|
+
*/
|
|
318
|
+
dialFailureUrl(sessionId) {
|
|
319
|
+
return this.#active.get(sessionId)?.dialFailureUrl;
|
|
320
|
+
}
|
|
182
321
|
/** Close every context and the browser. Pending waiters are rejected (the pool is terminal now). */
|
|
183
322
|
async shutdown() {
|
|
184
323
|
this.#closed = true; // set first so any woken waiter rejects instead of relaunching a browser
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Headless by default — the pool exists for fault-tolerant headless multi-agent testing.
|
|
7
7
|
*/
|
|
8
8
|
import { BrowserLaunchKind } from '@reticlehq/core';
|
|
9
|
+
import { chromiumLaunchOptions } from '../chromium-launch-options.js';
|
|
9
10
|
import { getSessionMetrics } from '../telemetry/session-metrics.js';
|
|
10
11
|
import { classifyConnectFailure } from '../telemetry/connect-failure.js';
|
|
11
12
|
import { chromiumInstallCommand, bundledPlaywrightVersion } from '../cli/chromium-hint.js';
|
|
@@ -39,7 +40,17 @@ function wrapBrowser(browser) {
|
|
|
39
40
|
return {
|
|
40
41
|
goto: (url, opts) => page.goto(url, gotoOptions(opts?.timeoutMs)),
|
|
41
42
|
close: () => page.close(),
|
|
43
|
+
// Playwright returns a Buffer; Uint8Array is what the visual store and differ take.
|
|
44
|
+
screenshot: async (opts) => new Uint8Array(await page.screenshot({ fullPage: true === opts?.fullPage })),
|
|
45
|
+
// Three moves, same as performGesture: a single move to the center can be a no-op if
|
|
46
|
+
// the pointer was already there, and CSS :hover needs a native hit-test to apply.
|
|
47
|
+
hover: async (x, y) => {
|
|
48
|
+
await page.mouse.move(x, y);
|
|
49
|
+
await page.mouse.move(x + 1, y);
|
|
50
|
+
await page.mouse.move(x, y);
|
|
51
|
+
},
|
|
42
52
|
onCrash: (handler) => page.on('crash', handler),
|
|
53
|
+
onConsole: (handler) => page.on('console', (msg) => handler(msg.text())),
|
|
43
54
|
};
|
|
44
55
|
},
|
|
45
56
|
close: () => context.close(),
|
|
@@ -72,7 +83,7 @@ export function playwrightLauncher(opts = {}) {
|
|
|
72
83
|
// number that actually costs memory and the one that explains a slow machine.
|
|
73
84
|
const settle = getSessionMetrics().recordConnectAttempt(BrowserLaunchKind.POOLED);
|
|
74
85
|
try {
|
|
75
|
-
const browser = wrapBrowser(await chromium.launch(
|
|
86
|
+
const browser = wrapBrowser(await chromium.launch(chromiumLaunchOptions(headless)));
|
|
76
87
|
settle();
|
|
77
88
|
return browser;
|
|
78
89
|
}
|
|
@@ -36,7 +36,10 @@ export interface FileSystemPort {
|
|
|
36
36
|
/** Modification time in epoch ms — for recency-based retention pruning. Rejects if absent. */
|
|
37
37
|
stat(path: string): Promise<{
|
|
38
38
|
mtimeMs: number;
|
|
39
|
+
size: number;
|
|
39
40
|
}>;
|
|
41
|
+
/** Resolve symlinks to their real path — for upload trust-boundary checks. Rejects if absent. */
|
|
42
|
+
realpath(path: string): Promise<string>;
|
|
40
43
|
/** ENOENT classifier — narrows unknown without `any`, so callers can distinguish missing-file. */
|
|
41
44
|
isNotFound(error: unknown): boolean;
|
|
42
45
|
}
|
package/dist/project/fs-port.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, appendFile, mkdir, open, readdir, readFile, rename, rm, stat, writeFile, } from 'node:fs/promises';
|
|
1
|
+
import { access, appendFile, mkdir, open, readdir, readFile, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
|
|
2
2
|
/** Production adapter — the single import site of node:fs/promises in the server. */
|
|
3
3
|
export function createNodeFileSystem() {
|
|
4
4
|
return {
|
|
@@ -46,8 +46,9 @@ export function createNodeFileSystem() {
|
|
|
46
46
|
},
|
|
47
47
|
stat: async (path) => {
|
|
48
48
|
const s = await stat(path);
|
|
49
|
-
return { mtimeMs: s.mtimeMs };
|
|
49
|
+
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
50
50
|
},
|
|
51
|
+
realpath: (path) => realpath(path),
|
|
51
52
|
isNotFound: (error) => 'ENOENT' === error?.code,
|
|
52
53
|
};
|
|
53
54
|
}
|
|
@@ -78,7 +78,12 @@ export function createMemoryFs() {
|
|
|
78
78
|
return Promise.resolve();
|
|
79
79
|
},
|
|
80
80
|
stat(path) {
|
|
81
|
-
return written.has(norm(path))
|
|
81
|
+
return written.has(norm(path))
|
|
82
|
+
? Promise.resolve({ mtimeMs: 0, size: 0 })
|
|
83
|
+
: Promise.reject(notFound());
|
|
84
|
+
},
|
|
85
|
+
realpath(path) {
|
|
86
|
+
return written.has(norm(path)) ? Promise.resolve(norm(path)) : Promise.reject(notFound());
|
|
82
87
|
},
|
|
83
88
|
isNotFound(error) {
|
|
84
89
|
return 'ENOENT' === error?.code;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ProjectReadError,
|
|
2
|
+
import { ProjectReadError, RunStatus } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
4
|
+
import { countSchema } from '../tools/numeric-bounds.js';
|
|
4
5
|
import { sessionIdShape } from '../tools/tool-kit.js';
|
|
5
6
|
import { asNumber, asString } from '../tools/tools-helpers.js';
|
|
6
|
-
import { fetchProjectRegressionFromCloud, resolveCloudConfig } from '../cloud/cloud-sync.js';
|
|
7
|
+
import { cloudFetch, fetchProjectRegressionFromCloud, resolveCloudConfig, } from '../cloud/cloud-sync.js';
|
|
7
8
|
import { RunStore } from '../runs/run-store.js';
|
|
8
9
|
import { diffRuns as diffVerificationRuns, } from '../runs/run-diff.js';
|
|
9
10
|
const REGRESSION_STATUSES = new Set([
|
|
@@ -45,7 +46,7 @@ async function cloudRegression(deps, sessionId) {
|
|
|
45
46
|
catch {
|
|
46
47
|
projectId = undefined;
|
|
47
48
|
}
|
|
48
|
-
const report = await fetchProjectRegressionFromCloud(config, projectId,
|
|
49
|
+
const report = await fetchProjectRegressionFromCloud(config, projectId, cloudFetch);
|
|
49
50
|
return report ?? undefined;
|
|
50
51
|
}
|
|
51
52
|
/** The two most-recent runs for `name`, oldest-first, or undefined if there are fewer than two. */
|
|
@@ -84,11 +85,10 @@ async function lastTwoRunArtifacts(deps) {
|
|
|
84
85
|
export const PROJECT_TOOLS = [
|
|
85
86
|
{
|
|
86
87
|
name: ReticleTool.PROJECT,
|
|
87
|
-
description: 'Read cross-run history from .reticle/project.json — the memory of how past runs behaved. With { name } it also returns the last run for that flow plus a diff-vs-last summary (status change, regressed flag, consoleErrors/driftSteps deltas) so you can answer "did it behave like last time?". `diff` is the lightweight status/console/drift delta vs the previous run of that NAME; `runDiff` is the per-flow duration/status delta between the two most-recent full verification artifacts ("step 3: 412ms -> 987ms (+140%), +2 requests"). When logged in to Reticle
|
|
88
|
+
description: 'Read cross-run history from .reticle/project.json — the memory of how past runs behaved. With { name } it also returns the last run for that flow plus a diff-vs-last summary (status change, regressed flag, consoleErrors/driftSteps deltas) so you can answer "did it behave like last time?". `diff` is the lightweight status/console/drift delta vs the previous run of that NAME; `runDiff` is the per-flow duration/status delta between the two most-recent full verification artifacts ("step 3: 412ms -> 987ms (+140%), +2 requests"). When logged in to Reticle, also returns `cloud`: the team\'s server-side regression report (broken/changed flows vs before) — the durable memory a fresh or context-lost agent can rely on even when local history is empty. Returns { runs, learned?, lastRun?, diff?, runDiff?, cloud? } or { error, reason, cloud? }.',
|
|
88
89
|
inputSchema: {
|
|
89
90
|
name: z.string().optional().describe('Filter runs by this name. Omit to return all runs.'),
|
|
90
|
-
limit:
|
|
91
|
-
.number()
|
|
91
|
+
limit: countSchema
|
|
92
92
|
.optional()
|
|
93
93
|
.describe('Most-recent N runs to return. Defaults to 25 — the full history is unbounded and grows with every run (measured at ~20KB / ~5,000 tokens on a modest project), which is a large slice of an agent context for data it mostly does not read. `totalRuns` always reports the true count, so a cap is never silent.'),
|
|
94
94
|
...sessionIdShape,
|
|
@@ -152,33 +152,4 @@ export const PROJECT_TOOLS = [
|
|
|
152
152
|
});
|
|
153
153
|
},
|
|
154
154
|
},
|
|
155
|
-
{
|
|
156
|
-
name: ReticleTool.RUN_RECORD,
|
|
157
|
-
description: 'Explicitly record a run outcome into .reticle/project.json (the manual companion to the auto-record on reticle_flow_replay). Use it to log the result of an assertion sequence or a manual journey so future runs can diff against it. Returns { recorded:true, name, status }.',
|
|
158
|
-
inputSchema: {
|
|
159
|
-
name: z.string().describe('Run name for grouping in reticle_project history.'),
|
|
160
|
-
status: z.nativeEnum(RunStatus).describe('Outcome: pass | fail | drift | error'),
|
|
161
|
-
kind: z.nativeEnum(RunKind).optional(),
|
|
162
|
-
summary: z.string().optional().describe('One-line human summary of what this run covered.'),
|
|
163
|
-
...sessionIdShape,
|
|
164
|
-
},
|
|
165
|
-
outputSchema: {
|
|
166
|
-
recorded: z.boolean(),
|
|
167
|
-
runName: z.string(),
|
|
168
|
-
status: z.string(),
|
|
169
|
-
},
|
|
170
|
-
handler: async (deps, args) => {
|
|
171
|
-
const name = asString(args['name']) ?? '';
|
|
172
|
-
const status = args['status'];
|
|
173
|
-
const kindArg = args['kind'];
|
|
174
|
-
const summary = asString(args['summary']);
|
|
175
|
-
await deps.project.recordRun({
|
|
176
|
-
kind: 'string' === typeof kindArg ? kindArg : RunKind.MANUAL,
|
|
177
|
-
name,
|
|
178
|
-
status,
|
|
179
|
-
...(summary !== undefined ? { summary } : {}),
|
|
180
|
-
});
|
|
181
|
-
return { recorded: true, runName: name, status };
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
155
|
];
|
|
@@ -15,6 +15,8 @@ export interface ReticleDirPaths {
|
|
|
15
15
|
project: string;
|
|
16
16
|
/**.../.reticle/impact.json (what Reticle has done for this user, local only) */
|
|
17
17
|
impact: string;
|
|
18
|
+
/**.../.reticle/intent.json (what changes were supposed to make true — git-checked) */
|
|
19
|
+
intent: string;
|
|
18
20
|
/**.../.reticle/visual (PNG baselines + diffs) */
|
|
19
21
|
visual: string;
|
|
20
22
|
/**.../.reticle/runs (verification-run artifacts) */
|
|
@@ -53,9 +55,22 @@ export declare function isValidSessionId(sessionId: string): sessionId is Sessio
|
|
|
53
55
|
*/
|
|
54
56
|
export declare function isValidRunId(runId: string): runId is RunId;
|
|
55
57
|
/** The PNG baseline path for `name`.reticle/visual/<name>.png). */
|
|
56
|
-
export declare function visualPath(root: string, name: string): string;
|
|
58
|
+
export declare function visualPath(root: string, name: string, runtime?: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* The directory a visual artifact belongs in, scoped by the runtime that produced it.
|
|
61
|
+
*
|
|
62
|
+
* An Electron window, a Tauri webview and a browser tab render the same url differently — different
|
|
63
|
+
* chrome, different fonts, a different device pixel ratio, a webview that is not the browser at all.
|
|
64
|
+
* One shared baseline makes every cross-runtime diff wrong, and the quiet direction is the dangerous
|
|
65
|
+
* one: a baseline overwritten from another runtime turns a later real regression into a pass.
|
|
66
|
+
*
|
|
67
|
+
* Scoped the way flows already are. A DESKTOP runtime gets a subdir; web, an unknown runtime, and
|
|
68
|
+
* everything captured before this existed stay on the flat path — the SDK has no screenshotter, so
|
|
69
|
+
* every pre-existing baseline came from a driven browser, and moving them would orphan them.
|
|
70
|
+
*/
|
|
71
|
+
export declare function visualDir(root: string, runtime?: string): string;
|
|
57
72
|
/** The overlay-diff PNG path for `name`.reticle/visual/<name>.diff.png). */
|
|
58
|
-
export declare function visualDiffPath(root: string, name: string): string;
|
|
73
|
+
export declare function visualDiffPath(root: string, name: string, runtime?: string): string;
|
|
59
74
|
/**
|
|
60
75
|
* Path to a flow file. With a `projectId`, the flow lives in a per-project subdir
|
|
61
76
|
* (`.reticle/flows/<projectId>/<name>.json`) so a shared daemon serving many apps can't collide two
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import { AppRuntime } from '@reticlehq/core';
|
|
2
3
|
import { CONTRACT_FILE_VERSION, ContractFileSchema, ContractReadError, FLOW_NAME_PATTERN, ReticleDir, } from '@reticlehq/core';
|
|
3
4
|
export function reticleDirPaths(root) {
|
|
4
5
|
return {
|
|
@@ -8,6 +9,7 @@ export function reticleDirPaths(root) {
|
|
|
8
9
|
baselines: join(root, ReticleDir.BASELINES_SUBDIR),
|
|
9
10
|
project: join(root, ReticleDir.PROJECT_FILE),
|
|
10
11
|
impact: join(root, ReticleDir.IMPACT_FILE),
|
|
12
|
+
intent: join(root, ReticleDir.INTENT_FILE),
|
|
11
13
|
visual: join(root, ReticleDir.VISUAL_SUBDIR),
|
|
12
14
|
runs: join(root, ReticleDir.RUNS_SUBDIR),
|
|
13
15
|
sessions: join(root, ReticleDir.SESSIONS_SUBDIR),
|
|
@@ -50,12 +52,28 @@ export function isValidRunId(runId) {
|
|
|
50
52
|
return FLOW_NAME_PATTERN.test(runId) && !runId.includes('..');
|
|
51
53
|
}
|
|
52
54
|
/** The PNG baseline path for `name`.reticle/visual/<name>.png). */
|
|
53
|
-
export function visualPath(root, name) {
|
|
54
|
-
return join(root,
|
|
55
|
+
export function visualPath(root, name, runtime) {
|
|
56
|
+
return join(visualDir(root, runtime), `${name}.png`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The directory a visual artifact belongs in, scoped by the runtime that produced it.
|
|
60
|
+
*
|
|
61
|
+
* An Electron window, a Tauri webview and a browser tab render the same url differently — different
|
|
62
|
+
* chrome, different fonts, a different device pixel ratio, a webview that is not the browser at all.
|
|
63
|
+
* One shared baseline makes every cross-runtime diff wrong, and the quiet direction is the dangerous
|
|
64
|
+
* one: a baseline overwritten from another runtime turns a later real regression into a pass.
|
|
65
|
+
*
|
|
66
|
+
* Scoped the way flows already are. A DESKTOP runtime gets a subdir; web, an unknown runtime, and
|
|
67
|
+
* everything captured before this existed stay on the flat path — the SDK has no screenshotter, so
|
|
68
|
+
* every pre-existing baseline came from a driven browser, and moving them would orphan them.
|
|
69
|
+
*/
|
|
70
|
+
export function visualDir(root, runtime) {
|
|
71
|
+
const visual = join(root, ReticleDir.VISUAL_SUBDIR);
|
|
72
|
+
return runtime === undefined || AppRuntime.WEB === runtime ? visual : join(visual, runtime);
|
|
55
73
|
}
|
|
56
74
|
/** The overlay-diff PNG path for `name`.reticle/visual/<name>.diff.png). */
|
|
57
|
-
export function visualDiffPath(root, name) {
|
|
58
|
-
return join(root,
|
|
75
|
+
export function visualDiffPath(root, name, runtime) {
|
|
76
|
+
return join(visualDir(root, runtime), `${name}.diff.png`);
|
|
59
77
|
}
|
|
60
78
|
/**
|
|
61
79
|
* Path to a flow file. With a `projectId`, the flow lives in a per-project subdir
|