@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
|
@@ -38,8 +38,8 @@ export const MAX_TIMEOUT_MS = 120_000;
|
|
|
38
38
|
*
|
|
39
39
|
* This does not make long waits possible, and is not meant to — it makes the ADVERTISED bound one
|
|
40
40
|
* that can actually be honoured. A caller that genuinely needs to outlast this polls: several short
|
|
41
|
-
* waits, each of which returns a verdict.
|
|
42
|
-
*
|
|
41
|
+
* waits, each of which returns a verdict. `reticle_wait_for` does that for the caller: see
|
|
42
|
+
* `waitForTimeoutMsSchema` and `resumeAfter`.
|
|
43
43
|
*/
|
|
44
44
|
const MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
45
45
|
export const MAX_BLOCKING_WAIT_MS = MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS - 5_000;
|
|
@@ -54,7 +54,7 @@ export const MAX_STATE_DEPTH = 256;
|
|
|
54
54
|
/** A crawl that clicks forever is not a bound. */
|
|
55
55
|
const MAX_CRAWL_STEPS = 500;
|
|
56
56
|
/** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
|
|
57
|
-
|
|
57
|
+
const MAX_SCROLLS = 200;
|
|
58
58
|
/** Fake-clock jump. One day of timers is a test; a billion ms is not. */
|
|
59
59
|
const MAX_ADVANCE_MS = 86_400_000;
|
|
60
60
|
/** Presenter idle window. One hour is a slow app; larger values never fire. */
|
|
@@ -71,6 +71,33 @@ export const cursorSchema = z.number().finite().int().nonnegative();
|
|
|
71
71
|
* A wait budget in ms. 0 means evaluate now (documented on assert). Negative cannot be honoured.
|
|
72
72
|
*/
|
|
73
73
|
export const timeoutMsSchema = z.number().finite().int().nonnegative().max(MAX_BLOCKING_WAIT_MS);
|
|
74
|
+
/**
|
|
75
|
+
* The longest `reticle_wait_for` accepts, across as many calls as it takes. Ten minutes is a slow
|
|
76
|
+
* build or a queued job; an hour is a hang somebody forgot about.
|
|
77
|
+
*/
|
|
78
|
+
const MAX_RESUMABLE_WAIT_MS = 600_000;
|
|
79
|
+
/**
|
|
80
|
+
* `reticle_wait_for`'s budget, which may exceed one call: it waits at most MAX_BLOCKING_WAIT_MS per
|
|
81
|
+
* call and hands back `resume_ms` for the rest (#601, from #635).
|
|
82
|
+
*/
|
|
83
|
+
export const waitForTimeoutMsSchema = z
|
|
84
|
+
.number()
|
|
85
|
+
.finite()
|
|
86
|
+
.int()
|
|
87
|
+
.nonnegative()
|
|
88
|
+
.max(MAX_RESUMABLE_WAIT_MS);
|
|
89
|
+
/**
|
|
90
|
+
* What is left of a wait that stopped at the per-call limit, or undefined when there is nothing to
|
|
91
|
+
* resume: the predicate held, the whole budget fit in this call, or the wait ended for a reason
|
|
92
|
+
* waiting longer cannot change (the page went away, or the read could not be made).
|
|
93
|
+
*/
|
|
94
|
+
export function resumeAfter(requestedMs, perCallMs, verdict) {
|
|
95
|
+
if (verdict.pass || true === verdict.observationLost || verdict.inconclusive !== undefined) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const left = requestedMs - perCallMs;
|
|
99
|
+
return left > 0 ? left : undefined;
|
|
100
|
+
}
|
|
74
101
|
/**
|
|
75
102
|
* A count / cap. 0 means "return none", which is a real request. Negative is not.
|
|
76
103
|
* Capped at the ring-buffer size so a `limit: 1e9` cannot pretend the buffer is larger than it is.
|
|
@@ -115,11 +115,23 @@ export function gradeOfPredicate(predicate) {
|
|
|
115
115
|
return HonestyGrade.PRESENCE;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Fold a combinator's branches on the ladder. No branches proves nothing, so it stays at the floor.
|
|
120
|
+
*
|
|
121
|
+
* The floor is `NONE`, not `PRESENCE`. This sentence has been here since the function was written
|
|
122
|
+
* and the code returned one rung above it, which is the entire defect: `VACUOUS_GRADE`
|
|
123
|
+
* (`engine/src/evidence/verified.ts`) fires on `NONE`, so an empty combinator graded `PRESENCE`
|
|
124
|
+
* walked past the one guard whose job is refusing a green that rests on nothing — and reported
|
|
125
|
+
* `verified: "yes"` for a drive that asserted nothing at all.
|
|
126
|
+
*
|
|
127
|
+
* The schema refuses an empty combinator now, so this is the second defence rather than the only
|
|
128
|
+
* one. It is kept because the two stop different things: the schema stops it ENTERING through the
|
|
129
|
+
* documented door, and this stops it COUNTING if it is ever constructed rather than parsed.
|
|
130
|
+
*/
|
|
119
131
|
function combine(branches, pick) {
|
|
120
132
|
const grades = branches.map(gradeOfPredicate);
|
|
121
133
|
const first = grades[0];
|
|
122
134
|
if (first === undefined)
|
|
123
|
-
return HonestyGrade.
|
|
135
|
+
return HonestyGrade.NONE;
|
|
124
136
|
return grades.reduce(pick, first);
|
|
125
137
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CaptureLoss, channelsRead, PredicateKind } from '@reticlehq/core';
|
|
2
|
+
import { sessionVerdictFacts } from '../../../portal/session/session-verdict-facts.js';
|
|
2
3
|
import { gapsForAction } from '@reticlehq/engine/evidence/instrumentation-gaps.js';
|
|
3
4
|
import { noteSessionGaps } from '@reticlehq/engine/evidence/gap-ledger.js';
|
|
4
5
|
import { declaresState } from '@reticlehq/engine/question/predicate/predicate-asks.js';
|
|
@@ -15,6 +16,7 @@ import { describeWaitTarget, namedNetIsInFlight } from '@reticlehq/engine/eviden
|
|
|
15
16
|
import { inFlightRequestLabels, repeatedRequestLabels, } from '../act/settle-in-flight.js';
|
|
16
17
|
import { gradeOfPredicate } from './assert-grade.js';
|
|
17
18
|
import { assertSource } from './assert-source.js';
|
|
19
|
+
import { VerdictAttribution, verdictAttributionOf } from '@reticlehq/core';
|
|
18
20
|
/**
|
|
19
21
|
* The honesty verdict for a plain `reticle_assert`.
|
|
20
22
|
*
|
|
@@ -85,6 +87,7 @@ changeUndeclared) {
|
|
|
85
87
|
currentDocumentId: session.currentDocumentId,
|
|
86
88
|
currentEditEpoch: session.currentEditEpoch,
|
|
87
89
|
appOrigin: session.url,
|
|
90
|
+
background: session.background,
|
|
88
91
|
expectedFailures: declared.netFailures,
|
|
89
92
|
namedNetUrls: declared.netUrls,
|
|
90
93
|
renderProved: pass && declared.rendersContent,
|
|
@@ -97,13 +100,48 @@ changeUndeclared) {
|
|
|
97
100
|
// rule — part of what happened was not seen — so both belong in `blindSpots`, which is the only
|
|
98
101
|
// input `decideVerified` reads for that.
|
|
99
102
|
const gap = transportGapNote(windowEvents);
|
|
103
|
+
/**
|
|
104
|
+
* The DURABLE half of the same question. `queryEvents` above falls through to the journal once the
|
|
105
|
+
* ring buffer has evicted, and that ledger refuses writes at its byte ceiling — so a window read
|
|
106
|
+
* from it after the ceiling was reached is missing whatever the cap turned away. The in-band
|
|
107
|
+
* marker cannot carry this: it is stamped with a `t`, and any `since` later than that filters it
|
|
108
|
+
* straight back out, leaving a partial answer that looks exactly like a complete one.
|
|
109
|
+
*
|
|
110
|
+
* Called optionally because `Session` is stubbed by cast in a great many suites, and a method
|
|
111
|
+
* added to it therefore arrives as a runtime `undefined` in files that have nothing to do with
|
|
112
|
+
* this change — the cost recorded at the top of `fake-session.ts`, four times over.
|
|
113
|
+
*/
|
|
114
|
+
const ledgerClosed = undefined !== (await session.journalWriteLoss?.());
|
|
100
115
|
// A consumer rule that crashed belongs here too: the engine ran with fewer rules than it claims,
|
|
101
116
|
// so part of what happened may simply not have been looked for.
|
|
102
117
|
const impeachingNotes = [impeaching.note, gap, ...crashedRuleNotes()].filter((n) => n !== undefined);
|
|
103
118
|
const outcomePending = acceptedWriteLabels(windowEvents);
|
|
104
119
|
const outcomeUnread = unreadWriteLabels(windowEvents);
|
|
105
|
-
const stillInFlight = inFlightRequestLabels(windowEvents);
|
|
120
|
+
const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
|
|
106
121
|
const effectiveInconclusive = inconclusive ?? (!pass ? session.preconditionFailure?.() : undefined);
|
|
122
|
+
/**
|
|
123
|
+
* Does a green here rest on the window having been COMPLETE?
|
|
124
|
+
*
|
|
125
|
+
* Not a refinement of the two losses below — it is what keeps either of them usable. Scarce loss
|
|
126
|
+
* is recorded for AGE eviction too, so `lostSince(0)` is true on any session past the 60s cutoff,
|
|
127
|
+
* and assert takes a caller-chosen `since` that is often 0. Impeaching every verdict over a wide
|
|
128
|
+
* window would make `unknown` the answer to everything, which is the failure this repo has already
|
|
129
|
+
* paid for once, when `unclean_capture` became the dominant cause of `unknown` in the field. A
|
|
130
|
+
* POSITIVE assertion that passed FOUND its evidence; things lost elsewhere do not unmake it.
|
|
131
|
+
*/
|
|
132
|
+
const restsOnComplete = restsOnCompleteWindow(predicate);
|
|
133
|
+
/**
|
|
134
|
+
* The buffer evicted scarce evidence from this window.
|
|
135
|
+
*
|
|
136
|
+
* assert never consulted the buffer at all, on the reasoning that it "observes an already-open
|
|
137
|
+
* window" — but eviction happens on push regardless of who opened the window, so
|
|
138
|
+
* `{ console, absent: true }` returned `yes` over a window whose evidence was gone, which is
|
|
139
|
+
* absence of evidence read as evidence of absence. The act path needs no `restsOnComplete` guard:
|
|
140
|
+
* its cursor is the action's own.
|
|
141
|
+
*/
|
|
142
|
+
const bufferLost = session.lostSince(since) && restsOnComplete;
|
|
143
|
+
/** The durable ledger refused writes, on a query path that read from it. Same rule, other store. */
|
|
144
|
+
const ledgerLost = ledgerClosed && restsOnComplete;
|
|
107
145
|
const decision = decideVerified({
|
|
108
146
|
pass,
|
|
109
147
|
// What this claim needs to read, against what the page said it can see -- the protocol's
|
|
@@ -112,9 +150,8 @@ changeUndeclared) {
|
|
|
112
150
|
// treating that silence as an empty set would refuse every claim from every older page.
|
|
113
151
|
channelsRead: channelsRead(predicate.kind),
|
|
114
152
|
...(session.channels === undefined ? {} : { channelsObservable: session.channels }),
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
...(session.sdkVersion === undefined ? {} : { sdkVersion: session.sdkVersion }),
|
|
153
|
+
// Threaded rather than looked up: decideVerified is pure and has no session.
|
|
154
|
+
...sessionVerdictFacts(session),
|
|
118
155
|
// Same rule as the act path: the caller named a consequence, so a settlement-only finding must
|
|
119
156
|
// not override it. A fix that lived on one half of the verdict surface would leave the other
|
|
120
157
|
// half broken, and this is the tool agents call most.
|
|
@@ -127,27 +164,19 @@ changeUndeclared) {
|
|
|
127
164
|
honesty: buildHonestyBlock({
|
|
128
165
|
grade: gradeOfPredicate(predicate),
|
|
129
166
|
attribution: 'window',
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
// which is absence of evidence read as evidence of absence.
|
|
135
|
-
//
|
|
136
|
-
// The second half of the condition is not a refinement, it is what keeps this usable. Scarce
|
|
137
|
-
// loss is recorded for AGE eviction too, so `lostSince(0)` is true on any session past the 60s
|
|
138
|
-
// cutoff, and assert takes a caller-chosen `since` that is often 0. Impeaching every verdict
|
|
139
|
-
// over a wide window would make `unknown` the answer to everything — the failure this repo has
|
|
140
|
-
// already paid for once, when `unclean_capture` became the dominant cause of `unknown` in the
|
|
141
|
-
// field. The act path needs no such guard: its cursor is the action's own.
|
|
142
|
-
truncated: session.lostSince(since) && restsOnCompleteWindow(predicate),
|
|
167
|
+
truncated: bufferLost,
|
|
168
|
+
// The durable ledger stopped, and the SAME absence rule applies: a positive assertion that
|
|
169
|
+
// passed found its evidence, and events missing from the end of a file do not unmake it.
|
|
170
|
+
ledgerClosed: ledgerLost,
|
|
143
171
|
coveragePartial: Coverage.PARTIAL === statement.coverage,
|
|
144
172
|
...(statement.note === undefined ? {} : { coverageNote: statement.note }),
|
|
145
173
|
...(0 === impeachingNotes.length ? {} : { blindSpots: impeachingNotes }),
|
|
146
|
-
// Which loss, as an enum, beside the prose.
|
|
174
|
+
// Which loss, as an enum, beside the prose. Ours before the page's, and the memory one before
|
|
175
|
+
// the disk one: `verification.uncleanLoss` takes the first, and a window that lost both is
|
|
176
|
+
// more usefully reported as the eviction, which is the one a caller can still drive around.
|
|
147
177
|
losses: [
|
|
148
|
-
...(
|
|
149
|
-
|
|
150
|
-
: []),
|
|
178
|
+
...(bufferLost ? [CaptureLoss.BUFFER_LOSS] : []),
|
|
179
|
+
...(ledgerLost ? [CaptureLoss.JOURNAL_LOSS] : []),
|
|
151
180
|
...(gap === undefined ? [] : [CaptureLoss.TRANSPORT_GAP]),
|
|
152
181
|
...(impeaching.note === undefined ? [] : [CaptureLoss.BLIND_SPOT]),
|
|
153
182
|
],
|
|
@@ -190,6 +219,8 @@ changeUndeclared) {
|
|
|
190
219
|
pass,
|
|
191
220
|
lastActSource: session.lastAct.source(),
|
|
192
221
|
});
|
|
222
|
+
// Who can act on this verdict, derived from the clause that decided it — see `attributedTo` below.
|
|
223
|
+
const attributedTo = verdictAttributionOf(decision.verifiedReason);
|
|
193
224
|
return {
|
|
194
225
|
decision: decision,
|
|
195
226
|
contradictions,
|
|
@@ -199,6 +230,9 @@ changeUndeclared) {
|
|
|
199
230
|
claim: describeWaitTarget(predicate),
|
|
200
231
|
verified: decision.verified,
|
|
201
232
|
...(source === undefined ? {} : { source }),
|
|
233
|
+
// What answering this claim READ. Known here and nowhere later: a run folded from the journal
|
|
234
|
+
// has only what the journal kept, and without this it stamped `element` on every check.
|
|
235
|
+
kind: predicate.kind,
|
|
202
236
|
// The three facts that make a verdict something other than a pass/fail line. All three were
|
|
203
237
|
// known right here and none of them survived into the durable record, so nothing downstream
|
|
204
238
|
// could report them however well this moment understood them.
|
|
@@ -213,6 +247,25 @@ changeUndeclared) {
|
|
|
213
247
|
// arrived from a capture that could not be read, and only the first is worth asking about
|
|
214
248
|
// again -- which is what makes a later answer a CORRECTION rather than a second opinion.
|
|
215
249
|
reason: decision.verifiedReason,
|
|
250
|
+
/*
|
|
251
|
+
* WHOSE problem it is, in four values, finishing the sentence above.
|
|
252
|
+
*
|
|
253
|
+
* The reason names the deciding clause; this names who can act on it, which for an `unknown`
|
|
254
|
+
* is the only question the reader has. `environment` resolves by waiting, `could-not-see` by
|
|
255
|
+
* looking again with better coverage, `harness` by fixing Reticle or the call, and `code` is
|
|
256
|
+
* the only one that means go and change the app. Omitted on a proof, which has no owner.
|
|
257
|
+
*
|
|
258
|
+
* Derived here rather than inside the verdict: it is a pure function of a reason the engine
|
|
259
|
+
* already returns, and `core-coupling-only-shrinks` caps what the engine may borrow from core.
|
|
260
|
+
*
|
|
261
|
+
* Named `attributedTo`, not `attribution`, because two other things already carry that word:
|
|
262
|
+
* `honesty.attribution` a few lines up ("what the grade is attributed to" — the window), and
|
|
263
|
+
* `BugAttribution` in telemetry ("we found a defect, whose defect is it?"). Three meanings on
|
|
264
|
+
* one noun is how a reader ends up acting on the wrong one.
|
|
265
|
+
*/
|
|
266
|
+
...(attributedTo === undefined || VerdictAttribution.NONE === attributedTo
|
|
267
|
+
? {}
|
|
268
|
+
: { attributedTo }),
|
|
216
269
|
grade: gradeOfPredicate(predicate),
|
|
217
270
|
couldNotSee: spots.map((spot) => spot.kind),
|
|
218
271
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { carryReticleIdentity } from './lease-tools.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { navigateResult } from './act/navigation/navigate-result.js';
|
|
4
|
-
import { awaitArrival, idsAtTarget, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
|
|
4
|
+
import { absoluteTarget, awaitArrival, idsAtTarget, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
|
|
5
5
|
import { reloadResult } from './act/navigation/reload-result.js';
|
|
6
6
|
import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS, } from '../../portal/session/session-reconnect.js';
|
|
7
7
|
import { ReticleCommand } from '@reticlehq/core';
|
|
@@ -89,7 +89,8 @@ export const BROWSER_TOOLS = [
|
|
|
89
89
|
// Sampled BEFORE dispatch, because that is the only moment it can be known. A session already
|
|
90
90
|
// sitting on the target is not evidence that THIS navigation arrived — and the arrival scan,
|
|
91
91
|
// which reads whatever is at the target afterwards, cannot tell the two apart on its own.
|
|
92
|
-
const
|
|
92
|
+
const target = absoluteTarget(url, session.url);
|
|
93
|
+
const priorIds = idsAtTarget(deps.sessions, target);
|
|
93
94
|
session.beginAction(ReticleTool.NAVIGATE, { url });
|
|
94
95
|
// Same floor as the reload path above, for the same reason: going to a new URL replaces the
|
|
95
96
|
// document just as thoroughly. `beginAction` attributes events to this action; it does not move
|
|
@@ -103,7 +104,7 @@ export const BROWSER_TOOLS = [
|
|
|
103
104
|
// reticle_sessions itself. A refusal skips the wait: there is nothing to wait FOR.
|
|
104
105
|
const timeoutMs = asNumber(args['timeout_ms']) ?? ARRIVAL_TIMEOUT_MS;
|
|
105
106
|
const arrival = true === result.ok
|
|
106
|
-
? await awaitArrival(deps.sessions,
|
|
107
|
+
? await awaitArrival(deps.sessions, target, { navigatedId: session.id, priorIds }, timeoutMs)
|
|
107
108
|
: null;
|
|
108
109
|
return navigateResult(result, arrival, timeoutMs);
|
|
109
110
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
3
|
+
import { projectForRoot } from '../../memory/project/project-for-root.js';
|
|
2
4
|
import { coverageRegressed, observabilityOf } from '@reticlehq/engine/evidence/observability.js';
|
|
3
5
|
import { foldFeatureCapture } from './feature-capture.js';
|
|
4
6
|
import { foldToolHitRate } from './tool-hit-rate.js';
|
|
@@ -153,10 +155,11 @@ export function buildCoverageTools(toolNames) {
|
|
|
153
155
|
// The number, and the floor under it, together. A coverage figure that can only ever be
|
|
154
156
|
// reported and never contradicted is one an agent learns to satisfy rather than to earn.
|
|
155
157
|
const observability = observabilityOf(session.actedRefs(), gaps);
|
|
156
|
-
const
|
|
158
|
+
const project = projectForRoot(deps, sessionRoot(deps, sessionId));
|
|
159
|
+
const best = await project.bestObservability();
|
|
157
160
|
const regressed = coverageRegressed(best, observability);
|
|
158
161
|
if (observability.percent !== undefined) {
|
|
159
|
-
await
|
|
162
|
+
await project.raiseObservability(observability.percent);
|
|
160
163
|
}
|
|
161
164
|
return {
|
|
162
165
|
total: parseControls(tree).length,
|
|
@@ -2,36 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import { runTool } from './invoke-tool.js';
|
|
3
3
|
import { buildErrorPayload } from './error-recovery.js';
|
|
4
4
|
import { mergedNameRedirect, mergedNameMessage, retiredToolNames } from './merged-name-redirect.js';
|
|
5
|
+
import { CATALOGUE_SUMMARY_LENGTH, firstSentence } from './first-sentence.js';
|
|
5
6
|
import { takeVersionSkewOnto } from '../../command/version/version-nudge.js';
|
|
6
7
|
import { ReticleTool } from '@reticlehq/core';
|
|
7
8
|
import { ADVERTISE_ALL_ENV } from './tool-surface.js';
|
|
8
9
|
import { getSessionMetrics } from '../../telemetry/session-metrics.js';
|
|
9
10
|
import { isPredicateParam, predicateGrammar, } from '@reticlehq/engine/question/predicate/predicate-eval.js';
|
|
10
|
-
/**
|
|
11
|
-
* On-demand tool loading for MCP — the answer to the per-turn tool-definition tax.
|
|
12
|
-
*
|
|
13
|
-
* A normal MCP server advertises every tool's full description + schema, and the client re-sends
|
|
14
|
-
* all of them to the model on EVERY turn. Measured here: Reticle at ~5.6k–14.6k tokens/turn just for
|
|
15
|
-
* definitions, which dominates a real agent loop and only grows as the tool surface grows.
|
|
16
|
-
*
|
|
17
|
-
* The `dynamic` profile advertises just TWO meta-tools (~hundreds of tokens, fixed regardless of
|
|
18
|
-
* how many real tools exist):
|
|
19
|
-
* reticle_tools — discover. No args ⇒ a compact catalog (name + one-line summary) of every tool.
|
|
20
|
-
* names:[...] ⇒ full description + params for just those tools, loaded on demand.
|
|
21
|
-
* reticle_run — invoke any tool by name. On a bad/unknown call it returns that tool's params as a
|
|
22
|
-
* hint, so the model self-corrects without ever needing the schema up front.
|
|
23
|
-
*
|
|
24
|
-
* The model lists once, loads the 2–3 tools it actually needs, and calls them — paying for tool
|
|
25
|
-
* detail only when used, not every turn. Works with any MCP client (no client-side support needed).
|
|
26
|
-
*/
|
|
27
|
-
/** First sentence (purpose) of a description — keeps the catalog one line per tool. */
|
|
28
|
-
function firstSentence(description) {
|
|
29
|
-
const nl = description.indexOf('\n');
|
|
30
|
-
const base = nl >= 0 ? description.slice(0, nl) : description;
|
|
31
|
-
const dot = base.search(/\.\s/);
|
|
32
|
-
const sentence = dot >= 0 ? base.slice(0, dot + 1) : base;
|
|
33
|
-
return sentence.length > 140 ? `${sentence.slice(0, 139)}…` : sentence;
|
|
34
|
-
}
|
|
35
11
|
/** Compact param list from a tool's zod shape — name/required/description (the description already
|
|
36
12
|
* carries the type and any enum hints, so no JSON-Schema machinery is needed). */
|
|
37
13
|
function paramInfo(shape) {
|
|
@@ -49,6 +25,8 @@ function paramInfo(shape) {
|
|
|
49
25
|
function unknownParamsError(toolName, unknown) {
|
|
50
26
|
return `unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')} — NOT applied, so any result would be an answer to a different question`;
|
|
51
27
|
}
|
|
28
|
+
/** The one `action` reticle_tools takes: every other tool on the surface is action-dispatched. */
|
|
29
|
+
const TOOLS_LIST_ACTION = 'list';
|
|
52
30
|
/** What to write instead — the one parameter reticle_tools declares, and the no-argument form. */
|
|
53
31
|
const TOOLS_ARG_HINT = 'name the tools you want in `names`: reticle_tools { names: ["reticle_act_and_wait"] } — or call it with no arguments for the full catalog';
|
|
54
32
|
/** The keys not declared by `shape`, in call order. */
|
|
@@ -73,6 +51,33 @@ export function buildDynamicTools(allTools, profile,
|
|
|
73
51
|
callable) {
|
|
74
52
|
const reachable = callable === undefined ? allTools : allTools.filter((tool) => callable.has(tool.name));
|
|
75
53
|
const byName = new Map(reachable.map((t) => [t.name, t]));
|
|
54
|
+
const registered = new Map(allTools.map((tool) => [tool.name, tool]));
|
|
55
|
+
/**
|
|
56
|
+
* A name the catalogue cannot hand back as a callable tool.
|
|
57
|
+
*
|
|
58
|
+
* A redirect wins, because the capability moved and the old name is how an older instruction
|
|
59
|
+
* still asks for it. A name that is a real tool on a surface that cannot call it is the other
|
|
60
|
+
* case: `unknown tool` is what a typo gets, and a headline capability must not get the same
|
|
61
|
+
* sentence (#977). A name in neither set is a typo.
|
|
62
|
+
*/
|
|
63
|
+
const explainAbsent = (name) => {
|
|
64
|
+
const moved = mergedNameRedirect(name);
|
|
65
|
+
if (moved !== undefined) {
|
|
66
|
+
return {
|
|
67
|
+
error: mergedNameMessage(name, moved),
|
|
68
|
+
tool: moved.tool,
|
|
69
|
+
...(moved.action === undefined ? {} : { action: moved.action }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (callable !== undefined && registered.has(name)) {
|
|
73
|
+
return {
|
|
74
|
+
error: `${name} is a Reticle tool and this surface cannot call it. Restart the daemon with ` +
|
|
75
|
+
`${ADVERTISE_ALL_ENV}=1, then call reticle_run { tool: "${name}", args }.`,
|
|
76
|
+
tool: name,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return { error: 'unknown tool' };
|
|
80
|
+
};
|
|
76
81
|
// The profile is a DAEMON-startup decision, so an agent that exported RETICLE_TOOL_PROFILE into its
|
|
77
82
|
// own environment sees no change and has, until now, no way to tell. Reported with the catalog.
|
|
78
83
|
const profileBlock = profile === undefined
|
|
@@ -89,6 +94,10 @@ callable) {
|
|
|
89
94
|
},
|
|
90
95
|
};
|
|
91
96
|
const toolsShape = {
|
|
97
|
+
action: z
|
|
98
|
+
.literal(TOOLS_LIST_ACTION)
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('"list": the catalogue, same as no arguments.'),
|
|
92
101
|
names: z
|
|
93
102
|
.array(z.string())
|
|
94
103
|
.optional()
|
|
@@ -135,7 +144,7 @@ callable) {
|
|
|
135
144
|
// turns "these are the tools" into "these are the tools I was shown".
|
|
136
145
|
const catalog = reachable.map((t) => ({
|
|
137
146
|
name: t.name,
|
|
138
|
-
summary: firstSentence(t.description),
|
|
147
|
+
summary: firstSentence(t.description, CATALOGUE_SUMMARY_LENGTH),
|
|
139
148
|
}));
|
|
140
149
|
// Names that USED to be tools, against the call that replaces each. Instructions written
|
|
141
150
|
// against an earlier release are a permanent fact of the product — `reticle_crawl` is still
|
|
@@ -171,15 +180,7 @@ callable) {
|
|
|
171
180
|
// `reticle_run` has answered a merged name with its replacement for a while; the tool
|
|
172
181
|
// asked to DISCOVER tools still said "unknown tool", which is the wrong answer in the one
|
|
173
182
|
// place an agent goes to resolve a name it is unsure of. Same redirect, same derivation.
|
|
174
|
-
|
|
175
|
-
return moved === undefined
|
|
176
|
-
? { name: n, error: 'unknown tool' }
|
|
177
|
-
: {
|
|
178
|
-
name: n,
|
|
179
|
-
error: mergedNameMessage(n, moved),
|
|
180
|
-
tool: moved.tool,
|
|
181
|
-
...(moved.action === undefined ? {} : { action: moved.action }),
|
|
182
|
-
};
|
|
183
|
+
return { name: n, ...explainAbsent(n) };
|
|
183
184
|
}),
|
|
184
185
|
...(carriesPredicate ? { predicateGrammar: predicateGrammar() } : {}),
|
|
185
186
|
});
|
|
@@ -190,9 +191,10 @@ callable) {
|
|
|
190
191
|
args: z.record(z.unknown()).optional().describe('Arguments object for that tool.'),
|
|
191
192
|
// Accepted AND FORWARDED, not merely tolerated. reticle_run is the only way to reach an
|
|
192
193
|
// unadvertised tool, so on a machine running several projects it has to be aimable — and
|
|
193
|
-
// `sessionId` is the shape an agent already uses on every other tool. Reported
|
|
194
|
-
//
|
|
195
|
-
// "no browser session for project X" while naming the very session it
|
|
194
|
+
// `sessionId` is the shape an agent already uses on every other tool. Reported from the field
|
|
195
|
+
// against every app it was tried on: it took this key, dropped it, resolved by the daemon's cwd
|
|
196
|
+
// project, and failed with "no browser session for project X" while naming the very session it
|
|
197
|
+
// had been given.
|
|
196
198
|
sessionId: z
|
|
197
199
|
.string()
|
|
198
200
|
.optional()
|
|
@@ -232,14 +234,9 @@ callable) {
|
|
|
232
234
|
getSessionMetrics().recordUnknownTool(name);
|
|
233
235
|
// ...and for 22 of those names the capability DOES exist, it just moved when tools merged.
|
|
234
236
|
// Answering "unknown tool" there is simply wrong; see merged-name-redirect.
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
238
|
-
error: mergedNameMessage(name, moved),
|
|
239
|
-
tool: moved.tool,
|
|
240
|
-
...(moved.action === undefined ? {} : { action: moved.action }),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
237
|
+
const absent = explainAbsent(name);
|
|
238
|
+
if ('unknown tool' !== absent.error)
|
|
239
|
+
return absent;
|
|
243
240
|
return { error: `unknown tool '${name}'`, available: reachable.map((t) => t.name) };
|
|
244
241
|
}
|
|
245
242
|
// The outer aim, forwarded ONLY to a tool that declares a session — injecting it into one that
|
|
@@ -66,6 +66,12 @@ export declare const RECOVERY: {
|
|
|
66
66
|
readonly COMMAND_TIMEOUT: string;
|
|
67
67
|
readonly FLOW_STEP_MISSING: string;
|
|
68
68
|
readonly NO_POOL: string;
|
|
69
|
+
/**
|
|
70
|
+
* The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
|
|
71
|
+
* agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
|
|
72
|
+
* who deliberately switched the harness off gets a bug report filed about their own decision.
|
|
73
|
+
*/
|
|
74
|
+
readonly HARNESS_OFF: string;
|
|
69
75
|
};
|
|
70
76
|
/**
|
|
71
77
|
* The actionable next move for a known error message, or undefined when none is recognized.
|
|
@@ -199,6 +199,15 @@ export const RECOVERY = {
|
|
|
199
199
|
chromiumInstallCommand(bundledPlaywrightVersion()) +
|
|
200
200
|
'` if that is what is missing. Meanwhile drive ' +
|
|
201
201
|
'a tab the human already has open — reticle_sessions lists them.',
|
|
202
|
+
/**
|
|
203
|
+
* The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
|
|
204
|
+
* agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
|
|
205
|
+
* who deliberately switched the harness off gets a bug report filed about their own decision.
|
|
206
|
+
*/
|
|
207
|
+
HARNESS_OFF: 'A human turned autonomous driving off for this project, or has not claimed the free harness ' +
|
|
208
|
+
'months. This is a deliberate refusal and there is nothing to report: drive the app yourself ' +
|
|
209
|
+
'through the MCP tools — reticle_navigate, then reticle_act_and_wait with an `until` — which is ' +
|
|
210
|
+
'the same verification without a model driving it.',
|
|
202
211
|
};
|
|
203
212
|
/**
|
|
204
213
|
* Which OWNER each recovery belongs to, for `tool_refused`.
|
|
@@ -236,6 +245,8 @@ const REASON_OF = {
|
|
|
236
245
|
WRONG_TARGET: RefusalReason.UNSUPPORTED,
|
|
237
246
|
BAD_ARGUMENTS: RefusalReason.BAD_ARGS,
|
|
238
247
|
INVALID_NAME: RefusalReason.BAD_ARGS,
|
|
248
|
+
// Nothing about the app or the call is wrong; the feature is switched off or unpaid for.
|
|
249
|
+
HARNESS_OFF: RefusalReason.UNSUPPORTED,
|
|
239
250
|
};
|
|
240
251
|
/** Hint text back to its reason. The hints are distinct strings, so this inverts cleanly. */
|
|
241
252
|
const REASON_BY_HINT = new Map(Object.entries(RECOVERY).map(([key, hint]) => [hint, REASON_OF[key]]));
|
|
@@ -248,6 +259,10 @@ const RULES = [
|
|
|
248
259
|
{ match: /no baseline named/i, hint: RECOVERY.MISSING_BASELINE },
|
|
249
260
|
{ match: /no (?:active|compiled) recording named/i, hint: RECOVERY.MISSING_RECORDING },
|
|
250
261
|
{ match: /pairing token is required/i, hint: RECOVERY.TOKEN_REQUIRED },
|
|
262
|
+
{
|
|
263
|
+
match: /Autonomous driving is turned OFF|no harness entitlement/i,
|
|
264
|
+
hint: RECOVERY.HARNESS_OFF,
|
|
265
|
+
},
|
|
251
266
|
// Three conditions the daemon understands perfectly and still asked for a bug report about. Each
|
|
252
267
|
// needs its own rule: none of them contains "no browser session connected" (the scope miss is
|
|
253
268
|
// "no browser session FOR project 'x'", which is the opposite claim — sessions exist).
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
* them deterministically with no model in the loop at all.
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
-
import { ReticleTool } from '@reticlehq/core';
|
|
14
|
+
import { ReticleTool, asRecord } from '@reticlehq/core';
|
|
15
15
|
import { stepCountSchema } from './args/numeric-bounds.js';
|
|
16
|
-
import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY } from './harness-explore.js';
|
|
16
|
+
import { exploreApp, harnessAvailable, withLinkedCredential, MSG_NO_HARNESS_KEY, } from './harness-explore.js';
|
|
17
|
+
import { DRIVER_NAMES } from '../../features/harness/drivers.js';
|
|
18
|
+
import { describeDrive, replayedFlows } from '../../features/harness/drive-report.js';
|
|
17
19
|
import { StopReason } from '../../features/harness/harness.js';
|
|
18
20
|
export const EXPLORE_TOOLS = [
|
|
19
21
|
{
|
|
@@ -27,6 +29,10 @@ export const EXPLORE_TOOLS = [
|
|
|
27
29
|
maxSteps: stepCountSchema
|
|
28
30
|
.optional()
|
|
29
31
|
.describe('Ceiling on model turns. Bounds cost, not value — the drive is graded however it ends.'),
|
|
32
|
+
driver: z
|
|
33
|
+
.enum(DRIVER_NAMES)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Which model drives. An unconfigured one is an error, never a substitution, so an A/B cannot measure the same driver twice. Omit for the default.'),
|
|
30
36
|
sessionId: z
|
|
31
37
|
.string()
|
|
32
38
|
.optional()
|
|
@@ -35,9 +41,24 @@ export const EXPLORE_TOOLS = [
|
|
|
35
41
|
// Everything the handler returns has to be declared, or a schema-aware client never sees it.
|
|
36
42
|
outputSchema: {
|
|
37
43
|
stopReason: z.string(),
|
|
44
|
+
/** Which driver actually drove, so a comparison can never misattribute its own result. */
|
|
45
|
+
driver: z.string(),
|
|
38
46
|
steps: z.number(),
|
|
39
47
|
savedFlows: z.array(z.string()),
|
|
40
|
-
/**
|
|
48
|
+
/** Flows that already existed and were driven and written again. A second run's ordinary result. */
|
|
49
|
+
rewroteFlows: z.array(z.string()),
|
|
50
|
+
/**
|
|
51
|
+
* What the drive set out to do, read from `.reticle` BEFORE it started — every recorded
|
|
52
|
+
* journey with the consequence that must still hold, and the declared intent nobody has
|
|
53
|
+
* tested. Returned so the caller can see the drive was aimed rather than wandering.
|
|
54
|
+
*/
|
|
55
|
+
plan: z.object({ summary: z.string(), steps: z.array(z.unknown()) }),
|
|
56
|
+
/**
|
|
57
|
+
* What the drive did, derived from the calls it made and the verdicts the engine returned.
|
|
58
|
+
*
|
|
59
|
+
* Not the driver's narration. A driver that cannot write a sentence used to leave this empty,
|
|
60
|
+
* and a driver that can is the one witness with a reason to round "unknown" up to "worked".
|
|
61
|
+
*/
|
|
41
62
|
summary: z.string(),
|
|
42
63
|
/** Present when the drive broke: a model that would not answer, a wedged browser. */
|
|
43
64
|
error: z.string().optional(),
|
|
@@ -52,28 +73,80 @@ export const EXPLORE_TOOLS = [
|
|
|
52
73
|
note: z.string().optional(),
|
|
53
74
|
},
|
|
54
75
|
handler: async (deps, args) => {
|
|
55
|
-
|
|
76
|
+
// The credential `reticle link` already filed counts as configured, so somebody who has
|
|
77
|
+
// signed in and linked does not also have to export a key by hand.
|
|
78
|
+
const env = await withLinkedCredential(deps, process.env);
|
|
79
|
+
if (!harnessAvailable(env))
|
|
56
80
|
throw new Error(MSG_NO_HARNESS_KEY);
|
|
57
81
|
const persona = args['persona'];
|
|
58
82
|
const maxSteps = args['maxSteps'];
|
|
59
83
|
const sessionId = args['sessionId'];
|
|
60
|
-
const
|
|
84
|
+
const driver = args['driver'];
|
|
85
|
+
const { drive, savedFlows, rewroteFlows, driverName, plan } = await exploreApp(deps, env, {
|
|
61
86
|
...('string' === typeof persona ? { focus: persona } : {}),
|
|
62
87
|
...('number' === typeof maxSteps ? { maxSteps } : {}),
|
|
63
88
|
...('string' === typeof sessionId ? { sessionId } : {}),
|
|
89
|
+
...('string' === typeof driver ? { driverName: driver } : {}),
|
|
64
90
|
});
|
|
65
91
|
return {
|
|
66
92
|
stopReason: drive.stopReason,
|
|
93
|
+
driver: driverName,
|
|
67
94
|
steps: drive.steps,
|
|
68
95
|
savedFlows: [...savedFlows],
|
|
69
|
-
|
|
96
|
+
rewroteFlows: [...rewroteFlows],
|
|
97
|
+
plan: { summary: plan.summary, steps: [...plan.steps] },
|
|
98
|
+
// Derived, not narrated. The driver's own `summary` is appended only when it said
|
|
99
|
+
// something — it is the one part of this a model authored, so it goes last and is labelled.
|
|
100
|
+
summary: [
|
|
101
|
+
describeDrive(drive.toolCalls, [...savedFlows, ...rewroteFlows]),
|
|
102
|
+
...(0 === drive.summary.length ? [] : [`The driver's own account: ${drive.summary}`]),
|
|
103
|
+
].join('\n'),
|
|
70
104
|
...(drive.error === undefined ? {} : { error: drive.error }),
|
|
71
105
|
usage: drive.usage,
|
|
72
|
-
|
|
106
|
+
// The note only fires when the drive left NOTHING behind. A rewritten flow is a flow: it
|
|
107
|
+
// replays, it proves what it asserts, and telling its author that "nothing will replay" is
|
|
108
|
+
// a lie this tool used to tell on every second run.
|
|
109
|
+
/*
|
|
110
|
+
* The note fires only when the run left NOTHING behind — and a run that REPLAYED left
|
|
111
|
+
* plenty. Sixteen recorded journeys re-proved for zero model tokens is the cheap half of
|
|
112
|
+
* the plan doing its job, and telling its author to "raise maxSteps" reads as a failure.
|
|
113
|
+
*/
|
|
114
|
+
...(0 === savedFlows.length &&
|
|
115
|
+
0 === rewroteFlows.length &&
|
|
116
|
+
0 === replayedFlows(drive.toolCalls).length
|
|
117
|
+
? { note: `${NOTHING_RECORDED[drive.stopReason]}${failureDetail(drive)}` }
|
|
118
|
+
: {}),
|
|
73
119
|
};
|
|
74
120
|
},
|
|
75
121
|
},
|
|
76
122
|
];
|
|
123
|
+
/**
|
|
124
|
+
* The tool failure behind an empty drive, when there was one.
|
|
125
|
+
*
|
|
126
|
+
* "The drive ran out of steps before saving a flow" is true of a drive that explored happily and ran
|
|
127
|
+
* long, and ALSO of one that reached its save and was refused — and those need opposite responses.
|
|
128
|
+
* Without this the two are indistinguishable from outside the daemon, which cost a full debugging
|
|
129
|
+
* session: a driver whose `reticle_record{stop}` was rejected for a missing argument retried until
|
|
130
|
+
* the budget ended, and the only thing the caller ever saw was the advice to raise `maxSteps`.
|
|
131
|
+
*/
|
|
132
|
+
function failureDetail(drive) {
|
|
133
|
+
const failed = drive.toolCalls.filter((call) => call.isError);
|
|
134
|
+
const last = failed[failed.length - 1];
|
|
135
|
+
if (last !== undefined) {
|
|
136
|
+
const message = asRecord(last.result)['error'];
|
|
137
|
+
return ` ${String(failed.length)} tool call(s) failed during the drive; the last was ${last.name}: ${'string' === typeof message ? message : 'no message'}`;
|
|
138
|
+
}
|
|
139
|
+
// Nothing threw, so the interesting case left is a save that was ACCEPTED and wrote nothing —
|
|
140
|
+
// an empty recording reports success, and from outside the daemon that is indistinguishable from
|
|
141
|
+
// a drive that never reached its save at all.
|
|
142
|
+
const saves = drive.toolCalls.filter((call) => ReticleTool.FLOW_SAVE === call.name);
|
|
143
|
+
const save = saves[saves.length - 1];
|
|
144
|
+
if (save !== undefined) {
|
|
145
|
+
const result = asRecord(save.result);
|
|
146
|
+
return ` The flow save was accepted but kept nothing: ${JSON.stringify({ stepCount: result['stepCount'], empty: result['empty'], warning: result['warning'] })}.`;
|
|
147
|
+
}
|
|
148
|
+
return ` The drive never reached a flow save (${String(drive.toolCalls.length)} tool calls made).`;
|
|
149
|
+
}
|
|
77
150
|
/**
|
|
78
151
|
* What an empty drive means, said rather than left for the caller to infer.
|
|
79
152
|
*
|