@canaryai/cli 0.2.9 → 0.2.13

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 (77) hide show
  1. package/dist/{chunk-PWWQGYFG.js → chunk-ACRIE2YR.js} +5 -2
  2. package/dist/chunk-ACRIE2YR.js.map +1 -0
  3. package/dist/chunk-BOS2YLKH.js +233 -0
  4. package/dist/chunk-BOS2YLKH.js.map +1 -0
  5. package/dist/{chunk-XGO62PO2.js → chunk-IFOJT3A5.js} +1198 -262
  6. package/dist/chunk-IFOJT3A5.js.map +1 -0
  7. package/dist/{chunk-LC7ZVXPH.js → chunk-SVU2XTYZ.js} +19 -5
  8. package/dist/chunk-SVU2XTYZ.js.map +1 -0
  9. package/dist/{chunk-A44B2PEA.js → chunk-SYPQF57S.js} +40 -8
  10. package/dist/chunk-SYPQF57S.js.map +1 -0
  11. package/dist/{chunk-C2PGZRYK.js → chunk-Z3F373YR.js} +37 -11
  12. package/dist/chunk-Z3F373YR.js.map +1 -0
  13. package/dist/{debug-workflow-I3F36JBL.js → debug-workflow-K2LL6CO4.js} +10 -12
  14. package/dist/debug-workflow-K2LL6CO4.js.map +1 -0
  15. package/dist/{docs-REHST3YB.js → docs-SR7CW24Y.js} +19 -14
  16. package/dist/docs-SR7CW24Y.js.map +1 -0
  17. package/dist/{feature-flag-3HB5NTMY.js → feature-flag-BIPFVVNC.js} +3 -3
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +83 -155
  20. package/dist/index.js.map +1 -1
  21. package/dist/init-KXAVWHYE.js +146 -0
  22. package/dist/init-KXAVWHYE.js.map +1 -0
  23. package/dist/{issues-YU57CHXS.js → issues-EWVB52CA.js} +37 -18
  24. package/dist/issues-EWVB52CA.js.map +1 -0
  25. package/dist/{knobs-QJ4IBLCT.js → knobs-VYABZESR.js} +3 -3
  26. package/dist/list-RCPYLS36.js +57 -0
  27. package/dist/list-RCPYLS36.js.map +1 -0
  28. package/dist/local-34FX3M5K.js +63 -0
  29. package/dist/local-34FX3M5K.js.map +1 -0
  30. package/dist/{local-browser-MKTJ36KY.js → local-browser-VPOSJS52.js} +4 -4
  31. package/dist/login-MSIM2VIH.js +130 -0
  32. package/dist/login-MSIM2VIH.js.map +1 -0
  33. package/dist/{mcp-ZOKM2AUE.js → mcp-YBR7G254.js} +7 -132
  34. package/dist/mcp-YBR7G254.js.map +1 -0
  35. package/dist/{psql-2YPIRMDY.js → psql-XO5BB5L5.js} +2 -2
  36. package/dist/{record-TNDBT3NY.js → record-DXXQHPGT.js} +10 -51
  37. package/dist/record-DXXQHPGT.js.map +1 -0
  38. package/dist/{redis-A7GWM23E.js → redis-CQTBPZ6F.js} +2 -2
  39. package/dist/{release-L4IXOHDF.js → release-DW7RPQSQ.js} +9 -5
  40. package/dist/release-DW7RPQSQ.js.map +1 -0
  41. package/dist/runner/preload.js +1 -1
  42. package/dist/{session-RNLKFS2Z.js → session-XQGCLWNC.js} +164 -75
  43. package/dist/session-XQGCLWNC.js.map +1 -0
  44. package/dist/skill-2TXI3IKP.js +424 -0
  45. package/dist/skill-2TXI3IKP.js.map +1 -0
  46. package/dist/{src-2WSMYBMJ.js → src-F7LQ5PY2.js} +8 -2
  47. package/dist/start-ZOMUD6LW.js +112 -0
  48. package/dist/start-ZOMUD6LW.js.map +1 -0
  49. package/dist/test.js +1 -1
  50. package/dist/test.js.map +1 -1
  51. package/dist/workflow-5UZTKX7X.js +624 -0
  52. package/dist/workflow-5UZTKX7X.js.map +1 -0
  53. package/package.json +1 -2
  54. package/dist/chunk-A44B2PEA.js.map +0 -1
  55. package/dist/chunk-C2PGZRYK.js.map +0 -1
  56. package/dist/chunk-DXIAHB72.js +0 -340
  57. package/dist/chunk-DXIAHB72.js.map +0 -1
  58. package/dist/chunk-LC7ZVXPH.js.map +0 -1
  59. package/dist/chunk-PWWQGYFG.js.map +0 -1
  60. package/dist/chunk-QLFSJG5O.js +0 -93
  61. package/dist/chunk-QLFSJG5O.js.map +0 -1
  62. package/dist/chunk-XGO62PO2.js.map +0 -1
  63. package/dist/debug-workflow-I3F36JBL.js.map +0 -1
  64. package/dist/docs-REHST3YB.js.map +0 -1
  65. package/dist/issues-YU57CHXS.js.map +0 -1
  66. package/dist/mcp-ZOKM2AUE.js.map +0 -1
  67. package/dist/record-TNDBT3NY.js.map +0 -1
  68. package/dist/release-L4IXOHDF.js.map +0 -1
  69. package/dist/session-RNLKFS2Z.js.map +0 -1
  70. package/dist/skill-CZ7SHI3P.js +0 -156
  71. package/dist/skill-CZ7SHI3P.js.map +0 -1
  72. /package/dist/{feature-flag-3HB5NTMY.js.map → feature-flag-BIPFVVNC.js.map} +0 -0
  73. /package/dist/{knobs-QJ4IBLCT.js.map → knobs-VYABZESR.js.map} +0 -0
  74. /package/dist/{local-browser-MKTJ36KY.js.map → local-browser-VPOSJS52.js.map} +0 -0
  75. /package/dist/{psql-2YPIRMDY.js.map → psql-XO5BB5L5.js.map} +0 -0
  76. /package/dist/{redis-A7GWM23E.js.map → redis-CQTBPZ6F.js.map} +0 -0
  77. /package/dist/{src-2WSMYBMJ.js.map → src-F7LQ5PY2.js.map} +0 -0
@@ -38199,7 +38199,7 @@ function setSnapshotAnalyzerLogger(l) {
38199
38199
  logger = l;
38200
38200
  }
38201
38201
  function extractSnapshotYaml(mcpResponse) {
38202
- const text = typeof mcpResponse === "string" ? mcpResponse : mcpResponse != null && typeof mcpResponse === "object" && "text" in mcpResponse && typeof mcpResponse.text === "string" ? mcpResponse.text : String(mcpResponse);
38202
+ const text = typeof mcpResponse === "string" ? mcpResponse : mcpResponse !== null && mcpResponse !== void 0 && typeof mcpResponse === "object" && "text" in mcpResponse && typeof mcpResponse.text === "string" ? mcpResponse.text : String(mcpResponse);
38203
38203
  const yamlMatch = text.match(/Page Snapshot:\s*```yaml\s*([\s\S]*?)```/);
38204
38204
  if (yamlMatch) {
38205
38205
  return yamlMatch[1].trim();
@@ -38537,6 +38537,18 @@ function normalizeIconLabelText(role, text) {
38537
38537
  }
38538
38538
  return ICON_ONLY_ROLES.has(role) ? "(icon)" : "";
38539
38539
  }
38540
+ var STYLED_WRAPPER_RE = /^Styled\(\w+\)$/;
38541
+ var MUI_CLASS_RE = /^Mui\w+-[\w-]+$/;
38542
+ var HAS_ASCII_ALNUM_RE = /[A-Za-z0-9]/;
38543
+ function isNoisyLabel(text) {
38544
+ const trimmed = text.trim();
38545
+ if (!trimmed) return false;
38546
+ if (STYLED_WRAPPER_RE.test(trimmed)) return true;
38547
+ if (MUI_CLASS_RE.test(trimmed)) return true;
38548
+ if (HAS_PRIVATE_USE_GLYPH_RE.test(trimmed)) return false;
38549
+ if (!HAS_ASCII_ALNUM_RE.test(trimmed)) return true;
38550
+ return false;
38551
+ }
38540
38552
 
38541
38553
  // ../browser-core/src/snapshot-formatter-shared.ts
38542
38554
  var MAX_SEARCH_MATCHES = 10;
@@ -38556,9 +38568,18 @@ var INTERACTIVE_LEAF_ROLES = /* @__PURE__ */ new Set([
38556
38568
  "switch",
38557
38569
  "svg-shape"
38558
38570
  ]);
38559
- var FORM_INPUT_ROLES = /* @__PURE__ */ new Set(["combobox", "textbox", "searchbox", "spinbutton", "slider"]);
38571
+ var FORM_INPUT_ROLES = /* @__PURE__ */ new Set([
38572
+ "combobox",
38573
+ "textbox",
38574
+ "searchbox",
38575
+ "spinbutton",
38576
+ "slider"
38577
+ ]);
38560
38578
  var INTERACTIVE_CONTAINER_ROLES = /* @__PURE__ */ new Set(["menu", "menubar", "tablist", "listbox"]);
38561
- var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([...INTERACTIVE_LEAF_ROLES, ...INTERACTIVE_CONTAINER_ROLES]);
38579
+ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
38580
+ ...INTERACTIVE_LEAF_ROLES,
38581
+ ...INTERACTIVE_CONTAINER_ROLES
38582
+ ]);
38562
38583
  var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
38563
38584
  "banner",
38564
38585
  "navigation",
@@ -38584,15 +38605,16 @@ var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
38584
38605
  "caption"
38585
38606
  ]);
38586
38607
  var VISUAL_HEADING_MAX_LENGTH = 60;
