pi-agent-browser-native 0.2.32 → 0.2.33
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/CHANGELOG.md +17 -0
- package/README.md +27 -16
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/COMMAND_REFERENCE.md +18 -10
- package/docs/ELECTRON.md +23 -4
- package/docs/RELEASE.md +4 -2
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +28 -16
- package/docs/TOOL_CONTRACT.md +29 -24
- package/extensions/agent-browser/index.ts +404 -4371
- package/extensions/agent-browser/lib/input-modes/electron.ts +170 -0
- package/extensions/agent-browser/lib/input-modes/job.ts +203 -0
- package/extensions/agent-browser/lib/input-modes/lookups.ts +447 -0
- package/extensions/agent-browser/lib/input-modes/params.ts +188 -0
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +107 -0
- package/extensions/agent-browser/lib/input-modes/shared.ts +46 -0
- package/extensions/agent-browser/lib/input-modes/types.ts +221 -0
- package/extensions/agent-browser/lib/input-modes.ts +41 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +696 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +450 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +46 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +711 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +386 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +868 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +476 -0
- package/extensions/agent-browser/lib/orchestration/browser-run.ts +1 -0
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +338 -0
- package/extensions/agent-browser/lib/playbook.ts +12 -11
- package/extensions/agent-browser/lib/process.ts +106 -4
- package/extensions/agent-browser/lib/results/action-recommendations.ts +269 -0
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +114 -0
- package/extensions/agent-browser/lib/results/artifact-state.ts +13 -0
- package/extensions/agent-browser/lib/results/categories.ts +106 -0
- package/extensions/agent-browser/lib/results/contracts.ts +220 -0
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +72 -0
- package/extensions/agent-browser/lib/results/envelope.ts +2 -1
- package/extensions/agent-browser/lib/results/network.ts +64 -0
- package/extensions/agent-browser/lib/results/next-actions.ts +117 -0
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +506 -0
- package/extensions/agent-browser/lib/results/presentation/batch.ts +355 -0
- package/extensions/agent-browser/lib/results/presentation/common.ts +53 -0
- package/extensions/agent-browser/lib/results/presentation/content.ts +36 -0
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +730 -0
- package/extensions/agent-browser/lib/results/presentation/errors.ts +125 -0
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +182 -0
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +216 -0
- package/extensions/agent-browser/lib/results/presentation/registry.ts +154 -0
- package/extensions/agent-browser/lib/results/presentation/skills.ts +143 -0
- package/extensions/agent-browser/lib/results/presentation.ts +87 -2399
- package/extensions/agent-browser/lib/results/recovery-actions.ts +139 -0
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +71 -0
- package/extensions/agent-browser/lib/results/selector-recovery.ts +312 -0
- package/extensions/agent-browser/lib/results/shared.ts +17 -789
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +262 -0
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +100 -0
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +366 -0
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +63 -0
- package/extensions/agent-browser/lib/results/snapshot.ts +37 -489
- package/extensions/agent-browser/lib/results/text.ts +40 -0
- package/extensions/agent-browser/lib/results.ts +16 -5
- package/extensions/agent-browser/lib/session-page-state.ts +486 -0
- package/package.json +2 -1
|
@@ -7,158 +7,45 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { isRecord } from "../parsing.js";
|
|
10
|
+
import type { PersistentSessionArtifactStore } from "../temp.js";
|
|
11
|
+
import type {
|
|
12
|
+
SessionArtifactManifest,
|
|
13
|
+
ToolPresentation,
|
|
14
|
+
} from "./contracts.js";
|
|
15
|
+
import { isHighValueControlEntry, selectHighValueControlEntries } from "./snapshot-high-value-controls.js";
|
|
10
16
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
buildFallbackSnapshotOutline,
|
|
18
|
+
buildRefLineOrderMap,
|
|
19
|
+
buildSegmentPreview,
|
|
20
|
+
buildSnapshotSegments,
|
|
21
|
+
canUseStructuredSnapshotPreview,
|
|
22
|
+
chooseAdditionalSegments,
|
|
23
|
+
choosePrimarySegment,
|
|
24
|
+
getMeaningfulSegmentLines,
|
|
25
|
+
getSnapshotRolePriority,
|
|
26
|
+
isChromeSectionName,
|
|
27
|
+
isNoiseName,
|
|
28
|
+
parseSnapshotLines,
|
|
29
|
+
} from "./snapshot-segments.js";
|
|
30
|
+
import { applySnapshotArtifactManifest, writeSnapshotSpillFile, type SnapshotSpillWriteResult } from "./snapshot-spill.js";
|
|
16
31
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
formatSessionArtifactRetentionSummary,
|
|
24
|
-
mergeSessionArtifactManifest,
|
|
25
|
-
normalizeWhitespace,
|
|
26
|
-
truncateText,
|
|
27
|
-
} from "./shared.js";
|
|
32
|
+
enrichSnapshotRefEntries,
|
|
33
|
+
getSnapshotRefEntries,
|
|
34
|
+
type SnapshotLineRefInfo,
|
|
35
|
+
type SnapshotRefEntry,
|
|
36
|
+
} from "./snapshot-refs.js";
|
|
37
|
+
import { compareRefIds, countLines, truncateText } from "./text.js";
|
|
28
38
|
|
|
29
39
|
const SNAPSHOT_INLINE_MAX_CHARS = 6_000;
|
|
30
40
|
const SNAPSHOT_INLINE_MAX_LINES = 80;
|
|
31
41
|
const SNAPSHOT_INLINE_MAX_REFS = 60;
|
|
32
42
|
const SNAPSHOT_PRIMARY_PREVIEW_LINES = 8;
|
|
33
43
|
const SNAPSHOT_SECTION_PREVIEW_LINES = 2;
|
|
34
|
-
const SNAPSHOT_MAX_ADDITIONAL_SECTIONS = 2;
|
|
35
44
|
const SNAPSHOT_KEY_REF_MAX_LINES = 8;
|
|
36
45
|
const SNAPSHOT_OTHER_REF_MAX_LINES = 4;
|
|
37
46
|
const SNAPSHOT_HIGH_VALUE_REF_MAX_LINES = 10;
|
|
38
47
|
const SNAPSHOT_ROLE_COUNT_MAX_ENTRIES = 4;
|
|
39
|
-
const SNAPSHOT_FALLBACK_PREVIEW_MAX_LINES = 12;
|
|
40
48
|
const SNAPSHOT_NAME_MAX_CHARS = 96;
|
|
41
|
-
const SNAPSHOT_LINE_MAX_CHARS = 140;
|
|
42
|
-
const SNAPSHOT_SPILL_FILE_PREFIX = "pi-agent-browser-snapshot";
|
|
43
|
-
const SNAPSHOT_SIGNAL_ROLES = new Set([
|
|
44
|
-
"article",
|
|
45
|
-
"banner",
|
|
46
|
-
"button",
|
|
47
|
-
"checkbox",
|
|
48
|
-
"combobox",
|
|
49
|
-
"dialog",
|
|
50
|
-
"gridcell",
|
|
51
|
-
"heading",
|
|
52
|
-
"link",
|
|
53
|
-
"listitem",
|
|
54
|
-
"main",
|
|
55
|
-
"menu",
|
|
56
|
-
"menuitem",
|
|
57
|
-
"navigation",
|
|
58
|
-
"option",
|
|
59
|
-
"radio",
|
|
60
|
-
"region",
|
|
61
|
-
"row",
|
|
62
|
-
"searchbox",
|
|
63
|
-
"tab",
|
|
64
|
-
"textbox",
|
|
65
|
-
]);
|
|
66
|
-
const SNAPSHOT_SEGMENT_ROOT_ROLES = new Set(["article", "dialog", "heading", "main", "menu", "region"]);
|
|
67
|
-
const SNAPSHOT_ROLE_PRIORITY: Record<string, number> = {
|
|
68
|
-
article: 0,
|
|
69
|
-
main: 1,
|
|
70
|
-
dialog: 2,
|
|
71
|
-
menu: 3,
|
|
72
|
-
region: 4,
|
|
73
|
-
heading: 5,
|
|
74
|
-
searchbox: 6,
|
|
75
|
-
textbox: 7,
|
|
76
|
-
combobox: 8,
|
|
77
|
-
button: 9,
|
|
78
|
-
checkbox: 10,
|
|
79
|
-
radio: 11,
|
|
80
|
-
tab: 12,
|
|
81
|
-
option: 13,
|
|
82
|
-
link: 14,
|
|
83
|
-
listitem: 14,
|
|
84
|
-
row: 15,
|
|
85
|
-
gridcell: 16,
|
|
86
|
-
navigation: 17,
|
|
87
|
-
generic: 99,
|
|
88
|
-
unknown: 100,
|
|
89
|
-
};
|
|
90
|
-
const SNAPSHOT_NOISE_NAME_PATTERNS = [
|
|
91
|
-
/^skip to /i,
|
|
92
|
-
/^ad$/i,
|
|
93
|
-
/^don't want to see ads\??$/i,
|
|
94
|
-
/keyboard shortcuts/i,
|
|
95
|
-
/\bpromoted\b/i,
|
|
96
|
-
/\bsponsored\b/i,
|
|
97
|
-
];
|
|
98
|
-
const SNAPSHOT_CHROME_SECTION_PATTERNS = [
|
|
99
|
-
/^primary$/i,
|
|
100
|
-
/^footer$/i,
|
|
101
|
-
/^navigation$/i,
|
|
102
|
-
/\bwhat['’]?s happening\b/i,
|
|
103
|
-
/\brelevant people\b/i,
|
|
104
|
-
/\btrending\b/i,
|
|
105
|
-
/\brelated\b/i,
|
|
106
|
-
/\brecommended\b/i,
|
|
107
|
-
/\bsuggested\b/i,
|
|
108
|
-
];
|
|
109
|
-
const SNAPSHOT_HIGH_VALUE_CONTROL_ROLES = new Set([
|
|
110
|
-
"button",
|
|
111
|
-
"checkbox",
|
|
112
|
-
"combobox",
|
|
113
|
-
"menuitem",
|
|
114
|
-
"option",
|
|
115
|
-
"radio",
|
|
116
|
-
"searchbox",
|
|
117
|
-
"tab",
|
|
118
|
-
"textbox",
|
|
119
|
-
]);
|
|
120
|
-
const SNAPSHOT_HIGH_VALUE_CONTROL_ROLE_PRIORITY: Record<string, number> = {
|
|
121
|
-
searchbox: 0,
|
|
122
|
-
textbox: 1,
|
|
123
|
-
combobox: 2,
|
|
124
|
-
button: 3,
|
|
125
|
-
tab: 4,
|
|
126
|
-
checkbox: 5,
|
|
127
|
-
radio: 6,
|
|
128
|
-
option: 7,
|
|
129
|
-
menuitem: 8,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
interface SnapshotRefEntry {
|
|
133
|
-
id: string;
|
|
134
|
-
name: string;
|
|
135
|
-
role: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface SnapshotLine {
|
|
139
|
-
depth: number;
|
|
140
|
-
headingLevel?: number;
|
|
141
|
-
index: number;
|
|
142
|
-
name: string;
|
|
143
|
-
raw: string;
|
|
144
|
-
ref?: string;
|
|
145
|
-
role: string;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
interface SnapshotSegment {
|
|
149
|
-
endIndexExclusive: number;
|
|
150
|
-
lines: SnapshotLine[];
|
|
151
|
-
root: SnapshotLine;
|
|
152
|
-
score: number;
|
|
153
|
-
startIndex: number;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
interface SnapshotPreview {
|
|
157
|
-
omittedCount: number;
|
|
158
|
-
refIds: string[];
|
|
159
|
-
lines: string[];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
49
|
function getSnapshotText(data: Record<string, unknown>): string | undefined {
|
|
163
50
|
return typeof data.snapshot === "string" ? data.snapshot : undefined;
|
|
164
51
|
}
|
|
@@ -167,32 +54,6 @@ function getSnapshotOrigin(data: Record<string, unknown>): string {
|
|
|
167
54
|
return typeof data.origin === "string" ? data.origin : "(unknown origin)";
|
|
168
55
|
}
|
|
169
56
|
|
|
170
|
-
function formatPreviewLine(line: SnapshotLine, baseDepth: number): string {
|
|
171
|
-
const leadingWhitespace = (line.raw.match(/^\s*/) ?? [""])[0].length;
|
|
172
|
-
const stripChars = Math.min(leadingWhitespace, Math.max(0, baseDepth) * 2);
|
|
173
|
-
return truncateText(line.raw.slice(stripChars), SNAPSHOT_LINE_MAX_CHARS);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function getRolePriority(role: string): number {
|
|
177
|
-
return SNAPSHOT_ROLE_PRIORITY[role] ?? 50;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function getSnapshotRefEntries(data: Record<string, unknown>): SnapshotRefEntry[] {
|
|
181
|
-
const refs = isRecord(data.refs) ? data.refs : undefined;
|
|
182
|
-
if (!refs) return [];
|
|
183
|
-
|
|
184
|
-
return Object.entries(refs)
|
|
185
|
-
.map(([id, value]) => {
|
|
186
|
-
if (!isRecord(value)) {
|
|
187
|
-
return { id, name: "", role: "unknown" } satisfies SnapshotRefEntry;
|
|
188
|
-
}
|
|
189
|
-
const name = typeof value.name === "string" ? normalizeWhitespace(value.name) : "";
|
|
190
|
-
const role = typeof value.role === "string" && value.role.length > 0 ? value.role : "unknown";
|
|
191
|
-
return { id, name, role } satisfies SnapshotRefEntry;
|
|
192
|
-
})
|
|
193
|
-
.sort((a, b) => compareRefIds(a.id, b.id));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
57
|
function getSnapshotRoleCounts(refEntries: SnapshotRefEntry[]): Record<string, number> {
|
|
197
58
|
const counts: Record<string, number> = {};
|
|
198
59
|
for (const entry of refEntries) {
|
|
@@ -207,7 +68,7 @@ function formatRoleCounts(roleCounts: Record<string, number>): string | undefine
|
|
|
207
68
|
|
|
208
69
|
const ordered = entries.sort((left, right) => {
|
|
209
70
|
if (right[1] !== left[1]) return right[1] - left[1];
|
|
210
|
-
return
|
|
71
|
+
return getSnapshotRolePriority(left[0]) - getSnapshotRolePriority(right[0]);
|
|
211
72
|
});
|
|
212
73
|
const visibleEntries = ordered.slice(0, SNAPSHOT_ROLE_COUNT_MAX_ENTRIES).map(([role, count]) => `${role} ${count}`);
|
|
213
74
|
const omittedEntries = Math.max(0, ordered.length - visibleEntries.length);
|
|
@@ -217,241 +78,6 @@ function formatRoleCounts(roleCounts: Record<string, number>): string | undefine
|
|
|
217
78
|
return visibleEntries.join(", ");
|
|
218
79
|
}
|
|
219
80
|
|
|
220
|
-
function parseSnapshotLines(snapshot: string): SnapshotLine[] {
|
|
221
|
-
return snapshot
|
|
222
|
-
.split("\n")
|
|
223
|
-
.filter((line) => line.length > 0)
|
|
224
|
-
.map((raw, index) => {
|
|
225
|
-
const trimmed = raw.trimStart();
|
|
226
|
-
const depth = Math.floor(((raw.match(/^\s*/) ?? [""])[0].length ?? 0) / 2);
|
|
227
|
-
const role = trimmed.match(/^[-*]\s+([^\s"]+)/)?.[1] ?? "unknown";
|
|
228
|
-
const name = normalizeWhitespace(trimmed.match(/"([^"]*)"/)?.[1] ?? "");
|
|
229
|
-
const ref = trimmed.match(/\bref=([^,\]\s]+)/)?.[1];
|
|
230
|
-
const headingLevel = trimmed.match(/\blevel=(\d+)/)?.[1];
|
|
231
|
-
return {
|
|
232
|
-
depth,
|
|
233
|
-
headingLevel: headingLevel ? Number(headingLevel) : undefined,
|
|
234
|
-
index,
|
|
235
|
-
name,
|
|
236
|
-
raw,
|
|
237
|
-
ref,
|
|
238
|
-
role,
|
|
239
|
-
} satisfies SnapshotLine;
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function isNoiseName(name: string): boolean {
|
|
244
|
-
return SNAPSHOT_NOISE_NAME_PATTERNS.some((pattern) => pattern.test(name));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function isChromeSectionName(name: string): boolean {
|
|
248
|
-
return SNAPSHOT_CHROME_SECTION_PATTERNS.some((pattern) => pattern.test(name));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function isNoiseSnapshotLine(line: SnapshotLine): boolean {
|
|
252
|
-
if (line.name.length > 0 && isNoiseName(line.name)) return true;
|
|
253
|
-
const loweredRaw = line.raw.toLowerCase();
|
|
254
|
-
return loweredRaw.includes("promoted") || loweredRaw.includes("sponsored");
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function isPotentialSegmentRootLine(line: SnapshotLine): boolean {
|
|
258
|
-
if (!SNAPSHOT_SEGMENT_ROOT_ROLES.has(line.role)) return false;
|
|
259
|
-
if (isNoiseSnapshotLine(line)) return false;
|
|
260
|
-
if (line.role === "heading") {
|
|
261
|
-
return line.name.length > 0 && (line.headingLevel ?? 99) <= 3;
|
|
262
|
-
}
|
|
263
|
-
if (line.role === "region") {
|
|
264
|
-
return line.name.length > 0;
|
|
265
|
-
}
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function scoreSegment(segment: SnapshotSegment): number {
|
|
270
|
-
const { root } = segment;
|
|
271
|
-
const distinctRefs = new Set(segment.lines.flatMap((line) => (line.ref ? [line.ref] : []))).size;
|
|
272
|
-
let score = 0;
|
|
273
|
-
|
|
274
|
-
score += 120 - getRolePriority(root.role) * 8;
|
|
275
|
-
score += Math.min(distinctRefs, 16);
|
|
276
|
-
score += Math.min(segment.lines.length, 12);
|
|
277
|
-
score -= Math.min(root.index, 60) / 3;
|
|
278
|
-
score -= root.depth * 6;
|
|
279
|
-
|
|
280
|
-
if (root.role === "heading") {
|
|
281
|
-
if (root.headingLevel === 1) score += 40;
|
|
282
|
-
else if (root.headingLevel === 2) score += 22;
|
|
283
|
-
else if (root.headingLevel === 3) score += 12;
|
|
284
|
-
}
|
|
285
|
-
if (root.name.length > 0) score += 10;
|
|
286
|
-
if (root.name.length <= 2) score -= 18;
|
|
287
|
-
if (isChromeSectionName(root.name)) score -= 45;
|
|
288
|
-
if (isNoiseName(root.name)) score -= 1000;
|
|
289
|
-
return score;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function buildSnapshotSegments(snapshotLines: SnapshotLine[]): SnapshotSegment[] {
|
|
293
|
-
const roots: SnapshotLine[] = [];
|
|
294
|
-
const stack: SnapshotLine[] = [];
|
|
295
|
-
|
|
296
|
-
for (const line of snapshotLines) {
|
|
297
|
-
stack.length = line.depth;
|
|
298
|
-
if (isPotentialSegmentRootLine(line)) {
|
|
299
|
-
const normalizedName = normalizeWhitespace(line.name.toLowerCase());
|
|
300
|
-
let duplicateAncestor: SnapshotLine | undefined;
|
|
301
|
-
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
302
|
-
const ancestor = stack[index];
|
|
303
|
-
if (
|
|
304
|
-
normalizedName.length > 0 &&
|
|
305
|
-
normalizeWhitespace(ancestor.name.toLowerCase()) === normalizedName &&
|
|
306
|
-
SNAPSHOT_SEGMENT_ROOT_ROLES.has(ancestor.role)
|
|
307
|
-
) {
|
|
308
|
-
duplicateAncestor = ancestor;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (!duplicateAncestor) {
|
|
313
|
-
roots.push(line);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
stack[line.depth] = line;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return roots.map((root, index) => {
|
|
320
|
-
let endIndexExclusive = snapshotLines.length;
|
|
321
|
-
for (let nextIndex = index + 1; nextIndex < roots.length; nextIndex += 1) {
|
|
322
|
-
const candidate = roots[nextIndex];
|
|
323
|
-
if (candidate.depth <= root.depth) {
|
|
324
|
-
endIndexExclusive = candidate.index;
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
const lines = snapshotLines.slice(root.index, endIndexExclusive);
|
|
329
|
-
const segment: SnapshotSegment = {
|
|
330
|
-
endIndexExclusive,
|
|
331
|
-
lines,
|
|
332
|
-
root,
|
|
333
|
-
score: 0,
|
|
334
|
-
startIndex: root.index,
|
|
335
|
-
};
|
|
336
|
-
segment.score = scoreSegment(segment);
|
|
337
|
-
return segment;
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function choosePrimarySegment(segments: SnapshotSegment[]): SnapshotSegment | undefined {
|
|
342
|
-
if (segments.length === 0) return undefined;
|
|
343
|
-
return (
|
|
344
|
-
segments.find((segment) => segment.root.role === "main" || segment.root.role === "article") ??
|
|
345
|
-
segments.find((segment) => segment.root.role === "heading" && segment.root.headingLevel === 1) ??
|
|
346
|
-
segments.find((segment) => segment.score >= 90) ??
|
|
347
|
-
[...segments].sort((left, right) => right.score - left.score || left.startIndex - right.startIndex)[0]
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function chooseAdditionalSegments(segments: SnapshotSegment[], primary: SnapshotSegment | undefined): SnapshotSegment[] {
|
|
352
|
-
if (!primary) return [];
|
|
353
|
-
|
|
354
|
-
const seenNames = new Set<string>([normalizeWhitespace(primary.root.name.toLowerCase())]);
|
|
355
|
-
const rankedCandidates = segments
|
|
356
|
-
.filter((segment) => segment !== primary && segment.score >= 45)
|
|
357
|
-
.sort((left, right) => {
|
|
358
|
-
const leftDistance = Math.abs(left.startIndex - primary.startIndex);
|
|
359
|
-
const rightDistance = Math.abs(right.startIndex - primary.startIndex);
|
|
360
|
-
if (leftDistance !== rightDistance) return leftDistance - rightDistance;
|
|
361
|
-
if (right.score !== left.score) return right.score - left.score;
|
|
362
|
-
return left.startIndex - right.startIndex;
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
const chosen: SnapshotSegment[] = [];
|
|
366
|
-
for (const segment of rankedCandidates) {
|
|
367
|
-
if (chosen.length >= SNAPSHOT_MAX_ADDITIONAL_SECTIONS) break;
|
|
368
|
-
if (isChromeSectionName(segment.root.name)) continue;
|
|
369
|
-
if (segment.root.role === "heading" && segment.root.name.length <= 2) continue;
|
|
370
|
-
const nameKey = normalizeWhitespace(segment.root.name.toLowerCase());
|
|
371
|
-
if (nameKey && seenNames.has(nameKey)) continue;
|
|
372
|
-
chosen.push(segment);
|
|
373
|
-
if (nameKey) seenNames.add(nameKey);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return chosen.sort((left, right) => left.startIndex - right.startIndex);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function getMeaningfulSegmentLines(segment: SnapshotSegment): SnapshotLine[] {
|
|
380
|
-
return segment.lines.filter((line) => {
|
|
381
|
-
if (isNoiseSnapshotLine(line)) return false;
|
|
382
|
-
if (line.role === "generic" && !line.ref && line.name.length === 0) return false;
|
|
383
|
-
if (line.role === "link" && line.name.length === 0) return false;
|
|
384
|
-
return true;
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function buildSegmentPreview(segment: SnapshotSegment, maxLines: number): SnapshotPreview {
|
|
389
|
-
const meaningfulLines = getMeaningfulSegmentLines(segment);
|
|
390
|
-
if (meaningfulLines.length === 0) {
|
|
391
|
-
return { omittedCount: 0, refIds: [], lines: [] };
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const previewLines: SnapshotLine[] = [];
|
|
395
|
-
const previewRefIds = new Set<string>();
|
|
396
|
-
const seenPreviewKeys = new Set<string>();
|
|
397
|
-
const rootDepth = segment.root.depth;
|
|
398
|
-
|
|
399
|
-
for (const line of meaningfulLines) {
|
|
400
|
-
if (previewLines.length >= maxLines) break;
|
|
401
|
-
if (line !== segment.root) {
|
|
402
|
-
const relativeDepth = line.depth - rootDepth;
|
|
403
|
-
if (segment.root.role !== "heading" && relativeDepth > 2) continue;
|
|
404
|
-
if (segment.root.name.length > 0 && line.name === segment.root.name && (line.role === "heading" || line.role === "link")) {
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const key = `${line.role}:${line.name}:${line.ref ?? ""}:${line.depth}`;
|
|
410
|
-
if (seenPreviewKeys.has(key)) continue;
|
|
411
|
-
seenPreviewKeys.add(key);
|
|
412
|
-
previewLines.push(line);
|
|
413
|
-
if (line.ref) previewRefIds.add(line.ref);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
omittedCount: Math.max(0, meaningfulLines.length - previewLines.length),
|
|
418
|
-
refIds: [...previewRefIds],
|
|
419
|
-
lines: previewLines.map((line) => formatPreviewLine(line, rootDepth)),
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function buildFallbackSnapshotOutline(snapshotLines: SnapshotLine[]): SnapshotPreview {
|
|
424
|
-
const selected = new Set<number>();
|
|
425
|
-
for (let index = 0; index < snapshotLines.length && selected.size < 4; index += 1) {
|
|
426
|
-
if (!isNoiseSnapshotLine(snapshotLines[index])) selected.add(index);
|
|
427
|
-
}
|
|
428
|
-
for (let index = 0; index < snapshotLines.length && selected.size < SNAPSHOT_FALLBACK_PREVIEW_MAX_LINES; index += 1) {
|
|
429
|
-
const line = snapshotLines[index];
|
|
430
|
-
if (isNoiseSnapshotLine(line)) continue;
|
|
431
|
-
if (SNAPSHOT_SIGNAL_ROLES.has(line.role) || line.ref || line.name.length > 0) {
|
|
432
|
-
selected.add(index);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
const chosenLines = [...selected]
|
|
436
|
-
.sort((left, right) => left - right)
|
|
437
|
-
.slice(0, SNAPSHOT_FALLBACK_PREVIEW_MAX_LINES)
|
|
438
|
-
.map((index) => snapshotLines[index]);
|
|
439
|
-
return {
|
|
440
|
-
omittedCount: Math.max(0, snapshotLines.length - chosenLines.length),
|
|
441
|
-
refIds: chosenLines.flatMap((line) => (line.ref ? [line.ref] : [])),
|
|
442
|
-
lines: chosenLines.map((line) => truncateText(line.raw, SNAPSHOT_LINE_MAX_CHARS)),
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function buildRefLineOrderMap(snapshotLines: SnapshotLine[]): Map<string, number> {
|
|
447
|
-
const map = new Map<string, number>();
|
|
448
|
-
for (const line of snapshotLines) {
|
|
449
|
-
if (!line.ref || map.has(line.ref)) continue;
|
|
450
|
-
map.set(line.ref, line.index);
|
|
451
|
-
}
|
|
452
|
-
return map;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
81
|
function rankRefEntries(
|
|
456
82
|
refEntries: SnapshotRefEntry[],
|
|
457
83
|
previewRefIds: Set<string>,
|
|
@@ -463,7 +89,7 @@ function rankRefEntries(
|
|
|
463
89
|
const rightBucket = previewRefIds.has(right.id) ? 0 : focusRefIds.has(right.id) ? 1 : 2;
|
|
464
90
|
if (leftBucket !== rightBucket) return leftBucket - rightBucket;
|
|
465
91
|
|
|
466
|
-
const rolePriority =
|
|
92
|
+
const rolePriority = getSnapshotRolePriority(left.role) - getSnapshotRolePriority(right.role);
|
|
467
93
|
if (rolePriority !== 0) return rolePriority;
|
|
468
94
|
|
|
469
95
|
const leftHasName = left.name.length > 0 ? 0 : 1;
|
|
@@ -483,25 +109,6 @@ function formatCompactRef(entry: SnapshotRefEntry): string {
|
|
|
483
109
|
return `- ${entry.id} ${entry.role}${suffix}`;
|
|
484
110
|
}
|
|
485
111
|
|
|
486
|
-
function isHighValueControlRef(entry: SnapshotRefEntry): boolean {
|
|
487
|
-
if (!SNAPSHOT_HIGH_VALUE_CONTROL_ROLES.has(entry.role)) return false;
|
|
488
|
-
if (isNoiseName(entry.name) || isChromeSectionName(entry.name)) return false;
|
|
489
|
-
return entry.name.length > 0 || entry.role === "searchbox" || entry.role === "textbox" || entry.role === "combobox";
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function rankHighValueControlRefs(left: SnapshotRefEntry, right: SnapshotRefEntry): number {
|
|
493
|
-
const rolePriority =
|
|
494
|
-
(SNAPSHOT_HIGH_VALUE_CONTROL_ROLE_PRIORITY[left.role] ?? 50) -
|
|
495
|
-
(SNAPSHOT_HIGH_VALUE_CONTROL_ROLE_PRIORITY[right.role] ?? 50);
|
|
496
|
-
if (rolePriority !== 0) return rolePriority;
|
|
497
|
-
|
|
498
|
-
const leftHasName = left.name.length > 0 ? 0 : 1;
|
|
499
|
-
const rightHasName = right.name.length > 0 ? 0 : 1;
|
|
500
|
-
if (leftHasName !== rightHasName) return leftHasName - rightHasName;
|
|
501
|
-
|
|
502
|
-
return compareRefIds(left.id, right.id);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
112
|
function shouldCompactSnapshot(rawText: string, data: Record<string, unknown>): boolean {
|
|
506
113
|
const snapshot = getSnapshotText(data) ?? "";
|
|
507
114
|
const refEntries = getSnapshotRefEntries(data);
|
|
@@ -512,63 +119,6 @@ function shouldCompactSnapshot(rawText: string, data: Record<string, unknown>):
|
|
|
512
119
|
);
|
|
513
120
|
}
|
|
514
121
|
|
|
515
|
-
function canUseStructuredSnapshotPreview(snapshotLines: SnapshotLine[], refEntries: SnapshotRefEntry[]): boolean {
|
|
516
|
-
if (snapshotLines.length === 0) return false;
|
|
517
|
-
const linesWithRecognizedRoles = snapshotLines.filter((line) => line.role !== "unknown").length;
|
|
518
|
-
const linesWithNames = snapshotLines.filter((line) => line.name.length > 0).length;
|
|
519
|
-
const parsedRefIds = new Set(snapshotLines.flatMap((line) => (line.ref ? [line.ref] : [])));
|
|
520
|
-
return (
|
|
521
|
-
linesWithRecognizedRoles >= Math.min(snapshotLines.length, 3) ||
|
|
522
|
-
linesWithNames >= Math.min(snapshotLines.length, 3) ||
|
|
523
|
-
parsedRefIds.size >= Math.min(refEntries.length, 3)
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
interface SnapshotSpillWriteResult {
|
|
528
|
-
evictedArtifacts: PersistentSessionArtifactEviction[];
|
|
529
|
-
path: string;
|
|
530
|
-
storageScope: "persistent-session" | "process-temp";
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
async function writeSnapshotSpillFile(
|
|
534
|
-
data: Record<string, unknown>,
|
|
535
|
-
persistentArtifactStore: PersistentSessionArtifactStore | undefined,
|
|
536
|
-
): Promise<SnapshotSpillWriteResult> {
|
|
537
|
-
const options = {
|
|
538
|
-
content: JSON.stringify(data, null, 2),
|
|
539
|
-
prefix: SNAPSHOT_SPILL_FILE_PREFIX,
|
|
540
|
-
suffix: ".json",
|
|
541
|
-
};
|
|
542
|
-
if (persistentArtifactStore) {
|
|
543
|
-
const result = await writePersistentSessionArtifactFile({ ...options, store: persistentArtifactStore });
|
|
544
|
-
return { ...result, storageScope: "persistent-session" };
|
|
545
|
-
}
|
|
546
|
-
return { evictedArtifacts: [], path: await writeSecureTempFile(options), storageScope: "process-temp" };
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function applySnapshotArtifactManifest(options: {
|
|
550
|
-
baseManifest?: SessionArtifactManifest;
|
|
551
|
-
command?: string;
|
|
552
|
-
fullOutputPath?: string;
|
|
553
|
-
spill?: SnapshotSpillWriteResult;
|
|
554
|
-
}): { artifactManifest?: SessionArtifactManifest; artifactRetentionSummary?: string } {
|
|
555
|
-
if (!options.fullOutputPath || !options.spill) return {};
|
|
556
|
-
const nowMs = Date.now();
|
|
557
|
-
const entries: SessionArtifactManifestEntry[] = [
|
|
558
|
-
{
|
|
559
|
-
command: options.command,
|
|
560
|
-
createdAtMs: nowMs,
|
|
561
|
-
kind: "spill",
|
|
562
|
-
path: options.fullOutputPath,
|
|
563
|
-
retentionState: options.spill.storageScope === "persistent-session" ? "live" : "ephemeral",
|
|
564
|
-
storageScope: options.spill.storageScope,
|
|
565
|
-
},
|
|
566
|
-
...buildEvictedSessionArtifactEntries(options.spill.evictedArtifacts, nowMs),
|
|
567
|
-
];
|
|
568
|
-
const artifactManifest = mergeSessionArtifactManifest({ base: options.baseManifest, entries, nowMs });
|
|
569
|
-
return artifactManifest ? { artifactManifest, artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest) } : {};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
122
|
export function formatSnapshotSummary(data: Record<string, unknown>): string {
|
|
573
123
|
const origin = typeof data.origin === "string" ? data.origin : "page";
|
|
574
124
|
const refs = isRecord(data.refs) ? Object.keys(data.refs).length : 0;
|
|
@@ -610,11 +160,11 @@ export async function buildSnapshotPresentation(
|
|
|
610
160
|
spillErrorText = error instanceof Error ? error.message : String(error);
|
|
611
161
|
}
|
|
612
162
|
|
|
613
|
-
const refEntries = getSnapshotRefEntries(data);
|
|
614
|
-
const roleCounts = getSnapshotRoleCounts(refEntries);
|
|
615
|
-
const roleCountsText = formatRoleCounts(roleCounts);
|
|
616
163
|
const snapshot = getSnapshotText(data) ?? "(no interactive elements)";
|
|
617
164
|
const snapshotLines = parseSnapshotLines(snapshot);
|
|
165
|
+
const refEntries = enrichSnapshotRefEntries(getSnapshotRefEntries(data), snapshotLines);
|
|
166
|
+
const roleCounts = getSnapshotRoleCounts(refEntries);
|
|
167
|
+
const roleCountsText = formatRoleCounts(roleCounts);
|
|
618
168
|
const useStructuredPreview = canUseStructuredSnapshotPreview(snapshotLines, refEntries);
|
|
619
169
|
const snapshotSegments = useStructuredPreview ? buildSnapshotSegments(snapshotLines) : [];
|
|
620
170
|
const primarySegment = useStructuredPreview ? choosePrimarySegment(snapshotSegments) : undefined;
|
|
@@ -656,15 +206,13 @@ export async function buildSnapshotPresentation(
|
|
|
656
206
|
.filter((entry) => !keyRefIdSet.has(entry.id))
|
|
657
207
|
.slice(0, SNAPSHOT_OTHER_REF_MAX_LINES);
|
|
658
208
|
const displayedRefIdSet = new Set([...keyRefEntries, ...otherRefEntries].map((entry) => entry.id));
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
.
|
|
662
|
-
const visibleHighValueControlEntries = omittedHighValueControlEntries.slice(0, SNAPSHOT_HIGH_VALUE_REF_MAX_LINES);
|
|
663
|
-
const omittedHighValueControls = Math.max(0, omittedHighValueControlEntries.length - visibleHighValueControlEntries.length);
|
|
664
|
-
const omittedNonHighlightedRefs = Math.max(
|
|
665
|
-
0,
|
|
666
|
-
visibleRankedRefEntries.length - keyRefEntries.length - otherRefEntries.length - omittedHighValueControlEntries.length,
|
|
209
|
+
const omittedRefEntries = visibleRankedRefEntries.filter((entry) => !displayedRefIdSet.has(entry.id));
|
|
210
|
+
const highValueControlEntries = omittedRefEntries.filter(
|
|
211
|
+
(entry) => isHighValueControlEntry(entry) && !isNoiseName(entry.name) && !isChromeSectionName(entry.name),
|
|
667
212
|
);
|
|
213
|
+
const visibleHighValueControlEntries = selectHighValueControlEntries(highValueControlEntries, SNAPSHOT_HIGH_VALUE_REF_MAX_LINES);
|
|
214
|
+
const omittedHighValueControls = Math.max(0, highValueControlEntries.length - visibleHighValueControlEntries.length);
|
|
215
|
+
const omittedNonHighlightedRefs = Math.max(0, omittedRefEntries.length - highValueControlEntries.length);
|
|
668
216
|
const origin = getSnapshotOrigin(data);
|
|
669
217
|
|
|
670
218
|
const lines: string[] = [
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Hold tiny text and ref-id helpers shared by result renderers.
|
|
3
|
+
* Responsibilities: Convert unknown values to text, count lines, normalize whitespace, truncate labels, and sort ref ids naturally.
|
|
4
|
+
* Scope: Generic pure utilities only; no agent-browser command policy belongs here.
|
|
5
|
+
* Usage: Imported by envelope, presentation, snapshot, and diagnostic helpers.
|
|
6
|
+
* Invariants/Assumptions: Helpers are deterministic and side-effect free.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function stringifyUnknown(value: unknown): string {
|
|
10
|
+
if (typeof value === "string") return value;
|
|
11
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
12
|
+
if (value === null || value === undefined) return "";
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(value, null, 2);
|
|
15
|
+
} catch {
|
|
16
|
+
return String(value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function countLines(text: string): number {
|
|
21
|
+
return text.length === 0 ? 0 : text.split("\n").length;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function normalizeWhitespace(text: string): string {
|
|
25
|
+
return text.replace(/\s+/g, " ").trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function truncateText(text: string, maxChars: number): string {
|
|
29
|
+
if (text.length <= maxChars) return text;
|
|
30
|
+
return `${text.slice(0, Math.max(1, maxChars - 1))}…`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function compareRefIds(left: string, right: string): number {
|
|
34
|
+
const leftMatch = left.match(/^(?:[a-zA-Z]+)?(\d+)$/);
|
|
35
|
+
const rightMatch = right.match(/^(?:[a-zA-Z]+)?(\d+)$/);
|
|
36
|
+
if (leftMatch && rightMatch) {
|
|
37
|
+
return Number(leftMatch[1]) - Number(rightMatch[1]);
|
|
38
|
+
}
|
|
39
|
+
return left.localeCompare(right);
|
|
40
|
+
}
|
|
@@ -9,22 +9,33 @@
|
|
|
9
9
|
export { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "./results/envelope.js";
|
|
10
10
|
export { buildToolPresentation } from "./results/presentation.js";
|
|
11
11
|
export {
|
|
12
|
-
buildAgentBrowserNextActions,
|
|
13
12
|
buildAgentBrowserResultCategoryDetails,
|
|
14
13
|
classifyAgentBrowserFailureCategory,
|
|
15
14
|
classifyAgentBrowserSuccessCategory,
|
|
16
|
-
|
|
17
|
-
} from "./results/
|
|
15
|
+
} from "./results/categories.js";
|
|
16
|
+
export { buildAgentBrowserNextActions } from "./results/action-recommendations.js";
|
|
17
|
+
export { compareRefIds } from "./results/text.js";
|
|
18
|
+
export {
|
|
19
|
+
AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS,
|
|
20
|
+
AGENT_BROWSER_RICH_INPUT_RECOVERY_NEXT_ACTION_IDS,
|
|
21
|
+
getAgentBrowserRichInputRecoveryNextActionId,
|
|
22
|
+
getAgentBrowserRichInputRecoveryNextActionIds,
|
|
23
|
+
} from "./results/recovery-actions.js";
|
|
18
24
|
export type {
|
|
19
25
|
AgentBrowserBatchResult,
|
|
20
26
|
AgentBrowserEnvelope,
|
|
21
27
|
AgentBrowserFailureCategory,
|
|
22
|
-
AgentBrowserResultCategory,
|
|
23
28
|
AgentBrowserNextAction,
|
|
24
29
|
AgentBrowserPageChangeSummary,
|
|
30
|
+
AgentBrowserResultCategory,
|
|
25
31
|
AgentBrowserResultCategoryDetails,
|
|
26
32
|
AgentBrowserSuccessCategory,
|
|
27
33
|
FileArtifactKind,
|
|
28
34
|
FileArtifactMetadata,
|
|
29
35
|
ToolPresentation,
|
|
30
|
-
} from "./results/
|
|
36
|
+
} from "./results/contracts.js";
|
|
37
|
+
export type {
|
|
38
|
+
AgentBrowserRecoveryContext,
|
|
39
|
+
AgentBrowserRecoveryKind,
|
|
40
|
+
AgentBrowserRichInputRecoveryNextActionKind,
|
|
41
|
+
} from "./results/recovery-actions.js";
|