@crestapps/webmcp 2.0.0-preview.250 → 2.0.0-preview.258

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.
@@ -10,9 +10,15 @@
10
10
  var MAX_LINKS = 200;
11
11
  var MAX_FIELDS = 200;
12
12
  var MAX_VALUE_LENGTH = 2e3;
13
+ var MAX_CONTENT_LENGTH = 8e3;
14
+ var MAX_TABLES = 10;
15
+ var MAX_TABLE_ROWS = 50;
16
+ var MAX_CELL_LENGTH = 300;
13
17
  var selectors = {
14
18
  description: "meta[name=\"description\"]",
15
19
  headings: "h1, h2",
20
+ main: "main, [role=\"main\"]",
21
+ ignored: "script, style, noscript, template, svg, nav, aside, select, textarea, datalist, dialog:not([open]), [hidden], [aria-hidden=\"true\"], [role=\"navigation\"], [data-webmcp-ignore], [class*=\"ai-chat-widget\"]",
16
22
  links: "nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]",
17
23
  fields: "form input, form select, form textarea"
18
24
  };
@@ -29,6 +35,112 @@
29
35
  function cleanText(value) {
30
36
  return (value == null ? "" : String(value)).replace(/\s+/g, " ").trim();
31
37
  }
38
+ var ABSOLUTE_ADDRESS = /^[A-Za-z][A-Za-z0-9+.-]*:/;
39
+ var registeredTools = /* @__PURE__ */ new WeakMap();
40
+ var BLOCK_TAGS = [
41
+ "ADDRESS",
42
+ "ARTICLE",
43
+ "BLOCKQUOTE",
44
+ "BR",
45
+ "DD",
46
+ "DETAILS",
47
+ "DIV",
48
+ "DL",
49
+ "DT",
50
+ "FIELDSET",
51
+ "FIGCAPTION",
52
+ "FIGURE",
53
+ "FOOTER",
54
+ "H1",
55
+ "H2",
56
+ "H3",
57
+ "H4",
58
+ "H5",
59
+ "H6",
60
+ "HEADER",
61
+ "HR",
62
+ "LI",
63
+ "OL",
64
+ "P",
65
+ "PRE",
66
+ "SECTION",
67
+ "SUMMARY",
68
+ "UL"
69
+ ];
70
+ function isIgnored(node) {
71
+ if (node.hidden || typeof node.matches === "function" && node.matches(selectors.ignored)) return true;
72
+ return typeof node.checkVisibility === "function" && !node.checkVisibility();
73
+ }
74
+ function cellText(cell) {
75
+ var text = cleanText(cell.textContent);
76
+ return text.length > MAX_CELL_LENGTH ? text.slice(0, MAX_CELL_LENGTH) + "…" : text;
77
+ }
78
+ function readTable(table) {
79
+ var rows = Array.prototype.slice.call(table.rows || []);
80
+ var headers = [];
81
+ if (rows.length && Array.prototype.every.call(rows[0].cells || [], function(cell) {
82
+ return (cell.tagName || "").toUpperCase() === "TH";
83
+ })) headers = Array.prototype.map.call(rows.shift().cells, cellText);
84
+ return {
85
+ caption: (table.caption ? cleanText(table.caption.textContent) : "") || null,
86
+ headers,
87
+ rows: rows.slice(0, MAX_TABLE_ROWS).map(function(row) {
88
+ return Array.prototype.map.call(row.cells || [], cellText);
89
+ }),
90
+ totalRows: rows.length
91
+ };
92
+ }
93
+ /**
94
+ * Reads the text and the tables of the page's main region, the way a person reads it: menus, forms, hidden parts
95
+ * and the chat are left out, and each table is read once, as rows, where the text says [table N].
96
+ */
97
+ function readContent(doc) {
98
+ var root = doc.querySelector(selectors.main) || doc.body;
99
+ var lines = [];
100
+ var line = [];
101
+ var tables = [];
102
+ var tableCount = 0;
103
+ function endLine() {
104
+ var text = cleanText(line.join(" "));
105
+ if (text) lines.push(text);
106
+ line = [];
107
+ }
108
+ function walk(node) {
109
+ if (node.nodeType === 3) {
110
+ line.push(node.nodeValue);
111
+ return;
112
+ }
113
+ if (node.nodeType !== 1 || isIgnored(node)) return;
114
+ var tag = (node.tagName || "").toUpperCase();
115
+ if (tag === "TABLE") {
116
+ endLine();
117
+ tableCount++;
118
+ if (tables.length < MAX_TABLES) {
119
+ tables.push(readTable(node));
120
+ lines.push("[table " + tables.length + "]");
121
+ }
122
+ return;
123
+ }
124
+ var isBlock = BLOCK_TAGS.indexOf(tag) >= 0;
125
+ if (isBlock) endLine();
126
+ var children = node.childNodes || [];
127
+ for (var i = 0; i < children.length; i++) walk(children[i]);
128
+ if (isBlock) endLine();
129
+ }
130
+ if (root) {
131
+ walk(root);
132
+ endLine();
133
+ }
134
+ var text = lines.join("\n");
135
+ var truncated = text.length > MAX_CONTENT_LENGTH || tableCount > tables.length || tables.some(function(table) {
136
+ return table.totalRows > table.rows.length;
137
+ });
138
+ return {
139
+ text: text.length > MAX_CONTENT_LENGTH ? text.slice(0, MAX_CONTENT_LENGTH) + "…" : text,
140
+ tables,
141
+ truncated
142
+ };
143
+ }
32
144
  function resolveSameOrigin(href, loc) {
33
145
  if (!href) return null;
34
146
  try {
@@ -97,22 +209,45 @@
97
209
  for (var m = 0; m < matchers.length; m++) for (var i = 0; i < fields.length; i++) if (matchers[m](fields[i])) return fields[i];
98
210
  return null;
99
211
  }
212
+ /**
213
+ * Asks the site whether a page exists, with a HEAD request that downloads nothing. Only a 404 or 410 answer means it
214
+ * does not: a page that refuses HEAD (405), sends to the login page or cannot be reached is opened as asked.
215
+ *
216
+ * @param {Function | undefined} fetchImpl The fetch function, or nothing to skip the check.
217
+ * @param {string} href The address.
218
+ */
219
+ async function pageIsMissing(fetchImpl, href) {
220
+ if (typeof fetchImpl !== "function") return false;
221
+ try {
222
+ var response = await fetchImpl(href, {
223
+ method: "HEAD",
224
+ credentials: "same-origin",
225
+ redirect: "follow"
226
+ });
227
+ return response.status === 404 || response.status === 410;
228
+ } catch (error) {
229
+ return false;
230
+ }
231
+ }
100
232
  function raise(field, type) {
101
233
  field.dispatchEvent(new Event(type, { bubbles: true }));
102
234
  }
103
235
  /**
104
236
  * Creates the page tools for a document.
105
237
  *
106
- * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.
238
+ * @param {{ doc: any, loc: any, isAdmin?: boolean, fetch?: Function, navigateDelayMs?: number }} options The document,
239
+ * its location, whether it is an admin page, the fetch function page_navigate checks that a page exists with (no
240
+ * check without it), and how long page_navigate waits after answering before it opens the page.
107
241
  */
108
242
  function createPageTools(options) {
109
243
  var doc = options.doc;
110
244
  var loc = options.loc;
245
+ var navigateDelayMs = typeof options.navigateDelayMs === "number" ? options.navigateDelayMs : 400;
111
246
  return [
112
247
  {
113
248
  name: "page_get_info",
114
249
  title: "Get page info",
115
- description: "Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.",
250
+ description: "Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page. To read what the page shows, use page_read_content.",
116
251
  inputSchema: {
117
252
  type: "object",
118
253
  properties: {}
@@ -138,6 +273,19 @@
138
273
  };
139
274
  }
140
275
  },
276
+ {
277
+ name: "page_read_content",
278
+ title: "Read page content",
279
+ description: "Reads what the page shows in its main area: its text, and its tables as headers and rows (for example the records a list page shows). Menus, forms and hidden parts are left out; use page_list_links and page_list_form_fields for those.",
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {}
283
+ },
284
+ annotations: { readOnlyHint: true },
285
+ execute: function() {
286
+ return readContent(doc);
287
+ }
288
+ },
141
289
  {
142
290
  name: "page_list_links",
143
291
  title: "List page links",
@@ -171,19 +319,25 @@
171
319
  {
172
320
  name: "page_navigate",
173
321
  title: "Open page",
174
- description: "Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.",
322
+ description: "Opens another page of this site in the current tab. Only addresses on this site are allowed; take them from page_list_links, and start a site path with /. Unsaved changes on the current page are lost.",
175
323
  inputSchema: {
176
324
  type: "object",
177
325
  properties: { url: {
178
326
  type: "string",
179
- description: "The address to open, relative to the site or absolute on the same site."
327
+ description: "The address to open: a path that starts with \"/\", or a full address on this site, as page_list_links lists them."
180
328
  } },
181
329
  required: ["url"]
182
330
  },
183
- execute: function(input) {
184
- var url = resolveSameOrigin(input && input.url, loc);
185
- if (!url) throw new Error("Only addresses on this site can be opened.");
186
- loc.assign(url.href);
331
+ execute: async function(input) {
332
+ var address = cleanText(input && input.url);
333
+ if (!address) throw new Error("Give the address of the page to open. Call page_list_links to find it.");
334
+ if (address.charAt(0) !== "/" && !ABSOLUTE_ADDRESS.test(address)) throw new Error("\"" + address + "\" is not an address on this site. Give a path that starts with \"/\" or a full address on this site, as page_list_links lists them. Call page_list_links to find the page to open.");
335
+ var url = resolveSameOrigin(address, loc);
336
+ if (!url) throw new Error("Only addresses on this site can be opened. Call page_list_links to find the page to open.");
337
+ if (await pageIsMissing(options.fetch, url.href)) throw new Error("The site has no page at \"" + url.pathname + url.search + "\" (404). Do not guess addresses: call page_list_links to find the page to open.");
338
+ setTimeout(function() {
339
+ loc.assign(url.href);
340
+ }, navigateDelayMs);
187
341
  return { navigatedTo: url.href };
188
342
  }
189
343
  },
@@ -240,26 +394,51 @@
240
394
  * Registers the page tools with the page's ModelContext.
241
395
  *
242
396
  * @param {any} modelContext The page's ModelContext (document.modelContext).
243
- * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.
397
+ * @param {{ doc: any, loc: any, isAdmin?: boolean, fetch?: Function }} options The document, its location, whether it
398
+ * is an admin page, and the fetch function page_navigate checks that a page exists with (no check without it).
244
399
  * @param {AbortSignal} [signal] Unregisters the tools when aborted.
245
400
  */
246
401
  function register(modelContext, options, signal) {
402
+ var tools = registeredTools.get(modelContext);
403
+ if (!tools) {
404
+ tools = /* @__PURE__ */ new Map();
405
+ registeredTools.set(modelContext, tools);
406
+ }
247
407
  return Promise.all(createPageTools(options).map(function(tool) {
248
- return modelContext.registerTool(tool, signal ? { signal } : {}).catch(function(error) {
408
+ return modelContext.registerTool(tool, signal ? { signal } : {}).then(function() {
409
+ if (signal && signal.aborted) return;
410
+ tools.set(tool.name, tool);
411
+ if (signal) signal.addEventListener("abort", function() {
412
+ if (tools.get(tool.name) === tool) tools.delete(tool.name);
413
+ }, { once: true });
414
+ }, function(error) {
249
415
  console.warn("The page tool \"" + tool.name + "\" could not be registered.", error);
250
416
  });
251
417
  }));
252
418
  }
419
+ /**
420
+ * Answers the page tool register() registered with a ModelContext under a name, with its execute callback, or null
421
+ * when it registered none: another script may have registered a tool of that name first.
422
+ *
423
+ * @param {any} modelContext The ModelContext the tool was registered with.
424
+ * @param {string} name The tool's name.
425
+ */
426
+ function getRegisteredTool(modelContext, name) {
427
+ var tools = modelContext ? registeredTools.get(modelContext) : null;
428
+ return tools && tools.get(name) || null;
429
+ }
253
430
  window.CrestAppsWebMcpPageTools = {
254
431
  selectors,
255
432
  createPageTools,
256
- register
433
+ register,
434
+ getRegisteredTool
257
435
  };
258
436
  if (window.document && window.document.modelContext) {
259
437
  var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;
260
438
  register(window.document.modelContext, {
261
439
  doc: window.document,
262
440
  loc: window.location,
441
+ fetch: typeof window.fetch === "function" ? window.fetch.bind(window) : void 0,
263
442
  isAdmin: !!(config && config.isAdmin)
264
443
  });
265
444
  }
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-page-tools.js","sources":["../../Assets/js/webmcp-page-tools.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP page tools.\n *\n * Tools that work on the page the user sees: what the page is, where its menus lead, opening another page of the same\n * site, and reading and filling its forms. They never submit a form and never leave the site, so the user always\n * reviews and saves the changes an agent makes.\n *\n * The tools are registered with the standard `document.modelContext.registerTool`, so any WebMCP agent can use them:\n * the browser's own assistant, or the CrestApps AI chat. As WebMCP defines, each answers a JSON-serializable value and\n * reports a failure by rejecting. Load webmcp-polyfill.js first in browsers without native WebMCP.\n */\n\n(function () {\n 'use strict';\n\n var MAX_HEADINGS = 20;\n var MAX_LINKS = 200;\n var MAX_FIELDS = 200;\n var MAX_VALUE_LENGTH = 2000;\n\n // The selectors the tools read the page with.\n var selectors = {\n description: 'meta[name=\"description\"]',\n headings: 'h1, h2',\n links: 'nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]',\n fields: 'form input, form select, form textarea',\n };\n\n // Field types an agent never reads or fills.\n var SKIPPED_TYPES = ['hidden', 'password', 'file', 'submit', 'button', 'reset', 'image'];\n var SKIPPED_NAMES = ['__RequestVerificationToken'];\n\n function cleanText(value) {\n return (value == null ? '' : String(value)).replace(/\\s+/g, ' ').trim();\n }\n\n\n function resolveSameOrigin(href, loc) {\n if (!href) {\n return null;\n }\n\n try {\n var url = new URL(href, loc.href);\n\n return url.origin === loc.origin ? url : null;\n } catch (error) {\n return null;\n }\n }\n\n function fieldType(field) {\n var tag = (field.tagName || '').toLowerCase();\n\n if (tag === 'select' || tag === 'textarea') {\n return tag;\n }\n\n return (field.type || 'text').toLowerCase();\n }\n\n function fieldLabel(field) {\n if (field.labels && field.labels.length > 0) {\n var label = cleanText(field.labels[0].textContent);\n\n if (label) {\n return label;\n }\n }\n\n return cleanText(field.getAttribute('aria-label')) || cleanText(field.getAttribute('placeholder'));\n }\n\n function isListed(field) {\n return !field.disabled &&\n SKIPPED_TYPES.indexOf(fieldType(field)) < 0 &&\n SKIPPED_NAMES.indexOf(field.name) < 0;\n }\n\n function listFields(doc) {\n var fields = [];\n var all = doc.querySelectorAll(selectors.fields);\n\n for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) {\n if (isListed(all[i])) {\n fields.push(all[i]);\n }\n }\n\n return fields;\n }\n\n function describeField(field) {\n var type = fieldType(field);\n var description = {\n key: field.name || field.id,\n name: field.name,\n id: field.id,\n label: fieldLabel(field),\n type: type,\n value: type === 'checkbox' || type === 'radio'\n ? !!field.checked\n : String(field.value == null ? '' : field.value).substring(0, MAX_VALUE_LENGTH),\n required: !!field.required,\n };\n\n if (type === 'select') {\n description.options = Array.prototype.map.call(field.options || [], function (option) {\n return { value: option.value, text: cleanText(option.text), selected: !!option.selected };\n });\n }\n\n return description;\n }\n\n function findField(doc, key) {\n var wanted = cleanText(key).toLowerCase();\n\n if (!wanted) {\n return null;\n }\n\n var fields = listFields(doc);\n var matchers = [\n function (field) {\n return (field.name || '').toLowerCase() === wanted;\n },\n function (field) {\n return (field.id || '').toLowerCase() === wanted;\n },\n function (field) {\n return fieldLabel(field).toLowerCase() === wanted;\n },\n ];\n\n for (var m = 0; m < matchers.length; m++) {\n for (var i = 0; i < fields.length; i++) {\n if (matchers[m](fields[i])) {\n return fields[i];\n }\n }\n }\n\n return null;\n }\n\n function raise(field, type) {\n field.dispatchEvent(new Event(type, { bubbles: true }));\n }\n\n /**\n * Creates the page tools for a document.\n *\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n */\n function createPageTools(options) {\n var doc = options.doc;\n var loc = options.loc;\n\n return [\n {\n name: 'page_get_info',\n title: 'Get page info',\n description: 'Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n var meta = doc.querySelector(selectors.description);\n var headings = [];\n var all = doc.querySelectorAll(selectors.headings);\n\n for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {\n var text = cleanText(all[i].textContent);\n\n if (text) {\n headings.push({ level: (all[i].tagName || '').toUpperCase() === 'H1' ? 1 : 2, text: text });\n }\n }\n\n return ({\n title: doc.title,\n url: loc.href,\n description: meta ? meta.getAttribute('content') : null,\n headings: headings,\n isAdmin: !!options.isAdmin,\n });\n },\n },\n {\n name: 'page_list_links',\n title: 'List page links',\n description: 'Lists the links in the page\\'s menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: { type: 'string', description: 'Only list links whose text contains these words.' },\n },\n },\n annotations: { readOnlyHint: true },\n execute: function (input) {\n var filter = cleanText(input && input.filter).toLowerCase();\n var seen = new Set();\n var links = [];\n var all = doc.querySelectorAll(selectors.links);\n\n for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {\n var url = resolveSameOrigin(all[i].getAttribute('href'), loc);\n var text = cleanText(all[i].textContent);\n\n if (!url || !text || seen.has(url.href) || (filter && text.toLowerCase().indexOf(filter) < 0)) {\n continue;\n }\n\n seen.add(url.href);\n links.push({ text: text, url: url.href });\n }\n\n return ({ links: links });\n },\n },\n {\n name: 'page_navigate',\n title: 'Open page',\n description: 'Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The address to open, relative to the site or absolute on the same site.' },\n },\n required: ['url'],\n },\n execute: function (input) {\n var url = resolveSameOrigin(input && input.url, loc);\n\n if (!url) {\n throw new Error('Only addresses on this site can be opened.');\n }\n\n loc.assign(url.href);\n\n return ({ navigatedTo: url.href });\n },\n },\n {\n name: 'page_list_form_fields',\n title: 'List form fields',\n description: 'Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return ({ fields: listFields(doc).map(describeField) });\n },\n },\n {\n name: 'page_set_form_field',\n title: 'Set form field',\n description: 'Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.',\n inputSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', description: 'The field\\'s name, id or label, as page_list_form_fields lists it.' },\n value: { description: 'The value. For a check box, true or false. For a list, a choice\\'s value or text.' },\n },\n required: ['field', 'value'],\n },\n execute: function (input) {\n var field = findField(doc, input && input.field);\n\n if (!field) {\n throw new Error('No editable field on the page is named \"' + cleanText(input && input.field) + '\".');\n }\n\n var type = fieldType(field);\n var value = input.value;\n\n if (type === 'checkbox' || type === 'radio') {\n field.checked = value === true || String(value).toLowerCase() === 'true';\n } else if (type === 'select') {\n var wanted = cleanText(value).toLowerCase();\n var option = Array.prototype.find.call(field.options || [], function (candidate) {\n return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;\n });\n\n if (!option) {\n throw new Error('The list \"' + fieldLabel(field) + '\" has no choice \"' + cleanText(value) + '\".');\n }\n\n field.value = option.value;\n } else {\n field.value = value == null ? '' : String(value);\n }\n\n raise(field, 'input');\n raise(field, 'change');\n\n return ({ field: describeField(field) });\n },\n },\n ];\n }\n\n /**\n * Registers the page tools with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext (document.modelContext).\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n */\n function register(modelContext, options, signal) {\n return Promise.all(createPageTools(options).map(function (tool) {\n return modelContext.registerTool(tool, signal ? { signal: signal } : {}).catch(function (error) {\n console.warn('The page tool \"' + tool.name + '\" could not be registered.', error);\n });\n }));\n }\n\n window.CrestAppsWebMcpPageTools = {\n selectors: selectors,\n createPageTools: createPageTools,\n register: register,\n };\n\n if (window.document && window.document.modelContext) {\n var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;\n\n register(window.document.modelContext, {\n doc: window.document,\n loc: window.location,\n isAdmin: !!(config && config.isAdmin),\n });\n }\n})();\n"],"names":[],"mappings":";;;;;;CAaA,CAAC,WAAY;EACX;EAEA,IAAI,eAAe;EACnB,IAAI,YAAY;EAChB,IAAI,aAAa;EACjB,IAAI,mBAAmB;EAGvB,IAAI,YAAY;GACd,aAAa;GACb,UAAU;GACV,OAAO;GACP,QAAQ;EACV;EAGA,IAAI,gBAAgB;GAAC;GAAU;GAAY;GAAQ;GAAU;GAAU;GAAS;EAAO;EACvF,IAAI,gBAAgB,CAAC,4BAA4B;EAEjD,SAAS,UAAU,OAAO;GACxB,QAAQ,SAAS,OAAO,KAAK,OAAO,KAAK,EAAA,CAAG,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;EACxE;EAGA,SAAS,kBAAkB,MAAM,KAAK;GACpC,IAAI,CAAC,MACH,OAAO;GAGT,IAAI;IACF,IAAI,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI;IAEhC,OAAO,IAAI,WAAW,IAAI,SAAS,MAAM;GAC3C,SAAS,OAAO;IACd,OAAO;GACT;EACF;EAEA,SAAS,UAAU,OAAO;GACxB,IAAI,OAAO,MAAM,WAAW,GAAA,CAAI,YAAY;GAE5C,IAAI,QAAQ,YAAY,QAAQ,YAC9B,OAAO;GAGT,QAAQ,MAAM,QAAQ,OAAA,CAAQ,YAAY;EAC5C;EAEA,SAAS,WAAW,OAAO;GACzB,IAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;IAC3C,IAAI,QAAQ,UAAU,MAAM,OAAO,EAAE,CAAC,WAAW;IAEjD,IAAI,OACF,OAAO;GAEX;GAEA,OAAO,UAAU,MAAM,aAAa,YAAY,CAAC,KAAK,UAAU,MAAM,aAAa,aAAa,CAAC;EACnG;EAEA,SAAS,SAAS,OAAO;GACvB,OAAO,CAAC,MAAM,YACZ,cAAc,QAAQ,UAAU,KAAK,CAAC,IAAI,KAC1C,cAAc,QAAQ,MAAM,IAAI,IAAI;EACxC;EAEA,SAAS,WAAW,KAAK;GACvB,IAAI,SAAS,CAAC;GACd,IAAI,MAAM,IAAI,iBAAiB,UAAU,MAAM;GAE/C,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,OAAO,SAAS,YAAY,KAC5D,IAAI,SAAS,IAAI,EAAE,GACjB,OAAO,KAAK,IAAI,EAAE;GAItB,OAAO;EACT;EAEA,SAAS,cAAc,OAAO;GAC5B,IAAI,OAAO,UAAU,KAAK;GAC1B,IAAI,cAAc;IAChB,KAAK,MAAM,QAAQ,MAAM;IACzB,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,OAAO,WAAW,KAAK;IACjB;IACN,OAAO,SAAS,cAAc,SAAS,UACnC,CAAC,CAAC,MAAM,UACR,OAAO,MAAM,SAAS,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,UAAU,GAAG,gBAAgB;IAChF,UAAU,CAAC,CAAC,MAAM;GACpB;GAEA,IAAI,SAAS,UACX,YAAY,UAAU,MAAM,UAAU,IAAI,KAAK,MAAM,WAAW,CAAC,GAAG,SAAU,QAAQ;IACpF,OAAO;KAAE,OAAO,OAAO;KAAO,MAAM,UAAU,OAAO,IAAI;KAAG,UAAU,CAAC,CAAC,OAAO;IAAS;GAC1F,CAAC;GAGH,OAAO;EACT;EAEA,SAAS,UAAU,KAAK,KAAK;GAC3B,IAAI,SAAS,UAAU,GAAG,CAAC,CAAC,YAAY;GAExC,IAAI,CAAC,QACH,OAAO;GAGT,IAAI,SAAS,WAAW,GAAG;GAC3B,IAAI,WAAW;IACb,SAAU,OAAO;KACf,QAAQ,MAAM,QAAQ,GAAA,CAAI,YAAY,MAAM;IAC9C;IACA,SAAU,OAAO;KACf,QAAQ,MAAM,MAAM,GAAA,CAAI,YAAY,MAAM;IAC5C;IACA,SAAU,OAAO;KACf,OAAO,WAAW,KAAK,CAAC,CAAC,YAAY,MAAM;IAC7C;GACF;GAEA,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KACnC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,SAAS,EAAE,CAAC,OAAO,EAAE,GACvB,OAAO,OAAO;GAKpB,OAAO;EACT;EAEA,SAAS,MAAM,OAAO,MAAM;GAC1B,MAAM,cAAc,IAAI,MAAM,MAAM,EAAE,SAAS,KAAK,CAAC,CAAC;EACxD;;;;;;EAOA,SAAS,gBAAgB,SAAS;GAChC,IAAI,MAAM,QAAQ;GAClB,IAAI,MAAM,QAAQ;GAElB,OAAO;IACL;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MAAE,MAAM;MAAU,YAAY,CAAC;KAAE;KAC9C,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,WAAY;MACnB,IAAI,OAAO,IAAI,cAAc,UAAU,WAAW;MAClD,IAAI,WAAW,CAAC;MAChB,IAAI,MAAM,IAAI,iBAAiB,UAAU,QAAQ;MAEjD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,SAAS,SAAS,cAAc,KAAK;OACrE,IAAI,OAAO,UAAU,IAAI,EAAE,CAAC,WAAW;OAEvC,IAAI,MACF,SAAS,KAAK;QAAE,QAAQ,IAAI,EAAE,CAAC,WAAW,GAAA,CAAI,YAAY,MAAM,OAAO,IAAI;QAAS;OAAK,CAAC;MAE9F;MAEA,OAAQ;OACN,OAAO,IAAI;OACX,KAAK,IAAI;OACT,aAAa,OAAO,KAAK,aAAa,SAAS,IAAI;OACzC;OACV,SAAS,CAAC,CAAC,QAAQ;MACrB;KACF;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MACX,MAAM;MACN,YAAY,EACV,QAAQ;OAAE,MAAM;OAAU,aAAa;MAAmD,EAC5F;KACF;KACA,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,SAAU,OAAO;MACxB,IAAI,SAAS,UAAU,SAAS,MAAM,MAAM,CAAC,CAAC,YAAY;MAC1D,IAAI,uBAAO,IAAI,IAAI;MACnB,IAAI,QAAQ,CAAC;MACb,IAAI,MAAM,IAAI,iBAAiB,UAAU,KAAK;MAE9C,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,MAAM,SAAS,WAAW,KAAK;OAC/D,IAAI,MAAM,kBAAkB,IAAI,EAAE,CAAC,aAAa,MAAM,GAAG,GAAG;OAC5D,IAAI,OAAO,UAAU,IAAI,EAAE,CAAC,WAAW;OAEvC,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,KAAM,UAAU,KAAK,YAAY,CAAC,CAAC,QAAQ,MAAM,IAAI,GACzF;OAGF,KAAK,IAAI,IAAI,IAAI;OACjB,MAAM,KAAK;QAAQ;QAAM,KAAK,IAAI;OAAK,CAAC;MAC1C;MAEA,OAAQ,EAAS,MAAM;KACzB;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MACX,MAAM;MACN,YAAY,EACV,KAAK;OAAE,MAAM;OAAU,aAAa;MAA0E,EAChH;MACA,UAAU,CAAC,KAAK;KAClB;KACA,SAAS,SAAU,OAAO;MACxB,IAAI,MAAM,kBAAkB,SAAS,MAAM,KAAK,GAAG;MAEnD,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,4CAA4C;MAG9D,IAAI,OAAO,IAAI,IAAI;MAEnB,OAAQ,EAAE,aAAa,IAAI,KAAK;KAClC;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MAAE,MAAM;MAAU,YAAY,CAAC;KAAE;KAC9C,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,WAAY;MACnB,OAAQ,EAAE,QAAQ,WAAW,GAAG,CAAC,CAAC,IAAI,aAAa,EAAE;KACvD;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MACX,MAAM;MACN,YAAY;OACV,OAAO;QAAE,MAAM;QAAU,aAAa;OAAqE;OAC3G,OAAO,EAAE,aAAa,mFAAoF;MAC5G;MACA,UAAU,CAAC,SAAS,OAAO;KAC7B;KACA,SAAS,SAAU,OAAO;MACxB,IAAI,QAAQ,UAAU,KAAK,SAAS,MAAM,KAAK;MAE/C,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,8CAA6C,UAAU,SAAS,MAAM,KAAK,IAAI,KAAI;MAGrG,IAAI,OAAO,UAAU,KAAK;MAC1B,IAAI,QAAQ,MAAM;MAElB,IAAI,SAAS,cAAc,SAAS,SAClC,MAAM,UAAU,UAAU,QAAQ,OAAO,KAAK,CAAC,CAAC,YAAY,MAAM;WAC7D,IAAI,SAAS,UAAU;OAC5B,IAAI,SAAS,UAAU,KAAK,CAAC,CAAC,YAAY;OAC1C,IAAI,SAAS,MAAM,UAAU,KAAK,KAAK,MAAM,WAAW,CAAC,GAAG,SAAU,WAAW;QAC/E,OAAO,OAAO,UAAU,KAAK,CAAC,CAAC,YAAY,MAAM,UAAU,UAAU,UAAU,IAAI,CAAC,CAAC,YAAY,MAAM;OACzG,CAAC;OAED,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,gBAAe,WAAW,KAAK,IAAI,wBAAsB,UAAU,KAAK,IAAI,KAAI;OAGlG,MAAM,QAAQ,OAAO;MACvB,OACE,MAAM,QAAQ,SAAS,OAAO,KAAK,OAAO,KAAK;MAGjD,MAAM,OAAO,OAAO;MACpB,MAAM,OAAO,QAAQ;MAErB,OAAQ,EAAE,OAAO,cAAc,KAAK,EAAE;KACxC;IACF;GACF;EACF;;;;;;;;EASA,SAAS,SAAS,cAAc,SAAS,QAAQ;GAC/C,OAAO,QAAQ,IAAI,gBAAgB,OAAO,CAAC,CAAC,IAAI,SAAU,MAAM;IAC9D,OAAO,aAAa,aAAa,MAAM,SAAS,EAAU,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,SAAU,OAAO;KAC9F,QAAQ,KAAK,qBAAoB,KAAK,OAAO,+BAA8B,KAAK;IAClF,CAAC;GACH,CAAC,CAAC;EACJ;EAEA,OAAO,2BAA2B;GACrB;GACM;GACP;EACZ;EAEA,IAAI,OAAO,YAAY,OAAO,SAAS,cAAc;GACnD,IAAI,SAAS,OAAO,mBAAmB,OAAO,gBAAgB;GAE9D,SAAS,OAAO,SAAS,cAAc;IACrC,KAAK,OAAO;IACZ,KAAK,OAAO;IACZ,SAAS,CAAC,EAAE,UAAU,OAAO;GAC/B,CAAC;EACH;CACF,EAAA,CAAG"}
1
+ {"version":3,"file":"webmcp-page-tools.js","sources":["../../Assets/js/webmcp-page-tools.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP page tools.\n *\n * Tools that work on the page the user sees: what the page is, what it shows, where its menus lead, opening another\n * page of the same site, and reading and filling its forms. They never submit a form and never leave the site, so the user always\n * reviews and saves the changes an agent makes.\n *\n * The tools are registered with the standard `document.modelContext.registerTool`, so any WebMCP agent can use them:\n * the browser's own assistant, or the CrestApps AI chat. As WebMCP defines, each answers a JSON-serializable value and\n * reports a failure by rejecting. Load webmcp-polyfill.js first in browsers without native WebMCP.\n */\n\n(function () {\n 'use strict';\n\n var MAX_HEADINGS = 20;\n var MAX_LINKS = 200;\n var MAX_FIELDS = 200;\n var MAX_VALUE_LENGTH = 2000;\n var MAX_CONTENT_LENGTH = 8000;\n var MAX_TABLES = 10;\n var MAX_TABLE_ROWS = 50;\n var MAX_CELL_LENGTH = 300;\n\n // The selectors the tools read the page with.\n var selectors = {\n description: 'meta[name=\"description\"]',\n headings: 'h1, h2',\n main: 'main, [role=\"main\"]',\n // What page_read_content leaves out of the page's text: scripts, menus, the choices and text of form controls\n // (page_list_form_fields reads them), hidden parts, the chat itself, and anything a page marks with\n // data-webmcp-ignore. A form's other content is read: admin pages often wrap a whole list in one, for bulk actions.\n ignored: 'script, style, noscript, template, svg, nav, aside, select, textarea, datalist, dialog:not([open]), [hidden], [aria-hidden=\"true\"], [role=\"navigation\"], [data-webmcp-ignore], [class*=\"ai-chat-widget\"]',\n links: 'nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]',\n fields: 'form input, form select, form textarea',\n };\n\n // Field types an agent never reads or fills.\n var SKIPPED_TYPES = ['hidden', 'password', 'file', 'submit', 'button', 'reset', 'image'];\n var SKIPPED_NAMES = ['__RequestVerificationToken'];\n\n function cleanText(value) {\n return (value == null ? '' : String(value)).replace(/\\s+/g, ' ').trim();\n }\n\n\n // An address that starts with a scheme, such as \"https:\" or \"javascript:\".\n var ABSOLUTE_ADDRESS = /^[A-Za-z][A-Za-z0-9+.-]*:/;\n\n // The tools register() registered, by ModelContext and name, so the CrestApps chat can run them through their own\n // execute and tell the model why one failed (executeTool reports every failure as \"Tool execution failed\").\n var registeredTools = new WeakMap();\n\n // Elements that start a new line in the text page_read_content reads.\n var BLOCK_TAGS = ['ADDRESS', 'ARTICLE', 'BLOCKQUOTE', 'BR', 'DD', 'DETAILS', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', 'FOOTER', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', 'HR', 'LI', 'OL', 'P', 'PRE', 'SECTION', 'SUMMARY', 'UL'];\n\n function isIgnored(node) {\n if (node.hidden || (typeof node.matches === 'function' && node.matches(selectors.ignored))) {\n return true;\n }\n\n // Parts the page hides with CSS, such as closed menus and modals.\n return typeof node.checkVisibility === 'function' && !node.checkVisibility();\n }\n\n function cellText(cell) {\n var text = cleanText(cell.textContent);\n\n return text.length > MAX_CELL_LENGTH ? text.slice(0, MAX_CELL_LENGTH) + '…' : text;\n }\n\n function readTable(table) {\n var rows = Array.prototype.slice.call(table.rows || []);\n var headers = [];\n\n if (rows.length && Array.prototype.every.call(rows[0].cells || [], function (cell) { return (cell.tagName || '').toUpperCase() === 'TH'; })) {\n headers = Array.prototype.map.call(rows.shift().cells, cellText);\n }\n\n var caption = table.caption ? cleanText(table.caption.textContent) : '';\n\n return {\n caption: caption || null,\n headers: headers,\n rows: rows.slice(0, MAX_TABLE_ROWS).map(function (row) {\n return Array.prototype.map.call(row.cells || [], cellText);\n }),\n totalRows: rows.length,\n };\n }\n\n /**\n * Reads the text and the tables of the page's main region, the way a person reads it: menus, forms, hidden parts\n * and the chat are left out, and each table is read once, as rows, where the text says [table N].\n */\n function readContent(doc) {\n var root = doc.querySelector(selectors.main) || doc.body;\n var lines = [];\n var line = [];\n var tables = [];\n var tableCount = 0;\n\n function endLine() {\n var text = cleanText(line.join(' '));\n\n if (text) {\n lines.push(text);\n }\n\n line = [];\n }\n\n function walk(node) {\n if (node.nodeType === 3) {\n line.push(node.nodeValue);\n\n return;\n }\n\n if (node.nodeType !== 1 || isIgnored(node)) {\n return;\n }\n\n var tag = (node.tagName || '').toUpperCase();\n\n if (tag === 'TABLE') {\n endLine();\n tableCount++;\n\n if (tables.length < MAX_TABLES) {\n tables.push(readTable(node));\n lines.push('[table ' + tables.length + ']');\n }\n\n return;\n }\n\n var isBlock = BLOCK_TAGS.indexOf(tag) >= 0;\n\n if (isBlock) {\n endLine();\n }\n\n var children = node.childNodes || [];\n\n for (var i = 0; i < children.length; i++) {\n walk(children[i]);\n }\n\n if (isBlock) {\n endLine();\n }\n }\n\n if (root) {\n walk(root);\n endLine();\n }\n\n var text = lines.join('\\n');\n var truncated = text.length > MAX_CONTENT_LENGTH || tableCount > tables.length ||\n tables.some(function (table) { return table.totalRows > table.rows.length; });\n\n return {\n text: text.length > MAX_CONTENT_LENGTH ? text.slice(0, MAX_CONTENT_LENGTH) + '…' : text,\n tables: tables,\n truncated: truncated,\n };\n }\n\n function resolveSameOrigin(href, loc) {\n if (!href) {\n return null;\n }\n\n try {\n var url = new URL(href, loc.href);\n\n return url.origin === loc.origin ? url : null;\n } catch (error) {\n return null;\n }\n }\n\n function fieldType(field) {\n var tag = (field.tagName || '').toLowerCase();\n\n if (tag === 'select' || tag === 'textarea') {\n return tag;\n }\n\n return (field.type || 'text').toLowerCase();\n }\n\n function fieldLabel(field) {\n if (field.labels && field.labels.length > 0) {\n var label = cleanText(field.labels[0].textContent);\n\n if (label) {\n return label;\n }\n }\n\n return cleanText(field.getAttribute('aria-label')) || cleanText(field.getAttribute('placeholder'));\n }\n\n function isListed(field) {\n return !field.disabled &&\n SKIPPED_TYPES.indexOf(fieldType(field)) < 0 &&\n SKIPPED_NAMES.indexOf(field.name) < 0;\n }\n\n function listFields(doc) {\n var fields = [];\n var all = doc.querySelectorAll(selectors.fields);\n\n for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) {\n if (isListed(all[i])) {\n fields.push(all[i]);\n }\n }\n\n return fields;\n }\n\n function describeField(field) {\n var type = fieldType(field);\n var description = {\n key: field.name || field.id,\n name: field.name,\n id: field.id,\n label: fieldLabel(field),\n type: type,\n value: type === 'checkbox' || type === 'radio'\n ? !!field.checked\n : String(field.value == null ? '' : field.value).substring(0, MAX_VALUE_LENGTH),\n required: !!field.required,\n };\n\n if (type === 'select') {\n description.options = Array.prototype.map.call(field.options || [], function (option) {\n return { value: option.value, text: cleanText(option.text), selected: !!option.selected };\n });\n }\n\n return description;\n }\n\n function findField(doc, key) {\n var wanted = cleanText(key).toLowerCase();\n\n if (!wanted) {\n return null;\n }\n\n var fields = listFields(doc);\n var matchers = [\n function (field) {\n return (field.name || '').toLowerCase() === wanted;\n },\n function (field) {\n return (field.id || '').toLowerCase() === wanted;\n },\n function (field) {\n return fieldLabel(field).toLowerCase() === wanted;\n },\n ];\n\n for (var m = 0; m < matchers.length; m++) {\n for (var i = 0; i < fields.length; i++) {\n if (matchers[m](fields[i])) {\n return fields[i];\n }\n }\n }\n\n return null;\n }\n\n /**\n * Asks the site whether a page exists, with a HEAD request that downloads nothing. Only a 404 or 410 answer means it\n * does not: a page that refuses HEAD (405), sends to the login page or cannot be reached is opened as asked.\n *\n * @param {Function | undefined} fetchImpl The fetch function, or nothing to skip the check.\n * @param {string} href The address.\n */\n async function pageIsMissing(fetchImpl, href) {\n if (typeof fetchImpl !== 'function') {\n return false;\n }\n\n try {\n var response = await fetchImpl(href, { method: 'HEAD', credentials: 'same-origin', redirect: 'follow' });\n\n return response.status === 404 || response.status === 410;\n } catch (error) {\n return false;\n }\n }\n\n function raise(field, type) {\n field.dispatchEvent(new Event(type, { bubbles: true }));\n }\n\n /**\n * Creates the page tools for a document.\n *\n * @param {{ doc: any, loc: any, isAdmin?: boolean, fetch?: Function, navigateDelayMs?: number }} options The document,\n * its location, whether it is an admin page, the fetch function page_navigate checks that a page exists with (no\n * check without it), and how long page_navigate waits after answering before it opens the page.\n */\n function createPageTools(options) {\n var doc = options.doc;\n var loc = options.loc;\n var navigateDelayMs = typeof options.navigateDelayMs === 'number' ? options.navigateDelayMs : 400;\n\n return [\n {\n name: 'page_get_info',\n title: 'Get page info',\n description: 'Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page. To read what the page shows, use page_read_content.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n var meta = doc.querySelector(selectors.description);\n var headings = [];\n var all = doc.querySelectorAll(selectors.headings);\n\n for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {\n var text = cleanText(all[i].textContent);\n\n if (text) {\n headings.push({ level: (all[i].tagName || '').toUpperCase() === 'H1' ? 1 : 2, text: text });\n }\n }\n\n return ({\n title: doc.title,\n url: loc.href,\n description: meta ? meta.getAttribute('content') : null,\n headings: headings,\n isAdmin: !!options.isAdmin,\n });\n },\n },\n {\n name: 'page_read_content',\n title: 'Read page content',\n description: 'Reads what the page shows in its main area: its text, and its tables as headers and rows (for example the records a list page shows). Menus, forms and hidden parts are left out; use page_list_links and page_list_form_fields for those.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return readContent(doc);\n },\n },\n {\n name: 'page_list_links',\n title: 'List page links',\n description: 'Lists the links in the page\\'s menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: { type: 'string', description: 'Only list links whose text contains these words.' },\n },\n },\n annotations: { readOnlyHint: true },\n execute: function (input) {\n var filter = cleanText(input && input.filter).toLowerCase();\n var seen = new Set();\n var links = [];\n var all = doc.querySelectorAll(selectors.links);\n\n for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {\n var url = resolveSameOrigin(all[i].getAttribute('href'), loc);\n var text = cleanText(all[i].textContent);\n\n if (!url || !text || seen.has(url.href) || (filter && text.toLowerCase().indexOf(filter) < 0)) {\n continue;\n }\n\n seen.add(url.href);\n links.push({ text: text, url: url.href });\n }\n\n return ({ links: links });\n },\n },\n {\n name: 'page_navigate',\n title: 'Open page',\n description: 'Opens another page of this site in the current tab. Only addresses on this site are allowed; take them from page_list_links, and start a site path with /. Unsaved changes on the current page are lost.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The address to open: a path that starts with \"/\", or a full address on this site, as page_list_links lists them.' },\n },\n required: ['url'],\n },\n execute: async function (input) {\n var address = cleanText(input && input.url);\n\n if (!address) {\n throw new Error('Give the address of the page to open. Call page_list_links to find it.');\n }\n\n // A bare relative path, such as \"ArticlesAdmin\", resolves against the current page and usually names a page\n // that does not exist: it is a guess, not an address the site gave.\n if (address.charAt(0) !== '/' && !ABSOLUTE_ADDRESS.test(address)) {\n throw new Error('\"' + address + '\" is not an address on this site. Give a path that starts with \"/\" or a full address on this site, as page_list_links lists them. Call page_list_links to find the page to open.');\n }\n\n var url = resolveSameOrigin(address, loc);\n\n if (!url) {\n throw new Error('Only addresses on this site can be opened. Call page_list_links to find the page to open.');\n }\n\n if (await pageIsMissing(options.fetch, url.href)) {\n throw new Error('The site has no page at \"' + url.pathname + url.search + '\" (404). Do not guess addresses: call page_list_links to find the page to open.');\n }\n\n // Answer first and open the page a moment later, so the agent gets the result before this page (and\n // the chat connection on it) goes away. The chat finishes the reply on the server and shows it on the\n // next page.\n setTimeout(function () {\n loc.assign(url.href);\n }, navigateDelayMs);\n\n return ({ navigatedTo: url.href });\n },\n },\n {\n name: 'page_list_form_fields',\n title: 'List form fields',\n description: 'Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return ({ fields: listFields(doc).map(describeField) });\n },\n },\n {\n name: 'page_set_form_field',\n title: 'Set form field',\n description: 'Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.',\n inputSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', description: 'The field\\'s name, id or label, as page_list_form_fields lists it.' },\n value: { description: 'The value. For a check box, true or false. For a list, a choice\\'s value or text.' },\n },\n required: ['field', 'value'],\n },\n execute: function (input) {\n var field = findField(doc, input && input.field);\n\n if (!field) {\n throw new Error('No editable field on the page is named \"' + cleanText(input && input.field) + '\".');\n }\n\n var type = fieldType(field);\n var value = input.value;\n\n if (type === 'checkbox' || type === 'radio') {\n field.checked = value === true || String(value).toLowerCase() === 'true';\n } else if (type === 'select') {\n var wanted = cleanText(value).toLowerCase();\n var option = Array.prototype.find.call(field.options || [], function (candidate) {\n return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;\n });\n\n if (!option) {\n throw new Error('The list \"' + fieldLabel(field) + '\" has no choice \"' + cleanText(value) + '\".');\n }\n\n field.value = option.value;\n } else {\n field.value = value == null ? '' : String(value);\n }\n\n raise(field, 'input');\n raise(field, 'change');\n\n return ({ field: describeField(field) });\n },\n },\n ];\n }\n\n /**\n * Registers the page tools with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext (document.modelContext).\n * @param {{ doc: any, loc: any, isAdmin?: boolean, fetch?: Function }} options The document, its location, whether it\n * is an admin page, and the fetch function page_navigate checks that a page exists with (no check without it).\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n */\n function register(modelContext, options, signal) {\n var tools = registeredTools.get(modelContext);\n\n if (!tools) {\n tools = new Map();\n registeredTools.set(modelContext, tools);\n }\n\n return Promise.all(createPageTools(options).map(function (tool) {\n return modelContext.registerTool(tool, signal ? { signal: signal } : {}).then(function () {\n if (signal && signal.aborted) {\n return;\n }\n\n tools.set(tool.name, tool);\n\n if (signal) {\n signal.addEventListener('abort', function () {\n if (tools.get(tool.name) === tool) {\n tools.delete(tool.name);\n }\n }, { once: true });\n }\n }, function (error) {\n console.warn('The page tool \"' + tool.name + '\" could not be registered.', error);\n });\n }));\n }\n\n /**\n * Answers the page tool register() registered with a ModelContext under a name, with its execute callback, or null\n * when it registered none: another script may have registered a tool of that name first.\n *\n * @param {any} modelContext The ModelContext the tool was registered with.\n * @param {string} name The tool's name.\n */\n function getRegisteredTool(modelContext, name) {\n var tools = modelContext ? registeredTools.get(modelContext) : null;\n\n return (tools && tools.get(name)) || null;\n }\n\n window.CrestAppsWebMcpPageTools = {\n selectors: selectors,\n createPageTools: createPageTools,\n register: register,\n getRegisteredTool: getRegisteredTool,\n };\n\n if (window.document && window.document.modelContext) {\n var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;\n\n register(window.document.modelContext, {\n doc: window.document,\n loc: window.location,\n fetch: typeof window.fetch === 'function' ? window.fetch.bind(window) : undefined,\n isAdmin: !!(config && config.isAdmin),\n });\n }\n})();\n"],"names":[],"mappings":";;;;;;CAaA,CAAC,WAAY;EACX;EAEA,IAAI,eAAe;EACnB,IAAI,YAAY;EAChB,IAAI,aAAa;EACjB,IAAI,mBAAmB;EACvB,IAAI,qBAAqB;EACzB,IAAI,aAAa;EACjB,IAAI,iBAAiB;EACrB,IAAI,kBAAkB;EAGtB,IAAI,YAAY;GACd,aAAa;GACb,UAAU;GACV,MAAM;GAIN,SAAS;GACT,OAAO;GACP,QAAQ;EACV;EAGA,IAAI,gBAAgB;GAAC;GAAU;GAAY;GAAQ;GAAU;GAAU;GAAS;EAAO;EACvF,IAAI,gBAAgB,CAAC,4BAA4B;EAEjD,SAAS,UAAU,OAAO;GACxB,QAAQ,SAAS,OAAO,KAAK,OAAO,KAAK,EAAA,CAAG,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;EACxE;EAIA,IAAI,mBAAmB;EAIvB,IAAI,kCAAkB,IAAI,QAAQ;EAGlC,IAAI,aAAa;GAAC;GAAW;GAAW;GAAc;GAAM;GAAM;GAAW;GAAO;GAAM;GAAM;GAAY;GAAc;GAAU;GAAU;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAU;GAAM;GAAM;GAAM;GAAK;GAAO;GAAW;GAAW;EAAI;EAEpP,SAAS,UAAU,MAAM;GACvB,IAAI,KAAK,UAAW,OAAO,KAAK,YAAY,cAAc,KAAK,QAAQ,UAAU,OAAO,GACtF,OAAO;GAIT,OAAO,OAAO,KAAK,oBAAoB,cAAc,CAAC,KAAK,gBAAgB;EAC7E;EAEA,SAAS,SAAS,MAAM;GACtB,IAAI,OAAO,UAAU,KAAK,WAAW;GAErC,OAAO,KAAK,SAAS,kBAAkB,KAAK,MAAM,GAAG,eAAe,IAAI,MAAM;EAChF;EAEA,SAAS,UAAU,OAAO;GACxB,IAAI,OAAO,MAAM,UAAU,MAAM,KAAK,MAAM,QAAQ,CAAC,CAAC;GACtD,IAAI,UAAU,CAAC;GAEf,IAAI,KAAK,UAAU,MAAM,UAAU,MAAM,KAAK,KAAK,EAAE,CAAC,SAAS,CAAC,GAAG,SAAU,MAAM;IAAE,QAAQ,KAAK,WAAW,GAAA,CAAI,YAAY,MAAM;GAAM,CAAC,GACxI,UAAU,MAAM,UAAU,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,OAAO,QAAQ;GAKjE,OAAO;IACL,UAHY,MAAM,UAAU,UAAU,MAAM,QAAQ,WAAW,IAAI,OAG/C;IACX;IACT,MAAM,KAAK,MAAM,GAAG,cAAc,CAAC,CAAC,IAAI,SAAU,KAAK;KACrD,OAAO,MAAM,UAAU,IAAI,KAAK,IAAI,SAAS,CAAC,GAAG,QAAQ;IAC3D,CAAC;IACD,WAAW,KAAK;GAClB;EACF;;;;;EAMA,SAAS,YAAY,KAAK;GACxB,IAAI,OAAO,IAAI,cAAc,UAAU,IAAI,KAAK,IAAI;GACpD,IAAI,QAAQ,CAAC;GACb,IAAI,OAAO,CAAC;GACZ,IAAI,SAAS,CAAC;GACd,IAAI,aAAa;GAEjB,SAAS,UAAU;IACjB,IAAI,OAAO,UAAU,KAAK,KAAK,GAAG,CAAC;IAEnC,IAAI,MACF,MAAM,KAAK,IAAI;IAGjB,OAAO,CAAC;GACV;GAEA,SAAS,KAAK,MAAM;IAClB,IAAI,KAAK,aAAa,GAAG;KACvB,KAAK,KAAK,KAAK,SAAS;KAExB;IACF;IAEA,IAAI,KAAK,aAAa,KAAK,UAAU,IAAI,GACvC;IAGF,IAAI,OAAO,KAAK,WAAW,GAAA,CAAI,YAAY;IAE3C,IAAI,QAAQ,SAAS;KACnB,QAAQ;KACR;KAEA,IAAI,OAAO,SAAS,YAAY;MAC9B,OAAO,KAAK,UAAU,IAAI,CAAC;MAC3B,MAAM,KAAK,YAAY,OAAO,SAAS,GAAG;KAC5C;KAEA;IACF;IAEA,IAAI,UAAU,WAAW,QAAQ,GAAG,KAAK;IAEzC,IAAI,SACF,QAAQ;IAGV,IAAI,WAAW,KAAK,cAAc,CAAC;IAEnC,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KACnC,KAAK,SAAS,EAAE;IAGlB,IAAI,SACF,QAAQ;GAEZ;GAEA,IAAI,MAAM;IACR,KAAK,IAAI;IACT,QAAQ;GACV;GAEA,IAAI,OAAO,MAAM,KAAK,IAAI;GAC1B,IAAI,YAAY,KAAK,SAAS,sBAAsB,aAAa,OAAO,UACtE,OAAO,KAAK,SAAU,OAAO;IAAE,OAAO,MAAM,YAAY,MAAM,KAAK;GAAQ,CAAC;GAE9E,OAAO;IACL,MAAM,KAAK,SAAS,qBAAqB,KAAK,MAAM,GAAG,kBAAkB,IAAI,MAAM;IAC3E;IACG;GACb;EACF;EAEA,SAAS,kBAAkB,MAAM,KAAK;GACpC,IAAI,CAAC,MACH,OAAO;GAGT,IAAI;IACF,IAAI,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI;IAEhC,OAAO,IAAI,WAAW,IAAI,SAAS,MAAM;GAC3C,SAAS,OAAO;IACd,OAAO;GACT;EACF;EAEA,SAAS,UAAU,OAAO;GACxB,IAAI,OAAO,MAAM,WAAW,GAAA,CAAI,YAAY;GAE5C,IAAI,QAAQ,YAAY,QAAQ,YAC9B,OAAO;GAGT,QAAQ,MAAM,QAAQ,OAAA,CAAQ,YAAY;EAC5C;EAEA,SAAS,WAAW,OAAO;GACzB,IAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;IAC3C,IAAI,QAAQ,UAAU,MAAM,OAAO,EAAE,CAAC,WAAW;IAEjD,IAAI,OACF,OAAO;GAEX;GAEA,OAAO,UAAU,MAAM,aAAa,YAAY,CAAC,KAAK,UAAU,MAAM,aAAa,aAAa,CAAC;EACnG;EAEA,SAAS,SAAS,OAAO;GACvB,OAAO,CAAC,MAAM,YACZ,cAAc,QAAQ,UAAU,KAAK,CAAC,IAAI,KAC1C,cAAc,QAAQ,MAAM,IAAI,IAAI;EACxC;EAEA,SAAS,WAAW,KAAK;GACvB,IAAI,SAAS,CAAC;GACd,IAAI,MAAM,IAAI,iBAAiB,UAAU,MAAM;GAE/C,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,OAAO,SAAS,YAAY,KAC5D,IAAI,SAAS,IAAI,EAAE,GACjB,OAAO,KAAK,IAAI,EAAE;GAItB,OAAO;EACT;EAEA,SAAS,cAAc,OAAO;GAC5B,IAAI,OAAO,UAAU,KAAK;GAC1B,IAAI,cAAc;IAChB,KAAK,MAAM,QAAQ,MAAM;IACzB,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,OAAO,WAAW,KAAK;IACjB;IACN,OAAO,SAAS,cAAc,SAAS,UACnC,CAAC,CAAC,MAAM,UACR,OAAO,MAAM,SAAS,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,UAAU,GAAG,gBAAgB;IAChF,UAAU,CAAC,CAAC,MAAM;GACpB;GAEA,IAAI,SAAS,UACX,YAAY,UAAU,MAAM,UAAU,IAAI,KAAK,MAAM,WAAW,CAAC,GAAG,SAAU,QAAQ;IACpF,OAAO;KAAE,OAAO,OAAO;KAAO,MAAM,UAAU,OAAO,IAAI;KAAG,UAAU,CAAC,CAAC,OAAO;IAAS;GAC1F,CAAC;GAGH,OAAO;EACT;EAEA,SAAS,UAAU,KAAK,KAAK;GAC3B,IAAI,SAAS,UAAU,GAAG,CAAC,CAAC,YAAY;GAExC,IAAI,CAAC,QACH,OAAO;GAGT,IAAI,SAAS,WAAW,GAAG;GAC3B,IAAI,WAAW;IACb,SAAU,OAAO;KACf,QAAQ,MAAM,QAAQ,GAAA,CAAI,YAAY,MAAM;IAC9C;IACA,SAAU,OAAO;KACf,QAAQ,MAAM,MAAM,GAAA,CAAI,YAAY,MAAM;IAC5C;IACA,SAAU,OAAO;KACf,OAAO,WAAW,KAAK,CAAC,CAAC,YAAY,MAAM;IAC7C;GACF;GAEA,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KACnC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,SAAS,EAAE,CAAC,OAAO,EAAE,GACvB,OAAO,OAAO;GAKpB,OAAO;EACT;;;;;;;;EASA,eAAe,cAAc,WAAW,MAAM;GAC5C,IAAI,OAAO,cAAc,YACvB,OAAO;GAGT,IAAI;IACF,IAAI,WAAW,MAAM,UAAU,MAAM;KAAE,QAAQ;KAAQ,aAAa;KAAe,UAAU;IAAS,CAAC;IAEvG,OAAO,SAAS,WAAW,OAAO,SAAS,WAAW;GACxD,SAAS,OAAO;IACd,OAAO;GACT;EACF;EAEA,SAAS,MAAM,OAAO,MAAM;GAC1B,MAAM,cAAc,IAAI,MAAM,MAAM,EAAE,SAAS,KAAK,CAAC,CAAC;EACxD;;;;;;;;EASA,SAAS,gBAAgB,SAAS;GAChC,IAAI,MAAM,QAAQ;GAClB,IAAI,MAAM,QAAQ;GAClB,IAAI,kBAAkB,OAAO,QAAQ,oBAAoB,WAAW,QAAQ,kBAAkB;GAE9F,OAAO;IACL;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MAAE,MAAM;MAAU,YAAY,CAAC;KAAE;KAC9C,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,WAAY;MACnB,IAAI,OAAO,IAAI,cAAc,UAAU,WAAW;MAClD,IAAI,WAAW,CAAC;MAChB,IAAI,MAAM,IAAI,iBAAiB,UAAU,QAAQ;MAEjD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,SAAS,SAAS,cAAc,KAAK;OACrE,IAAI,OAAO,UAAU,IAAI,EAAE,CAAC,WAAW;OAEvC,IAAI,MACF,SAAS,KAAK;QAAE,QAAQ,IAAI,EAAE,CAAC,WAAW,GAAA,CAAI,YAAY,MAAM,OAAO,IAAI;QAAS;OAAK,CAAC;MAE9F;MAEA,OAAQ;OACN,OAAO,IAAI;OACX,KAAK,IAAI;OACT,aAAa,OAAO,KAAK,aAAa,SAAS,IAAI;OACzC;OACV,SAAS,CAAC,CAAC,QAAQ;MACrB;KACF;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MAAE,MAAM;MAAU,YAAY,CAAC;KAAE;KAC9C,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,WAAY;MACnB,OAAO,YAAY,GAAG;KACxB;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MACX,MAAM;MACN,YAAY,EACV,QAAQ;OAAE,MAAM;OAAU,aAAa;MAAmD,EAC5F;KACF;KACA,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,SAAU,OAAO;MACxB,IAAI,SAAS,UAAU,SAAS,MAAM,MAAM,CAAC,CAAC,YAAY;MAC1D,IAAI,uBAAO,IAAI,IAAI;MACnB,IAAI,QAAQ,CAAC;MACb,IAAI,MAAM,IAAI,iBAAiB,UAAU,KAAK;MAE9C,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,UAAU,MAAM,SAAS,WAAW,KAAK;OAC/D,IAAI,MAAM,kBAAkB,IAAI,EAAE,CAAC,aAAa,MAAM,GAAG,GAAG;OAC5D,IAAI,OAAO,UAAU,IAAI,EAAE,CAAC,WAAW;OAEvC,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,KAAM,UAAU,KAAK,YAAY,CAAC,CAAC,QAAQ,MAAM,IAAI,GACzF;OAGF,KAAK,IAAI,IAAI,IAAI;OACjB,MAAM,KAAK;QAAQ;QAAM,KAAK,IAAI;OAAK,CAAC;MAC1C;MAEA,OAAQ,EAAS,MAAM;KACzB;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MACX,MAAM;MACN,YAAY,EACV,KAAK;OAAE,MAAM;OAAU,aAAa;MAAmH,EACzJ;MACA,UAAU,CAAC,KAAK;KAClB;KACA,SAAS,eAAgB,OAAO;MAC9B,IAAI,UAAU,UAAU,SAAS,MAAM,GAAG;MAE1C,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,wEAAwE;MAK1F,IAAI,QAAQ,OAAO,CAAC,MAAM,OAAO,CAAC,iBAAiB,KAAK,OAAO,GAC7D,MAAM,IAAI,MAAM,OAAM,UAAU,qLAAkL;MAGpN,IAAI,MAAM,kBAAkB,SAAS,GAAG;MAExC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,2FAA2F;MAG7G,IAAI,MAAM,cAAc,QAAQ,OAAO,IAAI,IAAI,GAC7C,MAAM,IAAI,MAAM,+BAA8B,IAAI,WAAW,IAAI,SAAS,kFAAiF;MAM7J,WAAW,WAAY;OACrB,IAAI,OAAO,IAAI,IAAI;MACrB,GAAG,eAAe;MAElB,OAAQ,EAAE,aAAa,IAAI,KAAK;KAClC;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MAAE,MAAM;MAAU,YAAY,CAAC;KAAE;KAC9C,aAAa,EAAE,cAAc,KAAK;KAClC,SAAS,WAAY;MACnB,OAAQ,EAAE,QAAQ,WAAW,GAAG,CAAC,CAAC,IAAI,aAAa,EAAE;KACvD;IACF;IACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;KACb,aAAa;MACX,MAAM;MACN,YAAY;OACV,OAAO;QAAE,MAAM;QAAU,aAAa;OAAqE;OAC3G,OAAO,EAAE,aAAa,mFAAoF;MAC5G;MACA,UAAU,CAAC,SAAS,OAAO;KAC7B;KACA,SAAS,SAAU,OAAO;MACxB,IAAI,QAAQ,UAAU,KAAK,SAAS,MAAM,KAAK;MAE/C,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,8CAA6C,UAAU,SAAS,MAAM,KAAK,IAAI,KAAI;MAGrG,IAAI,OAAO,UAAU,KAAK;MAC1B,IAAI,QAAQ,MAAM;MAElB,IAAI,SAAS,cAAc,SAAS,SAClC,MAAM,UAAU,UAAU,QAAQ,OAAO,KAAK,CAAC,CAAC,YAAY,MAAM;WAC7D,IAAI,SAAS,UAAU;OAC5B,IAAI,SAAS,UAAU,KAAK,CAAC,CAAC,YAAY;OAC1C,IAAI,SAAS,MAAM,UAAU,KAAK,KAAK,MAAM,WAAW,CAAC,GAAG,SAAU,WAAW;QAC/E,OAAO,OAAO,UAAU,KAAK,CAAC,CAAC,YAAY,MAAM,UAAU,UAAU,UAAU,IAAI,CAAC,CAAC,YAAY,MAAM;OACzG,CAAC;OAED,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,gBAAe,WAAW,KAAK,IAAI,wBAAsB,UAAU,KAAK,IAAI,KAAI;OAGlG,MAAM,QAAQ,OAAO;MACvB,OACE,MAAM,QAAQ,SAAS,OAAO,KAAK,OAAO,KAAK;MAGjD,MAAM,OAAO,OAAO;MACpB,MAAM,OAAO,QAAQ;MAErB,OAAQ,EAAE,OAAO,cAAc,KAAK,EAAE;KACxC;IACF;GACF;EACF;;;;;;;;;EAUA,SAAS,SAAS,cAAc,SAAS,QAAQ;GAC/C,IAAI,QAAQ,gBAAgB,IAAI,YAAY;GAE5C,IAAI,CAAC,OAAO;IACV,wBAAQ,IAAI,IAAI;IAChB,gBAAgB,IAAI,cAAc,KAAK;GACzC;GAEA,OAAO,QAAQ,IAAI,gBAAgB,OAAO,CAAC,CAAC,IAAI,SAAU,MAAM;IAC9D,OAAO,aAAa,aAAa,MAAM,SAAS,EAAU,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,WAAY;KACxF,IAAI,UAAU,OAAO,SACnB;KAGF,MAAM,IAAI,KAAK,MAAM,IAAI;KAEzB,IAAI,QACF,OAAO,iBAAiB,SAAS,WAAY;MAC3C,IAAI,MAAM,IAAI,KAAK,IAAI,MAAM,MAC3B,MAAM,OAAO,KAAK,IAAI;KAE1B,GAAG,EAAE,MAAM,KAAK,CAAC;IAErB,GAAG,SAAU,OAAO;KAClB,QAAQ,KAAK,qBAAoB,KAAK,OAAO,+BAA8B,KAAK;IAClF,CAAC;GACH,CAAC,CAAC;EACJ;;;;;;;;EASA,SAAS,kBAAkB,cAAc,MAAM;GAC7C,IAAI,QAAQ,eAAe,gBAAgB,IAAI,YAAY,IAAI;GAE/D,OAAQ,SAAS,MAAM,IAAI,IAAI,KAAM;EACvC;EAEA,OAAO,2BAA2B;GACrB;GACM;GACP;GACS;EACrB;EAEA,IAAI,OAAO,YAAY,OAAO,SAAS,cAAc;GACnD,IAAI,SAAS,OAAO,mBAAmB,OAAO,gBAAgB;GAE9D,SAAS,OAAO,SAAS,cAAc;IACrC,KAAK,OAAO;IACZ,KAAK,OAAO;IACZ,OAAO,OAAO,OAAO,UAAU,aAAa,OAAO,MAAM,KAAK,MAAM,IAAI;IACxE,SAAS,CAAC,EAAE,UAAU,OAAO;GAC/B,CAAC;EACH;CACF,EAAA,CAAG"}
@@ -1,2 +1,3 @@
1
- (function(){(function(){"use strict";var e={description:`meta[name="description"]`,headings:`h1, h2`,links:`nav a[href], header a[href], aside a[href], [role="navigation"] a[href]`,fields:`form input, form select, form textarea`},t=[`hidden`,`password`,`file`,`submit`,`button`,`reset`,`image`],n=[`__RequestVerificationToken`];function r(e){return(e==null?``:String(e)).replace(/\s+/g,` `).trim()}function i(e,t){if(!e)return null;try{var n=new URL(e,t.href);return n.origin===t.origin?n:null}catch{return null}}function a(e){var t=(e.tagName||``).toLowerCase();return t===`select`||t===`textarea`?t:(e.type||`text`).toLowerCase()}function o(e){if(e.labels&&e.labels.length>0){var t=r(e.labels[0].textContent);if(t)return t}return r(e.getAttribute(`aria-label`))||r(e.getAttribute(`placeholder`))}function s(e){return!e.disabled&&t.indexOf(a(e))<0&&n.indexOf(e.name)<0}function c(t){for(var n=[],r=t.querySelectorAll(e.fields),i=0;i<r.length&&n.length<200;i++)s(r[i])&&n.push(r[i]);return n}function l(e){var t=a(e),n={key:e.name||e.id,name:e.name,id:e.id,label:o(e),type:t,value:t===`checkbox`||t===`radio`?!!e.checked:String(e.value==null?``:e.value).substring(0,2e3),required:!!e.required};return t===`select`&&(n.options=Array.prototype.map.call(e.options||[],function(e){return{value:e.value,text:r(e.text),selected:!!e.selected}})),n}function u(e,t){var n=r(t).toLowerCase();if(!n)return null;for(var i=c(e),a=[function(e){return(e.name||``).toLowerCase()===n},function(e){return(e.id||``).toLowerCase()===n},function(e){return o(e).toLowerCase()===n}],s=0;s<a.length;s++)for(var l=0;l<i.length;l++)if(a[s](i[l]))return i[l];return null}function d(e,t){e.dispatchEvent(new Event(t,{bubbles:!0}))}function f(t){var n=t.doc,s=t.loc;return[{name:`page_get_info`,title:`Get page info`,description:`Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){for(var i=n.querySelector(e.description),a=[],o=n.querySelectorAll(e.headings),c=0;c<o.length&&a.length<20;c++){var l=r(o[c].textContent);l&&a.push({level:(o[c].tagName||``).toUpperCase()===`H1`?1:2,text:l})}return{title:n.title,url:s.href,description:i?i.getAttribute(`content`):null,headings:a,isAdmin:!!t.isAdmin}}},{name:`page_list_links`,title:`List page links`,description:`Lists the links in the page's menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.`,inputSchema:{type:`object`,properties:{filter:{type:`string`,description:`Only list links whose text contains these words.`}}},annotations:{readOnlyHint:!0},execute:function(t){for(var a=r(t&&t.filter).toLowerCase(),o=new Set,c=[],l=n.querySelectorAll(e.links),u=0;u<l.length&&c.length<200;u++){var d=i(l[u].getAttribute(`href`),s),f=r(l[u].textContent);!d||!f||o.has(d.href)||a&&f.toLowerCase().indexOf(a)<0||(o.add(d.href),c.push({text:f,url:d.href}))}return{links:c}}},{name:`page_navigate`,title:`Open page`,description:`Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.`,inputSchema:{type:`object`,properties:{url:{type:`string`,description:`The address to open, relative to the site or absolute on the same site.`}},required:[`url`]},execute:function(e){var t=i(e&&e.url,s);if(!t)throw Error(`Only addresses on this site can be opened.`);return s.assign(t.href),{navigatedTo:t.href}}},{name:`page_list_form_fields`,title:`List form fields`,description:`Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){return{fields:c(n).map(l)}}},{name:`page_set_form_field`,title:`Set form field`,description:`Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.`,inputSchema:{type:`object`,properties:{field:{type:`string`,description:`The field's name, id or label, as page_list_form_fields lists it.`},value:{description:`The value. For a check box, true or false. For a list, a choice's value or text.`}},required:[`field`,`value`]},execute:function(e){var t=u(n,e&&e.field);if(!t)throw Error(`No editable field on the page is named "`+r(e&&e.field)+`".`);var i=a(t),s=e.value;if(i===`checkbox`||i===`radio`)t.checked=s===!0||String(s).toLowerCase()===`true`;else if(i===`select`){var c=r(s).toLowerCase(),f=Array.prototype.find.call(t.options||[],function(e){return String(e.value).toLowerCase()===c||r(e.text).toLowerCase()===c});if(!f)throw Error(`The list "`+o(t)+`" has no choice "`+r(s)+`".`);t.value=f.value}else t.value=s==null?``:String(s);return d(t,`input`),d(t,`change`),{field:l(t)}}}]}function p(e,t,n){return Promise.all(f(t).map(function(t){return e.registerTool(t,n?{signal:n}:{}).catch(function(e){console.warn(`The page tool "`+t.name+`" could not be registered.`,e)})}))}if(window.CrestAppsWebMcpPageTools={selectors:e,createPageTools:f,register:p},window.document&&window.document.modelContext){var m=window.crestAppsWebMcp&&window.crestAppsWebMcp.config;p(window.document.modelContext,{doc:window.document,loc:window.location,isAdmin:!!(m&&m.isAdmin)})}})()})();
1
+ (function(){(function(){"use strict";var e=8e3,t={description:`meta[name="description"]`,headings:`h1, h2`,main:`main, [role="main"]`,ignored:`script, style, noscript, template, svg, nav, aside, select, textarea, datalist, dialog:not([open]), [hidden], [aria-hidden="true"], [role="navigation"], [data-webmcp-ignore], [class*="ai-chat-widget"]`,links:`nav a[href], header a[href], aside a[href], [role="navigation"] a[href]`,fields:`form input, form select, form textarea`},n=[`hidden`,`password`,`file`,`submit`,`button`,`reset`,`image`],r=[`__RequestVerificationToken`];function i(e){return(e==null?``:String(e)).replace(/\s+/g,` `).trim()}var a=/^[A-Za-z][A-Za-z0-9+.-]*:/,o=new WeakMap,s=`ADDRESS.ARTICLE.BLOCKQUOTE.BR.DD.DETAILS.DIV.DL.DT.FIELDSET.FIGCAPTION.FIGURE.FOOTER.H1.H2.H3.H4.H5.H6.HEADER.HR.LI.OL.P.PRE.SECTION.SUMMARY.UL`.split(`.`);function c(e){return e.hidden||typeof e.matches==`function`&&e.matches(t.ignored)?!0:typeof e.checkVisibility==`function`&&!e.checkVisibility()}function l(e){var t=i(e.textContent);return t.length>300?t.slice(0,300)+`…`:t}function u(e){var t=Array.prototype.slice.call(e.rows||[]),n=[];return t.length&&Array.prototype.every.call(t[0].cells||[],function(e){return(e.tagName||``).toUpperCase()===`TH`})&&(n=Array.prototype.map.call(t.shift().cells,l)),{caption:(e.caption?i(e.caption.textContent):``)||null,headers:n,rows:t.slice(0,50).map(function(e){return Array.prototype.map.call(e.cells||[],l)}),totalRows:t.length}}function d(n){var r=n.querySelector(t.main)||n.body,a=[],o=[],l=[],d=0;function f(){var e=i(o.join(` `));e&&a.push(e),o=[]}function p(e){if(e.nodeType===3)o.push(e.nodeValue);else if(!(e.nodeType!==1||c(e))){var t=(e.tagName||``).toUpperCase();if(t===`TABLE`)f(),d++,l.length<10&&(l.push(u(e)),a.push(`[table `+l.length+`]`));else{var n=s.indexOf(t)>=0;n&&f();for(var r=e.childNodes||[],i=0;i<r.length;i++)p(r[i]);n&&f()}}}r&&(p(r),f());var m=a.join(`
2
+ `),h=m.length>e||d>l.length||l.some(function(e){return e.totalRows>e.rows.length});return{text:m.length>e?m.slice(0,e)+`…`:m,tables:l,truncated:h}}function f(e,t){if(!e)return null;try{var n=new URL(e,t.href);return n.origin===t.origin?n:null}catch{return null}}function p(e){var t=(e.tagName||``).toLowerCase();return t===`select`||t===`textarea`?t:(e.type||`text`).toLowerCase()}function m(e){if(e.labels&&e.labels.length>0){var t=i(e.labels[0].textContent);if(t)return t}return i(e.getAttribute(`aria-label`))||i(e.getAttribute(`placeholder`))}function h(e){return!e.disabled&&n.indexOf(p(e))<0&&r.indexOf(e.name)<0}function g(e){for(var n=[],r=e.querySelectorAll(t.fields),i=0;i<r.length&&n.length<200;i++)h(r[i])&&n.push(r[i]);return n}function _(e){var t=p(e),n={key:e.name||e.id,name:e.name,id:e.id,label:m(e),type:t,value:t===`checkbox`||t===`radio`?!!e.checked:String(e.value==null?``:e.value).substring(0,2e3),required:!!e.required};return t===`select`&&(n.options=Array.prototype.map.call(e.options||[],function(e){return{value:e.value,text:i(e.text),selected:!!e.selected}})),n}function v(e,t){var n=i(t).toLowerCase();if(!n)return null;for(var r=g(e),a=[function(e){return(e.name||``).toLowerCase()===n},function(e){return(e.id||``).toLowerCase()===n},function(e){return m(e).toLowerCase()===n}],o=0;o<a.length;o++)for(var s=0;s<r.length;s++)if(a[o](r[s]))return r[s];return null}async function y(e,t){if(typeof e!=`function`)return!1;try{var n=await e(t,{method:`HEAD`,credentials:`same-origin`,redirect:`follow`});return n.status===404||n.status===410}catch{return!1}}function b(e,t){e.dispatchEvent(new Event(t,{bubbles:!0}))}function x(e){var n=e.doc,r=e.loc,o=typeof e.navigateDelayMs==`number`?e.navigateDelayMs:400;return[{name:`page_get_info`,title:`Get page info`,description:`Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page. To read what the page shows, use page_read_content.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){for(var a=n.querySelector(t.description),o=[],s=n.querySelectorAll(t.headings),c=0;c<s.length&&o.length<20;c++){var l=i(s[c].textContent);l&&o.push({level:(s[c].tagName||``).toUpperCase()===`H1`?1:2,text:l})}return{title:n.title,url:r.href,description:a?a.getAttribute(`content`):null,headings:o,isAdmin:!!e.isAdmin}}},{name:`page_read_content`,title:`Read page content`,description:`Reads what the page shows in its main area: its text, and its tables as headers and rows (for example the records a list page shows). Menus, forms and hidden parts are left out; use page_list_links and page_list_form_fields for those.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){return d(n)}},{name:`page_list_links`,title:`List page links`,description:`Lists the links in the page's menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.`,inputSchema:{type:`object`,properties:{filter:{type:`string`,description:`Only list links whose text contains these words.`}}},annotations:{readOnlyHint:!0},execute:function(e){for(var a=i(e&&e.filter).toLowerCase(),o=new Set,s=[],c=n.querySelectorAll(t.links),l=0;l<c.length&&s.length<200;l++){var u=f(c[l].getAttribute(`href`),r),d=i(c[l].textContent);!u||!d||o.has(u.href)||a&&d.toLowerCase().indexOf(a)<0||(o.add(u.href),s.push({text:d,url:u.href}))}return{links:s}}},{name:`page_navigate`,title:`Open page`,description:`Opens another page of this site in the current tab. Only addresses on this site are allowed; take them from page_list_links, and start a site path with /. Unsaved changes on the current page are lost.`,inputSchema:{type:`object`,properties:{url:{type:`string`,description:`The address to open: a path that starts with "/", or a full address on this site, as page_list_links lists them.`}},required:[`url`]},execute:async function(t){var n=i(t&&t.url);if(!n)throw Error(`Give the address of the page to open. Call page_list_links to find it.`);if(n.charAt(0)!==`/`&&!a.test(n))throw Error(`"`+n+`" is not an address on this site. Give a path that starts with "/" or a full address on this site, as page_list_links lists them. Call page_list_links to find the page to open.`);var s=f(n,r);if(!s)throw Error(`Only addresses on this site can be opened. Call page_list_links to find the page to open.`);if(await y(e.fetch,s.href))throw Error(`The site has no page at "`+s.pathname+s.search+`" (404). Do not guess addresses: call page_list_links to find the page to open.`);return setTimeout(function(){r.assign(s.href)},o),{navigatedTo:s.href}}},{name:`page_list_form_fields`,title:`List form fields`,description:`Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){return{fields:g(n).map(_)}}},{name:`page_set_form_field`,title:`Set form field`,description:`Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.`,inputSchema:{type:`object`,properties:{field:{type:`string`,description:`The field's name, id or label, as page_list_form_fields lists it.`},value:{description:`The value. For a check box, true or false. For a list, a choice's value or text.`}},required:[`field`,`value`]},execute:function(e){var t=v(n,e&&e.field);if(!t)throw Error(`No editable field on the page is named "`+i(e&&e.field)+`".`);var r=p(t),a=e.value;if(r===`checkbox`||r===`radio`)t.checked=a===!0||String(a).toLowerCase()===`true`;else if(r===`select`){var o=i(a).toLowerCase(),s=Array.prototype.find.call(t.options||[],function(e){return String(e.value).toLowerCase()===o||i(e.text).toLowerCase()===o});if(!s)throw Error(`The list "`+m(t)+`" has no choice "`+i(a)+`".`);t.value=s.value}else t.value=a==null?``:String(a);return b(t,`input`),b(t,`change`),{field:_(t)}}}]}function S(e,t,n){var r=o.get(e);return r||(r=new Map,o.set(e,r)),Promise.all(x(t).map(function(t){return e.registerTool(t,n?{signal:n}:{}).then(function(){n&&n.aborted||(r.set(t.name,t),n&&n.addEventListener(`abort`,function(){r.get(t.name)===t&&r.delete(t.name)},{once:!0}))},function(e){console.warn(`The page tool "`+t.name+`" could not be registered.`,e)})}))}function C(e,t){var n=e?o.get(e):null;return n&&n.get(t)||null}if(window.CrestAppsWebMcpPageTools={selectors:t,createPageTools:x,register:S,getRegisteredTool:C},window.document&&window.document.modelContext){var w=window.crestAppsWebMcp&&window.crestAppsWebMcp.config;S(window.document.modelContext,{doc:window.document,loc:window.location,fetch:typeof window.fetch==`function`?window.fetch.bind(window):void 0,isAdmin:!!(w&&w.isAdmin)})}})()})();
2
3
  //# sourceMappingURL=webmcp-page-tools.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-page-tools.min.js","sources":["../../Assets/js/webmcp-page-tools.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP page tools.\n *\n * Tools that work on the page the user sees: what the page is, where its menus lead, opening another page of the same\n * site, and reading and filling its forms. They never submit a form and never leave the site, so the user always\n * reviews and saves the changes an agent makes.\n *\n * The tools are registered with the standard `document.modelContext.registerTool`, so any WebMCP agent can use them:\n * the browser's own assistant, or the CrestApps AI chat. As WebMCP defines, each answers a JSON-serializable value and\n * reports a failure by rejecting. Load webmcp-polyfill.js first in browsers without native WebMCP.\n */\n\n(function () {\n 'use strict';\n\n var MAX_HEADINGS = 20;\n var MAX_LINKS = 200;\n var MAX_FIELDS = 200;\n var MAX_VALUE_LENGTH = 2000;\n\n // The selectors the tools read the page with.\n var selectors = {\n description: 'meta[name=\"description\"]',\n headings: 'h1, h2',\n links: 'nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]',\n fields: 'form input, form select, form textarea',\n };\n\n // Field types an agent never reads or fills.\n var SKIPPED_TYPES = ['hidden', 'password', 'file', 'submit', 'button', 'reset', 'image'];\n var SKIPPED_NAMES = ['__RequestVerificationToken'];\n\n function cleanText(value) {\n return (value == null ? '' : String(value)).replace(/\\s+/g, ' ').trim();\n }\n\n\n function resolveSameOrigin(href, loc) {\n if (!href) {\n return null;\n }\n\n try {\n var url = new URL(href, loc.href);\n\n return url.origin === loc.origin ? url : null;\n } catch (error) {\n return null;\n }\n }\n\n function fieldType(field) {\n var tag = (field.tagName || '').toLowerCase();\n\n if (tag === 'select' || tag === 'textarea') {\n return tag;\n }\n\n return (field.type || 'text').toLowerCase();\n }\n\n function fieldLabel(field) {\n if (field.labels && field.labels.length > 0) {\n var label = cleanText(field.labels[0].textContent);\n\n if (label) {\n return label;\n }\n }\n\n return cleanText(field.getAttribute('aria-label')) || cleanText(field.getAttribute('placeholder'));\n }\n\n function isListed(field) {\n return !field.disabled &&\n SKIPPED_TYPES.indexOf(fieldType(field)) < 0 &&\n SKIPPED_NAMES.indexOf(field.name) < 0;\n }\n\n function listFields(doc) {\n var fields = [];\n var all = doc.querySelectorAll(selectors.fields);\n\n for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) {\n if (isListed(all[i])) {\n fields.push(all[i]);\n }\n }\n\n return fields;\n }\n\n function describeField(field) {\n var type = fieldType(field);\n var description = {\n key: field.name || field.id,\n name: field.name,\n id: field.id,\n label: fieldLabel(field),\n type: type,\n value: type === 'checkbox' || type === 'radio'\n ? !!field.checked\n : String(field.value == null ? '' : field.value).substring(0, MAX_VALUE_LENGTH),\n required: !!field.required,\n };\n\n if (type === 'select') {\n description.options = Array.prototype.map.call(field.options || [], function (option) {\n return { value: option.value, text: cleanText(option.text), selected: !!option.selected };\n });\n }\n\n return description;\n }\n\n function findField(doc, key) {\n var wanted = cleanText(key).toLowerCase();\n\n if (!wanted) {\n return null;\n }\n\n var fields = listFields(doc);\n var matchers = [\n function (field) {\n return (field.name || '').toLowerCase() === wanted;\n },\n function (field) {\n return (field.id || '').toLowerCase() === wanted;\n },\n function (field) {\n return fieldLabel(field).toLowerCase() === wanted;\n },\n ];\n\n for (var m = 0; m < matchers.length; m++) {\n for (var i = 0; i < fields.length; i++) {\n if (matchers[m](fields[i])) {\n return fields[i];\n }\n }\n }\n\n return null;\n }\n\n function raise(field, type) {\n field.dispatchEvent(new Event(type, { bubbles: true }));\n }\n\n /**\n * Creates the page tools for a document.\n *\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n */\n function createPageTools(options) {\n var doc = options.doc;\n var loc = options.loc;\n\n return [\n {\n name: 'page_get_info',\n title: 'Get page info',\n description: 'Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n var meta = doc.querySelector(selectors.description);\n var headings = [];\n var all = doc.querySelectorAll(selectors.headings);\n\n for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {\n var text = cleanText(all[i].textContent);\n\n if (text) {\n headings.push({ level: (all[i].tagName || '').toUpperCase() === 'H1' ? 1 : 2, text: text });\n }\n }\n\n return ({\n title: doc.title,\n url: loc.href,\n description: meta ? meta.getAttribute('content') : null,\n headings: headings,\n isAdmin: !!options.isAdmin,\n });\n },\n },\n {\n name: 'page_list_links',\n title: 'List page links',\n description: 'Lists the links in the page\\'s menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: { type: 'string', description: 'Only list links whose text contains these words.' },\n },\n },\n annotations: { readOnlyHint: true },\n execute: function (input) {\n var filter = cleanText(input && input.filter).toLowerCase();\n var seen = new Set();\n var links = [];\n var all = doc.querySelectorAll(selectors.links);\n\n for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {\n var url = resolveSameOrigin(all[i].getAttribute('href'), loc);\n var text = cleanText(all[i].textContent);\n\n if (!url || !text || seen.has(url.href) || (filter && text.toLowerCase().indexOf(filter) < 0)) {\n continue;\n }\n\n seen.add(url.href);\n links.push({ text: text, url: url.href });\n }\n\n return ({ links: links });\n },\n },\n {\n name: 'page_navigate',\n title: 'Open page',\n description: 'Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The address to open, relative to the site or absolute on the same site.' },\n },\n required: ['url'],\n },\n execute: function (input) {\n var url = resolveSameOrigin(input && input.url, loc);\n\n if (!url) {\n throw new Error('Only addresses on this site can be opened.');\n }\n\n loc.assign(url.href);\n\n return ({ navigatedTo: url.href });\n },\n },\n {\n name: 'page_list_form_fields',\n title: 'List form fields',\n description: 'Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return ({ fields: listFields(doc).map(describeField) });\n },\n },\n {\n name: 'page_set_form_field',\n title: 'Set form field',\n description: 'Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.',\n inputSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', description: 'The field\\'s name, id or label, as page_list_form_fields lists it.' },\n value: { description: 'The value. For a check box, true or false. For a list, a choice\\'s value or text.' },\n },\n required: ['field', 'value'],\n },\n execute: function (input) {\n var field = findField(doc, input && input.field);\n\n if (!field) {\n throw new Error('No editable field on the page is named \"' + cleanText(input && input.field) + '\".');\n }\n\n var type = fieldType(field);\n var value = input.value;\n\n if (type === 'checkbox' || type === 'radio') {\n field.checked = value === true || String(value).toLowerCase() === 'true';\n } else if (type === 'select') {\n var wanted = cleanText(value).toLowerCase();\n var option = Array.prototype.find.call(field.options || [], function (candidate) {\n return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;\n });\n\n if (!option) {\n throw new Error('The list \"' + fieldLabel(field) + '\" has no choice \"' + cleanText(value) + '\".');\n }\n\n field.value = option.value;\n } else {\n field.value = value == null ? '' : String(value);\n }\n\n raise(field, 'input');\n raise(field, 'change');\n\n return ({ field: describeField(field) });\n },\n },\n ];\n }\n\n /**\n * Registers the page tools with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext (document.modelContext).\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n */\n function register(modelContext, options, signal) {\n return Promise.all(createPageTools(options).map(function (tool) {\n return modelContext.registerTool(tool, signal ? { signal: signal } : {}).catch(function (error) {\n console.warn('The page tool \"' + tool.name + '\" could not be registered.', error);\n });\n }));\n }\n\n window.CrestAppsWebMcpPageTools = {\n selectors: selectors,\n createPageTools: createPageTools,\n register: register,\n };\n\n if (window.document && window.document.modelContext) {\n var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;\n\n register(window.document.modelContext, {\n doc: window.document,\n loc: window.location,\n isAdmin: !!(config && config.isAdmin),\n });\n }\n})();\n"],"names":["selectors","SKIPPED_TYPES","SKIPPED_NAMES","cleanText","value","resolveSameOrigin","href","url","loc","fieldType","tag","field","fieldLabel","label","isListed","listFields","fields","all","doc","describeField","type","description","option","findField","wanted","key","matchers","m","i","raise","createPageTools","options","meta","headings","text","filter","input","seen","links","candidate","register","modelContext","tool","signal","error","config"],"mappings":"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"}
1
+ {"version":3,"file":"webmcp-page-tools.min.js","sources":["../../Assets/js/webmcp-page-tools.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP page tools.\n *\n * Tools that work on the page the user sees: what the page is, what it shows, where its menus lead, opening another\n * page of the same site, and reading and filling its forms. They never submit a form and never leave the site, so the user always\n * reviews and saves the changes an agent makes.\n *\n * The tools are registered with the standard `document.modelContext.registerTool`, so any WebMCP agent can use them:\n * the browser's own assistant, or the CrestApps AI chat. As WebMCP defines, each answers a JSON-serializable value and\n * reports a failure by rejecting. Load webmcp-polyfill.js first in browsers without native WebMCP.\n */\n\n(function () {\n 'use strict';\n\n var MAX_HEADINGS = 20;\n var MAX_LINKS = 200;\n var MAX_FIELDS = 200;\n var MAX_VALUE_LENGTH = 2000;\n var MAX_CONTENT_LENGTH = 8000;\n var MAX_TABLES = 10;\n var MAX_TABLE_ROWS = 50;\n var MAX_CELL_LENGTH = 300;\n\n // The selectors the tools read the page with.\n var selectors = {\n description: 'meta[name=\"description\"]',\n headings: 'h1, h2',\n main: 'main, [role=\"main\"]',\n // What page_read_content leaves out of the page's text: scripts, menus, the choices and text of form controls\n // (page_list_form_fields reads them), hidden parts, the chat itself, and anything a page marks with\n // data-webmcp-ignore. A form's other content is read: admin pages often wrap a whole list in one, for bulk actions.\n ignored: 'script, style, noscript, template, svg, nav, aside, select, textarea, datalist, dialog:not([open]), [hidden], [aria-hidden=\"true\"], [role=\"navigation\"], [data-webmcp-ignore], [class*=\"ai-chat-widget\"]',\n links: 'nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]',\n fields: 'form input, form select, form textarea',\n };\n\n // Field types an agent never reads or fills.\n var SKIPPED_TYPES = ['hidden', 'password', 'file', 'submit', 'button', 'reset', 'image'];\n var SKIPPED_NAMES = ['__RequestVerificationToken'];\n\n function cleanText(value) {\n return (value == null ? '' : String(value)).replace(/\\s+/g, ' ').trim();\n }\n\n\n // An address that starts with a scheme, such as \"https:\" or \"javascript:\".\n var ABSOLUTE_ADDRESS = /^[A-Za-z][A-Za-z0-9+.-]*:/;\n\n // The tools register() registered, by ModelContext and name, so the CrestApps chat can run them through their own\n // execute and tell the model why one failed (executeTool reports every failure as \"Tool execution failed\").\n var registeredTools = new WeakMap();\n\n // Elements that start a new line in the text page_read_content reads.\n var BLOCK_TAGS = ['ADDRESS', 'ARTICLE', 'BLOCKQUOTE', 'BR', 'DD', 'DETAILS', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', 'FOOTER', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', 'HR', 'LI', 'OL', 'P', 'PRE', 'SECTION', 'SUMMARY', 'UL'];\n\n function isIgnored(node) {\n if (node.hidden || (typeof node.matches === 'function' && node.matches(selectors.ignored))) {\n return true;\n }\n\n // Parts the page hides with CSS, such as closed menus and modals.\n return typeof node.checkVisibility === 'function' && !node.checkVisibility();\n }\n\n function cellText(cell) {\n var text = cleanText(cell.textContent);\n\n return text.length > MAX_CELL_LENGTH ? text.slice(0, MAX_CELL_LENGTH) + '…' : text;\n }\n\n function readTable(table) {\n var rows = Array.prototype.slice.call(table.rows || []);\n var headers = [];\n\n if (rows.length && Array.prototype.every.call(rows[0].cells || [], function (cell) { return (cell.tagName || '').toUpperCase() === 'TH'; })) {\n headers = Array.prototype.map.call(rows.shift().cells, cellText);\n }\n\n var caption = table.caption ? cleanText(table.caption.textContent) : '';\n\n return {\n caption: caption || null,\n headers: headers,\n rows: rows.slice(0, MAX_TABLE_ROWS).map(function (row) {\n return Array.prototype.map.call(row.cells || [], cellText);\n }),\n totalRows: rows.length,\n };\n }\n\n /**\n * Reads the text and the tables of the page's main region, the way a person reads it: menus, forms, hidden parts\n * and the chat are left out, and each table is read once, as rows, where the text says [table N].\n */\n function readContent(doc) {\n var root = doc.querySelector(selectors.main) || doc.body;\n var lines = [];\n var line = [];\n var tables = [];\n var tableCount = 0;\n\n function endLine() {\n var text = cleanText(line.join(' '));\n\n if (text) {\n lines.push(text);\n }\n\n line = [];\n }\n\n function walk(node) {\n if (node.nodeType === 3) {\n line.push(node.nodeValue);\n\n return;\n }\n\n if (node.nodeType !== 1 || isIgnored(node)) {\n return;\n }\n\n var tag = (node.tagName || '').toUpperCase();\n\n if (tag === 'TABLE') {\n endLine();\n tableCount++;\n\n if (tables.length < MAX_TABLES) {\n tables.push(readTable(node));\n lines.push('[table ' + tables.length + ']');\n }\n\n return;\n }\n\n var isBlock = BLOCK_TAGS.indexOf(tag) >= 0;\n\n if (isBlock) {\n endLine();\n }\n\n var children = node.childNodes || [];\n\n for (var i = 0; i < children.length; i++) {\n walk(children[i]);\n }\n\n if (isBlock) {\n endLine();\n }\n }\n\n if (root) {\n walk(root);\n endLine();\n }\n\n var text = lines.join('\\n');\n var truncated = text.length > MAX_CONTENT_LENGTH || tableCount > tables.length ||\n tables.some(function (table) { return table.totalRows > table.rows.length; });\n\n return {\n text: text.length > MAX_CONTENT_LENGTH ? text.slice(0, MAX_CONTENT_LENGTH) + '…' : text,\n tables: tables,\n truncated: truncated,\n };\n }\n\n function resolveSameOrigin(href, loc) {\n if (!href) {\n return null;\n }\n\n try {\n var url = new URL(href, loc.href);\n\n return url.origin === loc.origin ? url : null;\n } catch (error) {\n return null;\n }\n }\n\n function fieldType(field) {\n var tag = (field.tagName || '').toLowerCase();\n\n if (tag === 'select' || tag === 'textarea') {\n return tag;\n }\n\n return (field.type || 'text').toLowerCase();\n }\n\n function fieldLabel(field) {\n if (field.labels && field.labels.length > 0) {\n var label = cleanText(field.labels[0].textContent);\n\n if (label) {\n return label;\n }\n }\n\n return cleanText(field.getAttribute('aria-label')) || cleanText(field.getAttribute('placeholder'));\n }\n\n function isListed(field) {\n return !field.disabled &&\n SKIPPED_TYPES.indexOf(fieldType(field)) < 0 &&\n SKIPPED_NAMES.indexOf(field.name) < 0;\n }\n\n function listFields(doc) {\n var fields = [];\n var all = doc.querySelectorAll(selectors.fields);\n\n for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) {\n if (isListed(all[i])) {\n fields.push(all[i]);\n }\n }\n\n return fields;\n }\n\n function describeField(field) {\n var type = fieldType(field);\n var description = {\n key: field.name || field.id,\n name: field.name,\n id: field.id,\n label: fieldLabel(field),\n type: type,\n value: type === 'checkbox' || type === 'radio'\n ? !!field.checked\n : String(field.value == null ? '' : field.value).substring(0, MAX_VALUE_LENGTH),\n required: !!field.required,\n };\n\n if (type === 'select') {\n description.options = Array.prototype.map.call(field.options || [], function (option) {\n return { value: option.value, text: cleanText(option.text), selected: !!option.selected };\n });\n }\n\n return description;\n }\n\n function findField(doc, key) {\n var wanted = cleanText(key).toLowerCase();\n\n if (!wanted) {\n return null;\n }\n\n var fields = listFields(doc);\n var matchers = [\n function (field) {\n return (field.name || '').toLowerCase() === wanted;\n },\n function (field) {\n return (field.id || '').toLowerCase() === wanted;\n },\n function (field) {\n return fieldLabel(field).toLowerCase() === wanted;\n },\n ];\n\n for (var m = 0; m < matchers.length; m++) {\n for (var i = 0; i < fields.length; i++) {\n if (matchers[m](fields[i])) {\n return fields[i];\n }\n }\n }\n\n return null;\n }\n\n /**\n * Asks the site whether a page exists, with a HEAD request that downloads nothing. Only a 404 or 410 answer means it\n * does not: a page that refuses HEAD (405), sends to the login page or cannot be reached is opened as asked.\n *\n * @param {Function | undefined} fetchImpl The fetch function, or nothing to skip the check.\n * @param {string} href The address.\n */\n async function pageIsMissing(fetchImpl, href) {\n if (typeof fetchImpl !== 'function') {\n return false;\n }\n\n try {\n var response = await fetchImpl(href, { method: 'HEAD', credentials: 'same-origin', redirect: 'follow' });\n\n return response.status === 404 || response.status === 410;\n } catch (error) {\n return false;\n }\n }\n\n function raise(field, type) {\n field.dispatchEvent(new Event(type, { bubbles: true }));\n }\n\n /**\n * Creates the page tools for a document.\n *\n * @param {{ doc: any, loc: any, isAdmin?: boolean, fetch?: Function, navigateDelayMs?: number }} options The document,\n * its location, whether it is an admin page, the fetch function page_navigate checks that a page exists with (no\n * check without it), and how long page_navigate waits after answering before it opens the page.\n */\n function createPageTools(options) {\n var doc = options.doc;\n var loc = options.loc;\n var navigateDelayMs = typeof options.navigateDelayMs === 'number' ? options.navigateDelayMs : 400;\n\n return [\n {\n name: 'page_get_info',\n title: 'Get page info',\n description: 'Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page. To read what the page shows, use page_read_content.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n var meta = doc.querySelector(selectors.description);\n var headings = [];\n var all = doc.querySelectorAll(selectors.headings);\n\n for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {\n var text = cleanText(all[i].textContent);\n\n if (text) {\n headings.push({ level: (all[i].tagName || '').toUpperCase() === 'H1' ? 1 : 2, text: text });\n }\n }\n\n return ({\n title: doc.title,\n url: loc.href,\n description: meta ? meta.getAttribute('content') : null,\n headings: headings,\n isAdmin: !!options.isAdmin,\n });\n },\n },\n {\n name: 'page_read_content',\n title: 'Read page content',\n description: 'Reads what the page shows in its main area: its text, and its tables as headers and rows (for example the records a list page shows). Menus, forms and hidden parts are left out; use page_list_links and page_list_form_fields for those.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return readContent(doc);\n },\n },\n {\n name: 'page_list_links',\n title: 'List page links',\n description: 'Lists the links in the page\\'s menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: { type: 'string', description: 'Only list links whose text contains these words.' },\n },\n },\n annotations: { readOnlyHint: true },\n execute: function (input) {\n var filter = cleanText(input && input.filter).toLowerCase();\n var seen = new Set();\n var links = [];\n var all = doc.querySelectorAll(selectors.links);\n\n for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {\n var url = resolveSameOrigin(all[i].getAttribute('href'), loc);\n var text = cleanText(all[i].textContent);\n\n if (!url || !text || seen.has(url.href) || (filter && text.toLowerCase().indexOf(filter) < 0)) {\n continue;\n }\n\n seen.add(url.href);\n links.push({ text: text, url: url.href });\n }\n\n return ({ links: links });\n },\n },\n {\n name: 'page_navigate',\n title: 'Open page',\n description: 'Opens another page of this site in the current tab. Only addresses on this site are allowed; take them from page_list_links, and start a site path with /. Unsaved changes on the current page are lost.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The address to open: a path that starts with \"/\", or a full address on this site, as page_list_links lists them.' },\n },\n required: ['url'],\n },\n execute: async function (input) {\n var address = cleanText(input && input.url);\n\n if (!address) {\n throw new Error('Give the address of the page to open. Call page_list_links to find it.');\n }\n\n // A bare relative path, such as \"ArticlesAdmin\", resolves against the current page and usually names a page\n // that does not exist: it is a guess, not an address the site gave.\n if (address.charAt(0) !== '/' && !ABSOLUTE_ADDRESS.test(address)) {\n throw new Error('\"' + address + '\" is not an address on this site. Give a path that starts with \"/\" or a full address on this site, as page_list_links lists them. Call page_list_links to find the page to open.');\n }\n\n var url = resolveSameOrigin(address, loc);\n\n if (!url) {\n throw new Error('Only addresses on this site can be opened. Call page_list_links to find the page to open.');\n }\n\n if (await pageIsMissing(options.fetch, url.href)) {\n throw new Error('The site has no page at \"' + url.pathname + url.search + '\" (404). Do not guess addresses: call page_list_links to find the page to open.');\n }\n\n // Answer first and open the page a moment later, so the agent gets the result before this page (and\n // the chat connection on it) goes away. The chat finishes the reply on the server and shows it on the\n // next page.\n setTimeout(function () {\n loc.assign(url.href);\n }, navigateDelayMs);\n\n return ({ navigatedTo: url.href });\n },\n },\n {\n name: 'page_list_form_fields',\n title: 'List form fields',\n description: 'Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return ({ fields: listFields(doc).map(describeField) });\n },\n },\n {\n name: 'page_set_form_field',\n title: 'Set form field',\n description: 'Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.',\n inputSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', description: 'The field\\'s name, id or label, as page_list_form_fields lists it.' },\n value: { description: 'The value. For a check box, true or false. For a list, a choice\\'s value or text.' },\n },\n required: ['field', 'value'],\n },\n execute: function (input) {\n var field = findField(doc, input && input.field);\n\n if (!field) {\n throw new Error('No editable field on the page is named \"' + cleanText(input && input.field) + '\".');\n }\n\n var type = fieldType(field);\n var value = input.value;\n\n if (type === 'checkbox' || type === 'radio') {\n field.checked = value === true || String(value).toLowerCase() === 'true';\n } else if (type === 'select') {\n var wanted = cleanText(value).toLowerCase();\n var option = Array.prototype.find.call(field.options || [], function (candidate) {\n return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;\n });\n\n if (!option) {\n throw new Error('The list \"' + fieldLabel(field) + '\" has no choice \"' + cleanText(value) + '\".');\n }\n\n field.value = option.value;\n } else {\n field.value = value == null ? '' : String(value);\n }\n\n raise(field, 'input');\n raise(field, 'change');\n\n return ({ field: describeField(field) });\n },\n },\n ];\n }\n\n /**\n * Registers the page tools with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext (document.modelContext).\n * @param {{ doc: any, loc: any, isAdmin?: boolean, fetch?: Function }} options The document, its location, whether it\n * is an admin page, and the fetch function page_navigate checks that a page exists with (no check without it).\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n */\n function register(modelContext, options, signal) {\n var tools = registeredTools.get(modelContext);\n\n if (!tools) {\n tools = new Map();\n registeredTools.set(modelContext, tools);\n }\n\n return Promise.all(createPageTools(options).map(function (tool) {\n return modelContext.registerTool(tool, signal ? { signal: signal } : {}).then(function () {\n if (signal && signal.aborted) {\n return;\n }\n\n tools.set(tool.name, tool);\n\n if (signal) {\n signal.addEventListener('abort', function () {\n if (tools.get(tool.name) === tool) {\n tools.delete(tool.name);\n }\n }, { once: true });\n }\n }, function (error) {\n console.warn('The page tool \"' + tool.name + '\" could not be registered.', error);\n });\n }));\n }\n\n /**\n * Answers the page tool register() registered with a ModelContext under a name, with its execute callback, or null\n * when it registered none: another script may have registered a tool of that name first.\n *\n * @param {any} modelContext The ModelContext the tool was registered with.\n * @param {string} name The tool's name.\n */\n function getRegisteredTool(modelContext, name) {\n var tools = modelContext ? registeredTools.get(modelContext) : null;\n\n return (tools && tools.get(name)) || null;\n }\n\n window.CrestAppsWebMcpPageTools = {\n selectors: selectors,\n createPageTools: createPageTools,\n register: register,\n getRegisteredTool: getRegisteredTool,\n };\n\n if (window.document && window.document.modelContext) {\n var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;\n\n register(window.document.modelContext, {\n doc: window.document,\n loc: window.location,\n fetch: typeof window.fetch === 'function' ? window.fetch.bind(window) : undefined,\n isAdmin: !!(config && config.isAdmin),\n });\n }\n})();\n"],"names":["MAX_CONTENT_LENGTH","selectors","SKIPPED_TYPES","SKIPPED_NAMES","cleanText","value","ABSOLUTE_ADDRESS","registeredTools","BLOCK_TAGS","isIgnored","node","cellText","text","cell","readTable","rows","table","headers","row","readContent","root","doc","lines","line","tables","tableCount","endLine","walk","tag","isBlock","children","truncated","resolveSameOrigin","href","url","loc","fieldType","field","fieldLabel","label","isListed","listFields","fields","all","describeField","type","description","option","findField","wanted","key","matchers","m","i","pageIsMissing","fetchImpl","response","raise","createPageTools","options","navigateDelayMs","meta","headings","filter","input","seen","links","address","candidate","register","tools","modelContext","tool","signal","error","getRegisteredTool","name","config"],"mappings":"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"}
package/dist/webmcp.js CHANGED
@@ -131,12 +131,30 @@
131
131
  return descriptors;
132
132
  }
133
133
  /**
134
- * Runs a page tool the chat asked for with executeTool, and answers what the hub expects back: `{ text, isError }`.
134
+ * Runs one of the page tools CrestApps registered in this page itself, as executeTool would: on a copy of the input,
135
+ * with a signal, answering the result as text. Unlike executeTool, which reports every failure as "Tool execution
136
+ * failed", it keeps the message the tool failed with, so the model learns why and can correct itself.
137
+ *
138
+ * @param {any} tool The tool as it was registered, with its execute callback.
139
+ * @param {any} input The tool's input.
140
+ */
141
+ async function runOwnTool(tool, input) {
142
+ var value = await tool.execute(JSON.parse(JSON.stringify(input)), { signal: new AbortController().signal });
143
+ if (typeof value === "string") return value;
144
+ return value === void 0 ? "" : JSON.stringify(value);
145
+ }
146
+ /**
147
+ * Runs a page tool the chat asked for, and answers what the hub expects back: `{ text, isError }`.
148
+ *
149
+ * A tool another script registered runs through the standard executeTool. A page tool CrestApps registered in this
150
+ * page itself, which `findOwnTool` answers, runs through its own execute callback, so a failure reaches the model
151
+ * with its reason.
135
152
  *
136
153
  * @param {any} modelContext The page's ModelContext.
137
154
  * @param {string} name The tool's name.
138
155
  * @param {string} argumentsJson The tool's input as JSON.
139
- * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to refuse.
156
+ * @param {{ fromOrigins?: string[], excludedNames?: Set<string>, findOwnTool?: (registeredTool: any) => any }} [options]
157
+ * The frames to look in, the names to refuse, and how to find the tools CrestApps registered in this page.
140
158
  */
141
159
  async function runPageTool(modelContext, name, argumentsJson, options) {
142
160
  var input;
@@ -165,6 +183,11 @@
165
183
  text: "The page has no tool named \"" + name + "\".",
166
184
  isError: true
167
185
  };
186
+ var ownTool = options && typeof options.findOwnTool === "function" ? options.findOwnTool(tool) : null;
187
+ if (ownTool && typeof ownTool.execute === "function") return {
188
+ text: await runOwnTool(ownTool, input),
189
+ isError: false
190
+ };
168
191
  var serialized = await modelContext.executeTool(tool, input);
169
192
  var text = serialized;
170
193
  try {
@@ -188,7 +211,8 @@
188
211
  *
189
212
  * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.
190
213
  * @param {any} connection The SignalR hub connection of the chat.
191
- * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to leave out.
214
+ * @param {{ fromOrigins?: string[], excludedNames?: Set<string>, findOwnTool?: (registeredTool: any) => any }} [options]
215
+ * The frames to look in, the names to leave out, and how to find the page tools CrestApps registered in this page.
192
216
  */
193
217
  function connectChat(modelContext, connection, options) {
194
218
  if (!modelContext) return {
@@ -237,6 +261,19 @@
237
261
  }
238
262
  };
239
263
  }
264
+ /**
265
+ * Finds the page tool webmcp-page-tools.js registered in this window for a tool getTools() listed. A tool of another
266
+ * frame, or one another script registered, is not one of them.
267
+ *
268
+ * @param {any} win The window.
269
+ * @param {any} modelContext The window's ModelContext.
270
+ * @param {any} registeredTool The RegisteredTool record getTools() answered.
271
+ */
272
+ function findOwnPageTool(win, modelContext, registeredTool) {
273
+ var pageTools = win.CrestAppsWebMcpPageTools;
274
+ if (!registeredTool || registeredTool.window !== win || !pageTools || typeof pageTools.getRegisteredTool !== "function") return null;
275
+ return pageTools.getRegisteredTool(modelContext, registeredTool.name);
276
+ }
240
277
  function init(win) {
241
278
  if (win.crestAppsWebMcp) return win.crestAppsWebMcp;
242
279
  var config = readConfig(win.document) || {};
@@ -257,7 +294,10 @@
257
294
  connectChat: function(connection) {
258
295
  return connectChat(modelContext, connection, {
259
296
  fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : void 0,
260
- excludedNames: serverToolNames
297
+ excludedNames: serverToolNames,
298
+ findOwnTool: function(registeredTool) {
299
+ return findOwnPageTool(win, modelContext, registeredTool);
300
+ }
261
301
  });
262
302
  }
263
303
  };
@@ -272,6 +312,7 @@
272
312
  registerServerTools,
273
313
  toPageToolDescriptors,
274
314
  runPageTool,
315
+ findOwnPageTool,
275
316
  connectChat,
276
317
  init
277
318
  };
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp.js","sources":["../../Assets/js/webmcp.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP for CrestApps.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) lets a web page give tools to AI agents through\n * `document.modelContext`. This script uses only that standard API:\n *\n * - It registers the AI tools the site lets the signed-in user call, each as a standard tool whose `execute` posts to\n * the site with the user's antiforgery token. Any agent that reads the page's tools, such as the browser's own\n * assistant, can find and run them.\n * - It lets the CrestApps AI chat use every tool the page exposes, whoever registered it: `getTools()` lists them,\n * and `executeTool()` runs the one the model calls. Only the site tools it registered itself are left out, because\n * the chat reaches those on the server.\n *\n * Load webmcp-polyfill.js first, so browsers without native WebMCP get the reference implementation; a browser that\n * has WebMCP keeps its own. The page configuration is read from a\n * `<script type=\"application/json\" id=\"crestapps-webmcp-config\">` block: `toolsUrl` and `invokeUrlTemplate` (with a\n * `{name}` placeholder) for the site's tools, `antiforgeryHeaderName` and `antiforgeryToken` for the calls,\n * `pageToolOrigins` for the cross-origin frames whose tools the chat may use, and any setting other page scripts need.\n */\n(function () {\n 'use strict';\n\n var CONFIG_ELEMENT_ID = 'crestapps-webmcp-config';\n var MAX_PAGE_TOOLS = 64;\n var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;\n\n /**\n * Finds the page's WebMCP ModelContext.\n *\n * @param {any} doc The document.\n */\n function getModelContext(doc) {\n return doc && doc.modelContext ? doc.modelContext : null;\n }\n\n /**\n * Reads the configuration block the site writes into the page.\n *\n * @param {any} doc The document.\n */\n function readConfig(doc) {\n var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;\n\n if (!element) {\n return null;\n }\n\n try {\n return JSON.parse(element.textContent);\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Creates the standard tool that calls one of the site's AI tools.\n *\n * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.\n * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n */\n function createServerTool(descriptor, config, fetchImpl) {\n if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) {\n throw new TypeError('A site tool needs a name and an address to call.');\n }\n\n var url = config.invokeUrlTemplate.replace('{name}', encodeURIComponent(descriptor.name));\n /** @type {any} */\n var tool = {\n name: descriptor.name,\n description: descriptor.description,\n // The site's tools read data the page did not write, so agents are told to treat what they answer as data.\n annotations: { untrustedContentHint: true },\n execute: async function (input) {\n /** @type {Record<string, string>} */\n var headers = { 'Content-Type': 'application/json' };\n\n if (config.antiforgeryHeaderName && config.antiforgeryToken) {\n headers[config.antiforgeryHeaderName] = config.antiforgeryToken;\n }\n\n var response = await fetchImpl(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: headers,\n body: JSON.stringify(input || {}),\n });\n\n if (!response.ok) {\n throw new Error('The site refused the tool call (' + response.status + ').');\n }\n\n var data = await response.json();\n var text = (Array.isArray(data.content) ? data.content : [])\n .filter(function (part) {\n return part && part.type === 'text';\n })\n .map(function (part) {\n return part.text == null ? '' : String(part.text);\n })\n .join('\\n');\n\n if (data.isError) {\n // A rejected execute is how WebMCP reports a failed tool.\n throw new Error(text || 'The tool failed.');\n }\n\n return text;\n },\n };\n\n if (descriptor.title) {\n tool.title = descriptor.title;\n }\n\n if (descriptor.inputSchema) {\n tool.inputSchema = descriptor.inputSchema;\n }\n\n return tool;\n }\n\n /**\n * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {any} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n * @returns {Promise<string[]>} The names of the tools registered.\n */\n async function registerServerTools(modelContext, config, fetchImpl, signal) {\n if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) {\n return [];\n }\n\n var descriptors;\n\n try {\n var response = await fetchImpl(config.toolsUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } });\n\n if (!response.ok) {\n return [];\n }\n\n var data = await response.json();\n\n descriptors = data && Array.isArray(data.tools) ? data.tools : [];\n } catch (error) {\n console.warn('The site\\'s WebMCP tools could not be loaded.', error);\n\n return [];\n }\n\n /** @type {string[]} */\n var names = [];\n\n for (var i = 0; i < descriptors.length; i++) {\n try {\n await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal: signal } : {});\n names.push(descriptors[i].name);\n } catch (error) {\n console.warn('A site WebMCP tool could not be registered.', error);\n }\n }\n\n return names;\n }\n\n /**\n * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with\n * their schemas as JSON text and their annotations as flags.\n *\n * @param {Array<any>} tools The RegisteredTool records getTools() answered.\n * @param {Set<string>} [excludedNames] The names to leave out.\n */\n function toPageToolDescriptors(tools, excludedNames) {\n /** @type {Array<any>} */\n var descriptors = [];\n\n (tools || []).forEach(function (tool) {\n if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || '') || (excludedNames && excludedNames.has(tool.name))) {\n return;\n }\n\n var annotations = tool.annotations || {};\n\n descriptors.push({\n name: tool.name,\n title: tool.title || null,\n description: tool.description,\n inputSchema: tool.inputSchema === undefined ? null : JSON.stringify(tool.inputSchema),\n readOnly: annotations.readOnlyHint === true,\n consequential: annotations.consequentialHint === true,\n untrustedContent: annotations.untrustedContentHint === true,\n });\n });\n\n return descriptors;\n }\n\n /**\n * Runs a page tool the chat asked for with executeTool, and answers what the hub expects back: `{ text, isError }`.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {string} name The tool's name.\n * @param {string} argumentsJson The tool's input as JSON.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to refuse.\n */\n async function runPageTool(modelContext, name, argumentsJson, options) {\n var input;\n\n try {\n input = argumentsJson ? JSON.parse(argumentsJson) : {};\n } catch (error) {\n return { text: 'The tool input is not valid JSON.', isError: true };\n }\n\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return { text: 'The tool input must be a JSON object.', isError: true };\n }\n\n var excludedNames = options && options.excludedNames;\n\n if (excludedNames && excludedNames.has(name)) {\n return { text: 'The tool \"' + name + '\" is not a page tool.', isError: true };\n }\n\n try {\n var tools = await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {});\n var tool = tools.find(function (candidate) {\n return candidate.name === name;\n });\n\n if (!tool) {\n return { text: 'The page has no tool named \"' + name + '\".', isError: true };\n }\n\n var serialized = await modelContext.executeTool(tool, input);\n var text = serialized;\n\n try {\n var value = JSON.parse(serialized);\n\n if (typeof value === 'string') {\n text = value;\n }\n } catch (error) {\n // The result is not JSON; keep it as it is.\n }\n\n return { text: text == null ? '' : String(text), isError: false };\n } catch (error) {\n return { text: 'The tool failed: ' + (error && error.message ? error.message : String(error)), isError: true };\n }\n }\n\n /**\n * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection\n * and waits for the result; `sync` sends the page's tools before a message, only when they changed.\n *\n * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.\n * @param {any} connection The SignalR hub connection of the chat.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to leave out.\n */\n function connectChat(modelContext, connection, options) {\n if (!modelContext) {\n return {\n sync: function () {\n return Promise.resolve(0);\n },\n reset: function () { },\n };\n }\n\n var settings = options || {};\n /** @type {string | null} */\n var lastSignature = null;\n\n connection.on('ExecutePageTool', function (name, argumentsJson) {\n return runPageTool(modelContext, name, argumentsJson, settings);\n });\n\n if (typeof connection.onreconnected === 'function') {\n // A reconnected connection has a new identifier, and the hub forgot the tools of the old one.\n connection.onreconnected(function () {\n lastSignature = null;\n });\n }\n\n async function sync() {\n var tools;\n\n try {\n tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});\n } catch (error) {\n console.warn('The page\\'s WebMCP tools could not be read.', error);\n\n return 0;\n }\n\n var descriptors = toPageToolDescriptors(tools, settings.excludedNames);\n var signature = JSON.stringify(descriptors);\n\n if (signature === lastSignature) {\n return descriptors.length;\n }\n\n if (descriptors.length === 0 && lastSignature === null) {\n lastSignature = signature;\n\n return 0;\n }\n\n try {\n if (descriptors.length === 0) {\n await connection.invoke('ClearPageTools');\n } else {\n await connection.invoke('SetPageTools', descriptors);\n }\n\n lastSignature = signature;\n } catch (error) {\n console.warn('The page tools could not be sent to the chat.', error);\n }\n\n return descriptors.length;\n }\n\n return {\n sync: sync,\n reset: function () {\n lastSignature = null;\n },\n };\n }\n\n function init(win) {\n if (win.crestAppsWebMcp) {\n return win.crestAppsWebMcp;\n }\n\n var config = readConfig(win.document) || {};\n var modelContext = getModelContext(win.document);\n /** @type {Set<string>} */\n var serverToolNames = new Set();\n var ready = modelContext && config.toolsUrl && typeof win.fetch === 'function'\n ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function (names) {\n names.forEach(function (name) {\n serverToolNames.add(name);\n });\n\n return names;\n })\n : Promise.resolve([]);\n\n var api = {\n config: config,\n ready: ready,\n isSupported: function () {\n return !!modelContext;\n },\n connectChat: function (connection) {\n return connectChat(modelContext, connection, {\n fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : undefined,\n excludedNames: serverToolNames,\n });\n },\n };\n\n win.crestAppsWebMcp = api;\n\n if (win.document && typeof win.document.dispatchEvent === 'function' && typeof win.CustomEvent === 'function') {\n win.document.dispatchEvent(new win.CustomEvent('crestapps-webmcp-ready', { detail: api }));\n }\n\n return api;\n }\n\n window.CrestAppsWebMcp = {\n getModelContext: getModelContext,\n readConfig: readConfig,\n createServerTool: createServerTool,\n registerServerTools: registerServerTools,\n toPageToolDescriptors: toPageToolDescriptors,\n runPageTool: runPageTool,\n connectChat: connectChat,\n init: init,\n };\n\n if (window.document) {\n init(window);\n }\n})();\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webmcp.js","sources":["../../Assets/js/webmcp.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP for CrestApps.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) lets a web page give tools to AI agents through\n * `document.modelContext`. This script uses only that standard API:\n *\n * - It registers the AI tools the site lets the signed-in user call, each as a standard tool whose `execute` posts to\n * the site with the user's antiforgery token. Any agent that reads the page's tools, such as the browser's own\n * assistant, can find and run them.\n * - It lets the CrestApps AI chat use every tool the page exposes, whoever registered it: `getTools()` lists them,\n * and `executeTool()` runs the one the model calls. Only the site tools it registered itself are left out, because\n * the chat reaches those on the server. The page tools of webmcp-page-tools.js run through their own `execute`,\n * because `executeTool()` hides why a tool failed, and the model needs the reason to correct itself.\n *\n * Load webmcp-polyfill.js first, so browsers without native WebMCP get the reference implementation; a browser that\n * has WebMCP keeps its own. The page configuration is read from a\n * `<script type=\"application/json\" id=\"crestapps-webmcp-config\">` block: `toolsUrl` and `invokeUrlTemplate` (with a\n * `{name}` placeholder) for the site's tools, `antiforgeryHeaderName` and `antiforgeryToken` for the calls,\n * `pageToolOrigins` for the cross-origin frames whose tools the chat may use, and any setting other page scripts need.\n */\n(function () {\n 'use strict';\n\n var CONFIG_ELEMENT_ID = 'crestapps-webmcp-config';\n var MAX_PAGE_TOOLS = 64;\n var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;\n\n /**\n * Finds the page's WebMCP ModelContext.\n *\n * @param {any} doc The document.\n */\n function getModelContext(doc) {\n return doc && doc.modelContext ? doc.modelContext : null;\n }\n\n /**\n * Reads the configuration block the site writes into the page.\n *\n * @param {any} doc The document.\n */\n function readConfig(doc) {\n var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;\n\n if (!element) {\n return null;\n }\n\n try {\n return JSON.parse(element.textContent);\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Creates the standard tool that calls one of the site's AI tools.\n *\n * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.\n * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n */\n function createServerTool(descriptor, config, fetchImpl) {\n if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) {\n throw new TypeError('A site tool needs a name and an address to call.');\n }\n\n var url = config.invokeUrlTemplate.replace('{name}', encodeURIComponent(descriptor.name));\n /** @type {any} */\n var tool = {\n name: descriptor.name,\n description: descriptor.description,\n // The site's tools read data the page did not write, so agents are told to treat what they answer as data.\n annotations: { untrustedContentHint: true },\n execute: async function (input) {\n /** @type {Record<string, string>} */\n var headers = { 'Content-Type': 'application/json' };\n\n if (config.antiforgeryHeaderName && config.antiforgeryToken) {\n headers[config.antiforgeryHeaderName] = config.antiforgeryToken;\n }\n\n var response = await fetchImpl(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: headers,\n body: JSON.stringify(input || {}),\n });\n\n if (!response.ok) {\n throw new Error('The site refused the tool call (' + response.status + ').');\n }\n\n var data = await response.json();\n var text = (Array.isArray(data.content) ? data.content : [])\n .filter(function (part) {\n return part && part.type === 'text';\n })\n .map(function (part) {\n return part.text == null ? '' : String(part.text);\n })\n .join('\\n');\n\n if (data.isError) {\n // A rejected execute is how WebMCP reports a failed tool.\n throw new Error(text || 'The tool failed.');\n }\n\n return text;\n },\n };\n\n if (descriptor.title) {\n tool.title = descriptor.title;\n }\n\n if (descriptor.inputSchema) {\n tool.inputSchema = descriptor.inputSchema;\n }\n\n return tool;\n }\n\n /**\n * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {any} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n * @returns {Promise<string[]>} The names of the tools registered.\n */\n async function registerServerTools(modelContext, config, fetchImpl, signal) {\n if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) {\n return [];\n }\n\n var descriptors;\n\n try {\n var response = await fetchImpl(config.toolsUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } });\n\n if (!response.ok) {\n return [];\n }\n\n var data = await response.json();\n\n descriptors = data && Array.isArray(data.tools) ? data.tools : [];\n } catch (error) {\n console.warn('The site\\'s WebMCP tools could not be loaded.', error);\n\n return [];\n }\n\n /** @type {string[]} */\n var names = [];\n\n for (var i = 0; i < descriptors.length; i++) {\n try {\n await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal: signal } : {});\n names.push(descriptors[i].name);\n } catch (error) {\n console.warn('A site WebMCP tool could not be registered.', error);\n }\n }\n\n return names;\n }\n\n /**\n * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with\n * their schemas as JSON text and their annotations as flags.\n *\n * @param {Array<any>} tools The RegisteredTool records getTools() answered.\n * @param {Set<string>} [excludedNames] The names to leave out.\n */\n function toPageToolDescriptors(tools, excludedNames) {\n /** @type {Array<any>} */\n var descriptors = [];\n\n (tools || []).forEach(function (tool) {\n if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || '') || (excludedNames && excludedNames.has(tool.name))) {\n return;\n }\n\n var annotations = tool.annotations || {};\n\n descriptors.push({\n name: tool.name,\n title: tool.title || null,\n description: tool.description,\n inputSchema: tool.inputSchema === undefined ? null : JSON.stringify(tool.inputSchema),\n readOnly: annotations.readOnlyHint === true,\n consequential: annotations.consequentialHint === true,\n untrustedContent: annotations.untrustedContentHint === true,\n });\n });\n\n return descriptors;\n }\n\n /**\n * Runs one of the page tools CrestApps registered in this page itself, as executeTool would: on a copy of the input,\n * with a signal, answering the result as text. Unlike executeTool, which reports every failure as \"Tool execution\n * failed\", it keeps the message the tool failed with, so the model learns why and can correct itself.\n *\n * @param {any} tool The tool as it was registered, with its execute callback.\n * @param {any} input The tool's input.\n */\n async function runOwnTool(tool, input) {\n var value = await tool.execute(JSON.parse(JSON.stringify(input)), { signal: new AbortController().signal });\n\n if (typeof value === 'string') {\n return value;\n }\n\n return value === undefined ? '' : JSON.stringify(value);\n }\n\n /**\n * Runs a page tool the chat asked for, and answers what the hub expects back: `{ text, isError }`.\n *\n * A tool another script registered runs through the standard executeTool. A page tool CrestApps registered in this\n * page itself, which `findOwnTool` answers, runs through its own execute callback, so a failure reaches the model\n * with its reason.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {string} name The tool's name.\n * @param {string} argumentsJson The tool's input as JSON.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string>, findOwnTool?: (registeredTool: any) => any }} [options]\n * The frames to look in, the names to refuse, and how to find the tools CrestApps registered in this page.\n */\n async function runPageTool(modelContext, name, argumentsJson, options) {\n var input;\n\n try {\n input = argumentsJson ? JSON.parse(argumentsJson) : {};\n } catch (error) {\n return { text: 'The tool input is not valid JSON.', isError: true };\n }\n\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return { text: 'The tool input must be a JSON object.', isError: true };\n }\n\n var excludedNames = options && options.excludedNames;\n\n if (excludedNames && excludedNames.has(name)) {\n return { text: 'The tool \"' + name + '\" is not a page tool.', isError: true };\n }\n\n try {\n var tools = await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {});\n var tool = tools.find(function (candidate) {\n return candidate.name === name;\n });\n\n if (!tool) {\n return { text: 'The page has no tool named \"' + name + '\".', isError: true };\n }\n\n var ownTool = options && typeof options.findOwnTool === 'function' ? options.findOwnTool(tool) : null;\n\n if (ownTool && typeof ownTool.execute === 'function') {\n return { text: await runOwnTool(ownTool, input), isError: false };\n }\n\n var serialized = await modelContext.executeTool(tool, input);\n var text = serialized;\n\n try {\n var value = JSON.parse(serialized);\n\n if (typeof value === 'string') {\n text = value;\n }\n } catch (error) {\n // The result is not JSON; keep it as it is.\n }\n\n return { text: text == null ? '' : String(text), isError: false };\n } catch (error) {\n return { text: 'The tool failed: ' + (error && error.message ? error.message : String(error)), isError: true };\n }\n }\n\n /**\n * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection\n * and waits for the result; `sync` sends the page's tools before a message, only when they changed.\n *\n * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.\n * @param {any} connection The SignalR hub connection of the chat.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string>, findOwnTool?: (registeredTool: any) => any }} [options]\n * The frames to look in, the names to leave out, and how to find the page tools CrestApps registered in this page.\n */\n function connectChat(modelContext, connection, options) {\n if (!modelContext) {\n return {\n sync: function () {\n return Promise.resolve(0);\n },\n reset: function () { },\n };\n }\n\n var settings = options || {};\n /** @type {string | null} */\n var lastSignature = null;\n\n connection.on('ExecutePageTool', function (name, argumentsJson) {\n return runPageTool(modelContext, name, argumentsJson, settings);\n });\n\n if (typeof connection.onreconnected === 'function') {\n // A reconnected connection has a new identifier, and the hub forgot the tools of the old one.\n connection.onreconnected(function () {\n lastSignature = null;\n });\n }\n\n async function sync() {\n var tools;\n\n try {\n tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});\n } catch (error) {\n console.warn('The page\\'s WebMCP tools could not be read.', error);\n\n return 0;\n }\n\n var descriptors = toPageToolDescriptors(tools, settings.excludedNames);\n var signature = JSON.stringify(descriptors);\n\n if (signature === lastSignature) {\n return descriptors.length;\n }\n\n if (descriptors.length === 0 && lastSignature === null) {\n lastSignature = signature;\n\n return 0;\n }\n\n try {\n if (descriptors.length === 0) {\n await connection.invoke('ClearPageTools');\n } else {\n await connection.invoke('SetPageTools', descriptors);\n }\n\n lastSignature = signature;\n } catch (error) {\n console.warn('The page tools could not be sent to the chat.', error);\n }\n\n return descriptors.length;\n }\n\n return {\n sync: sync,\n reset: function () {\n lastSignature = null;\n },\n };\n }\n\n /**\n * Finds the page tool webmcp-page-tools.js registered in this window for a tool getTools() listed. A tool of another\n * frame, or one another script registered, is not one of them.\n *\n * @param {any} win The window.\n * @param {any} modelContext The window's ModelContext.\n * @param {any} registeredTool The RegisteredTool record getTools() answered.\n */\n function findOwnPageTool(win, modelContext, registeredTool) {\n var pageTools = win.CrestAppsWebMcpPageTools;\n\n if (!registeredTool || registeredTool.window !== win || !pageTools || typeof pageTools.getRegisteredTool !== 'function') {\n return null;\n }\n\n return pageTools.getRegisteredTool(modelContext, registeredTool.name);\n }\n\n function init(win) {\n if (win.crestAppsWebMcp) {\n return win.crestAppsWebMcp;\n }\n\n var config = readConfig(win.document) || {};\n var modelContext = getModelContext(win.document);\n /** @type {Set<string>} */\n var serverToolNames = new Set();\n var ready = modelContext && config.toolsUrl && typeof win.fetch === 'function'\n ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function (names) {\n names.forEach(function (name) {\n serverToolNames.add(name);\n });\n\n return names;\n })\n : Promise.resolve([]);\n\n var api = {\n config: config,\n ready: ready,\n isSupported: function () {\n return !!modelContext;\n },\n connectChat: function (connection) {\n return connectChat(modelContext, connection, {\n fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : undefined,\n excludedNames: serverToolNames,\n findOwnTool: function (registeredTool) {\n return findOwnPageTool(win, modelContext, registeredTool);\n },\n });\n },\n };\n\n win.crestAppsWebMcp = api;\n\n if (win.document && typeof win.document.dispatchEvent === 'function' && typeof win.CustomEvent === 'function') {\n win.document.dispatchEvent(new win.CustomEvent('crestapps-webmcp-ready', { detail: api }));\n }\n\n return api;\n }\n\n window.CrestAppsWebMcp = {\n getModelContext: getModelContext,\n readConfig: readConfig,\n createServerTool: createServerTool,\n registerServerTools: registerServerTools,\n toPageToolDescriptors: toPageToolDescriptors,\n runPageTool: runPageTool,\n findOwnPageTool: findOwnPageTool,\n connectChat: connectChat,\n init: init,\n };\n\n if (window.document) {\n init(window);\n }\n})();\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  (function(){(function(){"use strict";var e=/^[A-Za-z0-9_.-]{1,128}$/;function t(e){return e&&e.modelContext?e.modelContext:null}function n(e){var t=e&&e.getElementById?e.getElementById(`crestapps-webmcp-config`):null;if(!t)return null;try{return JSON.parse(t.textContent)}catch{return null}}function r(e,t,n){if(!e||!e.name||!t||!t.invokeUrlTemplate)throw TypeError(`A site tool needs a name and an address to call.`);var r=t.invokeUrlTemplate.replace(`{name}`,encodeURIComponent(e.name)),i={name:e.name,description:e.description,annotations:{untrustedContentHint:!0},execute:async function(e){var i={"Content-Type":`application/json`};t.antiforgeryHeaderName&&t.antiforgeryToken&&(i[t.antiforgeryHeaderName]=t.antiforgeryToken);var a=await n(r,{method:`POST`,credentials:`same-origin`,headers:i,body:JSON.stringify(e||{})});if(!a.ok)throw Error(`The site refused the tool call (`+a.status+`).`);var o=await a.json(),s=(Array.isArray(o.content)?o.content:[]).filter(function(e){return e&&e.type===`text`}).map(function(e){return e.text==null?``:String(e.text)}).join(`
2
- `);if(o.isError)throw Error(s||`The tool failed.`);return s}};return e.title&&(i.title=e.title),e.inputSchema&&(i.inputSchema=e.inputSchema),i}async function i(e,t,n,i){if(!e||!t||!t.toolsUrl||!t.invokeUrlTemplate)return[];var a;try{var o=await n(t.toolsUrl,{credentials:`same-origin`,headers:{Accept:`application/json`}});if(!o.ok)return[];var s=await o.json();a=s&&Array.isArray(s.tools)?s.tools:[]}catch(e){return console.warn(`The site's WebMCP tools could not be loaded.`,e),[]}for(var c=[],l=0;l<a.length;l++)try{await e.registerTool(r(a[l],t,n),i?{signal:i}:{}),c.push(a[l].name)}catch(e){console.warn(`A site WebMCP tool could not be registered.`,e)}return c}function a(t,n){var r=[];return(t||[]).forEach(function(t){if(!(r.length>=64||!t||!e.test(t.name||``)||n&&n.has(t.name))){var i=t.annotations||{};r.push({name:t.name,title:t.title||null,description:t.description,inputSchema:t.inputSchema===void 0?null:JSON.stringify(t.inputSchema),readOnly:i.readOnlyHint===!0,consequential:i.consequentialHint===!0,untrustedContent:i.untrustedContentHint===!0})}}),r}async function o(e,t,n,r){var i;try{i=n?JSON.parse(n):{}}catch{return{text:`The tool input is not valid JSON.`,isError:!0}}if(!i||typeof i!=`object`||Array.isArray(i))return{text:`The tool input must be a JSON object.`,isError:!0};var a=r&&r.excludedNames;if(a&&a.has(t))return{text:`The tool "`+t+`" is not a page tool.`,isError:!0};try{var o=(await e.getTools(r&&r.fromOrigins?{fromOrigins:r.fromOrigins}:{})).find(function(e){return e.name===t});if(!o)return{text:`The page has no tool named "`+t+`".`,isError:!0};var s=await e.executeTool(o,i),c=s;try{var l=JSON.parse(s);typeof l==`string`&&(c=l)}catch{}return{text:c==null?``:String(c),isError:!1}}catch(e){return{text:`The tool failed: `+(e&&e.message?e.message:String(e)),isError:!0}}}function s(e,t,n){if(!e)return{sync:function(){return Promise.resolve(0)},reset:function(){}};var r=n||{},i=null;t.on(`ExecutePageTool`,function(t,n){return o(e,t,n,r)}),typeof t.onreconnected==`function`&&t.onreconnected(function(){i=null});async function s(){var n;try{n=await e.getTools(r.fromOrigins?{fromOrigins:r.fromOrigins}:{})}catch(e){return console.warn(`The page's WebMCP tools could not be read.`,e),0}var o=a(n,r.excludedNames),s=JSON.stringify(o);if(s===i)return o.length;if(o.length===0&&i===null)return i=s,0;try{o.length===0?await t.invoke(`ClearPageTools`):await t.invoke(`SetPageTools`,o),i=s}catch(e){console.warn(`The page tools could not be sent to the chat.`,e)}return o.length}return{sync:s,reset:function(){i=null}}}function c(e){if(e.crestAppsWebMcp)return e.crestAppsWebMcp;var r=n(e.document)||{},a=t(e.document),o=new Set,c={config:r,ready:a&&r.toolsUrl&&typeof e.fetch==`function`?i(a,r,e.fetch.bind(e)).then(function(e){return e.forEach(function(e){o.add(e)}),e}):Promise.resolve([]),isSupported:function(){return!!a},connectChat:function(e){return s(a,e,{fromOrigins:Array.isArray(r.pageToolOrigins)?r.pageToolOrigins:void 0,excludedNames:o})}};return e.crestAppsWebMcp=c,e.document&&typeof e.document.dispatchEvent==`function`&&typeof e.CustomEvent==`function`&&e.document.dispatchEvent(new e.CustomEvent(`crestapps-webmcp-ready`,{detail:c})),c}window.CrestAppsWebMcp={getModelContext:t,readConfig:n,createServerTool:r,registerServerTools:i,toPageToolDescriptors:a,runPageTool:o,connectChat:s,init:c},window.document&&c(window)})()})();
2
+ `);if(o.isError)throw Error(s||`The tool failed.`);return s}};return e.title&&(i.title=e.title),e.inputSchema&&(i.inputSchema=e.inputSchema),i}async function i(e,t,n,i){if(!e||!t||!t.toolsUrl||!t.invokeUrlTemplate)return[];var a;try{var o=await n(t.toolsUrl,{credentials:`same-origin`,headers:{Accept:`application/json`}});if(!o.ok)return[];var s=await o.json();a=s&&Array.isArray(s.tools)?s.tools:[]}catch(e){return console.warn(`The site's WebMCP tools could not be loaded.`,e),[]}for(var c=[],l=0;l<a.length;l++)try{await e.registerTool(r(a[l],t,n),i?{signal:i}:{}),c.push(a[l].name)}catch(e){console.warn(`A site WebMCP tool could not be registered.`,e)}return c}function a(t,n){var r=[];return(t||[]).forEach(function(t){if(!(r.length>=64||!t||!e.test(t.name||``)||n&&n.has(t.name))){var i=t.annotations||{};r.push({name:t.name,title:t.title||null,description:t.description,inputSchema:t.inputSchema===void 0?null:JSON.stringify(t.inputSchema),readOnly:i.readOnlyHint===!0,consequential:i.consequentialHint===!0,untrustedContent:i.untrustedContentHint===!0})}}),r}async function o(e,t){var n=await e.execute(JSON.parse(JSON.stringify(t)),{signal:new AbortController().signal});return typeof n==`string`?n:n===void 0?``:JSON.stringify(n)}async function s(e,t,n,r){var i;try{i=n?JSON.parse(n):{}}catch{return{text:`The tool input is not valid JSON.`,isError:!0}}if(!i||typeof i!=`object`||Array.isArray(i))return{text:`The tool input must be a JSON object.`,isError:!0};var a=r&&r.excludedNames;if(a&&a.has(t))return{text:`The tool "`+t+`" is not a page tool.`,isError:!0};try{var s=(await e.getTools(r&&r.fromOrigins?{fromOrigins:r.fromOrigins}:{})).find(function(e){return e.name===t});if(!s)return{text:`The page has no tool named "`+t+`".`,isError:!0};var c=r&&typeof r.findOwnTool==`function`?r.findOwnTool(s):null;if(c&&typeof c.execute==`function`)return{text:await o(c,i),isError:!1};var l=await e.executeTool(s,i),u=l;try{var d=JSON.parse(l);typeof d==`string`&&(u=d)}catch{}return{text:u==null?``:String(u),isError:!1}}catch(e){return{text:`The tool failed: `+(e&&e.message?e.message:String(e)),isError:!0}}}function c(e,t,n){if(!e)return{sync:function(){return Promise.resolve(0)},reset:function(){}};var r=n||{},i=null;t.on(`ExecutePageTool`,function(t,n){return s(e,t,n,r)}),typeof t.onreconnected==`function`&&t.onreconnected(function(){i=null});async function o(){var n;try{n=await e.getTools(r.fromOrigins?{fromOrigins:r.fromOrigins}:{})}catch(e){return console.warn(`The page's WebMCP tools could not be read.`,e),0}var o=a(n,r.excludedNames),s=JSON.stringify(o);if(s===i)return o.length;if(o.length===0&&i===null)return i=s,0;try{o.length===0?await t.invoke(`ClearPageTools`):await t.invoke(`SetPageTools`,o),i=s}catch(e){console.warn(`The page tools could not be sent to the chat.`,e)}return o.length}return{sync:o,reset:function(){i=null}}}function l(e,t,n){var r=e.CrestAppsWebMcpPageTools;return!n||n.window!==e||!r||typeof r.getRegisteredTool!=`function`?null:r.getRegisteredTool(t,n.name)}function u(e){if(e.crestAppsWebMcp)return e.crestAppsWebMcp;var r=n(e.document)||{},a=t(e.document),o=new Set,s={config:r,ready:a&&r.toolsUrl&&typeof e.fetch==`function`?i(a,r,e.fetch.bind(e)).then(function(e){return e.forEach(function(e){o.add(e)}),e}):Promise.resolve([]),isSupported:function(){return!!a},connectChat:function(t){return c(a,t,{fromOrigins:Array.isArray(r.pageToolOrigins)?r.pageToolOrigins:void 0,excludedNames:o,findOwnTool:function(t){return l(e,a,t)}})}};return e.crestAppsWebMcp=s,e.document&&typeof e.document.dispatchEvent==`function`&&typeof e.CustomEvent==`function`&&e.document.dispatchEvent(new e.CustomEvent(`crestapps-webmcp-ready`,{detail:s})),s}window.CrestAppsWebMcp={getModelContext:t,readConfig:n,createServerTool:r,registerServerTools:i,toPageToolDescriptors:a,runPageTool:s,findOwnPageTool:l,connectChat:c,init:u},window.document&&u(window)})()})();
3
3
  //# sourceMappingURL=webmcp.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp.min.js","sources":["../../Assets/js/webmcp.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP for CrestApps.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) lets a web page give tools to AI agents through\n * `document.modelContext`. This script uses only that standard API:\n *\n * - It registers the AI tools the site lets the signed-in user call, each as a standard tool whose `execute` posts to\n * the site with the user's antiforgery token. Any agent that reads the page's tools, such as the browser's own\n * assistant, can find and run them.\n * - It lets the CrestApps AI chat use every tool the page exposes, whoever registered it: `getTools()` lists them,\n * and `executeTool()` runs the one the model calls. Only the site tools it registered itself are left out, because\n * the chat reaches those on the server.\n *\n * Load webmcp-polyfill.js first, so browsers without native WebMCP get the reference implementation; a browser that\n * has WebMCP keeps its own. The page configuration is read from a\n * `<script type=\"application/json\" id=\"crestapps-webmcp-config\">` block: `toolsUrl` and `invokeUrlTemplate` (with a\n * `{name}` placeholder) for the site's tools, `antiforgeryHeaderName` and `antiforgeryToken` for the calls,\n * `pageToolOrigins` for the cross-origin frames whose tools the chat may use, and any setting other page scripts need.\n */\n(function () {\n 'use strict';\n\n var CONFIG_ELEMENT_ID = 'crestapps-webmcp-config';\n var MAX_PAGE_TOOLS = 64;\n var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;\n\n /**\n * Finds the page's WebMCP ModelContext.\n *\n * @param {any} doc The document.\n */\n function getModelContext(doc) {\n return doc && doc.modelContext ? doc.modelContext : null;\n }\n\n /**\n * Reads the configuration block the site writes into the page.\n *\n * @param {any} doc The document.\n */\n function readConfig(doc) {\n var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;\n\n if (!element) {\n return null;\n }\n\n try {\n return JSON.parse(element.textContent);\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Creates the standard tool that calls one of the site's AI tools.\n *\n * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.\n * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n */\n function createServerTool(descriptor, config, fetchImpl) {\n if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) {\n throw new TypeError('A site tool needs a name and an address to call.');\n }\n\n var url = config.invokeUrlTemplate.replace('{name}', encodeURIComponent(descriptor.name));\n /** @type {any} */\n var tool = {\n name: descriptor.name,\n description: descriptor.description,\n // The site's tools read data the page did not write, so agents are told to treat what they answer as data.\n annotations: { untrustedContentHint: true },\n execute: async function (input) {\n /** @type {Record<string, string>} */\n var headers = { 'Content-Type': 'application/json' };\n\n if (config.antiforgeryHeaderName && config.antiforgeryToken) {\n headers[config.antiforgeryHeaderName] = config.antiforgeryToken;\n }\n\n var response = await fetchImpl(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: headers,\n body: JSON.stringify(input || {}),\n });\n\n if (!response.ok) {\n throw new Error('The site refused the tool call (' + response.status + ').');\n }\n\n var data = await response.json();\n var text = (Array.isArray(data.content) ? data.content : [])\n .filter(function (part) {\n return part && part.type === 'text';\n })\n .map(function (part) {\n return part.text == null ? '' : String(part.text);\n })\n .join('\\n');\n\n if (data.isError) {\n // A rejected execute is how WebMCP reports a failed tool.\n throw new Error(text || 'The tool failed.');\n }\n\n return text;\n },\n };\n\n if (descriptor.title) {\n tool.title = descriptor.title;\n }\n\n if (descriptor.inputSchema) {\n tool.inputSchema = descriptor.inputSchema;\n }\n\n return tool;\n }\n\n /**\n * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {any} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n * @returns {Promise<string[]>} The names of the tools registered.\n */\n async function registerServerTools(modelContext, config, fetchImpl, signal) {\n if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) {\n return [];\n }\n\n var descriptors;\n\n try {\n var response = await fetchImpl(config.toolsUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } });\n\n if (!response.ok) {\n return [];\n }\n\n var data = await response.json();\n\n descriptors = data && Array.isArray(data.tools) ? data.tools : [];\n } catch (error) {\n console.warn('The site\\'s WebMCP tools could not be loaded.', error);\n\n return [];\n }\n\n /** @type {string[]} */\n var names = [];\n\n for (var i = 0; i < descriptors.length; i++) {\n try {\n await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal: signal } : {});\n names.push(descriptors[i].name);\n } catch (error) {\n console.warn('A site WebMCP tool could not be registered.', error);\n }\n }\n\n return names;\n }\n\n /**\n * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with\n * their schemas as JSON text and their annotations as flags.\n *\n * @param {Array<any>} tools The RegisteredTool records getTools() answered.\n * @param {Set<string>} [excludedNames] The names to leave out.\n */\n function toPageToolDescriptors(tools, excludedNames) {\n /** @type {Array<any>} */\n var descriptors = [];\n\n (tools || []).forEach(function (tool) {\n if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || '') || (excludedNames && excludedNames.has(tool.name))) {\n return;\n }\n\n var annotations = tool.annotations || {};\n\n descriptors.push({\n name: tool.name,\n title: tool.title || null,\n description: tool.description,\n inputSchema: tool.inputSchema === undefined ? null : JSON.stringify(tool.inputSchema),\n readOnly: annotations.readOnlyHint === true,\n consequential: annotations.consequentialHint === true,\n untrustedContent: annotations.untrustedContentHint === true,\n });\n });\n\n return descriptors;\n }\n\n /**\n * Runs a page tool the chat asked for with executeTool, and answers what the hub expects back: `{ text, isError }`.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {string} name The tool's name.\n * @param {string} argumentsJson The tool's input as JSON.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to refuse.\n */\n async function runPageTool(modelContext, name, argumentsJson, options) {\n var input;\n\n try {\n input = argumentsJson ? JSON.parse(argumentsJson) : {};\n } catch (error) {\n return { text: 'The tool input is not valid JSON.', isError: true };\n }\n\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return { text: 'The tool input must be a JSON object.', isError: true };\n }\n\n var excludedNames = options && options.excludedNames;\n\n if (excludedNames && excludedNames.has(name)) {\n return { text: 'The tool \"' + name + '\" is not a page tool.', isError: true };\n }\n\n try {\n var tools = await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {});\n var tool = tools.find(function (candidate) {\n return candidate.name === name;\n });\n\n if (!tool) {\n return { text: 'The page has no tool named \"' + name + '\".', isError: true };\n }\n\n var serialized = await modelContext.executeTool(tool, input);\n var text = serialized;\n\n try {\n var value = JSON.parse(serialized);\n\n if (typeof value === 'string') {\n text = value;\n }\n } catch (error) {\n // The result is not JSON; keep it as it is.\n }\n\n return { text: text == null ? '' : String(text), isError: false };\n } catch (error) {\n return { text: 'The tool failed: ' + (error && error.message ? error.message : String(error)), isError: true };\n }\n }\n\n /**\n * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection\n * and waits for the result; `sync` sends the page's tools before a message, only when they changed.\n *\n * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.\n * @param {any} connection The SignalR hub connection of the chat.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to leave out.\n */\n function connectChat(modelContext, connection, options) {\n if (!modelContext) {\n return {\n sync: function () {\n return Promise.resolve(0);\n },\n reset: function () { },\n };\n }\n\n var settings = options || {};\n /** @type {string | null} */\n var lastSignature = null;\n\n connection.on('ExecutePageTool', function (name, argumentsJson) {\n return runPageTool(modelContext, name, argumentsJson, settings);\n });\n\n if (typeof connection.onreconnected === 'function') {\n // A reconnected connection has a new identifier, and the hub forgot the tools of the old one.\n connection.onreconnected(function () {\n lastSignature = null;\n });\n }\n\n async function sync() {\n var tools;\n\n try {\n tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});\n } catch (error) {\n console.warn('The page\\'s WebMCP tools could not be read.', error);\n\n return 0;\n }\n\n var descriptors = toPageToolDescriptors(tools, settings.excludedNames);\n var signature = JSON.stringify(descriptors);\n\n if (signature === lastSignature) {\n return descriptors.length;\n }\n\n if (descriptors.length === 0 && lastSignature === null) {\n lastSignature = signature;\n\n return 0;\n }\n\n try {\n if (descriptors.length === 0) {\n await connection.invoke('ClearPageTools');\n } else {\n await connection.invoke('SetPageTools', descriptors);\n }\n\n lastSignature = signature;\n } catch (error) {\n console.warn('The page tools could not be sent to the chat.', error);\n }\n\n return descriptors.length;\n }\n\n return {\n sync: sync,\n reset: function () {\n lastSignature = null;\n },\n };\n }\n\n function init(win) {\n if (win.crestAppsWebMcp) {\n return win.crestAppsWebMcp;\n }\n\n var config = readConfig(win.document) || {};\n var modelContext = getModelContext(win.document);\n /** @type {Set<string>} */\n var serverToolNames = new Set();\n var ready = modelContext && config.toolsUrl && typeof win.fetch === 'function'\n ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function (names) {\n names.forEach(function (name) {\n serverToolNames.add(name);\n });\n\n return names;\n })\n : Promise.resolve([]);\n\n var api = {\n config: config,\n ready: ready,\n isSupported: function () {\n return !!modelContext;\n },\n connectChat: function (connection) {\n return connectChat(modelContext, connection, {\n fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : undefined,\n excludedNames: serverToolNames,\n });\n },\n };\n\n win.crestAppsWebMcp = api;\n\n if (win.document && typeof win.document.dispatchEvent === 'function' && typeof win.CustomEvent === 'function') {\n win.document.dispatchEvent(new win.CustomEvent('crestapps-webmcp-ready', { detail: api }));\n }\n\n return api;\n }\n\n window.CrestAppsWebMcp = {\n getModelContext: getModelContext,\n readConfig: readConfig,\n createServerTool: createServerTool,\n registerServerTools: registerServerTools,\n toPageToolDescriptors: toPageToolDescriptors,\n runPageTool: runPageTool,\n connectChat: connectChat,\n init: init,\n };\n\n if (window.document) {\n init(window);\n }\n})();\n"],"names":["TOOL_NAME","getModelContext","doc","readConfig","element","createServerTool","descriptor","config","url","tool","headers","response","fetchImpl","input","data","text","part","registerServerTools","modelContext","descriptors","error","names","i","signal","toPageToolDescriptors","tools","excludedNames","annotations","runPageTool","argumentsJson","options","name","candidate","serialized","value","connectChat","settings","lastSignature","connection","sync","signature","init","win","serverToolNames","api"],"mappings":"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"}
1
+ {"version":3,"file":"webmcp.min.js","sources":["../../Assets/js/webmcp.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP for CrestApps.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) lets a web page give tools to AI agents through\n * `document.modelContext`. This script uses only that standard API:\n *\n * - It registers the AI tools the site lets the signed-in user call, each as a standard tool whose `execute` posts to\n * the site with the user's antiforgery token. Any agent that reads the page's tools, such as the browser's own\n * assistant, can find and run them.\n * - It lets the CrestApps AI chat use every tool the page exposes, whoever registered it: `getTools()` lists them,\n * and `executeTool()` runs the one the model calls. Only the site tools it registered itself are left out, because\n * the chat reaches those on the server. The page tools of webmcp-page-tools.js run through their own `execute`,\n * because `executeTool()` hides why a tool failed, and the model needs the reason to correct itself.\n *\n * Load webmcp-polyfill.js first, so browsers without native WebMCP get the reference implementation; a browser that\n * has WebMCP keeps its own. The page configuration is read from a\n * `<script type=\"application/json\" id=\"crestapps-webmcp-config\">` block: `toolsUrl` and `invokeUrlTemplate` (with a\n * `{name}` placeholder) for the site's tools, `antiforgeryHeaderName` and `antiforgeryToken` for the calls,\n * `pageToolOrigins` for the cross-origin frames whose tools the chat may use, and any setting other page scripts need.\n */\n(function () {\n 'use strict';\n\n var CONFIG_ELEMENT_ID = 'crestapps-webmcp-config';\n var MAX_PAGE_TOOLS = 64;\n var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;\n\n /**\n * Finds the page's WebMCP ModelContext.\n *\n * @param {any} doc The document.\n */\n function getModelContext(doc) {\n return doc && doc.modelContext ? doc.modelContext : null;\n }\n\n /**\n * Reads the configuration block the site writes into the page.\n *\n * @param {any} doc The document.\n */\n function readConfig(doc) {\n var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;\n\n if (!element) {\n return null;\n }\n\n try {\n return JSON.parse(element.textContent);\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Creates the standard tool that calls one of the site's AI tools.\n *\n * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.\n * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n */\n function createServerTool(descriptor, config, fetchImpl) {\n if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) {\n throw new TypeError('A site tool needs a name and an address to call.');\n }\n\n var url = config.invokeUrlTemplate.replace('{name}', encodeURIComponent(descriptor.name));\n /** @type {any} */\n var tool = {\n name: descriptor.name,\n description: descriptor.description,\n // The site's tools read data the page did not write, so agents are told to treat what they answer as data.\n annotations: { untrustedContentHint: true },\n execute: async function (input) {\n /** @type {Record<string, string>} */\n var headers = { 'Content-Type': 'application/json' };\n\n if (config.antiforgeryHeaderName && config.antiforgeryToken) {\n headers[config.antiforgeryHeaderName] = config.antiforgeryToken;\n }\n\n var response = await fetchImpl(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: headers,\n body: JSON.stringify(input || {}),\n });\n\n if (!response.ok) {\n throw new Error('The site refused the tool call (' + response.status + ').');\n }\n\n var data = await response.json();\n var text = (Array.isArray(data.content) ? data.content : [])\n .filter(function (part) {\n return part && part.type === 'text';\n })\n .map(function (part) {\n return part.text == null ? '' : String(part.text);\n })\n .join('\\n');\n\n if (data.isError) {\n // A rejected execute is how WebMCP reports a failed tool.\n throw new Error(text || 'The tool failed.');\n }\n\n return text;\n },\n };\n\n if (descriptor.title) {\n tool.title = descriptor.title;\n }\n\n if (descriptor.inputSchema) {\n tool.inputSchema = descriptor.inputSchema;\n }\n\n return tool;\n }\n\n /**\n * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {any} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n * @returns {Promise<string[]>} The names of the tools registered.\n */\n async function registerServerTools(modelContext, config, fetchImpl, signal) {\n if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) {\n return [];\n }\n\n var descriptors;\n\n try {\n var response = await fetchImpl(config.toolsUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } });\n\n if (!response.ok) {\n return [];\n }\n\n var data = await response.json();\n\n descriptors = data && Array.isArray(data.tools) ? data.tools : [];\n } catch (error) {\n console.warn('The site\\'s WebMCP tools could not be loaded.', error);\n\n return [];\n }\n\n /** @type {string[]} */\n var names = [];\n\n for (var i = 0; i < descriptors.length; i++) {\n try {\n await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal: signal } : {});\n names.push(descriptors[i].name);\n } catch (error) {\n console.warn('A site WebMCP tool could not be registered.', error);\n }\n }\n\n return names;\n }\n\n /**\n * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with\n * their schemas as JSON text and their annotations as flags.\n *\n * @param {Array<any>} tools The RegisteredTool records getTools() answered.\n * @param {Set<string>} [excludedNames] The names to leave out.\n */\n function toPageToolDescriptors(tools, excludedNames) {\n /** @type {Array<any>} */\n var descriptors = [];\n\n (tools || []).forEach(function (tool) {\n if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || '') || (excludedNames && excludedNames.has(tool.name))) {\n return;\n }\n\n var annotations = tool.annotations || {};\n\n descriptors.push({\n name: tool.name,\n title: tool.title || null,\n description: tool.description,\n inputSchema: tool.inputSchema === undefined ? null : JSON.stringify(tool.inputSchema),\n readOnly: annotations.readOnlyHint === true,\n consequential: annotations.consequentialHint === true,\n untrustedContent: annotations.untrustedContentHint === true,\n });\n });\n\n return descriptors;\n }\n\n /**\n * Runs one of the page tools CrestApps registered in this page itself, as executeTool would: on a copy of the input,\n * with a signal, answering the result as text. Unlike executeTool, which reports every failure as \"Tool execution\n * failed\", it keeps the message the tool failed with, so the model learns why and can correct itself.\n *\n * @param {any} tool The tool as it was registered, with its execute callback.\n * @param {any} input The tool's input.\n */\n async function runOwnTool(tool, input) {\n var value = await tool.execute(JSON.parse(JSON.stringify(input)), { signal: new AbortController().signal });\n\n if (typeof value === 'string') {\n return value;\n }\n\n return value === undefined ? '' : JSON.stringify(value);\n }\n\n /**\n * Runs a page tool the chat asked for, and answers what the hub expects back: `{ text, isError }`.\n *\n * A tool another script registered runs through the standard executeTool. A page tool CrestApps registered in this\n * page itself, which `findOwnTool` answers, runs through its own execute callback, so a failure reaches the model\n * with its reason.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {string} name The tool's name.\n * @param {string} argumentsJson The tool's input as JSON.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string>, findOwnTool?: (registeredTool: any) => any }} [options]\n * The frames to look in, the names to refuse, and how to find the tools CrestApps registered in this page.\n */\n async function runPageTool(modelContext, name, argumentsJson, options) {\n var input;\n\n try {\n input = argumentsJson ? JSON.parse(argumentsJson) : {};\n } catch (error) {\n return { text: 'The tool input is not valid JSON.', isError: true };\n }\n\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return { text: 'The tool input must be a JSON object.', isError: true };\n }\n\n var excludedNames = options && options.excludedNames;\n\n if (excludedNames && excludedNames.has(name)) {\n return { text: 'The tool \"' + name + '\" is not a page tool.', isError: true };\n }\n\n try {\n var tools = await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {});\n var tool = tools.find(function (candidate) {\n return candidate.name === name;\n });\n\n if (!tool) {\n return { text: 'The page has no tool named \"' + name + '\".', isError: true };\n }\n\n var ownTool = options && typeof options.findOwnTool === 'function' ? options.findOwnTool(tool) : null;\n\n if (ownTool && typeof ownTool.execute === 'function') {\n return { text: await runOwnTool(ownTool, input), isError: false };\n }\n\n var serialized = await modelContext.executeTool(tool, input);\n var text = serialized;\n\n try {\n var value = JSON.parse(serialized);\n\n if (typeof value === 'string') {\n text = value;\n }\n } catch (error) {\n // The result is not JSON; keep it as it is.\n }\n\n return { text: text == null ? '' : String(text), isError: false };\n } catch (error) {\n return { text: 'The tool failed: ' + (error && error.message ? error.message : String(error)), isError: true };\n }\n }\n\n /**\n * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection\n * and waits for the result; `sync` sends the page's tools before a message, only when they changed.\n *\n * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.\n * @param {any} connection The SignalR hub connection of the chat.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string>, findOwnTool?: (registeredTool: any) => any }} [options]\n * The frames to look in, the names to leave out, and how to find the page tools CrestApps registered in this page.\n */\n function connectChat(modelContext, connection, options) {\n if (!modelContext) {\n return {\n sync: function () {\n return Promise.resolve(0);\n },\n reset: function () { },\n };\n }\n\n var settings = options || {};\n /** @type {string | null} */\n var lastSignature = null;\n\n connection.on('ExecutePageTool', function (name, argumentsJson) {\n return runPageTool(modelContext, name, argumentsJson, settings);\n });\n\n if (typeof connection.onreconnected === 'function') {\n // A reconnected connection has a new identifier, and the hub forgot the tools of the old one.\n connection.onreconnected(function () {\n lastSignature = null;\n });\n }\n\n async function sync() {\n var tools;\n\n try {\n tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});\n } catch (error) {\n console.warn('The page\\'s WebMCP tools could not be read.', error);\n\n return 0;\n }\n\n var descriptors = toPageToolDescriptors(tools, settings.excludedNames);\n var signature = JSON.stringify(descriptors);\n\n if (signature === lastSignature) {\n return descriptors.length;\n }\n\n if (descriptors.length === 0 && lastSignature === null) {\n lastSignature = signature;\n\n return 0;\n }\n\n try {\n if (descriptors.length === 0) {\n await connection.invoke('ClearPageTools');\n } else {\n await connection.invoke('SetPageTools', descriptors);\n }\n\n lastSignature = signature;\n } catch (error) {\n console.warn('The page tools could not be sent to the chat.', error);\n }\n\n return descriptors.length;\n }\n\n return {\n sync: sync,\n reset: function () {\n lastSignature = null;\n },\n };\n }\n\n /**\n * Finds the page tool webmcp-page-tools.js registered in this window for a tool getTools() listed. A tool of another\n * frame, or one another script registered, is not one of them.\n *\n * @param {any} win The window.\n * @param {any} modelContext The window's ModelContext.\n * @param {any} registeredTool The RegisteredTool record getTools() answered.\n */\n function findOwnPageTool(win, modelContext, registeredTool) {\n var pageTools = win.CrestAppsWebMcpPageTools;\n\n if (!registeredTool || registeredTool.window !== win || !pageTools || typeof pageTools.getRegisteredTool !== 'function') {\n return null;\n }\n\n return pageTools.getRegisteredTool(modelContext, registeredTool.name);\n }\n\n function init(win) {\n if (win.crestAppsWebMcp) {\n return win.crestAppsWebMcp;\n }\n\n var config = readConfig(win.document) || {};\n var modelContext = getModelContext(win.document);\n /** @type {Set<string>} */\n var serverToolNames = new Set();\n var ready = modelContext && config.toolsUrl && typeof win.fetch === 'function'\n ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function (names) {\n names.forEach(function (name) {\n serverToolNames.add(name);\n });\n\n return names;\n })\n : Promise.resolve([]);\n\n var api = {\n config: config,\n ready: ready,\n isSupported: function () {\n return !!modelContext;\n },\n connectChat: function (connection) {\n return connectChat(modelContext, connection, {\n fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : undefined,\n excludedNames: serverToolNames,\n findOwnTool: function (registeredTool) {\n return findOwnPageTool(win, modelContext, registeredTool);\n },\n });\n },\n };\n\n win.crestAppsWebMcp = api;\n\n if (win.document && typeof win.document.dispatchEvent === 'function' && typeof win.CustomEvent === 'function') {\n win.document.dispatchEvent(new win.CustomEvent('crestapps-webmcp-ready', { detail: api }));\n }\n\n return api;\n }\n\n window.CrestAppsWebMcp = {\n getModelContext: getModelContext,\n readConfig: readConfig,\n createServerTool: createServerTool,\n registerServerTools: registerServerTools,\n toPageToolDescriptors: toPageToolDescriptors,\n runPageTool: runPageTool,\n findOwnPageTool: findOwnPageTool,\n connectChat: connectChat,\n init: init,\n };\n\n if (window.document) {\n init(window);\n }\n})();\n"],"names":["TOOL_NAME","getModelContext","doc","readConfig","element","createServerTool","descriptor","config","url","tool","headers","response","fetchImpl","input","data","text","part","registerServerTools","modelContext","descriptors","error","names","i","signal","toPageToolDescriptors","tools","excludedNames","annotations","runOwnTool","value","runPageTool","argumentsJson","options","name","candidate","ownTool","serialized","connectChat","settings","lastSignature","connection","sync","signature","findOwnPageTool","pageTools","win","registeredTool","init","serverToolNames","api"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crestapps/webmcp",
3
- "version": "2.0.0-preview.250",
3
+ "version": "2.0.0-preview.258",
4
4
  "description": "WebMCP for CrestApps: registers a site's AI tools and page tools with the standard document.modelContext API, and lets the CrestApps AI chat discover and run any WebMCP tool a page exposes. Ships the W3C Web Machine Learning Community Group polyfill for browsers without native WebMCP.",
5
5
  "license": "MIT",
6
6
  "author": "CrestApps",