@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
|
@@ -7,8 +7,19 @@
|
|
|
7
7
|
* nothing grades) but the flows that now exist on disk, because every future run replays those
|
|
8
8
|
* deterministically with no model in the loop at all.
|
|
9
9
|
*/
|
|
10
|
-
import { ReticleEnv } from '@reticlehq/core';
|
|
11
|
-
import {
|
|
10
|
+
import { ReticleEnv, ReticleTool, apiKeyFrom, asRecord } from '@reticlehq/core';
|
|
11
|
+
import { projectForRoot } from '../../memory/project/project-for-root.js';
|
|
12
|
+
import { harnessDriver, harnessOptionsFromEnv, } from '../../features/harness/driver.js';
|
|
13
|
+
import { fillValues } from '../../features/harness/fill-values.js';
|
|
14
|
+
import { openFillValues } from '../../memory/project/dir/fill-value-store.js';
|
|
15
|
+
import { ANTHROPIC_DRIVER_NAME, CUSTOM_DRIVER_NAME, DRIVER_NAMES, JEV_DRIVER_NAME, OPENAI_DRIVER_NAME, } from '../../features/harness/drivers.js';
|
|
16
|
+
import { jevDriver, jevOptionsFromEnv } from '../../features/harness/jev-driver.js';
|
|
17
|
+
import { buildDomainModel } from '../../judgement/domain/domain-model.js';
|
|
18
|
+
import { readContract } from '../../memory/project/dir/reticle-dir.js';
|
|
19
|
+
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
20
|
+
import { buildHarnessPlan, planAsText } from './harness-plan.js';
|
|
21
|
+
import { openAiDriver, openAiOptionsFromEnv } from '../../features/harness/openai-driver.js';
|
|
22
|
+
import { fetchPlatformConfig } from '../../features/harness/platform-config.js';
|
|
12
23
|
import { DEFAULT_MAX_STEPS, runHarness, } from '../../features/harness/harness.js';
|
|
13
24
|
import { reticleToolset } from './harness-toolset.js';
|
|
14
25
|
/**
|
|
@@ -18,11 +29,60 @@ import { reticleToolset } from './harness-toolset.js';
|
|
|
18
29
|
* the one thing to do about it. Named here because both the CLI and the honest no-flows refusal
|
|
19
30
|
* print it, and two copies of this sentence would drift.
|
|
20
31
|
*/
|
|
21
|
-
export const MSG_NO_HARNESS_KEY = `No model configured to drive the app: set ${ReticleEnv.HARNESS_KEY}
|
|
22
|
-
`
|
|
32
|
+
export const MSG_NO_HARNESS_KEY = `No model configured to drive the app: set ${ReticleEnv.HARNESS_KEY}, or run \`reticle link\` and ` +
|
|
33
|
+
`set ${ReticleEnv.CLOUD_KEY}, to let Reticle explore it for you. Without either, flows are ` +
|
|
34
|
+
`recorded by your own coding agent through the MCP tools.`;
|
|
35
|
+
/** Asked for Jev specifically and it is not configured. Distinct from having no model at all. */
|
|
36
|
+
export const MSG_NO_JEV_KEY = `The \`${JEV_DRIVER_NAME}\` driver was asked for but is not configured: set ` +
|
|
37
|
+
`${ReticleEnv.HARNESS_JEV_KEY}, or run \`reticle link\` and set ${ReticleEnv.CLOUD_KEY}. ` +
|
|
38
|
+
`Refusing rather than quietly driving with another model, which would misattribute the result.`;
|
|
39
|
+
/** Asked for OpenAI specifically and it is not configured. Never substituted, same as Jev. */
|
|
40
|
+
export const MSG_NO_OPENAI_KEY = `The \`${OPENAI_DRIVER_NAME}\` driver was asked for but is not configured: set ` +
|
|
41
|
+
`${ReticleEnv.HARNESS_OPENAI_KEY}, or run \`reticle link\` and set ${ReticleEnv.CLOUD_KEY}. ` +
|
|
42
|
+
`Refusing rather than quietly driving with another model, which would misattribute the result.`;
|
|
43
|
+
const msgUnknownDriver = (asked) => `Unknown harness driver \`${asked}\`. Known drivers: ${DRIVER_NAMES.join(', ')}.`;
|
|
44
|
+
/**
|
|
45
|
+
* The environment, plus the credential this machine already has.
|
|
46
|
+
*
|
|
47
|
+
* `reticle link` mints a project-scoped key and files it in `~/.reticle/credentials.json`; the CLI
|
|
48
|
+
* has read it from there for as long as it has existed. The harness did not, and read only
|
|
49
|
+
* `process.env` — so somebody who had signed in, linked their project and been told they were
|
|
50
|
+
* connected still got "no model configured to drive the app", and the only way out was to find the
|
|
51
|
+
* key in the console and export it by hand. Five manual steps to reach a feature they had already
|
|
52
|
+
* finished setting up.
|
|
53
|
+
*
|
|
54
|
+
* Resolved into the env rather than threaded through four call sites: the drivers and the
|
|
55
|
+
* preference lookup all read an env record, and giving them a completed one leaves each of them
|
|
56
|
+
* exactly as simple as it was. An EXPLICIT variable still wins — someone who exported a key meant
|
|
57
|
+
* that key, and CI has no linked project to read.
|
|
58
|
+
*
|
|
59
|
+
* The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
|
|
60
|
+
* the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
|
|
61
|
+
*/
|
|
62
|
+
export async function withLinkedCredential(deps, env) {
|
|
63
|
+
if (apiKeyFrom(env) !== undefined)
|
|
64
|
+
return env;
|
|
65
|
+
try {
|
|
66
|
+
const linked = await deps.linkedCloud?.();
|
|
67
|
+
if (linked === undefined || null === linked)
|
|
68
|
+
return env;
|
|
69
|
+
return {
|
|
70
|
+
...env,
|
|
71
|
+
[ReticleEnv.API_KEY]: linked.apiKey,
|
|
72
|
+
[ReticleEnv.CLOUD_URL]: env[ReticleEnv.CLOUD_URL] ?? linked.url,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// A credential store that cannot be read is "not linked", not an error. The harness is optional
|
|
77
|
+
// and its absence is a routine answer; a drive must never fail because a JSON file was odd.
|
|
78
|
+
return env;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
23
81
|
/** Is there a model the harness can drive with? A read, because "not configured" is not a failure. */
|
|
24
82
|
export function harnessAvailable(env) {
|
|
25
|
-
return harnessOptionsFromEnv(env) !== undefined
|
|
83
|
+
return (harnessOptionsFromEnv(env) !== undefined ||
|
|
84
|
+
jevOptionsFromEnv(env) !== undefined ||
|
|
85
|
+
openAiOptionsFromEnv(env) !== undefined);
|
|
26
86
|
}
|
|
27
87
|
/**
|
|
28
88
|
* Read the step ceiling out of the environment.
|
|
@@ -47,21 +107,347 @@ export function maxStepsFromEnv(env) {
|
|
|
47
107
|
* record and did not — and this product does not get to make that mistake about its own work.
|
|
48
108
|
*/
|
|
49
109
|
export async function exploreApp(deps, env, options = {}) {
|
|
50
|
-
const
|
|
110
|
+
const refusal = await refusedByPlatform(env, options);
|
|
111
|
+
if (refusal !== undefined)
|
|
112
|
+
throw new Error(refusal);
|
|
113
|
+
const maxSteps = options.maxSteps ?? maxStepsFromEnv(env);
|
|
51
114
|
const before = new Set(await deps.flows.list());
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
115
|
+
// `.reticle` FIRST, before the app and before anything reads a line of source. It already holds
|
|
116
|
+
// every saved flow, the consequence that must hold for each, and the declared intent nobody has
|
|
117
|
+
// tested — which is the whole of what a drive should be deciding against.
|
|
118
|
+
const plan = await readPlan(deps, options.sessionId);
|
|
119
|
+
/*
|
|
120
|
+
* The fixtures this project has already paid for.
|
|
121
|
+
*
|
|
122
|
+
* A drive is a sequence of DECISIONS and, occasionally, one COMPOSITION: what to type into a box
|
|
123
|
+
* whose label nothing can guess from. The cheap driver cannot write a string at all, so those
|
|
124
|
+
* boxes used to receive the words "reticle harness". This is the seam where a generating model
|
|
125
|
+
* answers instead -- for about 6% of steps, once per label, and never again on any later drive,
|
|
126
|
+
* because the answer is written into `.reticle` beside the flows that use it.
|
|
127
|
+
*/
|
|
128
|
+
const fills = await openFillValues(deps.fs, safeRoot(deps, options.sessionId));
|
|
129
|
+
const requested = options.driverName ?? env[ReticleEnv.HARNESS_DRIVER] ?? (await preferredDriver(env, options));
|
|
130
|
+
const built = options.driver === undefined
|
|
131
|
+
? buildDriver(env, maxSteps, plan, requested, fills)
|
|
132
|
+
: { driver: options.driver, name: CUSTOM_DRIVER_NAME };
|
|
133
|
+
const driver = built.driver;
|
|
134
|
+
const toolset = reticleToolset(deps, pinned(options));
|
|
135
|
+
const drive = await runHarness(driver, toolset, {
|
|
136
|
+
maxSteps,
|
|
137
|
+
// The plan rides in as standing instruction, so it is in front of the model on every turn
|
|
138
|
+
// rather than remembered from a first one. `focus` is the caller's own words and goes last:
|
|
139
|
+
// somebody who named a journey meant that journey, whatever the project's backlog says.
|
|
140
|
+
focus: [
|
|
141
|
+
planAsText(plan),
|
|
142
|
+
...(options.focus === undefined ? [] : [`Focus: ${options.focus}`]),
|
|
143
|
+
].join('\n\n'),
|
|
55
144
|
});
|
|
145
|
+
// Written once, after the drive, whatever the drive did: a run that broke still learned what it
|
|
146
|
+
// learned, and the next one should not pay for it again.
|
|
147
|
+
await fills.flush();
|
|
148
|
+
// MANDATORY, and deliberately outside the loop. A drive that runs out of budget mid-journey, or
|
|
149
|
+
// breaks, or whose model simply stops asking for tools, leaves a recording open and everything it
|
|
150
|
+
// drove unsaved — work paid for and thrown away. Saving is not a decision any model gets to make
|
|
151
|
+
// and not something a step budget gets to cut off, so it happens here, after the loop, always.
|
|
152
|
+
await bankOpenRecording(toolset, drive);
|
|
56
153
|
const after = await deps.flows.list();
|
|
57
|
-
return { drive,
|
|
154
|
+
return { drive, plan, driverName: built.name, ...reconcileFlows(before, after, drive.toolCalls) };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* What a drive actually left behind, split into flows that are NEW and flows it wrote over.
|
|
158
|
+
*
|
|
159
|
+
* Pure, and exported, because it is the part that was wrong and the part worth pinning. The
|
|
160
|
+
* integration around it — a real toolset dispatching a real save — is covered by driving a real app;
|
|
161
|
+
* what a test can usefully hold still is the reconciliation itself.
|
|
162
|
+
*
|
|
163
|
+
* A rewrite is only counted when the drive claims the save AND the store lists the name afterwards.
|
|
164
|
+
* The claim on its own is not evidence, which is the rule this function existed to keep: a model
|
|
165
|
+
* that says it saved something it did not must not be believed. Requiring both is what lets a
|
|
166
|
+
* rewrite be seen at all, since a name that was already there cannot show up in a before/after diff.
|
|
167
|
+
*/
|
|
168
|
+
export function reconcileFlows(before, after, toolCalls) {
|
|
169
|
+
const present = new Set(after);
|
|
170
|
+
const claimed = toolCalls
|
|
171
|
+
.filter((call) => ReticleTool.FLOW_SAVE === call.name && !call.isError)
|
|
172
|
+
.map((call) => {
|
|
173
|
+
// The flow's filename is `saveAs` when given, and the recording's name otherwise. Read from
|
|
174
|
+
// the result first, which is what the store actually wrote, and fall back to the request.
|
|
175
|
+
const result = asRecord(call.result)['name'];
|
|
176
|
+
if ('string' === typeof result)
|
|
177
|
+
return result;
|
|
178
|
+
const args = asRecord(call.args);
|
|
179
|
+
const saveAs = args['saveAs'];
|
|
180
|
+
const flowName = args['flowName'];
|
|
181
|
+
if ('string' === typeof saveAs)
|
|
182
|
+
return saveAs;
|
|
183
|
+
return 'string' === typeof flowName ? flowName : undefined;
|
|
184
|
+
})
|
|
185
|
+
.filter((name) => name !== undefined && present.has(name));
|
|
186
|
+
return {
|
|
187
|
+
savedFlows: after.filter((name) => !before.has(name)),
|
|
188
|
+
rewroteFlows: [...new Set(claimed.filter((name) => before.has(name)))],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The driver this project chose on the platform, if it chose one, we can reach it, and we have it.
|
|
193
|
+
*
|
|
194
|
+
* Only asked when nobody has been explicit, so the cost is paid exactly once per drive and never on
|
|
195
|
+
* a path where it could not change the answer. An unreachable platform returns undefined and the
|
|
196
|
+
* environment decides, exactly as it did before this lookup existed.
|
|
197
|
+
*
|
|
198
|
+
* A preference naming a driver this build does not have is IGNORED rather than refused, and that
|
|
199
|
+
* asymmetry with the per-call argument is deliberate. The platform offers providers this daemon may
|
|
200
|
+
* be too old to know about — it already offers `openai`, which has no binding here — so a stored
|
|
201
|
+
* preference is a statement about the account, not an instruction for this drive, and a daemon that
|
|
202
|
+
* refused to run because a web UI knew one more word than it does would be broken by its own
|
|
203
|
+
* upgrade cycle. Naming a driver in the CALL is an instruction, and an unknown one is still an
|
|
204
|
+
* error there.
|
|
205
|
+
*
|
|
206
|
+
* It is not a silent substitution either way: the result reports the driver that actually drove.
|
|
207
|
+
*/
|
|
208
|
+
async function preferredDriver(env, options) {
|
|
209
|
+
if (true === options.skipPlatformConfig)
|
|
210
|
+
return undefined;
|
|
211
|
+
return knownDriver((await platformConfig(env, options))?.provider);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* What a person said about autonomous driving on the platform, honoured here.
|
|
215
|
+
*
|
|
216
|
+
* The switch existed, persisted and round-tripped, and the daemon read it and threw it away — so
|
|
217
|
+
* turning the harness OFF changed a value in a database and nothing else. A control that does not
|
|
218
|
+
* control anything is worse than no control: somebody turns it off, watches Reticle drive their app
|
|
219
|
+
* anyway, and now correctly distrusts every other switch in the product.
|
|
220
|
+
*
|
|
221
|
+
* Absent means ON. The platform's own default is on, and a machine that cannot reach the platform —
|
|
222
|
+
* offline, CI, no link — must not silently lose a feature it was never told to stop using.
|
|
223
|
+
*/
|
|
224
|
+
export const MSG_HARNESS_DISABLED = 'Autonomous driving is turned OFF for this project. Turn it back on in the Reticle dashboard ' +
|
|
225
|
+
'(Settings → Verification model), or drive the app yourself through the MCP tools.';
|
|
226
|
+
/**
|
|
227
|
+
* The other reason a drive can be refused, and it is NOT the same reason.
|
|
228
|
+
*
|
|
229
|
+
* A drive through the platform spends Reticle's model budget. That is free for three months and
|
|
230
|
+
* included on a paid plan, and outside both it is somebody else's money being spent on nothing. The
|
|
231
|
+
* message says which of the two it is and what to do, because "the harness is off" told to a person
|
|
232
|
+
* who never turned anything off is a support ticket rather than an answer.
|
|
233
|
+
*/
|
|
234
|
+
export const MSG_HARNESS_UNCLAIMED = 'This workspace has no harness entitlement, so autonomous driving would run on Reticle’s model ' +
|
|
235
|
+
'budget with nothing paying for it. Claim the free 3 months in the Reticle dashboard, or export a ' +
|
|
236
|
+
'model API key of your own and drive with that.';
|
|
237
|
+
/**
|
|
238
|
+
* Whether a drive would be paid for by the person asking for it.
|
|
239
|
+
*
|
|
240
|
+
* Entitlement gates OUR spend, so it has no business stopping somebody who brought their own key:
|
|
241
|
+
* their harness costs us nothing whether they ever claim anything or not. Reading the same options
|
|
242
|
+
* the drivers read keeps the two answers from drifting apart.
|
|
243
|
+
*/
|
|
244
|
+
function ownsAModelKey(env) {
|
|
245
|
+
// The DIRECT variables only. Every driver also accepts the platform key as a fallback, so asking
|
|
246
|
+
// `jevOptionsFromEnv` here would answer "they have a key" for exactly the person whose drive would
|
|
247
|
+
// be billed to us — which is the one case this gate exists for.
|
|
248
|
+
return [ReticleEnv.HARNESS_KEY, ReticleEnv.HARNESS_JEV_KEY, ReticleEnv.HARNESS_OPENAI_KEY].some((name) => {
|
|
249
|
+
const value = env[name];
|
|
250
|
+
return value !== undefined && 0 < value.length;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/** One read, however many questions are asked of the answer. */
|
|
254
|
+
function platformConfig(env, options) {
|
|
255
|
+
return options.configFetch === undefined
|
|
256
|
+
? fetchPlatformConfig(env)
|
|
257
|
+
: fetchPlatformConfig(env, options.configFetch);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The reason this drive must not start, or `undefined` to go ahead.
|
|
261
|
+
*
|
|
262
|
+
* It FAILS OPEN, and that is worth stating plainly because the argument for it changed under it.
|
|
263
|
+
* `fetchPlatformConfig` answers `undefined` on a network error, a non-2xx, a body that does not
|
|
264
|
+
* parse, or a two-second timeout, and all four land here as "carry on". When this endpoint only
|
|
265
|
+
* answered "which model does this project prefer", failing open cost nothing: the drive used the
|
|
266
|
+
* environment and nobody was worse off. It now also answers "is anybody paying for this", and the
|
|
267
|
+
* same silence means a drive runs on Reticle's model budget unmetered.
|
|
268
|
+
*
|
|
269
|
+
* Kept open anyway, deliberately: a settings endpoint having a slow second must not break somebody
|
|
270
|
+
* mid-verification, and the exposure is one drive's worth of a very cheap model. But this is
|
|
271
|
+
* best-effort, not enforcement -- if the spend needs a hard floor, it belongs at the proxy, which
|
|
272
|
+
* is the only side that cannot be talked out of it by a dropped packet.
|
|
273
|
+
*/
|
|
274
|
+
async function refusedByPlatform(env, options) {
|
|
275
|
+
if (true === options.skipPlatformConfig)
|
|
276
|
+
return undefined;
|
|
277
|
+
const config = await platformConfig(env, options);
|
|
278
|
+
if (config === undefined)
|
|
279
|
+
return undefined;
|
|
280
|
+
if (!config.harnessEnabled)
|
|
281
|
+
return MSG_HARNESS_DISABLED;
|
|
282
|
+
if (!config.harnessEntitled && !ownsAModelKey(env))
|
|
283
|
+
return MSG_HARNESS_UNCLAIMED;
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* A stored preference, kept only if this build can actually honour it.
|
|
288
|
+
*
|
|
289
|
+
* Exported because it is the whole of the asymmetry with the per-call argument, and the asymmetry
|
|
290
|
+
* is the part somebody will later think is a bug.
|
|
291
|
+
*/
|
|
292
|
+
export function knownDriver(provider) {
|
|
293
|
+
if (provider === undefined)
|
|
294
|
+
return undefined;
|
|
295
|
+
return DRIVER_NAMES.some((name) => name === provider) ? provider : undefined;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Close and save whatever recording the drive left open.
|
|
299
|
+
*
|
|
300
|
+
* The driver reserves turns for its own teardown, which covers the ordinary ending. It does NOT
|
|
301
|
+
* cover a drive that broke, or one whose model went quiet, or one cut off by a budget the caller
|
|
302
|
+
* shortened — and in every one of those the app really was driven and the record of it is thrown
|
|
303
|
+
* away. Measured before this existed: a 24-step journey through a real dashboard, saved nothing.
|
|
304
|
+
*
|
|
305
|
+
* Failures are swallowed on purpose. This is a last chance, not a checkpoint: if the recording was
|
|
306
|
+
* already closed the stop is refused and there is nothing to do, and a drive must not fail at the
|
|
307
|
+
* finish line because the thing it was trying to rescue did not need rescuing.
|
|
308
|
+
*/
|
|
309
|
+
async function bankOpenRecording(toolset, drive) {
|
|
310
|
+
const open = openRecordingName(drive.toolCalls);
|
|
311
|
+
if (open === undefined)
|
|
312
|
+
return;
|
|
313
|
+
try {
|
|
314
|
+
await toolset.invoke(ReticleTool.RECORD, { action: 'stop', recordingName: open });
|
|
315
|
+
await toolset.invoke(ReticleTool.FLOW_SAVE, {
|
|
316
|
+
flowName: open,
|
|
317
|
+
intent: `Autonomous drive of ${open}, banked after the run ended.`,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
/* last chance, not a checkpoint */
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/** The recording started and never stopped, if there is one. Exported because it is the decision. */
|
|
325
|
+
export function openRecordingName(toolCalls) {
|
|
326
|
+
let open;
|
|
327
|
+
for (const call of toolCalls) {
|
|
328
|
+
if (ReticleTool.RECORD !== call.name)
|
|
329
|
+
continue;
|
|
330
|
+
const args = asRecord(call.args);
|
|
331
|
+
const name = args['recordingName'];
|
|
332
|
+
if ('string' !== typeof name)
|
|
333
|
+
continue;
|
|
334
|
+
if ('start' === args['action'] && !call.isError)
|
|
335
|
+
open = name;
|
|
336
|
+
if ('stop' === args['action'] && name === open)
|
|
337
|
+
open = undefined;
|
|
338
|
+
}
|
|
339
|
+
return open;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Read the project's own record of what it does and what is proved about it.
|
|
343
|
+
*
|
|
344
|
+
* No browser, no model, no source. A failure here is "nothing is recorded yet", which is the
|
|
345
|
+
* ordinary state of a new project and not a reason to refuse to drive — so it answers an empty
|
|
346
|
+
* plan rather than throwing.
|
|
347
|
+
*/
|
|
348
|
+
async function readPlan(deps, sessionId) {
|
|
349
|
+
try {
|
|
350
|
+
const flows = [];
|
|
351
|
+
for (const name of await deps.flows.list()) {
|
|
352
|
+
const loaded = await deps.flows.load(name);
|
|
353
|
+
if (loaded.ok)
|
|
354
|
+
flows.push(loaded.value);
|
|
355
|
+
}
|
|
356
|
+
const root = sessionRoot(deps, sessionId);
|
|
357
|
+
const contract = await readContract(deps.fs, root);
|
|
358
|
+
const project = await projectForRoot(deps, root).read();
|
|
359
|
+
return buildHarnessPlan(buildDomainModel(flows, contract.ok ? contract.capabilities : null, project.ok ? project.file.runs : []));
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return {
|
|
363
|
+
steps: [],
|
|
364
|
+
summary: 'The project record could not be read; driving without a plan.',
|
|
365
|
+
vocabulary: [],
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* The project directory, or nothing.
|
|
371
|
+
*
|
|
372
|
+
* `sessionRoot` needs a session and a project to answer, and a drive can legitimately be asked for
|
|
373
|
+
* before either is resolvable. Everything else that reads `.reticle` here already treats that as
|
|
374
|
+
* "no project record" rather than as a failure; the fixtures file is no different, and a throw on
|
|
375
|
+
* this path would turn a missing directory into a refused drive.
|
|
376
|
+
*/
|
|
377
|
+
function safeRoot(deps, sessionId) {
|
|
378
|
+
try {
|
|
379
|
+
return sessionRoot(deps, sessionId);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
58
384
|
}
|
|
59
385
|
function pinned(options) {
|
|
60
386
|
return options.sessionId === undefined ? {} : { sessionId: options.sessionId };
|
|
61
387
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Pick the driver.
|
|
390
|
+
*
|
|
391
|
+
* The Anthropic driver stays the default, and an explicit `RETICLE_HARNESS_DRIVER=jev` is the only
|
|
392
|
+
* thing that moves off it when both are configured — a driver change is a change in how the app gets
|
|
393
|
+
* driven, and inferring one from which key happens to be exported would swap it under people who
|
|
394
|
+
* merely linked their account.
|
|
395
|
+
*
|
|
396
|
+
* The fall-through is the other direction and is not a preference: with no Anthropic key and a
|
|
397
|
+
* platform key present, Jev is not the cheaper option, it is the only one. Choosing it there is what
|
|
398
|
+
* makes the harness work for somebody who never had a model API key of their own, which is most
|
|
399
|
+
* people — and the reason this driver is worth having at all.
|
|
400
|
+
*/
|
|
401
|
+
function buildDriver(env, maxSteps, plan, requested, fills) {
|
|
402
|
+
// The two halves of what `.reticle` knows, handed to the one driver that can use both: what is
|
|
403
|
+
// worth doing, and the names the app uses for what it does. The second is what lets a declared
|
|
404
|
+
// consequence be SAVED rather than merely proved; see `consequencesFor`.
|
|
405
|
+
const fromReticle = {
|
|
406
|
+
plan: plan.steps,
|
|
407
|
+
vocabulary: plan.vocabulary,
|
|
408
|
+
/*
|
|
409
|
+
* Generation is offered ONLY when a generating model is already configured, and it stays out of
|
|
410
|
+
* the per-turn loop either way: put a text model on every turn and you pay its per-turn tax on
|
|
411
|
+
* the 94% of steps that are clicks, which is the entire cost the cheap driver exists to avoid.
|
|
412
|
+
* With no such key the label heuristic answers, exactly as it did before this existed.
|
|
413
|
+
*/
|
|
414
|
+
fillValue: fillValues({
|
|
415
|
+
...(fills === undefined ? {} : { cache: fills }),
|
|
416
|
+
...(harnessOptionsFromEnv(env) === undefined
|
|
417
|
+
? {}
|
|
418
|
+
: { generator: harnessOptionsFromEnv(env) }),
|
|
419
|
+
}),
|
|
420
|
+
};
|
|
421
|
+
const jev = jevOptionsFromEnv(env);
|
|
422
|
+
const anthropic = harnessOptionsFromEnv(env);
|
|
423
|
+
const asked = requested;
|
|
424
|
+
// A driver that was ASKED for and is not configured is an error, never a substitution. Quietly
|
|
425
|
+
// falling back would make every comparison between two drivers a possible lie about which one
|
|
426
|
+
// produced the result — and comparing them is the main reason anyone names one.
|
|
427
|
+
if (JEV_DRIVER_NAME === asked) {
|
|
428
|
+
if (jev === undefined)
|
|
429
|
+
throw new Error(MSG_NO_JEV_KEY);
|
|
430
|
+
// The Jev driver is told the budget because it has a teardown to reach; the Anthropic driver is
|
|
431
|
+
// not, because it calls `finish` itself and being handed a number it did not ask for is how a
|
|
432
|
+
// second copy of the budget starts drifting from the loop's.
|
|
433
|
+
return { driver: jevDriver({ ...jev, maxSteps, ...fromReticle }), name: JEV_DRIVER_NAME };
|
|
434
|
+
}
|
|
435
|
+
if (ANTHROPIC_DRIVER_NAME === asked) {
|
|
436
|
+
if (anthropic === undefined)
|
|
437
|
+
throw new Error(MSG_NO_HARNESS_KEY);
|
|
438
|
+
return { driver: harnessDriver(anthropic), name: ANTHROPIC_DRIVER_NAME };
|
|
439
|
+
}
|
|
440
|
+
if (OPENAI_DRIVER_NAME === asked) {
|
|
441
|
+
const openai = openAiOptionsFromEnv(env);
|
|
442
|
+
if (openai === undefined)
|
|
443
|
+
throw new Error(MSG_NO_OPENAI_KEY);
|
|
444
|
+
return { driver: openAiDriver(openai), name: OPENAI_DRIVER_NAME };
|
|
445
|
+
}
|
|
446
|
+
if (asked !== undefined && 0 < asked.length)
|
|
447
|
+
throw new Error(msgUnknownDriver(asked));
|
|
448
|
+
if (anthropic !== undefined)
|
|
449
|
+
return { driver: harnessDriver(anthropic), name: ANTHROPIC_DRIVER_NAME };
|
|
450
|
+
if (jev !== undefined)
|
|
451
|
+
return { driver: jevDriver({ ...jev, maxSteps, ...fromReticle }), name: JEV_DRIVER_NAME };
|
|
452
|
+
throw new Error(MSG_NO_HARNESS_KEY);
|
|
67
453
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a drive is FOR, decided before it starts and read out of `.reticle` rather than the app.
|
|
3
|
+
*
|
|
4
|
+
* The driver used to open its eyes on a page and pick whatever looked interesting. That makes every
|
|
5
|
+
* run start from zero: it re-drives journeys that are already saved, and it has no idea which
|
|
6
|
+
* declared intent nobody has ever tested. Both are the same missing input — the project's own
|
|
7
|
+
* record of what it claims to do and what has been proved about it.
|
|
8
|
+
*
|
|
9
|
+
* `.reticle` already holds that. `buildDomainModel` reads `.reticle/flows/` and
|
|
10
|
+
* `.reticle/contract.json` and answers with every saved flow, the consequence that MUST hold for
|
|
11
|
+
* each one, and the GAPS: signals and testids the app DECLARED that no flow asserts. So the plan is
|
|
12
|
+
* derivable with no browser, no model and no source code — which is the order this product argues
|
|
13
|
+
* for everywhere else and did not follow here.
|
|
14
|
+
*
|
|
15
|
+
* Two kinds of step, and the split is the whole economics:
|
|
16
|
+
*
|
|
17
|
+
* REPLAY a journey that is already recorded. Deterministic, no model in the loop, a few hundred
|
|
18
|
+
* tokens. Anything already covered belongs here and must never be re-driven.
|
|
19
|
+
* DRIVE a gap. A model is worth paying for exactly where nothing is proved yet.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here calls a model. The plan is the deterministic half, and the model's only job is to
|
|
22
|
+
* choose among steps somebody already decided were worth taking.
|
|
23
|
+
*/
|
|
24
|
+
import type { DomainModel } from '../../judgement/domain/domain-model.js';
|
|
25
|
+
export declare const PlanStepKind: {
|
|
26
|
+
/** Already recorded: replay it deterministically instead of paying a model to rediscover it. */
|
|
27
|
+
readonly REPLAY: "replay";
|
|
28
|
+
/** Declared but never proved: this is what a drive is actually for. */
|
|
29
|
+
readonly DRIVE: "drive";
|
|
30
|
+
};
|
|
31
|
+
export type PlanStepKind = (typeof PlanStepKind)[keyof typeof PlanStepKind];
|
|
32
|
+
export interface PlanStep {
|
|
33
|
+
kind: PlanStepKind;
|
|
34
|
+
/** The flow to replay, or a short label for the gap to cover. */
|
|
35
|
+
target: string;
|
|
36
|
+
/**
|
|
37
|
+
* WHY this step is in the plan, in the project's own words.
|
|
38
|
+
*
|
|
39
|
+
* For a replay it is the consequence that must hold — the flow's `mustHold`, which is the thing
|
|
40
|
+
* that stops being true when the feature breaks. For a drive it is the declared intent nobody has
|
|
41
|
+
* tested. Carried because the model choosing between steps should be reading intent, not a list
|
|
42
|
+
* of names it has no way to rank.
|
|
43
|
+
*/
|
|
44
|
+
why: string;
|
|
45
|
+
}
|
|
46
|
+
export interface HarnessPlan {
|
|
47
|
+
steps: readonly PlanStep[];
|
|
48
|
+
/** One line an agent or a human can read, derived rather than narrated. */
|
|
49
|
+
summary: string;
|
|
50
|
+
/**
|
|
51
|
+
* The app's OWN vocabulary: the signals it declared in its capabilities, untested ones first.
|
|
52
|
+
*
|
|
53
|
+
* Carried out of `.reticle` for the same reason the steps are, and then used for something the
|
|
54
|
+
* plan itself never does. A drive declares what it expects BEFORE it acts, and a declaration can
|
|
55
|
+
* only be saved into the flow if it names something: `signal "order:placed"` records, a bare
|
|
56
|
+
* "some signal fires" does not, and a flow that records nothing replays green whatever the
|
|
57
|
+
* feature does. These are the names that make the difference.
|
|
58
|
+
*/
|
|
59
|
+
vocabulary: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build the plan.
|
|
63
|
+
*
|
|
64
|
+
* Replays come first, always. They are the cheap half and they establish what is still true before
|
|
65
|
+
* any model is paid to explore what is not yet known — and a regression found by a replay is found
|
|
66
|
+
* for a few hundred tokens rather than a few hundred thousand.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildHarnessPlan(domain: DomainModel): HarnessPlan;
|
|
69
|
+
/**
|
|
70
|
+
* The plan as the model reads it.
|
|
71
|
+
*
|
|
72
|
+
* Intent first and names second, because a model ranking "what next" against a list of identifiers
|
|
73
|
+
* is guessing, and ranking against what the project says each one is FOR is not. The kind is stated
|
|
74
|
+
* so it can prefer finishing the cheap deterministic half before spending itself on the rest.
|
|
75
|
+
*/
|
|
76
|
+
export declare function planAsText(plan: HarnessPlan): string;
|