@descryy/mcp 0.2.0 → 0.3.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/dist/bin/descry-mcp.js +6 -1
- package/dist/bin/descry-mcp.js.map +1 -1
- package/dist/browser/driver.d.ts +236 -0
- package/dist/browser/driver.d.ts.map +1 -0
- package/dist/browser/driver.js +70 -0
- package/dist/browser/driver.js.map +1 -0
- package/dist/browser/evidence.d.ts +47 -0
- package/dist/browser/evidence.d.ts.map +1 -0
- package/dist/browser/evidence.js +55 -0
- package/dist/browser/evidence.js.map +1 -0
- package/dist/browser/fake-driver.d.ts +58 -0
- package/dist/browser/fake-driver.d.ts.map +1 -0
- package/dist/browser/fake-driver.js +161 -0
- package/dist/browser/fake-driver.js.map +1 -0
- package/dist/browser/graph-write.d.ts +86 -0
- package/dist/browser/graph-write.d.ts.map +1 -0
- package/dist/browser/graph-write.js +135 -0
- package/dist/browser/graph-write.js.map +1 -0
- package/dist/browser/playwright-driver.d.ts +65 -0
- package/dist/browser/playwright-driver.d.ts.map +1 -0
- package/dist/browser/playwright-driver.js +359 -0
- package/dist/browser/playwright-driver.js.map +1 -0
- package/dist/browser/provider.d.ts +49 -0
- package/dist/browser/provider.d.ts.map +1 -0
- package/dist/browser/provider.js +28 -0
- package/dist/browser/provider.js.map +1 -0
- package/dist/browser/registry.d.ts +182 -0
- package/dist/browser/registry.d.ts.map +1 -0
- package/dist/browser/registry.js +215 -0
- package/dist/browser/registry.js.map +1 -0
- package/dist/browser/scenario-resolve.d.ts +62 -0
- package/dist/browser/scenario-resolve.d.ts.map +1 -0
- package/dist/browser/scenario-resolve.js +127 -0
- package/dist/browser/scenario-resolve.js.map +1 -0
- package/dist/browser/scenario-runner.d.ts +113 -0
- package/dist/browser/scenario-runner.d.ts.map +1 -0
- package/dist/browser/scenario-runner.js +315 -0
- package/dist/browser/scenario-runner.js.map +1 -0
- package/dist/browser/stack-parser.d.ts +43 -0
- package/dist/browser/stack-parser.d.ts.map +1 -0
- package/dist/browser/stack-parser.js +112 -0
- package/dist/browser/stack-parser.js.map +1 -0
- package/dist/browser/tool-support.d.ts +113 -0
- package/dist/browser/tool-support.d.ts.map +1 -0
- package/dist/browser/tool-support.js +181 -0
- package/dist/browser/tool-support.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +112 -10
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +79 -13
- package/dist/render.js.map +1 -1
- package/dist/scenarios/index.d.ts +4 -0
- package/dist/scenarios/index.d.ts.map +1 -0
- package/dist/scenarios/index.js +4 -0
- package/dist/scenarios/index.js.map +1 -0
- package/dist/scenarios/parse.d.ts +32 -0
- package/dist/scenarios/parse.d.ts.map +1 -0
- package/dist/scenarios/parse.js +199 -0
- package/dist/scenarios/parse.js.map +1 -0
- package/dist/scenarios/scenario.d.ts +115 -0
- package/dist/scenarios/scenario.d.ts.map +1 -0
- package/dist/scenarios/scenario.js +41 -0
- package/dist/scenarios/scenario.js.map +1 -0
- package/dist/scenarios/storage.d.ts +51 -0
- package/dist/scenarios/storage.d.ts.map +1 -0
- package/dist/scenarios/storage.js +93 -0
- package/dist/scenarios/storage.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -3
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +68 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +89 -0
- package/dist/session.js.map +1 -1
- package/dist/tools/analyze.d.ts +41 -1
- package/dist/tools/analyze.d.ts.map +1 -1
- package/dist/tools/analyze.js +81 -8
- package/dist/tools/analyze.js.map +1 -1
- package/dist/tools/browser-click.d.ts +36 -0
- package/dist/tools/browser-click.d.ts.map +1 -0
- package/dist/tools/browser-click.js +117 -0
- package/dist/tools/browser-click.js.map +1 -0
- package/dist/tools/browser-close-session.d.ts +24 -0
- package/dist/tools/browser-close-session.d.ts.map +1 -0
- package/dist/tools/browser-close-session.js +63 -0
- package/dist/tools/browser-close-session.js.map +1 -0
- package/dist/tools/browser-fill.d.ts +49 -0
- package/dist/tools/browser-fill.d.ts.map +1 -0
- package/dist/tools/browser-fill.js +138 -0
- package/dist/tools/browser-fill.js.map +1 -0
- package/dist/tools/browser-navigate.d.ts +32 -0
- package/dist/tools/browser-navigate.d.ts.map +1 -0
- package/dist/tools/browser-navigate.js +103 -0
- package/dist/tools/browser-navigate.js.map +1 -0
- package/dist/tools/browser-run-scenario.d.ts +54 -0
- package/dist/tools/browser-run-scenario.d.ts.map +1 -0
- package/dist/tools/browser-run-scenario.js +228 -0
- package/dist/tools/browser-run-scenario.js.map +1 -0
- package/dist/tools/browser-save-scenario.d.ts +44 -0
- package/dist/tools/browser-save-scenario.d.ts.map +1 -0
- package/dist/tools/browser-save-scenario.js +298 -0
- package/dist/tools/browser-save-scenario.js.map +1 -0
- package/dist/tools/browser-snapshot.d.ts +60 -0
- package/dist/tools/browser-snapshot.d.ts.map +1 -0
- package/dist/tools/browser-snapshot.js +139 -0
- package/dist/tools/browser-snapshot.js.map +1 -0
- package/dist/tools/browser-start-session.d.ts +43 -0
- package/dist/tools/browser-start-session.d.ts.map +1 -0
- package/dist/tools/browser-start-session.js +272 -0
- package/dist/tools/browser-start-session.js.map +1 -0
- package/dist/tools/browser-type.d.ts +48 -0
- package/dist/tools/browser-type.d.ts.map +1 -0
- package/dist/tools/browser-type.js +136 -0
- package/dist/tools/browser-type.js.map +1 -0
- package/dist/tools/contracts.d.ts +15 -0
- package/dist/tools/contracts.d.ts.map +1 -1
- package/dist/tools/contracts.js +14 -3
- package/dist/tools/contracts.js.map +1 -1
- package/dist/tools/git-diff.d.ts.map +1 -1
- package/dist/tools/git-diff.js +85 -6
- package/dist/tools/git-diff.js.map +1 -1
- package/dist/tools/impact.d.ts +9 -0
- package/dist/tools/impact.d.ts.map +1 -1
- package/dist/tools/impact.js +3 -3
- package/dist/tools/impact.js.map +1 -1
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +27 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/kit.d.ts +10 -1
- package/dist/tools/kit.d.ts.map +1 -1
- package/dist/tools/kit.js +2 -0
- package/dist/tools/kit.js.map +1 -1
- package/dist/tools/observe-runtime.d.ts +113 -19
- package/dist/tools/observe-runtime.d.ts.map +1 -1
- package/dist/tools/observe-runtime.js +293 -52
- package/dist/tools/observe-runtime.js.map +1 -1
- package/dist/tools/observe-tests.d.ts +125 -0
- package/dist/tools/observe-tests.d.ts.map +1 -0
- package/dist/tools/observe-tests.js +313 -0
- package/dist/tools/observe-tests.js.map +1 -0
- package/dist/tools/pr-analysis.d.ts +11 -0
- package/dist/tools/pr-analysis.d.ts.map +1 -1
- package/dist/tools/pr-analysis.js +3 -3
- package/dist/tools/pr-analysis.js.map +1 -1
- package/dist/tools/pre-push.d.ts +77 -0
- package/dist/tools/pre-push.d.ts.map +1 -0
- package/dist/tools/pre-push.js +250 -0
- package/dist/tools/pre-push.js.map +1 -0
- package/dist/tools/questions.d.ts.map +1 -1
- package/dist/tools/questions.js +41 -16
- package/dist/tools/questions.js.map +1 -1
- package/dist/tools/verification-status.d.ts +22 -0
- package/dist/tools/verification-status.d.ts.map +1 -1
- package/dist/tools/verification-status.js +51 -5
- package/dist/tools/verification-status.js.map +1 -1
- package/package.json +14 -13
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `browser_run_scenario` — replay a saved scenario in one batch call.
|
|
3
|
+
*
|
|
4
|
+
* ## One reply, not N
|
|
5
|
+
*
|
|
6
|
+
* The point of this tool over "call browser_click six times" is that a
|
|
7
|
+
* scenario is a *check*, and a check has one outcome. Six replies make the
|
|
8
|
+
* calling agent reassemble a verdict from fragments and give it six chances to
|
|
9
|
+
* stop early with a partial picture; one reply carries the whole run — every
|
|
10
|
+
* step, every assertion, and one aggregated evidence summary drained a step at
|
|
11
|
+
* a time.
|
|
12
|
+
*
|
|
13
|
+
* ## Class
|
|
14
|
+
*
|
|
15
|
+
* `action`, on the same test as `browser_click`: it changes a running
|
|
16
|
+
* application, and declaring it anything softer would be the lie. Like the
|
|
17
|
+
* verbs, it mints nothing — consent was granted once at
|
|
18
|
+
* `browser_start_session` and bound to that origin, and a scenario cannot
|
|
19
|
+
* widen it: a stored `navigate` step that would leave the consented origin
|
|
20
|
+
* stops the replay rather than following the file.
|
|
21
|
+
*
|
|
22
|
+
* ## What it will not do
|
|
23
|
+
*
|
|
24
|
+
* Report a verdict about the application beyond what the scenario's own author
|
|
25
|
+
* wrote down. `DEC-381` keeps classification
|
|
26
|
+
* in the desktop pipeline, so a console error observed during a replay is
|
|
27
|
+
* reported and is not blocking — while a console error the author *asserted*
|
|
28
|
+
* would not happen is a failed assertion and is. Same evidence, different
|
|
29
|
+
* status, and the difference is entirely whether a person claimed something
|
|
30
|
+
* about it.
|
|
31
|
+
*/
|
|
32
|
+
import { readScenario } from "../scenarios/index.js";
|
|
33
|
+
import { browserAnswer, emptyEvidence, mutationRefusal, useSession, withinConsent } from "../browser/tool-support.js";
|
|
34
|
+
import { NO_GRAPH_WRITE } from "../browser/graph-write.js";
|
|
35
|
+
import { mutates, replayScenario } from "../browser/scenario-runner.js";
|
|
36
|
+
import { scenarioPath } from "../scenarios/index.js";
|
|
37
|
+
import { requiredString } from "./kit.js";
|
|
38
|
+
const SCHEMA = {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
sessionId: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "REQUIRED. The id returned by browser_start_session. The replay runs in that session, under the consent it was granted.",
|
|
44
|
+
},
|
|
45
|
+
name: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "REQUIRED. The scenario's name, without the file extension — a scenario saved as " +
|
|
48
|
+
"descry/scenarios/checkout.scenario.json is named \"checkout\". Saved scenarios are committed files; " +
|
|
49
|
+
"browser_save_scenario writes them.",
|
|
50
|
+
},
|
|
51
|
+
confirmToken: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Not normally needed. Consent is granted once, for the whole session, by browser_start_session, so " +
|
|
54
|
+
"this call replays directly. The field is declared because the handshake reads it for every " +
|
|
55
|
+
"action-class tool.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: ["sessionId", "name"],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
};
|
|
61
|
+
function empty(sessionId, name) {
|
|
62
|
+
return {
|
|
63
|
+
sessionId,
|
|
64
|
+
scenario: name,
|
|
65
|
+
outcome: "notRun",
|
|
66
|
+
counts: { passed: 0, failed: 0, unresolved: 0, unverified: 0 },
|
|
67
|
+
blockingTotal: 0,
|
|
68
|
+
// 2 rather than 0: this is "the check could not run", and a refusal that
|
|
69
|
+
// reported clean would let a scenario nobody could replay keep claiming
|
|
70
|
+
// coverage.
|
|
71
|
+
exitCode: 2,
|
|
72
|
+
steps: [],
|
|
73
|
+
assertions: [],
|
|
74
|
+
evidence: emptyEvidence(),
|
|
75
|
+
graph: NO_GRAPH_WRITE,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function run(args, ctx) {
|
|
79
|
+
const sessionId = requiredString(args, "sessionId");
|
|
80
|
+
const name = requiredString(args, "name");
|
|
81
|
+
const stored = await readScenario(ctx.session.repoPath, name);
|
|
82
|
+
if (!stored.valid) {
|
|
83
|
+
return browserAnswer({
|
|
84
|
+
ctx,
|
|
85
|
+
state: "refused",
|
|
86
|
+
headline: `Scenario "${name}" cannot be replayed.`,
|
|
87
|
+
notes: [
|
|
88
|
+
...stored.problems,
|
|
89
|
+
`Scenarios live in ${scenarioPath(ctx.session.repoPath, name)} and are committed like source. ` +
|
|
90
|
+
"browser_save_scenario writes one from a session you have explored.",
|
|
91
|
+
],
|
|
92
|
+
data: empty(sessionId, name),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const scenario = stored.scenario;
|
|
96
|
+
const found = await useSession(ctx.session.browserSessions(), sessionId);
|
|
97
|
+
if (!found.ok) {
|
|
98
|
+
return browserAnswer({
|
|
99
|
+
ctx,
|
|
100
|
+
state: found.dead.state,
|
|
101
|
+
headline: found.dead.headline,
|
|
102
|
+
notes: found.dead.notes,
|
|
103
|
+
data: { ...empty(sessionId, name), evidence: found.dead.evidence },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const { entry } = found;
|
|
107
|
+
// Consent is bound to one origin and a stored file cannot widen it. Checked
|
|
108
|
+
// before anything runs, because a replay that got three steps in and then
|
|
109
|
+
// refused would have already changed the application.
|
|
110
|
+
if (!withinConsent(entry, scenario.targetUrl)) {
|
|
111
|
+
return browserAnswer({
|
|
112
|
+
ctx,
|
|
113
|
+
state: "refused",
|
|
114
|
+
headline: `Scenario "${name}" targets another origin.`,
|
|
115
|
+
notes: [
|
|
116
|
+
`It opens ${scenario.targetUrl}, and this session's consent covers ${entry.consentedOrigin} only. ` +
|
|
117
|
+
"Consent is granted per session and bound to the origin it was granted for, so a saved file cannot " +
|
|
118
|
+
"extend it. Start a session against that origin to replay this scenario.",
|
|
119
|
+
],
|
|
120
|
+
data: empty(sessionId, name),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const refusedForSafety = mutationRefusal(entry);
|
|
124
|
+
if (refusedForSafety !== null && mutates(scenario)) {
|
|
125
|
+
return browserAnswer({
|
|
126
|
+
ctx,
|
|
127
|
+
state: "refused",
|
|
128
|
+
headline: `${entry.profile?.name ?? "This environment"} is read-only.`,
|
|
129
|
+
notes: [
|
|
130
|
+
refusedForSafety,
|
|
131
|
+
`Scenario "${name}" clicks, types or fills, so replaying it would change the application. A ` +
|
|
132
|
+
"navigate-only scenario would still run here.",
|
|
133
|
+
],
|
|
134
|
+
data: empty(sessionId, name),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// Start where the scenario says it starts, rather than wherever the session
|
|
138
|
+
// happened to be left. A replay that began from an arbitrary page is not a
|
|
139
|
+
// repeat of the run that was saved.
|
|
140
|
+
if (entry.page.currentUrl() !== scenario.targetUrl) {
|
|
141
|
+
await entry.page.navigate(scenario.targetUrl);
|
|
142
|
+
}
|
|
143
|
+
const replay = await replayScenario(ctx, ctx.session.browserSessions(), sessionId, scenario);
|
|
144
|
+
const notes = [
|
|
145
|
+
...(replay.sessionEnded === null ? [] : replay.sessionEnded.notes),
|
|
146
|
+
...replay.notes,
|
|
147
|
+
...outcomeNotes(replay.outcome, replay.counts, name),
|
|
148
|
+
...replay.steps.filter((s) => s.resolution !== null).map((s) => s.resolution.message),
|
|
149
|
+
...replay.assertions.filter((a) => a.status === "failed" || a.status === "unevaluable").map((a) => a.detail),
|
|
150
|
+
];
|
|
151
|
+
return browserAnswer({
|
|
152
|
+
ctx,
|
|
153
|
+
...(replay.sessionEnded === null ? {} : { state: replay.sessionEnded.state }),
|
|
154
|
+
headline: `Scenario "${name}": ${replay.outcome} — ${replay.counts.passed} assertion(s) held, ` +
|
|
155
|
+
`${replay.counts.failed} failed, ${replay.counts.unresolved} could not run.`,
|
|
156
|
+
notes,
|
|
157
|
+
data: {
|
|
158
|
+
sessionId,
|
|
159
|
+
scenario: name,
|
|
160
|
+
outcome: replay.outcome,
|
|
161
|
+
counts: replay.counts,
|
|
162
|
+
blockingTotal: replay.blockingTotal,
|
|
163
|
+
exitCode: replay.exitCode,
|
|
164
|
+
steps: replay.steps,
|
|
165
|
+
assertions: replay.assertions,
|
|
166
|
+
evidence: replay.evidence,
|
|
167
|
+
graph: replay.graph,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The disclosure the three counts exist for.
|
|
173
|
+
*
|
|
174
|
+
* Emitted per outcome rather than always, because a note that appears on every
|
|
175
|
+
* reply is a note nobody reads by the third one — the same reasoning
|
|
176
|
+
* `evidenceNotes` uses for the triage disclaimer.
|
|
177
|
+
*/
|
|
178
|
+
function outcomeNotes(outcome, counts, name) {
|
|
179
|
+
switch (outcome) {
|
|
180
|
+
case "unverified":
|
|
181
|
+
return [
|
|
182
|
+
`Scenario "${name}" stores no assertions, so this replay verified nothing. Its steps still resolved ` +
|
|
183
|
+
"and still ran — real information — but that is not the same statement as the application behaving, " +
|
|
184
|
+
"and it is deliberately never reported as passed.",
|
|
185
|
+
];
|
|
186
|
+
case "unresolved":
|
|
187
|
+
return [
|
|
188
|
+
`${counts.unresolved} step or assertion could not run, so this replay neither passed nor failed. ` +
|
|
189
|
+
"Descry cannot tell a deliberate rename from a vanished element — both look identical from here — " +
|
|
190
|
+
"so it claims neither. The remedy is in the message below: re-save the step if the change was " +
|
|
191
|
+
"intended, or fix the application if it was not.",
|
|
192
|
+
];
|
|
193
|
+
case "failed":
|
|
194
|
+
return [
|
|
195
|
+
`${counts.failed} assertion(s) the scenario's author wrote down did not hold. Only those count as ` +
|
|
196
|
+
"blocking; console errors and failed requests observed during the replay are reported below and are " +
|
|
197
|
+
"not, because they are frequently true of a working page." +
|
|
198
|
+
(counts.unresolved > 0
|
|
199
|
+
? ` ${counts.unresolved} check(s) also could not run, counted separately and never added to the ` +
|
|
200
|
+
"blocking total."
|
|
201
|
+
: ""),
|
|
202
|
+
];
|
|
203
|
+
case "passed":
|
|
204
|
+
case "notRun":
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
export const browserRunScenarioTool = {
|
|
209
|
+
name: "browser_run_scenario",
|
|
210
|
+
title: "Replay a saved scenario",
|
|
211
|
+
description: "Replay a saved scenario — a named step sequence with the assertions its author wrote down — in one " +
|
|
212
|
+
"call, and return a single evidence summary rather than one reply per step. Each step re-resolves its " +
|
|
213
|
+
"element against a fresh page: exactly one match or the replay stops and says which of role, name and " +
|
|
214
|
+
"selector disagreed and what the page holds now. Reports three counts that are never added together — " +
|
|
215
|
+
"failed assertions, checks that could not run, and whether the scenario asserted anything at all. A " +
|
|
216
|
+
"scenario with no assertions replays as unverified, never as passed.",
|
|
217
|
+
class: "action",
|
|
218
|
+
tier: "evidence",
|
|
219
|
+
version: "1.0.0",
|
|
220
|
+
inputSchema: SCHEMA,
|
|
221
|
+
run,
|
|
222
|
+
// Undefined, always — the same property the verbs have. The session was
|
|
223
|
+
// confirmed at browser_start_session; a replay that could mint its own
|
|
224
|
+
// consent would be a gate that exists only for callers who were not going
|
|
225
|
+
// to abuse it.
|
|
226
|
+
describeAction: () => undefined,
|
|
227
|
+
};
|
|
228
|
+
//# sourceMappingURL=browser-run-scenario.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-run-scenario.js","sourceRoot":"","sources":["../../src/tools/browser-run-scenario.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `browser_save_scenario` — an explored session becomes a durable, repeatable
|
|
3
|
+
* scenario.
|
|
4
|
+
*
|
|
5
|
+
* ## Everything goes out through `writeScenario`
|
|
6
|
+
*
|
|
7
|
+
* There is deliberately no private path to disk here. `writeScenario`
|
|
8
|
+
* validates on the way *out* as well as in, and that is not redundant: a
|
|
9
|
+
* scenario is written once and read on every push afterwards, so a fault saved
|
|
10
|
+
* today surfaces during a check somebody is waiting on, long after the session
|
|
11
|
+
* that could have explained it is gone. A tool that serialized its own JSON
|
|
12
|
+
* would be a second writer with a first writer's rules, and the second one is
|
|
13
|
+
* the one that ships a file nothing can read.
|
|
14
|
+
*
|
|
15
|
+
* ## A step carries no ref, and this schema offers no way to send one
|
|
16
|
+
*
|
|
17
|
+
* The ref refusal is enforced twice on purpose. The format module rejects a
|
|
18
|
+
* step carrying a `ref` **by name**, with the reason in the message; this
|
|
19
|
+
* schema does not declare the field at all. A caller working from a
|
|
20
|
+
* `browser_snapshot` reply has the three durable fields already — every
|
|
21
|
+
* snapshot element carries `role`, `name` and `selector` — so the natural
|
|
22
|
+
* thing to copy is the right thing to store.
|
|
23
|
+
*
|
|
24
|
+
* ## What the session supplies
|
|
25
|
+
*
|
|
26
|
+
* The consented origin (a scenario may not be saved pointing somewhere this
|
|
27
|
+
* session was never allowed to drive), the URL to start from, and the
|
|
28
|
+
* provenance pair. The steps are the caller's: this server has no exploration
|
|
29
|
+
* planner and does not pretend to have recorded intent it never saw.
|
|
30
|
+
*/
|
|
31
|
+
import { type ToolDefinition } from "./kit.ts";
|
|
32
|
+
export interface BrowserSaveScenarioData {
|
|
33
|
+
readonly sessionId: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
/** Where it was written, or "" when nothing was written. */
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly targetUrl: string;
|
|
38
|
+
readonly stepCount: number;
|
|
39
|
+
readonly assertionCount: number;
|
|
40
|
+
readonly savedAt: string;
|
|
41
|
+
readonly savedAtCommit: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const browserSaveScenarioTool: ToolDefinition;
|
|
44
|
+
//# sourceMappingURL=browser-save-scenario.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-save-scenario.d.ts","sourceRoot":"","sources":["../../src/tools/browser-save-scenario.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH,OAAO,EAAoE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAGjH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AA2QD,eAAO,MAAM,uBAAuB,EAAE,cAoBrC,CAAC"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `browser_save_scenario` — an explored session becomes a durable, repeatable
|
|
3
|
+
* scenario.
|
|
4
|
+
*
|
|
5
|
+
* ## Everything goes out through `writeScenario`
|
|
6
|
+
*
|
|
7
|
+
* There is deliberately no private path to disk here. `writeScenario`
|
|
8
|
+
* validates on the way *out* as well as in, and that is not redundant: a
|
|
9
|
+
* scenario is written once and read on every push afterwards, so a fault saved
|
|
10
|
+
* today surfaces during a check somebody is waiting on, long after the session
|
|
11
|
+
* that could have explained it is gone. A tool that serialized its own JSON
|
|
12
|
+
* would be a second writer with a first writer's rules, and the second one is
|
|
13
|
+
* the one that ships a file nothing can read.
|
|
14
|
+
*
|
|
15
|
+
* ## A step carries no ref, and this schema offers no way to send one
|
|
16
|
+
*
|
|
17
|
+
* The ref refusal is enforced twice on purpose. The format module rejects a
|
|
18
|
+
* step carrying a `ref` **by name**, with the reason in the message; this
|
|
19
|
+
* schema does not declare the field at all. A caller working from a
|
|
20
|
+
* `browser_snapshot` reply has the three durable fields already — every
|
|
21
|
+
* snapshot element carries `role`, `name` and `selector` — so the natural
|
|
22
|
+
* thing to copy is the right thing to store.
|
|
23
|
+
*
|
|
24
|
+
* ## What the session supplies
|
|
25
|
+
*
|
|
26
|
+
* The consented origin (a scenario may not be saved pointing somewhere this
|
|
27
|
+
* session was never allowed to drive), the URL to start from, and the
|
|
28
|
+
* provenance pair. The steps are the caller's: this server has no exploration
|
|
29
|
+
* planner and does not pretend to have recorded intent it never saw.
|
|
30
|
+
*/
|
|
31
|
+
import { gitHead } from "@descryy/core";
|
|
32
|
+
import { browserAnswer, useSession, withinConsent } from "../browser/tool-support.js";
|
|
33
|
+
import { SCENARIO_SCHEMA_VERSION, scenarioPath, writeScenario } from "../scenarios/index.js";
|
|
34
|
+
import { optionalString, requiredString, ToolInputError } from "./kit.js";
|
|
35
|
+
const TARGET_SCHEMA = {
|
|
36
|
+
type: "object",
|
|
37
|
+
description: "The element, named the way a replay can find it again: all three of role, name and selector, together, " +
|
|
38
|
+
"always. Copy them from a browser_snapshot element — every one carries all three. There is no ref field " +
|
|
39
|
+
"and there cannot be one: a ref is scoped to the snapshot that issued it and a replay is always a later " +
|
|
40
|
+
"session. There is also no fallback between the three at replay: a selector that still matches while the " +
|
|
41
|
+
"role or name disagrees stops the replay rather than substituting quietly, because that substitution is " +
|
|
42
|
+
"how a scenario keeps reporting green against a page that changed underneath it.",
|
|
43
|
+
properties: {
|
|
44
|
+
role: { type: "string", description: "The element's ARIA role, as browser_snapshot reported it." },
|
|
45
|
+
name: { type: "string", description: "The element's accessible name, as browser_snapshot reported it." },
|
|
46
|
+
selector: { type: "string", description: "The selector browser_snapshot reported for it." },
|
|
47
|
+
},
|
|
48
|
+
required: ["role", "name", "selector"],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
};
|
|
51
|
+
const SCHEMA = {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
sessionId: { type: "string", description: "REQUIRED. The browser session this scenario was explored in." },
|
|
55
|
+
name: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "REQUIRED. What to call it. Becomes descry/scenarios/<name>.scenario.json — a committed file, " +
|
|
58
|
+
"reviewable in a diff, and deliberately not under .descry/, which is gitignored and would make the " +
|
|
59
|
+
"scenario vanish on CI's fresh checkout.",
|
|
60
|
+
},
|
|
61
|
+
description: { type: "string", description: "Free text for whoever reads the file later. Optional." },
|
|
62
|
+
targetUrl: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Where a replay should start. Defaults to the session's current URL, and must be on the origin this " +
|
|
65
|
+
"session was consented for — a saved file cannot widen a consent grant.",
|
|
66
|
+
},
|
|
67
|
+
steps: {
|
|
68
|
+
type: "array",
|
|
69
|
+
description: "REQUIRED, and at least one. The sequence to replay, in order. Every step is re-resolved against a " +
|
|
70
|
+
"fresh page at replay time.",
|
|
71
|
+
items: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
action: { type: "string", enum: ["navigate", "click", "type", "fill"], description: "navigate acts on the page; click, type and fill act on an element and need a target." },
|
|
75
|
+
target: TARGET_SCHEMA,
|
|
76
|
+
url: { type: "string", description: "Where to go. For navigate steps only." },
|
|
77
|
+
text: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "What to enter. For type and fill only, and the two differ in a way that decides whether a page " +
|
|
80
|
+
"behaves: type fires real keystrokes and appends, fill sets the value directly and fires none, " +
|
|
81
|
+
"so a field that opens an autocomplete or validates per character sees nothing at all from a fill.",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ["action"],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
assertions: {
|
|
89
|
+
type: "array",
|
|
90
|
+
description: "REQUIRED, and may be empty. What this scenario claims about the application. Absent is refused " +
|
|
91
|
+
"rather than defaulted, because absent and empty are different statements: an empty list is an " +
|
|
92
|
+
"exploration saved honestly, and it replays as \"unverified\" — never as passed. A replay that clicks " +
|
|
93
|
+
"through a broken page and reports success is a false negative with a confident face on it, and the " +
|
|
94
|
+
"runner cannot invent a check it was never told about.",
|
|
95
|
+
items: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
kind: {
|
|
99
|
+
type: "string",
|
|
100
|
+
enum: ["elementVisible", "textEquals", "urlEquals", "noConsoleErrors", "noFailedRequests"],
|
|
101
|
+
description: "elementVisible and textEquals are about one element and need a target; textEquals and urlEquals " +
|
|
102
|
+
"compare against expected. noConsoleErrors and noFailedRequests read the evidence gathered up to " +
|
|
103
|
+
"that point — an OBSERVED console error never blocks a push, but one the author asserted would " +
|
|
104
|
+
"not happen does, because a person claimed it.",
|
|
105
|
+
},
|
|
106
|
+
afterStep: {
|
|
107
|
+
type: "integer",
|
|
108
|
+
description: "Which step this runs after, by index. Omit for end-of-scenario. An index past the last step is " +
|
|
109
|
+
"refused here rather than discovered at replay: an assertion that never runs never says so, and " +
|
|
110
|
+
"the scenario reports green because its only check was unreachable.",
|
|
111
|
+
},
|
|
112
|
+
target: TARGET_SCHEMA,
|
|
113
|
+
expected: { type: "string", description: "The value to compare against, for the kinds that compare one." },
|
|
114
|
+
},
|
|
115
|
+
required: ["kind"],
|
|
116
|
+
additionalProperties: false,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
confirmToken: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Not normally needed. Consent for this session was granted at browser_start_session, and this call " +
|
|
122
|
+
"writes a file rather than driving the browser. The field is declared because the handshake reads it " +
|
|
123
|
+
"for every action-class tool.",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ["sessionId", "name", "steps", "assertions"],
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
};
|
|
129
|
+
function readObjectArray(args, key) {
|
|
130
|
+
const raw = args[key];
|
|
131
|
+
if (raw === undefined || raw === null) {
|
|
132
|
+
throw new ToolInputError(`"${key}" is required. Absent and empty are different statements here — send [] to say this scenario ` +
|
|
133
|
+
"has none, which is stored honestly and replays as unverified.");
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(raw) || raw.some((item) => typeof item !== "object" || item === null || Array.isArray(item))) {
|
|
136
|
+
throw new ToolInputError(`"${key}" must be an array of objects`);
|
|
137
|
+
}
|
|
138
|
+
return raw;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Shape the caller's arguments into a `Scenario` **without validating them**.
|
|
142
|
+
*
|
|
143
|
+
* Deliberate: `writeScenario` is the validator, and a second one here would be
|
|
144
|
+
* a second set of rules to keep in step with the first. Everything a caller
|
|
145
|
+
* got wrong comes back through the format module's own messages, which are the
|
|
146
|
+
* ones the file's future readers will see.
|
|
147
|
+
*/
|
|
148
|
+
function shape(args, name, targetUrl, savedAtCommit) {
|
|
149
|
+
const steps = readObjectArray(args, "steps").map((step) => ({
|
|
150
|
+
action: step["action"],
|
|
151
|
+
target: (step["target"] ?? null),
|
|
152
|
+
url: (step["url"] ?? null),
|
|
153
|
+
text: (step["text"] ?? null),
|
|
154
|
+
}));
|
|
155
|
+
const assertions = readObjectArray(args, "assertions").map((assertion) => ({
|
|
156
|
+
kind: assertion["kind"],
|
|
157
|
+
afterStep: (assertion["afterStep"] ?? null),
|
|
158
|
+
target: (assertion["target"] ?? null),
|
|
159
|
+
expected: (assertion["expected"] ?? null),
|
|
160
|
+
}));
|
|
161
|
+
return {
|
|
162
|
+
schemaVersion: SCENARIO_SCHEMA_VERSION,
|
|
163
|
+
name,
|
|
164
|
+
description: optionalString(args, "description") ?? null,
|
|
165
|
+
targetUrl,
|
|
166
|
+
savedAt: new Date().toISOString(),
|
|
167
|
+
savedAtCommit,
|
|
168
|
+
steps,
|
|
169
|
+
assertions,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The commit this scenario was authored against.
|
|
174
|
+
*
|
|
175
|
+
* Provenance only — nothing expires on it and nothing refuses because of it.
|
|
176
|
+
* It exists so a replay failure can say *the scenario was written against a
|
|
177
|
+
* different revision of this application*, which is usually the answer to "why
|
|
178
|
+
* did this break". A directory that is not a git checkout records the empty
|
|
179
|
+
* string and the reply says so, rather than a plausible-looking placeholder
|
|
180
|
+
* that a later reader would try to `git show`.
|
|
181
|
+
*/
|
|
182
|
+
async function commitOf(repoPath) {
|
|
183
|
+
try {
|
|
184
|
+
return await gitHead(repoPath);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function run(args, ctx) {
|
|
191
|
+
const sessionId = requiredString(args, "sessionId");
|
|
192
|
+
const name = requiredString(args, "name");
|
|
193
|
+
const found = await useSession(ctx.session.browserSessions(), sessionId);
|
|
194
|
+
if (!found.ok) {
|
|
195
|
+
return browserAnswer({
|
|
196
|
+
ctx,
|
|
197
|
+
state: found.dead.state,
|
|
198
|
+
headline: found.dead.headline,
|
|
199
|
+
notes: [
|
|
200
|
+
...found.dead.notes,
|
|
201
|
+
"Nothing was written. A scenario records a session somebody explored, and there is no session here " +
|
|
202
|
+
"to record.",
|
|
203
|
+
],
|
|
204
|
+
data: blank(sessionId, name),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const { entry } = found;
|
|
208
|
+
const targetUrl = optionalString(args, "targetUrl") ?? entry.page.currentUrl();
|
|
209
|
+
if (!withinConsent(entry, targetUrl)) {
|
|
210
|
+
return browserAnswer({
|
|
211
|
+
ctx,
|
|
212
|
+
state: "refused",
|
|
213
|
+
headline: `${targetUrl} is outside this session's consented origin.`,
|
|
214
|
+
notes: [
|
|
215
|
+
`This session's consent covers the origin ${entry.consentedOrigin} alone, and a saved scenario cannot point somewhere ` +
|
|
216
|
+
"it was never allowed to drive — storing one would launder a consent grant into a file that outlives " +
|
|
217
|
+
"the session. Nothing was written.",
|
|
218
|
+
],
|
|
219
|
+
data: blank(sessionId, name),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const scenario = shape(args, name, targetUrl, await commitOf(ctx.session.repoPath));
|
|
223
|
+
try {
|
|
224
|
+
// The one write path. `writeScenario` validates on the way out, so an
|
|
225
|
+
// artifact that this reader could not read back never reaches disk.
|
|
226
|
+
await writeScenario(ctx.session.repoPath, scenario);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
return browserAnswer({
|
|
230
|
+
ctx,
|
|
231
|
+
state: "refused",
|
|
232
|
+
headline: `Scenario "${name}" was not saved.`,
|
|
233
|
+
notes: [
|
|
234
|
+
String(error instanceof Error ? error.message : error),
|
|
235
|
+
"Validated on the way out on purpose: a scenario is written once and read on every push afterwards, " +
|
|
236
|
+
"so a fault stored today would surface during a check somebody is waiting on, long after this " +
|
|
237
|
+
"session could have explained it.",
|
|
238
|
+
],
|
|
239
|
+
data: blank(sessionId, name),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const path = scenarioPath(ctx.session.repoPath, name);
|
|
243
|
+
return browserAnswer({
|
|
244
|
+
ctx,
|
|
245
|
+
headline: `Saved scenario "${name}" — ${scenario.steps.length} step(s), ${scenario.assertions.length} assertion(s).`,
|
|
246
|
+
notes: [
|
|
247
|
+
`Written to ${path}. Commit it: scenarios are source, and one that is not committed works locally, ` +
|
|
248
|
+
"vanishes on CI's fresh checkout, and reports nothing rather than failing.",
|
|
249
|
+
...(scenario.assertions.length === 0
|
|
250
|
+
? [
|
|
251
|
+
"This scenario asserts nothing, so a replay of it will report \"unverified\" rather than passed. " +
|
|
252
|
+
"Saving an exploration is deliberately possible; an exploration replayed without incident has " +
|
|
253
|
+
"verified nothing.",
|
|
254
|
+
]
|
|
255
|
+
: []),
|
|
256
|
+
...(scenario.savedAtCommit === ""
|
|
257
|
+
? [
|
|
258
|
+
"No commit was recorded — this directory is not a git checkout. The scenario is stored and " +
|
|
259
|
+
"replayable; a later failure just cannot say which revision it was written against.",
|
|
260
|
+
]
|
|
261
|
+
: []),
|
|
262
|
+
],
|
|
263
|
+
data: {
|
|
264
|
+
sessionId,
|
|
265
|
+
name,
|
|
266
|
+
path,
|
|
267
|
+
targetUrl,
|
|
268
|
+
stepCount: scenario.steps.length,
|
|
269
|
+
assertionCount: scenario.assertions.length,
|
|
270
|
+
savedAt: scenario.savedAt,
|
|
271
|
+
savedAtCommit: scenario.savedAtCommit,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function blank(sessionId, name) {
|
|
276
|
+
return { sessionId, name, path: "", targetUrl: "", stepCount: 0, assertionCount: 0, savedAt: "", savedAtCommit: "" };
|
|
277
|
+
}
|
|
278
|
+
export const browserSaveScenarioTool = {
|
|
279
|
+
name: "browser_save_scenario",
|
|
280
|
+
title: "Save an explored session as a scenario",
|
|
281
|
+
description: "Turn a session you have explored into a durable, repeatable scenario — a committed file under " +
|
|
282
|
+
"descry/scenarios/ that browser_run_scenario replays and a pre-push check can run with no agent in the " +
|
|
283
|
+
"loop. Steps name their elements by role, name and selector together, never by a snapshot ref, because a " +
|
|
284
|
+
"ref means nothing in a later session. Assertions are required and may be empty; an empty one is stored " +
|
|
285
|
+
"honestly and replays as unverified rather than passed. The file is validated before it is written, so " +
|
|
286
|
+
"a scenario that could not be read back is refused now rather than during a check somebody is waiting on.",
|
|
287
|
+
class: "action",
|
|
288
|
+
tier: "evidence",
|
|
289
|
+
version: "1.0.0",
|
|
290
|
+
inputSchema: SCHEMA,
|
|
291
|
+
run,
|
|
292
|
+
// Undefined, for the reason the verbs have and one of its own: this call
|
|
293
|
+
// writes into the repository the developer already pointed Descry at, in a
|
|
294
|
+
// path they name, and the session it records was confirmed at
|
|
295
|
+
// browser_start_session.
|
|
296
|
+
describeAction: () => undefined,
|
|
297
|
+
};
|
|
298
|
+
//# sourceMappingURL=browser-save-scenario.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-save-scenario.js","sourceRoot":"","sources":["../../src/tools/browser-save-scenario.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `browser_snapshot` — what is on the page, as refs an action can take.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this tool implements `fitToBudget` from its first commit
|
|
5
|
+
*
|
|
6
|
+
* A real accessibility tree is tens of thousands of bytes. `get_git_diff`
|
|
7
|
+
* shipped without an aggregate reply ceiling, returned 228,860 characters on
|
|
8
|
+
* an ordinary branch diff, and was rejected by the host before the caller saw
|
|
9
|
+
* any of it; the fix that followed bounded patch text and still failed at
|
|
10
|
+
* 84,516, because a reply is not only patch text. That lesson was paid for
|
|
11
|
+
* twice, and a brand-new tool whose payload grows with the user's application
|
|
12
|
+
* is precisely where it would be paid for a third time.
|
|
13
|
+
*
|
|
14
|
+
* So the budget hook is here from day one, and the test asserts the
|
|
15
|
+
* serialized `CallToolResult` rather than asserting that a cap ran.
|
|
16
|
+
*
|
|
17
|
+
* ## `totalElements` is never the capped number
|
|
18
|
+
*
|
|
19
|
+
* A snapshot that quietly returned the first forty elements would teach a
|
|
20
|
+
* calling agent that the page has forty elements. It would then conclude the
|
|
21
|
+
* button it needs is absent, which is a wrong answer produced by a correct
|
|
22
|
+
* tool. So the uncapped count is always present and always the real one, and
|
|
23
|
+
* a shortened reply says so in a note.
|
|
24
|
+
*/
|
|
25
|
+
import type { BrowserEvidenceReport } from "../browser/evidence.ts";
|
|
26
|
+
import { type BrowserGraphWrite } from "../browser/graph-write.ts";
|
|
27
|
+
import { type ToolDefinition } from "./kit.ts";
|
|
28
|
+
import type { ReplyFit } from "../render.ts";
|
|
29
|
+
import type { SnapshotElement } from "../browser/driver.ts";
|
|
30
|
+
export interface BrowserPageData {
|
|
31
|
+
readonly sessionId: string;
|
|
32
|
+
readonly url: string;
|
|
33
|
+
readonly title: string;
|
|
34
|
+
readonly elements: readonly SnapshotElement[];
|
|
35
|
+
/**
|
|
36
|
+
* How many elements the page has. **Never the number in `elements`** when
|
|
37
|
+
* the two differ — that is the whole point of carrying it.
|
|
38
|
+
*/
|
|
39
|
+
readonly totalElements: number;
|
|
40
|
+
readonly evidence: BrowserEvidenceReport;
|
|
41
|
+
/** Graph nodes this page's own requests reached, and any R4 edges witnessed. §5.3's "any graph node reached, surfaced in the same reply". */
|
|
42
|
+
readonly graph: BrowserGraphWrite;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* What `browser_snapshot` returns. An alias of the shared page shape, so
|
|
46
|
+
* `browser_navigate` — which answers the same question — cannot drift from it.
|
|
47
|
+
*/
|
|
48
|
+
export type BrowserSnapshotData = BrowserPageData;
|
|
49
|
+
/**
|
|
50
|
+
* Shrink by dropping elements from the end of the window, never by dropping
|
|
51
|
+
* the count or the evidence.
|
|
52
|
+
*
|
|
53
|
+
* Evidence stays because it is the part a caller cannot re-request by paging —
|
|
54
|
+
* the observations were drained when this reply was built, and a second
|
|
55
|
+
* snapshot would return a different set. Elements can always be asked for
|
|
56
|
+
* again with `startAt`, so they are what gives way.
|
|
57
|
+
*/
|
|
58
|
+
export declare function fitSnapshot(data: BrowserSnapshotData, budgetBytes: number): ReplyFit<BrowserSnapshotData>;
|
|
59
|
+
export declare const browserSnapshotTool: ToolDefinition;
|
|
60
|
+
//# sourceMappingURL=browser-snapshot.d.ts.map
|