auto-webmcp 0.3.8 → 0.3.10

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.
@@ -631,6 +631,19 @@ function resolveNativeControlFallbackKey(control) {
631
631
  }
632
632
  return null;
633
633
  }
634
+ function resolveAriaElementKey(el) {
635
+ if (el.dataset["webmcpName"])
636
+ return sanitizeName(el.dataset["webmcpName"]);
637
+ if (el.id && !AUTO_GENERATED_ID_RE.test(el.id))
638
+ return sanitizeName(el.id);
639
+ const label = el.getAttribute("aria-label");
640
+ if (label)
641
+ return sanitizeName(label);
642
+ const placeholder = el.getAttribute("placeholder");
643
+ if (placeholder)
644
+ return sanitizeName(placeholder);
645
+ return null;
646
+ }
634
647
  function collectAriaControls(form) {
635
648
  const selector = ARIA_ROLES_TO_SCAN.map((r) => `[role="${r}"]`).join(", ");
636
649
  const rawResults = [];
@@ -889,6 +902,22 @@ function buildSchemaFromInputs(inputs) {
889
902
  const processedRadioGroups = /* @__PURE__ */ new Set();
890
903
  const processedCheckboxGroups = /* @__PURE__ */ new Set();
891
904
  for (const control of inputs) {
905
+ if (!(control instanceof HTMLInputElement) && !(control instanceof HTMLTextAreaElement) && !(control instanceof HTMLSelectElement)) {
906
+ const fieldKey2 = resolveAriaElementKey(control);
907
+ if (!fieldKey2)
908
+ continue;
909
+ if (!isControlVisible(control))
910
+ continue;
911
+ const prop = { type: "string" };
912
+ prop.title = control.getAttribute("aria-label") ?? fieldKey2;
913
+ const desc2 = control.getAttribute("aria-description") ?? control.getAttribute("aria-describedby") ? null : null;
914
+ if (desc2)
915
+ prop.description = desc2;
916
+ properties[fieldKey2] = prop;
917
+ fieldElements.set(fieldKey2, control);
918
+ required.push(fieldKey2);
919
+ continue;
920
+ }
892
921
  const rawName = control.name;
893
922
  const fieldKey = (rawName ? sanitizeName(rawName) : null) || resolveNativeControlFallbackKey(control);
894
923
  if (!fieldKey)
@@ -1284,10 +1313,17 @@ function fillAriaField(el, value) {
1284
1313
  }
1285
1314
  const htmlEl = el;
1286
1315
  if (htmlEl.isContentEditable) {
1287
- htmlEl.textContent = String(value ?? "");
1316
+ htmlEl.focus();
1317
+ const range = document.createRange();
1318
+ range.selectNodeContents(htmlEl);
1319
+ const sel = window.getSelection();
1320
+ sel?.removeAllRanges();
1321
+ sel?.addRange(range);
1322
+ document.execCommand("insertText", false, String(value ?? ""));
1323
+ } else {
1324
+ el.dispatchEvent(new Event("input", { bubbles: true }));
1325
+ el.dispatchEvent(new Event("change", { bubbles: true }));
1288
1326
  }
1289
- el.dispatchEvent(new Event("input", { bubbles: true }));
1290
- el.dispatchEvent(new Event("change", { bubbles: true }));
1291
1327
  }
1292
1328
  function serializeFormData(form, params, fieldEls) {
1293
1329
  const result = {};
@@ -1440,6 +1476,11 @@ async function registerForm(form, config) {
1440
1476
  await registerFormTool(form, metadata, execute);
1441
1477
  registeredForms.add(form);
1442
1478
  registeredFormCount++;
1479
+ const formSubmitBtn = form.querySelector(
1480
+ '[type="submit"], button[data-variant="primary"], button:not([type])'
1481
+ ) ?? null;
1482
+ const pendingBtns = window["__pendingSubmitBtns"] ??= {};
1483
+ pendingBtns[metadata.name] = formSubmitBtn;
1443
1484
  if (config.debug) {
1444
1485
  console.log(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
1445
1486
  }
@@ -1553,12 +1594,12 @@ var ORPHAN_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
1553
1594
  async function scanOrphanInputs(config) {
1554
1595
  if (!isWebMCPSupported())
1555
1596
  return;
1556
- const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button:not([type]):not([disabled])';
1557
- const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"]';
1558
- const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search/i;
1597
+ const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button[data-variant="primary"]:not([disabled])';
1598
+ const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"], button[data-variant="primary"]';
1599
+ const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search|post|tweet|publish/i;
1559
1600
  const orphanInputs = Array.from(
1560
1601
  document.querySelectorAll(
1561
- "input:not(form input), textarea:not(form textarea), select:not(form select)"
1602
+ 'input:not(form input), textarea:not(form textarea), select:not(form select), [role="textbox"]:not(form [role="textbox"]):not(input):not(textarea), [role="searchbox"]:not(form [role="searchbox"]):not(input):not(textarea), [contenteditable="true"]:not(form [contenteditable="true"]):not(input):not(textarea)'
1562
1603
  )
1563
1604
  ).filter((el) => {
1564
1605
  if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase())) {
@@ -1603,11 +1644,25 @@ async function scanOrphanInputs(config) {
1603
1644
  return r.width > 0 && r.height > 0;
1604
1645
  });
1605
1646
  let submitBtn = allCandidates[allCandidates.length - 1] ?? null;
1606
- const disabledCandidates = Array.from(
1607
- container.querySelectorAll(SUBMIT_BTN_GROUPING_SELECTOR)
1608
- ).filter((b) => b.disabled);
1609
- if (!submitBtn && disabledCandidates.length > 0) {
1610
- console.log(`[auto-webmcp] orphan: no enabled submit button found in container \u2014 ${disabledCandidates.length} disabled button(s) present:`, disabledCandidates.map((b) => b.textContent?.trim()));
1647
+ if (!submitBtn) {
1648
+ const disabledCandidates = Array.from(
1649
+ container.querySelectorAll(SUBMIT_BTN_GROUPING_SELECTOR)
1650
+ ).filter((b) => {
1651
+ const r = b.getBoundingClientRect();
1652
+ return r.width > 0 && r.height > 0 && b.disabled;
1653
+ });
1654
+ submitBtn = disabledCandidates[disabledCandidates.length - 1] ?? null;
1655
+ if (submitBtn)
1656
+ console.log(`[auto-webmcp] orphan: using disabled submit button as reference: "${submitBtn.textContent?.trim()}"`);
1657
+ }
1658
+ if (!submitBtn) {
1659
+ const containerBtns = Array.from(container.querySelectorAll("button")).filter((b) => {
1660
+ const r = b.getBoundingClientRect();
1661
+ return r.width > 0 && r.height > 0 && !b.disabled && SUBMIT_TEXT_RE.test(b.textContent ?? "");
1662
+ });
1663
+ submitBtn = containerBtns[containerBtns.length - 1] ?? null;
1664
+ if (submitBtn)
1665
+ console.log(`[auto-webmcp] orphan: using text-matched button in container: "${submitBtn.textContent?.trim()}"`);
1611
1666
  }
1612
1667
  if (!submitBtn) {
1613
1668
  const pageBtns = Array.from(document.querySelectorAll("button")).filter(
@@ -1628,15 +1683,19 @@ async function scanOrphanInputs(config) {
1628
1683
  const AUTO_ID_RE = /^_r_[0-9a-z]+_$/i;
1629
1684
  for (const el of inputs) {
1630
1685
  const id = el.id && !AUTO_ID_RE.test(el.id) ? el.id : null;
1631
- const key = el.name || el.dataset["webmcpName"] || id || el.getAttribute("aria-label") || (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el.placeholder || null : null) || null;
1686
+ const key = el.name || el.getAttribute("name") || el.dataset["webmcpName"] || id || el.getAttribute("aria-label") || el.getAttribute("placeholder") || null;
1632
1687
  const safeKey = key ? key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) : null;
1633
1688
  const matched = !!(safeKey && schemaProps[safeKey]);
1634
- console.log(`[auto-webmcp] orphan: field (name="${el.name}" id="${el.id}") rawKey="${key}" safeKey="${safeKey}" matched=${matched}`);
1689
+ console.log(`[auto-webmcp] orphan: field (name="${el.name ?? ""}" id="${el.id}") rawKey="${key}" safeKey="${safeKey}" matched=${matched}`);
1635
1690
  if (matched) {
1636
1691
  inputPairs.push({ key: safeKey, el });
1637
1692
  }
1638
1693
  }
1639
1694
  console.log(`[auto-webmcp] orphan: ${inputPairs.length}/${inputs.length} input(s) mapped to schema keys`);
1695
+ if (inputPairs.length === 0) {
1696
+ console.log(`[auto-webmcp] orphan: skipping group "${metadata.name}" \u2014 no inputs mapped to schema keys`);
1697
+ continue;
1698
+ }
1640
1699
  const toolName = metadata.name;
1641
1700
  const execute = async (params) => {
1642
1701
  console.log(`[auto-webmcp] orphan execute: tool="${toolName}" params=`, params);
@@ -1651,7 +1710,8 @@ async function scanOrphanInputs(config) {
1651
1710
  }
1652
1711
  }
1653
1712
  window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
1654
- if (!config.autoSubmit) {
1713
+ const shouldAutoSubmit = config.autoSubmit || !!submitBtn?.hasAttribute("toolautosubmit") || submitBtn instanceof HTMLElement && submitBtn.dataset["webmcpAutosubmit"] !== void 0 || container.hasAttribute("toolautosubmit") || container instanceof HTMLElement && container.dataset["webmcpAutosubmit"] !== void 0;
1714
+ if (!shouldAutoSubmit) {
1655
1715
  console.log(`[auto-webmcp] orphan execute: autoSubmit=false, returning without clicking submit`);
1656
1716
  return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
1657
1717
  }
@@ -1691,6 +1751,8 @@ async function scanOrphanInputs(config) {
1691
1751
  toolDef.annotations = metadata.annotations;
1692
1752
  }
1693
1753
  await navigator.modelContext.registerTool(toolDef);
1754
+ const pendingBtns = window["__pendingSubmitBtns"] ??= {};
1755
+ pendingBtns[metadata.name] = submitBtn;
1694
1756
  if (config.debug) {
1695
1757
  console.log(`[auto-webmcp] Orphan tool registered: ${metadata.name}`, metadata);
1696
1758
  }