@agent-native/core 0.68.3 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +54 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/changelog/parse.ts +245 -0
  5. package/corpus/core/src/cli/changelog.ts +199 -0
  6. package/corpus/core/src/cli/index.ts +19 -0
  7. package/corpus/core/src/cli/recap.ts +24 -1
  8. package/corpus/core/src/cli/skills.ts +72 -45
  9. package/corpus/core/src/client/CommandMenu.tsx +183 -75
  10. package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
  11. package/corpus/core/src/client/analytics.ts +181 -0
  12. package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
  13. package/corpus/core/src/client/index.ts +11 -0
  14. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
  15. package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
  16. package/corpus/core/src/extensions/web-search-tool.ts +45 -4
  17. package/corpus/core/src/mcp/build-server.ts +1 -1
  18. package/corpus/core/src/mcp/embed-app.ts +112 -6
  19. package/corpus/core/src/secrets/register-framework-secrets.ts +7 -0
  20. package/corpus/core/src/server/attribution.ts +211 -0
  21. package/corpus/core/src/server/auth.ts +6 -0
  22. package/corpus/core/src/server/better-auth-instance.ts +49 -5
  23. package/corpus/core/src/server/builder-browser.ts +27 -1
  24. package/corpus/core/src/server/core-routes-plugin.ts +58 -0
  25. package/corpus/core/src/server/google-oauth.ts +19 -6
  26. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  27. package/corpus/core/src/styles/agent-conversation.css +21 -0
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  29. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  30. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  31. package/corpus/templates/analytics/actions/install-dashboard-template.ts +2 -2
  32. package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
  33. package/corpus/templates/analytics/actions/update-dashboard.ts +5 -3
  34. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
  35. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -2
  36. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
  37. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -213
  38. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -3
  39. package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
  40. package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
  41. package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
  42. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
  43. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
  44. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
  45. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
  46. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
  47. package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
  48. package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
  49. package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
  50. package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
  51. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
  52. package/corpus/templates/analytics/server/db/index.ts +1 -1
  53. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
  54. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
  55. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
  56. package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
  57. package/corpus/templates/assets/CHANGELOG.md +10 -0
  58. package/corpus/templates/assets/app/root.tsx +7 -1
  59. package/corpus/templates/assets/app/routes/settings.tsx +6 -0
  60. package/corpus/templates/brain/CHANGELOG.md +10 -0
  61. package/corpus/templates/brain/app/root.tsx +7 -1
  62. package/corpus/templates/brain/app/routes/settings.tsx +8 -1
  63. package/corpus/templates/calendar/CHANGELOG.md +10 -0
  64. package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
  65. package/corpus/templates/calendar/app/root.tsx +7 -1
  66. package/corpus/templates/chat/CHANGELOG.md +10 -0
  67. package/corpus/templates/chat/app/root.tsx +7 -1
  68. package/corpus/templates/clips/CHANGELOG.md +10 -0
  69. package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
  70. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
  71. package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
  72. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +72 -20
  73. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +69 -2
  74. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
  75. package/corpus/templates/clips/app/lib/recorder-preferences.ts +67 -0
  76. package/corpus/templates/clips/app/root.tsx +7 -1
  77. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
  78. package/corpus/templates/clips/app/routes/_app.trash.tsx +21 -0
  79. package/corpus/templates/clips/app/routes/record.tsx +92 -8
  80. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
  81. package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
  85. package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
  86. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  87. package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
  88. package/corpus/templates/clips/desktop/src/styles.css +49 -0
  89. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  90. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +67 -1
  91. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  92. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
  93. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +51 -1
  94. package/corpus/templates/clips/shared/share-attribution.ts +79 -0
  95. package/corpus/templates/content/CHANGELOG.md +10 -0
  96. package/corpus/templates/content/app/root.tsx +7 -1
  97. package/corpus/templates/content/vitest.config.ts +3 -0
  98. package/corpus/templates/design/CHANGELOG.md +10 -0
  99. package/corpus/templates/design/actions/generate-design.ts +4 -0
  100. package/corpus/templates/design/app/root.tsx +7 -1
  101. package/corpus/templates/design/app/routes/settings.tsx +10 -2
  102. package/corpus/templates/dispatch/CHANGELOG.md +10 -0
  103. package/corpus/templates/dispatch/app/root.tsx +7 -1
  104. package/corpus/templates/forms/CHANGELOG.md +10 -0
  105. package/corpus/templates/forms/app/root.tsx +7 -1
  106. package/corpus/templates/macros/CHANGELOG.md +10 -0
  107. package/corpus/templates/macros/app/root.tsx +7 -1
  108. package/corpus/templates/mail/CHANGELOG.md +10 -0
  109. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
  110. package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
  111. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
  112. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
  113. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
  114. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
  115. package/corpus/templates/plan/AGENTS.md +4 -0
  116. package/corpus/templates/plan/CHANGELOG.md +10 -0
  117. package/corpus/templates/plan/actions/view-screen.ts +5 -0
  118. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
  119. package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
  120. package/corpus/templates/plan/app/root.tsx +7 -1
  121. package/corpus/templates/plan/shared/share-attribution.ts +72 -0
  122. package/corpus/templates/slides/CHANGELOG.md +10 -0
  123. package/corpus/templates/slides/app/root.tsx +7 -1
  124. package/corpus/templates/videos/CHANGELOG.md +4 -151
  125. package/corpus/templates/videos/app/root.tsx +7 -1
  126. package/dist/changelog/parse.d.ts +70 -0
  127. package/dist/changelog/parse.d.ts.map +1 -0
  128. package/dist/changelog/parse.js +200 -0
  129. package/dist/changelog/parse.js.map +1 -0
  130. package/dist/cli/changelog.d.ts +4 -0
  131. package/dist/cli/changelog.d.ts.map +1 -0
  132. package/dist/cli/changelog.js +166 -0
  133. package/dist/cli/changelog.js.map +1 -0
  134. package/dist/cli/index.js +18 -0
  135. package/dist/cli/index.js.map +1 -1
  136. package/dist/cli/recap.d.ts.map +1 -1
  137. package/dist/cli/recap.js +22 -1
  138. package/dist/cli/recap.js.map +1 -1
  139. package/dist/cli/skills.d.ts +3 -3
  140. package/dist/cli/skills.d.ts.map +1 -1
  141. package/dist/cli/skills.js +72 -45
  142. package/dist/cli/skills.js.map +1 -1
  143. package/dist/client/CommandMenu.d.ts +17 -1
  144. package/dist/client/CommandMenu.d.ts.map +1 -1
  145. package/dist/client/CommandMenu.js +49 -13
  146. package/dist/client/CommandMenu.js.map +1 -1
  147. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  148. package/dist/client/agent-chat-adapter.js +43 -6
  149. package/dist/client/agent-chat-adapter.js.map +1 -1
  150. package/dist/client/analytics.d.ts +18 -0
  151. package/dist/client/analytics.d.ts.map +1 -1
  152. package/dist/client/analytics.js +177 -0
  153. package/dist/client/analytics.js.map +1 -1
  154. package/dist/client/changelog/Changelog.d.ts +47 -0
  155. package/dist/client/changelog/Changelog.d.ts.map +1 -0
  156. package/dist/client/changelog/Changelog.js +138 -0
  157. package/dist/client/changelog/Changelog.js.map +1 -0
  158. package/dist/client/index.d.ts +2 -1
  159. package/dist/client/index.d.ts.map +1 -1
  160. package/dist/client/index.js +2 -1
  161. package/dist/client/index.js.map +1 -1
  162. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  163. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  164. package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
  165. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  166. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  167. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  168. package/dist/client/settings/useBuilderStatus.js +58 -10
  169. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  170. package/dist/extensions/web-search-tool.d.ts +5 -3
  171. package/dist/extensions/web-search-tool.d.ts.map +1 -1
  172. package/dist/extensions/web-search-tool.js +36 -4
  173. package/dist/extensions/web-search-tool.js.map +1 -1
  174. package/dist/mcp/build-server.js +1 -1
  175. package/dist/mcp/build-server.js.map +1 -1
  176. package/dist/mcp/embed-app.d.ts.map +1 -1
  177. package/dist/mcp/embed-app.js +112 -6
  178. package/dist/mcp/embed-app.js.map +1 -1
  179. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  180. package/dist/secrets/register-framework-secrets.js +6 -0
  181. package/dist/secrets/register-framework-secrets.js.map +1 -1
  182. package/dist/server/attribution.d.ts +83 -0
  183. package/dist/server/attribution.d.ts.map +1 -0
  184. package/dist/server/attribution.js +178 -0
  185. package/dist/server/attribution.js.map +1 -0
  186. package/dist/server/auth.d.ts.map +1 -1
  187. package/dist/server/auth.js +7 -0
  188. package/dist/server/auth.js.map +1 -1
  189. package/dist/server/better-auth-instance.d.ts +9 -1
  190. package/dist/server/better-auth-instance.d.ts.map +1 -1
  191. package/dist/server/better-auth-instance.js +32 -2
  192. package/dist/server/better-auth-instance.js.map +1 -1
  193. package/dist/server/builder-browser.d.ts +4 -0
  194. package/dist/server/builder-browser.d.ts.map +1 -1
  195. package/dist/server/builder-browser.js +20 -1
  196. package/dist/server/builder-browser.js.map +1 -1
  197. package/dist/server/core-routes-plugin.d.ts +25 -0
  198. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  199. package/dist/server/core-routes-plugin.js +38 -0
  200. package/dist/server/core-routes-plugin.js.map +1 -1
  201. package/dist/server/google-oauth.d.ts.map +1 -1
  202. package/dist/server/google-oauth.js +16 -2
  203. package/dist/server/google-oauth.js.map +1 -1
  204. package/dist/shared/mcp-embed-headers.js +1 -1
  205. package/dist/shared/mcp-embed-headers.js.map +1 -1
  206. package/dist/styles/agent-conversation.css +21 -0
  207. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  208. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  209. package/package.json +1 -1
  210. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  211. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  212. /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
@@ -428,6 +428,49 @@ export function embedApp(options = {}) {
428
428
  };
429
429
  }
430
430
 
