auto-webmcp 0.3.17 → 0.3.19
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/dist/auto-webmcp.cjs.js +183 -6
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +183 -6
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +1 -1
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +10 -0
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/auto-webmcp.cjs.js
CHANGED
|
@@ -40,6 +40,7 @@ function resolveConfig(userConfig) {
|
|
|
40
40
|
timeoutMs: Math.max(100, userConfig?.execution?.timeoutMs ?? 15e3)
|
|
41
41
|
},
|
|
42
42
|
overrides: userConfig?.overrides ?? {},
|
|
43
|
+
preserveExisting: userConfig?.preserveExisting ?? false,
|
|
43
44
|
debug: userConfig?.debug ?? false
|
|
44
45
|
};
|
|
45
46
|
}
|
|
@@ -999,6 +1000,53 @@ function buildSchemaFromInputs(inputs) {
|
|
|
999
1000
|
}
|
|
1000
1001
|
|
|
1001
1002
|
// src/registry.ts
|
|
1003
|
+
var EXECUTE_OUTPUT_SCHEMA = {
|
|
1004
|
+
type: "object",
|
|
1005
|
+
properties: {
|
|
1006
|
+
status: {
|
|
1007
|
+
type: "string",
|
|
1008
|
+
enum: ["success", "partial", "error", "awaiting_user_action", "timed_out", "blocked_invalid"],
|
|
1009
|
+
description: "Outcome of the form execution."
|
|
1010
|
+
},
|
|
1011
|
+
filled_fields: {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
description: "Field name to submitted value map."
|
|
1014
|
+
},
|
|
1015
|
+
skipped_fields: {
|
|
1016
|
+
type: "array",
|
|
1017
|
+
items: { type: "string" },
|
|
1018
|
+
description: "Fields the agent provided but that could not be filled."
|
|
1019
|
+
},
|
|
1020
|
+
missing_required: {
|
|
1021
|
+
type: "array",
|
|
1022
|
+
items: { type: "string" },
|
|
1023
|
+
description: "Required fields not supplied by the agent."
|
|
1024
|
+
},
|
|
1025
|
+
validation_errors: {
|
|
1026
|
+
type: "array",
|
|
1027
|
+
items: {
|
|
1028
|
+
type: "object",
|
|
1029
|
+
properties: {
|
|
1030
|
+
field: { type: "string" },
|
|
1031
|
+
constraint: { type: "string", description: "HTML ValidityState key that failed." },
|
|
1032
|
+
message: { type: "string" }
|
|
1033
|
+
},
|
|
1034
|
+
required: ["field", "constraint", "message"]
|
|
1035
|
+
},
|
|
1036
|
+
description: "Per-field HTML5 validation failures (present when status is blocked_invalid)."
|
|
1037
|
+
},
|
|
1038
|
+
existing_values: {
|
|
1039
|
+
type: "object",
|
|
1040
|
+
description: "Field values present in the form before the agent filled it."
|
|
1041
|
+
},
|
|
1042
|
+
warnings: {
|
|
1043
|
+
type: "array",
|
|
1044
|
+
items: { type: "object" },
|
|
1045
|
+
description: "Non-fatal fill warnings (alias_resolved, clamped, not_filled, etc.)."
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
required: ["status", "filled_fields", "skipped_fields", "missing_required", "warnings"]
|
|
1049
|
+
};
|
|
1002
1050
|
var registeredTools = /* @__PURE__ */ new Map();
|
|
1003
1051
|
var registrationControllers = /* @__PURE__ */ new Map();
|
|
1004
1052
|
function isWebMCPSupported() {
|
|
@@ -1015,6 +1063,7 @@ async function registerFormTool(form, metadata, execute) {
|
|
|
1015
1063
|
name: metadata.name,
|
|
1016
1064
|
description: metadata.description,
|
|
1017
1065
|
inputSchema: metadata.inputSchema,
|
|
1066
|
+
outputSchema: EXECUTE_OUTPUT_SCHEMA,
|
|
1018
1067
|
execute
|
|
1019
1068
|
};
|
|
1020
1069
|
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
@@ -1068,6 +1117,7 @@ var formFieldElements = /* @__PURE__ */ new WeakMap();
|
|
|
1068
1117
|
var pendingWarnings = /* @__PURE__ */ new WeakMap();
|
|
1069
1118
|
var pendingFillWarnings = /* @__PURE__ */ new WeakMap();
|
|
1070
1119
|
var lastFilledSnapshot = /* @__PURE__ */ new WeakMap();
|
|
1120
|
+
var preFillValues = /* @__PURE__ */ new WeakMap();
|
|
1071
1121
|
var _inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
1072
1122
|
var _textareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
1073
1123
|
var _checkedSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
|
|
@@ -1159,6 +1209,36 @@ function collectInvalidFieldWarnings(form) {
|
|
|
1159
1209
|
}
|
|
1160
1210
|
return warnings;
|
|
1161
1211
|
}
|
|
1212
|
+
function captureCurrentValues(form) {
|
|
1213
|
+
const result = {};
|
|
1214
|
+
try {
|
|
1215
|
+
const data = new FormData(form);
|
|
1216
|
+
for (const [key, val] of data.entries()) {
|
|
1217
|
+
if (result[key] !== void 0) {
|
|
1218
|
+
const existing = result[key];
|
|
1219
|
+
result[key] = Array.isArray(existing) ? [...existing, val] : [existing, val];
|
|
1220
|
+
} else {
|
|
1221
|
+
result[key] = val;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
return result;
|
|
1227
|
+
}
|
|
1228
|
+
function collectValidationErrors(form) {
|
|
1229
|
+
const errors = [];
|
|
1230
|
+
for (const control of Array.from(form.elements)) {
|
|
1231
|
+
if (!(control instanceof HTMLInputElement) && !(control instanceof HTMLTextAreaElement) && !(control instanceof HTMLSelectElement))
|
|
1232
|
+
continue;
|
|
1233
|
+
if (!control.willValidate || control.checkValidity())
|
|
1234
|
+
continue;
|
|
1235
|
+
const field = control.name || control.id || control.getAttribute("aria-label") || "unknown_field";
|
|
1236
|
+
const v = control.validity;
|
|
1237
|
+
const constraint = v.valueMissing ? "valueMissing" : v.typeMismatch ? "typeMismatch" : v.patternMismatch ? "patternMismatch" : v.tooLong ? "tooLong" : v.tooShort ? "tooShort" : v.rangeUnderflow ? "rangeUnderflow" : v.rangeOverflow ? "rangeOverflow" : v.stepMismatch ? "stepMismatch" : v.customError ? "customError" : "badInput";
|
|
1238
|
+
errors.push({ field, constraint, message: control.validationMessage || `field "${field}" failed validation` });
|
|
1239
|
+
}
|
|
1240
|
+
return errors;
|
|
1241
|
+
}
|
|
1162
1242
|
function buildExecuteHandler(form, config, toolName, metadata) {
|
|
1163
1243
|
if (metadata?.fieldElements) {
|
|
1164
1244
|
formFieldElements.set(form, metadata.fieldElements);
|
|
@@ -1180,6 +1260,8 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1180
1260
|
}
|
|
1181
1261
|
pendingFillWarnings.set(form, []);
|
|
1182
1262
|
pendingWarnings.delete(form);
|
|
1263
|
+
const existingSnapshot = captureCurrentValues(form);
|
|
1264
|
+
preFillValues.set(form, existingSnapshot);
|
|
1183
1265
|
const { resolved: resolvedParams, warnings: aliasWarnings } = resolveParamsForSchema(
|
|
1184
1266
|
form,
|
|
1185
1267
|
params,
|
|
@@ -1189,7 +1271,28 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1189
1271
|
if (aliasWarnings.length > 0) {
|
|
1190
1272
|
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...aliasWarnings]);
|
|
1191
1273
|
}
|
|
1192
|
-
|
|
1274
|
+
let paramsToFill = resolvedParams;
|
|
1275
|
+
if (config.preserveExisting) {
|
|
1276
|
+
const preserved = [];
|
|
1277
|
+
paramsToFill = Object.fromEntries(
|
|
1278
|
+
Object.entries(resolvedParams).filter(([key]) => {
|
|
1279
|
+
const current = existingSnapshot[key];
|
|
1280
|
+
const hasValue = current !== void 0 && current !== "" && current !== null;
|
|
1281
|
+
if (hasValue) {
|
|
1282
|
+
preserved.push({
|
|
1283
|
+
field: key,
|
|
1284
|
+
type: "not_filled",
|
|
1285
|
+
message: `field "${key}" already has a value and preserveExisting is enabled`
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
return !hasValue;
|
|
1289
|
+
})
|
|
1290
|
+
);
|
|
1291
|
+
if (preserved.length > 0) {
|
|
1292
|
+
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...preserved]);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
fillFormFields(form, paramsToFill);
|
|
1193
1296
|
const missingNow = getMissingRequired(metadata, resolvedParams);
|
|
1194
1297
|
if (missingNow.length > 0)
|
|
1195
1298
|
pendingWarnings.set(form, missingNow);
|
|
@@ -1207,16 +1310,19 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1207
1310
|
type: "timeout",
|
|
1208
1311
|
message: timedOutState === "timed_out" ? `tool execution timed out after ${timeoutMs}ms` : `waiting for user submit (timed out after ${timeoutMs}ms)`
|
|
1209
1312
|
};
|
|
1313
|
+
const _existingValsTimeout = preFillValues.get(form);
|
|
1210
1314
|
const structured = {
|
|
1211
1315
|
status: timedOutState,
|
|
1212
1316
|
filled_fields: serializeFormData(form, lastParams.get(form), formFieldElements.get(form)),
|
|
1213
1317
|
skipped_fields: [],
|
|
1214
1318
|
missing_required: pendingWarnings.get(form) ?? [],
|
|
1215
|
-
warnings: [...pendingFillWarnings.get(form) ?? [], warn]
|
|
1319
|
+
warnings: [...pendingFillWarnings.get(form) ?? [], warn],
|
|
1320
|
+
..._existingValsTimeout !== void 0 && { existing_values: _existingValsTimeout }
|
|
1216
1321
|
};
|
|
1217
1322
|
pendingWarnings.delete(form);
|
|
1218
1323
|
pendingFillWarnings.delete(form);
|
|
1219
1324
|
lastFilledSnapshot.delete(form);
|
|
1325
|
+
preFillValues.delete(form);
|
|
1220
1326
|
resolve({
|
|
1221
1327
|
content: [
|
|
1222
1328
|
{ type: "text", text: warn.message },
|
|
@@ -1267,17 +1373,21 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1267
1373
|
];
|
|
1268
1374
|
pendingFillWarnings.delete(submitForm);
|
|
1269
1375
|
pendingFillWarnings.delete(form);
|
|
1376
|
+
const _existingValsBlocked = preFillValues.get(form);
|
|
1270
1377
|
const structured = {
|
|
1271
1378
|
status: "blocked_invalid",
|
|
1272
1379
|
filled_fields: serializeFormData(submitForm, lastParams.get(submitForm) ?? lastParams.get(form), formFieldElements.get(submitForm) ?? formFieldElements.get(form)),
|
|
1273
1380
|
skipped_fields: [],
|
|
1274
1381
|
missing_required: pendingWarnings.get(submitForm) ?? pendingWarnings.get(form) ?? [],
|
|
1275
|
-
warnings
|
|
1382
|
+
warnings,
|
|
1383
|
+
validation_errors: collectValidationErrors(submitForm),
|
|
1384
|
+
..._existingValsBlocked !== void 0 && { existing_values: _existingValsBlocked }
|
|
1276
1385
|
};
|
|
1277
1386
|
pendingWarnings.delete(submitForm);
|
|
1278
1387
|
pendingWarnings.delete(form);
|
|
1279
1388
|
lastFilledSnapshot.delete(submitForm);
|
|
1280
1389
|
lastFilledSnapshot.delete(form);
|
|
1390
|
+
preFillValues.delete(form);
|
|
1281
1391
|
resolve({
|
|
1282
1392
|
content: [
|
|
1283
1393
|
{ type: "text", text: "Form submission blocked by native validation." },
|
|
@@ -1309,7 +1419,9 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1309
1419
|
clearTimeout(pending.timeoutId);
|
|
1310
1420
|
pendingExecutions.delete(form);
|
|
1311
1421
|
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
1422
|
+
const existingVals = preFillValues.get(form);
|
|
1312
1423
|
lastFilledSnapshot.delete(form);
|
|
1424
|
+
preFillValues.delete(form);
|
|
1313
1425
|
const missingRequired = pendingWarnings.get(form) ?? [];
|
|
1314
1426
|
pendingWarnings.delete(form);
|
|
1315
1427
|
const fillWarnings = pendingFillWarnings.get(form) ?? [];
|
|
@@ -1327,7 +1439,8 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1327
1439
|
message: `required field "${f}" was not provided`
|
|
1328
1440
|
})),
|
|
1329
1441
|
...fillWarnings
|
|
1330
|
-
]
|
|
1442
|
+
],
|
|
1443
|
+
...existingVals !== void 0 && { existing_values: existingVals }
|
|
1331
1444
|
};
|
|
1332
1445
|
const allWarnMessages = [
|
|
1333
1446
|
...missingRequired.length ? [`required fields were not filled: ${missingRequired.join(", ")}`] : [],
|
|
@@ -1349,6 +1462,7 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1349
1462
|
});
|
|
1350
1463
|
form.addEventListener("reset", () => {
|
|
1351
1464
|
lastFilledSnapshot.delete(form);
|
|
1465
|
+
preFillValues.delete(form);
|
|
1352
1466
|
window.dispatchEvent(new CustomEvent("toolcancel", { detail: { toolName } }));
|
|
1353
1467
|
});
|
|
1354
1468
|
}
|
|
@@ -1937,6 +2051,8 @@ var reAnalysisTimers = /* @__PURE__ */ new Map();
|
|
|
1937
2051
|
var RE_ANALYSIS_DEBOUNCE_MS = 300;
|
|
1938
2052
|
var orphanRescanTimer = null;
|
|
1939
2053
|
var ORPHAN_RESCAN_DEBOUNCE_MS = 500;
|
|
2054
|
+
var orphanRescanDelayedTimer = null;
|
|
2055
|
+
var ORPHAN_RESCAN_DELAYED_MS = 2e3;
|
|
1940
2056
|
var registeredOrphanToolNames = /* @__PURE__ */ new Set();
|
|
1941
2057
|
function scheduleOrphanRescan(config) {
|
|
1942
2058
|
if (orphanRescanTimer)
|
|
@@ -1946,10 +2062,20 @@ function scheduleOrphanRescan(config) {
|
|
|
1946
2062
|
void scanOrphanInputs(config);
|
|
1947
2063
|
}, ORPHAN_RESCAN_DEBOUNCE_MS);
|
|
1948
2064
|
}
|
|
2065
|
+
function scheduleOrphanRescanDelayed(config) {
|
|
2066
|
+
if (orphanRescanDelayedTimer)
|
|
2067
|
+
clearTimeout(orphanRescanDelayedTimer);
|
|
2068
|
+
orphanRescanDelayedTimer = setTimeout(() => {
|
|
2069
|
+
orphanRescanDelayedTimer = null;
|
|
2070
|
+
void scanOrphanInputs(config);
|
|
2071
|
+
}, ORPHAN_RESCAN_DELAYED_MS);
|
|
2072
|
+
}
|
|
1949
2073
|
function isInterestingNode(node) {
|
|
1950
2074
|
const tag = node.tagName.toLowerCase();
|
|
1951
2075
|
if (tag === "input" || tag === "textarea" || tag === "select")
|
|
1952
2076
|
return true;
|
|
2077
|
+
if (tag.includes("-"))
|
|
2078
|
+
return true;
|
|
1953
2079
|
const role = node.getAttribute("role");
|
|
1954
2080
|
if (role && ARIA_ROLES_TO_SCAN.includes(role))
|
|
1955
2081
|
return true;
|
|
@@ -2030,6 +2156,9 @@ function startObserver(config) {
|
|
|
2030
2156
|
}
|
|
2031
2157
|
if (isInterestingNode(node) && !node.closest("form")) {
|
|
2032
2158
|
scheduleOrphanRescan(config);
|
|
2159
|
+
if (node.tagName.toLowerCase().includes("-")) {
|
|
2160
|
+
scheduleOrphanRescanDelayed(config);
|
|
2161
|
+
}
|
|
2033
2162
|
}
|
|
2034
2163
|
}
|
|
2035
2164
|
for (const node of mutation.removedNodes) {
|
|
@@ -2078,6 +2207,35 @@ var ORPHAN_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
|
|
|
2078
2207
|
"button",
|
|
2079
2208
|
"image"
|
|
2080
2209
|
]);
|
|
2210
|
+
function collectShadowOrphanInputs(root, outerHost, visited = /* @__PURE__ */ new Set()) {
|
|
2211
|
+
if (visited.has(root))
|
|
2212
|
+
return [];
|
|
2213
|
+
visited.add(root);
|
|
2214
|
+
const results = [];
|
|
2215
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
2216
|
+
const sr = el.shadowRoot;
|
|
2217
|
+
if (!sr)
|
|
2218
|
+
continue;
|
|
2219
|
+
const host = outerHost ?? el;
|
|
2220
|
+
results.push(...collectShadowOrphanInputs(sr, host, visited));
|
|
2221
|
+
}
|
|
2222
|
+
if (root instanceof ShadowRoot) {
|
|
2223
|
+
const selector = 'input, textarea, select, [role="textbox"]:not(input):not(textarea), [role="searchbox"]:not(input):not(textarea), button[role="combobox"]';
|
|
2224
|
+
for (const el of Array.from(root.querySelectorAll(selector))) {
|
|
2225
|
+
if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase()))
|
|
2226
|
+
continue;
|
|
2227
|
+
if (el.closest("form"))
|
|
2228
|
+
continue;
|
|
2229
|
+
const rect = el.getBoundingClientRect();
|
|
2230
|
+
if (rect.width === 0 || rect.height === 0)
|
|
2231
|
+
continue;
|
|
2232
|
+
if (outerHost) {
|
|
2233
|
+
results.push({ el, shadowHost: outerHost });
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
return results;
|
|
2238
|
+
}
|
|
2081
2239
|
async function scanOrphanInputs(config) {
|
|
2082
2240
|
if (!isWebMCPSupported())
|
|
2083
2241
|
return;
|
|
@@ -2100,8 +2258,9 @@ async function scanOrphanInputs(config) {
|
|
|
2100
2258
|
}
|
|
2101
2259
|
return true;
|
|
2102
2260
|
});
|
|
2103
|
-
|
|
2104
|
-
|
|
2261
|
+
const shadowOrphans = collectShadowOrphanInputs(document.body, null);
|
|
2262
|
+
console.log(`[auto-webmcp] orphan: found ${orphanInputs.length} light-DOM + ${shadowOrphans.length} shadow-DOM orphan inputs`);
|
|
2263
|
+
if (orphanInputs.length === 0 && shadowOrphans.length === 0)
|
|
2105
2264
|
return;
|
|
2106
2265
|
const groups = /* @__PURE__ */ new Map();
|
|
2107
2266
|
for (const input of orphanInputs) {
|
|
@@ -2122,6 +2281,24 @@ async function scanOrphanInputs(config) {
|
|
|
2122
2281
|
groups.set(foundContainer, []);
|
|
2123
2282
|
groups.get(foundContainer).push(input);
|
|
2124
2283
|
}
|
|
2284
|
+
for (const { el, shadowHost } of shadowOrphans) {
|
|
2285
|
+
let container = shadowHost.parentElement;
|
|
2286
|
+
let foundContainer = shadowHost.parentElement ?? document.body;
|
|
2287
|
+
while (container && container !== document.body) {
|
|
2288
|
+
const hasSubmitBtn = container.querySelector(SUBMIT_BTN_GROUPING_SELECTOR) !== null || Array.from(container.querySelectorAll("button")).some(
|
|
2289
|
+
(b) => SUBMIT_TEXT_RE.test(b.textContent ?? "")
|
|
2290
|
+
);
|
|
2291
|
+
if (hasSubmitBtn) {
|
|
2292
|
+
foundContainer = container;
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2295
|
+
container = container.parentElement;
|
|
2296
|
+
}
|
|
2297
|
+
console.log(`[auto-webmcp] orphan (shadow): input (id="${el.id}") via host <${shadowHost.tagName.toLowerCase()}> grouped into container`, foundContainer);
|
|
2298
|
+
if (!groups.has(foundContainer))
|
|
2299
|
+
groups.set(foundContainer, []);
|
|
2300
|
+
groups.get(foundContainer).push(el);
|
|
2301
|
+
}
|
|
2125
2302
|
console.log(`[auto-webmcp] orphan: ${groups.size} group(s) found`);
|
|
2126
2303
|
for (const [container, inputs] of groups) {
|
|
2127
2304
|
const allCandidates = Array.from(
|