@anubhav-choubey/web-for-agents 0.1.0 → 0.3.0

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/index.js CHANGED
@@ -1,13 +1,27 @@
1
1
  import {
2
- AGENT_GLOBAL_VERSION,
3
- isValidActionName
4
- } from "./chunk-LCVI2PRP.js";
2
+ EMPTY_OBJECT_SCHEMA,
3
+ agentFields,
4
+ hasOwn,
5
+ inDisabledFieldset,
6
+ isConcealed,
7
+ isDisabled,
8
+ schemaFromForm,
9
+ validateFormInput,
10
+ validateInput
11
+ } from "./chunk-OKCYZE6C.js";
12
+ import {
13
+ DESCRIPTION_MAX_CHARS,
14
+ DESCRIPTION_MIN_CHARS,
15
+ TITLE_MAX_CHARS
16
+ } from "./chunk-L3AYWCSX.js";
5
17
  import {
6
18
  compareStrings
7
19
  } from "./chunk-AAIYCJD3.js";
8
20
  import {
9
- collapseWhitespace
10
- } from "./chunk-FEAKVNMG.js";
21
+ ACTION_NAME_RULE,
22
+ AGENT_GLOBAL_VERSION,
23
+ isValidActionName
24
+ } from "./chunk-4STTPCBR.js";
11
25
  import {
12
26
  ACTION_ATTR,
13
27
  ACTION_DESCRIPTION_ATTR,
@@ -17,15 +31,13 @@ import {
17
31
  CONTENT_ATTR,
18
32
  EXCLUDE_ATTR,
19
33
  HEAD_OWNED_ATTR,
20
- HIDDEN_SELECTOR,
21
34
  PRERENDERED_ATTR,
22
35
  READY_GLOBAL,
23
- WEBMCP_TOOLPARAMDESCRIPTION_ATTR,
24
36
  hasScheme,
25
37
  publicUrl,
26
38
  resolveUrl,
27
39
  routeOf
28
- } from "./chunk-AZG5MLNG.js";
40
+ } from "./chunk-2Y3LLYEC.js";
29
41
  import {
30
42
  errorMessage,
31
43
  withPrefix
@@ -185,6 +197,7 @@ function flush(doc) {
185
197
  else merged.set(tag.key, tag);
186
198
  }
187
199
  }
200
+ if (process.env.NODE_ENV !== "production") warnLengths(merged);
188
201
  for (const key of store.applied) if (!merged.has(key)) removeHeadTag(key, doc);
189
202
  applyHeadTags([...merged.values()], doc);
190
203
  store.applied = new Set(merged.keys());
@@ -240,6 +253,26 @@ function release() {
240
253
  }
241
254
  }
242
255
  var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
256
+ var warnedLengths = /* @__PURE__ */ new Set();
257
+ function warnLengths(merged) {
258
+ if (process.env.NODE_ENV !== "production") {
259
+ const t = merged.get("title");
260
+ const d = merged.get("name:description");
261
+ const title = t?.kind === "title" ? t.text : void 0;
262
+ const description = d?.kind === "meta" ? d.attrs.content : void 0;
263
+ const once = (value, message) => {
264
+ if (warnedLengths.has(message + value)) return;
265
+ warnedLengths.add(message + value);
266
+ console.warn(`[web-for-agents] ${message}: "${value}"`);
267
+ };
268
+ if (title && title.length > TITLE_MAX_CHARS) {
269
+ once(title, `<Seo> title is ${title.length} characters (over ${TITLE_MAX_CHARS})`);
270
+ }
271
+ if (description && (description.length < DESCRIPTION_MIN_CHARS || description.length > DESCRIPTION_MAX_CHARS)) {
272
+ once(description, `<Seo> description is ${description.length} characters (aim for ${DESCRIPTION_MIN_CHARS}-${DESCRIPTION_MAX_CHARS})`);
273
+ }
274
+ }
275
+ }
243
276
  var DefaultsContext = createContext({});