38587
- var STATUS_WORDS = /* @__PURE__ */ new Set(["favorable", "unfavorable", "success", "error", "warning", "pending"]);
38588
- var BLANK_PATTERNS = [
38589
- /\s*\(Blank\)\s*/gi,
38590
- /\s*\(blank\)\s*/gi
38591
- ];
38592
- var MENU_TRIGGER_PATTERNS = [
38593
- /^Open menu for /i,
38594
- /^Show more options$/i
38595
- ];
38608
+ var STATUS_WORDS = /* @__PURE__ */ new Set([
38609
+ "favorable",
38610
+ "unfavorable",
38611
+ "success",
38612
+ "error",
38613
+ "warning",
38614
+ "pending"
38615
+ ]);
38616
+ var BLANK_PATTERNS = [/\s*\(Blank\)\s*/gi, /\s*\(blank\)\s*/gi];
38617
+ var MENU_TRIGGER_PATTERNS = [/^Open menu for /i, /^Show more options$/i];
38596
38618
  var SECTION_BOUNDARY_ROLES = /* @__PURE__ */ new Set([
38597
38619
  "dialog",
38598
38620
  "alertdialog",
@@ -38605,7 +38627,7 @@ var SECTION_BOUNDARY_ROLES = /* @__PURE__ */ new Set([
38605
38627
  ]);
38606
38628
  var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
38607
38629
  function normalizeSearchText(text) {
38608
- return text.toLowerCase().replace(/[-_.,;:!?'"()\[\]{}\/\\@#$%^&*+=<>~`|]/g, "").replace(/\s+/g, " ").trim();
38630
+ return text.toLowerCase().replace(/[-_.,;:!?'"()[\]{}/\\@#$%^&*+=<>~`|]/g, "").replace(/\s+/g, " ").trim();
38609
38631
  }
38610
38632
  function isActiveElement(element) {
38611
38633
  return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
@@ -38651,13 +38673,39 @@ function composeLabel(element) {
38651
38673
  }
38652
38674
  return textParts.join(" | ") || element.role;
38653
38675
  }
38676
+ function composeChildLabel(element) {
38677
+ const textParts = [];
38678
+ function collectText(el, depth) {
38679
+ if (depth > 3) return;
38680
+ const text = getElementText(el);
38681
+ if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38682
+ textParts.push(text);
38683
+ }
38684
+ for (const child of el.children) {
38685
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38686
+ collectText(child, depth + 1);
38687
+ }
38688
+ }
38689
+ }
38690
+ for (const child of element.children) {
38691
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38692
+ collectText(child, 0);
38693
+ }
38694
+ }
38695
+ return textParts.length > 0 ? textParts.join(" | ") : null;
38696
+ }
38654
38697
  function getDisplayText(element, options) {
38655
38698
  const isClickable = isActionableClickableGeneric(element);
38656
- const rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
38699
+ let rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
38700
+ if (rawTextSource && isNoisyLabel(rawTextSource) && INTERACTIVE_LEAF_ROLES.has(element.role)) {
38701
+ const childText = composeChildLabel(element);
38702
+ rawTextSource = childText ?? "";
38703
+ }
38657
38704
  return normalizeIconLabelText(element.role, rawTextSource);
38658
38705
  }
38659
38706
  function hasInteractiveDescendants(element) {
38660
- if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
38707
+ if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element)))
38708
+ return true;
38661
38709
  for (const child of element.children) {
38662
38710
  if (hasInteractiveDescendants(child)) return true;
38663
38711
  }
@@ -38852,7 +38900,7 @@ function extractGridCell(cellEl) {
38852
38900
  findInteractive(cellEl);
38853
38901
  if (interactiveElements.length === 0) {
38854
38902
  const textValue = extractCellText(cellEl);
38855
- if (textValue) {
38903
+ if (textValue && !isNoisyLabel(textValue)) {
38856
38904
  return {
38857
38905
  ref: cellEl.ref,
38858
38906
  column: cellEl.text || "",
@@ -38866,7 +38914,8 @@ function extractGridCell(cellEl) {
38866
38914
  (best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
38867
38915
  );
38868
38916
  const column = stripBlankPatterns(found.column);
38869
- const value = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
38917
+ const rawValue = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
38918
+ const value = isNoisyLabel(rawValue) ? "" : rawValue;
38870
38919
  const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
38871
38920
  return {
38872
38921
  ref: found.ref,
@@ -39583,7 +39632,8 @@ function shouldCollapse(section) {
39583
39632
  if (section.hasSearchMatch) return false;
39584
39633
  if (section.isBackground) return true;
39585
39634
  if (section.role === "dialog" || section.role === "alertdialog") return false;
39586
- if (section.role === "navigation" || section.role === "menubar" || section.role === "menu") return false;
39635
+ if (section.role === "navigation" || section.role === "menubar" || section.role === "menu")
39636
+ return false;
39587
39637
  if (section.role === "banner") return false;
39588
39638
  if (section.role === "form") return false;
39589
39639
  if (section.elementCount >= MIN_ELEMENTS_TO_COLLAPSE) return true;
@@ -39636,7 +39686,12 @@ function collectInteractiveElements(element, elements, subsections, depth, inher
39636
39686
  if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
39637
39687
  const effectiveFieldLabel = shouldDropInheritedFieldLabel(element, inheritedFieldLabel) ? void 0 : inheritedFieldLabel;
39638
39688
  if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
39639
- elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: effectiveFieldLabel }));
39689
+ elements.push(
39690
+ formatElement(element, {
39691
+ overrideLabel: composeTriggerLabel(element),
39692
+ fieldLabel: effectiveFieldLabel
39693
+ })
39694
+ );
39640
39695
  collectExpandedChildren(element, elements);
39641
39696
  return;
39642
39697
  }
@@ -39692,7 +39747,9 @@ function collectInteractiveElements(element, elements, subsections, depth, inher
39692
39747
  const fieldLabel = shouldDropInheritedFieldLabel(child, rawFieldLabel) ? void 0 : rawFieldLabel;
39693
39748
  if (!isMenuTriggerNoise(child)) {
39694
39749
  if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39695
- elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
39750
+ elements.push(
39751
+ formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel })
39752
+ );
39696
39753
  collectExpandedChildren(child, elements);
39697
39754
  } else {
39698
39755
  elements.push(formatElement(child, { fieldLabel }));
@@ -39765,7 +39822,9 @@ function buildSection(element, depth) {
39765
39822
  } else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
39766
39823
  const fieldLabel = findFieldLabelInContext(child, children2, ci);
39767
39824
  if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39768
- interactiveElements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
39825
+ interactiveElements.push(
39826
+ formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel })
39827
+ );
39769
39828
  collectExpandedChildren(child, interactiveElements);
39770
39829
  } else {
39771
39830
  if (!isMenuTriggerNoise(child)) {
@@ -39773,13 +39832,25 @@ function buildSection(element, depth) {
39773
39832
  }
39774
39833
  if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
39775
39834
  for (const grandchild of child.children) {
39776
- collectInteractiveElements(grandchild, interactiveElements, subsections, depth + 1, fieldLabel);
39835
+ collectInteractiveElements(
39836
+ grandchild,
39837
+ interactiveElements,
39838
+ subsections,
39839
+ depth + 1,
39840
+ fieldLabel
39841
+ );
39777
39842
  }
39778
39843
  }
39779
39844
  }
39780
39845
  } else {
39781
39846
  const containerLabel = resolveContainerFieldLabel(child) ?? findFieldLabelFromSiblings(children2, ci);
39782
- collectInteractiveElements(child, interactiveElements, subsections, depth + 1, containerLabel);
39847
+ collectInteractiveElements(
39848
+ child,
39849
+ interactiveElements,
39850
+ subsections,
39851
+ depth + 1,
39852
+ containerLabel
39853
+ );
39783
39854
  }
39784
39855
  }
39785
39856
  if (isClickableByAttribute(element) && element.ref && !INTERACTIVE_ROLES.has(element.role)) {
@@ -39821,6 +39892,24 @@ function markBackgroundSections(sections) {
39821
39892
  markBackgroundSections(section.subsections);
39822
39893
  }
39823
39894
  }
39895
+ function mergeFilterDropdownPairs(elements) {
39896
+ for (let i = elements.length - 2; i >= 0; i--) {
39897
+ const current = elements[i];
39898
+ const next = elements[i + 1];
39899
+ if (current.role === "generic" && current.isClickable && next.role === "img" && next.isClickable && (!next.label || next.label === "img")) {
39900
+ if (!current.fieldLabel && next.fieldLabel) {
39901
+ current.fieldLabel = next.fieldLabel;
39902
+ }
39903
+ elements.splice(i + 1, 1);
39904
+ }
39905
+ }
39906
+ }
39907
+ function applyFilterMerge(sections) {
39908
+ for (const section of sections) {
39909
+ mergeFilterDropdownPairs(section.elements);
39910
+ applyFilterMerge(section.subsections);
39911
+ }
39912
+ }
39824
39913
  function buildSectionTree(elements) {
39825
39914
  const sections = [];
39826
39915
  for (const element of elements) {
@@ -39830,6 +39919,7 @@ function buildSectionTree(elements) {
39830
39919
  }
39831
39920
  }
39832
39921
  markBackgroundSections(sections);
39922
+ applyFilterMerge(sections);
39833
39923
  return sections;
39834
39924
  }
39835
39925
  function markMatchingSections(sections, matchedRefs) {
@@ -40013,11 +40103,15 @@ function formatSectionOutput(section, indent, showCollapsed) {
40013
40103
  const focusedPart = section.isFocused ? " [FOCUSED]" : "";
40014
40104
  const backgroundPart = section.isBackground ? " [BACKGROUND]" : "";
40015
40105
  const activePart = section.containsActive && !section.isFocused ? " \u2190 ACTIVE" : "";
40016
- lines.push(`${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`);
40106
+ lines.push(
40107
+ `${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`
40108
+ );
40017
40109
  if (section.collapsed && !showCollapsed) {
40018
40110
  if (section.isBackground) {
40019
40111
  if (section.ref) {
40020
- lines.push(`${indentStr} (${section.elementCount} elements - use expand="${section.ref}" to see content)`);
40112
+ lines.push(
40113
+ `${indentStr} (${section.elementCount} elements - use expand="${section.ref}" to see content)`
40114
+ );
40021
40115
  } else {
40022
40116
  lines.push(`${indentStr} (${section.elementCount} elements)`);
40023
40117
  }
@@ -40146,6 +40240,60 @@ ACTIONS:
40146
40240
  - Use bash or python_execute on the FULL TREE FILE for detailed element analysis
40147
40241
  ---`;
40148
40242
  }
40243
+ function disambiguateDuplicateLabels(sections) {
40244
+ const elementEntries = [];
40245
+ function collectElements(section) {
40246
+ for (const el of section.elements) {
40247
+ elementEntries.push({ el, sectionHeading: section.heading });
40248
+ }
40249
+ for (const sub of section.subsections) {
40250
+ collectElements(sub);
40251
+ }
40252
+ }
40253
+ for (const section of sections) {
40254
+ collectElements(section);
40255
+ }
40256
+ const SKIP_LABELS = /* @__PURE__ */ new Set(["(icon)", "button", "link", "generic"]);
40257
+ const byLabel = /* @__PURE__ */ new Map();
40258
+ for (const entry of elementEntries) {
40259
+ const label = entry.el.label || entry.el.name;
40260
+ if (!label || label.length === 0) continue;
40261
+ if (SKIP_LABELS.has(label)) continue;
40262
+ if (!entry.el.ref) continue;
40263
+ const existing = byLabel.get(label);
40264
+ if (existing) {
40265
+ existing.push(entry);
40266
+ } else {
40267
+ byLabel.set(label, [entry]);
40268
+ }
40269
+ }
40270
+ for (const [, entries] of byLabel) {
40271
+ if (entries.length < 2) continue;
40272
+ const distinctSections = new Set(entries.map((e) => e.sectionHeading));
40273
+ if (distinctSections.size > 1) {
40274
+ for (const entry of entries) {
40275
+ const currentLabel = entry.el.label || entry.el.name || "";
40276
+ if (entry.el.label) {
40277
+ entry.el.label = `${currentLabel} (in ${entry.sectionHeading})`;
40278
+ } else if (entry.el.name) {
40279
+ entry.el.name = `${currentLabel} (in ${entry.sectionHeading})`;
40280
+ }
40281
+ }
40282
+ } else {
40283
+ const ordinals = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th"];
40284
+ for (let i = 0; i < entries.length; i++) {
40285
+ const entry = entries[i];
40286
+ const ordinal = i < ordinals.length ? ordinals[i] : `${i + 1}th`;
40287
+ const currentLabel = entry.el.label || entry.el.name || "";
40288
+ if (entry.el.label) {
40289
+ entry.el.label = `${currentLabel} (${ordinal})`;
40290
+ } else if (entry.el.name) {
40291
+ entry.el.name = `${currentLabel} (${ordinal})`;
40292
+ }
40293
+ }
40294
+ }
40295
+ }
40296
+ }
40149
40297
  function formatSemanticSnapshot(yaml, options) {
40150
40298
  if (!yaml || yaml.trim() === "") {
40151
40299
  return "---\nSEMANTIC SNAPSHOT - 0 elements\nPage appears empty or not loaded.\n---";
@@ -40179,6 +40327,7 @@ function formatSemanticSnapshot(yaml, options) {
40179
40327
  if (options?.probeResult?.ref) {
40180
40328
  markMatchingSections(sortedSections, /* @__PURE__ */ new Set([options.probeResult.ref]));
40181
40329
  }
40330
+ disambiguateDuplicateLabels(sortedSections);
40182
40331
  const elementCount = countRenderedElements(sortedSections);
40183
40332
  const sectionCount = countTotalSections(sortedSections);
40184
40333
  const lines = [];
@@ -40204,7 +40353,11 @@ function formatSemanticSnapshot(yaml, options) {
40204
40353
  lines.push("");
40205
40354
  }
40206
40355
  }
40207
- return lines.join("\n").trim();
40356
+ let result = lines.join("\n").trim();
40357
+ if (options?.offScreenRefs && options.offScreenRefs.size > 0) {
40358
+ result = markOffScreenElements(result, options.offScreenRefs);
40359
+ }
40360
+ return result;
40208
40361
  }
40209
40362
  function expandSection(yaml, sectionRef) {
40210
40363
  if (!yaml || yaml.trim() === "") {
@@ -40278,6 +40431,15 @@ Section with ref="${sectionRef}" not found in snapshot.
40278
40431
  targetSection.collapsed = false;
40279
40432
  return formatExpandedSectionOutput(targetSection);
40280
40433
  }
40434
+ function markOffScreenElements(text, offScreenRefs) {
40435
+ if (offScreenRefs.size === 0) return text;
40436
+ return text.replace(/\[(e\d+)\](?! \[off-screen\])/g, (match, ref) => {
40437
+ if (offScreenRefs.has(ref)) {
40438
+ return `[${ref}] [off-screen]`;
40439
+ }
40440
+ return match;
40441
+ });
40442
+ }
40281
40443
 
40282
40444
  // ../browser-core/src/table-extractor.ts
40283
40445
  function extractTablesFromSnapshot(yamlOrResponse, options) {
@@ -40328,7 +40490,7 @@ function gridInfoToRows(grid) {
40328
40490
  }
40329
40491
  const rows = [];
40330
40492
  for (const row of grid.rows) {
40331
- const values = new Array(columns.length).fill("");
40493
+ const values = Array.from({ length: columns.length }).fill("");
40332
40494
  for (const cell of row.cells) {
40333
40495
  const idx = columnIndex.get(cell.column);
40334
40496
  if (idx !== void 0) {
@@ -41287,9 +41449,14 @@ var BrowserToolExecutor = class {
41287
41449
  this.autoSnapshotAfterAction = options?.autoSnapshotAfterAction !== false;
41288
41450
  this.includeScreenshotWithSnapshot = options?.includeScreenshotWithSnapshot !== false;
41289
41451
  if (!options?.workspaceDir && !options?.tmpBaseDir) {
41290
- options?.logger?.debug?.("[BrowserToolExecutor] No tmpBaseDir provided, using flat canary dir (not org-scoped)");
41452
+ options?.logger?.debug?.(
41453
+ "[BrowserToolExecutor] No tmpBaseDir provided, using flat canary dir (not org-scoped)"
41454
+ );
41291
41455
  }
41292
- this.workspaceDir = options?.workspaceDir ?? path2.join(options?.tmpBaseDir ?? getCanaryTmpDir(), `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`);
41456
+ this.workspaceDir = options?.workspaceDir ?? path2.join(
41457
+ options?.tmpBaseDir ?? getCanaryTmpDir(),
41458
+ `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`
41459
+ );
41293
41460
  this.logger = options?.logger;
41294
41461
  fs2.mkdir(this.workspaceDir, { recursive: true }).catch(() => {
41295
41462
  });
@@ -41357,10 +41524,14 @@ Viewport: ${viewportSize.width}x${viewportSize.height} | Image: ~${imgWidth}x${i
41357
41524
  ` + buildResolutionNote(viewportSize, maxImageDim, this.logger) + `
41358
41525
  ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from grid labels.\n" : ""}Click coordinates should be in viewport space (0-${viewportSize.width}, 0-${viewportSize.height}).
41359
41526
  `;
41360
- return withRecoveryNoticeMCP(this.client, {
41361
- text: scaleInfo + (rawTextContent || ""),
41362
- images: result.images
41363
- }, this.logger);
41527
+ return withRecoveryNoticeMCP(
41528
+ this.client,
41529
+ {
41530
+ text: scaleInfo + (rawTextContent || ""),
41531
+ images: result.images
41532
+ },
41533
+ this.logger
41534
+ );
41364
41535
  }
41365
41536
  return withRecoveryNoticeMCP(this.client, rawTextContent, this.logger);
41366
41537
  }
@@ -41373,10 +41544,14 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41373
41544
  this.maxImageDimension,
41374
41545
  this.logger
41375
41546
  );
41376
- return withRecoveryNoticeMCP(this.client, {
41377
- text: resolutionNote + "\n" + semanticText2,
41378
- images: result.images
41379
- }, this.logger);
41547
+ return withRecoveryNoticeMCP(
41548
+ this.client,
41549
+ {
41550
+ text: resolutionNote + "\n" + semanticText2,
41551
+ images: result.images
41552
+ },
41553
+ this.logger
41554
+ );
41380
41555
  }
41381
41556
  return withRecoveryNoticeMCP(this.client, semanticText2, this.logger);
41382
41557
  }
