@canaryai/cli 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +77 -92
  2. package/dist/chunk-C2PGZRYK.js +167 -0
  3. package/dist/chunk-C2PGZRYK.js.map +1 -0
  4. package/dist/{chunk-TEHABH2E.js → chunk-LC7ZVXPH.js} +2 -2
  5. package/dist/{chunk-6WWHXWCS.js → chunk-QLFSJG5O.js} +33 -5
  6. package/dist/chunk-QLFSJG5O.js.map +1 -0
  7. package/dist/{chunk-RL5Y6V3C.js → chunk-XGO62PO2.js} +2443 -1073
  8. package/dist/chunk-XGO62PO2.js.map +1 -0
  9. package/dist/{debug-workflow-ZFRF3JMY.js → debug-workflow-I3F36JBL.js} +57 -36
  10. package/dist/debug-workflow-I3F36JBL.js.map +1 -0
  11. package/dist/{docs-RPFT7ZJB.js → docs-REHST3YB.js} +2 -2
  12. package/dist/{feature-flag-2FDSKOVX.js → feature-flag-3HB5NTMY.js} +3 -2
  13. package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-3HB5NTMY.js.map} +1 -1
  14. package/dist/index.js +22 -9
  15. package/dist/index.js.map +1 -1
  16. package/dist/{issues-6ZDNDSD6.js → issues-YU57CHXS.js} +3 -2
  17. package/dist/{issues-6ZDNDSD6.js.map → issues-YU57CHXS.js.map} +1 -1
  18. package/dist/{knobs-MZRTYS3P.js → knobs-QJ4IBLCT.js} +3 -2
  19. package/dist/{knobs-MZRTYS3P.js.map → knobs-QJ4IBLCT.js.map} +1 -1
  20. package/dist/{local-browser-I2ANCFFH.js → local-browser-MKTJ36KY.js} +3 -3
  21. package/dist/{mcp-EOWUKFEB.js → mcp-ZOKM2AUE.js} +49 -238
  22. package/dist/mcp-ZOKM2AUE.js.map +1 -0
  23. package/dist/{record-TSF726OB.js → record-TNDBT3NY.js} +130 -28
  24. package/dist/record-TNDBT3NY.js.map +1 -0
  25. package/dist/session-RNLKFS2Z.js +751 -0
  26. package/dist/session-RNLKFS2Z.js.map +1 -0
  27. package/dist/skill-CZ7SHI3P.js +156 -0
  28. package/dist/skill-CZ7SHI3P.js.map +1 -0
  29. package/dist/{src-SCKO6YUB.js → src-2WSMYBMJ.js} +20 -2
  30. package/package.json +2 -2
  31. package/dist/chunk-6WWHXWCS.js.map +0 -1
  32. package/dist/chunk-RL5Y6V3C.js.map +0 -1
  33. package/dist/debug-workflow-ZFRF3JMY.js.map +0 -1
  34. package/dist/mcp-EOWUKFEB.js.map +0 -1
  35. package/dist/record-TSF726OB.js.map +0 -1
  36. /package/dist/{chunk-TEHABH2E.js.map → chunk-LC7ZVXPH.js.map} +0 -0
  37. /package/dist/{docs-RPFT7ZJB.js.map → docs-REHST3YB.js.map} +0 -0
  38. /package/dist/{local-browser-I2ANCFFH.js.map → local-browser-MKTJ36KY.js.map} +0 -0
  39. /package/dist/{src-SCKO6YUB.js.map → src-2WSMYBMJ.js.map} +0 -0
