@agent-native/core 0.206.0-nightly-20261009153146 → 0.206.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.
- package/dist/action.d.ts +7 -1
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/embed-auth.d.ts +2 -0
- package/dist/client/embed-auth.js +160 -12
- package/dist/client/host/index.d.ts +1 -1
- package/dist/client/host/index.js +1 -1
- package/dist/client/mcp-app-host.d.ts +1 -0
- package/dist/client/mcp-app-host.js +4 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/mcp/build-server.d.ts +15 -0
- package/dist/mcp/build-server.js +307 -91
- package/dist/mcp/connect-route.js +1 -0
- package/dist/mcp/embed-app.js +291 -31
- package/dist/mcp/oauth-route.js +3 -0
- package/dist/mcp/oauth-store.d.ts +2 -1
- package/dist/mcp/oauth-store.js +5 -6
- package/dist/mcp/oauth-token.d.ts +5 -0
- package/dist/mcp/oauth-token.js +17 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/handlers.d.ts +1 -1
- package/dist/org/workspace-app-access-request.d.ts +8 -0
- package/dist/org/workspace-app-access-request.js +20 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/action-routes.d.ts +2 -0
- package/dist/server/action-routes.js +187 -31
- package/dist/server/agent-chat/mcp-options.d.ts +3 -1
- package/dist/server/agent-chat-plugin.js +26 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/auth.js +11 -20
- package/dist/server/embed-route.js +5 -3
- package/dist/server/embed-session.d.ts +30 -1
- package/dist/server/embed-session.js +376 -30
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +25 -1
- package/dist/shared/embed-auth.d.ts +74 -0
- package/dist/shared/embed-auth.js +379 -10
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/triggers/actions/manage-automation.d.ts +3 -3
- package/package.json +4 -4
package/dist/mcp/embed-app.js
CHANGED
|
@@ -3,6 +3,7 @@ import { MCP_APP_HOST_FILL_ATTRIBUTE, MCP_APP_PANE_FILL_MAX_HEIGHT, } from "../s
|
|
|
3
3
|
const MCP_APP_IMPORT = "https://esm.sh/@modelcontextprotocol/ext-apps@1.7.5/app-with-deps";
|
|
4
4
|
export const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
|
|
5
5
|
const MCP_APP_WRAPPER_CHROME_HEIGHT = 44;
|
|
6
|
+
const MCP_APP_INLINE_LAUNCHER_HEIGHT = 56;
|
|
6
7
|
export const DEFAULT_MCP_APP_SHELL_HEIGHT = 560;
|
|
7
8
|
export const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = DEFAULT_MCP_APP_SHELL_HEIGHT - MCP_APP_WRAPPER_CHROME_HEIGHT;
|
|
8
9
|
function attr(value) {
|
|
@@ -29,12 +30,9 @@ export function embedApp(options = {}) {
|
|
|
29
30
|
...(options.description ? { description: options.description } : {}),
|
|
30
31
|
html: (ctx) => {
|
|
31
32
|
const remoteBridgeFallbackEnabled = ctx.catalogMode !== "directory";
|
|
32
|
-
// A directory widget is an editor that lives in a host pane, never a
|
|
33
|
-
// content-sized card, so it fills the pane even when the host will not
|
|
34
|
-
// give it a height (see paneFillHeight).
|
|
35
33
|
const fillsPane = ctx.catalogMode === "directory";
|
|
36
34
|
return `<!doctype html>
|
|
37
|
-
<html lang="en"
|
|
35
|
+
<html lang="en">
|
|
38
36
|
<head>
|
|
39
37
|
<meta charset="utf-8">
|
|
40
38
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@@ -60,6 +58,10 @@ export function embedApp(options = {}) {
|
|
|
60
58
|
.fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }
|
|
61
59
|
.fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }
|
|
62
60
|
.fallback-url { max-width: min(560px, 100%); overflow-wrap: anywhere; color: color-mix(in srgb, CanvasText 76%, Canvas); font-size: 12px; }
|
|
61
|
+
body[data-catalog-mode="directory"][data-widget-mode="inline"] .shell { display: flex; align-items: center; height: ${MCP_APP_INLINE_LAUNCHER_HEIGHT}px; min-height: ${MCP_APP_INLINE_LAUNCHER_HEIGHT}px; max-height: ${MCP_APP_INLINE_LAUNCHER_HEIGHT}px; overflow: hidden; }
|
|
62
|
+
body[data-catalog-mode="directory"][data-widget-mode="inline"] .bar { width: 100%; height: ${MCP_APP_INLINE_LAUNCHER_HEIGHT}px; min-height: ${MCP_APP_INLINE_LAUNCHER_HEIGHT}px; padding: 0 12px; border: 0; }
|
|
63
|
+
body[data-catalog-mode="directory"][data-widget-mode="inline"] [data-display] { display: none !important; }
|
|
64
|
+
body[data-catalog-mode="directory"][data-widget-mode="inline"] .stage { display: none; }
|
|
63
65
|
html[${MCP_APP_HOST_FILL_ATTRIBUTE}], html[${MCP_APP_HOST_FILL_ATTRIBUTE}] body { height: 100%; }
|
|
64
66
|
html[${MCP_APP_HOST_FILL_ATTRIBUTE}] .shell { display: flex; flex-direction: column; gap: 0; height: 100vh; height: 100dvh; min-height: 0; }
|
|
65
67
|
html[${MCP_APP_HOST_FILL_ATTRIBUTE}] .bar { display: none; }
|
|
@@ -73,6 +75,7 @@ export function embedApp(options = {}) {
|
|
|
73
75
|
data-open-label="${attr(openLabel)}"
|
|
74
76
|
data-start-tool="${attr(ctx.startToolName ?? startToolName)}"
|
|
75
77
|
data-catalog-mode="${attr(ctx.catalogMode)}"
|
|
78
|
+
data-widget-mode="${fillsPane ? "inline" : "content"}"
|
|
76
79
|
data-embed-default="${embedByDefault ? "1" : "0"}"
|
|
77
80
|
>
|
|
78
81
|
<main class="shell">
|
|
@@ -125,14 +128,18 @@ export function embedApp(options = {}) {
|
|
|
125
128
|
let toolResponseMetadata = {};
|
|
126
129
|
let openUrl = "";
|
|
127
130
|
let openStartUrl = "";
|
|
131
|
+
let activeEmbedSessionTicket = "";
|
|
128
132
|
let startedFor = "";
|
|
129
133
|
let appFrame = null;
|
|
130
134
|
let appFrameReady = false;
|
|
131
135
|
let appFrameReadyTimer = null;
|
|
132
136
|
let appFrameLoadTimer = null;
|
|
133
137
|
let lastFrameSrc = "";
|
|
138
|
+
const appFrameDocumentState = { loadGeneration: 0 };
|
|
134
139
|
let embedSessionRefreshAttempts = 0;
|
|
135
140
|
let reportedContentHeight = 0;
|
|
141
|
+
let directoryWidgetOpenRequested = false;
|
|
142
|
+
let lastHostDisplayMode = "";
|
|
136
143
|
|
|
137
144
|
function esc(value) {
|
|
138
145
|
return String(value ?? "")
|
|
@@ -178,6 +185,27 @@ export function embedApp(options = {}) {
|
|
|
178
185
|
return record.displayMode === "fullscreen" || record.displayMode === "pip";
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
function isCompactDirectoryWidget() {
|
|
189
|
+
return fillsPane && body.dataset.widgetMode === "inline";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function updateDirectoryWidgetLayout() {
|
|
193
|
+
if (!fillsPane) return false;
|
|
194
|
+
const context = hostState().context || {};
|
|
195
|
+
const displayMode = context.displayMode;
|
|
196
|
+
if (displayMode === "inline" && lastHostDisplayMode === "fullscreen") {
|
|
197
|
+
directoryWidgetOpenRequested = false;
|
|
198
|
+
}
|
|
199
|
+
if (typeof displayMode === "string") {
|
|
200
|
+
lastHostDisplayMode = displayMode;
|
|
201
|
+
}
|
|
202
|
+
const inline =
|
|
203
|
+
!directoryWidgetOpenRequested &&
|
|
204
|
+
(displayMode === undefined || displayMode === "inline");
|
|
205
|
+
body.dataset.widgetMode = inline ? "inline" : "pane";
|
|
206
|
+
return inline;
|
|
207
|
+
}
|
|
208
|
+
|
|
181
209
|
// A fullscreen view or fixed-height container sizes the frame itself, so
|
|
182
210
|
// the shell fills it with CSS and never reports a height. An inline card is
|
|
183
211
|
// the opposite: the host follows the height reported here, so filling the
|
|
@@ -187,15 +215,19 @@ export function embedApp(options = {}) {
|
|
|
187
215
|
function applyHostFillMode() {
|
|
188
216
|
const context = hostState().context || {};
|
|
189
217
|
const hostFill = hostFillsContainer(context);
|
|
218
|
+
const compactInline = isCompactDirectoryWidget();
|
|
190
219
|
const root = document.documentElement;
|
|
191
|
-
if (
|
|
220
|
+
if (compactInline) {
|
|
221
|
+
root.removeAttribute(hostFillAttribute);
|
|
222
|
+
if (appFrame) appFrame.style.height = "";
|
|
223
|
+
} else if (hostFill || fillsPane) {
|
|
192
224
|
root.setAttribute(hostFillAttribute, "1");
|
|
193
225
|
if (appFrame) appFrame.style.height = "";
|
|
194
226
|
} else {
|
|
195
227
|
root.removeAttribute(hostFillAttribute);
|
|
196
228
|
}
|
|
197
229
|
body.dataset.hostFill = hostFill ? "1" : "0";
|
|
198
|
-
body.dataset.paneFill = fillsPane && !hostFill ? "1" : "0";
|
|
230
|
+
body.dataset.paneFill = fillsPane && !hostFill && !compactInline ? "1" : "0";
|
|
199
231
|
body.dataset.hostDisplayMode = typeof context.displayMode === "string" ? context.displayMode : "";
|
|
200
232
|
return hostFill;
|
|
201
233
|
}
|
|
@@ -222,7 +254,13 @@ export function embedApp(options = {}) {
|
|
|
222
254
|
// the frame's height; in a pane the shell owns it, so tell the app so.
|
|
223
255
|
function hostStateForApp() {
|
|
224
256
|
const state = hostState();
|
|
225
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
!fillsPane ||
|
|
259
|
+
isCompactDirectoryWidget() ||
|
|
260
|
+
hostFillsContainer(state.context)
|
|
261
|
+
) {
|
|
262
|
+
return state;
|
|
263
|
+
}
|
|
226
264
|
const context = objectValue(state.context);
|
|
227
265
|
return {
|
|
228
266
|
...state,
|
|
@@ -298,7 +336,11 @@ export function embedApp(options = {}) {
|
|
|
298
336
|
function toolResultMeta(params) {
|
|
299
337
|
if (!params || typeof params !== "object") return {};
|
|
300
338
|
const direct = metadataRecord(params);
|
|
301
|
-
if (direct)
|
|
339
|
+
if (direct) {
|
|
340
|
+
const mcpToolResult = direct.mcp_tool_result;
|
|
341
|
+
const mcpMeta = metadataRecord(mcpToolResult);
|
|
342
|
+
return mcpMeta || direct;
|
|
343
|
+
}
|
|
302
344
|
if (params.result && typeof params.result === "object") {
|
|
303
345
|
return toolResultMeta(params.result);
|
|
304
346
|
}
|
|
@@ -396,8 +438,31 @@ export function embedApp(options = {}) {
|
|
|
396
438
|
};
|
|
397
439
|
}
|
|
398
440
|
|
|
441
|
+
function appFrameTargetOrigin() {
|
|
442
|
+
if (body.dataset.catalogMode !== "directory") return "*";
|
|
443
|
+
const candidates = [openStartUrl, openUrl];
|
|
444
|
+
for (const candidate of candidates) {
|
|
445
|
+
if (!candidate) continue;
|
|
446
|
+
try {
|
|
447
|
+
const origin = new URL(candidate, window.location.href).origin;
|
|
448
|
+
if (origin && origin !== "null") return origin;
|
|
449
|
+
// coercion-ok: skip a malformed optional URL so the next host URL can be tried.
|
|
450
|
+
} catch {}
|
|
451
|
+
}
|
|
452
|
+
return "";
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function isTrustedAppFrameOrigin(origin) {
|
|
456
|
+
return (
|
|
457
|
+
body.dataset.catalogMode !== "directory" ||
|
|
458
|
+
origin === "null" ||
|
|
459
|
+
(!!origin && origin === appFrameTargetOrigin())
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
399
463
|
function sendToAppFrame(message) {
|
|
400
464
|
if (!appFrame || !appFrame.contentWindow) return;
|
|
465
|
+
// ChatGPT sandboxes nested app frames to opaque origins. Only non-secret bridge data is sent here.
|
|
401
466
|
try { appFrame.contentWindow.postMessage(message, "*"); } catch {}
|
|
402
467
|
}
|
|
403
468
|
|
|
@@ -472,7 +537,7 @@ export function embedApp(options = {}) {
|
|
|
472
537
|
const originPayload = { type: "agentNative.frameOrigin", origin: window.location.origin };
|
|
473
538
|
frameReadyMessageDelays.forEach((delay) => {
|
|
474
539
|
setTimeout(() => {
|
|
475
|
-
|
|
540
|
+
if (frame.contentWindow) frame.contentWindow.postMessage(originPayload, "*");
|
|
476
541
|
sendHostContext();
|
|
477
542
|
}, delay);
|
|
478
543
|
});
|
|
@@ -492,20 +557,52 @@ export function embedApp(options = {}) {
|
|
|
492
557
|
}
|
|
493
558
|
}
|
|
494
559
|
|
|
495
|
-
function
|
|
496
|
-
|
|
560
|
+
function embedTicketFromStartUrl(value) {
|
|
561
|
+
if (typeof value !== "string" || !value) return null;
|
|
562
|
+
const url = new URL(value, window.location.href);
|
|
563
|
+
return url.pathname.endsWith("/_agent-native/embed/start")
|
|
564
|
+
? url.searchParams.get("ticket") || null
|
|
565
|
+
: null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function rememberActiveEmbedSessionTicket(value) {
|
|
569
|
+
const ticket = embedTicketFromStartUrl(value);
|
|
570
|
+
if (ticket) activeEmbedSessionTicket = ticket;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function embedSessionArgsFor(
|
|
574
|
+
value,
|
|
575
|
+
renewInPlace = false,
|
|
576
|
+
renewalSourceTicket = "",
|
|
577
|
+
) {
|
|
497
578
|
if (body.dataset.catalogMode === "directory") {
|
|
498
|
-
const widgetSource =
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
579
|
+
const widgetSource = objectValue(
|
|
580
|
+
toolResponseMetadata["agent-native/widgetSource"],
|
|
581
|
+
);
|
|
582
|
+
const originalSourceTicket =
|
|
583
|
+
typeof widgetSource.sourceTicket === "string" &&
|
|
584
|
+
widgetSource.sourceTicket.trim()
|
|
585
|
+
? widgetSource.sourceTicket
|
|
586
|
+
: embedTicketFromStartUrl(openStartUrl || value);
|
|
587
|
+
const sourceTicket = renewInPlace
|
|
588
|
+
? typeof renewalSourceTicket === "string" &&
|
|
589
|
+
renewalSourceTicket.trim()
|
|
590
|
+
? renewalSourceTicket
|
|
591
|
+
: null
|
|
592
|
+
: originalSourceTicket;
|
|
593
|
+
if (typeof sourceTicket !== "string" || !sourceTicket.trim()) {
|
|
594
|
+
throw new Error(
|
|
595
|
+
renewInPlace
|
|
596
|
+
? "The active widget session ticket is unavailable."
|
|
597
|
+
: "The original widget session ticket is unavailable.",
|
|
598
|
+
);
|
|
599
|
+
}
|
|
502
600
|
return {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
toolOutput: toolResultData,
|
|
506
|
-
chrome
|
|
601
|
+
sourceTicket,
|
|
602
|
+
...(renewInPlace ? { renewInPlace: true } : {}),
|
|
507
603
|
};
|
|
508
604
|
}
|
|
605
|
+
const chrome = typeof toolInput.chrome === "string" ? toolInput.chrome : "full";
|
|
509
606
|
return typeof value === "string" && value.startsWith("/")
|
|
510
607
|
? { path: value, chrome }
|
|
511
608
|
: { url: value, chrome };
|
|
@@ -995,6 +1092,7 @@ export function embedApp(options = {}) {
|
|
|
995
1092
|
}
|
|
996
1093
|
|
|
997
1094
|
async function transplantAppDocument(src) {
|
|
1095
|
+
rememberActiveEmbedSessionTicket(src);
|
|
998
1096
|
clearFrameReadyTimer();
|
|
999
1097
|
clearFrameLoadTimer();
|
|
1000
1098
|
appFrame = null;
|
|
@@ -1100,11 +1198,16 @@ export function embedApp(options = {}) {
|
|
|
1100
1198
|
const context = hostState().context || {};
|
|
1101
1199
|
const nextMode = context.displayMode === "fullscreen" ? "inline" : "fullscreen";
|
|
1102
1200
|
const supported = supportedDisplayMode(nextMode);
|
|
1103
|
-
displayButton.hidden = !supported;
|
|
1201
|
+
displayButton.hidden = !supported || (fillsPane && nextMode === "fullscreen");
|
|
1104
1202
|
displayButton.disabled = !supported;
|
|
1105
1203
|
displayButton.textContent = nextMode === "fullscreen" ? "Fullscreen" : "Inline";
|
|
1106
1204
|
displayButton.onclick = () => {
|
|
1107
1205
|
if (!supportedDisplayMode(nextMode)) return;
|
|
1206
|
+
if (fillsPane && nextMode === "inline") {
|
|
1207
|
+
directoryWidgetOpenRequested = false;
|
|
1208
|
+
updateDirectoryWidgetLayout();
|
|
1209
|
+
notifyHostHeight();
|
|
1210
|
+
}
|
|
1108
1211
|
void requestHostDisplayMode(nextMode).catch((err) => {
|
|
1109
1212
|
console.warn("[agent-native] MCP host rejected display mode request", err);
|
|
1110
1213
|
});
|
|
@@ -1355,6 +1458,7 @@ export function embedApp(options = {}) {
|
|
|
1355
1458
|
}
|
|
1356
1459
|
|
|
1357
1460
|
function renderFrame(src) {
|
|
1461
|
+
rememberActiveEmbedSessionTicket(src);
|
|
1358
1462
|
clearFrameReadyTimer();
|
|
1359
1463
|
clearFrameLoadTimer();
|
|
1360
1464
|
const frame = document.createElement("iframe");
|
|
@@ -1364,8 +1468,10 @@ export function embedApp(options = {}) {
|
|
|
1364
1468
|
appFrame = frame;
|
|
1365
1469
|
appFrameReady = false;
|
|
1366
1470
|
lastFrameSrc = src;
|
|
1471
|
+
appFrameDocumentState.loadGeneration += 1;
|
|
1367
1472
|
frame.addEventListener("load", () => {
|
|
1368
1473
|
if (appFrame !== frame) return;
|
|
1474
|
+
appFrameDocumentState.loadGeneration += 1;
|
|
1369
1475
|
clearFrameLoadTimer();
|
|
1370
1476
|
notifyOuterMcpAppReady();
|
|
1371
1477
|
sendFrameReadyMessages(frame);
|
|
@@ -1404,6 +1510,83 @@ export function embedApp(options = {}) {
|
|
|
1404
1510
|
void launchEmbed();
|
|
1405
1511
|
}
|
|
1406
1512
|
|
|
1513
|
+
async function renewExpiredEmbedSession(
|
|
1514
|
+
requestId,
|
|
1515
|
+
frame,
|
|
1516
|
+
frameLoadGeneration,
|
|
1517
|
+
) {
|
|
1518
|
+
const directoryMode = body.dataset.catalogMode === "directory";
|
|
1519
|
+
const canDeliverRenewal = () =>
|
|
1520
|
+
frame === appFrame &&
|
|
1521
|
+
(!directoryMode ||
|
|
1522
|
+
appFrameDocumentState.loadGeneration === frameLoadGeneration);
|
|
1523
|
+
const sendRenewalResult = (message) => {
|
|
1524
|
+
if (!canDeliverRenewal()) return;
|
|
1525
|
+
sendToAppFrame(message);
|
|
1526
|
+
};
|
|
1527
|
+
if (!requestId || !frame || !canDeliverRenewal()) return;
|
|
1528
|
+
if (!openUrl) {
|
|
1529
|
+
sendRenewalResult({
|
|
1530
|
+
type: "agentNative.embedSessionRenewed",
|
|
1531
|
+
data: { requestId, ok: false },
|
|
1532
|
+
});
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (embedSessionRefreshAttempts >= maxEmbedSessionRefreshAttempts) {
|
|
1536
|
+
sendRenewalResult({
|
|
1537
|
+
type: "agentNative.embedSessionRenewed",
|
|
1538
|
+
data: { requestId, ok: false },
|
|
1539
|
+
});
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
embedSessionRefreshAttempts += 1;
|
|
1544
|
+
try {
|
|
1545
|
+
const result = await callEmbedSessionTool(
|
|
1546
|
+
embedSessionArgsFor(
|
|
1547
|
+
directoryMode ? openStartUrl || openUrl : openUrl,
|
|
1548
|
+
directoryMode,
|
|
1549
|
+
activeEmbedSessionTicket,
|
|
1550
|
+
),
|
|
1551
|
+
);
|
|
1552
|
+
const data = parseToolResult(result);
|
|
1553
|
+
if (directoryMode) {
|
|
1554
|
+
if (data.renewed !== true) {
|
|
1555
|
+
sendRenewalResult({
|
|
1556
|
+
type: "agentNative.embedSessionRenewed",
|
|
1557
|
+
data: { requestId, ok: false },
|
|
1558
|
+
});
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
sendRenewalResult({
|
|
1562
|
+
type: "agentNative.embedSessionRenewed",
|
|
1563
|
+
data: { requestId, ok: true },
|
|
1564
|
+
});
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (typeof data.startUrl !== "string" || !data.startUrl) {
|
|
1568
|
+
sendRenewalResult({
|
|
1569
|
+
type: "agentNative.embedSessionRenewed",
|
|
1570
|
+
data: { requestId, ok: false },
|
|
1571
|
+
});
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
sendRenewalResult({
|
|
1575
|
+
type: "agentNative.embedSessionRenewed",
|
|
1576
|
+
data: {
|
|
1577
|
+
requestId,
|
|
1578
|
+
ok: true,
|
|
1579
|
+
startUrl: withChatBridgeParam(data.startUrl),
|
|
1580
|
+
},
|
|
1581
|
+
});
|
|
1582
|
+
} catch {
|
|
1583
|
+
sendRenewalResult({
|
|
1584
|
+
type: "agentNative.embedSessionRenewed",
|
|
1585
|
+
data: { requestId, ok: false },
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1407
1590
|
function shouldSelfNavigateToApp() {
|
|
1408
1591
|
const render = renderModeSource();
|
|
1409
1592
|
const mode = render.mode;
|
|
@@ -1464,6 +1647,7 @@ export function embedApp(options = {}) {
|
|
|
1464
1647
|
}
|
|
1465
1648
|
|
|
1466
1649
|
function navigateToAppFrame(src) {
|
|
1650
|
+
rememberActiveEmbedSessionTicket(src);
|
|
1467
1651
|
if (isCurrentFrameUrl(src)) {
|
|
1468
1652
|
clearFrameReadyTimer();
|
|
1469
1653
|
clearFrameLoadTimer();
|
|
@@ -1552,8 +1736,12 @@ export function embedApp(options = {}) {
|
|
|
1552
1736
|
}
|
|
1553
1737
|
|
|
1554
1738
|
function notifyHostHeight() {
|
|
1555
|
-
|
|
1556
|
-
const
|
|
1739
|
+
updateDirectoryWidgetLayout();
|
|
1740
|
+
const compactInline = isCompactDirectoryWidget();
|
|
1741
|
+
if (applyHostFillMode() && !compactInline) return;
|
|
1742
|
+
const height = compactInline
|
|
1743
|
+
? ${MCP_APP_INLINE_LAUNCHER_HEIGHT}
|
|
1744
|
+
: fillsPane
|
|
1557
1745
|
? paneFillHeight(hostState().context || {})
|
|
1558
1746
|
: applyIntrinsicHeight(visibleIntrinsicHeight());
|
|
1559
1747
|
if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== "function") {
|
|
@@ -1787,6 +1975,7 @@ export function embedApp(options = {}) {
|
|
|
1787
1975
|
event.source === null &&
|
|
1788
1976
|
(message.embedStartUrl === appFrame?.src ||
|
|
1789
1977
|
message.embedStartUrl === lastFrameSrc);
|
|
1978
|
+
if (!isTrustedAppFrameOrigin(event.origin)) return;
|
|
1790
1979
|
if (
|
|
1791
1980
|
!appFrame ||
|
|
1792
1981
|
(!expiredSessionMessage && event.source !== appFrame.contentWindow)
|
|
@@ -1812,7 +2001,23 @@ export function embedApp(options = {}) {
|
|
|
1812
2001
|
return;
|
|
1813
2002
|
}
|
|
1814
2003
|
if (message.type === "agentNative.embedSessionExpired") {
|
|
1815
|
-
|
|
2004
|
+
if (expiredSessionMessage) {
|
|
2005
|
+
refreshExpiredEmbedSession();
|
|
2006
|
+
} else if (typeof data.requestId === "string") {
|
|
2007
|
+
void renewExpiredEmbedSession(
|
|
2008
|
+
data.requestId,
|
|
2009
|
+
appFrame,
|
|
2010
|
+
appFrameDocumentState.loadGeneration,
|
|
2011
|
+
);
|
|
2012
|
+
} else if (body.dataset.catalogMode !== "directory") {
|
|
2013
|
+
// Older app frames do not include a renewal id, so preserve their
|
|
2014
|
+
// existing full-refresh fallback until they load the renewal client.
|
|
2015
|
+
refreshExpiredEmbedSession();
|
|
2016
|
+
}
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (message.type === "agentNative.embedSessionRenewalApplied") {
|
|
2020
|
+
embedSessionRefreshAttempts = 0;
|
|
1816
2021
|
return;
|
|
1817
2022
|
}
|
|
1818
2023
|
if (message.type === "agentNative.submitChat") {
|
|
@@ -1853,6 +2058,11 @@ export function embedApp(options = {}) {
|
|
|
1853
2058
|
}
|
|
1854
2059
|
|
|
1855
2060
|
async function launchEmbed() {
|
|
2061
|
+
updateDirectoryWidgetLayout();
|
|
2062
|
+
if (isCompactDirectoryWidget()) {
|
|
2063
|
+
notifyHostHeight();
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
1856
2066
|
let launchUrl = openStartUrl || openUrl;
|
|
1857
2067
|
if (!launchUrl) {
|
|
1858
2068
|
renderAppLaunchError("Open link was not available.");
|
|
@@ -1940,12 +2150,47 @@ export function embedApp(options = {}) {
|
|
|
1940
2150
|
function updateOpenButton() {
|
|
1941
2151
|
const buttonUrl = openUrl;
|
|
1942
2152
|
openButton.disabled = !buttonUrl;
|
|
2153
|
+
openButton.textContent = body.dataset.openLabel || "Open in app";
|
|
1943
2154
|
openButton.onclick = () => {
|
|
1944
|
-
if (buttonUrl)
|
|
2155
|
+
if (!buttonUrl) return;
|
|
2156
|
+
if (fillsPane) {
|
|
2157
|
+
void openDirectoryWidget();
|
|
2158
|
+
} else {
|
|
2159
|
+
void openHostLink({ url: buttonUrl });
|
|
2160
|
+
}
|
|
1945
2161
|
};
|
|
1946
2162
|
updateHostOpenInAppUrl();
|
|
1947
2163
|
}
|
|
1948
2164
|
|
|
2165
|
+
async function openDirectoryWidget() {
|
|
2166
|
+
directoryWidgetOpenRequested = true;
|
|
2167
|
+
updateDirectoryWidgetLayout();
|
|
2168
|
+
const displayMode = hostState().context?.displayMode;
|
|
2169
|
+
if (displayMode !== "fullscreen") {
|
|
2170
|
+
if (!supportedDisplayMode("fullscreen")) {
|
|
2171
|
+
notifyHostHeight();
|
|
2172
|
+
await launchEmbed();
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
try {
|
|
2176
|
+
const granted = await requestHostDisplayMode("fullscreen");
|
|
2177
|
+
if (granted?.mode !== "fullscreen") {
|
|
2178
|
+
notifyHostHeight();
|
|
2179
|
+
await launchEmbed();
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
} catch (err) {
|
|
2183
|
+
console.warn("[agent-native] MCP host could not open the widget fullscreen", err);
|
|
2184
|
+
notifyHostHeight();
|
|
2185
|
+
await launchEmbed();
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
updateDirectoryWidgetLayout();
|
|
2190
|
+
notifyHostHeight();
|
|
2191
|
+
await launchEmbed();
|
|
2192
|
+
}
|
|
2193
|
+
|
|
1949
2194
|
function updateTitle(data) {
|
|
1950
2195
|
const record = objectValue(data);
|
|
1951
2196
|
const openLink = objectValue(toolResponseMetadata["agent-native/openLink"]);
|
|
@@ -1993,7 +2238,7 @@ export function embedApp(options = {}) {
|
|
|
1993
2238
|
openAiBridge = bridge;
|
|
1994
2239
|
toolInput = objectValue(bridge.toolInput);
|
|
1995
2240
|
const params = openAiToolResultParams(bridge);
|
|
1996
|
-
toolResponseMetadata =
|
|
2241
|
+
toolResponseMetadata = toolResultMeta(params);
|
|
1997
2242
|
const data = parseToolResult(params);
|
|
1998
2243
|
toolResultData = objectValue(data);
|
|
1999
2244
|
openUrl = openLinkFrom(params, data);
|
|
@@ -2026,6 +2271,7 @@ export function embedApp(options = {}) {
|
|
|
2026
2271
|
}
|
|
2027
2272
|
lastOpenAiSyncSignature = signature;
|
|
2028
2273
|
updateTitle(data);
|
|
2274
|
+
updateDirectoryWidgetLayout();
|
|
2029
2275
|
updateOpenButton();
|
|
2030
2276
|
updateDisplayButton();
|
|
2031
2277
|
notifyHostHeight();
|
|
@@ -2284,19 +2530,33 @@ export function embedApp(options = {}) {
|
|
|
2284
2530
|
openUrl = openLinkFrom(params, data);
|
|
2285
2531
|
openStartUrl = embedStartUrlFrom(params, data);
|
|
2286
2532
|
updateTitle(data);
|
|
2533
|
+
updateDirectoryWidgetLayout();
|
|
2287
2534
|
updateOpenButton();
|
|
2288
2535
|
void launchEmbed();
|
|
2289
2536
|
};
|
|
2290
2537
|
app.onhostcontextchanged = () => {
|
|
2291
|
-
|
|
2292
|
-
notifyHostHeight();
|
|
2293
|
-
sendHostContext();
|
|
2538
|
+
handleHostContextChanged();
|
|
2294
2539
|
};
|
|
2295
2540
|
await ensureHostAppConnected();
|
|
2296
2541
|
notifyOuterMcpAppReady();
|
|
2542
|
+
updateDirectoryWidgetLayout();
|
|
2543
|
+
updateDisplayButton();
|
|
2544
|
+
notifyHostHeight();
|
|
2545
|
+
sendHostContext();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function handleHostContextChanged() {
|
|
2549
|
+
updateDirectoryWidgetLayout();
|
|
2297
2550
|
updateDisplayButton();
|
|
2298
2551
|
notifyHostHeight();
|
|
2299
2552
|
sendHostContext();
|
|
2553
|
+
if (
|
|
2554
|
+
fillsPane &&
|
|
2555
|
+
!isCompactDirectoryWidget() &&
|
|
2556
|
+
(openStartUrl || openUrl)
|
|
2557
|
+
) {
|
|
2558
|
+
void launchEmbed();
|
|
2559
|
+
}
|
|
2300
2560
|
}
|
|
2301
2561
|
|
|
2302
2562
|
${remoteBridgeFallbackEnabled
|
|
@@ -2318,16 +2578,16 @@ export function embedApp(options = {}) {
|
|
|
2318
2578
|
openUrl = openLinkFrom(params, data);
|
|
2319
2579
|
openStartUrl = embedStartUrlFrom(params, data);
|
|
2320
2580
|
updateTitle(data);
|
|
2581
|
+
updateDirectoryWidgetLayout();
|
|
2321
2582
|
updateOpenButton();
|
|
2322
2583
|
void launchEmbed();
|
|
2323
2584
|
};
|
|
2324
2585
|
app.onhostcontextchanged = () => {
|
|
2325
|
-
|
|
2326
|
-
notifyHostHeight();
|
|
2327
|
-
sendHostContext();
|
|
2586
|
+
handleHostContextChanged();
|
|
2328
2587
|
};
|
|
2329
2588
|
await ensureHostAppConnected();
|
|
2330
2589
|
notifyOuterMcpAppReady();
|
|
2590
|
+
updateDirectoryWidgetLayout();
|
|
2331
2591
|
updateDisplayButton();
|
|
2332
2592
|
notifyHostHeight();
|
|
2333
2593
|
sendHostContext();
|
package/dist/mcp/oauth-route.js
CHANGED
|
@@ -796,6 +796,7 @@ async function issueTokenSet(params, tx) {
|
|
|
796
796
|
orgDomain: params.orgDomain ?? null,
|
|
797
797
|
scope: params.scope,
|
|
798
798
|
resource: params.resource,
|
|
799
|
+
grantCreatedAtMs: params.grantCreatedAtMs,
|
|
799
800
|
}, tx);
|
|
800
801
|
const accessToken = await signMcpOAuthAccessToken(params);
|
|
801
802
|
return {
|
|
@@ -858,6 +859,7 @@ async function handleAuthorizationCodeGrant(event, body) {
|
|
|
858
859
|
scope: consumed.scope,
|
|
859
860
|
resource: consumed.resource,
|
|
860
861
|
issuer,
|
|
862
|
+
grantCreatedAtMs: consumed.createdAt,
|
|
861
863
|
}, tx));
|
|
862
864
|
});
|
|
863
865
|
}
|
|
@@ -917,6 +919,7 @@ async function handleRefreshTokenGrant(event, body) {
|
|
|
917
919
|
scope: existing.scope,
|
|
918
920
|
resource: existing.resource,
|
|
919
921
|
issuer,
|
|
922
|
+
grantCreatedAtMs: existing.grantCreatedAtMs,
|
|
920
923
|
});
|
|
921
924
|
return json({
|
|
922
925
|
access_token: accessToken,
|
|
@@ -49,7 +49,7 @@ export interface OAuthRefreshTokenRow {
|
|
|
49
49
|
orgDomain: string | null;
|
|
50
50
|
scope: string;
|
|
51
51
|
resource: string;
|
|
52
|
-
|
|
52
|
+
grantCreatedAtMs: number | null;
|
|
53
53
|
expiresAt: number | null;
|
|
54
54
|
lastUsedAt: number | null;
|
|
55
55
|
revokedAt: number | null;
|
|
@@ -87,6 +87,7 @@ export declare function createOAuthRefreshToken(params: {
|
|
|
87
87
|
orgDomain?: string | null;
|
|
88
88
|
scope: string;
|
|
89
89
|
resource: string;
|
|
90
|
+
grantCreatedAtMs: number | null;
|
|
90
91
|
}, db?: DbExec): Promise<OAuthRefreshTokenRow>;
|
|
91
92
|
export declare function rotateOAuthRefreshToken(params: {
|
|
92
93
|
oldRefreshToken: string;
|
package/dist/mcp/oauth-store.js
CHANGED
|
@@ -205,7 +205,7 @@ function mapRefreshRow(row) {
|
|
|
205
205
|
orgDomain: row.org_domain ?? row.orgDomain ?? null,
|
|
206
206
|
scope: row.scope,
|
|
207
207
|
resource: row.resource,
|
|
208
|
-
|
|
208
|
+
grantCreatedAtMs: numOrNull(row.created_at ?? row.createdAt),
|
|
209
209
|
expiresAt: refreshTokenExpiryFromRow(row),
|
|
210
210
|
lastUsedAt: numOrNull(row.last_used_at ?? row.lastUsedAt),
|
|
211
211
|
revokedAt: numOrNull(row.revoked_at ?? row.revokedAt),
|
|
@@ -382,7 +382,6 @@ export async function createOAuthRefreshToken(params, db) {
|
|
|
382
382
|
if (!db)
|
|
383
383
|
await ensureTable();
|
|
384
384
|
const client = db ?? getDbExec();
|
|
385
|
-
const now = Date.now();
|
|
386
385
|
const row = {
|
|
387
386
|
id: randomUUID(),
|
|
388
387
|
tokenHash: hashOAuthToken(params.refreshToken),
|
|
@@ -393,7 +392,8 @@ export async function createOAuthRefreshToken(params, db) {
|
|
|
393
392
|
orgDomain: params.orgDomain ?? null,
|
|
394
393
|
scope: params.scope,
|
|
395
394
|
resource: params.resource,
|
|
396
|
-
|
|
395
|
+
// This is the authorization grant's immutable timestamp, not row-creation time.
|
|
396
|
+
grantCreatedAtMs: params.grantCreatedAtMs,
|
|
397
397
|
expiresAt: null,
|
|
398
398
|
lastUsedAt: null,
|
|
399
399
|
revokedAt: null,
|
|
@@ -411,7 +411,7 @@ export async function createOAuthRefreshToken(params, db) {
|
|
|
411
411
|
row.orgDomain,
|
|
412
412
|
row.scope,
|
|
413
413
|
row.resource,
|
|
414
|
-
row.
|
|
414
|
+
row.grantCreatedAtMs,
|
|
415
415
|
row.expiresAt,
|
|
416
416
|
row.lastUsedAt,
|
|
417
417
|
row.revokedAt,
|
|
@@ -454,7 +454,6 @@ export async function rotateOAuthRefreshToken(params, db) {
|
|
|
454
454
|
...old,
|
|
455
455
|
id: randomUUID(),
|
|
456
456
|
tokenHash: newHash,
|
|
457
|
-
createdAt: now,
|
|
458
457
|
expiresAt: null,
|
|
459
458
|
lastUsedAt: null,
|
|
460
459
|
revokedAt: null,
|
|
@@ -472,7 +471,7 @@ export async function rotateOAuthRefreshToken(params, db) {
|
|
|
472
471
|
next.orgDomain,
|
|
473
472
|
next.scope,
|
|
474
473
|
next.resource,
|
|
475
|
-
next.
|
|
474
|
+
next.grantCreatedAtMs,
|
|
476
475
|
next.expiresAt,
|
|
477
476
|
next.lastUsedAt,
|
|
478
477
|
next.revokedAt,
|
|
@@ -18,6 +18,7 @@ export interface McpOAuthAccessTokenClaims {
|
|
|
18
18
|
scope: string;
|
|
19
19
|
client_id: string;
|
|
20
20
|
resource: string;
|
|
21
|
+
grant_created_at_ms?: number;
|
|
21
22
|
jti?: string;
|
|
22
23
|
typ: typeof MCP_OAUTH_TOKEN_TYPE;
|
|
23
24
|
credential_version: typeof MCP_OAUTH_CREDENTIAL_VERSION | typeof MCP_OAUTH_SERVICE_CREDENTIAL_VERSION;
|
|
@@ -36,6 +37,8 @@ export declare function signMcpOAuthAccessToken(params: {
|
|
|
36
37
|
scope: string;
|
|
37
38
|
resource: string;
|
|
38
39
|
issuer: string;
|
|
40
|
+
/** Immutable server-recorded creation time of the OAuth grant, in ms. */
|
|
41
|
+
grantCreatedAtMs?: number | null;
|
|
39
42
|
jti?: string;
|
|
40
43
|
expiresIn?: string | number;
|
|
41
44
|
catalogScope?: "full";
|
|
@@ -49,6 +52,8 @@ export declare function verifyMcpOAuthAccessToken(token: string, resource: strin
|
|
|
49
52
|
scopes: string[];
|
|
50
53
|
clientId: string;
|
|
51
54
|
jti?: string;
|
|
55
|
+
/** Immutable OAuth grant creation time, in milliseconds. */
|
|
56
|
+
grantCreatedAtMs?: number;
|
|
52
57
|
catalogScope?: "full";
|
|
53
58
|
/** `iat`, in seconds. */
|
|
54
59
|
issuedAt?: number;
|