@@ -41426,7 +41601,9 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41426
41601
  let probeResult;
41427
41602
  if (opts.probeAt && searchTerms.length === 0 && this.client.probeAtCoordinates) {
41428
41603
  try {
41429
- const probe = await this.client.probeAtCoordinates(opts.probeAt.x, opts.probeAt.y, { signal: this.signal });
41604
+ const probe = await this.client.probeAtCoordinates(opts.probeAt.x, opts.probeAt.y, {
41605
+ signal: this.signal
41606
+ });
41430
41607
  if (probe) {
41431
41608
  probeResult = probe;
41432
41609
  if (hasImages && probe.boundingBox) {
@@ -41458,6 +41635,42 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41458
41635
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
41459
41636
  } catch {
41460
41637
  }
41638
+ let offScreenRefs;
41639
+ try {
41640
+ const viewportSize = this.client.getViewportSize();
41641
+ const offScreen = await this.client.evaluate(
41642
+ `() => {
41643
+ const vw = ${viewportSize.width};
41644
+ const vh = ${viewportSize.height};
41645
+ const offscreen = [];
41646
+ const els = document.querySelectorAll('[aria-ref]');
41647
+ for (const el of els) {
41648
+ const ref = el.getAttribute('aria-ref');
41649
+ if (!ref) continue;
41650
+ const r = el.getBoundingClientRect();
41651
+ if (r.width === 0 && r.height === 0) continue;
41652
+ if (r.right < 0 || r.left > vw || r.bottom < 0 || r.top > vh) {
41653
+ offscreen.push(ref);
41654
+ }
41655
+ }
41656
+ return offscreen;
41657
+ }`,
41658
+ { signal: this.signal }
41659
+ );
41660
+ if (typeof offScreen === "string") {
41661
+ try {
41662
+ const parsed = JSON.parse(offScreen);
41663
+ if (Array.isArray(parsed) && parsed.length > 0) {
41664
+ offScreenRefs = new Set(parsed);
41665
+ }
41666
+ } catch {
41667
+ }
41668
+ }
41669
+ } catch (e) {
41670
+ this.logger?.debug?.("[browser_snapshot] Off-screen detection failed", {
41671
+ error: e instanceof Error ? e.message : String(e)
41672
+ });
41673
+ }
41461
41674
  const now = (/* @__PURE__ */ new Date()).toISOString();
41462
41675
  const semanticText = formatSemanticSnapshot(yaml, {
41463
41676
  searchMatches: searchMatches.length > 0 ? searchMatches : void 0,
@@ -41465,7 +41678,8 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41465
41678
  snapshotFilePath,
41466
41679
  probeResult,
41467
41680
  currentTime: now,
41468
- capturedToasts: this.client.getCapturedToasts?.() ?? []
41681
+ capturedToasts: this.client.getCapturedToasts?.() ?? [],
41682
+ offScreenRefs
41469
41683
  });
41470
41684
  if (hasImages) {
41471
41685
  const resolutionNote = buildResolutionNote(
@@ -41473,10 +41687,14 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41473
41687
  this.maxImageDimension,
41474
41688
  this.logger
41475
41689
  );
41476
- return withRecoveryNoticeMCP(this.client, {
41477
- text: resolutionNote + "\n" + semanticText,
41478
- images: result.images
41479
- }, this.logger);
41690
+ return withRecoveryNoticeMCP(
41691
+ this.client,
41692
+ {
41693
+ text: resolutionNote + "\n" + semanticText,
41694
+ images: result.images
41695
+ },
41696
+ this.logger
41697
+ );
41480
41698
  }
41481
41699
  return withRecoveryNoticeMCP(this.client, semanticText, this.logger);
41482
41700
  }
@@ -41495,10 +41713,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41495
41713
  if (img) {
41496
41714
  const base64Match = img.match(/^data:image\/(png|jpeg);base64,(.+)$/);
41497
41715
  if (base64Match) {
41498
- const downsampledBase64 = await downsampleImage(
41499
- base64Match[2],
41500
- this.maxImageDimension
41501
- );
41716
+ const downsampledBase64 = await downsampleImage(base64Match[2], this.maxImageDimension);
41502
41717
  return {
41503
41718
  text: `Screenshot captured${opts?.label ? ` (${opts.label})` : ""}`,
41504
41719
  images: [{ data: downsampledBase64, mimeType: "image/jpeg" }]
@@ -41559,18 +41774,32 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41559
41774
  if (dialogMsg) {
41560
41775
  result = result + "\n\n" + dialogMsg;
41561
41776
  }
41562
- return withRecoveryNoticeMCP(this.client, composeActionResult(result, autoSnapshot), this.logger);
41777
+ return withRecoveryNoticeMCP(
41778
+ this.client,
41779
+ composeActionResult(result, autoSnapshot),
41780
+ this.logger
41781
+ );
41563
41782
  }
41564
41783
  async type(opts) {
41565
41784
  const submitNote = opts.submit ? " and submitted" : "";
41566
41785
  const actionDesc = `Typed "${opts.text}" into "${opts.element}"${submitNote}`;
41567
41786
  if (opts.submit) {
41568
41787
  const { diffText, autoSnapshot } = await this.withSemanticDiff(actionDesc, async () => {
41569
- await this.client.type(opts.ref, opts.text, opts.element, opts.submit, { delay: opts.delay, signal: this.signal });
41788
+ await this.client.type(opts.ref, opts.text, opts.element, opts.submit, {
41789
+ delay: opts.delay,
41790
+ signal: this.signal
41791
+ });
41570
41792
  });
41571
- return withRecoveryNoticeMCP(this.client, composeActionResult(diffText, autoSnapshot), this.logger);
41793
+ return withRecoveryNoticeMCP(
41794
+ this.client,
41795
+ composeActionResult(diffText, autoSnapshot),
41796
+ this.logger
41797
+ );
41572
41798
  }
41573
- await this.client.type(opts.ref, opts.text, opts.element, opts.submit, { delay: opts.delay, signal: this.signal });
41799
+ await this.client.type(opts.ref, opts.text, opts.element, opts.submit, {
41800
+ delay: opts.delay,
41801
+ signal: this.signal
41802
+ });
41574
41803
  return withRecoveryNotice(this.client, actionDesc, this.logger);
41575
41804
  }
41576
41805
  async hover(opts) {
@@ -41591,9 +41820,18 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41591
41820
  const actionDesc = useCoordinates ? `Dragged from (${opts.startX}, ${opts.startY}) to (${opts.endX}, ${opts.endY})` : `Dragged "${opts.startElement}" to "${opts.endElement}"`;
41592
41821
  const { diffText, autoSnapshot } = await this.withSemanticDiff(actionDesc, async () => {
41593
41822
  if (useCoordinates) {
41594
- await this.client.dragCoordinates(opts.startX, opts.startY, opts.endX, opts.endY, opts.startElement, { signal: this.signal });
41823
+ await this.client.dragCoordinates(
41824
+ opts.startX,
41825
+ opts.startY,
41826
+ opts.endX,
41827
+ opts.endY,
41828
+ opts.startElement,
41829
+ { signal: this.signal }
41830
+ );
41595
41831
  } else {
41596
- await this.client.drag(opts.startRef, opts.startElement, opts.endRef, opts.endElement, { signal: this.signal });
41832
+ await this.client.drag(opts.startRef, opts.startElement, opts.endRef, opts.endElement, {
41833
+ signal: this.signal
41834
+ });
41597
41835
  }
41598
41836
  });
41599
41837
  return composeActionResult(diffText, autoSnapshot);
@@ -41604,7 +41842,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41604
41842
  const { diffText, autoSnapshot } = await this.withSemanticDiff(`Pressed ${key}`, async () => {
41605
41843
  await this.client.pressKey(key, { signal: this.signal });
41606
41844
  });
41607
- return withRecoveryNoticeMCP(this.client, composeActionResult(diffText, autoSnapshot), this.logger);
41845
+ return withRecoveryNoticeMCP(
41846
+ this.client,
41847
+ composeActionResult(diffText, autoSnapshot),
41848
+ this.logger
41849
+ );
41608
41850
  }
41609
41851
  await this.client.pressKey(key, { signal: this.signal });
41610
41852
  return withRecoveryNotice(this.client, `Pressed ${key}`, this.logger);
@@ -41634,7 +41876,13 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41634
41876
  // ==================== Scroll ====================
41635
41877
  async scroll(opts) {
41636
41878
  if (opts.toRef) {
41637
- const result2 = await this.client.scroll(opts.direction, opts.amount, opts.withinRef, opts.toRef, { signal: this.signal });
41879
+ const result2 = await this.client.scroll(
41880
+ opts.direction,
41881
+ opts.amount,
41882
+ opts.withinRef,
41883
+ opts.toRef,
41884
+ { signal: this.signal }
41885
+ );
41638
41886
  const scrollDesc2 = `Scrolled to element ${opts.toRef}`;
41639
41887
  if (isMCPContentWithImages(result2)) {
41640
41888
  return {
@@ -41645,7 +41893,13 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41645
41893
  return scrollDesc2;
41646
41894
  }
41647
41895
  if (opts.x !== void 0 && opts.y !== void 0) {
41648
- const result2 = await this.client.scrollAtCoordinates(opts.x, opts.y, opts.direction, opts.amount, { signal: this.signal });
41896
+ const result2 = await this.client.scrollAtCoordinates(
41897
+ opts.x,
41898
+ opts.y,
41899
+ opts.direction,
41900
+ opts.amount,
41901
+ { signal: this.signal }
41902
+ );
41649
41903
  const scrollDesc2 = `Scrolled ${opts.direction} at (${opts.x}, ${opts.y})${opts.amount ? ` (intensity ${opts.amount})` : ""}`;
41650
41904
  if (isMCPContentWithImages(result2)) {
41651
41905
  return {
@@ -41655,7 +41909,13 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41655
41909
  }
41656
41910
  return scrollDesc2;
41657
41911
  }
41658
- const result = await this.client.scroll(opts.direction, opts.amount, opts.withinRef, void 0, { signal: this.signal });
41912
+ const result = await this.client.scroll(
41913
+ opts.direction,
41914
+ opts.amount,
41915
+ opts.withinRef,
41916
+ void 0,
41917
+ { signal: this.signal }
41918
+ );
41659
41919
  const scrollDesc = `Scrolled ${opts.direction}${opts.amount ? ` (intensity ${opts.amount})` : ""}`;
41660
41920
  if (isMCPContentWithImages(result)) {
41661
41921
  return {
@@ -41667,7 +41927,9 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41667
41927
  }
41668
41928
  // ==================== Dialogs ====================
41669
41929
  async handleDialog(opts) {
41670
- const result = await this.client.handleDialog(opts.action, opts.promptText, { signal: this.signal });
41930
+ const result = await this.client.handleDialog(opts.action, opts.promptText, {
41931
+ signal: this.signal
41932
+ });
41671
41933
  return typeof result === "string" ? result : `Dialog ${opts.action}ed`;
41672
41934
  }
41673
41935
  async dismissOverlay(opts) {
@@ -41697,7 +41959,9 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41697
41959
  break;
41698
41960
  }
41699
41961
  case "backdrop":
41700
- await this.client.clickAtCoordinates(10, 10, "backdrop/outside modal", { signal: this.signal });
41962
+ await this.client.clickAtCoordinates(10, 10, "backdrop/outside modal", {
41963
+ signal: this.signal
41964
+ });
41701
41965
  break;
41702
41966
  }
41703
41967
  await this.client.waitFor({ timeSec: 0.5, signal: this.signal });
@@ -41708,7 +41972,12 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41708
41972
  if (!afterModal) {
41709
41973
  const afterState2 = captureSnapshotState(afterUrl2, afterYaml2);
41710
41974
  const diff3 = compareSnapshots(beforeState, afterState2);
41711
- return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}`, void 0, (/* @__PURE__ */ new Date()).toISOString());
41975
+ return formatSemanticDiff(
41976
+ diff3,
41977
+ `Dismissed overlay using ${strategy}`,
41978
+ void 0,
41979
+ (/* @__PURE__ */ new Date()).toISOString()
41980
+ );
41712
41981
  }
41713
41982
  } catch (err) {
41714
41983
  this.logger?.debug?.(`[BrowserTools] Dismiss strategy ${strategy} failed`, {
@@ -41724,7 +41993,12 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41724
41993
  const diff2 = compareSnapshots(beforeState, afterState);
41725
41994
  return `Could not dismiss overlay with strategies: ${strategies.join(", ")}. Modal may require specific interaction.
41726
41995
 
41727
- ` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)", void 0, (/* @__PURE__ */ new Date()).toISOString());
41996
+ ` + formatSemanticDiff(
41997
+ diff2,
41998
+ "Dismiss overlay attempted (failed)",
41999
+ void 0,
42000
+ (/* @__PURE__ */ new Date()).toISOString()
42001
+ );
41728
42002
  }
41729
42003
  // ==================== Waiting ====================
41730
42004
  async waitFor(opts) {
@@ -41763,7 +42037,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41763
42037
  `Size: ${result.sizeBytes} bytes`,
41764
42038
  `Type: ${result.mimeType}`
41765
42039
  ];
41766
- if (result.textContent != null) {
42040
+ if (result.textContent !== null && result.textContent !== void 0) {
41767
42041
  lines.push("", "--- Content ---", result.textContent);
41768
42042
  } else {
41769
42043
  lines.push("", "[Binary file \u2014 content cannot be displayed as text]");
@@ -41853,7 +42127,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41853
42127
  await fs2.writeFile(snapshotFilePath, afterYaml, "utf-8");
41854
42128
  } catch {
41855
42129
  }
41856
- const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath, currentTime: now, capturedToasts: this.client.getCapturedToasts?.() ?? [] });
42130
+ const semanticText = formatSemanticSnapshot(afterYaml, {
42131
+ snapshotFilePath,
42132
+ currentTime: now,
42133
+ capturedToasts: this.client.getCapturedToasts?.() ?? []
42134
+ });
41857
42135
  const afterImages = isMCPContentWithImages(afterResult) ? afterResult.images : void 0;
41858
42136
  if (afterImages?.length && this.onScreenshot) {
41859
42137
  const img = afterImages[0];
@@ -41894,7 +42172,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41894
42172
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
41895
42173
  } catch {
41896
42174
  }
41897
- const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath, currentTime: (/* @__PURE__ */ new Date()).toISOString(), capturedToasts: this.client.getCapturedToasts?.() ?? [] });
42175
+ const semanticText = formatSemanticSnapshot(yaml, {
42176
+ snapshotFilePath,
42177
+ currentTime: (/* @__PURE__ */ new Date()).toISOString(),
42178
+ capturedToasts: this.client.getCapturedToasts?.() ?? []
42179
+ });
41898
42180
  const yamlBlockPattern = /- Page Snapshot:\n```yaml\n[\s\S]*?```/;
41899
42181
  if (yamlBlockPattern.test(result)) {
41900
42182
  return result.replace(yamlBlockPattern, `- Page Snapshot (semantic):
@@ -41902,30 +42184,602 @@ ${semanticText}`);
41902
42184
  }
41903
42185
  return result + "\n\n" + semanticText;
41904
42186
  }
41905
- };
42187
+ };
42188
+
42189
+ // ../browser-core/src/tools/definitions.ts
42190
+ function getBrowserToolDefinitions() {
42191
+ return [
42192
+ {
42193
+ name: "browser_navigate",
42194
+ description: "Navigate to a URL in the browser",
42195
+ inputSchema: {
42196
+ type: "object",
42197
+ properties: {
42198
+ url: { type: "string", description: "The URL to navigate to" }
42199
+ },
42200
+ required: ["url"]
42201
+ }
42202
+ },
42203
+ {
42204
+ name: "browser_navigate_back",
42205
+ description: "Navigate back to the previous page",
42206
+ inputSchema: {
42207
+ type: "object",
42208
+ properties: {}
42209
+ }
42210
+ },
42211
+ {
42212
+ name: "browser_snapshot",
42213
+ description: 'Get a snapshot of the page accessibility tree. Use search="term" to find elements near your current focus. Search returns ONLY relevant matches within your working context (the dialog, form, or section containing the active element). Results are highlighted in frontmatter with coordinates and on screenshot.',
42214
+ inputSchema: {
42215
+ type: "object",
42216
+ properties: {
42217
+ mode: {
42218
+ type: "string",
42219
+ enum: ["both", "screenshot", "tree"],
42220
+ description: 'What to return: "both" (default) = tree + screenshot, "screenshot" = screenshot only (use coordinate-based clicking), "tree" = tree only'
42221
+ },
42222
+ expand: {
42223
+ type: "string",
42224
+ description: "Ref of a collapsed section to expand and show full details"
42225
+ },
42226
+ search: {
42227
+ type: "string",
42228
+ description: 'Search for 1-2 specific terms to LOCATE elements for your next action. Terms are OR-matched (use "|"), case-insensitive, and ignore punctuation. Results filtered to your current working context (not the whole page). IMPORTANT: Search overlays can obscure subtle UI states. Use search to LOCATE elements, not to VALIDATE visual states. If search results are confusing, take a snapshot without search for a clearer view. Example: "Save" or "Email|Username"'
42229
+ },
42230
+ showCoordinateGrid: {
42231
+ type: "boolean",
42232
+ description: "Overlay a coordinate grid showing pixel positions. Helps estimate click coordinates when using coordinate-based interaction."
42233
+ },
42234
+ probeAt: {
42235
+ type: "object",
42236
+ properties: {
42237
+ x: { type: "number", description: "X coordinate in viewport pixels" },
42238
+ y: { type: "number", description: "Y coordinate in viewport pixels" }
42239
+ },
42240
+ required: ["x", "y"],
42241
+ description: "RECOMMENDED before any coordinate-based click. Probes what element is at a pixel position. Returns the full tree with a PROBE RESULT in frontmatter showing the ref, role, and label of the element at those coordinates. If the probe returns a ref, prefer browser_click ref=... over coordinate clicking. Mutually exclusive with search."
42242
+ }
42243
+ }
42244
+ }
42245
+ },
42246
+ {
42247
+ name: "browser_screenshot",
42248
+ description: "Take a screenshot of the current page or a specific element. Optionally provide a human-readable label to reference this screenshot later.",
42249
+ inputSchema: {
42250
+ type: "object",
42251
+ properties: {
42252
+ fullPage: {
42253
+ type: "boolean",
42254
+ description: "Whether to capture the full page or just viewport"
42255
+ },
42256
+ element: { type: "string", description: "Human-readable element description" },
42257
+ ref: { type: "string", description: "Exact element reference from snapshot" },
42258
+ label: {
42259
+ type: "string",
42260
+ description: "Human-readable label to reference this screenshot later"
42261
+ },
42262
+ returnImage: {
42263
+ type: "boolean",
42264
+ description: "When true, returns the image data so the model can see it. Use sparingly as images consume many tokens."
42265
+ }
42266
+ }
42267
+ }
42268
+ },
42269
+ {
42270
+ name: "browser_evaluate",
42271
+ description: "Execute JavaScript in the page context and return the result",
42272
+ inputSchema: {
42273
+ type: "object",
42274
+ properties: {
42275
+ function: {
42276
+ type: "string",
42277
+ description: 'JavaScript function to execute (e.g., "() => window.location.href")'
42278
+ },
42279
+ element: {
42280
+ type: "string",
42281
+ description: "Human-readable element description to evaluate on"
42282
+ },
42283
+ ref: { type: "string", description: "Exact element reference to evaluate on" }
42284
+ },
42285
+ required: ["function"]
42286
+ }
42287
+ },
42288
+ {
42289
+ name: "browser_console_messages",
42290
+ description: "Get console messages from the page",
42291
+ inputSchema: {
42292
+ type: "object",
42293
+ properties: {
42294
+ onlyErrors: { type: "boolean", description: "Only return error messages" }
42295
+ }
42296
+ }
42297
+ },
42298
+ {
42299
+ name: "browser_network_requests",
42300
+ description: "Get network requests made by the page",
42301
+ inputSchema: {
42302
+ type: "object",
42303
+ properties: {}
42304
+ }
42305
+ },
42306
+ {
42307
+ name: "browser_click",
42308
+ description: "Click an element on the page. Provide EITHER a ref from the snapshot OR coordinates (x, y).\n- **ref**: Uses Playwright locator - handles scrolling into view and actionability checks automatically.\n- **coordinates**: Clicks at exact pixel position - useful when ref-based clicking fails due to overlays.\nAlways describe what you are clicking in the element parameter.",
42309
+ inputSchema: {
42310
+ type: "object",
42311
+ properties: {
42312
+ ref: {
42313
+ type: "string",
42314
+ description: "Element reference from snapshot (optional if x/y coordinates provided)"
42315
+ },
42316
+ x: { type: "number", description: "X coordinate on the page (use with y)" },
42317
+ y: { type: "number", description: "Y coordinate on the page (use with x)" },
42318
+ element: { type: "string", description: "Description of what you are clicking" },
42319
+ doubleClick: { type: "boolean", description: "Perform a double click" },
42320
+ button: {
42321
+ type: "string",
42322
+ description: "Mouse button to click: left, right, middle"
42323
+ },
42324
+ modifiers: {
42325
+ type: "array",
42326
+ items: { type: "string" },
42327
+ description: "Keyboard modifiers: Alt, Control, Meta, Shift"
42328
+ }
42329
+ },
42330
+ required: ["element"]
42331
+ }
42332
+ },
42333
+ {
42334
+ name: "browser_hover",
42335
+ description: "Hover over an element to reveal hidden content (dropdowns, tooltips, submenus). This does NOT click - use browser_click after hovering to interact with revealed items.",
42336
+ inputSchema: {
42337
+ type: "object",
42338
+ properties: {
42339
+ ref: { type: "string", description: "The element reference from snapshot" },
42340
+ element: { type: "string", description: "Description of what you are hovering over" }
42341
+ },
42342
+ required: ["ref", "element"]
42343
+ }
42344
+ },
42345
+ {
42346
+ name: "browser_drag",
42347
+ description: "Drag between two points. Provide EITHER refs OR coordinates.\n- **refs**: startRef + endRef \u2014 uses Playwright locators for drag-and-drop.\n- **coordinates**: startX, startY, endX, endY \u2014 drags between exact pixel positions.",
42348
+ inputSchema: {
42349
+ type: "object",
42350
+ properties: {
42351
+ startRef: { type: "string", description: "Starting element reference" },
42352
+ startElement: { type: "string", description: "Description of starting element" },
42353
+ endRef: { type: "string", description: "Target element reference" },
42354
+ endElement: { type: "string", description: "Description of target element" },
42355
+ startX: { type: "number", description: "Start X coordinate" },
42356
+ startY: { type: "number", description: "Start Y coordinate" },
42357
+ endX: { type: "number", description: "End X coordinate" },
42358
+ endY: { type: "number", description: "End Y coordinate" }
42359
+ },
42360
+ required: ["startElement", "endElement"]
42361
+ }
42362
+ },
42363
+ {
42364
+ name: "browser_type",
42365
+ description: 'Type text into an input field and optionally submit the form. WARNING: For date inputs (type="date"), use ISO format (YYYY-MM-DD) with browser_fill_form instead.',
42366
+ inputSchema: {
42367
+ type: "object",
42368
+ properties: {
42369
+ ref: { type: "string", description: "The element reference from snapshot" },
42370
+ text: { type: "string", description: "The text to type" },
42371
+ element: { type: "string", description: "Description of the input field" },
42372
+ submit: { type: "boolean", description: "Whether to submit the form after typing" },
42373
+ delay: { type: "number", description: "Delay between keystrokes in milliseconds" }
42374
+ },
42375
+ required: ["ref", "text", "element"]
42376
+ }
42377
+ },
42378
+ {
42379
+ name: "browser_press_key",
42380
+ description: "Press a keyboard key",
42381
+ inputSchema: {
42382
+ type: "object",
42383
+ properties: {
42384
+ key: {
42385
+ type: "string",
42386
+ description: 'Key to press (e.g., "Enter", "Escape", "ArrowDown")'
42387
+ }
42388
+ },
42389
+ required: ["key"]
42390
+ }
42391
+ },
42392
+ {
42393
+ name: "browser_fill_form",
42394
+ description: "Fill multiple form fields at once",
42395
+ inputSchema: {
42396
+ type: "object",
42397
+ properties: {
42398
+ fields: {
42399
+ type: "array",
42400
+ items: {
42401
+ type: "object",
42402
+ properties: {
42403
+ ref: { type: "string", description: "Element reference" },
42404
+ element: { type: "string", description: "Element description" },
42405
+ value: { type: "string", description: "Value to fill" }
42406
+ },
42407
+ required: ["ref", "value"]
42408
+ },
42409
+ description: "Array of form fields to fill"
42410
+ }
42411
+ },
42412
+ required: ["fields"]
42413
+ }
42414
+ },
42415
+ {
42416
+ name: "browser_select_option",
42417
+ description: "Select an option from a dropdown",
42418
+ inputSchema: {
42419
+ type: "object",
42420
+ properties: {
42421
+ ref: { type: "string", description: "Select element reference" },
42422
+ value: { type: "string", description: "Option value to select" },
42423
+ element: { type: "string", description: "Description of the dropdown" }
42424
+ },
42425
+ required: ["ref", "value", "element"]
42426
+ }
42427
+ },
42428
+ {
42429
+ name: "browser_file_upload",
42430
+ description: "Upload one or multiple files",
42431
+ inputSchema: {
42432
+ type: "object",
42433
+ properties: {
42434
+ paths: {
42435
+ type: "array",
42436
+ items: { type: "string" },
42437
+ description: "Array of file paths to upload"
42438
+ }
42439
+ },
42440
+ required: ["paths"]
42441
+ }
42442
+ },
42443
+ {
42444
+ name: "browser_scroll",
42445
+ description: "Scroll the page. Returns a screenshot showing the new viewport. Three modes:\n1. **Scroll at coordinates**: Use x + y to scroll at a specific point\n2. **Scroll by amount**: Use direction + amount to scroll up/down/left/right\n3. **Scroll to element**: Use toRef to scroll so that element is centered in the viewport",
42446
+ inputSchema: {
42447
+ type: "object",
42448
+ properties: {
42449
+ direction: {
42450
+ type: "string",
42451
+ enum: ["up", "down", "left", "right"],
42452
+ description: "Direction to scroll (ignored if toRef is provided)"
42453
+ },
42454
+ amount: {
42455
+ type: "number",
42456
+ description: "Scroll intensity from 1-10 (default 3). Ignored if toRef is provided."
42457
+ },
42458
+ withinRef: {
42459
+ type: "string",
42460
+ description: "Scroll within this scrollable element instead of the page"
42461
+ },
42462
+ toRef: {
42463
+ type: "string",
42464
+ description: "Scroll the page so this element is centered in the viewport. Handles both vertical and horizontal scrolling automatically \u2014 works for grid columns that are horizontally off-screen."
42465
+ },
42466
+ x: {
42467
+ type: "number",
42468
+ description: "X coordinate for visual scroll origin."
42469
+ },
42470
+ y: {
42471
+ type: "number",
42472
+ description: "Y coordinate for visual scroll origin."
42473
+ }
42474
+ },
42475
+ required: ["direction"]
42476
+ }
42477
+ },
42478
+ {
42479
+ name: "browser_handle_dialog",
42480
+ description: 'Pre-register how to handle the next browser dialog (alert, confirm, prompt). Call this with action "accept" BEFORE re-clicking the button that triggered the dialog.',
42481
+ inputSchema: {
42482
+ type: "object",
42483
+ properties: {
42484
+ action: {
42485
+ type: "string",
42486
+ enum: ["accept", "dismiss"],
42487
+ description: "Action to take on the dialog"
42488
+ },
42489
+ promptText: { type: "string", description: "Text to enter in prompt dialog" }
42490
+ },
42491
+ required: ["action"]
42492
+ }
42493
+ },
42494
+ {
42495
+ name: "browser_dismiss_overlay",
42496
+ description: "Systematically attempt to dismiss any modal, overlay, or dialog blocking the page. Tries multiple strategies: Escape key, close button, backdrop click.",
42497
+ inputSchema: {
42498
+ type: "object",
42499
+ properties: {
42500
+ preferredStrategy: {
42501
+ type: "string",
42502
+ enum: ["escape", "close_button", "backdrop", "auto"],
42503
+ description: 'Preferred dismissal method. "auto" (default) tries all strategies.'
42504
+ }
42505
+ }
42506
+ }
42507
+ },
42508
+ {
42509
+ name: "browser_wait_for",
42510
+ description: "Wait for a condition or time period",
42511
+ inputSchema: {
42512
+ type: "object",
42513
+ properties: {
42514
+ time: { type: "number", description: "Time to wait in seconds" },
42515
+ text: { type: "string", description: "Text to wait for to appear" },
42516
+ textGone: { type: "string", description: "Text to wait for to disappear" },
42517
+ selector: { type: "string", description: "CSS selector to wait for" },
42518
+ state: {
42519
+ type: "string",
42520
+ description: "State to wait for (visible, hidden, attached, detached)"
42521
+ },
42522
+ timeout: { type: "number", description: "Maximum wait time in milliseconds" }
42523
+ }
42524
+ }
42525
+ },
42526
+ {
42527
+ name: "browser_close",
42528
+ description: "Close the current page",
42529
+ inputSchema: {
42530
+ type: "object",
42531
+ properties: {}
42532
+ }
42533
+ },
42534
+ {
42535
+ name: "browser_resize",
42536
+ description: "Resize the browser viewport",
42537
+ inputSchema: {
42538
+ type: "object",
42539
+ properties: {
42540
+ width: { type: "number", description: "Viewport width in pixels" },
42541
+ height: { type: "number", description: "Viewport height in pixels" }
42542
+ },
42543
+ required: ["width", "height"]
42544
+ }
42545
+ },
42546
+ {
42547
+ name: "browser_tabs",
42548
+ description: "List, create, close, or select a browser tab",
42549
+ inputSchema: {
42550
+ type: "object",
42551
+ properties: {
42552
+ action: {
42553
+ type: "string",
42554
+ enum: ["list", "new", "close", "select"],
42555
+ description: "Operation to perform"
42556
+ },
42557
+ index: {
42558
+ type: "number",
42559
+ description: "Tab index for close/select operations"
42560
+ }
42561
+ },
42562
+ required: ["action"]
42563
+ }
42564
+ },
42565
+ {
42566
+ name: "browser_list_downloads",
42567
+ description: "List all files downloaded during this browser session.",
42568
+ inputSchema: {
42569
+ type: "object",
42570
+ properties: {}
42571
+ }
42572
+ },
42573
+ {
42574
+ name: "browser_read_download",
42575
+ description: "Read the content of a downloaded file. For PDFs, extracts text. For text formats, reads as text. Binary files return metadata only.",
42576
+ inputSchema: {
42577
+ type: "object",
42578
+ properties: {
42579
+ downloadId: {
42580
+ type: "number",
42581
+ description: "ID of the download to read. Defaults to most recent."
42582
+ }
42583
+ }
42584
+ }
42585
+ },
42586
+ {
42587
+ name: "browser_pdf_read",
42588
+ description: "Fetch a PDF from a URL and extract its text content using the browser's authenticated session.",
42589
+ inputSchema: {
42590
+ type: "object",
42591
+ properties: {
42592
+ url: {
42593
+ type: "string",
42594
+ description: "The URL of the PDF to fetch and extract text from"
42595
+ }
42596
+ },
42597
+ required: ["url"]
42598
+ }
42599
+ }
42600
+ ];
42601
+ }
42602
+ var BROWSER_LIFECYCLE_TOOLS = ["browser_start", "browser_stop"];
42603
+ function getBrowserToolDefinitionsWithLifecycle() {
42604
+ return [
42605
+ {
42606
+ name: "browser_start",
42607
+ description: "Launch a local browser for direct automation. Returns when browser is ready. The browser opens in headed mode so you can watch it.",
42608
+ inputSchema: {
42609
+ type: "object",
42610
+ properties: {
42611
+ headless: {
42612
+ type: "boolean",
42613
+ description: "Run browser headless (default: false \u2014 headed so you can watch)"
42614
+ },
42615
+ storageStatePath: {
42616
+ type: "string",
42617
+ description: "Path to Playwright storage state JSON for pre-authenticated sessions"
42618
+ }
42619
+ }
42620
+ }
42621
+ },
42622
+ {
42623
+ name: "browser_stop",
42624
+ description: "Stop the local browser and clean up resources.",
42625
+ inputSchema: {
42626
+ type: "object",
42627
+ properties: {}
42628
+ }
42629
+ },
42630
+ ...getBrowserToolDefinitions()
42631
+ ];
42632
+ }
42633
+
42634
+ // ../browser-core/src/tools/dispatcher.ts
42635
+ async function dispatchBrowserTool(executor, toolName, args) {
42636
+ switch (toolName) {
42637
+ case "browser_navigate":
42638
+ return executor.navigate(args.url);
42639
+ case "browser_navigate_back":
42640
+ return executor.navigateBack();
42641
+ case "browser_snapshot":
42642
+ return executor.snapshot({
42643
+ mode: args.mode,
42644
+ expand: args.expand,
42645
+ search: args.search,
42646
+ showCoordinateGrid: args.showCoordinateGrid,
42647
+ probeAt: args.probeAt
42648
+ });
42649
+ case "browser_screenshot":
42650
+ return executor.screenshot({
42651
+ fullPage: args.fullPage,
42652
+ element: args.element,
42653
+ ref: args.ref,
42654
+ label: args.label,
42655
+ returnImage: args.returnImage
42656
+ });
42657
+ case "browser_evaluate":
42658
+ return executor.evaluate({
42659
+ expression: args.function,
42660
+ element: args.element,
42661
+ ref: args.ref
42662
+ });
42663
+ case "browser_console_messages":
42664
+ return executor.consoleMessages({
42665
+ onlyErrors: args.onlyErrors
42666
+ });
42667
+ case "browser_network_requests":
42668
+ return executor.networkRequests();
42669
+ case "browser_click":
42670
+ return executor.click({
42671
+ ref: args.ref,
42672
+ x: args.x,
42673
+ y: args.y,
42674
+ element: args.element,
42675
+ doubleClick: args.doubleClick,
42676
+ button: args.button,
42677
+ modifiers: args.modifiers
42678
+ });
42679
+ case "browser_hover":
42680
+ return executor.hover({
42681
+ ref: args.ref,
42682
+ element: args.element
42683
+ });
42684
+ case "browser_drag":
42685
+ return executor.drag({
42686
+ startRef: args.startRef,
42687
+ endRef: args.endRef,
42688
+ startElement: args.startElement,
42689
+ endElement: args.endElement,
42690
+ startX: args.startX,
42691
+ startY: args.startY,
42692
+ endX: args.endX,
42693
+ endY: args.endY
42694
+ });
42695
+ case "browser_type":
42696
+ return executor.type({
42697
+ ref: args.ref,
42698
+ text: args.text,
42699
+ element: args.element,
42700
+ submit: args.submit,
42701
+ delay: args.delay
42702
+ });
42703
+ case "browser_press_key":
42704
+ return executor.pressKey(args.key);
42705
+ case "browser_fill_form":
42706
+ return executor.fillForm(
42707
+ args.fields
42708
+ );
42709
+ case "browser_select_option":
42710
+ return executor.selectOption({
42711
+ ref: args.ref,
42712
+ value: args.value,
42713
+ element: args.element
42714
+ });
42715
+ case "browser_file_upload":
42716
+ return executor.fileUpload(args.paths);
42717
+ case "browser_scroll":
42718
+ return executor.scroll({
42719
+ direction: args.direction,
42720
+ amount: args.amount,
42721
+ withinRef: args.withinRef,
42722
+ toRef: args.toRef,
42723
+ x: args.x,
42724
+ y: args.y
42725
+ });
42726
+ case "browser_handle_dialog":
42727
+ return executor.handleDialog({
42728
+ action: args.action,
42729
+ promptText: args.promptText
42730
+ });
42731
+ case "browser_dismiss_overlay":
42732
+ return executor.dismissOverlay({
42733
+ preferredStrategy: args.preferredStrategy
42734
+ });
42735
+ case "browser_wait_for":
42736
+ return executor.waitFor({
42737
+ timeSec: args.time,
42738
+ text: args.text,
42739
+ textGone: args.textGone,
42740
+ selector: args.selector,
42741
+ state: args.state,
42742
+ timeout: args.timeout
42743
+ });
42744
+ case "browser_close":
42745
+ return executor.close();
42746
+ case "browser_resize":
42747
+ return executor.resize(args.width, args.height);
42748
+ case "browser_tabs":
42749
+ return executor.tabs({
42750
+ action: args.action,
42751
+ index: args.index
42752
+ });
42753
+ case "browser_list_downloads":
42754
+ return executor.listDownloads();
42755
+ case "browser_read_download":
42756
+ return executor.readDownload(args.downloadId);
42757
+ case "browser_pdf_read":
42758
+ return executor.fetchPdfText(args.url);
42759
+ default:
42760
+ throw new Error(`Unknown browser tool: ${toolName}`);
42761
+ }
42762
+ }
41906
42763
 
41907
- // ../browser-core/src/tools/definitions.ts
41908
- function getBrowserToolDefinitions() {
42764
+ // ../browser-core/src/tools/consolidated-definitions.ts
42765
+ function getConsolidatedBrowserToolDefinitions() {
41909
42766
  return [
42767
+ // --- Navigation ---
41910
42768
  {
41911
42769
  name: "browser_navigate",
41912
- description: "Navigate to a URL in the browser",
42770
+ description: 'Navigate to a URL in the browser. Use url "back" to go to the previous page.',
41913
42771
  inputSchema: {
41914
42772
  type: "object",
41915
42773
  properties: {
41916
- url: { type: "string", description: "The URL to navigate to" }
42774
+ url: {
42775
+ type: "string",
42776
+ description: 'The URL to navigate to. Use "back" to go to the previous page.'
42777
+ }
41917
42778
  },
41918
42779
  required: ["url"]
41919
42780
  }
41920
42781
  },
41921
- {
41922
- name: "browser_navigate_back",
41923
- description: "Navigate back to the previous page",
41924
- inputSchema: {
41925
- type: "object",
41926
- properties: {}
41927
- }
41928
- },
42782
+ // --- Page Inspection ---
41929
42783
  {
41930
42784
  name: "browser_snapshot",
41931
42785
  description: 'Get a snapshot of the page accessibility tree. Use search="term" to find elements near your current focus. Search returns ONLY relevant matches within your working context (the dialog, form, or section containing the active element). Results are highlighted in frontmatter with coordinates and on screenshot.',
@@ -41956,7 +42810,7 @@ function getBrowserToolDefinitions() {
41956
42810
  y: { type: "number", description: "Y coordinate in viewport pixels" }
41957
42811
  },
41958
42812
  required: ["x", "y"],
41959
- description: "RECOMMENDED before any coordinate-based click. Probes what element is at a pixel position. Returns the full tree with a PROBE RESULT in frontmatter showing the ref, role, and label of the element at those coordinates. If the probe returns a ref, prefer browser_click ref=... over coordinate clicking. Mutually exclusive with search."
42813
+ description: 'RECOMMENDED before any coordinate-based click. Probes what element is at a pixel position. Returns the full tree with a PROBE RESULT in frontmatter showing the ref, role, and label of the element at those coordinates. If the probe returns a ref, prefer browser_action action="click" ref=... over coordinate clicking. Mutually exclusive with search.'
41960
42814
  }
41961
42815
  }
41962
42816
  }
@@ -41984,6 +42838,7 @@ function getBrowserToolDefinitions() {
41984
42838
  }
41985
42839
  }
41986
42840
  },