@@ -119,7 +119,7 @@ var require_omggif = __commonJS({
119
119
  var min_code_size = 0;
120
120
  while (num_colors >>= 1) ++min_code_size;
121
121
  num_colors = 1 << min_code_size;
122
- var delay = opts.delay === void 0 ? 0 : opts.delay;
122
+ var delay2 = opts.delay === void 0 ? 0 : opts.delay;
123
123
  var disposal = opts.disposal === void 0 ? 0 : opts.disposal;
124
124
  if (disposal < 0 || disposal > 3)
125
125
  throw new Error("Disposal out of range.");
@@ -131,13 +131,13 @@ var require_omggif = __commonJS({
131
131
  if (transparent_index < 0 || transparent_index >= num_colors)
132
132
  throw new Error("Transparent color index.");
133
133
  }
134
- if (disposal !== 0 || use_transparency || delay !== 0) {
134
+ if (disposal !== 0 || use_transparency || delay2 !== 0) {
135
135
  buf[p++] = 33;
136
136
  buf[p++] = 249;
137
137
  buf[p++] = 4;
138
138
  buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0);
139
- buf[p++] = delay & 255;
140
- buf[p++] = delay >> 8 & 255;
139
+ buf[p++] = delay2 & 255;
140
+ buf[p++] = delay2 >> 8 & 255;
141
141
  buf[p++] = transparent_index;
142
142
  buf[p++] = 0;
143
143
  }
@@ -279,7 +279,7 @@ var require_omggif = __commonJS({
279
279
  }
280
280
  var no_eof = true;
281
281
  var frames = [];
282
- var delay = 0;
282
+ var delay2 = 0;
283
283
  var transparent_index = null;
284
284
  var disposal = 0;
285
285
  var loop_count = null;
@@ -311,7 +311,7 @@ var require_omggif = __commonJS({
311
311
  if (buf[p++] !== 4 || buf[p + 4] !== 0)
312
312
  throw new Error("Invalid graphics extension block.");
313
313
  var pf1 = buf[p++];
314
- delay = buf[p++] | buf[p++] << 8;
314
+ delay2 = buf[p++] | buf[p++] << 8;
315
315
  transparent_index = buf[p++];
316
316
  if ((pf1 & 1) === 0) transparent_index = null;
317
317
  disposal = pf1 >> 2 & 7;
@@ -370,7 +370,7 @@ var require_omggif = __commonJS({
370
370
  data_length: p - data_offset,
371
371
  transparent_index,
372
372
  interlaced: !!interlace_flag,
373
- delay,
373
+ delay: delay2,
374
374
  disposal
375
375
  });
376
376
  break;
@@ -3777,7 +3777,7 @@ var require_gifframe = __commonJS({
3777
3777
  var require_gifutil = __commonJS({
3778
3778
  "../../node_modules/.bun/gifwrap@0.10.1/node_modules/gifwrap/src/gifutil.js"(exports2) {
3779
3779
  "use strict";
3780
- var fs4 = __require("fs");
3780
+ var fs5 = __require("fs");
3781
3781
  var ImageQ = require_image_q();
3782
3782
  var BitmapImage2 = require_bitmapimage();
3783
3783
  var { GifFrame: GifFrame2 } = require_gifframe();
@@ -3892,14 +3892,14 @@ var require_gifutil = __commonJS({
3892
3892
  jimpImage.bitmap.data = bitmapImageToShare.bitmap.data;
3893
3893
  return jimpImage;
3894
3894
  };
3895
- exports2.write = function(path4, frames, spec, encoder) {
3895
+ exports2.write = function(path5, frames, spec, encoder) {
3896
3896
  encoder = encoder || defaultCodec;
3897
- const matches = path4.match(/\.[a-zA-Z]+$/);
3897
+ const matches = path5.match(/\.[a-zA-Z]+$/);
3898
3898
  if (matches !== null && INVALID_SUFFIXES.includes(matches[0].toLowerCase())) {
3899
- throw new Error(`GIF '${path4}' has an unexpected suffix`);
3899
+ throw new Error(`GIF '${path5}' has an unexpected suffix`);
3900
3900
  }
3901
3901
  return encoder.encodeGif(frames, spec).then((gif2) => {
3902
- return _writeBinary(path4, gif2.buffer).then(() => {
3902
+ return _writeBinary(path5, gif2.buffer).then(() => {
3903
3903
  return gif2;
3904
3904
  });
3905
3905
  });
@@ -3971,9 +3971,9 @@ var require_gifutil = __commonJS({
3971
3971
  }
3972
3972
  }
3973
3973
  }
3974
- function _readBinary(path4) {
3974
+ function _readBinary(path5) {
3975
3975
  return new Promise((resolve2, reject2) => {
3976
- fs4.readFile(path4, (err, buffer) => {
3976
+ fs5.readFile(path5, (err, buffer) => {
3977
3977
  if (err) {
3978
3978
  return reject2(err);
3979
3979
  }
@@ -3981,9 +3981,9 @@ var require_gifutil = __commonJS({
3981
3981
  });
3982
3982
  });
3983
3983
  }
3984
- function _writeBinary(path4, buffer) {
3984
+ function _writeBinary(path5, buffer) {
3985
3985
  return new Promise((resolve2, reject2) => {
3986
- fs4.writeFile(path4, buffer, (err) => {
3986
+ fs5.writeFile(path5, buffer, (err) => {
3987
3987
  if (err) {
3988
3988
  return reject2(err);
3989
3989
  }
@@ -4383,7 +4383,7 @@ var require_src = __commonJS({
4383
4383
  var require_encoder = __commonJS({
4384
4384
  "../../node_modules/.bun/jpeg-js@0.4.4/node_modules/jpeg-js/lib/encoder.js"(exports2, module2) {
4385
4385
  "use strict";
4386
- var btoa = btoa || function(buf) {
4386
+ var btoa2 = btoa2 || function(buf) {
4387
4387
  return Buffer.from(buf).toString("base64");
4388
4388
  };
4389
4389
  function JPEGEncoder(quality2) {
@@ -5404,7 +5404,7 @@ var require_encoder = __commonJS({
5404
5404
  writeWord(65497);
5405
5405
  if (typeof module2 === "undefined") return new Uint8Array(byteout);
5406
5406
  return Buffer.from(byteout);
5407
- var jpegDataUri = "data:image/jpeg;base64," + btoa(byteout.join(""));
5407
+ var jpegDataUri = "data:image/jpeg;base64," + btoa2(byteout.join(""));
5408
5408
  byteout = [];
5409
5409
  var duration = (/* @__PURE__ */ new Date()).getTime() - time_start;
5410
5410
  return jpegDataUri;
@@ -5964,9 +5964,9 @@ var require_decoder = __commonJS({
5964
5964
  return a < 0 ? 0 : a > 255 ? 255 : a;
5965
5965
  }
5966
5966
  constructor.prototype = {
5967
- load: function load(path4) {
5967
+ load: function load(path5) {
5968
5968
  var xhr = new XMLHttpRequest();
5969
- xhr.open("GET", path4, true);
5969
+ xhr.open("GET", path5, true);
5970
5970
  xhr.responseType = "arraybuffer";
5971
5971
  xhr.onload = (function() {
5972
5972
  var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
@@ -18862,11 +18862,11 @@ var require_Mime = __commonJS({
18862
18862
  }
18863
18863
  }
18864
18864
  };
18865
- Mime.prototype.getType = function(path4) {
18866
- path4 = String(path4);
18867
- let last = path4.replace(/^.*[/\\]/, "").toLowerCase();
18865
+ Mime.prototype.getType = function(path5) {
18866
+ path5 = String(path5);
18867
+ let last = path5.replace(/^.*[/\\]/, "").toLowerCase();
18868
18868
  let ext = last.replace(/^.*\./, "").toLowerCase();
18869
- let hasPath = last.length < path4.length;
18869
+ let hasPath = last.length < path5.length;
18870
18870
  let hasDot = ext.length < last.length - 1;
18871
18871
  return (hasDot || !hasPath) && this._types[ext] || null;
18872
18872
  };
@@ -32623,9 +32623,9 @@ function createJimp({ plugins: pluginsArg, formats: formatsArg } = {}) {
32623
32623
  * await image.write("test/output.png");
32624
32624
  * ```
32625
32625
  */
32626
- async write(path4, options) {
32627
- const mimeType = import_lite.default.getType(path4);
32628
- await writeFile(path4, await this.getBuffer(mimeType, options));
32626
+ async write(path5, options) {
32627
+ const mimeType = import_lite.default.getType(path5);
32628
+ await writeFile(path5, await this.getBuffer(mimeType, options));
32629
32629
  }
32630
32630
  /**
32631
32631
  * Clone the image into a new Jimp instance.
@@ -37998,6 +37998,201 @@ var CdpScreencastManager = class {
37998
37998
  }
37999
37999
  };
38000
38000
 
38001
+ // ../browser-core/src/indexeddb-helpers.ts
38002
+ var MAX_TOTAL_SIZE_BYTES = 10 * 1024 * 1024;
38003
+ async function extractIndexedDB(page) {
38004
+ try {
38005
+ const data = await page.evaluate(async () => {
38006
+ if (!indexedDB.databases) return null;
38007
+ const dbInfos = await indexedDB.databases();
38008
+ if (!dbInfos || dbInfos.length === 0) return null;
38009
+ function serializeValue(value) {
38010
+ if (value instanceof ArrayBuffer) {
38011
+ return {
38012
+ __type: "ArrayBuffer",
38013
+ __data: btoa(String.fromCharCode(...new Uint8Array(value)))
38014
+ };
38015
+ }
38016
+ if (ArrayBuffer.isView(value)) {
38017
+ const buf = value.buffer instanceof ArrayBuffer ? value.buffer : value.buffer;
38018
+ return {
38019
+ __type: "TypedArray",
38020
+ __arrayType: value.constructor.name,
38021
+ __data: btoa(String.fromCharCode(...new Uint8Array(buf, value.byteOffset, value.byteLength)))
38022
+ };
38023
+ }
38024
+ if (value instanceof Blob) {
38025
+ return { __type: "Blob", __skipped: true };
38026
+ }
38027
+ if (value instanceof Date) {
38028
+ return { __type: "Date", __data: value.toISOString() };
38029
+ }
38030
+ if (Array.isArray(value)) {
38031
+ return value.map(serializeValue);
38032
+ }
38033
+ if (value !== null && typeof value === "object") {
38034
+ const result = {};
38035
+ for (const [k, v] of Object.entries(value)) {
38036
+ result[k] = serializeValue(v);
38037
+ }
38038
+ return result;
38039
+ }
38040
+ return value;
38041
+ }
38042
+ const databases = [];
38043
+ for (const dbInfo of dbInfos) {
38044
+ if (!dbInfo.name) continue;
38045
+ try {
38046
+ const db = await new Promise((resolve2, reject2) => {
38047
+ const request2 = indexedDB.open(dbInfo.name, dbInfo.version);
38048
+ request2.onsuccess = () => resolve2(request2.result);
38049
+ request2.onerror = () => reject2(request2.error);
38050
+ request2.onupgradeneeded = () => {
38051
+ request2.transaction?.abort();
38052
+ reject2(new Error("version_mismatch"));
38053
+ };
38054
+ });
38055
+ const objectStores = [];
38056
+ for (const storeName of Array.from(db.objectStoreNames)) {
38057
+ try {
38058
+ const tx = db.transaction(storeName, "readonly");
38059
+ const store = tx.objectStore(storeName);
38060
+ const entries = await new Promise(
38061
+ (resolve2, reject2) => {
38062
+ const items = [];
38063
+ const cursorReq = store.openCursor();
38064
+ cursorReq.onsuccess = () => {
38065
+ const cursor = cursorReq.result;
38066
+ if (cursor) {
38067
+ items.push({
38068
+ key: cursor.key,
38069
+ value: serializeValue(cursor.value)
38070
+ });
38071
+ cursor.continue();
38072
+ } else {
38073
+ resolve2(items);
38074
+ }
38075
+ };
38076
+ cursorReq.onerror = () => reject2(cursorReq.error);
38077
+ }
38078
+ );
38079
+ objectStores.push({
38080
+ name: storeName,
38081
+ keyPath: store.keyPath,
38082
+ autoIncrement: store.autoIncrement,
38083
+ entries
38084
+ });
38085
+ } catch {
38086
+ }
38087
+ }
38088
+ db.close();
38089
+ databases.push({
38090
+ name: dbInfo.name,
38091
+ version: dbInfo.version ?? 1,
38092
+ objectStores
38093
+ });
38094
+ } catch {
38095
+ }
38096
+ }
38097
+ return databases.length > 0 ? { databases } : null;
38098
+ });
38099
+ if (!data) return null;
38100
+ const serialized = JSON.stringify(data);
38101
+ if (serialized.length > MAX_TOTAL_SIZE_BYTES) {
38102
+ return null;
38103
+ }
38104
+ return data;
38105
+ } catch {
38106
+ return null;
38107
+ }
38108
+ }
38109
+ async function restoreIndexedDB(page, data) {
38110
+ if (!data.databases || data.databases.length === 0) return;
38111
+ await page.evaluate(async (databases) => {
38112
+ function deserializeValue(value) {
38113
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
38114
+ const obj = value;
38115
+ if (obj.__type === "ArrayBuffer" && typeof obj.__data === "string") {
38116
+ const binary = atob(obj.__data);
38117
+ const bytes = new Uint8Array(binary.length);
38118
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
38119
+ return bytes.buffer;
38120
+ }
38121
+ if (obj.__type === "TypedArray" && typeof obj.__data === "string") {
38122
+ const binary = atob(obj.__data);
38123
+ const bytes = new Uint8Array(binary.length);
38124
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
38125
+ return bytes.buffer;
38126
+ }
38127
+ if (obj.__type === "Date" && typeof obj.__data === "string") {
38128
+ return new Date(obj.__data);
38129
+ }
38130
+ if (obj.__type === "Blob" && obj.__skipped) {
38131
+ return null;
38132
+ }
38133
+ const result = {};
38134
+ for (const [k, v] of Object.entries(obj)) {
38135
+ result[k] = deserializeValue(v);
38136
+ }
38137
+ return result;
38138
+ }
38139
+ if (Array.isArray(value)) {
38140
+ return value.map(deserializeValue);
38141
+ }
38142
+ return value;
38143
+ }
38144
+ for (const dbData of databases) {
38145
+ try {
38146
+ await new Promise((resolve2, reject2) => {
38147
+ const req = indexedDB.deleteDatabase(dbData.name);
38148
+ req.onsuccess = () => resolve2();
38149
+ req.onerror = () => reject2(req.error);
38150
+ req.onblocked = () => resolve2();
38151
+ });
38152
+ const db = await new Promise((resolve2, reject2) => {
38153
+ const request2 = indexedDB.open(dbData.name, dbData.version);
38154
+ request2.onupgradeneeded = () => {
38155
+ const db2 = request2.result;
38156
+ for (const storeData of dbData.objectStores) {
38157
+ if (!db2.objectStoreNames.contains(storeData.name)) {
38158
+ db2.createObjectStore(storeData.name, {
38159
+ keyPath: storeData.keyPath ?? void 0,
38160
+ autoIncrement: storeData.autoIncrement
38161
+ });
38162
+ }
38163
+ }
38164
+ };
38165
+ request2.onsuccess = () => resolve2(request2.result);
38166
+ request2.onerror = () => reject2(request2.error);
38167
+ });
38168
+ for (const storeData of dbData.objectStores) {
38169
+ if (storeData.entries.length === 0) continue;
38170
+ if (!db.objectStoreNames.contains(storeData.name)) continue;
38171
+ try {
38172
+ const tx = db.transaction(storeData.name, "readwrite");
38173
+ const store = tx.objectStore(storeData.name);
38174
+ for (const entry of storeData.entries) {
38175
+ const value = deserializeValue(entry.value);
38176
+ if (storeData.keyPath) {
38177
+ store.put(value);
38178
+ } else {
38179
+ store.put(value, entry.key);
38180
+ }
38181
+ }
38182
+ await new Promise((resolve2, reject2) => {
38183
+ tx.oncomplete = () => resolve2();
38184
+ tx.onerror = () => reject2(tx.error);
38185
+ });
38186
+ } catch {
38187
+ }
38188
+ }
38189
+ db.close();
38190
+ } catch {
38191
+ }
38192
+ }
38193
+ }, data.databases);
38194
+ }
38195
+
38001
38196
  // ../browser-core/src/snapshot-analyzer.ts
38002
38197
  var logger = consoleLogger;
38003
38198
  function setSnapshotAnalyzerLogger(l) {
@@ -38343,7 +38538,7 @@ function normalizeIconLabelText(role, text) {
38343
38538
  return ICON_ONLY_ROLES.has(role) ? "(icon)" : "";
38344
38539
  }
38345
38540
 
38346
- // ../browser-core/src/snapshot-formatter.ts
38541
+ // ../browser-core/src/snapshot-formatter-shared.ts
38347
38542
  var MAX_SEARCH_MATCHES = 10;
38348
38543
  var INTERACTIVE_LEAF_ROLES = /* @__PURE__ */ new Set([
38349
38544
  "button",
@@ -38378,113 +38573,6 @@ var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
38378
38573
  "section",
38379
38574
  "iframe"
38380
38575
  ]);
38381
- var VISUAL_HEADING_MAX_LENGTH = 60;
38382
- function hasInteractiveDescendants(element) {
38383
- if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
38384
- for (const child of element.children) {
38385
- if (hasInteractiveDescendants(child)) return true;
38386
- }
38387
- return false;
38388
- }
38389
- function isVisualHeading(element, siblings, siblingIndex) {
38390
- if (element.role !== "generic" && element.role !== "paragraph") return false;
38391
- if (isClickableByAttribute(element)) return false;
38392
- const text = getVisualHeadingText(element);
38393
- if (!text || text.length < 3 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
38394
- if (hasInteractiveDescendants(element)) return false;
38395
- let interactiveCount = 0;
38396
- for (let i = siblingIndex + 1; i < siblings.length; i++) {
38397
- const sib = siblings[i];
38398
- if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) break;
38399
- if (sib.role === "generic" || sib.role === "paragraph") {
38400
- if (!isClickableByAttribute(sib) && !hasInteractiveDescendants(sib)) {
38401
- const sibText = getVisualHeadingText(sib);
38402
- if (sibText && sibText.length >= 3 && sibText.length <= VISUAL_HEADING_MAX_LENGTH) {
38403
- break;
38404
- }
38405
- }
38406
- }
38407
- if (hasInteractiveDescendants(sib) || isClickableByAttribute(sib)) {
38408
- interactiveCount++;
38409
- }
38410
- }
38411
- return interactiveCount >= 3;
38412
- }
38413
- function getVisualHeadingText(element) {
38414
- const direct = element.text;
38415
- if (direct) return direct;
38416
- for (const child of element.children) {
38417
- if (TEXT_CARRYING_ROLES.has(child.role) && child.text) return child.text;
38418
- }
38419
- return void 0;
38420
- }
38421
- function findNextSectionBoundary(siblings, startIndex) {
38422
- for (let i = startIndex; i < siblings.length; i++) {
38423
- const sib = siblings[i];
38424
- if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) return i;
38425
- if (isVisualHeading(sib, siblings, i)) return i;
38426
- }
38427
- return siblings.length;
38428
- }
38429
- function isFieldLabelCandidate(element) {
38430
- if (element.role !== "generic" && element.role !== "paragraph" && element.role !== "text" && element.role !== "label" && element.role !== "strong" && element.role !== "emphasis") {
38431
- return false;
38432
- }
38433
- if (isClickableByAttribute(element)) return false;
38434
- if (hasInteractiveDescendants(element)) return false;
38435
- const text = getFieldLabelText(element);
38436
- if (!text || text.length < 1 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
38437
- return true;
38438
- }
38439
- var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
38440
- "generic",
38441
- "paragraph",
38442
- "text",
38443
- "strong",
38444
- "emphasis",
38445
- "label",
38446
- "heading"
38447
- ]);
38448
- function getFieldLabelText(element) {
38449
- if (element.text) return element.text.trim();
38450
- if (element.inputValue && !INTERACTIVE_ROLES.has(element.role)) return element.inputValue.trim();
38451
- for (const child of element.children) {
38452
- if (FIELD_LABEL_TEXT_ROLES.has(child.role)) {
38453
- if (child.text) return child.text.trim();
38454
- if (child.inputValue && !INTERACTIVE_ROLES.has(child.role)) return child.inputValue.trim();
38455
- }
38456
- }
38457
- return void 0;
38458
- }
38459
- function findFieldLabelFromSiblings(siblings, interactiveIndex) {
38460
- for (let i = interactiveIndex - 1; i >= 0; i--) {
38461
- const sib = siblings[i];
38462
- if (INTERACTIVE_ROLES.has(sib.role) || isClickableByAttribute(sib) || STRUCTURAL_ROLES.has(sib.role) || sib.role === "heading") {
38463
- break;
38464
- }
38465
- if (isFieldLabelCandidate(sib)) {
38466
- return getFieldLabelText(sib);
38467
- }
38468
- if (sib.children.length >= 1 && sib.children.length <= 4) {
38469
- const containerLabel = resolveContainerFieldLabel(sib);
38470
- if (containerLabel) return containerLabel;
38471
- }
38472
- if (sib.children.length > 4) break;
38473
- }
38474
- return void 0;
38475
- }
38476
- function findFieldLabelInContext(_element, siblings, siblingIndex) {
38477
- return findFieldLabelFromSiblings(siblings, siblingIndex);
38478
- }
38479
- function resolveContainerFieldLabel(container) {
38480
- const kids = container.children;
38481
- if (kids.length < 2 || kids.length > 4) return void 0;
38482
- const firstChild = kids[0];
38483
- if (isFieldLabelCandidate(firstChild)) {
38484
- return getFieldLabelText(firstChild);
38485
- }
38486
- return void 0;
38487
- }
38488
38576
  var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
38489
38577
  "generic",
38490
38578
  "paragraph",
@@ -38495,12 +38583,11 @@ var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
38495
38583
  "blockquote",
38496
38584
  "caption"
38497
38585
  ]);
38586
+ var VISUAL_HEADING_MAX_LENGTH = 60;
38498
38587
  var STATUS_WORDS = /* @__PURE__ */ new Set(["favorable", "unfavorable", "success", "error", "warning", "pending"]);
38499
38588
  var BLANK_PATTERNS = [
38500
38589
  /\s*\(Blank\)\s*/gi,
38501
- // "(Blank)" in any case
38502
38590
  /\s*\(blank\)\s*/gi
38503
- // Redundant but explicit
38504
38591
  ];
38505
38592
  var MENU_TRIGGER_PATTERNS = [
38506
38593
  /^Open menu for /i,
@@ -38515,21 +38602,77 @@ var SECTION_BOUNDARY_ROLES = /* @__PURE__ */ new Set([
38515
38602
  "article",
38516
38603
  "section",
38517
38604
  "complementary"
38518
- // sidebars
38519
38605
  ]);
38606
+ var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
38520
38607
  function normalizeSearchText(text) {
38521
38608
  return text.toLowerCase().replace(/[-_.,;:!?'"()\[\]{}\/\\@#$%^&*+=<>~`|]/g, "").replace(/\s+/g, " ").trim();
38522
38609
  }
38523
- function isMenuTriggerNoise(element) {
38524
- if (isActiveElement(element)) return false;
38525
- if (element.role !== "button") return false;
38526
- const text = element.text || "";
38527
- if (!text.trim()) return false;
38528
- for (const pattern of MENU_TRIGGER_PATTERNS) {
38529
- if (pattern.test(text)) return true;
38610
+ function isActiveElement(element) {
38611
+ return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
38612
+ }
38613
+ function isDisabledElement(element) {
38614
+ return element.attributes["disabled"] !== void 0 || element.rawLine.includes("[disabled]");
38615
+ }
38616
+ function isClickableByAttribute(element) {
38617
+ return element.attributes["cursor"] === "pointer";
38618
+ }
38619
+ function isActionableClickableGeneric(element) {
38620
+ return isClickableByAttribute(element) && !INTERACTIVE_ROLES.has(element.role);
38621
+ }
38622
+ function isSearchInteractiveElement(element) {
38623
+ return INTERACTIVE_LEAF_ROLES.has(element.role) || isActionableClickableGeneric(element);
38624
+ }
38625
+ function getElementText(el) {
38626
+ if (el.text && el.inputValue && FRAMEWORK_NOISE_RE.test(el.text.trim())) {
38627
+ return el.inputValue;
38628
+ }
38629
+ return el.text || el.inputValue;
38630
+ }
38631
+ function composeLabel(element) {
38632
+ const directText = getElementText(element);
38633
+ if (directText) return directText;
38634
+ const textParts = [];
38635
+ function collectText(el, depth) {
38636
+ if (depth > 3) return;
38637
+ const text = getElementText(el);
38638
+ if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38639
+ textParts.push(text);
38640
+ }
38641
+ for (const child of el.children) {
38642
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38643
+ collectText(child, depth + 1);
38644
+ }
38645
+ }
38646
+ }
38647
+ for (const child of element.children) {
38648
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38649
+ collectText(child, 0);
38650
+ }
38651
+ }
38652
+ return textParts.join(" | ") || element.role;
38653
+ }
38654
+ function getDisplayText(element, options) {
38655
+ const isClickable = isActionableClickableGeneric(element);
38656
+ const rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
38657
+ return normalizeIconLabelText(element.role, rawTextSource);
38658
+ }
38659
+ function hasInteractiveDescendants(element) {
38660
+ if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
38661
+ for (const child of element.children) {
38662
+ if (hasInteractiveDescendants(child)) return true;
38663
+ }
38664
+ return false;
38665
+ }
38666
+ function subtreeContainsActive(element) {
38667
+ if (isActiveElement(element)) return true;
38668
+ for (const child of element.children) {
38669
+ if (subtreeContainsActive(child)) return true;
38530
38670
  }
38531
38671
  return false;
38532
38672
  }
38673
+ function cleanLabel(text) {
38674
+ return text.replace(/\s+/g, " ").replace(/…+/g, "...").trim();
38675
+ }
38533
38676
  function isBlankValue(value) {
38534
38677
  if (!value) return true;
38535
38678
  const trimmed = value.trim().toLowerCase();
@@ -38569,160 +38712,15 @@ function extractDataValue(text) {
38569
38712
  }
38570
38713
  return { label: cleanLabel(cleaned), status };
38571
38714
  }
38572
- function findRelevanceScope(elements, activeElementRef) {
38573
- if (!activeElementRef) return null;
38574
- const path4 = [];
38575
- function findPath(el) {
38576
- if (el.ref) {
38577
- path4.push({ ref: el.ref, role: el.role });
38578
- }
38579
- if (el.ref === activeElementRef) {
38580
- return true;
38581
- }
38582
- for (const child of el.children) {
38583
- if (findPath(child)) return true;
38584
- }
38585
- if (el.ref) path4.pop();
38586
- return false;
38587
- }
38588
- for (const el of elements) {
38589
- if (findPath(el)) break;
38590
- }
38591
- for (let i = path4.length - 1; i >= 0; i--) {
38592
- if (SECTION_BOUNDARY_ROLES.has(path4[i].role)) {
38593
- return path4[i].ref;
38594
- }
38595
- }
38596
- return null;
38597
- }
38598
- function searchElements(elements, searchTerms, scopeRef = null) {
38599
- const allMatches = [];
38600
- const normalizedTerms = searchTerms.map((t) => normalizeSearchText(t)).filter(Boolean);
38601
- if (normalizedTerms.length === 0) return { matches: [], totalFound: 0 };
38602
- function searchElement(el, inScope) {
38603
- const nowInScope = inScope || el.ref === scopeRef || scopeRef === null;
38604
- if (el.ref && nowInScope) {
38605
- const searchText = [el.text, el.inputValue].filter(Boolean).join(" ");
38606
- const normalizedSearchText = normalizeSearchText(searchText);
38607
- for (const term of normalizedTerms) {
38608
- if (normalizedSearchText.includes(term)) {
38609
- const normalizedLabel = normalizeIconLabelText(el.role, el.text || "");
38610
- allMatches.push({
38611
- ref: el.ref,
38612
- role: el.role,
38613
- label: normalizedLabel || el.role,
38614
- term,
38615
- isInteractive: INTERACTIVE_LEAF_ROLES.has(el.role)
38616
- });
38617
- break;
38618
- }
38619
- }
38620
- }
38621
- for (const child of el.children) {
38622
- searchElement(child, nowInScope);
38623
- }
38624
- }
38625
- for (const el of elements) {
38626
- searchElement(el, scopeRef === null);
38627
- }
38628
- const totalFound = allMatches.length;
38629
- allMatches.sort((a, b) => {
38630
- if (a.isInteractive && !b.isInteractive) return -1;
38631
- if (!a.isInteractive && b.isInteractive) return 1;
38632
- return 0;
38633
- });
38634
- return {
38635
- matches: allMatches.slice(0, MAX_SEARCH_MATCHES),
38636
- totalFound
38637
- };
38638
- }
38639
- function findElementInSections(sections, targetRef, ancestors = []) {
38640
- for (const section of sections) {
38641
- const newAncestors = [...ancestors, section];
38642
- const path4 = newAncestors.map((s) => s.heading);
38643
- if (section.elements.some((el) => el.ref === targetRef)) {
38644
- return { section, ancestors: newAncestors, path: path4 };
38645
- }
38646
- const found = findElementInSections(section.subsections, targetRef, newAncestors);
38647
- if (found) return found;
38648
- }
38649
- return null;
38650
- }
38651
- function populateSearchContext(matches, sections) {
38652
- for (const match of matches) {
38653
- const result = findElementInSections(sections, match.ref);
38654
- if (result) {
38655
- const { section, ancestors, path: path4 } = result;
38656
- const inFocusedDialog = ancestors.some((s) => s.isFocused);
38657
- const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
38658
- if (inFocusedDialog) {
38659
- match.context = "focused";
38660
- } else if (inBackground) {
38661
- match.context = "background";
38662
- } else {
38663
- match.context = "foreground";
38664
- }
38665
- const SKIP_HEADINGS = ["generic", "main", "iframe"];
38666
- match.sectionPath = path4.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
38667
- }
38668
- }
38669
- }
38670
- function sortMatchesByContext(matches) {
38671
- const contextOrder = {
38672
- focused: 0,
38673
- foreground: 1,
38674
- background: 2
38675
- };
38676
- return [...matches].sort((a, b) => {
38677
- const aContext = contextOrder[a.context ?? "foreground"] ?? 1;
38678
- const bContext = contextOrder[b.context ?? "foreground"] ?? 1;
38679
- if (aContext !== bContext) return aContext - bContext;
38680
- if (a.isInteractive && !b.isInteractive) return -1;
38681
- if (!a.isInteractive && b.isInteractive) return 1;
38682
- return 0;
38683
- });
38684
- }
38685
- function cleanLabel(text) {
38686
- return text.replace(/\s+/g, " ").replace(/…+/g, "...").trim();
38687
- }
38688
- function isActiveElement(element) {
38689
- return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
38690
- }
38691
- function isDisabledElement(element) {
38692
- return element.attributes["disabled"] !== void 0 || element.rawLine.includes("[disabled]");
38693
- }
38694
- function isClickableByAttribute(element) {
38695
- return element.attributes["cursor"] === "pointer";
38696
- }
38697
- var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
38698
- function getElementText(el) {
38699
- if (el.text && el.inputValue && FRAMEWORK_NOISE_RE.test(el.text.trim())) {
38700
- return el.inputValue;
38701
- }
38702
- return el.text || el.inputValue;
38703
- }
38704
- function composeLabel(element) {
38705
- const directText = getElementText(element);
38706
- if (directText) return directText;
38707
- const textParts = [];
38708
- function collectText(el, depth) {
38709
- if (depth > 3) return;
38710
- const text = getElementText(el);
38711
- if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38712
- textParts.push(text);
38713
- }
38714
- for (const child of el.children) {
38715
- if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38716
- collectText(child, depth + 1);
38717
- }
38718
- }
38719
- }
38720
- for (const child of element.children) {
38721
- if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38722
- collectText(child, 0);
38723
- }
38715
+ function isMenuTriggerNoise(element) {
38716
+ if (isActiveElement(element)) return false;
38717
+ if (element.role !== "button") return false;
38718
+ const text = element.text || "";
38719
+ if (!text.trim()) return false;
38720
+ for (const pattern of MENU_TRIGGER_PATTERNS) {
38721
+ if (pattern.test(text)) return true;
38724
38722
  }
38725
- return textParts.join(" | ") || element.role;
38723
+ return false;
38726
38724
  }
38727
38725
  function countElementsWithRefs(element) {
38728
38726
  let count = element.ref ? 1 : 0;
@@ -38731,74 +38729,23 @@ function countElementsWithRefs(element) {
38731
38729
  }
38732
38730
  return count;
38733
38731
  }
38734
- function isExpandedElement(element) {
38735
- return element.attributes["expanded"] === "true" || element.attributes["aria-expanded"] === "true";
38736
- }
38737
- function collectExpandedChildren(element, result) {
38738
- for (const child of element.children) {
38739
- if (STRUCTURAL_ROLES.has(child.role)) continue;
38740
- if (INTERACTIVE_LEAF_ROLES.has(child.role) && child.ref) {
38741
- result.push(formatElement(child));
38742
- continue;
38743
- }
38744
- if (child.role === "listitem" && child.ref) {
38745
- const text = child.text || composeLabel(child);
38746
- if (text && text !== "listitem") {
38747
- result.push(formatElement(child, { overrideLabel: text }));
38748
- continue;
38749
- }
38750
- }
38751
- if (isClickableByAttribute(child) && child.ref) {
38752
- result.push(formatElement(child));
38732
+ function collectComboboxOptions(element) {
38733
+ const options = [];
38734
+ function walk(el) {
38735
+ if (el.role === "option" && el.text?.trim()) {
38736
+ options.push(el.text.trim());
38753
38737
  }
38754
- collectExpandedChildren(child, result);
38755
- }
38756
- }
38757
- function hasActionableChildren(element) {
38758
- const children2 = [];
38759
- collectExpandedChildren(element, children2);
38760
- return children2.length > 0;
38761
- }
38762
- function isExpandedWithActionableChildren(element) {
38763
- if (!isExpandedElement(element)) return false;
38764
- if (element.role === "combobox") return false;
38765
- return hasActionableChildren(element);
38766
- }
38767
- function composeTriggerLabel(element) {
38768
- const ariaLabel = element.attributes["aria-label"];
38769
- if (ariaLabel) return ariaLabel;
38770
- const fullText = element.text || "";
38771
- if (fullText) {
38772
- let collectLeafTexts2 = function(el) {
38773
- for (const child of el.children) {
38774
- const text = child.text?.trim();
38775
- if (text) {
38776
- leafTexts.push(text);
38777
- }
38778
- collectLeafTexts2(child);
38779
- }
38780
- };
38781
- var collectLeafTexts = collectLeafTexts2;
38782
- const leafTexts = [];
38783
- collectLeafTexts2(element);
38784
- if (leafTexts.length > 0) {
38785
- let stripped = fullText;
38786
- for (const lt of leafTexts) {
38787
- stripped = stripped.replace(lt, "");
38788
- }
38789
- stripped = stripped.replace(/\s+/g, " ").trim();
38790
- if (stripped) return stripped;
38738
+ for (const child of el.children) {
38739
+ walk(child);
38791
38740
  }
38792
38741
  }
38793
- return element.role;
38794
- }
38795
- function subtreeContainsActive(element) {
38796
- if (isActiveElement(element)) return true;
38797
38742
  for (const child of element.children) {
38798
- if (subtreeContainsActive(child)) return true;
38743
+ walk(child);
38799
38744
  }
38800
- return false;
38745
+ return options.length > 0 ? options : void 0;
38801
38746
  }
38747
+
38748
+ // ../browser-core/src/snapshot-formatter-grid.ts
38802
38749
  function findDescendantByRole(el, role) {
38803
38750
  if (el.role === role) return el;
38804
38751
  for (const child of el.children) {
@@ -38825,51 +38772,250 @@ function findPrimaryColumnLabel(columnHeader) {
38825
38772
  };
38826
38773
  return search(columnHeader);
38827
38774
  }
38828
- function detectGrid(element) {
38829
- if (element.role !== "grid" && element.role !== "treegrid" && element.role !== "table") {
38830
- return null;
38831
- }
38832
- const isHTMLTable = element.role === "table";
38833
- const columns = [];
38834
- const rows = [];
38835
- let headerRowRef = null;
38836
- const columnByPosition = /* @__PURE__ */ new Map();
38837
- let headerChildCount;
38838
- const processRow = (rowEl, index) => {
38839
- if (rowEl.role !== "row") return null;
38840
- const hasColumnHeaders = rowEl.children.some((c2) => c2.role === "columnheader");
38841
- if (hasColumnHeaders) {
38842
- headerRowRef = rowEl.ref;
38843
- const columnHeaders = rowEl.children.filter((c2) => c2.role === "columnheader");
38844
- const hasChildful = columnHeaders.some((c2) => c2.children.length > 0);
38845
- const columnsBefore = columns.length;
38846
- for (let i = 0; i < rowEl.children.length; i++) {
38847
- const child = rowEl.children[i];
38848
- if (child.role === "columnheader") {
38849
- if (hasChildful && child.children.length === 0) continue;
38850
- const primaryLabel = findPrimaryColumnLabel(child);
38851
- const rawName = (primaryLabel ? getElementText(primaryLabel) : void 0) || child.text || "";
38852
- const cleanName = rawName.replace(/,?\s*sorted in \w+ order/i, "").trim();
38853
- if (cleanName) {
38854
- columnByPosition.set(i, cleanName);
38855
- columns.push({ name: cleanName, ref: primaryLabel?.ref || child.ref });
38856
- } else {
38857
- const checkboxChild = findDescendantByRole(child, "checkbox");
38858
- if (checkboxChild?.ref) {
38859
- const syntheticName = "\u2610";
38860
- columnByPosition.set(i, syntheticName);
38861
- columns.push({ name: syntheticName, ref: checkboxChild.ref });
38862
- }
38863
- }
38775
+ function isWidgetTable(element) {
38776
+ if (element.role !== "table") return false;
38777
+ const rows = element.children.filter((c2) => c2.role === "row");
38778
+ if (rows.length === 0 || rows.length > 2) return false;
38779
+ let totalCells = 0;
38780
+ let interactiveCells = 0;
38781
+ for (const row of rows) {
38782
+ for (const cell of row.children) {
38783
+ if (cell.role === "cell" || cell.role === "gridcell" || cell.role === "columnheader") {
38784
+ totalCells++;
38785
+ if (hasInteractiveDescendants(cell)) {
38786
+ interactiveCells++;
38864
38787
  }
38865
38788
  }
38866
- if (columns.length > columnsBefore) {
38867
- headerChildCount = rowEl.children.length;
38789
+ }
38790
+ }
38791
+ return totalCells > 0 && interactiveCells >= totalCells * 0.5;
38792
+ }
38793
+ function gridCellPriority(el) {
38794
+ if (el.isActive) return 5;
38795
+ if (FORM_INPUT_ROLES.has(el.role)) return 4;
38796
+ if (el.isClickableGeneric) return 3;
38797
+ if (el.role !== "button") return 2;
38798
+ return 1;
38799
+ }
38800
+ function extractCellText(el) {
38801
+ const findFirstParagraph = (node) => {
38802
+ if (node.role === "paragraph" && node.text) {
38803
+ return node.text.trim();
38804
+ }
38805
+ for (const child of node.children) {
38806
+ if (child.role === "generic" || child.role === "paragraph") {
38807
+ const found = findFirstParagraph(child);
38808
+ if (found) return found;
38868
38809
  }
38869
- return null;
38870
38810
  }
38871
- if (isHTMLTable && columns.length === 0) {
38872
- const cellChildren = rowEl.children.filter((c2) => c2.role === "cell");
38811
+ return null;
38812
+ };
38813
+ const paragraphText = findFirstParagraph(el);
38814
+ if (paragraphText) return paragraphText;
38815
+ const text = el.text?.trim();
38816
+ if (text) return text;
38817
+ return null;
38818
+ }
38819
+ function extractGridCell(cellEl) {
38820
+ const interactiveElements = [];
38821
+ const findInteractive = (el) => {
38822
+ if (isClickableByAttribute(el) && el.ref && el.role === "generic") {
38823
+ const childTexts = [];
38824
+ for (const child of el.children) {
38825
+ if (child.role === "generic") {
38826
+ const childText = child.text || child.inputValue;
38827
+ if (childText) childTexts.push(childText);
38828
+ }
38829
+ }
38830
+ interactiveElements.push({
38831
+ ref: el.ref,
38832
+ column: el.attributes["label"] || childTexts[0] || "",
38833
+ value: childTexts.join(" - ") || el.text || el.inputValue || "",
38834
+ role: el.role,
38835
+ isClickableGeneric: true,
38836
+ isActive: isActiveElement(el)
38837
+ });
38838
+ }
38839
+ if (INTERACTIVE_LEAF_ROLES.has(el.role) && el.ref) {
38840
+ const value2 = el.role === "checkbox" ? el.attributes["checked"] === "true" ? "checked" : "unchecked" : el.inputValue || el.text || "";
38841
+ interactiveElements.push({
38842
+ ref: el.ref,
38843
+ column: el.attributes["label"] || el.text || "",
38844
+ value: value2,
38845
+ role: el.role,
38846
+ isClickableGeneric: false,
38847
+ isActive: isActiveElement(el)
38848
+ });
38849
+ }
38850
+ for (const child of el.children) findInteractive(child);
38851
+ };
38852
+ findInteractive(cellEl);
38853
+ if (interactiveElements.length === 0) {
38854
+ const textValue = extractCellText(cellEl);
38855
+ if (textValue) {
38856
+ return {
38857
+ ref: cellEl.ref,
38858
+ column: cellEl.text || "",
38859
+ value: textValue,
38860
+ hasValue: true
38861
+ };
38862
+ }
38863
+ return null;
38864
+ }
38865
+ const found = interactiveElements.reduce(
38866
+ (best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
38867
+ );
38868
+ const column = stripBlankPatterns(found.column);
38869
+ const value = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
38870
+ const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
38871
+ return {
38872
+ ref: found.ref,
38873
+ column: column.trim(),
38874
+ value,
38875
+ hasValue: value !== "",
38876
+ gridcellLabel: gridcellLabel || void 0,
38877
+ role: found.role
38878
+ };
38879
+ }
38880
+ function reconcileCellColumn(cell, knownColumns) {
38881
+ if (knownColumns.has(cell.column)) return cell;
38882
+ if (cell.gridcellLabel && knownColumns.has(cell.gridcellLabel)) {
38883
+ const newValue = cell.column || cell.value;
38884
+ const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
38885
+ return {
38886
+ ...cell,
38887
+ column: cell.gridcellLabel,
38888
+ value: cleanValue,
38889
+ hasValue: cleanValue !== ""
38890
+ };
38891
+ }
38892
+ let bestMatch = "";
38893
+ for (const col of knownColumns) {
38894
+ if (cell.column.startsWith(col) && col.length > bestMatch.length && (cell.column.length === col.length || cell.column[col.length] === " ")) {
38895
+ bestMatch = col;
38896
+ }
38897
+ }
38898
+ if (bestMatch) {
38899
+ const remainder = cell.column.slice(bestMatch.length).trim();
38900
+ const newValue = isBlankValue(remainder) ? "" : remainder;
38901
+ return {
38902
+ ...cell,
38903
+ column: bestMatch,
38904
+ value: newValue || cell.value,
38905
+ hasValue: (newValue || cell.value) !== ""
38906
+ };
38907
+ }
38908
+ return cell;
38909
+ }
38910
+ function reconcileGridCells(grid) {
38911
+ if (grid.columns.length === 0) return grid;
38912
+ const knownColumns = new Set(grid.columns.map((c2) => c2.name));
38913
+ return {
38914
+ ...grid,
38915
+ rows: grid.rows.map((row) => {
38916
+ const reconciledCells = row.cells.map((cell) => {
38917
+ const reconciled = reconcileCellColumn(cell, knownColumns);
38918
+ if (knownColumns.has(reconciled.column)) return reconciled;
38919
+ if (grid.columnByPosition && cell.positionInRow !== void 0 && grid.headerChildCount !== void 0 && row.totalChildren === grid.headerChildCount) {
38920
+ const positionalColumn = grid.columnByPosition.get(cell.positionInRow);
38921
+ if (positionalColumn) {
38922
+ const newValue = cell.column || cell.value;
38923
+ const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
38924
+ return {
38925
+ ...cell,
38926
+ column: positionalColumn,
38927
+ value: cleanValue,
38928
+ hasValue: cleanValue !== ""
38929
+ };
38930
+ }
38931
+ }
38932
+ return reconciled;
38933
+ });
38934
+ if (grid.headerCellPositions && row.cellPositions) {
38935
+ const claimedColumns = new Set(
38936
+ reconciledCells.filter((c2) => knownColumns.has(c2.column)).map((c2) => c2.column)
38937
+ );
38938
+ for (let idx = 0; idx < reconciledCells.length; idx++) {
38939
+ const cell = reconciledCells[idx];
38940
+ if (knownColumns.has(cell.column) || cell.positionInRow === void 0) continue;
38941
+ const cellOrdinal = row.cellPositions.indexOf(cell.positionInRow);
38942
+ if (cellOrdinal >= 0 && cellOrdinal < grid.headerCellPositions.length) {
38943
+ const headerPos = grid.headerCellPositions[cellOrdinal];
38944
+ const positionalColumn = grid.columnByPosition?.get(headerPos);
38945
+ if (positionalColumn && !claimedColumns.has(positionalColumn)) {
38946
+ const newValue = cell.column || cell.value;
38947
+ const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
38948
+ reconciledCells[idx] = {
38949
+ ...cell,
38950
+ column: positionalColumn,
38951
+ value: cleanValue,
38952
+ hasValue: cleanValue !== ""
38953
+ };
38954
+ claimedColumns.add(positionalColumn);
38955
+ }
38956
+ }
38957
+ }
38958
+ }
38959
+ return { ...row, cells: reconciledCells };
38960
+ })
38961
+ };
38962
+ }
38963
+ function detectGrid(element) {
38964
+ if (element.role !== "grid" && element.role !== "treegrid" && element.role !== "table") {
38965
+ return null;
38966
+ }
38967
+ const isHTMLTable = element.role === "table";
38968
+ const columns = [];
38969
+ const rows = [];
38970
+ let headerRowRef = null;
38971
+ const columnByPosition = /* @__PURE__ */ new Map();
38972
+ let headerChildCount;
38973
+ let headerCellPositions;
38974
+ const isRowLike = (el) => {
38975
+ if (el.role === "row") return true;
38976
+ const cellCount = el.children.filter(
38977
+ (c2) => c2.role === "cell" || c2.role === "gridcell"
38978
+ ).length;
38979
+ return cellCount >= 2;
38980
+ };
38981
+ const processRow = (rowEl, index) => {
38982
+ if (!isRowLike(rowEl)) return null;
38983
+ const hasColumnHeaders = rowEl.children.some((c2) => c2.role === "columnheader");
38984
+ if (hasColumnHeaders) {
38985
+ headerRowRef = rowEl.ref;
38986
+ const columnHeaders = rowEl.children.filter((c2) => c2.role === "columnheader");
38987
+ const childful = columnHeaders.filter((c2) => c2.children.length > 0);
38988
+ const textBearingChildless = columnHeaders.filter((c2) => c2.children.length === 0 && c2.text);
38989
+ const skipChildlessHeaders = childful.length > 0 && textBearingChildless.length <= childful.length;
38990
+ const columnsBefore = columns.length;
38991
+ for (let i = 0; i < rowEl.children.length; i++) {
38992
+ const child = rowEl.children[i];
38993
+ if (child.role === "columnheader") {
38994
+ if (skipChildlessHeaders && child.children.length === 0) continue;
38995
+ const primaryLabel = findPrimaryColumnLabel(child);
38996
+ const rawName = (primaryLabel ? getElementText(primaryLabel) : void 0) || child.text || "";
38997
+ const cleanName = rawName.replace(/,?\s*sorted in \w+ order/i, "").trim();
38998
+ if (cleanName) {
38999
+ columnByPosition.set(i, cleanName);
39000
+ columns.push({ name: cleanName, ref: primaryLabel?.ref || child.ref });
39001
+ } else {
39002
+ const checkboxChild = findDescendantByRole(child, "checkbox");
39003
+ if (checkboxChild?.ref) {
39004
+ const syntheticName = "\u2610";
39005
+ columnByPosition.set(i, syntheticName);
39006
+ columns.push({ name: syntheticName, ref: checkboxChild.ref });
39007
+ }
39008
+ }
39009
+ }
39010
+ }
39011
+ if (columns.length > columnsBefore) {
39012
+ headerChildCount = rowEl.children.length;
39013
+ headerCellPositions = Array.from(columnByPosition.keys()).sort((a, b) => a - b);
39014
+ }
39015
+ return null;
39016
+ }
39017
+ if (isHTMLTable && columns.length === 0) {
39018
+ const cellChildren = rowEl.children.filter((c2) => c2.role === "cell");
38873
39019
  const textBearingCells = cellChildren.filter((c2) => {
38874
39020
  if (c2.text) return true;
38875
39021
  return c2.children.some((ch) => ch.text || ch.inputValue);
@@ -38906,6 +39052,7 @@ function detectGrid(element) {
38906
39052
  }
38907
39053
  if (columns.length > columnsBefore) {
38908
39054
  headerChildCount = rowEl.children.length;
39055
+ headerCellPositions = Array.from(columnByPosition.keys()).sort((a, b) => a - b);
38909
39056
  }
38910
39057
  return null;
38911
39058
  }
@@ -38931,7 +39078,20 @@ function detectGrid(element) {
38931
39078
  findButtons(cellEl);
38932
39079
  }
38933
39080
  }
38934
- const row = { ref: rowEl.ref, index, isSelected, cells, totalChildren: rowEl.children.length };
39081
+ const cellPositions = [];
39082
+ for (let i = 0; i < rowEl.children.length; i++) {
39083
+ if (rowEl.children[i].role === "gridcell" || rowEl.children[i].role === "cell") {
39084
+ cellPositions.push(i);
39085
+ }
39086
+ }
39087
+ const row = {
39088
+ ref: rowEl.ref,
39089
+ index,
39090
+ isSelected,
39091
+ cells,
39092
+ totalChildren: rowEl.children.length,
39093
+ cellPositions
39094
+ };
38935
39095
  if (cells.length === 0 && rowEl.children.some((c2) => c2.role === "rowheader")) {
38936
39096
  const labels = [];
38937
39097
  const buttons = [];
@@ -38959,7 +39119,7 @@ function detectGrid(element) {
38959
39119
  return row;
38960
39120
  };
38961
39121
  const findRows = (el) => {
38962
- if (el.role === "row") {
39122
+ if (isRowLike(el)) {
38963
39123
  const row = processRow(el, rows.length);
38964
39124
  if (row) rows.push(row);
38965
39125
  } else {
@@ -38977,180 +39137,417 @@ function detectGrid(element) {
38977
39137
  totalRows: rows.length,
38978
39138
  containsActive: subtreeContainsActive(element),
38979
39139
  columnByPosition: columnByPosition.size > 0 ? columnByPosition : void 0,
38980
- headerChildCount
39140
+ headerChildCount,
39141
+ headerCellPositions
38981
39142
  };
38982
39143
  return reconcileGridCells(rawGrid);
38983
39144
  }
38984
- function gridCellPriority(el) {
38985
- if (el.isActive) return 5;
38986
- if (FORM_INPUT_ROLES.has(el.role)) return 4;
38987
- if (el.isClickableGeneric) return 3;
38988
- if (el.role !== "button") return 2;
38989
- return 1;
39145
+ function gridContainsRef(grid, targetRef) {
39146
+ for (const column of grid.columns) {
39147
+ if (column.ref === targetRef) return true;
39148
+ }
39149
+ for (const row of grid.rows) {
39150
+ if (row.ref === targetRef) return true;
39151
+ if (row.cells.some((cell) => cell.ref === targetRef)) return true;
39152
+ if (row.rowActions?.some((action) => action.ref === targetRef)) return true;
39153
+ if (row.expandedContent?.buttons.some((button) => button.ref === targetRef)) return true;
39154
+ }
39155
+ return false;
38990
39156
  }
38991
- function extractCellText(el) {
38992
- const findFirstParagraph = (node) => {
38993
- if (node.role === "paragraph" && node.text) {
38994
- return node.text.trim();
39157
+ function countRenderedGridItems(grid) {
39158
+ const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
39159
+ let count = 0;
39160
+ for (const row of grid.rows) {
39161
+ count += row.cells.filter((c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))).length;
39162
+ count += row.rowActions?.length ?? 0;
39163
+ count += row.expandedContent?.buttons.length ?? 0;
39164
+ }
39165
+ return count;
39166
+ }
39167
+ function formatGridOutput(grid, indent, expanded = false) {
39168
+ const indentStr = " ".repeat(indent);
39169
+ const lines = [];
39170
+ const colInfo = grid.columns.length > 0 ? `${grid.columns.length} columns` : "unknown columns";
39171
+ lines.push(`${indentStr}GRID [${grid.ref}]: ${grid.totalRows} rows, ${colInfo}`);
39172
+ const MAX_DISPLAY_COLUMNS = 10;
39173
+ if (grid.columns.length > 0) {
39174
+ const displayColumns = grid.columns.slice(0, MAX_DISPLAY_COLUMNS);
39175
+ const colsWithRefs = displayColumns.map((c2) => `${c2.name} [${c2.ref}]`).join(", ");
39176
+ const moreColsNote = grid.columns.length > MAX_DISPLAY_COLUMNS ? ` (+${grid.columns.length - MAX_DISPLAY_COLUMNS} more)` : "";
39177
+ lines.push(`${indentStr} Columns: ${colsWithRefs}${moreColsNote}`);
39178
+ }
39179
+ lines.push("");
39180
+ const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
39181
+ const MAX_GRID_ROWS = 30;
39182
+ const displayRows = expanded ? grid.rows : grid.rows.slice(0, MAX_GRID_ROWS);
39183
+ for (const row of displayRows) {
39184
+ lines.push(formatGridRow(row, indent + 1, knownColumns));
39185
+ }
39186
+ if (!expanded && grid.rows.length > MAX_GRID_ROWS) {
39187
+ lines.push(`${indentStr} ... and ${grid.rows.length - MAX_GRID_ROWS} more rows (use expand="${grid.ref}" to see all)`);
39188
+ }
39189
+ return lines.join("\n");
39190
+ }
39191
+ function formatGridRow(row, indent, knownColumns) {
39192
+ const indentStr = " ".repeat(indent);
39193
+ if (row.expandedContent) {
39194
+ const parts = [];
39195
+ if (row.expandedContent.labels.length > 0) {
39196
+ parts.push(row.expandedContent.labels.join(", "));
38995
39197
  }
38996
- for (const child of node.children) {
38997
- if (child.role === "generic" || child.role === "paragraph") {
38998
- const found = findFirstParagraph(child);
38999
- if (found) return found;
39198
+ for (const btn of row.expandedContent.buttons) {
39199
+ parts.push(`${btn.label} [${btn.ref}]`);
39200
+ }
39201
+ return `${indentStr} \u21B3 Expanded [${row.ref}]: ${parts.join(" | ")}`;
39202
+ }
39203
+ const marker = row.isSelected ? "ACTIVE \u2192 Row" : "Row";
39204
+ const actionsPrefix = row.rowActions?.length ? row.rowActions.map((a) => `[\u25B6 ${a.ref}]`).join(" ") + " " : "";
39205
+ const cellsWithValue = row.cells.filter(
39206
+ (c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))
39207
+ );
39208
+ if (cellsWithValue.length > 0) {
39209
+ const cellStrs = cellsWithValue.map((c2) => {
39210
+ if (c2.role === "checkbox") {
39211
+ const icon = c2.value === "checked" ? "\u2611" : "\u2610";
39212
+ return `${icon} [${c2.ref}]`;
39213
+ }
39214
+ if (c2.column === c2.value) {
39215
+ return `${c2.column} [${c2.ref}]`;
39000
39216
  }
39217
+ return `${c2.column} [${c2.ref}]: ${c2.value}`;
39218
+ });
39219
+ return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: ${cellStrs.join(" | ")}`;
39220
+ }
39221
+ return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: (empty row)`;
39222
+ }
39223
+
39224
+ // ../browser-core/src/snapshot-formatter-search.ts
39225
+ function findRelevanceScope(elements, activeElementRef) {
39226
+ if (!activeElementRef) return null;
39227
+ const path5 = [];
39228
+ function findPath(el) {
39229
+ if (el.ref) {
39230
+ path5.push({ ref: el.ref, role: el.role });
39001
39231
  }
39002
- return null;
39003
- };
39004
- const paragraphText = findFirstParagraph(el);
39005
- if (paragraphText) return paragraphText;
39006
- const text = el.text?.trim();
39007
- if (text) return text;
39232
+ if (el.ref === activeElementRef) {
39233
+ return true;
39234
+ }
39235
+ for (const child of el.children) {
39236
+ if (findPath(child)) return true;
39237
+ }
39238
+ if (el.ref) path5.pop();
39239
+ return false;
39240
+ }
39241
+ for (const el of elements) {
39242
+ if (findPath(el)) break;
39243
+ }
39244
+ for (let i = path5.length - 1; i >= 0; i--) {
39245
+ if (SECTION_BOUNDARY_ROLES.has(path5[i].role)) {
39246
+ return path5[i].ref;
39247
+ }
39248
+ }
39008
39249
  return null;
39009
39250
  }
39010
- function extractGridCell(cellEl) {
39011
- const interactiveElements = [];
39012
- const findInteractive = (el) => {
39013
- if (isClickableByAttribute(el) && el.ref && el.role === "generic") {
39014
- const childTexts = [];
39015
- for (const child of el.children) {
39016
- if (child.role === "generic") {
39017
- const childText = child.text || child.inputValue;
39018
- if (childText) childTexts.push(childText);
39251
+ function searchElements(elements, searchTerms, scopeRef = null) {
39252
+ const allMatches = [];
39253
+ const normalizedTerms = searchTerms.map((t) => normalizeSearchText(t)).filter(Boolean);
39254
+ if (normalizedTerms.length === 0) return { matches: [], totalFound: 0 };
39255
+ function searchElement(el, inScope) {
39256
+ const nowInScope = inScope || el.ref === scopeRef || scopeRef === null;
39257
+ if (el.ref && nowInScope) {
39258
+ const searchLabel = getDisplayText(el);
39259
+ const searchText = [searchLabel, el.inputValue].filter(Boolean).join(" ");
39260
+ const normalizedSearchText = normalizeSearchText(searchText);
39261
+ for (const term of normalizedTerms) {
39262
+ if (normalizedSearchText.includes(term)) {
39263
+ allMatches.push({
39264
+ ref: el.ref,
39265
+ role: el.role,
39266
+ label: searchLabel || el.role,
39267
+ term,
39268
+ isInteractive: isSearchInteractiveElement(el)
39269
+ });
39270
+ break;
39019
39271
  }
39020
39272
  }
39021
- interactiveElements.push({
39022
- ref: el.ref,
39023
- column: el.attributes["label"] || childTexts[0] || "",
39024
- value: childTexts.join(" - ") || el.text || el.inputValue || "",
39025
- role: el.role,
39026
- isClickableGeneric: true,
39027
- isActive: isActiveElement(el)
39028
- });
39029
39273
  }
39030
- if (INTERACTIVE_LEAF_ROLES.has(el.role) && el.ref) {
39031
- const value2 = el.role === "checkbox" ? el.attributes["checked"] === "true" ? "checked" : "unchecked" : el.inputValue || el.text || "";
39032
- interactiveElements.push({
39033
- ref: el.ref,
39034
- column: el.attributes["label"] || el.text || "",
39035
- value: value2,
39036
- role: el.role,
39037
- isClickableGeneric: false,
39038
- isActive: isActiveElement(el)
39039
- });
39274
+ for (const child of el.children) {
39275
+ searchElement(child, nowInScope);
39040
39276
  }
39041
- for (const child of el.children) findInteractive(child);
39277
+ }
39278
+ for (const el of elements) {
39279
+ searchElement(el, scopeRef === null);
39280
+ }
39281
+ const totalFound = allMatches.length;
39282
+ allMatches.sort((a, b) => {
39283
+ if (a.isInteractive && !b.isInteractive) return -1;
39284
+ if (!a.isInteractive && b.isInteractive) return 1;
39285
+ return 0;
39286
+ });
39287
+ return {
39288
+ matches: allMatches.slice(0, MAX_SEARCH_MATCHES),
39289
+ totalFound
39042
39290
  };
39043
- findInteractive(cellEl);
39044
- if (interactiveElements.length === 0) {
39045
- const textValue = extractCellText(cellEl);
39046
- if (textValue) {
39047
- return {
39048
- ref: cellEl.ref,
39049
- column: cellEl.text || "",
39050
- value: textValue,
39051
- hasValue: true
39052
- };
39291
+ }
39292
+ function findElementInSections(sections, targetRef, ancestors = []) {
39293
+ for (const section of sections) {
39294
+ const newAncestors = [...ancestors, section];
39295
+ const path5 = newAncestors.map((s) => s.heading);
39296
+ if (section.elements.some((el) => el.ref === targetRef)) {
39297
+ return { section, ancestors: newAncestors, path: path5 };
39053
39298
  }
39054
- return null;
39299
+ if (section.gridInfo && gridContainsRef(section.gridInfo, targetRef)) {
39300
+ return { section, ancestors: newAncestors, path: path5 };
39301
+ }
39302
+ const found = findElementInSections(section.subsections, targetRef, newAncestors);
39303
+ if (found) return found;
39055
39304
  }
39056
- const found = interactiveElements.reduce(
39057
- (best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
39058
- );
39059
- const column = stripBlankPatterns(found.column);
39060
- const value = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
39061
- const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
39062
- return {
39063
- ref: found.ref,
39064
- column: column.trim(),
39065
- value,
39066
- hasValue: value !== "",
39067
- gridcellLabel: gridcellLabel || void 0,
39068
- role: found.role
39305
+ return null;
39306
+ }
39307
+ function populateSearchContext(matches, sections) {
39308
+ for (const match of matches) {
39309
+ const result = findElementInSections(sections, match.ref);
39310
+ if (result) {
39311
+ const { section, ancestors, path: path5 } = result;
39312
+ const inFocusedDialog = ancestors.some((s) => s.isFocused);
39313
+ const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
39314
+ if (inFocusedDialog) {
39315
+ match.context = "focused";
39316
+ } else if (inBackground) {
39317
+ match.context = "background";
39318
+ } else {
39319
+ match.context = "foreground";
39320
+ }
39321
+ const SKIP_HEADINGS = ["generic", "main", "iframe"];
39322
+ match.sectionPath = path5.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
39323
+ }
39324
+ }
39325
+ }
39326
+ function sortMatchesByContext(matches) {
39327
+ const contextOrder = {
39328
+ focused: 0,
39329
+ foreground: 1,
39330
+ background: 2
39069
39331
  };
39332
+ return [...matches].sort((a, b) => {
39333
+ const aContext = contextOrder[a.context ?? "foreground"] ?? 1;
39334
+ const bContext = contextOrder[b.context ?? "foreground"] ?? 1;
39335
+ if (aContext !== bContext) return aContext - bContext;
39336
+ if (a.isInteractive && !b.isInteractive) return -1;
39337
+ if (!a.isInteractive && b.isInteractive) return 1;
39338
+ return 0;
39339
+ });
39070
39340
  }
39071
- function reconcileCellColumn(cell, knownColumns) {
39072
- if (knownColumns.has(cell.column)) return cell;
39073
- if (cell.gridcellLabel && knownColumns.has(cell.gridcellLabel)) {
39074
- const newValue = cell.column || cell.value;
39075
- const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
39076
- return {
39077
- ...cell,
39078
- column: cell.gridcellLabel,
39079
- value: cleanValue,
39080
- hasValue: cleanValue !== ""
39081
- };
39341
+
39342
+ // ../browser-core/src/snapshot-formatter-sections.ts
39343
+ var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
39344
+ "generic",
39345
+ "paragraph",
39346
+ "text",
39347
+ "strong",
39348
+ "emphasis",
39349
+ "label",
39350
+ "heading"
39351
+ ]);
39352
+ var MIN_ELEMENTS_TO_COLLAPSE = 20;
39353
+ var DIALOG_ROLES = /* @__PURE__ */ new Set(["dialog", "alertdialog"]);
39354
+ function isVisualHeading(element, siblings, siblingIndex) {
39355
+ if (element.role !== "generic" && element.role !== "paragraph") return false;
39356
+ if (isClickableByAttribute(element)) return false;
39357
+ const text = getVisualHeadingText(element);
39358
+ if (!text || text.length < 3 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
39359
+ if (hasInteractiveDescendants(element)) return false;
39360
+ let interactiveCount = 0;
39361
+ for (let i = siblingIndex + 1; i < siblings.length; i++) {
39362
+ const sib = siblings[i];
39363
+ if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) break;
39364
+ if (sib.role === "generic" || sib.role === "paragraph") {
39365
+ if (!isClickableByAttribute(sib) && !hasInteractiveDescendants(sib)) {
39366
+ const sibText = getVisualHeadingText(sib);
39367
+ if (sibText && sibText.length >= 3 && sibText.length <= VISUAL_HEADING_MAX_LENGTH) {
39368
+ break;
39369
+ }
39370
+ }
39371
+ }
39372
+ if (hasInteractiveDescendants(sib) || isClickableByAttribute(sib)) {
39373
+ interactiveCount++;
39374
+ }
39082
39375
  }
39083
- let bestMatch = "";
39084
- for (const col of knownColumns) {
39085
- if (cell.column.startsWith(col) && col.length > bestMatch.length && // Ensure there's a space separator after the column name (not a partial word match)
39086
- (cell.column.length === col.length || cell.column[col.length] === " ")) {
39087
- bestMatch = col;
39376
+ return interactiveCount >= 3;
39377
+ }
39378
+ function getVisualHeadingText(element) {
39379
+ const direct = element.text;
39380
+ if (direct) return direct;
39381
+ for (const child of element.children) {
39382
+ if (TEXT_CARRYING_ROLES.has(child.role) && child.text) return child.text;
39383
+ }
39384
+ return void 0;
39385
+ }
39386
+ function findNextSectionBoundary(siblings, startIndex) {
39387
+ for (let i = startIndex; i < siblings.length; i++) {
39388
+ const sib = siblings[i];
39389
+ if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) return i;
39390
+ if (isVisualHeading(sib, siblings, i)) return i;
39391
+ }
39392
+ return siblings.length;
39393
+ }
39394
+ function isFieldLabelCandidate(element) {
39395
+ if (element.role !== "generic" && element.role !== "paragraph" && element.role !== "text" && element.role !== "label" && element.role !== "strong" && element.role !== "emphasis") {
39396
+ return false;
39397
+ }
39398
+ if (isClickableByAttribute(element)) return false;
39399
+ if (hasInteractiveDescendants(element)) return false;
39400
+ const text = getFieldLabelText(element);
39401
+ if (!text || text.length < 1 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
39402
+ return true;
39403
+ }
39404
+ function getFieldLabelText(element) {
39405
+ if (element.text) return element.text.trim();
39406
+ if (element.inputValue && !INTERACTIVE_ROLES.has(element.role)) return element.inputValue.trim();
39407
+ for (const child of element.children) {
39408
+ if (FIELD_LABEL_TEXT_ROLES.has(child.role)) {
39409
+ if (child.text) return child.text.trim();
39410
+ if (child.inputValue && !INTERACTIVE_ROLES.has(child.role)) return child.inputValue.trim();
39088
39411
  }
39089
39412
  }
39090
- if (bestMatch) {
39091
- const remainder = cell.column.slice(bestMatch.length).trim();
39092
- const newValue = isBlankValue(remainder) ? "" : remainder;
39093
- return {
39094
- ...cell,
39095
- column: bestMatch,
39096
- value: newValue || cell.value,
39097
- hasValue: (newValue || cell.value) !== ""
39098
- };
39413
+ return void 0;
39414
+ }
39415
+ function resolveContainerFieldLabel(container) {
39416
+ const kids = container.children;
39417
+ if (kids.length < 2 || kids.length > 4) return void 0;
39418
+ const firstChild = kids[0];
39419
+ if (isFieldLabelCandidate(firstChild)) {
39420
+ return getFieldLabelText(firstChild);
39099
39421
  }
39100
- return cell;
39422
+ return void 0;
39101
39423
  }
39102
- function reconcileGridCells(grid) {
39103
- if (grid.columns.length === 0) return grid;
39104
- const knownColumns = new Set(grid.columns.map((c2) => c2.name));
39105
- return {
39106
- ...grid,
39107
- rows: grid.rows.map((row) => ({
39108
- ...row,
39109
- cells: row.cells.map((cell) => {
39110
- const reconciled = reconcileCellColumn(cell, knownColumns);
39111
- if (knownColumns.has(reconciled.column)) return reconciled;
39112
- if (grid.columnByPosition && cell.positionInRow !== void 0 && grid.headerChildCount !== void 0 && row.totalChildren === grid.headerChildCount) {
39113
- const positionalColumn = grid.columnByPosition.get(cell.positionInRow);
39114
- if (positionalColumn) {
39115
- const newValue = cell.column || cell.value;
39116
- const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
39117
- return {
39118
- ...cell,
39119
- column: positionalColumn,
39120
- value: cleanValue,
39121
- hasValue: cleanValue !== ""
39122
- };
39424
+ function findFieldLabelFromSiblings(siblings, interactiveIndex) {
39425
+ for (let i = interactiveIndex - 1; i >= 0; i--) {
39426
+ const sib = siblings[i];
39427
+ if (INTERACTIVE_ROLES.has(sib.role) || STRUCTURAL_ROLES.has(sib.role) || sib.role === "heading") {
39428
+ break;
39429
+ }
39430
+ if (isClickableByAttribute(sib)) {
39431
+ if (sib.role === "generic" || sib.role === "paragraph" || sib.role === "label") {
39432
+ for (let j = i - 1; j >= 0; j--) {
39433
+ const prev = siblings[j];
39434
+ if (INTERACTIVE_ROLES.has(prev.role) || isClickableByAttribute(prev) || STRUCTURAL_ROLES.has(prev.role) || prev.role === "heading") {
39435
+ break;
39436
+ }
39437
+ if (isFieldLabelCandidate(prev)) {
39438
+ return getFieldLabelText(prev);
39123
39439
  }
39440
+ if (prev.children.length >= 1 && prev.children.length <= 4) {
39441
+ const containerLabel = resolveContainerFieldLabel(prev);
39442
+ if (containerLabel) return containerLabel;
39443
+ }
39444
+ if (prev.children.length > 4) break;
39124
39445
  }
39125
- return reconciled;
39126
- })
39127
- }))
39128
- };
39446
+ }
39447
+ break;
39448
+ }
39449
+ if (isFieldLabelCandidate(sib)) {
39450
+ return getFieldLabelText(sib);
39451
+ }
39452
+ if (sib.children.length >= 1 && sib.children.length <= 4) {
39453
+ const containerLabel = resolveContainerFieldLabel(sib);
39454
+ if (containerLabel) return containerLabel;
39455
+ }
39456
+ if (sib.children.length > 4) break;
39457
+ }
39458
+ return void 0;
39129
39459
  }
39130
- function findActiveElement(elements) {
39131
- let deepestActive = null;
39132
- function findDeepest(element) {
39133
- for (const child of element.children) {
39134
- findDeepest(child);
39460
+ function findFieldLabelInContext(_element, siblings, siblingIndex) {
39461
+ return findFieldLabelFromSiblings(siblings, siblingIndex);
39462
+ }
39463
+ function shouldDropInheritedFieldLabel(element, inheritedFieldLabel) {
39464
+ if (!inheritedFieldLabel) return false;
39465
+ if (INTERACTIVE_LEAF_ROLES.has(element.role)) return false;
39466
+ if (!isClickableByAttribute(element)) return false;
39467
+ const directText = getElementText(element);
39468
+ if (directText) {
39469
+ return directText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
39470
+ }
39471
+ const childTexts = [];
39472
+ for (const child of element.children) {
39473
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
39474
+ const text = getElementText(child);
39475
+ if (text && TEXT_CARRYING_ROLES.has(child.role)) {
39476
+ childTexts.push(text);
39477
+ }
39135
39478
  }
39136
- if (isActiveElement(element)) {
39137
- if (!deepestActive) {
39138
- deepestActive = element;
39479
+ }
39480
+ const composedText = childTexts.join(" ");
39481
+ if (!composedText) return false;
39482
+ return composedText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
39483
+ }
39484
+ function isExpandedElement(element) {
39485
+ return element.attributes["expanded"] === "true" || element.attributes["aria-expanded"] === "true";
39486
+ }
39487
+ function collectExpandedChildren(element, result) {
39488
+ for (const child of element.children) {
39489
+ if (STRUCTURAL_ROLES.has(child.role)) continue;
39490
+ if (INTERACTIVE_LEAF_ROLES.has(child.role) && child.ref) {
39491
+ result.push(formatElement(child));
39492
+ continue;
39493
+ }
39494
+ if (child.role === "listitem" && child.ref) {
39495
+ const text = child.text || child.children.map((c2) => getElementText(c2)).filter((value) => Boolean(value)).join(" | ") || getDisplayText(child);
39496
+ if (text && text !== "listitem") {
39497
+ result.push(formatElement(child, { overrideLabel: text }));
39498
+ continue;
39139
39499
  }
39140
39500
  }
39501
+ if (isClickableByAttribute(child) && child.ref) {
39502
+ result.push(formatElement(child));
39503
+ }
39504
+ collectExpandedChildren(child, result);
39141
39505
  }
39142
- for (const element of elements) {
39143
- findDeepest(element);
39506
+ }
39507
+ function hasActionableChildren(element) {
39508
+ const children2 = [];
39509
+ collectExpandedChildren(element, children2);
39510
+ return children2.length > 0;
39511
+ }
39512
+ function isExpandedWithActionableChildren(element) {
39513
+ if (!isExpandedElement(element)) return false;
39514
+ if (element.role === "combobox") return false;
39515
+ return hasActionableChildren(element);
39516
+ }
39517
+ function composeTriggerLabel(element) {
39518
+ const ariaLabel = element.attributes["aria-label"];
39519
+ if (ariaLabel) return ariaLabel;
39520
+ const fullText = element.text || "";
39521
+ if (fullText) {
39522
+ let collectLeafTexts2 = function(el) {
39523
+ for (const child of el.children) {
39524
+ const text = child.text?.trim();
39525
+ if (text) {
39526
+ leafTexts.push(text);
39527
+ }
39528
+ collectLeafTexts2(child);
39529
+ }
39530
+ };
39531
+ var collectLeafTexts = collectLeafTexts2;
39532
+ const leafTexts = [];
39533
+ collectLeafTexts2(element);
39534
+ if (leafTexts.length > 0) {
39535
+ let stripped = fullText;
39536
+ for (const lt of leafTexts) {
39537
+ stripped = stripped.replace(lt, "");
39538
+ }
39539
+ stripped = stripped.replace(/\s+/g, " ").trim();
39540
+ if (stripped) return stripped;
39541
+ }
39144
39542
  }
39145
- return deepestActive;
39543
+ return element.role;
39146
39544
  }
39147
39545
  function formatElement(element, options) {
39148
- const isClickable = isClickableByAttribute(element) && !INTERACTIVE_ROLES.has(element.role);
39149
- const rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
39150
- const textSource = normalizeIconLabelText(element.role, rawTextSource);
39546
+ const isClickable = isActionableClickableGeneric(element);
39547
+ const textSource = getDisplayText(element, { overrideLabel: options?.overrideLabel });
39151
39548
  const extracted = extractDataValue(textSource);
39152
39549
  const normalizedAttributes = Object.entries(element.attributes).filter(
39153
- ([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options"
39550
+ ([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options" && key !== "field"
39154
39551
  ).map(([key, val]) => {
39155
39552
  if (key === "aria-haspopup") return "haspopup";
39156
39553
  if (key === "aria-expanded") return `expanded=${val}`;
@@ -39169,7 +39566,7 @@ function formatElement(element, options) {
39169
39566
  ref: element.ref,
39170
39567
  role: element.role,
39171
39568
  name: textSource,
39172
- fieldLabel: options?.fieldLabel,
39569
+ fieldLabel: options?.fieldLabel || element.attributes["field"] || void 0,
39173
39570
  label: extracted.label || void 0,
39174
39571
  value: extracted.value,
39175
39572
  status: extracted.status,
@@ -39181,105 +39578,141 @@ function formatElement(element, options) {
39181
39578
  attributes: normalizedAttributes
39182
39579
  };
39183
39580
  }
39184
- function collectComboboxOptions(element) {
39185
- const options = [];
39186
- function walk(el) {
39187
- if (el.role === "option" && el.text?.trim()) {
39188
- options.push(el.text.trim());
39189
- }
39190
- for (const child of el.children) {
39191
- walk(child);
39192
- }
39193
- }
39194
- for (const child of element.children) {
39195
- walk(child);
39196
- }
39197
- return options.length > 0 ? options : void 0;
39198
- }
39199
- var MIN_ELEMENTS_TO_COLLAPSE = 20;
39200
39581
  function shouldCollapse(section) {
39201
39582
  if (section.containsActive) return false;
39202
39583
  if (section.hasSearchMatch) return false;
39203
39584
  if (section.isBackground) return true;
39204
39585
  if (section.role === "dialog" || section.role === "alertdialog") return false;
39205
- if (section.role === "navigation" || section.role === "menubar" || section.role === "menu") {
39206
- return false;
39207
- }
39586
+ if (section.role === "navigation" || section.role === "menubar" || section.role === "menu") return false;
39208
39587
  if (section.role === "banner") return false;
39209
39588
  if (section.role === "form") return false;
39210
39589
  if (section.elementCount >= MIN_ELEMENTS_TO_COLLAPSE) return true;
39211
39590
  return false;
39212
39591
  }
39213
- function buildSectionTree(elements) {
39214
- const sections = [];
39215
- for (const element of elements) {
39216
- const section = buildSection(element, 0);
39217
- if (section) {
39218
- sections.push(section);
39592
+ function deduplicateFieldLabels(elements) {
39593
+ const byFieldLabel = /* @__PURE__ */ new Map();
39594
+ for (const el of elements) {
39595
+ if (!el.fieldLabel) continue;
39596
+ const existing = byFieldLabel.get(el.fieldLabel);
39597
+ if (existing) {
39598
+ existing.push(el);
39599
+ } else {
39600
+ byFieldLabel.set(el.fieldLabel, [el]);
39219
39601
  }
39220
39602
  }
39221
- markBackgroundSections(sections);
39222
- return sections;
39223
- }
39224
- function markBackgroundSections(sections) {
39225
- const hasActiveSibling = sections.some((s) => s.containsActive);
39226
- if (hasActiveSibling) {
39227
- for (const section of sections) {
39228
- if (!section.containsActive) {
39229
- section.isBackground = true;
39230
- section.collapsed = shouldCollapse(section);
39603
+ for (const [, group] of byFieldLabel) {
39604
+ if (group.length < 2) continue;
39605
+ for (let i = 0; i < group.length; i++) {
39606
+ const el = group[i];
39607
+ if (el.name && el.name !== el.fieldLabel) {
39608
+ el.fieldLabel = `${el.fieldLabel}: ${el.name}`;
39609
+ } else {
39610
+ el.fieldLabel = `${el.fieldLabel} (${i + 1})`;
39231
39611
  }
39232
39612
  }
39233
39613
  }
39234
- for (const section of sections) {
39235
- markBackgroundSections(section.subsections);
39236
- }
39237
39614
  }
39238
- function markMatchingSections(sections, matchedRefs) {
39239
- for (const section of sections) {
39240
- const hasDirectMatch = section.elements.some((el) => matchedRefs.has(el.ref));
39241
- markMatchingSections(section.subsections, matchedRefs);
39242
- const hasSubsectionMatch = section.subsections.some((s) => s.hasSearchMatch);
39243
- if (hasDirectMatch || hasSubsectionMatch) {
39244
- section.hasSearchMatch = true;
39245
- section.collapsed = false;
39246
- }
39615
+ function buildGridSection(element) {
39616
+ const gridInfo = detectGrid(element);
39617
+ if (!gridInfo) {
39618
+ throw new Error("buildGridSection requires a grid element");
39247
39619
  }
39620
+ return {
39621
+ ref: element.ref || "",
39622
+ role: element.role,
39623
+ heading: "Grid",
39624
+ level: 0,
39625
+ elements: [],
39626
+ subsections: [],
39627
+ containsActive: gridInfo.containsActive,
39628
+ collapsed: false,
39629
+ isBackground: false,
39630
+ isFocused: false,
39631
+ elementCount: gridInfo.totalRows * Math.max(gridInfo.columns.length, 1),
39632
+ gridInfo
39633
+ };
39248
39634
  }
39249
- var DIALOG_ROLES = /* @__PURE__ */ new Set(["dialog", "alertdialog"]);
39250
- function sortSectionsByFocus(sections) {
39251
- const dialogStack = [];
39252
- function processLevel(levelSections) {
39253
- for (const section of levelSections) {
39254
- const result = processLevel(section.subsections);
39255
- section.subsections = result;
39635
+ function collectInteractiveElements(element, elements, subsections, depth, inheritedFieldLabel) {
39636
+ if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
39637
+ const effectiveFieldLabel = shouldDropInheritedFieldLabel(element, inheritedFieldLabel) ? void 0 : inheritedFieldLabel;
39638
+ if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
39639
+ elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: effectiveFieldLabel }));
39640
+ collectExpandedChildren(element, elements);
39641
+ return;
39256
39642
  }
39257
- const dialogs = levelSections.filter((s) => DIALOG_ROLES.has(s.role));
39258
- const nonDialogs = levelSections.filter((s) => !DIALOG_ROLES.has(s.role));
39259
- const focusedDialog = dialogs.find((d) => d.containsActive);
39260
- if (focusedDialog) {
39261
- focusedDialog.isFocused = true;
39262
- dialogStack.unshift(focusedDialog.heading);
39643
+ if (!isMenuTriggerNoise(element)) {
39644
+ elements.push(formatElement(element, { fieldLabel: effectiveFieldLabel }));
39263
39645
  }
39264
- for (const dialog of dialogs) {
39265
- if (!dialog.containsActive) {
39266
- dialogStack.push(dialog.heading);
39646
+ if (!INTERACTIVE_LEAF_ROLES.has(element.role) && isClickableByAttribute(element)) {
39647
+ for (const child of element.children) {
39648
+ collectInteractiveElements(child, elements, subsections, depth + 1, effectiveFieldLabel);
39267
39649
  }
39268
39650
  }
39269
- const sortedDialogs = dialogs.sort((a, b) => {
39270
- if (a.containsActive && !b.containsActive) return -1;
39271
- if (!a.containsActive && b.containsActive) return 1;
39272
- return 0;
39273
- });
39274
- return [...sortedDialogs, ...nonDialogs];
39651
+ return;
39652
+ }
39653
+ if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table" && !isWidgetTable(element)) {
39654
+ const section = buildSection(element, depth);
39655
+ if (section) {
39656
+ subsections.push(section);
39657
+ }
39658
+ return;
39659
+ }
39660
+ const kids = element.children;
39661
+ for (let ki = 0; ki < kids.length; ki++) {
39662
+ const child = kids[ki];
39663
+ if (isVisualHeading(child, kids, ki)) {
39664
+ const headingText = getVisualHeadingText(child);
39665
+ const sectionElements = [];
39666
+ const sectionSubsections = [];
39667
+ const nextBoundary = findNextSectionBoundary(kids, ki + 1);
39668
+ for (let si = ki + 1; si < nextBoundary; si++) {
39669
+ collectInteractiveElements(kids[si], sectionElements, sectionSubsections, depth + 2);
39670
+ }
39671
+ if (sectionElements.length > 0 || sectionSubsections.length > 0) {
39672
+ const totalElements = sectionElements.length + sectionSubsections.reduce((sum, s) => sum + s.elementCount, 0);
39673
+ subsections.push({
39674
+ heading: headingText ?? "",
39675
+ ref: child.ref,
39676
+ role: "heading",
39677
+ level: 4,
39678
+ elements: sectionElements,
39679
+ subsections: sectionSubsections,
39680
+ collapsed: false,
39681
+ containsActive: false,
39682
+ isBackground: false,
39683
+ isFocused: false,
39684
+ elementCount: totalElements
39685
+ });
39686
+ ki = nextBoundary - 1;
39687
+ } else {
39688
+ collectInteractiveElements(child, elements, subsections, depth + 1);
39689
+ }
39690
+ } else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
39691
+ const rawFieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
39692
+ const fieldLabel = shouldDropInheritedFieldLabel(child, rawFieldLabel) ? void 0 : rawFieldLabel;
39693
+ if (!isMenuTriggerNoise(child)) {
39694
+ if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39695
+ elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
39696
+ collectExpandedChildren(child, elements);
39697
+ } else {
39698
+ elements.push(formatElement(child, { fieldLabel }));
39699
+ }
39700
+ }
39701
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
39702
+ for (const grandchild of child.children) {
39703
+ collectInteractiveElements(grandchild, elements, subsections, depth + 1, fieldLabel);
39704
+ }
39705
+ }
39706
+ } else {
39707
+ const containerLabel = resolveContainerFieldLabel(child) ?? (child.role === "table" ? findFieldLabelFromSiblings(kids, ki) : void 0) ?? inheritedFieldLabel;
39708
+ collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
39709
+ }
39275
39710
  }
39276
- const sorted = processLevel(sections);
39277
- return { sorted, dialogStack };
39278
39711
  }
39279
39712
  function buildSection(element, depth) {
39280
39713
  const gridInfo = detectGrid(element);
39281
39714
  if (gridInfo) {
39282
- return buildGridSection(element, gridInfo);
39715
+ return buildGridSection(element);
39283
39716
  }
39284
39717
  const isHeading = element.role === "heading";
39285
39718
  const isStructural = STRUCTURAL_ROLES.has(element.role);
@@ -39317,7 +39750,6 @@ function buildSection(element, depth) {
39317
39750
  ref: child.ref,
39318
39751
  role: "heading",
39319
39752
  level: 4,
39320
- // Visual headings rendered at h6 level (4 + 2 = 6)
39321
39753
  elements: sectionElements,
39322
39754
  subsections: sectionSubsections,
39323
39755
  collapsed: false,
@@ -39361,113 +39793,214 @@ function buildSection(element, depth) {
39361
39793
  const section = {
39362
39794
  ref: element.ref || "",
39363
39795
  role: element.role,
39364
- heading: element.text || (isClickableByAttribute(element) ? composeLabel(element) : element.role),
39796
+ heading: element.text || (isClickableByAttribute(element) ? getDisplayText(element) : element.role),
39365
39797
  level,
39366
39798
  elements: interactiveElements,
39367
39799
  subsections,
39368
39800
  containsActive,
39369
39801
  collapsed: false,
39370
39802
  isBackground: false,
39371
- // Will be set by markBackgroundSections if needed
39372
39803
  isFocused: false,
39373
- // Will be set by sortSectionsByFocus if needed
39374
39804
  elementCount
39375
39805
  };
39806
+ deduplicateFieldLabels(interactiveElements);
39376
39807
  section.collapsed = shouldCollapse(section);
39377
39808
  return section;
39378
39809
  }
39379
- function buildGridSection(element, gridInfo) {
39380
- return {
39381
- ref: element.ref || "",
39382
- role: element.role,
39383
- heading: "Grid",
39384
- level: 0,
39385
- elements: [],
39386
- // Grid cells formatted via gridInfo instead
39387
- subsections: [],
39388
- containsActive: gridInfo.containsActive,
39389
- collapsed: false,
39390
- // Grids use their own summarization
39391
- isBackground: false,
39392
- isFocused: false,
39393
- elementCount: gridInfo.totalRows * Math.max(gridInfo.columns.length, 1),
39394
- gridInfo
39395
- };
39810
+ function markBackgroundSections(sections) {
39811
+ const hasActiveSibling = sections.some((s) => s.containsActive);
39812
+ if (hasActiveSibling) {
39813
+ for (const section of sections) {
39814
+ if (!section.containsActive) {
39815
+ section.isBackground = true;
39816
+ section.collapsed = shouldCollapse(section);
39817
+ }
39818
+ }
39819
+ }
39820
+ for (const section of sections) {
39821
+ markBackgroundSections(section.subsections);
39822
+ }
39396
39823
  }
39397
- function collectInteractiveElements(element, elements, subsections, depth, inheritedFieldLabel) {
39398
- if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
39399
- if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
39400
- elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: inheritedFieldLabel }));
39401
- collectExpandedChildren(element, elements);
39402
- return;
39824
+ function buildSectionTree(elements) {
39825
+ const sections = [];
39826
+ for (const element of elements) {
39827
+ const section = buildSection(element, 0);
39828
+ if (section) {
39829
+ sections.push(section);
39403
39830
  }
39404
- if (!isMenuTriggerNoise(element)) {
39405
- elements.push(formatElement(element, { fieldLabel: inheritedFieldLabel }));
39831
+ }
39832
+ markBackgroundSections(sections);
39833
+ return sections;
39834
+ }
39835
+ function markMatchingSections(sections, matchedRefs) {
39836
+ for (const section of sections) {
39837
+ const hasDirectMatch = section.elements.some((el) => matchedRefs.has(el.ref)) || (section.gridInfo ? Array.from(matchedRefs).some((ref) => gridContainsRef(section.gridInfo, ref)) : false);
39838
+ markMatchingSections(section.subsections, matchedRefs);
39839
+ const hasSubsectionMatch = section.subsections.some((s) => s.hasSearchMatch);
39840
+ if (hasDirectMatch || hasSubsectionMatch) {
39841
+ section.hasSearchMatch = true;
39842
+ section.collapsed = false;
39406
39843
  }
39407
- if (!INTERACTIVE_LEAF_ROLES.has(element.role) && isClickableByAttribute(element)) {
39408
- for (const child of element.children) {
39409
- collectInteractiveElements(child, elements, subsections, depth + 1, inheritedFieldLabel);
39844
+ }
39845
+ }
39846
+ function sortSectionsByFocus(sections) {
39847
+ const dialogStack = [];
39848
+ function processLevel(levelSections) {
39849
+ for (const section of levelSections) {
39850
+ section.subsections = processLevel(section.subsections);
39851
+ }
39852
+ const dialogs = levelSections.filter((s) => DIALOG_ROLES.has(s.role));
39853
+ const nonDialogs = levelSections.filter((s) => !DIALOG_ROLES.has(s.role));
39854
+ const focusedDialog = dialogs.find((d) => d.containsActive);
39855
+ if (focusedDialog) {
39856
+ focusedDialog.isFocused = true;
39857
+ dialogStack.unshift(focusedDialog.heading);
39858
+ }
39859
+ for (const dialog of dialogs) {
39860
+ if (!dialog.containsActive) {
39861
+ dialogStack.push(dialog.heading);
39410
39862
  }
39411
39863
  }
39412
- return;
39864
+ const sortedDialogs = dialogs.sort((a, b) => {
39865
+ if (a.containsActive && !b.containsActive) return -1;
39866
+ if (!a.containsActive && b.containsActive) return 1;
39867
+ return 0;
39868
+ });
39869
+ return [...sortedDialogs, ...nonDialogs];
39413
39870
  }
39414
- if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table") {
39415
- const section = buildSection(element, depth);
39416
- if (section) {
39417
- subsections.push(section);
39871
+ const sorted = processLevel(sections);
39872
+ return { sorted, dialogStack };
39873
+ }
39874
+ function expandSectionByRef(sections, targetRef) {
39875
+ for (const section of sections) {
39876
+ if (section.ref === targetRef) {
39877
+ section.collapsed = false;
39878
+ return true;
39879
+ }
39880
+ if (expandSectionByRef(section.subsections, targetRef)) {
39881
+ return true;
39418
39882
  }
39419
- return;
39420
39883
  }
39421
- const kids = element.children;
39422
- for (let ki = 0; ki < kids.length; ki++) {
39423
- const child = kids[ki];
39424
- if (isVisualHeading(child, kids, ki)) {
39425
- const headingText = getVisualHeadingText(child);
39426
- const sectionElements = [];
39427
- const sectionSubsections = [];
39428
- const nextBoundary = findNextSectionBoundary(kids, ki + 1);
39429
- for (let si = ki + 1; si < nextBoundary; si++) {
39430
- collectInteractiveElements(kids[si], sectionElements, sectionSubsections, depth + 2);
39431
- }
39432
- if (sectionElements.length > 0 || sectionSubsections.length > 0) {
39433
- const totalElements = sectionElements.length + sectionSubsections.reduce((sum, s) => sum + s.elementCount, 0);
39434
- subsections.push({
39435
- heading: headingText ?? "",
39436
- ref: child.ref,
39437
- role: "heading",
39438
- level: 4,
39439
- elements: sectionElements,
39440
- subsections: sectionSubsections,
39441
- collapsed: false,
39442
- containsActive: false,
39443
- isBackground: false,
39444
- isFocused: false,
39445
- elementCount: totalElements
39446
- });
39447
- ki = nextBoundary - 1;
39448
- } else {
39449
- collectInteractiveElements(child, elements, subsections, depth + 1);
39450
- }
39451
- } else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
39452
- const fieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
39453
- if (!isMenuTriggerNoise(child)) {
39454
- if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39455
- elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
39456
- collectExpandedChildren(child, elements);
39457
- } else {
39458
- elements.push(formatElement(child, { fieldLabel }));
39459
- }
39460
- }
39461
- if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
39462
- for (const grandchild of child.children) {
39463
- collectInteractiveElements(grandchild, elements, subsections, depth + 1, fieldLabel);
39464
- }
39884
+ return false;
39885
+ }
39886
+ function findSectionByRef(sections, targetRef) {
39887
+ for (const section of sections) {
39888
+ if (section.ref === targetRef) {
39889
+ return section;
39890
+ }
39891
+ const found = findSectionByRef(section.subsections, targetRef);
39892
+ if (found) {
39893
+ return found;
39894
+ }
39895
+ }
39896
+ return null;
39897
+ }
39898
+
39899
+ // ../browser-core/src/snapshot-formatter-render.ts
39900
+ var COLOR_NAMES = [
39901
+ "red",
39902
+ "green",
39903
+ "blue",
39904
+ "yellow",
39905
+ "magenta",
39906
+ "cyan",
39907
+ "orange",
39908
+ "purple",
39909
+ "teal",
39910
+ "pink"
39911
+ ];
39912
+ function findActiveElement(elements) {
39913
+ let deepestActive = null;
39914
+ function findDeepest(element) {
39915
+ for (const child of element.children) {
39916
+ findDeepest(child);
39917
+ }
39918
+ if (element.attributes["active"] !== void 0 || element.rawLine.includes("[active]")) {
39919
+ if (!deepestActive) {
39920
+ deepestActive = element;
39465
39921
  }
39922
+ }
39923
+ }
39924
+ for (const element of elements) {
39925
+ findDeepest(element);
39926
+ }
39927
+ return deepestActive;
39928
+ }
39929
+ function formatElementLine(el, indent) {
39930
+ const indentStr = " ".repeat(indent);
39931
+ const parts = [];
39932
+ if (el.isActive) {
39933
+ parts.push("ACTIVE \u2192");
39934
+ }
39935
+ parts.push(`[${el.ref}]`);
39936
+ parts.push(el.role);
39937
+ if (el.isClickable) {
39938
+ parts.push("[clickable]");
39939
+ }
39940
+ if (el.isDisabled) {
39941
+ parts.push("[disabled]");
39942
+ }
39943
+ for (const attr of el.attributes) {
39944
+ if (attr !== "disabled") {
39945
+ parts.push(`[${attr}]`);
39946
+ }
39947
+ }
39948
+ if (el.fieldLabel) {
39949
+ parts.push(`| field: "${el.fieldLabel}"`);
39950
+ }
39951
+ if (el.label && el.value) {
39952
+ parts.push(`| label: "${el.label}" | value: "${el.value}"`);
39953
+ } else if (el.value) {
39954
+ parts.push(`| value: "${el.value}"`);
39955
+ } else if (el.label) {
39956
+ parts.push(`| label: "${el.label}"`);
39957
+ } else if (el.name) {
39958
+ parts.push(`"${el.name}"`);
39959
+ }
39960
+ if (el.status) {
39961
+ parts.push(`| status: "${el.status}"`);
39962
+ }
39963
+ if ((el.role === "textbox" || el.role === "combobox" || el.role === "searchbox") && el.inputValue) {
39964
+ parts.push(`| input: "${el.inputValue}"`);
39965
+ }
39966
+ if (el.options?.length) {
39967
+ const MAX_DISPLAY = 8;
39968
+ const displayed = el.options.slice(0, MAX_DISPLAY);
39969
+ const optionsList = displayed.map((o) => `"${o}"`).join(", ");
39970
+ const suffix = el.options.length > MAX_DISPLAY ? `, ... +${el.options.length - MAX_DISPLAY} more` : "";
39971
+ if (el.role === "combobox") {
39972
+ parts.push(`| options: [${optionsList}${suffix}] \u2192 use browser_select_option`);
39466
39973
  } else {
39467
- const containerLabel = resolveContainerFieldLabel(child) ?? inheritedFieldLabel;
39468
- collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
39974
+ parts.push(`| options: [${optionsList}${suffix}]`);
39469
39975
  }
39470
39976
  }
39977
+ return indentStr + parts.join(" ");
39978
+ }
39979
+ function collectAllElementsFlat(section) {
39980
+ const elements = [...section.elements];
39981
+ for (const subsection of section.subsections) {
39982
+ elements.push(...collectAllElementsFlat(subsection));
39983
+ }
39984
+ return elements;
39985
+ }
39986
+ function formatSubsectionSummary(subsection) {
39987
+ const refPart = subsection.ref ? ` [${subsection.ref}]` : "";
39988
+ if (subsection.gridInfo) {
39989
+ const colInfo = subsection.gridInfo.columns.length > 0 ? `${subsection.gridInfo.columns.length} columns` : "unknown columns";
39990
+ return `GRID${refPart}: ${subsection.gridInfo.totalRows} rows, ${colInfo}`;
39991
+ }
39992
+ const allElements = collectAllElementsFlat(subsection);
39993
+ const elementNames = allElements.map((el) => el.label || el.name || el.role).filter(Boolean);
39994
+ const heading = subsection.heading || subsection.role;
39995
+ if (elementNames.length === 0) {
39996
+ return `${heading}${refPart}`;
39997
+ }
39998
+ const MAX_PREVIEW_NAMES = 3;
39999
+ const previewNames = elementNames.slice(0, MAX_PREVIEW_NAMES);
40000
+ const remainingCount = elementNames.length - previewNames.length;
40001
+ const namesList = previewNames.join(", ");
40002
+ const moreNote = remainingCount > 0 ? `... +${remainingCount} more` : "";
40003
+ return `${heading}${refPart}: ${namesList}${moreNote}`;
39471
40004
  }
39472
40005
  function formatSectionOutput(section, indent, showCollapsed) {
39473
40006
  if (section.gridInfo) {
@@ -39480,8 +40013,7 @@ function formatSectionOutput(section, indent, showCollapsed) {
39480
40013
  const focusedPart = section.isFocused ? " [FOCUSED]" : "";
39481
40014
  const backgroundPart = section.isBackground ? " [BACKGROUND]" : "";
39482
40015
  const activePart = section.containsActive && !section.isFocused ? " \u2190 ACTIVE" : "";
39483
- const headerLine = `${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`;
39484
- lines.push(headerLine);
40016
+ lines.push(`${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`);
39485
40017
  if (section.collapsed && !showCollapsed) {
39486
40018
  if (section.isBackground) {
39487
40019
  if (section.ref) {
@@ -39520,102 +40052,39 @@ function formatSectionOutput(section, indent, showCollapsed) {
39520
40052
  }
39521
40053
  return lines.join("\n");
39522
40054
  }
39523
- function collectAllElementsFlat(section) {
39524
- const elements = [...section.elements];
39525
- for (const subsection of section.subsections) {
39526
- elements.push(...collectAllElementsFlat(subsection));
39527
- }
39528
- return elements;
39529
- }
39530
- function formatSubsectionSummary(subsection) {
39531
- const refPart = subsection.ref ? ` [${subsection.ref}]` : "";
39532
- if (subsection.gridInfo) {
39533
- const colInfo = subsection.gridInfo.columns.length > 0 ? `${subsection.gridInfo.columns.length} columns` : "unknown columns";
39534
- return `GRID${refPart}: ${subsection.gridInfo.totalRows} rows, ${colInfo}`;
39535
- }
39536
- const allElements = collectAllElementsFlat(subsection);
39537
- const elementNames = allElements.map((el) => el.label || el.name || el.role).filter(Boolean);
39538
- const heading = subsection.heading || subsection.role;
39539
- if (elementNames.length === 0) {
39540
- return `${heading}${refPart}`;
39541
- }
39542
- const MAX_PREVIEW_NAMES = 3;
39543
- const previewNames = elementNames.slice(0, MAX_PREVIEW_NAMES);
39544
- const remainingCount = elementNames.length - previewNames.length;
39545
- const namesList = previewNames.join(", ");
39546
- const moreNote = remainingCount > 0 ? `... +${remainingCount} more` : "";
39547
- return `${heading}${refPart}: ${namesList}${moreNote}`;
39548
- }
39549
- function formatElementLine(el, indent) {
39550
- const indentStr = " ".repeat(indent);
39551
- const parts = [];
39552
- if (el.isActive) {
39553
- parts.push("ACTIVE \u2192");
39554
- }
39555
- parts.push(`[${el.ref}]`);
39556
- parts.push(el.role);
39557
- if (el.isClickable) {
39558
- parts.push("[clickable]");
39559
- }
39560
- if (el.isDisabled) {
39561
- parts.push("[disabled]");
39562
- }
39563
- for (const attr of el.attributes) {
39564
- if (attr !== "disabled") {
39565
- parts.push(`[${attr}]`);
40055
+ function countRenderedElements(sections) {
40056
+ let count = 0;
40057
+ for (const section of sections) {
40058
+ count += section.elements.length;
40059
+ if (section.gridInfo) {
40060
+ count += countRenderedGridItems(section.gridInfo);
39566
40061
  }
40062
+ count += countRenderedElements(section.subsections);
39567
40063
  }
39568
- if (el.fieldLabel) {
39569
- parts.push(`| field: "${el.fieldLabel}"`);
39570
- }
39571
- if (el.label && el.value) {
39572
- parts.push(`| label: "${el.label}" | value: "${el.value}"`);
39573
- } else if (el.value) {
39574
- parts.push(`| value: "${el.value}"`);
39575
- } else if (el.label) {
39576
- parts.push(`| label: "${el.label}"`);
39577
- } else if (el.name) {
39578
- parts.push(`"${el.name}"`);
39579
- }
39580
- if (el.status) {
39581
- parts.push(`| status: "${el.status}"`);
39582
- }
39583
- if ((el.role === "textbox" || el.role === "combobox" || el.role === "searchbox") && el.inputValue) {
39584
- parts.push(`| input: "${el.inputValue}"`);
39585
- }
39586
- if (el.options?.length) {
39587
- const MAX_DISPLAY = 8;
39588
- const displayed = el.options.slice(0, MAX_DISPLAY);
39589
- const optionsList = displayed.map((o) => `"${o}"`).join(", ");
39590
- const suffix = el.options.length > MAX_DISPLAY ? `, ... +${el.options.length - MAX_DISPLAY} more` : "";
39591
- if (el.role === "combobox") {
39592
- parts.push(`| options: [${optionsList}${suffix}] \u2192 use browser_select_option`);
39593
- } else {
39594
- parts.push(`| options: [${optionsList}${suffix}]`);
39595
- }
40064
+ return count;
40065
+ }
40066
+ function countTotalSections(sections) {
40067
+ let count = sections.length;
40068
+ for (const section of sections) {
40069
+ count += countTotalSections(section.subsections);
39596
40070
  }
39597
- return indentStr + parts.join(" ");
40071
+ return count;
39598
40072
  }
39599
- var COLOR_NAMES = [
39600
- "red",
39601
- "green",
39602
- "blue",
39603
- "yellow",
39604
- "magenta",
39605
- "cyan",
39606
- "orange",
39607
- "purple",
39608
- "teal",
39609
- "pink"
39610
- ];
39611
- function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult) {
39612
- const activeLabel = activeElement ? normalizeIconLabelText(
39613
- activeElement.role,
39614
- INTERACTIVE_LEAF_ROLES.has(activeElement.role) && isExpandedWithActionableChildren(activeElement) ? composeTriggerLabel(activeElement) : activeElement.text || ""
39615
- ) : "";
40073
+ function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult, currentTime, capturedToasts) {
40074
+ const activeLabel = activeElement ? (FORM_INPUT_ROLES.has(activeElement.role) || activeElement.role === "button" || activeElement.role === "link") && (activeElement.attributes["expanded"] === "true" || activeElement.attributes["aria-expanded"] === "true") ? getDisplayText(activeElement, { overrideLabel: composeTriggerLabel(activeElement) }) : getDisplayText(activeElement) : "";
39616
40075
  const activeDesc = activeElement ? `${activeElement.ref} ${activeElement.role} "${activeLabel}"` : "None";
39617
40076
  const dialogStackLine = dialogStack.length > 0 ? `
39618
40077
  DIALOG STACK: ${dialogStack.join(" \u2192 ")}` : "";
40078
+ let toastSection = "";
40079
+ if (capturedToasts && capturedToasts.length > 0) {
40080
+ const toastLines = capturedToasts.map((t) => {
40081
+ const ttl = t.snapshotsRemaining !== void 0 ? ` (expires in ${t.snapshotsRemaining} snapshots)` : "";
40082
+ return ` - [${t.role}] "${t.text}"${ttl}`;
40083
+ });
40084
+ toastSection = `
40085
+ TOAST NOTIFICATIONS:
40086
+ ${toastLines.join("\n")}`;
40087
+ }
39619
40088
  let searchSection = "";
39620
40089
  if (searchMatches && searchMatches.length > 0) {
39621
40090
  const hasContextInfo = searchMatches.some((m) => m.context);
@@ -39641,9 +40110,8 @@ DIALOG STACK: ${dialogStack.join(" \u2192 ")}` : "";
39641
40110
  SEARCH RESULTS (${matchCountText}${sortNote}):
39642
40111
  ${matchLines.join("\n")}`;
39643
40112
  }
39644
- const INPUT_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox", "spinbutton"]);
39645
40113
  let typeActionHint = "";
39646
- if (activeElement && INPUT_ROLES.has(activeElement.role)) {
40114
+ if (activeElement && FORM_INPUT_ROLES.has(activeElement.role)) {
39647
40115
  typeActionHint = `
39648
40116
  - Type in active field: browser_type ref=${activeElement.ref} text="..."`;
39649
40117
  }
@@ -39665,10 +40133,12 @@ PROBE RESULT:
39665
40133
  FULL TREE FILE: ${snapshotFilePath}
39666
40134
  Use bash or python_execute to grep/parse this file for detailed element analysis.`;
39667
40135
  }
40136
+ const browserTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
39668
40137
  return `---
39669
40138
  SEMANTIC SNAPSHOT - ${elementCount} elements in ${sectionCount} sections
40139
+ BROWSER TIME: ${browserTime} (UTC)
39670
40140
  FORMAT: Sections grouped by headings. [ref] tags identify clickable elements.
39671
- ACTIVE ELEMENT: ${activeDesc}${dialogStackLine}${searchSection}${probeSection}${fullTreeFileSection}
40141
+ ACTIVE ELEMENT: ${activeDesc}${dialogStackLine}${toastSection}${searchSection}${probeSection}${fullTreeFileSection}
39672
40142
  ACTIONS:
39673
40143
  - Click/type using [ref] values (e.g., browser_click ref=e123)${typeActionHint}
39674
40144
  - Click using coordinates from search results (e.g., browser_click x=450 y=320)
@@ -39676,21 +40146,6 @@ ACTIONS:
39676
40146
  - Use bash or python_execute on the FULL TREE FILE for detailed element analysis
39677
40147
  ---`;
39678
40148
  }
39679
- function countTotalElements(sections) {
39680
- let count = 0;
39681
- for (const section of sections) {
39682
- count += section.elements.length;
39683
- count += countTotalElements(section.subsections);
39684
- }
39685
- return count;
39686
- }
39687
- function countTotalSections(sections) {
39688
- let count = sections.length;
39689
- for (const section of sections) {
39690
- count += countTotalSections(section.subsections);
39691
- }
39692
- return count;
39693
- }
39694
40149
  function formatSemanticSnapshot(yaml, options) {
39695
40150
  if (!yaml || yaml.trim() === "") {
39696
40151
  return "---\nSEMANTIC SNAPSHOT - 0 elements\nPage appears empty or not loaded.\n---";
@@ -39722,10 +40177,9 @@ function formatSemanticSnapshot(yaml, options) {
39722
40177
  }
39723
40178
  }
39724
40179
  if (options?.probeResult?.ref) {
39725
- const probeRefs = /* @__PURE__ */ new Set([options.probeResult.ref]);
39726
- markMatchingSections(sortedSections, probeRefs);
40180
+ markMatchingSections(sortedSections, /* @__PURE__ */ new Set([options.probeResult.ref]));
39727
40181
  }
39728
- const elementCount = countTotalElements(sortedSections);
40182
+ const elementCount = countRenderedElements(sortedSections);
39729
40183
  const sectionCount = countTotalSections(sortedSections);
39730
40184
  const lines = [];
39731
40185
  lines.push(
@@ -39735,10 +40189,11 @@ function formatSemanticSnapshot(yaml, options) {
39735
40189
  activeElement,
39736
40190
  dialogStack,
39737
40191
  sortedMatches,
39738
- // Use re-sorted matches with context
39739
40192
  options?.totalMatchCount,
39740
40193
  options?.snapshotFilePath,
39741
- options?.probeResult
40194
+ options?.probeResult,
40195
+ options?.currentTime,
40196
+ options?.capturedToasts
39742
40197
  )
39743
40198
  );
39744
40199
  lines.push("");
@@ -39763,7 +40218,7 @@ function expandSection(yaml, sectionRef) {
39763
40218
  const sections = buildSectionTree(elements);
39764
40219
  const { sorted: sortedSections, dialogStack } = sortSectionsByFocus(sections);
39765
40220
  expandSectionByRef(sortedSections, sectionRef);
39766
- const elementCount = countTotalElements(sortedSections);
40221
+ const elementCount = countRenderedElements(sortedSections);
39767
40222
  const sectionCount = countTotalSections(sortedSections);
39768
40223
  const lines = [];
39769
40224
  lines.push(generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack));
@@ -39777,29 +40232,26 @@ function expandSection(yaml, sectionRef) {
39777
40232
  }
39778
40233
  return lines.join("\n").trim();
39779
40234
  }
39780
- function expandSectionByRef(sections, targetRef) {
39781
- for (const section of sections) {
39782
- if (section.ref === targetRef) {
39783
- section.collapsed = false;
39784
- return true;
39785
- }
39786
- if (expandSectionByRef(section.subsections, targetRef)) {
39787
- return true;
39788
- }
40235
+ function formatExpandedSectionOutput(section) {
40236
+ const lines = [];
40237
+ const subsectionInfo = section.subsections.length > 0 ? `, ${section.subsections.length} subsections` : "";
40238
+ lines.push(`---`);
40239
+ lines.push(`EXPANDED SECTION: ${section.heading} [${section.ref}]`);
40240
+ lines.push(`Contains ${section.elementCount} elements${subsectionInfo}`);
40241
+ lines.push(`---`);
40242
+ lines.push("");
40243
+ if (section.gridInfo) {
40244
+ lines.push(formatGridOutput(section.gridInfo, 0, true));
40245
+ return lines.join("\n").trim();
39789
40246
  }
39790
- return false;
39791
- }
39792
- function findSectionByRef(sections, targetRef) {
39793
- for (const section of sections) {
39794
- if (section.ref === targetRef) {
39795
- return section;
39796
- }
39797
- const found = findSectionByRef(section.subsections, targetRef);
39798
- if (found) {
39799
- return found;
39800
- }
40247
+ for (const el of section.elements) {
40248
+ lines.push(formatElementLine(el, 0));
39801
40249
  }
39802
- return null;
40250
+ for (const subsection of section.subsections) {
40251
+ lines.push("");
40252
+ lines.push(formatSectionOutput(subsection, 0, false));
40253
+ }
40254
+ return lines.join("\n").trim();
39803
40255
  }
39804
40256
  function expandSectionOnly(yaml, sectionRef) {
39805
40257
  if (!yaml || yaml.trim() === "") {
@@ -39826,79 +40278,133 @@ Section with ref="${sectionRef}" not found in snapshot.
39826
40278
  targetSection.collapsed = false;
39827
40279
  return formatExpandedSectionOutput(targetSection);
39828
40280
  }
39829
- function formatExpandedSectionOutput(section) {
39830
- const lines = [];
39831
- const subsectionInfo = section.subsections.length > 0 ? `, ${section.subsections.length} subsections` : "";
39832
- lines.push(`---`);
39833
- lines.push(`EXPANDED SECTION: ${section.heading} [${section.ref}]`);
39834
- lines.push(`Contains ${section.elementCount} elements${subsectionInfo}`);
39835
- lines.push(`---`);
39836
- lines.push("");
39837
- for (const el of section.elements) {
39838
- lines.push(formatElementLine(el, 0));
39839
- }
39840
- for (const subsection of section.subsections) {
39841
- lines.push("");
39842
- lines.push(formatSectionOutput(subsection, 0, false));
40281
+
40282
+ // ../browser-core/src/table-extractor.ts
40283
+ function extractTablesFromSnapshot(yamlOrResponse, options) {
40284
+ const yaml = yamlOrResponse.includes("Page Snapshot:") ? extractSnapshotYaml(yamlOrResponse) : yamlOrResponse;
40285
+ if (!yaml) return null;
40286
+ const elements = parseSnapshot(yaml);
40287
+ if (elements.length === 0) return null;
40288
+ const grids = [];
40289
+ const findGrids = (els) => {
40290
+ for (const el of els) {
40291
+ const grid = detectGrid(el);
40292
+ if (grid && grid.rows.length > 0) {
40293
+ grids.push({ grid, element: el });
40294
+ }
40295
+ if (!grid) {
40296
+ findGrids(el.children);
40297
+ }
40298
+ }
40299
+ };
40300
+ findGrids(elements);
40301
+ if (grids.length === 0) return null;
40302
+ let target;
40303
+ if (options?.ref) {
40304
+ const match = grids.find((g) => g.grid.ref === options.ref);
40305
+ if (!match) return null;
40306
+ target = match;
40307
+ } else {
40308
+ target = grids.reduce(
40309
+ (best, current) => current.grid.rows.length > best.grid.rows.length ? current : best
40310
+ );
39843
40311
  }
39844
- return lines.join("\n").trim();
40312
+ const { columns, rows } = gridInfoToRows(target.grid);
40313
+ if (columns.length === 0) return null;
40314
+ const pagination = detectPaginationInfo(elements, target.grid.ref);
40315
+ return {
40316
+ columns,
40317
+ rows,
40318
+ gridRef: target.grid.ref,
40319
+ pagination: pagination ?? void 0
40320
+ };
39845
40321
  }
39846
- function formatGridOutput(grid, indent) {
39847
- const indentStr = " ".repeat(indent);
39848
- const lines = [];
39849
- const colInfo = grid.columns.length > 0 ? `${grid.columns.length} columns` : "unknown columns";
39850
- lines.push(`${indentStr}GRID [${grid.ref}]: ${grid.totalRows} rows, ${colInfo}`);
39851
- const MAX_DISPLAY_COLUMNS = 10;
39852
- if (grid.columns.length > 0) {
39853
- const displayColumns = grid.columns.slice(0, MAX_DISPLAY_COLUMNS);
39854
- const colsWithRefs = displayColumns.map((c2) => `${c2.name} [${c2.ref}]`).join(", ");
39855
- const moreColsNote = grid.columns.length > MAX_DISPLAY_COLUMNS ? ` (+${grid.columns.length - MAX_DISPLAY_COLUMNS} more)` : "";
39856
- lines.push(`${indentStr} Columns: ${colsWithRefs}${moreColsNote}`);
40322
+ function gridInfoToRows(grid) {
40323
+ const columns = grid.columns.map((c2) => c2.name);
40324
+ if (columns.length === 0) return { columns: [], rows: [] };
40325
+ const columnIndex = /* @__PURE__ */ new Map();
40326
+ for (let i = 0; i < columns.length; i++) {
40327
+ columnIndex.set(columns[i], i);
39857
40328
  }
39858
- lines.push("");
39859
- const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
39860
- const MAX_GRID_ROWS = 30;
39861
- const displayRows = grid.rows.slice(0, MAX_GRID_ROWS);
39862
- for (const row of displayRows) {
39863
- lines.push(formatGridRow(row, indent + 1, knownColumns));
40329
+ const rows = [];
40330
+ for (const row of grid.rows) {
40331
+ const values = new Array(columns.length).fill("");
40332
+ for (const cell of row.cells) {
40333
+ const idx = columnIndex.get(cell.column);
40334
+ if (idx !== void 0) {
40335
+ values[idx] = cleanCellValue(cell.value, cell.gridcellLabel);
40336
+ }
40337
+ }
40338
+ rows.push(values);
39864
40339
  }
39865
- if (grid.rows.length > MAX_GRID_ROWS) {
39866
- lines.push(`${indentStr} ... and ${grid.rows.length - MAX_GRID_ROWS} more rows (use expand="${grid.ref}" to see all)`);
40340
+ return { columns, rows };
40341
+ }
40342
+ var SVG_ARTIFACT_PATTERN = /^Styled\(svg\)$/i;
40343
+ function cleanCellValue(value, gridcellLabel) {
40344
+ if (!value || SVG_ARTIFACT_PATTERN.test(value)) {
40345
+ if (gridcellLabel) {
40346
+ return gridcellLabel.replace(/\s*Styled\(svg\)\s*/gi, "").trim();
40347
+ }
40348
+ return "";
39867
40349
  }
39868
- return lines.join("\n");
40350
+ return value;
39869
40351
  }
39870
- function formatGridRow(row, indent, knownColumns) {
39871
- const indentStr = " ".repeat(indent);
39872
- if (row.expandedContent) {
39873
- const parts = [];
39874
- if (row.expandedContent.labels.length > 0) {
39875
- parts.push(row.expandedContent.labels.join(", "));
40352
+ function formatCSVWithFrontmatter(columns, rows, _options) {
40353
+ const lines = [];
40354
+ lines.push(columns.map(escapeCSVField).join(","));
40355
+ for (const row of rows) {
40356
+ lines.push(row.map(escapeCSVField).join(","));
40357
+ }
40358
+ return lines.join("\n") + "\n";
40359
+ }
40360
+ function appendRowsToCSV(existingCSV, newRows, columns, _options) {
40361
+ const existingLines = existingCSV.split("\n").filter((l) => l.trim() !== "");
40362
+ const dataLines = [];
40363
+ let headerLine = "";
40364
+ for (const line of existingLines) {
40365
+ if (line.startsWith("#")) continue;
40366
+ if (!headerLine) {
40367
+ headerLine = line;
40368
+ continue;
39876
40369
  }
39877
- for (const btn of row.expandedContent.buttons) {
39878
- parts.push(`${btn.label} [${btn.ref}]`);
40370
+ dataLines.push(line);
40371
+ }
40372
+ const existingRowSet = new Set(dataLines);
40373
+ const newRowStrings = newRows.map((row) => row.map(escapeCSVField).join(","));
40374
+ const addedRows = [];
40375
+ for (const rowStr of newRowStrings) {
40376
+ if (!existingRowSet.has(rowStr)) {
40377
+ addedRows.push(rowStr);
40378
+ existingRowSet.add(rowStr);
39879
40379
  }
39880
- return `${indentStr} \u21B3 Expanded [${row.ref}]: ${parts.join(" | ")}`;
39881
40380
  }
39882
- const marker = row.isSelected ? "ACTIVE \u2192 Row" : "Row";
39883
- const actionsPrefix = row.rowActions?.length ? row.rowActions.map((a) => `[\u25B6 ${a.ref}]`).join(" ") + " " : "";
39884
- const cellsWithValue = row.cells.filter(
39885
- (c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))
39886
- );
39887
- if (cellsWithValue.length > 0) {
39888
- const cellStrs = cellsWithValue.map((c2) => {
39889
- if (c2.role === "checkbox") {
39890
- const icon = c2.value === "checked" ? "\u2611" : "\u2610";
39891
- return `${icon} [${c2.ref}]`;
39892
- }
39893
- if (c2.column === c2.value) {
39894
- return `${c2.column} [${c2.ref}]`;
40381
+ const allDataLines = [...dataLines, ...addedRows];
40382
+ const result = [headerLine || columns.map(escapeCSVField).join(","), ...allDataLines];
40383
+ return result.join("\n") + "\n";
40384
+ }
40385
+ function detectPaginationInfo(elements, _gridRef) {
40386
+ const paginationPattern = /(\d+)\s*[-–]\s*(\d+)\s+of\s+([\d,]+)/i;
40387
+ const searchText = (els) => {
40388
+ for (const el of els) {
40389
+ const text = el.text || "";
40390
+ const match = text.match(paginationPattern);
40391
+ if (match) {
40392
+ const total = parseInt(match[3].replace(/,/g, ""), 10);
40393
+ return { showing: `${match[1]}-${match[2]}`, total };
39895
40394
  }
39896
- return `${c2.column} [${c2.ref}]: ${c2.value}`;
39897
- });
39898
- return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: ${cellStrs.join(" | ")}`;
39899
- } else {
39900
- return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: (empty row)`;
40395
+ const childResult = searchText(el.children);
40396
+ if (childResult) return childResult;
40397
+ }
40398
+ return null;
40399
+ };
40400
+ return searchText(elements);
40401
+ }
40402
+ function escapeCSVField(field) {
40403
+ if (!field) return "";
40404
+ if (field.includes(",") || field.includes('"') || field.includes("\n")) {
40405
+ return `"${field.replace(/"/g, '""')}"`;
39901
40406
  }
40407
+ return field;
39902
40408
  }
39903
40409
 
39904
40410
  // ../browser-core/src/snapshot-diff.ts
@@ -40137,10 +40643,11 @@ function generateHint(diff2, networkInfo) {
40137
40643
  }
40138
40644
  return hints.join(" ");
40139
40645
  }
40140
- function formatSemanticDiff(diff2, actionDescription, networkInfo) {
40646
+ function formatSemanticDiff(diff2, actionDescription, networkInfo, currentTime) {
40141
40647
  const lines = [];
40142
40648
  lines.push("---");
40143
40649
  lines.push("SEMANTIC DIFF: Page state changes after browser action.");
40650
+ lines.push(`BROWSER TIME: ${currentTime ?? (/* @__PURE__ */ new Date()).toISOString()} (UTC)`);
40144
40651
  lines.push("Format: [PREVIOUS] value [CURRENT] value");
40145
40652
  lines.push("");
40146
40653
  lines.push(`ACTION: ${actionDescription}`);
@@ -40219,6 +40726,38 @@ function formatValue(value) {
40219
40726
  return `"${truncate(value, 20)}"`;
40220
40727
  }
40221
40728
 
40729
+ // ../browser-core/src/playwright-client/download-utils.ts
40730
+ import * as path from "path";
40731
+ function formatFileSize(bytes) {
40732
+ if (bytes < 1024) return `${bytes}B`;
40733
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
40734
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
40735
+ }
40736
+ function guessMimeType(filename) {
40737
+ const ext = path.extname(filename).toLowerCase();
40738
+ const mimeMap = {
40739
+ ".pdf": "application/pdf",
40740
+ ".txt": "text/plain",
40741
+ ".csv": "text/csv",
40742
+ ".json": "application/json",
40743
+ ".xml": "application/xml",
40744
+ ".html": "text/html",
40745
+ ".htm": "text/html",
40746
+ ".md": "text/markdown",
40747
+ ".png": "image/png",
40748
+ ".jpg": "image/jpeg",
40749
+ ".jpeg": "image/jpeg",
40750
+ ".gif": "image/gif",
40751
+ ".svg": "image/svg+xml",
40752
+ ".zip": "application/zip",
40753
+ ".doc": "application/msword",
40754
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
40755
+ ".xls": "application/vnd.ms-excel",
40756
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
40757
+ };
40758
+ return mimeMap[ext] ?? "application/octet-stream";
40759
+ }
40760
+
40222
40761
  // ../browser-core/src/playwright-client/element-inspection.ts
40223
40762
  function errorMessage(error) {
40224
40763
  return error instanceof Error ? error.message : String(error);
@@ -40618,7 +41157,7 @@ async function extractElementMetadata(page, locator, ref, logger2) {
40618
41157
  }
40619
41158
 
40620
41159
  // ../browser-core/src/tools/executor.ts
40621
- import * as path from "path";
41160
+ import * as path2 from "path";
40622
41161
  import * as fs2 from "fs/promises";
40623
41162
  import * as crypto2 from "crypto";
40624
41163
 
@@ -40750,7 +41289,7 @@ var BrowserToolExecutor = class {
40750
41289
  if (!options?.workspaceDir && !options?.tmpBaseDir) {
40751
41290
  options?.logger?.debug?.("[BrowserToolExecutor] No tmpBaseDir provided, using flat canary dir (not org-scoped)");
40752
41291
  }
40753
- this.workspaceDir = options?.workspaceDir ?? path.join(options?.tmpBaseDir ?? getCanaryTmpDir(), `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`);
41292
+ this.workspaceDir = options?.workspaceDir ?? path2.join(options?.tmpBaseDir ?? getCanaryTmpDir(), `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`);
40754
41293
  this.logger = options?.logger;
40755
41294
  fs2.mkdir(this.workspaceDir, { recursive: true }).catch(() => {
40756
41295
  });
@@ -40915,15 +41454,18 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
40915
41454
  try {
40916
41455
  await fs2.mkdir(this.workspaceDir, { recursive: true });
40917
41456
  const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
40918
- snapshotFilePath = path.join(this.workspaceDir, filename);
41457
+ snapshotFilePath = path2.join(this.workspaceDir, filename);
40919
41458
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
40920
41459
  } catch {
40921
41460
  }
41461
+ const now = (/* @__PURE__ */ new Date()).toISOString();
40922
41462
  const semanticText = formatSemanticSnapshot(yaml, {
40923
41463
  searchMatches: searchMatches.length > 0 ? searchMatches : void 0,
40924
41464
  totalMatchCount: totalMatchCount > 0 ? totalMatchCount : void 0,
40925
41465
  snapshotFilePath,
40926
- probeResult
41466
+ probeResult,
41467
+ currentTime: now,
41468
+ capturedToasts: this.client.getCapturedToasts?.() ?? []
40927
41469
  });
40928
41470
  if (hasImages) {
40929
41471
  const resolutionNote = buildResolutionNote(
@@ -41166,7 +41708,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41166
41708
  if (!afterModal) {
41167
41709
  const afterState2 = captureSnapshotState(afterUrl2, afterYaml2);
41168
41710
  const diff3 = compareSnapshots(beforeState, afterState2);
41169
- return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}`);
41711
+ return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}`, void 0, (/* @__PURE__ */ new Date()).toISOString());
41170
41712
  }
41171
41713
  } catch (err) {
41172
41714
  this.logger?.debug?.(`[BrowserTools] Dismiss strategy ${strategy} failed`, {
@@ -41182,7 +41724,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41182
41724
  const diff2 = compareSnapshots(beforeState, afterState);
41183
41725
  return `Could not dismiss overlay with strategies: ${strategies.join(", ")}. Modal may require specific interaction.
41184
41726
 
41185
- ` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)");
41727
+ ` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)", void 0, (/* @__PURE__ */ new Date()).toISOString());
41186
41728
  }
41187
41729
  // ==================== Waiting ====================
41188
41730
  async waitFor(opts) {
@@ -41296,7 +41838,8 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41296
41838
  }
41297
41839
  const diff2 = compareSnapshots(beforeState, afterState);
41298
41840
  const networkInfo = this.client.getPendingNetworkInfo?.() ?? void 0;
41299
- const diffText = formatSemanticDiff(diff2, actionDescription, networkInfo);
41841
+ const now = (/* @__PURE__ */ new Date()).toISOString();
41842
+ const diffText = formatSemanticDiff(diff2, actionDescription, networkInfo, now);
41300
41843
  const hasChanges = hasDiffChanges(diff2);
41301
41844
  const isStable = !networkInfo || networkInfo.pendingCount === 0;
41302
41845
  let autoSnapshot;
@@ -41306,11 +41849,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41306
41849
  try {
41307
41850
  await fs2.mkdir(this.workspaceDir, { recursive: true });
41308
41851
  const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
41309
- snapshotFilePath = path.join(this.workspaceDir, filename);
41852
+ snapshotFilePath = path2.join(this.workspaceDir, filename);
41310
41853
  await fs2.writeFile(snapshotFilePath, afterYaml, "utf-8");
41311
41854
  } catch {
41312
41855
  }
41313
- const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath });
41856
+ const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath, currentTime: now, capturedToasts: this.client.getCapturedToasts?.() ?? [] });
41314
41857
  const afterImages = isMCPContentWithImages(afterResult) ? afterResult.images : void 0;
41315
41858
  if (afterImages?.length && this.onScreenshot) {
41316
41859
  const img = afterImages[0];
@@ -41347,11 +41890,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41347
41890
  try {
41348
41891
  await fs2.mkdir(this.workspaceDir, { recursive: true });
41349
41892
  const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
41350
- snapshotFilePath = path.join(this.workspaceDir, filename);
41893
+ snapshotFilePath = path2.join(this.workspaceDir, filename);
41351
41894
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
41352
41895
  } catch {
41353
41896
  }
41354
- const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath });
41897
+ const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath, currentTime: (/* @__PURE__ */ new Date()).toISOString(), capturedToasts: this.client.getCapturedToasts?.() ?? [] });
41355
41898
  const yamlBlockPattern = /- Page Snapshot:\n```yaml\n[\s\S]*?```/;
41356
41899
  if (yamlBlockPattern.test(result)) {
41357
41900
  return result.replace(yamlBlockPattern, `- Page Snapshot (semantic):
@@ -41806,6 +42349,136 @@ function getBrowserToolDefinitionsWithLifecycle() {
41806
42349
  ];
41807
42350
  }
41808
42351
 
42352
+ // ../browser-core/src/tools/dispatcher.ts
42353
+ async function dispatchBrowserTool(executor, toolName, args) {
42354
+ switch (toolName) {
42355
+ case "browser_navigate":
42356
+ return executor.navigate(args.url);
42357
+ case "browser_navigate_back":
42358
+ return executor.navigateBack();
42359
+ case "browser_snapshot":
42360
+ return executor.snapshot({
42361
+ mode: args.mode,
42362
+ expand: args.expand,
42363
+ search: args.search,
42364
+ showCoordinateGrid: args.showCoordinateGrid,
42365
+ probeAt: args.probeAt
42366
+ });
42367
+ case "browser_screenshot":
42368
+ return executor.screenshot({
42369
+ fullPage: args.fullPage,
42370
+ element: args.element,
42371
+ ref: args.ref,
42372
+ label: args.label,
42373
+ returnImage: args.returnImage
42374
+ });
42375
+ case "browser_evaluate":
42376
+ return executor.evaluate({
42377
+ expression: args.function,
42378
+ element: args.element,
42379
+ ref: args.ref
42380
+ });
42381
+ case "browser_console_messages":
42382
+ return executor.consoleMessages({
42383
+ onlyErrors: args.onlyErrors
42384
+ });
42385
+ case "browser_network_requests":
42386
+ return executor.networkRequests();
42387
+ case "browser_click":
42388
+ return executor.click({
42389
+ ref: args.ref,
42390
+ x: args.x,
42391
+ y: args.y,
42392
+ element: args.element,
42393
+ doubleClick: args.doubleClick,
42394
+ button: args.button,
42395
+ modifiers: args.modifiers
42396
+ });
42397
+ case "browser_hover":
42398
+ return executor.hover({
42399
+ ref: args.ref,
42400
+ element: args.element
42401
+ });
42402
+ case "browser_drag":
42403
+ return executor.drag({
42404
+ startRef: args.startRef,
42405
+ endRef: args.endRef,
42406
+ startElement: args.startElement,
42407
+ endElement: args.endElement,
42408
+ startX: args.startX,
42409
+ startY: args.startY,
42410
+ endX: args.endX,
42411
+ endY: args.endY
42412
+ });
42413
+ case "browser_type":
42414
+ return executor.type({
42415
+ ref: args.ref,
42416
+ text: args.text,
42417
+ element: args.element,
42418
+ submit: args.submit,
42419
+ delay: args.delay
42420
+ });
42421
+ case "browser_press_key":
42422
+ return executor.pressKey(args.key);
42423
+ case "browser_fill_form":
42424
+ return executor.fillForm(
42425
+ args.fields
42426
+ );
42427
+ case "browser_select_option":
42428
+ return executor.selectOption({
42429
+ ref: args.ref,
42430
+ value: args.value,
42431
+ element: args.element
42432
+ });
42433
+ case "browser_file_upload":
42434
+ return executor.fileUpload(args.paths);
42435
+ case "browser_scroll":
42436
+ return executor.scroll({
42437
+ direction: args.direction,
42438
+ amount: args.amount,
42439
+ withinRef: args.withinRef,
42440
+ toRef: args.toRef,
42441
+ x: args.x,
42442
+ y: args.y
42443
+ });
42444
+ case "browser_handle_dialog":
42445
+ return executor.handleDialog({
42446
+ action: args.action,
42447
+ promptText: args.promptText
42448
+ });
42449
+ case "browser_dismiss_overlay":
42450
+ return executor.dismissOverlay({
42451
+ preferredStrategy: args.preferredStrategy
42452
+ });
42453
+ case "browser_wait_for":
42454
+ return executor.waitFor({
42455
+ timeSec: args.time,
42456
+ text: args.text,
42457
+ textGone: args.textGone,
42458
+ selector: args.selector,
42459
+ state: args.state,
42460
+ timeout: args.timeout
42461
+ });
42462
+ case "browser_close":
42463
+ return executor.close();
42464
+ case "browser_resize":
42465
+ return executor.resize(args.width, args.height);
42466
+ case "browser_tabs":
42467
+ return executor.tabs({
42468
+ action: args.action,
42469
+ index: args.index
42470
+ });
42471
+ case "browser_list_downloads":
42472
+ return executor.listDownloads();
42473
+ case "browser_read_download":
42474
+ return executor.readDownload(args.downloadId);
42475
+ case "browser_pdf_read":
42476
+ return executor.fetchPdfText(args.url);
42477
+ default:
42478
+ throw new Error(`Unknown browser tool: ${toolName}`);
42479
+ }
42480
+ }
42481
+
41809
42482
  // ../browser-core/src/playwright-client.ts
41810
42483
  import {
41811
42484
  chromium as playwrightChromium
@@ -42586,8 +43259,8 @@ var errors = playwrightLoader.lazyloadExportOrDie("errors");
42586
43259
 
42587
43260
  // ../browser-core/src/playwright-client.ts
42588
43261
  var import_puppeteer_extra_plugin_stealth = __toESM(require_puppeteer_extra_plugin_stealth(), 1);
42589
- import * as fs3 from "fs/promises";
42590
- import * as path3 from "path";
43262
+ import * as fs4 from "fs/promises";
43263
+ import * as path4 from "path";
42591
43264
 
42592
43265
  // ../browser-core/src/playwright-client/toast-capture-script.ts
42593
43266
  var TOAST_CAPTURE_SCRIPT = `
@@ -42926,6 +43599,29 @@ function appendAttributeIfMissing(line, key, value) {
42926
43599
  const baseLine = hasTrailingColon ? line.replace(/:\s*$/, "") : line;
42927
43600
  return `${baseLine} [${key}=${value}]${hasTrailingColon ? ":" : ""}`;
42928
43601
  }
43602
+ function findLabelFromYamlSiblings(elements, targetRef) {
43603
+ const findParent = (els, parent2) => {
43604
+ for (const el of els) {
43605
+ if (el.ref === targetRef) return parent2;
43606
+ const found = findParent(el.children, el);
43607
+ if (found) return found;
43608
+ }
43609
+ return null;
43610
+ };
43611
+ const parent = findParent(elements, null);
43612
+ if (!parent) return void 0;
43613
+ const targetIdx = parent.children.findIndex((c2) => c2.ref === targetRef);
43614
+ if (targetIdx < 0) return void 0;
43615
+ for (let i = targetIdx - 1; i >= 0; i--) {
43616
+ const sib = parent.children[i];
43617
+ const sibText = sib.text?.trim();
43618
+ if (sibText && sibText.length > 0 && sibText.length < 80 && /[a-zA-Z0-9]/.test(sibText)) {
43619
+ if (FORM_FIELD_ROLES.has(sib.role)) continue;
43620
+ return sibText;
43621
+ }
43622
+ }
43623
+ return void 0;
43624
+ }
42929
43625
  async function augmentUnlabeledElements(page, yaml, logger2) {
42930
43626
  try {
42931
43627
  const elements = parseSnapshot(yaml);
@@ -43231,6 +43927,9 @@ async function augmentGridCellValues(page, yaml, logger2) {
43231
43927
  }
43232
43928
  }
43233
43929
  var FORM_FIELD_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox"]);
43930
+ function splitCamelCase(name) {
43931
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim();
43932
+ }
43234
43933
  async function augmentFormFieldLabels(page, yaml, logger2) {
43235
43934
  try {
43236
43935
  const elements = parseSnapshot(yaml);
@@ -43248,16 +43947,30 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
43248
43947
  walkForFields(el);
43249
43948
  }
43250
43949
  if (formFieldRefs.length === 0) return yaml;
43251
- logger2.debug("[DirectPlaywright] Augmenting form field labels", {
43950
+ const glyphFields = formFieldRefs.filter((f) => f.text && !/[a-zA-Z0-9]/.test(f.text));
43951
+ const displayNameIdx = yaml.indexOf("Display Name");
43952
+ const yamlAroundDisplayName = displayNameIdx >= 0 ? yaml.slice(Math.max(0, displayNameIdx - 50), displayNameIdx + 200) : "NOT FOUND";
43953
+ logger2.warn("[DirectPlaywright] augmentFormFieldLabels called", {
43252
43954
  count: formFieldRefs.length,
43253
- refs: formFieldRefs.slice(0, 10).map((f) => f.ref)
43955
+ glyphCount: glyphFields.length,
43956
+ allTexts: formFieldRefs.map((f) => `${f.ref}:${JSON.stringify(f.text)}`),
43957
+ hasDisplayName: yaml.includes("Display Name"),
43958
+ yamlHasGlyph: yaml.includes("\u268A"),
43959
+ yamlAroundDisplayName
43254
43960
  });
43255
43961
  let augmented = yaml;
43256
43962
  for (const { ref, role, text } of formFieldRefs) {
43257
43963
  try {
43964
+ const isGlyphField = text && !/[a-zA-Z0-9]/.test(text);
43965
+ if (isGlyphField) {
43966
+ logger2.info("[DirectPlaywright] Glyph-labeled field entering augmentation", { ref, role, text });
43967
+ }
43258
43968
  const locator = page.locator(`aria-ref=${ref}`);
43259
43969
  const count = await locator.count();
43260
- if (count === 0) continue;
43970
+ if (count === 0) {
43971
+ if (isGlyphField) logger2.info("[DirectPlaywright] Glyph field: locator count=0, skipping", { ref });
43972
+ continue;
43973
+ }
43261
43974
  const label = await locator.first().evaluate((el) => {
43262
43975
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
43263
43976
  const labels = el.labels;
@@ -43266,6 +43979,30 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
43266
43979
  if (labelText) return labelText;
43267
43980
  }
43268
43981
  }
43982
+ {
43983
+ const precedingLabels = [];
43984
+ let prevEl = el.previousElementSibling;
43985
+ while (prevEl) {
43986
+ if (prevEl.tagName === "LABEL") {
43987
+ precedingLabels.unshift(prevEl);
43988
+ } else {
43989
+ break;
43990
+ }
43991
+ prevEl = prevEl.previousElementSibling;
43992
+ }
43993
+ if (precedingLabels.length > 1) {
43994
+ const selected = precedingLabels.find((l) => {
43995
+ const cls = l.className.toLowerCase();
43996
+ return cls.includes("selected") || cls.includes("active") || cls.includes("current") || cls.includes("checked");
43997
+ });
43998
+ if (selected?.textContent?.trim()) return selected.textContent.trim();
43999
+ const first = precedingLabels[0];
44000
+ if (first?.textContent?.trim()) return first.textContent.trim();
44001
+ } else if (precedingLabels.length === 1) {
44002
+ const labelText = precedingLabels[0].textContent?.trim();
44003
+ if (labelText) return labelText;
44004
+ }
44005
+ }
43269
44006
  const labelledBy = el.getAttribute("aria-labelledby");
43270
44007
  if (labelledBy) {
43271
44008
  const labelEl = document.getElementById(labelledBy);
@@ -43273,31 +44010,101 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
43273
44010
  }
43274
44011
  const ariaLabel = el.getAttribute("aria-label");
43275
44012
  const placeholder = el.getAttribute("placeholder");
43276
- if (ariaLabel && ariaLabel !== placeholder) return ariaLabel;
43277
- let sibling = el.previousElementSibling;
44013
+ if (ariaLabel && ariaLabel !== placeholder && /[a-zA-Z0-9]/.test(ariaLabel)) return ariaLabel;
43278
44014
  let target = el;
43279
- for (let i = 0; i < 3 && target; i++) {
43280
- sibling = target.previousElementSibling;
43281
- if (sibling) {
44015
+ for (let depth = 0; depth < 3 && target; depth++) {
44016
+ let sibling = target.previousElementSibling;
44017
+ for (let sibCount = 0; sibling && sibCount < 3; sibCount++) {
43282
44018
  const sibText = sibling.textContent?.trim();
43283
44019
  if (sibText && sibText.length > 0 && sibText.length < 80 && !sibling.querySelector("input, textarea, select, button")) {
43284
44020
  return sibText;
43285
44021
  }
44022
+ if (sibling.querySelector("input, textarea, select, button")) break;
44023
+ sibling = sibling.previousElementSibling;
43286
44024
  }
43287
44025
  target = target.parentElement;
43288
44026
  }
43289
44027
  return void 0;
43290
44028
  });
44029
+ if (isGlyphField) {
44030
+ logger2.info("[DirectPlaywright] Glyph field: DOM evaluate result", { ref, label, text });
44031
+ }
43291
44032
  if (label && label !== text) {
43292
44033
  augmented = updateElementLineByRef(
43293
44034
  augmented,
43294
44035
  ref,
43295
44036
  (line) => injectElementLabel(line, role, label)
43296
44037
  );
44038
+ if (isGlyphField) {
44039
+ logger2.info("[DirectPlaywright] Glyph field: injected DOM label", { ref, label });
44040
+ }
44041
+ }
44042
+ const hasGlyphOnlyLabel = text && !/[a-zA-Z0-9]/.test(text);
44043
+ if (hasGlyphOnlyLabel && (!label || label === text || !/[a-zA-Z0-9]/.test(label))) {
44044
+ const yamlLabel = findLabelFromYamlSiblings(elements, ref);
44045
+ logger2.info("[DirectPlaywright] YAML sibling fallback for glyph field", {
44046
+ ref,
44047
+ text,
44048
+ label,
44049
+ yamlLabel
44050
+ });
44051
+ if (yamlLabel) {
44052
+ augmented = updateElementLineByRef(
44053
+ augmented,
44054
+ ref,
44055
+ (line) => injectElementLabel(line, role, yamlLabel)
44056
+ );
44057
+ }
43297
44058
  }
43298
44059
  } catch {
43299
44060
  }
43300
44061
  }
44062
+ try {
44063
+ const postElements = parseSnapshot(augmented);
44064
+ const labeledFields = [];
44065
+ const walkForLabeled = (el) => {
44066
+ if (FORM_FIELD_ROLES.has(el.role) && el.ref && el.text) {
44067
+ labeledFields.push({ ref: el.ref, role: el.role, label: el.text });
44068
+ }
44069
+ for (const child of el.children) walkForLabeled(child);
44070
+ };
44071
+ for (const el of postElements) walkForLabeled(el);
44072
+ const byLabel = /* @__PURE__ */ new Map();
44073
+ for (const f of labeledFields) {
44074
+ const existing = byLabel.get(f.label);
44075
+ if (existing) {
44076
+ existing.push({ ref: f.ref, role: f.role });
44077
+ } else {
44078
+ byLabel.set(f.label, [{ ref: f.ref, role: f.role }]);
44079
+ }
44080
+ }
44081
+ for (const [label, fields] of byLabel) {
44082
+ if (fields.length < 2) continue;
44083
+ for (const field of fields) {
44084
+ try {
44085
+ const locator = page.locator(`aria-ref=${field.ref}`);
44086
+ const count = await locator.count();
44087
+ if (count === 0) continue;
44088
+ const disambiguation = await locator.first().evaluate((el) => {
44089
+ const placeholder = el.getAttribute("placeholder") || void 0;
44090
+ const ariaLabel = el.getAttribute("aria-label") || void 0;
44091
+ const name = el.getAttribute("name") || void 0;
44092
+ return { placeholder, ariaLabel, name };
44093
+ });
44094
+ const disambig = disambiguation.placeholder || disambiguation.ariaLabel || (disambiguation.name ? splitCamelCase(disambiguation.name) : void 0);
44095
+ if (disambig && disambig !== label) {
44096
+ augmented = updateElementLineByRef(
44097
+ augmented,
44098
+ field.ref,
44099
+ (line) => injectElementLabel(line, field.role, disambig)
44100
+ );
44101
+ }
44102
+ } catch {
44103
+ }
44104
+ }
44105
+ }
44106
+ } catch {
44107
+ }
43301
44108
  return augmented;
43302
44109
  } catch (err) {
43303
44110
  logger2.debug("[DirectPlaywright] Form field label augmentation failed", {
@@ -43310,11 +44117,44 @@ async function enrichHiddenClickableElements(page, logger2) {
43310
44117
  try {
43311
44118
  const enrichedCount = await page.evaluate(() => {
43312
44119
  let count = 0;
43313
- const patterns = [
43314
- ["abbr.search-choice-close", "search-choice-close"],
44120
+ const chosenCloseButtons = document.querySelectorAll("abbr.search-choice-close");
44121
+ for (const el of chosenCloseButtons) {
44122
+ if (el.getAttribute("data-enriched-clickable") === "true") continue;
44123
+ if (el.getAttribute("role") === "button") continue;
44124
+ let fieldName;
44125
+ const container = el.closest(".chosen-container");
44126
+ if (container) {
44127
+ const selectEl = container.previousElementSibling;
44128
+ if (selectEl && selectEl.tagName === "SELECT") {
44129
+ const selectId = selectEl.id;
44130
+ if (selectId) {
44131
+ const labelEl = document.querySelector(`label[for="${selectId}"]`);
44132
+ if (labelEl?.textContent?.trim()) {
44133
+ fieldName = labelEl.textContent.trim();
44134
+ }
44135
+ }
44136
+ if (!fieldName) {
44137
+ fieldName = selectEl.getAttribute("aria-label") || void 0;
44138
+ }
44139
+ }
44140
+ if (!fieldName && container.parentElement) {
44141
+ const prev = container.parentElement.previousElementSibling;
44142
+ if (prev && (prev.tagName === "LABEL" || prev.querySelector("label"))) {
44143
+ const labelText = prev.textContent?.trim();
44144
+ if (labelText && labelText.length < 80) fieldName = labelText;
44145
+ }
44146
+ }
44147
+ }
44148
+ const label = fieldName ? `Clear ${fieldName}` : "Clear selection";
44149
+ el.setAttribute("role", "button");
44150
+ el.setAttribute("aria-label", label);
44151
+ el.setAttribute("data-enriched-clickable", "true");
44152
+ count++;
44153
+ }
44154
+ const genericPatterns = [
43315
44155
  [".select2-selection__clear", "select2-selection__clear"]
43316
44156
  ];
43317
- for (const [selector, fallbackLabel] of patterns) {
44157
+ for (const [selector, fallbackLabel] of genericPatterns) {
43318
44158
  const elements = document.querySelectorAll(selector);
43319
44159
  for (const el of elements) {
43320
44160
  if (el.getAttribute("data-enriched-clickable") === "true") continue;
@@ -43326,6 +44166,12 @@ async function enrichHiddenClickableElements(page, logger2) {
43326
44166
  count++;
43327
44167
  }
43328
44168
  }
44169
+ const chosenSearchInputs = document.querySelectorAll(
44170
+ ".chosen-container:not(.chosen-with-drop) .chosen-search-input"
44171
+ );
44172
+ for (const input of chosenSearchInputs) {
44173
+ input.setAttribute("aria-hidden", "true");
44174
+ }
43329
44175
  return count;
43330
44176
  });
43331
44177
  if (enrichedCount > 0) {
@@ -43364,10 +44210,29 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43364
44210
  if (!trigger) continue;
43365
44211
  const rect = trigger.getBoundingClientRect();
43366
44212
  const triggerText = trigger.textContent?.trim()?.slice(0, 80) || null;
44213
+ let fieldLabel = null;
44214
+ const selectId = sel.id;
44215
+ if (selectId) {
44216
+ const labelEl = document.querySelector(`label[for="${selectId}"]`);
44217
+ if (labelEl?.textContent?.trim()) {
44218
+ fieldLabel = labelEl.textContent.trim();
44219
+ }
44220
+ }
44221
+ if (!fieldLabel) {
44222
+ fieldLabel = sel.getAttribute("aria-label") || null;
44223
+ }
44224
+ if (!fieldLabel && parent.previousElementSibling) {
44225
+ const prev = parent.previousElementSibling;
44226
+ if (prev.tagName === "LABEL" || prev.querySelector("label")) {
44227
+ const labelText = prev.textContent?.trim();
44228
+ if (labelText && labelText.length < 80) fieldLabel = labelText;
44229
+ }
44230
+ }
43367
44231
  results.push({
43368
44232
  options: opts.slice(0, 15),
43369
44233
  triggerText,
43370
- triggerRect: rect.width > 0 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null
44234
+ triggerRect: rect.width > 0 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null,
44235
+ fieldLabel
43371
44236
  });
43372
44237
  }
43373
44238
  return results;
@@ -43379,7 +44244,7 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43379
44244
  const elements = parseSnapshot(yaml);
43380
44245
  let augmented = yaml;
43381
44246
  for (const selectInfo of hiddenSelects) {
43382
- const { options: opts, triggerText, triggerRect } = selectInfo;
44247
+ const { options: opts, triggerText, triggerRect, fieldLabel } = selectInfo;
43383
44248
  if (!opts.length) continue;
43384
44249
  let matchedRef = null;
43385
44250
  if (triggerText) {
@@ -43428,11 +44293,13 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43428
44293
  }
43429
44294
  if (!matchedRef) continue;
43430
44295
  const optionsValue = opts.join("|");
43431
- augmented = updateElementLineByRef(
43432
- augmented,
43433
- matchedRef,
43434
- (line) => appendAttributeIfMissing(line, "options", optionsValue)
43435
- );
44296
+ augmented = updateElementLineByRef(augmented, matchedRef, (line) => {
44297
+ let next = appendAttributeIfMissing(line, "options", optionsValue);
44298
+ if (fieldLabel) {
44299
+ next = appendAttributeIfMissing(next, "field", fieldLabel);
44300
+ }
44301
+ return next;
44302
+ });
43436
44303
  }
43437
44304
  return augmented;
43438
44305
  } catch (err) {
@@ -43442,6 +44309,148 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43442
44309
  return yaml;
43443
44310
  }
43444
44311
  }
44312
+ async function enrichModalDialogElements(page, logger2) {
44313
+ try {
44314
+ const enrichedCount = await page.evaluate(() => {
44315
+ let count = 0;
44316
+ const MAX_ENRICHMENTS = 3;
44317
+ function shouldSkip(el) {
44318
+ if (el.getAttribute("role") === "dialog" || el.getAttribute("role") === "alertdialog") return true;
44319
+ if (el.getAttribute("data-enriched-dialog") === "true") return true;
44320
+ return false;
44321
+ }
44322
+ function isVisible(el) {
44323
+ const style = window.getComputedStyle(el);
44324
+ if (style.display === "none" || style.visibility === "hidden") return false;
44325
+ if (el instanceof HTMLElement && el.offsetWidth === 0 && el.offsetHeight === 0) return false;
44326
+ return true;
44327
+ }
44328
+ function enrichAsDialog(el) {
44329
+ if (count >= MAX_ENRICHMENTS) return;
44330
+ if (shouldSkip(el)) return;
44331
+ if (!isVisible(el)) return;
44332
+ el.setAttribute("role", "dialog");
44333
+ const heading = el.querySelector("h1, h2, h3, h4, h5, h6");
44334
+ if (heading?.textContent?.trim()) {
44335
+ el.setAttribute("aria-label", heading.textContent.trim());
44336
+ }
44337
+ el.setAttribute("data-enriched-dialog", "true");
44338
+ count++;
44339
+ }
44340
+ function hasInteractiveContent(el) {
44341
+ return el.querySelector('input, textarea, select, button, a[href], [role="button"], [role="link"], [role="textbox"], [role="combobox"]') !== null;
44342
+ }
44343
+ const CLASS_PATTERNS = [
44344
+ /\bmodal(?![-_]?(backdrop|mask|overlay|fade|bg|background))\b/i,
44345
+ /\bdialog\b/i,
44346
+ /\bpopup\b/i,
44347
+ /\blightbox\b/i,
44348
+ /\bdrawer\b/i
44349
+ ];
44350
+ const FRAMEWORK_SELECTORS = [
44351
+ ".cdk-overlay-pane",
44352
+ ".mat-dialog-container",
44353
+ ".mat-mdc-dialog-container",
44354
+ ".ui-dialog",
44355
+ ".modal-dialog",
44356
+ ".ant-modal-content",
44357
+ ".el-dialog",
44358
+ ".v-dialog",
44359
+ ".p-dialog"
44360
+ ];
44361
+ for (const selector of FRAMEWORK_SELECTORS) {
44362
+ if (count >= MAX_ENRICHMENTS) break;
44363
+ const els = document.querySelectorAll(selector);
44364
+ for (const el of els) {
44365
+ if (count >= MAX_ENRICHMENTS) break;
44366
+ if (hasInteractiveContent(el)) {
44367
+ enrichAsDialog(el);
44368
+ }
44369
+ }
44370
+ }
44371
+ if (count < MAX_ENRICHMENTS) {
44372
+ const allElements = document.querySelectorAll("*");
44373
+ for (const el of allElements) {
44374
+ if (count >= MAX_ENRICHMENTS) break;
44375
+ const className = el.className;
44376
+ if (typeof className !== "string") continue;
44377
+ if (CLASS_PATTERNS.some((pattern) => pattern.test(className))) {
44378
+ if (hasInteractiveContent(el)) {
44379
+ enrichAsDialog(el);
44380
+ }
44381
+ }
44382
+ }
44383
+ }
44384
+ if (count < MAX_ENRICHMENTS) {
44385
+ const allFixed = document.querySelectorAll("*");
44386
+ for (const el of allFixed) {
44387
+ if (count >= MAX_ENRICHMENTS) break;
44388
+ if (!(el instanceof HTMLElement)) continue;
44389
+ const style = window.getComputedStyle(el);
44390
+ if (style.position !== "fixed" && style.position !== "absolute") continue;
44391
+ const rect = el.getBoundingClientRect();
44392
+ const viewportW = window.innerWidth;
44393
+ const viewportH = window.innerHeight;
44394
+ const coversViewport = rect.width >= viewportW * 0.8 && rect.height >= viewportH * 0.8;
44395
+ if (!coversViewport) continue;
44396
+ const bg = style.backgroundColor;
44397
+ const opacity = parseFloat(style.opacity);
44398
+ const isSemiTransparent = bg.includes("rgba") && !bg.includes("rgba(0, 0, 0, 0)") && bg.match(/,\s*([\d.]+)\)/) && parseFloat(bg.match(/,\s*([\d.]+)\)/)?.[1] || "1") < 1 || opacity < 1 && opacity > 0;
44399
+ if (!isSemiTransparent) continue;
44400
+ const nextSib = el.nextElementSibling;
44401
+ if (nextSib && nextSib instanceof HTMLElement && isVisible(nextSib) && hasInteractiveContent(nextSib)) {
44402
+ enrichAsDialog(nextSib);
44403
+ continue;
44404
+ }
44405
+ if (el.parentElement) {
44406
+ const backdropZ = parseInt(style.zIndex) || 0;
44407
+ for (const sibling of el.parentElement.children) {
44408
+ if (sibling === el || !(sibling instanceof HTMLElement)) continue;
44409
+ const sibStyle = window.getComputedStyle(sibling);
44410
+ const sibZ = parseInt(sibStyle.zIndex) || 0;
44411
+ if (sibZ > backdropZ && isVisible(sibling) && hasInteractiveContent(sibling)) {
44412
+ enrichAsDialog(sibling);
44413
+ break;
44414
+ }
44415
+ }
44416
+ }
44417
+ }
44418
+ }
44419
+ if (count < MAX_ENRICHMENTS) {
44420
+ const allElements = document.querySelectorAll("*");
44421
+ for (const el of allElements) {
44422
+ if (count >= MAX_ENRICHMENTS) break;
44423
+ if (!(el instanceof HTMLElement)) continue;
44424
+ if (el.getAttribute("data-enriched-dialog") === "true") continue;
44425
+ if (el.getAttribute("role") === "dialog" || el.getAttribute("role") === "alertdialog") continue;
44426
+ const style = window.getComputedStyle(el);
44427
+ if (style.position !== "fixed" && style.position !== "absolute") continue;
44428
+ const zIndex = parseInt(style.zIndex) || 0;
44429
+ if (zIndex <= 100) continue;
44430
+ if (!isVisible(el)) continue;
44431
+ const rect = el.getBoundingClientRect();
44432
+ if (rect.width < 200 || rect.height < 200) continue;
44433
+ const viewportW = window.innerWidth;
44434
+ const viewportH = window.innerHeight;
44435
+ if (rect.width >= viewportW * 0.95 && rect.height >= viewportH * 0.95) continue;
44436
+ if (hasInteractiveContent(el)) {
44437
+ enrichAsDialog(el);
44438
+ }
44439
+ }
44440
+ }
44441
+ return count;
44442
+ });
44443
+ if (enrichedCount > 0) {
44444
+ logger2.debug("[DirectPlaywright] Enriched modal dialog elements", {
44445
+ count: enrichedCount
44446
+ });
44447
+ }
44448
+ } catch (err) {
44449
+ logger2.debug("[DirectPlaywright] Modal dialog enrichment failed", {
44450
+ error: errorMessage2(err)
44451
+ });
44452
+ }
44453
+ }
43445
44454
  async function enrichInteractiveSVGElements(page, logger2) {
43446
44455
  try {
43447
44456
  const enrichedCount = await page.evaluate(() => {
@@ -43488,38 +44497,6 @@ async function enrichInteractiveSVGElements(page, logger2) {
43488
44497
  }
43489
44498
  }
43490
44499
 
43491
- // ../browser-core/src/playwright-client/download-utils.ts
43492
- import * as path2 from "path";
43493
- function formatFileSize(bytes) {
43494
- if (bytes < 1024) return `${bytes}B`;
43495
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
43496
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
43497
- }
43498
- function guessMimeType(filename) {
43499
- const ext = path2.extname(filename).toLowerCase();
43500
- const mimeMap = {
43501
- ".pdf": "application/pdf",
43502
- ".txt": "text/plain",
43503
- ".csv": "text/csv",
43504
- ".json": "application/json",
43505
- ".xml": "application/xml",
43506
- ".html": "text/html",
43507
- ".htm": "text/html",
43508
- ".md": "text/markdown",
43509
- ".png": "image/png",
43510
- ".jpg": "image/jpeg",
43511
- ".jpeg": "image/jpeg",
43512
- ".gif": "image/gif",
43513
- ".svg": "image/svg+xml",
43514
- ".zip": "application/zip",
43515
- ".doc": "application/msword",
43516
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
43517
- ".xls": "application/vnd.ms-excel",
43518
- ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
43519
- };
43520
- return mimeMap[ext] ?? "application/octet-stream";
43521
- }
43522
-
43523
44500
  // ../browser-core/src/playwright-client/response-format.ts
43524
44501
  var TOAST_SNAPSHOT_TTL = 5;
43525
44502
  function formatMcpResponse(input) {
@@ -43578,10 +44555,172 @@ function formatMcpResponse(input) {
43578
44555
  };
43579
44556
  }
43580
44557
 
44558
+ // ../browser-core/src/playwright-client/launch-retry.ts
44559
+ var TRANSIENT_BROWSER_LAUNCH_PATTERNS = [
44560
+ /failed to connect/i,
44561
+ /syscall["':\s]+connect/i,
44562
+ /\bENOENT\b/i,
44563
+ /browser has been closed/i,
44564
+ /target page, context or browser has been closed/i,
44565
+ /failed to launch/i,
44566
+ /\bSIGKILL\b/i,
44567
+ /process was terminated/i
44568
+ ];
44569
+ function formatBrowserLaunchError(error) {
44570
+ if (error instanceof Error) {
44571
+ const extra = error;
44572
+ const parts = [error.message];
44573
+ if (extra.code) parts.push(`code=${extra.code}`);
44574
+ if (typeof extra.errno === "number") parts.push(`errno=${extra.errno}`);
44575
+ if (extra.syscall) parts.push(`syscall=${extra.syscall}`);
44576
+ if (extra.cause) parts.push(`cause=${formatBrowserLaunchError(extra.cause)}`);
44577
+ return parts.join(" ");
44578
+ }
44579
+ return String(error);
44580
+ }
44581
+ function isTransientBrowserLaunchError(error) {
44582
+ const text = formatBrowserLaunchError(error);
44583
+ return TRANSIENT_BROWSER_LAUNCH_PATTERNS.some((pattern) => pattern.test(text));
44584
+ }
44585
+ async function withBrowserLaunchRetry(operation, options) {
44586
+ const maxAttempts = options.maxAttempts ?? 3;
44587
+ const initialDelayMs = options.initialDelayMs ?? 150;
44588
+ let lastError;
44589
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
44590
+ try {
44591
+ return await operation(attempt);
44592
+ } catch (error) {
44593
+ lastError = error;
44594
+ if (attempt >= maxAttempts || !isTransientBrowserLaunchError(error)) {
44595
+ throw error;
44596
+ }
44597
+ const delayMs = initialDelayMs * 2 ** (attempt - 1);
44598
+ options.logger.warn("[DirectPlaywright] Browser launch failed transiently, retrying", {
44599
+ label: options.label,
44600
+ attempt,
44601
+ nextAttempt: attempt + 1,
44602
+ delayMs,
44603
+ error: formatBrowserLaunchError(error)
44604
+ });
44605
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
44606
+ }
44607
+ }
44608
+ throw lastError;
44609
+ }
44610
+
44611
+ // ../browser-core/src/playwright-client/test-browser-lock.ts
44612
+ import * as fs3 from "fs/promises";
44613
+ import * as path3 from "path";
44614
+ var TEST_BROWSER_LOCK_DIR = path3.join(getCanaryTmpDir(), "playwright-test-browser-lock");
44615
+ var TEST_BROWSER_LOCK_METADATA_PATH = path3.join(TEST_BROWSER_LOCK_DIR, "owner.json");
44616
+ var TEST_BROWSER_LOCK_POLL_MS = 100;
44617
+ var TEST_BROWSER_LOCK_TIMEOUT_MS = 12e4;
44618
+ var TEST_BROWSER_LOCK_STALE_MS = 15 * 6e4;
44619
+ function shouldUseTestBrowserLock(options) {
44620
+ return process.env.NODE_ENV === "test" && !options.browserLease && !options.cdpUrl && options.browserMode !== "headed";
44621
+ }
44622
+ async function acquireTestBrowserLock(logger2) {
44623
+ const startedAt = Date.now();
44624
+ const owner = {
44625
+ pid: process.pid,
44626
+ acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
44627
+ };
44628
+ while (true) {
44629
+ try {
44630
+ await fs3.mkdir(TEST_BROWSER_LOCK_DIR);
44631
+ await fs3.writeFile(TEST_BROWSER_LOCK_METADATA_PATH, JSON.stringify(owner), "utf8");
44632
+ logger2.debug("[DirectPlaywright] Acquired test browser lock", {
44633
+ lockDir: TEST_BROWSER_LOCK_DIR,
44634
+ pid: process.pid
44635
+ });
44636
+ let released = false;
44637
+ return async () => {
44638
+ if (released) return;
44639
+ released = true;
44640
+ try {
44641
+ await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
44642
+ logger2.debug("[DirectPlaywright] Released test browser lock", {
44643
+ lockDir: TEST_BROWSER_LOCK_DIR,
44644
+ pid: process.pid
44645
+ });
44646
+ } catch (error) {
44647
+ logger2.warn("[DirectPlaywright] Failed to release test browser lock", {
44648
+ lockDir: TEST_BROWSER_LOCK_DIR,
44649
+ pid: process.pid,
44650
+ error: error instanceof Error ? error.message : String(error)
44651
+ });
44652
+ }
44653
+ };
44654
+ } catch (error) {
44655
+ const code = error.code;
44656
+ if (code !== "EEXIST") {
44657
+ throw error;
44658
+ }
44659
+ await cleanupStaleTestBrowserLock(logger2);
44660
+ if (Date.now() - startedAt >= TEST_BROWSER_LOCK_TIMEOUT_MS) {
44661
+ throw new Error(
44662
+ `Timed out acquiring test browser lock after ${TEST_BROWSER_LOCK_TIMEOUT_MS}ms`
44663
+ );
44664
+ }
44665
+ await delay(TEST_BROWSER_LOCK_POLL_MS);
44666
+ }
44667
+ }
44668
+ }
44669
+ async function cleanupStaleTestBrowserLock(logger2) {
44670
+ const metadata = await readLockMetadata();
44671
+ if (!metadata) {
44672
+ const orphanedLockStats = await fs3.stat(TEST_BROWSER_LOCK_DIR).catch(() => null);
44673
+ if (!orphanedLockStats) {
44674
+ return;
44675
+ }
44676
+ const orphanedAgeMs = Date.now() - orphanedLockStats.mtimeMs;
44677
+ if (orphanedAgeMs < 1e3) {
44678
+ return;
44679
+ }
44680
+ logger2.warn("[DirectPlaywright] Removing orphaned test browser lock", {
44681
+ lockDir: TEST_BROWSER_LOCK_DIR,
44682
+ ageMs: orphanedAgeMs
44683
+ });
44684
+ await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
44685
+ return;
44686
+ }
44687
+ const ageMs = Date.now() - Date.parse(metadata.acquiredAt);
44688
+ if (ageMs < TEST_BROWSER_LOCK_STALE_MS && isProcessAlive(metadata.pid)) {
44689
+ return;
44690
+ }
44691
+ logger2.warn("[DirectPlaywright] Removing stale test browser lock", {
44692
+ lockDir: TEST_BROWSER_LOCK_DIR,
44693
+ ownerPid: metadata.pid,
44694
+ ageMs
44695
+ });
44696
+ await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
44697
+ }
44698
+ async function readLockMetadata() {
44699
+ try {
44700
+ const raw = await fs3.readFile(TEST_BROWSER_LOCK_METADATA_PATH, "utf8");
44701
+ return JSON.parse(raw);
44702
+ } catch {
44703
+ return null;
44704
+ }
44705
+ }
44706
+ function isProcessAlive(pid) {
44707
+ try {
44708
+ process.kill(pid, 0);
44709
+ return true;
44710
+ } catch (error) {
44711
+ const code = error.code;
44712
+ return code !== "ESRCH";
44713
+ }
44714
+ }
44715
+ function delay(ms) {
44716
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
44717
+ }
44718
+
43581
44719
  // ../browser-core/src/playwright-client.ts
43582
44720
  var PlaywrightClient = class _PlaywrightClient {
43583
44721
  disconnectInProgress = false;
43584
44722
  browserLease;
44723
+ releaseTestBrowserLock = null;
43585
44724
  logger;
43586
44725
  screencastEncoderFactory;
43587
44726
  tmpBaseDir;
@@ -43707,7 +44846,8 @@ var PlaywrightClient = class _PlaywrightClient {
43707
44846
  extensions,
43708
44847
  highlightInteractions,
43709
44848
  cursorOverlay,
43710
- extraHTTPHeaders
44849
+ extraHTTPHeaders,
44850
+ httpCredentials
43711
44851
  } = options;
43712
44852
  const resolvedBrowserMode = browserMode ?? "headless";
43713
44853
  const resolvedViewport = viewport ?? DEFAULT_VIEWPORT;
@@ -43720,7 +44860,8 @@ var PlaywrightClient = class _PlaywrightClient {
43720
44860
  extensions,
43721
44861
  highlightInteractions,
43722
44862
  cursorOverlay,
43723
- extraHTTPHeaders
44863
+ extraHTTPHeaders,
44864
+ httpCredentials
43724
44865
  };
43725
44866
  this.storageStatePath = storageStatePath;
43726
44867
  this.pageClosedIntentionally = false;
@@ -43743,8 +44884,8 @@ var PlaywrightClient = class _PlaywrightClient {
43743
44884
  const cursorOpts = typeof cursorOverlay === "object" ? cursorOverlay : {};
43744
44885
  this.cursorOverlay = new CursorOverlay({ ...cursorOpts, enabled: cursorEnabled });
43745
44886
  }
43746
- this._downloadDir = path3.join(this.tmpBaseDir, `playwright-downloads-${Date.now()}`);
43747
- await fs3.mkdir(this._downloadDir, { recursive: true });
44887
+ this._downloadDir = path4.join(this.tmpBaseDir, `playwright-downloads-${Date.now()}`);
44888
+ await fs4.mkdir(this._downloadDir, { recursive: true });
43748
44889
  this.logger.info("[DirectPlaywright] Launching browser", {
43749
44890
  browserMode: resolvedBrowserMode,
43750
44891
  hasStorageState: !!storageStatePath,
@@ -43755,82 +44896,107 @@ var PlaywrightClient = class _PlaywrightClient {
43755
44896
  highlightEnabled: this.highlightEnabled,
43756
44897
  downloadDir: this._downloadDir
43757
44898
  });
43758
- if (cdpUrl) {
43759
- this.logger.info("[DirectPlaywright] Connecting via CDP", { cdpUrl });
43760
- this.browser = await this.withOperationTimeout(
43761
- playwrightChromium.connectOverCDP(cdpUrl),
43762
- 3e4,
43763
- "[DirectPlaywright] connectOverCDP"
43764
- );
43765
- const existingContexts = this.browser.contexts();
43766
- this.context = existingContexts[0] ?? await this.browser.newContext({ viewport: resolvedViewport });
43767
- } else if (this.browserLease && !this.stealthEnabled && !this.extensionsEnabled && resolvedBrowserMode !== "headed") {
43768
- const videoConfig2 = this.resolveVideoConfig(recordVideo);
43769
- const contextOptions = {
43770
- viewport: resolvedViewport,
43771
- acceptDownloads: true
43772
- };
43773
- if (storageStatePath) {
43774
- try {
43775
- await fs3.access(storageStatePath);
43776
- contextOptions.storageState = storageStatePath;
43777
- } catch {
44899
+ try {
44900
+ if (shouldUseTestBrowserLock({
44901
+ browserLease: !!this.browserLease,
44902
+ cdpUrl,
44903
+ browserMode: resolvedBrowserMode
44904
+ }) && !this.releaseTestBrowserLock) {
44905
+ this.releaseTestBrowserLock = await acquireTestBrowserLock(this.logger);
44906
+ }
44907
+ if (cdpUrl) {
44908
+ this.logger.info("[DirectPlaywright] Connecting via CDP", { cdpUrl });
44909
+ this.browser = await this.withOperationTimeout(
44910
+ playwrightChromium.connectOverCDP(cdpUrl),
44911
+ 3e4,
44912
+ "[DirectPlaywright] connectOverCDP"
44913
+ );
44914
+ const existingContexts = this.browser.contexts();
44915
+ this.context = existingContexts[0] ?? await this.browser.newContext({ viewport: resolvedViewport });
44916
+ } else if (this.browserLease && !this.stealthEnabled && !this.extensionsEnabled && resolvedBrowserMode !== "headed") {
44917
+ const videoConfig2 = this.resolveVideoConfig(recordVideo);
44918
+ const contextOptions = {
44919
+ viewport: resolvedViewport,
44920
+ acceptDownloads: true
44921
+ };
44922
+ if (storageStatePath) {
44923
+ try {
44924
+ await fs4.access(storageStatePath);
44925
+ contextOptions.storageState = storageStatePath;
44926
+ } catch {
44927
+ }
43778
44928
  }
43779
- }
43780
- if (videoConfig2) {
43781
- await fs3.mkdir(videoConfig2.dir, { recursive: true });
43782
- contextOptions.recordVideo = videoConfig2;
43783
- this.videoDir = videoConfig2.dir;
43784
- }
43785
- if (extraHTTPHeaders && Object.keys(extraHTTPHeaders).length > 0) {
43786
- contextOptions.extraHTTPHeaders = extraHTTPHeaders;
43787
- }
43788
- this.logger.info("[DirectPlaywright] Using pooled browser via lease", {
43789
- leaseId: this.browserLease.leaseId,
43790
- browserId: this.browserLease.browserId
43791
- });
43792
- this.context = await this.withOperationTimeout(
43793
- this.browserLease.createContext(contextOptions),
43794
- 3e4,
43795
- "[DirectPlaywright] browserLease.createContext"
43796
- );
43797
- this.browser = null;
43798
- } else {
43799
- if (this.browserLease) {
43800
- this.logger.info("[DirectPlaywright] Releasing pool lease for direct launch", {
44929
+ if (videoConfig2) {
44930
+ await fs4.mkdir(videoConfig2.dir, { recursive: true });
44931
+ contextOptions.recordVideo = videoConfig2;
44932
+ this.videoDir = videoConfig2.dir;
44933
+ }
44934
+ if (extraHTTPHeaders && Object.keys(extraHTTPHeaders).length > 0) {
44935
+ contextOptions.extraHTTPHeaders = extraHTTPHeaders;
44936
+ }
44937
+ if (httpCredentials) {
44938
+ contextOptions.httpCredentials = httpCredentials;
44939
+ }
44940
+ this.logger.info("[DirectPlaywright] Using pooled browser via lease", {
43801
44941
  leaseId: this.browserLease.leaseId,
43802
- reason: resolvedBrowserMode === "headed" ? "headed-mode" : "requires-dedicated-browser"
44942
+ browserId: this.browserLease.browserId
43803
44943
  });
43804
- await this.browserLease.release();
43805
- this.browserLease = null;
44944
+ this.context = await this.withOperationTimeout(
44945
+ this.browserLease.createContext(contextOptions),
44946
+ 3e4,
44947
+ "[DirectPlaywright] browserLease.createContext"
44948
+ );
44949
+ this.browser = null;
44950
+ } else {
44951
+ if (this.browserLease) {
44952
+ this.logger.info("[DirectPlaywright] Releasing pool lease for direct launch", {
44953
+ leaseId: this.browserLease.leaseId,
44954
+ reason: resolvedBrowserMode === "headed" ? "headed-mode" : "requires-dedicated-browser"
44955
+ });
44956
+ await this.browserLease.release();
44957
+ this.browserLease = null;
44958
+ }
44959
+ const { browser, context } = await withBrowserLaunchRetry(
44960
+ () => this.launchBrowser({
44961
+ browserMode: resolvedBrowserMode,
44962
+ storageStatePath,
44963
+ viewport,
44964
+ recordVideo,
44965
+ stealth,
44966
+ extensions,
44967
+ extraHTTPHeaders,
44968
+ httpCredentials
44969
+ }),
44970
+ {
44971
+ logger: this.logger,
44972
+ label: "direct-launch"
44973
+ }
44974
+ );
44975
+ this.browser = browser;
44976
+ this.context = context;
43806
44977
  }
43807
- const { browser, context } = await this.launchBrowser({
44978
+ this.attachContextListeners(this.context);
44979
+ if (this.browser) {
44980
+ this.attachBrowserListeners(this.browser);
44981
+ }
44982
+ await this.createPageWithListeners(this.context, "initial_connect");
44983
+ const now = Date.now();
44984
+ const initialPageId = this._assignPageId(this.page);
44985
+ this._pageCreationTimes.set(initialPageId, now);
44986
+ this._tabFocusLog.push({ pageIndex: initialPageId, timestamp: now });
44987
+ if (storageStatePath) {
44988
+ await this.restoreIndexedDBFromStorageState(storageStatePath);
44989
+ }
44990
+ this.logger.info("[DirectPlaywright] Browser connected successfully", {
43808
44991
  browserMode: resolvedBrowserMode,
43809
- storageStatePath,
43810
- viewport,
43811
- recordVideo,
43812
- stealth,
43813
- extensions,
43814
- extraHTTPHeaders
44992
+ stealthEnabled: this.stealthEnabled,
44993
+ extensionsEnabled: this.extensionsEnabled,
44994
+ pooled: !!this.browserLease
43815
44995
  });
43816
- this.browser = browser;
43817
- this.context = context;
43818
- }
43819
- this.attachContextListeners(this.context);
43820
- if (this.browser) {
43821
- this.attachBrowserListeners(this.browser);
44996
+ } catch (error) {
44997
+ await this.cleanupFailedConnect(error);
44998
+ throw error;
43822
44999
  }
43823
- await this.createPageWithListeners(this.context, "initial_connect");
43824
- const now = Date.now();
43825
- const initialPageId = this._assignPageId(this.page);
43826
- this._pageCreationTimes.set(initialPageId, now);
43827
- this._tabFocusLog.push({ pageIndex: initialPageId, timestamp: now });
43828
- this.logger.info("[DirectPlaywright] Browser connected successfully", {
43829
- browserMode: resolvedBrowserMode,
43830
- stealthEnabled: this.stealthEnabled,
43831
- extensionsEnabled: this.extensionsEnabled,
43832
- pooled: !!this.browserLease
43833
- });
43834
45000
  }
43835
45001
  supportsContextSwap() {
43836
45002
  return !this.extensionsEnabled && (!!this.browser || !!this.browserLease);
@@ -43900,9 +45066,16 @@ var PlaywrightClient = class _PlaywrightClient {
43900
45066
  headerNames: Object.keys(mergedHeaders)
43901
45067
  });
43902
45068
  }
45069
+ const resolvedHttpCredentials = options.httpCredentials ?? this.lastConnectOptions?.httpCredentials;
45070
+ if (resolvedHttpCredentials) {
45071
+ contextOptions.httpCredentials = resolvedHttpCredentials;
45072
+ if (this.lastConnectOptions) {
45073
+ this.lastConnectOptions.httpCredentials = resolvedHttpCredentials;
45074
+ }
45075
+ }
43903
45076
  if (nextStorageStatePath) {
43904
45077
  try {
43905
- await fs3.access(nextStorageStatePath);
45078
+ await fs4.access(nextStorageStatePath);
43906
45079
  contextOptions.storageState = nextStorageStatePath;
43907
45080
  this.logger.debug("[DirectPlaywright] Loading storage state for swapped context", {
43908
45081
  storageStatePath: nextStorageStatePath
@@ -43923,7 +45096,7 @@ var PlaywrightClient = class _PlaywrightClient {
43923
45096
  this._pageCreationTimes.clear();
43924
45097
  this._closedPageVideos = [];
43925
45098
  if (videoConfig) {
43926
- await fs3.mkdir(videoConfig.dir, { recursive: true });
45099
+ await fs4.mkdir(videoConfig.dir, { recursive: true });
43927
45100
  contextOptions.recordVideo = videoConfig;
43928
45101
  this.videoDir = videoConfig.dir;
43929
45102
  this.highlightEnabled = true;
@@ -43951,6 +45124,9 @@ var PlaywrightClient = class _PlaywrightClient {
43951
45124
  const swapPageId = this._assignPageId(this.page);
43952
45125
  this._pageCreationTimes.set(swapPageId, swapNow);
43953
45126
  this._tabFocusLog.push({ pageIndex: swapPageId, timestamp: swapNow });
45127
+ if (nextStorageStatePath) {
45128
+ await this.restoreIndexedDBFromStorageState(nextStorageStatePath);
45129
+ }
43954
45130
  if (wasScreencasting && this.page && this.screencastHandler) {
43955
45131
  await this.startScreencast(this.screencastHandler);
43956
45132
  }
@@ -43969,7 +45145,8 @@ var PlaywrightClient = class _PlaywrightClient {
43969
45145
  recordVideo,
43970
45146
  stealth,
43971
45147
  extensions,
43972
- extraHTTPHeaders
45148
+ extraHTTPHeaders,
45149
+ httpCredentials
43973
45150
  } = options;
43974
45151
  const resolvedViewport = viewport ?? DEFAULT_VIEWPORT;
43975
45152
  const useStealthMode = stealth === true || typeof stealth === "object" && stealth.enabled;
@@ -43980,6 +45157,7 @@ var PlaywrightClient = class _PlaywrightClient {
43980
45157
  this.logger.debug("[DirectPlaywright] Stealth plugin applied");
43981
45158
  }
43982
45159
  const chromiumLauncher = useStealthMode ? chromium : playwrightChromium;
45160
+ const executablePath = await _PlaywrightClient.resolveChromiumExecutable(this.logger);
43983
45161
  const args = ["--no-sandbox", "--disable-dev-shm-usage"];
43984
45162
  if (browserMode === "headed" && process.platform === "darwin") {
43985
45163
  args.push(
@@ -43999,9 +45177,13 @@ var PlaywrightClient = class _PlaywrightClient {
43999
45177
  headerNames: Object.keys(extraHTTPHeaders)
44000
45178
  });
44001
45179
  }
45180
+ if (httpCredentials) {
45181
+ contextOptions.httpCredentials = httpCredentials;
45182
+ this.logger.info("[DirectPlaywright] HTTP Basic Authentication configured");
45183
+ }
44002
45184
  if (storageStatePath) {
44003
45185
  try {
44004
- await fs3.access(storageStatePath);
45186
+ await fs4.access(storageStatePath);
44005
45187
  contextOptions.storageState = storageStatePath;
44006
45188
  this.logger.debug("[DirectPlaywright] Loading storage state from file", {
44007
45189
  storageStatePath
@@ -44013,7 +45195,7 @@ var PlaywrightClient = class _PlaywrightClient {
44013
45195
  }
44014
45196
  }
44015
45197
  if (videoConfig) {
44016
- await fs3.mkdir(videoConfig.dir, { recursive: true });
45198
+ await fs4.mkdir(videoConfig.dir, { recursive: true });
44017
45199
  contextOptions.recordVideo = videoConfig;
44018
45200
  this.videoDir = videoConfig.dir;
44019
45201
  this.logger.info("[DirectPlaywright] Video recording enabled", {
@@ -44022,9 +45204,9 @@ var PlaywrightClient = class _PlaywrightClient {
44022
45204
  });
44023
45205
  }
44024
45206
  if (hasExtensions) {
44025
- this.userDataDir = extensions.userDataDir || path3.join(this.tmpBaseDir, `playwright-userdata-${Date.now()}`);
45207
+ this.userDataDir = extensions.userDataDir || path4.join(this.tmpBaseDir, `playwright-userdata-${Date.now()}`);
44026
45208
  this.shouldCleanupUserDataDir = !extensions.persistUserData;
44027
- await fs3.mkdir(this.userDataDir, { recursive: true });
45209
+ await fs4.mkdir(this.userDataDir, { recursive: true });
44028
45210
  if (browserMode === "headless") {
44029
45211
  args.push("--headless=new");
44030
45212
  }
@@ -44040,6 +45222,7 @@ var PlaywrightClient = class _PlaywrightClient {
44040
45222
  headless: false,
44041
45223
  // We use --headless=new in args for extension support
44042
45224
  args,
45225
+ ...executablePath ? { executablePath } : {},
44043
45226
  ...contextOptions
44044
45227
  }),
44045
45228
  3e4,
@@ -44050,7 +45233,8 @@ var PlaywrightClient = class _PlaywrightClient {
44050
45233
  const browser = await this.withOperationTimeout(
44051
45234
  chromiumLauncher.launch({
44052
45235
  headless: browserMode === "headless",
44053
- args
45236
+ args,
45237
+ ...executablePath ? { executablePath } : {}
44054
45238
  }),
44055
45239
  3e4,
44056
45240
  "[DirectPlaywright] chromium.launch"
@@ -44062,6 +45246,114 @@ var PlaywrightClient = class _PlaywrightClient {
44062
45246
  );
44063
45247
  return { browser, context };
44064
45248
  }
45249
+ /**
45250
+ * Read the storage state file, check for IndexedDB data, and restore it.
45251
+ * IndexedDB is origin-scoped, so we navigate to the appropriate origin
45252
+ * before writing data, then navigate back.
45253
+ */
45254
+ async restoreIndexedDBFromStorageState(storageStatePath) {
45255
+ try {
45256
+ const raw = await fs4.readFile(storageStatePath, "utf-8");
45257
+ const parsed = JSON.parse(raw);
45258
+ if (!parsed.indexedDB || !parsed.indexedDB.databases || parsed.indexedDB.databases.length === 0) {
45259
+ return;
45260
+ }
45261
+ const page = this.page;
45262
+ if (!page || page.isClosed()) return;
45263
+ let targetOrigin = null;
45264
+ if (parsed.origins && parsed.origins.length > 0) {
45265
+ targetOrigin = parsed.origins[0].origin;
45266
+ }
45267
+ if (!targetOrigin) {
45268
+ const cookies = parsed.cookies;
45269
+ if (cookies && cookies.length > 0) {
45270
+ const domain = cookies[0].domain.replace(/^\./, "");
45271
+ targetOrigin = `https://${domain}`;
45272
+ }
45273
+ }
45274
+ if (!targetOrigin) {
45275
+ this.logger.debug("[DirectPlaywright] No origin found for IndexedDB restoration, skipping");
45276
+ return;
45277
+ }
45278
+ this.logger.debug("[DirectPlaywright] Restoring IndexedDB data", {
45279
+ targetOrigin,
45280
+ databaseCount: parsed.indexedDB.databases.length,
45281
+ databaseNames: parsed.indexedDB.databases.map((db) => db.name)
45282
+ });
45283
+ await page.goto(targetOrigin, { waitUntil: "commit", timeout: 15e3 });
45284
+ await restoreIndexedDB(page, parsed.indexedDB);
45285
+ this.logger.info("[DirectPlaywright] IndexedDB data restored successfully", {
45286
+ databaseCount: parsed.indexedDB.databases.length
45287
+ });
45288
+ } catch (err) {
45289
+ this.logger.warn("[DirectPlaywright] Failed to restore IndexedDB from storage state", {
45290
+ error: err instanceof Error ? err.message : String(err),
45291
+ storageStatePath
45292
+ });
45293
+ }
45294
+ }
45295
+ /**
45296
+ * Resolve a working Chromium executable path, handling version mismatches
45297
+ * between the Playwright version bundled in this package and the browser
45298
+ * binaries installed on the system.
45299
+ *
45300
+ * When the exact expected binary doesn't exist (e.g. Playwright expects
45301
+ * chromium-1191 but only chromium-1194 is installed), this finds the
45302
+ * newest available chromium binary as a fallback.
45303
+ */
45304
+ static async resolveChromiumExecutable(logger2) {
45305
+ const defaultPath = playwrightChromium.executablePath();
45306
+ try {
45307
+ await fs4.access(defaultPath);
45308
+ return void 0;
45309
+ } catch {
45310
+ }
45311
+ const parts = defaultPath.split(path4.sep);
45312
+ const msPlaywrightIdx = parts.findIndex((p) => p === "ms-playwright");
45313
+ if (msPlaywrightIdx === -1) {
45314
+ logger2.warn("[DirectPlaywright] Cannot locate ms-playwright cache directory", {
45315
+ defaultPath
45316
+ });
45317
+ return void 0;
45318
+ }
45319
+ const cacheDir = parts.slice(0, msPlaywrightIdx + 1).join(path4.sep);
45320
+ const relativeBinaryPath = parts.slice(msPlaywrightIdx + 2).join(path4.sep);
45321
+ try {
45322
+ const entries = await fs4.readdir(cacheDir);
45323
+ const chromiumDirs = entries.filter((e) => e.startsWith("chromium-") && !e.includes("headless")).sort((a, b) => {
45324
+ const revA = parseInt(a.split("-")[1] ?? "0", 10);
45325
+ const revB = parseInt(b.split("-")[1] ?? "0", 10);
45326
+ return revB - revA;
45327
+ });
45328
+ for (const dir of chromiumDirs) {
45329
+ const candidate = path4.join(cacheDir, dir, relativeBinaryPath);
45330
+ try {
45331
+ await fs4.access(candidate);
45332
+ logger2.warn(
45333
+ "[DirectPlaywright] Using fallback Chromium binary (version mismatch)",
45334
+ {
45335
+ expected: defaultPath,
45336
+ using: candidate
45337
+ }
45338
+ );
45339
+ return candidate;
45340
+ } catch {
45341
+ continue;
45342
+ }
45343
+ }
45344
+ logger2.warn("[DirectPlaywright] No compatible Chromium binary found", {
45345
+ cacheDir,
45346
+ candidates: chromiumDirs,
45347
+ expectedRelativePath: relativeBinaryPath
45348
+ });
45349
+ } catch (err) {
45350
+ logger2.warn("[DirectPlaywright] Failed to scan browser cache directory", {
45351
+ cacheDir,
45352
+ error: String(err)
45353
+ });
45354
+ }
45355
+ return void 0;
45356
+ }
44065
45357
  async disconnect() {
44066
45358
  this.logger.info("[DirectPlaywright] Disconnecting");
44067
45359
  this.disconnectInProgress = true;
@@ -44121,7 +45413,7 @@ var PlaywrightClient = class _PlaywrightClient {
44121
45413
  this.lastSnapshotYaml = "";
44122
45414
  if (this._downloadDir) {
44123
45415
  try {
44124
- await fs3.rm(this._downloadDir, { recursive: true, force: true });
45416
+ await fs4.rm(this._downloadDir, { recursive: true, force: true });
44125
45417
  this.logger.debug("[DirectPlaywright] Cleaned up download directory", {
44126
45418
  downloadDir: this._downloadDir
44127
45419
  });
@@ -44133,12 +45425,26 @@ var PlaywrightClient = class _PlaywrightClient {
44133
45425
  }
44134
45426
  this._downloadDir = null;
44135
45427
  }
45428
+ if (this.traceDir) {
45429
+ try {
45430
+ await fs4.rm(this.traceDir, { recursive: true, force: true });
45431
+ this.logger.debug("[DirectPlaywright] Cleaned up trace directory", {
45432
+ traceDir: this.traceDir
45433
+ });
45434
+ } catch (err) {
45435
+ this.logger.warn("[DirectPlaywright] Failed to clean up trace directory", {
45436
+ traceDir: this.traceDir,
45437
+ error: err instanceof Error ? err.message : String(err)
45438
+ });
45439
+ }
45440
+ this.traceDir = null;
45441
+ }
44136
45442
  this._capturedDownloads = [];
44137
45443
  this._downloadCounter = 0;
44138
45444
  this._lastReportedDownloadIndex = 0;
44139
45445
  if (this.shouldCleanupUserDataDir && this.userDataDir) {
44140
45446
  try {
44141
- await fs3.rm(this.userDataDir, { recursive: true, force: true });
45447
+ await fs4.rm(this.userDataDir, { recursive: true, force: true });
44142
45448
  this.logger.debug("[DirectPlaywright] Cleaned up user data directory", {
44143
45449
  userDataDir: this.userDataDir
44144
45450
  });
@@ -44152,7 +45458,7 @@ var PlaywrightClient = class _PlaywrightClient {
44152
45458
  if (this.snapshotFilePaths.size > 0) {
44153
45459
  for (const filePath of this.snapshotFilePaths) {
44154
45460
  try {
44155
- await fs3.unlink(filePath);
45461
+ await fs4.unlink(filePath);
44156
45462
  } catch (err) {
44157
45463
  this.logger.warn("[DirectPlaywright] Failed to clean up snapshot file", {
44158
45464
  filePath,
@@ -44171,11 +45477,33 @@ var PlaywrightClient = class _PlaywrightClient {
44171
45477
  this.stealthEnabled = false;
44172
45478
  this.logger.info("[DirectPlaywright] Disconnected");
44173
45479
  } finally {
45480
+ await this.releaseTestBrowserLockIfHeld();
44174
45481
  this.contextClosedIntentionally = false;
44175
45482
  this.pageClosedIntentionally = false;
44176
45483
  this.disconnectInProgress = false;
44177
45484
  }
44178
45485
  }
45486
+ async cleanupFailedConnect(error) {
45487
+ this.logger.warn("[DirectPlaywright] Cleaning up after failed connect", {
45488
+ error: error instanceof Error ? error.message : String(error)
45489
+ });
45490
+ try {
45491
+ await this.disconnect();
45492
+ } catch (disconnectError) {
45493
+ this.logger.warn("[DirectPlaywright] Failed cleanup after connect error", {
45494
+ error: disconnectError instanceof Error ? disconnectError.message : String(disconnectError)
45495
+ });
45496
+ await this.releaseTestBrowserLockIfHeld();
45497
+ }
45498
+ }
45499
+ async releaseTestBrowserLockIfHeld() {
45500
+ if (!this.releaseTestBrowserLock) {
45501
+ return;
45502
+ }
45503
+ const release = this.releaseTestBrowserLock;
45504
+ this.releaseTestBrowserLock = null;
45505
+ await release();
45506
+ }
44179
45507
  // ================ NAVIGATION ================
44180
45508
  async navigate(url, opts) {
44181
45509
  const dialogMsg = this.getPendingDialogMessage();
@@ -44448,8 +45776,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
44448
45776
  }
44449
45777
  const sampleUrls = pendingRequests.slice(0, 3).map((url) => {
44450
45778
  try {
44451
- const path4 = new URL(url).pathname + new URL(url).search;
44452
- return path4.length > 60 ? path4.slice(0, 57) + "..." : path4;
45779
+ const path5 = new URL(url).pathname + new URL(url).search;
45780
+ return path5.length > 60 ? path5.slice(0, 57) + "..." : path5;
44453
45781
  } catch {
44454
45782
  return url.length > 60 ? url.slice(0, 57) + "..." : url;
44455
45783
  }
@@ -45124,7 +46452,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45124
46452
  const video = closingPage.video();
45125
46453
  if (video) {
45126
46454
  const videoFileName = `video-tab${closePageId}-${Date.now()}.webm`;
45127
- const savePath = path3.join(this.videoDir, videoFileName);
46455
+ const savePath = path4.join(this.videoDir, videoFileName);
45128
46456
  await closingPage.close();
45129
46457
  await video.saveAs(savePath);
45130
46458
  const createdAt = this._pageCreationTimes.get(closePageId) ?? Date.now();
@@ -45185,7 +46513,25 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45185
46513
  this.logger.debug("[DirectPlaywright] Getting storage state", {
45186
46514
  method: opts?.method ?? "playwright"
45187
46515
  });
45188
- return await context.storageState();
46516
+ const playwrightState = await context.storageState();
46517
+ try {
46518
+ const page = await this.getPage();
46519
+ const indexedDB2 = await extractIndexedDB(page);
46520
+ if (indexedDB2) {
46521
+ this.logger.info("[DirectPlaywright] Extracted IndexedDB data", {
46522
+ databaseCount: indexedDB2.databases.length,
46523
+ databaseNames: indexedDB2.databases.map((db) => db.name)
46524
+ });
46525
+ return { ...playwrightState, indexedDB: indexedDB2 };
46526
+ } else {
46527
+ this.logger.debug("[DirectPlaywright] No IndexedDB databases found on current page");
46528
+ }
46529
+ } catch (err) {
46530
+ this.logger.warn("[DirectPlaywright] Failed to extract IndexedDB, saving without it", {
46531
+ error: err instanceof Error ? err.message : String(err)
46532
+ });
46533
+ }
46534
+ return playwrightState;
45189
46535
  }
45190
46536
  async getCurrentUrl(_opts) {
45191
46537
  return (await this.getPage()).url();
@@ -45221,8 +46567,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45221
46567
  const timestamp = Date.now();
45222
46568
  const shortId = crypto.randomUUID().slice(0, 8);
45223
46569
  const filename = `snapshot-${timestamp}-${shortId}.yaml`;
45224
- const filePath = path3.join(this.tmpBaseDir, filename);
45225
- await fs3.writeFile(filePath, yaml, "utf-8");
46570
+ const filePath = path4.join(this.tmpBaseDir, filename);
46571
+ await fs4.writeFile(filePath, yaml, "utf-8");
45226
46572
  this.snapshotFilePaths.add(filePath);
45227
46573
  this.logger.debug("[DirectPlaywright] Wrote snapshot to disk", {
45228
46574
  filePath,
@@ -45366,8 +46712,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45366
46712
  async startTracing(_opts) {
45367
46713
  const context = await this.getContext();
45368
46714
  this.logger.info("[DirectPlaywright] Starting trace recording");
45369
- this.traceDir = path3.join(this.tmpBaseDir, `playwright-trace-${Date.now()}`);
45370
- await fs3.mkdir(this.traceDir, { recursive: true });
46715
+ this.traceDir = path4.join(this.tmpBaseDir, `playwright-trace-${Date.now()}`);
46716
+ await fs4.mkdir(this.traceDir, { recursive: true });
45371
46717
  await context.tracing.start({ screenshots: true, snapshots: true });
45372
46718
  this.tracingActive = true;
45373
46719
  }
@@ -45383,7 +46729,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45383
46729
  const context = await this.getContext();
45384
46730
  if (!this.traceDir) throw new Error("Tracing not started");
45385
46731
  this.logger.info("[DirectPlaywright] Stopping trace recording");
45386
- const tracePath = path3.join(this.traceDir, "trace.zip");
46732
+ const tracePath = path4.join(this.traceDir, "trace.zip");
45387
46733
  await context.tracing.stop({ path: tracePath });
45388
46734
  this.tracingActive = false;
45389
46735
  return {
@@ -45432,6 +46778,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45432
46778
  }
45433
46779
  await this.enrichInteractiveSVGElements(page);
45434
46780
  await this.enrichHiddenClickableElements(page);
46781
+ await this.enrichModalDialogElements(page);
45435
46782
  let snapshot;
45436
46783
  const maxRetries = 3;
45437
46784
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -45560,6 +46907,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45560
46907
  async enrichHiddenClickableElements(page) {
45561
46908
  await enrichHiddenClickableElements(page, this.logger);
45562
46909
  }
46910
+ /**
46911
+ * Enrich positioned overlays that look like modals but lack `role="dialog"`.
46912
+ * Injects `role="dialog"` and `aria-label` so the agent recognizes them.
46913
+ */
46914
+ async enrichModalDialogElements(page) {
46915
+ await enrichModalDialogElements(page, this.logger);
46916
+ }
45563
46917
  /**
45564
46918
  * Resolve a ref to a Playwright locator using the internal aria-ref selector.
45565
46919
  * This is the same mechanism MCP uses to resolve refs.
@@ -46047,18 +47401,18 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46047
47401
  this.logger.error("[DirectPlaywright] Download failed: no download directory", { id });
46048
47402
  return;
46049
47403
  }
46050
- const savePath = path3.join(this._downloadDir, `${id}-${suggestedFilename}`);
47404
+ const savePath = path4.join(this._downloadDir, `${id}-${suggestedFilename}`);
46051
47405
  download.saveAs(savePath).then(
46052
47406
  async () => {
46053
47407
  try {
46054
- const stat2 = await fs3.stat(savePath);
47408
+ const stat3 = await fs4.stat(savePath);
46055
47409
  entry.savedPath = savePath;
46056
- entry.sizeBytes = stat2.size;
47410
+ entry.sizeBytes = stat3.size;
46057
47411
  entry.completed = true;
46058
47412
  this.logger.info("[DirectPlaywright] Download completed", {
46059
47413
  id,
46060
47414
  suggestedFilename,
46061
- sizeBytes: stat2.size,
47415
+ sizeBytes: stat3.size,
46062
47416
  savedPath: savePath
46063
47417
  });
46064
47418
  } catch {
@@ -46200,13 +47554,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46200
47554
  };
46201
47555
  if (ssp) {
46202
47556
  try {
46203
- await fs3.access(ssp);
47557
+ await fs4.access(ssp);
46204
47558
  ctxOpts.storageState = ssp;
46205
47559
  } catch {
46206
47560
  }
46207
47561
  }
46208
47562
  if (videoConfig2) {
46209
- await fs3.mkdir(videoConfig2.dir, { recursive: true });
47563
+ await fs4.mkdir(videoConfig2.dir, { recursive: true });
46210
47564
  ctxOpts.recordVideo = videoConfig2;
46211
47565
  }
46212
47566
  if (headers && Object.keys(headers).length > 0) {
@@ -46232,7 +47586,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46232
47586
  }
46233
47587
  if (this.shouldCleanupUserDataDir && this.userDataDir) {
46234
47588
  try {
46235
- await fs3.rm(this.userDataDir, { recursive: true, force: true });
47589
+ await fs4.rm(this.userDataDir, { recursive: true, force: true });
46236
47590
  } catch {
46237
47591
  }
46238
47592
  this.userDataDir = null;
@@ -46321,7 +47675,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46321
47675
  */
46322
47676
  resolveVideoConfig(recordVideo) {
46323
47677
  if (!recordVideo) return null;
46324
- const dir = recordVideo === true || !recordVideo.dir ? path3.join(this.tmpBaseDir, `playwright-video-${Date.now()}`) : recordVideo.dir;
47678
+ const dir = recordVideo === true || !recordVideo.dir ? path4.join(this.tmpBaseDir, `playwright-video-${Date.now()}`) : recordVideo.dir;
46325
47679
  const size = recordVideo !== true && recordVideo.size ? recordVideo.size : DEFAULT_VIEWPORT;
46326
47680
  return { dir, size };
46327
47681
  }
@@ -46380,7 +47734,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46380
47734
  for (const handle of videoHandles) {
46381
47735
  try {
46382
47736
  const videoFileName = `video-tab${handle.pageIndex}-${timestamp}.webm`;
46383
- const savePath = path3.join(this.videoDir, videoFileName);
47737
+ const savePath = path4.join(this.videoDir, videoFileName);
46384
47738
  await handle.video.saveAs(savePath);
46385
47739
  const createdAt = this._pageCreationTimes.get(handle.pageIndex) ?? timestamp;
46386
47740
  allPageVideos.push({
@@ -46446,7 +47800,14 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46446
47800
  );
46447
47801
  return null;
46448
47802
  }
47803
+ // ================ TOASTS ================
47804
+ getCapturedToasts() {
47805
+ return this._lastCapturedToasts;
47806
+ }
46449
47807
  // ================ DOWNLOADS ================
47808
+ getCapturedDownloads() {
47809
+ return this._capturedDownloads;
47810
+ }
46450
47811
  async listDownloads() {
46451
47812
  if (this._capturedDownloads.length === 0) {
46452
47813
  return "No downloads captured during this session.";
@@ -46487,7 +47848,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46487
47848
  const filename = target.suggestedFilename;
46488
47849
  const sizeBytes = target.sizeBytes ?? 0;
46489
47850
  const mimeType = this.guessMimeType(filename);
46490
- const ext = path3.extname(filename).toLowerCase();
47851
+ const ext = path4.extname(filename).toLowerCase();
46491
47852
  if (ext === ".pdf") {
46492
47853
  const { extractPdfText: extractPdfText2 } = await import("./pdf-extract-XYDS42VL.js");
46493
47854
  const { text } = await extractPdfText2(target.savedPath);
@@ -46508,7 +47869,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46508
47869
  ".svg"
46509
47870
  ]);
46510
47871
  if (textExtensions.has(ext)) {
46511
- const content = await fs3.readFile(target.savedPath, "utf-8");
47872
+ const content = await fs4.readFile(target.savedPath, "utf-8");
46512
47873
  return { filename, sizeBytes, mimeType, textContent: content };
46513
47874
  }
46514
47875
  return { filename, sizeBytes, mimeType, textContent: null };
@@ -46585,6 +47946,8 @@ export {
46585
47946
  CursorOverlay,
46586
47947
  setCdpScreencastLogger,
46587
47948
  CdpScreencastManager,
47949
+ extractIndexedDB,
47950
+ restoreIndexedDB,
46588
47951
  setSnapshotAnalyzerLogger,
46589
47952
  extractSnapshotYaml,
46590
47953
  parseSnapshot,
@@ -46599,26 +47962,32 @@ export {
46599
47962
  normalizeIconLabelText,
46600
47963
  normalizeSearchText,
46601
47964
  extractDataValue,
47965
+ extractCellText,
47966
+ extractGridCell,
47967
+ reconcileCellColumn,
47968
+ detectGrid,
47969
+ formatGridOutput,
47970
+ formatGridRow,
46602
47971
  findRelevanceScope,
46603
47972
  searchElements,
46604
47973
  populateSearchContext,
46605
47974
  sortMatchesByContext,
46606
- detectGrid,
46607
- extractCellText,
46608
- extractGridCell,
46609
- reconcileCellColumn,
46610
- findActiveElement,
46611
47975
  buildSectionTree,
46612
47976
  markMatchingSections,
47977
+ findActiveElement,
46613
47978
  formatSemanticSnapshot,
46614
47979
  expandSection,
46615
47980
  expandSectionOnly,
46616
- formatGridOutput,
46617
- formatGridRow,
47981
+ extractTablesFromSnapshot,
47982
+ gridInfoToRows,
47983
+ formatCSVWithFrontmatter,
47984
+ appendRowsToCSV,
47985
+ detectPaginationInfo,
46618
47986
  captureSnapshotState,
46619
47987
  compareSnapshots,
46620
47988
  hasDiffChanges,
46621
47989
  formatSemanticDiff,
47990
+ guessMimeType,
46622
47991
  captureElementAtPoint,
46623
47992
  AUTO_SNAPSHOT_MARKER,
46624
47993
  isMCPContentWithImages,
@@ -46633,6 +48002,7 @@ export {
46633
48002
  getBrowserToolDefinitions,
46634
48003
  BROWSER_LIFECYCLE_TOOLS,
46635
48004
  getBrowserToolDefinitionsWithLifecycle,
48005
+ dispatchBrowserTool,
46636
48006
  extractSemanticHint,
46637
48007
  PlaywrightClient
46638
48008
  };
@@ -46915,4 +48285,4 @@ playwright-extra/dist/index.esm.js:
46915
48285
  * @license MIT
46916
48286
  *)
46917
48287
  */
46918
- //# sourceMappingURL=chunk-RL5Y6V3C.js.map
48288
+ //# sourceMappingURL=chunk-XGO62PO2.js.map