@reticlehq/server 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +6 -2
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +24 -4
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +64 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.d.ts +1 -1
- package/dist/command/cli/cloud-login.js +27 -8
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +21 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +79 -11
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
- package/dist/command/setup/bringup/dev-server-wait.js +1 -1
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +91 -9
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +86 -45
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +28 -1
- package/dist/command/setup/run-setup.js +103 -15
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +17 -8
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- 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/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +10 -0
- package/dist/features/harness/fill-values.js +50 -11
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.d.ts +12 -0
- package/dist/features/harness/openai-driver.js +21 -12
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/features/license/license.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -19
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +77 -3
- package/dist/memory/cloud/sync-cycle.js +325 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/intent/intent-tools.js +8 -3
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
- package/dist/memory/project/artifact-root-resolver.js +46 -19
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +12 -3
- package/dist/portal/bridge/bridge.js +49 -3
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/input/viewport-tools.js +16 -6
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +10 -8
- package/dist/portal/session/session.js +24 -32
- package/dist/surface/http-server.d.ts +3 -2
- package/dist/surface/http-server.js +8 -5
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +28 -52
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/error-recovery.js +5 -6
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +12 -7
- package/dist/surface/tools/invoke-tool.js +12 -8
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tool-kit.d.ts +10 -0
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +1 -5
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -4
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +20 -8
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +22 -7
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +15 -5
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +2 -2
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +4 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +12 -10
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -67,7 +67,7 @@ export const RECOVERY = {
|
|
|
67
67
|
UNKNOWN_SESSION: 'That sessionId is not connected. Call reticle_sessions for the current ids and retry with a valid one.',
|
|
68
68
|
THROTTLED: 'The target tab is backgrounded/throttled, so actions may silently no-op. Ask the human to bring ' +
|
|
69
69
|
'the tab to the front, or acquire a guaranteed scriptable context yourself with ' +
|
|
70
|
-
'`reticle_run { tool: "reticle_lease", action: "acquire", url }` (a human can equivalently run ' +
|
|
70
|
+
'`reticle_run { tool: "reticle_lease", args: { action: "acquire", url } }` (a human can equivalently run ' +
|
|
71
71
|
'`reticle drive <url>`).',
|
|
72
72
|
MISSING_BASELINE: 'That baseline does not exist yet. Call reticle_baseline { action: "list" } to see saved names, or ' +
|
|
73
73
|
'reticle_baseline { action: "save", name } to capture one before diffing against it.',
|
|
@@ -159,7 +159,7 @@ export const RECOVERY = {
|
|
|
159
159
|
'assert on the result instead of typing it. Known gap, already reported: no need to file it.',
|
|
160
160
|
HOVER_NEEDS_POINTER: 'Hover needs a real pointer: a synthetic mouseover does not apply CSS :hover, and Reticle ' +
|
|
161
161
|
'refuses rather than reporting the styles as applied. Acquire a tab with ' +
|
|
162
|
-
'reticle_run { tool: "reticle_lease", action: "acquire", url } — reticle_lease is not ' +
|
|
162
|
+
'reticle_run { tool: "reticle_lease", args: { action: "acquire", url } } — reticle_lease is not ' +
|
|
163
163
|
'advertised under the default profile, so it is reached through reticle_run, not called ' +
|
|
164
164
|
'directly — or ask the human to drive with `reticle drive` / RETICLE_CDP_URL. This is a ' +
|
|
165
165
|
'deliberate refusal, not a defect: there is nothing to report.',
|
|
@@ -182,7 +182,7 @@ export const RECOVERY = {
|
|
|
182
182
|
COMMAND_TIMEOUT: 'The page did not answer within the command window. That is a fact about the page, not a Reticle ' +
|
|
183
183
|
'failure: check reticle_sessions for `throttled`/`stale` on this session — a backgrounded tab is ' +
|
|
184
184
|
'throttled and may never answer, so ask the human to bring it to the front or drive your own ' +
|
|
185
|
-
'browser with reticle_run { tool: "reticle_lease", action: "acquire", url } — reticle_lease is ' +
|
|
185
|
+
'browser with reticle_run { tool: "reticle_lease", args: { action: "acquire", url } } — reticle_lease is ' +
|
|
186
186
|
'not advertised under the default profile, so it is reached through reticle_run, not called ' +
|
|
187
187
|
'directly. If the tab is in front, the page is busy or blocked (a long ' +
|
|
188
188
|
'synchronous task, an alert/confirm dialog); reticle_console usually shows what it hit. Retry ' +
|
|
@@ -293,8 +293,7 @@ const RULES = [
|
|
|
293
293
|
{ match: /contenteditable/i, hint: RECOVERY.UNSUPPORTED_SURFACE },
|
|
294
294
|
{ match: /cannot hover without a real pointer/i, hint: RECOVERY.HOVER_NEEDS_POINTER },
|
|
295
295
|
{ 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.
|
|
296
|
+
// Both "action" and "native action" refusals need the same confirmation guidance.
|
|
298
297
|
{
|
|
299
298
|
match: /potentially destructive (?:\w+ )*(?:action|tool) blocked/i,
|
|
300
299
|
hint: RECOVERY.CONFIRM_DANGEROUS,
|
|
@@ -495,7 +494,7 @@ const INVALID_NAME_REJECTION = /^invalid (?:[a-z]+ )*name: /i;
|
|
|
495
494
|
* us nothing new.
|
|
496
495
|
*/
|
|
497
496
|
export const FEEDBACK_ASK = 'This error is not one Reticle recognizes, which means it may be a defect in Reticle rather than in ' +
|
|
498
|
-
'the app. If you believe Reticle misbehaved, call
|
|
497
|
+
'the app. If you believe Reticle misbehaved, call reticle_session { action: "feedback" } with a root-cause analysis and ' +
|
|
499
498
|
'the call trace before moving on — that report is the only way this gets fixed.';
|
|
500
499
|
/**
|
|
501
500
|
* Build the tool-error payload spliced at the MCP boundary. `recovery` is added when the error is
|
|
@@ -36,12 +36,14 @@ export const FrictionKind = {
|
|
|
36
36
|
* a run of three is where a deliberate retry becomes a loop.
|
|
37
37
|
*/
|
|
38
38
|
const REPEAT_INVITE_AT = 3;
|
|
39
|
+
/** The call that files feedback. The old `reticle_feedback` name only answers with a redirect. */
|
|
40
|
+
const FEEDBACK_CALL = 'reticle_session { action: "feedback" }';
|
|
39
41
|
const LINES = {
|
|
40
|
-
[FrictionKind.UNKNOWN_TOOL]:
|
|
41
|
-
[FrictionKind.UNKNOWN_VERDICT]:
|
|
42
|
+
[FrictionKind.UNKNOWN_TOOL]: `no such tool — ${FEEDBACK_CALL} tells us what you expected it to do, which is how it gets built`,
|
|
43
|
+
[FrictionKind.UNKNOWN_VERDICT]: `Reticle could not tell what happened here. If you expected otherwise, ${FEEDBACK_CALL}. An ` +
|
|
42
44
|
'unknown verdict is our defect, not yours',
|
|
43
|
-
[FrictionKind.REPEATING]:
|
|
44
|
-
[FrictionKind.REFUSED]:
|
|
45
|
+
[FrictionKind.REPEATING]: `stuck on the same call? ${FEEDBACK_CALL} — say what you were trying to do and it gets fixed`,
|
|
46
|
+
[FrictionKind.REFUSED]: `if that diagnosis was wrong or unhelpful, ${FEEDBACK_CALL} — error messages are a product ` +
|
|
45
47
|
'surface and we fix the ones that fail',
|
|
46
48
|
};
|
|
47
49
|
/**
|
|
@@ -98,8 +98,9 @@ export declare const MSG_NO_OPENAI_KEY: string;
|
|
|
98
98
|
*
|
|
99
99
|
* Resolved into the env rather than threaded through four call sites: the drivers and the
|
|
100
100
|
* preference lookup all read an env record, and giving them a completed one leaves each of them
|
|
101
|
-
* exactly as simple as it was.
|
|
102
|
-
*
|
|
101
|
+
* exactly as simple as it was. The key precedence is the resolver's, the same as sync's and the
|
|
102
|
+
* CLI's: the stored key for the link's host, else the exported one (which is how CI, with no
|
|
103
|
+
* keystore, gets here). An explicitly exported HOST still wins, so a proxy is not overridden.
|
|
103
104
|
*
|
|
104
105
|
* The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
|
|
105
106
|
* the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* nothing grades) but the flows that now exist on disk, because every future run replays those
|
|
8
8
|
* deterministically with no model in the loop at all.
|
|
9
9
|
*/
|
|
10
|
-
import { ReticleEnv, ReticleTool,
|
|
10
|
+
import { ReticleEnv, ReticleTool, cloudUrlFrom, asRecord } from '@reticlehq/core';
|
|
11
11
|
import { projectForRoot } from '../../memory/project/project-for-root.js';
|
|
12
12
|
import { harnessDriver, harnessOptionsFromEnv, } from '../../features/harness/driver.js';
|
|
13
13
|
import { fillValues } from '../../features/harness/fill-values.js';
|
|
@@ -18,10 +18,11 @@ import { buildDomainModel } from '../../judgement/domain/domain-model.js';
|
|
|
18
18
|
import { readContract } from '../../memory/project/dir/reticle-dir.js';
|
|
19
19
|
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
20
20
|
import { buildHarnessPlan, planAsText } from './harness-plan.js';
|
|
21
|
-
import { openAiDriver, openAiOptionsFromEnv } from '../../features/harness/openai-driver.js';
|
|
21
|
+
import { openAiDriver, openAiOptionsFromEnv, } from '../../features/harness/openai-driver.js';
|
|
22
22
|
import { fetchPlatformConfig } from '../../features/harness/platform-config.js';
|
|
23
23
|
import { DEFAULT_MAX_STEPS, runHarness, } from '../../features/harness/harness.js';
|
|
24
24
|
import { reticleToolset } from './harness-toolset.js';
|
|
25
|
+
import { secretEnvKey } from '../../language/flows/flows.js';
|
|
25
26
|
/**
|
|
26
27
|
* Why the harness is unavailable, phrased as the way to make it available.
|
|
27
28
|
*
|
|
@@ -53,15 +54,14 @@ const msgUnknownDriver = (asked) => `Unknown harness driver \`${asked}\`. Known
|
|
|
53
54
|
*
|
|
54
55
|
* Resolved into the env rather than threaded through four call sites: the drivers and the
|
|
55
56
|
* preference lookup all read an env record, and giving them a completed one leaves each of them
|
|
56
|
-
* exactly as simple as it was.
|
|
57
|
-
*
|
|
57
|
+
* exactly as simple as it was. The key precedence is the resolver's, the same as sync's and the
|
|
58
|
+
* CLI's: the stored key for the link's host, else the exported one (which is how CI, with no
|
|
59
|
+
* keystore, gets here). An explicitly exported HOST still wins, so a proxy is not overridden.
|
|
58
60
|
*
|
|
59
61
|
* The credential arrives through `deps.linkedCloud`, a port, because resolving it here would make
|
|
60
62
|
* the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
|
|
61
63
|
*/
|
|
62
64
|
export async function withLinkedCredential(deps, env) {
|
|
63
|
-
if (apiKeyFrom(env) !== undefined)
|
|
64
|
-
return env;
|
|
65
65
|
try {
|
|
66
66
|
const linked = await deps.linkedCloud?.();
|
|
67
67
|
if (linked === undefined || null === linked)
|
|
@@ -69,7 +69,7 @@ export async function withLinkedCredential(deps, env) {
|
|
|
69
69
|
return {
|
|
70
70
|
...env,
|
|
71
71
|
[ReticleEnv.API_KEY]: linked.apiKey,
|
|
72
|
-
[ReticleEnv.CLOUD_URL]: env
|
|
72
|
+
[ReticleEnv.CLOUD_URL]: cloudUrlFrom(env) ?? linked.url,
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
catch {
|
|
@@ -412,7 +412,12 @@ function buildDriver(env, maxSteps, plan, requested, fills) {
|
|
|
412
412
|
* With no such key the label heuristic answers, exactly as it did before this existed.
|
|
413
413
|
*/
|
|
414
414
|
fillValue: fillValues({
|
|
415
|
+
secret: (label) => env[secretEnvKey(label)],
|
|
415
416
|
...(fills === undefined ? {} : { cache: fills }),
|
|
417
|
+
// GPT first: a linked machine reaches it through the platform with no key of its own.
|
|
418
|
+
...(openAiOptionsFromEnv(env) === undefined
|
|
419
|
+
? {}
|
|
420
|
+
: { openai: openAiOptionsFromEnv(env) }),
|
|
416
421
|
...(harnessOptionsFromEnv(env) === undefined
|
|
417
422
|
? {}
|
|
418
423
|
: { generator: harnessOptionsFromEnv(env) }),
|
|
@@ -351,9 +351,6 @@ export async function runTool(tool, deps, args) {
|
|
|
351
351
|
// closure carries this call's own identity, which is also what makes peak-concurrency measurable.
|
|
352
352
|
// A daemon that has served even one tool call is doing a job for somebody; see daemon-usefulness.
|
|
353
353
|
noteToolCall();
|
|
354
|
-
// The ONBOARD firsts, at the chokepoint every call already passes through — so a second dispatch
|
|
355
|
-
// path cannot quietly stop reporting them the way an extra listener would.
|
|
356
|
-
noteOnboardingFirst(tool.name);
|
|
357
354
|
// The impact record needs the project's own `.reticle` root, and this is the first place every
|
|
358
355
|
// call knows it. Idempotent: the first root wins for the daemon's lifetime.
|
|
359
356
|
initImpact({ reticleRoot: deps.reticleRoot });
|
|
@@ -385,10 +382,6 @@ export async function runTool(tool, deps, args) {
|
|
|
385
382
|
// page and then wandered off" becomes a number instead of an impression.
|
|
386
383
|
if (ACTION_TOOLS.has(tool.name)) {
|
|
387
384
|
getSessionMetrics().recordAction();
|
|
388
|
-
// The two funnel rungs this branch is the authority on: the first action of the run, and the
|
|
389
|
-
// fact that this install has been driven at all. Reported from the set that already decides
|
|
390
|
-
// what "drove the page" means, so a fourth driving tool inherits both.
|
|
391
|
-
noteActed(args);
|
|
392
385
|
}
|
|
393
386
|
const settleTiming = getSessionMetrics().startToolCall(tool.name, args);
|
|
394
387
|
const startedAt = Date.now();
|
|
@@ -533,6 +526,17 @@ export async function runTool(tool, deps, args) {
|
|
|
533
526
|
// concurrency slot and peakConcurrentTools would climb forever on an unhealthy session.
|
|
534
527
|
settleTiming(Date.now() - startedAt);
|
|
535
528
|
}
|
|
529
|
+
// Milestones describe work that ran. Refusals and paused calls must not consume the firsts.
|
|
530
|
+
// Record action before verdict: act_and_wait can complete both in this same call.
|
|
531
|
+
if (isPlainObject(raw) && !resultIsError(raw) && raw['paused'] !== true && raw['ok'] !== false) {
|
|
532
|
+
noteOnboardingFirst(tool.name);
|
|
533
|
+
const effect = raw['effect'];
|
|
534
|
+
const acted = raw['dispatched'] !== false &&
|
|
535
|
+
(!isPlainObject(effect) || effect['dispatched'] !== false) &&
|
|
536
|
+
(tool.name !== ReticleTool.ACT_AND_WAIT || isPlainObject(effect));
|
|
537
|
+
if (ACTION_TOOLS.has(tool.name) && acted)
|
|
538
|
+
noteActed(args);
|
|
539
|
+
}
|
|
536
540
|
// The human-feedback ask rides out on the first VERIFICATION that completes — the one moment the
|
|
537
541
|
// experience is fresh and there is something concrete to react to. It is spliced BEFORE the
|
|
538
542
|
// session-bound early return, because two of the four verification tools (flow_verify,
|
|
@@ -584,7 +588,7 @@ export async function runTool(tool, deps, args) {
|
|
|
584
588
|
// reticle_sessions.versionSkew and a CLI log line — two places an agent driving a flow never
|
|
585
589
|
// looks — so it could work a whole session against a mismatched pair and never learn the one fact
|
|
586
590
|
// that explains the behaviour. It rides out here on whatever tool it happens to be calling.
|
|
587
|
-
const skew = isPlainObject(raw) ? takeVersionSkew() : undefined;
|
|
591
|
+
const skew = isPlainObject(raw) ? takeVersionSkew(deps.peerSkew) : undefined;
|
|
588
592
|
// A feedback report that was accepted and then failed to send. Same one-shot channel, because the
|
|
589
593
|
// reporter is the only person who can act on it and they are not reading the daemon log — and a
|
|
590
594
|
// report announced as accepted and then silently lost is the failure the awaited send existed to
|
|
@@ -21,7 +21,7 @@ import { hasAnyAppConnectedBefore } from '../../memory/recall/prior/connection-m
|
|
|
21
21
|
import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../../portal/session/lease-visibility.js';
|
|
22
22
|
import { reticleStateHome } from '../../command/daemon/daemon.js';
|
|
23
23
|
import { LeaseNotReadyReason, REDACTED_VALUE, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, ReticleCommand, SeedStorageSchema, scrubKnownSecrets, } from '@reticlehq/core';
|
|
24
|
-
import { ReticleTool } from '@reticlehq/core';
|
|
24
|
+
import { ReticleTool, SESSION_HEALTH } from '@reticlehq/core';
|
|
25
25
|
import { asString } from '@reticlehq/core';
|
|
26
26
|
import { chromiumHint } from '../../command/cli/doctor/browser/chromium-hint.js';
|
|
27
27
|
/**
|
|
@@ -165,6 +165,10 @@ export function scrubSeedFromError(text, seed) {
|
|
|
165
165
|
}
|
|
166
166
|
return out;
|
|
167
167
|
}
|
|
168
|
+
/** True for a Playwright `storageState()` export (`{ origins: [...] }`), not our seed shape. */
|
|
169
|
+
function looksLikeStorageStateExport(seed) {
|
|
170
|
+
return Array.isArray(seed?.origins);
|
|
171
|
+
}
|
|
168
172
|
/**
|
|
169
173
|
* Turn a raw navigation failure (Playwright's `page.goto: net::ERR_… at <url>\nCall log:…`, often
|
|
170
174
|
* with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
|
|
@@ -494,8 +498,13 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
494
498
|
if (seedStorageArg !== undefined) {
|
|
495
499
|
const parsed = SeedStorageSchema.safeParse(seedStorageArg);
|
|
496
500
|
if (!parsed.success) {
|
|
497
|
-
const
|
|
498
|
-
|
|
501
|
+
const msg = scrubSeedFromError(parsed.error.issues
|
|
502
|
+
.map((i) => (i.path.length > 0 ? `${i.path.join('.')}: ${i.message}` : i.message))
|
|
503
|
+
.join(', '), seedStorageArg);
|
|
504
|
+
const hint = looksLikeStorageStateExport(seedStorageArg)
|
|
505
|
+
? 'This looks like a Playwright storageState() export ({ origins, cookies }); seedStorage takes { local?, session?, cookies? } (map origins[].localStorage into `local`).'
|
|
506
|
+
: 'Expected { local?, session?, cookies? }.';
|
|
507
|
+
throw new Error(`reticle_lease{action:"acquire"} seedStorage is invalid: ${msg}. ${hint}`);
|
|
499
508
|
}
|
|
500
509
|
validatedSeed = parsed.data;
|
|
501
510
|
}
|
|
@@ -699,9 +708,7 @@ const PREFER_EXISTING_NOTE = 'a non-leased tab for this app was already connecte
|
|
|
699
708
|
* is the right trade for a broadcast and the wrong one here, where a single `[0]` is handed back as
|
|
700
709
|
* "drive this instead". With `projectId` undefined — and it is an OPTIONAL argument that an
|
|
701
710
|
* acquiring agent has no reason to pass — every non-leased session qualified and the answer was
|
|
702
|
-
* whichever the map yielded first
|
|
703
|
-
* recommended a Phanpy tab on `:5173`, so an agent that complied would have driven a different
|
|
704
|
-
* application and reported a verdict about it.
|
|
711
|
+
* whichever the map yielded first: leasing `localhost:4312` recommended a tab on `:5173`, another app.
|
|
705
712
|
*
|
|
706
713
|
* So this narrows by the one thing a lease always knows: the ORIGIN it is being taken against.
|
|
707
714
|
* `projectId` still narrows further when given. Nothing is recommended when nothing matches, which
|
|
@@ -718,6 +725,8 @@ function liveTabFor(deps, projectId, url) {
|
|
|
718
725
|
const candidates = deps.sessions
|
|
719
726
|
.all()
|
|
720
727
|
.filter((session) => session.url !== undefined && originOf(session.url) === wanted)
|
|
728
|
+
// Nor a tab gone silent: one last heard from 105s earlier was named here and answered nothing.
|
|
729
|
+
.filter((session) => (session.lastSeenMs?.() ?? 0) <= SESSION_HEALTH.STALE_THRESHOLD_MS)
|
|
721
730
|
.map((session) => ({ id: session.id, projectId: session.projectId }));
|
|
722
731
|
return watchersToNotify(candidates, pool.leasedSessionIds(), projectId)[0];
|
|
723
732
|
}
|
|
@@ -38,7 +38,10 @@ const NO_TOOL_EQUIVALENT = [
|
|
|
38
38
|
// \u2014 so the optional group could not match, the clause survived, and the replacement landed
|
|
39
39
|
// inside the original quoting. What an agent read was the human-equivalent clause twice over,
|
|
40
40
|
// with backticks nested three deep.
|
|
41
|
-
|
|
41
|
+
// The body allows ONE nested object, because the call is `{ tool, args: { action, url } }`: the
|
|
42
|
+
// hint used to put `action` and `url` beside `tool`, which reticle_run refuses, and a `[^}]*\}`
|
|
43
|
+
// body stopped at the inner brace and left ` }` behind once the hint was corrected.
|
|
44
|
+
pattern: /`?reticle_run\s*\{\s*tool:\s*\\?"reticle_lease\\?"(?:[^{}]|\{[^{}]*\})*\}`?(\s*\([^)]*\))?(\s*[-\u2014]+\s*reticle_lease is[^.]*\.)?/g,
|
|
42
45
|
instead: 'the CLI: `reticle open <url>` (a human can equivalently run `reticle drive <url>`)',
|
|
43
46
|
},
|
|
44
47
|
{
|
|
@@ -33,6 +33,7 @@ import { withControl } from '../../portal/session/control-envelope.js';
|
|
|
33
33
|
import { asNumber, asRecord, asString } from '@reticlehq/core';
|
|
34
34
|
import { intentArg, sessionIdShape, commandOrThrow } from './tool-kit.js';
|
|
35
35
|
import { gradeOfPredicate } from './assert/assert-grade.js';
|
|
36
|
+
import { captureAssertion } from '../../language/flows/replay.js';
|
|
36
37
|
/**
|
|
37
38
|
* Evidence-completeness block: present on observe/network/console only when the ring buffer has
|
|
38
39
|
* evicted events, so a "no such event" answer is distinguishable from "I dropped it" (issue #27).
|
|
@@ -302,7 +303,7 @@ export const OBSERVE_TOOLS = [
|
|
|
302
303
|
'{ net, urlContains|method|status|count|bodyMatches|bodyContains|requestBodyContains|requestBodyMatches } ' +
|
|
303
304
|
'{ state, path|equals } { route, pathname (exact) | contains (path+query+hash) } ' +
|
|
304
305
|
'{ element, testid|role|text } { text } { console, level|contains|absent } { animation, name } ' +
|
|
305
|
-
'{ settled } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
|
|
306
|
+
'{ settled } { compare, left, right } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
|
|
306
307
|
'over element/text presence.'),
|
|
307
308
|
until: PredicateSchema.optional().describe("Alias for `predicate` (act_and_wait's name)."),
|
|
308
309
|
timeout_ms: timeoutMsSchema
|
|
@@ -446,6 +447,13 @@ export const OBSERVE_TOOLS = [
|
|
|
446
447
|
// destroys — see runs/run-context.ts. Recorded WITHOUT an attribution window: this tool drives
|
|
447
448
|
// nothing, so no event it observed was caused by it.
|
|
448
449
|
session.recordAction(ReticleTool.ASSERT, asRecord(args), verdictEffect);
|
|
450
|
+
// A passing check over the default window (since the last act) is proof of that act, so it
|
|
451
|
+
// joins that step in the recording — otherwise "act, then assert" saves a bare click. An
|
|
452
|
+
// explicit `since` may reach back past the last step, where replay would not find it. The
|
|
453
|
+
// store itself refuses when a navigation came after that step: see RecordingStore.markNavigated.
|
|
454
|
+
if (Verified.YES === decision['verified'] && args['since'] === undefined) {
|
|
455
|
+
captureAssertion(deps.recordings, predicate);
|
|
456
|
+
}
|
|
449
457
|
return withControl(session, {
|
|
450
458
|
...decision,
|
|
451
459
|
...annotateStarvedFailure(session, verdict),
|
|
@@ -59,6 +59,16 @@ export interface ToolDeps<Ext = unknown> {
|
|
|
59
59
|
* `tools.ts`, which keeps every existing `ToolDeps` construction working unchanged.
|
|
60
60
|
*/
|
|
61
61
|
snapshots?: SnapshotCache;
|
|
62
|
+
/**
|
|
63
|
+
* The skew between THIS client's MCP server and the daemon, told once to this connection only.
|
|
64
|
+
*
|
|
65
|
+
* Per attach for the same reason as `snapshots`: one daemon serves every agent on the machine, and
|
|
66
|
+
* a daemon-wide queue handed one agent's "restart your MCP server" to whichever agent called next,
|
|
67
|
+
* whose server matched (#1136). Absent ⇒ this connection's server matched, or it did not announce.
|
|
68
|
+
*/
|
|
69
|
+
peerSkew?: {
|
|
70
|
+
take(): string | undefined;
|
|
71
|
+
};
|
|
62
72
|
/** cross-run outcome memory (.reticle/project.json). */
|
|
63
73
|
project: ProjectStore;
|
|
64
74
|
/** optional native-input provider. undefined ⇒ everything stays synthetic. */
|
|
@@ -236,6 +236,10 @@ export const RAW_TOOLS = [
|
|
|
236
236
|
.boolean()
|
|
237
237
|
.optional()
|
|
238
238
|
.describe('True when the page exceeded the snapshot cap. The tree is then a document-order PREFIX: an element being absent from it does NOT mean it is absent from the page.'),
|
|
239
|
+
// The refs a capped read never entered — re-read one with `scope`. Undeclared, a validating
|
|
240
|
+
// client stripped it, and a truncated tree arrived with no way to finish reading it.
|
|
241
|
+
unread: z.array(z.string()).optional(),
|
|
242
|
+
unreadOverflow: z.boolean().optional(),
|
|
239
243
|
note: z
|
|
240
244
|
.string()
|
|
241
245
|
.optional()
|
|
@@ -19,8 +19,6 @@
|
|
|
19
19
|
* never quietly start forwarding free text.
|
|
20
20
|
*/
|
|
21
21
|
export declare const OTHER_VALUE = "other";
|
|
22
|
-
/** True for anything we are willing to report a value for. */
|
|
23
|
-
export declare function isSafeEnumParam(name: string): boolean;
|
|
24
22
|
/**
|
|
25
23
|
* The reportable form of one parameter: `name` on its own, or `name:value` when the parameter is a
|
|
26
24
|
* known enum. Never the raw value of anything else.
|
|
@@ -27,7 +27,7 @@ import { ActionType, FeedbackKind, QueryBy, SnapshotMode } from '@reticlehq/core
|
|
|
27
27
|
const SAFE_ENUM_PARAMS = new Map([
|
|
28
28
|
// Every entry DERIVED from the enum core already owns, never hand-listed. The first version listed
|
|
29
29
|
// them literally and was wrong on the day it shipped, not merely at risk: `action` was missing
|
|
30
|
-
// `fill`, `dblclick`, `submit`, `scrollIntoView
|
|
30
|
+
// `fill`, `dblclick`, `submit`, `scrollIntoView` and `drag` — so `fill`, one of the most
|
|
31
31
|
// common actions there is, silently reported as `other` — while carrying a `scroll` that
|
|
32
32
|
// `ActionType` does not define. `mode` mixed SnapshotMode with unrelated session-lifecycle values.
|
|
33
33
|
// Deriving is not tidiness here; it is the difference between the data being right and being wrong.
|
|
@@ -41,10 +41,6 @@ const SAFE_ENUM_PARAMS = new Map([
|
|
|
41
41
|
['level', new Set(['error', 'warn', 'info', 'log', 'debug'])],
|
|
42
42
|
]);
|
|
43
43
|
export const OTHER_VALUE = 'other';
|
|
44
|
-
/** True for anything we are willing to report a value for. */
|
|
45
|
-
export function isSafeEnumParam(name) {
|
|
46
|
-
return SAFE_ENUM_PARAMS.has(name);
|
|
47
|
-
}
|
|
48
44
|
/**
|
|
49
45
|
* The reportable form of one parameter: `name` on its own, or `name:value` when the parameter is a
|
|
50
46
|
* known enum. Never the raw value of anything else.
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Whose defect was that tool error?
|
|
3
3
|
*
|
|
4
4
|
* `toolErrors` was one number covering three failures with three different fixes, so the top-line
|
|
5
|
-
* count could be acted on only by unpacking `errors[]` by hand and reading the prose.
|
|
6
|
-
*
|
|
7
|
-
* Read as "126 tool errors" that hides the sixth of them where OUR schema failed to explain itself —
|
|
5
|
+
* count could be acted on only by unpacking `errors[]` by hand and reading the prose. Read as one
|
|
6
|
+
* number, it hides the share where OUR schema failed to explain itself (serialized zod arrays),
|
|
8
7
|
* which is the only bucket we can fix by writing better descriptions.
|
|
9
8
|
*
|
|
10
9
|
* Classified from the message SHAPE, never stored raw: this returns a bucket, and the caller keeps
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Whose defect was that tool error?
|
|
3
3
|
*
|
|
4
4
|
* `toolErrors` was one number covering three failures with three different fixes, so the top-line
|
|
5
|
-
* count could be acted on only by unpacking `errors[]` by hand and reading the prose.
|
|
6
|
-
*
|
|
7
|
-
* Read as "126 tool errors" that hides the sixth of them where OUR schema failed to explain itself —
|
|
5
|
+
* count could be acted on only by unpacking `errors[]` by hand and reading the prose. Read as one
|
|
6
|
+
* number, it hides the share where OUR schema failed to explain itself (serialized zod arrays),
|
|
8
7
|
* which is the only bucket we can fix by writing better descriptions.
|
|
9
8
|
*
|
|
10
9
|
* Classified from the message SHAPE, never stored raw: this returns a bucket, and the caller keeps
|
|
@@ -2,5 +2,3 @@
|
|
|
2
2
|
export declare function withHarnessDrive<T>(drive: () => Promise<T>): Promise<T>;
|
|
3
3
|
/** True while a harness drive is in progress. */
|
|
4
4
|
export declare function harnessDriving(): boolean;
|
|
5
|
-
/** Tests only — a leaked span would mis-attribute every verdict after it. */
|
|
6
|
-
export declare function resetHarnessDrive(): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/** A
|
|
1
|
+
/** A successful page read. The dispatcher calls this after excluding refusals and pauses. */
|
|
2
2
|
export declare function noteOnboardingFirst(toolName: string): void;
|
|
3
3
|
/**
|
|
4
4
|
* The first ACTION of this run, and the fact that this install has been driven at all.
|
|
5
5
|
*
|
|
6
|
-
* Called
|
|
6
|
+
* Called after the dispatcher observes a dispatched action, rather than on an attempted call.
|
|
7
7
|
* The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
|
|
8
8
|
* one call as two different firsts — and the worry was backwards: this server's instructions tell
|
|
9
9
|
* every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
|
|
@@ -15,7 +15,7 @@ import { reportOnboardingStep } from './onboarding-funnel.js';
|
|
|
15
15
|
* drives in another has still only crossed each of these once.
|
|
16
16
|
*/
|
|
17
17
|
const seen = { look: false, act: false, verdict: false, driven: false };
|
|
18
|
-
/** A
|
|
18
|
+
/** A successful page read. The dispatcher calls this after excluding refusals and pauses. */
|
|
19
19
|
export function noteOnboardingFirst(toolName) {
|
|
20
20
|
if (!seen.look && LOOK_TOOLS.has(toolName)) {
|
|
21
21
|
seen.look = true;
|
|
@@ -29,7 +29,7 @@ export function noteOnboardingFirst(toolName) {
|
|
|
29
29
|
/**
|
|
30
30
|
* The first ACTION of this run, and the fact that this install has been driven at all.
|
|
31
31
|
*
|
|
32
|
-
* Called
|
|
32
|
+
* Called after the dispatcher observes a dispatched action, rather than on an attempted call.
|
|
33
33
|
* The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
|
|
34
34
|
* one call as two different firsts — and the worry was backwards: this server's instructions tell
|
|
35
35
|
* every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
|
package/dist/wire-journal.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Bridge } from './portal/bridge/bridge.js';
|
|
2
2
|
import { FlowStore } from './language/flows/flows.js';
|
|
3
|
+
import type { FlowFile } from '@reticlehq/core';
|
|
3
4
|
import type { FileSystemPort } from './memory/project/fs/fs-port.js';
|
|
4
5
|
import type { TapeStep } from './memory/journal/drive-flow.js';
|
|
5
6
|
import type { OnboardingStep } from '@reticlehq/core/telemetry';
|
|
@@ -26,6 +27,8 @@ export declare function attachJournal(bridge: Bridge, deps: {
|
|
|
26
27
|
/** Tell cloud sync a run landed, so it cycles instead of waiting for its timer. */
|
|
27
28
|
onRunPersisted?: () => void;
|
|
28
29
|
flows?: FlowStore;
|
|
30
|
+
/** Who is driving, stamped on each flow teardown saves. */
|
|
31
|
+
author?: () => FlowFile['author'];
|
|
29
32
|
/** What this project is willing to keep — the `retain` block of its `.reticle.json`. */
|
|
30
33
|
retain?: PruneWorkspaceOptions;
|
|
31
34
|
}): void;
|
package/dist/wire-journal.js
CHANGED
|
@@ -65,6 +65,10 @@ export function attachJournal(bridge, deps) {
|
|
|
65
65
|
// Teardown: flush the journal tail to disk + persist what this session learned.
|
|
66
66
|
bridge.attachSessionEnd(makeSessionEnd({
|
|
67
67
|
...deps,
|
|
68
|
+
// A drive is saved in the session's own project, where replay will look for it.
|
|
69
|
+
...(deps.flows === undefined
|
|
70
|
+
? {}
|
|
71
|
+
: { flowsAt: (root) => new FlowStore(deps.fs, root, { now: Date.now }) }),
|
|
68
72
|
...(deps.retain === undefined ? {} : { retain: deps.retain }),
|
|
69
73
|
// Retention runs from teardown, and it must not delete the journal of a session that is still
|
|
70
74
|
// being written. The registry is the only thing that knows which those are.
|
|
@@ -18,5 +18,3 @@
|
|
|
18
18
|
* allowed to depend on anything, which is what makes them useful as fixtures.
|
|
19
19
|
*/
|
|
20
20
|
export declare function publishedPackageDirs(): string[];
|
|
21
|
-
/** The same list, as the last part of each path: `core`, `server`, `dom`, and so on. */
|
|
22
|
-
export declare function publishedPackageNames(): string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import { workspaceGlobs } from '../../scripts/check-boundaries.mjs';
|
|
4
4
|
import { REPO_ROOT } from './machine/repo-root.js';
|
|
5
5
|
/**
|
|
@@ -47,11 +47,3 @@ function collect(dir, levels, into) {
|
|
|
47
47
|
collect(join(dir, entry.name), levels - 1, into);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
/** The same list, as the last part of each path: `core`, `server`, `dom`, and so on. */
|
|
51
|
-
export function publishedPackageNames() {
|
|
52
|
-
// `basename`, not `split('/')`: these directories are built with `join`, so on Windows they hold
|
|
53
|
-
// backslashes and contain no forward slash at all — the split then returns the WHOLE ABSOLUTE
|
|
54
|
-
// PATH as the package name. Every caller comparing a name against a real one silently stops
|
|
55
|
-
// matching, which is the same defect that reported fourteen packages as uncovered.
|
|
56
|
-
return publishedPackageDirs().map((dir) => basename(dir));
|
|
57
|
-
}
|
package/docs/README.md
CHANGED
|
@@ -13,8 +13,9 @@ Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `d
|
|
|
13
13
|
| Page | What it answers |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| [quickstart.mdx](quickstart.mdx) | five minutes to a real verdict; every response on it was captured live |
|
|
16
|
-
| [what-is-reticle.mdx](what-is-reticle.mdx) | the definition,
|
|
17
|
-
| [why-reticle.mdx](why-reticle.mdx) | the false-green problem, the measured case, and
|
|
16
|
+
| [what-is-reticle.mdx](what-is-reticle.mdx) | the definition, where it fits beside other tools, and what it needs to run |
|
|
17
|
+
| [why-reticle.mdx](why-reticle.mdx) | the false-green problem, the measured case, and what pairs well with it |
|
|
18
|
+
| [use-cases.mdx](use-cases.mdx) | what people verify with it: agent-built changes, security behaviour, accessibility, performance, SEO basics, personas |
|
|
18
19
|
| [install-agentic.mdx](install-agentic.mdx) | what `npx @reticlehq/server init` writes, and how to read its marks |
|
|
19
20
|
| [install-manual.mdx](install-manual.mdx) | wiring the MCP server and SDK by hand, per agent and framework |
|
|
20
21
|
| [skill-setup.md](skill-setup.md) | the setup half of SKILL.md: what to pass `init`, reading its report, and what to do when it cannot finish |
|
|
@@ -24,6 +25,7 @@ Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `d
|
|
|
24
25
|
| [capabilities.mdx](capabilities.mdx) | coverage, storage, fake clocks, crawl, visual diff, network mocking |
|
|
25
26
|
| [recipes.mdx](recipes.mdx) | worked examples for real situations, with the response each returned |
|
|
26
27
|
| [actions.mdx](actions.mdx) | every action and its arguments, including `press` and its history |
|
|
28
|
+
| [environment.mdx](environment.mdx) | every environment variable Reticle reads, what it changes, and which process reads it |
|
|
27
29
|
| [faq.mdx](faq.mdx) | production, frameworks, comparisons, and the honest limits |
|
|
28
30
|
| [tools/overview.mdx](tools/overview.mdx) | the 18 advertised tools, the 30 in the cold tail, and why |
|
|
29
31
|
| [tools/snapshot.mdx](tools/snapshot.mdx) | `reticle_look { action: "page" }`: three modes, from full tree to a 25-token route check |
|
package/docs/actions.mdx
CHANGED
|
@@ -5,7 +5,7 @@ description: Every action reticle_act and reticle_act_and_wait can perform, with
|
|
|
5
5
|
icon: hand-pointer
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Every action Reticle can perform takes the same shape: a `ref` from [`
|
|
8
|
+
Every action Reticle can perform takes the same shape: a `ref` from [`reticle_look { action: "page" }`](/tools/snapshot) or [`reticle_look { action: "find" }`](/tools/query), an `action` name, and an optional `args` object. The same actions are available on [`reticle_act`](/tools/act), which proves nothing, and on [`reticle_act_and_wait`](/tools/act-and-wait), which returns a verdict.
|
|
9
9
|
|
|
10
10
|
```json
|
|
11
11
|
{ "ref": "e104", "action": "fill", "args": { "value": "reticle" } }
|
|
@@ -20,13 +20,12 @@ Every action Reticle can perform takes the same shape: a `ref` from [`reticle_sn
|
|
|
20
20
|
| `type` | `{ text }` | **Appends** to the current value |
|
|
21
21
|
| `clear` | none | Empties an input |
|
|
22
22
|
| `select` | `{ value }` | For `<select>` |
|
|
23
|
-
| `check` / `uncheck` | none | Checkbox and radio |
|
|
23
|
+
| `check` / `uncheck` | none | Checkbox and radio, or a `role="checkbox"`/`role="switch"` element (fails if `aria-checked` does not change) |
|
|
24
24
|
| `submit` | none | Submits the element's `<form>` |
|
|
25
25
|
| `press` | `{ text }` | The key **name**: `Escape`, `Tab`, `Enter` |
|
|
26
26
|
| `scrollIntoView` | none | |
|
|
27
27
|
| `upload` | `{ content?, name?, type?, path? }` | Sets a file on `<input type="file">`. Inline bytes default to `"reticle test file"` / `"file.txt"` / `"text/plain"`. `path` reads a file from disk, scoped to the project root plus any opt-in directories listed in `.reticle.json` as `"uploadRoots": ["~/Downloads", "/Volumes/fixtures"]`. A refusal names every allowed root. |
|
|
28
28
|
| `drag` | `{ toRef }` | Pointer-based drag (dnd-kit, react-beautiful-dnd) plus HTML5 DnD. `target` is an alias |
|
|
29
|
-
| `webmcp` | `{ tool, params }` | Calls a `navigator.modelContext` tool, if the site exposes one |
|
|
30
29
|
|
|
31
30
|
### Arguments that are not actions
|
|
32
31
|
|
|
@@ -93,7 +92,7 @@ Several actions in one round trip:
|
|
|
93
92
|
```
|
|
94
93
|
|
|
95
94
|
<Warning>
|
|
96
|
-
[`
|
|
95
|
+
[`reticle_act { steps: [...] }`](/tools/act-sequence) returns `ok: true` when the steps were
|
|
97
96
|
**dispatched**, not when they worked. Batch the setup, then prove the outcome with
|
|
98
97
|
[`reticle_act_and_wait`](/tools/act-and-wait) on the final step.
|
|
99
98
|
</Warning>
|
package/docs/agent-cheatsheet.md
CHANGED
|
@@ -4,7 +4,7 @@ description: 'One screen to get fluent: the look → act → observe → assert
|
|
|
4
4
|
icon: bolt
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The loop is **look → act → observe → assert**, and
|
|
7
|
+
The loop is **look → act → observe → assert**, and verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`. Reach for `reticle_act_and_wait({ ref, action, until })` first: it names the expected consequence before the action, which is the difference between a check and a rationalisation. `verified: "unknown"` is not a pass.
|
|
8
8
|
|
|
9
9
|
One screen to get fluent. Reticle is the **proof layer for AI agents**: no screenshots, no vision model, evidence not prose. Everything below returns structured data. Full guide: [usage.md](usage.md).
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ One screen to get fluent. Reticle is the **proof layer for AI agents**: no scree
|
|
|
17
17
|
| **observe** | `reticle_observe` / `reticle_assert { action: "wait" }` | Everything the app did after `since` / block until true. |
|
|
18
18
|
| **assert** | `reticle_assert` | Evaluate a predicate → `{ pass, evidence, failureReason? }`. The end of every loop. |
|
|
19
19
|
|
|
20
|
-
> **
|
|
20
|
+
> **Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`.** Everything else moves or reads the app and proves nothing, so a drive ending without a verdict has no result however many tools it used. `reticle_act` is the tool agents reach for by habit and it catches nothing; `act_and_wait` is where defects actually surface. And `verified: "unknown"` is not a pass: it means Reticle drove the app and could not tell what happened. Report it as unknown.
|
|
21
21
|
|
|
22
22
|
`reticle_act` returns a `since` cursor; pass it to `reticle_observe({ since })` to scope the window. Elements are addressed by stable refs (`e7`) from `snapshot`/`query`; they re-resolve across re-renders.
|
|
23
23
|
|
|
@@ -125,9 +125,9 @@ Call an old name and you are told the new one. `reticle_tools` prints the live s
|
|
|
125
125
|
|
|
126
126
|
- `reticle_verify`: **the only core tool that CONCLUDES.** `{ action:"crawl" }` drives every reachable control itself and reports the whole fault set in one call (destructive: it really clicks). `{ action:"flows" }` replays every saved flow with no model in the loop. Reach for it when you think you are done: every other tool here answers "here is more to look at".
|
|
127
127
|
|
|
128
|
-
Not on this surface at all,
|
|
128
|
+
Not on this surface at all, but one hop away: `reticle_run { tool, args }` calls any registered tool by name, advertised or not, and `reticle_tools { names: [...] }` loads its arguments. That reaches `reticle_capabilities` (the app's whole testable surface in one call), `reticle_domain` (learn the app + gaps), `reticle_baseline {action:"diff"}`, `reticle_project` (run history), `reticle_screenshot`, `reticle_visual_diff`, `reticle_storage`, `reticle_network_mock` and `reticle_clock`. A daemon started with `RETICLE_ADVERTISE_ALL_TOOLS=1` advertises them directly instead.
|
|
129
129
|
|
|
130
|
-
**What the wider surface adds,
|
|
130
|
+
**What the wider surface adds, through `reticle_run` or advertised directly with `RETICLE_ADVERTISE_ALL_TOOLS=1`:** record/replay a journey (`reticle_record {action:"start"}/stop`, `reticle_replay`), persist a self-healing golden flow (`reticle_flow_save*` / `reticle_flow_replay` / `reticle_verify { action: "heal" }`), compile annotations (`reticle_annotate`), explore autonomously (`reticle_explore` lists controls; `reticle_verify {action:"crawl"}` is now advertised directly, clicks them all and reports anomalies, and is **destructive**), reveal a virtualized off-screen row (`reticle_scroll_to`, for when `reticle_look { action: "find" }` finds nothing because a windowed list hasn't rendered it yet), visual-check (`reticle_screenshot` / `reticle_visual_diff`, pinned with `reticle_viewport` for reproducible baselines), test error/edge states by stubbing the network (`reticle_network_mock`: 500 / offline / delay, driven or leased), control time for toasts/debounces/auto-dismiss (`reticle_clock { freeze | advanceMs | reset }`), or work with a human (`reticle_session {action:"end"}` / `reticle_session {action:"resume"}` / `reticle_session {action:"messages"}`, and **`reticle_session {action:"review"}`** to drain + fix the bugs the human flagged from the panel).
|
|
131
131
|
|
|
132
132
|
## flows vs baselines vs project.json (the persistence layers)
|
|
133
133
|
|