42841
+ // --- Code Execution ---
41987
42842
  {
41988
42843
  name: "browser_evaluate",
41989
42844
  description: "Execute JavaScript in the page context and return the result",
@@ -42004,62 +42859,61 @@ function getBrowserToolDefinitions() {
42004
42859
  }
42005
42860
  },
42006
42861
  {
42007
- name: "browser_console_messages",
42008
- description: "Get console messages from the page",
42862
+ name: "browser_run_code",
42863
+ description: "Execute Playwright Node.js code with `page` object access. Runs in Node.js, NOT in the browser.",
42009
42864
  inputSchema: {
42010
42865
  type: "object",
42011
42866
  properties: {
42012
- onlyErrors: { type: "boolean", description: "Only return error messages" }
42013
- }
42014
- }
42015
- },
42016
- {
42017
- name: "browser_network_requests",
42018
- description: "Get network requests made by the page",
42019
- inputSchema: {
42020
- type: "object",
42021
- properties: {}
42867
+ code: {
42868
+ type: "string",
42869
+ description: 'Playwright code to run with access to `page` object (e.g., `await page.getByRole("button", { name: "Submit" }).click();`)'
42870
+ }
42871
+ },
42872
+ required: ["code"]
42022
42873
  }
42023
42874
  },
42875
+ // --- Consolidated: browser_action (click + hover + type) ---
42024
42876
  {
42025
- name: "browser_click",
42026
- description: "Click an element on the page. Provide EITHER a ref from the snapshot OR coordinates (x, y).\n- **ref**: Uses Playwright locator - handles scrolling into view and actionability checks automatically.\n- **coordinates**: Clicks at exact pixel position - useful when ref-based clicking fails due to overlays.\nAlways describe what you are clicking in the element parameter.",
42877
+ name: "browser_action",
42878
+ description: 'Interact with a page element. Use action="click" to click, "hover" to reveal dropdowns/tooltips, "type" to enter text.\n- **click**: Provide ref OR coordinates (x, y). Always describe what you are clicking in element.\n- **hover**: Provide ref. Reveals hidden content without clicking.\n- **type**: Provide ref + text. Types into an input field. For date inputs, use browser_fill_form instead.',
42027
42879
  inputSchema: {
42028
42880
  type: "object",
42029
42881
  properties: {
42882
+ action: {
42883
+ type: "string",
42884
+ enum: ["click", "hover", "type"],
42885
+ description: "The interaction to perform"
42886
+ },
42030
42887
  ref: {
42031
42888
  type: "string",
42032
- description: "Element reference from snapshot (optional if x/y coordinates provided)"
42889
+ description: "Element reference from snapshot"
42033
42890
  },
42034
- x: { type: "number", description: "X coordinate on the page (use with y)" },
42035
- y: { type: "number", description: "Y coordinate on the page (use with x)" },
42036
- element: { type: "string", description: "Description of what you are clicking" },
42037
- doubleClick: { type: "boolean", description: "Perform a double click" },
42891
+ element: {
42892
+ type: "string",
42893
+ description: "Description of the element you are interacting with"
42894
+ },
42895
+ // click-specific
42896
+ x: { type: "number", description: "X coordinate on the page (click only, use with y)" },
42897
+ y: { type: "number", description: "Y coordinate on the page (click only, use with x)" },
42898
+ doubleClick: { type: "boolean", description: "Perform a double click (click only)" },
42038
42899
  button: {
42039
42900
  type: "string",
42040
- description: "Mouse button to click: left, right, middle"
42901
+ description: "Mouse button: left, right, middle (click only)"
42041
42902
  },
42042
42903
  modifiers: {
42043
42904
  type: "array",
42044
42905
  items: { type: "string" },
42045
- description: "Keyboard modifiers: Alt, Control, Meta, Shift"
42046
- }
42047
- },
42048
- required: ["element"]
42049
- }
42050
- },
42051
- {
42052
- name: "browser_hover",
42053
- description: "Hover over an element to reveal hidden content (dropdowns, tooltips, submenus). This does NOT click - use browser_click after hovering to interact with revealed items.",
42054
- inputSchema: {
42055
- type: "object",
42056
- properties: {
42057
- ref: { type: "string", description: "The element reference from snapshot" },
42058
- element: { type: "string", description: "Description of what you are hovering over" }
42906
+ description: "Keyboard modifiers: Alt, Control, Meta, Shift (click only)"
42907
+ },
42908
+ // type-specific
42909
+ text: { type: "string", description: "The text to type (type only)" },
42910
+ submit: { type: "boolean", description: "Submit the form after typing (type only)" },
42911
+ delay: { type: "number", description: "Delay between keystrokes in ms (type only)" }
42059
42912
  },
42060
- required: ["ref", "element"]
42913
+ required: ["action", "element"]
42061
42914
  }
