@reticlehq/server 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +17 -18
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +13 -1
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse.d.ts +2 -1
- package/dist/command/cli/cli-parse.js +5 -2
- package/dist/command/cli/cli-verify.js +2 -2
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.js +2 -6
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +10 -2
- package/dist/command/cli/launch/cli-launch.js +32 -39
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +2 -1
- package/dist/command/cli/ports/resolve/cli-port.js +5 -1
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +24 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +48 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.js +2 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +664 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +52 -0
- package/dist/features/harness/platform-config.js +89 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.js +60 -55
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/runs/verification-sync.d.ts +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- package/dist/language/flows/flow-replay-run.d.ts +25 -2
- package/dist/language/flows/flow-replay-run.js +73 -27
- package/dist/language/flows/flow-replay.d.ts +17 -0
- package/dist/language/flows/flow-replay.js +148 -100
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- package/dist/language/flows/flow-store-for-session.d.ts +2 -1
- package/dist/language/flows/flow-tools.js +8 -4
- package/dist/language/flows/flows.d.ts +8 -7
- package/dist/language/flows/flows.js +7 -3
- package/dist/language/flows/narrow-write.d.ts +4 -4
- package/dist/language/flows/replay.js +1 -1
- package/dist/language/flows/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +7 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -2
- package/dist/memory/cloud/cloud-sync.js +10 -5
- package/dist/memory/cloud/harness-config.d.ts +10 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-daemon.d.ts +22 -2
- package/dist/memory/cloud/sync-daemon.js +24 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +19 -0
- package/dist/memory/intent/intent-shard-store.js +2 -2
- package/dist/memory/intent/intent-shard.d.ts +12 -12
- package/dist/memory/journal/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +50 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- package/dist/memory/journal/on-disk/retention.d.ts +87 -2
- package/dist/memory/journal/on-disk/retention.js +177 -8
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +9 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.js +9 -2
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +13 -3
- package/dist/memory/journal/session-end.js +24 -4
- package/dist/memory/journal/session-journal.d.ts +115 -5
- package/dist/memory/journal/session-journal.js +479 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +14 -4
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +33 -5
- package/dist/memory/project/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +15 -1
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/session/facts/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-manager.d.ts +4 -0
- package/dist/portal/session/session-manager.js +19 -1
- package/dist/portal/session/session.d.ts +21 -10
- package/dist/portal/session/session.js +29 -42
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +20 -9
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/tools/act/act-preflight.js +6 -1
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- package/dist/surface/tools/act-capsule.js +27 -1
- package/dist/surface/tools/assert/assert-verdict.js +44 -17
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +37 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.js +49 -2
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +65 -5
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +55 -9
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- package/dist/surface/tools/tool-kit.d.ts +18 -3
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +6 -1
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -3
- package/dist/telemetry/harness-drive.d.ts +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/verification-of.js +31 -3
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +16 -0
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to type into a field, and the one place in a drive worth paying a generating model for.
|
|
3
|
+
*
|
|
4
|
+
* A System One model answers typed questions against a state; it cannot write a string. That is not
|
|
5
|
+
* a shortcoming to route around everywhere — it is why the drive is two orders of magnitude cheaper,
|
|
6
|
+
* and every decision it makes (which tool, which element, what should follow) is a decision, not a
|
|
7
|
+
* composition. But a text field is a composition. "A card number", "a search term that returns
|
|
8
|
+
* results", "a description a human would write" cannot be enumerated from the page, and a label-keyed
|
|
9
|
+
* table of guesses is what stands in for them today.
|
|
10
|
+
*
|
|
11
|
+
* ## Why this is escalation rather than partnership
|
|
12
|
+
*
|
|
13
|
+
* Put a generating model in the per-turn loop and you pay its per-turn tax on every step, which is
|
|
14
|
+
* the cost the cheap driver exists to avoid. Typing is a small minority of what a drive does —
|
|
15
|
+
* clicks and navigations are the overwhelming majority — so the model is called for the few steps
|
|
16
|
+
* that compose text, never for the many that select, and a drive with no text fields never calls it
|
|
17
|
+
* at all.
|
|
18
|
+
*
|
|
19
|
+
* ## Why it is cached, and cached where it is
|
|
20
|
+
*
|
|
21
|
+
* A generated value is paid for ONCE and then belongs to the project: the same field on the same
|
|
22
|
+
* app wants the same value tomorrow, and a replay must send exactly what the recording sent or it
|
|
23
|
+
* is not a replay. Writing them to `.reticle` makes the second drive free, makes the values
|
|
24
|
+
* reviewable by a human who can correct a bad one by hand, and keeps them out of the model's way
|
|
25
|
+
* entirely. It is the same economics as the flows themselves: pay once, replay forever.
|
|
26
|
+
*
|
|
27
|
+
* ## What it must never do
|
|
28
|
+
*
|
|
29
|
+
* Fill a slot, never propose a call. The driver decides what to click and what to claim; this
|
|
30
|
+
* answers one question — "what text goes in this box" — and a wrong answer costs one act, not a
|
|
31
|
+
* wrong verdict. Nothing here touches what is judged.
|
|
32
|
+
*/
|
|
33
|
+
import { DEFAULT_HARNESS_BASE_URL, } from './driver.js';
|
|
34
|
+
/** Deliberately small. A field value is a phrase; anything longer is the model misunderstanding. */
|
|
35
|
+
const MAX_TOKENS = 64;
|
|
36
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
37
|
+
const MESSAGES_PATH = '/v1/messages';
|
|
38
|
+
/**
|
|
39
|
+
* How many values one drive will generate.
|
|
40
|
+
*
|
|
41
|
+
* The cache makes the steady state nearly free, but the FIRST drive on a form-heavy app is the one
|
|
42
|
+
* that pays, and an uncapped loop on a page with forty inputs is a bill nobody agreed to. Past the
|
|
43
|
+
* cap the heuristic answers, which is what every drive used before this existed.
|
|
44
|
+
*/
|
|
45
|
+
const MAX_PER_DRIVE = 12;
|
|
46
|
+
/**
|
|
47
|
+
* The fallback, and still the answer for most fields.
|
|
48
|
+
*
|
|
49
|
+
* An exploration drive needs PLAUSIBLE input, not creative input: the engine is judging what the app
|
|
50
|
+
* did with the value, not the value. A field that rejects everything here is itself worth finding,
|
|
51
|
+
* and shows up as an act that did not settle.
|
|
52
|
+
*/
|
|
53
|
+
export function heuristicFillValue(label) {
|
|
54
|
+
const l = label.toLowerCase();
|
|
55
|
+
if (l.includes('email'))
|
|
56
|
+
return 'harness@reticle.dev';
|
|
57
|
+
if (l.includes('password'))
|
|
58
|
+
return 'password';
|
|
59
|
+
if (l.includes('url') || l.includes('link'))
|
|
60
|
+
return 'https://example.com';
|
|
61
|
+
if (l.includes('phone') || l.includes('tel'))
|
|
62
|
+
return '5550100';
|
|
63
|
+
if (l.includes('date'))
|
|
64
|
+
return '2026-01-01';
|
|
65
|
+
if (l.includes('amount') || l.includes('price') || l.includes('qty') || l.includes('quantity'))
|
|
66
|
+
return '2';
|
|
67
|
+
if (l.includes('search') || l.includes('filter'))
|
|
68
|
+
return 'a';
|
|
69
|
+
if (l.includes('number') || l.includes('count'))
|
|
70
|
+
return '42';
|
|
71
|
+
return 'reticle harness';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Whether a label is worth spending a model call on.
|
|
75
|
+
*
|
|
76
|
+
* The heuristic is RIGHT about an email box and an amount box, and a generated "john@example.com"
|
|
77
|
+
* is not an improvement on `harness@reticle.dev`. What it cannot do is a field whose label it does
|
|
78
|
+
* not recognise at all, where it falls back to typing the words "reticle harness" into whatever the
|
|
79
|
+
* app asked for. That fallback is the whole of what generation is for.
|
|
80
|
+
*/
|
|
81
|
+
export function needsGeneration(label) {
|
|
82
|
+
return heuristicFillValue(label) === heuristicFillValue('');
|
|
83
|
+
}
|
|
84
|
+
const PROMPT = (label, context) => `A test harness is filling in a form field on a web application so it can check what the app does with the input.\n\n` +
|
|
85
|
+
`The field is labelled: "${label}"\n` +
|
|
86
|
+
`${context}\n\n` +
|
|
87
|
+
`Reply with ONLY the value to type. No quotes, no explanation, no markdown. It must be something a ` +
|
|
88
|
+
`real user would plausibly enter, and short. If the field wants an identifier or a code, invent a ` +
|
|
89
|
+
`well-formed one.`;
|
|
90
|
+
/** One string from a generating model, or nothing. Never throws: a drive must not fail over a field. */
|
|
91
|
+
async function generateOne(options, label, context) {
|
|
92
|
+
const call = options.fetch ?? ((url, init) => fetch(url, init));
|
|
93
|
+
const baseUrl = options.baseUrl ?? DEFAULT_HARNESS_BASE_URL;
|
|
94
|
+
try {
|
|
95
|
+
const res = await call(`${baseUrl}${MESSAGES_PATH}`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'content-type': 'application/json',
|
|
99
|
+
'x-api-key': options.apiKey,
|
|
100
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
model: options.model ?? 'claude-sonnet-5',
|
|
104
|
+
max_tokens: MAX_TOKENS,
|
|
105
|
+
messages: [{ role: 'user', content: PROMPT(label, context) }],
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok)
|
|
109
|
+
return undefined;
|
|
110
|
+
const parsed = JSON.parse(await res.text());
|
|
111
|
+
const content = parsed.content ?? [];
|
|
112
|
+
const text = content
|
|
113
|
+
.filter((block) => 'text' === block.type)
|
|
114
|
+
.map((block) => block.text ?? '')
|
|
115
|
+
.join('')
|
|
116
|
+
.trim()
|
|
117
|
+
.split('\n')[0];
|
|
118
|
+
if (text === undefined || 0 === text.length)
|
|
119
|
+
return undefined;
|
|
120
|
+
// A model asked for one value occasionally answers with a sentence about the value. Anything
|
|
121
|
+
// long enough to be prose is not a field value, and typing prose proves nothing about the app.
|
|
122
|
+
return 80 < text.length ? undefined : text.replace(/^["']|["']$/g, '');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Offline, refused, rate-limited, malformed. All the same answer: use the heuristic.
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The seam the driver calls: a label in, a value out, always.
|
|
131
|
+
*
|
|
132
|
+
* Three layers, cheapest first, and the expensive one is skipped entirely unless the two above it
|
|
133
|
+
* have nothing better than "type the words reticle harness into this box".
|
|
134
|
+
*/
|
|
135
|
+
export function fillValues(deps) {
|
|
136
|
+
let spent = 0;
|
|
137
|
+
return async (label) => {
|
|
138
|
+
const cached = deps.cache?.get(label);
|
|
139
|
+
if (cached !== undefined)
|
|
140
|
+
return cached;
|
|
141
|
+
const fallback = heuristicFillValue(label);
|
|
142
|
+
if (deps.generator === undefined || !needsGeneration(label) || MAX_PER_DRIVE <= spent)
|
|
143
|
+
return fallback;
|
|
144
|
+
spent += 1;
|
|
145
|
+
const generated = await generateOne(deps.generator, label, deps.context?.() ?? '');
|
|
146
|
+
if (generated === undefined)
|
|
147
|
+
return fallback;
|
|
148
|
+
// Cached under the LABEL, not the ref: refs expire with the page, labels are what the next drive
|
|
149
|
+
// and every replay will meet again.
|
|
150
|
+
deps.cache?.set(label, generated);
|
|
151
|
+
return generated;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A harness driver with no text generator in it.
|
|
3
|
+
*
|
|
4
|
+
* `driver.ts` binds the harness to a frontier model, and the comment at the top of it has always
|
|
5
|
+
* claimed the tier is "a standing test of the design": the driver is not reasoning about business
|
|
6
|
+
* logic, it is choosing what to touch next and saying what it expects, and if that needed a large
|
|
7
|
+
* model then the engine would not be doing its job. Nothing tested the claim, because the only
|
|
8
|
+
* driver in the repo was a large model.
|
|
9
|
+
*
|
|
10
|
+
* This is the test. Jev is a System One model — it evaluates typed questions against a state and
|
|
11
|
+
* answers with probabilities, in one parallel pass, and it does not emit prose. It therefore cannot
|
|
12
|
+
* invent a tool call. So every call this driver makes is built HERE, deterministically, out of what
|
|
13
|
+
* the page actually offers, and the model's only contribution is picking one of them. That is a
|
|
14
|
+
* strictly smaller surface than a generating driver has, and it is the whole reason this is worth
|
|
15
|
+
* having: a driver that cannot name an element that is not on the page cannot hallucinate one.
|
|
16
|
+
*
|
|
17
|
+
* What did NOT move: the verdict. `decideVerified` still decides, on evidence, with no model in it.
|
|
18
|
+
* A System One model cannot hallucinate a type, but it still returns a PROBABILITY, and a
|
|
19
|
+
* probability is not proof. Anyone reading this file as an invitation to let Jev answer
|
|
20
|
+
* "did it work?" should read `engine/src/evidence/verified.ts` first — the seventeen clauses there
|
|
21
|
+
* exist because this product is the verdict, and a verdict sourced from a model's confidence is the
|
|
22
|
+
* thing Reticle sells the absence of.
|
|
23
|
+
*/
|
|
24
|
+
import type { ModelDriver } from './harness.js';
|
|
25
|
+
import type { HarnessFetch } from './driver.js';
|
|
26
|
+
export declare const DEFAULT_JEV_MODEL = "jev-latest";
|
|
27
|
+
export declare const DEFAULT_JEV_BASE_URL = "https://api.typesafe.ai";
|
|
28
|
+
/**
|
|
29
|
+
* The consequences the driver can declare, and the predicate each one becomes.
|
|
30
|
+
*
|
|
31
|
+
* Declaring one is not optional decoration — it is the difference between a drive and a click. The
|
|
32
|
+
* first version of this driver emitted `act_and_wait` with no `until` at all, and the engine said
|
|
33
|
+
* so in every single result: "the page settled and no channel reported a problem, but nothing was
|
|
34
|
+
* declared to prove — this is not verification". Six actions, zero proved. A flow recorded from that
|
|
35
|
+
* drive passes even when the feature is broken, which is the exact failure this product exists to
|
|
36
|
+
* catch, sitting inside the thing meant to catch it.
|
|
37
|
+
*
|
|
38
|
+
* Every predicate here is BARE — a kind with no specifics — because before acting the driver does
|
|
39
|
+
* not know which endpoint, signal or route to expect. Bare is still consequence-grade evidence: it
|
|
40
|
+
* asserts the app's own machinery moved, which a presence check on screen text does not.
|
|
41
|
+
*
|
|
42
|
+
* Only SIGNAL and NET can be declared bare, and that is a fact about the engine rather than a
|
|
43
|
+
* preference. `engine/src/evidence/already-true.ts` lists the kinds that read live state — element,
|
|
44
|
+
* text, route and state — and those are checked BEFORE the action as well as after, precisely so a
|
|
45
|
+
* condition that already held cannot be sold as something the action caused. A bare predicate of
|
|
46
|
+
* one of those kinds is unconditionally true: there is always a current route, and there is always
|
|
47
|
+
* some store. That file says so in as many words at its `ROUTE` case.
|
|
48
|
+
*
|
|
49
|
+
* This driver tried all four anyway and the engine refused all four, every time, with
|
|
50
|
+
* `already_true` — "the declared consequence was already true before this action, so it proves
|
|
51
|
+
* nothing about it". Signal and net are event-based and floored at the act's own cursor, so they
|
|
52
|
+
* cannot be answered by the past, which is exactly what makes them safe to declare with no
|
|
53
|
+
* specifics. Route and state come back the moment the driver can name a pathname or a store path
|
|
54
|
+
* instead of a bare kind, and they are the stronger evidence when it can.
|
|
55
|
+
*
|
|
56
|
+
* `anyOf` is not a loophole: it reports as live-reading if ANY branch does, so it holds only the
|
|
57
|
+
* two safe kinds for the same reason.
|
|
58
|
+
*
|
|
59
|
+
* ponytail: bare `net` can in principle be satisfied by unrelated traffic inside the action's
|
|
60
|
+
* window — a page polling every second would answer for a button that did nothing. The window is
|
|
61
|
+
* the action's own, not "ever", so the exposure is small but real. The upgrade is to learn the
|
|
62
|
+
* app's vocabulary first (one `reticle_observe` early in the drive gives the actual signal names
|
|
63
|
+
* and endpoint paths) and declare `urlContains`/`name` instead of a bare kind. Until then `state`
|
|
64
|
+
* and `signal` are the trustworthy ones and `net` is the one to distrust on a chatty app.
|
|
65
|
+
*/
|
|
66
|
+
export interface Consequence {
|
|
67
|
+
predicate: Record<string, unknown>;
|
|
68
|
+
describes: string;
|
|
69
|
+
/**
|
|
70
|
+
* Whether a SAVED FLOW can carry this claim, which is a different question from whether the drive
|
|
71
|
+
* can prove it.
|
|
72
|
+
*
|
|
73
|
+
* A recorded step keeps an expectation only in the forms a replay can re-check, and that filter
|
|
74
|
+
* refuses every unbound kind: `signal "export:generated"` survives, a bare "some signal fires"
|
|
75
|
+
* does not; `net { method: "POST" }` survives, a bare "some request happens" does not; `not` and
|
|
76
|
+
* `anyOf` have no representation at all.
|
|
77
|
+
*
|
|
78
|
+
* Measured on a real dashboard before this field existed: every one of the four consequences on
|
|
79
|
+
* offer was unbound, so 0 of 22 steps across 16 machine-driven flows recorded a single
|
|
80
|
+
* expectation, against 211 of 419 in the hand-authored flows beside them. The drive proved things
|
|
81
|
+
* and then saved flows that had forgotten all of it. Since a flow is meant to BE the regression
|
|
82
|
+
* suite, that is the product's own claim failing at its last step: those flows can only ever
|
|
83
|
+
* catch "the click stopped landing", never "the feature broke".
|
|
84
|
+
*/
|
|
85
|
+
recordable: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The app's own route, with Reticle's fingerprints wiped off it.
|
|
89
|
+
*
|
|
90
|
+
* A pooled launcher appends `__reticle_opened=1` to the URL it opens, and that parameter ended up
|
|
91
|
+
* inside a declared consequence: "this click leaves a route containing /?__reticle_opened=1". On a
|
|
92
|
+
* hash-routed app that claim can never hold, because the query string never changes -- only the
|
|
93
|
+
* fragment does -- so every navigation link was a guaranteed red, and the red was about OUR query
|
|
94
|
+
* parameter rather than about the application. Found by making a failed action say what it claimed;
|
|
95
|
+
* before that it read as an ordinary "the declared consequence did not hold".
|
|
96
|
+
*
|
|
97
|
+
* The fragment is kept, and is the point: on a hash-routed app it is the only part that moves.
|
|
98
|
+
*/
|
|
99
|
+
export declare function appRoute(route: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* The consequences on offer, given where the app currently is and what it says it can do.
|
|
102
|
+
*
|
|
103
|
+
* Two things shape this list, and the second one was missing for a whole release.
|
|
104
|
+
*
|
|
105
|
+
* It is a function of the CURRENT ROUTE, because "this takes us off the page we are on" is only
|
|
106
|
+
* falsifiable against a route actually read.
|
|
107
|
+
*
|
|
108
|
+
* It is also a function of the app's OWN VOCABULARY, because an unbound claim cannot be saved. A
|
|
109
|
+
* drive that declares "some signal fires" proves something real at the time and writes a flow that
|
|
110
|
+
* asserts nothing, so every later replay passes as long as the clicks still land. Offering the
|
|
111
|
+
* declared signal NAMES turns the same choice into one a flow can keep, and costs nothing extra:
|
|
112
|
+
* the names are already read out of `.reticle` to build the plan.
|
|
113
|
+
*/
|
|
114
|
+
export declare function consequencesFor(route: string | undefined, vocabulary?: readonly string[]): Record<string, Consequence>;
|
|
115
|
+
/**
|
|
116
|
+
* One step of the plan the caller built from `.reticle`.
|
|
117
|
+
*
|
|
118
|
+
* Structural rather than imported: this package is a SINK — it is handed a toolset and a driver and
|
|
119
|
+
* reaches into nothing — so the plan arrives as data with the shape it needs and no dependency on
|
|
120
|
+
* where it was built.
|
|
121
|
+
*/
|
|
122
|
+
export interface DrivePlanStep {
|
|
123
|
+
/** `replay` for a journey already recorded, `drive` for declared intent nobody has proved. */
|
|
124
|
+
kind: string;
|
|
125
|
+
/** The flow to replay, or the signal/control a drive step is aimed at. */
|
|
126
|
+
target: string;
|
|
127
|
+
/** Why this step is worth taking, in the project's own words. */
|
|
128
|
+
why: string;
|
|
129
|
+
}
|
|
130
|
+
export interface JevDriverOptions {
|
|
131
|
+
apiKey: string;
|
|
132
|
+
model?: string;
|
|
133
|
+
baseUrl?: string;
|
|
134
|
+
/**
|
|
135
|
+
* The loop's step budget, so the driver can stop exploring in time to save what it drove.
|
|
136
|
+
*
|
|
137
|
+
* The loop does not tell a driver how much budget is left — it does not have to, because a
|
|
138
|
+
* generating model is expected to call `finish` when it is ready. This driver has a teardown it
|
|
139
|
+
* must reach, so it needs the number. Absent ⇒ no reserve, and a run that ends on BUDGET keeps
|
|
140
|
+
* nothing, which is why every caller inside the repo passes it.
|
|
141
|
+
*/
|
|
142
|
+
maxSteps?: number;
|
|
143
|
+
/**
|
|
144
|
+
* What this drive is FOR, read from `.reticle` before it started.
|
|
145
|
+
*
|
|
146
|
+
* Replay steps run FIRST and deterministically, with no model call at all — a journey already on
|
|
147
|
+
* disk must never be re-driven, which is the whole reason the plan exists. Only once they are
|
|
148
|
+
* done is the model asked anything, and then it is asked against the intent these steps carry
|
|
149
|
+
* rather than against whatever happens to be on the page.
|
|
150
|
+
*/
|
|
151
|
+
plan?: readonly DrivePlanStep[];
|
|
152
|
+
/**
|
|
153
|
+
* The signal names the app declares, untested ones first, read out of `.reticle` with the plan.
|
|
154
|
+
*
|
|
155
|
+
* Offered as consequences so a declaration can name something. Absent is not a failure: the
|
|
156
|
+
* method-bound network consequences are still recordable, and an app that declares no signals is
|
|
157
|
+
* simply an app with less to claim.
|
|
158
|
+
*/
|
|
159
|
+
vocabulary?: readonly string[];
|
|
160
|
+
/**
|
|
161
|
+
* What to type into a text field, when the label alone is not enough to guess.
|
|
162
|
+
*
|
|
163
|
+
* The one place a drive is a COMPOSITION rather than a decision, and therefore the one place worth
|
|
164
|
+
* paying a generating model for. Absent ⇒ the label heuristic answers, which is what every drive
|
|
165
|
+
* did before this seam existed; a drive with no text fields never reaches it either way. See
|
|
166
|
+
* `fill-values.ts` for why it is escalation rather than a second model in the loop.
|
|
167
|
+
*/
|
|
168
|
+
fillValue?: (label: string) => Promise<string>;
|
|
169
|
+
/** Injected for tests. Defaults to the platform `fetch`. */
|
|
170
|
+
fetch?: HarnessFetch;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Read this driver's configuration out of the environment.
|
|
174
|
+
*
|
|
175
|
+
* A direct Jev key wins over the platform key, so that someone debugging the upstream is never
|
|
176
|
+
* quietly talking to the proxy they were trying to bypass. Returns `undefined` when neither is set,
|
|
177
|
+
* which is a routine answer and not a failure — the caller falls back to the Anthropic driver.
|
|
178
|
+
*/
|
|
179
|
+
export declare function jevOptionsFromEnv(env: Record<string, string | undefined>): JevDriverOptions | undefined;
|
|
180
|
+
/**
|
|
181
|
+
* Build a driver backed by a System One model.
|
|
182
|
+
*
|
|
183
|
+
* Every turn emits exactly ONE call. The loop dispatches a turn's calls with `Promise.all`, so an
|
|
184
|
+
* act and the snapshot that reads its result cannot be batched into one turn — they would race, and
|
|
185
|
+
* the snapshot would describe a page mid-flight. One call per turn costs a step and buys a reading
|
|
186
|
+
* that is actually of the page the action produced.
|
|
187
|
+
*/
|
|
188
|
+
export declare function jevDriver(options: JevDriverOptions): ModelDriver;
|