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.
@@ -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
- fillFormFields(form, resolvedParams);
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
- console.log(`[auto-webmcp] orphan: found ${orphanInputs.length} visible orphan input(s)`);
2104
- if (orphanInputs.length === 0)
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(