42062
42915
  },
42916
+ // --- Kept separate ---
42063
42917
  {
42064
42918
  name: "browser_drag",
42065
42919
  description: "Drag between two points. Provide EITHER refs OR coordinates.\n- **refs**: startRef + endRef \u2014 uses Playwright locators for drag-and-drop.\n- **coordinates**: startX, startY, endX, endY \u2014 drags between exact pixel positions.",
@@ -42078,21 +42932,6 @@ function getBrowserToolDefinitions() {
42078
42932
  required: ["startElement", "endElement"]
42079
42933
  }
42080
42934
  },
42081
- {
42082
- name: "browser_type",
42083
- description: 'Type text into an input field and optionally submit the form. WARNING: For date inputs (type="date"), use ISO format (YYYY-MM-DD) with browser_fill_form instead.',
42084
- inputSchema: {
42085
- type: "object",
42086
- properties: {
42087
- ref: { type: "string", description: "The element reference from snapshot" },
42088
- text: { type: "string", description: "The text to type" },
42089
- element: { type: "string", description: "Description of the input field" },
42090
- submit: { type: "boolean", description: "Whether to submit the form after typing" },
42091
- delay: { type: "number", description: "Delay between keystrokes in milliseconds" }
42092
- },
42093
- required: ["ref", "text", "element"]
42094
- }
42095
- },
42096
42935
  {
42097
42936
  name: "browser_press_key",
42098
42937
  description: "Press a keyboard key",
@@ -42179,50 +43018,14 @@ function getBrowserToolDefinitions() {
42179
43018
  },
42180
43019
  toRef: {
42181
43020
  type: "string",
42182
- description: "Scroll the page so this element is centered vertically in the viewport."
42183
- },
42184
- x: {
42185
- type: "number",
42186
- description: "X coordinate for visual scroll origin."
43021
+ description: "Scroll the page so this element is centered in the viewport. Handles both vertical and horizontal scrolling automatically."
42187
43022
  },
42188
- y: {
42189
- type: "number",
42190
- description: "Y coordinate for visual scroll origin."
42191
- }
43023
+ x: { type: "number", description: "X coordinate for visual scroll origin." },
43024
+ y: { type: "number", description: "Y coordinate for visual scroll origin." }
42192
43025
  },