244
277
  function SeoProvider(props) {
245
278
  const { defaults, children } = props;
@@ -307,6 +340,23 @@ function node(type, fields) {
307
340
  for (const [k, v] of Object.entries(fields)) if (v !== void 0) out[k] = v;
308
341
  return out;
309
342
  }
343
+ function definedOnly(o) {
344
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
345
+ }
346
+ function nested(n) {
347
+ const { "@context": _context, ...rest } = n;
348
+ return rest;
349
+ }
350
+ function offer(o) {
351
+ if (o === void 0) return void 0;
352
+ return {
353
+ "@type": "Offer",
354
+ price: o.price,
355
+ priceCurrency: o.priceCurrency,
356
+ availability: o.availability && `https://schema.org/${o.availability}`,
357
+ url: o.url
358
+ };
359
+ }
310
360
  var schema = {
311
361
  article(i) {
312
362
  const { author, ...rest } = i;
@@ -320,13 +370,7 @@ var schema = {
320
370
  return node("Product", {
321
371
  ...rest,
322
372
  brand: brand === void 0 ? void 0 : { "@type": "Brand", name: brand },
323
- offers: offers === void 0 ? void 0 : {
324
- "@type": "Offer",
325
- price: offers.price,
326
- priceCurrency: offers.priceCurrency,
327
- availability: offers.availability && `https://schema.org/${offers.availability}`,
328
- url: offers.url
329
- }
373
+ offers: offer(offers)
330
374
  });
331
375
  },
332
376
  faqPage(items) {
@@ -361,6 +405,41 @@ var schema = {
361
405
  "query-input": "required name=search_term_string"
362
406
  } : void 0
363
407
  });
408
+ },
409
+ person(i) {
410
+ const { worksFor, ...rest } = i;
411
+ return node("Person", {
412
+ ...rest,
413
+ worksFor: worksFor === void 0 ? void 0 : typeof worksFor === "string" ? { "@type": "Organization", name: worksFor } : definedOnly({ "@type": "Organization", name: worksFor.name, url: worksFor.url })
414
+ });
415
+ },
416
+ softwareApplication(i) {
417
+ const { offers, aggregateRating, author, ...rest } = i;
418
+ return node("SoftwareApplication", {
419
+ ...rest,
420
+ offers: offer(offers),
421
+ aggregateRating: aggregateRating === void 0 ? void 0 : { "@type": "AggregateRating", ...definedOnly(aggregateRating) },
422
+ author: author === void 0 ? void 0 : person(author)
423
+ });
424
+ },
425
+ profilePage(i) {
426
+ return node("ProfilePage", { ...i, mainEntity: nested(i.mainEntity) });
427
+ },
428
+ creativeWork(i) {
429
+ const { author, keywords, ...rest } = i;
430
+ return node("CreativeWork", {
431
+ ...rest,
432
+ author: author === void 0 ? void 0 : person(author),
433
+ keywords: Array.isArray(keywords) ? keywords.join(", ") : keywords
434
+ });
435
+ },
436
+ webPage(i) {
437
+ const { breadcrumb, ...rest } = i;
438
+ return node("WebPage", { ...rest, breadcrumb: breadcrumb && nested(breadcrumb) });
439
+ },
440
+ /** One `@graph` document from several nodes; each node's own `@context` is dropped in favour of a single top-level one. */
441
+ graph(nodes) {
442
+ return { "@context": "https://schema.org", "@graph": nodes.map(nested) };
364
443
  }
365
444
  };
366
445
 
