@reticlehq/server 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +30 -28
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +37 -5
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- 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 +8 -1
- package/dist/command/cli/cli-parse.js +38 -7
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +131 -19
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -7
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +3 -23
- package/dist/command/cli/launch/cli-launch.js +29 -70
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
- package/dist/command/cli/ports/resolve/cli-port.js +50 -2
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +29 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +3 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +661 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +62 -0
- package/dist/features/harness/platform-config.js +97 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +80 -130
- 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 +38 -9
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- 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/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 +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- 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/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -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-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +55 -10
- package/dist/language/flows/flow-replay-run.js +217 -58
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +29 -11
- package/dist/language/flows/flow-replay.js +221 -203
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- 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-store-for-session.d.ts +2 -1
- 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 +8 -4
- package/dist/language/flows/flows.d.ts +15 -10
- package/dist/language/flows/flows.js +17 -4
- 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/narrow-write.d.ts +4 -4
- 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/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +13 -9
- 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/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +21 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -4
- package/dist/memory/cloud/cloud-sync.js +11 -6
- package/dist/memory/cloud/harness-config.d.ts +12 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +35 -2
- package/dist/memory/cloud/sync-daemon.js +45 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +25 -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 +253 -141
- package/dist/memory/intent/intent-shard.d.ts +22 -23
- package/dist/memory/intent/intent-shard.js +1 -1
- 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/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- 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 +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +71 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- 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 +91 -2
- package/dist/memory/journal/on-disk/retention.js +181 -11
- 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 -2
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +17 -4
- package/dist/memory/journal/session-end.js +85 -7
- package/dist/memory/journal/session-journal.d.ts +122 -5
- package/dist/memory/journal/session-journal.js +489 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +72 -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/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- 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 +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +20 -3
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/input/real-input.js +2 -1
- 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.js +1 -0
- 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-server-probe.d.ts +2 -1
- 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/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +13 -0
- package/dist/portal/session/session-manager.js +42 -3
- 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 +26 -10
- package/dist/portal/session/session.js +48 -49
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +23 -12
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/act-preflight.js +6 -1
- 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/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- 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.js +49 -8
- package/dist/surface/tools/act-tools.js +25 -24
- 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 +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +74 -21
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +43 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +50 -3
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +82 -9
- 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 +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +69 -15
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- 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 +23 -3
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +70 -30
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -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 +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/telemetry/verification-of.js +31 -3
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +11 -9
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +144 -4
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/tools/query.mdx +10 -10
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +2 -2
- package/docs/what-is-recorded.md +62 -0
- package/package.json +5 -5
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
package/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { writeHarnessSwitch } from './memory/cloud/harness-switch.js';
|
|
2
|
+
import { harnessConfigSource } from './memory/cloud/harness-config.js';
|
|
3
|
+
import { fetchPlatformConfig } from './features/harness/platform-config.js';
|
|
1
4
|
import { join } from 'node:path';
|
|
2
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
6
|
import { homedir } from 'node:os';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
7
|
+
import { linkedCloudPort } from './memory/cloud/cloud-config.js';
|
|
8
|
+
import { attachCloudSync } from './memory/cloud/sync-daemon.js';
|
|
6
9
|
import { wireHooks } from './hooks/hook-commands.js';
|
|
7
|
-
import { PROJECT_REGISTRY_FILE,
|
|
8
|
-
import { discoverProjectConfigs
|
|
9
|
-
import {
|
|
10
|
+
import { PROJECT_REGISTRY_FILE, parseProjectRegistry, projectCandidates, } from '@reticlehq/core/artifacts';
|
|
11
|
+
import { discoverProjectConfigs } from './command/cli/config/config-discovery.js';
|
|
12
|
+
import { artifactRootResolver } from './memory/project/artifact-root-resolver.js';
|
|
10
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
|
-
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';
|
|
12
15
|
import { originOf } from './portal/session/session-manager.js';
|
|
13
16
|
import { setBrowserMode, BrowserMode } from './telemetry/browser-mode.js';
|
|
14
17
|
import { replayAndLearn } from './language/flows/flow-learning.js';
|
|
@@ -25,6 +28,7 @@ import { initImpact } from './memory/impact/impact-recorder.js';
|
|
|
25
28
|
import { FlowStore } from './language/flows/flows.js';
|
|
26
29
|
import { buildFlowChips } from './language/flows/flow-scope.js';
|
|
27
30
|
import { ProjectStore } from './memory/project/project-store.js';
|
|
31
|
+
import { projectStoreResolver } from './memory/project/project-for-root.js';
|
|
28
32
|
import { attachRouteLearning } from './memory/project/learned-routes.js';
|
|
29
33
|
import { AnnotationStore } from './language/flows/stores/annotation-store.js';
|
|
30
34
|
import { createNodeFileSystem } from './memory/project/fs/fs-port.js';
|
|
@@ -47,19 +51,16 @@ import { BrowserPool } from './portal/pool/browser-pool.js';
|
|
|
47
51
|
import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './portal/session/lease-visibility.js';
|
|
48
52
|
import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
|
|
49
53
|
import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
50
|
-
import { readJournalEnabled, readProjectId } from './command/cli/ports/resolve/cli-port.js';
|
|
51
|
-
import {
|
|
54
|
+
import { findProjectConfig, projectIdsAt, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
55
|
+
import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
|
|
52
56
|
import { reticleStateHome } from './command/daemon/daemon.js';
|
|
53
57
|
import { probeChromium } from './command/cli/doctor/browser/chromium-hint.js';
|
|
54
|
-
import {
|
|
55
|
-
import { makeSessionEnd, recordDriveRun } from './memory/journal/session-end.js';
|
|
56
|
-
import { attachDriveRunFlush } from './memory/journal/drive-run-flush.js';
|
|
58
|
+
import { attachJournal } from './wire-journal.js';
|
|
57
59
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
58
60
|
import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js';
|
|
59
|
-
import {
|
|
60
|
-
import { ensureWorkspaceGitignore } from './memory/journal/on-disk/workspace-gitignore.js';
|
|
61
|
-
import { pruneFeedback, pruneSessions, pruneVisualDiffs, } from './memory/journal/on-disk/retention.js';
|
|
61
|
+
import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
|
|
62
62
|
import { log } from './log.js';
|
|
63
|
+
import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
|
|
63
64
|
/** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored. */
|
|
64
65
|
function replayVerdictLine(result) {
|
|
65
66
|
if (result.status === ReplayStatus.OK)
|
|
@@ -82,7 +83,7 @@ export { runTool, SESSION_BOUND_TOOLS, SESSION_EXEMPT_TOOLS } from './surface/to
|
|
|
82
83
|
export { BaselineStore, normalizeLines, diffLines } from './memory/project/baselines.js';
|
|
83
84
|
export { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
84
85
|
export { FlowStore, recordedStepToFlowStep } from './language/flows/flows.js';
|
|
85
|
-
export { assertSuccess,
|
|
86
|
+
export { assertSuccess, dynamicTestids, successLabel, successToPredicate, } from './language/flows/flow-success.js';
|
|
86
87
|
export { classifyFlowAssertions, FlowAssertionGrade } from './language/flows/flow-classify.js';
|
|
87
88
|
export { buildDomainModel } from './judgement/domain/domain-model.js';
|
|
88
89
|
export { ProjectStore } from './memory/project/project-store.js';
|
|
@@ -104,12 +105,29 @@ export { resolveBridgeSecurity } from './portal/bridge/bridge-security.js';
|
|
|
104
105
|
* Build the shared browser pool (one headless Chromium, N capped isolated leased contexts). Lazy —
|
|
105
106
|
* no Chromium launches until the first lease — so creating it is free even when never used.
|
|
106
107
|
*/
|
|
107
|
-
|
|
108
|
+
/** Where the zero-install reader connects: this daemon's own bridge, with its pairing token. */
|
|
109
|
+
function readerConnect(port, token) {
|
|
110
|
+
return { url: bridgeWsUrl(port), ...(token === undefined ? {} : { token }) };
|
|
111
|
+
}
|
|
112
|
+
function createBrowserPool(headless, reader) {
|
|
108
113
|
const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
|
|
109
114
|
const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
|
|
110
115
|
? globalThis.crypto.randomUUID()
|
|
111
116
|
: String(Date.now())}`;
|
|
112
|
-
|
|
117
|
+
// Built on first use and kept: the page that needs it is one whose app never connected.
|
|
118
|
+
let script;
|
|
119
|
+
const zeroInstallScript = () => {
|
|
120
|
+
if (script === undefined) {
|
|
121
|
+
const bundle = readReaderBundle();
|
|
122
|
+
script = bundle === undefined ? null : zeroInstallScriptFor(bundle, reader);
|
|
123
|
+
}
|
|
124
|
+
return script ?? undefined;
|
|
125
|
+
};
|
|
126
|
+
return new BrowserPool(playwrightLauncher({ headless }), {
|
|
127
|
+
maxContexts,
|
|
128
|
+
genSessionId,
|
|
129
|
+
zeroInstallScript,
|
|
130
|
+
});
|
|
113
131
|
}
|
|
114
132
|
/**
|
|
115
133
|
* Route CDP-authoritative network detail onto the driven session's journal.
|
|
@@ -149,70 +167,6 @@ function makeNetworkDetailRouter(bridge, driveUrl) {
|
|
|
149
167
|
}
|
|
150
168
|
};
|
|
151
169
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Wire journal capture, ambient seeding and the journal-tail flush onto a bridge.
|
|
154
|
-
*
|
|
155
|
-
* Both entry points need all three, and both used to hand-roll them. `startDaemon` only ever wired the
|
|
156
|
-
* first, so on the path every user actually takes (`reticle serve` / `reticle mcp`) the journal tail was
|
|
157
|
-
* dropped at session end and the learned ambient map was never persisted OR seeded — meaning ambient
|
|
158
|
-
* learning could not converge across sessions and the last events of every session were lost. The two
|
|
159
|
-
* call sites had already drifted once before, which is why this is one function rather than a
|
|
160
|
-
* copy-paste both are asked to keep in step.
|
|
161
|
-
*/
|
|
162
|
-
function attachJournal(bridge, deps) {
|
|
163
|
-
const journalAttach = makeJournalAttach(deps);
|
|
164
|
-
const ambientStore = new AmbientStore(deps.fs, deps.reticleRoot);
|
|
165
|
-
// Built once, not per session: the resolver walks config discovery and the user-level registry,
|
|
166
|
-
// and neither changes between two tabs connecting a second apart.
|
|
167
|
-
const resolveArtifactRoot = artifactRootResolver(deps.reticleRoot);
|
|
168
|
-
bridge.attachSessionCreate((session) => {
|
|
169
|
-
// Stamp the project's own `.reticle` before ANY counter fires for this session. Without it every
|
|
170
|
-
// verdict is recorded against wherever the daemon was started, which is how one app's evidence
|
|
171
|
-
// reached a different account's production dashboard.
|
|
172
|
-
session.artifactRoot = resolveArtifactRoot(session.projectId).root;
|
|
173
|
-
journalAttach(session);
|
|
174
|
-
// Seed the learned ambient map so a fresh session starts knowing which regions churn, instead of
|
|
175
|
-
// re-learning from zero. Best-effort + async: a late seed still helps, a failure is silent.
|
|
176
|
-
if (deps.enabled) {
|
|
177
|
-
void ambientStore
|
|
178
|
-
.load()
|
|
179
|
-
.then((counts) => session.seedAmbient(counts))
|
|
180
|
-
.catch(() => undefined);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
// Teardown: flush the journal tail to disk + persist what this session learned.
|
|
184
|
-
bridge.attachSessionEnd(makeSessionEnd(deps));
|
|
185
|
-
/*
|
|
186
|
-
* And the same write, DURING the session rather than only at the end of it.
|
|
187
|
-
*
|
|
188
|
-
* Teardown was the only writer of a run artifact, so every verdict produced while a tab stayed
|
|
189
|
-
* open was invisible to cloud sync — which can only push artifacts that exist. Reported as "sync
|
|
190
|
-
* is not happening" and diagnosed as the agent forgetting to sync; there is no sync command to
|
|
191
|
-
* forget. The evidence simply was not on disk yet.
|
|
192
|
-
*
|
|
193
|
-
* Safe to call repeatedly because the run id is derived from the session, so this rewrites one
|
|
194
|
-
* artifact rather than accumulating them — a property `recordDriveRun` already had, for the
|
|
195
|
-
* unrelated reason that a reconnecting tab must not publish two overlapping rows.
|
|
196
|
-
*/
|
|
197
|
-
if (deps.enabled) {
|
|
198
|
-
attachDriveRunFlush({
|
|
199
|
-
resolve: (sessionId) => bridge.sessions.get(sessionId),
|
|
200
|
-
write: (session) => recordDriveRun(deps, session),
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
if (deps.enabled) {
|
|
204
|
-
void pruneSessions(deps.fs, deps.reticleRoot);
|
|
205
|
-
// The largest thing in the workspace, and until now the only one with no delete path at all.
|
|
206
|
-
void pruneVisualDiffs(deps.fs, deps.reticleRoot);
|
|
207
|
-
// Write-only local copies of reports the outbox already carries.
|
|
208
|
-
void pruneFeedback(deps.fs, deps.reticleRoot);
|
|
209
|
-
// Here rather than in `init`, because this is the moment we are actually about to write into
|
|
210
|
-
// somebody's repository — and the paths that reach it without ever running `init` (a plugin
|
|
211
|
-
// install, a hand-added client config) are exactly the ones that would otherwise leave an
|
|
212
|
-
// unexplained pile of untracked files behind. Best-effort and write-once; see the helper.
|
|
213
|
-
void ensureWorkspaceGitignore(deps.fs, deps.reticleRoot);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
170
|
/**
|
|
217
171
|
* Resolve the drive/real-input provider from options — shared by start and startDaemon so the
|
|
218
172
|
* precedence (driveUrl launch+own → CDP attach → none) and the storageState/injectConnect plumbing
|
|
@@ -309,39 +263,18 @@ function knownProjectRoots() {
|
|
|
309
263
|
}
|
|
310
264
|
return [...roots];
|
|
311
265
|
}
|
|
312
|
-
function artifactRootResolver(daemonRoot) {
|
|
313
|
-
return (projectId) => {
|
|
314
|
-
let registry = emptyProjectRegistry();
|
|
315
|
-
try {
|
|
316
|
-
const path = join(homedir(), ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
|
|
317
|
-
registry = existsSync(path)
|
|
318
|
-
? parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')))
|
|
319
|
-
: registry;
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
// A cache that cannot be read is an empty cache, never an error: the daemon still resolves
|
|
323
|
-
// through discovery, and falls back to its own root exactly as it did before this existed.
|
|
324
|
-
}
|
|
325
|
-
let discovery = { found: [], searched: [] };
|
|
326
|
-
try {
|
|
327
|
-
discovery = discoverProjectConfigs(process.cwd());
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
// Same reasoning: a diagnostic search that throws must not take a tool call with it.
|
|
331
|
-
}
|
|
332
|
-
return resolveArtifactRoot({
|
|
333
|
-
projectId,
|
|
334
|
-
candidates: projectCandidatesFrom(discovery, registry),
|
|
335
|
-
daemonRoot,
|
|
336
|
-
});
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
266
|
export async function start(options = {}) {
|
|
340
267
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
341
268
|
// Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
|
|
342
269
|
// serving a browser with no agent attached still has a HUD to answer, and a report that reads
|
|
343
270
|
// "nothing recorded yet" over a month of history on disk is the worst version of this feature.
|
|
344
|
-
initImpact({
|
|
271
|
+
initImpact({
|
|
272
|
+
reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
|
|
273
|
+
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
274
|
+
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
275
|
+
// to reach for the other.
|
|
276
|
+
config: harnessConfigSource(() => fetchPlatformConfig(process.env)),
|
|
277
|
+
});
|
|
345
278
|
const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
|
|
346
279
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
347
280
|
const bridge = new Bridge({ port, sdkFix: sdkFixForCwd, ...security });
|
|
@@ -369,21 +302,25 @@ export async function start(options = {}) {
|
|
|
369
302
|
const fs = createNodeFileSystem();
|
|
370
303
|
const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
371
304
|
const now = options.now ?? (() => Date.now());
|
|
372
|
-
const
|
|
305
|
+
const retain = readRetainPolicy(findProjectConfig(process.cwd()));
|
|
306
|
+
// `retain.sessions: 0` says keep no journals. Honoured at CAPTURE as well as at the sweep —
|
|
307
|
+
// writing a directory in order to delete it on the way out is work nobody asked for.
|
|
308
|
+
const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]) && 0 < retain.sessions;
|
|
373
309
|
const flows = new FlowStore(fs, reticleRoot, { now });
|
|
374
310
|
attachJournal(bridge, {
|
|
375
311
|
fs,
|
|
376
312
|
reticleRoot,
|
|
377
313
|
enabled: journalEnabled,
|
|
314
|
+
retain,
|
|
378
315
|
// Bound HERE, where the recorder is already in hand: teardown gets the tape, not the recorder.
|
|
379
316
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
380
317
|
reportStep: reportOnboardingStep,
|
|
381
318
|
flows,
|
|
382
319
|
});
|
|
383
320
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
384
|
-
attachRouteLearning(bridge, project);
|
|
321
|
+
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
385
322
|
const annotations = new AnnotationStore();
|
|
386
|
-
pool = createBrowserPool(options.headless ?? true);
|
|
323
|
+
pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
|
|
387
324
|
leaseReaper = new LeaseReaper(pool);
|
|
388
325
|
leaseReaper.start();
|
|
389
326
|
/*
|
|
@@ -424,7 +361,7 @@ export async function start(options = {}) {
|
|
|
424
361
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
425
362
|
};
|
|
426
363
|
const profile = resolveToolSurface(options.toolProfile);
|
|
427
|
-
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
364
|
+
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile, hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())));
|
|
428
365
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
429
366
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
430
367
|
// self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
|
|
@@ -462,7 +399,13 @@ export async function startDaemon(options = {}) {
|
|
|
462
399
|
// was never opened in the process the HUD talks to: tool calls still recorded (the dispatch
|
|
463
400
|
// chokepoint opens it lazily), but a tab that connected before the first tool call was pushed
|
|
464
401
|
// nothing, so the report read "nothing recorded yet" over a file with history in it.
|
|
465
|
-
initImpact({
|
|
402
|
+
initImpact({
|
|
403
|
+
reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
|
|
404
|
+
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
405
|
+
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
406
|
+
// to reach for the other.
|
|
407
|
+
config: harnessConfigSource(() => fetchPlatformConfig(process.env)),
|
|
408
|
+
});
|
|
466
409
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
467
410
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
468
411
|
const bridge = new Bridge({ port, server: shared.httpServer, sdkFix: sdkFixForCwd, ...security });
|
|
@@ -477,7 +420,7 @@ export async function startDaemon(options = {}) {
|
|
|
477
420
|
// answers WHY rather than just "sessionCount: 0".
|
|
478
421
|
// `verifyPort` rides along so a later `serve --http` can tell whether this daemon already honours
|
|
479
422
|
// the requested `--http-port` instead of silently ignoring the flag (#687).
|
|
480
|
-
shared.attachStatus(() => statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port));
|
|
423
|
+
shared.attachStatus(() => statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port, bridge.sessions.noSessionLead()));
|
|
481
424
|
// Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
|
|
482
425
|
// connection drops (it stopped, or is waiting on the human), end every session and push a clear
|
|
483
426
|
// "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
|
|
@@ -496,7 +439,9 @@ export async function startDaemon(options = {}) {
|
|
|
496
439
|
const fs = createNodeFileSystem();
|
|
497
440
|
const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
498
441
|
const now = options.now ?? (() => Date.now());
|
|
499
|
-
const
|
|
442
|
+
const retain = readRetainPolicy(findProjectConfig(process.cwd()));
|
|
443
|
+
// See the sibling path: `retain.sessions: 0` stops the capture too, not only the sweep.
|
|
444
|
+
const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]) && 0 < retain.sessions;
|
|
500
445
|
const flows = new FlowStore(fs, reticleRoot, { now });
|
|
501
446
|
// Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
|
|
502
447
|
// paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
|
|
@@ -504,7 +449,7 @@ export async function startDaemon(options = {}) {
|
|
|
504
449
|
/*
|
|
505
450
|
* Bound AFTER the sync daemon exists, read only when a run is actually written.
|
|
506
451
|
*
|
|
507
|
-
* `attachJournal` runs before `
|
|
452
|
+
* `attachJournal` runs before `attachCloudSync` because the journal has to be capturing before
|
|
508
453
|
* anything can connect, and reordering them so this could be a direct reference would put sync
|
|
509
454
|
* setup ahead of session capture for the sake of one callback.
|
|
510
455
|
*/
|
|
@@ -513,15 +458,16 @@ export async function startDaemon(options = {}) {
|
|
|
513
458
|
fs,
|
|
514
459
|
reticleRoot,
|
|
515
460
|
enabled: journalEnabled,
|
|
461
|
+
retain,
|
|
516
462
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
517
463
|
reportStep: reportOnboardingStep,
|
|
518
464
|
flows,
|
|
519
465
|
onRunPersisted: () => syncNudge.run?.(),
|
|
520
466
|
});
|
|
521
467
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
522
|
-
attachRouteLearning(bridge, project);
|
|
468
|
+
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
523
469
|
const annotations = new AnnotationStore();
|
|
524
|
-
const pool = createBrowserPool(options.headless ?? true);
|
|
470
|
+
const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
|
|
525
471
|
const leaseReaper = new LeaseReaper(pool);
|
|
526
472
|
leaseReaper.start();
|
|
527
473
|
/*
|
|
@@ -540,19 +486,22 @@ export async function startDaemon(options = {}) {
|
|
|
540
486
|
* events a user most wants: the ones at the start of a run.
|
|
541
487
|
*/
|
|
542
488
|
wireHooks(reticleRoot, readProjectId(process.cwd()));
|
|
543
|
-
const cloudSync =
|
|
489
|
+
const cloudSync = attachCloudSync({
|
|
490
|
+
fs,
|
|
544
491
|
reticleRoot,
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// reporting nothing — indistinguishable, on the dashboard, from nobody having verified anything.
|
|
549
|
-
otherRoots: () => Promise.resolve(knownProjectRoots()),
|
|
550
|
-
cloudFor: (root) => resolveProjectCloud(fs, root, homedir(), process.env),
|
|
492
|
+
homeDir: homedir(),
|
|
493
|
+
env: process.env,
|
|
494
|
+
otherRoots: knownProjectRoots,
|
|
551
495
|
});
|
|
552
496
|
syncNudge.run = () => cloudSync.nudge();
|
|
553
|
-
//
|
|
554
|
-
//
|
|
497
|
+
// `syncNow`, not `nudge`: a nudge schedules a cycle soon, which is right for "a run landed" and
|
|
498
|
+
// wrong for a button somebody is watching. Never awaited.
|
|
555
499
|
bridge.attachSyncRequest(() => void cloudSync.syncNow());
|
|
500
|
+
// The panel's harness switch. Written through to the platform rather than kept locally, so the
|
|
501
|
+
// console and the panel cannot disagree about a setting they both offer. Nothing is awaited and a
|
|
502
|
+
// failure is not surfaced: the next snapshot re-reads the platform, so a lost write shows up as
|
|
503
|
+
// the switch springing back, which is the truthful outcome.
|
|
504
|
+
bridge.attachHarnessRequest((enabled) => void writeHarnessSwitch(process.env, enabled));
|
|
556
505
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
557
506
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
558
507
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -574,6 +523,7 @@ export async function startDaemon(options = {}) {
|
|
|
574
523
|
project,
|
|
575
524
|
fs,
|
|
576
525
|
reticleRoot,
|
|
526
|
+
linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
|
|
577
527
|
// The long-lived daemon needs this MORE than the standalone MCP process does, not less: it is
|
|
578
528
|
// the one that outlives a single project and serves every app on the machine. Omitting it here
|
|
579
529
|
// silently disabled per-session artifact resolution for every agent that attaches to a running
|
|
@@ -591,7 +541,7 @@ export async function startDaemon(options = {}) {
|
|
|
591
541
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
592
542
|
// Read per attach, not once: a project that gets wired while this daemon is alive should stop
|
|
593
543
|
// being told to wire itself on the next agent that connects.
|
|
594
|
-
shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
|
|
544
|
+
shared.attachMcp(() => createMcpServer(effectiveDeps, profile, hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
|
|
595
545
|
// `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
|
|
596
546
|
// bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
|
|
597
547
|
// through runTool, so it is counted and reported like any other call rather than being a second,
|
|
@@ -693,7 +643,7 @@ export async function startDaemon(options = {}) {
|
|
|
693
643
|
if (vh !== undefined)
|
|
694
644
|
await new Promise((resolve) => vh.server.close(() => resolve()));
|
|
695
645
|
leaseReaper.stop();
|
|
696
|
-
cloudSync.stop()
|
|
646
|
+
await cloudSync.flush(); // not stop(): the last run written is the one nobody has yet
|
|
697
647
|
await loopbackAlias.close?.();
|
|
698
648
|
await pool.shutdown();
|
|
699
649
|
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,
|