42193
43026
  required: ["direction"]
42194
43027
  }
42195
43028
  },
42196
- {
42197
- name: "browser_handle_dialog",
42198
- description: 'Pre-register how to handle the next browser dialog (alert, confirm, prompt). Call this with action "accept" BEFORE re-clicking the button that triggered the dialog.',
42199
- inputSchema: {
42200
- type: "object",
42201
- properties: {
42202
- action: {
42203
- type: "string",
42204
- enum: ["accept", "dismiss"],
42205
- description: "Action to take on the dialog"
42206
- },
42207
- promptText: { type: "string", description: "Text to enter in prompt dialog" }
42208
- },
42209
- required: ["action"]
42210
- }
42211
- },
42212
- {
42213
- name: "browser_dismiss_overlay",
42214
- description: "Systematically attempt to dismiss any modal, overlay, or dialog blocking the page. Tries multiple strategies: Escape key, close button, backdrop click.",
42215
- inputSchema: {
42216
- type: "object",
42217
- properties: {
42218
- preferredStrategy: {
42219
- type: "string",
42220
- enum: ["escape", "close_button", "backdrop", "auto"],
42221
- description: 'Preferred dismissal method. "auto" (default) tries all strategies.'
42222
- }
42223
- }
42224
- }
42225
- },
42226
43029
  {
42227
43030
  name: "browser_wait_for",
42228
43031
  description: "Wait for a condition or time period",
@@ -42241,14 +43044,6 @@ function getBrowserToolDefinitions() {
42241
43044
  }
42242
43045
  }
