@reticlehq/server 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/SKILL.md +251 -33
- package/dist/bridge/bridge.d.ts +6 -1
- package/dist/bridge/bridge.js +27 -4
- package/dist/capsule/causal-summary.d.ts +20 -0
- package/dist/capsule/causal-summary.js +31 -6
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +68 -0
- package/dist/cli/chromium-hint.js +145 -0
- package/dist/cli/cli-doctor.js +31 -36
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-launch.d.ts +18 -0
- package/dist/cli/cli-launch.js +32 -1
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +8 -0
- package/dist/cli/cli-port.js +36 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/doctor-daemon-line.js +10 -1
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +66 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli.js +139 -23
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/daemon/daemon-resilience.js +2 -2
- package/dist/daemon/loopback-alias.d.ts +32 -0
- package/dist/daemon/loopback-alias.js +80 -0
- package/dist/events/contradictions.d.ts +14 -0
- package/dist/events/contradictions.js +26 -17
- package/dist/events/predicate-eval.d.ts +26 -3
- package/dist/events/predicate-eval.js +284 -95
- package/dist/events/predicate-parse.d.ts +1 -1
- package/dist/events/predicate-parse.js +18 -4
- package/dist/events/predicate.js +170 -18
- package/dist/events/ring-buffer.d.ts +8 -0
- package/dist/events/ring-buffer.js +34 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-tools.js +12 -4
- package/dist/flows/heal-run.js +1 -1
- package/dist/honesty/blind-spots.js +6 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +9 -1
- package/dist/honesty/unsettled.d.ts +40 -0
- package/dist/honesty/unsettled.js +78 -0
- package/dist/honesty/verified.d.ts +7 -0
- package/dist/honesty/verified.js +27 -4
- package/dist/http-server.js +11 -1
- package/dist/index.js +29 -2
- package/dist/init/agent-rules.d.ts +16 -0
- package/dist/init/agent-rules.js +96 -17
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +11 -5
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +96 -0
- package/dist/init/confirm.js +138 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +15 -0
- package/dist/init/non-js-project.d.ts +27 -0
- package/dist/init/non-js-project.js +84 -0
- package/dist/init/plan-framework.d.ts +9 -0
- package/dist/init/plan-framework.js +72 -16
- package/dist/init/plan.d.ts +40 -4
- package/dist/init/plan.js +183 -43
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +181 -46
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +46 -6
- package/dist/init/snippets.d.ts +64 -5
- package/dist/init/snippets.js +220 -31
- package/dist/init/vite-config.js +29 -3
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +29 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +69 -3
- package/dist/mcp/mcp-proxy.js +215 -23
- package/dist/mcp/mcp.js +58 -7
- package/dist/mcp/proxy-lifecycle.d.ts +20 -1
- package/dist/mcp/proxy-lifecycle.js +22 -1
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/pool/playwright-launcher.js +10 -2
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/session/live-control-tools.js +39 -2
- package/dist/session/live-control.d.ts +7 -9
- package/dist/session/live-control.js +34 -1
- package/dist/session/no-session-diagnosis.d.ts +23 -4
- package/dist/session/no-session-diagnosis.js +128 -44
- package/dist/session/no-session-watch.js +12 -0
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/review-store.d.ts +19 -0
- package/dist/session/review-store.js +34 -5
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +9 -0
- package/dist/session/session-manager.js +11 -0
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +12 -0
- package/dist/status-payload.d.ts +13 -1
- package/dist/status-payload.js +6 -1
- package/dist/telemetry/app-instrumented.d.ts +37 -0
- package/dist/telemetry/app-instrumented.js +54 -0
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +0 -20
- package/dist/telemetry/bug-found.js +38 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.d.ts +4 -4
- package/dist/telemetry/daemon-telemetry.js +8 -4
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +36 -0
- package/dist/telemetry/install-source.js +40 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +3 -3
- package/dist/telemetry/session-metrics.js +9 -4
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +72 -8
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +77 -11
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +25 -1
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +1 -1
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +70 -5
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +149 -32
- package/dist/tools/lease-tools.d.ts +29 -1
- package/dist/tools/lease-tools.js +58 -5
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +1 -1
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +48 -0
- package/dist/tools/react-grace.js +87 -0
- package/dist/tools/read-tools.js +20 -4
- package/dist/tools/settle-in-flight.d.ts +79 -0
- package/dist/tools/settle-in-flight.js +106 -0
- package/dist/tools/snapshot-delta.d.ts +7 -0
- package/dist/tools/snapshot-delta.js +130 -6
- package/dist/tools/tool-kit.d.ts +52 -5
- package/dist/tools/tool-kit.js +55 -24
- package/dist/tools/tool-surface.js +28 -9
- package/dist/tools/tools.js +38 -4
- package/dist/visual/capture-cleanup.d.ts +16 -0
- package/dist/visual/capture-cleanup.js +39 -0
- package/dist/visual/visual-tools.d.ts +18 -0
- package/dist/visual/visual-tools.js +24 -5
- package/docs/README.md +56 -11
- package/docs/actions.mdx +87 -0
- package/docs/agent-cheatsheet.md +102 -26
- package/docs/architecture.md +46 -35
- package/docs/benchmarks.md +76 -72
- package/docs/best-practices.mdx +211 -0
- package/docs/capabilities.mdx +387 -0
- package/docs/cli/affected.mdx +51 -0
- package/docs/cli/capsules.mdx +69 -0
- package/docs/cli/cloud.mdx +127 -0
- package/docs/cli/doctor.mdx +76 -0
- package/docs/cli/drive.mdx +55 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +92 -0
- package/docs/cli/hunt.mdx +57 -0
- package/docs/cli/identify.mdx +62 -0
- package/docs/cli/init.mdx +94 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +68 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +62 -0
- package/docs/cli/status.mdx +64 -0
- package/docs/cli/stop.mdx +56 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +57 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +55 -0
- package/docs/cli.mdx +219 -0
- package/docs/debugging.md +13 -9
- package/docs/deploy-checks.md +15 -11
- package/docs/desktop-apps.md +49 -40
- package/docs/desktop.mdx +188 -0
- package/docs/docs.json +363 -0
- package/docs/enterprise.md +20 -16
- package/docs/faq.mdx +88 -0
- package/docs/fixtures.md +11 -7
- package/docs/flows.md +26 -22
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +199 -0
- package/docs/gate-plan.md +45 -41
- package/docs/gates.md +23 -19
- package/docs/getting-started.md +76 -65
- package/docs/human-control.md +22 -18
- package/docs/index.mdx +101 -0
- package/docs/install-agentic.mdx +215 -0
- package/docs/install-manual.mdx +194 -0
- package/docs/instrumentation.mdx +232 -0
- package/docs/integration-patterns.md +38 -34
- package/docs/local-registry.md +16 -12
- package/docs/multi-agent-testing.md +16 -12
- package/docs/packages/babel-plugin.mdx +70 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +106 -0
- package/docs/packages/electron.mdx +87 -0
- package/docs/packages/eslint-plugin.mdx +84 -0
- package/docs/packages/next.mdx +91 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +108 -0
- package/docs/packages/vite-plugin.mdx +96 -0
- package/docs/packages.mdx +217 -0
- package/docs/platform-integration.md +33 -26
- package/docs/predicates.mdx +179 -0
- package/docs/quickstart.mdx +211 -0
- package/docs/recipes.mdx +242 -0
- package/docs/skill-file.mdx +105 -0
- package/docs/state-management.mdx +197 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +47 -32
- package/docs/telemetry-contract.md +101 -46
- package/docs/telemetry-events.mdx +430 -0
- package/docs/telemetry.md +63 -50
- package/docs/testing.md +10 -6
- package/docs/token-efficiency.md +30 -26
- package/docs/tools-act-and-wait.mdx +123 -0
- package/docs/tools-act-sequence.mdx +120 -0
- package/docs/tools-act.mdx +82 -0
- package/docs/tools-assert.mdx +70 -0
- package/docs/tools-console.mdx +59 -0
- package/docs/tools-inspect.mdx +85 -0
- package/docs/tools-navigate.mdx +63 -0
- package/docs/tools-network.mdx +86 -0
- package/docs/tools-observe.mdx +89 -0
- package/docs/tools-overview.mdx +157 -0
- package/docs/tools-query.mdx +82 -0
- package/docs/tools-session-and-feedback.mdx +91 -0
- package/docs/tools-sessions.mdx +65 -0
- package/docs/tools-snapshot.mdx +75 -0
- package/docs/tools-state.mdx +81 -0
- package/docs/tools-tools-and-run.mdx +125 -0
- package/docs/tools-wait-for.mdx +66 -0
- package/docs/troubleshooting.mdx +193 -0
- package/docs/usage.md +175 -153
- package/docs/vs-chrome-devtools-mcp.mdx +82 -0
- package/docs/vs-playwright-mcp.mdx +85 -0
- package/docs/vs-screenshots.mdx +96 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +107 -0
- package/package.json +2 -2
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "The app ignored this response" is only sayable once the app has had a chance to read it.
|
|
3
|
+
*
|
|
4
|
+
* `response-ignored` fires when a write succeeded on the server and nothing on the client moved. It
|
|
5
|
+
* is a real and valuable finding — a lost write, a response parsed into the void, a render that never
|
|
6
|
+
* happened. It is also an ACCUSATION, and it is decided by where a window happens to end.
|
|
7
|
+
*
|
|
8
|
+
* Waiting for in-flight requests before taking the verdict made that boundary dangerous in a way it
|
|
9
|
+
* was not before. Previously the write was still pending when the window closed, so it never reached
|
|
10
|
+
* the settled list and this rule could not fire at all. Now the response lands inside the window BY
|
|
11
|
+
* DESIGN — and the app's re-render happens in a later task, a few milliseconds after it. Close the
|
|
12
|
+
* window in that gap and every channel agrees that the app took a successful write and did nothing.
|
|
13
|
+
*
|
|
14
|
+
* The verdict is then `verified:"no"` on a completely correct application, produced entirely by where
|
|
15
|
+
* we chose to stop looking. For a verification tool that is the more damaging direction of error: a
|
|
16
|
+
* false green is a missed catch, but a false accusation sends someone to fix code that is not broken,
|
|
17
|
+
* and it is how the instrument stops being believed.
|
|
18
|
+
*
|
|
19
|
+
* So the response is not the end of the window — the app's REACTION to it is. The grace is paid only
|
|
20
|
+
* in the exact case that would otherwise be accused: a successful mutating write with nothing moved
|
|
21
|
+
* after it. Every other action returns without a single poll.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately short. This covers a task-boundary re-render, not a slow app: if nothing has moved
|
|
24
|
+
* within the grace, "ignored" has become an honest description and the finding should stand.
|
|
25
|
+
*/
|
|
26
|
+
import { EventType } from '@reticlehq/core';
|
|
27
|
+
/**
|
|
28
|
+
* How long to let the app react. A React commit after a resolved fetch is one or two task hops; this
|
|
29
|
+
* is generous against that and still far too short to paper over an app that genuinely dropped the
|
|
30
|
+
* response.
|
|
31
|
+
*/
|
|
32
|
+
const REACTION_GRACE_MS = 300;
|
|
33
|
+
/** Poll cadence — the same as the settle wait, for the same reason. */
|
|
34
|
+
const POLL_MS = 50;
|
|
35
|
+
const MUTATING = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
36
|
+
/** Movement that would clear `response-ignored`; mirrors `uiAdvanced` in the detector. */
|
|
37
|
+
const MOVEMENT = new Set([
|
|
38
|
+
EventType.DOM_ADDED,
|
|
39
|
+
EventType.DOM_REMOVED,
|
|
40
|
+
EventType.DOM_ATTR,
|
|
41
|
+
EventType.DOM_TEXT,
|
|
42
|
+
EventType.STATE_CHANGE,
|
|
43
|
+
EventType.ROUTE_CHANGE,
|
|
44
|
+
]);
|
|
45
|
+
function moved(events) {
|
|
46
|
+
return events.some((e) => MOVEMENT.has(e.type));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Is this a window that would be wrongly accused if it ended right now?
|
|
50
|
+
*
|
|
51
|
+
* True only for the accusable shape: a SUCCESSFUL MUTATING request settled, and nothing has moved. A
|
|
52
|
+
* read that changed nothing is a prefetch, a failed write is a different finding entirely, and an
|
|
53
|
+
* action that already moved the UI has nothing to wait for — none of them buy a grace period.
|
|
54
|
+
*/
|
|
55
|
+
export function awaitsReaction(events) {
|
|
56
|
+
if (moved(events))
|
|
57
|
+
return false;
|
|
58
|
+
return events.some((e) => {
|
|
59
|
+
if (e.type !== EventType.NET_REQUEST)
|
|
60
|
+
return false;
|
|
61
|
+
const method = 'string' === typeof e.data['method'] ? e.data['method'].toUpperCase() : '';
|
|
62
|
+
const ok = e.data['ok'];
|
|
63
|
+
return MUTATING.includes(method) && true === ok;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Wait, bounded by BOTH the grace and the caller's remaining budget, for the app to react to a write
|
|
68
|
+
* it has just been told succeeded.
|
|
69
|
+
*
|
|
70
|
+
* Returns whether the app moved. A `false` is not a verdict — it hands the question back to the
|
|
71
|
+
* contradiction engine, which will raise `response-ignored` exactly as it does today. The point is
|
|
72
|
+
* only that by then the app has been given its chance, so the finding means what it says.
|
|
73
|
+
*/
|
|
74
|
+
export async function waitForReaction(session, since, budgetMs, opts) {
|
|
75
|
+
if (!awaitsReaction(session.eventsSince(since)))
|
|
76
|
+
return true;
|
|
77
|
+
const window = Math.min(REACTION_GRACE_MS, budgetMs);
|
|
78
|
+
if (window <= 0)
|
|
79
|
+
return false;
|
|
80
|
+
const deadline = session.elapsed() + window;
|
|
81
|
+
while (session.elapsed() < deadline) {
|
|
82
|
+
await opts.sleep(Math.min(POLL_MS, deadline - session.elapsed()));
|
|
83
|
+
if (moved(session.eventsSince(since)))
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
package/dist/tools/read-tools.js
CHANGED
|
@@ -11,7 +11,7 @@ import { replayProgram } from '../flows/replay.js';
|
|
|
11
11
|
import { diffLines } from '../project/baselines.js';
|
|
12
12
|
import { selectPath, capDepth, projectComponentState } from '../session/state-select.js';
|
|
13
13
|
import { costHint } from '../session/output-budget.js';
|
|
14
|
-
import { buildReactionReport } from '../events/reaction.js';
|
|
14
|
+
import { buildReactionReport, summarizeReaction } from '../events/reaction.js';
|
|
15
15
|
import { asString, asNumber, parseInteractive } from './tools-helpers.js';
|
|
16
16
|
import { sessionIdShape, commandOrThrow, snapshotTree } from './tool-kit.js';
|
|
17
17
|
import { bufferEnvelope } from '../session/session-health.js';
|
|
@@ -178,17 +178,33 @@ export const READ_TOOLS = [
|
|
|
178
178
|
const report = buildReactionReport(events, session.elapsed() - rec.cursor);
|
|
179
179
|
// Self-generating oracles: propose ranked mustHold from what the recorded window actually did.
|
|
180
180
|
const proposedConsequences = proposeConsequences(events);
|
|
181
|
-
|
|
181
|
+
const digest = summarizeReaction(report);
|
|
182
|
+
// Say that the timeline was left out, and where it lives.
|
|
183
|
+
//
|
|
184
|
+
// Dropping the raw events is right: they were most of a response large enough to truncate the
|
|
185
|
+
// part that mattered. But a field that simply stops appearing is indistinguishable from one
|
|
186
|
+
// this version never had, and an agent cannot ask for something it does not know exists. The
|
|
187
|
+
// same rule the capped diff arrays follow: a trim is never silent.
|
|
188
|
+
const timeline = events.length > 0
|
|
189
|
+
? {
|
|
190
|
+
timeline_omitted: `${String(events.length)} event(s) were recorded and are not included here. Call reticle_observe { since: ${String(rec.cursor)} } for the raw timeline.`,
|
|
191
|
+
}
|
|
192
|
+
: {};
|
|
193
|
+
const body = {
|
|
182
194
|
recordingName: name,
|
|
183
195
|
program,
|
|
196
|
+
...timeline,
|
|
184
197
|
...(unstable > 0
|
|
185
198
|
? {
|
|
186
199
|
warning: unanchoredWarning(unstable),
|
|
187
200
|
}
|
|
188
201
|
: {}),
|
|
189
202
|
...(proposedConsequences.length > 0 ? { proposedConsequences } : {}),
|
|
190
|
-
...
|
|
191
|
-
|
|
203
|
+
...digest,
|
|
204
|
+
};
|
|
205
|
+
return Promise.resolve({
|
|
206
|
+
...body,
|
|
207
|
+
cost: costHint(body, events.length),
|
|
192
208
|
});
|
|
193
209
|
},
|
|
194
210
|
},
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend the budget you were given before reporting that you ran out of certainty.
|
|
3
|
+
*
|
|
4
|
+
* `waitForPredicate` resolves the instant the predicate holds. On an app that navigates
|
|
5
|
+
* optimistically — which is normal, correct behaviour, and the norm on exactly the flows agents
|
|
6
|
+
* verify most: login, submit, save — that instant arrives while the write is still in flight. The
|
|
7
|
+
* verdict was then taken over a window the app had not finished writing, and came back
|
|
8
|
+
* `unknown / unsettled` with a recovery note asking the CALLER to re-check.
|
|
9
|
+
*
|
|
10
|
+
* Measured on the most common flow there is: a login that genuinely succeeded returned at 492ms out
|
|
11
|
+
* of an 8000ms budget. The declared signal had fired with matching data, state had moved from
|
|
12
|
+
* anonymous to authenticated, the token was in storage, honesty grade was `signal` and capture
|
|
13
|
+
* integrity was clean. Seven and a half seconds of authorised waiting went unused, and the agent was
|
|
14
|
+
* handed a shrug.
|
|
15
|
+
*
|
|
16
|
+
* A false NEGATIVE is not the mirror of a false positive here. A false positive stops an agent early;
|
|
17
|
+
* a false negative makes it redo work that already succeeded, and — far worse — teaches it to
|
|
18
|
+
* discount the verdict channel. The verdict channel is the entire product, and an agent that learns
|
|
19
|
+
* to ignore `unknown` will ignore the true ones too.
|
|
20
|
+
*
|
|
21
|
+
* What this does NOT do is decide the request succeeded. At 492ms nobody can know that, and inventing
|
|
22
|
+
* a green here would be the exact defect this product exists to prevent. It waits for the answer
|
|
23
|
+
* rather than guessing at it:
|
|
24
|
+
*
|
|
25
|
+
* - everything settles → the normal contradiction machinery sees the real responses, so a failure
|
|
26
|
+
* still contradicts and a success no longer looks like an absence;
|
|
27
|
+
* - still in flight when the budget genuinely runs out → `unsettled`, as before, but now an honest
|
|
28
|
+
* limit instead of an early exit.
|
|
29
|
+
*/
|
|
30
|
+
/** Just the shape this needs — a real Session satisfies it, and a test can supply it. */
|
|
31
|
+
export interface SettleSource {
|
|
32
|
+
eventsSince(cursor: number): readonly {
|
|
33
|
+
type: string;
|
|
34
|
+
data: Record<string, unknown>;
|
|
35
|
+
}[];
|
|
36
|
+
elapsed(): number;
|
|
37
|
+
}
|
|
38
|
+
interface WaitOpts {
|
|
39
|
+
/** Injected so the wait is testable without real time passing (rule: never call the clock inline). */
|
|
40
|
+
sleep(ms: number): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Requests that started in this window and have not come back.
|
|
44
|
+
*
|
|
45
|
+
* A settle for a request this window never saw START is ignored on purpose: the window is floored at
|
|
46
|
+
* the act cursor, so a response belonging to an earlier action can land inside it, and counting that
|
|
47
|
+
* as progress would let unrelated traffic close the window early.
|
|
48
|
+
*/
|
|
49
|
+
export declare function inFlightRequestIds(events: readonly {
|
|
50
|
+
type: string;
|
|
51
|
+
data: Record<string, unknown>;
|
|
52
|
+
}[]): string[];
|
|
53
|
+
/**
|
|
54
|
+
* The same outstanding requests, as "METHOD url" — what an unsettled verdict has to be able to name.
|
|
55
|
+
*
|
|
56
|
+
* A correlation id is the right key for the wait and useless in a sentence: an agent told that
|
|
57
|
+
* `r-17` is open cannot act on it, while `POST /api/login` is the thing to re-check or to assert
|
|
58
|
+
* directly. Read off the NET_PENDING the window already holds, so this costs one pass over events
|
|
59
|
+
* that are in hand.
|
|
60
|
+
*/
|
|
61
|
+
export declare function inFlightRequestLabels(events: readonly {
|
|
62
|
+
type: string;
|
|
63
|
+
data: Record<string, unknown>;
|
|
64
|
+
}[]): string[];
|
|
65
|
+
export interface SettleResult {
|
|
66
|
+
/** True when nothing this window started is still outstanding. */
|
|
67
|
+
settled: boolean;
|
|
68
|
+
/** What was still open when the wait ended — empty when `settled`. */
|
|
69
|
+
stillInFlight: string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Wait, bounded by `budgetMs`, for the requests this window started to come back.
|
|
73
|
+
*
|
|
74
|
+
* Returns immediately when nothing is in flight, so the common green path — an action with no traffic
|
|
75
|
+
* — pays nothing at all. A zero or negative budget means no wait: the caller may have spent the whole
|
|
76
|
+
* timeout reaching the predicate, and a negative deadline must mean "stop", never "loop forever".
|
|
77
|
+
*/
|
|
78
|
+
export declare function waitForInFlight(session: SettleSource, since: number, budgetMs: number, opts: WaitOpts): Promise<SettleResult>;
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend the budget you were given before reporting that you ran out of certainty.
|
|
3
|
+
*
|
|
4
|
+
* `waitForPredicate` resolves the instant the predicate holds. On an app that navigates
|
|
5
|
+
* optimistically — which is normal, correct behaviour, and the norm on exactly the flows agents
|
|
6
|
+
* verify most: login, submit, save — that instant arrives while the write is still in flight. The
|
|
7
|
+
* verdict was then taken over a window the app had not finished writing, and came back
|
|
8
|
+
* `unknown / unsettled` with a recovery note asking the CALLER to re-check.
|
|
9
|
+
*
|
|
10
|
+
* Measured on the most common flow there is: a login that genuinely succeeded returned at 492ms out
|
|
11
|
+
* of an 8000ms budget. The declared signal had fired with matching data, state had moved from
|
|
12
|
+
* anonymous to authenticated, the token was in storage, honesty grade was `signal` and capture
|
|
13
|
+
* integrity was clean. Seven and a half seconds of authorised waiting went unused, and the agent was
|
|
14
|
+
* handed a shrug.
|
|
15
|
+
*
|
|
16
|
+
* A false NEGATIVE is not the mirror of a false positive here. A false positive stops an agent early;
|
|
17
|
+
* a false negative makes it redo work that already succeeded, and — far worse — teaches it to
|
|
18
|
+
* discount the verdict channel. The verdict channel is the entire product, and an agent that learns
|
|
19
|
+
* to ignore `unknown` will ignore the true ones too.
|
|
20
|
+
*
|
|
21
|
+
* What this does NOT do is decide the request succeeded. At 492ms nobody can know that, and inventing
|
|
22
|
+
* a green here would be the exact defect this product exists to prevent. It waits for the answer
|
|
23
|
+
* rather than guessing at it:
|
|
24
|
+
*
|
|
25
|
+
* - everything settles → the normal contradiction machinery sees the real responses, so a failure
|
|
26
|
+
* still contradicts and a success no longer looks like an absence;
|
|
27
|
+
* - still in flight when the budget genuinely runs out → `unsettled`, as before, but now an honest
|
|
28
|
+
* limit instead of an early exit.
|
|
29
|
+
*/
|
|
30
|
+
import { EventType } from '@reticlehq/core';
|
|
31
|
+
/** How often to re-read the buffer. Short enough to return promptly, long enough not to spin. */
|
|
32
|
+
const POLL_MS = 50;
|
|
33
|
+
const idOf = (data) => 'string' === typeof data['id'] ? data['id'] : undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Requests that started in this window and have not come back.
|
|
36
|
+
*
|
|
37
|
+
* A settle for a request this window never saw START is ignored on purpose: the window is floored at
|
|
38
|
+
* the act cursor, so a response belonging to an earlier action can land inside it, and counting that
|
|
39
|
+
* as progress would let unrelated traffic close the window early.
|
|
40
|
+
*/
|
|
41
|
+
export function inFlightRequestIds(events) {
|
|
42
|
+
const settled = new Set();
|
|
43
|
+
for (const e of events) {
|
|
44
|
+
if (e.type !== EventType.NET_REQUEST)
|
|
45
|
+
continue;
|
|
46
|
+
const id = idOf(e.data);
|
|
47
|
+
if (id !== undefined)
|
|
48
|
+
settled.add(id);
|
|
49
|
+
}
|
|
50
|
+
const open = [];
|
|
51
|
+
for (const e of events) {
|
|
52
|
+
if (e.type !== EventType.NET_PENDING)
|
|
53
|
+
continue;
|
|
54
|
+
const id = idOf(e.data);
|
|
55
|
+
if (id !== undefined && !settled.has(id) && !open.includes(id))
|
|
56
|
+
open.push(id);
|
|
57
|
+
}
|
|
58
|
+
return open;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The same outstanding requests, as "METHOD url" — what an unsettled verdict has to be able to name.
|
|
62
|
+
*
|
|
63
|
+
* A correlation id is the right key for the wait and useless in a sentence: an agent told that
|
|
64
|
+
* `r-17` is open cannot act on it, while `POST /api/login` is the thing to re-check or to assert
|
|
65
|
+
* directly. Read off the NET_PENDING the window already holds, so this costs one pass over events
|
|
66
|
+
* that are in hand.
|
|
67
|
+
*/
|
|
68
|
+
export function inFlightRequestLabels(events) {
|
|
69
|
+
const open = new Set(inFlightRequestIds(events));
|
|
70
|
+
const labels = [];
|
|
71
|
+
for (const e of events) {
|
|
72
|
+
if (e.type !== EventType.NET_PENDING)
|
|
73
|
+
continue;
|
|
74
|
+
const id = idOf(e.data);
|
|
75
|
+
if (id === undefined || !open.has(id))
|
|
76
|
+
continue;
|
|
77
|
+
open.delete(id); // one label per request, however many times its start was recorded
|
|
78
|
+
const method = 'string' === typeof e.data['method'] ? e.data['method'] : 'request';
|
|
79
|
+
const url = 'string' === typeof e.data['url'] ? e.data['url'] : 'an unreported url';
|
|
80
|
+
labels.push(`${method} ${url}`);
|
|
81
|
+
}
|
|
82
|
+
return labels;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Wait, bounded by `budgetMs`, for the requests this window started to come back.
|
|
86
|
+
*
|
|
87
|
+
* Returns immediately when nothing is in flight, so the common green path — an action with no traffic
|
|
88
|
+
* — pays nothing at all. A zero or negative budget means no wait: the caller may have spent the whole
|
|
89
|
+
* timeout reaching the predicate, and a negative deadline must mean "stop", never "loop forever".
|
|
90
|
+
*/
|
|
91
|
+
export async function waitForInFlight(session, since, budgetMs, opts) {
|
|
92
|
+
let open = inFlightRequestIds(session.eventsSince(since));
|
|
93
|
+
if (0 === open.length)
|
|
94
|
+
return { settled: true, stillInFlight: [] };
|
|
95
|
+
if (budgetMs <= 0)
|
|
96
|
+
return { settled: false, stillInFlight: open };
|
|
97
|
+
const deadline = session.elapsed() + budgetMs;
|
|
98
|
+
while (session.elapsed() < deadline) {
|
|
99
|
+
// Never overshoot the budget: the last sleep is trimmed to whatever is actually left.
|
|
100
|
+
await opts.sleep(Math.min(POLL_MS, deadline - session.elapsed()));
|
|
101
|
+
open = inFlightRequestIds(session.eventsSince(since));
|
|
102
|
+
if (0 === open.length)
|
|
103
|
+
return { settled: true, stillInFlight: [] };
|
|
104
|
+
}
|
|
105
|
+
return { settled: false, stillInFlight: open };
|
|
106
|
+
}
|
|
@@ -24,13 +24,20 @@ interface SnapshotDelta {
|
|
|
24
24
|
addedCount: number;
|
|
25
25
|
removedCount: number;
|
|
26
26
|
}
|
|
27
|
+
/** Where focus moved, when it moved. Absent fields mean nothing held focus on that side. */
|
|
28
|
+
export interface FocusChange {
|
|
29
|
+
from?: string;
|
|
30
|
+
to?: string;
|
|
31
|
+
}
|
|
27
32
|
type DeltaDecision = {
|
|
28
33
|
mode: typeof SnapshotDeltaMode.FULL;
|
|
29
34
|
} | {
|
|
30
35
|
mode: typeof SnapshotDeltaMode.UNCHANGED;
|
|
36
|
+
focusChanged?: FocusChange;
|
|
31
37
|
} | {
|
|
32
38
|
mode: typeof SnapshotDeltaMode.DELTA;
|
|
33
39
|
delta: SnapshotDelta;
|
|
40
|
+
focusChanged?: FocusChange;
|
|
34
41
|
};
|
|
35
42
|
/** Pure: decide full vs delta vs unchanged given the previous tree (same route) and the next tree. */
|
|
36
43
|
export declare function snapshotDelta(prevTree: string | undefined, nextTree: string): DeltaDecision;
|
|
@@ -12,7 +12,108 @@
|
|
|
12
12
|
* invalidates the prior snapshot (a diff across pages would be meaningless), so the next snapshot
|
|
13
13
|
* returns full — never a misleading cross-page delta.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Identity for comparison, ref for action.
|
|
17
|
+
*
|
|
18
|
+
* The delta path used `normalizeLines` from the baseline layer, which strips the ref marker on
|
|
19
|
+
* purpose — a ref stored in a baseline would make every later diff noisy. Correct there, wrong here:
|
|
20
|
+
* every delta line came back as `- button "Row actions"` with no ref, and acting needs a ref, so the
|
|
21
|
+
* full snapshot had to be taken anyway. The diff call cost tokens and bought nothing. One helper, two
|
|
22
|
+
* callers, opposite requirements — so this path keeps the ref-stripped line as the KEY and emits the
|
|
23
|
+
* ORIGINAL line with its ref.
|
|
24
|
+
*
|
|
25
|
+
* Focus is stripped from the key too. It rides in the line as `[focused]`, alone or comma-joined with
|
|
26
|
+
* other states, so a focus move used to surface as one line removed and its twin added — structural
|
|
27
|
+
* change reported where nothing had been added at all. Focus is a property OF a line, so it gets its
|
|
28
|
+
* own field. Only focus is exempt: `disabled` moving is a real change and must still show.
|
|
29
|
+
*/
|
|
30
|
+
/** The ref marker, as `formatLine` writes it. Bounded quantifier: `\s*` backtracks polynomially. */
|
|
31
|
+
const REF_MARKER = /\s?\(ref=e\d+\)/;
|
|
32
|
+
/** The state bracket — ` [disabled,checked,expanded,focused]` — and the one state that is not structural. */
|
|
33
|
+
const STATE_BRACKET = /\s\[([a-z,]+)\]$/;
|
|
34
|
+
const FOCUSED_STATE = 'focused';
|
|
35
|
+
/** True when this line carries focus. */
|
|
36
|
+
function isFocused(line) {
|
|
37
|
+
return true === STATE_BRACKET.exec(line)?.[1]?.split(',').includes(FOCUSED_STATE);
|
|
38
|
+
}
|
|
39
|
+
/** The same line with focus removed — and the bracket dropped when focus was all it held. */
|
|
40
|
+
function withoutFocus(line) {
|
|
41
|
+
const match = STATE_BRACKET.exec(line);
|
|
42
|
+
const states = match?.[1];
|
|
43
|
+
if (null === match || undefined === match || undefined === states)
|
|
44
|
+
return line;
|
|
45
|
+
const rest = states.split(',').filter((s) => s !== FOCUSED_STATE);
|
|
46
|
+
const head = line.slice(0, match.index);
|
|
47
|
+
return 0 === rest.length ? head : `${head} [${rest.join(',')}]`;
|
|
48
|
+
}
|
|
49
|
+
/** What makes two lines "the same element" for diffing: no ref, no focus, no surrounding space. */
|
|
50
|
+
function deltaKey(line) {
|
|
51
|
+
return withoutFocus(line.replace(REF_MARKER, '')).trim();
|
|
52
|
+
}
|
|
53
|
+
function deltaLines(tree) {
|
|
54
|
+
return tree
|
|
55
|
+
.split('\n')
|
|
56
|
+
.map((line) => ({ key: deltaKey(line), text: withoutFocus(line).trim() }))
|
|
57
|
+
.filter((line) => line.key.length > 0);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Multiset diff on the key, emitting the original text.
|
|
61
|
+
*
|
|
62
|
+
* Multiset rather than set because duplicate labels are the norm — twelve identical `Row actions`
|
|
63
|
+
* buttons — and the count is the only thing that says how many left. Emitting the ORIGINAL text is
|
|
64
|
+
* what finally tells them apart: with refs, "which twelve" has an answer.
|
|
65
|
+
*/
|
|
66
|
+
function diffKeyed(prev, next) {
|
|
67
|
+
const bucket = (lines) => {
|
|
68
|
+
const map = new Map();
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const existing = map.get(line.key);
|
|
71
|
+
if (existing === undefined)
|
|
72
|
+
map.set(line.key, [line.text]);
|
|
73
|
+
else
|
|
74
|
+
existing.push(line.text);
|
|
75
|
+
}
|
|
76
|
+
return map;
|
|
77
|
+
};
|
|
78
|
+
const before = bucket(prev);
|
|
79
|
+
const after = bucket(next);
|
|
80
|
+
const removed = [];
|
|
81
|
+
const added = [];
|
|
82
|
+
for (const key of new Set([...before.keys(), ...after.keys()])) {
|
|
83
|
+
const was = before.get(key) ?? [];
|
|
84
|
+
const now = after.get(key) ?? [];
|
|
85
|
+
// Pair the SAME REF first. Twelve identical `Row actions` buttons are one key, and the ref is the
|
|
86
|
+
// only thing that says which of them left — an end-of-list surplus would name an arbitrary one.
|
|
87
|
+
const survived = new Set(now);
|
|
88
|
+
const wentOrChanged = was.filter((text) => !survived.has(text));
|
|
89
|
+
const arrivedOrChanged = now.filter((text) => !new Set(was).has(text));
|
|
90
|
+
// Whatever is left over on both sides is the SAME element with a re-minted ref (a re-render, or a
|
|
91
|
+
// navigation that restarts the sequence). Pairing those off is what stops a ref change reading as
|
|
92
|
+
// a removal plus an addition; only the count difference is a real structural change.
|
|
93
|
+
const paired = Math.min(wentOrChanged.length, arrivedOrChanged.length);
|
|
94
|
+
removed.push(...wentOrChanged.slice(paired));
|
|
95
|
+
added.push(...arrivedOrChanged.slice(paired));
|
|
96
|
+
}
|
|
97
|
+
return { removed, added };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Where focus sits now: the line to show, plus the identity to compare it BY.
|
|
101
|
+
*
|
|
102
|
+
* These have to be two different things. Comparing the rendered lines said focus had moved whenever
|
|
103
|
+
* the focused element merely changed — typing into a focused textbox rewrites its `[value=...]`, so
|
|
104
|
+
* `from` and `to` came back naming the same element with different text. Focus identity is the
|
|
105
|
+
* ELEMENT, so the comparison is on the ref, and the line is only what gets displayed. Where there is
|
|
106
|
+
* no ref to compare (a non-interactive line), the ref-stripped key stands in.
|
|
107
|
+
*
|
|
108
|
+
* Read from the raw tree so a line the diff filters out cannot silently swallow the focus report.
|
|
109
|
+
*/
|
|
110
|
+
function focusedLine(tree) {
|
|
111
|
+
const focused = tree.split('\n').find((line) => isFocused(line));
|
|
112
|
+
if (focused === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
const text = withoutFocus(focused).trim();
|
|
115
|
+
return { id: REF_MARKER.exec(focused)?.[0]?.trim() ?? deltaKey(focused), text };
|
|
116
|
+
}
|
|
16
117
|
export const SnapshotDeltaMode = {
|
|
17
118
|
FULL: 'full',
|
|
18
119
|
DELTA: 'delta',
|
|
@@ -22,12 +123,25 @@ export const SnapshotDeltaMode = {
|
|
|
22
123
|
export function snapshotDelta(prevTree, nextTree) {
|
|
23
124
|
if (prevTree === undefined)
|
|
24
125
|
return { mode: SnapshotDeltaMode.FULL };
|
|
25
|
-
const { added, removed } =
|
|
26
|
-
|
|
27
|
-
|
|
126
|
+
const { added, removed } = diffKeyed(deltaLines(prevTree), deltaLines(nextTree));
|
|
127
|
+
const before = focusedLine(prevTree);
|
|
128
|
+
const now = focusedLine(nextTree);
|
|
129
|
+
// Reported only on a MOVE. Repeating "focus is still here" every turn is the noise the delta exists
|
|
130
|
+
// to remove, and an absent field is how the agent knows nothing happened.
|
|
131
|
+
const focusChanged = before?.id === now?.id
|
|
132
|
+
? undefined
|
|
133
|
+
: {
|
|
134
|
+
...(before === undefined ? {} : { from: before.text }),
|
|
135
|
+
...(now === undefined ? {} : { to: now.text }),
|
|
136
|
+
};
|
|
137
|
+
const moved = focusChanged === undefined ? {} : { focusChanged };
|
|
138
|
+
if (0 === added.length && 0 === removed.length) {
|
|
139
|
+
return { mode: SnapshotDeltaMode.UNCHANGED, ...moved };
|
|
140
|
+
}
|
|
28
141
|
return {
|
|
29
142
|
mode: SnapshotDeltaMode.DELTA,
|
|
30
143
|
delta: { added, removed, addedCount: added.length, removedCount: removed.length },
|
|
144
|
+
...moved,
|
|
31
145
|
};
|
|
32
146
|
}
|
|
33
147
|
const DEFAULT_MAX_ENTRIES = 50;
|
|
@@ -105,8 +219,18 @@ export function applySnapshotDelta(raw, opts, cache) {
|
|
|
105
219
|
// statement about the cap, not about the page. Carried through on both branches: a delta computed
|
|
106
220
|
// over a prefix is real but not exhaustive either.
|
|
107
221
|
const capped = true === r['truncated'] ? { truncated: true, note: CAPPED_DIFF_NOTE } : {};
|
|
222
|
+
// Spread onto BOTH branches. A value computed correctly and declared nowhere on the way out is the
|
|
223
|
+
// defect shape that hit this release five separate times: nothing throws, no test reddens, the
|
|
224
|
+
// field is simply absent forever.
|
|
225
|
+
const moved = decision.focusChanged === undefined ? {} : { focusChanged: decision.focusChanged };
|
|
108
226
|
if (decision.mode === SnapshotDeltaMode.UNCHANGED) {
|
|
109
|
-
return { mode: SnapshotDeltaMode.UNCHANGED, status: r['status'], ...capped };
|
|
227
|
+
return { mode: SnapshotDeltaMode.UNCHANGED, status: r['status'], ...moved, ...capped };
|
|
110
228
|
}
|
|
111
|
-
return {
|
|
229
|
+
return {
|
|
230
|
+
mode: SnapshotDeltaMode.DELTA,
|
|
231
|
+
delta: decision.delta,
|
|
232
|
+
status: r['status'],
|
|
233
|
+
...moved,
|
|
234
|
+
...capped,
|
|
235
|
+
};
|
|
112
236
|
}
|
package/dist/tools/tool-kit.d.ts
CHANGED
|
@@ -84,11 +84,58 @@ export declare const sessionIdShape: {
|
|
|
84
84
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
85
85
|
};
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* envelope
|
|
87
|
+
* Every key `runTool` splices onto a session-bound result, as a closed list.
|
|
88
|
+
*
|
|
89
|
+
* These are wire strings and were inlined at the splice sites, which is exactly how four of them
|
|
90
|
+
* came to be spliced and never declared. `sessionEnvelopeShape` is derived from this object and
|
|
91
|
+
* `envelope-keys-declared.test.ts` fails if a member is missing from it, so the two cannot drift.
|
|
92
|
+
*
|
|
93
|
+
* Each of these is a channel that exists BECAUSE the agent had no other way to learn the fact.
|
|
94
|
+
* Undeclared, a schema-strict client validating `structuredContent` drops it, and the channel is
|
|
95
|
+
* indistinguishable from never having been built.
|
|
96
|
+
*/
|
|
97
|
+
export declare const EnvelopeKey: {
|
|
98
|
+
/** Session health — which tab, how fresh. */
|
|
99
|
+
readonly SESSION: "session";
|
|
100
|
+
/** One-shot pool-lease reminder. */
|
|
101
|
+
readonly SESSION_LEASE: "session_lease";
|
|
102
|
+
/** One-shot "these sessions are old, clean them up". */
|
|
103
|
+
readonly SESSION_AGE_WARNING: "session_age_warning";
|
|
104
|
+
/** The delivered-once human-in-the-loop guidance envelope. Losing it is a safety failure. */
|
|
105
|
+
readonly CONTROL: "control";
|
|
106
|
+
/**
|
|
107
|
+
* Rides alongside `session` whenever the tab is THROTTLED. It was declared on `reticle_act`'s
|
|
108
|
+
* schema and nowhere else, so on a validating profile every other session-bound tool silently
|
|
109
|
+
* dropped it — a throttled tab, where drives can no-op, returned a healthy-looking result.
|
|
110
|
+
*/
|
|
111
|
+
readonly WARNING: "warning";
|
|
112
|
+
/** One-shot "ask the human how this went". */
|
|
113
|
+
readonly FEEDBACK_PROMPT: "feedback_prompt";
|
|
114
|
+
/** One-shot "a newer Reticle exists". */
|
|
115
|
+
readonly UPDATE_AVAILABLE: "update_available";
|
|
116
|
+
/**
|
|
117
|
+
* "You have driven this page N times without asking for a verdict."
|
|
118
|
+
*
|
|
119
|
+
* The largest known lever on the metric this product is judged by: verdict-less sessions
|
|
120
|
+
* overwhelmingly never call a verdict-producing tool at all. It was spliced undeclared, so on a
|
|
121
|
+
* validating client the nudge was built, fired, and thrown away before any agent saw it.
|
|
122
|
+
*/
|
|
123
|
+
readonly VERIFY_NEXT: "verify_next";
|
|
124
|
+
/** The contextual "tell us what just went wrong" invitation. Same story, same release. */
|
|
125
|
+
readonly FEEDBACK_INVITE: "feedback_invite";
|
|
126
|
+
/** SDK/daemon version skew — often the one fact that explains everything else in the session. */
|
|
127
|
+
readonly VERSION_SKEW: "version_skew";
|
|
128
|
+
/** A feedback report that was accepted and then failed to send. Only the reporter can act on it. */
|
|
129
|
+
readonly FEEDBACK_UNDELIVERED: "feedback_undelivered";
|
|
130
|
+
};
|
|
131
|
+
export type EnvelopeKey = (typeof EnvelopeKey)[keyof typeof EnvelopeKey];
|
|
132
|
+
/**
|
|
133
|
+
* Fields that `runTool` / `withControl` splice onto EVERY session-bound tool result at runtime.
|
|
134
|
+
* Merged into each session-bound tool's outputSchema so a schema-strict client (structuredContent
|
|
135
|
+
* validation) keeps them instead of silently dropping them.
|
|
136
|
+
*
|
|
137
|
+
* Derived from `EnvelopeKey` rather than re-listed: this shape and the splice sites disagreed for a
|
|
138
|
+
* whole release, and every key that went missing was a channel nobody could see was missing.
|
|
92
139
|
*/
|
|
93
140
|
export declare const sessionEnvelopeShape: z.ZodRawShape;
|
|
94
141
|
/** Unwrap a browser command result or throw its error so the agent sees a clean failure. */
|