@reticlehq/server 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +23 -22
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +41 -0
- package/dist/command/cli/adhoc-suite.js +159 -0
- package/dist/command/cli/adhoc-verdict.d.ts +58 -2
- package/dist/command/cli/adhoc-verdict.js +108 -13
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +30 -8
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +14 -1
- package/dist/command/cli/cli-parse.js +54 -8
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +80 -0
- package/dist/command/cli/cli-verify.js +181 -26
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +9 -29
- package/dist/command/cli/launch/cli-launch.js +27 -49
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
- package/dist/command/cli/ports/resolve/cli-port.js +169 -15
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +67 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +171 -156
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +36 -22
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +13 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +78 -104
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +40 -9
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +33 -12
- package/dist/language/flows/flow-replay-run.js +131 -52
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +27 -12
- package/dist/language/flows/flow-replay.js +127 -111
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +18 -5
- package/dist/language/flows/flows.js +101 -16
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +53 -13
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/verify-change-tools.js +19 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -3
- package/dist/memory/cloud/cloud-sync.js +7 -10
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +78 -1
- package/dist/memory/cloud/sync-cycle.js +321 -53
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +45 -20
- package/dist/memory/cloud/sync-disk.js +27 -8
- package/dist/memory/impact/impact-store.js +6 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +252 -140
- package/dist/memory/intent/intent-shard.d.ts +0 -23
- package/dist/memory/intent/intent-shard.js +1 -11
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +19 -14
- package/dist/memory/journal/drive-flow.js +65 -48
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +13 -2
- package/dist/memory/journal/session-end.js +88 -18
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/fs/fs-port.js +44 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +31 -5
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +21 -13
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +43 -3
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +29 -6
- package/dist/portal/session/session-manager.js +42 -5
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +10 -0
- package/dist/portal/session/session.js +34 -26
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +5 -5
- package/dist/surface/mcp/mcp.js +4 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +21 -8
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +68 -8
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +51 -74
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +26 -3
- package/dist/surface/tools/assert/assert-verdict.js +32 -6
- package/dist/surface/tools/browser-tools.js +17 -4
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/invoke-tool.js +17 -4
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +68 -11
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +23 -7
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +68 -29
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +34 -0
- package/dist/wire-journal.js +104 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +5 -2
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +21 -9
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +32 -2
- package/docs/frameworks.mdx +140 -9
- package/docs/getting-started.md +3 -3
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +37 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +176 -7
- package/docs/quickstart.mdx +2 -2
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +11 -11
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +8 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +62 -0
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +11 -10
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
- package/dist/judgement/phenomena/phenomena.d.ts +0 -26
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -25,6 +25,8 @@ function paramInfo(shape) {
|
|
|
25
25
|
function unknownParamsError(toolName, unknown) {
|
|
26
26
|
return `unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')} — NOT applied, so any result would be an answer to a different question`;
|
|
27
27
|
}
|
|
28
|
+
/** The one `action` reticle_tools takes: every other tool on the surface is action-dispatched. */
|
|
29
|
+
const TOOLS_LIST_ACTION = 'list';
|
|
28
30
|
/** What to write instead — the one parameter reticle_tools declares, and the no-argument form. */
|
|
29
31
|
const TOOLS_ARG_HINT = 'name the tools you want in `names`: reticle_tools { names: ["reticle_act_and_wait"] } — or call it with no arguments for the full catalog';
|
|
30
32
|
/** The keys not declared by `shape`, in call order. */
|
|
@@ -92,6 +94,10 @@ callable) {
|
|
|
92
94
|
},
|
|
93
95
|
};
|
|
94
96
|
const toolsShape = {
|
|
97
|
+
action: z
|
|
98
|
+
.literal(TOOLS_LIST_ACTION)
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('"list": the catalogue, same as no arguments.'),
|
|
95
101
|
names: z
|
|
96
102
|
.array(z.string())
|
|
97
103
|
.optional()
|
|
@@ -67,7 +67,7 @@ export const RECOVERY = {
|
|
|
67
67
|
UNKNOWN_SESSION: 'That sessionId is not connected. Call reticle_sessions for the current ids and retry with a valid one.',
|
|
68
68
|
THROTTLED: 'The target tab is backgrounded/throttled, so actions may silently no-op. Ask the human to bring ' +
|
|
69
69
|
'the tab to the front, or acquire a guaranteed scriptable context yourself with ' +
|
|
70
|
-
'`reticle_run { tool: "reticle_lease", action: "acquire", url }` (a human can equivalently run ' +
|
|
70
|
+
'`reticle_run { tool: "reticle_lease", args: { action: "acquire", url } }` (a human can equivalently run ' +
|
|
71
71
|
'`reticle drive <url>`).',
|
|
72
72
|
MISSING_BASELINE: 'That baseline does not exist yet. Call reticle_baseline { action: "list" } to see saved names, or ' +
|
|
73
73
|
'reticle_baseline { action: "save", name } to capture one before diffing against it.',
|
|
@@ -159,7 +159,7 @@ export const RECOVERY = {
|
|
|
159
159
|
'assert on the result instead of typing it. Known gap, already reported: no need to file it.',
|
|
160
160
|
HOVER_NEEDS_POINTER: 'Hover needs a real pointer: a synthetic mouseover does not apply CSS :hover, and Reticle ' +
|
|
161
161
|
'refuses rather than reporting the styles as applied. Acquire a tab with ' +
|
|
162
|
-
'reticle_run { tool: "reticle_lease", action: "acquire", url } — reticle_lease is not ' +
|
|
162
|
+
'reticle_run { tool: "reticle_lease", args: { action: "acquire", url } } — reticle_lease is not ' +
|
|
163
163
|
'advertised under the default profile, so it is reached through reticle_run, not called ' +
|
|
164
164
|
'directly — or ask the human to drive with `reticle drive` / RETICLE_CDP_URL. This is a ' +
|
|
165
165
|
'deliberate refusal, not a defect: there is nothing to report.',
|
|
@@ -182,7 +182,7 @@ export const RECOVERY = {
|
|
|
182
182
|
COMMAND_TIMEOUT: 'The page did not answer within the command window. That is a fact about the page, not a Reticle ' +
|
|
183
183
|
'failure: check reticle_sessions for `throttled`/`stale` on this session — a backgrounded tab is ' +
|
|
184
184
|
'throttled and may never answer, so ask the human to bring it to the front or drive your own ' +
|
|
185
|
-
'browser with reticle_run { tool: "reticle_lease", action: "acquire", url } — reticle_lease is ' +
|
|
185
|
+
'browser with reticle_run { tool: "reticle_lease", args: { action: "acquire", url } } — reticle_lease is ' +
|
|
186
186
|
'not advertised under the default profile, so it is reached through reticle_run, not called ' +
|
|
187
187
|
'directly. If the tab is in front, the page is busy or blocked (a long ' +
|
|
188
188
|
'synchronous task, an alert/confirm dialog); reticle_console usually shows what it hit. Retry ' +
|
|
@@ -495,7 +495,7 @@ const INVALID_NAME_REJECTION = /^invalid (?:[a-z]+ )*name: /i;
|
|
|
495
495
|
* us nothing new.
|
|
496
496
|
*/
|
|
497
497
|
export const FEEDBACK_ASK = 'This error is not one Reticle recognizes, which means it may be a defect in Reticle rather than in ' +
|
|
498
|
-
'the app. If you believe Reticle misbehaved, call
|
|
498
|
+
'the app. If you believe Reticle misbehaved, call reticle_session { action: "feedback" } with a root-cause analysis and ' +
|
|
499
499
|
'the call trace before moving on — that report is the only way this gets fixed.';
|
|
500
500
|
/**
|
|
501
501
|
* Build the tool-error payload spliced at the MCP boundary. `recovery` is added when the error is
|
|
@@ -36,12 +36,14 @@ export const FrictionKind = {
|
|
|
36
36
|
* a run of three is where a deliberate retry becomes a loop.
|
|
37
37
|
*/
|
|
38
38
|
const REPEAT_INVITE_AT = 3;
|
|
39
|
+
/** The call that files feedback. The old `reticle_feedback` name only answers with a redirect. */
|
|
40
|
+
const FEEDBACK_CALL = 'reticle_session { action: "feedback" }';
|
|
39
41
|
const LINES = {
|
|
40
|
-
[FrictionKind.UNKNOWN_TOOL]:
|
|
41
|
-
[FrictionKind.UNKNOWN_VERDICT]:
|
|
42
|
+
[FrictionKind.UNKNOWN_TOOL]: `no such tool — ${FEEDBACK_CALL} tells us what you expected it to do, which is how it gets built`,
|
|
43
|
+
[FrictionKind.UNKNOWN_VERDICT]: `Reticle could not tell what happened here. If you expected otherwise, ${FEEDBACK_CALL}. An ` +
|
|
42
44
|
'unknown verdict is our defect, not yours',
|
|
43
|
-
[FrictionKind.REPEATING]:
|
|
44
|
-
[FrictionKind.REFUSED]:
|
|
45
|
+
[FrictionKind.REPEATING]: `stuck on the same call? ${FEEDBACK_CALL} — say what you were trying to do and it gets fixed`,
|
|
46
|
+
[FrictionKind.REFUSED]: `if that diagnosis was wrong or unhelpful, ${FEEDBACK_CALL} — error messages are a product ` +
|
|
45
47
|
'surface and we fix the ones that fail',
|
|
46
48
|
};
|
|
47
49
|
/**
|
|
@@ -8,7 +8,7 @@ export declare const FEEDBACK_TOOLS: ToolDef[];
|
|
|
8
8
|
* Delivered ONCE per server process. A prompt on every verification would be nagging, and a nagged
|
|
9
9
|
* user opts out of telemetry entirely, which costs us the counters too.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
declare const FEEDBACK_PROMPT: {
|
|
12
12
|
readonly ask: "Ask the human, in your own words and only once: how did this verification go, and what would have made Reticle more useful? If they answer, file it with `reticle feedback` (they run it) or relay it yourself.";
|
|
13
13
|
readonly command: "reticle feedback";
|
|
14
14
|
};
|
|
@@ -33,3 +33,4 @@ export declare const VERDICT_TOOLS: ReadonlySet<string>;
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function takeFeedbackPrompt(toolName: string): typeof FEEDBACK_PROMPT | undefined;
|
|
35
35
|
export declare function resetFeedbackPrompt(): void;
|
|
36
|
+
export {};
|
|
@@ -196,7 +196,7 @@ export const FEEDBACK_TOOLS = [
|
|
|
196
196
|
* Delivered ONCE per server process. A prompt on every verification would be nagging, and a nagged
|
|
197
197
|
* user opts out of telemetry entirely, which costs us the counters too.
|
|
198
198
|
*/
|
|
199
|
-
|
|
199
|
+
const FEEDBACK_PROMPT = {
|
|
200
200
|
ask: 'Ask the human, in your own words and only once: how did this verification go, and what would have made Reticle more useful? If they answer, file it with `reticle feedback` (they run it) or relay it yourself.',
|
|
201
201
|
command: 'reticle feedback',
|
|
202
202
|
};
|
|
@@ -98,8 +98,9 @@ export declare const MSG_NO_OPENAI_KEY: string;
|
|
|
98
98
|
*
|
|
99
99
|
* Resolved into the env rather than threaded through four call sites: the drivers and the
|
|
100
100
|
* preference lookup all read an env record, and giving them a completed one leaves each of them
|
|
101
|
-
* exactly as simple as it was.
|
|
102
|
-
*
|
|
101
|
+
* exactly as simple as it was. The key precedence is the resolver's, the same as sync's and the
|
|
102
|
+
* CLI's: the stored key for the link's host, else the exported one (which is how CI, with no
|
|
103
|
+
* keystore, gets here). An explicitly exported HOST still wins, so a proxy is not overridden.
|
|
103
104
|
*
|
|
104
105
|
* The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
|
|
105
106
|
* the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* nothing grades) but the flows that now exist on disk, because every future run replays those
|
|
8
8
|
* deterministically with no model in the loop at all.
|
|
9
9
|
*/
|
|
10
|
-
import { ReticleEnv, ReticleTool,
|
|
10
|
+
import { ReticleEnv, ReticleTool, cloudUrlFrom, asRecord } from '@reticlehq/core';
|
|
11
11
|
import { projectForRoot } from '../../memory/project/project-for-root.js';
|
|
12
12
|
import { harnessDriver, harnessOptionsFromEnv, } from '../../features/harness/driver.js';
|
|
13
13
|
import { fillValues } from '../../features/harness/fill-values.js';
|
|
@@ -22,6 +22,7 @@ import { openAiDriver, openAiOptionsFromEnv } from '../../features/harness/opena
|
|
|
22
22
|
import { fetchPlatformConfig } from '../../features/harness/platform-config.js';
|
|
23
23
|
import { DEFAULT_MAX_STEPS, runHarness, } from '../../features/harness/harness.js';
|
|
24
24
|
import { reticleToolset } from './harness-toolset.js';
|
|
25
|
+
import { secretEnvKey } from '../../language/flows/flows.js';
|
|
25
26
|
/**
|
|
26
27
|
* Why the harness is unavailable, phrased as the way to make it available.
|
|
27
28
|
*
|
|
@@ -53,15 +54,14 @@ const msgUnknownDriver = (asked) => `Unknown harness driver \`${asked}\`. Known
|
|
|
53
54
|
*
|
|
54
55
|
* Resolved into the env rather than threaded through four call sites: the drivers and the
|
|
55
56
|
* preference lookup all read an env record, and giving them a completed one leaves each of them
|
|
56
|
-
* exactly as simple as it was.
|
|
57
|
-
*
|
|
57
|
+
* exactly as simple as it was. The key precedence is the resolver's, the same as sync's and the
|
|
58
|
+
* CLI's: the stored key for the link's host, else the exported one (which is how CI, with no
|
|
59
|
+
* keystore, gets here). An explicitly exported HOST still wins, so a proxy is not overridden.
|
|
58
60
|
*
|
|
59
61
|
* The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
|
|
60
62
|
* the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
|
|
61
63
|
*/
|
|
62
64
|
export async function withLinkedCredential(deps, env) {
|
|
63
|
-
if (apiKeyFrom(env) !== undefined)
|
|
64
|
-
return env;
|
|
65
65
|
try {
|
|
66
66
|
const linked = await deps.linkedCloud?.();
|
|
67
67
|
if (linked === undefined || null === linked)
|
|
@@ -69,7 +69,7 @@ export async function withLinkedCredential(deps, env) {
|
|
|
69
69
|
return {
|
|
70
70
|
...env,
|
|
71
71
|
[ReticleEnv.API_KEY]: linked.apiKey,
|
|
72
|
-
[ReticleEnv.CLOUD_URL]: env
|
|
72
|
+
[ReticleEnv.CLOUD_URL]: cloudUrlFrom(env) ?? linked.url,
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
catch {
|
|
@@ -412,6 +412,7 @@ function buildDriver(env, maxSteps, plan, requested, fills) {
|
|
|
412
412
|
* With no such key the label heuristic answers, exactly as it did before this existed.
|
|
413
413
|
*/
|
|
414
414
|
fillValue: fillValues({
|
|
415
|
+
secret: (label) => env[secretEnvKey(label)],
|
|
415
416
|
...(fills === undefined ? {} : { cache: fills }),
|
|
416
417
|
...(harnessOptionsFromEnv(env) === undefined
|
|
417
418
|
? {}
|
|
@@ -17,7 +17,7 @@ import { reportOnboardingStep } from '../../telemetry/onboarding-funnel.js';
|
|
|
17
17
|
import { noteActed, noteFirstVerdict, noteOnboardingFirst } from '../../telemetry/onboarding-firsts.js';
|
|
18
18
|
import { withHarnessDrive } from '../../telemetry/harness-drive.js';
|
|
19
19
|
import { OnboardingPhase, OnboardingStepStatus } from '@reticlehq/core/telemetry';
|
|
20
|
-
import { asString } from '@reticlehq/core';
|
|
20
|
+
import { DiscoveryInvite, asString } from '@reticlehq/core';
|
|
21
21
|
import { SESSION_ID_ARG, sessionIdFromArgs, spentRefFromArgs } from './tools-helpers.js';
|
|
22
22
|
import { EnvelopeKey } from './tool-kit.js';
|
|
23
23
|
import { ReticleTool } from '@reticlehq/core';
|
|
@@ -134,7 +134,7 @@ export const SESSION_EXEMPT_TOOLS = new Set([
|
|
|
134
134
|
// FLOW_HEAL is no longer a name runTool ever sees: it became `reticle_verify {action:"heal"}`,
|
|
135
135
|
// and VERIFY is already listed here, so the exemption is inherited from the parent. Left as a
|
|
136
136
|
// dangling name it would be a lie this file's own test 6 is written to catch.
|
|
137
|
-
ReticleTool.INTENT, // reads/writes .reticle/intent
|
|
137
|
+
ReticleTool.INTENT, // reads/writes .reticle/intent/; sessionId only picks the project
|
|
138
138
|
ReticleTool.CONTEXT, // folds the journal + intent ledger; must still answer when nothing is connected
|
|
139
139
|
ReticleTool.PROJECT, // reads .reticle/project.json
|
|
140
140
|
// Reads the team's shared memory over HTTP; sessionId only resolves WHICH project's link file to
|
|
@@ -609,7 +609,13 @@ export async function runTool(tool, deps, args) {
|
|
|
609
609
|
? raw
|
|
610
610
|
: {
|
|
611
611
|
...raw,
|
|
612
|
-
...(friction !== undefined
|
|
612
|
+
...(friction !== undefined
|
|
613
|
+
? {
|
|
614
|
+
[EnvelopeKey.FEEDBACK_INVITE]: inviteFor(friction),
|
|
615
|
+
// The moment Reticle got in somebody's way is the moment to offer them the founder.
|
|
616
|
+
[EnvelopeKey.TALK_TO_US]: DiscoveryInvite.AGENT,
|
|
617
|
+
}
|
|
618
|
+
: {}),
|
|
613
619
|
...(prompt !== undefined ? { [EnvelopeKey.FEEDBACK_PROMPT]: prompt } : {}),
|
|
614
620
|
...(update !== undefined ? { [EnvelopeKey.UPDATE_AVAILABLE]: update } : {}),
|
|
615
621
|
...(skew !== undefined ? { [EnvelopeKey.VERSION_SKEW]: skew } : {}),
|
|
@@ -623,7 +629,14 @@ export async function runTool(tool, deps, args) {
|
|
|
623
629
|
return result;
|
|
624
630
|
// Reuse the session resolved above so the health envelope describes the SAME session the handler
|
|
625
631
|
// drove; only re-resolve if the up-front attempt failed but the handler somehow succeeded.
|
|
626
|
-
const
|
|
632
|
+
const driven = session ?? deps.sessions.resolve(rawSessionId);
|
|
633
|
+
// ...unless the call replaced that document. A full navigation or reload registers a NEW Session
|
|
634
|
+
// under the same id (or under the id the result reports it arrived at), and the object resolved
|
|
635
|
+
// before the call is the page that unloaded: it reported itself hidden on the way out, so every
|
|
636
|
+
// response after a navigation said "throttled" about a tab that was fine. Describe what is there
|
|
637
|
+
// now. Optional-called because a test double is a partial SessionManager.
|
|
638
|
+
const arrivedId = 'string' === typeof result['sessionId'] ? result['sessionId'] : driven.id;
|
|
639
|
+
const resolved = deps.sessions.get?.(arrivedId) ?? driven;
|
|
627
640
|
const envelope = {};
|
|
628
641
|
// The health block is idempotent: add it only when the handler didn't already include a `session`.
|
|
629
642
|
if (!('session' in result))
|
|
@@ -36,6 +36,14 @@ export interface LeaseEvidence {
|
|
|
36
36
|
* reported as a marker that was not found.
|
|
37
37
|
*/
|
|
38
38
|
sdkMarker?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The page's own Content-Security-Policy, read off the served document, when it refuses the
|
|
41
|
+
* bridge. Proof rather than a guess: the connection cannot happen until the policy allows it.
|
|
42
|
+
*/
|
|
43
|
+
cspBlock?: {
|
|
44
|
+
problem: string;
|
|
45
|
+
fix: string;
|
|
46
|
+
};
|
|
39
47
|
/**
|
|
40
48
|
* The websocket URL the leased page actually tried, read from its own console.
|
|
41
49
|
*
|
|
@@ -85,6 +85,16 @@ export function leaseNotConnectedHint(url, port, evidence = {}) {
|
|
|
85
85
|
`RETICLE_PORT, or the build plugin's \`port\`), and \`VITE_RETICLE_WS_URL\` overrides it ` +
|
|
86
86
|
`outright.${marker} ${RELEASE}`);
|
|
87
87
|
}
|
|
88
|
+
// 0b. The page's own policy forbids the connection. Read off the document it served, so it is
|
|
89
|
+
// proof about the page in hand, and it holds whether or not the app ships an SDK: neither
|
|
90
|
+
// `init` nor the zero-install reader can dial out of a page that forbids it. Every cause
|
|
91
|
+
// below presupposes a page that is allowed to connect, which is why this comes first.
|
|
92
|
+
if (evidence.cspBlock !== undefined) {
|
|
93
|
+
return (`${opening} This page's Content-Security-Policy blocks the connection: ` +
|
|
94
|
+
`${evidence.cspBlock.problem}. That is true whether or not the app has Reticle installed, ` +
|
|
95
|
+
`so installing it will not help until the policy allows the bridge. For development, ` +
|
|
96
|
+
`${evidence.cspBlock.fix}. ${RELEASE}`);
|
|
97
|
+
}
|
|
88
98
|
// 1. A recorded refusal. The only cause with proof behind it, so nothing outranks it.
|
|
89
99
|
if (evidence.refusal !== undefined) {
|
|
90
100
|
return `${opening} A dial DID arrive and was refused: ${evidence.refusal}${marker} ${RELEASE}`;
|
|
@@ -15,8 +15,6 @@ import { type NetworkMutationPort } from '../../portal/input/network-mutation.js
|
|
|
15
15
|
import type { Perturbation } from '@reticlehq/core';
|
|
16
16
|
import { type SeedStorage } from '@reticlehq/core';
|
|
17
17
|
import type { ToolDef } from './tool-kit.js';
|
|
18
|
-
/** The origin of a URL, or undefined when it does not parse. */
|
|
19
|
-
export declare function originOf(url: string): string | undefined;
|
|
20
18
|
/**
|
|
21
19
|
* Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
|
|
22
20
|
* adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
|
|
@@ -78,31 +76,6 @@ export declare function resolveLeasedSessionId(sessions: {
|
|
|
78
76
|
* a human's own URL.
|
|
79
77
|
*/
|
|
80
78
|
export declare function carryReticleIdentity(fromUrl: string | undefined, toUrl: string): string;
|
|
81
|
-
/** The narrow slice of a session the probe needs. Anything that quacks like this works. */
|
|
82
|
-
interface ProbeableSession {
|
|
83
|
-
command?: (name: string, args: Record<string, unknown>, timeoutMs: number) => Promise<unknown>;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Does this tab still answer?
|
|
87
|
-
*
|
|
88
|
-
* PRESENCE IS NOT LIVENESS. The sessions map still holds a tab that is attached, streaming events,
|
|
89
|
-
* and answering nothing, so `ready` read off a row in that map hands back a lease that `snapshot`,
|
|
90
|
-
* `state` and `console` then reject (#692). It has to mean the tab replied.
|
|
91
|
-
*
|
|
92
|
-
* ANY reply counts, including one that reports the command failed. The question is whether the SDK
|
|
93
|
-
* answers at all, not what it says — so an SDK too old to know the command replies
|
|
94
|
-
* `unknown command '…'`, and that is proof. Only the absence of a reply is evidence of absence:
|
|
95
|
-
* `PendingCommands.track` REJECTS on timeout and on a dropped socket, and resolves on every real
|
|
96
|
-
* answer, so the two cases are already separated for us.
|
|
97
|
-
*
|
|
98
|
-
* `CAPABILITIES` is the probe because it takes no arguments and returns a small fixed list. No new
|
|
99
|
-
* wire command is introduced: this rides an existing round trip.
|
|
100
|
-
*
|
|
101
|
-
* Fails OPEN. A registry entry with no `command` is a shape this code did not put there, and
|
|
102
|
-
* turning a lease that works into a refusal over a probe that could not run would be a worse
|
|
103
|
-
* failure than the one being fixed.
|
|
104
|
-
*/
|
|
105
|
-
export declare function probeLeaseAlive(session: ProbeableSession | undefined, timeoutMs?: number): Promise<boolean>;
|
|
106
79
|
/**
|
|
107
80
|
* Poll until the leased tab's SDK has actually registered on the bridge, so the sessionId we return
|
|
108
81
|
* is immediately usable (the page navigates first, then connects a moment later — without this wait
|
|
@@ -122,6 +95,7 @@ export declare function acquireLeasedSession(pool: {
|
|
|
122
95
|
sessionId: string;
|
|
123
96
|
release: () => Promise<void>;
|
|
124
97
|
navStatus?: number;
|
|
98
|
+
injectReader?: () => Promise<boolean>;
|
|
125
99
|
}>;
|
|
126
100
|
/**
|
|
127
101
|
* The address this lease's page said it could not reach. Optional so a test double need not
|
|
@@ -141,6 +115,21 @@ export declare function acquireLeasedSession(pool: {
|
|
|
141
115
|
release: () => Promise<void>;
|
|
142
116
|
}>;
|
|
143
117
|
export declare function waitForLeasedSession(isConnected: () => boolean, sleeper?: (ms: number) => Promise<void>, attempts?: number, pollMs?: number): Promise<boolean>;
|
|
118
|
+
/**
|
|
119
|
+
* Wait for the leased tab to connect; if its app never does, hand it the zero-install reader.
|
|
120
|
+
*
|
|
121
|
+
* The decision is taken HERE, by the daemon, on the one fact that settles it: no HELLO arrived. A
|
|
122
|
+
* timer inside the page cannot know that — an app can connect from a dynamic import that resolves
|
|
123
|
+
* after `load` — and a reader injected into a page whose app was about to connect would take its
|
|
124
|
+
* place and drop the app's own options. So an app that connected itself is never touched, and a page
|
|
125
|
+
* that stayed silent through the whole readiness wait is the one that gets a reader.
|
|
126
|
+
*/
|
|
127
|
+
export declare function connectOrInject(lease: {
|
|
128
|
+
injectReader?: () => Promise<boolean>;
|
|
129
|
+
}, isConnected: () => boolean, wait?: (isConnected: () => boolean) => Promise<boolean>): Promise<{
|
|
130
|
+
ready: boolean;
|
|
131
|
+
zeroInstall: boolean;
|
|
132
|
+
}>;
|
|
144
133
|
export declare function hasOriginLock(origin: string): boolean;
|
|
145
134
|
export declare const LEASE_ACQUIRE_TOOL: ToolDef;
|
|
146
135
|
export declare const LEASE_TOOLS: ToolDef[];
|
|
@@ -192,4 +181,3 @@ export declare function suiteFixturePort(realInput: RealInputProvider | undefine
|
|
|
192
181
|
* is the reach the boundary guard exists to ask about.
|
|
193
182
|
*/
|
|
194
183
|
export declare function sessionMutationPort(realInput: RealInputProvider | undefined, appUrl: string | undefined): Promise<NetworkMutationPort | undefined>;
|
|
195
|
-
export {};
|
|
@@ -14,12 +14,14 @@ import { mutationPortFor } from '../../portal/input/network-mutation.js';
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { leaseNotConnectedHint } from './lease-hint.js';
|
|
16
16
|
import { probeSdkMarker } from './gaps/sdk-marker-probe.js';
|
|
17
|
+
import { observeWebDocument } from '../../portal/session/dev-server/served-document.js';
|
|
18
|
+
import { diagnoseObservedWebCsp } from '@reticlehq/init';
|
|
17
19
|
import { readProjectFramework, readProjectId } from '../../command/cli/ports/resolve/cli-port.js';
|
|
18
20
|
import { hasAnyAppConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
|
|
19
21
|
import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../../portal/session/lease-visibility.js';
|
|
20
22
|
import { reticleStateHome } from '../../command/daemon/daemon.js';
|
|
21
23
|
import { LeaseNotReadyReason, REDACTED_VALUE, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, ReticleCommand, SeedStorageSchema, scrubKnownSecrets, } from '@reticlehq/core';
|
|
22
|
-
import { ReticleTool } from '@reticlehq/core';
|
|
24
|
+
import { ReticleTool, SESSION_HEALTH } from '@reticlehq/core';
|
|
23
25
|
import { asString } from '@reticlehq/core';
|
|
24
26
|
import { chromiumHint } from '../../command/cli/doctor/browser/chromium-hint.js';
|
|
25
27
|
/**
|
|
@@ -36,14 +38,38 @@ import { chromiumHint } from '../../command/cli/doctor/browser/chromium-hint.js'
|
|
|
36
38
|
* to print a paragraph, and it can only ever add a sentence: the probe returns `undefined` on any
|
|
37
39
|
* doubt and the hint then says nothing about markers.
|
|
38
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* Whether the served page's own CSP refuses the bridge's SOCKET, read by the same code `doctor` uses.
|
|
43
|
+
*
|
|
44
|
+
* Only `connect-src` counts here. A lease injects the SDK itself, so the inline-snippet problem a
|
|
45
|
+
* `script-src` finding describes does not apply; what stops a leased page is the socket. Said in the
|
|
46
|
+
* lease's own words, because doctor's advice is about wiring a project, not about a tab.
|
|
47
|
+
*/
|
|
48
|
+
async function observedCspBlock(url, port) {
|
|
49
|
+
const document = await observeWebDocument(url);
|
|
50
|
+
if (document === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
const refusesSocket = diagnoseObservedWebCsp(document, port).some((finding) => `${finding.problem} ${finding.fix}`.includes(CONNECT_SRC));
|
|
53
|
+
if (!refusesSocket)
|
|
54
|
+
return undefined;
|
|
55
|
+
const allow = `ws://localhost:${String(port)} ws://127.0.0.1:${String(port)}`;
|
|
56
|
+
return {
|
|
57
|
+
problem: `its ${CONNECT_SRC} does not allow ws://localhost:${String(port)}`,
|
|
58
|
+
fix: `add ${allow} to ${CONNECT_SRC}, behind a development-only check`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** The CSP directive that governs the socket the SDK dials. */
|
|
62
|
+
const CONNECT_SRC = 'connect-src';
|
|
39
63
|
async function leaseEvidence(deps, port, url) {
|
|
40
64
|
const cwd = process.cwd();
|
|
41
65
|
const projectId = readProjectId(cwd);
|
|
42
66
|
const refusal = deps.sessions.lastClosure()?.reason;
|
|
43
67
|
const framework = readProjectFramework(cwd);
|
|
44
68
|
const sdkMarker = await probeSdkMarker(url);
|
|
69
|
+
const cspBlock = await observedCspBlock(url, port);
|
|
45
70
|
return {
|
|
46
71
|
...(refusal === undefined ? {} : { refusal }),
|
|
72
|
+
...(cspBlock === undefined ? {} : { cspBlock }),
|
|
47
73
|
...(framework === undefined ? {} : { framework }),
|
|
48
74
|
...(sdkMarker === undefined ? {} : { sdkMarker }),
|
|
49
75
|
previouslyConnected: hasAnyAppConnectedBefore(reticleStateHome(), port, projectId),
|
|
@@ -52,7 +78,7 @@ async function leaseEvidence(deps, port, url) {
|
|
|
52
78
|
}
|
|
53
79
|
const POOL_UNAVAILABLE = 'browser pool unavailable — the lease tools need the daemon-managed pool (start Reticle via `reticle mcp`).';
|
|
54
80
|
/** The origin of a URL, or undefined when it does not parse. */
|
|
55
|
-
|
|
81
|
+
function originOf(url) {
|
|
56
82
|
try {
|
|
57
83
|
return new URL(url).origin;
|
|
58
84
|
}
|
|
@@ -139,6 +165,10 @@ export function scrubSeedFromError(text, seed) {
|
|
|
139
165
|
}
|
|
140
166
|
return out;
|
|
141
167
|
}
|
|
168
|
+
/** True for a Playwright `storageState()` export (`{ origins: [...] }`), not our seed shape. */
|
|
169
|
+
function looksLikeStorageStateExport(seed) {
|
|
170
|
+
return Array.isArray(seed?.origins);
|
|
171
|
+
}
|
|
142
172
|
/**
|
|
143
173
|
* Turn a raw navigation failure (Playwright's `page.goto: net::ERR_… at <url>\nCall log:…`, often
|
|
144
174
|
* with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
|
|
@@ -298,7 +328,7 @@ const LEASE_PROBE_TIMEOUT_MS = 1_500;
|
|
|
298
328
|
* turning a lease that works into a refusal over a probe that could not run would be a worse
|
|
299
329
|
* failure than the one being fixed.
|
|
300
330
|
*/
|
|
301
|
-
|
|
331
|
+
async function probeLeaseAlive(session, timeoutMs = LEASE_PROBE_TIMEOUT_MS) {
|
|
302
332
|
const send = session?.command;
|
|
303
333
|
if (send === undefined)
|
|
304
334
|
return true;
|
|
@@ -338,7 +368,7 @@ export async function acquireLeasedSession(pool, sessions, url, projectId, seedS
|
|
|
338
368
|
// id comes back, so handing it the lease id for an app that named its own session sent every flow
|
|
339
369
|
// in the run at a session that does not exist.
|
|
340
370
|
let registeredId;
|
|
341
|
-
await
|
|
371
|
+
await connectOrInject(lease, () => {
|
|
342
372
|
registeredId = resolveLeasedSessionId(sessions, lease.sessionId);
|
|
343
373
|
return registeredId !== undefined;
|
|
344
374
|
});
|
|
@@ -364,6 +394,23 @@ export async function waitForLeasedSession(isConnected, sleeper = sleep, attempt
|
|
|
364
394
|
}
|
|
365
395
|
return isConnected();
|
|
366
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Wait for the leased tab to connect; if its app never does, hand it the zero-install reader.
|
|
399
|
+
*
|
|
400
|
+
* The decision is taken HERE, by the daemon, on the one fact that settles it: no HELLO arrived. A
|
|
401
|
+
* timer inside the page cannot know that — an app can connect from a dynamic import that resolves
|
|
402
|
+
* after `load` — and a reader injected into a page whose app was about to connect would take its
|
|
403
|
+
* place and drop the app's own options. So an app that connected itself is never touched, and a page
|
|
404
|
+
* that stayed silent through the whole readiness wait is the one that gets a reader.
|
|
405
|
+
*/
|
|
406
|
+
export async function connectOrInject(lease, isConnected, wait = waitForLeasedSession) {
|
|
407
|
+
if (await wait(isConnected))
|
|
408
|
+
return { ready: true, zeroInstall: false };
|
|
409
|
+
if (true !== (await lease.injectReader?.()))
|
|
410
|
+
return { ready: false, zeroInstall: false };
|
|
411
|
+
const ready = await wait(isConnected);
|
|
412
|
+
return { ready, zeroInstall: ready };
|
|
413
|
+
}
|
|
367
414
|
/**
|
|
368
415
|
* Named on its own because `reticle drive <url>` runs it too.
|
|
369
416
|
*
|
|
@@ -381,7 +428,7 @@ export function hasOriginLock(origin) {
|
|
|
381
428
|
}
|
|
382
429
|
export const LEASE_ACQUIRE_TOOL = {
|
|
383
430
|
name: ReticleTool.LEASE_ACQUIRE,
|
|
384
|
-
description: 'Lease a fresh isolated headless browser context from the shared pool and navigate it to the app URL (the app must already be running
|
|
431
|
+
description: 'Lease a fresh isolated headless browser context from the shared pool and navigate it to the app URL (the app must already be running; one with no Reticle SDK gets one supplied, reported as zeroInstall). If this origin is already leased and still connected, this returns THAT session rather than minting a second tab — a second acquire on the same origin poisons default session resolution. Returns the sessionId the leased tab registers — pass it to other tools. The pool keeps all leases in ONE browser and caps concurrency; if at capacity this waits for a free slot. Release with reticle_lease{action:"release"} when the flow is done. PREFER AN ALREADY-OPEN TAB: if reticle_sessions lists a non-leased session for this app, drive THAT instead — a lease is invisible to the person watching the app, whose HUD lives in their own tab, and a tab flagged hidden/throttled is often still driveable. Lease for isolation you actually need (a second identity, a clean context, parallel flows) or when driving the open tab has failed — this call answers with `preferExisting` when a live tab was available.',
|
|
385
432
|
inputSchema: {
|
|
386
433
|
url: z
|
|
387
434
|
.string()
|
|
@@ -398,6 +445,10 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
398
445
|
ready: z
|
|
399
446
|
.boolean()
|
|
400
447
|
.describe('Whether the leased tab is connected AND answering. On a reused lease this is probed, not assumed — a row in the session map is presence, not liveness. False ⇒ read notReadyReason.'),
|
|
448
|
+
zeroInstall: z
|
|
449
|
+
.boolean()
|
|
450
|
+
.optional()
|
|
451
|
+
.describe('Present when this app ships no Reticle SDK and the lease supplied one. Verdicts work on the DOM, network, console and routes; there is no framework adapter, so no component state and no source file:line. Install with `npx @reticlehq/server init` for those.'),
|
|
401
452
|
notReadyReason: z
|
|
402
453
|
.enum([LeaseNotReadyReason.SDK_NEVER_DIALLED, LeaseNotReadyReason.SDK_STOPPED_ANSWERING])
|
|
403
454
|
.optional()
|
|
@@ -447,8 +498,13 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
447
498
|
if (seedStorageArg !== undefined) {
|
|
448
499
|
const parsed = SeedStorageSchema.safeParse(seedStorageArg);
|
|
449
500
|
if (!parsed.success) {
|
|
450
|
-
const
|
|
451
|
-
|
|
501
|
+
const msg = scrubSeedFromError(parsed.error.issues
|
|
502
|
+
.map((i) => (i.path.length > 0 ? `${i.path.join('.')}: ${i.message}` : i.message))
|
|
503
|
+
.join(', '), seedStorageArg);
|
|
504
|
+
const hint = looksLikeStorageStateExport(seedStorageArg)
|
|
505
|
+
? 'This looks like a Playwright storageState() export ({ origins, cookies }); seedStorage takes { local?, session?, cookies? } (map origins[].localStorage into `local`).'
|
|
506
|
+
: 'Expected { local?, session?, cookies? }.';
|
|
507
|
+
throw new Error(`reticle_lease{action:"acquire"} seedStorage is invalid: ${msg}. ${hint}`);
|
|
452
508
|
}
|
|
453
509
|
validatedSeed = parsed.data;
|
|
454
510
|
}
|
|
@@ -538,7 +594,7 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
538
594
|
// Resolved rather than assumed: an app that names its own session registers under that name,
|
|
539
595
|
// and the id we hand back has to be the one the agent can actually drive.
|
|
540
596
|
let registeredId;
|
|
541
|
-
const ready = await
|
|
597
|
+
const { ready, zeroInstall } = await connectOrInject(lease, () => {
|
|
542
598
|
registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
|
|
543
599
|
return registeredId !== undefined;
|
|
544
600
|
});
|
|
@@ -569,6 +625,7 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
569
625
|
// word: no SDK ever dialled in (look at the install) versus one dialled in and stopped
|
|
570
626
|
// answering (recover the tab). They want different next actions, so they get names.
|
|
571
627
|
...(ready ? {} : { notReadyReason: LeaseNotReadyReason.SDK_NEVER_DIALLED }),
|
|
628
|
+
...(zeroInstall ? { zeroInstall: true } : {}),
|
|
572
629
|
expiresInMs: pool.leaseTtlMs(),
|
|
573
630
|
leased: pool.activeCount(),
|
|
574
631
|
queued: pool.queuedCount(),
|
|
@@ -651,9 +708,7 @@ const PREFER_EXISTING_NOTE = 'a non-leased tab for this app was already connecte
|
|
|
651
708
|
* is the right trade for a broadcast and the wrong one here, where a single `[0]` is handed back as
|
|
652
709
|
* "drive this instead". With `projectId` undefined — and it is an OPTIONAL argument that an
|
|
653
710
|
* acquiring agent has no reason to pass — every non-leased session qualified and the answer was
|
|
654
|
-
* whichever the map yielded first
|
|
655
|
-
* recommended a Phanpy tab on `:5173`, so an agent that complied would have driven a different
|
|
656
|
-
* application and reported a verdict about it.
|
|
711
|
+
* whichever the map yielded first: leasing `localhost:4312` recommended a tab on `:5173`, another app.
|
|
657
712
|
*
|
|
658
713
|
* So this narrows by the one thing a lease always knows: the ORIGIN it is being taken against.
|
|
659
714
|
* `projectId` still narrows further when given. Nothing is recommended when nothing matches, which
|
|
@@ -670,6 +725,8 @@ function liveTabFor(deps, projectId, url) {
|
|
|
670
725
|
const candidates = deps.sessions
|
|
671
726
|
.all()
|
|
672
727
|
.filter((session) => session.url !== undefined && originOf(session.url) === wanted)
|
|
728
|
+
// Nor a tab gone silent: one last heard from 105s earlier was named here and answered nothing.
|
|
729
|
+
.filter((session) => (session.lastSeenMs?.() ?? 0) <= SESSION_HEALTH.STALE_THRESHOLD_MS)
|
|
673
730
|
.map((session) => ({ id: session.id, projectId: session.projectId }));
|
|
674
731
|
return watchersToNotify(candidates, pool.leasedSessionIds(), projectId)[0];
|
|
675
732
|
}
|
|
@@ -19,7 +19,7 @@ export const LINEAGE_TOOLS = [
|
|
|
19
19
|
{
|
|
20
20
|
name: ReticleTool.LINEAGE,
|
|
21
21
|
example: { path: 'user.profile.name' },
|
|
22
|
-
description: 'Trace BACKWARDS from a runtime value: which state change set it, which signal preceded
|
|
22
|
+
description: 'Trace BACKWARDS from a runtime value: which state change set it, which driven action it was observed under, which signal preceded it, and which request preceded that. Read-only — it draws no verdict and changes nothing. The state change is OBSERVED; every link above it is an INFERENCE and says so in its own text — a driven action is the window-tier stamp the SDK put on the event, a signal or request is a timing look-back. A candidate stamped with a DIFFERENT action is excluded; an unstamped one is kept. Where several candidates could still explain a link it names ALL of them rather than choosing, because timing alone cannot justify the choice — so a chain here is a place to look, never a proven cause. Use it after a `no` verdict or a contradiction, instead of correlating reticle_look { action: "state" }, reticle_observe { action: "network" } and reticle_observe by hand.',
|
|
23
23
|
inputSchema: {
|
|
24
24
|
path: z
|
|
25
25
|
.string()
|
|
@@ -38,7 +38,10 @@ const NO_TOOL_EQUIVALENT = [
|
|
|
38
38
|
// \u2014 so the optional group could not match, the clause survived, and the replacement landed
|
|
39
39
|
// inside the original quoting. What an agent read was the human-equivalent clause twice over,
|
|
40
40
|
// with backticks nested three deep.
|
|
41
|
-
|
|
41
|
+
// The body allows ONE nested object, because the call is `{ tool, args: { action, url } }`: the
|
|
42
|
+
// hint used to put `action` and `url` beside `tool`, which reticle_run refuses, and a `[^}]*\}`
|
|
43
|
+
// body stopped at the inner brace and left ` }` behind once the hint was corrected.
|
|
44
|
+
pattern: /`?reticle_run\s*\{\s*tool:\s*\\?"reticle_lease\\?"(?:[^{}]|\{[^{}]*\})*\}`?(\s*\([^)]*\))?(\s*[-\u2014]+\s*reticle_lease is[^.]*\.)?/g,
|
|
42
45
|
instead: 'the CLI: `reticle open <url>` (a human can equivalently run `reticle drive <url>`)',
|
|
43
46
|
},
|
|
44
47
|
{
|