@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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { readFileSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
+
import { desktopLaunch } from '@reticlehq/init';
|
|
13
14
|
/**
|
|
14
15
|
* Script names checked, in order.
|
|
15
16
|
*
|
|
@@ -47,23 +48,26 @@ function pinnedPort(script) {
|
|
|
47
48
|
const port = Number.parseInt(digits, 10);
|
|
48
49
|
return Number.isSafeInteger(port) ? port : undefined;
|
|
49
50
|
}
|
|
50
|
-
/** The
|
|
51
|
-
function
|
|
51
|
+
/** The parsed package.json, or undefined for anything unreadable/unparseable. */
|
|
52
|
+
function readManifest(raw) {
|
|
52
53
|
if (raw === undefined)
|
|
53
|
-
return
|
|
54
|
+
return undefined;
|
|
54
55
|
try {
|
|
55
|
-
|
|
56
|
-
if (null === parsed || 'object' !== typeof parsed)
|
|
57
|
-
return {};
|
|
58
|
-
const scripts = parsed.scripts;
|
|
59
|
-
if (null === scripts || 'object' !== typeof scripts)
|
|
60
|
-
return {};
|
|
61
|
-
return scripts;
|
|
56
|
+
return JSON.parse(raw);
|
|
62
57
|
}
|
|
63
58
|
catch {
|
|
64
|
-
return
|
|
59
|
+
return undefined;
|
|
65
60
|
}
|
|
66
61
|
}
|
|
62
|
+
/** The scripts block of a parsed package.json, or an empty one. */
|
|
63
|
+
function scriptsOf(manifest) {
|
|
64
|
+
if (null === manifest || 'object' !== typeof manifest)
|
|
65
|
+
return {};
|
|
66
|
+
const scripts = manifest.scripts;
|
|
67
|
+
if (null === scripts || 'object' !== typeof scripts)
|
|
68
|
+
return {};
|
|
69
|
+
return scripts;
|
|
70
|
+
}
|
|
67
71
|
/**
|
|
68
72
|
* The dev command for the project in `directory`, or undefined when there is not one to be sure of.
|
|
69
73
|
*
|
|
@@ -71,9 +75,23 @@ function readScripts(raw) {
|
|
|
71
75
|
* default disk reader.
|
|
72
76
|
*/
|
|
73
77
|
export function detectDevCommand(directory, read = readTextFile) {
|
|
74
|
-
const
|
|
78
|
+
const manifest = readManifest(read(join(directory, PACKAGE_JSON)));
|
|
79
|
+
const scripts = scriptsOf(manifest);
|
|
75
80
|
const manager = LOCKFILES.find(({ file }) => read(join(directory, file)) !== undefined)?.manager ??
|
|
76
81
|
DEFAULT_PACKAGE_MANAGER;
|
|
82
|
+
// A desktop shell's `dev` is its renderer's dev server, which no window ever opens onto. init's
|
|
83
|
+
// rule decides which launcher it is, so the command the daemon hands an agent and the one init
|
|
84
|
+
// spawns are the same.
|
|
85
|
+
const desktop = desktopLaunch(manifest);
|
|
86
|
+
if (desktop !== undefined) {
|
|
87
|
+
if ('command' in desktop)
|
|
88
|
+
return { command: desktop.command };
|
|
89
|
+
const command = `${manager} run ${desktop.script}`;
|
|
90
|
+
return {
|
|
91
|
+
command: 0 < desktop.args.length ? `${command} ${desktop.args}` : command,
|
|
92
|
+
script: desktop.script,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
77
95
|
for (const script of DEV_SCRIPTS) {
|
|
78
96
|
const body = scripts[script];
|
|
79
97
|
if ('string' !== typeof body || 0 === body.trim().length)
|
|
@@ -25,7 +25,7 @@ export declare const PortState: {
|
|
|
25
25
|
/** Answered with a document: a dev server serving an app. */
|
|
26
26
|
readonly SERVES_DOCUMENT: "serves-document";
|
|
27
27
|
};
|
|
28
|
-
|
|
28
|
+
type PortState = (typeof PortState)[keyof typeof PortState];
|
|
29
29
|
/** The probe's answer for one port, however it was obtained. */
|
|
30
30
|
export declare function classifyPort(port: number, probe: (port: number) => Promise<PortState>): Promise<PortState>;
|
|
31
31
|
/**
|
|
@@ -71,3 +71,4 @@ export declare function probeDevServerStates(ports?: readonly number[], probe?:
|
|
|
71
71
|
}>;
|
|
72
72
|
/** Every candidate port serving a document, in ascending order. Never rejects. */
|
|
73
73
|
export declare function probeDevServers(ports?: readonly number[], probe?: (port: number) => Promise<boolean>): Promise<number[]>;
|
|
74
|
+
export {};
|
|
@@ -64,6 +64,15 @@ export function classifyPort(port, probe) {
|
|
|
64
64
|
* accepts v4-mapped connections — so between the two, every ordinary bind is covered.
|
|
65
65
|
*/
|
|
66
66
|
export const PROBE_HOSTS = ['127.0.0.1', '::1'];
|
|
67
|
+
/**
|
|
68
|
+
* The name the probe gives in its Host header, whichever address it dialled.
|
|
69
|
+
*
|
|
70
|
+
* Node names the dialled address, so the `::1` probe sent `Host: [::1]:4200`. Angular's SSR dev
|
|
71
|
+
* server validates Host, answered 400, and printed `ERROR: Bad Request ("http://[::1]:4200/")` into
|
|
72
|
+
* the user's dev terminal on every sweep, for as long as the daemon ran. The address picks the
|
|
73
|
+
* family; the header says what a browser on this machine would.
|
|
74
|
+
*/
|
|
75
|
+
const PROBE_HOST_HEADER = 'localhost';
|
|
67
76
|
/**
|
|
68
77
|
* Does this port serve a DOCUMENT — i.e. is it a dev server rather than merely something listening?
|
|
69
78
|
*
|
|
@@ -76,7 +85,14 @@ function probeState(port, host) {
|
|
|
76
85
|
// Whether the SOCKET came up is tracked separately from whether a response did, because that is
|
|
77
86
|
// the whole difference between "nothing is there" and "something is there and still compiling".
|
|
78
87
|
let connected = false;
|
|
79
|
-
const req = request({
|
|
88
|
+
const req = request({
|
|
89
|
+
host,
|
|
90
|
+
port,
|
|
91
|
+
path: '/',
|
|
92
|
+
method: 'GET',
|
|
93
|
+
timeout: HTTP_PROBE_TIMEOUT_MS,
|
|
94
|
+
headers: { host: `${PROBE_HOST_HEADER}:${String(port)}` },
|
|
95
|
+
}, (res) => {
|
|
80
96
|
const answer = looksLikeDevServer(res.statusCode ?? 0, res.headers['content-type']);
|
|
81
97
|
res.resume(); // drain, so the socket closes rather than lingering
|
|
82
98
|
resolve(answer ? PortState.SERVES_DOCUMENT : PortState.CLOSED);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What does the route a tab died on answer right now?
|
|
3
|
+
*
|
|
4
|
+
* A route that throws server-side tears the page down and the SDK never reconnects, so the daemon
|
|
5
|
+
* is left with an empty session list and the URL it last saw (#862). The URL alone cannot separate
|
|
6
|
+
* "the route 500s" from "somebody closed the tab" — one plain GET can, and the daemon is the only
|
|
7
|
+
* party placed to make it: the page is gone, the agent is inside an MCP call, and the daemon is on
|
|
8
|
+
* the same machine as the dev server.
|
|
9
|
+
*
|
|
10
|
+
* What it refuses to do, because this runs unattended in a diagnostic loop:
|
|
11
|
+
* - only loopback hostnames — the daemon is localhost-only and a diagnostic must not reach out;
|
|
12
|
+
* - only `http:` — `https:` on localhost means a self-signed certificate, and fetching it with
|
|
13
|
+
* verification off would be a posture change for a status code;
|
|
14
|
+
* - GET only, redirects not followed, the body never read — the status is the only thing that
|
|
15
|
+
* leaves the socket, and it is drained so the connection closes rather than lingers;
|
|
16
|
+
* - an error or a timeout is `undefined`, never a status. No fact is better than a wrong one.
|
|
17
|
+
*
|
|
18
|
+
* Same shape as its neighbour `dev-server-probe.ts`, which asks a port whether it serves a document; this asks
|
|
19
|
+
* one exact path what it answers.
|
|
20
|
+
*/
|
|
21
|
+
/** Short on purpose: a dev server that has not answered a route in this long is not going to. */
|
|
22
|
+
export declare const ROUTE_STATUS_TIMEOUT_MS = 2000;
|
|
23
|
+
/** The HTTP status `url` answers a GET with, or undefined when there is no answer to report. */
|
|
24
|
+
export declare function probeRouteStatus(url: string, timeoutMs?: number): Promise<number | undefined>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What does the route a tab died on answer right now?
|
|
3
|
+
*
|
|
4
|
+
* A route that throws server-side tears the page down and the SDK never reconnects, so the daemon
|
|
5
|
+
* is left with an empty session list and the URL it last saw (#862). The URL alone cannot separate
|
|
6
|
+
* "the route 500s" from "somebody closed the tab" — one plain GET can, and the daemon is the only
|
|
7
|
+
* party placed to make it: the page is gone, the agent is inside an MCP call, and the daemon is on
|
|
8
|
+
* the same machine as the dev server.
|
|
9
|
+
*
|
|
10
|
+
* What it refuses to do, because this runs unattended in a diagnostic loop:
|
|
11
|
+
* - only loopback hostnames — the daemon is localhost-only and a diagnostic must not reach out;
|
|
12
|
+
* - only `http:` — `https:` on localhost means a self-signed certificate, and fetching it with
|
|
13
|
+
* verification off would be a posture change for a status code;
|
|
14
|
+
* - GET only, redirects not followed, the body never read — the status is the only thing that
|
|
15
|
+
* leaves the socket, and it is drained so the connection closes rather than lingers;
|
|
16
|
+
* - an error or a timeout is `undefined`, never a status. No fact is better than a wrong one.
|
|
17
|
+
*
|
|
18
|
+
* Same shape as its neighbour `dev-server-probe.ts`, which asks a port whether it serves a document; this asks
|
|
19
|
+
* one exact path what it answers.
|
|
20
|
+
*/
|
|
21
|
+
import { request } from 'node:http';
|
|
22
|
+
import { isLoopbackHostname } from '@reticlehq/core';
|
|
23
|
+
/** Short on purpose: a dev server that has not answered a route in this long is not going to. */
|
|
24
|
+
export const ROUTE_STATUS_TIMEOUT_MS = 2_000;
|
|
25
|
+
const HTTP = 'http:';
|
|
26
|
+
/** The HTTP status `url` answers a GET with, or undefined when there is no answer to report. */
|
|
27
|
+
export function probeRouteStatus(url, timeoutMs = ROUTE_STATUS_TIMEOUT_MS) {
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = new URL(url);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return Promise.resolve(undefined);
|
|
34
|
+
}
|
|
35
|
+
if (parsed.protocol !== HTTP || !isLoopbackHostname(parsed.hostname)) {
|
|
36
|
+
return Promise.resolve(undefined);
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const req = request({
|
|
40
|
+
host: parsed.hostname,
|
|
41
|
+
port: parsed.port,
|
|
42
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
43
|
+
method: 'GET',
|
|
44
|
+
timeout: timeoutMs,
|
|
45
|
+
}, (res) => {
|
|
46
|
+
const status = res.statusCode;
|
|
47
|
+
res.resume(); // drain, so the socket closes rather than lingering
|
|
48
|
+
resolve(status);
|
|
49
|
+
});
|
|
50
|
+
req.once('timeout', () => {
|
|
51
|
+
req.destroy();
|
|
52
|
+
resolve(undefined);
|
|
53
|
+
});
|
|
54
|
+
req.once('error', () => resolve(undefined));
|
|
55
|
+
req.end();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document a local dev server actually serves: its CSP header and HTML, read once and failing
|
|
3
|
+
* soft. Shared by `doctor` (is a policy blocking the bridge?) and a lease that never dialled (was
|
|
4
|
+
* it the page's own policy?), which is why it lives beside the dev-server probe and not in either.
|
|
5
|
+
*/
|
|
6
|
+
/** What a served page said about its policy. The same shape `diagnoseObservedWebCsp` reads. */
|
|
7
|
+
export interface ObservedWebDocument {
|
|
8
|
+
url: string;
|
|
9
|
+
headers: string[];
|
|
10
|
+
html: string;
|
|
11
|
+
}
|
|
12
|
+
export type PageRequest = (url: string, init: RequestInit) => Promise<Response>;
|
|
13
|
+
interface ConnectedDocument {
|
|
14
|
+
url: string;
|
|
15
|
+
projectId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** A connected tab for this project, or on its observed origin, disproves a CSP diagnosis. */
|
|
18
|
+
export declare function hasConnectedDocument(sessions: readonly ConnectedDocument[], documentUrls: readonly string[], projectId?: string): boolean;
|
|
19
|
+
/** Read the document a live local dev server actually sends, failing soft like the rest of doctor. */
|
|
20
|
+
export declare function observeWebDocument(rawUrl: string, request?: PageRequest): Promise<ObservedWebDocument | undefined>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const CSP_HEADER = 'content-security-policy';
|
|
2
|
+
const DOCUMENT_TIMEOUT_MS = 1_000;
|
|
3
|
+
const LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];
|
|
4
|
+
const requestPage = (url, init) => fetch(url, init);
|
|
5
|
+
function loopbackDocumentUrl(raw) {
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(raw);
|
|
8
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
9
|
+
return undefined;
|
|
10
|
+
return LOOPBACK_HOSTS.includes(url.hostname) ? url : undefined;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function originOf(raw) {
|
|
17
|
+
try {
|
|
18
|
+
return new URL(raw).origin;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** A connected tab for this project, or on its observed origin, disproves a CSP diagnosis. */
|
|
25
|
+
export function hasConnectedDocument(sessions, documentUrls, projectId) {
|
|
26
|
+
if (projectId !== undefined && sessions.some((session) => session.projectId === projectId)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const origins = new Set(documentUrls.map(originOf).filter((value) => value !== undefined));
|
|
30
|
+
return sessions.some((session) => {
|
|
31
|
+
const origin = originOf(session.url);
|
|
32
|
+
return undefined !== origin && origins.has(origin);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Read the document a live local dev server actually sends, failing soft like the rest of doctor. */
|
|
36
|
+
export async function observeWebDocument(rawUrl, request = requestPage) {
|
|
37
|
+
const url = loopbackDocumentUrl(rawUrl);
|
|
38
|
+
if (url === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
try {
|
|
41
|
+
const response = await request(url.href, {
|
|
42
|
+
redirect: 'error',
|
|
43
|
+
signal: AbortSignal.timeout(DOCUMENT_TIMEOUT_MS),
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok)
|
|
46
|
+
return undefined;
|
|
47
|
+
const policy = response.headers.get(CSP_HEADER);
|
|
48
|
+
return {
|
|
49
|
+
url: url.href,
|
|
50
|
+
headers: null === policy ? [] : [policy],
|
|
51
|
+
html: await response.text(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read `background` out of an already-parsed `.reticle.json`: the same-origin endpoints the app calls
|
|
3
|
+
* on its own (telemetry, heartbeats), which must not hold a settle window open or contradict a claim.
|
|
4
|
+
*
|
|
5
|
+
* Takes the record rather than a path, like `readRetainPolicy`, because the config search belongs to
|
|
6
|
+
* the startup code that already does it once. Never throws, and drops a bad entry rather than the
|
|
7
|
+
* whole list.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readBackgroundTraffic(config: Record<string, unknown> | undefined): string[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The key a project writes in `.reticle.json`. */
|
|
2
|
+
const BACKGROUND_KEY = 'background';
|
|
3
|
+
/**
|
|
4
|
+
* Shorter than this names no endpoint. `/` and `//` are refused on this rule, and so is anything a
|
|
5
|
+
* mistype could leave behind.
|
|
6
|
+
*
|
|
7
|
+
* ponytail: a length floor, not a proof. `/api` passes and would still exclude the whole backend;
|
|
8
|
+
* the entries a verdict ignored are reported on it, which is what keeps a too-broad pattern visible.
|
|
9
|
+
*/
|
|
10
|
+
const MIN_PATTERN_LENGTH = 3;
|
|
11
|
+
function isEndpointPattern(value) {
|
|
12
|
+
return 'string' === typeof value && value.replace(/\//g, '').length >= MIN_PATTERN_LENGTH - 1;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Read `background` out of an already-parsed `.reticle.json`: the same-origin endpoints the app calls
|
|
16
|
+
* on its own (telemetry, heartbeats), which must not hold a settle window open or contradict a claim.
|
|
17
|
+
*
|
|
18
|
+
* Takes the record rather than a path, like `readRetainPolicy`, because the config search belongs to
|
|
19
|
+
* the startup code that already does it once. Never throws, and drops a bad entry rather than the
|
|
20
|
+
* whole list.
|
|
21
|
+
*/
|
|
22
|
+
export function readBackgroundTraffic(config) {
|
|
23
|
+
const raw = config?.[BACKGROUND_KEY];
|
|
24
|
+
return Array.isArray(raw) ? raw.filter(isEndpointPattern) : [];
|
|
25
|
+
}
|
|
@@ -48,6 +48,8 @@ export declare class ObservedState {
|
|
|
48
48
|
actedRefs(): ReadonlySet<string>;
|
|
49
49
|
/** Every driven control's label, for matching across re-renders. */
|
|
50
50
|
actedLabels(): ReadonlySet<string>;
|
|
51
|
+
recordProvedLabel(label: string): void;
|
|
52
|
+
provedLabels(): ReadonlySet<string>;
|
|
51
53
|
/**
|
|
52
54
|
* Record bridge-side sampling as a blind spot.
|
|
53
55
|
*
|
|
@@ -58,3 +60,12 @@ export declare class ObservedState {
|
|
|
58
60
|
noteRateLimited(dropped: number): void;
|
|
59
61
|
blindSpots(): Readonly<Record<string, number>>;
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The snapshot-shaped labels an act reply names its control by: the testid, and `role "name"`.
|
|
65
|
+
*
|
|
66
|
+
* The TESTID first: it is the strongest identity a control has and it survives any re-render.
|
|
67
|
+
* Coverage previously matched only on `role "name"`, so a control with a testid but no accessible
|
|
68
|
+
* name — or on a stack where the act reply carried neither — was unrecognisable after a re-render,
|
|
69
|
+
* and coverage read `exercised: 0` however much work had been done.
|
|
70
|
+
*/
|
|
71
|
+
export declare function controlLabelsOf(payload: unknown): string[];
|
|
@@ -105,6 +105,18 @@ export class ObservedState {
|
|
|
105
105
|
actedLabels() {
|
|
106
106
|
return this.#actedLabels;
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Controls whose action came back `verified: "yes"` on a declared consequence — the PROVED level
|
|
110
|
+
* of the coverage ledger. Same label identity as `actedLabels`, so the two compare directly.
|
|
111
|
+
*/
|
|
112
|
+
#provedLabels = new Set();
|
|
113
|
+
recordProvedLabel(label) {
|
|
114
|
+
if (label.length > 0)
|
|
115
|
+
this.#provedLabels.add(label);
|
|
116
|
+
}
|
|
117
|
+
provedLabels() {
|
|
118
|
+
return this.#provedLabels;
|
|
119
|
+
}
|
|
108
120
|
/**
|
|
109
121
|
* Record bridge-side sampling as a blind spot.
|
|
110
122
|
*
|
|
@@ -119,3 +131,25 @@ export class ObservedState {
|
|
|
119
131
|
return this.#blindSpots;
|
|
120
132
|
}
|
|
121
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The snapshot-shaped labels an act reply names its control by: the testid, and `role "name"`.
|
|
136
|
+
*
|
|
137
|
+
* The TESTID first: it is the strongest identity a control has and it survives any re-render.
|
|
138
|
+
* Coverage previously matched only on `role "name"`, so a control with a testid but no accessible
|
|
139
|
+
* name — or on a stack where the act reply carried neither — was unrecognisable after a re-render,
|
|
140
|
+
* and coverage read `exercised: 0` however much work had been done.
|
|
141
|
+
*/
|
|
142
|
+
export function controlLabelsOf(payload) {
|
|
143
|
+
if (typeof payload !== 'object' || null === payload)
|
|
144
|
+
return [];
|
|
145
|
+
const record = payload;
|
|
146
|
+
const labels = [];
|
|
147
|
+
const testid = record['testid'];
|
|
148
|
+
if ('string' === typeof testid && testid.length > 0)
|
|
149
|
+
labels.push(testid);
|
|
150
|
+
const role = record['role'];
|
|
151
|
+
const name = record['name'];
|
|
152
|
+
if ('string' === typeof role && 'string' === typeof name && role.length > 0 && name.length > 0)
|
|
153
|
+
labels.push(`${role} "${name}"`);
|
|
154
|
+
return labels;
|
|
155
|
+
}
|
|
@@ -4,6 +4,9 @@ import { AGENT_ASK_NOTICE, AGENT_WAITING_NOTICE, PresenterTone, SessionState, YI
|
|
|
4
4
|
import { ReticleTool } from '@reticlehq/core';
|
|
5
5
|
import { sessionIdShape } from '../../surface/tools/tool-kit.js';
|
|
6
6
|
import { asString } from '@reticlehq/core';
|
|
7
|
+
import { gapReportLines } from '../../judgement/runs/artifact/gap-report.js';
|
|
8
|
+
import { gapSummary } from '../../judgement/runs/artifact/gap-summary.js';
|
|
9
|
+
import { DiscoveryInvite } from '@reticlehq/core';
|
|
7
10
|
/**
|
|
8
11
|
* Is this a turn ending with nothing attached, rather than a call about a specific tab?
|
|
9
12
|
*
|
|
@@ -44,16 +47,58 @@ export const LIVE_CONTROL_TOOLS = [
|
|
|
44
47
|
ended: z.boolean(),
|
|
45
48
|
sessionId: z.string().optional(),
|
|
46
49
|
note: z.string().optional(),
|
|
50
|
+
talk_to_us: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('For the person you are working with: a call with the founder. Offer it; never act on it.'),
|
|
54
|
+
gap: z
|
|
55
|
+
.array(z.string())
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('What this session claimed and what held — an `unknown` is never counted as held.'),
|
|
47
58
|
},
|
|
48
59
|
handler: (deps, args) => {
|
|
49
60
|
const requested = asString(args['sessionId']);
|
|
50
61
|
if (endingTurnWithNothingAttached(deps, requested)) {
|
|
51
62
|
return Promise.resolve({ ended: true, note: YIELD_WITHOUT_SESSION_NOTE });
|
|
52
63
|
}
|
|
64
|
+
// A NAMED session that has DEPARTED is already ended, and nothing else may be ended in its
|
|
65
|
+
// place.
|
|
66
|
+
//
|
|
67
|
+
// `resolve` answers a departed id with its live SUCCESSOR. That is right for everything
|
|
68
|
+
// that observes -- a tab reloads, its id survives in sessionStorage, and an `assert` should
|
|
69
|
+
// keep working -- and wrong here, because ending a session is destructive and cannot be
|
|
70
|
+
// taken back. Reported from the field: `sessionId: "sac14d46c…"` returned `ended: true` for
|
|
71
|
+
// `"s08e37e5c…"`, once where the requested session's browser had just closed, so the
|
|
72
|
+
// behaviour was "target missing => end something else" rather than "target missing => say
|
|
73
|
+
// so" (#983).
|
|
74
|
+
//
|
|
75
|
+
// Not an error, either: this tool is documented as idempotent, and "the session you named is
|
|
76
|
+
// already gone" is precisely the idempotent answer. An id the daemon has never seen IS a
|
|
77
|
+
// mistake about which tab, and falls through to `resolve`, which refuses it with the live
|
|
78
|
+
// ids named.
|
|
79
|
+
if (requested !== undefined &&
|
|
80
|
+
deps.sessions.get(requested) === undefined &&
|
|
81
|
+
deps.sessions.departed(requested) !== undefined) {
|
|
82
|
+
return Promise.resolve({
|
|
83
|
+
ended: true,
|
|
84
|
+
sessionId: requested,
|
|
85
|
+
note: `session '${requested}' had already disconnected, so there was nothing to end. ` +
|
|
86
|
+
'Nothing else was ended: a named session is matched exactly, never substituted.',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Resolved synchronously, as before: a named session that does not exist still throws here
|
|
90
|
+
// rather than becoming a rejected promise the caller did not expect.
|
|
53
91
|
const session = deps.sessions.resolve(requested);
|
|
54
|
-
|
|
55
|
-
session.
|
|
56
|
-
|
|
92
|
+
const summary = asString(args['summary']);
|
|
93
|
+
return session.readJournalActions().then((actions) => {
|
|
94
|
+
// The moment the agent calls the task complete is when "and what did not hold" is worth a
|
|
95
|
+
// line: the same fold `reticle_context` and `reticle report` serve, so they cannot disagree.
|
|
96
|
+
const gap = gapReportLines(gapSummary(actions));
|
|
97
|
+
const panel = [summary, gap[0]].filter((line) => line !== undefined);
|
|
98
|
+
// One PRESENTER push for the transition; the summary and the gap headline ride together.
|
|
99
|
+
session.setState(SessionState.ENDED, 0 === panel.length ? undefined : panel.join('\n'));
|
|
100
|
+
return { ended: true, sessionId: session.id, gap, talk_to_us: DiscoveryInvite.AGENT };
|
|
101
|
+
});
|
|
57
102
|
},
|
|
58
103
|
},
|
|
59
104
|
{
|
|
@@ -91,6 +91,20 @@ export interface NoSessionFacts {
|
|
|
91
91
|
* already read it; the message did not.
|
|
92
92
|
*/
|
|
93
93
|
authRefused?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The bridge refused the last hello for something OTHER than its token — a protocol number out of
|
|
96
|
+
* range, or a hello that failed the wire schema outright — and nothing has connected since.
|
|
97
|
+
*
|
|
98
|
+
* The same positive evidence as `authRefused`, with a different fix. Without it the daemon had
|
|
99
|
+
* recorded the refusal and `init` still ended on "the SDK is in the page and never dialled the
|
|
100
|
+
* bridge", about a page that dialled and was turned away. `reason` follows the `cause — fix`
|
|
101
|
+
* shape the bridge writes; the page and project are what the refused hello said about itself.
|
|
102
|
+
*/
|
|
103
|
+
helloRefused?: {
|
|
104
|
+
reason: string;
|
|
105
|
+
url?: string;
|
|
106
|
+
projectId?: string;
|
|
107
|
+
};
|
|
94
108
|
/**
|
|
95
109
|
* The framework `.reticle.json` declares, when there is one.
|
|
96
110
|
*
|
|
@@ -100,6 +114,14 @@ export interface NoSessionFacts {
|
|
|
100
114
|
* hint the agent reads hours later never mentioned it.
|
|
101
115
|
*/
|
|
102
116
|
framework?: string;
|
|
117
|
+
/**
|
|
118
|
+
* The project is a desktop shell (Electron, Tauri), read from the project the way `doctor` reads it.
|
|
119
|
+
*
|
|
120
|
+
* Its page exists only inside its own window, so every "open the app's URL in a browser" and
|
|
121
|
+
* every lease offer below is the wrong move there: it loads the renderer without the shell's IPC
|
|
122
|
+
* bridge or native APIs, which is not the app being verified. Absent means web.
|
|
123
|
+
*/
|
|
124
|
+
desktop?: boolean;
|
|
103
125
|
/**
|
|
104
126
|
* `.reticle.json` files found OUTSIDE this daemon's directory — a workspace app, a repo root.
|
|
105
127
|
*
|
|
@@ -144,6 +166,13 @@ export interface NoSessionFacts {
|
|
|
144
166
|
* or the ring has forgotten — inventing a URL is worse than the silence this exists to end.
|
|
145
167
|
*/
|
|
146
168
|
lastKnownUrl?: string;
|
|
169
|
+
/**
|
|
170
|
+
* What a plain GET of `lastKnownUrl` answered, done out of band by the watch after the session
|
|
171
|
+
* went. The one fact that separates a server error from a closed tab and from an install
|
|
172
|
+
* problem (#808, option 3). Present only when the fetch got an HTTP status back — a failed or
|
|
173
|
+
* timed-out fetch is no fact, and this stays absent rather than guessing.
|
|
174
|
+
*/
|
|
175
|
+
lastKnownStatus?: number;
|
|
147
176
|
}
|
|
148
177
|
/**
|
|
149
178
|
* Every branch ends with this, and `recoveryFor` keys on it to suppress the generic no-session
|
|
@@ -17,6 +17,10 @@ import { DEV_SERVER_PORTS } from '../../command/cli/ports/resolve/cli-port.js';
|
|
|
17
17
|
import { siblingListenerNote } from '../../command/cli/ports/sibling-ports.js';
|
|
18
18
|
import { STALL_AFTER_MS } from './stall-clock.js';
|
|
19
19
|
import { pageTornDownWhileOn } from '@reticlehq/engine/evidence/page-teardown.js';
|
|
20
|
+
/** How every hello-refusal reason the bridge records separates its cause from its fix. */
|
|
21
|
+
const CAUSE_FIX_SEPARATOR = ' — ';
|
|
22
|
+
/** The lowest HTTP status that means the server itself failed to answer the route. */
|
|
23
|
+
const SERVER_ERROR_FLOOR = 500;
|
|
20
24
|
/** The one framework whose most likely cause differs from every other framework's. */
|
|
21
25
|
const NUXT = 'nuxt';
|
|
22
26
|
/**
|
|
@@ -49,6 +53,8 @@ const URL_THEN_LEASE = 'Once you have that URL, open it with reticle_lease {acti
|
|
|
49
53
|
* diagnosed the problem.
|
|
50
54
|
*/
|
|
51
55
|
function leaseAdvice(base, facts) {
|
|
56
|
+
if (true === facts.desktop)
|
|
57
|
+
return DESKTOP_NO_BROWSER;
|
|
52
58
|
const caveat = leaseCaveat(facts.leaseBrowser);
|
|
53
59
|
return caveat === undefined ? base : `${base} ${caveat}`;
|
|
54
60
|
}
|
|
@@ -180,6 +186,25 @@ function slowListenerClause(facts) {
|
|
|
180
186
|
const OPEN_THE_APP = 'Reticle only ever sees a page that is LOADED, so the commonest cause by a distance is that no ' +
|
|
181
187
|
`browser has opened the app yet: run ${OPEN_CMD} with the app's own URL (or ask the ` +
|
|
182
188
|
'human to open it).';
|
|
189
|
+
/**
|
|
190
|
+
* The desktop twin of OPEN_THE_APP. A desktop app's page exists only inside its own window, and the
|
|
191
|
+
* guidance used to send an Electron user to a browser URL — which reaches the renderer's dev server
|
|
192
|
+
* and none of the app around it.
|
|
193
|
+
*/
|
|
194
|
+
const DESKTOP_OPEN_THE_APP = 'This is a desktop app, and Reticle sees it only from inside its own window, so the commonest ' +
|
|
195
|
+
'cause by a distance is that the desktop app is not running: start it yourself, in the ' +
|
|
196
|
+
"background, with the project's desktop dev command (the script that runs `tauri dev`, " +
|
|
197
|
+
'`electron-vite dev`, `electron-forge start` or `electron .`), not the plain web dev server.';
|
|
198
|
+
/** Why the browser and lease routes are withheld for a desktop app. Replaces the lease offer. */
|
|
199
|
+
const DESKTOP_NO_BROWSER = 'Do not open its URL in a browser or with a lease: that loads the renderer outside the desktop ' +
|
|
200
|
+
'shell, without its IPC bridge or native APIs, so it is not the app you are verifying.';
|
|
201
|
+
function openTheApp(facts) {
|
|
202
|
+
return true === facts.desktop ? DESKTOP_OPEN_THE_APP : OPEN_THE_APP;
|
|
203
|
+
}
|
|
204
|
+
/** The "(or run open)" aside — a desktop window is reopened by the app, not by a browser command. */
|
|
205
|
+
function orOpenCommand(facts) {
|
|
206
|
+
return true === facts.desktop ? '' : ` (or run ${OPEN_CMD_BARE})`;
|
|
207
|
+
}
|
|
183
208
|
/**
|
|
184
209
|
* What a machine-wide port scan can and cannot say.
|
|
185
210
|
*
|
|
@@ -334,6 +359,22 @@ export function explainNoSession(facts) {
|
|
|
334
359
|
'while the dev server runs from a shell — they can be reading different `~/.reticle` ' +
|
|
335
360
|
'directories instead. The page says the same thing in the browser console.');
|
|
336
361
|
}
|
|
362
|
+
// Beside the token refusal and for the same reason: a turned-away hello is current, positive
|
|
363
|
+
// evidence, and every branch below reasons from an absence.
|
|
364
|
+
if (facts.helloRefused !== undefined) {
|
|
365
|
+
const { reason: said, url, projectId } = facts.helloRefused;
|
|
366
|
+
const split = said.indexOf(CAUSE_FIX_SEPARATOR);
|
|
367
|
+
const cause = -1 === split ? said : said.slice(0, split);
|
|
368
|
+
const fix = -1 === split ? undefined : said.slice(split + CAUSE_FIX_SEPARATOR.length);
|
|
369
|
+
const page = undefined === url || '' === url ? 'a page' : `the page at ${url}`;
|
|
370
|
+
const project = undefined === projectId ? '' : ` (project ${projectId})`;
|
|
371
|
+
return reason(
|
|
372
|
+
// ponytail: shares the token refusal's code, because a new code is a telemetry wire change
|
|
373
|
+
// and that surface has its own owner; split it out as `hello_refused` there.
|
|
374
|
+
NoSessionReason.AUTH_REFUSED, `no browser session connected: ${page}${project} dialled this daemon and was REFUSED ` +
|
|
375
|
+
`because ${cause}${undefined === fix ? '' : `; fix: ${fix}`}. Only an SDK dials the ` +
|
|
376
|
+
'bridge, so the app is running and instrumented — do not go looking for a stopped dev server.');
|
|
377
|
+
}
|
|
337
378
|
if (everConnected) {
|
|
338
379
|
// A reaped lease first, because it is the one cause with POSITIVE evidence behind it. Calling
|
|
339
380
|
// it "the tab was closed, ask the human to reopen the app" is wrong on every clause (#157):
|
|
@@ -347,9 +388,25 @@ export function explainNoSession(facts) {
|
|
|
347
388
|
`with reticle_lease {action:"acquire", url} and carry on. ${RETRY}`, 'A lease is a headless context, not a human tab, and it takes its cookies with it, so an ' +
|
|
348
389
|
`authenticated app needs signing in again.${alreadyListeningClause(listening)}`);
|
|
349
390
|
}
|
|
391
|
+
// Ranked above TAB_GONE because it is not an absence: the route itself answered. A tab that a
|
|
392
|
+
// 5xx tore down and a tab a human closed produce the same empty list, and until the watch asked
|
|
393
|
+
// the route, they produced the same sentence. Present tense throughout — "answers ... right
|
|
394
|
+
// now" — because a 500 now does not prove the teardown was a 500 then, and saying so would
|
|
395
|
+
// turn an observation into a story.
|
|
396
|
+
if (facts.lastKnownStatus !== undefined &&
|
|
397
|
+
facts.lastKnownStatus >= SERVER_ERROR_FLOOR &&
|
|
398
|
+
facts.lastKnownUrl !== undefined &&
|
|
399
|
+
'' !== facts.lastKnownUrl) {
|
|
400
|
+
return reason(NoSessionReason.ROUTE_SERVER_ERROR, 'no browser session connected, but one WAS connected to this daemon earlier, so the wiring ' +
|
|
401
|
+
`is correct. The page was torn down while on ${facts.lastKnownUrl}, and that route answers ` +
|
|
402
|
+
`HTTP ${String(facts.lastKnownStatus)} right now — a server error in the app, not a closed ` +
|
|
403
|
+
'tab and not an install problem. A route that throws server-side tears the page down and ' +
|
|
404
|
+
'the SDK cannot reconnect to it. Fix the route, then reload the tab' +
|
|
405
|
+
`${orOpenCommand(facts)}.${alreadyListeningClause(listening)} ${RETRY}`);
|
|
406
|
+
}
|
|
350
407
|
return reason(NoSessionReason.TAB_GONE, 'no browser session connected, but one WAS connected to this daemon earlier, so the wiring ' +
|
|
351
408
|
`is correct. ${tabGoneWhat(facts.lastKnownUrl)} Ask the human to reopen ` +
|
|
352
|
-
`the app
|
|
409
|
+
`the app${orOpenCommand(facts)}, or reload the tab. ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, alreadyListeningClause(listening).trim());
|
|
353
410
|
}
|
|
354
411
|
// Ranked ahead of every "we have never seen one" branch below, because it is the one fact that
|
|
355
412
|
// OUTRANKS them: those branches all reason from an absence, and this reasons from a recorded
|
|
@@ -360,14 +417,14 @@ export function explainNoSession(facts) {
|
|
|
360
417
|
? `${unattributedListeners(listening)} If the dev server is not running, start it first ` +
|
|
361
418
|
'(the command is in `next_action`).'
|
|
362
419
|
: unattributedListeners(listening);
|
|
363
|
-
return reason(NoSessionReason.APP_NOT_REOPENED, `${RESTARTED_LEAD} ${
|
|
420
|
+
return reason(NoSessionReason.APP_NOT_REOPENED, `${RESTARTED_LEAD} ${openTheApp(facts)} ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, `${DO_NOT_REINSTALL} ${listeners} ${rankedCauses(facts)}`);
|
|
364
421
|
}
|
|
365
422
|
// A config found elsewhere outranks every "you may have no SDK" branch below: those reason from
|
|
366
423
|
// an absence in ONE directory, and this is a file we read in another. Ranked here so the reader is
|
|
367
424
|
// never sent through an install on a project that has demonstrably been installed.
|
|
368
425
|
if (!initialized && (facts.configsElsewhere ?? []).length > 0) {
|
|
369
426
|
return reason(NoSessionReason.CONFIG_ELSEWHERE, 'no browser session connected, and this daemon has never seen one. ' +
|
|
370
|
-
`${configsElsewhereClause(facts)} ${
|
|
427
|
+
`${configsElsewhereClause(facts)} ${openTheApp(facts)} ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, 'This daemon was started somewhere that is not the project, which is the normal outcome when ' +
|
|
371
428
|
`an editor launches it from your home directory. ${unattributedListeners(listening)} ` +
|
|
372
429
|
`${rankedCauses(facts)}`);
|
|
373
430
|
}
|
|
@@ -399,7 +456,7 @@ export function explainNoSession(facts) {
|
|
|
399
456
|
'the Vite or Babel plugin carries the SDK without that file at all.' +
|
|
400
457
|
`${searchedClause(facts)}${siblingListenerClause(facts)} ${leaseAdvice(URL_THEN_LEASE, facts)}`);
|
|
401
458
|
}
|
|
402
|
-
return reason(NoSessionReason.NO_LISTENER, `${stallClause(facts)}no browser session connected, and this daemon has never seen one. ${
|
|
459
|
+
return reason(NoSessionReason.NO_LISTENER, `${stallClause(facts)}no browser session connected, and this daemon has never seen one. ${openTheApp(facts)} ` +
|
|
403
460
|
'Nothing is listening on the ports Reticle scans either, and the usual reason is a dev ' +
|
|
404
461
|
'server that is not running: start it yourself in the background with the command in ' +
|
|
405
462
|
'`next_action`, tell the human in one line that it is running, then open the app in a ' +
|
|
@@ -428,7 +485,7 @@ export function explainNoSession(facts) {
|
|
|
428
485
|
`plugin. ${unattributedListeners(listening)}${searchedClause(facts)}${siblingListenerClause(facts)}`);
|
|
429
486
|
}
|
|
430
487
|
return reason(NoSessionReason.SDK_NOT_REACHING_DAEMON, `${stallClause(facts)}no browser session connected, and this daemon has never seen one for this project, which is ` +
|
|
431
|
-
`wired for Reticle. ${
|
|
488
|
+
`wired for Reticle. ${openTheApp(facts)} If the page IS open and still does not appear, the SDK ` +
|
|
432
489
|
`is not reaching this daemon (on ${String(port)}). ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, `${unattributedListeners(listening)} ${rankedCauses(facts)}`);
|
|
433
490
|
}
|
|
434
491
|
/**
|