auto-webmcp 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/analyzer.d.ts +2 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/auto-webmcp.cjs.js +330 -41
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +330 -41
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +2 -2
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +2 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/auto-webmcp.esm.js
CHANGED
|
@@ -91,6 +91,16 @@ function resolveConfig(userConfig) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// src/schema.ts
|
|
94
|
+
var ARIA_ROLES_TO_SCAN = [
|
|
95
|
+
"textbox",
|
|
96
|
+
"combobox",
|
|
97
|
+
"checkbox",
|
|
98
|
+
"radio",
|
|
99
|
+
"switch",
|
|
100
|
+
"spinbutton",
|
|
101
|
+
"searchbox",
|
|
102
|
+
"slider"
|
|
103
|
+
];
|
|
94
104
|
function inputTypeToSchema(input) {
|
|
95
105
|
if (input instanceof HTMLInputElement) {
|
|
96
106
|
return mapInputElement(input);
|
|
@@ -186,6 +196,49 @@ function collectRadioOneOf(form, name) {
|
|
|
186
196
|
return { const: r.value, title: title || r.value };
|
|
187
197
|
});
|
|
188
198
|
}
|
|
199
|
+
function ariaRoleToSchema(el, role) {
|
|
200
|
+
switch (role) {
|
|
201
|
+
case "checkbox":
|
|
202
|
+
case "switch":
|
|
203
|
+
return { type: "boolean" };
|
|
204
|
+
case "spinbutton":
|
|
205
|
+
case "slider": {
|
|
206
|
+
const prop = { type: "number" };
|
|
207
|
+
const min = el.getAttribute("aria-valuemin");
|
|
208
|
+
const max = el.getAttribute("aria-valuemax");
|
|
209
|
+
if (min !== null)
|
|
210
|
+
prop.minimum = parseFloat(min);
|
|
211
|
+
if (max !== null)
|
|
212
|
+
prop.maximum = parseFloat(max);
|
|
213
|
+
return prop;
|
|
214
|
+
}
|
|
215
|
+
case "combobox": {
|
|
216
|
+
const ownedId = el.getAttribute("aria-owns") ?? el.getAttribute("aria-controls");
|
|
217
|
+
if (ownedId) {
|
|
218
|
+
const listbox = document.getElementById(ownedId);
|
|
219
|
+
if (listbox) {
|
|
220
|
+
const options = Array.from(listbox.querySelectorAll('[role="option"]')).filter(
|
|
221
|
+
(o) => o.getAttribute("aria-disabled") !== "true"
|
|
222
|
+
);
|
|
223
|
+
if (options.length > 0) {
|
|
224
|
+
const enumValues = options.map((o) => (o.getAttribute("data-value") ?? o.textContent ?? "").trim()).filter(Boolean);
|
|
225
|
+
const oneOf = options.map((o) => ({
|
|
226
|
+
const: (o.getAttribute("data-value") ?? o.textContent ?? "").trim(),
|
|
227
|
+
title: (o.textContent ?? "").trim()
|
|
228
|
+
}));
|
|
229
|
+
return { type: "string", enum: enumValues, oneOf };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { type: "string" };
|
|
234
|
+
}
|
|
235
|
+
case "textbox":
|
|
236
|
+
case "searchbox":
|
|
237
|
+
case "radio":
|
|
238
|
+
default:
|
|
239
|
+
return { type: "string" };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
189
242
|
function getRadioLabelText(radio) {
|
|
190
243
|
const parent = radio.closest("label");
|
|
191
244
|
if (parent) {
|
|
@@ -211,8 +264,8 @@ var formIndex = 0;
|
|
|
211
264
|
function analyzeForm(form, override) {
|
|
212
265
|
const name = override?.name ?? inferToolName(form);
|
|
213
266
|
const description = override?.description ?? inferToolDescription(form);
|
|
214
|
-
const inputSchema = buildSchema(form);
|
|
215
|
-
return { name, description, inputSchema };
|
|
267
|
+
const { schema: inputSchema, fieldElements } = buildSchema(form);
|
|
268
|
+
return { name, description, inputSchema, fieldElements };
|
|
216
269
|
}
|
|
217
270
|
function inferToolName(form) {
|
|
218
271
|
const nativeName = form.getAttribute("toolname");
|
|
@@ -312,6 +365,7 @@ function inferToolDescription(form) {
|
|
|
312
365
|
function buildSchema(form) {
|
|
313
366
|
const properties = {};
|
|
314
367
|
const required = [];
|
|
368
|
+
const fieldElements = /* @__PURE__ */ new Map();
|
|
315
369
|
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
316
370
|
const controls = Array.from(
|
|
317
371
|
form.querySelectorAll(
|
|
@@ -320,12 +374,13 @@ function buildSchema(form) {
|
|
|
320
374
|
);
|
|
321
375
|
for (const control of controls) {
|
|
322
376
|
const name = control.name;
|
|
323
|
-
|
|
377
|
+
const fieldKey = name || resolveNativeControlFallbackKey(control);
|
|
378
|
+
if (!fieldKey)
|
|
324
379
|
continue;
|
|
325
380
|
if (control instanceof HTMLInputElement && control.type === "radio") {
|
|
326
|
-
if (processedRadioGroups.has(
|
|
381
|
+
if (processedRadioGroups.has(fieldKey))
|
|
327
382
|
continue;
|
|
328
|
-
processedRadioGroups.add(
|
|
383
|
+
processedRadioGroups.add(fieldKey);
|
|
329
384
|
}
|
|
330
385
|
const schemaProp = inputTypeToSchema(control);
|
|
331
386
|
if (!schemaProp)
|
|
@@ -335,17 +390,123 @@ function buildSchema(form) {
|
|
|
335
390
|
if (desc)
|
|
336
391
|
schemaProp.description = desc;
|
|
337
392
|
if (control instanceof HTMLInputElement && control.type === "radio") {
|
|
338
|
-
schemaProp.enum = collectRadioEnum(form,
|
|
339
|
-
const radioOneOf = collectRadioOneOf(form,
|
|
393
|
+
schemaProp.enum = collectRadioEnum(form, fieldKey);
|
|
394
|
+
const radioOneOf = collectRadioOneOf(form, fieldKey);
|
|
340
395
|
if (radioOneOf.length > 0)
|
|
341
396
|
schemaProp.oneOf = radioOneOf;
|
|
342
397
|
}
|
|
343
|
-
properties[
|
|
398
|
+
properties[fieldKey] = schemaProp;
|
|
399
|
+
if (!name) {
|
|
400
|
+
fieldElements.set(fieldKey, control);
|
|
401
|
+
}
|
|
344
402
|
if (control.required) {
|
|
345
|
-
required.push(
|
|
403
|
+
required.push(fieldKey);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const ariaControls = collectAriaControls(form);
|
|
407
|
+
const processedAriaRadioGroups = /* @__PURE__ */ new Set();
|
|
408
|
+
for (const { el, role, key } of ariaControls) {
|
|
409
|
+
if (properties[key])
|
|
410
|
+
continue;
|
|
411
|
+
if (role === "radio") {
|
|
412
|
+
if (processedAriaRadioGroups.has(key))
|
|
413
|
+
continue;
|
|
414
|
+
processedAriaRadioGroups.add(key);
|
|
415
|
+
}
|
|
416
|
+
const schemaProp = ariaRoleToSchema(el, role);
|
|
417
|
+
schemaProp.title = inferAriaFieldTitle(el);
|
|
418
|
+
const desc = inferAriaFieldDescription(el);
|
|
419
|
+
if (desc)
|
|
420
|
+
schemaProp.description = desc;
|
|
421
|
+
properties[key] = schemaProp;
|
|
422
|
+
fieldElements.set(key, el);
|
|
423
|
+
if (el.getAttribute("aria-required") === "true") {
|
|
424
|
+
required.push(key);
|
|
346
425
|
}
|
|
347
426
|
}
|
|
348
|
-
return { type: "object", properties, required };
|
|
427
|
+
return { schema: { type: "object", properties, required }, fieldElements };
|
|
428
|
+
}
|
|
429
|
+
function resolveNativeControlFallbackKey(control) {
|
|
430
|
+
const el = control;
|
|
431
|
+
if (el.dataset["webmcpName"])
|
|
432
|
+
return sanitizeName(el.dataset["webmcpName"]);
|
|
433
|
+
if (control.id)
|
|
434
|
+
return sanitizeName(control.id);
|
|
435
|
+
const label = control.getAttribute("aria-label");
|
|
436
|
+
if (label)
|
|
437
|
+
return sanitizeName(label);
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
function collectAriaControls(form) {
|
|
441
|
+
const selector = ARIA_ROLES_TO_SCAN.map((r) => `[role="${r}"]`).join(", ");
|
|
442
|
+
const results = [];
|
|
443
|
+
for (const el of Array.from(form.querySelectorAll(selector))) {
|
|
444
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)
|
|
445
|
+
continue;
|
|
446
|
+
if (el.getAttribute("aria-hidden") === "true" || el.hidden)
|
|
447
|
+
continue;
|
|
448
|
+
const role = el.getAttribute("role");
|
|
449
|
+
const key = resolveAriaFieldKey(el);
|
|
450
|
+
if (!key)
|
|
451
|
+
continue;
|
|
452
|
+
results.push({ el, role, key });
|
|
453
|
+
}
|
|
454
|
+
return results;
|
|
455
|
+
}
|
|
456
|
+
function resolveAriaFieldKey(el) {
|
|
457
|
+
const htmlEl = el;
|
|
458
|
+
if (htmlEl.dataset?.["webmcpName"])
|
|
459
|
+
return sanitizeName(htmlEl.dataset["webmcpName"]);
|
|
460
|
+
if (el.id)
|
|
461
|
+
return sanitizeName(el.id);
|
|
462
|
+
const label = el.getAttribute("aria-label");
|
|
463
|
+
if (label)
|
|
464
|
+
return sanitizeName(label);
|
|
465
|
+
const labelledById = el.getAttribute("aria-labelledby");
|
|
466
|
+
if (labelledById) {
|
|
467
|
+
const text = document.getElementById(labelledById)?.textContent?.trim();
|
|
468
|
+
if (text)
|
|
469
|
+
return sanitizeName(text);
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function inferAriaFieldTitle(el) {
|
|
474
|
+
const htmlEl = el;
|
|
475
|
+
if (htmlEl.dataset?.["webmcpTitle"])
|
|
476
|
+
return htmlEl.dataset["webmcpTitle"];
|
|
477
|
+
const label = el.getAttribute("aria-label");
|
|
478
|
+
if (label)
|
|
479
|
+
return label.trim();
|
|
480
|
+
const labelledById = el.getAttribute("aria-labelledby");
|
|
481
|
+
if (labelledById) {
|
|
482
|
+
const text = document.getElementById(labelledById)?.textContent?.trim();
|
|
483
|
+
if (text)
|
|
484
|
+
return text;
|
|
485
|
+
}
|
|
486
|
+
if (el.id)
|
|
487
|
+
return humanizeName(el.id);
|
|
488
|
+
return "";
|
|
489
|
+
}
|
|
490
|
+
function inferAriaFieldDescription(el) {
|
|
491
|
+
const nativeParamDesc = el.getAttribute("toolparamdescription");
|
|
492
|
+
if (nativeParamDesc)
|
|
493
|
+
return nativeParamDesc.trim();
|
|
494
|
+
const htmlEl = el;
|
|
495
|
+
if (htmlEl.dataset?.["webmcpDescription"])
|
|
496
|
+
return htmlEl.dataset["webmcpDescription"];
|
|
497
|
+
const ariaDesc = el.getAttribute("aria-description");
|
|
498
|
+
if (ariaDesc)
|
|
499
|
+
return ariaDesc;
|
|
500
|
+
const describedById = el.getAttribute("aria-describedby");
|
|
501
|
+
if (describedById) {
|
|
502
|
+
const text = document.getElementById(describedById)?.textContent?.trim();
|
|
503
|
+
if (text)
|
|
504
|
+
return text;
|
|
505
|
+
}
|
|
506
|
+
const placeholder = el.getAttribute("placeholder") ?? el.dataset?.["placeholder"];
|
|
507
|
+
if (placeholder)
|
|
508
|
+
return placeholder.trim();
|
|
509
|
+
return "";
|
|
349
510
|
}
|
|
350
511
|
function inferFieldTitle(control) {
|
|
351
512
|
if ("dataset" in control && control.dataset["webmcpTitle"]) {
|
|
@@ -414,7 +575,15 @@ init_registry();
|
|
|
414
575
|
|
|
415
576
|
// src/interceptor.ts
|
|
416
577
|
var pendingExecutions = /* @__PURE__ */ new WeakMap();
|
|
417
|
-
|
|
578
|
+
var lastParams = /* @__PURE__ */ new WeakMap();
|
|
579
|
+
var formFieldElements = /* @__PURE__ */ new WeakMap();
|
|
580
|
+
var _inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
581
|
+
var _textareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
582
|
+
var _checkedSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
|
|
583
|
+
function buildExecuteHandler(form, config, toolName, metadata) {
|
|
584
|
+
if (metadata?.fieldElements) {
|
|
585
|
+
formFieldElements.set(form, metadata.fieldElements);
|
|
586
|
+
}
|
|
418
587
|
attachSubmitInterceptor(form, toolName);
|
|
419
588
|
return async (params) => {
|
|
420
589
|
fillFormFields(form, params);
|
|
@@ -437,8 +606,8 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
437
606
|
return;
|
|
438
607
|
const { resolve } = pending;
|
|
439
608
|
pendingExecutions.delete(form);
|
|
440
|
-
const formData = serializeFormData(form);
|
|
441
|
-
const text = JSON.stringify(formData)
|
|
609
|
+
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
610
|
+
const text = `Form submitted. Fields: ${JSON.stringify(formData)}`;
|
|
442
611
|
const result = { content: [{ type: "text", text }] };
|
|
443
612
|
if (e.agentInvoked && typeof e.respondWith === "function") {
|
|
444
613
|
e.preventDefault();
|
|
@@ -450,52 +619,112 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
450
619
|
window.dispatchEvent(new CustomEvent("toolcancel", { detail: { toolName } }));
|
|
451
620
|
});
|
|
452
621
|
}
|
|
622
|
+
function setReactValue(el, v) {
|
|
623
|
+
el.focus();
|
|
624
|
+
el.select?.();
|
|
625
|
+
if (document.execCommand("insertText", false, v)) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const setter = el instanceof HTMLTextAreaElement ? _textareaValueSetter : _inputValueSetter;
|
|
629
|
+
if (setter) {
|
|
630
|
+
setter.call(el, v);
|
|
631
|
+
} else {
|
|
632
|
+
el.value = v;
|
|
633
|
+
}
|
|
634
|
+
el.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true, inputType: "insertText", data: v }));
|
|
635
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
636
|
+
}
|
|
637
|
+
function setReactChecked(el, checked) {
|
|
638
|
+
if (_checkedSetter) {
|
|
639
|
+
_checkedSetter.call(el, checked);
|
|
640
|
+
} else {
|
|
641
|
+
el.checked = checked;
|
|
642
|
+
}
|
|
643
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
644
|
+
}
|
|
645
|
+
function findNativeField(form, key) {
|
|
646
|
+
const esc = CSS.escape(key);
|
|
647
|
+
return form.querySelector(`[name="${esc}"]`) ?? form.querySelector(
|
|
648
|
+
`input#${esc}, textarea#${esc}, select#${esc}`
|
|
649
|
+
);
|
|
650
|
+
}
|
|
453
651
|
function fillFormFields(form, params) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
)
|
|
459
|
-
|
|
652
|
+
lastParams.set(form, params);
|
|
653
|
+
const fieldEls = formFieldElements.get(form);
|
|
654
|
+
for (const [key, value] of Object.entries(params)) {
|
|
655
|
+
const input = findNativeField(form, key);
|
|
656
|
+
if (input) {
|
|
657
|
+
if (input instanceof HTMLInputElement) {
|
|
658
|
+
fillInput(input, form, key, value);
|
|
659
|
+
} else if (input instanceof HTMLTextAreaElement) {
|
|
660
|
+
setReactValue(input, String(value ?? ""));
|
|
661
|
+
} else if (input instanceof HTMLSelectElement) {
|
|
662
|
+
input.value = String(value ?? "");
|
|
663
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
664
|
+
}
|
|
460
665
|
continue;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
666
|
+
}
|
|
667
|
+
const ariaEl = fieldEls?.get(key);
|
|
668
|
+
if (ariaEl) {
|
|
669
|
+
if (ariaEl instanceof HTMLInputElement) {
|
|
670
|
+
fillInput(ariaEl, form, key, value);
|
|
671
|
+
} else if (ariaEl instanceof HTMLTextAreaElement) {
|
|
672
|
+
setReactValue(ariaEl, String(value ?? ""));
|
|
673
|
+
} else if (ariaEl instanceof HTMLSelectElement) {
|
|
674
|
+
ariaEl.value = String(value ?? "");
|
|
675
|
+
ariaEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
676
|
+
} else {
|
|
677
|
+
fillAriaField(ariaEl, value);
|
|
678
|
+
}
|
|
470
679
|
}
|
|
471
680
|
}
|
|
472
681
|
}
|
|
473
|
-
function fillInput(input, form,
|
|
682
|
+
function fillInput(input, form, key, value) {
|
|
474
683
|
const type = input.type.toLowerCase();
|
|
475
684
|
if (type === "checkbox") {
|
|
476
|
-
input
|
|
477
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
685
|
+
setReactChecked(input, Boolean(value));
|
|
478
686
|
return;
|
|
479
687
|
}
|
|
480
688
|
if (type === "radio") {
|
|
481
|
-
const
|
|
689
|
+
const esc = CSS.escape(key);
|
|
482
690
|
const radios = form.querySelectorAll(
|
|
483
|
-
`input[type="radio"][name="${
|
|
691
|
+
`input[type="radio"][name="${esc}"]`
|
|
484
692
|
);
|
|
485
693
|
for (const radio of radios) {
|
|
486
694
|
if (radio.value === String(value)) {
|
|
487
|
-
|
|
695
|
+
if (_checkedSetter) {
|
|
696
|
+
_checkedSetter.call(radio, true);
|
|
697
|
+
} else {
|
|
698
|
+
radio.checked = true;
|
|
699
|
+
}
|
|
488
700
|
radio.dispatchEvent(new Event("change", { bubbles: true }));
|
|
489
701
|
break;
|
|
490
702
|
}
|
|
491
703
|
}
|
|
492
704
|
return;
|
|
493
705
|
}
|
|
494
|
-
input
|
|
495
|
-
|
|
496
|
-
|
|
706
|
+
setReactValue(input, String(value ?? ""));
|
|
707
|
+
}
|
|
708
|
+
function fillAriaField(el, value) {
|
|
709
|
+
const role = el.getAttribute("role");
|
|
710
|
+
if (role === "checkbox" || role === "switch") {
|
|
711
|
+
el.setAttribute("aria-checked", String(Boolean(value)));
|
|
712
|
+
el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (role === "radio") {
|
|
716
|
+
el.setAttribute("aria-checked", "true");
|
|
717
|
+
el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const htmlEl = el;
|
|
721
|
+
if (htmlEl.isContentEditable) {
|
|
722
|
+
htmlEl.textContent = String(value ?? "");
|
|
723
|
+
}
|
|
724
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
725
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
497
726
|
}
|
|
498
|
-
function serializeFormData(form) {
|
|
727
|
+
function serializeFormData(form, params, fieldEls) {
|
|
499
728
|
const result = {};
|
|
500
729
|
const data = new FormData(form);
|
|
501
730
|
for (const [key, val] of data.entries()) {
|
|
@@ -510,6 +739,27 @@ function serializeFormData(form) {
|
|
|
510
739
|
result[key] = val;
|
|
511
740
|
}
|
|
512
741
|
}
|
|
742
|
+
if (params) {
|
|
743
|
+
for (const key of Object.keys(params)) {
|
|
744
|
+
if (key in result)
|
|
745
|
+
continue;
|
|
746
|
+
const el = findNativeField(form, key) ?? fieldEls?.get(key) ?? null;
|
|
747
|
+
if (!el)
|
|
748
|
+
continue;
|
|
749
|
+
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
|
750
|
+
result[key] = el.checked;
|
|
751
|
+
} else if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
752
|
+
result[key] = el.value;
|
|
753
|
+
} else {
|
|
754
|
+
const role = el.getAttribute("role");
|
|
755
|
+
if (role === "checkbox" || role === "switch") {
|
|
756
|
+
result[key] = el.getAttribute("aria-checked") === "true";
|
|
757
|
+
} else {
|
|
758
|
+
result[key] = el.textContent?.trim() ?? "";
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
513
763
|
return result;
|
|
514
764
|
}
|
|
515
765
|
|
|
@@ -621,8 +871,9 @@ async function registerForm(form, config) {
|
|
|
621
871
|
if (config.debug) {
|
|
622
872
|
warnToolQuality(metadata.name, metadata.description);
|
|
623
873
|
}
|
|
624
|
-
const execute = buildExecuteHandler(form, config, metadata.name);
|
|
874
|
+
const execute = buildExecuteHandler(form, config, metadata.name, metadata);
|
|
625
875
|
await registerFormTool(form, metadata, execute);
|
|
876
|
+
registeredForms.add(form);
|
|
626
877
|
if (config.debug) {
|
|
627
878
|
console.debug(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
|
|
628
879
|
}
|
|
@@ -634,12 +885,43 @@ async function unregisterForm(form, config) {
|
|
|
634
885
|
if (!name)
|
|
635
886
|
return;
|
|
636
887
|
await unregisterFormTool(form);
|
|
888
|
+
registeredForms.delete(form);
|
|
637
889
|
if (config.debug) {
|
|
638
890
|
console.debug(`[auto-webmcp] Unregistered: ${name}`);
|
|
639
891
|
}
|
|
640
892
|
emit("form:unregistered", form, name);
|
|
641
893
|
}
|
|
642
894
|
var observer = null;
|
|
895
|
+
var registeredForms = /* @__PURE__ */ new WeakSet();
|
|
896
|
+
var reAnalysisTimers = /* @__PURE__ */ new Map();
|
|
897
|
+
var RE_ANALYSIS_DEBOUNCE_MS = 300;
|
|
898
|
+
function isInterestingNode(node) {
|
|
899
|
+
const tag = node.tagName.toLowerCase();
|
|
900
|
+
if (tag === "input" || tag === "textarea" || tag === "select")
|
|
901
|
+
return true;
|
|
902
|
+
const role = node.getAttribute("role");
|
|
903
|
+
if (role && ARIA_ROLES_TO_SCAN.includes(role))
|
|
904
|
+
return true;
|
|
905
|
+
if (node.querySelector("input, textarea, select"))
|
|
906
|
+
return true;
|
|
907
|
+
for (const r of ARIA_ROLES_TO_SCAN) {
|
|
908
|
+
if (node.querySelector(`[role="${r}"]`))
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
function scheduleReAnalysis(form, config) {
|
|
914
|
+
const existing = reAnalysisTimers.get(form);
|
|
915
|
+
if (existing)
|
|
916
|
+
clearTimeout(existing);
|
|
917
|
+
reAnalysisTimers.set(
|
|
918
|
+
form,
|
|
919
|
+
setTimeout(() => {
|
|
920
|
+
reAnalysisTimers.delete(form);
|
|
921
|
+
void registerForm(form, config);
|
|
922
|
+
}, RE_ANALYSIS_DEBOUNCE_MS)
|
|
923
|
+
);
|
|
924
|
+
}
|
|
643
925
|
function startObserver(config) {
|
|
644
926
|
if (observer)
|
|
645
927
|
return;
|
|
@@ -648,8 +930,15 @@ function startObserver(config) {
|
|
|
648
930
|
for (const node of mutation.addedNodes) {
|
|
649
931
|
if (!(node instanceof Element))
|
|
650
932
|
continue;
|
|
651
|
-
|
|
652
|
-
|
|
933
|
+
if (node instanceof HTMLFormElement) {
|
|
934
|
+
void registerForm(node, config);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
const parentForm = node.closest("form");
|
|
938
|
+
if (parentForm instanceof HTMLFormElement && registeredForms.has(parentForm) && isInterestingNode(node)) {
|
|
939
|
+
scheduleReAnalysis(parentForm, config);
|
|
940
|
+
}
|
|
941
|
+
for (const form of Array.from(node.querySelectorAll("form"))) {
|
|
653
942
|
void registerForm(form, config);
|
|
654
943
|
}
|
|
655
944
|
}
|