42243
43046
  },
42244
- {
42245
- name: "browser_close",
42246
- description: "Close the current page",
42247
- inputSchema: {
42248
- type: "object",
42249
- properties: {}
42250
- }
42251
- },
42252
43047
  {
42253
43048
  name: "browser_resize",
42254
43049
  description: "Resize the browser viewport",
@@ -42280,45 +43075,70 @@ function getBrowserToolDefinitions() {
42280
43075
  required: ["action"]
42281
43076
  }
42282
43077
  },
43078
+ // --- Consolidated: browser_dialog (replaces browser_handle_dialog) ---
42283
43079
  {
42284
- name: "browser_list_downloads",
42285
- description: "List all files downloaded during this browser session.",
43080
+ name: "browser_dialog",
43081
+ description: 'Pre-register how to handle the next browser dialog (alert, confirm, prompt). Call this with action "accept" BEFORE re-clicking the button that triggered the dialog.',
42286
43082
  inputSchema: {
42287
43083
  type: "object",
42288
- properties: {}
43084
+ properties: {
43085
+ action: {
43086
+ type: "string",
43087
+ enum: ["accept", "dismiss"],
43088
+ description: "Action to take on the dialog"
43089
+ },
43090
+ promptText: { type: "string", description: "Text to enter in prompt dialog" }
43091
+ },
43092
+ required: ["action"]
42289
43093
  }
42290
43094
  },
43095
+ // --- Consolidated: browser_devtools (console + network) ---
42291
43096
  {
42292
- name: "browser_read_download",
42293
- description: "Read the content of a downloaded file. For PDFs, extracts text. For text formats, reads as text. Binary files return metadata only.",
43097
+ name: "browser_devtools",
43098
+ description: 'Access browser developer tools data. Use target="console" to get console messages, target="network" to get network requests.',
42294
43099
  inputSchema: {
42295
43100
  type: "object",
42296
43101
  properties: {
42297
- downloadId: {
42298
- type: "number",
42299
- description: "ID of the download to read. Defaults to most recent."
43102
+ target: {
43103
+ type: "string",
43104
+ enum: ["console", "network"],
43105
+ description: 'What to inspect: "console" for console messages, "network" for network requests'
43106
+ },
43107
+ onlyErrors: {
43108
+ type: "boolean",
43109
+ description: "Only return error messages (console target only)"
42300
43110
  }
42301
- }
43111
+ },
43112
+ required: ["target"]
42302
43113
  }
42303
43114
  },
43115
+ // --- Consolidated: browser_download (list + read + pdf) ---
42304
43116
  {
42305
- name: "browser_pdf_read",
42306
- description: "Fetch a PDF from a URL and extract its text content using the browser's authenticated session.",
43117
+ name: "browser_download",
43118
+ description: `Manage downloaded files. Use action="list" to list downloads, "read" to read a download's content, "pdf" to fetch and extract text from a PDF URL using the browser's authenticated session.`,
42307
43119
  inputSchema: {
42308
43120
  type: "object",
42309
43121
  properties: {
43122
+ action: {
43123
+ type: "string",
43124
+ enum: ["list", "read", "pdf"],
43125
+ description: 'Operation: "list" downloads, "read" a download, or fetch a "pdf" from URL'
43126
+ },
43127
+ downloadId: {
43128
+ type: "number",
43129
+ description: 'ID of the download to read (for action="read"). Defaults to most recent.'
43130
+ },
42310
43131
  url: {
42311
43132
  type: "string",
42312
- description: "The URL of the PDF to fetch and extract text from"
43133
+ description: 'URL of the PDF to fetch (for action="pdf")'
42313
43134
  }
42314
43135
  },
42315
- required: ["url"]
43136
+ required: ["action"]
42316
43137
  }
42317
43138
  }
42318
43139
  ];
42319
43140
  }
