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.esm.js
CHANGED
|
@@ -14,6 +14,7 @@ function resolveConfig(userConfig) {
|
|
|
14
14
|
timeoutMs: Math.max(100, userConfig?.execution?.timeoutMs ?? 15e3)
|
|
15
15
|
},
|
|
16
16
|
overrides: userConfig?.overrides ?? {},
|
|
17
|
+
preserveExisting: userConfig?.preserveExisting ?? false,
|
|
17
18
|
debug: userConfig?.debug ?? false
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -973,6 +974,53 @@ function buildSchemaFromInputs(inputs) {
|
|
|
973
974
|
}
|
|
974
975
|
|
|
975
976
|
// src/registry.ts
|
|
977
|
+
var EXECUTE_OUTPUT_SCHEMA = {
|
|
978
|
+
type: "object",
|
|
979
|
+
properties: {
|
|
980
|
+
status: {
|
|
981
|
+
type: "string",
|
|
982
|
+
enum: ["success", "partial", "error", "awaiting_user_action", "timed_out", "blocked_invalid"],
|
|
983
|
+
description: "Outcome of the form execution."
|
|
984
|
+
},
|
|
985
|
+
filled_fields: {
|
|
986
|
+
type: "object",
|
|
987
|
+
description: "Field name to submitted value map."
|
|
988
|
+
},
|
|
989
|
+
skipped_fields: {
|
|
990
|
+
type: "array",
|
|
991
|
+
items: { type: "string" },
|
|
992
|
+
description: "Fields the agent provided but that could not be filled."
|
|
993
|
+
},
|
|
994
|
+
missing_required: {
|
|
995
|
+
type: "array",
|
|
996
|
+
items: { type: "string" },
|
|
997
|
+
description: "Required fields not supplied by the agent."
|
|
998
|
+
},
|
|
999
|
+
validation_errors: {
|
|
1000
|
+
type: "array",
|
|
1001
|
+
items: {
|
|
1002
|
+
type: "object",
|
|
1003
|
+
properties: {
|
|
1004
|
+
field: { type: "string" },
|
|
1005
|
+
constraint: { type: "string", description: "HTML ValidityState key that failed." },
|
|
1006
|
+
message: { type: "string" }
|
|
1007
|
+
},
|
|
1008
|
+
required: ["field", "constraint", "message"]
|
|
1009
|
+
},
|
|
1010
|
+
description: "Per-field HTML5 validation failures (present when status is blocked_invalid)."
|
|
1011
|
+
},
|
|
1012
|
+
existing_values: {
|
|
1013
|
+
type: "object",
|
|
1014
|
+
description: "Field values present in the form before the agent filled it."
|
|
1015
|
+
},
|
|
1016
|
+
warnings: {
|
|
1017
|
+
type: "array",
|
|
1018
|
+
items: { type: "object" },
|
|
1019
|
+
description: "Non-fatal fill warnings (alias_resolved, clamped, not_filled, etc.)."
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
required: ["status", "filled_fields", "skipped_fields", "missing_required", "warnings"]
|
|
1023
|
+
};
|
|
976
1024
|
var registeredTools = /* @__PURE__ */ new Map();
|
|
977
1025
|
var registrationControllers = /* @__PURE__ */ new Map();
|
|
978
1026
|
function isWebMCPSupported() {
|
|
@@ -989,6 +1037,7 @@ async function registerFormTool(form, metadata, execute) {
|
|
|
989
1037
|
name: metadata.name,
|
|
990
1038
|
description: metadata.description,
|
|
991
1039
|
inputSchema: metadata.inputSchema,
|
|
1040
|
+
outputSchema: EXECUTE_OUTPUT_SCHEMA,
|
|
992
1041
|
execute
|
|
993
1042
|
};
|
|
994
1043
|
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
@@ -1042,6 +1091,7 @@ var formFieldElements = /* @__PURE__ */ new WeakMap();
|
|
|
1042
1091
|
var pendingWarnings = /* @__PURE__ */ new WeakMap();
|
|
1043
1092
|
var pendingFillWarnings = /* @__PURE__ */ new WeakMap();
|
|
1044
1093
|
var lastFilledSnapshot = /* @__PURE__ */ new WeakMap();
|
|
1094
|
+
var preFillValues = /* @__PURE__ */ new WeakMap();
|
|
1045
1095
|
var _inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
1046
1096
|
var _textareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
1047
1097
|
var _checkedSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
|
|
@@ -1133,6 +1183,36 @@ function collectInvalidFieldWarnings(form) {
|
|
|
1133
1183
|
}
|
|
1134
1184
|
return warnings;
|
|
1135
1185
|
}
|
|
1186
|
+
function captureCurrentValues(form) {
|
|
1187
|
+
const result = {};
|
|
1188
|
+
try {
|
|
1189
|
+
const data = new FormData(form);
|
|
1190
|
+
for (const [key, val] of data.entries()) {
|
|
1191
|
+
if (result[key] !== void 0) {
|
|
1192
|
+
const existing = result[key];
|
|
1193
|
+
result[key] = Array.isArray(existing) ? [...existing, val] : [existing, val];
|
|
1194
|
+
} else {
|
|
1195
|
+
result[key] = val;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
} catch {
|
|
1199
|
+
}
|
|
1200
|
+
return result;
|
|
1201
|
+
}
|
|
1202
|
+
function collectValidationErrors(form) {
|
|
1203
|
+
const errors = [];
|
|
1204
|
+
for (const control of Array.from(form.elements)) {
|
|
1205
|
+
if (!(control instanceof HTMLInputElement) && !(control instanceof HTMLTextAreaElement) && !(control instanceof HTMLSelectElement))
|
|
1206
|
+
continue;
|
|
1207
|
+
if (!control.willValidate || control.checkValidity())
|
|
1208
|
+
continue;
|
|
1209
|
+
const field = control.name || control.id || control.getAttribute("aria-label") || "unknown_field";
|
|
1210
|
+
const v = control.validity;
|
|
1211
|
+
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";
|
|
1212
|
+
errors.push({ field, constraint, message: control.validationMessage || `field "${field}" failed validation` });
|
|
1213
|
+
}
|
|
1214
|
+
return errors;
|
|
1215
|
+
}
|
|
1136
1216
|
function buildExecuteHandler(form, config, toolName, metadata) {
|
|
1137
1217
|
if (metadata?.fieldElements) {
|
|
1138
1218
|
formFieldElements.set(form, metadata.fieldElements);
|
|
@@ -1154,6 +1234,8 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1154
1234
|
}
|
|
1155
1235
|
pendingFillWarnings.set(form, []);
|
|
1156
1236
|
pendingWarnings.delete(form);
|
|
1237
|
+
const existingSnapshot = captureCurrentValues(form);
|
|
1238
|
+
preFillValues.set(form, existingSnapshot);
|
|
1157
1239
|
const { resolved: resolvedParams, warnings: aliasWarnings } = resolveParamsForSchema(
|
|
1158
1240
|
form,
|
|
1159
1241
|
params,
|
|
@@ -1163,7 +1245,28 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1163
1245
|
if (aliasWarnings.length > 0) {
|
|
1164
1246
|
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...aliasWarnings]);
|
|
1165
1247
|
}
|
|
1166
|
-
|
|
1248
|
+
let paramsToFill = resolvedParams;
|
|
1249
|
+
if (config.preserveExisting) {
|
|
1250
|
+
const preserved = [];
|
|
1251
|
+
paramsToFill = Object.fromEntries(
|
|
1252
|
+
Object.entries(resolvedParams).filter(([key]) => {
|
|
1253
|
+
const current = existingSnapshot[key];
|
|
1254
|
+
const hasValue = current !== void 0 && current !== "" && current !== null;
|
|
1255
|
+
if (hasValue) {
|
|
1256
|
+
preserved.push({
|
|
1257
|
+
field: key,
|
|
1258
|
+
type: "not_filled",
|
|
1259
|
+
message: `field "${key}" already has a value and preserveExisting is enabled`
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
return !hasValue;
|
|
1263
|
+
})
|
|
1264
|
+
);
|
|
1265
|
+
if (preserved.length > 0) {
|
|
1266
|
+
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...preserved]);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
fillFormFields(form, paramsToFill);
|
|
1167
1270
|
const missingNow = getMissingRequired(metadata, resolvedParams);
|
|
1168
1271
|
if (missingNow.length > 0)
|
|
1169
1272
|
pendingWarnings.set(form, missingNow);
|
|
@@ -1181,16 +1284,19 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1181
1284
|
type: "timeout",
|
|
1182
1285
|
message: timedOutState === "timed_out" ? `tool execution timed out after ${timeoutMs}ms` : `waiting for user submit (timed out after ${timeoutMs}ms)`
|
|
1183
1286
|
};
|
|
1287
|
+
const _existingValsTimeout = preFillValues.get(form);
|
|
1184
1288
|
const structured = {
|
|
1185
1289
|
status: timedOutState,
|
|
1186
1290
|
filled_fields: serializeFormData(form, lastParams.get(form), formFieldElements.get(form)),
|
|
1187
1291
|
skipped_fields: [],
|
|
1188
1292
|
missing_required: pendingWarnings.get(form) ?? [],
|
|
1189
|
-
warnings: [...pendingFillWarnings.get(form) ?? [], warn]
|
|
1293
|
+
warnings: [...pendingFillWarnings.get(form) ?? [], warn],
|
|
1294
|
+
..._existingValsTimeout !== void 0 && { existing_values: _existingValsTimeout }
|
|
1190
1295
|
};
|
|
1191
1296
|
pendingWarnings.delete(form);
|
|
1192
1297
|
pendingFillWarnings.delete(form);
|
|
1193
1298
|
lastFilledSnapshot.delete(form);
|
|
1299
|
+
preFillValues.delete(form);
|
|
1194
1300
|
resolve({
|
|
1195
1301
|
content: [
|
|
1196
1302
|
{ type: "text", text: warn.message },
|
|
@@ -1241,17 +1347,21 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1241
1347
|
];
|
|
1242
1348
|
pendingFillWarnings.delete(submitForm);
|
|
1243
1349
|
pendingFillWarnings.delete(form);
|
|
1350
|
+
const _existingValsBlocked = preFillValues.get(form);
|
|
1244
1351
|
const structured = {
|
|
1245
1352
|
status: "blocked_invalid",
|
|
1246
1353
|
filled_fields: serializeFormData(submitForm, lastParams.get(submitForm) ?? lastParams.get(form), formFieldElements.get(submitForm) ?? formFieldElements.get(form)),
|
|
1247
1354
|
skipped_fields: [],
|
|
1248
1355
|
missing_required: pendingWarnings.get(submitForm) ?? pendingWarnings.get(form) ?? [],
|
|
1249
|
-
warnings
|
|
1356
|
+
warnings,
|
|
1357
|
+
validation_errors: collectValidationErrors(submitForm),
|
|
1358
|
+
..._existingValsBlocked !== void 0 && { existing_values: _existingValsBlocked }
|
|
1250
1359
|
};
|
|
1251
1360
|
pendingWarnings.delete(submitForm);
|
|
1252
1361
|
pendingWarnings.delete(form);
|
|
1253
1362
|
lastFilledSnapshot.delete(submitForm);
|
|
1254
1363
|
lastFilledSnapshot.delete(form);
|
|
1364
|
+
preFillValues.delete(form);
|
|
1255
1365
|
resolve({
|
|
1256
1366
|
content: [
|
|
1257
1367
|
{ type: "text", text: "Form submission blocked by native validation." },
|
|
@@ -1283,7 +1393,9 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1283
1393
|
clearTimeout(pending.timeoutId);
|
|
1284
1394
|
pendingExecutions.delete(form);
|
|
1285
1395
|
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
1396
|
+
const existingVals = preFillValues.get(form);
|
|
1286
1397
|
lastFilledSnapshot.delete(form);
|
|
1398
|
+
preFillValues.delete(form);
|
|
1287
1399
|
const missingRequired = pendingWarnings.get(form) ?? [];
|
|
1288
1400
|
pendingWarnings.delete(form);
|
|
1289
1401
|
const fillWarnings = pendingFillWarnings.get(form) ?? [];
|
|
@@ -1301,7 +1413,8 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1301
1413
|
message: `required field "${f}" was not provided`
|
|
1302
1414
|
})),
|
|
1303
1415
|
...fillWarnings
|
|
1304
|
-
]
|
|
1416
|
+
],
|
|
1417
|
+
...existingVals !== void 0 && { existing_values: existingVals }
|
|
1305
1418
|
};
|
|
1306
1419
|
const allWarnMessages = [
|
|
1307
1420
|
...missingRequired.length ? [`required fields were not filled: ${missingRequired.join(", ")}`] : [],
|
|
@@ -1323,6 +1436,7 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1323
1436
|
});
|
|
1324
1437
|
form.addEventListener("reset", () => {
|
|
1325
1438
|
lastFilledSnapshot.delete(form);
|
|
1439
|
+
preFillValues.delete(form);
|
|
1326
1440
|
window.dispatchEvent(new CustomEvent("toolcancel", { detail: { toolName } }));
|
|
1327
1441
|
});
|
|
1328
1442
|
}
|
|
@@ -1911,6 +2025,8 @@ var reAnalysisTimers = /* @__PURE__ */ new Map();
|
|
|
1911
2025
|
var RE_ANALYSIS_DEBOUNCE_MS = 300;
|
|
1912
2026
|
var orphanRescanTimer = null;
|
|
1913
2027
|
var ORPHAN_RESCAN_DEBOUNCE_MS = 500;
|
|
2028
|
+
var orphanRescanDelayedTimer = null;
|
|
2029
|
+
var ORPHAN_RESCAN_DELAYED_MS = 2e3;
|
|
1914
2030
|
var registeredOrphanToolNames = /* @__PURE__ */ new Set();
|
|
1915
2031
|
function scheduleOrphanRescan(config) {
|
|
1916
2032
|
if (orphanRescanTimer)
|
|
@@ -1920,10 +2036,20 @@ function scheduleOrphanRescan(config) {
|
|
|
1920
2036
|
void scanOrphanInputs(config);
|
|
1921
2037
|
}, ORPHAN_RESCAN_DEBOUNCE_MS);
|
|
1922
2038
|
}
|
|
2039
|
+
function scheduleOrphanRescanDelayed(config) {
|
|
2040
|
+
if (orphanRescanDelayedTimer)
|
|
2041
|
+
clearTimeout(orphanRescanDelayedTimer);
|
|
2042
|
+
orphanRescanDelayedTimer = setTimeout(() => {
|
|
2043
|
+
orphanRescanDelayedTimer = null;
|
|
2044
|
+
void scanOrphanInputs(config);
|
|
2045
|
+
}, ORPHAN_RESCAN_DELAYED_MS);
|
|
2046
|
+
}
|
|
1923
2047
|
function isInterestingNode(node) {
|
|
1924
2048
|
const tag = node.tagName.toLowerCase();
|
|
1925
2049
|
if (tag === "input" || tag === "textarea" || tag === "select")
|
|
1926
2050
|
return true;
|
|
2051
|
+
if (tag.includes("-"))
|
|
2052
|
+
return true;
|
|
1927
2053
|
const role = node.getAttribute("role");
|
|
1928
2054
|
if (role && ARIA_ROLES_TO_SCAN.includes(role))
|
|
1929
2055
|
return true;
|
|
@@ -2004,6 +2130,9 @@ function startObserver(config) {
|
|
|
2004
2130
|
}
|
|
2005
2131
|
if (isInterestingNode(node) && !node.closest("form")) {
|
|
2006
2132
|
scheduleOrphanRescan(config);
|
|
2133
|
+
if (node.tagName.toLowerCase().includes("-")) {
|
|
2134
|
+
scheduleOrphanRescanDelayed(config);
|
|
2135
|
+
}
|
|
2007
2136
|
}
|
|
2008
2137
|
}
|
|
2009
2138
|
for (const node of mutation.removedNodes) {
|
|
@@ -2052,6 +2181,35 @@ var ORPHAN_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
|
|
|
2052
2181
|
"button",
|
|
2053
2182
|
"image"
|
|
2054
2183
|
]);
|
|
2184
|
+
function collectShadowOrphanInputs(root, outerHost, visited = /* @__PURE__ */ new Set()) {
|
|
2185
|
+
if (visited.has(root))
|
|
2186
|
+
return [];
|
|
2187
|
+
visited.add(root);
|
|
2188
|
+
const results = [];
|
|
2189
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
2190
|
+
const sr = el.shadowRoot;
|
|
2191
|
+
if (!sr)
|
|
2192
|
+
continue;
|
|
2193
|
+
const host = outerHost ?? el;
|
|
2194
|
+
results.push(...collectShadowOrphanInputs(sr, host, visited));
|
|
2195
|
+
}
|
|
2196
|
+
if (root instanceof ShadowRoot) {
|
|
2197
|
+
const selector = 'input, textarea, select, [role="textbox"]:not(input):not(textarea), [role="searchbox"]:not(input):not(textarea), button[role="combobox"]';
|
|
2198
|
+
for (const el of Array.from(root.querySelectorAll(selector))) {
|
|
2199
|
+
if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase()))
|
|
2200
|
+
continue;
|
|
2201
|
+
if (el.closest("form"))
|
|
2202
|
+
continue;
|
|
2203
|
+
const rect = el.getBoundingClientRect();
|
|
2204
|
+
if (rect.width === 0 || rect.height === 0)
|
|
2205
|
+
continue;
|
|
2206
|
+
if (outerHost) {
|
|
2207
|
+
results.push({ el, shadowHost: outerHost });
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return results;
|
|
2212
|
+
}
|
|
2055
2213
|
async function scanOrphanInputs(config) {
|
|
2056
2214
|
if (!isWebMCPSupported())
|
|
2057
2215
|
return;
|
|
@@ -2074,8 +2232,9 @@ async function scanOrphanInputs(config) {
|
|
|
2074
2232
|
}
|
|
2075
2233
|
return true;
|
|
2076
2234
|
});
|
|
2077
|
-
|
|
2078
|
-
|
|
2235
|
+
const shadowOrphans = collectShadowOrphanInputs(document.body, null);
|
|
2236
|
+
console.log(`[auto-webmcp] orphan: found ${orphanInputs.length} light-DOM + ${shadowOrphans.length} shadow-DOM orphan inputs`);
|
|
2237
|
+
if (orphanInputs.length === 0 && shadowOrphans.length === 0)
|
|
2079
2238
|
return;
|
|
2080
2239
|
const groups = /* @__PURE__ */ new Map();
|
|
2081
2240
|
for (const input of orphanInputs) {
|
|
@@ -2096,6 +2255,24 @@ async function scanOrphanInputs(config) {
|
|
|
2096
2255
|
groups.set(foundContainer, []);
|
|
2097
2256
|
groups.get(foundContainer).push(input);
|
|
2098
2257
|
}
|
|
2258
|
+
for (const { el, shadowHost } of shadowOrphans) {
|
|
2259
|
+
let container = shadowHost.parentElement;
|
|
2260
|
+
let foundContainer = shadowHost.parentElement ?? document.body;
|
|
2261
|
+
while (container && container !== document.body) {
|
|
2262
|
+
const hasSubmitBtn = container.querySelector(SUBMIT_BTN_GROUPING_SELECTOR) !== null || Array.from(container.querySelectorAll("button")).some(
|
|
2263
|
+
(b) => SUBMIT_TEXT_RE.test(b.textContent ?? "")
|
|
2264
|
+
);
|
|
2265
|
+
if (hasSubmitBtn) {
|
|
2266
|
+
foundContainer = container;
|
|
2267
|
+
break;
|
|
2268
|
+
}
|
|
2269
|
+
container = container.parentElement;
|
|
2270
|
+
}
|
|
2271
|
+
console.log(`[auto-webmcp] orphan (shadow): input (id="${el.id}") via host <${shadowHost.tagName.toLowerCase()}> grouped into container`, foundContainer);
|
|
2272
|
+
if (!groups.has(foundContainer))
|
|
2273
|
+
groups.set(foundContainer, []);
|
|
2274
|
+
groups.get(foundContainer).push(el);
|
|
2275
|
+
}
|
|
2099
2276
|
console.log(`[auto-webmcp] orphan: ${groups.size} group(s) found`);
|
|
2100
2277
|
for (const [container, inputs] of groups) {
|
|
2101
2278
|
const allCandidates = Array.from(
|