@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
package/dist/index.js
CHANGED
|
@@ -4,14 +4,14 @@ import { fetchPlatformConfig } from './features/harness/platform-config.js';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
|
-
import { linkedCloudPort } from './memory/cloud/cloud-config.js';
|
|
7
|
+
import { linkedCloudPort, platformEnvPort } from './memory/cloud/cloud-config.js';
|
|
8
8
|
import { attachCloudSync } from './memory/cloud/sync-daemon.js';
|
|
9
9
|
import { wireHooks } from './hooks/hook-commands.js';
|
|
10
10
|
import { PROJECT_REGISTRY_FILE, parseProjectRegistry, projectCandidates, } from '@reticlehq/core/artifacts';
|
|
11
11
|
import { discoverProjectConfigs } from './command/cli/config/config-discovery.js';
|
|
12
12
|
import { artifactRootResolver } from './memory/project/artifact-root-resolver.js';
|
|
13
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
-
import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
|
|
14
|
+
import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
|
|
15
15
|
import { originOf } from './portal/session/session-manager.js';
|
|
16
16
|
import { setBrowserMode, BrowserMode } from './telemetry/browser-mode.js';
|
|
17
17
|
import { replayAndLearn } from './language/flows/flow-learning.js';
|
|
@@ -25,6 +25,7 @@ import { SERVER_VERSION } from './command/version/identity/server-version.js';
|
|
|
25
25
|
import { BaselineStore } from './memory/project/baselines.js';
|
|
26
26
|
import { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
27
27
|
import { initImpact } from './memory/impact/impact-recorder.js';
|
|
28
|
+
import { flowAuthor } from './language/flows/flow-author.js';
|
|
28
29
|
import { FlowStore } from './language/flows/flows.js';
|
|
29
30
|
import { buildFlowChips } from './language/flows/flow-scope.js';
|
|
30
31
|
import { ProjectStore } from './memory/project/project-store.js';
|
|
@@ -42,7 +43,7 @@ import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
|
|
|
42
43
|
import { runTool } from './surface/tools/invoke-tool.js';
|
|
43
44
|
import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
|
|
44
45
|
import { wireSessionScope } from './portal/session/no-session-watch.js';
|
|
45
|
-
import { buildIdlePredicate } from './command/daemon/lifetime/daemon-usefulness.js';
|
|
46
|
+
import { buildIdlePredicate, noteSetupHold } from './command/daemon/lifetime/daemon-usefulness.js';
|
|
46
47
|
import { resolveToolSurface } from './surface/tools/tool-surface.js';
|
|
47
48
|
import { statusPayload } from './status-payload.js';
|
|
48
49
|
import { CdpRealInputProvider, LaunchedRealInputProvider } from './portal/input/real-input.js';
|
|
@@ -51,19 +52,16 @@ import { BrowserPool } from './portal/pool/browser-pool.js';
|
|
|
51
52
|
import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './portal/session/lease-visibility.js';
|
|
52
53
|
import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
|
|
53
54
|
import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
54
|
-
import { readJournalEnabled, readProjectId } from './command/cli/ports/resolve/cli-port.js';
|
|
55
|
-
import {
|
|
55
|
+
import { findProjectConfig, projectIdsAt, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
56
|
+
import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
|
|
56
57
|
import { reticleStateHome } from './command/daemon/daemon.js';
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import { makeSessionEnd, recordDriveRun } from './memory/journal/session-end.js';
|
|
60
|
-
import { attachDriveRunFlush } from './memory/journal/drive-run-flush.js';
|
|
58
|
+
import { probeLaunchableChromium } from './launch-chromium.js';
|
|
59
|
+
import { attachJournal } from './wire-journal.js';
|
|
61
60
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
62
61
|
import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js';
|
|
63
|
-
import {
|
|
64
|
-
import { ensureWorkspaceGitignore } from './memory/journal/on-disk/workspace-gitignore.js';
|
|
65
|
-
import { pruneFeedback, pruneSessions, pruneVisualDiffs, } from './memory/journal/on-disk/retention.js';
|
|
62
|
+
import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
|
|
66
63
|
import { log } from './log.js';
|
|
64
|
+
import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
|
|
67
65
|
/** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored. */
|
|
68
66
|
function replayVerdictLine(result) {
|
|
69
67
|
if (result.status === ReplayStatus.OK)
|
|
@@ -83,10 +81,19 @@ export { buildSessionRecommendation } from './portal/session/presence/session-re
|
|
|
83
81
|
export { TOOLS } from './surface/tools/tools.js';
|
|
84
82
|
export { createToolInvoker, UNKNOWN_TOOL_ERROR } from './surface/tools/tool-invoker.js';
|
|
85
83
|
export { runTool, SESSION_BOUND_TOOLS, SESSION_EXEMPT_TOOLS } from './surface/tools/invoke-tool.js';
|
|
84
|
+
/**
|
|
85
|
+
* The port-presence check the CLI commands (`drive`, `verify`, `status`, `doctor`, `kill`) all share,
|
|
86
|
+
* re-exported so a consumer embedding `start()` in its own process can refuse a taken port the same
|
|
87
|
+
* honest way instead of letting a raw `EADDRINUSE` stack out — the gap `@reticlehq/test`'s
|
|
88
|
+
* `bootSession()` had (reticlehq/reticle#1141): it called `start()` directly with no pre-flight probe.
|
|
89
|
+
*/
|
|
90
|
+
export { probePresence, describePresence, PortPresence, } from './command/daemon/binding/port-presence.js';
|
|
91
|
+
export { probeDaemon } from './surface/mcp/proxy/proxy-daemon-probe.js';
|
|
92
|
+
export { fetchStatus } from './command/daemon/binding/daemon-status-probe.js';
|
|
86
93
|
export { BaselineStore, normalizeLines, diffLines } from './memory/project/baselines.js';
|
|
87
94
|
export { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
88
95
|
export { FlowStore, recordedStepToFlowStep } from './language/flows/flows.js';
|
|
89
|
-
export { assertSuccess,
|
|
96
|
+
export { assertSuccess, dynamicTestids, successLabel, successToPredicate, } from './language/flows/flow-success.js';
|
|
90
97
|
export { classifyFlowAssertions, FlowAssertionGrade } from './language/flows/flow-classify.js';
|
|
91
98
|
export { buildDomainModel } from './judgement/domain/domain-model.js';
|
|
92
99
|
export { ProjectStore } from './memory/project/project-store.js';
|
|
@@ -108,12 +115,29 @@ export { resolveBridgeSecurity } from './portal/bridge/bridge-security.js';
|
|
|
108
115
|
* Build the shared browser pool (one headless Chromium, N capped isolated leased contexts). Lazy —
|
|
109
116
|
* no Chromium launches until the first lease — so creating it is free even when never used.
|
|
110
117
|
*/
|
|
111
|
-
|
|
118
|
+
/** Where the zero-install reader connects: this daemon's own bridge, with its pairing token. */
|
|
119
|
+
function readerConnect(port, token) {
|
|
120
|
+
return { url: bridgeWsUrl(port), ...(token === undefined ? {} : { token }) };
|
|
121
|
+
}
|
|
122
|
+
function createBrowserPool(headless, reader) {
|
|
112
123
|
const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
|
|
113
124
|
const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
|
|
114
125
|
? globalThis.crypto.randomUUID()
|
|
115
126
|
: String(Date.now())}`;
|
|
116
|
-
|
|
127
|
+
// Built on first use and kept: the page that needs it is one whose app never connected.
|
|
128
|
+
let script;
|
|
129
|
+
const zeroInstallScript = () => {
|
|
130
|
+
if (script === undefined) {
|
|
131
|
+
const bundle = readReaderBundle();
|
|
132
|
+
script = bundle === undefined ? null : zeroInstallScriptFor(bundle, reader);
|
|
133
|
+
}
|
|
134
|
+
return script ?? undefined;
|
|
135
|
+
};
|
|
136
|
+
return new BrowserPool(playwrightLauncher({ headless }), {
|
|
137
|
+
maxContexts,
|
|
138
|
+
genSessionId,
|
|
139
|
+
zeroInstallScript,
|
|
140
|
+
});
|
|
117
141
|
}
|
|
118
142
|
/**
|
|
119
143
|
* Route CDP-authoritative network detail onto the driven session's journal.
|
|
@@ -153,82 +177,6 @@ function makeNetworkDetailRouter(bridge, driveUrl) {
|
|
|
153
177
|
}
|
|
154
178
|
};
|
|
155
179
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Wire journal capture, ambient seeding and the journal-tail flush onto a bridge.
|
|
158
|
-
*
|
|
159
|
-
* Both entry points need all three, and both used to hand-roll them. `startDaemon` only ever wired the
|
|
160
|
-
* first, so on the path every user actually takes (`reticle serve` / `reticle mcp`) the journal tail was
|
|
161
|
-
* dropped at session end and the learned ambient map was never persisted OR seeded — meaning ambient
|
|
162
|
-
* learning could not converge across sessions and the last events of every session were lost. The two
|
|
163
|
-
* call sites had already drifted once before, which is why this is one function rather than a
|
|
164
|
-
* copy-paste both are asked to keep in step.
|
|
165
|
-
*/
|
|
166
|
-
function attachJournal(bridge, deps) {
|
|
167
|
-
const journalAttach = makeJournalAttach(deps);
|
|
168
|
-
const ambientStore = new AmbientStore(deps.fs, deps.reticleRoot);
|
|
169
|
-
// Built once, not per session: the resolver walks config discovery and the user-level registry,
|
|
170
|
-
// and neither changes between two tabs connecting a second apart.
|
|
171
|
-
const resolveArtifactRoot = artifactRootResolver(deps.reticleRoot);
|
|
172
|
-
bridge.attachSessionCreate((session) => {
|
|
173
|
-
// Stamp the project's own `.reticle` before ANY counter fires for this session. Without it every
|
|
174
|
-
// verdict is recorded against wherever the daemon was started, which is how one app's evidence
|
|
175
|
-
// reached a different account's production dashboard.
|
|
176
|
-
// The origin is passed for the case where the page never stamped a project id: it is the only
|
|
177
|
-
// distinguishing fact left, and without it every such app shares one bucket.
|
|
178
|
-
session.artifactRoot = resolveArtifactRoot(session.projectId, originOf(session.url)).root;
|
|
179
|
-
// Here rather than on the start path, which was neither the moment we were about to write into a
|
|
180
|
-
// repository nor the root we were about to write into: it created `.reticle/` — holding nothing
|
|
181
|
-
// but the ignore file — wherever the daemon was launched, coming back every boot after the user
|
|
182
|
-
// deleted it, while the journals this ignore protects landed in another tree, uncovered.
|
|
183
|
-
if (deps.enabled)
|
|
184
|
-
void ensureWorkspaceGitignore(deps.fs, session.artifactRoot);
|
|
185
|
-
journalAttach(session);
|
|
186
|
-
// Seed the learned ambient map so a fresh session starts knowing which regions churn, instead of
|
|
187
|
-
// re-learning from zero. Best-effort + async: a late seed still helps, a failure is silent.
|
|
188
|
-
if (deps.enabled) {
|
|
189
|
-
void ambientStore
|
|
190
|
-
.load()
|
|
191
|
-
.then((counts) => session.seedAmbient(counts))
|
|
192
|
-
.catch(() => undefined);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
// Teardown: flush the journal tail to disk + persist what this session learned.
|
|
196
|
-
bridge.attachSessionEnd(makeSessionEnd({
|
|
197
|
-
...deps,
|
|
198
|
-
// Retention runs from teardown, and it must not delete the journal of a session that is still
|
|
199
|
-
// being written. The registry is the only thing that knows which those are.
|
|
200
|
-
liveSessionIds: () => new Set(bridge.sessions.all().map((s) => s.id)),
|
|
201
|
-
}));
|
|
202
|
-
/*
|
|
203
|
-
* And the same write, DURING the session rather than only at the end of it.
|
|
204
|
-
*
|
|
205
|
-
* Teardown was the only writer of a run artifact, so every verdict produced while a tab stayed
|
|
206
|
-
* open was invisible to cloud sync — which can only push artifacts that exist. Reported as "sync
|
|
207
|
-
* is not happening" and diagnosed as the agent forgetting to sync; there is no sync command to
|
|
208
|
-
* forget. The evidence simply was not on disk yet.
|
|
209
|
-
*
|
|
210
|
-
* Safe to call repeatedly because the run id is derived from the session, so this rewrites one
|
|
211
|
-
* artifact rather than accumulating them — a property `recordDriveRun` already had, for the
|
|
212
|
-
* unrelated reason that a reconnecting tab must not publish two overlapping rows.
|
|
213
|
-
*/
|
|
214
|
-
if (deps.enabled) {
|
|
215
|
-
attachDriveRunFlush({
|
|
216
|
-
resolve: (sessionId) => bridge.sessions.get(sessionId),
|
|
217
|
-
write: (session) => recordDriveRun(deps, session),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
if (deps.enabled) {
|
|
221
|
-
// Empty in the ordinary case (nothing has connected yet), but this path also runs on a daemon
|
|
222
|
-
// that is already serving sessions.
|
|
223
|
-
void pruneSessions(deps.fs, deps.reticleRoot, {
|
|
224
|
-
live: new Set(bridge.sessions.all().map((s) => s.id)),
|
|
225
|
-
});
|
|
226
|
-
// The largest thing in the workspace, and until now the only one with no delete path at all.
|
|
227
|
-
void pruneVisualDiffs(deps.fs, deps.reticleRoot);
|
|
228
|
-
// Write-only local copies of reports the outbox already carries.
|
|
229
|
-
void pruneFeedback(deps.fs, deps.reticleRoot);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
180
|
/**
|
|
233
181
|
* Resolve the drive/real-input provider from options — shared by start and startDaemon so the
|
|
234
182
|
* precedence (driveUrl launch+own → CDP attach → none) and the storageState/injectConnect plumbing
|
|
@@ -325,6 +273,8 @@ function knownProjectRoots() {
|
|
|
325
273
|
}
|
|
326
274
|
return [...roots];
|
|
327
275
|
}
|
|
276
|
+
/** This project's platform credential (stored, else the env key), as the env the platform readers take. */
|
|
277
|
+
const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
|
|
328
278
|
export async function start(options = {}) {
|
|
329
279
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
330
280
|
// Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
|
|
@@ -335,7 +285,7 @@ export async function start(options = {}) {
|
|
|
335
285
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
336
286
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
337
287
|
// to reach for the other.
|
|
338
|
-
config: harnessConfigSource(() => fetchPlatformConfig(
|
|
288
|
+
config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
|
|
339
289
|
});
|
|
340
290
|
const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
|
|
341
291
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
@@ -364,21 +314,26 @@ export async function start(options = {}) {
|
|
|
364
314
|
const fs = createNodeFileSystem();
|
|
365
315
|
const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
366
316
|
const now = options.now ?? (() => Date.now());
|
|
367
|
-
const
|
|
317
|
+
const retain = readRetainPolicy(findProjectConfig(process.cwd()));
|
|
318
|
+
// `retain.sessions: 0` says keep no journals. Honoured at CAPTURE as well as at the sweep —
|
|
319
|
+
// writing a directory in order to delete it on the way out is work nobody asked for.
|
|
320
|
+
const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]) && 0 < retain.sessions;
|
|
368
321
|
const flows = new FlowStore(fs, reticleRoot, { now });
|
|
369
322
|
attachJournal(bridge, {
|
|
370
323
|
fs,
|
|
371
324
|
reticleRoot,
|
|
372
325
|
enabled: journalEnabled,
|
|
326
|
+
retain,
|
|
373
327
|
// Bound HERE, where the recorder is already in hand: teardown gets the tape, not the recorder.
|
|
374
328
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
375
329
|
reportStep: reportOnboardingStep,
|
|
376
330
|
flows,
|
|
331
|
+
author: flowAuthor,
|
|
377
332
|
});
|
|
378
333
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
379
334
|
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
380
335
|
const annotations = new AnnotationStore();
|
|
381
|
-
pool = createBrowserPool(options.headless ?? true);
|
|
336
|
+
pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
|
|
382
337
|
leaseReaper = new LeaseReaper(pool);
|
|
383
338
|
leaseReaper.start();
|
|
384
339
|
/*
|
|
@@ -410,16 +365,23 @@ export async function start(options = {}) {
|
|
|
410
365
|
project,
|
|
411
366
|
fs,
|
|
412
367
|
reticleRoot,
|
|
368
|
+
linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
|
|
413
369
|
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
414
370
|
now,
|
|
415
371
|
bridgePort: port,
|
|
416
|
-
browserProbe:
|
|
372
|
+
browserProbe: probeLaunchableChromium,
|
|
417
373
|
// The daemon's OWN project, so a tool can tell "this session is mine" from "this session
|
|
418
374
|
// belongs to a sibling app under the same daemon". contract_save refuses on the second.
|
|
419
375
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
420
376
|
};
|
|
421
377
|
const profile = resolveToolSurface(options.toolProfile);
|
|
422
|
-
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
378
|
+
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
379
|
+
// A session live RIGHT NOW is stronger evidence than the durable memory, which can be empty or
|
|
380
|
+
// stale for a project the plugin wired without writing `.reticle.json` (see connection-memory.ts's
|
|
381
|
+
// KNOWN LIMIT). Without this OR, that project's `initialize` led with the first-install steps
|
|
382
|
+
// while a real session was already connected — see #1138.
|
|
383
|
+
bridge.sessions.count() > 0 ||
|
|
384
|
+
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())));
|
|
423
385
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
424
386
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
425
387
|
// self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
|
|
@@ -462,7 +424,7 @@ export async function startDaemon(options = {}) {
|
|
|
462
424
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
463
425
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
464
426
|
// to reach for the other.
|
|
465
|
-
config: harnessConfigSource(() => fetchPlatformConfig(
|
|
427
|
+
config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
|
|
466
428
|
});
|
|
467
429
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
468
430
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
@@ -478,7 +440,12 @@ export async function startDaemon(options = {}) {
|
|
|
478
440
|
// answers WHY rather than just "sessionCount: 0".
|
|
479
441
|
// `verifyPort` rides along so a later `serve --http` can tell whether this daemon already honours
|
|
480
442
|
// the requested `--http-port` instead of silently ignoring the flag (#687).
|
|
481
|
-
shared.attachStatus(() =>
|
|
443
|
+
shared.attachStatus((held) => {
|
|
444
|
+
// `init` holds the daemon it is waiting on, so the idle rule cannot shut it down mid-wait.
|
|
445
|
+
if (held)
|
|
446
|
+
noteSetupHold(Date.now());
|
|
447
|
+
return statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port, bridge.sessions.noSessionLead());
|
|
448
|
+
});
|
|
482
449
|
// Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
|
|
483
450
|
// connection drops (it stopped, or is waiting on the human), end every session and push a clear
|
|
484
451
|
// "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
|
|
@@ -497,7 +464,9 @@ export async function startDaemon(options = {}) {
|
|
|
497
464
|
const fs = createNodeFileSystem();
|
|
498
465
|
const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
499
466
|
const now = options.now ?? (() => Date.now());
|
|
500
|
-
const
|
|
467
|
+
const retain = readRetainPolicy(findProjectConfig(process.cwd()));
|
|
468
|
+
// See the sibling path: `retain.sessions: 0` stops the capture too, not only the sweep.
|
|
469
|
+
const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]) && 0 < retain.sessions;
|
|
501
470
|
const flows = new FlowStore(fs, reticleRoot, { now });
|
|
502
471
|
// Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
|
|
503
472
|
// paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
|
|
@@ -514,15 +483,17 @@ export async function startDaemon(options = {}) {
|
|
|
514
483
|
fs,
|
|
515
484
|
reticleRoot,
|
|
516
485
|
enabled: journalEnabled,
|
|
486
|
+
retain,
|
|
517
487
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
518
488
|
reportStep: reportOnboardingStep,
|
|
519
489
|
flows,
|
|
490
|
+
author: flowAuthor,
|
|
520
491
|
onRunPersisted: () => syncNudge.run?.(),
|
|
521
492
|
});
|
|
522
493
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
523
494
|
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
524
495
|
const annotations = new AnnotationStore();
|
|
525
|
-
const pool = createBrowserPool(options.headless ?? true);
|
|
496
|
+
const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
|
|
526
497
|
const leaseReaper = new LeaseReaper(pool);
|
|
527
498
|
leaseReaper.start();
|
|
528
499
|
/*
|
|
@@ -556,7 +527,7 @@ export async function startDaemon(options = {}) {
|
|
|
556
527
|
// console and the panel cannot disagree about a setting they both offer. Nothing is awaited and a
|
|
557
528
|
// failure is not surfaced: the next snapshot re-reads the platform, so a lost write shows up as
|
|
558
529
|
// the switch springing back, which is the truthful outcome.
|
|
559
|
-
bridge.attachHarnessRequest((enabled) => void
|
|
530
|
+
bridge.attachHarnessRequest((enabled) => void platformEnvFor(reticleRoot)().then((env) => writeHarnessSwitch(env, enabled)));
|
|
560
531
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
561
532
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
562
533
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -588,7 +559,7 @@ export async function startDaemon(options = {}) {
|
|
|
588
559
|
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
589
560
|
now,
|
|
590
561
|
bridgePort: port,
|
|
591
|
-
browserProbe:
|
|
562
|
+
browserProbe: probeLaunchableChromium,
|
|
592
563
|
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
593
564
|
onRunPersisted: () => cloudSync.nudge(),
|
|
594
565
|
};
|
|
@@ -596,7 +567,10 @@ export async function startDaemon(options = {}) {
|
|
|
596
567
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
597
568
|
// Read per attach, not once: a project that gets wired while this daemon is alive should stop
|
|
598
569
|
// being told to wire itself on the next agent that connects.
|
|
599
|
-
shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
|
|
570
|
+
shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
|
|
571
|
+
// See the sibling call in `start`: a live session outweighs empty/stale durable memory (#1138).
|
|
572
|
+
bridge.sessions.count() > 0 ||
|
|
573
|
+
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
|
|
600
574
|
// `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
|
|
601
575
|
// bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
|
|
602
576
|
// through runTool, so it is counted and reported like any other call rather than being a second,
|
|
@@ -698,7 +672,7 @@ export async function startDaemon(options = {}) {
|
|
|
698
672
|
if (vh !== undefined)
|
|
699
673
|
await new Promise((resolve) => vh.server.close(() => resolve()));
|
|
700
674
|
leaseReaper.stop();
|
|
701
|
-
cloudSync.stop()
|
|
675
|
+
await cloudSync.flush(); // not stop(): the last run written is the one nobody has yet
|
|
702
676
|
await loopbackAlias.close?.();
|
|
703
677
|
await pool.shutdown();
|
|
704
678
|
await owned?.dispose();
|
|
@@ -29,6 +29,18 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
29
29
|
observed: z.ZodString;
|
|
30
30
|
/** Minimal steps to reproduce (prefix-trimmed). */
|
|
31
31
|
steps: z.ZodArray<z.ZodType<FlowStep, z.ZodTypeDef, FlowStep>, "many">;
|
|
32
|
+
/**
|
|
33
|
+
* How many times this exact failure has been captured, and when it last was.
|
|
34
|
+
*
|
|
35
|
+
* A repeat is not a new capsule -- it is the same one, again. "This failed 37 times, most
|
|
36
|
+
* recently at T" is strictly MORE than 37 files each holding one timestamp, so folding the
|
|
37
|
+
* duplicates in adds information rather than discarding it.
|
|
38
|
+
*
|
|
39
|
+
* Optional, because a capsule written before these existed has neither and must not be read as
|
|
40
|
+
* though it were seen once when nobody counted.
|
|
41
|
+
*/
|
|
42
|
+
seen: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
lastSeenAt: z.ZodOptional<z.ZodNumber>;
|
|
32
44
|
}, "strip", z.ZodTypeAny, {
|
|
33
45
|
expected: string;
|
|
34
46
|
origin: string;
|
|
@@ -38,6 +50,8 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
38
50
|
createdAt: number;
|
|
39
51
|
observed: string;
|
|
40
52
|
flow?: string | undefined;
|
|
53
|
+
seen?: number | undefined;
|
|
54
|
+
lastSeenAt?: number | undefined;
|
|
41
55
|
}, {
|
|
42
56
|
expected: string;
|
|
43
57
|
origin: string;
|
|
@@ -47,6 +61,8 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
47
61
|
createdAt: number;
|
|
48
62
|
observed: string;
|
|
49
63
|
flow?: string | undefined;
|
|
64
|
+
seen?: number | undefined;
|
|
65
|
+
lastSeenAt?: number | undefined;
|
|
50
66
|
}>;
|
|
51
67
|
export type Capsule = z.infer<typeof CapsuleSchema>;
|
|
52
68
|
/** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
|
|
@@ -63,8 +79,21 @@ export declare class CapsuleStore {
|
|
|
63
79
|
/** Every readable capsule, newest-first — what `reticle capsules` prints. */
|
|
64
80
|
all(): Promise<Capsule[]>;
|
|
65
81
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
82
|
+
/**
|
|
83
|
+
* What makes two captures the SAME failure.
|
|
84
|
+
*
|
|
85
|
+
* Everything that describes the failure, and nothing that describes the capture: `createdAt` is
|
|
86
|
+
* excluded because it is precisely what turned every retry of a broken step into a new file, and
|
|
87
|
+
* `id` because it is derived from this. `flow` is included -- the same assertion failing in two
|
|
88
|
+
* different journeys is two findings, not one.
|
|
89
|
+
*/
|
|
90
|
+
export declare function capsuleFingerprint(capsule: Omit<Capsule, 'id' | 'createdAt' | 'seen' | 'lastSeenAt'>): string;
|
|
91
|
+
/**
|
|
92
|
+
* Deterministic, path-safe capsule id: time-ordered so `list` sorts newest-first for free, with the
|
|
93
|
+
* fingerprint on the END so a duplicate can be found by listing the directory rather than reading
|
|
94
|
+
* every file in it.
|
|
95
|
+
*/
|
|
96
|
+
export declare function capsuleId(now: number, label: string, fingerprint: string): string;
|
|
68
97
|
/** The minimal reproduction: the steps up to and including the one that failed (prefix trim). */
|
|
69
98
|
export declare function minimalSteps(steps: readonly FlowStep[], failedIndex: number): FlowStep[];
|
|
70
99
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import { fnv1a } from '@reticlehq/core';
|
|
3
|
+
import { writeFileAtomic } from '../../memory/project/fs/write-atomic.js';
|
|
2
4
|
import { z } from 'zod';
|
|
3
5
|
import { FlowStepSchema } from '@reticlehq/core';
|
|
4
6
|
import { reticleDirPaths } from '../../memory/project/dir/reticle-dir.js';
|
|
@@ -30,6 +32,18 @@ const CapsuleSchema = z.object({
|
|
|
30
32
|
observed: z.string(),
|
|
31
33
|
/** Minimal steps to reproduce (prefix-trimmed). */
|
|
32
34
|
steps: z.array(FlowStepSchema),
|
|
35
|
+
/**
|
|
36
|
+
* How many times this exact failure has been captured, and when it last was.
|
|
37
|
+
*
|
|
38
|
+
* A repeat is not a new capsule -- it is the same one, again. "This failed 37 times, most
|
|
39
|
+
* recently at T" is strictly MORE than 37 files each holding one timestamp, so folding the
|
|
40
|
+
* duplicates in adds information rather than discarding it.
|
|
41
|
+
*
|
|
42
|
+
* Optional, because a capsule written before these existed has neither and must not be read as
|
|
43
|
+
* though it were seen once when nobody counted.
|
|
44
|
+
*/
|
|
45
|
+
seen: z.number().int().positive().optional(),
|
|
46
|
+
lastSeenAt: z.number().optional(),
|
|
33
47
|
});
|
|
34
48
|
/** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
|
|
35
49
|
export function isValidCapsuleId(id) {
|
|
@@ -51,9 +65,26 @@ export class CapsuleStore {
|
|
|
51
65
|
return false;
|
|
52
66
|
try {
|
|
53
67
|
await this.#fs.mkdir(this.#dir);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
/*
|
|
69
|
+
* A capture of a failure already on disk folds into it rather than adding a file. Found by
|
|
70
|
+
* LISTING: the fingerprint is the id's suffix, so this costs one directory read instead of
|
|
71
|
+
* one read per capsule. An id written before fingerprints existed simply never matches, which
|
|
72
|
+
* is the correct outcome -- it is left exactly as it was.
|
|
73
|
+
*
|
|
74
|
+
* `createdAt` keeps the FIRST sighting. A repeat is the same failure happening again, not a
|
|
75
|
+
* new one, and the interesting pair is when it started and when it last happened.
|
|
76
|
+
*/
|
|
77
|
+
const fingerprint = capsuleFingerprint(capsule);
|
|
78
|
+
const existingId = (await this.list()).find((id) => id.endsWith(`-${fingerprint}`));
|
|
79
|
+
const previous = existingId === undefined ? undefined : await this.read(existingId);
|
|
80
|
+
const folded = previous === undefined
|
|
81
|
+
? { ...capsule, seen: 1, lastSeenAt: capsule.createdAt }
|
|
82
|
+
: {
|
|
83
|
+
...previous,
|
|
84
|
+
seen: (previous.seen ?? 1) + 1,
|
|
85
|
+
lastSeenAt: capsule.createdAt,
|
|
86
|
+
};
|
|
87
|
+
await writeFileAtomic(this.#fs, this.#pathFor(folded.id), `${JSON.stringify(folded, null, 2)}\n`);
|
|
57
88
|
return true;
|
|
58
89
|
}
|
|
59
90
|
catch {
|
|
@@ -100,10 +131,31 @@ export class CapsuleStore {
|
|
|
100
131
|
return out;
|
|
101
132
|
}
|
|
102
133
|
}
|
|
103
|
-
/**
|
|
104
|
-
|
|
134
|
+
/**
|
|
135
|
+
* What makes two captures the SAME failure.
|
|
136
|
+
*
|
|
137
|
+
* Everything that describes the failure, and nothing that describes the capture: `createdAt` is
|
|
138
|
+
* excluded because it is precisely what turned every retry of a broken step into a new file, and
|
|
139
|
+
* `id` because it is derived from this. `flow` is included -- the same assertion failing in two
|
|
140
|
+
* different journeys is two findings, not one.
|
|
141
|
+
*/
|
|
142
|
+
export function capsuleFingerprint(capsule) {
|
|
143
|
+
return fnv1a(JSON.stringify([
|
|
144
|
+
capsule.flow ?? '',
|
|
145
|
+
capsule.origin,
|
|
146
|
+
capsule.expected,
|
|
147
|
+
capsule.observed,
|
|
148
|
+
capsule.steps,
|
|
149
|
+
]));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Deterministic, path-safe capsule id: time-ordered so `list` sorts newest-first for free, with the
|
|
153
|
+
* fingerprint on the END so a duplicate can be found by listing the directory rather than reading
|
|
154
|
+
* every file in it.
|
|
155
|
+
*/
|
|
156
|
+
export function capsuleId(now, label, fingerprint) {
|
|
105
157
|
const safe = label.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 40);
|
|
106
|
-
return `${String(now)}-${safe.length > 0 ? safe : 'capsule'}`;
|
|
158
|
+
return `${String(now)}-${safe.length > 0 ? safe : 'capsule'}-${fingerprint}`;
|
|
107
159
|
}
|
|
108
160
|
/** The minimal reproduction: the steps up to and including the one that failed (prefix trim). */
|
|
109
161
|
export function minimalSteps(steps, failedIndex) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConsequenceKind, EventType } from '@reticlehq/core';
|
|
2
|
+
import { isDocumentInitiated } from '@reticlehq/engine/question/predicate/predicate-eval.js';
|
|
2
3
|
import { causalSummary } from './causal-summary.js';
|
|
3
4
|
import { firstDivergence } from './divergence.js';
|
|
4
5
|
/**
|
|
@@ -34,6 +35,10 @@ export function blastRadius(expected, observed) {
|
|
|
34
35
|
continue;
|
|
35
36
|
if (declaredUrls.some((fragment) => url.includes(fragment)))
|
|
36
37
|
continue;
|
|
38
|
+
// A subresource the document fetched is the page loading, not a request the action sent: one
|
|
39
|
+
// navigation is a couple of hundred of them. IPC and beacons carry an initiator too, and stay.
|
|
40
|
+
if (isDocumentInitiated(event))
|
|
41
|
+
continue;
|
|
37
42
|
const method = event.data['method'];
|
|
38
43
|
add(`net ${'string' === typeof method ? method : 'request'} ${url}`);
|
|
39
44
|
continue;
|
|
@@ -63,6 +63,18 @@ export interface CausalSummary {
|
|
|
63
63
|
elided?: Partial<Record<CappedList, number>>;
|
|
64
64
|
route?: string;
|
|
65
65
|
signals: string[];
|
|
66
|
+
/**
|
|
67
|
+
* Form fields whose value moved in this window, by NAME.
|
|
68
|
+
*
|
|
69
|
+
* Names only, never values: a form is where somebody types their password, and the observer that
|
|
70
|
+
* reports this already refuses to carry one. The names are what an assertion is written against,
|
|
71
|
+
* and a field moving when nothing should have touched it (or not moving when something should)
|
|
72
|
+
* is the silently-cleared-field class — which until this channel existed left no trace at all,
|
|
73
|
+
* because React sets the input PROPERTY and MutationObserver watches attributes.
|
|
74
|
+
*
|
|
75
|
+
* Present only when a field moved, so a page with no form pays nothing.
|
|
76
|
+
*/
|
|
77
|
+
fieldsChanged?: string[];
|
|
66
78
|
layoutShift?: number;
|
|
67
79
|
longTasks: number;
|
|
68
80
|
/**
|
|
@@ -114,6 +126,6 @@ interface SummaryContext {
|
|
|
114
126
|
*/
|
|
115
127
|
export declare const MAX_SUMMARY_ENTRIES = 20;
|
|
116
128
|
/** The lists that are capped, named so `elided` can say which one lost entries. */
|
|
117
|
-
type CappedList = 'statePathsChanged' | 'storageKeysChanged' | 'stateDiffs' | 'storageDiffs' | 'signals';
|
|
129
|
+
type CappedList = 'statePathsChanged' | 'storageKeysChanged' | 'stateDiffs' | 'storageDiffs' | 'signals' | 'fieldsChanged';
|
|
118
130
|
export declare function causalSummary(events: readonly ReticleEvent[], context?: SummaryContext): CausalSummary;
|
|
119
131
|
export {};
|
|
@@ -69,6 +69,7 @@ export function causalSummary(events, context = {}) {
|
|
|
69
69
|
const storageDiffs = [];
|
|
70
70
|
let route;
|
|
71
71
|
const signals = [];
|
|
72
|
+
const fieldsChanged = [];
|
|
72
73
|
let layoutShift;
|
|
73
74
|
let longTasks = 0;
|
|
74
75
|
for (const event of events) {
|
|
@@ -131,6 +132,9 @@ export function causalSummary(events, context = {}) {
|
|
|
131
132
|
case EventType.SIGNAL:
|
|
132
133
|
pushUnique(signals, data['name']);
|
|
133
134
|
break;
|
|
135
|
+
case EventType.FIELD_CHANGE:
|
|
136
|
+
pushUnique(fieldsChanged, data['field']);
|
|
137
|
+
break;
|
|
134
138
|
case EventType.PERF:
|
|
135
139
|
if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
|
|
136
140
|
layoutShift = Math.max(layoutShift ?? 0, data['value']);
|
|
@@ -163,6 +167,9 @@ export function causalSummary(events, context = {}) {
|
|
|
163
167
|
storageDiffs: cap(storageDiffs, 'storageDiffs', elided),
|
|
164
168
|
...(route === undefined ? {} : { route }),
|
|
165
169
|
signals: cap(signals, 'signals', elided),
|
|
170
|
+
...(0 === fieldsChanged.length
|
|
171
|
+
? {}
|
|
172
|
+
: { fieldsChanged: cap(fieldsChanged, 'fieldsChanged', elided) }),
|
|
166
173
|
...(0 === Object.keys(elided).length ? {} : { elided }),
|
|
167
174
|
...(layoutShift === undefined ? {} : { layoutShift }),
|
|
168
175
|
longTasks,
|