42320
- var BROWSER_LIFECYCLE_TOOLS = ["browser_start", "browser_stop"];
42321
- function getBrowserToolDefinitionsWithLifecycle() {
43141
+ function getConsolidatedBrowserToolDefinitionsWithLifecycle() {
42322
43142
  return [
42323
43143
  {
42324
43144
  name: "browser_start",
@@ -42345,17 +43165,23 @@ function getBrowserToolDefinitionsWithLifecycle() {
42345
43165
  properties: {}
42346
43166
  }
42347
43167
  },
42348
- ...getBrowserToolDefinitions()
43168
+ ...getConsolidatedBrowserToolDefinitions()
42349
43169
  ];
42350
43170
  }
42351
43171
 
42352
- // ../browser-core/src/tools/dispatcher.ts
42353
- async function dispatchBrowserTool(executor, toolName, args) {
43172
+ // ../browser-core/src/tools/consolidated-dispatcher.ts
43173
+ async function dispatchConsolidatedBrowserTool(executor, toolName, args) {
42354
43174
  switch (toolName) {
43175
+ // --- Navigation ---
42355
43176
  case "browser_navigate":
43177
+ if (args.url === "back") {
43178
+ return executor.navigateBack();
43179
+ }
42356
43180
  return executor.navigate(args.url);
43181
+ // Legacy alias
42357
43182
  case "browser_navigate_back":
42358
43183
  return executor.navigateBack();
43184
+ // --- Page Inspection (unchanged) ---
42359
43185
  case "browser_snapshot":
42360
43186
  return executor.snapshot({
42361
43187
  mode: args.mode,
@@ -42372,18 +43198,45 @@ async function dispatchBrowserTool(executor, toolName, args) {
42372
43198
  label: args.label,
42373
43199
  returnImage: args.returnImage
42374
43200
  });
43201
+ // --- Code Execution (unchanged) ---
42375
43202
  case "browser_evaluate":
42376
43203
  return executor.evaluate({
42377
43204
  expression: args.function,
42378
43205
  element: args.element,
42379
43206
  ref: args.ref
42380
43207
  });
42381
- case "browser_console_messages":
42382
- return executor.consoleMessages({
42383
- onlyErrors: args.onlyErrors
42384
- });
42385
- case "browser_network_requests":
42386
- return executor.networkRequests();
43208
+ // --- Consolidated: browser_action (click + hover + type) ---
43209
+ case "browser_action": {
43210
+ const action = args.action;
43211
+ switch (action) {
43212
+ case "click":
43213
+ return executor.click({
43214
+ ref: args.ref,
43215
+ x: args.x,
43216
+ y: args.y,
43217
+ element: args.element,
43218
+ doubleClick: args.doubleClick,
43219
+ button: args.button,
43220
+ modifiers: args.modifiers
43221
+ });
43222
+ case "hover":
43223
+ return executor.hover({
43224
+ ref: args.ref,
43225
+ element: args.element
43226
+ });
43227
+ case "type":
43228
+ return executor.type({
43229
+ ref: args.ref,
43230
+ text: args.text,
43231
+ element: args.element,
43232
+ submit: args.submit,
43233
+ delay: args.delay
43234
+ });
43235
+ default:
43236
+ throw new Error(`Unknown browser_action action: ${action}`);
43237
+ }
43238
+ }
43239
+ // Legacy aliases for browser_action
42387
43240
  case "browser_click":
42388
43241
  return executor.click({
42389
43242
  ref: args.ref,
@@ -42399,6 +43252,15 @@ async function dispatchBrowserTool(executor, toolName, args) {
42399
43252
  ref: args.ref,
42400
43253
  element: args.element
42401
43254
  });
43255
+ case "browser_type":
43256
+ return executor.type({
43257
+ ref: args.ref,
43258
+ text: args.text,
43259
+ element: args.element,
43260
+ submit: args.submit,
43261
+ delay: args.delay
43262
+ });
43263
+ // --- Kept separate (unchanged) ---
42402
43264
  case "browser_drag":
42403
43265
  return executor.drag({
42404
43266
  startRef: args.startRef,
@@ -42410,14 +43272,6 @@ async function dispatchBrowserTool(executor, toolName, args) {
42410
43272
  endX: args.endX,
42411
43273
  endY: args.endY
42412
43274
  });
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
43275
  case "browser_press_key":
42422
43276
  return executor.pressKey(args.key);
42423
43277
  case "browser_fill_form":
@@ -42441,15 +43295,6 @@ async function dispatchBrowserTool(executor, toolName, args) {
42441
43295
  x: args.x,
42442
43296
  y: args.y
42443
43297
  });
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
43298
  case "browser_wait_for":
42454
43299
  return executor.waitFor({
42455
43300
  timeSec: args.time,
@@ -42459,8 +43304,6 @@ async function dispatchBrowserTool(executor, toolName, args) {
42459
43304
  state: args.state,
42460
43305
  timeout: args.timeout
42461
43306
  });
42462
- case "browser_close":
42463
- return executor.close();
42464
43307
  case "browser_resize":
42465
43308
  return executor.resize(args.width, args.height);
42466
43309
  case "browser_tabs":
@@ -42468,12 +43311,64 @@ async function dispatchBrowserTool(executor, toolName, args) {
42468
43311
  action: args.action,
42469
43312
  index: args.index
42470
43313
  });
43314
+ // --- Consolidated: browser_dialog ---
43315
+ case "browser_dialog":
43316
+ return executor.handleDialog({
43317
+ action: args.action,
43318
+ promptText: args.promptText
43319
+ });
43320
+ // Legacy alias
43321
+ case "browser_handle_dialog":
43322
+ return executor.handleDialog({
43323
+ action: args.action,
43324
+ promptText: args.promptText
43325
+ });
43326
+ // --- Consolidated: browser_devtools ---
43327
+ case "browser_devtools": {
43328
+ const target = args.target;
43329
+ if (target === "console") {
43330
+ return executor.consoleMessages({
43331
+ onlyErrors: args.onlyErrors
43332
+ });
43333
+ }
43334
+ return executor.networkRequests();
43335
+ }
43336
+ // Legacy aliases
43337
+ case "browser_console_messages":
43338
+ return executor.consoleMessages({
43339
+ onlyErrors: args.onlyErrors
43340
+ });
43341
+ case "browser_network_requests":
43342
+ return executor.networkRequests();
43343
+ // --- Consolidated: browser_download ---
43344
+ case "browser_download": {
43345
+ const downloadAction = args.action;
43346
+ switch (downloadAction) {
43347
+ case "list":
43348
+ return executor.listDownloads();
43349
+ case "read":
43350
+ return executor.readDownload(args.downloadId);
43351
+ case "pdf":
43352
+ return executor.fetchPdfText(args.url);
43353
+ default:
43354
+ throw new Error(`Unknown browser_download action: ${downloadAction}`);
43355
+ }
43356
+ }
43357
+ // Legacy aliases
42471
43358
  case "browser_list_downloads":
42472
43359
  return executor.listDownloads();
42473
43360
  case "browser_read_download":
42474
43361
  return executor.readDownload(args.downloadId);
42475
43362
  case "browser_pdf_read":
42476
43363
  return executor.fetchPdfText(args.url);
43364
+ // Legacy: browser_close (dropped but kept for compat)
43365
+ case "browser_close":
43366
+ return executor.close();
43367
+ // Legacy: browser_dismiss_overlay (dropped but kept for compat)
43368
+ case "browser_dismiss_overlay":
43369
+ return executor.dismissOverlay({
43370
+ preferredStrategy: args.preferredStrategy
43371
+ });
42477
43372
  default:
42478
43373
  throw new Error(`Unknown browser tool: ${toolName}`);
42479
43374
  }
@@ -44506,7 +45401,9 @@ function formatMcpResponse(input) {
44506
45401
  for (const toast of input.capturedToasts) {
44507
45402
  const remaining = toast.snapshotsRemaining ?? 1;
44508
45403
  const placementHint = toast.placement ? ` [visible at (${toast.placement.x}, ${toast.placement.y}), ${toast.placement.width}\xD7${toast.placement.height}px]` : "";
44509
- lines.push(`- [${toast.role}] ${toast.text}${placementHint} (expires in ${remaining} snapshot${remaining !== 1 ? "s" : ""})`);
45404
+ lines.push(
45405
+ `- [${toast.role}] ${toast.text}${placementHint} (expires in ${remaining} snapshot${remaining !== 1 ? "s" : ""})`
45406
+ );
44510
45407
  }
44511
45408
  lines.push("");
44512
45409
  }
@@ -44516,7 +45413,7 @@ function formatMcpResponse(input) {
44516
45413
  if (completedNew.length > 0) {
44517
45414
  lines.push("### Downloads");
44518
45415
  for (const dl of completedNew) {
44519
- const size = dl.sizeBytes != null ? ` (${formatFileSize(dl.sizeBytes)})` : "";
45416
+ const size = dl.sizeBytes !== null && dl.sizeBytes !== void 0 ? ` (${formatFileSize(dl.sizeBytes)})` : "";
44520
45417
  lines.push(
44521
45418
  `- [${dl.id}] ${dl.suggestedFilename}${size} \u2014 use browser_read_download to view content`
44522
45419
  );
@@ -44730,7 +45627,9 @@ var PlaywrightClient = class _PlaywrightClient {
44730
45627
  this.screencastEncoderFactory = options?.screencastEncoderFactory ?? null;
44731
45628
  this.cursorOverlay = new CursorOverlay({ enabled: false });
44732
45629
  if (!options?.tmpBaseDir) {
44733
- this.logger.debug("[PlaywrightClient] No tmpBaseDir provided, using flat canary dir (not org-scoped)");
45630
+ this.logger.debug(
45631
+ "[PlaywrightClient] No tmpBaseDir provided, using flat canary dir (not org-scoped)"
45632
+ );
44734
45633
  }
44735
45634
  this.tmpBaseDir = options?.tmpBaseDir ?? getCanaryTmpDir();
44736
45635
  }
@@ -44750,10 +45649,7 @@ var PlaywrightClient = class _PlaywrightClient {
44750
45649
  return Promise.race([
44751
45650
  operation,
44752
45651
  new Promise(
44753
- (_, reject2) => setTimeout(
44754
- () => reject2(new Error(`${label} timed out after ${timeoutMs}ms`)),
44755
- timeoutMs
44756
- )
45652
+ (_, reject2) => setTimeout(() => reject2(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)
44757
45653
  )
44758
45654
  ]);
44759
45655
  }
@@ -45053,8 +45949,8 @@ var PlaywrightClient = class _PlaywrightClient {
45053
45949
  acceptDownloads: true
45054
45950
  };
45055
45951
  const mergedHeaders = {
45056
- ...this.lastConnectOptions?.extraHTTPHeaders ?? {},
45057
- ...options.extraHTTPHeaders ?? {}
45952
+ ...this.lastConnectOptions?.extraHTTPHeaders,
45953
+ ...options.extraHTTPHeaders
45058
45954
  };
45059
45955
  if (Object.keys(mergedHeaders).length > 0) {
45060
45956
  contextOptions.extraHTTPHeaders = mergedHeaders;
@@ -45329,13 +46225,10 @@ var PlaywrightClient = class _PlaywrightClient {
45329
46225
  const candidate = path4.join(cacheDir, dir, relativeBinaryPath);
45330
46226
  try {
45331
46227
  await fs4.access(candidate);
45332
- logger2.warn(
45333
- "[DirectPlaywright] Using fallback Chromium binary (version mismatch)",
45334
- {
45335
- expected: defaultPath,
45336
- using: candidate
45337
- }
45338
- );
46228
+ logger2.warn("[DirectPlaywright] Using fallback Chromium binary (version mismatch)", {
46229
+ expected: defaultPath,
46230
+ using: candidate
46231
+ });
45339
46232
  return candidate;
45340
46233
  } catch {
45341
46234
  continue;
@@ -45586,7 +46479,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45586
46479
  opts?.depth,
45587
46480
  opts?.filterToInteractive
45588
46481
  );
45589
- let snapshotText = this.formatMCPResponse("", snapshotYaml, void 0, title);
46482
+ const snapshotText = this.formatMCPResponse("", snapshotYaml, void 0, title);
45590
46483
  const DOM_COMPLEXITY_THRESHOLD = 3e4;
45591
46484
  const estimatedTokens = estimateTextTokens(snapshotText);
45592
46485
  const refCount = (snapshotYaml.match(/\[ref=/g) || []).length;
@@ -45762,7 +46655,9 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45762
46655
  try {
45763
46656
  await page.waitForLoadState("networkidle", { timeout: timeoutMs });
45764
46657
  } catch {
45765
- this.logger.debug("[DirectPlaywright] waitForNetworkIdle timed out, proceeding", { timeoutMs });
46658
+ this.logger.debug("[DirectPlaywright] waitForNetworkIdle timed out, proceeding", {
46659
+ timeoutMs
46660
+ });
45766
46661
  }
45767
46662
  }
45768
46663
  getPendingNetworkInfo() {
@@ -46149,17 +47044,42 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46149
47044
  const locator = await this.resolveRef(toRef);
46150
47045
  const box = await locator.boundingBox();
46151
47046
  if (box) {
47047
+ const viewportSize = this.getViewportSize();
46152
47048
  const elementCenterY = box.y + box.height / 2;
46153
- const viewportHeight = 1080;
46154
- const viewportCenterY = viewportHeight / 2;
47049
+ const viewportCenterY = viewportSize.height / 2;
46155
47050
  const scrollY = elementCenterY - viewportCenterY;
47051
+ const elementCenterX = box.x + box.width / 2;
47052
+ const viewportCenterX = viewportSize.width / 2;
47053
+ const boxRight = box.x + box.width;
47054
+ const needsHorizontalScroll = boxRight < 0 || box.x > viewportSize.width || elementCenterX < 0 || elementCenterX > viewportSize.width;
47055
+ const scrollX = needsHorizontalScroll ? elementCenterX - viewportCenterX : 0;
46156
47056
  this.logger.debug("[DirectPlaywright] Scrolling to center element", {
46157
47057
  toRef,
46158
47058
  elementCenterY,
46159
47059
  viewportCenterY,
46160
- scrollY
47060
+ scrollY,
47061
+ elementCenterX,
47062
+ scrollX,
47063
+ needsHorizontalScroll
46161
47064
  });
46162
- await page.evaluate(`window.scrollBy(0, ${scrollY})`);
47065
+ await page.evaluate(`window.scrollBy(${scrollX}, ${scrollY})`);
47066
+ if (needsHorizontalScroll) {
47067
+ await page.evaluate((ref) => {
47068
+ const el = document.querySelector(`[aria-ref="${ref}"]`);
47069
+ if (!el) return;
47070
+ let parent = el.parentElement;
47071
+ for (let i = 0; i < 10 && parent; i++) {
47072
+ if (parent.scrollWidth > parent.clientWidth) {
47073
+ const elRect = el.getBoundingClientRect();
47074
+ const parentRect = parent.getBoundingClientRect();
47075
+ const targetScrollLeft = parent.scrollLeft + (elRect.left - parentRect.left) - parentRect.width / 2 + elRect.width / 2;
47076
+ parent.scrollLeft = Math.max(0, targetScrollLeft);
47077
+ break;
47078
+ }
47079
+ parent = parent.parentElement;
47080
+ }
47081
+ }, toRef);
47082
+ }
46163
47083
  } else {
46164
47084
  this.logger.warn("[DirectPlaywright] No bounding box, using scrollIntoViewIfNeeded", {
46165
47085
  toRef
@@ -46603,19 +47523,22 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46603
47523
  async probeAtCoordinates(x2, y2, _opts) {
46604
47524
  try {
46605
47525
  const page = await this.getPage();
46606
- const elementInfo = await page.evaluate(({ px, py }) => {
46607
- const el = document.elementFromPoint(px, py);
46608
- if (!el) return null;
46609
- el.setAttribute("data-probe-target", "true");
46610
- return {
46611
- tagName: el.tagName.toLowerCase(),
46612
- id: el.id || void 0,
46613
- className: typeof el.className === "string" ? el.className || void 0 : void 0,
46614
- ariaLabel: el.getAttribute("aria-label") || void 0,
46615
- textContent: el.textContent?.trim().slice(0, 100) || void 0,
46616
- role: el.getAttribute("role") || void 0
46617
- };
46618
- }, { px: x2, py: y2 });
47526
+ const elementInfo = await page.evaluate(
47527
+ ({ px, py }) => {
47528
+ const el = document.elementFromPoint(px, py);
47529
+ if (!el) return null;
47530
+ el.setAttribute("data-probe-target", "true");
47531
+ return {
47532
+ tagName: el.tagName.toLowerCase(),
47533
+ id: el.id || void 0,
47534
+ className: typeof el.className === "string" ? el.className || void 0 : void 0,
47535
+ ariaLabel: el.getAttribute("aria-label") || void 0,
47536
+ textContent: el.textContent?.trim().slice(0, 100) || void 0,
47537
+ role: el.getAttribute("role") || void 0
47538
+ };
47539
+ },
47540
+ { px: x2, py: y2 }
47541
+ );
46619
47542
  if (!elementInfo) return null;
46620
47543
  let ref;
46621
47544
  let label;
@@ -46794,10 +47717,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46794
47717
  });
46795
47718
  await new Promise((resolve2) => setTimeout(resolve2, 200 * attempt));
46796
47719
  } else {
46797
- this.logger.warn("[DirectPlaywright] All snapshot attempts failed, using empty fallback", {
46798
- attempts: maxRetries,
46799
- error: err instanceof Error ? err.message : String(err)
46800
- });
47720
+ this.logger.warn(
47721
+ "[DirectPlaywright] All snapshot attempts failed, using empty fallback",
47722
+ {
47723
+ attempts: maxRetries,
47724
+ error: err instanceof Error ? err.message : String(err)
47725
+ }
47726
+ );
46801
47727
  }
46802
47728
  }
46803
47729
  }
@@ -46987,7 +47913,10 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46987
47913
  return;
46988
47914
  }
46989
47915
  if (!this.screencastManager) {
46990
- this.screencastManager = new CdpScreencastManager(options, this.screencastEncoderFactory ?? void 0);
47916
+ this.screencastManager = new CdpScreencastManager(
47917
+ options,
47918
+ this.screencastEncoderFactory ?? void 0
47919
+ );
46991
47920
  }
46992
47921
  this.screencastHandler = onFrame;
46993
47922
  await this.screencastManager.start(this.page, onFrame);
@@ -47543,7 +48472,11 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
47543
48472
  });
47544
48473
  const newLease = await this.browserLease.recover();
47545
48474
  this.browserLease = newLease;
47546
- const { storageStatePath: ssp, recordVideo: rv, extraHTTPHeaders: headers } = connectOptions;
48475
+ const {
48476
+ storageStatePath: ssp,
48477
+ recordVideo: rv,
48478
+ extraHTTPHeaders: headers
48479
+ } = connectOptions;
47547
48480
  const videoConfig2 = this.resolveVideoConfig(rv);
47548
48481
  this.videoEnabled = !!videoConfig2;
47549
48482
  this.videoDir = videoConfig2?.dir ?? null;
@@ -47814,7 +48747,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
47814
48747
  }
47815
48748
  const lines = [`${this._capturedDownloads.length} download(s):`];
47816
48749
  for (const dl of this._capturedDownloads) {
47817
- const size = dl.sizeBytes != null ? ` (${this.formatFileSize(dl.sizeBytes)})` : "";
48750
+ const size = dl.sizeBytes !== null && dl.sizeBytes !== void 0 ? ` (${this.formatFileSize(dl.sizeBytes)})` : "";
47818
48751
  let status;
47819
48752
  if (dl.completed) {
47820
48753
  status = "completed";
@@ -47831,7 +48764,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
47831
48764
  if (this._capturedDownloads.length === 0) {
47832
48765
  throw new Error("No downloads captured during this session.");
47833
48766
  }
47834
- const target = downloadId != null ? this._capturedDownloads.find((d) => d.id === downloadId) : this._capturedDownloads[this._capturedDownloads.length - 1];
48767
+ const target = downloadId !== null && downloadId !== void 0 ? this._capturedDownloads.find((d) => d.id === downloadId) : this._capturedDownloads[this._capturedDownloads.length - 1];
47835
48768
  if (!target) {
47836
48769
  throw new Error(
47837
48770
  `Download #${downloadId} not found. Use browser_list_downloads to see available downloads.`
@@ -48003,6 +48936,9 @@ export {
48003
48936
  BROWSER_LIFECYCLE_TOOLS,
48004
48937
  getBrowserToolDefinitionsWithLifecycle,
48005
48938
  dispatchBrowserTool,
48939
+ getConsolidatedBrowserToolDefinitions,
48940
+ getConsolidatedBrowserToolDefinitionsWithLifecycle,
48941
+ dispatchConsolidatedBrowserTool,
48006
48942
  extractSemanticHint,
48007
48943
  PlaywrightClient
48008
48944
  };
@@ -48285,4 +49221,4 @@ playwright-extra/dist/index.esm.js:
48285
49221
  * @license MIT
48286
49222
  *)
48287
49223
  */
48288
- //# sourceMappingURL=chunk-XGO62PO2.js.map
49224
+ //# sourceMappingURL=chunk-IFOJT3A5.js.map