1
+(() => {
2
+ const GLOBAL_KEY = "__spaceBrowserPageContent__";
3
+ const DOM_HELPER_KEY = "__spaceBrowserDomHelper__";
4
+ const VERSION = "6";
5
+ const BLOCK_TAGS = new Set([
6
+ "ADDRESS",
7
+ "ARTICLE",
8
+ "ASIDE",
9
+ "BLOCKQUOTE",
10
+ "BODY",
11
+ "DETAILS",
12
+ "DIV",
13
+ "DL",
14
+ "FIELDSET",
15
+ "FIGCAPTION",
16
+ "FIGURE",
17
+ "FOOTER",
18
+ "FORM",
19
+ "H1",
20
+ "H2",
21
+ "H3",
22
+ "H4",
23
+ "H5",
24
+ "H6",
25
+ "HEADER",
26
+ "HR",
27
+ "HTML",
28
+ "LI",
29
+ "MAIN",
30
+ "NAV",
31
+ "OL",
32
+ "P",
33
+ "PRE",
34
+ "SECTION",
35
+ "TABLE",
36
+ "TBODY",
37
+ "TD",
38
+ "TFOOT",
39
+ "TH",
40
+ "THEAD",
41
+ "TR",
42
+ "UL"
43
+ ]);
44
+ const SKIP_TAGS = new Set([
45
+ "HEAD",
46
+ "LINK",
47
+ "META",
48
+ "NOSCRIPT",
49
+ "SCRIPT",
50
+ "STYLE",
51
+ "TEMPLATE"
52
+ ]);
53
+ const INTERACTIVE_ROLES = new Set([
54
+ "button",
55
+ "checkbox",
56
+ "combobox",
57
+ "link",
58
+ "menuitem",
59
+ "menuitemcheckbox",
60
+ "menuitemradio",
61
+ "option",
62
+ "radio",
63
+ "searchbox",
64
+ "slider",
65
+ "spinbutton",
66
+ "switch",
67
+ "tab",
68
+ "textbox"
69
+ ]);
70
+ const INTERACTIVE_EVENT_NAMES = new Set([
71
+ "auxclick",
72
+ "change",
73
+ "click",
74
+ "contextmenu",
75
+ "dblclick",
76
+ "input",
77
+ "keydown",
78
+ "keypress",
79
+ "keyup",
80
+ "mousedown",
81
+ "mouseup",
82
+ "pointerdown",
83
+ "pointerup",
84
+ "submit",
85
+ "touchend",
86
+ "touchstart"
87
+ ]);
88
+ const INTERACTIVE_EVENT_PROPERTIES = [...INTERACTIVE_EVENT_NAMES]
89
+ .map((eventName) => `on${eventName}`);
90
+
91
+ if (globalThis[GLOBAL_KEY]?.version === VERSION) {
92
+ return;
93
+ }
94
+
95
+ const state = {
96
+ backend: "live",
97
+ captureId: 0,
98
+ capturedAt: 0,
99
+ captureOptions: {
100
+ includeLabelQuotes: false,
101
+ includeLinkUrls: false,
102
+ includeSemanticTags: true,
103
+ includeStateTags: true,
104
+ includeListIndentation: true,
105
+ includeListMarkers: false
106
+ },
107
+ entries: new Map()
108
+ };
109
+
110
+ function isElementNode(value) {
111
+ return Boolean(value && value.nodeType === 1);
112
+ }
113
+
114
+ function isTextNode(value) {
115
+ return Boolean(value && value.nodeType === 3);
116
+ }
117
+
118
+ function normalizeText(value) {
119
+ return String(value ?? "")
120
+ .replace(/\s+/gu, " ")
121
+ .trim();
122
+ }
123
+
124
+ function looksLikeSerializedHtmlText(value) {
125
+ const normalizedValue = normalizeText(value);
126
+ if (!normalizedValue || !normalizedValue.includes("<") || !normalizedValue.includes(">")) {
127
+ return false;
128
+ }
129
+
130
+ if (/<!(?:doctype|--)\b/iu.test(normalizedValue)) {
131
+ return true;
132
+ }
133
+
134
+ if (/<\/?(?:style|script)\b[\s\S]*?>/iu.test(normalizedValue)) {
135
+ return true;
136
+ }
137
+
138
+ const tagMatches = normalizedValue.match(/<\/?[a-z][^>]*>/giu) || [];
139
+ return tagMatches.length >= 3 && normalizedValue.length >= 80;
140
+ }
141
+
142
+ function looksLikeBrowserHelperMarkupText(value) {
143
+ const normalizedValue = normalizeText(value);
144
+ if (!normalizedValue) {
145
+ return false;
146
+ }
147
+
148
+ return /space-browser-(?:frame-document|shadow-root)/iu.test(normalizedValue)
149
+ || /data-space-browser-(?:frame|node|status|frame-url|frame-title|frame-src)/iu.test(normalizedValue);
150
+ }
151
+
152
+ function looksLikeMinifiedScriptText(value) {
153
+ const normalizedValue = normalizeText(value);
154
+ if (!normalizedValue || normalizedValue.length < 400) {
155
+ return false;
156
+ }
157
+
158
+ const jsSignals = [
159
+ /\bfunction\b/u,
160
+ /\breturn\b/u,
161
+ /\bvar\b/u,
162
+ /\bnew\b/u,
163
+ /\bcase\b/u,
164
+ /\bswitch\b/u,
165
+ /\bwhile\b/u,
166
+ /\bfor\b/u,
167
+ /\b(?:localStorage|postMessage|document\.|window\.|parent\.)/u,
168
+ /\bthis\./u,
169
+ /(?:&&|\|\||>>>|!==|===)/u
170
+ ].reduce((count, pattern) => count + (pattern.test(normalizedValue) ? 1 : 0), 0);
171
+
172
+ if (jsSignals < 4) {
173
+ return false;
174
+ }
175
+
176
+ const punctuationCount = (normalizedValue.match(/[{}[\]();=<>\\]/gu) || []).length;
177
+ return punctuationCount / normalizedValue.length >= 0.12;
178
+ }
179
+
180
+ function shouldDropReadableText(value) {
181
+ const normalizedValue = normalizeText(value);
182
+ if (!normalizedValue) {
183
+ return true;
184
+ }
185
+
186
+ return looksLikeBrowserHelperMarkupText(normalizedValue)
187
+ || looksLikeSerializedHtmlText(normalizedValue)
188
+ || looksLikeMinifiedScriptText(normalizedValue);
189
+ }
190
+
191
+ function normalizeAttributeText(value) {
192
+ return normalizeText(value).slice(0, 160);
193
+ }
194
+
195
+ function escapeMarkdownText(value) {
196
+ return String(value ?? "").replace(/([\\`*_{}\[\]()#+\-!|>])/gu, "\\$1");
197
+ }
198
+
199
+ function quoteText(value) {
200
+ return JSON.stringify(String(value ?? ""));
201
+ }
202
+
203
+ function truncateText(value, maxLength = 120) {
204
+ const normalizedValue = normalizeText(value);
205
+ if (normalizedValue.length <= maxLength) {
206
+ return normalizedValue;
207
+ }
208
+
209
+ return `${normalizedValue.slice(0, Math.max(0, maxLength - 1)).trimEnd()}...`;
210
+ }
211
+
212
+ function delayMs(timeoutMs) {
213
+ return new Promise((resolve) => {
214
+ globalThis.setTimeout(resolve, Math.max(0, Number(timeoutMs) || 0));
215
+ });
216
+ }
217
+
218
+ function parseCssColor(value) {
219
+ const normalizedValue = normalizeText(value);
220
+ if (!normalizedValue || normalizedValue === "transparent") {
221
+ return null;
222
+ }
223
+
224
+ const rgbMatch = normalizedValue.match(/^rgba?\(([^)]+)\)$/iu);
225
+ if (rgbMatch) {
226
+ const parts = rgbMatch[1]
227
+ .split(",")
228
+ .map((part) => Number.parseFloat(String(part || "").trim()))
229
+ .filter((part) => Number.isFinite(part));
230
+ if (parts.length >= 3) {
231
+ return {
232
+ r: Math.max(0, Math.min(255, parts[0])),
233
+ g: Math.max(0, Math.min(255, parts[1])),
234
+ b: Math.max(0, Math.min(255, parts[2])),
235
+ a: parts.length >= 4 ? Math.max(0, Math.min(1, parts[3])) : 1
236
+ };
237
+ }
238
+ }
239
+
240
+ const hexMatch = normalizedValue.match(/^#([\da-f]{3,8})$/iu);
241
+ if (!hexMatch) {
242
+ return null;
243
+ }
244
+
245
+ const hex = hexMatch[1];
246
+ if (hex.length === 3 || hex.length === 4) {
247
+ const [r, g, b, a = "f"] = hex.split("");
248
+ return {
249
+ r: Number.parseInt(`${r}${r}`, 16),
250
+ g: Number.parseInt(`${g}${g}`, 16),
251
+ b: Number.parseInt(`${b}${b}`, 16),
252
+ a: Number.parseInt(`${a}${a}`, 16) / 255
253
+ };
254
+ }
255
+
256
+ if (hex.length === 6 || hex.length === 8) {
257
+ return {
258
+ r: Number.parseInt(hex.slice(0, 2), 16),
259
+ g: Number.parseInt(hex.slice(2, 4), 16),
260
+ b: Number.parseInt(hex.slice(4, 6), 16),
261
+ a: hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1
262
+ };
263
+ }
264
+
265
+ return null;
266
+ }
267
+
268
+ function rgbToHsl(color) {
269
+ if (!color) {
270
+ return null;
271
+ }
272
+
273
+ const r = color.r / 255;
274
+ const g = color.g / 255;
275
+ const b = color.b / 255;
276
+ const max = Math.max(r, g, b);
277
+ const min = Math.min(r, g, b);
278
+ const delta = max - min;
279
+ const lightness = (max + min) / 2;
280
+ let hue = 0;
281
+ let saturation = 0;
282
+
283
+ if (delta > 0) {
284
+ saturation = delta / (1 - Math.abs(2 * lightness - 1));
285
+ if (max === r) {
286
+ hue = 60 * (((g - b) / delta) % 6);
287
+ } else if (max === g) {
288
+ hue = 60 * (((b - r) / delta) + 2);
289
+ } else {
290
+ hue = 60 * (((r - g) / delta) + 4);
291
+ }
292
+ }
293
+
294
+ if (hue < 0) {
295
+ hue += 360;
296
+ }
297
+
298
+ return {
299
+ hue,
300
+ lightness,
301
+ saturation
302
+ };
303
+ }
304
+
305
+ function isTrustedHtmlRequirementError(error) {
306
+ return /TrustedHTML/iu.test(String(error?.message || error || ""));
307
+ }
308
+
309
+ function joinBlocks(blocks) {
310
+ return blocks
311
+ .map((block) => String(block || "").trim())
312
+ .filter(Boolean)
313
+ .join("\n\n")
314
+ .trim();
315
+ }
316
+
317
+ function cleanReadableMarkdown(value) {
318
+ const lines = String(value || "")
319
+ .replace(/<style\\?>[\s\S]*?<\/style\\?>/giu, "")
320
+ .replace(/<script\\?>[\s\S]*?<\/script\\?>/giu, "")
321
+ .replace(/<space\\-browser\\-(?:frame\\-document|shadow\\-root)\b[\s\S]*?<\/space\\-browser\\-(?:frame\\-document|shadow\\-root)>/giu, "")
322
+ .split("\n");
323
+
324
+ const filteredLines = [];
325
+ let insideCodeFence = false;
326
+
327
+ lines.forEach((line) => {
328
+ const trimmedLine = String(line || "").trim();
329
+ if (trimmedLine.startsWith("```")) {
330
+ insideCodeFence = !insideCodeFence;
331
+ filteredLines.push(line);
332
+ return;
333
+ }
334
+
335
+ if (!trimmedLine || insideCodeFence) {
336
+ filteredLines.push(line);
337
+ return;
338
+ }
339
+
340
+ if (shouldDropReadableText(trimmedLine)) {
341
+ return;
342
+ }
343
+
344
+ filteredLines.push(line);
345
+ });
346
+
347
+ return filteredLines
348
+ .join("\n")
349
+ .replace(/\n{3,}/gu, "\n\n")
350
+ .trim();
351
+ }
352
+
353
+ function joinInlineParts(parts) {
354
+ return String(parts
355
+ .map((part) => String(part || "").trim())
356
+ .filter(Boolean)
357
+ .join(" "))
358
+ .replace(/\s+([,.;!?])/gu, "$1")
359
+ .replace(/([([{\u201c])\s+/gu, "$1")
360
+ .replace(/\s+([\])}\u201d])/gu, "$1")
361
+ .replace(/\s*\n\s*/gu, "\n")
362
+ .replace(/[ \t]+\n/gu, "\n")
363
+ .replace(/\n{3,}/gu, "\n\n")
364
+ .trim();
365
+ }
366
+
367
+ function indentBlock(text, level = 1) {
368
+ const prefix = " ".repeat(Math.max(0, level));
369
+ return String(text || "")
370
+ .split("\n")
371
+ .map((line) => `${prefix}${line}`)
372
+ .join("\n");
373
+ }
374
+
375
+ function createNamedError(name, message, details = {}) {
376
+ const error = new Error(message);
377
+ error.name = name;
378
+ Object.assign(error, details);
379
+ return error;
380
+ }
381
+
382
+ function coerceSelectorList(payload) {
383
+ if (typeof payload === "string") {
384
+ return [payload];
385
+ }
386
+
387
+ if (Array.isArray(payload?.selectors)) {
388
+ return payload.selectors;
389
+ }
390
+
391
+ if (typeof payload?.selectors === "string") {
392
+ return [payload.selectors];
393
+ }
394
+
395
+ if (Array.isArray(payload?.selector)) {
396
+ return payload.selector;
397
+ }
398
+
399
+ if (typeof payload?.selector === "string") {
400
+ return [payload.selector];
401
+ }
402
+
403
+ if (Array.isArray(payload)) {
404
+ return payload;
405
+ }
406
+
407
+ return [];
408
+ }
409
+
410
+ function normalizeSelectorList(payload) {
411
+ return coerceSelectorList(payload)
412
+ .map((selector) => String(selector || "").trim())
413
+ .filter(Boolean);
414
+ }
415
+
416
+ function normalizeIncludeLinkUrls(payload) {
417
+ return payload?.includeLinkUrls === true;
418
+ }
419
+
420
+ function normalizeIncludeLabelQuotes(payload) {
421
+ return payload?.includeLabelQuotes === true;
422
+ }
423
+
424
+ function normalizeIncludeListIndentation(payload) {
425
+ return payload?.includeListIndentation !== false;
426
+ }
427
+
428
+ function normalizeIncludeListMarkers(payload) {
429
+ return payload?.includeListMarkers === true;
430
+ }
431
+
432
+ function normalizeIncludeStateTags(payload) {
433
+ return payload?.includeStateTags !== false;
434
+ }
435
+
436
+ function normalizeIncludeSemanticTags(payload) {
437
+ return payload?.includeSemanticTags !== false;
438
+ }
439
+
440
+ function formatSummaryValue(value, options = {}) {
441
+ const normalizedValue = normalizeText(value);
442
+ if (!normalizedValue) {
443
+ return "";
444
+ }
445
+
446
+ if (options.includeLabelQuotes === true) {
447
+ return quoteText(normalizedValue);
448
+ }
449
+
450
+ return escapeMarkdownText(normalizedValue);
451
+ }
452
+
453
+ function normalizeFrameChain(value) {
454
+ const rawFrameChain = Array.isArray(value)
455
+ ? value
456
+ : typeof value === "string"
457
+ ? value.split(">")
458
+ : [];
459
+
460
+ return rawFrameChain
461
+ .map((entry) => String(entry || "").trim())
462
+ .filter(Boolean);
463
+ }
464
+
465
+ function getDomHelper() {
466
+ const helper = globalThis[DOM_HELPER_KEY];
467
+ if (
468
+ helper
469
+ && typeof helper.captureDocument === "function"
470
+ && typeof helper.detailNode === "function"
471
+ && typeof helper.clickNode === "function"
472
+ && typeof helper.typeNode === "function"
473
+ && typeof helper.submitNode === "function"
474
+ && typeof helper.typeSubmitNode === "function"
475
+ && typeof helper.scrollNode === "function"
476
+ ) {
477
+ return helper;
478
+ }
479
+
480
+ return null;
481
+ }
482
+
483
+ function requireDomHelper(actionLabel) {
484
+ const helper = getDomHelper();
485
+ if (helper) {
486
+ return helper;
487
+ }
488
+
489
+ throw createNamedError(
490
+ "BrowserPageContentHelperUnavailableError",
491
+ `Browser page content cannot ${actionLabel} without the desktop DOM helper.`,
492
+ {
493
+ code: "browser_page_content_dom_helper_unavailable",
494
+ details: {
495
+ action: String(actionLabel || "resolve")
496
+ }
497
+ }
498
+ );
499
+ }
500
+
501
+ function normalizeReferenceId(value) {
502
+ if (typeof value === "number" && Number.isFinite(value)) {
503
+ return String(Math.trunc(value));
504
+ }
505
+
506
+ if (typeof value === "string") {
507
+ return value.trim();
508
+ }
509
+
510
+ if (value && typeof value === "object") {
511
+ return normalizeReferenceId(value.referenceId ?? value.ref ?? value.id);
512
+ }
513
+
514
+ return "";
515
+ }
516
+
517
+ function getTagName(element) {
518
+ return String(element?.tagName || "").toUpperCase();
519
+ }
520
+
521
+ function getAttributeNamesSafe(element) {
522
+ try {
523
+ if (typeof element?.getAttributeNames === "function") {
524
+ return element.getAttributeNames();
525
+ }
526
+
527
+ return [...(element?.attributes || [])]
528
+ .map((attribute) => String(attribute?.name || "").trim())
529
+ .filter(Boolean);
530
+ } catch {
531
+ return [];
532
+ }
533
+ }
534
+
535
+ function normalizeInteractiveEventName(value) {
536
+ return String(value || "")
537
+ .trim()
538
+ .toLowerCase()
539
+ .split(/[.:]/u, 1)[0];
540
+ }
541
+
542
+ function isInteractiveEventName(value) {
543
+ return INTERACTIVE_EVENT_NAMES.has(normalizeInteractiveEventName(value));
544
+ }
545
+
546
+ function isInteractiveEventAttributeName(attributeName) {
547
+ const normalizedName = String(attributeName || "").trim().toLowerCase();
548
+ if (!normalizedName) {
549
+ return false;
550
+ }
551
+
552
+ if (normalizedName.startsWith("@")) {
553
+ return isInteractiveEventName(normalizedName.slice(1));
554
+ }
555
+
556
+ if (normalizedName.startsWith("x-on:") || normalizedName.startsWith("v-on:")) {
557
+ return isInteractiveEventName(normalizedName.slice(5));
558
+ }
559
+
560
+ if (normalizedName.startsWith("ng-")) {
561
+ return isInteractiveEventName(normalizedName.slice(3));
562
+ }
563
+
564
+ if (normalizedName.startsWith("on") && normalizedName.length > 2) {
565
+ return isInteractiveEventName(normalizedName.slice(2));
566
+ }
567
+
568
+ return false;
569
+ }
570
+
571
+ function hasHelperManagedNodeReference(element) {
572
+ return Boolean(normalizeAttributeText(element?.getAttribute?.("data-space-browser-node-id")));
573
+ }
574
+
575
+ function hasInteractiveEventHandlerAttribute(element) {
576
+ return getAttributeNamesSafe(element).some((attributeName) => {
577
+ return isInteractiveEventAttributeName(attributeName);
578
+ });
579
+ }
580
+
581
+ function hasInteractiveEventHandlerProperty(element) {
582
+ return INTERACTIVE_EVENT_PROPERTIES.some((propertyName) => {
583
+ return typeof element?.[propertyName] === "function";
584
+ });
585
+ }
586
+
587
+ function hasInteractiveEventHandler(element) {
588
+ return hasInteractiveEventHandlerAttribute(element) || hasInteractiveEventHandlerProperty(element);
589
+ }
590
+
591
+ function isStyleDeclarationHidden(styleValue) {
592
+ const normalizedStyleValue = String(styleValue || "")
593
+ .toLowerCase()
594
+ .replace(/\s+/gu, "");
595
+
596
+ if (!normalizedStyleValue) {
597
+ return false;
598
+ }
599
+
600
+ return /(?:^|;)display:none(?:;|$)/u.test(normalizedStyleValue)
601
+ || /(?:^|;)visibility:hidden(?:;|$)/u.test(normalizedStyleValue)
602
+ || /(?:^|;)visibility:collapse(?:;|$)/u.test(normalizedStyleValue)
603
+ || /(?:^|;)content-visibility:hidden(?:;|$)/u.test(normalizedStyleValue)
604
+ || /(?:^|;)opacity:0(?:\.0+)?(?:;|$)/u.test(normalizedStyleValue);
605
+ }
606
+
607
+ function isComputedStyleHidden(computedStyle) {
608
+ if (!computedStyle) {
609
+ return false;
610
+ }
611
+
612
+ const display = normalizeText(computedStyle.display).toLowerCase();
613
+ const visibility = normalizeText(computedStyle.visibility).toLowerCase();
614
+ const contentVisibility = normalizeText(computedStyle.contentVisibility).toLowerCase();
615
+ const opacity = Number(computedStyle.opacity || 1);
616
+
617
+ return display === "none"
618
+ || visibility === "hidden"
619
+ || visibility === "collapse"
620
+ || contentVisibility === "hidden"
621
+ || opacity <= 0;
622
+ }
623
+
624
+ function isEffectivelyHiddenByAncestor(element) {
625
+ let current = element;
626
+
627
+ while (isElementNode(current)) {
628
+ if (current.hidden || current.getAttribute?.("aria-hidden") === "true") {
629
+ return true;
630
+ }
631
+
632
+ if (isStyleDeclarationHidden(current.getAttribute?.("style"))) {
633
+ return true;
634
+ }
635
+
636
+ if (isComputedStyleHidden(getComputedStyleSafe(current))) {
637
+ return true;
638
+ }
639
+
640
+ current = current.parentElement;
641
+ }
642
+
643
+ return false;
644
+ }
645
+
646
+ function isHiddenElement(element) {
647
+ if (!isElementNode(element)) {
648
+ return true;
649
+ }
650
+
651
+ const tagName = getTagName(element);
652
+ if (SKIP_TAGS.has(tagName)) {
653
+ return true;
654
+ }
655
+
656
+ if (element.hidden || element.getAttribute?.("aria-hidden") === "true") {
657
+ return true;
658
+ }
659
+
660
+ if (tagName === "INPUT" && String(element.getAttribute?.("type") || "").toLowerCase() === "hidden") {
661
+ return true;
662
+ }
663
+
664
+ if (isStyleDeclarationHidden(element.getAttribute?.("style"))) {
665
+ return true;
666
+ }
667
+
668
+ const computedStyle = getComputedStyleSafe(element);
669
+ if (isComputedStyleHidden(computedStyle)) {
670
+ return true;
671
+ }
672
+
673
+ return isEffectivelyHiddenByAncestor(element.parentElement);
674
+ }
675
+
676
+ function isBlockElement(element) {
677
+ return BLOCK_TAGS.has(getTagName(element));
678
+ }
679
+
680
+ function isInteractiveElement(element) {
681
+ if (!isElementNode(element) || isHiddenElement(element)) {
682
+ return false;
683
+ }
684
+
685
+ if (hasHelperManagedNodeReference(element)) {
686
+ return true;
687
+ }
688
+
689
+ const tagName = getTagName(element);
690
+ if (tagName === "A" && element.hasAttribute?.("href")) {
691
+ return true;
692
+ }
693
+
694
+ if (tagName === "BUTTON" || tagName === "INPUT" || tagName === "SELECT" || tagName === "TEXTAREA" || tagName === "SUMMARY") {
695
+ return true;
696
+ }
697
+
698
+ if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
699
+ return true;
700
+ }
701
+
702
+ const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
703
+ return INTERACTIVE_ROLES.has(role) || hasInteractiveEventHandler(element);
704
+ }
705
+
706
+ function getComputedStyleSafe(element) {
707
+ try {
708
+ return globalThis.getComputedStyle?.(element) || null;
709
+ } catch {
710
+ return null;
711
+ }
712
+ }
713
+
714
+ function getElementRectSafe(element) {
715
+ try {
716
+ const rect = element?.getBoundingClientRect?.();
717
+ if (!rect) {
718
+ return null;
719
+ }
720
+
721
+ return {
722
+ height: Number(rect.height) || 0,
723
+ width: Number(rect.width) || 0,
724
+ x: Number(rect.x) || 0,
725
+ y: Number(rect.y) || 0
726
+ };
727
+ } catch {
728
+ return null;
729
+ }
730
+ }
731
+
732
+ function readSerializedTagList(element, attributeName) {
733
+ const rawValue = normalizeText(element?.getAttribute?.(attributeName));
734
+ if (!rawValue) {
735
+ return [];
736
+ }
737
+
738
+ return rawValue
739
+ .split(/\s+/u)
740
+ .map((part) => normalizeText(part))
741
+ .filter(Boolean);
742
+ }
743
+
744
+ function detectSemanticTone(element, computedStyle, metadata = {}) {
745
+ const opacity = Number(computedStyle?.opacity || 1);
746
+ const backgroundColor = parseCssColor(computedStyle?.backgroundColor || "");
747
+ const borderColor = parseCssColor(computedStyle?.borderTopColor || "");
748
+ const foregroundColor = parseCssColor(computedStyle?.color || "");
749
+ const isButtonLike = ["BUTTON", "INPUT", "SUMMARY"].includes(getTagName(element))
750
+ || ["button", "tab", "menuitem"].includes(String(element?.getAttribute?.("role") || "").trim().toLowerCase());
751
+
752
+ if (metadata.disabled || metadata.blocked || opacity <= 0.58) {
753
+ return "muted";
754
+ }
755
+
756
+ const preferredColor = [backgroundColor, borderColor, foregroundColor]
757
+ .filter((color) => color && color.a > 0.15)
758
+ .map((color) => ({
759
+ color,
760
+ hsl: rgbToHsl(color)
761
+ }))
762
+ .find((entry) => entry.hsl && entry.hsl.saturation >= 0.2);
763
+
764
+ if (!preferredColor) {
765
+ return "";
766
+ }
767
+
768
+ const {
769
+ hue,
770
+ lightness,
771
+ saturation
772
+ } = preferredColor.hsl;
773
+ if (saturation < 0.2) {
774
+ return "";
775
+ }
776
+
777
+ if ((hue >= 345 || hue < 20) && lightness >= 0.18 && lightness <= 0.82) {
778
+ return "error";
779
+ }
780
+
781
+ if (hue >= 20 && hue < 65 && lightness >= 0.2 && lightness <= 0.9) {
782
+ return "warning";
783
+ }
784
+
785
+ if (hue >= 65 && hue < 170 && lightness >= 0.16 && lightness <= 0.84) {
786
+ return "success";
787
+ }
788
+
789
+ if (hue >= 170 && hue < 280 && lightness >= 0.14 && lightness <= 0.82) {
790
+ if (isButtonLike && backgroundColor?.a > 0.2) {
791
+ return "primary";
792
+ }
793
+ return "";
794
+ }
795
+
796
+ return "";
797
+ }
798
+
799
+ function collectElementStateMetadata(element, options = {}) {
800
+ if (!isElementNode(element)) {
801
+ return {
802
+ descriptorTags: [],
803
+ semanticTags: [],
804
+ stateTags: []
805
+ };
806
+ }
807
+
808
+ const computedStyle = getComputedStyleSafe(element);
809
+ const rect = getElementRectSafe(element);
810
+ const tagName = getTagName(element);
811
+ const ariaDisabled = String(element.getAttribute?.("aria-disabled") || "").trim().toLowerCase() === "true";
812
+ const ariaBusy = String(element.getAttribute?.("aria-busy") || "").trim().toLowerCase() === "true";
813
+ const ariaChecked = String(element.getAttribute?.("aria-checked") || "").trim().toLowerCase() === "true";
814
+ const ariaCurrent = normalizeText(element.getAttribute?.("aria-current"));
815
+ const ariaInvalid = String(element.getAttribute?.("aria-invalid") || "").trim().toLowerCase() === "true";
816
+ const ariaPressed = String(element.getAttribute?.("aria-pressed") || "").trim().toLowerCase() === "true";
817
+ const ariaReadonly = String(element.getAttribute?.("aria-readonly") || "").trim().toLowerCase() === "true";
818
+ const ariaRequired = String(element.getAttribute?.("aria-required") || "").trim().toLowerCase() === "true";
819
+ const ariaSelected = String(element.getAttribute?.("aria-selected") || "").trim().toLowerCase() === "true";
820
+ const helperStateTags = readSerializedTagList(element, "data-space-browser-state-tags");
821
+ const helperSemanticTags = readSerializedTagList(element, "data-space-browser-semantic-tags");
822
+ const closestInert = typeof element.closest === "function" ? element.closest("[inert]") : null;
823
+ const pointerEventsNone = normalizeText(computedStyle?.pointerEvents || "").toLowerCase() === "none";
824
+ const disabled = Boolean(element.disabled || ariaDisabled || closestInert || helperStateTags.includes("disabled"));
825
+ const blocked = !disabled && (pointerEventsNone || helperStateTags.includes("blocked"));
826
+ const checked = Boolean(element.checked || ariaChecked || helperStateTags.includes("checked"));
827
+ const selected = tagName === "OPTION"
828
+ ? Boolean(element.selected)
829
+ : Boolean(ariaSelected || helperStateTags.includes("selected"));
830
+ const invalid = Boolean(ariaInvalid || helperStateTags.includes("invalid") || element.matches?.(":invalid"));
831
+ const readonly = Boolean(element.readOnly || ariaReadonly);
832
+ const required = Boolean(element.required || ariaRequired);
833
+ const expanded = String(element.getAttribute?.("aria-expanded") || "").trim().toLowerCase() === "true" || helperStateTags.includes("expanded");
834
+ const pressed = ariaPressed || helperStateTags.includes("pressed");
835
+ const busy = ariaBusy || helperStateTags.includes("busy");
836
+ const current = Boolean((ariaCurrent && ariaCurrent !== "false") || helperStateTags.includes("current"));
837
+ const zeroRect = Boolean(
838
+ rect
839
+ && element.ownerDocument === globalThis.document
840
+ && rect.width <= 1
841
+ && rect.height <= 1
842
+ );
843
+ const opacity = Number(computedStyle?.opacity || 1);
844
+ const semanticTone = helperSemanticTags[0] || detectSemanticTone(element, computedStyle, {
845
+ blocked,
846
+ disabled
847
+ });
848
+ const stateTags = helperStateTags.length
849
+ ? helperStateTags.slice()
850
+ : [
851
+ disabled ? "disabled" : "",
852
+ !disabled && (blocked || zeroRect) ? "blocked" : "",
853
+ checked ? "checked" : "",
854
+ selected && tagName !== "SELECT" ? "selected" : "",
855
+ invalid ? "invalid" : "",
856
+ expanded ? "expanded" : "",
857
+ pressed ? "pressed" : ""
858
+ ].filter(Boolean);
859
+
860
+ const semanticTags = helperSemanticTags.length
861
+ ? helperSemanticTags.slice(0, 1)
862
+ : (semanticTone ? [semanticTone] : []);
863
+ const descriptorTags = [
864
+ ...(options.includeStateTags !== false ? stateTags : []),
865
+ ...(options.includeSemanticTags !== false ? semanticTags : [])
866
+ ];
867
+
868
+ return {
869
+ blocked,
870
+ busy,
871
+ checked,
872
+ current,
873
+ cursor: normalizeText(computedStyle?.cursor || "").toLowerCase(),
874
+ descriptorTags,
875
+ disabled,
876
+ expanded,
877
+ invalid,
878
+ opacity,
879
+ pointerEventsNone,
880
+ pressed,
881
+ readonly,
882
+ required,
883
+ selected,
884
+ semanticTags,
885
+ semanticTone,
886
+ stateTags,
887
+ visible: !isHiddenElement(element),
888
+ zeroRect
889
+ };
890
+ }
891
+
892
+ function getReferenceValueMetadata(element) {
893
+ const tagName = getTagName(element);
894
+ const helperLiveValue = normalizeText(element?.getAttribute?.("data-space-browser-live-value"));
895
+ const helperSelectedValue = normalizeText(element?.getAttribute?.("data-space-browser-selected-text"));
896
+ if (tagName === "INPUT") {
897
+ const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
898
+ if (inputType === "password") {
899
+ return "";
900
+ }
901
+ return truncateText(helperLiveValue || element.value || element.getAttribute?.("value") || "", 96);
902
+ }
903
+
904
+ if (tagName === "TEXTAREA") {
905
+ return truncateText(helperLiveValue || element.value || "", 96);
906
+ }
907
+
908
+ if (tagName === "SELECT") {
909
+ if (helperSelectedValue) {
910
+ return helperSelectedValue;
911
+ }
912
+ const selectedOptions = [...(element.selectedOptions || [])]
913
+ .map((option) => truncateText(option.textContent || option.label || option.value || "", 48))
914
+ .filter(Boolean);
915
+ return selectedOptions.join(" | ");
916
+ }
917
+
918
+ if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
919
+ return truncateText(element.textContent || "", 96);
920
+ }
921
+
922
+ return "";
923
+ }
924
+
925
+ function collectMetaLines(doc = globalThis.document) {
926
+ const lines = [];
927
+ const title = normalizeAttributeText(doc?.title || "");
928
+ const description = normalizeAttributeText(
929
+ doc?.querySelector?.('meta[name="description"]')?.getAttribute?.("content") || ""
930
+ );
931
+ const url = String(globalThis.location?.href || "");
932
+
933
+ if (!title && !description && !url) {
934
+ return "";
935
+ }
936
+
937
+ lines.push("---");
938
+ if (title) {
939
+ lines.push(`title: ${quoteText(title)}`);
940
+ }
941
+ if (description) {
942
+ lines.push(`description: ${quoteText(description)}`);
943
+ }
944
+ if (url) {
945
+ lines.push(`url: ${quoteText(url)}`);
946
+ }
947
+ lines.push("---");
948
+ return lines.join("\n");
949
+ }
950
+
951
+ function summarizeUrl(value) {
952
+ const normalizedValue = String(value || "").trim();
953
+ if (!normalizedValue) {
954
+ return "";
955
+ }
956
+
957
+ try {
958
+ const url = new URL(normalizedValue, globalThis.location?.href || "http://localhost/");
959
+ if (url.origin === globalThis.location?.origin) {
960
+ const relative = `${url.pathname || "/"}${url.search || ""}${url.hash || ""}`;
961
+ return truncateText(relative || "/", 96);
962
+ }
963
+
964
+ return truncateText(`${url.hostname}${url.pathname || "/"}`, 96);
965
+ } catch {
966
+ return truncateText(normalizedValue, 96);
967
+ }
968
+ }
969
+
970
+ function getElementText(element) {
971
+ return normalizeText(element?.textContent || "");
972
+ }
973
+
974
+ function collectLabelCandidates(element, options = {}) {
975
+ const includeAlt = options.includeAlt !== false;
976
+ const includeDescendantImageAlt = options.includeDescendantImageAlt !== false;
977
+ const includePlaceholder = options.includePlaceholder === true;
978
+ const includeText = options.includeText !== false;
979
+ const collectedLabels = [];
980
+
981
+ try {
982
+ if (Array.isArray(element?.labels) || typeof element?.labels?.forEach === "function") {
983
+ element.labels.forEach((labelElement) => {
984
+ const text = getElementText(labelElement);
985
+ if (text) {
986
+ collectedLabels.push(text);
987
+ }
988
+ });
989
+ }
990
+ } catch {
991
+ // Ignore labels lookup failures from non-form elements.
992
+ }
993
+
994
+ [
995
+ element?.getAttribute?.("aria-label"),
996
+ element?.getAttribute?.("title")
997
+ ].forEach((candidate) => {
998
+ const text = normalizeAttributeText(candidate);
999
+ if (text) {
1000
+ collectedLabels.push(text);
1001
+ }
1002
+ });
1003
+
1004
+ if (includeAlt) {
1005
+ const altText = normalizeAttributeText(element?.getAttribute?.("alt"));
1006
+ if (altText) {
1007
+ collectedLabels.push(altText);
1008
+ }
1009
+ }
1010
+
1011
+ if (includePlaceholder) {
1012
+ const placeholderText = normalizeAttributeText(element?.getAttribute?.("placeholder"));
1013
+ if (placeholderText) {
1014
+ collectedLabels.push(placeholderText);
1015
+ }
1016
+ }
1017
+
1018
+ if (includeDescendantImageAlt) {
1019
+ try {
1020
+ [...(element?.querySelectorAll?.("img[alt], img[title]") || [])]
1021
+ .slice(0, 3)
1022
+ .forEach((mediaElement) => {
1023
+ const text = normalizeAttributeText(
1024
+ mediaElement.getAttribute?.("alt")
1025
+ || mediaElement.getAttribute?.("title")
1026
+ );
1027
+ if (text) {
1028
+ collectedLabels.push(text);
1029
+ }
1030
+ });
1031
+ } catch {
1032
+ // Ignore descendant-media lookup failures.
1033
+ }
1034
+ }
1035
+
1036
+ if (includeText) {
1037
+ const textContent = getElementText(element);
1038
+ if (textContent) {
1039
+ collectedLabels.push(textContent);
1040
+ }
1041
+ }
1042
+
1043
+ return [...new Set(collectedLabels.filter(Boolean))];
1044
+ }
1045
+
1046
+ function getLabelText(element, options = {}) {
1047
+ return collectLabelCandidates(element, options)[0] || "";
1048
+ }
1049
+
1050
+ function serializeElementSnapshot(element) {
1051
+ if (!isElementNode(element)) {
1052
+ return "";
1053
+ }
1054
+
1055
+ try {
1056
+ if (typeof element.outerHTML === "string" && element.outerHTML) {
1057
+ return element.outerHTML;
1058
+ }
1059
+ } catch {
1060
+ // Fall through to XMLSerializer.
1061
+ }
1062
+
1063
+ try {
1064
+ if (typeof globalThis.XMLSerializer === "function") {
1065
+ return new globalThis.XMLSerializer().serializeToString(element);
1066
+ }
1067
+ } catch {
1068
+ // Ignore serialization errors.
1069
+ }
1070
+
1071
+ return "";
1072
+ }
1073
+
1074
+ function getReferenceKind(element) {
1075
+ const tagName = getTagName(element);
1076
+ const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
1077
+ const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
1078
+
1079
+ if (tagName === "A" || role === "link") {
1080
+ return "link";
1081
+ }
1082
+
1083
+ if (tagName === "IMG") {
1084
+ return "image";
1085
+ }
1086
+
1087
+ if (tagName === "BUTTON" || ["button", "menuitem", "tab"].includes(role)) {
1088
+ return "button";
1089
+ }
1090
+
1091
+ if (tagName === "TEXTAREA") {
1092
+ return "textarea";
1093
+ }
1094
+
1095
+ if (tagName === "SELECT" || role === "combobox") {
1096
+ return "select";
1097
+ }
1098
+
1099
+ if (tagName === "SUMMARY") {
1100
+ return "summary";
1101
+ }
1102
+
1103
+ if (tagName === "INPUT") {
1104
+ if (["button", "submit", "reset"].includes(inputType)) {
1105
+ return "button";
1106
+ }
1107
+
1108
+ if (inputType === "checkbox") {
1109
+ return "checkbox";
1110
+ }
1111
+
1112
+ if (inputType === "radio") {
1113
+ return "radio";
1114
+ }
1115
+
1116
+ return `input ${inputType || "text"}`;
1117
+ }
1118
+
1119
+ if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
1120
+ return "editable";
1121
+ }
1122
+
1123
+ if (role === "searchbox") {
1124
+ return "input search";
1125
+ }
1126
+
1127
+ if (role === "textbox") {
1128
+ return "input text";
1129
+ }
1130
+
1131
+ if (hasHelperManagedNodeReference(element) || hasInteractiveEventHandler(element)) {
1132
+ return "button";
1133
+ }
1134
+
1135
+ return role || tagName.toLowerCase();
1136
+ }
1137
+
1138
+ function collectReferenceSummaryData(element, options = {}) {
1139
+ const tagName = getTagName(element);
1140
+ const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
1141
+ const id = normalizeAttributeText(element.getAttribute?.("id"));
1142
+ const name = normalizeAttributeText(element.getAttribute?.("name"));
1143
+ const kind = getReferenceKind(element);
1144
+ const stateMetadata = collectElementStateMetadata(element, options);
1145
+ const formatValue = (value) => formatSummaryValue(value, options);
1146
+ const includeLinkUrls = options.includeLinkUrls === true;
1147
+ const parts = [];
1148
+ const appendFallbackIdOrName = () => {
1149
+ if (id) {
1150
+ parts.push(`#${id}`);
1151
+ return;
1152
+ }
1153
+
1154
+ if (name) {
1155
+ parts.push(`name=${formatValue(name)}`);
1156
+ }
1157
+ };
1158
+
1159
+ if (tagName === "A" || role === "link") {
1160
+ const hrefSummary = summarizeUrl(element.getAttribute?.("href") || element.href || "");
1161
+ const label = truncateText(getLabelText(element, {
1162
+ includeAlt: false,
1163
+ includeDescendantImageAlt: true,
1164
+ includePlaceholder: false,
1165
+ includeText: true
1166
+ }), 120);
1167
+ const displayLabel = label || hrefSummary;
1168
+
1169
+ if (displayLabel) {
1170
+ parts.push(formatValue(displayLabel));
1171
+ } else {
1172
+ appendFallbackIdOrName();
1173
+ }
1174
+
1175
+ if (includeLinkUrls) {
1176
+ if (hrefSummary && hrefSummary !== displayLabel) {
1177
+ parts.push(`-> ${hrefSummary}`);
1178
+ }
1179
+ }
1180
+ } else if (tagName === "BUTTON" || ["button", "menuitem", "tab"].includes(role)) {
1181
+ const label = truncateText(getLabelText(element, {
1182
+ includeAlt: false,
1183
+ includeDescendantImageAlt: true,
1184
+ includePlaceholder: false,
1185
+ includeText: true
1186
+ }), 120);
1187
+ if (label) {
1188
+ parts.push(formatValue(label));
1189
+ } else {
1190
+ appendFallbackIdOrName();
1191
+ }
1192
+ } else if (tagName === "TEXTAREA" || role === "textbox" || role === "searchbox") {
1193
+ const label = truncateText(getLabelText(element, {
1194
+ includeAlt: false,
1195
+ includeDescendantImageAlt: false,
1196
+ includePlaceholder: false,
1197
+ includeText: true
1198
+ }), 120);
1199
+ if (label) {
1200
+ parts.push(formatValue(label));
1201
+ }
1202
+ const placeholder = normalizeAttributeText(element.getAttribute?.("placeholder"));
1203
+ if (placeholder) {
1204
+ parts.push(`placeholder=${formatValue(placeholder)}`);
1205
+ } else if (!label) {
1206
+ appendFallbackIdOrName();
1207
+ }
1208
+ } else if (tagName === "SELECT" || role === "combobox") {
1209
+ const label = truncateText(getLabelText(element, {
1210
+ includeAlt: false,
1211
+ includeDescendantImageAlt: false,
1212
+ includePlaceholder: false,
1213
+ includeText: true
1214
+ }), 120);
1215
+ if (label) {
1216
+ parts.push(formatValue(label));
1217
+ } else {
1218
+ appendFallbackIdOrName();
1219
+ }
1220
+
1221
+ const selectedValue = getReferenceValueMetadata(element);
1222
+ const selectedOptions = selectedValue
1223
+ ? [selectedValue]
1224
+ : [...(element.selectedOptions || [])]
1225
+ .map((option) => truncateText(option.textContent || "", 48))
1226
+ .filter(Boolean);
1227
+ if (selectedOptions.length) {
1228
+ parts.push(`selected=${formatValue(selectedOptions.join(" | "))}`);
1229
+ }
1230
+ } else if (tagName === "SUMMARY") {
1231
+ const label = truncateText(getLabelText(element, {
1232
+ includeAlt: false,
1233
+ includeDescendantImageAlt: true,
1234
+ includePlaceholder: false,
1235
+ includeText: true
1236
+ }), 120);
1237
+ if (label) {
1238
+ parts.push(formatValue(label));
1239
+ } else {
1240
+ appendFallbackIdOrName();
1241
+ }
1242
+ } else if (tagName === "INPUT") {
1243
+ const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
1244
+ if (["button", "submit", "reset"].includes(inputType)) {
1245
+ const label = truncateText(getLabelText(element, {
1246
+ includeAlt: false,
1247
+ includeDescendantImageAlt: false,
1248
+ includePlaceholder: false,
1249
+ includeText: false
1250
+ }) || element.value || "", 120);
1251
+ if (label) {
1252
+ parts.push(formatValue(label));
1253
+ } else {
1254
+ appendFallbackIdOrName();
1255
+ }
1256
+ } else if (["checkbox", "radio"].includes(inputType)) {
1257
+ const label = truncateText(getLabelText(element, {
1258
+ includeAlt: false,
1259
+ includeDescendantImageAlt: false,
1260
+ includePlaceholder: false,
1261
+ includeText: false
1262
+ }), 120);
1263
+ if (label) {
1264
+ parts.push(formatValue(label));
1265
+ } else {
1266
+ appendFallbackIdOrName();
1267
+ }
1268
+ } else if (inputType === "file") {
1269
+ const label = truncateText(getLabelText(element, {
1270
+ includeAlt: false,
1271
+ includeDescendantImageAlt: false,
1272
+ includePlaceholder: false,
1273
+ includeText: false
1274
+ }), 120);
1275
+ if (label) {
1276
+ parts.push(formatValue(label));
1277
+ } else {
1278
+ appendFallbackIdOrName();
1279
+ }
1280
+ } else {
1281
+ const label = truncateText(getLabelText(element, {
1282
+ includeAlt: false,
1283
+ includeDescendantImageAlt: false,
1284
+ includePlaceholder: false,
1285
+ includeText: false
1286
+ }), 120);
1287
+ if (label) {
1288
+ parts.push(formatValue(label));
1289
+ }
1290
+
1291
+ const placeholder = normalizeAttributeText(element.getAttribute?.("placeholder"));
1292
+ const value = inputType === "password"
1293
+ ? ""
1294
+ : getReferenceValueMetadata(element);
1295
+
1296
+ if (placeholder) {
1297
+ parts.push(`placeholder=${formatValue(placeholder)}`);
1298
+ }
1299
+ if (value) {
1300
+ parts.push(`value=${formatValue(value)}`);
1301
+ }
1302
+ if (!label && !placeholder && !value) {
1303
+ appendFallbackIdOrName();
1304
+ }
1305
+ }
1306
+ } else if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
1307
+ const label = truncateText(getLabelText(element, {
1308
+ includeAlt: false,
1309
+ includeDescendantImageAlt: false,
1310
+ includePlaceholder: false,
1311
+ includeText: true
1312
+ }), 120);
1313
+ if (label) {
1314
+ parts.push(formatValue(label));
1315
+ } else {
1316
+ appendFallbackIdOrName();
1317
+ }
1318
+ } else if (tagName === "IMG") {
1319
+ const srcSummary = summarizeUrl(element.currentSrc || element.getAttribute?.("src") || element.src || "");
1320
+ const label = truncateText(getLabelText(element, {
1321
+ includeAlt: true,
1322
+ includeDescendantImageAlt: false,
1323
+ includePlaceholder: false,
1324
+ includeText: false
1325
+ }), 120);
1326
+ const displayLabel = label || srcSummary;
1327
+ if (displayLabel) {
1328
+ parts.push(formatValue(displayLabel));
1329
+ } else {
1330
+ appendFallbackIdOrName();
1331
+ }
1332
+ } else if (role) {
1333
+ const label = truncateText(getLabelText(element, {
1334
+ includeAlt: false,
1335
+ includeDescendantImageAlt: true,
1336
+ includePlaceholder: false,
1337
+ includeText: true
1338
+ }), 120);
1339
+ if (label) {
1340
+ parts.push(formatValue(label));
1341
+ } else {
1342
+ appendFallbackIdOrName();
1343
+ }
1344
+ } else {
1345
+ const label = truncateText(getLabelText(element, {
1346
+ includeAlt: false,
1347
+ includeDescendantImageAlt: true,
1348
+ includePlaceholder: false,
1349
+ includeText: true
1350
+ }), 120);
1351
+ if (label) {
1352
+ parts.push(formatValue(label));
1353
+ } else {
1354
+ appendFallbackIdOrName();
1355
+ }
1356
+ }
1357
+
1358
+ return {
1359
+ descriptorTags: stateMetadata.descriptorTags.slice(),
1360
+ kind,
1361
+ semanticTags: stateMetadata.semanticTags.slice(),
1362
+ state: stateMetadata,
1363
+ summary: parts.filter(Boolean).join(" ")
1364
+ };
1365
+ }
1366
+
1367
+ function createReferenceEntry(element, referenceId, options = {}) {
1368
+ const nodeId = normalizeAttributeText(element.getAttribute?.("data-space-browser-node-id"));
1369
+ const frameId = normalizeAttributeText(element.getAttribute?.("data-space-browser-frame-id"));
1370
+ const frameChain = normalizeFrameChain(element.getAttribute?.("data-space-browser-frame-chain"));
1371
+ const helperBacked = Boolean(nodeId && frameChain.length);
1372
+ const summaryData = collectReferenceSummaryData(element, options);
1373
+
1374
+ return {
1375
+ connected: helperBacked ? true : element.isConnected !== false,
1376
+ dom: serializeElementSnapshot(element),
1377
+ descriptorTags: summaryData.descriptorTags,
1378
+ element: helperBacked ? null : element,
1379
+ frameChain,
1380
+ frameId,
1381
+ helperBacked,
1382
+ id: normalizeAttributeText(element.getAttribute?.("id")),
1383
+ name: normalizeAttributeText(element.getAttribute?.("name")),
1384
+ nodeId,
1385
+ referenceId,
1386
+ kind: summaryData.kind,
1387
+ semanticTags: summaryData.semanticTags,
1388
+ state: summaryData.state,
1389
+ summary: summaryData.summary,
1390
+ tagName: getTagName(element)
1391
+ };
1392
+ }
1393
+
1394
+ function ensureReference(element, context) {
1395
+ if (context.referenceIdsByElement.has(element)) {
1396
+ return context.referenceIdsByElement.get(element);
1397
+ }
1398
+
1399
+ const referenceId = String(context.nextReferenceId++);
1400
+ const entry = createReferenceEntry(element, referenceId, context.options);
1401
+ context.referenceIdsByElement.set(element, referenceId);
1402
+ context.entries.set(referenceId, entry);
1403
+ return referenceId;
1404
+ }
1405
+
1406
+ function renderReference(element, context) {
1407
+ const referenceId = ensureReference(element, context);
1408
+ const entry = context.entries.get(referenceId);
1409
+ const kind = normalizeText(entry?.kind || getTagName(element).toLowerCase());
1410
+ const descriptorTags = Array.isArray(entry?.descriptorTags)
1411
+ ? entry.descriptorTags.map((tag) => normalizeText(tag)).filter(Boolean)
1412
+ : [];
1413
+ const summary = normalizeText(entry?.summary || "");
1414
+ const descriptor = [...descriptorTags, kind, referenceId].filter(Boolean).join(" ");
1415
+ return summary ? `[${descriptor}] ${summary}` : `[${descriptor}]`;
1416
+ }
1417
+
1418
+ function isReferenceableElement(element) {
1419
+ return isInteractiveElement(element) || getTagName(element) === "IMG";
1420
+ }
1421
+
1422
+ function renderInlineNode(node, context) {
1423
+ if (isTextNode(node)) {
1424
+ const textContent = normalizeText(node.textContent || "");
1425
+ if (shouldDropReadableText(textContent)) {
1426
+ return "";
1427
+ }
1428
+
1429
+ return escapeMarkdownText(textContent);
1430
+ }
1431
+
1432
+ if (!isElementNode(node) || isHiddenElement(node)) {
1433
+ return "";
1434
+ }
1435
+
1436
+ if (isReferenceableElement(node)) {
1437
+ return renderReference(node, context);
1438
+ }
1439
+
1440
+ const tagName = getTagName(node);
1441
+
1442
+ if (tagName === "LABEL" && (node.getAttribute?.("for") || node.querySelector?.("input, textarea, select, button"))) {
1443
+ return "";
1444
+ }
1445
+
1446
+ if (tagName === "BR") {
1447
+ return "\n";
1448
+ }
1449
+
1450
+ if (tagName === "STRONG" || tagName === "B") {
1451
+ const content = renderInlineChildren(node, context);
1452
+ return content ? `**${content}**` : "";
1453
+ }
1454
+
1455
+ if (tagName === "EM" || tagName === "I") {
1456
+ const content = renderInlineChildren(node, context);
1457
+ return content ? `*${content}*` : "";
1458
+ }
1459
+
1460
+ if (tagName === "S" || tagName === "STRIKE" || tagName === "DEL") {
1461
+ const content = renderInlineChildren(node, context);
1462
+ return content ? `~~${content}~~` : "";
1463
+ }
1464
+
1465
+ if (tagName === "CODE") {
1466
+ const content = normalizeText(node.textContent || "");
1467
+ return content ? `\`${content.replace(/`/gu, "\\`")}\`` : "";
1468
+ }
1469
+
1470
+ return renderInlineChildren(node, context);
1471
+ }
1472
+
1473
+ function renderInlineChildren(element, context) {
1474
+ const parts = [];
1475
+
1476
+ element.childNodes.forEach((childNode) => {
1477
+ const renderedChild = renderInlineNode(childNode, context);
1478
+ if (renderedChild) {
1479
+ parts.push(renderedChild);
1480
+ }
1481
+ });
1482
+
1483
+ return joinInlineParts(parts);
1484
+ }
1485
+
1486
+ function renderParagraph(element, context) {
1487
+ return renderInlineChildren(element, context);
1488
+ }
1489
+
1490
+ function renderHeading(element, context) {
1491
+ const level = Math.min(6, Math.max(1, Number.parseInt(getTagName(element).slice(1), 10) || 1));
1492
+ const content = renderInlineChildren(element, context);
1493
+ return content ? `${"#".repeat(level)} ${content}` : "";
1494
+ }
1495
+
1496
+ function renderCodeBlock(element) {
1497
+ const content = String(element.textContent || "").trimEnd();
1498
+ if (!content) {
1499
+ return "";
1500
+ }
1501
+
1502
+ return `\`\`\`\n${content.replace(/```/gu, "\\`\\`\\`")}\n\`\`\``;
1503
+ }
1504
+
1505
+ function renderBlockquote(element, context) {
1506
+ const content = renderBlockChildren(element, context);
1507
+ if (!content) {
1508
+ return "";
1509
+ }
1510
+
1511
+ return content
1512
+ .split("\n")
1513
+ .map((line) => `> ${line}`)
1514
+ .join("\n");
1515
+ }
1516
+
1517
+ function renderListItem(element, context, depth, index, ordered) {
1518
+ const includeListMarkers = context.options.includeListMarkers === true;
1519
+ const includeListIndentation = context.options.includeListIndentation !== false;
1520
+ const marker = includeListMarkers ? (ordered ? `${index + 1}.` : "-") : "";
1521
+ const indentation = includeListIndentation ? " ".repeat(Math.max(0, depth)) : "";
1522
+ const inlineParts = [];
1523
+ const nestedBlocks = [];
1524
+
1525
+ element.childNodes.forEach((childNode) => {
1526
+ if (isElementNode(childNode) && (getTagName(childNode) === "UL" || getTagName(childNode) === "OL")) {
1527
+ const nestedList = renderList(childNode, context, depth + 1);
1528
+ if (nestedList) {
1529
+ nestedBlocks.push(nestedList);
1530
+ }
1531
+ return;
1532
+ }
1533
+
1534
+ const renderedChild = renderInlineNode(childNode, context);
1535
+ if (renderedChild) {
1536
+ inlineParts.push(renderedChild);
1537
+ }
1538
+ });
1539
+
1540
+ const head = joinInlineParts(inlineParts);
1541
+ const linePrefix = marker ? `${indentation}${marker} ` : indentation;
1542
+ const lines = [`${linePrefix}${head || "(empty)"}`];
1543
+ nestedBlocks.forEach((nestedBlock) => {
1544
+ lines.push(indentBlock(nestedBlock, includeListIndentation ? 1 : 0));
1545
+ });
1546
+ return lines.join("\n");
1547
+ }
1548
+
1549
+ function renderList(element, context, depth = 0) {
1550
+ const ordered = getTagName(element) === "OL";
1551
+ return [...element.children]
1552
+ .filter((child) => getTagName(child) === "LI" && !isHiddenElement(child))
1553
+ .map((item, index) => renderListItem(item, context, depth, index, ordered))
1554
+ .filter(Boolean)
1555
+ .join("\n");
1556
+ }
1557
+
1558
+ function renderTableCell(element, context) {
1559
+ return renderInlineChildren(element, context);
1560
+ }
1561
+
1562
+ function renderTable(element, context) {
1563
+ const rows = [...element.querySelectorAll?.(":scope > thead > tr, :scope > tbody > tr, :scope > tr, :scope > tfoot > tr") || []]
1564
+ .filter((row) => getTagName(row) === "TR");
1565
+
1566
+ if (!rows.length) {
1567
+ return "";
1568
+ }
1569
+
1570
+ const renderedRows = rows.map((row) => {
1571
+ return [...row.children]
1572
+ .filter((cell) => ["TD", "TH"].includes(getTagName(cell)) && !isHiddenElement(cell))
1573
+ .map((cell) => renderTableCell(cell, context));
1574
+ }).filter((cells) => cells.length);
1575
+
1576
+ if (!renderedRows.length) {
1577
+ return "";
1578
+ }
1579
+
1580
+ const columnCount = Math.max(...renderedRows.map((cells) => cells.length));
1581
+ const normalizedRows = renderedRows.map((cells) => {
1582
+ const nextCells = cells.slice();
1583
+ while (nextCells.length < columnCount) {
1584
+ nextCells.push("");
1585
+ }
1586
+ return nextCells;
1587
+ });
1588
+
1589
+ const headerRow = normalizedRows[0];
1590
+ const separatorRow = headerRow.map(() => "---");
1591
+ const tableLines = [
1592
+ `| ${headerRow.join(" | ")} |`,
1593
+ `| ${separatorRow.join(" | ")} |`
1594
+ ];
1595
+
1596
+ normalizedRows.slice(1).forEach((row) => {
1597
+ tableLines.push(`| ${row.join(" | ")} |`);
1598
+ });
1599
+
1600
+ return tableLines.join("\n");
1601
+ }
1602
+
1603
+ function renderGenericContainer(element, context) {
1604
+ return renderBlockChildren(element, context);
1605
+ }
1606
+
1607
+ function renderElementAsBlock(element, context) {
1608
+ if (!isElementNode(element) || isHiddenElement(element)) {
1609
+ return "";
1610
+ }
1611
+
1612
+ if (isReferenceableElement(element)) {
1613
+ return renderReference(element, context);
1614
+ }
1615
+
1616
+ const tagName = getTagName(element);
1617
+
1618
+ if (tagName === "LABEL" && (element.getAttribute?.("for") || element.querySelector?.("input, textarea, select, button"))) {
1619
+ return "";
1620
+ }
1621
+
1622
+ if (/^H[1-6]$/u.test(tagName)) {
1623
+ return renderHeading(element, context);
1624
+ }
1625
+
1626
+ if (tagName === "P") {
1627
+ return renderParagraph(element, context);
1628
+ }
1629
+
1630
+ if (tagName === "PRE") {
1631
+ return renderCodeBlock(element);
1632
+ }
1633
+
1634
+ if (tagName === "BLOCKQUOTE") {
1635
+ return renderBlockquote(element, context);
1636
+ }
1637
+
1638
+ if (tagName === "UL" || tagName === "OL") {
1639
+ return renderList(element, context);
1640
+ }
1641
+
1642
+ if (tagName === "TABLE") {
1643
+ return renderTable(element, context);
1644
+ }
1645
+
1646
+ if (tagName === "HR") {
1647
+ return "---";
1648
+ }
1649
+
1650
+ return renderGenericContainer(element, context);
1651
+ }
1652
+
1653
+ function renderBlockChildren(element, context) {
1654
+ const blocks = [];
1655
+ const inlineParts = [];
1656
+
1657
+ const flushInlineParts = () => {
1658
+ const inlineText = joinInlineParts(inlineParts.splice(0, inlineParts.length));
1659
+ if (inlineText) {
1660
+ blocks.push(inlineText);
1661
+ }
1662
+ };
1663
+
1664
+ element.childNodes.forEach((childNode) => {
1665
+ if (isTextNode(childNode)) {
1666
+ const rawTextContent = normalizeText(childNode.textContent || "");
1667
+ if (shouldDropReadableText(rawTextContent)) {
1668
+ return;
1669
+ }
1670
+
1671
+ const textContent = escapeMarkdownText(rawTextContent);
1672
+ if (textContent) {
1673
+ inlineParts.push(textContent);
1674
+ }
1675
+ return;
1676
+ }
1677
+
1678
+ if (!isElementNode(childNode) || isHiddenElement(childNode)) {
1679
+ return;
1680
+ }
1681
+
1682
+ const renderedChild = renderElementAsBlock(childNode, context);
1683
+ if (!renderedChild) {
1684
+ return;
1685
+ }
1686
+
1687
+ if (isBlockElement(childNode) || isReferenceableElement(childNode)) {
1688
+ flushInlineParts();
1689
+ blocks.push(renderedChild);
1690
+ return;
1691
+ }
1692
+
1693
+ inlineParts.push(renderedChild);
1694
+ });
1695
+
1696
+ flushInlineParts();
1697
+ return joinBlocks(blocks);
1698
+ }
1699
+
1700
+ function createCaptureContext(payload = null) {
1701
+ return {
1702
+ entries: new Map(),
1703
+ nextReferenceId: 1,
1704
+ options: {
1705
+ includeLabelQuotes: normalizeIncludeLabelQuotes(payload),
1706
+ includeLinkUrls: normalizeIncludeLinkUrls(payload),
1707
+ includeSemanticTags: normalizeIncludeSemanticTags(payload),
1708
+ includeStateTags: normalizeIncludeStateTags(payload),
1709
+ includeListIndentation: normalizeIncludeListIndentation(payload),
1710
+ includeListMarkers: normalizeIncludeListMarkers(payload)
1711
+ },
1712
+ referenceIdsByElement: new WeakMap()
1713
+ };
1714
+ }
1715
+
1716
+ function resolveSelectorTargets(payload, doc = globalThis.document) {
1717
+ const selectors = normalizeSelectorList(payload);
1718
+ if (!selectors.length) {
1719
+ return {
1720
+ includeMetaData: true,
1721
+ items: [
1722
+ {
1723
+ key: "document",
1724
+ targets: [doc?.body || doc?.documentElement].filter(Boolean)
1725
+ }
1726
+ ]
1727
+ };
1728
+ }
1729
+
1730
+ return {
1731
+ includeMetaData: false,
1732
+ items: selectors.map((selector) => {
1733
+ let targets = [];
1734
+ try {
1735
+ targets = [...(doc?.querySelectorAll?.(selector) || [])];
1736
+ } catch (error) {
1737
+ throw createNamedError(
1738
+ "BrowserPageContentSelectorError",
1739
+ `Browser page content could not resolve selector "${selector}".`,
1740
+ {
1741
+ code: "browser_page_content_selector_error",
1742
+ details: {
1743
+ selector
1744
+ },
1745
+ cause: error
1746
+ }
1747
+ );
1748
+ }
1749
+
1750
+ return {
1751
+ key: selector,
1752
+ targets
1753
+ };
1754
+ })
1755
+ };
1756
+ }
1757
+
1758
+ function parseSnapshotFragment(html, parser) {
1759
+ return parser.parseFromString(
1760
+ `<!DOCTYPE html><html><body>${String(html || "")}</body></html>`,
1761
+ "text/html"
1762
+ );
1763
+ }
1764
+
1765
+ function renderSnapshotFragment(html, captureContext, parser) {
1766
+ const parsedDocument = parseSnapshotFragment(html, parser);
1767
+ const blocks = [];
1768
+ const inlineParts = [];
1769
+
1770
+ const flushInlineParts = () => {
1771
+ const inlineText = joinInlineParts(inlineParts.splice(0, inlineParts.length));
1772
+ if (inlineText) {
1773
+ blocks.push(inlineText);
1774
+ }
1775
+ };
1776
+
1777
+ parsedDocument.body.childNodes.forEach((childNode) => {
1778
+ if (isTextNode(childNode)) {
1779
+ const rawTextContent = normalizeText(childNode.textContent || "");
1780
+ if (shouldDropReadableText(rawTextContent)) {
1781
+ return;
1782
+ }
1783
+
1784
+ const textContent = escapeMarkdownText(rawTextContent);
1785
+ if (textContent) {
1786
+ inlineParts.push(textContent);
1787
+ }
1788
+ return;
1789
+ }
1790
+
1791
+ if (!isElementNode(childNode) || isHiddenElement(childNode)) {
1792
+ return;
1793
+ }
1794
+
1795
+ const renderedChild = renderElementAsBlock(childNode, captureContext);
1796
+ if (!renderedChild) {
1797
+ return;
1798
+ }
1799
+
1800
+ if (isBlockElement(childNode) || isReferenceableElement(childNode)) {
1801
+ flushInlineParts();
1802
+ blocks.push(renderedChild);
1803
+ return;
1804
+ }
1805
+
1806
+ inlineParts.push(renderedChild);
1807
+ });
1808
+
1809
+ flushInlineParts();
1810
+ return cleanReadableMarkdown(joinBlocks(blocks));
1811
+ }
1812
+
1813
+ function captureLive(payload = null) {
1814
+ const captureContext = createCaptureContext(payload);
1815
+ const resolvedTargets = resolveSelectorTargets(payload);
1816
+ const snapshot = {};
1817
+
1818
+ resolvedTargets.items.forEach((item) => {
1819
+ const blocks = [];
1820
+ if (resolvedTargets.includeMetaData && item.key === "document") {
1821
+ const meta = collectMetaLines(globalThis.document);
1822
+ if (meta) {
1823
+ blocks.push(meta);
1824
+ }
1825
+ }
1826
+
1827
+ item.targets.forEach((target) => {
1828
+ const renderedTarget = renderElementAsBlock(target, captureContext);
1829
+ if (renderedTarget) {
1830
+ blocks.push(renderedTarget);
1831
+ }
1832
+ });
1833
+
1834
+ snapshot[item.key] = cleanReadableMarkdown(joinBlocks(blocks));
1835
+ });
1836
+
1837
+ state.captureId += 1;
1838
+ state.capturedAt = Date.now();
1839
+ state.backend = "live";
1840
+ state.captureOptions = { ...captureContext.options };
1841
+ state.entries = captureContext.entries;
1842
+ return snapshot;
1843
+ }
1844
+
1845
+ async function captureWithDomHelper(payload = null) {
1846
+ const helper = requireDomHelper("capture content");
1847
+ const selectors = normalizeSelectorList(payload);
1848
+ const helperPayload = {
1849
+ snapshotMode: "content"
1850
+ };
1851
+ if (selectors.length) {
1852
+ helperPayload.selectors = selectors;
1853
+ }
1854
+ const documentSnapshot = await helper.captureDocument({
1855
+ ...helperPayload
1856
+ });
1857
+ const snapshot = {};
1858
+ const parser = new globalThis.DOMParser();
1859
+ const captureContext = createCaptureContext(payload);
1860
+ try {
1861
+ if (selectors.length && documentSnapshot?.targets && typeof documentSnapshot.targets === "object") {
1862
+ selectors.forEach((selector) => {
1863
+ snapshot[selector] = renderSnapshotFragment(documentSnapshot.targets?.[selector] || "", captureContext, parser);
1864
+ });
1865
+
1866
+ state.captureId += 1;
1867
+ state.capturedAt = Date.now();
1868
+ state.backend = "dom_helper";
1869
+ state.captureOptions = { ...captureContext.options };
1870
+ state.entries = captureContext.entries;
1871
+ return snapshot;
1872
+ }
1873
+
1874
+ const parsedDocument = parser.parseFromString(String(documentSnapshot?.html || ""), "text/html");
1875
+ const resolvedTargets = resolveSelectorTargets(payload, parsedDocument);
1876
+
1877
+ resolvedTargets.items.forEach((item) => {
1878
+ const blocks = [];
1879
+ if (resolvedTargets.includeMetaData && item.key === "document") {
1880
+ const meta = collectMetaLines(parsedDocument);
1881
+ if (meta) {
1882
+ blocks.push(meta);
1883
+ }
1884
+ }
1885
+
1886
+ item.targets.forEach((target) => {
1887
+ const renderedTarget = renderElementAsBlock(target, captureContext);
1888
+ if (renderedTarget) {
1889
+ blocks.push(renderedTarget);
1890
+ }
1891
+ });
1892
+
1893
+ snapshot[item.key] = cleanReadableMarkdown(joinBlocks(blocks));
1894
+ });
1895
+
1896
+ state.captureId += 1;
1897
+ state.capturedAt = Date.now();
1898
+ state.backend = "dom_helper";
1899
+ state.captureOptions = { ...captureContext.options };
1900
+ state.entries = captureContext.entries;
1901
+ return snapshot;
1902
+ } catch (error) {
1903
+ if (!isTrustedHtmlRequirementError(error)) {
1904
+ throw error;
1905
+ }
1906
+
1907
+ return captureLive(payload);
1908
+ }
1909
+ }
1910
+
1911
+ async function capture(payload = null) {
1912
+ if (getDomHelper()) {
1913
+ return captureWithDomHelper(payload);
1914
+ }
1915
+
1916
+ return captureLive(payload);
1917
+ }
1918
+
1919
+ function detailLive(entry) {
1920
+ const liveState = entry.connected && entry.element
1921
+ ? collectElementStateMetadata(entry.element, state.captureOptions)
1922
+ : entry.state || collectElementStateMetadata(null);
1923
+ return {
1924
+ captureId: state.captureId,
1925
+ capturedAt: state.capturedAt,
1926
+ connected: entry.connected,
1927
+ descriptorTags: liveState.descriptorTags,
1928
+ dom: entry.connected ? serializeElementSnapshot(entry.element) || entry.dom : entry.dom,
1929
+ referenceId: entry.referenceId,
1930
+ semanticTags: liveState.semanticTags,
1931
+ state: liveState,
1932
+ summary: entry.summary,
1933
+ tagName: entry.tagName
1934
+ };
1935
+ }
1936
+
1937
+ async function detail(referenceId) {
1938
+ const entry = requireReferenceEntry(referenceId, {
1939
+ actionLabel: "detail",
1940
+ requireConnected: false
1941
+ });
1942
+
1943
+ if (entry.helperBacked) {
1944
+ const helper = requireDomHelper("resolve detail");
1945
+ const resolvedDetail = await helper.detailNode(entry.frameChain, entry.nodeId);
1946
+ return {
1947
+ captureId: state.captureId,
1948
+ capturedAt: state.capturedAt,
1949
+ connected: resolvedDetail?.connected !== false,
1950
+ descriptorTags: Array.isArray(resolvedDetail?.descriptorTags) ? resolvedDetail.descriptorTags : (entry.descriptorTags || []),
1951
+ dom: String(resolvedDetail?.dom || entry.dom || ""),
1952
+ frameChain: entry.frameChain.slice(),
1953
+ frameId: entry.frameId,
1954
+ nodeId: entry.nodeId,
1955
+ referenceId: entry.referenceId,
1956
+ semanticTags: Array.isArray(resolvedDetail?.semanticTags) ? resolvedDetail.semanticTags : (entry.semanticTags || []),
1957
+ state: resolvedDetail?.state || entry.state || collectElementStateMetadata(null),
1958
+ summary: entry.summary,
1959
+ tagName: String(resolvedDetail?.tagName || entry.tagName || "")
1960
+ };
1961
+ }
1962
+
1963
+ return detailLive(entry);
1964
+ }
1965
+
1966
+ function requireReferenceEntry(referenceId, options = {}) {
1967
+ const normalizedReferenceId = normalizeReferenceId(referenceId);
1968
+ if (!normalizedReferenceId) {
1969
+ throw createNamedError(
1970
+ "BrowserPageContentReferenceError",
1971
+ "Browser page content requests require a reference id.",
1972
+ {
1973
+ code: "browser_page_content_reference_required",
1974
+ details: {
1975
+ action: String(options.actionLabel || "resolve")
1976
+ }
1977
+ }
1978
+ );
1979
+ }
1980
+
1981
+ if (!state.entries.size) {
1982
+ throw createNamedError(
1983
+ "BrowserPageContentReferenceError",
1984
+ `Browser page content has no reference capture for "${normalizedReferenceId}".`,
1985
+ {
1986
+ code: "browser_page_content_reference_missing_capture",
1987
+ details: {
1988
+ action: String(options.actionLabel || "resolve"),
1989
+ referenceId: normalizedReferenceId
1990
+ }
1991
+ }
1992
+ );
1993
+ }
1994
+
1995
+ const entry = state.entries.get(normalizedReferenceId);
1996
+ if (!entry) {
1997
+ throw createNamedError(
1998
+ "BrowserPageContentReferenceError",
1999
+ `Browser page content could not find reference "${normalizedReferenceId}".`,
2000
+ {
2001
+ code: "browser_page_content_reference_not_found",
2002
+ details: {
2003
+ action: String(options.actionLabel || "resolve"),
2004
+ referenceId: normalizedReferenceId
2005
+ }
2006
+ }
2007
+ );
2008
+ }
2009
+
2010
+ refreshReferenceEntry(entry);
2011
+
2012
+ if (options.requireConnected !== false && !entry.connected) {
2013
+ throw createNamedError(
2014
+ "BrowserPageContentReferenceError",
2015
+ `Browser page content reference "${normalizedReferenceId}" is no longer connected.`,
2016
+ {
2017
+ code: "browser_page_content_reference_disconnected",
2018
+ details: {
2019
+ action: String(options.actionLabel || "resolve"),
2020
+ referenceId: normalizedReferenceId
2021
+ }
2022
+ }
2023
+ );
2024
+ }
2025
+
2026
+ return entry;
2027
+ }
2028
+
2029
+ function refreshReferenceEntry(entry) {
2030
+ if (!entry || entry.helperBacked || !entry.element) {
2031
+ return entry;
2032
+ }
2033
+
2034
+ entry.connected = entry.element.isConnected !== false;
2035
+ if (entry.connected) {
2036
+ entry.dom = serializeElementSnapshot(entry.element) || entry.dom;
2037
+ entry.id = normalizeAttributeText(entry.element.getAttribute?.("id"));
2038
+ entry.name = normalizeAttributeText(entry.element.getAttribute?.("name"));
2039
+ const summaryData = collectReferenceSummaryData(entry.element, state.captureOptions);
2040
+ entry.descriptorTags = summaryData.descriptorTags;
2041
+ entry.kind = summaryData.kind;
2042
+ entry.semanticTags = summaryData.semanticTags;
2043
+ entry.state = summaryData.state;
2044
+ entry.summary = summaryData.summary;
2045
+ entry.tagName = getTagName(entry.element);
2046
+ }
2047
+
2048
+ return entry;
2049
+ }
2050
+
2051
+ function scrollElementIntoView(element) {
2052
+ try {
2053
+ element.scrollIntoView?.({
2054
+ behavior: "auto",
2055
+ block: "center",
2056
+ inline: "center"
2057
+ });
2058
+ return true;
2059
+ } catch {
2060
+ return false;
2061
+ }
2062
+ }
2063
+
2064
+ function focusElement(element) {
2065
+ try {
2066
+ element.focus?.({
2067
+ preventScroll: true
2068
+ });
2069
+ return true;
2070
+ } catch {
2071
+ try {
2072
+ element.focus?.();
2073
+ return true;
2074
+ } catch {
2075
+ return false;
2076
+ }
2077
+ }
2078
+ }
2079
+
2080
+ function describeActiveElement(element) {
2081
+ if (!isElementNode(element)) {
2082
+ return "";
2083
+ }
2084
+
2085
+ const tagName = getTagName(element).toLowerCase();
2086
+ const id = normalizeAttributeText(element.getAttribute?.("id"));
2087
+ const name = normalizeAttributeText(element.getAttribute?.("name"));
2088
+ const label = truncateText(getLabelText(element, {
2089
+ includeAlt: false,
2090
+ includeDescendantImageAlt: true,
2091
+ includePlaceholder: false,
2092
+ includeText: false
2093
+ }), 48);
2094
+ return [tagName, id ? `#${id}` : "", name ? `name=${name}` : "", label].filter(Boolean).join(" ");
2095
+ }
2096
+
2097
+ function getActionObservationRoot(element) {
2098
+ if (!isElementNode(element)) {
2099
+ return globalThis.document?.body || globalThis.document?.documentElement || null;
2100
+ }
2101
+
2102
+ return element.closest?.("form, fieldset, dialog, [role='dialog'], [role='alert'], [role='status'], [aria-live], article, section, main, li, tr, td, th")
2103
+ || element.parentElement
2104
+ || element;
2105
+ }
2106
+
2107
+ function getElementDirectText(element) {
2108
+ if (!isElementNode(element)) {
2109
+ return "";
2110
+ }
2111
+
2112
+ return normalizeText(
2113
+ [...(element.childNodes || [])]
2114
+ .filter((node) => isTextNode(node))
2115
+ .map((node) => node.textContent || "")
2116
+ .join(" ")
2117
+ );
2118
+ }
2119
+
2120
+ function collectNearbyTextEntries(root, limit = 24) {
2121
+ if (!isElementNode(root)) {
2122
+ return [];
2123
+ }
2124
+
2125
+ const entries = [];
2126
+ const seen = new Set();
2127
+ const acceptElement = (element) => {
2128
+ if (!isElementNode(element) || isHiddenElement(element) || entries.length >= limit) {
2129
+ return;
2130
+ }
2131
+
2132
+ const role = normalizeText(element.getAttribute?.("role")).toLowerCase();
2133
+ const directText = getElementDirectText(element);
2134
+ const fallbackText = ["alert", "status"].includes(role) || element.hasAttribute?.("aria-live")
2135
+ ? getElementText(element)
2136
+ : "";
2137
+ const text = truncateText(directText || fallbackText, 220);
2138
+ if (!text) {
2139
+ return;
2140
+ }
2141
+
2142
+ const key = `${role}|${text}`;
2143
+ if (seen.has(key)) {
2144
+ return;
2145
+ }
2146
+ seen.add(key);
2147
+ const state = collectElementStateMetadata(element, {
2148
+ includeSemanticTags: true,
2149
+ includeStateTags: true
2150
+ });
2151
+ entries.push({
2152
+ invalid: state.invalid === true,
2153
+ role,
2154
+ semanticTone: state.semanticTone || "",
2155
+ text
2156
+ });
2157
+ };
2158
+
2159
+ acceptElement(root);
2160
+ const walker = globalThis.document?.createTreeWalker?.(root, globalThis.NodeFilter?.SHOW_ELEMENT ?? 1);
2161
+ if (!walker) {
2162
+ return entries;
2163
+ }
2164
+
2165
+ let currentNode = walker.nextNode();
2166
+ while (currentNode && entries.length < limit) {
2167
+ acceptElement(currentNode);
2168
+ currentNode = walker.nextNode();
2169
+ }
2170
+
2171
+ return entries;
2172
+ }
2173
+
2174
+ function captureActionEffectSnapshot(element) {
2175
+ const observationRoot = getActionObservationRoot(element);
2176
+ return {
2177
+ activeElement: describeActiveElement(globalThis.document?.activeElement),
2178
+ observationRoot,
2179
+ observationText: truncateText(getElementText(observationRoot), 2000),
2180
+ targetDom: truncateText(serializeElementSnapshot(element), 2000),
2181
+ targetState: collectElementStateMetadata(element, {
2182
+ includeSemanticTags: true,
2183
+ includeStateTags: true
2184
+ }),
2185
+ textEntries: collectNearbyTextEntries(observationRoot),
2186
+ value: getReferenceValueMetadata(element)
2187
+ };
2188
+ }
2189
+
2190
+ async function waitForObservedActionWindow(observationRoot, {
2191
+ quietMs = 40,
2192
+ timeoutMs = 180
2193
+ } = {}) {
2194
+ const target = observationRoot?.ownerDocument?.body
2195
+ || observationRoot?.ownerDocument?.documentElement
2196
+ || globalThis.document?.body
2197
+ || globalThis.document?.documentElement;
2198
+ if (!target || typeof globalThis.MutationObserver !== "function") {
2199
+ await delayMs(timeoutMs);
2200
+ return {
2201
+ attributeNames: [],
2202
+ mutationCount: 0
2203
+ };
2204
+ }
2205
+
2206
+ const attributeNames = new Set();
2207
+ let lastMutationAt = 0;
2208
+ let mutationCount = 0;
2209
+ const observer = new globalThis.MutationObserver((mutations) => {
2210
+ mutationCount += mutations.length;
2211
+ lastMutationAt = Date.now();
2212
+ mutations.forEach((mutation) => {
2213
+ if (mutation.type === "attributes" && mutation.attributeName) {
2214
+ attributeNames.add(String(mutation.attributeName));
2215
+ }
2216
+ });
2217
+ });
2218
+
2219
+ try {
2220
+ observer.observe(target, {
2221
+ attributes: true,
2222
+ characterData: true,
2223
+ childList: true,
2224
+ subtree: true
2225
+ });
2226
+ const startedAt = Date.now();
2227
+ while (Date.now() - startedAt < timeoutMs) {
2228
+ await delayMs(20);
2229
+ if (mutationCount > 0 && Date.now() - lastMutationAt >= quietMs) {
2230
+ break;
2231
+ }
2232
+ }
2233
+ } finally {
2234
+ observer.disconnect();
2235
+ }
2236
+
2237
+ return {
2238
+ attributeNames: [...attributeNames],
2239
+ mutationCount
2240
+ };
2241
+ }
2242
+
2243
+ async function withObservedActionWindow(observationRoot, action, options = {}) {
2244
+ const target = observationRoot?.ownerDocument?.body
2245
+ || observationRoot?.ownerDocument?.documentElement
2246
+ || globalThis.document?.body
2247
+ || globalThis.document?.documentElement;
2248
+ if (!target || typeof globalThis.MutationObserver !== "function") {
2249
+ const result = await action();
2250
+ const observedMutations = await waitForObservedActionWindow(observationRoot, options);
2251
+ return {
2252
+ observedMutations,
2253
+ result
2254
+ };
2255
+ }
2256
+
2257
+ const attributeNames = new Set();
2258
+ let lastMutationAt = 0;
2259
+ let mutationCount = 0;
2260
+ const observer = new globalThis.MutationObserver((mutations) => {
2261
+ mutationCount += mutations.length;
2262
+ lastMutationAt = Date.now();
2263
+ mutations.forEach((mutation) => {
2264
+ if (mutation.type === "attributes" && mutation.attributeName) {
2265
+ attributeNames.add(String(mutation.attributeName));
2266
+ }
2267
+ });
2268
+ });
2269
+
2270
+ try {
2271
+ observer.observe(target, {
2272
+ attributes: true,
2273
+ characterData: true,
2274
+ childList: true,
2275
+ subtree: true
2276
+ });
2277
+ const result = await action();
2278
+ const quietMs = Math.max(0, Number(options.quietMs) || 40);
2279
+ const timeoutMs = Math.max(0, Number(options.timeoutMs) || 180);
2280
+ const startedAt = Date.now();
2281
+ while (Date.now() - startedAt < timeoutMs) {
2282
+ await delayMs(20);
2283
+ if (mutationCount > 0 && Date.now() - lastMutationAt >= quietMs) {
2284
+ break;
2285
+ }
2286
+ }
2287
+ return {
2288
+ observedMutations: {
2289
+ attributeNames: [...attributeNames],
2290
+ mutationCount
2291
+ },
2292
+ result
2293
+ };
2294
+ } finally {
2295
+ observer.disconnect();
2296
+ }
2297
+ }
2298
+
2299
+ function compareDescriptorTags(beforeTags = [], afterTags = []) {
2300
+ const beforeValue = beforeTags.filter(Boolean).join("|");
2301
+ const afterValue = afterTags.filter(Boolean).join("|");
2302
+ return beforeValue !== afterValue;
2303
+ }
2304
+
2305
+ function buildActionEffectResult(entry, beforeSnapshot, afterSnapshot, observedMutations, extra = {}) {
2306
+ const newTextEntries = afterSnapshot.textEntries.filter((entryData) => {
2307
+ return !beforeSnapshot.textEntries.some((beforeEntry) => beforeEntry.text === entryData.text);
2308
+ });
2309
+ const validationEntries = newTextEntries.filter((entryData) => {
2310
+ return entryData.invalid
2311
+ || ["alert", "status"].includes(entryData.role)
2312
+ || ["error", "warning"].includes(entryData.semanticTone);
2313
+ });
2314
+ const focusChanged = beforeSnapshot.activeElement !== afterSnapshot.activeElement;
2315
+ const nearbyTextChanged = beforeSnapshot.observationText !== afterSnapshot.observationText;
2316
+ const valueChanged = beforeSnapshot.value !== afterSnapshot.value;
2317
+ const checkedChanged = beforeSnapshot.targetState.checked !== afterSnapshot.targetState.checked;
2318
+ const selectedChanged = beforeSnapshot.targetState.selected !== afterSnapshot.targetState.selected;
2319
+ const expandedChanged = beforeSnapshot.targetState.expanded !== afterSnapshot.targetState.expanded;
2320
+ const pressedChanged = beforeSnapshot.targetState.pressed !== afterSnapshot.targetState.pressed;
2321
+ const descriptorChanged = compareDescriptorTags(beforeSnapshot.targetState.descriptorTags, afterSnapshot.targetState.descriptorTags);
2322
+ const targetDomChanged = beforeSnapshot.targetDom !== afterSnapshot.targetDom;
2323
+ const domChanged = Boolean(observedMutations.mutationCount) || targetDomChanged || nearbyTextChanged;
2324
+ const status = {
2325
+ alertTextAdded: newTextEntries.some((entryData) => ["alert", "status"].includes(entryData.role)),
2326
+ checkedChanged,
2327
+ descriptorChanged,
2328
+ domChanged,
2329
+ expandedChanged,
2330
+ focusChanged,
2331
+ nearbyTextChanged,
2332
+ pressedChanged,
2333
+ reacted: false,
2334
+ selectedChanged,
2335
+ targetChanged: descriptorChanged || targetDomChanged || valueChanged || checkedChanged || selectedChanged || expandedChanged || pressedChanged,
2336
+ targetDomChanged,
2337
+ valueChanged,
2338
+ validationTextAdded: validationEntries.length > 0
2339
+ };
2340
+ status.reacted = Object.entries(status).some(([key, value]) => key !== "reacted" && value === true);
2341
+ status.noObservedEffect = !status.reacted;
2342
+
2343
+ return {
2344
+ ...extra,
2345
+ descriptorTags: afterSnapshot.targetState.descriptorTags.slice(),
2346
+ effect: {
2347
+ mutationAttributes: observedMutations.attributeNames.slice(0, 8),
2348
+ mutationCount: observedMutations.mutationCount,
2349
+ newText: newTextEntries.map((entryData) => entryData.text).slice(0, 3),
2350
+ semanticHints: [...new Set(newTextEntries.map((entryData) => entryData.semanticTone).filter(Boolean))].slice(0, 3),
2351
+ validationText: validationEntries.map((entryData) => entryData.text).slice(0, 3)
2352
+ },
2353
+ semanticTags: afterSnapshot.targetState.semanticTags.slice(),
2354
+ state: afterSnapshot.targetState,
2355
+ status
2356
+ };
2357
+ }
2358
+
2359
+ function buildActionResult(entry, extra = {}) {
2360
+ return {
2361
+ captureId: state.captureId,
2362
+ descriptorTags: Array.isArray(entry?.descriptorTags) ? entry.descriptorTags.slice() : [],
2363
+ referenceId: entry.referenceId,
2364
+ semanticTags: Array.isArray(entry?.semanticTags) ? entry.semanticTags.slice() : [],
2365
+ state: entry.state || collectElementStateMetadata(entry.element, state.captureOptions),
2366
+ summary: entry.summary,
2367
+ tagName: entry.tagName,
2368
+ ...extra
2369
+ };
2370
+ }
2371
+
2372
+ function buildHelperBackedActionResult(entry, helperResult, extra = {}) {
2373
+ return {
2374
+ captureId: state.captureId,
2375
+ descriptorTags: Array.isArray(helperResult?.descriptorTags) ? helperResult.descriptorTags : (entry.descriptorTags || []),
2376
+ frameChain: entry.frameChain.slice(),
2377
+ frameId: entry.frameId,
2378
+ nodeId: entry.nodeId,
2379
+ referenceId: entry.referenceId,
2380
+ semanticTags: Array.isArray(helperResult?.semanticTags) ? helperResult.semanticTags : (entry.semanticTags || []),
2381
+ state: helperResult?.state || entry.state || collectElementStateMetadata(null),
2382
+ summary: entry.summary,
2383
+ tagName: String(helperResult?.tagName || entry.tagName || ""),
2384
+ ...extra
2385
+ };
2386
+ }
2387
+
2388
+ function mergeActionOutcomeResults(...results) {
2389
+ const normalizedResults = results.filter(Boolean);
2390
+ const mergedStatus = {};
2391
+ const mergedEffect = {
2392
+ mutationAttributes: [],
2393
+ mutationCount: 0,
2394
+ newText: [],
2395
+ semanticHints: [],
2396
+ validationText: []
2397
+ };
2398
+
2399
+ normalizedResults.forEach((result) => {
2400
+ Object.entries(result?.status || {}).forEach(([key, value]) => {
2401
+ if (typeof value === "boolean") {
2402
+ mergedStatus[key] = mergedStatus[key] === true || value === true;
2403
+ }
2404
+ });
2405
+ if (Number.isFinite(result?.effect?.mutationCount)) {
2406
+ mergedEffect.mutationCount += Number(result.effect.mutationCount);
2407
+ }
2408
+ ["mutationAttributes", "newText", "semanticHints", "validationText"].forEach((key) => {
2409
+ const values = Array.isArray(result?.effect?.[key]) ? result.effect[key] : [];
2410
+ values.forEach((value) => {
2411
+ if (value && !mergedEffect[key].includes(value)) {
2412
+ mergedEffect[key].push(value);
2413
+ }
2414
+ });
2415
+ });
2416
+ });
2417
+
2418
+ mergedStatus.reacted = Object.entries(mergedStatus).some(([key, value]) => key !== "reacted" && key !== "noObservedEffect" && value === true);
2419
+ mergedStatus.noObservedEffect = !mergedStatus.reacted;
2420
+ return {
2421
+ effect: mergedEffect,
2422
+ status: mergedStatus
2423
+ };
2424
+ }
2425
+
2426
+ function dispatchDomEvent(target, eventName, EventType = "Event", options = {}) {
2427
+ const EventConstructor = typeof globalThis[EventType] === "function"
2428
+ ? globalThis[EventType]
2429
+ : globalThis.Event;
2430
+ const event = new EventConstructor(eventName, {
2431
+ bubbles: true,
2432
+ cancelable: true,
2433
+ composed: true,
2434
+ ...options
2435
+ });
2436
+ target.dispatchEvent(event);
2437
+ return event;
2438
+ }
2439
+
2440
+ function dispatchKeyboardEvent(target, eventName, options = {}) {
2441
+ const KeyboardEventConstructor = typeof globalThis.KeyboardEvent === "function"
2442
+ ? globalThis.KeyboardEvent
2443
+ : globalThis.Event;
2444
+ const event = new KeyboardEventConstructor(eventName, {
2445
+ bubbles: true,
2446
+ cancelable: true,
2447
+ composed: true,
2448
+ code: "Enter",
2449
+ key: "Enter",
2450
+ ...options
2451
+ });
2452
+
2453
+ [
2454
+ ["charCode", Number(options.charCode ?? 0)],
2455
+ ["keyCode", Number(options.keyCode ?? 13)],
2456
+ ["which", Number(options.which ?? 13)]
2457
+ ].forEach(([propertyName, propertyValue]) => {
2458
+ try {
2459
+ if (typeof event[propertyName] !== "number") {
2460
+ Object.defineProperty(event, propertyName, {
2461
+ configurable: true,
2462
+ enumerable: true,
2463
+ value: propertyValue
2464
+ });
2465
+ }
2466
+ } catch {
2467
+ // Ignore read-only KeyboardEvent properties.
2468
+ }
2469
+ });
2470
+
2471
+ target.dispatchEvent(event);
2472
+ return event;
2473
+ }
2474
+
2475
+ function setNativeValue(element, nextValue) {
2476
+ const tagName = getTagName(element);
2477
+ const normalizedValue = String(nextValue ?? "");
2478
+
2479
+ if (tagName === "INPUT") {
2480
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLInputElement?.prototype || {}, "value");
2481
+ if (typeof descriptor?.set === "function") {
2482
+ descriptor.set.call(element, normalizedValue);
2483
+ } else {
2484
+ element.value = normalizedValue;
2485
+ }
2486
+ return normalizedValue;
2487
+ }
2488
+
2489
+ if (tagName === "TEXTAREA") {
2490
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLTextAreaElement?.prototype || {}, "value");
2491
+ if (typeof descriptor?.set === "function") {
2492
+ descriptor.set.call(element, normalizedValue);
2493
+ } else {
2494
+ element.value = normalizedValue;
2495
+ }
2496
+ return normalizedValue;
2497
+ }
2498
+
2499
+ if (tagName === "SELECT") {
2500
+ const matchedOption = [...(element.options || [])].find((option) => {
2501
+ return option.value === normalizedValue
2502
+ || normalizeText(option.textContent || "") === normalizeText(normalizedValue)
2503
+ || normalizeText(option.label || "") === normalizeText(normalizedValue);
2504
+ });
2505
+
2506
+ const resolvedValue = matchedOption ? matchedOption.value : normalizedValue;
2507
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLSelectElement?.prototype || {}, "value");
2508
+ if (typeof descriptor?.set === "function") {
2509
+ descriptor.set.call(element, resolvedValue);
2510
+ } else {
2511
+ element.value = resolvedValue;
2512
+ }
2513
+ return resolvedValue;
2514
+ }
2515
+
2516
+ if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
2517
+ element.textContent = normalizedValue;
2518
+ return normalizedValue;
2519
+ }
2520
+
2521
+ throw createNamedError(
2522
+ "BrowserPageContentActionError",
2523
+ `Browser page content cannot type into <${getTagName(element).toLowerCase()}>.`,
2524
+ {
2525
+ code: "browser_page_content_type_unsupported"
2526
+ }
2527
+ );
2528
+ }
2529
+
2530
+ async function updateElementValue(referenceId, value) {
2531
+ const entry = requireReferenceEntry(referenceId, {
2532
+ actionLabel: "type"
2533
+ });
2534
+
2535
+ if (entry.helperBacked) {
2536
+ const helper = requireDomHelper("type into reference");
2537
+ const typedResult = await helper.typeNode(entry.frameChain, entry.nodeId, value);
2538
+ return buildHelperBackedActionResult(entry, typedResult, {
2539
+ effect: typedResult?.effect || {},
2540
+ status: typedResult?.status || {},
2541
+ value: typedResult?.value ?? String(value ?? "")
2542
+ });
2543
+ }
2544
+
2545
+ const element = entry.element;
2546
+ const beforeSnapshot = captureActionEffectSnapshot(element);
2547
+
2548
+ const {
2549
+ result: appliedValue,
2550
+ observedMutations
2551
+ } = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
2552
+ scrollElementIntoView(element);
2553
+ focusElement(element);
2554
+ const nextValue = setNativeValue(element, value);
2555
+
2556
+ if (typeof element.setSelectionRange === "function") {
2557
+ try {
2558
+ element.setSelectionRange(String(nextValue).length, String(nextValue).length);
2559
+ } catch {
2560
+ // Ignore selection errors for unsupported input types.
2561
+ }
2562
+ }
2563
+
2564
+ dispatchDomEvent(element, "beforeinput", "InputEvent", {
2565
+ data: String(value ?? ""),
2566
+ inputType: "insertText"
2567
+ });
2568
+ dispatchDomEvent(element, "input", "InputEvent", {
2569
+ data: String(value ?? ""),
2570
+ inputType: "insertText"
2571
+ });
2572
+ dispatchDomEvent(element, "change");
2573
+ return nextValue;
2574
+ });
2575
+
2576
+ refreshReferenceEntry(entry);
2577
+ return buildActionResult(entry, {
2578
+ ...buildActionEffectResult(entry, beforeSnapshot, captureActionEffectSnapshot(element), observedMutations),
2579
+ value: appliedValue
2580
+ });
2581
+ }
2582
+
2583
+ async function activateElement(referenceId) {
2584
+ const entry = requireReferenceEntry(referenceId, {
2585
+ actionLabel: "click"
2586
+ });
2587
+
2588
+ if (entry.helperBacked) {
2589
+ const helper = requireDomHelper("click reference");
2590
+ const clickedResult = await helper.clickNode(entry.frameChain, entry.nodeId);
2591
+ return buildHelperBackedActionResult(entry, clickedResult, {
2592
+ effect: clickedResult?.effect || {},
2593
+ status: clickedResult?.status || {}
2594
+ });
2595
+ }
2596
+
2597
+ const element = entry.element;
2598
+ const beforeSnapshot = captureActionEffectSnapshot(element);
2599
+
2600
+ scrollElementIntoView(element);
2601
+ focusElement(element);
2602
+
2603
+ if (beforeSnapshot.targetState.disabled) {
2604
+ throw createNamedError(
2605
+ "BrowserPageContentActionError",
2606
+ `Browser page content reference "${entry.referenceId}" is disabled.`,
2607
+ {
2608
+ code: "browser_page_content_click_disabled"
2609
+ }
2610
+ );
2611
+ }
2612
+
2613
+ const {
2614
+ observedMutations
2615
+ } = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
2616
+ if (typeof element.click === "function") {
2617
+ element.click();
2618
+ } else {
2619
+ dispatchDomEvent(element, "click", "MouseEvent", {
2620
+ button: 0
2621
+ });
2622
+ }
2623
+ });
2624
+
2625
+ refreshReferenceEntry(entry);
2626
+ return buildActionResult(entry, buildActionEffectResult(
2627
+ entry,
2628
+ beforeSnapshot,
2629
+ captureActionEffectSnapshot(element),
2630
+ observedMutations
2631
+ ));
2632
+ }
2633
+
2634
+ async function submitElement(referenceId) {
2635
+ const entry = requireReferenceEntry(referenceId, {
2636
+ actionLabel: "submit"
2637
+ });
2638
+
2639
+ if (entry.helperBacked) {
2640
+ const helper = requireDomHelper("submit reference");
2641
+ const submittedResult = await helper.submitNode(entry.frameChain, entry.nodeId);
2642
+ return buildHelperBackedActionResult(entry, submittedResult, {
2643
+ effect: submittedResult?.effect || {},
2644
+ status: submittedResult?.status || {}
2645
+ });
2646
+ }
2647
+
2648
+ const element = entry.element;
2649
+ const tagName = getTagName(element);
2650
+ const beforeSnapshot = captureActionEffectSnapshot(element);
2651
+
2652
+ const {
2653
+ observedMutations
2654
+ } = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
2655
+ scrollElementIntoView(element);
2656
+ focusElement(element);
2657
+
2658
+ if (tagName === "FORM") {
2659
+ if (typeof element.requestSubmit === "function") {
2660
+ element.requestSubmit();
2661
+ } else {
2662
+ const submitEvent = dispatchDomEvent(element, "submit");
2663
+ if (!submitEvent.defaultPrevented) {
2664
+ element.submit?.();
2665
+ }
2666
+ }
2667
+ } else if (typeof element.form?.requestSubmit === "function") {
2668
+ if (tagName === "BUTTON" || tagName === "INPUT") {
2669
+ element.form.requestSubmit(element);
2670
+ } else {
2671
+ element.form.requestSubmit();
2672
+ }
2673
+ } else if (element.form) {
2674
+ const submitEvent = dispatchDomEvent(element.form, "submit");
2675
+ if (!submitEvent.defaultPrevented) {
2676
+ element.form.submit?.();
2677
+ }
2678
+ } else if (typeof element.click === "function") {
2679
+ element.click();
2680
+ } else {
2681
+ throw createNamedError(
2682
+ "BrowserPageContentActionError",
2683
+ `Browser page content cannot submit reference "${entry.referenceId}".`,
2684
+ {
2685
+ code: "browser_page_content_submit_unsupported"
2686
+ }
2687
+ );
2688
+ }
2689
+ });
2690
+
2691
+ refreshReferenceEntry(entry);
2692
+ return buildActionResult(entry, buildActionEffectResult(
2693
+ entry,
2694
+ beforeSnapshot,
2695
+ captureActionEffectSnapshot(element),
2696
+ observedMutations
2697
+ ));
2698
+ }
2699
+
2700
+ function shouldEnterSubmitForm(element) {
2701
+ const tagName = getTagName(element);
2702
+ if (tagName !== "INPUT") {
2703
+ return false;
2704
+ }
2705
+
2706
+ const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
2707
+ return ![
2708
+ "button",
2709
+ "checkbox",
2710
+ "color",
2711
+ "file",
2712
+ "hidden",
2713
+ "image",
2714
+ "radio",
2715
+ "range",
2716
+ "reset",
2717
+ "submit"
2718
+ ].includes(inputType);
2719
+ }
2720
+
2721
+ async function pressEnterElement(referenceId, actionLabel = "type_submit") {
2722
+ const entry = requireReferenceEntry(referenceId, {
2723
+ actionLabel
2724
+ });
2725
+
2726
+ if (entry.helperBacked) {
2727
+ const helper = requireDomHelper("press enter on reference");
2728
+ const submittedResult = await helper.typeSubmitNode(entry.frameChain, entry.nodeId, "");
2729
+ return buildHelperBackedActionResult(entry, submittedResult, {
2730
+ effect: submittedResult?.effect || {},
2731
+ status: submittedResult?.status || {}
2732
+ });
2733
+ }
2734
+
2735
+ const element = entry.element;
2736
+ const beforeSnapshot = captureActionEffectSnapshot(element);
2737
+
2738
+ const {
2739
+ observedMutations
2740
+ } = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
2741
+ scrollElementIntoView(element);
2742
+ focusElement(element);
2743
+
2744
+ const keydownEvent = dispatchKeyboardEvent(element, "keydown", {
2745
+ charCode: 0,
2746
+ keyCode: 13,
2747
+ which: 13
2748
+ });
2749
+ const keypressEvent = dispatchKeyboardEvent(element, "keypress", {
2750
+ charCode: 13,
2751
+ keyCode: 13,
2752
+ which: 13
2753
+ });
2754
+ const keyupEvent = dispatchKeyboardEvent(element, "keyup", {
2755
+ charCode: 0,
2756
+ keyCode: 13,
2757
+ which: 13
2758
+ });
2759
+
2760
+ if (
2761
+ !keydownEvent.defaultPrevented
2762
+ && !keypressEvent.defaultPrevented
2763
+ && !keyupEvent.defaultPrevented
2764
+ && shouldEnterSubmitForm(element)
2765
+ ) {
2766
+ if (typeof element.form?.requestSubmit === "function") {
2767
+ element.form.requestSubmit();
2768
+ } else if (element.form) {
2769
+ const submitEvent = dispatchDomEvent(element.form, "submit");
2770
+ if (!submitEvent.defaultPrevented) {
2771
+ element.form.submit?.();
2772
+ }
2773
+ }
2774
+ }
2775
+ });
2776
+
2777
+ refreshReferenceEntry(entry);
2778
+ return buildActionResult(entry, buildActionEffectResult(
2779
+ entry,
2780
+ beforeSnapshot,
2781
+ captureActionEffectSnapshot(element),
2782
+ observedMutations
2783
+ ));
2784
+ }
2785
+
2786
+ async function typeAndSubmit(referenceId, value) {
2787
+ const entry = requireReferenceEntry(referenceId, {
2788
+ actionLabel: "type_submit"
2789
+ });
2790
+
2791
+ if (entry.helperBacked) {
2792
+ const helper = requireDomHelper("type and submit reference");
2793
+ const submittedResult = await helper.typeSubmitNode(entry.frameChain, entry.nodeId, value);
2794
+ return buildHelperBackedActionResult(entry, submittedResult, {
2795
+ effect: submittedResult?.effect || {},
2796
+ status: submittedResult?.status || {},
2797
+ value: submittedResult?.value ?? String(value ?? "")
2798
+ });
2799
+ }
2800
+
2801
+ const typed = await updateElementValue(referenceId, value);
2802
+ const submitted = await pressEnterElement(referenceId);
2803
+ const mergedOutcome = mergeActionOutcomeResults(typed, submitted);
2804
+
2805
+ return {
2806
+ ...submitted,
2807
+ ...mergedOutcome,
2808
+ value: typed.value
2809
+ };
2810
+ }
2811
+
2812
+ async function scrollToReference(referenceId) {
2813
+ const entry = requireReferenceEntry(referenceId, {
2814
+ actionLabel: "scroll"
2815
+ });
2816
+
2817
+ if (entry.helperBacked) {
2818
+ const helper = requireDomHelper("scroll to reference");
2819
+ const scrollResult = await helper.scrollNode(entry.frameChain, entry.nodeId);
2820
+ return buildHelperBackedActionResult(entry, scrollResult, {
2821
+ effect: scrollResult?.effect || {},
2822
+ status: scrollResult?.status || {}
2823
+ });
2824
+ }
2825
+
2826
+ const beforeSnapshot = captureActionEffectSnapshot(entry.element);
2827
+ scrollElementIntoView(entry.element);
2828
+ focusElement(entry.element);
2829
+ refreshReferenceEntry(entry);
2830
+ const afterSnapshot = captureActionEffectSnapshot(entry.element);
2831
+ const scrollEffect = buildActionEffectResult(entry, beforeSnapshot, afterSnapshot, {
2832
+ attributeNames: [],
2833
+ mutationCount: 0
2834
+ });
2835
+ return buildActionResult(entry, {
2836
+ ...scrollEffect,
2837
+ status: {
2838
+ ...scrollEffect.status,
2839
+ reacted: true,
2840
+ noObservedEffect: false
2841
+ }
2842
+ });
2843
+ }
2844
+
2845
+ globalThis[GLOBAL_KEY] = {
2846
+ click(referenceId) {
2847
+ return activateElement(referenceId);
2848
+ },
2849
+ capture,
2850
+ clear() {
2851
+ state.captureId = 0;
2852
+ state.capturedAt = 0;
2853
+ state.captureOptions = {
2854
+ includeLabelQuotes: false,
2855
+ includeLinkUrls: false,
2856
+ includeSemanticTags: true,
2857
+ includeStateTags: true,
2858
+ includeListIndentation: true,
2859
+ includeListMarkers: false
2860
+ };
2861
+ state.entries = new Map();
2862
+ },
2863
+ detail,
2864
+ getState() {
2865
+ return {
2866
+ captureId: state.captureId,
2867
+ capturedAt: state.capturedAt,
2868
+ includeLabelQuotes: state.captureOptions.includeLabelQuotes === true,
2869
+ includeLinkUrls: state.captureOptions.includeLinkUrls === true,
2870
+ includeSemanticTags: state.captureOptions.includeSemanticTags !== false,
2871
+ includeStateTags: state.captureOptions.includeStateTags !== false,
2872
+ includeListIndentation: state.captureOptions.includeListIndentation !== false,
2873
+ includeListMarkers: state.captureOptions.includeListMarkers === true,
2874
+ referenceCount: state.entries.size
2875
+ };
2876
+ },
2877
+ scroll(referenceId) {
2878
+ return scrollToReference(referenceId);
2879
+ },
2880
+ submit(referenceId) {
2881
+ return submitElement(referenceId);
2882
+ },
2883
+ type(referenceId, value) {
2884
+ return updateElementValue(referenceId, value);
2885
+ },
2886
+ typeSubmit(referenceId, value) {
2887
+ return typeAndSubmit(referenceId, value);
2888
+ },
2889
+ version: VERSION
2890
+ };
2891
+})();