431
+ function externalOpenUrlForAppUrl(appUrl) {
432
+ if (openUrl) return openUrl;
433
+ try {
434
+ const url = new URL(appUrl.href);
435
+ url.searchParams.delete("embedded");
436
+ url.searchParams.delete("__an_embed_token");
437
+ url.searchParams.delete(chatBridgeParam);
438
+ return url.toString();
439
+ } catch (_err) {
440
+ return "";
441
+ }
442
+ }
443
+
444
+ function installExternalOpenControl(appUrl) {
445
+ const href = externalOpenUrlForAppUrl(appUrl);
446
+ if (!href || !document.body || document.getElementById("agent-native-external-open-control")) {
447
+ return;
448
+ }
449
+ const host = document.createElement("div");
450
+ host.id = "agent-native-external-open-control";
451
+ host.style.position = "fixed";
452
+ host.style.top = "10px";
453
+ host.style.right = "10px";
454
+ host.style.zIndex = "2147483647";
455
+ host.style.pointerEvents = "auto";
456
+ const root = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
457
+ root.innerHTML =
458
+ '<style>' +
459
+ ':host{all:initial;}' +
460
+ 'button{box-sizing:border-box;min-height:30px;border:1px solid color-mix(in srgb, CanvasText 18%, Canvas);border-radius:7px;background:Canvas;color:CanvasText;box-shadow:0 6px 22px color-mix(in srgb, CanvasText 14%, transparent);cursor:pointer;font:700 12px ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:0 10px;white-space:nowrap;}' +
461
+ 'button:hover{background:color-mix(in srgb, CanvasText 6%, Canvas);}' +
462
+ '</style>' +
463
+ '<button type="button">Open in new tab</button>';
464
+ const button = root.querySelector("button");
465
+ if (button) {
466
+ button.addEventListener("click", (event) => {
467
+ event.preventDefault();
468
+ void openHostLink({ url: href });
469
+ });
470
+ }
471
+ document.body.appendChild(host);
472
+ }
473
+
431
474
  function installReactRefreshPreambleFallback() {
432
475
  window.__vite_plugin_react_preamble_installed__ = true;
433
476
  if (typeof window.$RefreshReg$ !== "function") {
@@ -768,6 +811,7 @@ export function embedApp(options = {}) {
768
811
  base.href = config.baseHref;
769
812
  document.head.prepend(base);
770
813
  importChildren(parsed.body, document.body);
814
+ installExternalOpenControl(appUrl);
771
815
  for (const script of scripts) {
772
816
  if (isRunnableClassicScript(script)) runClassicScript(script);
773
817
  }
@@ -960,6 +1004,42 @@ export function embedApp(options = {}) {
960
1004
  }
961
1005
  }
962
1006
 
1007
+ function renderAppLaunchError(message) {
1008
+ clearFrameReadyTimer();
1009
+ clearFrameLoadTimer();
1010
+ appFrame = null;
1011
+ const fallbackCopy = openUrl
1012
+ ? "The inline MCP app could not load in this chat host. You can open the same app route in a new tab or retry the inline load."
1013
+ : "The inline MCP app could not load in this chat host.";
1014
+ const copyHtml = message
1015
+ ? '<div>' + esc(message) + '</div><div>' + esc(fallbackCopy) + '</div>'
1016
+ : esc(fallbackCopy);
1017
+ stage.innerHTML =
1018
+ '<div class="fallback">' +
1019
+ '<div class="fallback-title">App did not load</div>' +
1020
+ '<div class="fallback-copy">' + copyHtml + '</div>' +
1021
+ '<div class="fallback-actions">' +
1022
+ '<button type="button" data-fallback-open>Open in new tab</button>' +
1023
+ '<button type="button" data-fallback-retry>Retry</button>' +
1024
+ '</div>' +
1025
+ (openUrl ? '<a class="fallback-url" href="' + esc(openUrl) + '" target="_blank" rel="noreferrer">' + esc(openUrl) + '</a>' : '') +
1026
+ '</div>';
1027
+ const fallbackOpen = stage.querySelector("[data-fallback-open]");
1028
+ const fallbackRetry = stage.querySelector("[data-fallback-retry]");
1029
+ if (fallbackOpen) {
1030
+ fallbackOpen.disabled = !openUrl;
1031
+ fallbackOpen.onclick = () => {
1032
+ if (openUrl) void openHostLink({ url: openUrl });
1033
+ };
1034
+ }
1035
+ if (fallbackRetry) {
1036
+ fallbackRetry.onclick = () => {
1037
+ startedFor = "";
1038
+ void launchEmbed();
1039
+ };
1040
+ }
1041
+ }
1042
+
963
1043
  async function openFallbackExternal() {
964
1044
  if (!openUrl) return;
965
1045
  let url = withChatBridgeParam(openUrl);
@@ -1024,12 +1104,29 @@ export function embedApp(options = {}) {
1024
1104
  return true;
1025
1105
  }
1026
1106
 
1107
+ // We have connected to a standards-track MCP Apps host (Codex, Cursor,
1108
+ // Claude over the SDK, our own renderer, …) through the postMessage
1109
+ // \`ui/*\` bridge rather than ChatGPT's \`window.openai\` global. These hosts
1110
+ // render the resource in a strict sandboxed iframe (typically
1111
+ // \`sandbox="allow-scripts"\`, opaque origin). Self-navigating that iframe to
1112
+ // the real app origin tears down the host bridge and loses the opaque-origin
1113
+ // auth context, which shows up as a permanent / flashing loading state.
1114
+ // Transplanting the app document into the shell keeps the bridge alive and
1115
+ // works under the opaque origin via embed-token auth, exactly like Claude.
1116
+ function isNativeMcpAppsBridgeHost() {
1117
+ return !!app && !openAiBridge;
1118
+ }
1119
+
1027
1120
  function shouldTransplantAppDocument() {
1028
1121
  const render = renderModeSource();
1029
1122
  const mode = render.mode;
1123
+ if (mode === "iframe" || mode === "nested" || render.frame === "iframe" || render.nested) {
1124
+ return false;
1125
+ }
1030
1126
  return (
1031
1127
  isClaudeMcpContentHost() ||
1032
1128
  isChatGptSandboxHost() ||
1129
+ isNativeMcpAppsBridgeHost() ||
1033
1130
  mode === "transplant" ||
1034
1131
  render.frame === "transplant"
1035
1132
  );
@@ -1047,7 +1144,7 @@ export function embedApp(options = {}) {
1047
1144
  try {
1048
1145
  const host = window.location.hostname || "";
1049
1146
  const appParam = new URL(window.location.href).searchParams.get("app");
1050
- return /^[^.]+\\.web-sandbox\\.oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
1147
+ return /^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
1051
1148
  } catch {
1052
1149
  return false;
1053
1150
  }
@@ -1270,7 +1367,7 @@ export function embedApp(options = {}) {
1270
1367
  async function launchEmbed() {
1271
1368
  const launchUrl = openStartUrl || openUrl;
1272
1369
  if (!launchUrl) {
1273
- setMessage("Open link was not available.");
1370
+ renderAppLaunchError("Open link was not available.");
1274
1371
  return;
1275
1372
  }
1276
1373
  if (!wantsEmbed()) {
@@ -1301,7 +1398,7 @@ export function embedApp(options = {}) {
1301
1398
  const data = parseToolResult(result);
1302
1399
  if (typeof data.startUrl !== "string" || !data.startUrl) {
1303
1400
  startedFor = "";
1304
- setMessage(data.error || "This app can be opened, but not embedded from this MCP server.");
1401
+ renderAppLaunchError(data.error || "This app can be opened, but not embedded from this MCP server.");
1305
1402
  return;
1306
1403
  }
1307
1404
  const startUrl = withChatBridgeParam(data.startUrl);
@@ -1318,7 +1415,7 @@ export function embedApp(options = {}) {
1318
1415
  }
1319
1416
  } catch (err) {
1320
1417
  startedFor = "";
1321
- setMessage(err && err.message ? err.message : "Could not launch embedded app.");
1418
+ renderAppLaunchError(err && err.message ? err.message : "Could not launch embedded app.");
1322
1419
  }
1323
1420
  }
1324
1421
 
@@ -1368,6 +1465,9 @@ export function embedApp(options = {}) {
1368
1465
  try {
1369
1466
  if (new URLSearchParams(window.location.search).get("app") === "chatgpt") return true;
1370
1467
  } catch (_err) {}
1468
+ try {
1469
+ if (/^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i.test(window.location.hostname || "")) return true;
1470
+ } catch (_err) {}
1371
1471
  try {
1372
1472
  return /chatgpt/i.test(String(navigator.userAgent || ""));
1373
1473
  } catch (_err) {
@@ -1551,8 +1651,13 @@ export function embedApp(options = {}) {
1551
1651
  async openLink(params) {
1552
1652
  const url = typeof (params && params.url) === "string" ? params.url : "";
1553
1653
  if (!url) return { isError: true };
1554
- window.open(url, "_blank", "noopener,noreferrer");
1555
- return { ok: true };
1654
+ try {
1655
+ return await rpcRequest("ui/open-link", { url }, wrapperRequestTimeoutMs);
1656
+ } catch (err) {
1657
+ console.warn("[agent-native] MCP host open-link request failed", err);
1658
+ }
1659
+ const opened = window.open(url, "_blank", "noopener,noreferrer");
1660
+ return opened ? { ok: true } : { isError: true };
1556
1661
  },
1557
1662
  async requestDisplayMode(params) {
1558
1663
  return await rpcRequest("ui/request-display-mode", objectValue(params));
@@ -1588,6 +1693,7 @@ export function embedApp(options = {}) {
1588
1693
  return;
1589
1694
  }
1590
1695
  if (
1696
+ message.method === "ui/notifications/host-context-changed" ||
1591
1697
  message.method === "ui/notifications/host-context" ||
1592
1698
  message.method === "ui/notifications/context"
1593
1699
  ) {
@@ -1 +1 @@
1
- {"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\nimport { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from \"../shared/embed-auth.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\nconst MCP_APP_WRAPPER_CHROME_HEIGHT = 44;\nexport const DEFAULT_MCP_APP_SHELL_HEIGHT = 560;\nexport const DEFAULT_MCP_APP_VIEWPORT_HEIGHT =\n DEFAULT_MCP_APP_SHELL_HEIGHT - MCP_APP_WRAPPER_CHROME_HEIGHT;\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n connectDomains?: string[];\n resourceDomains?: string[];\n baseUriDomains?: string[];\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(\n 320,\n Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT),\n );\n const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;\n const frameDomains = [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ];\n\n return {\n title,\n ...(options.description ? { description: options.description } : {}),\n html: () => `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: Canvas; color: CanvasText; --agent-native-shell-height: ${height}px; --agent-native-viewport-height: ${viewportHeight}px; }\n * { box-sizing: border-box; }\n body { margin: 0; }\n .shell { display: grid; gap: 8px; min-height: var(--agent-native-shell-height); padding: 0; }\n .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }\n .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }\n .actions { display: flex; align-items: center; gap: 6px; }\n button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }\n button:disabled { opacity: .55; cursor: default; }\n .stage { position: relative; min-height: var(--agent-native-viewport-height); }\n iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }\n .message { display: grid; place-items: center; min-height: var(--agent-native-viewport-height); padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }\n .fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height: var(--agent-native-viewport-height); padding: 24px; background: Canvas; color: CanvasText; text-align: center; }\n .fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }\n .fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }\n .fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }\n .fallback-url { max-width: min(560px, 100%); overflow-wrap: anywhere; color: color-mix(in srgb, CanvasText 76%, Canvas); font-size: 12px; }\n </style>\n</head>\n<body\n data-app-title=\"${attr(title)}\"\n data-iframe-title=\"${attr(iframeTitle)}\"\n data-open-label=\"${attr(openLabel)}\"\n data-start-tool=\"${attr(startToolName)}\"\n data-embed-default=\"${embedByDefault ? \"1\" : \"0\"}\"\n>\n <main class=\"shell\">\n <div class=\"bar\">\n <div class=\"title\" data-title-label>${attr(title)}</div>\n <div class=\"actions\">\n <button type=\"button\" data-display hidden disabled>Fullscreen</button>\n <button type=\"button\" data-open disabled>${attr(openLabel)}</button>\n </div>\n </div>\n <section class=\"stage\" data-stage>\n <div class=\"message\">Preparing app</div>\n </section>\n </main>\n <script>\n (async () => {\n const body = document.body;\n const stage = document.querySelector(\"[data-stage]\");\n const titleEl = document.querySelector(\"[data-title-label]\");\n const openButton = document.querySelector(\"[data-open]\");\n const displayButton = document.querySelector(\"[data-display]\");\n const startTool = body.dataset.startTool || \"create_embed_session\";\n const embedByDefault = body.dataset.embedDefault !== \"0\";\n const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};\n const defaultIntrinsicHeight = ${height};\n const chromeHeight = ${MCP_APP_WRAPPER_CHROME_HEIGHT};\n const frameReadyMessageDelays = [0, 200, 500, 1500, 3000, 7000, 15000, 30000];\n const frameReadyTimeoutMs = 45000;\n const frameLoadTimeoutMs = 45000;\n const defaultOpenAiBridgeWaitMs = 200;\n const chatGptOpenAiBridgeWaitMs = 5000;\n const openAiBridgePollMs = 50;\n const nativeBridgeInitializeTimeoutMs = 5000;\n const nativeBridgeRequestTimeoutMs = 30000;\n const wrapperRequestTimeoutMs = 5000;\n let app = null;\n let openAiBridge = null;\n let wrapperRequestId = 0;\n const wrapperRequests = new Map();\n let toolInput = {};\n let toolResultData = {};\n let openUrl = \"\";\n let openStartUrl = \"\";\n let startedFor = \"\";\n let appFrame = null;\n let appFrameReady = false;\n let appFrameReadyTimer = null;\n let appFrameLoadTimer = null;\n let lastFrameSrc = \"\";\n\n function esc(value) {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n }\n\n function parseJson(value, fallback) {\n if (value && typeof value === \"object\") return value;\n if (typeof value !== \"string\" || !value.trim()) return fallback;\n try { return JSON.parse(value); } catch { return fallback; }\n }\n\n function objectValue(value) {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : {};\n }\n\n function finiteNumber(value) {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0\n ? value\n : null;\n }\n\n function contextMaxHeight(context) {\n if (!context || typeof context !== \"object\") return null;\n return finiteNumber(context.maxHeight) ||\n finiteNumber(context.containerDimensions && context.containerDimensions.maxHeight);\n }\n\n function visibleIntrinsicHeight() {\n const context = hostState().context || {};\n const hostMaxHeight = contextMaxHeight(context);\n if (hostMaxHeight) return Math.floor(hostMaxHeight);\n const viewportHeight = finiteNumber(window.visualViewport && window.visualViewport.height) ||\n finiteNumber(window.innerHeight);\n return Math.floor(viewportHeight || defaultIntrinsicHeight);\n }\n\n function applyIntrinsicHeight(nextHeight) {\n const boundedHeight = Math.min(\n defaultIntrinsicHeight,\n Math.floor(nextHeight || defaultIntrinsicHeight)\n );\n const height = Math.max(320, boundedHeight);\n const viewportHeight = Math.max(0, height - chromeHeight);\n document.documentElement.style.setProperty(\"--agent-native-shell-height\", height + \"px\");\n document.documentElement.style.setProperty(\"--agent-native-viewport-height\", viewportHeight + \"px\");\n if (appFrame) appFrame.style.height = viewportHeight + \"px\";\n return height;\n }\n\n function parseToolResult(params) {\n if (!params) return {};\n if (params.result && typeof params.result === \"object\") {\n return parseToolResult(params.result);\n }\n if (params.toolResult && typeof params.toolResult === \"object\") {\n return parseToolResult(params.toolResult);\n }\n if (params.structuredContent && typeof params.structuredContent === \"object\") {\n return params.structuredContent;\n }\n const parts = Array.isArray(params.content) ? params.content : [];\n const textPart = parts.find((part) => part && part.type === \"text\" && typeof part.text === \"string\");\n const text = textPart ? textPart.text : \"\";\n if (params.isError && typeof text === \"string\" && text.trim()) {\n return { error: text.trim() };\n }\n return parseJson(text, {});\n }\n\n function metadataRecord(value) {\n const meta = value && typeof value === \"object\" && !Array.isArray(value)\n ? value._meta\n : null;\n return meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? meta\n : null;\n }\n\n function toolResultMeta(params) {\n if (!params || typeof params !== \"object\") return {};\n const direct = metadataRecord(params);\n if (direct) return direct;\n if (params.result && typeof params.result === \"object\") {\n return toolResultMeta(params.result);\n }\n if (params.toolResult && typeof params.toolResult === \"object\") {\n return toolResultMeta(params.toolResult);\n }\n return {};\n }\n\n function openLinkRecordFrom(value) {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : {};\n }\n\n function openLinkWebUrlFrom(value) {\n const record = openLinkRecordFrom(value);\n return typeof record.webUrl === \"string\" ? record.webUrl : \"\";\n }\n\n function firstNonEmbedStartUrl(values) {\n for (const value of values) {\n if (typeof value === \"string\" && value && !isEmbedStartUrl(value)) return value;\n }\n return \"\";\n }\n\n function firstEmbedStartUrl(values) {\n for (const value of values) {\n if (typeof value === \"string\" && value && isEmbedStartUrl(value)) {\n return withChatBridgeParam(value);\n }\n }\n return \"\";\n }\n\n function openLinkFrom(params, data) {\n const meta = toolResultMeta(params);\n const openLink = meta[\"agent-native/openLink\"];\n const metaUrl = openLinkWebUrlFrom(openLink);\n const record = data && typeof data === \"object\" ? data : {};\n const structuredOpenLinkUrl = openLinkWebUrlFrom(record.openLink);\n return firstNonEmbedStartUrl([\n record.embedTargetPath,\n record.deepLinkUrl,\n record.deepLink,\n record.openUrl,\n record.url,\n structuredOpenLinkUrl,\n metaUrl\n ]);\n }\n\n function embedStartUrlFrom(params, data) {\n const meta = toolResultMeta(params);\n const embedStart = meta[\"agent-native/embedStart\"];\n const embedStartRecord =\n embedStart && typeof embedStart === \"object\" && !Array.isArray(embedStart)\n ? embedStart\n : {};\n const openLink = meta[\"agent-native/openLink\"];\n const metaUrl = openLinkWebUrlFrom(openLink);\n const record = data && typeof data === \"object\" ? data : {};\n return firstEmbedStartUrl([\n embedStartRecord.startUrl,\n record.embedStartUrl,\n record.startUrl,\n record.url,\n openLinkWebUrlFrom(record.openLink),\n metaUrl\n ]);\n }\n\n function hostState() {\n if (openAiBridge) {\n return {\n context: {\n displayMode: openAiBridge.displayMode,\n availableDisplayModes: typeof openAiBridge.requestDisplayMode === \"function\"\n ? [\"inline\", \"fullscreen\", \"pip\"]\n : [],\n maxHeight: openAiBridge.maxHeight,\n locale: openAiBridge.locale,\n theme: openAiBridge.theme,\n view: openAiBridge.view\n },\n capabilities: { openai: true },\n version: openAiBridge.userAgent\n };\n }\n return {\n context: app && app.getHostContext ? app.getHostContext() : undefined,\n capabilities: app && app.getHostCapabilities ? app.getHostCapabilities() : undefined,\n version: app && app.getHostVersion ? app.getHostVersion() : undefined\n };\n }\n\n function sendToAppFrame(message) {\n if (!appFrame || !appFrame.contentWindow) return;\n try { appFrame.contentWindow.postMessage(message, \"*\"); } catch {}\n }\n\n function nextWrapperRequestId() {\n wrapperRequestId += 1;\n return \"mcp-wrapper-\" + Date.now() + \"-\" + wrapperRequestId;\n }\n\n function respondToWrapperRequest(requestId, result) {\n if (!requestId) return;\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: {\n requestId,\n ok: !!(result && result.ok),\n result: result || {}\n }\n });\n }\n\n function wrapperRpcRequest(method, params, timeoutMs) {\n return new Promise((resolve) => {\n const id = nextWrapperRequestId();\n const timer = window.setTimeout(() => {\n wrapperRequests.delete(id);\n resolve({ ok: false, error: \"MCP host bridge request timed out.\" });\n }, timeoutMs || wrapperRequestTimeoutMs);\n wrapperRequests.set(id, { resolve, timer });\n try {\n window.parent.postMessage(\n { jsonrpc: \"2.0\", id, method, params: params || {} },\n \"*\"\n );\n } catch (err) {\n wrapperRequests.delete(id);\n clearTimeout(timer);\n resolve({ ok: false, error: err && err.message ? err.message : String(err) });\n }\n });\n }\n\n function settleWrapperRpcResponse(message) {\n const id = typeof (message && message.id) === \"string\" ? message.id : \"\";\n if (!id) return false;\n const pending = wrapperRequests.get(id);\n if (!pending) return false;\n wrapperRequests.delete(id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.resolve({ ok: false, error: message.error });\n } else {\n pending.resolve({ ok: true, result: message.result });\n }\n return true;\n }\n\n function sendHostContext() {\n sendToAppFrame({ type: \"agentNative.mcpHostContext\", data: hostState() });\n }\n\n function sendFrameReadyMessages(frame) {\n const originPayload = { type: \"agentNative.frameOrigin\", origin: window.location.origin };\n frameReadyMessageDelays.forEach((delay) => {\n setTimeout(() => {\n try { frame.contentWindow && frame.contentWindow.postMessage(originPayload, \"*\"); } catch {}\n sendHostContext();\n }, delay);\n });\n }\n\n function withChatBridgeParam(value) {\n if (typeof value !== \"string\" || !value) return value;\n try {\n const base = \"http://agent-native.invalid\";\n const url = value.startsWith(\"/\") ? new URL(value, base) : new URL(value);\n url.searchParams.set(chatBridgeParam, \"1\");\n return value.startsWith(\"/\")\n ? url.pathname + url.search + url.hash\n : url.toString();\n } catch {\n return value;\n }\n }\n\n function embedSessionArgsFor(value) {\n const chrome = typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\";\n return typeof value === \"string\" && value.startsWith(\"/\")\n ? { path: value, chrome }\n : { url: value, chrome };\n }\n\n function isEmbedStartUrl(value) {\n if (typeof value !== \"string\" || !value) return false;\n try {\n const url = new URL(value, window.location.href);\n return url.pathname.endsWith(\"/_agent-native/embed/start\");\n } catch {\n return false;\n }\n }\n\n function localPathFromUrl(url, includeToken) {\n const next = new URL(url.href);\n if (!includeToken) next.searchParams.delete(\"__an_embed_token\");\n return next.pathname + next.search + next.hash;\n }\n\n function rewriteRootRelativeHtmlUrls(html, appOrigin) {\n return String(html).replace(\n /\\\\b(src|href|poster|action)\\\\s*=\\\\s*([\"'])\\\\/(?!\\\\/)/gi,\n (_match, name, quote) => String(name) + \"=\" + quote + appOrigin + \"/\"\n );\n }\n\n function removeHtmlCspMeta(html) {\n return String(html).replace(\n /<meta\\\\s+[^>]*http-equiv\\\\s*=\\\\s*([\"'])?content-security-policy\\\\1?[^>]*>/gi,\n \"\"\n );\n }\n\n function embedConfigForAppUrl(appUrl) {\n const sanitizedTarget = localPathFromUrl(appUrl, false);\n return {\n origin: appUrl.origin,\n href: appUrl.href,\n baseHref: appUrl.origin + appUrl.pathname,\n target: sanitizedTarget,\n token: appUrl.searchParams.get(\"__an_embed_token\") || \"\",\n chatBridgeActive: appUrl.searchParams.get(chatBridgeParam) === \"1\",\n chatBridgeParam,\n embedTokenParam: \"__an_embed_token\",\n embedTargetHeader: \"x-agent-native-embed-target\"\n };\n }\n\n function installReactRefreshPreambleFallback() {\n window.__vite_plugin_react_preamble_installed__ = true;\n if (typeof window.$RefreshReg$ !== \"function\") {\n window.$RefreshReg$ = function() {};\n }\n if (typeof window.$RefreshSig$ !== \"function\") {\n window.$RefreshSig$ = function() {\n return function(type) {\n return type;\n };\n };\n }\n }\n\n function installExternalEmbedRuntime(config) {\n window.__AGENT_NATIVE_EXTERNAL_EMBED = config;\n installReactRefreshPreambleFallback();\n try {\n if (config.target) {\n window.history.replaceState(window.history.state, \"\", config.target);\n }\n } catch (_err) {}\n try {\n if (config.token) {\n sessionStorage.setItem(\"agent-native:embed-auth-token\", config.token);\n }\n if (config.chatBridgeActive && config.token) {\n sessionStorage.setItem(\"agent-native:mcp-chat-bridge\", config.token);\n }\n } catch (_err) {}\n if (window.__agentNativeExternalEmbedRuntimeInstalled) return;\n window.__agentNativeExternalEmbedRuntimeInstalled = true;\n function appOrigin() {\n try {\n return new URL(config.origin).origin;\n } catch (_err) {\n return \"\";\n }\n }\n function targetPath() {\n return config.target || location.pathname + location.search;\n }\n function rewrittenUrl(value, appendToken) {\n const origin = appOrigin();\n if (!origin) return null;\n let url;\n try {\n url = new URL(value, location.href);\n } catch (_err) {\n return null;\n }\n if (url.origin !== location.origin && url.origin !== origin) return null;\n if (url.origin !== origin) {\n const app = new URL(origin);\n url.protocol = app.protocol;\n url.host = app.host;\n }\n if (appendToken && config.token && url.pathname === \"/_agent-native/events\") {\n url.searchParams.set(config.embedTokenParam, config.token);\n }\n return url.toString();\n }\n function authHeaders(input, init) {\n const headers = new Headers(\n init && init.headers ? init.headers : input instanceof Request ? input.headers : undefined\n );\n if (config.token && !headers.has(\"Authorization\")) {\n headers.set(\"Authorization\", \"Bearer \" + config.token);\n }\n if (!headers.has(config.embedTargetHeader)) {\n headers.set(config.embedTargetHeader, targetPath());\n }\n return headers;\n }\n if (typeof fetch === \"function\") {\n const originalFetch = fetch.bind(window);\n window.fetch = function(input, init) {\n const raw = input instanceof Request ? input.url : String(input);\n const url = rewrittenUrl(raw, false);\n if (!url) return originalFetch(input, init);\n const nextInit = Object.assign({}, init || {}, {\n headers: authHeaders(input, init),\n credentials: \"omit\"\n });\n if (input instanceof Request) {\n return originalFetch(new Request(url, input), nextInit);\n }\n return originalFetch(url, nextInit);\n };\n }\n if (typeof XMLHttpRequest !== \"undefined\") {\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n XMLHttpRequest.prototype.open = function(method, url) {\n const rewritten = rewrittenUrl(url, false);\n this.__agentNativeExternalEmbed = !!rewritten;\n return originalOpen.call(\n this,\n method,\n rewritten || url,\n arguments.length > 2 ? arguments[2] : true,\n arguments[3],\n arguments[4]\n );\n };\n XMLHttpRequest.prototype.send = function(body) {\n if (this.__agentNativeExternalEmbed) {\n try {\n if (config.token) this.setRequestHeader(\"Authorization\", \"Bearer \" + config.token);\n this.setRequestHeader(config.embedTargetHeader, targetPath());\n } catch (_err) {}\n }\n return originalSend.call(this, body);\n };\n }\n if (typeof EventSource !== \"undefined\") {\n const OriginalEventSource = EventSource;\n window.EventSource = function(url, options) {\n return new OriginalEventSource(rewrittenUrl(url, true) || url, options);\n };\n window.EventSource.prototype = OriginalEventSource.prototype;\n }\n }\n\n function copyDocumentElementAttributes(source) {\n const target = document.documentElement;\n for (const attr of Array.from(target.attributes)) {\n target.removeAttribute(attr.name);\n }\n for (const attr of Array.from(source.attributes)) {\n target.setAttribute(attr.name, attr.value);\n }\n }\n\n function importChildren(source, target) {\n target.replaceChildren(\n ...Array.from(source.childNodes).map((node) => document.importNode(node, true))\n );\n }\n\n function isModuleScript(script) {\n return (script.getAttribute(\"type\") || \"\").trim().toLowerCase() === \"module\";\n }\n\n function isRunnableClassicScript(script) {\n const type = (script.getAttribute(\"type\") || \"\").trim().toLowerCase();\n return !type || type === \"text/javascript\" || type === \"application/javascript\";\n }\n\n function runClassicScript(script) {\n const next = document.createElement(\"script\");\n for (const attr of Array.from(script.attributes)) {\n if (attr.name === \"type\") continue;\n next.setAttribute(attr.name, attr.value);\n }\n if (script.src) {\n next.src = script.src;\n } else {\n next.textContent = script.textContent || \"\";\n }\n document.body.appendChild(next);\n next.remove();\n }\n\n function isEmbedRuntimeModulePath(pathname) {\n if (typeof pathname !== \"string\" || !pathname) return false;\n return /(?:^|\\\\/)(?:@(?:id|vite|fs|react-refresh)|app|node_modules|packages|src)(?:\\\\/|$)/.test(pathname) ||\n /(?:^|\\\\/)__x00__virtual:/.test(pathname) ||\n pathname.includes(\"virtual:react-router\");\n }\n\n function appendEmbedParamsToAppUrl(url, config) {\n if (isEmbedRuntimeModulePath(url.pathname)) return url;\n if (config.token) url.searchParams.set(config.embedTokenParam, config.token);\n if (config.chatBridgeActive) url.searchParams.set(config.chatBridgeParam, \"1\");\n return url;\n }\n\n function rootRelativeSpecifierToAppUrl(specifier, config) {\n if (typeof specifier !== \"string\" || !specifier.startsWith(\"/\") || specifier.startsWith(\"//\")) {\n return specifier;\n }\n try {\n const url = new URL(specifier, config.origin);\n return appendEmbedParamsToAppUrl(url, config).toString();\n } catch (_err) {\n return specifier;\n }\n }\n\n function rootRelativeSpecifiersToAbsolute(code, config) {\n return String(code).replace(/([\"'])\\\\/(?!\\\\/)([^\"']*)/g, (_match, quote, rest) => {\n return quote + rootRelativeSpecifierToAppUrl(\"/\" + rest, config);\n });\n }\n\n function relativeSpecifierToAppUrl(specifier, config, baseUrl) {\n if (typeof specifier !== \"string\" || !/^\\\\.\\\\.?\\\\//.test(specifier)) {\n return specifier;\n }\n try {\n const url = new URL(specifier, baseUrl || config.baseHref);\n if (url.origin === config.origin) {\n appendEmbedParamsToAppUrl(url, config);\n }\n return url.toString();\n } catch (_err) {\n return specifier;\n }\n }\n\n function relativeModuleSpecifiersToAbsolute(code, config, baseUrl) {\n return String(code)\n .replace(/(\\\\bimport\\\\s+(?:[^\"']+?\\\\s+from\\\\s+)?)([\"'])(\\\\.\\\\.?\\\\/[^\"']*)\\\\2/g, (_match, prefix, quote, specifier) => {\n return prefix + quote + relativeSpecifierToAppUrl(specifier, config, baseUrl) + quote;\n })\n .replace(/(\\\\bimport\\\\s*\\\\(\\\\s*)([\"'])(\\\\.\\\\.?\\\\/[^\"']*)\\\\2/g, (_match, prefix, quote, specifier) => {\n return prefix + quote + relativeSpecifierToAppUrl(specifier, config, baseUrl) + quote;\n })\n .replace(/(\\\\bexport\\\\s+[^\"']*?\\\\s+from\\\\s+)([\"'])(\\\\.\\\\.?\\\\/[^\"']*)\\\\2/g, (_match, prefix, quote, specifier) => {\n return prefix + quote + relativeSpecifierToAppUrl(specifier, config, baseUrl) + quote;\n });\n }\n\n function stripDevOnlyModuleImports(code) {\n return String(code).replace(\n /\\\\bimport\\\\s+(?:[^\"']+\\\\s+from\\\\s+)?[\"'][^\"']*(?:virtual:react-router\\\\/inject-hmr-runtime|__x00__virtual:react-router\\\\/inject-hmr-runtime)[^\"']*[\"']\\\\s*;?/g,\n \"\"\n );\n }\n\n function namedImportBindings(specifierList) {\n return String(specifierList)\n .split(\",\")\n .map((part) => {\n const trimmed = part.trim();\n if (!trimmed) return \"\";\n return trimmed.replace(\n /^([A-Za-z_$][\\\\w$]*)\\\\s+as\\\\s+([A-Za-z_$][\\\\w$]*)$/,\n \"$1: $2\"\n );\n })\n .filter(Boolean)\n .join(\", \");\n }\n\n function moduleCodeToClassicAsync(code, config, baseUrl) {\n return relativeModuleSpecifiersToAbsolute(\n rootRelativeSpecifiersToAbsolute(stripDevOnlyModuleImports(code), config),\n config,\n baseUrl\n )\n .replace(\n /\\\\bimport\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s*,\\\\s*\\\\*\\\\s+as\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s+from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n \"const $2 = await import($3); const $1 = $2.default;\"\n )\n .replace(\n /\\\\bimport\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s*,\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n (_match, defaultName, specifiers, source) =>\n \"const { default: \" +\n defaultName +\n (namedImportBindings(specifiers) ? \", \" + namedImportBindings(specifiers) : \"\") +\n \" } = await import(\" +\n source +\n \");\"\n )\n .replace(\n /\\\\bimport\\\\s+\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n (_match, specifiers, source) =>\n \"const { \" + namedImportBindings(specifiers) + \" } = await import(\" + source + \");\"\n )\n .replace(\n /\\\\bimport\\\\s+\\\\*\\\\s+as\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s+from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n \"const $1 = await import($2);\"\n )\n .replace(\n /\\\\bimport\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s+from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n \"const { default: $1 } = await import($2);\"\n )\n .replace(/\\\\bimport\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g, \"await import($1);\")\n .replace(/\\\\bimport\\\\(([\"'][^\"']+[\"'])\\\\)\\\\s*;?/g, \"await import($1);\");\n }\n\n function scriptSourceUrl(script, config) {\n const raw = script.getAttribute(\"src\") || \"\";\n if (!raw) return \"\";\n try {\n const url = new URL(raw, config.baseHref);\n if (url.origin === config.origin) {\n appendEmbedParamsToAppUrl(url, config);\n }\n return url.toString();\n } catch (_err) {\n return raw;\n }\n }\n\n async function moduleScriptCode(script, config) {\n const src = scriptSourceUrl(script, config);\n if (!src) return script.textContent || \"\";\n const response = await fetch(src, {\n credentials: \"omit\",\n headers: { Accept: \"text/javascript, application/javascript, */*\" }\n });\n if (!response.ok) {\n throw new Error(\"Module script returned HTTP \" + response.status + \".\");\n }\n return await response.text();\n }\n\n async function runModuleScriptAsClassic(script, config) {\n const sourceUrl = scriptSourceUrl(script, config) || config.baseHref;\n const code = moduleCodeToClassicAsync(\n await moduleScriptCode(script, config),\n config,\n sourceUrl\n );\n const runner = document.createElement(\"script\");\n runner.textContent =\n \"(async()=>{\" +\n code +\n \"})().catch((err)=>{console.error('[agent-native] transplanted app module failed',err);document.body.setAttribute('data-agent-native-hydration-error',String(err&&err.message||err));});\";\n document.body.appendChild(runner);\n runner.remove();\n }\n\n async function mountTransplantedHtml(html, appUrl) {\n const config = embedConfigForAppUrl(appUrl);\n installExternalEmbedRuntime(config);\n const parsed = new DOMParser().parseFromString(\n rewriteRootRelativeHtmlUrls(removeHtmlCspMeta(html), appUrl.origin),\n \"text/html\"\n );\n const scripts = Array.from(parsed.querySelectorAll(\"script\"));\n copyDocumentElementAttributes(parsed.documentElement);\n importChildren(parsed.head, document.head);\n const base = document.createElement(\"base\");\n base.href = config.baseHref;\n document.head.prepend(base);\n importChildren(parsed.body, document.body);\n for (const script of scripts) {\n if (isRunnableClassicScript(script)) runClassicScript(script);\n }\n for (const script of scripts) {\n if (isModuleScript(script)) await runModuleScriptAsClassic(script, config);\n }\n }\n\n function absoluteUrl(value, base) {\n try {\n return new URL(value, base).toString();\n } catch {\n return \"\";\n }\n }\n\n async function resolveTransplantAppDocumentSource(src) {\n if (!isEmbedStartUrl(src)) {\n return { url: new URL(src), response: null };\n }\n const response = await fetch(src, {\n credentials: \"omit\",\n redirect: \"follow\",\n headers: {\n Accept: \"application/json\",\n \"X-Agent-Native-Embed-Transplant\": \"1\"\n }\n });\n const contentType = response.headers.get(\"content-type\") || \"\";\n if (response.ok && /\\\\bapplication\\\\/json\\\\b/i.test(contentType)) {\n const data = await response.json();\n const location = typeof data.location === \"string\" ? data.location : \"\";\n const url = absoluteUrl(location, src);\n if (url) return { url: new URL(withChatBridgeParam(url)), response: null };\n throw new Error(\"Embedded app did not return a launch URL.\");\n }\n return {\n url: new URL(response.url || src),\n response\n };\n }\n\n async function transplantAppDocument(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n lastFrameSrc = src;\n setMessage(\"Loading app\");\n const source = await resolveTransplantAppDocumentSource(src);\n const response = source.response || await fetch(source.url.href, {\n credentials: \"omit\",\n redirect: \"follow\",\n headers: { Accept: \"text/html\" }\n });\n if (!response.ok) {\n if (response.status === 401 && isEmbedStartUrl(src)) {\n refreshExpiredEmbedSession();\n return;\n }\n throw new Error(\"Embedded app returned HTTP \" + response.status + \".\");\n }\n const html = await response.text();\n const appUrl = source.url || new URL(response.url || src);\n try {\n window.history.replaceState(window.history.state, \"\", localPathFromUrl(appUrl, false));\n } catch {}\n await mountTransplantedHtml(html, appUrl);\n notifyHostHeightRepeatedly();\n }\n\n function wantsEmbed() {\n if (toolInput.embed === false || toolInput.embed === \"false\") return false;\n if (embedByDefault) return true;\n return toolInput.embed === true || toolInput.embed === \"true\";\n }\n\n function renderModeSource() {\n const input = objectValue(toolInput);\n const result = objectValue(toolResultData);\n return {\n mode: typeof input.embedMode === \"string\"\n ? input.embedMode\n : typeof input.renderMode === \"string\"\n ? input.renderMode\n : typeof result.embedMode === \"string\"\n ? result.embedMode\n : typeof result.renderMode === \"string\"\n ? result.renderMode\n : \"\",\n frame: typeof input.frame === \"string\"\n ? input.frame\n : typeof result.frame === \"string\"\n ? result.frame\n : \"\",\n nested: input.nested === true || result.nested === true\n };\n }\n\n function supportedDisplayMode(mode) {\n if (openAiBridge && typeof openAiBridge.requestDisplayMode === \"function\") {\n return mode === \"inline\" || mode === \"fullscreen\" || mode === \"pip\";\n }\n const modes = hostState().context && hostState().context.availableDisplayModes;\n return Array.isArray(modes) && modes.includes(mode);\n }\n\n async function requestHostDisplayMode(mode) {\n let result;\n if (openAiBridge && typeof openAiBridge.requestDisplayMode === \"function\") {\n result = await openAiBridge.requestDisplayMode({ mode });\n } else {\n if (!app || typeof app.requestDisplayMode !== \"function\") {\n throw new Error(\"Display mode changes are not available in this host.\");\n }\n result = await app.requestDisplayMode({ mode });\n }\n updateDisplayButton();\n sendHostContext();\n return result;\n }\n\n function updateDisplayButton() {\n const context = hostState().context || {};\n const nextMode = context.displayMode === \"fullscreen\" ? \"inline\" : \"fullscreen\";\n const supported = supportedDisplayMode(nextMode);\n displayButton.hidden = !supported;\n displayButton.disabled = !supported;\n displayButton.textContent = nextMode === \"fullscreen\" ? \"Fullscreen\" : \"Inline\";\n displayButton.onclick = () => {\n if (!supportedDisplayMode(nextMode)) return;\n void requestHostDisplayMode(nextMode).catch((err) => {\n console.warn(\"[agent-native] MCP host rejected display mode request\", err);\n });\n };\n }\n\n function setMessage(message) {\n stage.innerHTML = '<div class=\"message\">' + esc(message) + '</div>';\n }\n\n function clearFrameReadyTimer() {\n if (!appFrameReadyTimer) return;\n clearTimeout(appFrameReadyTimer);\n appFrameReadyTimer = null;\n }\n\n function clearFrameLoadTimer() {\n if (!appFrameLoadTimer) return;\n clearTimeout(appFrameLoadTimer);\n appFrameLoadTimer = null;\n }\n\n function startFrameReadyTimer(frame) {\n clearFrameReadyTimer();\n appFrameReadyTimer = setTimeout(() => {\n if (!appFrameReady && appFrame === frame) renderFrameFallback();\n }, frameReadyTimeoutMs);\n }\n\n function renderFrameFallback() {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n const fallbackCopy = openUrl\n ? \"This chat host did not allow the embedded app frame to load inline. You can still open the same app route through the host or use the URL below.\"\n : \"This chat host did not allow the embedded app frame to load inline.\";\n stage.innerHTML =\n '<div class=\"fallback\">' +\n '<div class=\"fallback-title\">Open this app in its own tab</div>' +\n '<div class=\"fallback-copy\">' + esc(fallbackCopy) + '</div>' +\n '<div class=\"fallback-actions\">' +\n '<button type=\"button\" data-fallback-open>Open app</button>' +\n '<button type=\"button\" data-fallback-retry>Try inline again</button>' +\n '</div>' +\n (openUrl ? '<a class=\"fallback-url\" href=\"' + esc(openUrl) + '\" target=\"_blank\" rel=\"noreferrer\">' + esc(openUrl) + '</a>' : '') +\n '</div>';\n const fallbackOpen = stage.querySelector(\"[data-fallback-open]\");\n const fallbackRetry = stage.querySelector(\"[data-fallback-retry]\");\n if (fallbackOpen) {\n fallbackOpen.disabled = !openUrl;\n fallbackOpen.onclick = () => {\n if (openUrl) void openFallbackExternal();\n };\n }\n if (fallbackRetry) {\n fallbackRetry.disabled = !lastFrameSrc;\n fallbackRetry.onclick = () => {\n if (lastFrameSrc) renderFrame(lastFrameSrc);\n };\n }\n }\n\n async function openFallbackExternal() {\n if (!openUrl) return;\n let url = withChatBridgeParam(openUrl);\n try {\n if (url) {\n const result = await callEmbedSessionTool(embedSessionArgsFor(url));\n const data = parseToolResult(result);\n if (typeof data.startUrl === \"string\" && data.startUrl) {\n url = withChatBridgeParam(data.startUrl);\n }\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP fallback could not mint a fresh app session\", err);\n }\n await openHostLink({ url });\n }\n\n function renderFrame(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n const frame = document.createElement(\"iframe\");\n frame.title = body.dataset.iframeTitle || \"Agent Native app\";\n frame.src = src;\n frame.allow = \"clipboard-read; clipboard-write\";\n appFrame = frame;\n appFrameReady = false;\n lastFrameSrc = src;\n frame.addEventListener(\"load\", () => {\n if (appFrame !== frame) return;\n clearFrameLoadTimer();\n sendFrameReadyMessages(frame);\n startFrameReadyTimer(frame);\n });\n stage.replaceChildren(frame);\n notifyHostHeight();\n appFrameLoadTimer = setTimeout(() => {\n if (!appFrameReady && appFrame === frame) renderFrameFallback();\n }, frameLoadTimeoutMs);\n }\n\n function refreshExpiredEmbedSession() {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrameReady = false;\n if (!openUrl) {\n renderFrameFallback();\n return;\n }\n openStartUrl = \"\";\n startedFor = \"\";\n lastFrameSrc = \"\";\n setMessage(\"Refreshing app session\");\n void launchEmbed();\n }\n\n function shouldSelfNavigateToApp() {\n const render = renderModeSource();\n const mode = render.mode;\n if (isClaudeMcpContentHost()) return true;\n if (mode === \"iframe\" || mode === \"nested\") return false;\n if (render.nested || render.frame === \"iframe\") return false;\n return true;\n }\n\n function shouldTransplantAppDocument() {\n const render = renderModeSource();\n const mode = render.mode;\n return (\n isClaudeMcpContentHost() ||\n isChatGptSandboxHost() ||\n mode === \"transplant\" ||\n render.frame === \"transplant\"\n );\n }\n\n function isClaudeMcpContentHost() {\n try {\n return /(^|\\\\.)claudemcpcontent\\\\.com$/i.test(window.location.hostname || \"\");\n } catch {\n return false;\n }\n }\n\n function isChatGptSandboxHost() {\n try {\n const host = window.location.hostname || \"\";\n const appParam = new URL(window.location.href).searchParams.get(\"app\");\n return /^[^.]+\\\\.web-sandbox\\\\.oaiusercontent\\\\.com$/i.test(host) || appParam === \"chatgpt\";\n } catch {\n return false;\n }\n }\n\n function shouldRenderControlledAppFrame() {\n return !!openAiBridge || isChatGptSandboxHost();\n }\n\n function navigateToAppFrame(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n lastFrameSrc = src;\n setMessage(\"Opening app\");\n try {\n window.location.replace(src);\n } catch (err) {\n console.warn(\"[agent-native] MCP app self-navigation failed\", err);\n renderFrameFallback();\n }\n }\n\n async function updateHostModelContext(data) {\n const params = {};\n if (Array.isArray(data && data.content)) params.content = data.content;\n if (data && data.structuredContent && typeof data.structuredContent === \"object\") {\n params.structuredContent = data.structuredContent;\n }\n if (openAiBridge && typeof openAiBridge.setWidgetState === \"function\") {\n openAiBridge.setWidgetState({\n ...objectValue(openAiBridge.widgetState),\n agentNativeModelContext: params\n });\n return { ok: true };\n }\n if (!app || typeof app.updateModelContext !== \"function\") return { ok: false };\n await app.updateModelContext(params);\n return { ok: true };\n }\n\n async function openHostLink(data) {\n const url = typeof (data && data.url) === \"string\" ? data.url : \"\";\n if (!url) return { isError: true };\n if (openAiBridge && typeof openAiBridge.openExternal === \"function\") {\n return await openAiBridge.openExternal({ href: url, redirectUrl: false });\n }\n if (app && typeof app.openLink === \"function\") {\n return await app.openLink({ url });\n }\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n return { ok: true };\n }\n\n function notifyHostHeight() {\n const height = applyIntrinsicHeight(visibleIntrinsicHeight());\n if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== \"function\") {\n if (app && typeof app.sendSizeChanged === \"function\") {\n try {\n app.sendSizeChanged({ height });\n } catch (err) {\n console.warn(\"[agent-native] MCP host rejected size update\", err);\n }\n }\n return;\n }\n try {\n openAiBridge.notifyIntrinsicHeight({ height });\n } catch (err) {\n console.warn(\"[agent-native] ChatGPT rejected intrinsic height update\", err);\n }\n }\n\n function respondToAppFrame(requestId, work) {\n if (!requestId) return;\n Promise.resolve(work)\n .then((result) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: { requestId, ok: true, result }\n });\n })\n .catch((err) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: {\n requestId,\n ok: false,\n error: err && err.message ? err.message : String(err)\n }\n });\n });\n }\n\n async function sendHostChat(chat) {\n const requestId = typeof (chat && chat.requestId) === \"string\" ? chat.requestId : \"\";\n if (!chat || chat.submit === false) return;\n const message = typeof chat.message === \"string\" ? chat.message : \"\";\n if (!message.trim()) return;\n const context = typeof chat.context === \"string\" ? chat.context.trim() : \"\";\n const content = Array.isArray(chat.content) && chat.content.length\n ? chat.content\n : [{ type: \"text\", text: message }];\n const structuredContent =\n chat && chat.structuredContent !== undefined\n ? chat.structuredContent\n : undefined;\n try {\n const contextContent = context\n ? [{ type: \"text\", text: context }, ...content.filter((part) => part && part.type !== \"text\")]\n : content.filter((part) => part && part.type !== \"text\");\n const modelContext = {\n content: contextContent,\n ...(structuredContent !== undefined ? { structuredContent } : {})\n };\n if (openAiBridge && typeof openAiBridge.setWidgetState === \"function\") {\n openAiBridge.setWidgetState({\n ...objectValue(openAiBridge.widgetState),\n agentNativeChatContext: context || null,\n agentNativeModelContext: modelContext\n });\n } else if (app && typeof app.updateModelContext === \"function\") {\n await app.updateModelContext(modelContext);\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP host rejected model context update\", err);\n }\n try {\n if (openAiBridge && typeof openAiBridge.sendFollowUpMessage === \"function\") {\n await openAiBridge.sendFollowUpMessage({\n prompt: message,\n scrollToBottom: true\n });\n respondToWrapperRequest(requestId, { ok: true });\n return;\n }\n let result = null;\n if (app && typeof app.sendMessage === \"function\") {\n result = await app.sendMessage({\n role: \"user\",\n content\n });\n } else {\n result = await wrapperRpcRequest(\"ui/message\", {\n role: \"user\",\n content\n });\n }\n if (result && result.isError) {\n console.warn(\"[agent-native] MCP host rejected chat message\", result);\n respondToWrapperRequest(requestId, { ok: false, result });\n return;\n }\n if (result && result.ok === false) {\n console.warn(\"[agent-native] MCP host chat bridge failed\", result);\n respondToWrapperRequest(requestId, { ok: false, result });\n return;\n }\n respondToWrapperRequest(requestId, { ok: true, result });\n } catch (err) {\n console.warn(\"[agent-native] MCP host chat bridge failed\", err);\n respondToWrapperRequest(requestId, { ok: false, error: err && err.message ? err.message : String(err) });\n }\n }\n\n window.addEventListener(\"message\", (event) => {\n if (event.source !== window.parent) return;\n const message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n settleWrapperRpcResponse(message);\n });\n\n window.addEventListener(\"message\", (event) => {\n if (!appFrame || event.source !== appFrame.contentWindow) return;\n if (!event.data) return;\n const data = event.data.data || {};\n if (event.data.type === \"agentNative.embeddedAppReady\") {\n appFrameReady = true;\n clearFrameLoadTimer();\n clearFrameReadyTimer();\n return;\n }\n if (event.data.type === \"agentNative.embedSessionExpired\") {\n refreshExpiredEmbedSession();\n return;\n }\n if (event.data.type === \"agentNative.submitChat\") {\n void sendHostChat(data);\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.updateModelContext\") {\n respondToAppFrame(data.requestId, updateHostModelContext(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.openLink\") {\n respondToAppFrame(data.requestId, openHostLink(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.requestDisplayMode\") {\n respondToAppFrame(data.requestId, requestHostDisplayMode(data.mode));\n }\n });\n\n function notifyHostHeightSoon() {\n requestAnimationFrame(() => notifyHostHeight());\n }\n\n function notifyHostHeightRepeatedly() {\n notifyHostHeight();\n [0, 250, 1000, 2500].forEach((delay) => {\n setTimeout(() => notifyHostHeight(), delay);\n });\n }\n\n window.addEventListener(\"resize\", notifyHostHeightSoon, { passive: true });\n if (window.visualViewport) {\n window.visualViewport.addEventListener(\"resize\", notifyHostHeightSoon, { passive: true });\n }\n\n async function launchEmbed() {\n const launchUrl = openStartUrl || openUrl;\n if (!launchUrl) {\n setMessage(\"Open link was not available.\");\n return;\n }\n if (!wantsEmbed()) {\n setMessage(\"Ready to open.\");\n return;\n }\n if (startedFor === launchUrl) return;\n startedFor = launchUrl;\n setMessage(\"Loading app\");\n try {\n const selfNavigate = shouldSelfNavigateToApp();\n const embedUrl = withChatBridgeParam(launchUrl);\n if (selfNavigate && isEmbedStartUrl(embedUrl)) {\n if (shouldTransplantAppDocument()) {\n await transplantAppDocument(embedUrl);\n } else if (shouldRenderControlledAppFrame()) {\n renderFrame(embedUrl);\n } else {\n navigateToAppFrame(embedUrl);\n }\n return;\n }\n if (!selfNavigate && isEmbedStartUrl(embedUrl)) {\n renderFrame(embedUrl);\n return;\n }\n const result = await callEmbedSessionTool(embedSessionArgsFor(embedUrl));\n const data = parseToolResult(result);\n if (typeof data.startUrl !== \"string\" || !data.startUrl) {\n startedFor = \"\";\n setMessage(data.error || \"This app can be opened, but not embedded from this MCP server.\");\n return;\n }\n const startUrl = withChatBridgeParam(data.startUrl);\n if (selfNavigate) {\n if (shouldTransplantAppDocument()) {\n await transplantAppDocument(startUrl);\n } else if (shouldRenderControlledAppFrame()) {\n renderFrame(startUrl);\n } else {\n navigateToAppFrame(startUrl);\n }\n } else {\n renderFrame(startUrl);\n }\n } catch (err) {\n startedFor = \"\";\n setMessage(err && err.message ? err.message : \"Could not launch embedded app.\");\n }\n }\n\n async function callEmbedSessionTool(args) {\n if (openAiBridge && typeof openAiBridge.callTool === \"function\") {\n return await openAiBridge.callTool(startTool, args);\n }\n if (!app || typeof app.callServerTool !== \"function\") {\n throw new Error(\"Host tool calls are not available.\");\n }\n return await app.callServerTool({ name: startTool, arguments: args });\n }\n\n function updateHostOpenInAppUrl() {\n if (!openAiBridge || !openUrl || typeof openAiBridge.setOpenInAppUrl !== \"function\") {\n return;\n }\n try {\n openAiBridge.setOpenInAppUrl({ href: openUrl });\n } catch (err) {\n console.warn(\"[agent-native] ChatGPT rejected open-in-app URL\", err);\n }\n }\n\n function updateOpenButton() {\n const buttonUrl = openUrl;\n openButton.disabled = !buttonUrl;\n openButton.onclick = () => {\n if (buttonUrl) void openHostLink({ url: buttonUrl });\n };\n updateHostOpenInAppUrl();\n }\n\n function updateTitle(data) {\n const record = objectValue(data);\n const label = record.label || record.app || record.view || body.dataset.appTitle || \"App\";\n titleEl.textContent = String(label);\n }\n\n function readOpenAiBridge() {\n return window.openai && typeof window.openai === \"object\"\n ? window.openai\n : null;\n }\n\n function isChatGptHostHint() {\n try {\n if (new URLSearchParams(window.location.search).get(\"app\") === \"chatgpt\") return true;\n } catch (_err) {}\n try {\n return /chatgpt/i.test(String(navigator.userAgent || \"\"));\n } catch (_err) {\n return false;\n }\n }\n\n function openAiToolResultParams(bridge) {\n const params = {};\n if (bridge && bridge.toolOutput !== undefined) {\n if (bridge.toolOutput && typeof bridge.toolOutput === \"object\") {\n params.structuredContent = bridge.toolOutput;\n } else {\n params.content = [{ type: \"text\", text: String(bridge.toolOutput) }];\n }\n }\n if (bridge && bridge.toolResponseMetadata && typeof bridge.toolResponseMetadata === \"object\") {\n params._meta = bridge.toolResponseMetadata;\n }\n return params;\n }\n\n function syncOpenAiBridge(bridge) {\n if (!bridge) return false;\n openAiBridge = bridge;\n toolInput = objectValue(bridge.toolInput);\n const params = openAiToolResultParams(bridge);\n const data = parseToolResult(params);\n toolResultData = objectValue(data);\n openUrl = openLinkFrom(params, data);\n openStartUrl = embedStartUrlFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n if (openUrl || openStartUrl) {\n void launchEmbed();\n } else if (!appFrame) {\n setMessage(\"Waiting for app result\");\n }\n return true;\n }\n\n function waitForOpenAiBridge() {\n const existing = readOpenAiBridge();\n if (existing) return Promise.resolve(existing);\n return new Promise((resolve) => {\n let settled = false;\n let pollTimer = 0;\n const finish = (bridge) => {\n if (settled) return;\n settled = true;\n window.removeEventListener(\"openai:set_globals\", onGlobals);\n clearTimeout(timer);\n clearTimeout(pollTimer);\n resolve(bridge || readOpenAiBridge());\n };\n const poll = () => {\n const bridge = readOpenAiBridge();\n if (bridge) {\n finish(bridge);\n return;\n }\n pollTimer = window.setTimeout(poll, openAiBridgePollMs);\n };\n const onGlobals = () => finish(readOpenAiBridge());\n const timer = window.setTimeout(\n () => finish(null),\n isChatGptHostHint() ? chatGptOpenAiBridgeWaitMs : defaultOpenAiBridgeWaitMs\n );\n window.addEventListener(\"openai:set_globals\", onGlobals, { passive: true });\n pollTimer = window.setTimeout(poll, openAiBridgePollMs);\n });\n }\n\n window.addEventListener(\"openai:set_globals\", () => {\n const bridge = readOpenAiBridge();\n if (bridge && (!appFrame || openAiBridge)) syncOpenAiBridge(bridge);\n }, { passive: true });\n\n function createNativeMcpAppsBridge() {\n let rpcId = 0;\n let connected = false;\n let hostContext = {};\n const pendingRequests = new Map();\n\n function rpcNotify(method, params) {\n window.parent.postMessage({ jsonrpc: \"2.0\", method, params: params || {} }, \"*\");\n }\n\n function rpcRequest(method, params, timeoutMs) {\n return new Promise((resolve, reject) => {\n const id = ++rpcId;\n const timer = window.setTimeout(() => {\n pendingRequests.delete(id);\n reject(new Error(\"MCP Apps bridge request timed out: \" + method));\n }, timeoutMs || nativeBridgeRequestTimeoutMs);\n pendingRequests.set(id, { resolve, reject, timer });\n window.parent.postMessage(\n { jsonrpc: \"2.0\", id, method, params: params || {} },\n \"*\"\n );\n });\n }\n\n function settleRpcResponse(message) {\n const pending = pendingRequests.get(message.id);\n if (!pending) return true;\n pendingRequests.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n const error = new Error(message.error.message || \"MCP Apps bridge request failed.\");\n error.data = message.error.data;\n pending.reject(error);\n return true;\n }\n pending.resolve(message.result);\n return true;\n }\n\n function notificationParams(message) {\n return message && typeof message.params === \"object\" && message.params\n ? message.params\n : {};\n }\n\n function toolInputNotificationParams(params) {\n if (params && typeof params.arguments === \"object\" && params.arguments) {\n return params;\n }\n if (params && typeof params.input === \"object\" && params.input) {\n return { arguments: params.input };\n }\n return { arguments: objectValue(params) };\n }\n\n const nativeApp = {\n ontoolinput: null,\n ontoolresult: null,\n onhostcontextchanged: null,\n getHostContext() {\n return hostContext.context || hostContext;\n },\n getHostCapabilities() {\n return hostContext.capabilities || { tools: true, messaging: true };\n },\n getHostVersion() {\n return hostContext.protocolVersion || \"mcp-apps-postmessage\";\n },\n async connect() {\n if (connected) return hostContext;\n connected = true;\n window.addEventListener(\"message\", onMessage, { passive: true });\n const result = await rpcRequest(\n \"ui/initialize\",\n {\n appInfo: { name: \"Agent Native Embed\", version: \"1.0.0\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n },\n nativeBridgeInitializeTimeoutMs\n );\n hostContext = objectValue(result);\n rpcNotify(\"ui/notifications/initialized\", {});\n if (typeof nativeApp.onhostcontextchanged === \"function\") {\n nativeApp.onhostcontextchanged(hostContext);\n }\n return hostContext;\n },\n async callServerTool(request) {\n const record = objectValue(request);\n return await rpcRequest(\"tools/call\", {\n name: record.name,\n arguments: objectValue(record.arguments)\n });\n },\n async updateModelContext(params) {\n return await rpcRequest(\"ui/update-model-context\", objectValue(params));\n },\n async openLink(params) {\n const url = typeof (params && params.url) === \"string\" ? params.url : \"\";\n if (!url) return { isError: true };\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n return { ok: true };\n },\n async requestDisplayMode(params) {\n return await rpcRequest(\"ui/request-display-mode\", objectValue(params));\n },\n sendSizeChanged(params) {\n rpcNotify(\"ui/notifications/size-changed\", objectValue(params));\n },\n async sendMessage(params) {\n return await rpcRequest(\"ui/message\", objectValue(params));\n }\n };\n\n function onMessage(event) {\n if (event.source !== window.parent) return;\n const message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (typeof message.id === \"number\" || typeof message.id === \"string\") {\n settleRpcResponse(message);\n return;\n }\n if (typeof message.method !== \"string\") return;\n const params = notificationParams(message);\n if (message.method === \"ui/notifications/tool-input\") {\n if (typeof nativeApp.ontoolinput === \"function\") {\n nativeApp.ontoolinput(toolInputNotificationParams(params));\n }\n return;\n }\n if (message.method === \"ui/notifications/tool-result\") {\n if (typeof nativeApp.ontoolresult === \"function\") {\n nativeApp.ontoolresult(params);\n }\n return;\n }\n if (\n message.method === \"ui/notifications/host-context\" ||\n message.method === \"ui/notifications/context\"\n ) {\n hostContext = objectValue(params);\n if (typeof nativeApp.onhostcontextchanged === \"function\") {\n nativeApp.onhostcontextchanged(hostContext);\n }\n }\n }\n\n return nativeApp;\n }\n\n async function startNativeMcpAppsBridge() {\n app = createNativeMcpAppsBridge();\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n toolResultData = objectValue(data);\n openUrl = openLinkFrom(params, data);\n openStartUrl = embedStartUrlFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n app.onhostcontextchanged = () => {\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n };\n await app.connect();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n }\n\n async function startMcpAppsBridge() {\n const { App } = await import(\"${MCP_APP_IMPORT}\");\n app = new App(\n { name: \"Agent Native Embed\", version: \"1.0.0\" },\n {},\n { autoResize: false }\n );\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n toolResultData = objectValue(data);\n openUrl = openLinkFrom(params, data);\n openStartUrl = embedStartUrlFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n app.onhostcontextchanged = () => {\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n };\n await app.connect();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n }\n\n try {\n const initialOpenAiBridge = await waitForOpenAiBridge();\n if (!syncOpenAiBridge(initialOpenAiBridge)) {\n try {\n await startNativeMcpAppsBridge();\n } catch (nativeErr) {\n console.warn(\"[agent-native] native MCP Apps bridge failed\", nativeErr);\n await startMcpAppsBridge();\n }\n }\n } catch (err) {\n console.error(\"[agent-native] MCP app shell failed\", err);\n setMessage(err && err.message ? err.message : \"Could not initialize app.\");\n }\n })();\n </script>\n</body>\n</html>`,\n csp: {\n connectDomains: [\n \"https://esm.sh\",\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.connectDomains ?? []),\n ...(options.frameDomains ?? []),\n ],\n resourceDomains: [\n \"https://esm.sh\",\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.resourceDomains ?? []),\n ...(options.frameDomains ?? []),\n ],\n baseUriDomains: [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.baseUriDomains ?? []),\n ],\n frameDomains,\n },\n prefersBorder: false,\n };\n}\n"]}
1
+ {"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\nimport { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from \"../shared/embed-auth.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\nconst MCP_APP_WRAPPER_CHROME_HEIGHT = 44;\nexport const DEFAULT_MCP_APP_SHELL_HEIGHT = 560;\nexport const DEFAULT_MCP_APP_VIEWPORT_HEIGHT =\n DEFAULT_MCP_APP_SHELL_HEIGHT - MCP_APP_WRAPPER_CHROME_HEIGHT;\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n connectDomains?: string[];\n resourceDomains?: string[];\n baseUriDomains?: string[];\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(\n 320,\n Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT),\n );\n const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;\n const frameDomains = [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ];\n\n return {\n title,\n ...(options.description ? { description: options.description } : {}),\n html: () => `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: Canvas; color: CanvasText; --agent-native-shell-height: ${height}px; --agent-native-viewport-height: ${viewportHeight}px; }\n * { box-sizing: border-box; }\n body { margin: 0; }\n .shell { display: grid; gap: 8px; min-height: var(--agent-native-shell-height); padding: 0; }\n .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }\n .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }\n .actions { display: flex; align-items: center; gap: 6px; }\n button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }\n button:disabled { opacity: .55; cursor: default; }\n .stage { position: relative; min-height: var(--agent-native-viewport-height); }\n iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }\n .message { display: grid; place-items: center; min-height: var(--agent-native-viewport-height); padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }\n .fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height: var(--agent-native-viewport-height); padding: 24px; background: Canvas; color: CanvasText; text-align: center; }\n .fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }\n .fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }\n .fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }\n .fallback-url { max-width: min(560px, 100%); overflow-wrap: anywhere; color: color-mix(in srgb, CanvasText 76%, Canvas); font-size: 12px; }\n </style>\n</head>\n<body\n data-app-title=\"${attr(title)}\"\n data-iframe-title=\"${attr(iframeTitle)}\"\n data-open-label=\"${attr(openLabel)}\"\n data-start-tool=\"${attr(startToolName)}\"\n data-embed-default=\"${embedByDefault ? \"1\" : \"0\"}\"\n>\n <main class=\"shell\">\n <div class=\"bar\">\n <div class=\"title\" data-title-label>${attr(title)}</div>\n <div class=\"actions\">\n <button type=\"button\" data-display hidden disabled>Fullscreen</button>\n <button type=\"button\" data-open disabled>${attr(openLabel)}</button>\n </div>\n </div>\n <section class=\"stage\" data-stage>\n <div class=\"message\">Preparing app</div>\n </section>\n </main>\n <script>\n (async () => {\n const body = document.body;\n const stage = document.querySelector(\"[data-stage]\");\n const titleEl = document.querySelector(\"[data-title-label]\");\n const openButton = document.querySelector(\"[data-open]\");\n const displayButton = document.querySelector(\"[data-display]\");\n const startTool = body.dataset.startTool || \"create_embed_session\";\n const embedByDefault = body.dataset.embedDefault !== \"0\";\n const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};\n const defaultIntrinsicHeight = ${height};\n const chromeHeight = ${MCP_APP_WRAPPER_CHROME_HEIGHT};\n const frameReadyMessageDelays = [0, 200, 500, 1500, 3000, 7000, 15000, 30000];\n const frameReadyTimeoutMs = 45000;\n const frameLoadTimeoutMs = 45000;\n const defaultOpenAiBridgeWaitMs = 200;\n const chatGptOpenAiBridgeWaitMs = 5000;\n const openAiBridgePollMs = 50;\n const nativeBridgeInitializeTimeoutMs = 5000;\n const nativeBridgeRequestTimeoutMs = 30000;\n const wrapperRequestTimeoutMs = 5000;\n let app = null;\n let openAiBridge = null;\n let wrapperRequestId = 0;\n const wrapperRequests = new Map();\n let toolInput = {};\n let toolResultData = {};\n let openUrl = \"\";\n let openStartUrl = \"\";\n let startedFor = \"\";\n let appFrame = null;\n let appFrameReady = false;\n let appFrameReadyTimer = null;\n let appFrameLoadTimer = null;\n let lastFrameSrc = \"\";\n\n function esc(value) {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n }\n\n function parseJson(value, fallback) {\n if (value && typeof value === \"object\") return value;\n if (typeof value !== \"string\" || !value.trim()) return fallback;\n try { return JSON.parse(value); } catch { return fallback; }\n }\n\n function objectValue(value) {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : {};\n }\n\n function finiteNumber(value) {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0\n ? value\n : null;\n }\n\n function contextMaxHeight(context) {\n if (!context || typeof context !== \"object\") return null;\n return finiteNumber(context.maxHeight) ||\n finiteNumber(context.containerDimensions && context.containerDimensions.maxHeight);\n }\n\n function visibleIntrinsicHeight() {\n const context = hostState().context || {};\n const hostMaxHeight = contextMaxHeight(context);\n if (hostMaxHeight) return Math.floor(hostMaxHeight);\n const viewportHeight = finiteNumber(window.visualViewport && window.visualViewport.height) ||\n finiteNumber(window.innerHeight);\n return Math.floor(viewportHeight || defaultIntrinsicHeight);\n }\n\n function applyIntrinsicHeight(nextHeight) {\n const boundedHeight = Math.min(\n defaultIntrinsicHeight,\n Math.floor(nextHeight || defaultIntrinsicHeight)\n );\n const height = Math.max(320, boundedHeight);\n const viewportHeight = Math.max(0, height - chromeHeight);\n document.documentElement.style.setProperty(\"--agent-native-shell-height\", height + \"px\");\n document.documentElement.style.setProperty(\"--agent-native-viewport-height\", viewportHeight + \"px\");\n if (appFrame) appFrame.style.height = viewportHeight + \"px\";\n return height;\n }\n\n function parseToolResult(params) {\n if (!params) return {};\n if (params.result && typeof params.result === \"object\") {\n return parseToolResult(params.result);\n }\n if (params.toolResult && typeof params.toolResult === \"object\") {\n return parseToolResult(params.toolResult);\n }\n if (params.structuredContent && typeof params.structuredContent === \"object\") {\n return params.structuredContent;\n }\n const parts = Array.isArray(params.content) ? params.content : [];\n const textPart = parts.find((part) => part && part.type === \"text\" && typeof part.text === \"string\");\n const text = textPart ? textPart.text : \"\";\n if (params.isError && typeof text === \"string\" && text.trim()) {\n return { error: text.trim() };\n }\n return parseJson(text, {});\n }\n\n function metadataRecord(value) {\n const meta = value && typeof value === \"object\" && !Array.isArray(value)\n ? value._meta\n : null;\n return meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? meta\n : null;\n }\n\n function toolResultMeta(params) {\n if (!params || typeof params !== \"object\") return {};\n const direct = metadataRecord(params);\n if (direct) return direct;\n if (params.result && typeof params.result === \"object\") {\n return toolResultMeta(params.result);\n }\n if (params.toolResult && typeof params.toolResult === \"object\") {\n return toolResultMeta(params.toolResult);\n }\n return {};\n }\n\n function openLinkRecordFrom(value) {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : {};\n }\n\n function openLinkWebUrlFrom(value) {\n const record = openLinkRecordFrom(value);\n return typeof record.webUrl === \"string\" ? record.webUrl : \"\";\n }\n\n function firstNonEmbedStartUrl(values) {\n for (const value of values) {\n if (typeof value === \"string\" && value && !isEmbedStartUrl(value)) return value;\n }\n return \"\";\n }\n\n function firstEmbedStartUrl(values) {\n for (const value of values) {\n if (typeof value === \"string\" && value && isEmbedStartUrl(value)) {\n return withChatBridgeParam(value);\n }\n }\n return \"\";\n }\n\n function openLinkFrom(params, data) {\n const meta = toolResultMeta(params);\n const openLink = meta[\"agent-native/openLink\"];\n const metaUrl = openLinkWebUrlFrom(openLink);\n const record = data && typeof data === \"object\" ? data : {};\n const structuredOpenLinkUrl = openLinkWebUrlFrom(record.openLink);\n return firstNonEmbedStartUrl([\n record.embedTargetPath,\n record.deepLinkUrl,\n record.deepLink,\n record.openUrl,\n record.url,\n structuredOpenLinkUrl,\n metaUrl\n ]);\n }\n\n function embedStartUrlFrom(params, data) {\n const meta = toolResultMeta(params);\n const embedStart = meta[\"agent-native/embedStart\"];\n const embedStartRecord =\n embedStart && typeof embedStart === \"object\" && !Array.isArray(embedStart)\n ? embedStart\n : {};\n const openLink = meta[\"agent-native/openLink\"];\n const metaUrl = openLinkWebUrlFrom(openLink);\n const record = data && typeof data === \"object\" ? data : {};\n return firstEmbedStartUrl([\n embedStartRecord.startUrl,\n record.embedStartUrl,\n record.startUrl,\n record.url,\n openLinkWebUrlFrom(record.openLink),\n metaUrl\n ]);\n }\n\n function hostState() {\n if (openAiBridge) {\n return {\n context: {\n displayMode: openAiBridge.displayMode,\n availableDisplayModes: typeof openAiBridge.requestDisplayMode === \"function\"\n ? [\"inline\", \"fullscreen\", \"pip\"]\n : [],\n maxHeight: openAiBridge.maxHeight,\n locale: openAiBridge.locale,\n theme: openAiBridge.theme,\n view: openAiBridge.view\n },\n capabilities: { openai: true },\n version: openAiBridge.userAgent\n };\n }\n return {\n context: app && app.getHostContext ? app.getHostContext() : undefined,\n capabilities: app && app.getHostCapabilities ? app.getHostCapabilities() : undefined,\n version: app && app.getHostVersion ? app.getHostVersion() : undefined\n };\n }\n\n function sendToAppFrame(message) {\n if (!appFrame || !appFrame.contentWindow) return;\n try { appFrame.contentWindow.postMessage(message, \"*\"); } catch {}\n }\n\n function nextWrapperRequestId() {\n wrapperRequestId += 1;\n return \"mcp-wrapper-\" + Date.now() + \"-\" + wrapperRequestId;\n }\n\n function respondToWrapperRequest(requestId, result) {\n if (!requestId) return;\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: {\n requestId,\n ok: !!(result && result.ok),\n result: result || {}\n }\n });\n }\n\n function wrapperRpcRequest(method, params, timeoutMs) {\n return new Promise((resolve) => {\n const id = nextWrapperRequestId();\n const timer = window.setTimeout(() => {\n wrapperRequests.delete(id);\n resolve({ ok: false, error: \"MCP host bridge request timed out.\" });\n }, timeoutMs || wrapperRequestTimeoutMs);\n wrapperRequests.set(id, { resolve, timer });\n try {\n window.parent.postMessage(\n { jsonrpc: \"2.0\", id, method, params: params || {} },\n \"*\"\n );\n } catch (err) {\n wrapperRequests.delete(id);\n clearTimeout(timer);\n resolve({ ok: false, error: err && err.message ? err.message : String(err) });\n }\n });\n }\n\n function settleWrapperRpcResponse(message) {\n const id = typeof (message && message.id) === \"string\" ? message.id : \"\";\n if (!id) return false;\n const pending = wrapperRequests.get(id);\n if (!pending) return false;\n wrapperRequests.delete(id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.resolve({ ok: false, error: message.error });\n } else {\n pending.resolve({ ok: true, result: message.result });\n }\n return true;\n }\n\n function sendHostContext() {\n sendToAppFrame({ type: \"agentNative.mcpHostContext\", data: hostState() });\n }\n\n function sendFrameReadyMessages(frame) {\n const originPayload = { type: \"agentNative.frameOrigin\", origin: window.location.origin };\n frameReadyMessageDelays.forEach((delay) => {\n setTimeout(() => {\n try { frame.contentWindow && frame.contentWindow.postMessage(originPayload, \"*\"); } catch {}\n sendHostContext();\n }, delay);\n });\n }\n\n function withChatBridgeParam(value) {\n if (typeof value !== \"string\" || !value) return value;\n try {\n const base = \"http://agent-native.invalid\";\n const url = value.startsWith(\"/\") ? new URL(value, base) : new URL(value);\n url.searchParams.set(chatBridgeParam, \"1\");\n return value.startsWith(\"/\")\n ? url.pathname + url.search + url.hash\n : url.toString();\n } catch {\n return value;\n }\n }\n\n function embedSessionArgsFor(value) {\n const chrome = typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\";\n return typeof value === \"string\" && value.startsWith(\"/\")\n ? { path: value, chrome }\n : { url: value, chrome };\n }\n\n function isEmbedStartUrl(value) {\n if (typeof value !== \"string\" || !value) return false;\n try {\n const url = new URL(value, window.location.href);\n return url.pathname.endsWith(\"/_agent-native/embed/start\");\n } catch {\n return false;\n }\n }\n\n function localPathFromUrl(url, includeToken) {\n const next = new URL(url.href);\n if (!includeToken) next.searchParams.delete(\"__an_embed_token\");\n return next.pathname + next.search + next.hash;\n }\n\n function rewriteRootRelativeHtmlUrls(html, appOrigin) {\n return String(html).replace(\n /\\\\b(src|href|poster|action)\\\\s*=\\\\s*([\"'])\\\\/(?!\\\\/)/gi,\n (_match, name, quote) => String(name) + \"=\" + quote + appOrigin + \"/\"\n );\n }\n\n function removeHtmlCspMeta(html) {\n return String(html).replace(\n /<meta\\\\s+[^>]*http-equiv\\\\s*=\\\\s*([\"'])?content-security-policy\\\\1?[^>]*>/gi,\n \"\"\n );\n }\n\n function embedConfigForAppUrl(appUrl) {\n const sanitizedTarget = localPathFromUrl(appUrl, false);\n return {\n origin: appUrl.origin,\n href: appUrl.href,\n baseHref: appUrl.origin + appUrl.pathname,\n target: sanitizedTarget,\n token: appUrl.searchParams.get(\"__an_embed_token\") || \"\",\n chatBridgeActive: appUrl.searchParams.get(chatBridgeParam) === \"1\",\n chatBridgeParam,\n embedTokenParam: \"__an_embed_token\",\n embedTargetHeader: \"x-agent-native-embed-target\"\n };\n }\n\n function externalOpenUrlForAppUrl(appUrl) {\n if (openUrl) return openUrl;\n try {\n const url = new URL(appUrl.href);\n url.searchParams.delete(\"embedded\");\n url.searchParams.delete(\"__an_embed_token\");\n url.searchParams.delete(chatBridgeParam);\n return url.toString();\n } catch (_err) {\n return \"\";\n }\n }\n\n function installExternalOpenControl(appUrl) {\n const href = externalOpenUrlForAppUrl(appUrl);\n if (!href || !document.body || document.getElementById(\"agent-native-external-open-control\")) {\n return;\n }\n const host = document.createElement(\"div\");\n host.id = \"agent-native-external-open-control\";\n host.style.position = \"fixed\";\n host.style.top = \"10px\";\n host.style.right = \"10px\";\n host.style.zIndex = \"2147483647\";\n host.style.pointerEvents = \"auto\";\n const root = host.attachShadow ? host.attachShadow({ mode: \"open\" }) : host;\n root.innerHTML =\n '<style>' +\n ':host{all:initial;}' +\n 'button{box-sizing:border-box;min-height:30px;border:1px solid color-mix(in srgb, CanvasText 18%, Canvas);border-radius:7px;background:Canvas;color:CanvasText;box-shadow:0 6px 22px color-mix(in srgb, CanvasText 14%, transparent);cursor:pointer;font:700 12px ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;padding:0 10px;white-space:nowrap;}' +\n 'button:hover{background:color-mix(in srgb, CanvasText 6%, Canvas);}' +\n '</style>' +\n '<button type=\"button\">Open in new tab</button>';\n const button = root.querySelector(\"button\");\n if (button) {\n button.addEventListener(\"click\", (event) => {\n event.preventDefault();\n void openHostLink({ url: href });\n });\n }\n document.body.appendChild(host);\n }\n\n function installReactRefreshPreambleFallback() {\n window.__vite_plugin_react_preamble_installed__ = true;\n if (typeof window.$RefreshReg$ !== \"function\") {\n window.$RefreshReg$ = function() {};\n }\n if (typeof window.$RefreshSig$ !== \"function\") {\n window.$RefreshSig$ = function() {\n return function(type) {\n return type;\n };\n };\n }\n }\n\n function installExternalEmbedRuntime(config) {\n window.__AGENT_NATIVE_EXTERNAL_EMBED = config;\n installReactRefreshPreambleFallback();\n try {\n if (config.target) {\n window.history.replaceState(window.history.state, \"\", config.target);\n }\n } catch (_err) {}\n try {\n if (config.token) {\n sessionStorage.setItem(\"agent-native:embed-auth-token\", config.token);\n }\n if (config.chatBridgeActive && config.token) {\n sessionStorage.setItem(\"agent-native:mcp-chat-bridge\", config.token);\n }\n } catch (_err) {}\n if (window.__agentNativeExternalEmbedRuntimeInstalled) return;\n window.__agentNativeExternalEmbedRuntimeInstalled = true;\n function appOrigin() {\n try {\n return new URL(config.origin).origin;\n } catch (_err) {\n return \"\";\n }\n }\n function targetPath() {\n return config.target || location.pathname + location.search;\n }\n function rewrittenUrl(value, appendToken) {\n const origin = appOrigin();\n if (!origin) return null;\n let url;\n try {\n url = new URL(value, location.href);\n } catch (_err) {\n return null;\n }\n if (url.origin !== location.origin && url.origin !== origin) return null;\n if (url.origin !== origin) {\n const app = new URL(origin);\n url.protocol = app.protocol;\n url.host = app.host;\n }\n if (appendToken && config.token && url.pathname === \"/_agent-native/events\") {\n url.searchParams.set(config.embedTokenParam, config.token);\n }\n return url.toString();\n }\n function authHeaders(input, init) {\n const headers = new Headers(\n init && init.headers ? init.headers : input instanceof Request ? input.headers : undefined\n );\n if (config.token && !headers.has(\"Authorization\")) {\n headers.set(\"Authorization\", \"Bearer \" + config.token);\n }\n if (!headers.has(config.embedTargetHeader)) {\n headers.set(config.embedTargetHeader, targetPath());\n }\n return headers;\n }\n if (typeof fetch === \"function\") {\n const originalFetch = fetch.bind(window);\n window.fetch = function(input, init) {\n const raw = input instanceof Request ? input.url : String(input);\n const url = rewrittenUrl(raw, false);\n if (!url) return originalFetch(input, init);\n const nextInit = Object.assign({}, init || {}, {\n headers: authHeaders(input, init),\n credentials: \"omit\"\n });\n if (input instanceof Request) {\n return originalFetch(new Request(url, input), nextInit);\n }\n return originalFetch(url, nextInit);\n };\n }\n if (typeof XMLHttpRequest !== \"undefined\") {\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n XMLHttpRequest.prototype.open = function(method, url) {\n const rewritten = rewrittenUrl(url, false);\n this.__agentNativeExternalEmbed = !!rewritten;\n return originalOpen.call(\n this,\n method,\n rewritten || url,\n arguments.length > 2 ? arguments[2] : true,\n arguments[3],\n arguments[4]\n );\n };\n XMLHttpRequest.prototype.send = function(body) {\n if (this.__agentNativeExternalEmbed) {\n try {\n if (config.token) this.setRequestHeader(\"Authorization\", \"Bearer \" + config.token);\n this.setRequestHeader(config.embedTargetHeader, targetPath());\n } catch (_err) {}\n }\n return originalSend.call(this, body);\n };\n }\n if (typeof EventSource !== \"undefined\") {\n const OriginalEventSource = EventSource;\n window.EventSource = function(url, options) {\n return new OriginalEventSource(rewrittenUrl(url, true) || url, options);\n };\n window.EventSource.prototype = OriginalEventSource.prototype;\n }\n }\n\n function copyDocumentElementAttributes(source) {\n const target = document.documentElement;\n for (const attr of Array.from(target.attributes)) {\n target.removeAttribute(attr.name);\n }\n for (const attr of Array.from(source.attributes)) {\n target.setAttribute(attr.name, attr.value);\n }\n }\n\n function importChildren(source, target) {\n target.replaceChildren(\n ...Array.from(source.childNodes).map((node) => document.importNode(node, true))\n );\n }\n\n function isModuleScript(script) {\n return (script.getAttribute(\"type\") || \"\").trim().toLowerCase() === \"module\";\n }\n\n function isRunnableClassicScript(script) {\n const type = (script.getAttribute(\"type\") || \"\").trim().toLowerCase();\n return !type || type === \"text/javascript\" || type === \"application/javascript\";\n }\n\n function runClassicScript(script) {\n const next = document.createElement(\"script\");\n for (const attr of Array.from(script.attributes)) {\n if (attr.name === \"type\") continue;\n next.setAttribute(attr.name, attr.value);\n }\n if (script.src) {\n next.src = script.src;\n } else {\n next.textContent = script.textContent || \"\";\n }\n document.body.appendChild(next);\n next.remove();\n }\n\n function isEmbedRuntimeModulePath(pathname) {\n if (typeof pathname !== \"string\" || !pathname) return false;\n return /(?:^|\\\\/)(?:@(?:id|vite|fs|react-refresh)|app|node_modules|packages|src)(?:\\\\/|$)/.test(pathname) ||\n /(?:^|\\\\/)__x00__virtual:/.test(pathname) ||\n pathname.includes(\"virtual:react-router\");\n }\n\n function appendEmbedParamsToAppUrl(url, config) {\n if (isEmbedRuntimeModulePath(url.pathname)) return url;\n if (config.token) url.searchParams.set(config.embedTokenParam, config.token);\n if (config.chatBridgeActive) url.searchParams.set(config.chatBridgeParam, \"1\");\n return url;\n }\n\n function rootRelativeSpecifierToAppUrl(specifier, config) {\n if (typeof specifier !== \"string\" || !specifier.startsWith(\"/\") || specifier.startsWith(\"//\")) {\n return specifier;\n }\n try {\n const url = new URL(specifier, config.origin);\n return appendEmbedParamsToAppUrl(url, config).toString();\n } catch (_err) {\n return specifier;\n }\n }\n\n function rootRelativeSpecifiersToAbsolute(code, config) {\n return String(code).replace(/([\"'])\\\\/(?!\\\\/)([^\"']*)/g, (_match, quote, rest) => {\n return quote + rootRelativeSpecifierToAppUrl(\"/\" + rest, config);\n });\n }\n\n function relativeSpecifierToAppUrl(specifier, config, baseUrl) {\n if (typeof specifier !== \"string\" || !/^\\\\.\\\\.?\\\\//.test(specifier)) {\n return specifier;\n }\n try {\n const url = new URL(specifier, baseUrl || config.baseHref);\n if (url.origin === config.origin) {\n appendEmbedParamsToAppUrl(url, config);\n }\n return url.toString();\n } catch (_err) {\n return specifier;\n }\n }\n\n function relativeModuleSpecifiersToAbsolute(code, config, baseUrl) {\n return String(code)\n .replace(/(\\\\bimport\\\\s+(?:[^\"']+?\\\\s+from\\\\s+)?)([\"'])(\\\\.\\\\.?\\\\/[^\"']*)\\\\2/g, (_match, prefix, quote, specifier) => {\n return prefix + quote + relativeSpecifierToAppUrl(specifier, config, baseUrl) + quote;\n })\n .replace(/(\\\\bimport\\\\s*\\\\(\\\\s*)([\"'])(\\\\.\\\\.?\\\\/[^\"']*)\\\\2/g, (_match, prefix, quote, specifier) => {\n return prefix + quote + relativeSpecifierToAppUrl(specifier, config, baseUrl) + quote;\n })\n .replace(/(\\\\bexport\\\\s+[^\"']*?\\\\s+from\\\\s+)([\"'])(\\\\.\\\\.?\\\\/[^\"']*)\\\\2/g, (_match, prefix, quote, specifier) => {\n return prefix + quote + relativeSpecifierToAppUrl(specifier, config, baseUrl) + quote;\n });\n }\n\n function stripDevOnlyModuleImports(code) {\n return String(code).replace(\n /\\\\bimport\\\\s+(?:[^\"']+\\\\s+from\\\\s+)?[\"'][^\"']*(?:virtual:react-router\\\\/inject-hmr-runtime|__x00__virtual:react-router\\\\/inject-hmr-runtime)[^\"']*[\"']\\\\s*;?/g,\n \"\"\n );\n }\n\n function namedImportBindings(specifierList) {\n return String(specifierList)\n .split(\",\")\n .map((part) => {\n const trimmed = part.trim();\n if (!trimmed) return \"\";\n return trimmed.replace(\n /^([A-Za-z_$][\\\\w$]*)\\\\s+as\\\\s+([A-Za-z_$][\\\\w$]*)$/,\n \"$1: $2\"\n );\n })\n .filter(Boolean)\n .join(\", \");\n }\n\n function moduleCodeToClassicAsync(code, config, baseUrl) {\n return relativeModuleSpecifiersToAbsolute(\n rootRelativeSpecifiersToAbsolute(stripDevOnlyModuleImports(code), config),\n config,\n baseUrl\n )\n .replace(\n /\\\\bimport\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s*,\\\\s*\\\\*\\\\s+as\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s+from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n \"const $2 = await import($3); const $1 = $2.default;\"\n )\n .replace(\n /\\\\bimport\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s*,\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n (_match, defaultName, specifiers, source) =>\n \"const { default: \" +\n defaultName +\n (namedImportBindings(specifiers) ? \", \" + namedImportBindings(specifiers) : \"\") +\n \" } = await import(\" +\n source +\n \");\"\n )\n .replace(\n /\\\\bimport\\\\s+\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n (_match, specifiers, source) =>\n \"const { \" + namedImportBindings(specifiers) + \" } = await import(\" + source + \");\"\n )\n .replace(\n /\\\\bimport\\\\s+\\\\*\\\\s+as\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s+from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n \"const $1 = await import($2);\"\n )\n .replace(\n /\\\\bimport\\\\s+([A-Za-z_$][\\\\w$]*)\\\\s+from\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g,\n \"const { default: $1 } = await import($2);\"\n )\n .replace(/\\\\bimport\\\\s+([\"'][^\"']+[\"'])\\\\s*;?/g, \"await import($1);\")\n .replace(/\\\\bimport\\\\(([\"'][^\"']+[\"'])\\\\)\\\\s*;?/g, \"await import($1);\");\n }\n\n function scriptSourceUrl(script, config) {\n const raw = script.getAttribute(\"src\") || \"\";\n if (!raw) return \"\";\n try {\n const url = new URL(raw, config.baseHref);\n if (url.origin === config.origin) {\n appendEmbedParamsToAppUrl(url, config);\n }\n return url.toString();\n } catch (_err) {\n return raw;\n }\n }\n\n async function moduleScriptCode(script, config) {\n const src = scriptSourceUrl(script, config);\n if (!src) return script.textContent || \"\";\n const response = await fetch(src, {\n credentials: \"omit\",\n headers: { Accept: \"text/javascript, application/javascript, */*\" }\n });\n if (!response.ok) {\n throw new Error(\"Module script returned HTTP \" + response.status + \".\");\n }\n return await response.text();\n }\n\n async function runModuleScriptAsClassic(script, config) {\n const sourceUrl = scriptSourceUrl(script, config) || config.baseHref;\n const code = moduleCodeToClassicAsync(\n await moduleScriptCode(script, config),\n config,\n sourceUrl\n );\n const runner = document.createElement(\"script\");\n runner.textContent =\n \"(async()=>{\" +\n code +\n \"})().catch((err)=>{console.error('[agent-native] transplanted app module failed',err);document.body.setAttribute('data-agent-native-hydration-error',String(err&&err.message||err));});\";\n document.body.appendChild(runner);\n runner.remove();\n }\n\n async function mountTransplantedHtml(html, appUrl) {\n const config = embedConfigForAppUrl(appUrl);\n installExternalEmbedRuntime(config);\n const parsed = new DOMParser().parseFromString(\n rewriteRootRelativeHtmlUrls(removeHtmlCspMeta(html), appUrl.origin),\n \"text/html\"\n );\n const scripts = Array.from(parsed.querySelectorAll(\"script\"));\n copyDocumentElementAttributes(parsed.documentElement);\n importChildren(parsed.head, document.head);\n const base = document.createElement(\"base\");\n base.href = config.baseHref;\n document.head.prepend(base);\n importChildren(parsed.body, document.body);\n installExternalOpenControl(appUrl);\n for (const script of scripts) {\n if (isRunnableClassicScript(script)) runClassicScript(script);\n }\n for (const script of scripts) {\n if (isModuleScript(script)) await runModuleScriptAsClassic(script, config);\n }\n }\n\n function absoluteUrl(value, base) {\n try {\n return new URL(value, base).toString();\n } catch {\n return \"\";\n }\n }\n\n async function resolveTransplantAppDocumentSource(src) {\n if (!isEmbedStartUrl(src)) {\n return { url: new URL(src), response: null };\n }\n const response = await fetch(src, {\n credentials: \"omit\",\n redirect: \"follow\",\n headers: {\n Accept: \"application/json\",\n \"X-Agent-Native-Embed-Transplant\": \"1\"\n }\n });\n const contentType = response.headers.get(\"content-type\") || \"\";\n if (response.ok && /\\\\bapplication\\\\/json\\\\b/i.test(contentType)) {\n const data = await response.json();\n const location = typeof data.location === \"string\" ? data.location : \"\";\n const url = absoluteUrl(location, src);\n if (url) return { url: new URL(withChatBridgeParam(url)), response: null };\n throw new Error(\"Embedded app did not return a launch URL.\");\n }\n return {\n url: new URL(response.url || src),\n response\n };\n }\n\n async function transplantAppDocument(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n lastFrameSrc = src;\n setMessage(\"Loading app\");\n const source = await resolveTransplantAppDocumentSource(src);\n const response = source.response || await fetch(source.url.href, {\n credentials: \"omit\",\n redirect: \"follow\",\n headers: { Accept: \"text/html\" }\n });\n if (!response.ok) {\n if (response.status === 401 && isEmbedStartUrl(src)) {\n refreshExpiredEmbedSession();\n return;\n }\n throw new Error(\"Embedded app returned HTTP \" + response.status + \".\");\n }\n const html = await response.text();\n const appUrl = source.url || new URL(response.url || src);\n try {\n window.history.replaceState(window.history.state, \"\", localPathFromUrl(appUrl, false));\n } catch {}\n await mountTransplantedHtml(html, appUrl);\n notifyHostHeightRepeatedly();\n }\n\n function wantsEmbed() {\n if (toolInput.embed === false || toolInput.embed === \"false\") return false;\n if (embedByDefault) return true;\n return toolInput.embed === true || toolInput.embed === \"true\";\n }\n\n function renderModeSource() {\n const input = objectValue(toolInput);\n const result = objectValue(toolResultData);\n return {\n mode: typeof input.embedMode === \"string\"\n ? input.embedMode\n : typeof input.renderMode === \"string\"\n ? input.renderMode\n : typeof result.embedMode === \"string\"\n ? result.embedMode\n : typeof result.renderMode === \"string\"\n ? result.renderMode\n : \"\",\n frame: typeof input.frame === \"string\"\n ? input.frame\n : typeof result.frame === \"string\"\n ? result.frame\n : \"\",\n nested: input.nested === true || result.nested === true\n };\n }\n\n function supportedDisplayMode(mode) {\n if (openAiBridge && typeof openAiBridge.requestDisplayMode === \"function\") {\n return mode === \"inline\" || mode === \"fullscreen\" || mode === \"pip\";\n }\n const modes = hostState().context && hostState().context.availableDisplayModes;\n return Array.isArray(modes) && modes.includes(mode);\n }\n\n async function requestHostDisplayMode(mode) {\n let result;\n if (openAiBridge && typeof openAiBridge.requestDisplayMode === \"function\") {\n result = await openAiBridge.requestDisplayMode({ mode });\n } else {\n if (!app || typeof app.requestDisplayMode !== \"function\") {\n throw new Error(\"Display mode changes are not available in this host.\");\n }\n result = await app.requestDisplayMode({ mode });\n }\n updateDisplayButton();\n sendHostContext();\n return result;\n }\n\n function updateDisplayButton() {\n const context = hostState().context || {};\n const nextMode = context.displayMode === \"fullscreen\" ? \"inline\" : \"fullscreen\";\n const supported = supportedDisplayMode(nextMode);\n displayButton.hidden = !supported;\n displayButton.disabled = !supported;\n displayButton.textContent = nextMode === \"fullscreen\" ? \"Fullscreen\" : \"Inline\";\n displayButton.onclick = () => {\n if (!supportedDisplayMode(nextMode)) return;\n void requestHostDisplayMode(nextMode).catch((err) => {\n console.warn(\"[agent-native] MCP host rejected display mode request\", err);\n });\n };\n }\n\n function setMessage(message) {\n stage.innerHTML = '<div class=\"message\">' + esc(message) + '</div>';\n }\n\n function clearFrameReadyTimer() {\n if (!appFrameReadyTimer) return;\n clearTimeout(appFrameReadyTimer);\n appFrameReadyTimer = null;\n }\n\n function clearFrameLoadTimer() {\n if (!appFrameLoadTimer) return;\n clearTimeout(appFrameLoadTimer);\n appFrameLoadTimer = null;\n }\n\n function startFrameReadyTimer(frame) {\n clearFrameReadyTimer();\n appFrameReadyTimer = setTimeout(() => {\n if (!appFrameReady && appFrame === frame) renderFrameFallback();\n }, frameReadyTimeoutMs);\n }\n\n function renderFrameFallback() {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n const fallbackCopy = openUrl\n ? \"This chat host did not allow the embedded app frame to load inline. You can still open the same app route through the host or use the URL below.\"\n : \"This chat host did not allow the embedded app frame to load inline.\";\n stage.innerHTML =\n '<div class=\"fallback\">' +\n '<div class=\"fallback-title\">Open this app in its own tab</div>' +\n '<div class=\"fallback-copy\">' + esc(fallbackCopy) + '</div>' +\n '<div class=\"fallback-actions\">' +\n '<button type=\"button\" data-fallback-open>Open app</button>' +\n '<button type=\"button\" data-fallback-retry>Try inline again</button>' +\n '</div>' +\n (openUrl ? '<a class=\"fallback-url\" href=\"' + esc(openUrl) + '\" target=\"_blank\" rel=\"noreferrer\">' + esc(openUrl) + '</a>' : '') +\n '</div>';\n const fallbackOpen = stage.querySelector(\"[data-fallback-open]\");\n const fallbackRetry = stage.querySelector(\"[data-fallback-retry]\");\n if (fallbackOpen) {\n fallbackOpen.disabled = !openUrl;\n fallbackOpen.onclick = () => {\n if (openUrl) void openFallbackExternal();\n };\n }\n if (fallbackRetry) {\n fallbackRetry.disabled = !lastFrameSrc;\n fallbackRetry.onclick = () => {\n if (lastFrameSrc) renderFrame(lastFrameSrc);\n };\n }\n }\n\n function renderAppLaunchError(message) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n const fallbackCopy = openUrl\n ? \"The inline MCP app could not load in this chat host. You can open the same app route in a new tab or retry the inline load.\"\n : \"The inline MCP app could not load in this chat host.\";\n const copyHtml = message\n ? '<div>' + esc(message) + '</div><div>' + esc(fallbackCopy) + '</div>'\n : esc(fallbackCopy);\n stage.innerHTML =\n '<div class=\"fallback\">' +\n '<div class=\"fallback-title\">App did not load</div>' +\n '<div class=\"fallback-copy\">' + copyHtml + '</div>' +\n '<div class=\"fallback-actions\">' +\n '<button type=\"button\" data-fallback-open>Open in new tab</button>' +\n '<button type=\"button\" data-fallback-retry>Retry</button>' +\n '</div>' +\n (openUrl ? '<a class=\"fallback-url\" href=\"' + esc(openUrl) + '\" target=\"_blank\" rel=\"noreferrer\">' + esc(openUrl) + '</a>' : '') +\n '</div>';\n const fallbackOpen = stage.querySelector(\"[data-fallback-open]\");\n const fallbackRetry = stage.querySelector(\"[data-fallback-retry]\");\n if (fallbackOpen) {\n fallbackOpen.disabled = !openUrl;\n fallbackOpen.onclick = () => {\n if (openUrl) void openHostLink({ url: openUrl });\n };\n }\n if (fallbackRetry) {\n fallbackRetry.onclick = () => {\n startedFor = \"\";\n void launchEmbed();\n };\n }\n }\n\n async function openFallbackExternal() {\n if (!openUrl) return;\n let url = withChatBridgeParam(openUrl);\n try {\n if (url) {\n const result = await callEmbedSessionTool(embedSessionArgsFor(url));\n const data = parseToolResult(result);\n if (typeof data.startUrl === \"string\" && data.startUrl) {\n url = withChatBridgeParam(data.startUrl);\n }\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP fallback could not mint a fresh app session\", err);\n }\n await openHostLink({ url });\n }\n\n function renderFrame(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n const frame = document.createElement(\"iframe\");\n frame.title = body.dataset.iframeTitle || \"Agent Native app\";\n frame.src = src;\n frame.allow = \"clipboard-read; clipboard-write\";\n appFrame = frame;\n appFrameReady = false;\n lastFrameSrc = src;\n frame.addEventListener(\"load\", () => {\n if (appFrame !== frame) return;\n clearFrameLoadTimer();\n sendFrameReadyMessages(frame);\n startFrameReadyTimer(frame);\n });\n stage.replaceChildren(frame);\n notifyHostHeight();\n appFrameLoadTimer = setTimeout(() => {\n if (!appFrameReady && appFrame === frame) renderFrameFallback();\n }, frameLoadTimeoutMs);\n }\n\n function refreshExpiredEmbedSession() {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrameReady = false;\n if (!openUrl) {\n renderFrameFallback();\n return;\n }\n openStartUrl = \"\";\n startedFor = \"\";\n lastFrameSrc = \"\";\n setMessage(\"Refreshing app session\");\n void launchEmbed();\n }\n\n function shouldSelfNavigateToApp() {\n const render = renderModeSource();\n const mode = render.mode;\n if (isClaudeMcpContentHost()) return true;\n if (mode === \"iframe\" || mode === \"nested\") return false;\n if (render.nested || render.frame === \"iframe\") return false;\n return true;\n }\n\n // We have connected to a standards-track MCP Apps host (Codex, Cursor,\n // Claude over the SDK, our own renderer, …) through the postMessage\n // \\`ui/*\\` bridge rather than ChatGPT's \\`window.openai\\` global. These hosts\n // render the resource in a strict sandboxed iframe (typically\n // \\`sandbox=\"allow-scripts\"\\`, opaque origin). Self-navigating that iframe to\n // the real app origin tears down the host bridge and loses the opaque-origin\n // auth context, which shows up as a permanent / flashing loading state.\n // Transplanting the app document into the shell keeps the bridge alive and\n // works under the opaque origin via embed-token auth, exactly like Claude.\n function isNativeMcpAppsBridgeHost() {\n return !!app && !openAiBridge;\n }\n\n function shouldTransplantAppDocument() {\n const render = renderModeSource();\n const mode = render.mode;\n if (mode === \"iframe\" || mode === \"nested\" || render.frame === \"iframe\" || render.nested) {\n return false;\n }\n return (\n isClaudeMcpContentHost() ||\n isChatGptSandboxHost() ||\n isNativeMcpAppsBridgeHost() ||\n mode === \"transplant\" ||\n render.frame === \"transplant\"\n );\n }\n\n function isClaudeMcpContentHost() {\n try {\n return /(^|\\\\.)claudemcpcontent\\\\.com$/i.test(window.location.hostname || \"\");\n } catch {\n return false;\n }\n }\n\n function isChatGptSandboxHost() {\n try {\n const host = window.location.hostname || \"\";\n const appParam = new URL(window.location.href).searchParams.get(\"app\");\n return /^(?:[^.]+\\\\.)?web-sandbox\\\\.oaiusercontent\\\\.com$/i.test(host) || appParam === \"chatgpt\";\n } catch {\n return false;\n }\n }\n\n function shouldRenderControlledAppFrame() {\n return !!openAiBridge || isChatGptSandboxHost();\n }\n\n function navigateToAppFrame(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n lastFrameSrc = src;\n setMessage(\"Opening app\");\n try {\n window.location.replace(src);\n } catch (err) {\n console.warn(\"[agent-native] MCP app self-navigation failed\", err);\n renderFrameFallback();\n }\n }\n\n async function updateHostModelContext(data) {\n const params = {};\n if (Array.isArray(data && data.content)) params.content = data.content;\n if (data && data.structuredContent && typeof data.structuredContent === \"object\") {\n params.structuredContent = data.structuredContent;\n }\n if (openAiBridge && typeof openAiBridge.setWidgetState === \"function\") {\n openAiBridge.setWidgetState({\n ...objectValue(openAiBridge.widgetState),\n agentNativeModelContext: params\n });\n return { ok: true };\n }\n if (!app || typeof app.updateModelContext !== \"function\") return { ok: false };\n await app.updateModelContext(params);\n return { ok: true };\n }\n\n async function openHostLink(data) {\n const url = typeof (data && data.url) === \"string\" ? data.url : \"\";\n if (!url) return { isError: true };\n if (openAiBridge && typeof openAiBridge.openExternal === \"function\") {\n return await openAiBridge.openExternal({ href: url, redirectUrl: false });\n }\n if (app && typeof app.openLink === \"function\") {\n return await app.openLink({ url });\n }\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n return { ok: true };\n }\n\n function notifyHostHeight() {\n const height = applyIntrinsicHeight(visibleIntrinsicHeight());\n if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== \"function\") {\n if (app && typeof app.sendSizeChanged === \"function\") {\n try {\n app.sendSizeChanged({ height });\n } catch (err) {\n console.warn(\"[agent-native] MCP host rejected size update\", err);\n }\n }\n return;\n }\n try {\n openAiBridge.notifyIntrinsicHeight({ height });\n } catch (err) {\n console.warn(\"[agent-native] ChatGPT rejected intrinsic height update\", err);\n }\n }\n\n function respondToAppFrame(requestId, work) {\n if (!requestId) return;\n Promise.resolve(work)\n .then((result) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: { requestId, ok: true, result }\n });\n })\n .catch((err) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: {\n requestId,\n ok: false,\n error: err && err.message ? err.message : String(err)\n }\n });\n });\n }\n\n async function sendHostChat(chat) {\n const requestId = typeof (chat && chat.requestId) === \"string\" ? chat.requestId : \"\";\n if (!chat || chat.submit === false) return;\n const message = typeof chat.message === \"string\" ? chat.message : \"\";\n if (!message.trim()) return;\n const context = typeof chat.context === \"string\" ? chat.context.trim() : \"\";\n const content = Array.isArray(chat.content) && chat.content.length\n ? chat.content\n : [{ type: \"text\", text: message }];\n const structuredContent =\n chat && chat.structuredContent !== undefined\n ? chat.structuredContent\n : undefined;\n try {\n const contextContent = context\n ? [{ type: \"text\", text: context }, ...content.filter((part) => part && part.type !== \"text\")]\n : content.filter((part) => part && part.type !== \"text\");\n const modelContext = {\n content: contextContent,\n ...(structuredContent !== undefined ? { structuredContent } : {})\n };\n if (openAiBridge && typeof openAiBridge.setWidgetState === \"function\") {\n openAiBridge.setWidgetState({\n ...objectValue(openAiBridge.widgetState),\n agentNativeChatContext: context || null,\n agentNativeModelContext: modelContext\n });\n } else if (app && typeof app.updateModelContext === \"function\") {\n await app.updateModelContext(modelContext);\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP host rejected model context update\", err);\n }\n try {\n if (openAiBridge && typeof openAiBridge.sendFollowUpMessage === \"function\") {\n await openAiBridge.sendFollowUpMessage({\n prompt: message,\n scrollToBottom: true\n });\n respondToWrapperRequest(requestId, { ok: true });\n return;\n }\n let result = null;\n if (app && typeof app.sendMessage === \"function\") {\n result = await app.sendMessage({\n role: \"user\",\n content\n });\n } else {\n result = await wrapperRpcRequest(\"ui/message\", {\n role: \"user\",\n content\n });\n }\n if (result && result.isError) {\n console.warn(\"[agent-native] MCP host rejected chat message\", result);\n respondToWrapperRequest(requestId, { ok: false, result });\n return;\n }\n if (result && result.ok === false) {\n console.warn(\"[agent-native] MCP host chat bridge failed\", result);\n respondToWrapperRequest(requestId, { ok: false, result });\n return;\n }\n respondToWrapperRequest(requestId, { ok: true, result });\n } catch (err) {\n console.warn(\"[agent-native] MCP host chat bridge failed\", err);\n respondToWrapperRequest(requestId, { ok: false, error: err && err.message ? err.message : String(err) });\n }\n }\n\n window.addEventListener(\"message\", (event) => {\n if (event.source !== window.parent) return;\n const message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n settleWrapperRpcResponse(message);\n });\n\n window.addEventListener(\"message\", (event) => {\n if (!appFrame || event.source !== appFrame.contentWindow) return;\n if (!event.data) return;\n const data = event.data.data || {};\n if (event.data.type === \"agentNative.embeddedAppReady\") {\n appFrameReady = true;\n clearFrameLoadTimer();\n clearFrameReadyTimer();\n return;\n }\n if (event.data.type === \"agentNative.embedSessionExpired\") {\n refreshExpiredEmbedSession();\n return;\n }\n if (event.data.type === \"agentNative.submitChat\") {\n void sendHostChat(data);\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.updateModelContext\") {\n respondToAppFrame(data.requestId, updateHostModelContext(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.openLink\") {\n respondToAppFrame(data.requestId, openHostLink(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.requestDisplayMode\") {\n respondToAppFrame(data.requestId, requestHostDisplayMode(data.mode));\n }\n });\n\n function notifyHostHeightSoon() {\n requestAnimationFrame(() => notifyHostHeight());\n }\n\n function notifyHostHeightRepeatedly() {\n notifyHostHeight();\n [0, 250, 1000, 2500].forEach((delay) => {\n setTimeout(() => notifyHostHeight(), delay);\n });\n }\n\n window.addEventListener(\"resize\", notifyHostHeightSoon, { passive: true });\n if (window.visualViewport) {\n window.visualViewport.addEventListener(\"resize\", notifyHostHeightSoon, { passive: true });\n }\n\n async function launchEmbed() {\n const launchUrl = openStartUrl || openUrl;\n if (!launchUrl) {\n renderAppLaunchError(\"Open link was not available.\");\n return;\n }\n if (!wantsEmbed()) {\n setMessage(\"Ready to open.\");\n return;\n }\n if (startedFor === launchUrl) return;\n startedFor = launchUrl;\n setMessage(\"Loading app\");\n try {\n const selfNavigate = shouldSelfNavigateToApp();\n const embedUrl = withChatBridgeParam(launchUrl);\n if (selfNavigate && isEmbedStartUrl(embedUrl)) {\n if (shouldTransplantAppDocument()) {\n await transplantAppDocument(embedUrl);\n } else if (shouldRenderControlledAppFrame()) {\n renderFrame(embedUrl);\n } else {\n navigateToAppFrame(embedUrl);\n }\n return;\n }\n if (!selfNavigate && isEmbedStartUrl(embedUrl)) {\n renderFrame(embedUrl);\n return;\n }\n const result = await callEmbedSessionTool(embedSessionArgsFor(embedUrl));\n const data = parseToolResult(result);\n if (typeof data.startUrl !== \"string\" || !data.startUrl) {\n startedFor = \"\";\n renderAppLaunchError(data.error || \"This app can be opened, but not embedded from this MCP server.\");\n return;\n }\n const startUrl = withChatBridgeParam(data.startUrl);\n if (selfNavigate) {\n if (shouldTransplantAppDocument()) {\n await transplantAppDocument(startUrl);\n } else if (shouldRenderControlledAppFrame()) {\n renderFrame(startUrl);\n } else {\n navigateToAppFrame(startUrl);\n }\n } else {\n renderFrame(startUrl);\n }\n } catch (err) {\n startedFor = \"\";\n renderAppLaunchError(err && err.message ? err.message : \"Could not launch embedded app.\");\n }\n }\n\n async function callEmbedSessionTool(args) {\n if (openAiBridge && typeof openAiBridge.callTool === \"function\") {\n return await openAiBridge.callTool(startTool, args);\n }\n if (!app || typeof app.callServerTool !== \"function\") {\n throw new Error(\"Host tool calls are not available.\");\n }\n return await app.callServerTool({ name: startTool, arguments: args });\n }\n\n function updateHostOpenInAppUrl() {\n if (!openAiBridge || !openUrl || typeof openAiBridge.setOpenInAppUrl !== \"function\") {\n return;\n }\n try {\n openAiBridge.setOpenInAppUrl({ href: openUrl });\n } catch (err) {\n console.warn(\"[agent-native] ChatGPT rejected open-in-app URL\", err);\n }\n }\n\n function updateOpenButton() {\n const buttonUrl = openUrl;\n openButton.disabled = !buttonUrl;\n openButton.onclick = () => {\n if (buttonUrl) void openHostLink({ url: buttonUrl });\n };\n updateHostOpenInAppUrl();\n }\n\n function updateTitle(data) {\n const record = objectValue(data);\n const label = record.label || record.app || record.view || body.dataset.appTitle || \"App\";\n titleEl.textContent = String(label);\n }\n\n function readOpenAiBridge() {\n return window.openai && typeof window.openai === \"object\"\n ? window.openai\n : null;\n }\n\n function isChatGptHostHint() {\n try {\n if (new URLSearchParams(window.location.search).get(\"app\") === \"chatgpt\") return true;\n } catch (_err) {}\n try {\n if (/^(?:[^.]+\\\\.)?web-sandbox\\\\.oaiusercontent\\\\.com$/i.test(window.location.hostname || \"\")) return true;\n } catch (_err) {}\n try {\n return /chatgpt/i.test(String(navigator.userAgent || \"\"));\n } catch (_err) {\n return false;\n }\n }\n\n function openAiToolResultParams(bridge) {\n const params = {};\n if (bridge && bridge.toolOutput !== undefined) {\n if (bridge.toolOutput && typeof bridge.toolOutput === \"object\") {\n params.structuredContent = bridge.toolOutput;\n } else {\n params.content = [{ type: \"text\", text: String(bridge.toolOutput) }];\n }\n }\n if (bridge && bridge.toolResponseMetadata && typeof bridge.toolResponseMetadata === \"object\") {\n params._meta = bridge.toolResponseMetadata;\n }\n return params;\n }\n\n function syncOpenAiBridge(bridge) {\n if (!bridge) return false;\n openAiBridge = bridge;\n toolInput = objectValue(bridge.toolInput);\n const params = openAiToolResultParams(bridge);\n const data = parseToolResult(params);\n toolResultData = objectValue(data);\n openUrl = openLinkFrom(params, data);\n openStartUrl = embedStartUrlFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n if (openUrl || openStartUrl) {\n void launchEmbed();\n } else if (!appFrame) {\n setMessage(\"Waiting for app result\");\n }\n return true;\n }\n\n function waitForOpenAiBridge() {\n const existing = readOpenAiBridge();\n if (existing) return Promise.resolve(existing);\n return new Promise((resolve) => {\n let settled = false;\n let pollTimer = 0;\n const finish = (bridge) => {\n if (settled) return;\n settled = true;\n window.removeEventListener(\"openai:set_globals\", onGlobals);\n clearTimeout(timer);\n clearTimeout(pollTimer);\n resolve(bridge || readOpenAiBridge());\n };\n const poll = () => {\n const bridge = readOpenAiBridge();\n if (bridge) {\n finish(bridge);\n return;\n }\n pollTimer = window.setTimeout(poll, openAiBridgePollMs);\n };\n const onGlobals = () => finish(readOpenAiBridge());\n const timer = window.setTimeout(\n () => finish(null),\n isChatGptHostHint() ? chatGptOpenAiBridgeWaitMs : defaultOpenAiBridgeWaitMs\n );\n window.addEventListener(\"openai:set_globals\", onGlobals, { passive: true });\n pollTimer = window.setTimeout(poll, openAiBridgePollMs);\n });\n }\n\n window.addEventListener(\"openai:set_globals\", () => {\n const bridge = readOpenAiBridge();\n if (bridge && (!appFrame || openAiBridge)) syncOpenAiBridge(bridge);\n }, { passive: true });\n\n function createNativeMcpAppsBridge() {\n let rpcId = 0;\n let connected = false;\n let hostContext = {};\n const pendingRequests = new Map();\n\n function rpcNotify(method, params) {\n window.parent.postMessage({ jsonrpc: \"2.0\", method, params: params || {} }, \"*\");\n }\n\n function rpcRequest(method, params, timeoutMs) {\n return new Promise((resolve, reject) => {\n const id = ++rpcId;\n const timer = window.setTimeout(() => {\n pendingRequests.delete(id);\n reject(new Error(\"MCP Apps bridge request timed out: \" + method));\n }, timeoutMs || nativeBridgeRequestTimeoutMs);\n pendingRequests.set(id, { resolve, reject, timer });\n window.parent.postMessage(\n { jsonrpc: \"2.0\", id, method, params: params || {} },\n \"*\"\n );\n });\n }\n\n function settleRpcResponse(message) {\n const pending = pendingRequests.get(message.id);\n if (!pending) return true;\n pendingRequests.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n const error = new Error(message.error.message || \"MCP Apps bridge request failed.\");\n error.data = message.error.data;\n pending.reject(error);\n return true;\n }\n pending.resolve(message.result);\n return true;\n }\n\n function notificationParams(message) {\n return message && typeof message.params === \"object\" && message.params\n ? message.params\n : {};\n }\n\n function toolInputNotificationParams(params) {\n if (params && typeof params.arguments === \"object\" && params.arguments) {\n return params;\n }\n if (params && typeof params.input === \"object\" && params.input) {\n return { arguments: params.input };\n }\n return { arguments: objectValue(params) };\n }\n\n const nativeApp = {\n ontoolinput: null,\n ontoolresult: null,\n onhostcontextchanged: null,\n getHostContext() {\n return hostContext.context || hostContext;\n },\n getHostCapabilities() {\n return hostContext.capabilities || { tools: true, messaging: true };\n },\n getHostVersion() {\n return hostContext.protocolVersion || \"mcp-apps-postmessage\";\n },\n async connect() {\n if (connected) return hostContext;\n connected = true;\n window.addEventListener(\"message\", onMessage, { passive: true });\n const result = await rpcRequest(\n \"ui/initialize\",\n {\n appInfo: { name: \"Agent Native Embed\", version: \"1.0.0\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n },\n nativeBridgeInitializeTimeoutMs\n );\n hostContext = objectValue(result);\n rpcNotify(\"ui/notifications/initialized\", {});\n if (typeof nativeApp.onhostcontextchanged === \"function\") {\n nativeApp.onhostcontextchanged(hostContext);\n }\n return hostContext;\n },\n async callServerTool(request) {\n const record = objectValue(request);\n return await rpcRequest(\"tools/call\", {\n name: record.name,\n arguments: objectValue(record.arguments)\n });\n },\n async updateModelContext(params) {\n return await rpcRequest(\"ui/update-model-context\", objectValue(params));\n },\n async openLink(params) {\n const url = typeof (params && params.url) === \"string\" ? params.url : \"\";\n if (!url) return { isError: true };\n try {\n return await rpcRequest(\"ui/open-link\", { url }, wrapperRequestTimeoutMs);\n } catch (err) {\n console.warn(\"[agent-native] MCP host open-link request failed\", err);\n }\n const opened = window.open(url, \"_blank\", \"noopener,noreferrer\");\n return opened ? { ok: true } : { isError: true };\n },\n async requestDisplayMode(params) {\n return await rpcRequest(\"ui/request-display-mode\", objectValue(params));\n },\n sendSizeChanged(params) {\n rpcNotify(\"ui/notifications/size-changed\", objectValue(params));\n },\n async sendMessage(params) {\n return await rpcRequest(\"ui/message\", objectValue(params));\n }\n };\n\n function onMessage(event) {\n if (event.source !== window.parent) return;\n const message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (typeof message.id === \"number\" || typeof message.id === \"string\") {\n settleRpcResponse(message);\n return;\n }\n if (typeof message.method !== \"string\") return;\n const params = notificationParams(message);\n if (message.method === \"ui/notifications/tool-input\") {\n if (typeof nativeApp.ontoolinput === \"function\") {\n nativeApp.ontoolinput(toolInputNotificationParams(params));\n }\n return;\n }\n if (message.method === \"ui/notifications/tool-result\") {\n if (typeof nativeApp.ontoolresult === \"function\") {\n nativeApp.ontoolresult(params);\n }\n return;\n }\n if (\n message.method === \"ui/notifications/host-context-changed\" ||\n message.method === \"ui/notifications/host-context\" ||\n message.method === \"ui/notifications/context\"\n ) {\n hostContext = objectValue(params);\n if (typeof nativeApp.onhostcontextchanged === \"function\") {\n nativeApp.onhostcontextchanged(hostContext);\n }\n }\n }\n\n return nativeApp;\n }\n\n async function startNativeMcpAppsBridge() {\n app = createNativeMcpAppsBridge();\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n toolResultData = objectValue(data);\n openUrl = openLinkFrom(params, data);\n openStartUrl = embedStartUrlFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n app.onhostcontextchanged = () => {\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n };\n await app.connect();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n }\n\n async function startMcpAppsBridge() {\n const { App } = await import(\"${MCP_APP_IMPORT}\");\n app = new App(\n { name: \"Agent Native Embed\", version: \"1.0.0\" },\n {},\n { autoResize: false }\n );\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n toolResultData = objectValue(data);\n openUrl = openLinkFrom(params, data);\n openStartUrl = embedStartUrlFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n app.onhostcontextchanged = () => {\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n };\n await app.connect();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n }\n\n try {\n const initialOpenAiBridge = await waitForOpenAiBridge();\n if (!syncOpenAiBridge(initialOpenAiBridge)) {\n try {\n await startNativeMcpAppsBridge();\n } catch (nativeErr) {\n console.warn(\"[agent-native] native MCP Apps bridge failed\", nativeErr);\n await startMcpAppsBridge();\n }\n }\n } catch (err) {\n console.error(\"[agent-native] MCP app shell failed\", err);\n setMessage(err && err.message ? err.message : \"Could not initialize app.\");\n }\n })();\n </script>\n</body>\n</html>`,\n csp: {\n connectDomains: [\n \"https://esm.sh\",\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.connectDomains ?? []),\n ...(options.frameDomains ?? []),\n ],\n resourceDomains: [\n \"https://esm.sh\",\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.resourceDomains ?? []),\n ...(options.frameDomains ?? []),\n ],\n baseUriDomains: [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.baseUriDomains ?? []),\n ],\n frameDomains,\n },\n prefersBorder: false,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"register-framework-secrets.d.ts","sourceRoot":"","sources":["../../src/secrets/register-framework-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,wBAAgB,wBAAwB,IAAI,IAAI,CA2D/C"}
1
+ {"version":3,"file":"register-framework-secrets.d.ts","sourceRoot":"","sources":["../../src/secrets/register-framework-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,wBAAgB,wBAAwB,IAAI,IAAI,CAkE/C"}
@@ -37,6 +37,12 @@ export function registerFrameworkSecrets() {
37
37
  description: "Enables the web-search agent tool via Exa. Used as fallback when Brave and Tavily are not set and before Builder-managed search.",
38
38
  docsUrl: "https://exa.ai/",
39
39
  },
40
+ {
41
+ key: "FIRECRAWL_API_KEY",
42
+ label: "Firecrawl API Key",
43
+ description: "Enables the web-search agent tool via Firecrawl. Used as fallback when Brave, Tavily, and Exa are not set and before Builder-managed search.",
44
+ docsUrl: "https://firecrawl.dev/",
45
+ },
40
46
  ];
41
47
  for (const entry of webSearchKeys) {
42
48
  if (!getRequiredSecret(entry.key)) {
@@ -1 +1 @@
1
- {"version":3,"file":"register-framework-secrets.js","sourceRoot":"","sources":["../../src/secrets/register-framework-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE1E,MAAM,UAAU,wBAAwB;IACtC,kEAAkE;IAClE,0EAA0E;IAC1E,MAAM,aAAa,GAKd;QACH;YACE,GAAG,EAAE,sBAAsB;YAC3B,KAAK,EAAE,sBAAsB;YAC7B,WAAW,EACT,mHAAmH;YACrH,OAAO,EAAE,+BAA+B;SACzC;QACD;YACE,GAAG,EAAE,gBAAgB;YACrB,KAAK,EAAE,gBAAgB;YACvB,WAAW,EACT,wIAAwI;YAC1I,OAAO,EAAE,qBAAqB;SAC/B;QACD;YACE,GAAG,EAAE,aAAa;YAClB,KAAK,EAAE,aAAa;YACpB,WAAW,EACT,kIAAkI;YACpI,OAAO,EAAE,iBAAiB;SAC3B;KACF,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAClC,sBAAsB,CAAC;gBACrB,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,KAAK;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;QACvC,sBAAsB,CAAC;YACrB,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,cAAc;YACrB,WAAW,EACT,0FAA0F;YAC5F,OAAO,EACL,kHAAkH;YACpH,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["/**\n * Framework-level secret registrations.\n *\n * Side-effect module — imported by the core-routes plugin at boot so the\n * sidebar settings UI and the `/_agent-native/secrets` list route surface the\n * relevant keys in every template.\n *\n * Each call uses a `getRequiredSecret` guard so a template that has already\n * registered the same key (often with stricter settings like `required: true`)\n * wins — the framework registration is a fallback, not an override.\n *\n * NOTE: The framework previously registered OPENAI_API_KEY here for Whisper\n * voice transcription. Voice transcription now routes through the Builder.io\n * gateway (or Groq as a BYOK fallback), so the framework no longer registers\n * the OpenAI key. Templates that need it (e.g. Clips) register it themselves.\n */\n\nimport { getRequiredSecret, registerRequiredSecret } from \"./register.js\";\n\nexport function registerFrameworkSecrets(): void {\n // Web-search tool backends — optional; the tool selects the first\n // configured manual key at call time, then falls back to Builder Connect.\n const webSearchKeys: Array<{\n key: string;\n label: string;\n description: string;\n docsUrl: string;\n }> = [\n {\n key: \"BRAVE_SEARCH_API_KEY\",\n label: \"Brave Search API Key\",\n description:\n \"Enables the web-search agent tool via Brave Search. Optional when Builder.io is connected for managed web search.\",\n docsUrl: \"https://brave.com/search/api/\",\n },\n {\n key: \"TAVILY_API_KEY\",\n label: \"Tavily API Key\",\n description:\n \"Enables the web-search agent tool via Tavily. Used as fallback when BRAVE_SEARCH_API_KEY is not set and before Builder-managed search.\",\n docsUrl: \"https://tavily.com/\",\n },\n {\n key: \"EXA_API_KEY\",\n label: \"Exa API Key\",\n description:\n \"Enables the web-search agent tool via Exa. Used as fallback when Brave and Tavily are not set and before Builder-managed search.\",\n docsUrl: \"https://exa.ai/\",\n },\n ];\n\n for (const entry of webSearchKeys) {\n if (!getRequiredSecret(entry.key)) {\n registerRequiredSecret({\n key: entry.key,\n label: entry.label,\n description: entry.description,\n docsUrl: entry.docsUrl,\n scope: \"workspace\",\n kind: \"api-key\",\n required: false,\n });\n }\n }\n\n if (!getRequiredSecret(\"GITHUB_TOKEN\")) {\n registerRequiredSecret({\n key: \"GITHUB_TOKEN\",\n label: \"GitHub token\",\n description:\n \"Enables connector-scoped repository file reads and writes for headless/cloud agent runs.\",\n docsUrl:\n \"https://docs.github.com/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens\",\n scope: \"workspace\",\n kind: \"api-key\",\n required: false,\n });\n }\n}\n"]}
1
+ {"version":3,"file":"register-framework-secrets.js","sourceRoot":"","sources":["../../src/secrets/register-framework-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE1E,MAAM,UAAU,wBAAwB;IACtC,kEAAkE;IAClE,0EAA0E;IAC1E,MAAM,aAAa,GAKd;QACH;YACE,GAAG,EAAE,sBAAsB;YAC3B,KAAK,EAAE,sBAAsB;YAC7B,WAAW,EACT,mHAAmH;YACrH,OAAO,EAAE,+BAA+B;SACzC;QACD;YACE,GAAG,EAAE,gBAAgB;YACrB,KAAK,EAAE,gBAAgB;YACvB,WAAW,EACT,wIAAwI;YAC1I,OAAO,EAAE,qBAAqB;SAC/B;QACD;YACE,GAAG,EAAE,aAAa;YAClB,KAAK,EAAE,aAAa;YACpB,WAAW,EACT,kIAAkI;YACpI,OAAO,EAAE,iBAAiB;SAC3B;QACD;YACE,GAAG,EAAE,mBAAmB;YACxB,KAAK,EAAE,mBAAmB;YAC1B,WAAW,EACT,8IAA8I;YAChJ,OAAO,EAAE,wBAAwB;SAClC;KACF,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAClC,sBAAsB,CAAC;gBACrB,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,KAAK;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;QACvC,sBAAsB,CAAC;YACrB,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,cAAc;YACrB,WAAW,EACT,0FAA0F;YAC5F,OAAO,EACL,kHAAkH;YACpH,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["/**\n * Framework-level secret registrations.\n *\n * Side-effect module — imported by the core-routes plugin at boot so the\n * sidebar settings UI and the `/_agent-native/secrets` list route surface the\n * relevant keys in every template.\n *\n * Each call uses a `getRequiredSecret` guard so a template that has already\n * registered the same key (often with stricter settings like `required: true`)\n * wins — the framework registration is a fallback, not an override.\n *\n * NOTE: The framework previously registered OPENAI_API_KEY here for Whisper\n * voice transcription. Voice transcription now routes through the Builder.io\n * gateway (or Groq as a BYOK fallback), so the framework no longer registers\n * the OpenAI key. Templates that need it (e.g. Clips) register it themselves.\n */\n\nimport { getRequiredSecret, registerRequiredSecret } from \"./register.js\";\n\nexport function registerFrameworkSecrets(): void {\n // Web-search tool backends — optional; the tool selects the first\n // configured manual key at call time, then falls back to Builder Connect.\n const webSearchKeys: Array<{\n key: string;\n label: string;\n description: string;\n docsUrl: string;\n }> = [\n {\n key: \"BRAVE_SEARCH_API_KEY\",\n label: \"Brave Search API Key\",\n description:\n \"Enables the web-search agent tool via Brave Search. Optional when Builder.io is connected for managed web search.\",\n docsUrl: \"https://brave.com/search/api/\",\n },\n {\n key: \"TAVILY_API_KEY\",\n label: \"Tavily API Key\",\n description:\n \"Enables the web-search agent tool via Tavily. Used as fallback when BRAVE_SEARCH_API_KEY is not set and before Builder-managed search.\",\n docsUrl: \"https://tavily.com/\",\n },\n {\n key: \"EXA_API_KEY\",\n label: \"Exa API Key\",\n description:\n \"Enables the web-search agent tool via Exa. Used as fallback when Brave and Tavily are not set and before Builder-managed search.\",\n docsUrl: \"https://exa.ai/\",\n },\n {\n key: \"FIRECRAWL_API_KEY\",\n label: \"Firecrawl API Key\",\n description:\n \"Enables the web-search agent tool via Firecrawl. Used as fallback when Brave, Tavily, and Exa are not set and before Builder-managed search.\",\n docsUrl: \"https://firecrawl.dev/\",\n },\n ];\n\n for (const entry of webSearchKeys) {\n if (!getRequiredSecret(entry.key)) {\n registerRequiredSecret({\n key: entry.key,\n label: entry.label,\n description: entry.description,\n docsUrl: entry.docsUrl,\n scope: \"workspace\",\n kind: \"api-key\",\n required: false,\n });\n }\n }\n\n if (!getRequiredSecret(\"GITHUB_TOKEN\")) {\n registerRequiredSecret({\n key: \"GITHUB_TOKEN\",\n label: \"GitHub token\",\n description:\n \"Enables connector-scoped repository file reads and writes for headless/cloud agent runs.\",\n docsUrl:\n \"https://docs.github.com/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens\",\n scope: \"workspace\",\n kind: \"api-key\",\n required: false,\n });\n }\n}\n"]}