@reticlehq/server 3.4.0 → 3.6.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/README.md +1 -1
- package/SKILL.md +16 -8
- package/dist/chromium-launch-options.d.ts +3 -1
- package/dist/chromium-launch-options.js +2 -1
- package/dist/command/cli/adhoc-verdict.d.ts +1 -0
- package/dist/command/cli/adhoc-verdict.js +6 -3
- package/dist/command/cli/answers/open-note.d.ts +2 -0
- package/dist/command/cli/answers/open-note.js +22 -0
- package/dist/command/cli/auth/cloud-session.d.ts +4 -4
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-flow-commands.js +4 -8
- package/dist/command/cli/cli-parse.d.ts +1 -1
- package/dist/command/cli/cli-parse.js +13 -11
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +35 -25
- package/dist/command/cli/cloud-cli.js +110 -12
- package/dist/command/cli/cloud-login.d.ts +9 -1
- package/dist/command/cli/cloud-login.js +43 -8
- package/dist/command/cli/demo/demo-page.js +11 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
- package/dist/command/cli/drive/drive-attach.d.ts +27 -2
- package/dist/command/cli/drive/drive-attach.js +71 -4
- package/dist/command/cli/drive/drive-command.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +13 -7
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
- package/dist/command/cli/ports/port-holder.d.ts +10 -0
- package/dist/command/cli/ports/port-holder.js +24 -0
- package/dist/command/cli/setup-mcp-cli.js +15 -5
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +6 -9
- package/dist/command/cli.d.ts +6 -0
- package/dist/command/cli.js +45 -23
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
- package/dist/command/daemon/daemon-resolve.d.ts +14 -0
- package/dist/command/daemon/daemon-resolve.js +33 -2
- package/dist/command/setup/agent-writer.d.ts +1 -1
- package/dist/command/setup/agent-writer.js +3 -2
- package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
- package/dist/command/setup/bringup/connect-lease.js +1 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
- package/dist/command/setup/bringup/dev-server-wait.js +20 -1
- package/dist/command/setup/init/init-port.d.ts +4 -0
- package/dist/command/setup/init/init-port.js +10 -0
- package/dist/command/setup/init/init-runtime.d.ts +10 -0
- package/dist/command/setup/init/init-runtime.js +71 -9
- package/dist/command/setup/node-effects.js +8 -2
- package/dist/command/setup/remaining-steps.js +2 -1
- package/dist/command/setup/run-setup.d.ts +7 -0
- package/dist/command/setup/run-setup.js +38 -25
- package/dist/command/setup/setup-command.d.ts +2 -1
- package/dist/command/setup/setup-command.js +2 -0
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +14 -5
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/command/version/version-skew.js +11 -2
- package/dist/features/crawl/crawl-tools.js +4 -0
- package/dist/features/crawl/crawl.d.ts +6 -0
- package/dist/features/crawl/crawl.js +9 -1
- package/dist/features/exhaust/ledger.d.ts +7 -0
- package/dist/features/exhaust/ledger.js +35 -9
- package/dist/features/harness/app-findings.d.ts +2 -0
- package/dist/features/harness/app-findings.js +52 -0
- package/dist/features/harness/drive-report.d.ts +27 -1
- package/dist/features/harness/drive-report.js +162 -11
- package/dist/features/harness/drivers.d.ts +10 -14
- package/dist/features/harness/drivers.js +10 -14
- package/dist/features/harness/fill-values.d.ts +3 -69
- package/dist/features/harness/fill-values.js +3 -133
- package/dist/features/harness/goals.d.ts +21 -0
- package/dist/features/harness/goals.js +77 -0
- package/dist/features/harness/harness.d.ts +21 -0
- package/dist/features/harness/harness.js +69 -2
- package/dist/features/harness/platform/drive-target.d.ts +30 -0
- package/dist/features/harness/platform/drive-target.js +81 -0
- package/dist/features/harness/platform/local-apps.d.ts +94 -0
- package/dist/features/harness/platform/local-apps.js +204 -0
- package/dist/features/harness/platform/remote-drive.d.ts +180 -0
- package/dist/features/harness/platform/remote-drive.js +365 -0
- package/dist/features/harness/platform/script.d.ts +48 -0
- package/dist/features/harness/platform/script.js +128 -0
- package/dist/features/harness/platform/server-driver.d.ts +41 -0
- package/dist/features/harness/platform/server-driver.js +179 -0
- package/dist/features/harness/platform-config.d.ts +7 -0
- package/dist/features/harness/platform-config.js +14 -2
- package/dist/features/harness/script/app-controls.d.ts +5 -0
- package/dist/features/harness/script/app-controls.js +40 -0
- package/dist/features/harness/script/lane-ids.d.ts +14 -0
- package/dist/features/harness/script/lane-ids.js +16 -0
- package/dist/features/harness/script/script-run.d.ts +41 -0
- package/dist/features/harness/script/script-run.js +299 -0
- package/dist/features/license/license.d.ts +2 -2
- package/dist/features/visual/visual-tools.d.ts +10 -1
- package/dist/features/visual/visual-tools.js +40 -3
- package/dist/hooks/driven-by.d.ts +4 -0
- package/dist/hooks/driven-by.js +16 -0
- package/dist/hooks/hook-commands.js +15 -4
- package/dist/hooks/tool-log.d.ts +9 -0
- package/dist/hooks/tool-log.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +91 -89
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
- package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
- package/dist/judgement/runs/artifact/run-store.js +26 -1
- package/dist/judgement/runs/drive-run.d.ts +22 -0
- package/dist/judgement/runs/drive-run.js +99 -1
- package/dist/judgement/runs/repair-prompt.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +8 -1
- package/dist/judgement/runs/verification-sync.d.ts +3 -2
- package/dist/judgement/runs/verification-sync.js +4 -3
- package/dist/language/flows/change/gate.d.ts +10 -0
- package/dist/language/flows/change/gate.js +22 -0
- package/dist/language/flows/decision.js +34 -0
- package/dist/language/flows/flow-anchor.d.ts +54 -8
- package/dist/language/flows/flow-anchor.js +88 -3
- package/dist/language/flows/flow-cross-step.d.ts +15 -0
- package/dist/language/flows/flow-cross-step.js +21 -0
- package/dist/language/flows/flow-learning.js +8 -11
- package/dist/language/flows/flow-mutate-tools.js +5 -3
- package/dist/language/flows/flow-replay-run.d.ts +23 -9
- package/dist/language/flows/flow-replay-run.js +101 -94
- package/dist/language/flows/flow-replay.d.ts +23 -1
- package/dist/language/flows/flow-replay.js +92 -19
- package/dist/language/flows/flow-scope.js +5 -1
- package/dist/language/flows/flow-step-runners.d.ts +20 -2
- package/dist/language/flows/flow-step-runners.js +236 -42
- package/dist/language/flows/flow-store-for-session.d.ts +2 -2
- package/dist/language/flows/flow-store-for-session.js +2 -2
- package/dist/language/flows/flow-tools.js +58 -34
- package/dist/language/flows/flows.js +5 -2
- package/dist/language/flows/heal-run.js +6 -4
- package/dist/language/flows/recording/flake.d.ts +2 -2
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
- package/dist/language/flows/recording/tape/recordings.js +40 -8
- package/dist/language/flows/replay-from-hud.d.ts +20 -0
- package/dist/language/flows/replay-from-hud.js +51 -0
- package/dist/language/flows/replay.d.ts +9 -5
- package/dist/language/flows/replay.js +27 -9
- package/dist/language/flows/suite/suite-flakes.js +2 -0
- package/dist/language/flows/suite-selection.d.ts +2 -15
- package/dist/language/flows/suite-selection.js +4 -3
- package/dist/language/flows/verify-change-tools.js +8 -4
- package/dist/launch-chromium.d.ts +27 -5
- package/dist/launch-chromium.js +38 -8
- package/dist/memory/cloud/cloud-config.d.ts +12 -0
- package/dist/memory/cloud/cloud-config.js +15 -0
- package/dist/memory/cloud/cloud-sync.d.ts +17 -14
- package/dist/memory/cloud/cloud-sync.js +12 -1
- package/dist/memory/cloud/harness-config.d.ts +26 -1
- package/dist/memory/cloud/harness-config.js +99 -4
- package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
- package/dist/memory/cloud/hud-notices-source.js +128 -0
- package/dist/memory/cloud/memory-scope.d.ts +144 -0
- package/dist/memory/cloud/memory-scope.js +205 -0
- package/dist/memory/cloud/sync-cycle.d.ts +21 -3
- package/dist/memory/cloud/sync-cycle.js +45 -5
- package/dist/memory/cloud/sync-daemon.js +7 -5
- package/dist/memory/cloud/sync-disk.d.ts +1 -1
- package/dist/memory/cloud/sync-disk.js +5 -8
- package/dist/memory/impact/account-watch.d.ts +19 -0
- package/dist/memory/impact/account-watch.js +34 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +12 -2
- package/dist/memory/impact/impact-store.d.ts +16 -0
- package/dist/memory/impact/impact-store.js +142 -26
- package/dist/memory/intent/intent-shard.d.ts +2 -2
- package/dist/memory/intent/intent-tools.js +88 -5
- package/dist/memory/journal/envelope.d.ts +16 -16
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/journal-recorder.js +7 -1
- package/dist/memory/journal/on-disk/retention.d.ts +3 -0
- package/dist/memory/journal/on-disk/retention.js +5 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
- package/dist/memory/journal/session-end.js +6 -4
- package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
- package/dist/memory/project/artifact-root-resolver.js +134 -26
- package/dist/memory/project/artifact-root.d.ts +42 -5
- package/dist/memory/project/artifact-root.js +87 -8
- package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
- package/dist/memory/project/dir/reticle-dir.js +3 -1
- package/dist/memory/project/project-tools.js +22 -3
- package/dist/memory/project/session-root.d.ts +24 -1
- package/dist/memory/project/session-root.js +41 -5
- package/dist/memory/project/sync-status.d.ts +39 -0
- package/dist/memory/project/sync-status.js +90 -0
- package/dist/memory/recall/memory-tools.js +2 -0
- package/dist/memory/recall/project-memory.d.ts +19 -0
- package/dist/memory/recall/project-memory.js +45 -6
- package/dist/portal/bridge/bridge.d.ts +7 -4
- package/dist/portal/bridge/bridge.js +25 -3
- package/dist/portal/bridge/pairing-token.d.ts +9 -0
- package/dist/portal/bridge/pairing-token.js +17 -0
- package/dist/portal/input/real-input.d.ts +81 -10
- package/dist/portal/input/real-input.js +207 -22
- package/dist/portal/input/viewport-tools.js +19 -9
- package/dist/portal/pool/browser-pool.d.ts +22 -9
- package/dist/portal/pool/browser-pool.js +70 -35
- package/dist/portal/pool/playwright-launcher.d.ts +13 -0
- package/dist/portal/pool/playwright-launcher.js +17 -1
- package/dist/portal/pool/pool-contract.d.ts +7 -4
- package/dist/portal/session/command-payload.d.ts +8 -0
- package/dist/portal/session/command-payload.js +19 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
- package/dist/portal/session/dev-server/dev-command.js +61 -12
- package/dist/portal/session/facts/departure-note.d.ts +22 -0
- package/dist/portal/session/facts/departure-note.js +47 -0
- package/dist/portal/session/facts/session-replaced.d.ts +5 -0
- package/dist/portal/session/facts/session-replaced.js +5 -0
- package/dist/portal/session/first-run-wiring.d.ts +42 -0
- package/dist/portal/session/first-run-wiring.js +132 -0
- package/dist/portal/session/human/notes-ledger.d.ts +20 -0
- package/dist/portal/session/human/notes-ledger.js +55 -0
- package/dist/portal/session/human/review-store.d.ts +12 -0
- package/dist/portal/session/human/review-store.js +18 -10
- package/dist/portal/session/live-control-tools.d.ts +0 -13
- package/dist/portal/session/live-control-tools.js +28 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
- package/dist/portal/session/no-session-diagnosis.js +15 -0
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +31 -5
- package/dist/portal/session/no-session-watch.d.ts +14 -1
- package/dist/portal/session/no-session-watch.js +63 -10
- package/dist/portal/session/presence/session-mirror.js +3 -0
- package/dist/portal/session/serving-directory.d.ts +27 -0
- package/dist/portal/session/serving-directory.js +45 -0
- package/dist/portal/session/session-health.d.ts +7 -0
- package/dist/portal/session/session-health.js +6 -0
- package/dist/portal/session/session-info.d.ts +8 -1
- package/dist/portal/session/session-info.js +6 -0
- package/dist/portal/session/session-manager.d.ts +21 -0
- package/dist/portal/session/session-manager.js +44 -8
- package/dist/portal/session/session-reaper.js +4 -1
- package/dist/portal/session/session-successor.d.ts +5 -0
- package/dist/portal/session/session-tools.js +26 -3
- package/dist/portal/session/session.d.ts +12 -10
- package/dist/portal/session/session.js +30 -31
- package/dist/start-options.d.ts +6 -0
- package/dist/status-payload.d.ts +10 -0
- package/dist/status-payload.js +23 -0
- package/dist/surface/http-server.d.ts +1 -1
- package/dist/surface/http-server.js +15 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
- package/dist/surface/mcp/mcp-proxy.js +36 -5
- package/dist/surface/mcp/mcp.d.ts +11 -6
- package/dist/surface/mcp/mcp.js +28 -14
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.d.ts +4 -2
- package/dist/surface/mcp/server-instructions.js +14 -4
- package/dist/surface/mcp/shared-params.js +2 -2
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
- package/dist/surface/tools/act/settle-in-flight.js +25 -3
- package/dist/surface/tools/act-sequence-tool.js +2 -2
- package/dist/surface/tools/act-tools.js +25 -9
- package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +17 -2
- package/dist/surface/tools/browser-tools.js +8 -1
- package/dist/surface/tools/chat-drives.d.ts +26 -0
- package/dist/surface/tools/chat-drives.js +165 -0
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/empty-session-list.d.ts +41 -0
- package/dist/surface/tools/empty-session-list.js +80 -0
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +38 -6
- package/dist/surface/tools/explore-tools.d.ts +10 -1
- package/dist/surface/tools/explore-tools.js +82 -13
- package/dist/surface/tools/feedback-tools.js +14 -0
- package/dist/surface/tools/harness-explore.d.ts +96 -14
- package/dist/surface/tools/harness-explore.js +227 -202
- package/dist/surface/tools/harness-plan.d.ts +12 -0
- package/dist/surface/tools/harness-plan.js +31 -0
- package/dist/surface/tools/harness-script.d.ts +8 -0
- package/dist/surface/tools/harness-script.js +342 -0
- package/dist/surface/tools/harness-toolset.d.ts +14 -1
- package/dist/surface/tools/harness-toolset.js +77 -6
- package/dist/surface/tools/invoke-tool.js +64 -9
- package/dist/surface/tools/lease-seed.d.ts +29 -0
- package/dist/surface/tools/lease-seed.js +123 -0
- package/dist/surface/tools/lease-tools.d.ts +3 -23
- package/dist/surface/tools/lease-tools.js +30 -131
- package/dist/surface/tools/live-call-text.js +2 -2
- package/dist/surface/tools/next-step.d.ts +19 -0
- package/dist/surface/tools/next-step.js +159 -0
- package/dist/surface/tools/observe-tools.js +2 -4
- package/dist/surface/tools/platform-moment.d.ts +12 -0
- package/dist/surface/tools/platform-moment.js +168 -0
- package/dist/surface/tools/query-hint-schema.d.ts +7 -0
- package/dist/surface/tools/query-hint-schema.js +10 -0
- package/dist/surface/tools/read-tools.js +8 -2
- package/dist/surface/tools/real-input-attempt.js +96 -13
- package/dist/surface/tools/tool-kit.d.ts +25 -1
- package/dist/surface/tools/tool-kit.js +4 -6
- package/dist/surface/tools/tools.js +20 -48
- package/dist/telemetry/argument-shape.js +1 -1
- package/dist/telemetry/hud-metrics.d.ts +2 -0
- package/dist/telemetry/hud-metrics.js +17 -0
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/telemetry/telemetry.d.ts +12 -0
- package/dist/telemetry/telemetry.js +43 -14
- package/dist/telemetry/verification-of.js +1 -0
- package/dist/wire-journal.js +4 -1
- package/docs/README.md +1 -1
- package/docs/actions.mdx +1 -2
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/autodrive.md +29 -25
- package/docs/cli/cloud.mdx +6 -4
- package/docs/cli/doctor.mdx +6 -0
- package/docs/cli/drive.mdx +8 -2
- package/docs/cli/init.mdx +11 -13
- package/docs/cli/status.mdx +5 -1
- package/docs/cli/version.mdx +3 -3
- package/docs/cli.mdx +14 -8
- package/docs/desktop.mdx +5 -1
- package/docs/environment.mdx +3 -7
- package/docs/getting-started.md +14 -2
- package/docs/human-control.md +1 -2
- package/docs/index.mdx +4 -3
- package/docs/install-agentic.mdx +1 -1
- package/docs/install-manual.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/tauri.mdx +2 -2
- package/docs/packages/vite-plugin.mdx +24 -1
- package/docs/packages.mdx +1 -1
- package/docs/quickstart.mdx +23 -11
- package/docs/telemetry.md +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/session-and-feedback.mdx +9 -9
- package/docs/usage.md +5 -3
- package/docs/what-is-recorded.md +13 -3
- package/docs/what-is-reticle.mdx +3 -1
- package/package.json +8 -12
- package/dist/features/harness/driver.d.ts +0 -50
- package/dist/features/harness/driver.js +0 -236
- package/dist/features/harness/jev-driver.d.ts +0 -188
- package/dist/features/harness/jev-driver.js +0 -678
- package/dist/features/harness/openai-driver.d.ts +0 -46
- package/dist/features/harness/openai-driver.js +0 -194
- package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
- package/dist/memory/project/dir/fill-value-store.js +0 -88
- package/docs/local-registry.md +0 -143
|
@@ -29,6 +29,17 @@ function unknownParamsError(toolName, unknown) {
|
|
|
29
29
|
const TOOLS_LIST_ACTION = 'list';
|
|
30
30
|
/** What to write instead — the one parameter reticle_tools declares, and the no-argument form. */
|
|
31
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';
|
|
32
|
+
/**
|
|
33
|
+
* The refusal for arguments of the wrong type, naming each offending key and what it declared.
|
|
34
|
+
* `path` is joined for a nested value (`intents.0.id`); a missing required key reads as the key.
|
|
35
|
+
*/
|
|
36
|
+
function invalidParamsError(toolName, issues) {
|
|
37
|
+
const named = issues.map((issue) => {
|
|
38
|
+
const where = 0 === issue.path.length ? '(arguments)' : issue.path.join('.');
|
|
39
|
+
return `${where} (${issue.message})`;
|
|
40
|
+
});
|
|
41
|
+
return `invalid ${1 === named.length ? 'parameter' : 'parameters'} for ${toolName}: ${named.join('; ')} — NOT applied, so any result would be an answer to a different question`;
|
|
42
|
+
}
|
|
32
43
|
/** The keys not declared by `shape`, in call order. */
|
|
33
44
|
function unknownKeys(args, shape) {
|
|
34
45
|
const declared = new Set(Object.keys(shape));
|
|
@@ -263,8 +274,24 @@ callable) {
|
|
|
263
274
|
hint: 'fix the arguments and call reticle_run again',
|
|
264
275
|
};
|
|
265
276
|
}
|
|
277
|
+
// Keys are not enough: the TYPES are the other half of what a direct call's SDK validation
|
|
278
|
+
// refuses. Unchecked here, `reticle_viewport { width: "1440" }` from a client that stringifies
|
|
279
|
+
// numbers reached the handler, which read a non-number as missing and set the page to 64x64,
|
|
280
|
+
// and `reticle_intent { action: "declare", intents: "[...]" }` stored nothing and answered
|
|
281
|
+
// `{ intents: [] }` (#1118). The parsed value goes on, as it does on the direct path, so a
|
|
282
|
+
// schema's defaults and transforms apply the same way through either door.
|
|
283
|
+
const typed = z.object(target.inputSchema).safeParse(callArgs);
|
|
284
|
+
if (!typed.success) {
|
|
285
|
+
return {
|
|
286
|
+
error: invalidParamsError(name, typed.error.issues),
|
|
287
|
+
tool: name,
|
|
288
|
+
params: paramInfo(target.inputSchema),
|
|
289
|
+
...(target.example === undefined ? {} : { example: target.example }),
|
|
290
|
+
hint: 'fix the arguments and call reticle_run again',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
266
293
|
try {
|
|
267
|
-
return await runTool(target, deps,
|
|
294
|
+
return await runTool(target, deps, typed.data);
|
|
268
295
|
}
|
|
269
296
|
catch (error) {
|
|
270
297
|
// The SAME payload the direct tool path builds — recovery included. Answering every failure
|
|
@@ -272,7 +299,7 @@ callable) {
|
|
|
272
299
|
// is reached through here: a stale ref, a paused session, a missing pairing token all came
|
|
273
300
|
// back as the agent's arguments being wrong, which is advice that spends the retry.
|
|
274
301
|
const message = error instanceof Error ? error.message : String(error);
|
|
275
|
-
return { ...takeVersionSkewOnto(buildErrorPayload(message)), tool: name };
|
|
302
|
+
return { ...takeVersionSkewOnto(buildErrorPayload(message), deps.peerSkew), tool: name };
|
|
276
303
|
}
|
|
277
304
|
},
|
|
278
305
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ToolDeps } from './tool-kit.js';
|
|
3
|
+
/** The fields only an empty list carries, declared so a schema-aware client does not strip them. */
|
|
4
|
+
export declare const EMPTY_SESSION_LIST_OUTPUT: {
|
|
5
|
+
why: z.ZodOptional<z.ZodString>;
|
|
6
|
+
next_action: z.ZodOptional<z.ZodObject<{
|
|
7
|
+
action: z.ZodNativeEnum<{
|
|
8
|
+
readonly START_DEV_SERVER: "start_dev_server";
|
|
9
|
+
readonly RUN_INIT: "run_init";
|
|
10
|
+
readonly OPEN_APP: "open_app";
|
|
11
|
+
readonly REOPEN_APP: "reopen_app";
|
|
12
|
+
readonly DAEMON_SPLIT: "daemon_split";
|
|
13
|
+
}>;
|
|
14
|
+
command: z.ZodOptional<z.ZodString>;
|
|
15
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
reason: z.ZodString;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
action: "start_dev_server" | "run_init" | "open_app" | "reopen_app" | "daemon_split";
|
|
19
|
+
reason: string;
|
|
20
|
+
port?: number | undefined;
|
|
21
|
+
command?: string | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
action: "start_dev_server" | "run_init" | "open_app" | "reopen_app" | "daemon_split";
|
|
24
|
+
reason: string;
|
|
25
|
+
port?: number | undefined;
|
|
26
|
+
command?: string | undefined;
|
|
27
|
+
}>>;
|
|
28
|
+
talk_to_us: z.ZodOptional<z.ZodString>;
|
|
29
|
+
wired: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
30
|
+
lastKnown: z.ZodOptional<z.ZodObject<{
|
|
31
|
+
sessionId: z.ZodString;
|
|
32
|
+
url: z.ZodString;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
sessionId: string;
|
|
35
|
+
url: string;
|
|
36
|
+
}, {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
url: string;
|
|
39
|
+
}>>;
|
|
40
|
+
};
|
|
41
|
+
export declare function emptySessionList(deps: ToolDeps, sessions: unknown[]): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `reticle_session { action: "list" }` answers when nothing is connected.
|
|
3
|
+
*
|
|
4
|
+
* An empty list is the most common thing that tool returns, and on its own it is a dead end: the
|
|
5
|
+
* agent asked the one question it knows to ask and got no next step. So it carries why nothing is
|
|
6
|
+
* connected, the executable next action, the last tab that was here, and, on a project that has
|
|
7
|
+
* never connected, the first-run wiring itself (see first-run-wiring.ts).
|
|
8
|
+
*/
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { DiscoveryInvite, NoSessionAction } from '@reticlehq/core';
|
|
12
|
+
/** The answers that mean "this app is not set up yet", which a first run can settle by wiring it. */
|
|
13
|
+
const FIRST_RUN_ACTIONS = new Set([
|
|
14
|
+
NoSessionAction.RUN_INIT,
|
|
15
|
+
NoSessionAction.START_DEV_SERVER,
|
|
16
|
+
NoSessionAction.OPEN_APP,
|
|
17
|
+
]);
|
|
18
|
+
/** The fields only an empty list carries, declared so a schema-aware client does not strip them. */
|
|
19
|
+
export const EMPTY_SESSION_LIST_OUTPUT = {
|
|
20
|
+
why: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Present ONLY when `sessions` is empty: why nothing is connected, and the next action that fixes it. An empty list is never the end of the road — read this before concluding the app cannot be driven.'),
|
|
24
|
+
next_action: z
|
|
25
|
+
.object({
|
|
26
|
+
action: z.nativeEnum(NoSessionAction),
|
|
27
|
+
command: z.string().optional(),
|
|
28
|
+
port: z.number().optional(),
|
|
29
|
+
reason: z.string(),
|
|
30
|
+
})
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Present ONLY when `sessions` is empty: the same answer as `why`, executable. `command` is the LITERAL command to run, sourced from this project's own package.json scripts and lockfile — it is absent, never guessed, when the project declares no dev script. `action` is one of daemon_split | start_dev_server | run_init | open_app | reopen_app. `daemon_split` outranks the rest and means the app IS running and instrumented, on a DIFFERENT daemon than the one you are attached to — do not start or re-init anything, read `reason`."),
|
|
33
|
+
talk_to_us: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Present ONLY when `sessions` is empty: a call with the founder for the person you are working with. Offer it; never act on it.'),
|
|
37
|
+
wired: z
|
|
38
|
+
.record(z.unknown())
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Files the first run changed (`steps`); tell the user.'),
|
|
41
|
+
lastKnown: z
|
|
42
|
+
.object({
|
|
43
|
+
sessionId: z.string(),
|
|
44
|
+
url: z.string(),
|
|
45
|
+
})
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Present ONLY when `sessions` is empty AND a tab was connected to this daemon earlier: the last sessionId and URL that tab was on when it disappeared. A route that 500s tears the page down and the SDK never reconnects — this is the last thing we knew, not a route-500 verdict. An empty list with no lastKnown is a tab that never arrived.'),
|
|
48
|
+
};
|
|
49
|
+
export async function emptySessionList(deps, sessions) {
|
|
50
|
+
const why = deps.sessions.noSessionHint();
|
|
51
|
+
// The executable half. `why` is for the human reading the transcript; this is the one the
|
|
52
|
+
// agent acts on, so it never has to parse a paragraph to find a command inside it.
|
|
53
|
+
const next = deps.sessions.noSessionNextAction();
|
|
54
|
+
// The first run wires the app, not the installation: the daemon runs init itself, once,
|
|
55
|
+
// in its own project, and says what it changed. A daemon outside any project asks instead.
|
|
56
|
+
// Not only on `run_init`: an unwired app whose dev server is not running yet answers
|
|
57
|
+
// `start_dev_server`, which is the commonest first run of all, and init starts that server itself.
|
|
58
|
+
// `projectDirectory` refuses a project that is already wired, so a wired app is never re-run.
|
|
59
|
+
const dir = next !== undefined && FIRST_RUN_ACTIONS.has(next.action)
|
|
60
|
+
? deps.firstRun?.projectDirectory(dirname(deps.reticleRoot))
|
|
61
|
+
: undefined;
|
|
62
|
+
if (dir !== undefined && deps.firstRun !== undefined) {
|
|
63
|
+
const wired = await deps.firstRun.wire(dir);
|
|
64
|
+
const connected = deps.sessions.list();
|
|
65
|
+
if (wired.ok && 0 < connected.length)
|
|
66
|
+
return { sessions: connected, wired };
|
|
67
|
+
return { sessions, wired, ...(why === undefined ? {} : { why }) };
|
|
68
|
+
}
|
|
69
|
+
// The last tab, when one was here. Optional-call: test stubs of SessionManager predate it.
|
|
70
|
+
const known = deps.sessions.lastKnown?.();
|
|
71
|
+
const lastKnown = undefined === known ? undefined : { sessionId: known.id, url: known.url };
|
|
72
|
+
return {
|
|
73
|
+
sessions,
|
|
74
|
+
...(why === undefined ? {} : { why }),
|
|
75
|
+
...(next === undefined ? {} : { next_action: next }),
|
|
76
|
+
...(undefined === lastKnown ? {} : { lastKnown }),
|
|
77
|
+
// Setup that never connects is where most people give up; it is the talk worth having.
|
|
78
|
+
talk_to_us: DiscoveryInvite.AGENT,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -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
|
+
* RETICLE_CHROMIUM_PATH names a browser the daemon cannot launch. The call was fine and so is the
|
|
71
|
+
* Playwright install, so neither a retry nor the install command can help: only the daemon's own
|
|
72
|
+
* environment can, and a running daemon keeps the values it started with.
|
|
73
|
+
*/
|
|
74
|
+
readonly CHROMIUM_PATH: string;
|
|
69
75
|
/**
|
|
70
76
|
* The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
|
|
71
77
|
* agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* on stable, human-authored substrings of the thrown messages (see session-manager.ts, the flow/
|
|
9
9
|
* baseline stores). No clock, no IO — unit-testable in isolation.
|
|
10
10
|
*/
|
|
11
|
-
import { RefusalReason, TRANSPORT_LIMITS } from '@reticlehq/core';
|
|
11
|
+
import { RefusalReason, ReticleEnv, TRANSPORT_LIMITS } from '@reticlehq/core';
|
|
12
12
|
import { SELF_RECOVERING_MARKER } from '../../portal/session/no-session-diagnosis.js';
|
|
13
|
-
import {
|
|
13
|
+
import { BODY_CLAUSE_REFUSAL_OPENING } from '@reticlehq/engine/evidence/body-capture-remedy.js';
|
|
14
|
+
import { CHROMIUM_PATH_REFUSAL_PREFIX, chromiumInstallCommand, bundledPlaywrightVersion, } from '../../command/cli/doctor/browser/chromium-hint.js';
|
|
14
15
|
/** Marks where an over-long message had its middle removed. Named so tests assert it by value. */
|
|
15
16
|
const ELISION = '… [elided] …';
|
|
16
17
|
/**
|
|
@@ -33,11 +34,21 @@ function capMessage(message) {
|
|
|
33
34
|
const keep = Math.floor((max - ELISION.length) / 2);
|
|
34
35
|
return `${message.slice(0, keep)}${ELISION}${message.slice(message.length - keep)}`;
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* A fixed phrase as a pattern for the table below, which matches on phrasing.
|
|
39
|
+
*
|
|
40
|
+
* The phrase comes from the module that throws it rather than being retyped here, and a constant is
|
|
41
|
+
* still text: escaped so a future reword containing `(` or `?` cannot turn into a pattern that means
|
|
42
|
+
* something else — or fails to compile at import time, taking the whole surface with it.
|
|
43
|
+
*/
|
|
44
|
+
function literally(phrase) {
|
|
45
|
+
return new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
46
|
+
}
|
|
36
47
|
/**
|
|
37
48
|
* Messages that already spell out the exact retry, so any hint we add can only argue with them.
|
|
38
49
|
*
|
|
39
50
|
* The no-session diagnosis is one (it inspected the machine and named the cause). The native-input
|
|
40
|
-
* refusal is
|
|
51
|
+
* refusal is another: it names the tool, the argument and the follow-up call to make instead — and
|
|
41
52
|
* it was still collecting the defect ask, inviting a bug report about a refusal that had just told
|
|
42
53
|
* the agent precisely what to do.
|
|
43
54
|
*/
|
|
@@ -47,6 +58,11 @@ const SELF_RECOVERING = [
|
|
|
47
58
|
// the current ids" to a message that just named them contradicts the shorter path it offers, and
|
|
48
59
|
// a contradicted instruction is how one agent came to retry a dead id twelve times.
|
|
49
60
|
{ match: /Connected right now:/i, reason: RefusalReason.NO_SESSION },
|
|
61
|
+
// The body-clause pre-flight: nothing ran, no action was spent, and the message names the setting
|
|
62
|
+
// to switch on — or says the page's SDK predates it, which is a limit and not a defect either. It
|
|
63
|
+
// was collecting the defect ask on top of its own remedy, so the envelope contradicted itself and
|
|
64
|
+
// asked for a bug report about somebody's connect() options.
|
|
65
|
+
{ match: literally(BODY_CLAUSE_REFUSAL_OPENING), reason: RefusalReason.UNSUPPORTED },
|
|
50
66
|
];
|
|
51
67
|
/** A message that already carries its own concrete next action, so nothing should be appended. */
|
|
52
68
|
function isSelfRecovering(message) {
|
|
@@ -199,6 +215,17 @@ export const RECOVERY = {
|
|
|
199
215
|
chromiumInstallCommand(bundledPlaywrightVersion()) +
|
|
200
216
|
'` if that is what is missing. Meanwhile drive ' +
|
|
201
217
|
'a tab the human already has open — reticle_sessions lists them.',
|
|
218
|
+
/**
|
|
219
|
+
* RETICLE_CHROMIUM_PATH names a browser the daemon cannot launch. The call was fine and so is the
|
|
220
|
+
* Playwright install, so neither a retry nor the install command can help: only the daemon's own
|
|
221
|
+
* environment can, and a running daemon keeps the values it started with.
|
|
222
|
+
*/
|
|
223
|
+
CHROMIUM_PATH: `Reticle cannot launch the browser ${ReticleEnv.CHROMIUM_PATH} names (the message above says ` +
|
|
224
|
+
'what is wrong with the path). The call itself was fine, so retrying it will not help, and ' +
|
|
225
|
+
'neither will installing Chromium. Ask the human to fix that path, or unset it to use ' +
|
|
226
|
+
"Playwright's own Chromium, in the environment the daemon starts from, then run " +
|
|
227
|
+
'`npx @reticlehq/server restart`. Meanwhile drive a tab the human already has open, which ' +
|
|
228
|
+
'reticle_sessions lists. This is configuration, not a Reticle defect: there is nothing to report.',
|
|
202
229
|
/**
|
|
203
230
|
* The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
|
|
204
231
|
* agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
|
|
@@ -227,6 +254,7 @@ const REASON_OF = {
|
|
|
227
254
|
THROTTLED: RefusalReason.NOT_READY,
|
|
228
255
|
COMMAND_TIMEOUT: RefusalReason.NOT_READY,
|
|
229
256
|
NO_POOL: RefusalReason.NOT_READY,
|
|
257
|
+
CHROMIUM_PATH: RefusalReason.NOT_READY,
|
|
230
258
|
TOKEN_REQUIRED: RefusalReason.NOT_READY,
|
|
231
259
|
MISSING_BASELINE: RefusalReason.NO_MATCH,
|
|
232
260
|
MISSING_RECORDING: RefusalReason.NO_MATCH,
|
|
@@ -252,6 +280,11 @@ const REASON_OF = {
|
|
|
252
280
|
const REASON_BY_HINT = new Map(Object.entries(RECOVERY).map(([key, hint]) => [hint, REASON_OF[key]]));
|
|
253
281
|
/** Ordered match rules; the first hit wins. Substrings track the thrown messages they recover. */
|
|
254
282
|
const RULES = [
|
|
283
|
+
// First, because the message carries a path the user chose, and a path can hold any word a later
|
|
284
|
+
// rule keys on. Without it the variable's name read as a `reticle_*` tool and the agent was told
|
|
285
|
+
// its call failed the schema. Not anchored, so a launch error that wraps it is still caught. The
|
|
286
|
+
// prefix has no regex metacharacters, so it is used as is.
|
|
287
|
+
{ match: new RegExp(CHROMIUM_PATH_REFUSAL_PREFIX), hint: RECOVERY.CHROMIUM_PATH },
|
|
255
288
|
{ match: /no browser session connected/i, hint: RECOVERY.NO_SESSION },
|
|
256
289
|
{ match: /multiple sessions connected/i, hint: RECOVERY.MULTIPLE_SESSIONS },
|
|
257
290
|
{ match: /no connected session with id/i, hint: RECOVERY.UNKNOWN_SESSION },
|
|
@@ -267,7 +300,7 @@ const RULES = [
|
|
|
267
300
|
// needs its own rule: none of them contains "no browser session connected" (the scope miss is
|
|
268
301
|
// "no browser session FOR project 'x'", which is the opposite claim — sessions exist).
|
|
269
302
|
{
|
|
270
|
-
match: /scope mismatch|session\(s\) ARE connected under a different project/i,
|
|
303
|
+
match: /scope mismatch|session\(s\) ARE connected (?:under a different project|with no projectId)/i,
|
|
271
304
|
hint: RECOVERY.SCOPE_MISMATCH,
|
|
272
305
|
},
|
|
273
306
|
{ match: /^session disconnected$|session .* never connected/i, hint: RECOVERY.SESSION_GONE },
|
|
@@ -293,8 +326,7 @@ const RULES = [
|
|
|
293
326
|
{ match: /contenteditable/i, hint: RECOVERY.UNSUPPORTED_SURFACE },
|
|
294
327
|
{ match: /cannot hover without a real pointer/i, hint: RECOVERY.HOVER_NEEDS_POINTER },
|
|
295
328
|
{ match: /cannot \w+ a (disabled|readonly) </i, hint: RECOVERY.NOT_EDITABLE },
|
|
296
|
-
//
|
|
297
|
-
// two thirds of the same deliberate refusal still read as a possible defect.
|
|
329
|
+
// Both "action" and "native action" refusals need the same confirmation guidance.
|
|
298
330
|
{
|
|
299
331
|
match: /potentially destructive (?:\w+ )*(?:action|tool) blocked/i,
|
|
300
332
|
hint: RECOVERY.CONFIRM_DANGEROUS,
|
|
@@ -10,5 +10,14 @@
|
|
|
10
10
|
* Those flows are the point. From the second run onwards `reticle_verify { action: "flows" }` replays
|
|
11
11
|
* them deterministically with no model in the loop at all.
|
|
12
12
|
*/
|
|
13
|
-
import type { ToolDef } from './tool-kit.js';
|
|
13
|
+
import type { ToolDef, ToolDeps } from './tool-kit.js';
|
|
14
|
+
import { type RemoteDriveOutcome } from '../../features/harness/platform/remote-drive.js';
|
|
14
15
|
export declare const EXPLORE_TOOLS: ToolDef[];
|
|
16
|
+
/**
|
|
17
|
+
* A drive the platform's chat asked for, run as the very tool an agent would call.
|
|
18
|
+
*
|
|
19
|
+
* Through `runTool`, not a second path to `exploreApp`: a dispatch that skips it is a drive nobody
|
|
20
|
+
* counted, and the chat's answer is then the same derived summary the agent would have read — never
|
|
21
|
+
* a model's account of its own drive.
|
|
22
|
+
*/
|
|
23
|
+
export declare function driveForChat(deps: ToolDeps, goal: string, sessionId?: string): Promise<RemoteDriveOutcome>;
|
|
@@ -11,28 +11,28 @@
|
|
|
11
11
|
* them deterministically with no model in the loop at all.
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
+
import { unprovedGoals } from '../../features/harness/goals.js';
|
|
14
15
|
import { ReticleTool, asRecord } from '@reticlehq/core';
|
|
15
16
|
import { stepCountSchema } from './args/numeric-bounds.js';
|
|
17
|
+
import { runTool } from './invoke-tool.js';
|
|
18
|
+
import { driveVerdict } from '../../features/harness/platform/remote-drive.js';
|
|
16
19
|
import { exploreApp, harnessAvailable, withLinkedCredential, MSG_NO_HARNESS_KEY, } from './harness-explore.js';
|
|
17
|
-
import {
|
|
18
|
-
import { describeDrive, replayedFlows } from '../../features/harness/drive-report.js';
|
|
20
|
+
import { EXPLORE_NEEDS } from '../../features/harness/drivers.js';
|
|
21
|
+
import { checkTally, describeDrive, replayedFlows } from '../../features/harness/drive-report.js';
|
|
19
22
|
import { StopReason } from '../../features/harness/harness.js';
|
|
20
23
|
export const EXPLORE_TOOLS = [
|
|
21
24
|
{
|
|
22
25
|
name: ReticleTool.VERIFY_EXPLORE,
|
|
23
|
-
description: 'Drive the app yourself? Do not — call this instead.
|
|
26
|
+
description: 'Drive the app yourself? Do not — call this instead. The Reticle Harness drives it through this same tool surface (it decides on the Reticle platform, this daemon executes each step), records what it drove as saved flows, and answers in a few lines, so the whole drive costs you one tool call instead of a context full of snapshots. Pass `persona` to say who to be or what to accomplish ("a returning customer checking out", "an admin revoking a seat") and it completes that whole journey rather than clicking at random. Returns { stopReason, steps, savedFlows, summary, usage }. The saved flows are the point: from the next run on, reticle_verify { action: "flows" } replays them deterministically with NO model in the loop. DESTRUCTIVE — it really drives the app, and it spends Harness credits. ' +
|
|
27
|
+
EXPLORE_NEEDS,
|
|
24
28
|
inputSchema: {
|
|
25
29
|
persona: z
|
|
26
30
|
.string()
|
|
27
31
|
.optional()
|
|
28
|
-
.describe('
|
|
32
|
+
.describe('The journey in plain words; any "quoted text" must be on the page when it ends (checked).'),
|
|
29
33
|
maxSteps: stepCountSchema
|
|
30
34
|
.optional()
|
|
31
|
-
.describe('Ceiling on model turns
|
|
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.'),
|
|
35
|
+
.describe('Ceiling on model turns; the drive is graded however it ends.'),
|
|
36
36
|
sessionId: z
|
|
37
37
|
.string()
|
|
38
38
|
.optional()
|
|
@@ -47,6 +47,21 @@ export const EXPLORE_TOOLS = [
|
|
|
47
47
|
savedFlows: z.array(z.string()),
|
|
48
48
|
/** Flows that already existed and were driven and written again. A second run's ordinary result. */
|
|
49
49
|
rewroteFlows: z.array(z.string()),
|
|
50
|
+
/** Saved flows with no step that asserts anything: their replay verifies nothing. */
|
|
51
|
+
unverifiedFlows: z.array(z.string()),
|
|
52
|
+
/** Whether the drive ran at least one check. A drive that did not proved nothing. */
|
|
53
|
+
proved: z.boolean(),
|
|
54
|
+
/**
|
|
55
|
+
* Whether every journey reached its goal, as the platform judged when the drive finished.
|
|
56
|
+
* Absent when no journey's goal was judged. Checks that held are not the goal reached.
|
|
57
|
+
*/
|
|
58
|
+
goalMet: z.boolean().optional(),
|
|
59
|
+
/** How the drive's checks came out, one per control and claim at its worst. */
|
|
60
|
+
checks: z.object({ held: z.number(), failed: z.number(), undecided: z.number() }),
|
|
61
|
+
/** The runs this drive syncs as (`harness-<uuid>`): what the platform links its check to. */
|
|
62
|
+
runIds: z.array(z.string()).optional(),
|
|
63
|
+
/** One verdict per requested goal, checked by the harness itself. Only `yes` is proved. */
|
|
64
|
+
goals: z.array(z.object({ text: z.string(), verified: z.string() })),
|
|
50
65
|
/**
|
|
51
66
|
* What the drive set out to do, read from `.reticle` BEFORE it started — every recorded
|
|
52
67
|
* journey with the consequence that must still hold, and the declared intent nobody has
|
|
@@ -81,12 +96,10 @@ export const EXPLORE_TOOLS = [
|
|
|
81
96
|
const persona = args['persona'];
|
|
82
97
|
const maxSteps = args['maxSteps'];
|
|
83
98
|
const sessionId = args['sessionId'];
|
|
84
|
-
const
|
|
85
|
-
const { drive, savedFlows, rewroteFlows, driverName, plan } = await exploreApp(deps, env, {
|
|
99
|
+
const { drive, savedFlows, rewroteFlows, unverifiedFlows, driverName, plan, goals, planLines, runIds, } = await exploreApp(deps, env, {
|
|
86
100
|
...('string' === typeof persona ? { focus: persona } : {}),
|
|
87
101
|
...('number' === typeof maxSteps ? { maxSteps } : {}),
|
|
88
102
|
...('string' === typeof sessionId ? { sessionId } : {}),
|
|
89
|
-
...('string' === typeof driver ? { driverName: driver } : {}),
|
|
90
103
|
});
|
|
91
104
|
return {
|
|
92
105
|
stopReason: drive.stopReason,
|
|
@@ -94,11 +107,19 @@ export const EXPLORE_TOOLS = [
|
|
|
94
107
|
steps: drive.steps,
|
|
95
108
|
savedFlows: [...savedFlows],
|
|
96
109
|
rewroteFlows: [...rewroteFlows],
|
|
110
|
+
unverifiedFlows: [...unverifiedFlows],
|
|
111
|
+
proved: drive.proved,
|
|
112
|
+
checks: checkTally(drive.toolCalls),
|
|
113
|
+
...(drive.goalMet === undefined ? {} : { goalMet: drive.goalMet }),
|
|
114
|
+
...(runIds === undefined ? {} : { runIds: [...runIds] }),
|
|
115
|
+
goals: [...goals],
|
|
97
116
|
plan: { summary: plan.summary, steps: [...plan.steps] },
|
|
98
117
|
// Derived, not narrated. The driver's own `summary` is appended only when it said
|
|
99
118
|
// something — it is the one part of this a model authored, so it goes last and is labelled.
|
|
100
119
|
summary: [
|
|
101
|
-
|
|
120
|
+
...(planLines ?? []),
|
|
121
|
+
describeDrive(drive.toolCalls, [...savedFlows, ...rewroteFlows], unverifiedFlows),
|
|
122
|
+
...[unprovedGoals(goals)].filter((line) => line !== undefined),
|
|
102
123
|
...(0 === drive.summary.length ? [] : [`The driver's own account: ${drive.summary}`]),
|
|
103
124
|
].join('\n'),
|
|
104
125
|
...(drive.error === undefined ? {} : { error: drive.error }),
|
|
@@ -159,4 +180,52 @@ const NOTHING_RECORDED = {
|
|
|
159
180
|
[StopReason.BUDGET]: 'The drive ran out of steps before saving a flow. Raise `maxSteps`, or give it a narrower `persona` so it spends the budget on one journey.',
|
|
160
181
|
[StopReason.STALLED]: 'The drive stopped asking for tools without finishing. Nothing was saved, and nothing is proved.',
|
|
161
182
|
[StopReason.BROKEN]: 'The drive broke before saving anything — read `error`. Nothing is proved.',
|
|
183
|
+
[StopReason.STOPPED]: 'Autonomous driving was switched off before the drive saved anything. Nothing is proved; switch it back on to drive again.',
|
|
162
184
|
};
|
|
185
|
+
/**
|
|
186
|
+
* A drive the platform's chat asked for, run as the very tool an agent would call.
|
|
187
|
+
*
|
|
188
|
+
* Through `runTool`, not a second path to `exploreApp`: a dispatch that skips it is a drive nobody
|
|
189
|
+
* counted, and the chat's answer is then the same derived summary the agent would have read — never
|
|
190
|
+
* a model's account of its own drive.
|
|
191
|
+
*/
|
|
192
|
+
export async function driveForChat(deps, goal, sessionId) {
|
|
193
|
+
const explore = EXPLORE_TOOLS.find((tool) => ReticleTool.VERIFY_EXPLORE === tool.name);
|
|
194
|
+
if (explore === undefined)
|
|
195
|
+
throw new Error('this build has no Harness drive');
|
|
196
|
+
const out = asRecord(await runTool(explore, deps, {
|
|
197
|
+
persona: goal,
|
|
198
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
199
|
+
}));
|
|
200
|
+
const summary = 'string' === typeof out['summary'] ? out['summary'] : '';
|
|
201
|
+
const error = 'string' === typeof out['error'] ? out['error'] : undefined;
|
|
202
|
+
const note = 'string' === typeof out['note'] ? out['note'] : undefined;
|
|
203
|
+
const goals = Array.isArray(out['goals'])
|
|
204
|
+
? out['goals'].flatMap((g) => {
|
|
205
|
+
const verified = asRecord(g)['verified'];
|
|
206
|
+
return 'string' === typeof verified ? [{ verified }] : [];
|
|
207
|
+
})
|
|
208
|
+
: [];
|
|
209
|
+
const goalMet = out['goalMet'];
|
|
210
|
+
const tally = asRecord(out['checks']);
|
|
211
|
+
const count = (key) => ('number' === typeof tally[key] ? tally[key] : 0);
|
|
212
|
+
const checks = { held: count('held'), failed: count('failed'), undecided: count('undecided') };
|
|
213
|
+
const runIds = Array.isArray(out['runIds'])
|
|
214
|
+
? out['runIds'].filter((id) => 'string' === typeof id)
|
|
215
|
+
: [];
|
|
216
|
+
return {
|
|
217
|
+
// Only a drive that ran a check and did not break counts as one that proved anything.
|
|
218
|
+
ok: true === out['proved'] && error === undefined,
|
|
219
|
+
...(0 === runIds.length ? {} : { runIds }),
|
|
220
|
+
verdict: driveVerdict({
|
|
221
|
+
proved: true === out['proved'],
|
|
222
|
+
checks,
|
|
223
|
+
goals,
|
|
224
|
+
...(error === undefined ? {} : { error }),
|
|
225
|
+
...('boolean' === typeof goalMet ? { goalMet } : {}),
|
|
226
|
+
}),
|
|
227
|
+
summary: [summary, note, error === undefined ? undefined : `Error: ${error}`]
|
|
228
|
+
.filter((line) => line !== undefined && 0 < line.length)
|
|
229
|
+
.join('\n'),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
@@ -17,6 +17,17 @@ import { ReticleTool } from '@reticlehq/core';
|
|
|
17
17
|
import { asNumber, asString } from '@reticlehq/core';
|
|
18
18
|
import { submitFeedback, feedbackDisabled } from '../../telemetry/feedback.js';
|
|
19
19
|
import { getTelemetry } from '../../telemetry/telemetry.js';
|
|
20
|
+
import { dirname } from 'node:path';
|
|
21
|
+
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
22
|
+
/** The project folder this call's session belongs to, when one can be told. */
|
|
23
|
+
function projectDir(deps, sessionId) {
|
|
24
|
+
try {
|
|
25
|
+
return { cwd: dirname(sessionRoot(deps, sessionId)) };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
20
31
|
/** One optional string arg, present only when the agent actually supplied it. */
|
|
21
32
|
function optionalText(args, key) {
|
|
22
33
|
const value = asString(args[key]);
|
|
@@ -175,6 +186,9 @@ export const FEEDBACK_TOOLS = [
|
|
|
175
186
|
...optionalRating(args),
|
|
176
187
|
}, {
|
|
177
188
|
session: sessionFacts(deps, asString(args['sessionId'])),
|
|
189
|
+
// A report that cannot be sent is written into the project being verified, never into
|
|
190
|
+
// whatever folder the daemon happened to start in.
|
|
191
|
+
...projectDir(deps, asString(args['sessionId'])),
|
|
178
192
|
// The AGENT does not wait for the network. `reticle feedback` (a human, at a terminal,
|
|
179
193
|
// watching for an answer) still does — see submitFeedback's `background`.
|
|
180
194
|
background: true,
|