@@ -416,229 +495,9 @@ function hydrateApp(container, app, options) {
416
495
  // src/runtime/agent/react.tsx
417
496
  import { useCallback, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
418
497
 
419
- // src/shared/object.ts
420
- var hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
421
-
422
- // src/runtime/agent/dom.ts
423
- var INERT_OR_CLOSED = "[inert], dialog:not([open]), details:not([open]) > :not(summary)";
424
- var CONCEALED = `${HIDDEN_SELECTOR}, ${INERT_OR_CLOSED}`;
425
- function inDisabledFieldset(el) {
426
- for (let child = el, p = el.parentElement; p; child = p, p = p.parentElement) {
427
- if (p.matches("fieldset[disabled]") && child !== p.querySelector(":scope > legend")) return true;
428
- }
429
- return false;
430
- }
431
- function isDisabled(el) {
432
- return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true" || inDisabledFieldset(el);
433
- }
434
- function isConcealed(el) {
435
- if (!el.closest(CONCEALED)) return false;
436
- const labels = el.tagName === "INPUT" && /^(checkbox|radio)$/.test(el.type) ? el.labels : null;
437
- return !(labels && el.getAttribute("aria-hidden") !== "true" && !el.matches(INERT_OR_CLOSED) && !el.parentElement?.closest(CONCEALED) && Array.from(labels).some((l) => !isConcealed(l) && !isDisabled(l)));
438
- }
439
-
440
- // src/runtime/agent/formSchema.ts
441
- var EMPTY_OBJECT_SCHEMA = Object.freeze({
442
- type: "object",
443
- properties: Object.freeze({}),
444
- additionalProperties: false
445
- });
446
- var EXCLUDED_TYPES = /* @__PURE__ */ new Set(["hidden", "password", "file", "submit", "button", "reset", "image"]);
447
- function isAgentField(e) {
448
- const tag = e.tagName;
449
- if (tag !== "INPUT" && tag !== "SELECT" && tag !== "TEXTAREA") return false;
450
- if (!e.getAttribute("name")) return false;
451
- if (e.hasAttribute("disabled") || e.hasAttribute("readonly") || inDisabledFieldset(e)) return false;
452
- if (tag === "INPUT" && EXCLUDED_TYPES.has((e.getAttribute("type") ?? "text").toLowerCase())) return false;
453
- return !isConcealed(e);
454
- }
455
- function agentFields(form) {
456
- const groups = /* @__PURE__ */ new Map();
457
- for (const el of Array.from(form.elements)) {
458
- if (!isAgentField(el)) continue;
459
- const name = el.getAttribute("name");
460
- const list = groups.get(name);
461
- if (list) list.push(el);
462
- else groups.set(name, [el]);
463
- }
464
- return groups;
465
- }
466
- function labelText(el) {
467
- const labels = el.labels ? Array.from(el.labels) : [];
468
- if (labels.length === 0) {
469
- const wrap = el.closest("label");
470
- if (wrap) labels.push(wrap);
471
- }
472
- return collapseWhitespace(
473
- labels.map((l) => {
474
- const c = l.cloneNode(true);
475
- c.querySelectorAll("input,select,textarea,button").forEach((n) => n.remove());
476
- return c.textContent;
477
- }).join(" ")
478
- );
479
- }
480
- function describe(el) {
481
- return collapseWhitespace(el.getAttribute(ACTION_PARAM_ATTR)) || collapseWhitespace(el.getAttribute(WEBMCP_TOOLPARAMDESCRIPTION_ATTR)) || labelText(el) || collapseWhitespace(el.getAttribute("aria-label")) || collapseWhitespace(el.getAttribute("placeholder"));
482
- }
483
- function num(el, attr) {
484
- const v = el.getAttribute(attr);
485
- if (v === null || v.trim() === "") return void 0;
486
- const n = Number(v);
487
- return Number.isFinite(n) ? n : void 0;
488
- }
489
- function inputSchema(el) {
490
- const type = el instanceof HTMLTextAreaElement ? "textarea" : (el.getAttribute("type") ?? "text").toLowerCase();
491
- const s = {};
492
- if (type === "checkbox") {
493
- s.type = "boolean";
494
- return s;
495
- }
496
- if (type === "number" || type === "range") {
497
- const step = el.getAttribute("step");
498
- s.type = step === null || /^\d+$/.test(step.trim()) ? "integer" : "number";
499
- const min = num(el, "min");
500
- const max = num(el, "max");
501
- if (min !== void 0) s.minimum = min;
502
- if (max !== void 0) s.maximum = max;
503
- return s;
504
- }
505
- s.type = "string";
506
- if (type === "email") s.format = "email";
507
- else if (type === "url") s.format = "uri";
508
- else if (type === "date") s.format = "date";
509
- else if (type === "time") s.format = "time";
510
- const minLength = num(el, "minlength");
511
- const maxLength = num(el, "maxlength");
512
- if (minLength !== void 0) s.minLength = minLength;
513
- if (maxLength !== void 0) s.maxLength = maxLength;
514
- const pattern = el.getAttribute("pattern");
515
- if (pattern) s.pattern = `^(?:${pattern})$`;
516
- return s;
517
- }
518
- function withDescription(s, desc) {
519
- if (desc) s.description = desc;
520
- return s;
521
- }
522
- function selectSchema(sel, desc) {
523
- const opts = Array.from(sel.querySelectorAll("option")).filter((o) => !o.hasAttribute("disabled") && !o.hasAttribute("hidden") && !o.closest("optgroup[disabled]")).map((o) => ({ value: o.getAttribute("value") ?? collapseWhitespace(o.textContent), label: collapseWhitespace(o.textContent) })).filter((o) => o.value !== "");
524
- const values = opts.map((o) => o.value);
525
- const list = opts.map((o) => o.label && o.label !== o.value ? `${o.value} (${o.label})` : o.value).join(", ");
526
- const schema2 = sel.hasAttribute("multiple") ? { type: "array", items: { type: "string", enum: values } } : { type: "string", enum: values };
527
- return withDescription(schema2, [desc, `Options: ${list}`].filter(Boolean).join(". "));
528
- }
529
- var isType = (e, t) => e.tagName === "INPUT" && (e.getAttribute("type") ?? "").toLowerCase() === t;
530
- function groupSchema(els) {
531
- const first = els[0];
532
- const desc = els.map(describe).find(Boolean) ?? "";
533
- if (first.tagName === "SELECT") return selectSchema(first, desc);
534
- const values = () => els.map((e) => e.getAttribute("value") ?? "on");
535
- if (els.every((e) => isType(e, "radio"))) return withDescription({ type: "string", enum: values() }, desc);
536
- if (els.length > 1 && els.every((e) => isType(e, "checkbox"))) {
537
- return withDescription({ type: "array", items: { type: "string", enum: values() } }, desc);
538
- }
539
- return withDescription(inputSchema(first), desc);
540
- }
541
- function schemaFromForm(form) {
542
- const properties = /* @__PURE__ */ Object.create(null);
543
- const required = [];
544
- for (const [name, els] of agentFields(form)) {
545
- properties[name] = groupSchema(els);
546
- if (els.some((e) => e.hasAttribute("required"))) required.push(name);
547
- }
548
- const out = { type: "object", properties, additionalProperties: false };
549
- if (required.length) out.required = required;
550
- return out;
551
- }
552
- var MAX_REGEX_INPUT = 1e4;
553
- function checkValue(s, v, field, errors) {
554
- const bad = (message) => {
555
- errors.push({ field, message });
556
- };
557
- switch (s.type) {
558
- case "string":
559
- if (typeof v !== "string") return bad("must be string");
560
- break;
561
- case "number":
562
- if (typeof v !== "number" || !Number.isFinite(v)) return bad("must be number");
563
- break;
564
- case "integer":
565
- if (typeof v !== "number" || !Number.isInteger(v)) return bad("must be integer");
566
- break;
567
- case "boolean":
568
- if (typeof v !== "boolean") return bad("must be boolean");
569
- break;
570
- case "array":
571
- if (!Array.isArray(v)) return bad("must be array");
572
- break;
573
- case "object":
574
- if (!isPlainObject(v)) return bad("must be object");
575
- checkObject(s, v, `${field}.`, errors);
576
- return;
577
- }
578
- if (s.enum && !s.enum.includes(v)) {
579
- return bad(`must be one of: ${s.enum.join(", ")}`);
580
- }
581
- if (typeof v === "string") {
582
- if (s.minLength !== void 0 && v.length < s.minLength) bad(`must be at least ${s.minLength} characters`);
583
- if (s.maxLength !== void 0 && v.length > s.maxLength) bad(`must be at most ${s.maxLength} characters`);
584
- if ((s.pattern !== void 0 || s.format === "email") && v.length > MAX_REGEX_INPUT) {
585
- return bad(`must be at most ${MAX_REGEX_INPUT} characters to be checked`);
586
- }
587
- if (s.pattern !== void 0) {
588
- let ok = false;
589
- try {
590
- ok = new RegExp(s.pattern).test(v);
591
- } catch {
592
- ok = false;
593
- }
594
- if (!ok) bad(`must match pattern ${s.pattern}`);
595
- }
596
- if (s.format === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) bad("must be a valid email");
597
- if (s.format === "uri") {
598
- try {
599
- new URL(v);
600
- } catch {
601
- bad("must be a valid URI");
602
- }
603
- }
604
- }
605
- if (typeof v === "number") {
606
- if (s.minimum !== void 0 && v < s.minimum) bad(`must be >= ${s.minimum}`);
607
- if (s.maximum !== void 0 && v > s.maximum) bad(`must be <= ${s.maximum}`);
608
- }
609
- if (Array.isArray(v) && s.items) {
610
- v.forEach((item, i) => checkValue(s.items, item, `${field}[${i}]`, errors));
611
- }
612
- }
613
- function isPlainObject(v) {
614
- if (typeof v !== "object" || v === null) return false;
615
- const proto = Object.getPrototypeOf(v);
616
- return proto === Object.prototype || proto === null;
617
- }
618
- function checkObject(schema2, obj, prefix, errors) {
619
- const props = schema2.properties ?? {};
620
- for (const name of schema2.required ?? []) {
621
- if (!hasOwn(obj, name) || obj[name] === void 0) errors.push({ field: prefix + name, message: "is required" });
622
- }
623
- for (const [key, value] of Object.entries(obj)) {
624
- const prop = props[key];
625
- if (!hasOwn(props, key) || !prop) {
626
- if (schema2.additionalProperties !== true) errors.push({ field: prefix + key, message: "is not an allowed field" });
627
- continue;
628
- }
629
- if (value === void 0) continue;
630
- checkValue(prop, value, prefix + key, errors);
631
- }
632
- }
633
- function validateInput(schema2, input) {
634
- if (!isPlainObject(input)) return { ok: false, errors: [{ field: "", message: "input must be an object" }] };
635
- const errors = [];
636
- checkObject(schema2, input, "", errors);
637
- return errors.length ? { ok: false, errors } : { ok: true, value: input };
638
- }
639
- function validateFormInput(schema2, input) {
640
- const r = validateInput({ ...schema2, additionalProperties: true }, input);
641
- return r.ok ? r : validateInput(schema2, input);
498
+ // src/shared/actionName.ts
499
+ function assertValidActionName(name) {
500
+ if (!isValidActionName(name)) throw new Error(withPrefix(`invalid action name "${name}": ${ACTION_NAME_RULE}`));
642
501
  }
643
502
 
644
503
  // src/runtime/agent/formAutomation.ts
@@ -794,11 +653,6 @@ var nextId = 1;
794
653
  var syncScheduled = false;
795
654
  var forceScheduled = false;
796
655
  var CONFIRM_BUDGET = 1e3;
797
- function assertValidActionName(name) {
798
- if (!isValidActionName(name)) {
799
- throw new Error(withPrefix(`invalid action name "${name}": use 1-128 chars of A-Z a-z 0-9 _ - .`));
800
- }
801
- }
802
656
  function installGlobal() {
803
657
  if (typeof window === "undefined") return;
804
658
  const w = window;
@@ -850,7 +704,7 @@ function schemaOf(r) {
850
704
  }
851
705
  return r.spec.inputSchema ?? EMPTY_OBJECT_SCHEMA;
852
706
  }
853
- function describe2(name, entry) {
707
+ function describe(name, entry) {
854
708
  const r = resolve(entry);
855
709
  if (r.kind === null) return null;
856
710
  const d = {
@@ -865,7 +719,7 @@ function describe2(name, entry) {
865
719
  return d;
866
720
  }
867
721
  function listAgentActions() {
868
- return Array.from(activeEntries(), ([name, [, entry]]) => describe2(name, entry)).filter((d) => d !== null).sort((a, b) => compareStrings(a.name, b.name));
722
+ return Array.from(activeEntries(), ([name, [, entry]]) => describe(name, entry)).filter((d) => d !== null).sort((a, b) => compareStrings(a.name, b.name));
869
723
  }
870
724
  var UNSAFE = /[\0-\t\v-\x1F\x7F-\x9F\xAD\u061C\u200B\u200E\u200F\u2028-\u202E\u2060\u2066-\u2069\uFEFF]/g;
871
725
  var UNIT = /\\u[\da-f]{4}|\\[^]|[^]/giu;
@@ -981,7 +835,7 @@ function warnUndescribable(name, err) {
981
835
  function buildTool(name, entry) {
982
836
  let d;
983
837
  try {
984
- d = describe2(name, entry);
838
+ d = describe(name, entry);
985
839
  } catch (err) {
986
840
  warnUndescribable(name, err);
987
841
  return null;
@@ -1113,6 +967,18 @@ function AgentForm(props) {
1113
967
  function agentParam(description) {
1114
968
  return { [ACTION_PARAM_ATTR]: description };
1115
969
  }
970
+
971
+ // src/runtime/agent/link.ts
972
+ function agentLinkProps(spec) {
973
+ assertValidActionName(spec.name);
974
+ const props = {
975
+ [ACTION_ATTR]: spec.name,
976
+ [ACTION_DESCRIPTION_ATTR]: spec.description,
977
+ "aria-description": spec.description
978
+ };
979
+ if (spec.destructive) props[ACTION_DESTRUCTIVE_ATTR] = "true";
980
+ return props;
981
+ }
1116
982
  export {
1117
983
  AgentForm,
1118
984
  CONTENT_ATTR,
@@ -1124,6 +990,7 @@ export {
1124
990
  READY_GLOBAL,
1125
991
  Seo,
1126
992
  SeoProvider,
993
+ agentLinkProps,
1127
994
  agentParam,
1128
995
  executeAgentAction,
1129
996
  holdPrerender,
package/dist/overlay.js CHANGED
@@ -1,29 +1,31 @@
1
1
  import {
2
2
  DEFAULT_CONTENT_SELECTOR,
3
3
  DEFAULT_SHORTCUT,
4
+ INVALID_NAME_REASON,
5
+ actionGroups,
4
6
  buildPageMarkdown,
7
+ firstPerName,
8
+ markupActions,
5
9
  matchesShortcut,
6
10
  parseShortcut,
7
11
  readPageMeta,
12
+ resolveLinkHref,
8
13
  selectContentRegion
9
- } from "./chunk-ZYMNO5AO.js";
14
+ } from "./chunk-T42S3WRV.js";
10
15
  import {
11
16
  compareStrings
12
17
  } from "./chunk-AAIYCJD3.js";
13
18
  import {
14
19
  jsonLdTypes
15
- } from "./chunk-LCLDQ3A3.js";
16
- import "./chunk-FEAKVNMG.js";
20
+ } from "./chunk-MIVGE5TM.js";
21
+ import {
22
+ isValidActionName
23
+ } from "./chunk-4STTPCBR.js";
17
24
  import {
18
- ACTION_ATTR,
19
- ACTION_DESCRIPTION_ATTR,
20
- ACTION_DESTRUCTIVE_ATTR,
21
25
  AGENT_GLOBAL,
22
- WEBMCP_TOOLDESCRIPTION_ATTR,
23
- WEBMCP_TOOLNAME_ATTR,
24
26
  routeOf,
25
27
  trimTrailingSlash
26
- } from "./chunk-AZG5MLNG.js";
28
+ } from "./chunk-2Y3LLYEC.js";
27
29
  import {
28
30
  errorMessage
29
31
  } from "./chunk-JJN6FNJD.js";
@@ -67,39 +69,15 @@ function registryActions(doc) {
67
69
  return [];
68
70
  }
69
71
  }
70
- function collectActions(doc) {
71
- const markup = [];
72
- doc.querySelectorAll(`[${ACTION_ATTR}]`).forEach((el2) => {
73
- const name = el2.getAttribute(ACTION_ATTR)?.trim();
74
- if (!name) return;
75
- markup.push({
76
- name,
77
- description: el2.getAttribute(ACTION_DESCRIPTION_ATTR) || null,
78
- kind: el2.tagName === "FORM" ? "form" : "click",
79
- destructive: el2.getAttribute(ACTION_DESTRUCTIVE_ATTR) === "true",
80
- source: "markup"
81
- });
72
+ function collectActions(doc, pageUrl) {
73
+ const markup = markupActions(doc).map(({ name, description, kind, destructive, source, href }) => {
74
+ const a = { name, description, kind, destructive, source };
75
+ if (!isValidActionName(name)) return { ...a, warning: `Not listed in agents.json: ${INVALID_NAME_REASON}` };
76
+ if (kind !== "link") return a;
77
+ const r = resolveLinkHref(href ?? "", pageUrl);
78
+ return r.ok ? { ...a, href: r.href } : { ...a, warning: `Not listed in agents.json: ${r.reason}` };
82
79
  });
83
- const declarative = [];
84
- doc.querySelectorAll(`form[${WEBMCP_TOOLNAME_ATTR}]`).forEach((el2) => {
85
- const name = el2.getAttribute(WEBMCP_TOOLNAME_ATTR)?.trim();
86
- if (!name) return;
87
- declarative.push({
88
- name,
89
- description: el2.getAttribute(WEBMCP_TOOLDESCRIPTION_ATTR) || null,
90
- kind: "form",
91
- destructive: false,
92
- source: "webmcp-declarative"
93
- });
94
- });
95
- const seen = /* @__PURE__ */ new Set();
96
- const out = [];
97
- for (const a of [...registryActions(doc), ...markup, ...declarative]) {
98
- if (seen.has(a.name)) continue;
99
- seen.add(a.name);
100
- out.push(a);
101
- }
102
- return out.sort((a, b) => compareStrings(a.name, b.name));
80
+ return firstPerName(registryActions(doc), ...actionGroups(markup)).sort((a, b) => compareStrings(a.name, b.name));
103
81
  }
104
82
  function collectPageReport(doc = document, opts = {}) {
105
83
  const url = doc.location?.href ?? "";
@@ -135,7 +113,7 @@ function collectPageReport(doc = document, opts = {}) {
135
113
  } catch {
136
114
  }
137
115
  try {
138
- actions = collectActions(doc);
116
+ actions = collectActions(doc, pageUrl);
139
117
  } catch {
140
118
  }
141
119
  return { url, meta, jsonLd, content, actions };
@@ -206,6 +184,8 @@ function renderPanel(root, report) {
206
184
  const line = [a.name, a.kind, a.source, a.destructive ? "destructive" : ""].filter(Boolean).join(" \xB7 ");
207
185
  actions.append(el(doc, "div", "action", el(doc, "strong", void 0, a.name), line.slice(a.name.length)));
208
186
  if (a.description) actions.append(el(doc, "div", "note", a.description));
187
+ if (a.href) actions.append(el(doc, "div", "note", a.href));
188
+ if (a.warning) actions.append(el(doc, "p", "warn", a.warning));
209
189
  }
210
190
  const refresh = el(doc, "button", "btn", "Refresh");
211
191
  refresh.setAttribute("type", "button");
@@ -1,4 +1,4 @@
1
- import "./chunk-5TNG4QMP.js";
1
+ import "./chunk-PGZH33B5.js";
2
2
  import {
3
3
  errorMessage
4
4
  } from "./chunk-JJN6FNJD.js";
@@ -196,13 +196,35 @@ function isSyncResources(v) {
196
196
  (r) => r !== null && typeof r === "object" && typeof r.body === "string" && typeof r.contentType === "string"
197
197
  );
198
198
  }
199
+ function parseChildOptions(json) {
200
+ const o = JSON.parse(json);
201
+ if (o === null || typeof o !== "object" || Array.isArray(o)) throw new Error("render options are not an object");
202
+ const v = o;
203
+ const bad = (field, want) => {
204
+ throw new Error(`render option ${field} must be ${want}, got ${JSON.stringify(v[field])}`);
205
+ };
206
+ const strings = (x) => Array.isArray(x) && x.every((s) => typeof s === "string");
207
+ if (!strings(v.localOrigins)) bad("localOrigins", "an array of strings");
208
+ if (v.allowOrigins !== void 0 && !strings(v.allowOrigins)) bad("allowOrigins", "an array of strings");
209
+ if (v.idleMs !== void 0 && !(typeof v.idleMs === "number" && Number.isInteger(v.idleMs) && v.idleMs >= 0)) {
210
+ bad("idleMs", "a non-negative integer");
211
+ }
212
+ if (v.readySelector !== void 0 && !(typeof v.readySelector === "string" && v.readySelector.trim() !== "")) {
213
+ bad("readySelector", "a non-empty string");
214
+ }
215
+ if (v.onTimeout !== void 0 && v.onTimeout !== "fail" && v.onTimeout !== "snapshot") {
216
+ bad("onTimeout", '"fail" or "snapshot"');
217
+ }
218
+ if (v.syncResources !== void 0 && !isSyncResources(v.syncResources)) bad("syncResources", "a resources object");
219
+ return o;
220
+ }
199
221
  async function runRenderChild() {
200
222
  const send = (m) => process.send(m);
201
- const options = JSON.parse(process.env[CHILD_OPTIONS_ENV] ?? "{}");
223
+ const options = parseChildOptions(process.env[CHILD_OPTIONS_ENV] ?? "{}");
202
224
  const allow = createAllowList();
203
225
  for (const o of [...options.localOrigins ?? [], ...options.allowOrigins ?? []]) allowOrigin(allow, o);
204
226
  containNetwork(allow);
205
- const { createHappyDomRenderer } = await import("./happyDom-XVDHYJDM.js");
227
+ const { createHappyDomRenderer } = await import("./happyDom-ZEFNMD7B.js");
206
228
  const renderer = createHappyDomRenderer(options);
207
229
  process.on("message", (m) => {
208
230
  if (m.type === "resources") {
@@ -241,5 +263,6 @@ export {
241
263
  CHILD_ENTRY,
242
264
  CHILD_ENV,
243
265
  CHILD_OPTIONS_ENV,
244
- isSyncResources
266
+ isSyncResources,
267
+ parseChildOptions
245
268
  };