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