@reticlehq/server 3.3.0 → 3.4.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 +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- 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 +21 -3
- 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 +5 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- 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 +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +62 -10
- 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/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 +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- 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 +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- 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.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- 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 +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.js +40 -11
- 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 +68 -3
- package/dist/memory/cloud/sync-cycle.js +298 -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/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- 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/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 +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- 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/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 +5 -0
- package/dist/portal/session/session.js +15 -19
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- 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 +4 -2
- 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/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 +26 -50
- 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/error-recovery.js +4 -4
- 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 +7 -6
- 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/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- 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/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 -3
- 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 +14 -4
- 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 +9 -0
- 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 +3 -3
- 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 +1 -1
- 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/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +2 -2
- 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/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 -6
- 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
|
@@ -13,7 +13,7 @@ export const BROWSER_TOOLS = [
|
|
|
13
13
|
{
|
|
14
14
|
name: ReticleTool.NAVIGATE,
|
|
15
15
|
example: { url: '/settings' },
|
|
16
|
-
description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the page
|
|
16
|
+
description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the old page cannot report on it. The daemon then waits up to `timeout_ms` (default 5000) for the SDK to reconnect. `confirmed:true` with a `sessionId` means the requested page arrived and you can act immediately. `confirmed:false` with `landedOn` means the navigation arrived but the app landed at another URL, such as an authentication or route-guard redirect; use `landedOn` to understand where the browser actually ended up. `confirmed:false` without `landedOn` means the target was not observed within the wait window; a slow app may still be on its way, so increase `timeout_ms` when appropriate.',
|
|
17
17
|
inputSchema: {
|
|
18
18
|
url: z.string().optional().describe('The URL to navigate to. Omit when using reload.'),
|
|
19
19
|
reload: z
|
|
@@ -38,6 +38,10 @@ export const BROWSER_TOOLS = [
|
|
|
38
38
|
confirmed: z.boolean().optional(),
|
|
39
39
|
/** The session the SDK reconnected as, when arrival was confirmed — it is a NEW id. */
|
|
40
40
|
sessionId: z.string().optional(),
|
|
41
|
+
/**
|
|
42
|
+
* The URL where the navigated document landed when it differs from the requested target.
|
|
43
|
+
*/
|
|
44
|
+
landedOn: z.string().optional(),
|
|
41
45
|
/**
|
|
42
46
|
* On confirmed:false — the budget (ms) that expired without the page coming back. Says which
|
|
43
47
|
* of "Reticle stopped waiting" and "the page never came back" you are looking at.
|
|
@@ -58,6 +62,7 @@ export const BROWSER_TOOLS = [
|
|
|
58
62
|
// back past this point. Reported from the field as an assertion whose clauses all passed
|
|
59
63
|
// coming back `contradicted` by hundreds of 500s against resources that were already gone.
|
|
60
64
|
before.lastAct.markNavigated(before.elapsed());
|
|
65
|
+
deps.recordings.markNavigated();
|
|
61
66
|
await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
|
|
62
67
|
hard: true === args['hard'],
|
|
63
68
|
});
|
|
@@ -96,6 +101,7 @@ export const BROWSER_TOOLS = [
|
|
|
96
101
|
// document just as thoroughly. `beginAction` attributes events to this action; it does not move
|
|
97
102
|
// the window a later assert judges over, and those are two different jobs.
|
|
98
103
|
session.lastAct.markNavigated(session.elapsed());
|
|
104
|
+
deps.recordings.markNavigated();
|
|
99
105
|
try {
|
|
100
106
|
const result = (await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.NAVIGATE, { url }));
|
|
101
107
|
// `ok` is the browser accepting the instruction, not the page arriving — see navigate-result.
|
|
@@ -104,7 +110,13 @@ export const BROWSER_TOOLS = [
|
|
|
104
110
|
// reticle_sessions itself. A refusal skips the wait: there is nothing to wait FOR.
|
|
105
111
|
const timeoutMs = asNumber(args['timeout_ms']) ?? ARRIVAL_TIMEOUT_MS;
|
|
106
112
|
const arrival = true === result.ok
|
|
107
|
-
? await awaitArrival(deps.sessions, target, {
|
|
113
|
+
? await awaitArrival(deps.sessions, target, {
|
|
114
|
+
// Keep the original Session object and URL so arrival detection can distinguish
|
|
115
|
+
// an in-progress navigation from a document that arrived somewhere else.
|
|
116
|
+
navigatedSession: session,
|
|
117
|
+
navigatedFrom: session.url,
|
|
118
|
+
priorIds,
|
|
119
|
+
}, timeoutMs)
|
|
108
120
|
: null;
|
|
109
121
|
return navigateResult(result, arrival, timeoutMs);
|
|
110
122
|
}
|
|
@@ -10,6 +10,7 @@ import { ReticleTool } from '@reticlehq/core';
|
|
|
10
10
|
import { asString } from '@reticlehq/core';
|
|
11
11
|
import { exercisedCount } from './gaps/coverage-identity.js';
|
|
12
12
|
import { commandOrThrow, sessionIdShape } from './tool-kit.js';
|
|
13
|
+
import { foldAppCoverage } from '../../features/exhaust/app-coverage.js';
|
|
13
14
|
/**
|
|
14
15
|
* `reticle_coverage` — which interactive controls this session has driven, and which it has not.
|
|
15
16
|
*
|
|
@@ -69,8 +70,17 @@ export function buildCoverageTools(toolNames) {
|
|
|
69
70
|
name: ReticleTool.COVERAGE,
|
|
70
71
|
description: 'Which interactive controls you have driven this session, and which you have NOT. Returns { total, exercised, untouched:[{ref,label}], alsoDroveGone? } over the controls currently on the page. Use it to decide whether verification is finished: an untouched list that still holds the controls your change affects means you are not done. This is about what you did not TOUCH — distinct from the `coverage` field on an action result, which reports what the layer could not SEE.',
|
|
71
72
|
example: {},
|
|
72
|
-
inputSchema: {
|
|
73
|
+
inputSchema: {
|
|
74
|
+
app: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Also fold into the app-wide ledger; returns `app.levels`.'),
|
|
78
|
+
...sessionIdShape,
|
|
79
|
+
},
|
|
73
80
|
outputSchema: {
|
|
81
|
+
// The app-wide ledger — see features/exhaust/ledger.ts. Loose: run-only, and the levels
|
|
82
|
+
// carry their own shape.
|
|
83
|
+
app: z.record(z.unknown()).optional(),
|
|
74
84
|
total: z.number(),
|
|
75
85
|
exercised: z.number(),
|
|
76
86
|
untouched: z.array(z.object({ ref: z.string(), label: z.string() })),
|
|
@@ -171,6 +181,22 @@ export function buildCoverageTools(toolNames) {
|
|
|
171
181
|
...(regressed === undefined ? {} : { observabilityRegressed: regressed }),
|
|
172
182
|
featureUse,
|
|
173
183
|
toolHitRate,
|
|
184
|
+
...(true === args['app']
|
|
185
|
+
? {
|
|
186
|
+
app: await foldAppCoverage({
|
|
187
|
+
fs: deps.fs,
|
|
188
|
+
reticleRoot: sessionRoot(deps, sessionId),
|
|
189
|
+
seen: parseControls(tree).map((c) => c.label),
|
|
190
|
+
session,
|
|
191
|
+
now: deps.now,
|
|
192
|
+
...(deps.realInput?.takeCodeCoverage === undefined
|
|
193
|
+
? {}
|
|
194
|
+
: {
|
|
195
|
+
takeCode: (url) => deps.realInput?.takeCodeCoverage?.(url) ?? Promise.resolve(undefined),
|
|
196
|
+
}),
|
|
197
|
+
}),
|
|
198
|
+
}
|
|
199
|
+
: {}),
|
|
174
200
|
};
|
|
175
201
|
},
|
|
176
202
|
},
|
|
@@ -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 ' +
|
|
@@ -495,7 +495,7 @@ const INVALID_NAME_REJECTION = /^invalid (?:[a-z]+ )*name: /i;
|
|
|
495
495
|
* us nothing new.
|
|
496
496
|
*/
|
|
497
497
|
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
|
|
498
|
+
'the app. If you believe Reticle misbehaved, call reticle_session { action: "feedback" } with a root-cause analysis and ' +
|
|
499
499
|
'the call trace before moving on — that report is the only way this gets fixed.';
|
|
500
500
|
/**
|
|
501
501
|
* 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';
|
|
@@ -22,6 +22,7 @@ import { openAiDriver, openAiOptionsFromEnv } from '../../features/harness/opena
|
|
|
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,6 +412,7 @@ 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 }),
|
|
416
417
|
...(harnessOptionsFromEnv(env) === undefined
|
|
417
418
|
? {}
|
|
@@ -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),
|
|
@@ -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.
|
|
@@ -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;
|
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 seventeen 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,7 +20,7 @@ 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 | |
|
|
@@ -93,7 +93,7 @@ Several actions in one round trip:
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
<Warning>
|
|
96
|
-
[`
|
|
96
|
+
[`reticle_act { steps: [...] }`](/tools/act-sequence) returns `ok: true` when the steps were
|
|
97
97
|
**dispatched**, not when they worked. Batch the setup, then prove the outcome with
|
|
98
98
|
[`reticle_act_and_wait`](/tools/act-and-wait) on the final step.
|
|
99
99
|
</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
|
|
package/docs/architecture.md
CHANGED
|
@@ -14,7 +14,7 @@ Reticle has three moving parts: a dev-only **SDK** inside your page that instrum
|
|
|
14
14
|
|
|
15
15
|
Your app, in dev, embeds a tiny **SDK** that instruments the page (DOM, network, console, routing, framework state) and opens a WebSocket to a local **bridge**. The bridge runs inside the Reticle **server**, which also exposes an **MCP server**, the standard protocol AI agents speak.
|
|
16
16
|
|
|
17
|
-
Your coding agent calls MCP tools (`
|
|
17
|
+
Your coding agent calls MCP tools (`reticle_look { action: "find" }`, `reticle_act`, `reticle_assert`, …); the server turns them into commands over the WebSocket; the SDK executes them in the page and streams back structured events. The agent thus **looks, acts, observes, and asserts** on the real running app, never on a screenshot.
|
|
18
18
|
|
|
19
19
|
```mermaid
|
|
20
20
|
flowchart LR
|
|
@@ -23,7 +23,7 @@ flowchart LR
|
|
|
23
23
|
B["@reticlehq/browser<br/>SDK in your app<br/>(the DOM)"]
|
|
24
24
|
D[(".reticle/<br/>flows · baselines<br/>runs · contract")]
|
|
25
25
|
|
|
26
|
-
A -- "MCP (stdio/SSE)<br/>
|
|
26
|
+
A -- "MCP (stdio/SSE)<br/>reticle_look / act / assert" --> S
|
|
27
27
|
S -- "tool results" --> A
|
|
28
28
|
S <-- "WebSocket<br/>commands / events" --> B
|
|
29
29
|
S <--> D
|
|
@@ -37,7 +37,7 @@ Reticle is a pnpm + Turborepo monorepo. The split is not cosmetic: each boundary
|
|
|
37
37
|
|
|
38
38
|
| Package | Runs in | Responsibility | Hard rule |
|
|
39
39
|
| --- | --- | --- | --- |
|
|
40
|
-
| `@reticlehq/core` | both | The **wire contract**: every constant + zod schema crossing any boundary | Depends only on `zod` |
|
|
40
|
+
| `@reticlehq/core` | both | The **wire contract**: every constant + zod schema crossing any boundary | Depends only on `zod` and `open-verification` |
|
|
41
41
|
| `@reticlehq/browser` | the browser | Instrument the page; execute commands; emit events | Never imports Node APIs |
|
|
42
42
|
| `@reticlehq/server` | Node | The bridge, the MCP server, the `reticle` CLI, flow/run storage | Never imports DOM APIs |
|
|
43
43
|
| `@reticlehq/react` | the browser | The SDK **kit** you install in a browser app: re-exports the browser sensor (so one install gives both `reticle` and `install`) and maps a DOM node → React component → source `file:line` | Core works without the source-mapping half |
|
|
@@ -59,8 +59,8 @@ The browser and server both import it; neither can invent a message the other do
|
|
|
59
59
|
|
|
60
60
|
1. **Connect.** In dev, your app calls `reticle.connect({ session })`. The SDK opens a WebSocket to the bridge (`ws://localhost:4400/reticle` by default) and sends a `HELLO` carrying the session id, protocol version, and (if configured) a pairing token. Each browser tab uses `SESSION_AUTO` (a unique id), so multiple apps/tabs never collide.
|
|
61
61
|
2. **Capture.** The SDK installs observers: a `MutationObserver` for DOM changes, wrappers around `fetch`/`XHR` for network, a console hook, a history hook for routing, and registries the app opts into (`registerStore`, `registerCapabilities`, `reticle.signal`). Events flow into a bounded **ring buffer**, so recent history is always available and memory is capped.
|
|
62
|
-
3. **Look / act.** The agent calls an MCP tool. `
|
|
63
|
-
4. **Observe.** After an action, the agent reads what happened with `
|
|
62
|
+
3. **Look / act.** The agent calls an MCP tool. `reticle_look { action: "find" }` finds an element by role/text/testid/ component and returns a stable **ref**. `reticle_act` dispatches an action against that ref and returns an **effect** report (did it land, did the DOM mutate, did focus move…). The server sends the command over the WebSocket; the SDK runs it and replies.
|
|
63
|
+
4. **Observe.** After an action, the agent reads what happened with `reticle_observe { action: "network" }`, `reticle_observe { action: "console" }`, `reticle_look { action: "state" }`, or the reaction digest from `reticle_act_and_wait`. The server pulls the relevant slice of the ring buffer (scoped to a cursor so stale events can't leak in) and returns a compact summary.
|
|
64
64
|
5. **Assert.** `reticle_assert` (and a flow's declared `success`) evaluates a **predicate** over program truth (a network call that returned 200, a store value, a `signal` the app emitted), not just "an element exists." This is the difference between "looks done" and "is done."
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -87,7 +87,7 @@ The same verification runs over and over, every commit, every CI run. Reticle re
|
|
|
87
87
|
|
|
88
88
|
### 4. Dev-only, localhost-only, your app data stays local
|
|
89
89
|
|
|
90
|
-
The SDK is tree-shaken out of production builds and connects only to a local bridge. The bridge binds to loopback by default; exposing it beyond localhost _requires_ a pairing token (the server refuses to bind a non-loopback host without one). Every environment variable that gates a security control is a single named constant, so a typo can't silently disable auth. Nothing from the app under test ever leaves your machine: no DOM, network, console, state, or source. The CLI reports anonymous, opt-out usage metrics only (a random id + event names like `invoke`/`session_start`; no code, no PII; see [telemetry](telemetry.md)); opt out with `reticle telemetry disable`, `RETICLE_TELEMETRY=0`, or `DO_NOT_TRACK=1`. The one thing that carries free text is feedback you or your agent deliberately send (`reticle feedback` / `
|
|
90
|
+
The SDK is tree-shaken out of production builds and connects only to a local bridge. The bridge binds to loopback by default; exposing it beyond localhost _requires_ a pairing token (the server refuses to bind a non-loopback host without one). Every environment variable that gates a security control is a single named constant, so a typo can't silently disable auth. Nothing from the app under test ever leaves your machine: no DOM, network, console, state, or source. The CLI reports anonymous, opt-out usage metrics only (a random id + event names like `invoke`/`session_start`; no code, no PII; see [telemetry](telemetry.md)); opt out with `reticle telemetry disable`, `RETICLE_TELEMETRY=0`, or `DO_NOT_TRACK=1`. The one thing that carries free text is feedback you or your agent deliberately send (`reticle feedback` / `reticle_session { action: "feedback" }`). It is never collected passively, it is redacted before sending, and it is disabled separately with `RETICLE_FEEDBACK=0`.
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|