@agent-native/core 0.22.19 → 0.22.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +85 -3
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +3 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +86 -9
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +73 -5
- package/dist/deploy/build.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +40 -3
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +6 -3
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/embed-app.d.ts +2 -2
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +420 -29
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +37 -10
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/create-server.js +21 -7
- package/dist/server/create-server.js.map +1 -1
- package/dist/server/embed-route.d.ts.map +1 -1
- package/dist/server/embed-route.js +62 -21
- package/dist/server/embed-route.js.map +1 -1
- package/dist/server/security-headers.d.ts.map +1 -1
- package/dist/server/security-headers.js +9 -1
- package/dist/server/security-headers.js.map +1 -1
- package/dist/server/ssr-handler.d.ts +2 -0
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +66 -11
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +12 -0
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -0
- package/dist/shared/mcp-embed-headers.js +51 -0
- package/dist/shared/mcp-embed-headers.js.map +1 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +23 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.md +16 -5
- package/docs/content/external-agents.md +61 -26
- package/docs/content/mcp-protocol.md +32 -4
- package/package.json +1 -1
package/dist/mcp/embed-app.js
CHANGED
|
@@ -2,8 +2,8 @@ import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
|
|
|
2
2
|
const MCP_APP_IMPORT = "https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps";
|
|
3
3
|
export const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
|
|
4
4
|
const MCP_APP_WRAPPER_CHROME_HEIGHT = 44;
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
5
|
+
export const DEFAULT_MCP_APP_SHELL_HEIGHT = 560;
|
|
6
|
+
export const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = DEFAULT_MCP_APP_SHELL_HEIGHT - MCP_APP_WRAPPER_CHROME_HEIGHT;
|
|
7
7
|
function attr(value) {
|
|
8
8
|
return String(value ?? "")
|
|
9
9
|
.replace(/&/g, "&")
|
|
@@ -19,6 +19,10 @@ export function embedApp(options = {}) {
|
|
|
19
19
|
const embedByDefault = options.embedByDefault !== false;
|
|
20
20
|
const height = Math.max(320, Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT));
|
|
21
21
|
const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;
|
|
22
|
+
const frameDomains = [
|
|
23
|
+
MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
|
|
24
|
+
...(options.frameDomains ?? []),
|
|
25
|
+
];
|
|
22
26
|
return {
|
|
23
27
|
title,
|
|
24
28
|
...(options.description ? { description: options.description } : {}),
|
|
@@ -28,19 +32,19 @@ export function embedApp(options = {}) {
|
|
|
28
32
|
<meta charset="utf-8">
|
|
29
33
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
30
34
|
<style>
|
|
31
|
-
:root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; }
|
|
35
|
+
: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; }
|
|
32
36
|
* { box-sizing: border-box; }
|
|
33
37
|
body { margin: 0; }
|
|
34
|
-
.shell { display: grid; gap: 8px; min-height:
|
|
38
|
+
.shell { display: grid; gap: 8px; min-height: var(--agent-native-shell-height); padding: 0; }
|
|
35
39
|
.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); }
|
|
36
40
|
.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); }
|
|
37
41
|
.actions { display: flex; align-items: center; gap: 6px; }
|
|
38
42
|
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; }
|
|
39
43
|
button:disabled { opacity: .55; cursor: default; }
|
|
40
|
-
.stage { position: relative; min-height:
|
|
41
|
-
iframe { display: block; width: 100%; height:
|
|
42
|
-
.message { display: grid; place-items: center; min-height:
|
|
43
|
-
.fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height:
|
|
44
|
+
.stage { position: relative; min-height: var(--agent-native-viewport-height); }
|
|
45
|
+
iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }
|
|
46
|
+
.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; }
|
|
47
|
+
.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; }
|
|
44
48
|
.fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }
|
|
45
49
|
.fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }
|
|
46
50
|
.fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }
|
|
@@ -75,7 +79,11 @@ export function embedApp(options = {}) {
|
|
|
75
79
|
const startTool = body.dataset.startTool || "create_embed_session";
|
|
76
80
|
const embedByDefault = body.dataset.embedDefault !== "0";
|
|
77
81
|
const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};
|
|
78
|
-
const
|
|
82
|
+
const defaultIntrinsicHeight = ${height};
|
|
83
|
+
const chromeHeight = ${MCP_APP_WRAPPER_CHROME_HEIGHT};
|
|
84
|
+
const frameReadyMessageDelays = [0, 200, 500, 1500, 3000, 7000, 15000, 30000];
|
|
85
|
+
const frameReadyTimeoutMs = 45000;
|
|
86
|
+
const frameLoadTimeoutMs = 45000;
|
|
79
87
|
let app = null;
|
|
80
88
|
let openAiBridge = null;
|
|
81
89
|
let toolInput = {};
|
|
@@ -107,6 +115,40 @@ export function embedApp(options = {}) {
|
|
|
107
115
|
: {};
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
function finiteNumber(value) {
|
|
119
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
120
|
+
? value
|
|
121
|
+
: null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function contextMaxHeight(context) {
|
|
125
|
+
if (!context || typeof context !== "object") return null;
|
|
126
|
+
return finiteNumber(context.maxHeight) ||
|
|
127
|
+
finiteNumber(context.containerDimensions && context.containerDimensions.maxHeight);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function visibleIntrinsicHeight() {
|
|
131
|
+
const context = hostState().context || {};
|
|
132
|
+
const hostMaxHeight = contextMaxHeight(context);
|
|
133
|
+
if (hostMaxHeight) return Math.floor(hostMaxHeight);
|
|
134
|
+
const viewportHeight = finiteNumber(window.visualViewport && window.visualViewport.height) ||
|
|
135
|
+
finiteNumber(window.innerHeight);
|
|
136
|
+
return Math.floor(viewportHeight || defaultIntrinsicHeight);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function applyIntrinsicHeight(nextHeight) {
|
|
140
|
+
const boundedHeight = Math.min(
|
|
141
|
+
defaultIntrinsicHeight,
|
|
142
|
+
Math.floor(nextHeight || defaultIntrinsicHeight)
|
|
143
|
+
);
|
|
144
|
+
const height = Math.max(320, boundedHeight);
|
|
145
|
+
const viewportHeight = Math.max(0, height - chromeHeight);
|
|
146
|
+
document.documentElement.style.setProperty("--agent-native-shell-height", height + "px");
|
|
147
|
+
document.documentElement.style.setProperty("--agent-native-viewport-height", viewportHeight + "px");
|
|
148
|
+
if (appFrame) appFrame.style.height = viewportHeight + "px";
|
|
149
|
+
return height;
|
|
150
|
+
}
|
|
151
|
+
|
|
110
152
|
function parseToolResult(params) {
|
|
111
153
|
if (!params) return {};
|
|
112
154
|
if (params.result && typeof params.result === "object") {
|
|
@@ -128,7 +170,8 @@ export function embedApp(options = {}) {
|
|
|
128
170
|
const metaUrl = openLink && typeof openLink === "object" && typeof openLink.webUrl === "string"
|
|
129
171
|
? openLink.webUrl
|
|
130
172
|
: "";
|
|
131
|
-
|
|
173
|
+
const record = data && typeof data === "object" ? data : {};
|
|
174
|
+
return metaUrl || record.url || record.deepLink || record.openUrl || "";
|
|
132
175
|
}
|
|
133
176
|
|
|
134
177
|
function hostState() {
|
|
@@ -166,7 +209,7 @@ export function embedApp(options = {}) {
|
|
|
166
209
|
|
|
167
210
|
function sendFrameReadyMessages(frame) {
|
|
168
211
|
const originPayload = { type: "agentNative.frameOrigin", origin: window.location.origin };
|
|
169
|
-
|
|
212
|
+
frameReadyMessageDelays.forEach((delay) => {
|
|
170
213
|
setTimeout(() => {
|
|
171
214
|
try { frame.contentWindow && frame.contentWindow.postMessage(originPayload, "*"); } catch {}
|
|
172
215
|
sendHostContext();
|
|
@@ -188,6 +231,277 @@ export function embedApp(options = {}) {
|
|
|
188
231
|
}
|
|
189
232
|
}
|
|
190
233
|
|
|
234
|
+
function embedSessionArgsFor(value) {
|
|
235
|
+
const chrome = typeof toolInput.chrome === "string" ? toolInput.chrome : "full";
|
|
236
|
+
return typeof value === "string" && value.startsWith("/")
|
|
237
|
+
? { path: value, chrome }
|
|
238
|
+
: { url: value, chrome };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isEmbedStartUrl(value) {
|
|
242
|
+
if (typeof value !== "string" || !value) return false;
|
|
243
|
+
try {
|
|
244
|
+
const url = new URL(value, window.location.href);
|
|
245
|
+
return /\\/_agent-native\\/embed\\/start$/.test(url.pathname);
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function localPathFromUrl(url, includeToken) {
|
|
252
|
+
const next = new URL(url.href);
|
|
253
|
+
if (!includeToken) next.searchParams.delete("__an_embed_token");
|
|
254
|
+
return next.pathname + next.search + next.hash;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function rewriteRootRelativeHtmlUrls(html, appOrigin) {
|
|
258
|
+
return String(html).replace(
|
|
259
|
+
/\\b(src|href|poster|action)\\s*=\\s*(["'])\\/(?!\\/)/gi,
|
|
260
|
+
(_match, name, quote) => String(name) + "=" + quote + appOrigin + "/"
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function removeHtmlCspMeta(html) {
|
|
265
|
+
return String(html).replace(
|
|
266
|
+
/<meta\\s+[^>]*http-equiv\\s*=\\s*(["'])?content-security-policy\\1?[^>]*>/gi,
|
|
267
|
+
""
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function embedConfigForAppUrl(appUrl) {
|
|
272
|
+
const sanitizedTarget = localPathFromUrl(appUrl, false);
|
|
273
|
+
return {
|
|
274
|
+
origin: appUrl.origin,
|
|
275
|
+
href: appUrl.href,
|
|
276
|
+
baseHref: appUrl.origin + appUrl.pathname,
|
|
277
|
+
target: sanitizedTarget,
|
|
278
|
+
token: appUrl.searchParams.get("__an_embed_token") || "",
|
|
279
|
+
chatBridgeActive: appUrl.searchParams.get(chatBridgeParam) === "1",
|
|
280
|
+
chatBridgeParam,
|
|
281
|
+
embedTokenParam: "__an_embed_token",
|
|
282
|
+
embedTargetHeader: "x-agent-native-embed-target"
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function installExternalEmbedRuntime(config) {
|
|
287
|
+
window.__AGENT_NATIVE_EXTERNAL_EMBED = config;
|
|
288
|
+
try {
|
|
289
|
+
if (config.target) {
|
|
290
|
+
window.history.replaceState(window.history.state, "", config.target);
|
|
291
|
+
}
|
|
292
|
+
} catch (_err) {}
|
|
293
|
+
try {
|
|
294
|
+
if (config.token) {
|
|
295
|
+
sessionStorage.setItem("agent-native:embed-auth-token", config.token);
|
|
296
|
+
}
|
|
297
|
+
if (config.chatBridgeActive && config.token) {
|
|
298
|
+
sessionStorage.setItem("agent-native:mcp-chat-bridge", config.token);
|
|
299
|
+
}
|
|
300
|
+
} catch (_err) {}
|
|
301
|
+
if (window.__agentNativeExternalEmbedRuntimeInstalled) return;
|
|
302
|
+
window.__agentNativeExternalEmbedRuntimeInstalled = true;
|
|
303
|
+
function appOrigin() {
|
|
304
|
+
try {
|
|
305
|
+
return new URL(config.origin).origin;
|
|
306
|
+
} catch (_err) {
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function targetPath() {
|
|
311
|
+
return config.target || location.pathname + location.search;
|
|
312
|
+
}
|
|
313
|
+
function rewrittenUrl(value, appendToken) {
|
|
314
|
+
const origin = appOrigin();
|
|
315
|
+
if (!origin) return null;
|
|
316
|
+
let url;
|
|
317
|
+
try {
|
|
318
|
+
url = new URL(value, location.href);
|
|
319
|
+
} catch (_err) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
if (url.origin !== location.origin && url.origin !== origin) return null;
|
|
323
|
+
if (url.origin !== origin) {
|
|
324
|
+
const app = new URL(origin);
|
|
325
|
+
url.protocol = app.protocol;
|
|
326
|
+
url.host = app.host;
|
|
327
|
+
}
|
|
328
|
+
if (appendToken && config.token && url.pathname === "/_agent-native/events") {
|
|
329
|
+
url.searchParams.set(config.embedTokenParam, config.token);
|
|
330
|
+
}
|
|
331
|
+
return url.toString();
|
|
332
|
+
}
|
|
333
|
+
function authHeaders(input, init) {
|
|
334
|
+
const headers = new Headers(
|
|
335
|
+
init && init.headers ? init.headers : input instanceof Request ? input.headers : undefined
|
|
336
|
+
);
|
|
337
|
+
if (config.token && !headers.has("Authorization")) {
|
|
338
|
+
headers.set("Authorization", "Bearer " + config.token);
|
|
339
|
+
}
|
|
340
|
+
if (!headers.has(config.embedTargetHeader)) {
|
|
341
|
+
headers.set(config.embedTargetHeader, targetPath());
|
|
342
|
+
}
|
|
343
|
+
return headers;
|
|
344
|
+
}
|
|
345
|
+
if (typeof fetch === "function") {
|
|
346
|
+
const originalFetch = fetch.bind(window);
|
|
347
|
+
window.fetch = function(input, init) {
|
|
348
|
+
const raw = input instanceof Request ? input.url : String(input);
|
|
349
|
+
const url = rewrittenUrl(raw, false);
|
|
350
|
+
if (!url) return originalFetch(input, init);
|
|
351
|
+
const nextInit = Object.assign({}, init || {}, {
|
|
352
|
+
headers: authHeaders(input, init),
|
|
353
|
+
credentials: "omit"
|
|
354
|
+
});
|
|
355
|
+
if (input instanceof Request) {
|
|
356
|
+
return originalFetch(new Request(url, input), nextInit);
|
|
357
|
+
}
|
|
358
|
+
return originalFetch(url, nextInit);
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
362
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
363
|
+
const originalSend = XMLHttpRequest.prototype.send;
|
|
364
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
365
|
+
const rewritten = rewrittenUrl(url, false);
|
|
366
|
+
this.__agentNativeExternalEmbed = !!rewritten;
|
|
367
|
+
return originalOpen.call(
|
|
368
|
+
this,
|
|
369
|
+
method,
|
|
370
|
+
rewritten || url,
|
|
371
|
+
arguments.length > 2 ? arguments[2] : true,
|
|
372
|
+
arguments[3],
|
|
373
|
+
arguments[4]
|
|
374
|
+
);
|
|
375
|
+
};
|
|
376
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
377
|
+
if (this.__agentNativeExternalEmbed) {
|
|
378
|
+
try {
|
|
379
|
+
if (config.token) this.setRequestHeader("Authorization", "Bearer " + config.token);
|
|
380
|
+
this.setRequestHeader(config.embedTargetHeader, targetPath());
|
|
381
|
+
} catch (_err) {}
|
|
382
|
+
}
|
|
383
|
+
return originalSend.call(this, body);
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (typeof EventSource !== "undefined") {
|
|
387
|
+
const OriginalEventSource = EventSource;
|
|
388
|
+
window.EventSource = function(url, options) {
|
|
389
|
+
return new OriginalEventSource(rewrittenUrl(url, true) || url, options);
|
|
390
|
+
};
|
|
391
|
+
window.EventSource.prototype = OriginalEventSource.prototype;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function copyDocumentElementAttributes(source) {
|
|
396
|
+
const target = document.documentElement;
|
|
397
|
+
for (const attr of Array.from(target.attributes)) {
|
|
398
|
+
target.removeAttribute(attr.name);
|
|
399
|
+
}
|
|
400
|
+
for (const attr of Array.from(source.attributes)) {
|
|
401
|
+
target.setAttribute(attr.name, attr.value);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function importChildren(source, target) {
|
|
406
|
+
target.replaceChildren(
|
|
407
|
+
...Array.from(source.childNodes).map((node) => document.importNode(node, true))
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function isModuleScript(script) {
|
|
412
|
+
return (script.getAttribute("type") || "").trim().toLowerCase() === "module";
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function isRunnableClassicScript(script) {
|
|
416
|
+
const type = (script.getAttribute("type") || "").trim().toLowerCase();
|
|
417
|
+
return !type || type === "text/javascript" || type === "application/javascript";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function runClassicScript(script) {
|
|
421
|
+
const next = document.createElement("script");
|
|
422
|
+
for (const attr of Array.from(script.attributes)) {
|
|
423
|
+
if (attr.name === "type") continue;
|
|
424
|
+
next.setAttribute(attr.name, attr.value);
|
|
425
|
+
}
|
|
426
|
+
if (script.src) {
|
|
427
|
+
next.src = script.src;
|
|
428
|
+
} else {
|
|
429
|
+
next.textContent = script.textContent || "";
|
|
430
|
+
}
|
|
431
|
+
document.body.appendChild(next);
|
|
432
|
+
next.remove();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function rootRelativeSpecifiersToAbsolute(code, appOrigin) {
|
|
436
|
+
return String(code).replace(/(["'])\\/(?!\\/)/g, "$1" + appOrigin + "/");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function moduleCodeToClassicAsync(code, appOrigin) {
|
|
440
|
+
return rootRelativeSpecifiersToAbsolute(code, appOrigin)
|
|
441
|
+
.replace(
|
|
442
|
+
/\\bimport\\s+\\*\\s+as\\s+([A-Za-z_$][\\w$]*)\\s+from\\s+(["'][^"']+["'])\\s*;?/g,
|
|
443
|
+
"const $1 = await import($2);"
|
|
444
|
+
)
|
|
445
|
+
.replace(/\\bimport\\s+(["'][^"']+["'])\\s*;?/g, "await import($1);")
|
|
446
|
+
.replace(/\\bimport\\((["'][^"']+["'])\\)\\s*;?/g, "await import($1);");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function runModuleScriptAsClassic(script, appOrigin) {
|
|
450
|
+
const code = moduleCodeToClassicAsync(script.textContent || "", appOrigin);
|
|
451
|
+
const runner = document.createElement("script");
|
|
452
|
+
runner.textContent =
|
|
453
|
+
"(async()=>{" +
|
|
454
|
+
code +
|
|
455
|
+
"})().catch((err)=>{console.error('[agent-native] transplanted app module failed',err);document.body.setAttribute('data-agent-native-hydration-error',String(err&&err.message||err));});";
|
|
456
|
+
document.body.appendChild(runner);
|
|
457
|
+
runner.remove();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function mountTransplantedHtml(html, appUrl) {
|
|
461
|
+
const config = embedConfigForAppUrl(appUrl);
|
|
462
|
+
installExternalEmbedRuntime(config);
|
|
463
|
+
const parsed = new DOMParser().parseFromString(
|
|
464
|
+
rewriteRootRelativeHtmlUrls(removeHtmlCspMeta(html), appUrl.origin),
|
|
465
|
+
"text/html"
|
|
466
|
+
);
|
|
467
|
+
const scripts = Array.from(parsed.querySelectorAll("script"));
|
|
468
|
+
copyDocumentElementAttributes(parsed.documentElement);
|
|
469
|
+
importChildren(parsed.head, document.head);
|
|
470
|
+
const base = document.createElement("base");
|
|
471
|
+
base.href = config.baseHref;
|
|
472
|
+
document.head.prepend(base);
|
|
473
|
+
importChildren(parsed.body, document.body);
|
|
474
|
+
for (const script of scripts) {
|
|
475
|
+
if (isRunnableClassicScript(script)) runClassicScript(script);
|
|
476
|
+
}
|
|
477
|
+
for (const script of scripts) {
|
|
478
|
+
if (isModuleScript(script)) runModuleScriptAsClassic(script, appUrl.origin);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function transplantAppDocument(src) {
|
|
483
|
+
clearFrameReadyTimer();
|
|
484
|
+
clearFrameLoadTimer();
|
|
485
|
+
appFrame = null;
|
|
486
|
+
lastFrameSrc = src;
|
|
487
|
+
setMessage("Loading app");
|
|
488
|
+
const response = await fetch(src, {
|
|
489
|
+
credentials: "omit",
|
|
490
|
+
redirect: "follow",
|
|
491
|
+
headers: { Accept: "text/html" }
|
|
492
|
+
});
|
|
493
|
+
if (!response.ok) {
|
|
494
|
+
throw new Error("Embedded app returned HTTP " + response.status + ".");
|
|
495
|
+
}
|
|
496
|
+
const html = await response.text();
|
|
497
|
+
const appUrl = new URL(response.url || src);
|
|
498
|
+
try {
|
|
499
|
+
window.history.replaceState(window.history.state, "", localPathFromUrl(appUrl, false));
|
|
500
|
+
} catch {}
|
|
501
|
+
mountTransplantedHtml(html, appUrl);
|
|
502
|
+
notifyHostHeightRepeatedly();
|
|
503
|
+
}
|
|
504
|
+
|
|
191
505
|
function wantsEmbed() {
|
|
192
506
|
if (toolInput.embed === false || toolInput.embed === "false") return false;
|
|
193
507
|
if (embedByDefault) return true;
|
|
@@ -252,7 +566,7 @@ export function embedApp(options = {}) {
|
|
|
252
566
|
clearFrameReadyTimer();
|
|
253
567
|
appFrameReadyTimer = setTimeout(() => {
|
|
254
568
|
if (!appFrameReady && appFrame === frame) renderFrameFallback();
|
|
255
|
-
},
|
|
569
|
+
}, frameReadyTimeoutMs);
|
|
256
570
|
}
|
|
257
571
|
|
|
258
572
|
function renderFrameFallback() {
|
|
@@ -288,10 +602,7 @@ export function embedApp(options = {}) {
|
|
|
288
602
|
async function openFallbackExternal() {
|
|
289
603
|
let url = withChatBridgeParam(openUrl);
|
|
290
604
|
try {
|
|
291
|
-
const result = await callEmbedSessionTool(
|
|
292
|
-
url,
|
|
293
|
-
chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
|
|
294
|
-
});
|
|
605
|
+
const result = await callEmbedSessionTool(embedSessionArgsFor(url));
|
|
295
606
|
const data = parseToolResult(result);
|
|
296
607
|
if (typeof data.startUrl === "string" && data.startUrl) {
|
|
297
608
|
url = data.startUrl;
|
|
@@ -322,7 +633,7 @@ export function embedApp(options = {}) {
|
|
|
322
633
|
notifyHostHeight();
|
|
323
634
|
appFrameLoadTimer = setTimeout(() => {
|
|
324
635
|
if (!appFrameReady && appFrame === frame) renderFrameFallback();
|
|
325
|
-
},
|
|
636
|
+
}, frameLoadTimeoutMs);
|
|
326
637
|
}
|
|
327
638
|
|
|
328
639
|
function shouldSelfNavigateToApp() {
|
|
@@ -336,6 +647,41 @@ export function embedApp(options = {}) {
|
|
|
336
647
|
return true;
|
|
337
648
|
}
|
|
338
649
|
|
|
650
|
+
function shouldTransplantAppDocument() {
|
|
651
|
+
const mode = typeof toolInput.embedMode === "string"
|
|
652
|
+
? toolInput.embedMode
|
|
653
|
+
: typeof toolInput.renderMode === "string"
|
|
654
|
+
? toolInput.renderMode
|
|
655
|
+
: "";
|
|
656
|
+
return (
|
|
657
|
+
mode === "transplant" ||
|
|
658
|
+
toolInput.frame === "transplant" ||
|
|
659
|
+
isClaudeMcpContentHost()
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function isClaudeMcpContentHost() {
|
|
664
|
+
try {
|
|
665
|
+
return /(^|\\.)claudemcpcontent\\.com$/i.test(window.location.hostname || "");
|
|
666
|
+
} catch {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function isChatGptSandboxHost() {
|
|
672
|
+
try {
|
|
673
|
+
const host = window.location.hostname || "";
|
|
674
|
+
const appParam = new URL(window.location.href).searchParams.get("app");
|
|
675
|
+
return /(^|\\.)oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
|
|
676
|
+
} catch {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function shouldRenderControlledAppFrame() {
|
|
682
|
+
return !!openAiBridge || isChatGptSandboxHost();
|
|
683
|
+
}
|
|
684
|
+
|
|
339
685
|
function navigateToAppFrame(src) {
|
|
340
686
|
clearFrameReadyTimer();
|
|
341
687
|
clearFrameLoadTimer();
|
|
@@ -382,11 +728,19 @@ export function embedApp(options = {}) {
|
|
|
382
728
|
}
|
|
383
729
|
|
|
384
730
|
function notifyHostHeight() {
|
|
731
|
+
const height = applyIntrinsicHeight(visibleIntrinsicHeight());
|
|
385
732
|
if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== "function") {
|
|
733
|
+
if (app && typeof app.sendSizeChanged === "function") {
|
|
734
|
+
try {
|
|
735
|
+
app.sendSizeChanged({ height });
|
|
736
|
+
} catch (err) {
|
|
737
|
+
console.warn("[agent-native] MCP host rejected size update", err);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
386
740
|
return;
|
|
387
741
|
}
|
|
388
742
|
try {
|
|
389
|
-
openAiBridge.notifyIntrinsicHeight({ height
|
|
743
|
+
openAiBridge.notifyIntrinsicHeight({ height });
|
|
390
744
|
} catch (err) {
|
|
391
745
|
console.warn("[agent-native] ChatGPT rejected intrinsic height update", err);
|
|
392
746
|
}
|
|
@@ -482,6 +836,22 @@ export function embedApp(options = {}) {
|
|
|
482
836
|
}
|
|
483
837
|
});
|
|
484
838
|
|
|
839
|
+
function notifyHostHeightSoon() {
|
|
840
|
+
requestAnimationFrame(() => notifyHostHeight());
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function notifyHostHeightRepeatedly() {
|
|
844
|
+
notifyHostHeight();
|
|
845
|
+
[0, 250, 1000, 2500].forEach((delay) => {
|
|
846
|
+
setTimeout(() => notifyHostHeight(), delay);
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
window.addEventListener("resize", notifyHostHeightSoon, { passive: true });
|
|
851
|
+
if (window.visualViewport) {
|
|
852
|
+
window.visualViewport.addEventListener("resize", notifyHostHeightSoon, { passive: true });
|
|
853
|
+
}
|
|
854
|
+
|
|
485
855
|
async function launchEmbed() {
|
|
486
856
|
if (!openUrl) {
|
|
487
857
|
setMessage("Open link was not available.");
|
|
@@ -497,10 +867,21 @@ export function embedApp(options = {}) {
|
|
|
497
867
|
try {
|
|
498
868
|
const selfNavigate = shouldSelfNavigateToApp();
|
|
499
869
|
const embedUrl = withChatBridgeParam(openUrl);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
870
|
+
if (selfNavigate && isEmbedStartUrl(embedUrl)) {
|
|
871
|
+
if (isClaudeMcpContentHost() && shouldTransplantAppDocument()) {
|
|
872
|
+
await transplantAppDocument(embedUrl);
|
|
873
|
+
} else if (shouldRenderControlledAppFrame()) {
|
|
874
|
+
renderFrame(embedUrl);
|
|
875
|
+
} else {
|
|
876
|
+
navigateToAppFrame(embedUrl);
|
|
877
|
+
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (!selfNavigate && isEmbedStartUrl(embedUrl)) {
|
|
881
|
+
renderFrame(embedUrl);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const result = await callEmbedSessionTool(embedSessionArgsFor(embedUrl));
|
|
504
885
|
const data = parseToolResult(result);
|
|
505
886
|
if (typeof data.startUrl !== "string" || !data.startUrl) {
|
|
506
887
|
startedFor = "";
|
|
@@ -508,7 +889,13 @@ export function embedApp(options = {}) {
|
|
|
508
889
|
return;
|
|
509
890
|
}
|
|
510
891
|
if (selfNavigate) {
|
|
511
|
-
|
|
892
|
+
if (isClaudeMcpContentHost() && shouldTransplantAppDocument()) {
|
|
893
|
+
await transplantAppDocument(data.startUrl);
|
|
894
|
+
} else if (shouldRenderControlledAppFrame()) {
|
|
895
|
+
renderFrame(data.startUrl);
|
|
896
|
+
} else {
|
|
897
|
+
navigateToAppFrame(data.startUrl);
|
|
898
|
+
}
|
|
512
899
|
} else {
|
|
513
900
|
renderFrame(data.startUrl);
|
|
514
901
|
}
|
|
@@ -618,7 +1005,11 @@ export function embedApp(options = {}) {
|
|
|
618
1005
|
|
|
619
1006
|
async function startMcpAppsBridge() {
|
|
620
1007
|
const { App } = await import("${MCP_APP_IMPORT}");
|
|
621
|
-
app = new App(
|
|
1008
|
+
app = new App(
|
|
1009
|
+
{ name: "Agent Native Embed", version: "1.0.0" },
|
|
1010
|
+
{},
|
|
1011
|
+
{ autoResize: false }
|
|
1012
|
+
);
|
|
622
1013
|
app.ontoolinput = (params) => {
|
|
623
1014
|
toolInput = params.arguments || {};
|
|
624
1015
|
};
|
|
@@ -631,10 +1022,12 @@ export function embedApp(options = {}) {
|
|
|
631
1022
|
};
|
|
632
1023
|
app.onhostcontextchanged = () => {
|
|
633
1024
|
updateDisplayButton();
|
|
1025
|
+
notifyHostHeight();
|
|
634
1026
|
sendHostContext();
|
|
635
1027
|
};
|
|
636
1028
|
await app.connect();
|
|
637
1029
|
updateDisplayButton();
|
|
1030
|
+
notifyHostHeight();
|
|
638
1031
|
sendHostContext();
|
|
639
1032
|
}
|
|
640
1033
|
|
|
@@ -646,16 +1039,14 @@ export function embedApp(options = {}) {
|
|
|
646
1039
|
</body>
|
|
647
1040
|
</html>`,
|
|
648
1041
|
csp: {
|
|
649
|
-
connectDomains: ["https://esm.sh"],
|
|
1042
|
+
connectDomains: ["https://esm.sh", MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
|
|
650
1043
|
resourceDomains: [
|
|
651
1044
|
"https://esm.sh",
|
|
652
1045
|
MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
|
|
653
1046
|
...(options.frameDomains ?? []),
|
|
654
1047
|
],
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
...(options.frameDomains ?? []),
|
|
658
|
-
],
|
|
1048
|
+
baseUriDomains: [MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
|
|
1049
|
+
frameDomains,
|
|
659
1050
|
},
|
|
660
1051
|
prefersBorder: false,
|
|
661
1052
|
};
|