1(function (root, factory) { 2 "use strict"; 3 var api = factory(root); 4 if (typeof module === "object" && module.exports) module.exports = api; 5 if (root) root.XAgentWebMCP = api; 6 if (root && root.document) { 7 Promise.resolve(api.initialize(root.document)).catch(function () {}); 8 } 9})(typeof window !== "undefined" ? window : (typeof globalThis !== "undefined" ? globalThis : this), function (root) { 10 "use strict"; 11 12 var TOOL_NAMES = [ 13 "inspect_onboarding", 14 "preview_agent_id", 15 "get_install_plan", 16 "choose_verification_method", 17 "request_owner_approval", 18 "prepare_install_bundle", 19 "verify_installation", 20 "get_onboarding_status" 21 ]; 22 var READ_ONLY_TOOLS = { 23 inspect_onboarding: true, 24 get_onboarding_status: true 25 }; 26 var UNTRUSTED_OUTPUT_TOOLS = { 27 preview_agent_id: true, 28 get_install_plan: true, 29 request_owner_approval: true, 30 prepare_install_bundle: true, 31 verify_installation: true, 32 get_onboarding_status: true 33 }; 34 var SAFE_WORKFLOW_IDS = ["dry_run", "apply", "domain_verify", "activate"]; 35 var VERIFIED_STATUSES = ["verified_domain", "sdk_active", "connector_active"]; 36 var CREDENTIAL_PATTERN = /(private[_-]?key|agent[_-]?key|site[_-]?token|api[_-]?key|authorization\s*:|bearer\s+)/i; 37 38 function clone(value) { 39 return value == null ? value : JSON.parse(JSON.stringify(value)); 40 } 41 42 function stableStringify(value) { 43 if (value === null || typeof value === "string" || typeof value === "boolean") { 44 return JSON.stringify(value); 45 } 46 if (typeof value === "number") { 47 if (!Number.isFinite(value)) throw new TypeError("non_finite_number"); 48 return JSON.stringify(value); 49 } 50 if (Array.isArray(value)) { 51 return "[" + value.map(stableStringify).join(",") + "]"; 52 } 53 if (typeof value === "object") { 54 return "{" + Object.keys(value).sort().map(function (key) { 55 if (typeof value[key] === "undefined") throw new TypeError("undefined_manifest_value"); 56 return JSON.stringify(key) + ":" + stableStringify(value[key]); 57 }).join(",") + "}"; 58 } 59 throw new TypeError("unsupported_manifest_value"); 60 } 61 62 function normalizeTargetUrl(raw) { 63 if (typeof raw !== "string" || !raw.trim() || raw.length > 2048) return ""; 64 try { 65 var candidate = /^https:\/\//i.test(raw.trim()) ? raw.trim() : "https://" + raw.trim(); 66 var parsed = new URL(candidate); 67 var host = parsed.hostname.toLowerCase().replace(/\.$/, ""); 68 if (parsed.protocol !== "https:" || !host || parsed.username || parsed.password) return ""; 69 return "https://" + host + (parsed.port ? ":" + parsed.port : ""); 70 } catch (_error) { 71 return ""; 72 } 73 } 74 75 function manifestValidationError(target, manifest) { 76 if (!target || !manifest || typeof manifest !== "object") { 77 return { code: "manifest_invalid", message: "The production install manifest could not be validated." }; 78 } 79 if (manifest.schema !== "xagent-install-manifest-v1") { 80 return { code: "manifest_invalid", message: "The production install manifest schema is invalid." }; 81 } 82 if (!manifest.release || manifest.release.ready !== true) { 83 return { code: "release_not_ready", message: "The target-bound production release is not ready." }; 84 } 85 var expectedHost = new URL(target).hostname.toLowerCase().replace(/\.$/, ""); 86 var manifestHost = String(manifest.target && manifest.target.host || "").toLowerCase().replace(/\.$/, ""); 87 if (!manifestHost || manifestHost !== expectedHost) { 88 return { code: "target_mismatch", message: "The manifest is not bound to the selected website." }; 89 } 90 if (!manifest.target.url || normalizeTargetUrl(manifest.target.url) !== target) { 91 return { code: "target_mismatch", message: "The manifest target origin does not match the selected website." }; 92 } 93 return null; 94 } 95 96 async function canonicalManifestDigest(manifest, cryptoImpl) { 97 var snapshot = clone(manifest); 98 var cryptoObject = cryptoImpl || (root && root.crypto); 99 var Encoder = root && root.TextEncoder 100 ? root.TextEncoder 101 : (typeof TextEncoder !== "undefined" ? TextEncoder : null);
102 if (!cryptoObject || !cryptoObject.subtle || typeof cryptoObject.subtle.digest !== "function" || !Encoder) { 103 throw new Error("manifest_digest_unavailable"); 104 } 105 var canonical = stableStringify(snapshot); 106 var digest = await cryptoObject.subtle.digest("SHA-256", new Encoder().encode(canonical)); 107 var hex = Array.prototype.map.call(new Uint8Array(digest), function (byte) { 108 return byte.toString(16).padStart(2, "0"); 109 }).join(""); 110 return { manifest: snapshot, digest: "sha256:" + hex }; 111 } 112 113 function safeModules(modules) { 114 return (Array.isArray(modules) ? modules : []).slice(0, 32).map(function (item) { 115 return { 116 id: String(item && item.id || "").slice(0, 80), 117 grant: String(item && item.grant || "").slice(0, 120), 118 default: item && item.default === true, 119 writes: (Array.isArray(item && item.writes) ? item.writes : []).slice(0, 32).map(function (path) { 120 return String(path).slice(0, 240); 121 }) 122 }; 123 }); 124 } 125 126 function safePlan(manifest, digest) { 127 var modules = safeModules(manifest.modules); 128 var writes = []; 129 modules.forEach(function (item) { 130 if (!item.default) return; 131 item.writes.forEach(function (path) { 132 if (writes.indexOf(path) < 0) writes.push(path); 133 }); 134 }); 135 return { 136 schema: manifest.schema, 137 manifest_digest: digest, 138 target_host: manifest.target.host, 139 release: { 140 package: String(manifest.release.package || "").slice(0, 160), 141 version: String(manifest.release.version || "").slice(0, 80), 142 integrity: String(manifest.release.integrity || "").slice(0, 240), 143 ready: manifest.release.ready === true, 144 rollback_available: Boolean(manifest.release.rollback) 145 }, 146 authority: { 147 default: manifest.authority && manifest.authority.default || "read_only", 148 mother_service_can_write_site: false, 149 customer_agent_executes: true 150 }, 151 modules: modules, 152 declared_write_paths: writes, 153 owner_approval_required: writes.length > 0 || ( 154 Array.isArray(manifest.workflow) && manifest.workflow.some(function (step) { 155 return step && step.owner_approval_required === true; 156 }) 157 ) 158 }; 159 } 160 161 function safeVerification(value) { 162 var steps = {}; 163 var source = value && value.steps && typeof value.steps === "object" ? value.steps : {}; 164 var publicSource = value && value.public_state && typeof value.public_state === "object" 165 ? value.public_state 166 : null; 167 var publicState = publicSource ? { 168 schema: String(publicSource.schema || "").slice(0, 80), 169 state: String(publicSource.state || "unknown").slice(0, 80), 170 identity_status: String(publicSource.identity && publicSource.identity.status || "unknown").slice(0, 80), 171 identity_verified: publicSource.identity && publicSource.identity.verified === true, 172 installation_status: String(publicSource.installation && publicSource.installation.status || "unknown").slice(0, 80), 173 installed: publicSource.installation && typeof publicSource.installation.installed === "boolean" 174 ? publicSource.installation.installed 175 : null, 176 missing_data_policy: String(publicSource.missing_data_policy || "pending_not_zero").slice(0, 80) 177 } : null; 178 Object.keys(source).slice(0, 32).forEach(function (key) { 179 steps[String(key).slice(0, 80)] = source[key] === true; 180 }); 181 return { 182 ok: value && value.ok === true, 183 status: String(value && value.status || "pending").slice(0, 80), 184 public_state: publicState, 185 steps: steps 186 }; 187 } 188 189 function nextAction(currentState, busyOperation) { 190 if (busyOperation === "manifest_digest") return "Wait for the newest target-bound plan to finish validation."; 191 if (busyOperation === "verification") return "Wait for the independent public verification request to finish."; 192 var actions = { 193 idle: "Select the HTTPS website to onboard.", 194 target_selected: "Load the target-bound install plan or inspect existing public installation evidence.", 195 plan_ready: "Choose the public-file verification method.", 196 verification_method_selected: "Request owner approval in the visible permissions UI.", 197 approval_pending: "The website owner must review and use Continue to install.", 198 approved: "Prepare the install bundle for the customer-side Agent.", 199 bundle_ready: "The customer-side Agent may dry-run and apply the approved bundle.", 200 verifying: "Wait for the independent public refetch.", 201 verified: "Open the verified xAgent ID dashboard.", 202 blocked: "Resolve the reported validation or safety error, then reload the plan." 203 }; 204 return actions[currentState] || actions.idle; 205 } 206 207 function createControllerInternal(options) { 208 options = options || {}; 209 var doc = options.document || null; 210 var fetcher = options.fetch || (root && root.fetch ? root.fetch.bind(root) : null); 211 var eventTarget = options.eventTarget || root; 212 var cryptoImpl = options.crypto || (root && root.crypto); 213 var digestManifest = typeof options.digestManifest === "function" 214 ? options.digestManifest 215 : function (manifest) { return canonicalManifestDigest(manifest, cryptoImpl); }; 216 var locationOrigin = options.origin || (root && root.location ? root.location.origin : "https://xagent.id"); 217 var now = typeof options.now === "function" ? options.now : Date.now; 218 var nonceFactory = typeof options.nonceFactory === "function" ? options.nonceFactory : null; 219 var userActivation = options.userActivation || (root && root.navigator ? root.navigator.userActivation : null); 220 // How many times to look for the visible permissions-to-install transition 221 // after a trusted approval click before giving up on it. 222 var APPROVAL_TRANSITION_ATTEMPTS = 12; 223 var scheduleTransitionCheck = options.scheduleTransitionCheck || ( 224 root && typeof root.setTimeout === "function" 225 ? function (callback) { root.setTimeout(callback, 16); } 226 : function (callback) { Promise.resolve().then(callback); } 227 ); 228 var queueTask = options.queueMicrotask || ( 229 root && typeof root.queueMicrotask === "function" 230 ? root.queueMicrotask.bind(root) 231 : function (callback) { Promise.resolve().then(callback); } 232 ); 233 var disposed = false;
234 var planRequestSequence = 0; 235 var manifestEventSequence = 0; 236 var verificationRequestSequence = 0; 237 var listeners = []; 238 var state = { 239 state: "idle", 240 targetUrl: null, 241 manifest: null, 242 manifestDigest: null, 243 verificationMethod: null, 244 approval: null, 245 preparedBundle: null, 246 verification: null, 247 error: null, 248 busyOperation: null 249 }; 250 251 function t(source) { 252 return root && root.XAgentI18n && typeof root.XAgentI18n.t === "function" 253 ? root.XAgentI18n.t(source) 254 : source; 255 } 256 257 function query(selector) { 258 return doc && typeof doc.querySelector === "function" ? doc.querySelector(selector) : null; 259 } 260 261 function queryAll(selector) { 262 if (!doc || typeof doc.querySelectorAll !== "function") return []; 263 return Array.prototype.slice.call(doc.querySelectorAll(selector) || []); 264 } 265 266 function hasCurrentApproval() { 267 return Boolean( 268 state.approval 269 && state.approval.target_url === state.targetUrl 270 && state.approval.manifest_digest === state.manifestDigest 271 ); 272 } 273 274 function hasCurrentBundle() { 275 return Boolean( 276 state.preparedBundle 277 && state.preparedBundle.target_url === state.targetUrl 278 && state.preparedBundle.manifest_digest === state.manifestDigest 279 && hasCurrentApproval() 280 ); 281 } 282 283 function stateSnapshot() { 284 return { 285 state: state.state, 286 target_url: state.targetUrl, 287 manifest_digest: state.manifestDigest, 288 verification_method: state.verificationMethod, 289 human_approval_present: hasCurrentApproval(), 290 approval_consumed: Boolean(state.approval && state.approval.consumed_at), 291 bundle_prepared: hasCurrentBundle(), 292 busy_operation: state.busyOperation, 293 verification: clone(state.verification), 294 error: clone(state.error), 295 next_action: nextAction(state.state, state.busyOperation) 296 }; 297 } 298 299 function result(tool, ok, data, error) { 300 return { 301 ok: ok, 302 tool: tool, 303 state: state.state, 304 target_url: state.targetUrl, 305 data: data || {}, 306 error: error || null, 307 next_action: nextAction(state.state, state.busyOperation) 308 }; 309 } 310 311 function reject(tool, code, message) { 312 state.error = { code: code, message: message }; 313 return result(tool, false, {}, clone(state.error)); 314 } 315 316 function setText(selector, value) { 317 var node = query(selector); 318 if (node) node.textContent = value; 319 } 320 321 function renderCommands(manifest) { 322 var workflow = Array.isArray(manifest && manifest.workflow) ? manifest.workflow : []; 323 var steps = {}; 324 workflow.forEach(function (step) { 325 if (step && SAFE_WORKFLOW_IDS.concat(["dashboard_login"]).indexOf(step.id) >= 0) { 326 steps[step.id] = step; 327 } 328 }); 329 queryAll("[data-workflow-id]").forEach(function (node) { 330 var id = node.dataset ? node.dataset.workflowId : ""; 331 var step = steps[id]; 332 node.textContent = step && typeof step.command === "string" && step.command 333 ? step.command 334 : t("Command unavailable"); 335 }); 336 } 337 338 function approvalNotice() { 339 var panel = query('[data-wizard-panel="permissions"]'); 340 if (!panel) return null; 341 var notice = query("[data-webmcp-approval-notice]"); 342 if (!notice && doc && typeof doc.createElement === "function") { 343 notice = doc.createElement("p"); 344 notice.id = "webmcp-approval-status"; 345 notice.className = "manifest-status webmcp-approval-notice"; 346 notice.setAttribute("data-webmcp-approval-notice", ""); 347 notice.setAttribute("role", "status"); 348 notice.setAttribute("aria-live", "polite"); 349 var copy = typeof panel.querySelector === "function" ? panel.querySelector(".wizard-copy") : null; 350 var parent = copy || panel; 351 if (parent && typeof parent.appendChild === "function") parent.appendChild(notice); 352 } 353 return notice; 354 } 355 356 function renderApprovalNotice(mode) { 357 var notice = approvalNotice(); 358 var button = query('[data-wizard-next="install"]'); 359 if (!notice) return; 360 if (!mode) { 361 notice.hidden = true; 362 notice.textContent = ""; 363 if (button && typeof button.getAttribute === "function" && button.getAttribute("aria-describedby") === notice.id) { 364 button.removeAttribute("aria-describedby"); 365 } 366 return; 367 } 368 notice.hidden = false;
369 if (button && typeof button.setAttribute === "function") button.setAttribute("aria-describedby", notice.id); 370 if (mode === "pending") { 371 notice.textContent = t("An AI Agent is waiting for your review. Continue to install releases only the bounded command bundle; xagent.id does not execute it."); 372 } else if (mode === "approved") { 373 notice.textContent = t("Browser approval recorded for this exact plan. The Agent may now request the bounded command bundle."); 374 } else if (mode === "bundle") { 375 notice.textContent = t("The approved command bundle is ready for the customer-side Agent. No command has been executed by xagent.id."); 376 } 377 } 378 379 function renderManifestLinks(target) { 380 var manifestUrl = "/.well-known/xagent-install.json?url=" + encodeURIComponent(target); 381 queryAll("[data-manifest-link]").forEach(function (link) { 382 link.href = manifestUrl; 383 }); 384 } 385 386 function renderPlan(manifest, digest, source) { 387 if (!manifest) return; 388 renderCommands(manifest); 389 renderManifestLinks(state.targetUrl); 390 setText("#release-version", String(manifest.release.version || t("Ready"))); 391 setText( 392 "#manifest-integrity", 393 manifest.release.integrity 394 ? t("Published npm integrity") + " · " + String(manifest.release.integrity).slice(0, 24) + "â¦" 395 : t("Published integrity unavailable") 396 ); 397 setText("#hero-release", t("Release") + " " + String(manifest.release.version || "")); 398 setText("#hero-host", String(manifest.target.host || "")); 399 setText("#hero-package", String(manifest.release.package || "") + "@" + String(manifest.release.version || "")); 400 var plan = safePlan(manifest, digest); 401 setText( 402 "#hero-writes", 403 plan.declared_write_paths.length 404 ? plan.declared_write_paths.length + " " + t("declared paths, approval required") 405 : t("None") 406 ); 407 setText("#hero-state", t("Ready")); 408 var heroState = query("#hero-state"); 409 if (heroState && heroState.classList && typeof heroState.classList.add === "function") { 410 heroState.classList.add("is-ready"); 411 } 412 setText( 413 "#install-status", 414 (source === "agent" ? t("AI Agent loaded a target-bound plan for") : t("Plan loaded for")) 415 + " " + String(manifest.target.host || "") + ". " 416 + t("Review the exact permissions before installation.") 417 ); 418 } 419 420 function renderFailure(message) { 421 renderCommands(null); 422 renderApprovalNotice(null); 423 setText("#release-version", t("Blocked")); 424 setText("#manifest-integrity", t("Release metadata unavailable")); 425 setText("#install-status", t(message || "Production manifest unavailable or target mismatch. Stop before installation.")); 426 setText("#hero-state", t("Unavailable")); 427 } 428
429 function renderStateMessage() { 430 if (!state.targetUrl) return; 431 var host = new URL(state.targetUrl).hostname; 432 if (state.busyOperation === "manifest_digest") { 433 setText("#install-status", t("Validating the newest complete install plan for") + " " + host + "â¦"); 434 return; 435 } 436 if (state.busyOperation === "verification") { 437 setText("#install-status", t("Checking public installation evidence for") + " " + host + "â¦"); 438 return; 439 } 440 if (state.state === "approval_pending") { 441 setText("#install-status", t("An AI Agent loaded the plan and is waiting for owner review in Permissions.")); 442 renderApprovalNotice("pending"); 443 } else if (state.state === "approved") { 444 setText("#install-status", t("Browser approval is bound to the current plan. The Agent may request the bundle.")); 445 renderApprovalNotice("approved"); 446 } else if (state.state === "bundle_ready") { 447 setText("#install-status", t("The approved bundle is ready. xagent.id has not executed any command.")); 448 renderApprovalNotice("bundle"); 449 } else if (state.manifest) { 450 renderApprovalNotice(null); 451 } 452 } 453 454 function clearPlanState() { 455 state.manifest = null; 456 state.manifestDigest = null; 457 state.verificationMethod = null; 458 state.approval = null; 459 state.preparedBundle = null; 460 state.verification = null; 461 state.busyOperation = null; 462 renderApprovalNotice(null); 463 } 464
465 function invalidatePlan(code, message, targetState) { 466 clearPlanState(); 467 state.state = targetState || "blocked"; 468 state.error = code ? { code: code, message: message } : null; 469 renderFailure(message); 470 } 471 472 function cancelPendingRequests() { 473 planRequestSequence += 1; 474 manifestEventSequence += 1; 475 verificationRequestSequence += 1; 476 state.busyOperation = null; 477 } 478 479 function selectTarget(target, forceReset) { 480 cancelPendingRequests(); 481 if (forceReset || state.targetUrl !== target) clearPlanState(); 482 state.targetUrl = target; 483 state.state = "target_selected"; 484 state.error = null; 485 } 486 487 function restoreStateAfterVerification(previousState) { 488 if (previousState === "approval_pending" && state.manifest && state.manifestDigest && state.verificationMethod && !hasCurrentApproval()) { 489 return "approval_pending"; 490 } 491 if (previousState === "bundle_ready" && hasCurrentBundle()) return "bundle_ready"; 492 if (previousState === "approved" && hasCurrentApproval()) return "approved"; 493 if (previousState === "verification_method_selected" && state.manifest && state.manifestDigest && state.verificationMethod) { 494 return "verification_method_selected"; 495 } 496 if (previousState === "plan_ready" && state.manifest && state.manifestDigest) return "plan_ready"; 497 if (state.manifest && state.manifestDigest) { 498 if (hasCurrentBundle()) return "bundle_ready"; 499 if (hasCurrentApproval()) return "approved"; 500 if (state.verificationMethod) return "verification_method_selected"; 501 return "plan_ready"; 502 } 503 return state.targetUrl ? "target_selected" : "idle"; 504 } 505 506 function updateVisibleTarget(target) { 507 var input = query("#target-url"); 508 if (input) input.value = target; 509 } 510 511 function createNonce() { 512 if (nonceFactory) return String(nonceFactory()); 513 if (cryptoImpl && typeof cryptoImpl.randomUUID === "function") return cryptoImpl.randomUUID(); 514 if (cryptoImpl && typeof cryptoImpl.getRandomValues === "function") { 515 var bytes = new Uint8Array(16); 516 cryptoImpl.getRandomValues(bytes); 517 return Array.prototype.map.call(bytes, function (byte) { 518 return byte.toString(16).padStart(2, "0"); 519 }).join(""); 520 } 521 throw new Error("approval_nonce_unavailable"); 522 } 523 524 function targetInputMatches() { 525 var input = query("#target-url"); 526 return Boolean(input && normalizeTargetUrl(String(input.value || "")) === state.targetUrl); 527 } 528 529 function approvalButtonFromEvent(event) { 530 var target = event && event.target; 531 if (!target || typeof target.closest !== "function") return null; 532 var button = target.closest('[data-wizard-next="install"]'); 533 if (!button || typeof button.closest !== "function" || !button.closest("[data-install-wizard]")) return null; 534 if (button.disabled || button.hidden) return null; 535 if (button.getAttribute && button.getAttribute("aria-disabled") === "true") return null; 536 if (button.closest("[hidden]")) return null; 537 return button; 538 } 539 540 function trustedActivationPresent(event) { 541 if (!event || event.isTrusted !== true || event.defaultPrevented === true) return false; 542 if (userActivation && userActivation.isActive !== true) return false; 543 return true; 544 } 545 546 function scheduleVisibleApproval(event) { 547 if ( 548 disposed 549 || !trustedActivationPresent(event) 550 || state.state !== "approval_pending" 551 || state.busyOperation 552 || !state.manifest 553 || !state.manifestDigest 554 || !state.verificationMethod 555 || !targetInputMatches() 556 ) return; 557 var button = approvalButtonFromEvent(event); 558 if (!button) return; 559 var permissionsPanel = query('[data-wizard-panel="permissions"]'); 560 if (!permissionsPanel || permissionsPanel.hidden === true) return; 561 var candidate; 562 try { 563 candidate = { 564 target_url: state.targetUrl, 565 manifest_digest: state.manifestDigest, 566 nonce: createNonce(), 567 approved_at: new Date(now()).toISOString() 568 }; 569 } catch (_error) { 570 invalidatePlan("approval_nonce_unavailable", "The browser could not create an approval correlation nonce."); 571 return; 572 } 573 // The confirmation waits for the permissions-to-install transition to be on 574 // screen. A microtask alone is too early: the browser runs a microtask 575 // checkpoint between listeners, so it fires after this capture listener but 576 // before the wizard's own click handler has moved the panels, and approval 577 // never lands in a real browser. Poll instead, revalidating every 578 // precondition on each attempt, and give up if the transition never comes. 579 var attemptsLeft = APPROVAL_TRANSITION_ATTEMPTS; 580 function confirmVisibleApproval() { 581 if ( 582 disposed 583 || state.state !== "approval_pending" 584 || state.busyOperation 585 || state.targetUrl !== candidate.target_url 586 || state.manifestDigest !== candidate.manifest_digest 587 || !targetInputMatches() 588 ) return;
589 var installPanel = query('[data-wizard-panel="install"]'); 590 var currentPermissionsPanel = query('[data-wizard-panel="permissions"]'); 591 if (!installPanel || installPanel.hidden === true || (currentPermissionsPanel && currentPermissionsPanel.hidden !== true)) { 592 attemptsLeft -= 1; 593 if (attemptsLeft > 0) scheduleTransitionCheck(confirmVisibleApproval); 594 return; 595 } 596 state.approval = candidate; 597 state.preparedBundle = null; 598 state.state = "approved"; 599 state.error = null; 600 renderStateMessage(); 601 } 602 queueTask(confirmVisibleApproval); 603 } 604 605 function invalidateForVisibleTargetEdit(event) { 606 var target = event && event.target; 607 if (!target || target.id !== "target-url" || !state.targetUrl) return; 608 var normalized = normalizeTargetUrl(String(target.value || "")); 609 if (normalized === state.targetUrl) return; 610 cancelPendingRequests(); 611 invalidatePlan( 612 "target_changed", 613 "The website target changed. Load and approve a new target-bound plan.", 614 normalized ? "target_selected" : "idle" 615 ); 616 state.targetUrl = normalized || null; 617 } 618 619 function addListener(target, name, handler, optionsValue) { 620 if (!target || typeof target.addEventListener !== "function") return; 621 target.addEventListener(name, handler, optionsValue); 622 listeners.push([target, name, handler, optionsValue]); 623 } 624 625 async function ingestManifest(targetUrl, manifest, context) { 626 context = context || {}; 627 var target = normalizeTargetUrl(targetUrl || manifest && manifest.target && manifest.target.url); 628 var validation = manifestValidationError(target, manifest); 629 if (validation) { 630 if (context.current()) { 631 state.targetUrl = target || state.targetUrl; 632 invalidatePlan(validation.code, validation.message); 633 } 634 return false; 635 } 636 637 var previous = { 638 targetUrl: state.targetUrl, 639 state: state.state, 640 digest: state.manifestDigest, 641 approval: state.approval, 642 bundle: state.preparedBundle, 643 method: state.verificationMethod, 644 verification: state.verification 645 }; 646 state.busyOperation = "manifest_digest"; 647 renderStateMessage(); 648 var digested;
649 try { 650 digested = await digestManifest(manifest); 651 } catch (_error) { 652 if (context.current()) { 653 state.targetUrl = target; 654 invalidatePlan("manifest_digest_unavailable", "The complete install manifest could not be bound to an approval digest."); 655 } 656 return false; 657 } 658 if (disposed || !context.current()) return false; 659 state.busyOperation = null; 660 661 if (previous.targetUrl === target && previous.digest === digested.digest) { 662 state.targetUrl = target; 663 state.manifest = digested.manifest; 664 state.manifestDigest = digested.digest; 665 state.verificationMethod = previous.method; 666 state.approval = previous.approval; 667 state.preparedBundle = previous.bundle; 668 state.verification = previous.verification; 669 state.state = previous.state; 670 state.error = null; 671 updateVisibleTarget(target); 672 renderPlan(state.manifest, state.manifestDigest, context.source); 673 renderStateMessage(); 674 return true; 675 } 676 677 clearPlanState(); 678 state.targetUrl = target; 679 state.manifest = digested.manifest; 680 state.manifestDigest = digested.digest; 681 state.state = "plan_ready"; 682 state.error = null; 683 updateVisibleTarget(target); 684 verificationRequestSequence += 1; 685 renderPlan(state.manifest, state.manifestDigest, context.source); 686 return true; 687 } 688 689 function recordManifestFailure() { 690 cancelPendingRequests(); 691 invalidatePlan("manifest_unavailable", "The production install manifest is unavailable."); 692 } 693 694 function dispatchManifest(manifest) { 695 if (!eventTarget || typeof eventTarget.dispatchEvent !== "function") return; 696 var EventCtor = options.CustomEvent || (root && root.CustomEvent); 697 var detail = { 698 targetUrl: state.targetUrl, 699 manifest: clone(manifest), 700 source: "xagent-webmcp-controller" 701 }; 702 var event = EventCtor 703 ? new EventCtor("xagent:manifestloaded", { detail: detail }) 704 : { type: "xagent:manifestloaded", detail: detail }; 705 eventTarget.dispatchEvent(event); 706 } 707 708 async function inspectOnboarding() { 709 return result("inspect_onboarding", true, { 710 contract: "xagent-webmcp-onboarding-v1", 711 tools: TOOL_NAMES.slice(), 712 verification_methods: ["public_file"], 713 boundary: { 714 customer_agent_holds_credentials: true, 715 customer_agent_executes_changes: true, 716 xagent_id_public_refetch_only: true, 717 direct_dns_github_or_deploy_control: false, 718 page_script_synthetic_approval_blocked: true, 719 browser_activation_is_not_natural_person_attestation: true, 720 approval_bound_to_complete_manifest_digest: true 721 } 722 }); 723 } 724 725 async function previewAgentId(input) { 726 var target = normalizeTargetUrl(input && input.target_url); 727 if (!target) return reject("preview_agent_id", "invalid_target", "Provide an HTTPS website origin without credentials."); 728 selectTarget(target, true); 729 updateVisibleTarget(target); 730 renderManifestLinks(target); 731 var previewUrl = new URL("/card", locationOrigin); 732 previewUrl.searchParams.set("url", target); 733 return result("preview_agent_id", true, { 734 preview_url: previewUrl.toString(), 735 navigation_performed: false 736 }); 737 } 738 739 async function getInstallPlan(input) { 740 var target = normalizeTargetUrl(input && input.target_url); 741 if (!target) return reject("get_install_plan", "invalid_target", "Provide an HTTPS website origin without credentials."); 742 if (!fetcher) return reject("get_install_plan", "fetch_unavailable", "The browser cannot load the install manifest."); 743 selectTarget(target, true); 744 updateVisibleTarget(target); 745 renderManifestLinks(target); 746 var requestId = planRequestSequence; 747 state.busyOperation = "manifest_fetch"; 748 setText("#install-status", t("Loading the production install planâ¦")); 749 try { 750 var response = await fetcher( 751 "/.well-known/xagent-install.json?url=" + encodeURIComponent(target), 752 { headers: { Accept: "application/json" }, credentials: "omit" } 753 ); 754 if (requestId !== planRequestSequence || state.targetUrl !== target) { 755 return reject("get_install_plan", "request_superseded", "A newer website target replaced this install-plan request."); 756 } 757 if (!response || !response.ok) { 758 invalidatePlan("release_unavailable", "The production install manifest is unavailable."); 759 return result("get_install_plan", false, {}, clone(state.error)); 760 } 761 var manifest = await response.json(); 762 if (requestId !== planRequestSequence || state.targetUrl !== target) { 763 return reject("get_install_plan", "request_superseded", "A newer website target replaced this install-plan request."); 764 } 765 var accepted = await ingestManifest(target, manifest, { 766 requestId: requestId, 767 source: "agent", 768 current: function () { 769 return requestId === planRequestSequence && state.targetUrl === target; 770 } 771 }); 772 if (!accepted) { 773 if (requestId !== planRequestSequence || state.targetUrl !== target) { 774 return reject("get_install_plan", "request_superseded", "A newer website target or manifest replaced this install-plan request."); 775 } 776 return result("get_install_plan", false, {}, clone(state.error)); 777 } 778 dispatchManifest(state.manifest); 779 return result("get_install_plan", true, safePlan(state.manifest, state.manifestDigest)); 780 } catch (_error) { 781 if (requestId === planRequestSequence && state.targetUrl === target) { 782 invalidatePlan("manifest_invalid", "The production install manifest could not be validated."); 783 return result("get_install_plan", false, {}, clone(state.error)); 784 } 785 return reject("get_install_plan", "request_superseded", "A newer website target replaced this install-plan request."); 786 } 787 } 788 789 async function chooseVerificationMethod(input) { 790 if (state.busyOperation) return reject("choose_verification_method", "plan_busy", "The current plan operation must finish first."); 791 if (!state.manifest || !state.manifestDigest) return reject("choose_verification_method", "plan_required", "Load a target-bound install plan first."); 792 if (!input || input.method !== "public_file") return reject("choose_verification_method", "unsupported_method", "Version 1 supports only public_file verification."); 793 state.verificationMethod = "public_file"; 794 state.approval = null; 795 state.preparedBundle = null; 796 state.state = "verification_method_selected"; 797 state.error = null; 798 renderStateMessage(); 799 return result("choose_verification_method", true, { 800 method: "public_file", 801 publisher: "customer_agent", 802 verifier: "xagent_id_public_refetch" 803 }); 804 } 805 806 async function requestOwnerApproval() { 807 if (state.busyOperation) return reject("request_owner_approval", "plan_busy", "The current plan operation must finish first."); 808 if (!state.manifest || !state.manifestDigest) return reject("request_owner_approval", "plan_required", "Load a target-bound install plan first."); 809 if (!state.verificationMethod) return reject("request_owner_approval", "verification_method_required", "Choose the verification method first."); 810 state.approval = null; 811 state.preparedBundle = null; 812 state.state = "approval_pending"; 813 state.error = null; 814 dispatchManifest(state.manifest); 815 var step = query('[data-wizard-step="permissions"]'); 816 if (step && typeof step.click === "function") step.click();
817 var panel = query('[data-wizard-panel="permissions"]'); 818 if (panel && typeof panel.scrollIntoView === "function") { 819 panel.scrollIntoView({ behavior: "smooth", block: "center" }); 820 } 821 renderStateMessage(); 822 var plan = safePlan(state.manifest, state.manifestDigest); 823 return result("request_owner_approval", true, { 824 approval_granted: false, 825 visible_confirmation_required: true, 826 browser_signal_limit: "not_cryptographic_proof_of_natural_person_or_owner_identity", 827 manifest_digest: state.manifestDigest, 828 requested_grants: plan.modules.filter(function (item) { return item.default; }).map(function (item) { 829 return item.grant; 830 }).filter(Boolean), 831 declared_write_paths: plan.declared_write_paths 832 }); 833 } 834 835 async function prepareInstallBundle() { 836 if (state.busyOperation || state.state === "verifying") { 837 return reject("prepare_install_bundle", "plan_busy", "The current plan or verification operation is still in progress."); 838 } 839 if (!state.manifest || !state.manifestDigest) return reject("prepare_install_bundle", "plan_required", "Load a target-bound install plan first."); 840 if (state.state === "blocked") return reject("prepare_install_bundle", "plan_invalidated", "The install plan is blocked and must be loaded again."); 841 if (hasCurrentBundle()) { 842 var replay = clone(state.preparedBundle); 843 replay.idempotent_replay = true; 844 return result("prepare_install_bundle", true, replay); 845 } 846 if (state.state !== "approved" || !hasCurrentApproval()) { 847 return reject("prepare_install_bundle", "human_approval_required", "The website owner must review the visible permissions first."); 848 } 849 var byId = {}; 850 (Array.isArray(state.manifest.workflow) ? state.manifest.workflow : []).forEach(function (step) { 851 if (step && SAFE_WORKFLOW_IDS.indexOf(step.id) >= 0) byId[step.id] = step; 852 }); 853 var commands = []; 854 for (var i = 0; i < SAFE_WORKFLOW_IDS.length; i += 1) { 855 var id = SAFE_WORKFLOW_IDS[i]; 856 var command = byId[id] && byId[id].command; 857 if (typeof command !== "string" || !command || command.length > 4096) continue; 858 if (CREDENTIAL_PATTERN.test(command)) { 859 invalidatePlan("credential_material_rejected", "The install bundle contained credential-like material and was blocked."); 860 return result("prepare_install_bundle", false, {}, clone(state.error)); 861 } 862 commands.push({ 863 id: id, 864 command: command, 865 owner_approval_required: id === "apply" || byId[id].owner_approval_required === true 866 }); 867 } 868 if (!commands.length) { 869 invalidatePlan("bundle_unavailable", "No approved install commands are available."); 870 return result("prepare_install_bundle", false, {}, clone(state.error)); 871 } 872 state.approval.consumed_at = new Date(now()).toISOString(); 873 state.preparedBundle = { 874 commands: commands, 875 target_url: state.targetUrl, 876 manifest_digest: state.manifestDigest, 877 approval: { 878 source: "browser_user_activation_and_visible_transition", 879 present: true, 880 correlation_nonce: state.approval.nonce, 881 approved_at: state.approval.approved_at, 882 consumed_at: state.approval.consumed_at, 883 attestation_limit: "not_cryptographic_proof_of_natural_person_or_owner_identity" 884 }, 885 customer_agent_executes: true, 886 xagent_id_executes_commands: false, 887 credentials_requested: false, 888 idempotent_replay: false 889 }; 890 state.state = "bundle_ready"; 891 state.error = null; 892 renderStateMessage(); 893 return result("prepare_install_bundle", true, clone(state.preparedBundle)); 894 } 895 896 async function verifyInstallation(input) { 897 var requested = input && input.target_url ? normalizeTargetUrl(input.target_url) : state.targetUrl; 898 if (!requested || requested !== state.targetUrl) { 899 return reject("verify_installation", "target_mismatch", "Verification must use the currently selected website target."); 900 } 901 if (!fetcher) return reject("verify_installation", "fetch_unavailable", "The browser cannot request public verification."); 902 if (state.busyOperation) return reject("verify_installation", "plan_busy", "Another plan or verification operation is already in progress."); 903 var previousState = state.state; 904 var requestId = ++verificationRequestSequence; 905 state.busyOperation = "verification"; 906 state.state = "verifying"; 907 state.error = null; 908 renderStateMessage();
909 try { 910 var response = await fetcher( 911 "/api/site_agents/install_status?url=" + encodeURIComponent(requested), 912 { headers: { Accept: "application/json" }, credentials: "omit" } 913 ); 914 if (requestId !== verificationRequestSequence || state.targetUrl !== requested) { 915 return reject("verify_installation", "request_superseded", "A newer website target replaced this verification request."); 916 } 917 if (!response || !response.ok) { 918 state.busyOperation = null; 919 state.state = restoreStateAfterVerification(previousState); 920 renderStateMessage(); 921 return reject("verify_installation", "verification_unavailable", "Public installation status is unavailable."); 922 } 923 var verification = safeVerification(await response.json()); 924 if (requestId !== verificationRequestSequence || state.targetUrl !== requested) { 925 return reject("verify_installation", "request_superseded", "A newer website target replaced this verification request."); 926 } 927 state.verification = verification; 928 var publicStatus = verification.public_state && verification.public_state.state; 929 var verified = verification.ok && ( 930 VERIFIED_STATUSES.indexOf(verification.status) >= 0 931 || VERIFIED_STATUSES.indexOf(publicStatus) >= 0 932 ); 933 state.busyOperation = null; 934 state.state = verified ? "verified" : restoreStateAfterVerification(previousState); 935 state.error = null; 936 renderStateMessage(); 937 return result("verify_installation", true, { 938 verified: verified, 939 evidence: verification, 940 restored_state: verified ? null : state.state 941 }); 942 } catch (_error) { 943 if (requestId === verificationRequestSequence && state.targetUrl === requested) { 944 state.busyOperation = null; 945 state.state = restoreStateAfterVerification(previousState); 946 renderStateMessage(); 947 return reject("verify_installation", "verification_invalid", "Public installation status could not be validated."); 948 } 949 return reject("verify_installation", "request_superseded", "A newer website target replaced this verification request."); 950 } 951 } 952 953 async function getOnboardingStatus() { 954 return result("get_onboarding_status", true, stateSnapshot()); 955 } 956 957 function execute(name, input) { 958 var handlers = { 959 inspect_onboarding: inspectOnboarding, 960 preview_agent_id: previewAgentId, 961 get_install_plan: getInstallPlan, 962 choose_verification_method: chooseVerificationMethod, 963 request_owner_approval: requestOwnerApproval, 964 prepare_install_bundle: prepareInstallBundle, 965 verify_installation: verifyInstallation, 966 get_onboarding_status: getOnboardingStatus 967 }; 968 if (!handlers[name]) { 969 return Promise.resolve(reject(String(name || "unknown"), "unknown_tool", "The requested WebMCP onboarding tool is not registered.")); 970 } 971 return handlers[name](input || {}); 972 } 973 974 function onManifestLoaded(event) { 975 var detail = event && event.detail || {}; 976 if (detail.source === "xagent-webmcp-controller") { 977 renderPlan(detail.manifest, state.manifestDigest, "agent"); 978 renderStateMessage(); 979 return; 980 } 981 var target = normalizeTargetUrl(detail.targetUrl || detail.manifest && detail.manifest.target && detail.manifest.target.url); 982 var eventId = ++manifestEventSequence; 983 planRequestSequence += 1; 984 verificationRequestSequence += 1; 985 var requestId = planRequestSequence; 986 if (target && target !== state.targetUrl) { 987 clearPlanState(); 988 state.targetUrl = target; 989 state.state = "target_selected"; 990 state.error = null; 991 } 992 ingestManifest(detail.targetUrl, detail.manifest, { 993 eventId: eventId, 994 requestId: requestId, 995 source: "human_ui", 996 current: function () { 997 return eventId === manifestEventSequence && requestId === planRequestSequence; 998 } 999 }).catch(function () { 1000 if (eventId === manifestEventSequence && requestId === planRequestSequence) recordManifestFailure(); 1001 }); 1002 } 1003 1004 function onManifestFailed() { 1005 recordManifestFailure(); 1006 } 1007 1008 function onLanguageChange() { 1009 if (state.manifest) renderPlan(state.manifest, state.manifestDigest, "agent"); 1010 renderStateMessage(); 1011 } 1012 1013 addListener(doc, "click", scheduleVisibleApproval, true); 1014 addListener(doc, "input", invalidateForVisibleTargetEdit, true); 1015 addListener(eventTarget, "xagent:manifestloaded", onManifestLoaded); 1016 addListener(eventTarget, "xagent:manifestfailed", onManifestFailed); 1017 addListener(eventTarget, "xagent:languagechange", onLanguageChange); 1018 1019 function disposeInternal() { 1020 if (disposed) return; 1021 disposed = true; 1022 cancelPendingRequests();
1023 listeners.forEach(function (entry) { 1024 if (entry[0] && typeof entry[0].removeEventListener === "function") { 1025 entry[0].removeEventListener(entry[1], entry[2], entry[3]); 1026 } 1027 }); 1028 listeners = []; 1029 clearPlanState(); 1030 state.targetUrl = null; 1031 state.state = "idle"; 1032 state.error = null; 1033 } 1034 1035 return { 1036 publicController: { 1037 execute: execute, 1038 getState: stateSnapshot 1039 }, 1040 dispose: disposeInternal 1041 }; 1042 } 1043 1044 function createController(options) { 1045 return createControllerInternal(options).publicController; 1046 } 1047 1048 function schema(properties, required) { 1049 return { 1050 type: "object", 1051 properties: properties || {}, 1052 required: required || [], 1053 additionalProperties: false 1054 }; 1055 } 1056 1057 function toolDefinitions(controller) { 1058 var definitions = [ 1059 { 1060 name: "inspect_onboarding", 1061 description: "Inspect xAgent ID onboarding capabilities and authority boundaries. This reads page state and makes no external change.", 1062 inputSchema: schema() 1063 }, 1064 { 1065 name: "preview_agent_id", 1066 description: "Validate and select an HTTPS website, then return its xAgent ID card preview URL without navigating or changing the website.", 1067 inputSchema: schema({ target_url: { type: "string", maxLength: 2048, description: "HTTPS website origin controlled by the user." } }, ["target_url"]) 1068 }, 1069 { 1070 name: "get_install_plan", 1071 description: "Read, validate, and bind the complete production target-bound install plan. Commands are withheld until visible browser approval.", 1072 inputSchema: schema({ target_url: { type: "string", maxLength: 2048, description: "HTTPS website origin controlled by the user." } }, ["target_url"]) 1073 }, 1074 { 1075 name: "choose_verification_method", 1076 description: "Choose the supported public proof method in browser-local onboarding state. This does not publish proof.", 1077 inputSchema: schema({ method: { type: "string", enum: ["public_file"], description: "Public xAgent ID file verified by independent refetch." } }, ["method"]) 1078 }, 1079 { 1080 name: "request_owner_approval", 1081 description: "Open the visible permissions UI and request review. The result is not approval; a trusted browser activation must complete the visible transition.", 1082 inputSchema: schema() 1083 }, 1084 { 1085 name: "prepare_install_bundle", 1086 description: "After browser approval bound to the complete manifest digest, return bounded commands for the customer-side Agent. This tool never executes commands or receives credentials.", 1087 inputSchema: schema() 1088 }, 1089 { 1090 name: "verify_installation", 1091 description: "Ask xagent.id to independently refetch public installation evidence for the selected target. This cannot accept a browser assertion of success.", 1092 inputSchema: schema({ target_url: { type: "string", maxLength: 2048, description: "Optional HTTPS origin; it must match the selected target." } }) 1093 }, 1094 { 1095 name: "get_onboarding_status", 1096 description: "Read the current onboarding state and next safe action without returning install commands.", 1097 inputSchema: schema() 1098 } 1099 ]; 1100 return definitions.map(function (definition) { 1101 var annotations = {}; 1102 if (READ_ONLY_TOOLS[definition.name]) annotations.readOnlyHint = true; 1103 if (UNTRUSTED_OUTPUT_TOOLS[definition.name]) annotations.untrustedContentHint = true; 1104 definition.annotations = annotations; 1105 definition.execute = function (input) { return controller.execute(definition.name, input); }; 1106 return definition; 1107 }); 1108 } 1109 1110 var initialized = false; 1111 var initializationPromise = null; 1112 var activeController = null; 1113 var activeDispose = null; 1114 var registrationAbortController = null; 1115 1116 function safeRegistrationError(error) { 1117 return { 1118 name: String(error && error.name || "Error").slice(0, 80), 1119 message: String(error && error.message || "WebMCP tool registration failed.").slice(0, 240) 1120 }; 1121 } 1122 1123 function optionsAbortController(contextRoot) { 1124 if (contextRoot && typeof contextRoot.AbortController === "function") return contextRoot.AbortController; 1125 return typeof AbortController === "function" ? AbortController : null; 1126 } 1127 1128 // WebMCP page registration. On a real page `doc` is the live document, so the 1129 // registration call below is literally document.modelContext.registerTool(...). 1130 // The `doc` parameter exists only so the Node test harness can drive the same 1131 // code with a fake document. 1132 function registerPageTool(doc, definition, options) { 1133 if (typeof document !== "undefined" && doc === document) { 1134 return document.modelContext.registerTool(definition, options); 1135 } 1136 return doc.modelContext.registerTool(definition, options); 1137 } 1138 1139 function initialize(doc) { 1140 if (initialized) { 1141 return Promise.resolve({ 1142 registered: true,
1143 count: TOOL_NAMES.length, 1144 reason: "already_initialized", 1145 controller: activeController 1146 }); 1147 } 1148 if (initializationPromise) return initializationPromise; 1149 if (!doc) return Promise.resolve({ registered: false, reason: "document_unavailable" }); 1150 if (!doc.modelContext || typeof doc.modelContext.registerTool !== "function") { 1151 return Promise.resolve({ registered: false, reason: "webmcp_unavailable" }); 1152 } 1153 var AbortControllerCtor = optionsAbortController(root); 1154 if (!AbortControllerCtor) return Promise.resolve({ registered: false, reason: "abort_controller_unavailable" }); 1155 1156 initializationPromise = (async function () { 1157 var internal = createControllerInternal({ document: doc }); 1158 var controller = internal.publicController; 1159 var abortController = new AbortControllerCtor(); 1160 var definitions = toolDefinitions(controller); 1161 try { 1162 for (var i = 0; i < definitions.length; i += 1) { 1163 await registerPageTool(doc, definitions[i], { signal: abortController.signal }); 1164 } 1165 } catch (error) { 1166 abortController.abort(); 1167 internal.dispose(); 1168 return { 1169 registered: false, 1170 count: 0, 1171 reason: "registration_failed", 1172 error: safeRegistrationError(error) 1173 }; 1174 } 1175 activeController = controller; 1176 activeDispose = internal.dispose; 1177 registrationAbortController = abortController; 1178 initialized = true; 1179 return { registered: true, count: definitions.length, controller: activeController }; 1180 })().then(function (value) { 1181 initializationPromise = null; 1182 return value; 1183 }, function (error) { 1184 initializationPromise = null; 1185 return { 1186 registered: false, 1187 count: 0, 1188 reason: "registration_failed", 1189 error: safeRegistrationError(error) 1190 }; 1191 }); 1192 return initializationPromise; 1193 } 1194 1195 return { 1196 TOOL_NAMES: TOOL_NAMES, 1197 READ_ONLY_TOOLS: READ_ONLY_TOOLS, 1198 normalizeTargetUrl: normalizeTargetUrl, 1199 canonicalManifestDigest: canonicalManifestDigest, 1200 createController: createController, 1201 toolDefinitions: toolDefinitions, 1202 initialize: initialize, 1203 getController: function () { return activeController; }, 1204 getRegistrationStatus: function () { 1205 return { 1206 initialized: initialized, 1207 initializing: Boolean(initializationPromise), 1208 abortable: Boolean(registrationAbortController), 1209 internal_disposer_retained: Boolean(activeDispose) 1210 }; 1211 } 1212 }; 1213});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.