@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
|
@@ -23,7 +23,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
|
|
|
23
23
|
export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticle <command>\` once the bin is on your PATH)
|
|
24
24
|
|
|
25
25
|
reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
|
|
26
|
-
[--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)
|
|
26
|
+
[--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)
|
|
27
27
|
init is ONBOARDING: it wires the project, boots the app and proves a session
|
|
28
28
|
connected. It does not drive. The FIRST RUN is the stage that proves a flow:
|
|
29
29
|
reticle verify <url> --explore --persona "<who does what>", or the same thing
|
|
@@ -56,12 +56,21 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
|
|
|
56
56
|
reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)
|
|
57
57
|
[--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)
|
|
58
58
|
[--select <label>] (repeatable: verify only flows carrying these labels — no model, exit 0=pass)
|
|
59
|
-
[--expect '<json predicate>']
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
[--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved
|
|
60
|
+
flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the
|
|
61
|
+
single quotes the docs show, and PowerShell strips the inner double quotes, and
|
|
62
|
+
a predicate that does not parse produces no verdict at all — asks the daemon that
|
|
63
|
+
is already running, so nothing is bound and nothing is stopped. It NEEDS that
|
|
64
|
+
daemon: with none on the port it refuses and names the flag, rather than reporting
|
|
65
|
+
on your saved flows instead. Cannot be combined with --storage-state, which this
|
|
66
|
+
path has nowhere to load. This is the path when your client never loaded the
|
|
67
|
+
reticle_* tools. exit 0 ONLY on verified:"yes" — "unknown" is not a pass)
|
|
63
68
|
reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)
|
|
64
|
-
reticle gate [--since <ref>] [file...]
|
|
69
|
+
reticle gate [--since <ref>] [--accept-coverage] [file...] (exit non-zero unless passing artifacts cover the affected flows)
|
|
70
|
+
reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)
|
|
71
|
+
reticle setup mcp (register the MCP server with your agents; the installer's registration half)
|
|
72
|
+
reticle capsules (list the saved fail-to-pass bug capsules in .reticle/capsules)
|
|
73
|
+
reticle hunt <dir> (aggregate a directory of crawl reports into one false-green rate)
|
|
65
74
|
reticle watch [url] (on save, report which saved flows must re-verify)
|
|
66
75
|
reticle drive <url> [--headless] (foreground mode, for debugging)
|
|
67
76
|
reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)
|
|
@@ -102,6 +111,10 @@ const CAPSULES_COMMAND = 'capsules';
|
|
|
102
111
|
const GATE_COMMAND = 'gate';
|
|
103
112
|
/** Hook mode: prose a human can read, and silence when there was simply nothing to check. */
|
|
104
113
|
const HOOK_FLAG = '--hook';
|
|
114
|
+
const ACCEPT_COVERAGE_FLAG = '--accept-coverage';
|
|
115
|
+
/** `reticle report [--session <id>] [--hook]` — what the latest session claimed, and what held. */
|
|
116
|
+
const REPORT_COMMAND = 'report';
|
|
117
|
+
const SESSION_FLAG = '--session';
|
|
105
118
|
const WATCH_COMMAND = 'watch';
|
|
106
119
|
const UPDATE_COMMAND = 'update';
|
|
107
120
|
const ROLLBACK_COMMAND = 'rollback';
|
|
@@ -146,6 +159,7 @@ const KNOWN_COMMANDS = new Set([
|
|
|
146
159
|
HUNT_COMMAND,
|
|
147
160
|
CAPSULES_COMMAND,
|
|
148
161
|
GATE_COMMAND,
|
|
162
|
+
REPORT_COMMAND,
|
|
149
163
|
WATCH_COMMAND,
|
|
150
164
|
UPDATE_COMMAND,
|
|
151
165
|
ROLLBACK_COMMAND,
|
|
@@ -228,6 +242,8 @@ const FILES_ONLY_FLAG = '--files-only';
|
|
|
228
242
|
* and `init` runs unattended. Whoever passes this has decided; nothing decides it for them.
|
|
229
243
|
*/
|
|
230
244
|
const CAPTURE_BODIES_FLAG = '--capture-bodies';
|
|
245
|
+
/** `--hooks`: also install the print-only Claude Code Stop hook (`reticle report --hook`). */
|
|
246
|
+
const HOOKS_FLAG = '--hooks';
|
|
231
247
|
/**
|
|
232
248
|
* The rest of the runtime surface.
|
|
233
249
|
*
|
|
@@ -410,6 +426,7 @@ function parseInitFlags(args) {
|
|
|
410
426
|
const env = [];
|
|
411
427
|
let filesOnly = false;
|
|
412
428
|
let captureBodies = false;
|
|
429
|
+
let hooks = false;
|
|
413
430
|
let json = false;
|
|
414
431
|
let open = true;
|
|
415
432
|
let relaunch = false;
|
|
@@ -457,6 +474,9 @@ function parseInitFlags(args) {
|
|
|
457
474
|
else if (arg === CAPTURE_BODIES_FLAG) {
|
|
458
475
|
captureBodies = true;
|
|
459
476
|
}
|
|
477
|
+
else if (arg === HOOKS_FLAG) {
|
|
478
|
+
hooks = true;
|
|
479
|
+
}
|
|
460
480
|
else if (arg === JSON_FLAG) {
|
|
461
481
|
json = true;
|
|
462
482
|
}
|
|
@@ -520,6 +540,7 @@ function parseInitFlags(args) {
|
|
|
520
540
|
env,
|
|
521
541
|
filesOnly,
|
|
522
542
|
captureBodies,
|
|
543
|
+
hooks,
|
|
523
544
|
json,
|
|
524
545
|
open,
|
|
525
546
|
relaunch,
|
|
@@ -561,6 +582,18 @@ function parseTargetArgs(rest) {
|
|
|
561
582
|
}
|
|
562
583
|
return since === undefined ? { files } : { files, since };
|
|
563
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Whether this command talks to a daemon on the resolved port, and so must not run when
|
|
587
|
+
* the workspace port conflict (cli-port) has said the port is a guess. `init` carries a port but resolves the
|
|
588
|
+
* workspace itself, and a tutorial only dials one when asked to run.
|
|
589
|
+
*/
|
|
590
|
+
export function dialsTheDaemon(parsed) {
|
|
591
|
+
if ('init' === parsed.kind)
|
|
592
|
+
return false;
|
|
593
|
+
if ('tutorial' === parsed.kind)
|
|
594
|
+
return parsed.run;
|
|
595
|
+
return 'port' in parsed;
|
|
596
|
+
}
|
|
564
597
|
/**
|
|
565
598
|
* @param defaultHeadless Whether a browser Reticle launches should be hidden when no flag says
|
|
566
599
|
* otherwise. Injected rather than read from the environment here, because this module is pure —
|
|
@@ -606,6 +639,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
606
639
|
env: r.env,
|
|
607
640
|
filesOnly: r.filesOnly,
|
|
608
641
|
captureBodies: r.captureBodies,
|
|
642
|
+
hooks: r.hooks,
|
|
609
643
|
json: r.json,
|
|
610
644
|
open: r.open,
|
|
611
645
|
relaunch: r.relaunch,
|
|
@@ -772,6 +806,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
772
806
|
...(r.storageState !== undefined ? { storageState: r.storageState } : {}),
|
|
773
807
|
...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
|
|
774
808
|
...(r.expect !== undefined ? { expect: r.expect } : {}),
|
|
809
|
+
...(r.expectFile !== undefined ? { expectFile: r.expectFile } : {}),
|
|
775
810
|
...(true === r.explore ? { explore: true } : {}),
|
|
776
811
|
...(r.persona !== undefined ? { persona: r.persona } : {}),
|
|
777
812
|
...(r.select !== undefined ? { select: r.select } : {}),
|
|
@@ -791,15 +826,26 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
791
826
|
return { kind: 'affected', files: t.files, ...(since === undefined ? {} : { since }) };
|
|
792
827
|
}
|
|
793
828
|
case GATE_COMMAND: {
|
|
794
|
-
//
|
|
829
|
+
// Flags are stripped before target parsing, which would reject them as unknown.
|
|
795
830
|
const hook = rest.includes(HOOK_FLAG);
|
|
796
|
-
const
|
|
831
|
+
const acceptCoverage = rest.includes(ACCEPT_COVERAGE_FLAG);
|
|
832
|
+
const t = parseTargetArgs(rest.filter((a) => a !== HOOK_FLAG && a !== ACCEPT_COVERAGE_FLAG));
|
|
797
833
|
const since = t.since ?? implicitSince(t.files);
|
|
798
834
|
return {
|
|
799
835
|
kind: 'gate',
|
|
800
836
|
files: t.files,
|
|
801
837
|
...(since === undefined ? {} : { since }),
|
|
802
838
|
...(hook ? { hook: true } : {}),
|
|
839
|
+
...(acceptCoverage ? { acceptCoverage: true } : {}),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
case REPORT_COMMAND: {
|
|
843
|
+
const at = rest.indexOf(SESSION_FLAG);
|
|
844
|
+
const session = -1 === at ? undefined : rest[at + 1];
|
|
845
|
+
return {
|
|
846
|
+
kind: 'report',
|
|
847
|
+
hook: rest.includes(HOOK_FLAG),
|
|
848
|
+
...(session === undefined ? {} : { session }),
|
|
803
849
|
};
|
|
804
850
|
}
|
|
805
851
|
case WATCH_COMMAND: {
|
|
@@ -14,6 +14,7 @@ import { log } from '../../log.js';
|
|
|
14
14
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { reticleDepsOf, sdkSyncCommand } from '../update/sdk-sync.js';
|
|
17
|
+
const WINDOWS_MANUAL_UPDATE = 'irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1 | iex';
|
|
17
18
|
/**
|
|
18
19
|
* Bring the SDK in the CURRENT project to the version being installed.
|
|
19
20
|
*
|
|
@@ -94,7 +95,13 @@ export async function handleUpdate() {
|
|
|
94
95
|
await applyUpdate(target); // calls process.exit; Claude Code restarts
|
|
95
96
|
}
|
|
96
97
|
catch (error) {
|
|
97
|
-
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
log('reticle_update_failed', {
|
|
100
|
+
error: message,
|
|
101
|
+
...('win32' === process.platform && message.includes('spawn EINVAL')
|
|
102
|
+
? { hint: `run \`${WINDOWS_MANUAL_UPDATE}\` in PowerShell to update manually` }
|
|
103
|
+
: {}),
|
|
104
|
+
});
|
|
98
105
|
process.exitCode = 1;
|
|
99
106
|
}
|
|
100
107
|
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { type ReticleVerificationRun } from '@reticlehq/core';
|
|
13
13
|
import { type RunningServer } from '../../index.js';
|
|
14
|
+
import { PortPresence } from '../daemon/binding/port-presence.js';
|
|
15
|
+
import { type CloudConfig } from '../../memory/cloud/cloud-sync.js';
|
|
14
16
|
import { type VerifyProgressListener } from '../../judgement/runs/reticle-runner.js';
|
|
15
17
|
import type { ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
16
18
|
/** The live capabilities runVerify needs — faked in tests so the logic runs without a browser. */
|
|
@@ -46,6 +48,11 @@ export interface VerifyPorts {
|
|
|
46
48
|
out: (line: string) => void;
|
|
47
49
|
fail: (line: string) => void;
|
|
48
50
|
exit: (code: number) => void;
|
|
51
|
+
/**
|
|
52
|
+
* The platform credential for this repo: the stored key for a linked repo, else the exported
|
|
53
|
+
* one. A port so the push is testable, and so this reads what every other caller reads.
|
|
54
|
+
*/
|
|
55
|
+
cloud: () => Promise<CloudConfig | null>;
|
|
49
56
|
}
|
|
50
57
|
interface VerifyArgs {
|
|
51
58
|
url: string;
|
|
@@ -104,10 +111,83 @@ export declare function urlParts(url: string): {
|
|
|
104
111
|
* first bullet — not this change.
|
|
105
112
|
*/
|
|
106
113
|
export declare function portBusyMessage(port: number): string;
|
|
114
|
+
/**
|
|
115
|
+
* What `--expect` is answered with when the port is not serving a daemon.
|
|
116
|
+
*
|
|
117
|
+
* It used to be answered with nothing at all. The predicate was read only inside the daemon branch,
|
|
118
|
+
* so on a free or foreign port the flag was dropped without a word and the command carried on into
|
|
119
|
+
* the saved-flows path — where it reported, accurately and about something else entirely, that there
|
|
120
|
+
* were no saved flows to verify. Byte-identical output to the same command with no `--expect` on it.
|
|
121
|
+
*
|
|
122
|
+
* That is the documented recovery for a client that never loaded the `reticle_*` tools, so it failed
|
|
123
|
+
* for the people with no other route to a verdict, and it failed in the one way they could not
|
|
124
|
+
* diagnose: the refusal they got was true.
|
|
125
|
+
*/
|
|
126
|
+
export declare function expectNeedsDaemonMessage(port: number, presence: PortPresence): string;
|
|
127
|
+
/**
|
|
128
|
+
* The model key is set here but not in the daemon, which is where the drive runs. The daemon is
|
|
129
|
+
* usually the one `init` or the agent's MCP client started, from an environment without the key.
|
|
130
|
+
*/
|
|
131
|
+
export declare function daemonLacksKeyMessage(port: number): string;
|
|
132
|
+
/** What `verify` does next, once the options and the state of the port are both known. */
|
|
133
|
+
export declare const VerifyRoute: {
|
|
134
|
+
/** Ask the daemon that already owns the port for a one-shot verdict on the predicate. */
|
|
135
|
+
readonly ADHOC: "adhoc";
|
|
136
|
+
/** Ask the daemon that already owns the port to replay the saved flows. See runAdhocSuite. */
|
|
137
|
+
readonly ADHOC_SUITE: "adhoc-suite";
|
|
138
|
+
/** Ask the daemon that already owns the port to explore the url and record flows. */
|
|
139
|
+
readonly ADHOC_EXPLORE: "adhoc-explore";
|
|
140
|
+
/** Boot our own daemon, drive the url, replay the flows saved on disk. */
|
|
141
|
+
readonly FLOWS: "flows";
|
|
142
|
+
/** Answer the caller, and run nothing. */
|
|
143
|
+
readonly REFUSE: "refuse";
|
|
144
|
+
};
|
|
145
|
+
export type VerifyRoute = (typeof VerifyRoute)[keyof typeof VerifyRoute];
|
|
146
|
+
export type VerifyPlan = {
|
|
147
|
+
route: typeof VerifyRoute.ADHOC;
|
|
148
|
+
} | {
|
|
149
|
+
route: typeof VerifyRoute.ADHOC_SUITE;
|
|
150
|
+
} | {
|
|
151
|
+
route: typeof VerifyRoute.ADHOC_EXPLORE;
|
|
152
|
+
} | {
|
|
153
|
+
route: typeof VerifyRoute.FLOWS;
|
|
154
|
+
} | {
|
|
155
|
+
route: typeof VerifyRoute.REFUSE;
|
|
156
|
+
message: string;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* All four routes this command has, in one pure function.
|
|
160
|
+
*
|
|
161
|
+
* They used to be a pair of nested `if`s that consulted `--expect` only after the port had already
|
|
162
|
+
* answered as a daemon, which is how one of the four went missing: a predicate on any other port
|
|
163
|
+
* state fell through to a path that cannot see it. Stating the cases together is what makes an
|
|
164
|
+
* unhandled one visible, and it lets every one of them be driven with no port, no daemon and no
|
|
165
|
+
* browser — which is where an argument-level decision belongs.
|
|
166
|
+
*/
|
|
167
|
+
export declare function routeVerify(args: {
|
|
168
|
+
/** True when the caller supplied a predicate, by `--expect` or `--expect-file`. */
|
|
169
|
+
hasPredicate: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* True when the caller asked for something only a browser of OUR OWN can do: `--headed` or
|
|
172
|
+
* `--storage-state`. The running daemon's tab can honour neither, so replaying there would
|
|
173
|
+
* silently ignore the request.
|
|
174
|
+
*/
|
|
175
|
+
wantsOwnBrowser?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* `--explore`: a model drives the url and records flows. The daemon on the port runs the same
|
|
178
|
+
* drive (`reticle_verify { action: "explore" }`), so this is NOT a reason to need our own browser —
|
|
179
|
+
* it used to be, and `init` leaves exactly that daemon behind before recommending this command.
|
|
180
|
+
*/
|
|
181
|
+
explore?: boolean;
|
|
182
|
+
presence: PortPresence;
|
|
183
|
+
port: number;
|
|
184
|
+
}): VerifyPlan;
|
|
107
185
|
export declare function handleVerify(parsed: {
|
|
108
186
|
url: string;
|
|
109
187
|
/** A parsed predicate for a flow-free, one-shot verdict against the running daemon. */
|
|
110
188
|
expect?: unknown;
|
|
189
|
+
/** The same predicate in a file, which is the form no shell can mangle. Resolved below. */
|
|
190
|
+
expectFile?: string;
|
|
111
191
|
headless: boolean;
|
|
112
192
|
timeoutMs?: number;
|
|
113
193
|
storageState?: string;
|
|
@@ -10,17 +10,22 @@
|
|
|
10
10
|
* — is unit-tested without launching a real browser.
|
|
11
11
|
*/
|
|
12
12
|
import { join, basename } from 'node:path';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
13
14
|
import { runAdhocVerdict } from './adhoc-verdict.js';
|
|
15
|
+
import { runAdhocExplore, runAdhocSuite } from './adhoc-suite.js';
|
|
14
16
|
import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY, } from '../../surface/tools/harness-explore.js';
|
|
15
17
|
import { resolveSuiteSelection } from '../../language/flows/suite-selection.js';
|
|
16
18
|
import { readOrCreatePairingTokenSync, defaultPairingTokenDir, } from '../../portal/bridge/pairing-token.js';
|
|
17
19
|
import { randomUUID } from 'node:crypto';
|
|
18
20
|
import { RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, VerifyPhase, } from '@reticlehq/core';
|
|
19
21
|
import { start } from '../../index.js';
|
|
20
|
-
import { probePresence, PortPresence } from '../daemon/binding/port-presence.js';
|
|
22
|
+
import { probePresence, describePresence, PortPresence, } from '../daemon/binding/port-presence.js';
|
|
23
|
+
import { EXPECT_FLAG } from './cli-parse-grammar.js';
|
|
21
24
|
import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
|
|
22
|
-
import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
23
|
-
import { cloudFetch, createProgressReporter,
|
|
25
|
+
import { fetchStatus, summarizeStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
26
|
+
import { cloudFetch, createProgressReporter, syncProgressToCloud, syncRunToCloud, SyncOutcome, } from '../../memory/cloud/cloud-sync.js';
|
|
27
|
+
import { linkedCloudPort } from '../../memory/cloud/cloud-config.js';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
24
29
|
import { ReticleRunner } from '../../judgement/runs/reticle-runner.js';
|
|
25
30
|
import { createRunnerPort } from '../../judgement/runs/runner-port.js';
|
|
26
31
|
import { RunStore } from '../../judgement/runs/artifact/run-store.js';
|
|
@@ -125,7 +130,7 @@ export async function runVerify(args, ports) {
|
|
|
125
130
|
* live progress by it until the finished run replaces the whole picture.
|
|
126
131
|
*/
|
|
127
132
|
const streamId = randomUUID();
|
|
128
|
-
const cloud =
|
|
133
|
+
const cloud = await ports.cloud().catch(() => null);
|
|
129
134
|
const progress = createProgressReporter(streamId, cloud, cloudFetch);
|
|
130
135
|
let run;
|
|
131
136
|
try {
|
|
@@ -137,7 +142,7 @@ export async function runVerify(args, ports) {
|
|
|
137
142
|
progress.stop();
|
|
138
143
|
await progress.flush().catch(() => undefined);
|
|
139
144
|
}
|
|
140
|
-
await pushRunToCloud(run, ports); // best-effort; opt-in; never changes the verdict or exit code
|
|
145
|
+
await pushRunToCloud(run, cloud, ports); // best-effort; opt-in; never changes the verdict or exit code
|
|
141
146
|
/*
|
|
142
147
|
* The last event anybody watching is waiting for, sent after the artifact has actually landed —
|
|
143
148
|
* so "pushed" on a dashboard means the run is really there, not that we were about to try.
|
|
@@ -181,13 +186,12 @@ async function explore(conn, args, ports) {
|
|
|
181
186
|
return true;
|
|
182
187
|
}
|
|
183
188
|
/**
|
|
184
|
-
* Best-effort push of a finished run to the cloud dashboard. Opt-in: only fires when
|
|
185
|
-
*
|
|
186
|
-
*
|
|
189
|
+
* Best-effort push of a finished run to the cloud dashboard. Opt-in: only fires when this repo is
|
|
190
|
+
* linked or RETICLE_API_KEY is set. Absent → no-op, nothing leaves the machine (the no-phone-home
|
|
191
|
+
* default). A push failure NEVER changes the verdict or exit code — the run is
|
|
187
192
|
* already reported locally; the cloud copy is an enhancement.
|
|
188
193
|
*/
|
|
189
|
-
async function pushRunToCloud(run, ports) {
|
|
190
|
-
const config = resolveCloudConfig(process.env);
|
|
194
|
+
async function pushRunToCloud(run, config, ports) {
|
|
191
195
|
if (null === config)
|
|
192
196
|
return;
|
|
193
197
|
const result = await syncRunToCloud(run, config, cloudFetch);
|
|
@@ -365,8 +369,106 @@ export function portBusyMessage(port) {
|
|
|
365
369
|
"your agent's MCP link and the proxy usually respawns a daemon into the gap before verify can " +
|
|
366
370
|
'bind — prefer one of the above.');
|
|
367
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* What `--expect` is answered with when the port is not serving a daemon.
|
|
374
|
+
*
|
|
375
|
+
* It used to be answered with nothing at all. The predicate was read only inside the daemon branch,
|
|
376
|
+
* so on a free or foreign port the flag was dropped without a word and the command carried on into
|
|
377
|
+
* the saved-flows path — where it reported, accurately and about something else entirely, that there
|
|
378
|
+
* were no saved flows to verify. Byte-identical output to the same command with no `--expect` on it.
|
|
379
|
+
*
|
|
380
|
+
* That is the documented recovery for a client that never loaded the `reticle_*` tools, so it failed
|
|
381
|
+
* for the people with no other route to a verdict, and it failed in the one way they could not
|
|
382
|
+
* diagnose: the refusal they got was true.
|
|
383
|
+
*/
|
|
384
|
+
export function expectNeedsDaemonMessage(port, presence) {
|
|
385
|
+
return (`${EXPECT_FLAG} asks the Reticle daemon that already owns port ${String(port)} for the ` +
|
|
386
|
+
'verdict — it binds nothing and launches nothing, which is what lets it answer while the port ' +
|
|
387
|
+
`is busy. Right now ${describePresence(presence, port)}.\n\n` +
|
|
388
|
+
' The predicate did not run, and nothing was proved.\n\n' +
|
|
389
|
+
' • Start a daemon on that port, pointed at the app, then re-run this command: ' +
|
|
390
|
+
`npx @reticlehq/server serve --port ${String(port)} --drive <url>\n` +
|
|
391
|
+
` • Or drop ${EXPECT_FLAG}, and verify drives the url itself and replays your saved flows — ` +
|
|
392
|
+
'a different question, and it proves nothing about your predicate.');
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The model key is set here but not in the daemon, which is where the drive runs. The daemon is
|
|
396
|
+
* usually the one `init` or the agent's MCP client started, from an environment without the key.
|
|
397
|
+
*/
|
|
398
|
+
export function daemonLacksKeyMessage(port) {
|
|
399
|
+
return (`${ReticleEnv.HARNESS_KEY} is set in this shell, but the daemon on port ${String(port)} was ` +
|
|
400
|
+
'started without it, and the drive runs inside the daemon. Restart it from this shell so it ' +
|
|
401
|
+
`inherits the key: npx @reticlehq/server restart --port ${String(port)}`);
|
|
402
|
+
}
|
|
403
|
+
/** What `verify` does next, once the options and the state of the port are both known. */
|
|
404
|
+
export const VerifyRoute = {
|
|
405
|
+
/** Ask the daemon that already owns the port for a one-shot verdict on the predicate. */
|
|
406
|
+
ADHOC: 'adhoc',
|
|
407
|
+
/** Ask the daemon that already owns the port to replay the saved flows. See runAdhocSuite. */
|
|
408
|
+
ADHOC_SUITE: 'adhoc-suite',
|
|
409
|
+
/** Ask the daemon that already owns the port to explore the url and record flows. */
|
|
410
|
+
ADHOC_EXPLORE: 'adhoc-explore',
|
|
411
|
+
/** Boot our own daemon, drive the url, replay the flows saved on disk. */
|
|
412
|
+
FLOWS: 'flows',
|
|
413
|
+
/** Answer the caller, and run nothing. */
|
|
414
|
+
REFUSE: 'refuse',
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* All four routes this command has, in one pure function.
|
|
418
|
+
*
|
|
419
|
+
* They used to be a pair of nested `if`s that consulted `--expect` only after the port had already
|
|
420
|
+
* answered as a daemon, which is how one of the four went missing: a predicate on any other port
|
|
421
|
+
* state fell through to a path that cannot see it. Stating the cases together is what makes an
|
|
422
|
+
* unhandled one visible, and it lets every one of them be driven with no port, no daemon and no
|
|
423
|
+
* browser — which is where an argument-level decision belongs.
|
|
424
|
+
*/
|
|
425
|
+
export function routeVerify(args) {
|
|
426
|
+
if (args.hasPredicate) {
|
|
427
|
+
return args.presence === PortPresence.DAEMON
|
|
428
|
+
? { route: VerifyRoute.ADHOC }
|
|
429
|
+
: { route: VerifyRoute.REFUSE, message: expectNeedsDaemonMessage(args.port, args.presence) };
|
|
430
|
+
}
|
|
431
|
+
if (args.presence !== PortPresence.DAEMON)
|
|
432
|
+
return { route: VerifyRoute.FLOWS };
|
|
433
|
+
if (true === args.explore && true !== args.wantsOwnBrowser) {
|
|
434
|
+
return { route: VerifyRoute.ADHOC_EXPLORE };
|
|
435
|
+
}
|
|
436
|
+
// No predicate, but there may be SAVED FLOWS, and a daemon already owns the port: replay them
|
|
437
|
+
// there rather than refusing, unless the request needs a browser that path never opens.
|
|
438
|
+
return true === args.wantsOwnBrowser
|
|
439
|
+
? { route: VerifyRoute.REFUSE, message: portBusyMessage(args.port) }
|
|
440
|
+
: { route: VerifyRoute.ADHOC_SUITE };
|
|
441
|
+
}
|
|
368
442
|
export function handleVerify(parsed) {
|
|
369
443
|
const now = () => Date.now();
|
|
444
|
+
/*
|
|
445
|
+
* `--expect-file` is `--expect` with the shell taken out of the loop.
|
|
446
|
+
*
|
|
447
|
+
* Resolved here rather than in the parser because the parser is pure and reads no disk, and
|
|
448
|
+
* because the two failures a reader can hit — the file is not there, and what is in it is not
|
|
449
|
+
* JSON — are worth saying apart. Either one ends the run before anything binds: a predicate that
|
|
450
|
+
* could not be read produces NO verdict, and exiting 1 with the reason is the only honest answer.
|
|
451
|
+
*/
|
|
452
|
+
let expectation = parsed.expect;
|
|
453
|
+
if (parsed.expectFile !== undefined) {
|
|
454
|
+
let text;
|
|
455
|
+
try {
|
|
456
|
+
text = readFileSync(parsed.expectFile, 'utf8');
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
process.stderr.write(`--expect-file: cannot read ${parsed.expectFile}\n`);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
expectation = JSON.parse(text);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
process.stderr.write(`--expect-file: ${parsed.expectFile} is not valid JSON, so no predicate was read\n`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
370
472
|
const reticleRoot = join(process.cwd(), ReticleDir.ROOT);
|
|
371
473
|
const projectName = basename(process.cwd()) || DEFAULT_PROJECT_NAME;
|
|
372
474
|
const ports = {
|
|
@@ -383,26 +485,43 @@ export function handleVerify(parsed) {
|
|
|
383
485
|
out: (line) => process.stdout.write(`${line}\n`),
|
|
384
486
|
fail: (line) => process.stderr.write(`${line}\n`),
|
|
385
487
|
exit: (code) => process.exit(code),
|
|
488
|
+
cloud: linkedCloudPort(createNodeFileSystem(), reticleRoot, homedir(), process.env),
|
|
386
489
|
};
|
|
387
490
|
// Asked BEFORE anything binds. The listen failure arrives asynchronously on the server object,
|
|
388
491
|
// long after `start` has resolved, so no `.catch` on that promise can ever see it — which is why
|
|
389
492
|
// this used to reach the user as a raw node stack rather than as an answer.
|
|
390
493
|
void (async () => {
|
|
391
494
|
const port = parsed.port ?? RETICLE_DEFAULT_PORT;
|
|
392
|
-
|
|
393
|
-
|
|
495
|
+
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
496
|
+
const plan = routeVerify({
|
|
497
|
+
hasPredicate: expectation !== undefined,
|
|
498
|
+
wantsOwnBrowser: !parsed.headless || parsed.storageState !== undefined,
|
|
499
|
+
explore: true === parsed.explore || parsed.persona !== undefined,
|
|
500
|
+
presence,
|
|
501
|
+
port,
|
|
502
|
+
});
|
|
503
|
+
switch (plan.route) {
|
|
504
|
+
// Either the busy port, or a predicate this state cannot answer. Both are refusals that name
|
|
505
|
+
// what was asked for — never a report about a check nobody requested.
|
|
506
|
+
case VerifyRoute.REFUSE:
|
|
507
|
+
ports.fail(plan.message);
|
|
508
|
+
ports.exit(EXIT_FAIL);
|
|
509
|
+
return;
|
|
394
510
|
// A daemon owning the port is the NORMAL state after a working install, and it used to be the
|
|
395
511
|
// end of the road: `verify` refused, the other verdict paths need saved flows a first-install
|
|
396
512
|
// project does not have, and stopping the daemon cuts the agent's own MCP link. An agent could
|
|
397
513
|
// hold a live, correctly-wired app and have no way to reach a verdict at all.
|
|
398
514
|
//
|
|
399
|
-
// With `--expect` there is
|
|
400
|
-
//
|
|
401
|
-
|
|
515
|
+
// With `--expect` there is a way, and it does not need the port: ask the daemon that already
|
|
516
|
+
// owns it. See runAdhocVerdict.
|
|
517
|
+
case VerifyRoute.ADHOC: {
|
|
402
518
|
const verdict = await runAdhocVerdict({
|
|
403
519
|
port,
|
|
404
520
|
...(parsed.url !== undefined && '' !== parsed.url ? { url: parsed.url } : {}),
|
|
405
|
-
predicate:
|
|
521
|
+
predicate: expectation,
|
|
522
|
+
// Read fresh, not from `presence`: that probe asks whether a daemon is on the port, and
|
|
523
|
+
// this asks whether a TAB is — the difference between asserting and opening the url first.
|
|
524
|
+
sessions: async () => summarizeStatus(await fetchStatus(port)).sessions,
|
|
406
525
|
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
407
526
|
...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
|
|
408
527
|
});
|
|
@@ -411,16 +530,52 @@ export function handleVerify(parsed) {
|
|
|
411
530
|
ports.exit(verdict.code);
|
|
412
531
|
return;
|
|
413
532
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
533
|
+
// No predicate, a daemon on the port, and saved flows to run: the other half of the same
|
|
534
|
+
// dead end. The whole promise of a saved suite is that it runs again without an agent.
|
|
535
|
+
case VerifyRoute.ADHOC_SUITE: {
|
|
536
|
+
const suite = await runAdhocSuite({
|
|
537
|
+
port,
|
|
538
|
+
...(parsed.select === undefined ? {} : { select: parsed.select }),
|
|
539
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
540
|
+
...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
|
|
541
|
+
});
|
|
542
|
+
for (const line of suite.lines)
|
|
543
|
+
ports.out(line);
|
|
544
|
+
ports.exit(suite.code);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
// `--explore` with a daemon on the port — the state `init` leaves behind right before it names
|
|
548
|
+
// this command as the first run. The daemon drives and records; this process only asks.
|
|
549
|
+
case VerifyRoute.ADHOC_EXPLORE: {
|
|
550
|
+
const explored = await runAdhocExplore({
|
|
551
|
+
port,
|
|
552
|
+
url: parsed.url,
|
|
553
|
+
...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
|
|
554
|
+
sessions: async () => summarizeStatus(await fetchStatus(port)).sessions,
|
|
555
|
+
...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
|
|
556
|
+
});
|
|
557
|
+
for (const line of explored.lines)
|
|
558
|
+
ports.out(line);
|
|
559
|
+
// The drive runs INSIDE the daemon, so the key has to be in ITS environment. A reader who
|
|
560
|
+
// exported it in this shell and got "set ANTHROPIC_API_KEY" back has been told to do the
|
|
561
|
+
// thing they just did; say where the key is actually missing.
|
|
562
|
+
if (0 !== explored.code && harnessAvailable(process.env)) {
|
|
563
|
+
const refusedForKey = explored.lines.some((line) => line.includes(MSG_NO_HARNESS_KEY));
|
|
564
|
+
if (refusedForKey)
|
|
565
|
+
ports.fail(daemonLacksKeyMessage(port));
|
|
566
|
+
}
|
|
567
|
+
ports.exit(explored.code);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
case VerifyRoute.FLOWS:
|
|
571
|
+
await runVerify({
|
|
572
|
+
url: parsed.url,
|
|
573
|
+
timeoutMs: parsed.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS,
|
|
574
|
+
...(true === parsed.explore ? { explore: true } : {}),
|
|
575
|
+
...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
|
|
576
|
+
...(parsed.select === undefined ? {} : { select: parsed.select }),
|
|
577
|
+
}, ports);
|
|
578
|
+
return;
|
|
417
579
|
}
|
|
418
|
-
await runVerify({
|
|
419
|
-
url: parsed.url,
|
|
420
|
-
timeoutMs: parsed.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS,
|
|
421
|
-
...(true === parsed.explore ? { explore: true } : {}),
|
|
422
|
-
...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
|
|
423
|
-
...(parsed.select === undefined ? {} : { select: parsed.select }),
|
|
424
|
-
}, ports);
|
|
425
580
|
})();
|
|
426
581
|
}
|
|
@@ -489,7 +489,7 @@ const cmdSync = async (argv) => {
|
|
|
489
489
|
const reticleRoot = join(process.cwd(), RETICLE_DIR);
|
|
490
490
|
const cloud = await resolveProjectCloud(fs, reticleRoot, homedir(), process.env);
|
|
491
491
|
if (null === cloud.config) {
|
|
492
|
-
err('cloud not attached here
|
|
492
|
+
err(cloud.reason ?? 'cloud not attached here: run `reticle link`, or set RETICLE_API_KEY');
|
|
493
493
|
return 1;
|
|
494
494
|
}
|
|
495
495
|
const config = cloud.config;
|
|
@@ -521,7 +521,10 @@ const cmdSync = async (argv) => {
|
|
|
521
521
|
flows: report.flowsSent,
|
|
522
522
|
records: report.derivedSent,
|
|
523
523
|
...(report.runsRejected.length > 0 ? { rejected: report.runsRejected } : {}),
|
|
524
|
+
...(report.refused.length > 0 ? { refused: report.refused } : {}),
|
|
524
525
|
},
|
|
526
|
+
...(report.held.length > 0 ? { notSent: report.held } : {}),
|
|
527
|
+
...(report.notRetried.length > 0 ? { notRetried: report.notRetried } : {}),
|
|
525
528
|
pulled: report.pulled,
|
|
526
529
|
...(report.morePending ? { morePending: true } : {}),
|
|
527
530
|
...(report.error === undefined ? {} : { error: report.error }),
|
|
@@ -546,7 +549,7 @@ const repoCloud = async () => {
|
|
|
546
549
|
const fs = createNodeFileSystem();
|
|
547
550
|
const cloud = await resolveProjectCloud(fs, join(process.cwd(), RETICLE_DIR), homedir(), process.env);
|
|
548
551
|
if (null === cloud.config)
|
|
549
|
-
throw new Error('cloud not attached here
|
|
552
|
+
throw new Error(cloud.reason ?? 'cloud not attached here: run `reticle link`, or set RETICLE_API_KEY');
|
|
550
553
|
return cloud.config;
|
|
551
554
|
};
|
|
552
555
|
/** `reticle runs` — the linked project's recent run artifacts (the key scopes it server-side). */
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { type Session } from './auth/cloud-session.js';
|
|
2
|
-
/** Where `reticle login` dials when nothing says otherwise: the hosted service. */
|
|
3
|
-
export declare const DEFAULT_URL = "https://app.reticle.sh";
|
|
4
2
|
export declare const RETICLE_DIR = ".reticle";
|
|
5
3
|
export declare const SESSION_FILE = "session.json";
|
|
6
4
|
export declare const CREDENTIALS_FILE = "credentials.json";
|
|
@@ -8,15 +8,13 @@
|
|
|
8
8
|
* `cloud-login` is the first to move — and it is a real seam rather than a file-size dodge: nothing
|
|
9
9
|
* here knows what a project, a credential or a link is.
|
|
10
10
|
*/
|
|
11
|
-
import { apiKeyFrom } from '@reticlehq/core';
|
|
11
|
+
import { DEFAULT_PLATFORM_URL, apiKeyFrom, cloudUrlFrom } from '@reticlehq/core';
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
import { cloudFetch } from '../../memory/cloud/cloud-sync.js';
|
|
17
17
|
import { normalizeUrl, readSessionFrom, resolveSessionFor, sessionPath, } from './auth/cloud-session.js';
|
|
18
|
-
/** Where `reticle login` dials when nothing says otherwise: the hosted service. */
|
|
19
|
-
export const DEFAULT_URL = 'https://app.reticle.sh';
|
|
20
18
|
export const RETICLE_DIR = '.reticle';
|
|
21
19
|
export const SESSION_FILE = 'session.json';
|
|
22
20
|
export const CREDENTIALS_FILE = 'credentials.json';
|
|
@@ -72,7 +70,7 @@ export const baseUrl = (session, explicit) => {
|
|
|
72
70
|
// and it cannot leak into a sibling process the way an exported variable does.
|
|
73
71
|
if (explicit !== undefined && explicit.length > 0)
|
|
74
72
|
return normalizeUrl(explicit);
|
|
75
|
-
const env = process.env
|
|
73
|
+
const env = cloudUrlFrom(process.env);
|
|
76
74
|
if (env !== undefined && env.length > 0)
|
|
77
75
|
return env.replace(/\/+$/, '');
|
|
78
76
|
if (null !== session && session.url.length > 0)
|
|
@@ -81,7 +79,7 @@ export const baseUrl = (session, explicit) => {
|
|
|
81
79
|
// saying because that default was wrong for everyone except us. The default is now the hosted
|
|
82
80
|
// service, so the fallback IS the intended path — and a warning printed on the correct path is
|
|
83
81
|
// how people learn to ignore stderr, which is where the real problems are written.
|
|
84
|
-
return
|
|
82
|
+
return DEFAULT_PLATFORM_URL;
|
|
85
83
|
};
|
|
86
84
|
/** Bearer for a command: an explicit api key (agent) wins, else the human login token. */
|
|
87
85
|
export const bearer = (session) => apiKeyFrom(process.env) ?? session?.token ?? null;
|