@crestapps/webmcp 2.0.0-preview.257 → 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.
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
description: "meta[name=\"description\"]",
|
|
19
19
|
headings: "h1, h2",
|
|
20
20
|
main: "main, [role=\"main\"]",
|
|
21
|
-
ignored: "script, style, noscript, template, svg, nav, aside,
|
|
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\"]",
|
|
22
22
|
links: "nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]",
|
|
23
23
|
fields: "form input, form select, form textarea"
|
|
24
24
|
};
|
|
@@ -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, 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, forms (page_list_form_fields reads them),\n // hidden parts, the chat itself, and anything a page marks with data-webmcp-ignore.\n ignored: 'script, style, noscript, template, svg, nav, aside, form, 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":"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"}
|
|
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":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
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,
|
|
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
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)})}})()})();
|
|
3
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, 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, forms (page_list_form_fields reads them),\n // hidden parts, the chat itself, and anything a page marks with data-webmcp-ignore.\n ignored: 'script, style, noscript, template, svg, nav, aside, form, 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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"}
|
|
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":"C,YAaC,UAAY,CACX,aAEA,IAIIA,EAAqB,IAMrBC,EAAY,CACd,YAAa,2BACb,SAAU,SACV,KAAM,sBAIN,QAAS,2MACT,MAAO,0EACP,OAAQ,wCACV,EAGIC,EAAgB,CAAC,SAAU,WAAY,OAAQ,SAAU,SAAU,QAAS,OAAO,EACnFC,EAAgB,CAAC,4BAA4B,EAEjD,SAASC,EAAU,EAAO,CACxB,OAAQC,GAAS,KAAO,GAAK,OAAOA,CAAK,EAAA,CAAG,QAAQ,OAAQ,GAAG,CAAC,CAAC,KAAK,CACxE,CAIA,IAAIC,EAAmB,4BAInBC,EAAkB,IAAI,QAGtBC,EAAa,2JAAmO,EAEpP,SAASC,EAAU,EAAM,CAMvB,OALIC,EAAK,QAAW,OAAOA,EAAK,SAAY,YAAcA,EAAK,QAAQT,EAAU,OAAO,EAC/E,GAIF,OAAOS,EAAK,iBAAoB,YAAc,CAACA,EAAK,gBAAgB,CAC7E,CAEA,SAASC,EAAS,EAAM,CACtB,IAAIC,EAAOR,EAAUS,EAAK,WAAW,EAErC,OAAOD,EAAK,OAAS,IAAkBA,EAAK,MAAM,EAAG,GAAe,EAAI,IAAMA,CAChF,CAEA,SAASE,EAAU,EAAO,CACxB,IAAIC,EAAO,MAAM,UAAU,MAAM,KAAKC,EAAM,MAAQ,CAAC,CAAC,EAClDC,EAAU,CAAC,EAQf,OANIF,EAAK,QAAU,MAAM,UAAU,MAAM,KAAKA,EAAK,EAAE,CAAC,OAAS,CAAC,EAAG,SAAU,EAAM,CAAE,OAAQF,EAAK,SAAW,GAAA,CAAI,YAAY,IAAM,IAAM,CAAC,IACxI,EAAU,MAAM,UAAU,IAAI,KAAKE,EAAK,MAAM,CAAC,CAAC,MAAOJ,CAAQ,GAK1D,CACL,SAHYK,EAAM,QAAUZ,EAAUY,EAAM,QAAQ,WAAW,EAAI,KAG/C,KACX,UACT,KAAMD,EAAK,MAAM,EAAG,EAAc,CAAC,CAAC,IAAI,SAAU,EAAK,CACrD,OAAO,MAAM,UAAU,IAAI,KAAKG,EAAI,OAAS,CAAC,EAAGP,CAAQ,CAC3D,CAAC,EACD,UAAWI,EAAK,MAClB,CACF,CAMA,SAASI,EAAY,EAAK,CACxB,IAAIC,EAAOC,EAAI,cAAcpB,EAAU,IAAI,GAAKoB,EAAI,KAChDC,EAAQ,CAAC,EACTC,EAAO,CAAC,EACRC,EAAS,CAAC,EACVC,EAAa,EAEjB,SAASC,GAAU,CACjB,IAAId,EAAOR,EAAUmB,EAAK,KAAK,GAAG,CAAC,EAE/BX,GACFU,EAAM,KAAKV,CAAI,EAGjB,EAAO,CAAC,CACV,CAEA,SAASe,EAAK,EAAM,CAClB,GAAIjB,EAAK,WAAa,EACpBa,EAAK,KAAKb,EAAK,SAAS,OAK1B,GAAI,IAAK,WAAa,GAAKD,EAAUC,CAAI,GAIzC,KAAIkB,GAAOlB,EAAK,SAAW,GAAA,CAAI,YAAY,EAE3C,GAAIkB,IAAQ,QACVF,EAAQ,EACR,IAEIF,EAAO,OAAS,KAClBA,EAAO,KAAKV,EAAUJ,CAAI,CAAC,EAC3BY,EAAM,KAAK,UAAYE,EAAO,OAAS,GAAG,OAM9C,KAAIK,EAAUrB,EAAW,QAAQoB,CAAG,GAAK,EAErCC,GACFH,EAAQ,EAKV,IAAK,IAFDI,EAAWpB,EAAK,YAAc,CAAC,EAE1B,EAAI,EAAG,EAAIoB,EAAS,OAAQ,IACnCH,EAAKG,EAAS,EAAE,EAGdD,GACFH,EAAQ,CAb+B,CAdE,CA6B7C,CAEIN,IACFO,EAAKP,CAAI,EACTM,EAAQ,GAGV,IAAId,EAAOU,EAAM,KAAK;CAAI,EACtBS,EAAYnB,EAAK,OAASZ,GAAsByB,EAAaD,EAAO,QACtEA,EAAO,KAAK,SAAU,EAAO,CAAE,OAAOR,EAAM,UAAYA,EAAM,KAAK,MAAQ,CAAC,EAE9E,MAAO,CACL,KAAMJ,EAAK,OAASZ,EAAqBY,EAAK,MAAM,EAAGZ,CAAkB,EAAI,IAAMY,EAC3E,SACG,WACb,CACF,CAEA,SAASoB,EAAkB,EAAM,EAAK,CACpC,GAAI,CAACC,EACH,OAAO,KAGT,GAAI,CACF,IAAIC,EAAM,IAAI,IAAID,EAAME,EAAI,IAAI,EAEhC,OAAOD,EAAI,SAAWC,EAAI,OAASD,EAAM,IAC3C,MAAgB,CACd,OAAO,IACT,CACF,CAEA,SAASE,EAAU,EAAO,CACxB,IAAIR,GAAOS,EAAM,SAAW,GAAA,CAAI,YAAY,EAM5C,OAJIT,IAAQ,UAAYA,IAAQ,WACvBA,GAGDS,EAAM,MAAQ,OAAA,CAAQ,YAAY,CAC5C,CAEA,SAASC,EAAW,EAAO,CACzB,GAAID,EAAM,QAAUA,EAAM,OAAO,OAAS,EAAG,CAC3C,IAAIE,EAAQnC,EAAUiC,EAAM,OAAO,EAAE,CAAC,WAAW,EAEjD,GAAIE,EACF,OAAOA,CAEX,CAEA,OAAOnC,EAAUiC,EAAM,aAAa,YAAY,CAAC,GAAKjC,EAAUiC,EAAM,aAAa,aAAa,CAAC,CACnG,CAEA,SAASG,EAAS,EAAO,CACvB,MAAO,CAACH,EAAM,UACZnC,EAAc,QAAQkC,EAAUC,CAAK,CAAC,EAAI,GAC1ClC,EAAc,QAAQkC,EAAM,IAAI,EAAI,CACxC,CAEA,SAASI,EAAW,EAAK,CAIvB,IAAK,IAHDC,EAAS,CAAC,EACVC,EAAMtB,EAAI,iBAAiBpB,EAAU,MAAM,EAEtC,EAAI,EAAG,EAAI0C,EAAI,QAAUD,EAAO,OAAS,IAAY,IACxDF,EAASG,EAAI,EAAE,GACjBD,EAAO,KAAKC,EAAI,EAAE,EAItB,OAAOD,CACT,CAEA,SAASE,EAAc,EAAO,CAC5B,IAAIC,EAAOT,EAAUC,CAAK,EACtBS,EAAc,CAChB,IAAKT,EAAM,MAAQA,EAAM,GACzB,KAAMA,EAAM,KACZ,GAAIA,EAAM,GACV,MAAOC,EAAWD,CAAK,EACjB,OACN,MAAOQ,IAAS,YAAcA,IAAS,QACnC,CAAC,CAACR,EAAM,QACR,OAAOA,EAAM,OAAS,KAAO,GAAKA,EAAM,KAAK,CAAC,CAAC,UAAU,EAAG,GAAgB,EAChF,SAAU,CAAC,CAACA,EAAM,QACpB,EAQA,OANIQ,IAAS,WACX,EAAY,QAAU,MAAM,UAAU,IAAI,KAAKR,EAAM,SAAW,CAAC,EAAG,SAAU,EAAQ,CACpF,MAAO,CAAE,MAAOU,EAAO,MAAO,KAAM3C,EAAU2C,EAAO,IAAI,EAAG,SAAU,CAAC,CAACA,EAAO,QAAS,CAC1F,CAAC,GAGID,CACT,CAEA,SAASE,EAAU,EAAK,EAAK,CAC3B,IAAIC,EAAS7C,EAAU8C,CAAG,CAAC,CAAC,YAAY,EAExC,GAAI,CAACD,EACH,OAAO,KAgBT,IAAK,IAbDP,EAASD,EAAWpB,CAAG,EACvB8B,EAAW,CACb,SAAU,EAAO,CACf,OAAQd,EAAM,MAAQ,GAAA,CAAI,YAAY,IAAMY,CAC9C,EACA,SAAU,EAAO,CACf,OAAQZ,EAAM,IAAM,GAAA,CAAI,YAAY,IAAMY,CAC5C,EACA,SAAU,EAAO,CACf,OAAOX,EAAWD,CAAK,CAAC,CAAC,YAAY,IAAMY,CAC7C,CACF,EAESG,EAAI,EAAGA,EAAID,EAAS,OAAQ,IACnC,IAAK,IAAIE,EAAI,EAAGA,EAAIX,EAAO,OAAQ,IACjC,GAAIS,EAASC,EAAE,CAACV,EAAOW,EAAE,EACvB,OAAOX,EAAOW,GAKpB,OAAO,IACT,CASA,eAAeC,EAAc,EAAW,EAAM,CAC5C,GAAI,OAAOC,GAAc,WACvB,MAAO,GAGT,GAAI,CACF,IAAIC,EAAW,MAAMD,EAAUtB,EAAM,CAAE,OAAQ,OAAQ,YAAa,cAAe,SAAU,QAAS,CAAC,EAEvG,OAAOuB,EAAS,SAAW,KAAOA,EAAS,SAAW,GACxD,MAAgB,CACd,MAAO,EACT,CACF,CAEA,SAASC,EAAM,EAAO,EAAM,CAC1BpB,EAAM,cAAc,IAAI,MAAMQ,EAAM,CAAE,QAAS,EAAK,CAAC,CAAC,CACxD,CASA,SAASa,EAAgB,EAAS,CAChC,IAAIrC,EAAMsC,EAAQ,IACdxB,EAAMwB,EAAQ,IACdC,EAAkB,OAAOD,EAAQ,iBAAoB,SAAWA,EAAQ,gBAAkB,IAE9F,MAAO,CACL,CACE,KAAM,gBACN,MAAO,gBACP,YAAa,gLACb,YAAa,CAAE,KAAM,SAAU,WAAY,CAAC,CAAE,EAC9C,YAAa,CAAE,aAAc,EAAK,EAClC,QAAS,UAAY,CAKnB,IAAK,IAJDE,EAAOxC,EAAI,cAAcpB,EAAU,WAAW,EAC9C6D,EAAW,CAAC,EACZnB,EAAMtB,EAAI,iBAAiBpB,EAAU,QAAQ,EAExCoD,EAAI,EAAGA,EAAIV,EAAI,QAAUmB,EAAS,OAAS,GAAc,IAAK,CACrE,IAAIlD,EAAOR,EAAUuC,EAAIU,EAAE,CAAC,WAAW,EAEnCzC,GACFkD,EAAS,KAAK,CAAE,OAAQnB,EAAIU,EAAE,CAAC,SAAW,GAAA,CAAI,YAAY,IAAM,KAAO,EAAI,EAAS,MAAK,CAAC,CAE9F,CAEA,MAAQ,CACN,MAAOhC,EAAI,MACX,IAAKc,EAAI,KACT,YAAa0B,EAAOA,EAAK,aAAa,SAAS,EAAI,KACzC,WACV,QAAS,CAAC,CAACF,EAAQ,OACrB,CACF,CACF,EACA,CACE,KAAM,oBACN,MAAO,oBACP,YAAa,6OACb,YAAa,CAAE,KAAM,SAAU,WAAY,CAAC,CAAE,EAC9C,YAAa,CAAE,aAAc,EAAK,EAClC,QAAS,UAAY,CACnB,OAAOxC,EAAYE,CAAG,CACxB,CACF,EACA,CACE,KAAM,kBACN,MAAO,kBACP,YAAa,0IACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,OAAQ,CAAE,KAAM,SAAU,YAAa,kDAAmD,CAC5F,CACF,EACA,YAAa,CAAE,aAAc,EAAK,EAClC,QAAS,SAAU,EAAO,CAMxB,IAAK,IALD0C,EAAS3D,EAAU4D,GAASA,EAAM,MAAM,CAAC,CAAC,YAAY,EACtDC,EAAO,IAAI,IACXC,EAAQ,CAAC,EACTvB,EAAMtB,EAAI,iBAAiBpB,EAAU,KAAK,EAErCoD,EAAI,EAAGA,EAAIV,EAAI,QAAUuB,EAAM,OAAS,IAAW,IAAK,CAC/D,IAAIhC,EAAMF,EAAkBW,EAAIU,EAAE,CAAC,aAAa,MAAM,EAAGlB,CAAG,EACxDvB,EAAOR,EAAUuC,EAAIU,EAAE,CAAC,WAAW,EAEnC,CAACnB,GAAO,CAACtB,GAAQqD,EAAK,IAAI/B,EAAI,IAAI,GAAM6B,GAAUnD,EAAK,YAAY,CAAC,CAAC,QAAQmD,CAAM,EAAI,IAI3FE,EAAK,IAAI/B,EAAI,IAAI,EACjBgC,EAAM,KAAK,CAAQ,OAAM,IAAKhC,EAAI,IAAK,CAAC,EAC1C,CAEA,MAAQ,CAAS,OAAM,CACzB,CACF,EACA,CACE,KAAM,gBACN,MAAO,YACP,YAAa,2MACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,IAAK,CAAE,KAAM,SAAU,YAAa,kHAAmH,CACzJ,EACA,SAAU,CAAC,KAAK,CAClB,EACA,QAAS,eAAgB,EAAO,CAC9B,IAAIiC,EAAU/D,EAAU4D,GAASA,EAAM,GAAG,EAE1C,GAAI,CAACG,EACH,MAAU,MAAM,wEAAwE,EAK1F,GAAIA,EAAQ,OAAO,CAAC,IAAM,KAAO,CAAC7D,EAAiB,KAAK6D,CAAO,EAC7D,MAAU,MAAM,IAAMA,EAAU,kLAAkL,EAGpN,IAAIjC,EAAMF,EAAkBmC,EAAShC,CAAG,EAExC,GAAI,CAACD,EACH,MAAU,MAAM,2FAA2F,EAG7G,GAAI,MAAMoB,EAAcK,EAAQ,MAAOzB,EAAI,IAAI,EAC7C,MAAU,MAAM,4BAA8BA,EAAI,SAAWA,EAAI,OAAS,iFAAiF,EAU7J,OAJA,WAAW,UAAY,CACrBC,EAAI,OAAOD,EAAI,IAAI,CACrB,EAAG0B,CAAe,EAEV,CAAE,YAAa1B,EAAI,IAAK,CAClC,CACF,EACA,CACE,KAAM,wBACN,MAAO,mBACP,YAAa,oLACb,YAAa,CAAE,KAAM,SAAU,WAAY,CAAC,CAAE,EAC9C,YAAa,CAAE,aAAc,EAAK,EAClC,QAAS,UAAY,CACnB,MAAQ,CAAE,OAAQO,EAAWpB,CAAG,CAAC,CAAC,IAAIuB,CAAa,CAAE,CACvD,CACF,EACA,CACE,KAAM,sBACN,MAAO,iBACP,YAAa,+JACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,MAAO,CAAE,KAAM,SAAU,YAAa,mEAAqE,EAC3G,MAAO,CAAE,YAAa,kFAAoF,CAC5G,EACA,SAAU,CAAC,QAAS,OAAO,CAC7B,EACA,QAAS,SAAU,EAAO,CACxB,IAAIP,EAAQW,EAAU3B,EAAK2C,GAASA,EAAM,KAAK,EAE/C,GAAI,CAAC3B,EACH,MAAU,MAAM,2CAA6CjC,EAAU4D,GAASA,EAAM,KAAK,EAAI,IAAI,EAGrG,IAAInB,EAAOT,EAAUC,CAAK,EACtBhC,EAAQ2D,EAAM,MAElB,GAAInB,IAAS,YAAcA,IAAS,QAClC,EAAM,QAAUxC,IAAU,IAAQ,OAAOA,CAAK,CAAC,CAAC,YAAY,IAAM,YAC7D,GAAIwC,IAAS,SAAU,CAC5B,IAAII,EAAS7C,EAAUC,CAAK,CAAC,CAAC,YAAY,EACtC0C,EAAS,MAAM,UAAU,KAAK,KAAKV,EAAM,SAAW,CAAC,EAAG,SAAU,EAAW,CAC/E,OAAO,OAAO+B,EAAU,KAAK,CAAC,CAAC,YAAY,IAAMnB,GAAU7C,EAAUgE,EAAU,IAAI,CAAC,CAAC,YAAY,IAAMnB,CACzG,CAAC,EAED,GAAI,CAACF,EACH,MAAU,MAAM,aAAeT,EAAWD,CAAK,EAAI,oBAAsBjC,EAAUC,CAAK,EAAI,IAAI,EAGlG,EAAM,MAAQ0C,EAAO,KACvB,KACE,GAAM,MAAQ1C,GAAS,KAAO,GAAK,OAAOA,CAAK,EAMjD,OAHAoD,EAAMpB,EAAO,OAAO,EACpBoB,EAAMpB,EAAO,QAAQ,EAEb,CAAE,MAAOO,EAAcP,CAAK,CAAE,CACxC,CACF,CACF,CACF,CAUA,SAASgC,EAAS,EAAc,EAAS,EAAQ,CAC/C,IAAIC,EAAQ/D,EAAgB,IAAIgE,CAAY,EAO5C,OALKD,IACH,EAAQ,IAAI,IACZ/D,EAAgB,IAAIgE,EAAcD,CAAK,GAGlC,QAAQ,IAAIZ,EAAgBC,CAAO,CAAC,CAAC,IAAI,SAAU,EAAM,CAC9D,OAAOY,EAAa,aAAaC,EAAMC,EAAS,CAAU,QAAO,EAAI,CAAC,CAAC,CAAC,CAAC,KAAK,UAAY,CACpFA,GAAUA,EAAO,UAIrBH,EAAM,IAAIE,EAAK,KAAMA,CAAI,EAErBC,GACFA,EAAO,iBAAiB,QAAS,UAAY,CACvCH,EAAM,IAAIE,EAAK,IAAI,IAAMA,GAC3BF,EAAM,OAAOE,EAAK,IAAI,CAE1B,EAAG,CAAE,KAAM,EAAK,CAAC,EAErB,EAAG,SAAU,EAAO,CAClB,QAAQ,KAAK,kBAAoBA,EAAK,KAAO,6BAA8BE,CAAK,CAClF,CAAC,CACH,CAAC,CAAC,CACJ,CASA,SAASC,EAAkB,EAAc,EAAM,CAC7C,IAAIL,EAAQC,EAAehE,EAAgB,IAAIgE,CAAY,EAAI,KAE/D,OAAQD,GAASA,EAAM,IAAIM,CAAI,GAAM,IACvC,CASA,GAPA,OAAO,yBAA2B,CACrB,YACM,kBACP,WACS,mBACrB,EAEI,OAAO,UAAY,OAAO,SAAS,aAAc,CACnD,IAAIC,EAAS,OAAO,iBAAmB,OAAO,gBAAgB,OAE9DR,EAAS,OAAO,SAAS,aAAc,CACrC,IAAK,OAAO,SACZ,IAAK,OAAO,SACZ,MAAO,OAAO,OAAO,OAAU,WAAa,OAAO,MAAM,KAAK,MAAM,EAAI,OACxE,QAAS,CAAC,EAAEQ,GAAUA,EAAO,QAC/B,CAAC,CACH,CACF,EAAA,CAAG,C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crestapps/webmcp",
|
|
3
|
-
"version": "2.0.0-preview.
|
|
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",
|