@noodleseed/assistant 1.24.0 → 1.25.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/README.md +4 -9
- package/dist/{app-view-element-DSPtyEul.d.cts → app-view-element-9ojWWYov.d.cts} +3 -2
- package/dist/{app-view-element-DDtXu08a.d.ts → app-view-element-BM4lral-.d.ts} +3 -2
- package/dist/app-view.cjs +213 -41
- package/dist/app-view.cjs.map +1 -1
- package/dist/app-view.d.cts +3 -3
- package/dist/app-view.d.ts +3 -3
- package/dist/app-view.js +2 -2
- package/dist/{appearance-0nS2LFjb.d.cts → appearance-B6vSg8Ub.d.cts} +4 -0
- package/dist/{appearance-0nS2LFjb.d.ts → appearance-B6vSg8Ub.d.ts} +4 -0
- package/dist/{chunk-IBDP6PXQ.js → chunk-7BQHW37I.js} +71 -3
- package/dist/chunk-7BQHW37I.js.map +1 -0
- package/dist/{chunk-5NBHHNI3.js → chunk-LNBUYHL7.js} +2 -2
- package/dist/chunk-LNBUYHL7.js.map +1 -0
- package/dist/{chunk-XKTUXFGX.js → chunk-P32MAR4K.js} +214 -42
- package/dist/chunk-P32MAR4K.js.map +1 -0
- package/dist/{chunk-6PVDV7SO.js → chunk-SGMECNNU.js} +55 -21
- package/dist/chunk-SGMECNNU.js.map +1 -0
- package/dist/{client-By4gqB1k.d.cts → client-BkogON2A.d.ts} +11 -1
- package/dist/{client-Cf38TgwN.d.ts → client-C07ppTU_.d.cts} +11 -1
- package/dist/client.cjs +70 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/embed.global.js +149 -114
- package/dist/index.cjs +357 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/react/client.cjs +70 -2
- package/dist/react/client.cjs.map +1 -1
- package/dist/react/client.d.cts +2 -2
- package/dist/react/client.d.ts +2 -2
- package/dist/react/client.js +1 -1
- package/dist/react.cjs +357 -83
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +3 -3
- package/dist/react.d.ts +3 -3
- package/dist/react.js +4 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-5NBHHNI3.js.map +0 -1
- package/dist/chunk-6PVDV7SO.js.map +0 -1
- package/dist/chunk-IBDP6PXQ.js.map +0 -1
- package/dist/chunk-XKTUXFGX.js.map +0 -1
package/README.md
CHANGED
|
@@ -35,8 +35,8 @@ troubleshooting) lives at <https://docs.noodleseed.dev/guides/embedded-assistant
|
|
|
35
35
|
the server itself is `@noodleseed/one` (`npm install -g @noodleseed/one`).
|
|
36
36
|
|
|
37
37
|
If your page sends a `Content-Security-Policy`, allow the Noodle service origin in `connect-src` (turns and
|
|
38
|
-
event streams)
|
|
39
|
-
|
|
38
|
+
event streams) and the exact session-advertised `endpoints.sandbox` origin in `frame-src` (it may be a
|
|
39
|
+
dedicated sandbox origin, so your `script-src` can stay strict). Details are in the guide's
|
|
40
40
|
"Content-Security-Policy on your page" section.
|
|
41
41
|
|
|
42
42
|
## Quick start
|
|
@@ -410,13 +410,8 @@ import "@noodleseed/assistant/app-view";
|
|
|
410
410
|
</template>
|
|
411
411
|
```
|
|
412
412
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
`<noodle-app-view>` is the canonical host; `NoodleAppView` delegates to it. Both use the
|
|
417
|
-
service-advertised sandbox URL, route App calls through the supplied client, publish theme changes without
|
|
418
|
-
replacing the iframe, and request standard teardown on semantic replacement, disconnect, or an App teardown
|
|
419
|
-
request.
|
|
413
|
+
Framework setup, lifecycle ownership, sandbox/CSP behavior, and staged diagnostics are owned by the
|
|
414
|
+
[Bring your own UI guide](https://docs.noodleseed.dev/docs/guides/bring-your-own-ui).
|
|
420
415
|
|
|
421
416
|
The App document owns its action intent: it calls standard `tools/call` after connecting and never relies on
|
|
422
417
|
native form navigation. If that call needs input or confirmation, the same `client` publishes the normal
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { A as AssistantClient, a as AssistantViewAvailableDetail, e as AssistantErrorDetail } from './client-
|
|
1
|
+
import { A as AssistantClient, a as AssistantViewAvailableDetail, e as AssistantErrorDetail } from './client-C07ppTU_.cjs';
|
|
2
2
|
|
|
3
3
|
declare const APP_VIEW_TAG_NAME = "noodle-app-view";
|
|
4
4
|
interface AssistantAppViewErrorDetail {
|
|
5
|
-
readonly code: 'view_render_timeout';
|
|
5
|
+
readonly code: 'view_render_timeout' | 'view_bridge_error';
|
|
6
6
|
readonly retryable: false;
|
|
7
|
+
readonly stage: 'sandbox_load' | 'sandbox_proxy' | 'app_initialize';
|
|
7
8
|
}
|
|
8
9
|
declare global {
|
|
9
10
|
interface HTMLElementTagNameMap {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { A as AssistantClient, a as AssistantViewAvailableDetail, e as AssistantErrorDetail } from './client-
|
|
1
|
+
import { A as AssistantClient, a as AssistantViewAvailableDetail, e as AssistantErrorDetail } from './client-BkogON2A.js';
|
|
2
2
|
|
|
3
3
|
declare const APP_VIEW_TAG_NAME = "noodle-app-view";
|
|
4
4
|
interface AssistantAppViewErrorDetail {
|
|
5
|
-
readonly code: 'view_render_timeout';
|
|
5
|
+
readonly code: 'view_render_timeout' | 'view_bridge_error';
|
|
6
6
|
readonly retryable: false;
|
|
7
|
+
readonly stage: 'sandbox_load' | 'sandbox_proxy' | 'app_initialize';
|
|
7
8
|
}
|
|
8
9
|
declare global {
|
|
9
10
|
interface HTMLElementTagNameMap {
|
package/dist/app-view.cjs
CHANGED
|
@@ -15197,7 +15197,7 @@ __export(app_view_exports, {
|
|
|
15197
15197
|
});
|
|
15198
15198
|
module.exports = __toCommonJS(app_view_exports);
|
|
15199
15199
|
|
|
15200
|
-
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
|
|
15200
|
+
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_supports-color@7.2.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
|
|
15201
15201
|
init_v4();
|
|
15202
15202
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
15203
15203
|
var JSONRPC_VERSION = "2.0";
|
|
@@ -16718,7 +16718,7 @@ var UrlElicitationRequiredError = class extends McpError {
|
|
|
16718
16718
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/parse.js
|
|
16719
16719
|
init_core2();
|
|
16720
16720
|
|
|
16721
|
-
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
16721
|
+
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_supports-color@7.2.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
16722
16722
|
function isZ4Schema(s2) {
|
|
16723
16723
|
const schema = s2;
|
|
16724
16724
|
return !!schema._zod;
|
|
@@ -16781,7 +16781,7 @@ function getLiteralValue(schema) {
|
|
|
16781
16781
|
return void 0;
|
|
16782
16782
|
}
|
|
16783
16783
|
|
|
16784
|
-
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
|
|
16784
|
+
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_supports-color@7.2.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
|
|
16785
16785
|
function isTerminal(status) {
|
|
16786
16786
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
16787
16787
|
}
|
|
@@ -16789,7 +16789,7 @@ function isTerminal(status) {
|
|
|
16789
16789
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@4.4.3/node_modules/zod-to-json-schema/dist/esm/parsers/string.js
|
|
16790
16790
|
var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
|
|
16791
16791
|
|
|
16792
|
-
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
|
|
16792
|
+
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_supports-color@7.2.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
|
|
16793
16793
|
function getMethodLiteral(schema) {
|
|
16794
16794
|
const shape = getObjectShape(schema);
|
|
16795
16795
|
const methodSchema = shape?.method;
|
|
@@ -16810,7 +16810,7 @@ function parseWithCompat(schema, data) {
|
|
|
16810
16810
|
return result.data;
|
|
16811
16811
|
}
|
|
16812
16812
|
|
|
16813
|
-
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
|
|
16813
|
+
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_supports-color@7.2.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
|
|
16814
16814
|
var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
|
|
16815
16815
|
var Protocol = class {
|
|
16816
16816
|
constructor(_options) {
|
|
@@ -17922,6 +17922,15 @@ var E = class {
|
|
|
17922
17922
|
sessionId;
|
|
17923
17923
|
setProtocolVersion;
|
|
17924
17924
|
};
|
|
17925
|
+
function AZ(X) {
|
|
17926
|
+
if (!X) return "";
|
|
17927
|
+
let Y = [];
|
|
17928
|
+
if (X.camera) Y.push("camera");
|
|
17929
|
+
if (X.microphone) Y.push("microphone");
|
|
17930
|
+
if (X.geolocation) Y.push("geolocation");
|
|
17931
|
+
if (X.clipboardWrite) Y.push("clipboard-write");
|
|
17932
|
+
return Y.join("; ");
|
|
17933
|
+
}
|
|
17925
17934
|
var KX = [j];
|
|
17926
17935
|
var DX = class extends _ {
|
|
17927
17936
|
_client;
|
|
@@ -18192,34 +18201,131 @@ function GX(X, Y) {
|
|
|
18192
18201
|
return JSON.stringify(X) === JSON.stringify(Y);
|
|
18193
18202
|
}
|
|
18194
18203
|
|
|
18204
|
+
// src/app-sandbox-policy.ts
|
|
18205
|
+
var MAX_DOMAINS_PER_DIRECTIVE = 32;
|
|
18206
|
+
function isRecord(value) {
|
|
18207
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18208
|
+
}
|
|
18209
|
+
function isLoopback(hostname3) {
|
|
18210
|
+
return hostname3 === "localhost" || hostname3 === "127.0.0.1" || hostname3 === "[::1]";
|
|
18211
|
+
}
|
|
18212
|
+
function normalizeDomain(value, connect) {
|
|
18213
|
+
if (value.length > 512 || /[;\r\n'"\s]/u.test(value)) return void 0;
|
|
18214
|
+
try {
|
|
18215
|
+
const parsed = new URL(value);
|
|
18216
|
+
const secure = parsed.protocol === "https:" || connect && parsed.protocol === "wss:";
|
|
18217
|
+
const local = isLoopback(parsed.hostname) && (parsed.protocol === "http:" || connect && parsed.protocol === "ws:");
|
|
18218
|
+
if (!secure && !local) return void 0;
|
|
18219
|
+
if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
18220
|
+
return void 0;
|
|
18221
|
+
}
|
|
18222
|
+
return parsed.origin;
|
|
18223
|
+
} catch {
|
|
18224
|
+
return void 0;
|
|
18225
|
+
}
|
|
18226
|
+
}
|
|
18227
|
+
function normalizeDomains(values, connect) {
|
|
18228
|
+
const normalized = [
|
|
18229
|
+
...new Set(
|
|
18230
|
+
values.slice(0, MAX_DOMAINS_PER_DIRECTIVE).map((value) => typeof value === "string" ? normalizeDomain(value, connect) : void 0).filter((value) => value !== void 0)
|
|
18231
|
+
)
|
|
18232
|
+
];
|
|
18233
|
+
return normalized;
|
|
18234
|
+
}
|
|
18235
|
+
function resolveAssistantAppSandboxPolicy(resourceMeta) {
|
|
18236
|
+
if (!isRecord(resourceMeta)) return {};
|
|
18237
|
+
const rawCsp = resourceMeta.csp;
|
|
18238
|
+
const csp = isRecord(rawCsp) ? {
|
|
18239
|
+
...Array.isArray(rawCsp.connectDomains) ? { connectDomains: normalizeDomains(rawCsp.connectDomains, true) } : {},
|
|
18240
|
+
...Array.isArray(rawCsp.resourceDomains) ? { resourceDomains: normalizeDomains(rawCsp.resourceDomains, false) } : {},
|
|
18241
|
+
...Array.isArray(rawCsp.frameDomains) ? { frameDomains: normalizeDomains(rawCsp.frameDomains, false) } : {},
|
|
18242
|
+
...Array.isArray(rawCsp.baseUriDomains) ? { baseUriDomains: normalizeDomains(rawCsp.baseUriDomains, false) } : {}
|
|
18243
|
+
} : void 0;
|
|
18244
|
+
const rawPermissions = resourceMeta.permissions;
|
|
18245
|
+
const permissions = isRecord(rawPermissions) ? {
|
|
18246
|
+
...isRecord(rawPermissions.camera) ? { camera: {} } : {},
|
|
18247
|
+
...isRecord(rawPermissions.microphone) ? { microphone: {} } : {},
|
|
18248
|
+
...isRecord(rawPermissions.geolocation) ? { geolocation: {} } : {},
|
|
18249
|
+
...isRecord(rawPermissions.clipboardWrite) ? { clipboardWrite: {} } : {}
|
|
18250
|
+
} : void 0;
|
|
18251
|
+
const allowAttribute = AZ(permissions);
|
|
18252
|
+
return {
|
|
18253
|
+
...csp === void 0 ? {} : { csp },
|
|
18254
|
+
...permissions === void 0 ? {} : { permissions },
|
|
18255
|
+
...allowAttribute ? { allowAttribute } : {}
|
|
18256
|
+
};
|
|
18257
|
+
}
|
|
18258
|
+
function assistantAppSandboxUrl(sandboxUrl, policy) {
|
|
18259
|
+
const parsed = new URL(sandboxUrl);
|
|
18260
|
+
if (policy.csp) parsed.searchParams.set("csp", JSON.stringify(policy.csp));
|
|
18261
|
+
const parentOrigin = globalThis.location?.origin;
|
|
18262
|
+
if (parentOrigin && parentOrigin !== "null") {
|
|
18263
|
+
parsed.hash = new URLSearchParams({ parentOrigin }).toString();
|
|
18264
|
+
}
|
|
18265
|
+
return parsed.href;
|
|
18266
|
+
}
|
|
18267
|
+
|
|
18195
18268
|
// src/app-host.ts
|
|
18196
|
-
var PROXY_DOCUMENT = `<!doctype html><meta charset="utf-8"><style>html,body,iframe{border:0;margin:0;width:100%;height:100%;overflow:hidden}body{background:transparent}</style><script>
|
|
18197
|
-
let inner;
|
|
18269
|
+
var PROXY_DOCUMENT = `<!doctype html><meta charset="utf-8"><style>html,body,iframe{border:0;margin:0;width:100%;height:100%;overflow:hidden}body{background:transparent}</style><body><script>
|
|
18270
|
+
let inner,loaded=false;
|
|
18271
|
+
if(self===top)throw new Error('The assistant sandbox relay must be embedded');
|
|
18272
|
+
const standardSandbox='allow-scripts allow-same-origin allow-forms';
|
|
18273
|
+
const rawParentOrigin=new URLSearchParams(location.hash.slice(1)).get('parentOrigin');
|
|
18274
|
+
let expectedParentOrigin;
|
|
18275
|
+
try{const parsed=new URL(rawParentOrigin||'');if(parsed.origin===rawParentOrigin&&/^https?:$/.test(parsed.protocol))expectedParentOrigin=parsed.origin}catch{}
|
|
18276
|
+
const parentTarget=expectedParentOrigin||'*';
|
|
18277
|
+
const ownOrigin=self.origin;
|
|
18198
18278
|
const ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};
|
|
18199
18279
|
// Re-announce until the host acknowledges with the resource: when this document is hosted
|
|
18200
18280
|
// cross-origin, the first announcement can arrive before the host's load handler is listening.
|
|
18201
|
-
const announce=setInterval(()=>parent.postMessage(ready,
|
|
18281
|
+
const announce=setInterval(()=>parent.postMessage(ready,parentTarget),120);
|
|
18202
18282
|
setTimeout(()=>clearInterval(announce),15000);
|
|
18203
18283
|
addEventListener('message',(event)=>{
|
|
18204
18284
|
if(event.source===parent){
|
|
18285
|
+
if(expectedParentOrigin&&event.origin!==expectedParentOrigin)return;
|
|
18205
18286
|
const message=event.data;
|
|
18206
18287
|
if(message?.method==='ui/notifications/sandbox-resource-ready'){
|
|
18207
18288
|
clearInterval(announce);
|
|
18208
|
-
if(
|
|
18289
|
+
if(loaded)return;
|
|
18290
|
+
loaded=true;
|
|
18291
|
+
const requestedSandbox=message.params?.sandbox;
|
|
18209
18292
|
inner=document.createElement('iframe');
|
|
18210
|
-
inner.setAttribute('sandbox',
|
|
18293
|
+
inner.setAttribute('sandbox',requestedSandbox===standardSandbox?standardSandbox:'allow-scripts');
|
|
18211
18294
|
inner.setAttribute('referrerpolicy','no-referrer');
|
|
18212
|
-
|
|
18213
|
-
|
|
18295
|
+
const permissions=message.params?.permissions;
|
|
18296
|
+
const allow=[];
|
|
18297
|
+
if(permissions?.camera)allow.push('camera');
|
|
18298
|
+
if(permissions?.microphone)allow.push('microphone');
|
|
18299
|
+
if(permissions?.geolocation)allow.push('geolocation');
|
|
18300
|
+
if(permissions?.clipboardWrite)allow.push('clipboard-write');
|
|
18301
|
+
if(allow.length)inner.setAttribute('allow',allow.join('; '));
|
|
18302
|
+
document.body.append(inner);
|
|
18303
|
+
let html=String(message.params?.html||'');
|
|
18304
|
+
const csp=message.params?.csp;
|
|
18305
|
+
if(csp&&typeof csp==='object'){
|
|
18306
|
+
const list=(value,fallback)=>Array.isArray(value)&&value.length?value.join(' '):fallback;
|
|
18307
|
+
const resources=Array.isArray(csp.resourceDomains)?csp.resourceDomains:[];
|
|
18308
|
+
const suffix=resources.length?' '+resources.join(' '):'';
|
|
18309
|
+
const policy=["default-src 'none'","script-src 'unsafe-inline'"+suffix,"style-src 'unsafe-inline'"+suffix,'img-src data: blob:'+suffix,'font-src data: blob:'+suffix,'media-src data: blob:'+suffix,'worker-src blob:'+suffix,'connect-src '+list(csp.connectDomains,"'none'"),'frame-src '+list(csp.frameDomains,"'none'"),"object-src 'none'","form-action 'none'",'base-uri '+list(csp.baseUriDomains,"'none'")].join('; ');
|
|
18310
|
+
const meta='<meta http-equiv="Content-Security-Policy" content="'+policy.replace(/&/g,'&').replace(/"/g,'"')+'">';
|
|
18311
|
+
const doctype=html.match(/^\\s*<!doctype[^>]*>/i);
|
|
18312
|
+
html=doctype?html.slice(0,doctype[0].length)+meta+html.slice(doctype[0].length):meta+html;
|
|
18313
|
+
}
|
|
18314
|
+
try{
|
|
18315
|
+
const doc=inner.contentDocument||inner.contentWindow?.document;
|
|
18316
|
+
if(!doc)throw new Error('document unavailable');
|
|
18317
|
+
doc.open();doc.write(html);doc.close();
|
|
18318
|
+
}catch{inner.srcdoc=html}
|
|
18214
18319
|
return;
|
|
18215
18320
|
}
|
|
18216
18321
|
inner?.contentWindow?.postMessage(message,'*');
|
|
18217
18322
|
} else if(inner && event.source===inner.contentWindow) {
|
|
18218
|
-
|
|
18323
|
+
if(event.origin!==ownOrigin)return;
|
|
18324
|
+
parent.postMessage(event.data,parentTarget);
|
|
18219
18325
|
}
|
|
18220
18326
|
});
|
|
18221
|
-
parent.postMessage(ready,
|
|
18222
|
-
</script>`;
|
|
18327
|
+
parent.postMessage(ready,parentTarget);
|
|
18328
|
+
</script></body>`;
|
|
18223
18329
|
function isAllowedAppLink(value, allowedDomains, baseUrl) {
|
|
18224
18330
|
try {
|
|
18225
18331
|
const parsed = new URL(value, baseUrl);
|
|
@@ -18246,16 +18352,42 @@ function createAssistantAppHostContext(theme, allowFullscreen = false, displayMo
|
|
|
18246
18352
|
}
|
|
18247
18353
|
var APP_RENDER_TIMEOUT_MS = 1e4;
|
|
18248
18354
|
var APP_TEARDOWN_TIMEOUT_MS = 1e3;
|
|
18355
|
+
var STANDARD_SANDBOX = "allow-scripts allow-same-origin allow-forms";
|
|
18249
18356
|
var mountedApps = /* @__PURE__ */ new WeakMap();
|
|
18250
18357
|
function resolveSandboxUrl(value) {
|
|
18251
18358
|
if (!value) return void 0;
|
|
18252
18359
|
try {
|
|
18253
18360
|
const parsed = new URL(value);
|
|
18254
|
-
|
|
18361
|
+
const loopback = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
|
|
18362
|
+
const permittedProtocol = parsed.protocol === "https:" || parsed.protocol === "http:" && loopback;
|
|
18363
|
+
const embedderOrigin = globalThis.location?.origin;
|
|
18364
|
+
if (!permittedProtocol || embedderOrigin && embedderOrigin !== "null" && parsed.origin === embedderOrigin) {
|
|
18365
|
+
return void 0;
|
|
18366
|
+
}
|
|
18367
|
+
return parsed.href;
|
|
18255
18368
|
} catch {
|
|
18256
18369
|
return void 0;
|
|
18257
18370
|
}
|
|
18258
18371
|
}
|
|
18372
|
+
function copyAppJson(value, depth = 0) {
|
|
18373
|
+
if (depth > 64) throw new Error("App payload nesting exceeds the host limit");
|
|
18374
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
18375
|
+
if (typeof value === "number") {
|
|
18376
|
+
if (!Number.isFinite(value)) throw new Error("App payload numbers must be finite");
|
|
18377
|
+
return value;
|
|
18378
|
+
}
|
|
18379
|
+
if (Array.isArray(value)) return value.map((entry) => copyAppJson(entry, depth + 1));
|
|
18380
|
+
const copied = {};
|
|
18381
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
18382
|
+
Object.defineProperty(copied, key, {
|
|
18383
|
+
value: copyAppJson(entry, depth + 1),
|
|
18384
|
+
enumerable: true,
|
|
18385
|
+
configurable: true,
|
|
18386
|
+
writable: true
|
|
18387
|
+
});
|
|
18388
|
+
}
|
|
18389
|
+
return copied;
|
|
18390
|
+
}
|
|
18259
18391
|
function mountAssistantApp(detail, actions, options = {}) {
|
|
18260
18392
|
if (!detail.html) return void 0;
|
|
18261
18393
|
const card = document.createElement("section");
|
|
@@ -18276,16 +18408,25 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18276
18408
|
const frame = document.createElement("iframe");
|
|
18277
18409
|
frame.className = "noodle-app-frame";
|
|
18278
18410
|
frame.title = detail.title ?? detail.tool;
|
|
18279
|
-
|
|
18411
|
+
const policy = resolveAssistantAppSandboxPolicy(detail.resourceMeta);
|
|
18280
18412
|
frame.setAttribute("referrerpolicy", "no-referrer");
|
|
18281
18413
|
const sandboxUrl = resolveSandboxUrl(options.sandboxUrl);
|
|
18282
|
-
|
|
18283
|
-
|
|
18414
|
+
const appliedPolicy = sandboxUrl ? policy : { csp: policy.csp ?? {} };
|
|
18415
|
+
if (sandboxUrl) {
|
|
18416
|
+
frame.setAttribute("sandbox", STANDARD_SANDBOX);
|
|
18417
|
+
if (policy.allowAttribute) frame.setAttribute("allow", policy.allowAttribute);
|
|
18418
|
+
frame.src = assistantAppSandboxUrl(sandboxUrl, policy);
|
|
18419
|
+
} else {
|
|
18420
|
+
frame.setAttribute("sandbox", "allow-scripts");
|
|
18421
|
+
frame.srcdoc = PROXY_DOCUMENT;
|
|
18422
|
+
}
|
|
18284
18423
|
card.append(frame);
|
|
18285
18424
|
let mountedBridge;
|
|
18286
18425
|
let mountedTransport;
|
|
18287
18426
|
let destroyed = false;
|
|
18288
18427
|
let initialized = false;
|
|
18428
|
+
let renderFailed = false;
|
|
18429
|
+
let renderStage = "sandbox_load";
|
|
18289
18430
|
let currentTheme = actions.theme;
|
|
18290
18431
|
let displayMode = "inline";
|
|
18291
18432
|
let hostContext = createAssistantAppHostContext(
|
|
@@ -18294,6 +18435,15 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18294
18435
|
displayMode
|
|
18295
18436
|
);
|
|
18296
18437
|
let destroyPromise;
|
|
18438
|
+
let transportClosePromise;
|
|
18439
|
+
let renderTimer;
|
|
18440
|
+
const closeTransport = () => {
|
|
18441
|
+
if (transportClosePromise) return transportClosePromise;
|
|
18442
|
+
const closing = mountedTransport?.close().catch(() => {
|
|
18443
|
+
}) ?? Promise.resolve();
|
|
18444
|
+
transportClosePromise = closing;
|
|
18445
|
+
return closing;
|
|
18446
|
+
};
|
|
18297
18447
|
const applyDisplayMode = (requestedMode) => {
|
|
18298
18448
|
displayMode = requestedMode === "fullscreen" && options.allowFullscreen === true ? "fullscreen" : "inline";
|
|
18299
18449
|
card.toggleAttribute("data-fullscreen", displayMode === "fullscreen");
|
|
@@ -18311,18 +18461,26 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18311
18461
|
applyDisplayMode("inline");
|
|
18312
18462
|
frame.focus();
|
|
18313
18463
|
});
|
|
18314
|
-
const
|
|
18315
|
-
if (initialized || !frame.isConnected)
|
|
18464
|
+
const showRenderFailure = (code) => {
|
|
18465
|
+
if (code === "view_render_timeout" && initialized || destroyed || renderFailed || !frame.isConnected)
|
|
18466
|
+
return;
|
|
18467
|
+
renderFailed = true;
|
|
18468
|
+
clearTimeout(renderTimer);
|
|
18316
18469
|
const note = document.createElement("div");
|
|
18317
18470
|
note.className = "noodle-app-fallback";
|
|
18318
18471
|
note.textContent = "This app view could not be displayed.";
|
|
18319
18472
|
frame.replaceWith(note);
|
|
18320
|
-
options.onRenderFailure?.({ code
|
|
18473
|
+
options.onRenderFailure?.({ code, retryable: false, stage: renderStage });
|
|
18474
|
+
void closeTransport();
|
|
18475
|
+
};
|
|
18476
|
+
renderTimer = setTimeout(() => {
|
|
18477
|
+
showRenderFailure("view_render_timeout");
|
|
18321
18478
|
}, APP_RENDER_TIMEOUT_MS);
|
|
18322
18479
|
frame.addEventListener(
|
|
18323
18480
|
"load",
|
|
18324
18481
|
() => {
|
|
18325
|
-
if (destroyed) return;
|
|
18482
|
+
if (destroyed || renderFailed) return;
|
|
18483
|
+
renderStage = "sandbox_proxy";
|
|
18326
18484
|
const target = frame.contentWindow;
|
|
18327
18485
|
if (!target) return;
|
|
18328
18486
|
const bridge = new DX(
|
|
@@ -18333,7 +18491,11 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18333
18491
|
serverTools: {},
|
|
18334
18492
|
serverResources: {},
|
|
18335
18493
|
updateModelContext: { text: {}, structuredContent: {} },
|
|
18336
|
-
message: { text: {} }
|
|
18494
|
+
message: { text: {} },
|
|
18495
|
+
sandbox: {
|
|
18496
|
+
...appliedPolicy.csp === void 0 ? {} : { csp: appliedPolicy.csp },
|
|
18497
|
+
...appliedPolicy.permissions === void 0 ? {} : { permissions: appliedPolicy.permissions }
|
|
18498
|
+
}
|
|
18337
18499
|
},
|
|
18338
18500
|
{
|
|
18339
18501
|
hostContext
|
|
@@ -18341,13 +18503,15 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18341
18503
|
);
|
|
18342
18504
|
mountedBridge = bridge;
|
|
18343
18505
|
bridge.onsandboxready = () => {
|
|
18344
|
-
if (destroyed) return;
|
|
18506
|
+
if (destroyed || renderFailed) return;
|
|
18507
|
+
renderStage = "app_initialize";
|
|
18345
18508
|
const current = options.getDetail?.() ?? detail;
|
|
18346
18509
|
void bridge.sendSandboxResourceReady({
|
|
18347
18510
|
html: current.html,
|
|
18348
|
-
sandbox: "allow-scripts",
|
|
18349
|
-
...
|
|
18350
|
-
|
|
18511
|
+
sandbox: sandboxUrl ? STANDARD_SANDBOX : "allow-scripts",
|
|
18512
|
+
...appliedPolicy.csp === void 0 ? {} : { csp: appliedPolicy.csp },
|
|
18513
|
+
...appliedPolicy.permissions === void 0 ? {} : { permissions: appliedPolicy.permissions }
|
|
18514
|
+
}).catch(() => showRenderFailure("view_bridge_error"));
|
|
18351
18515
|
};
|
|
18352
18516
|
bridge.onsizechange = ({ height }) => {
|
|
18353
18517
|
if (destroyed) return;
|
|
@@ -18411,21 +18575,30 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18411
18575
|
return await actions.client.requestApp("resources/read", params);
|
|
18412
18576
|
};
|
|
18413
18577
|
bridge.oninitialized = () => {
|
|
18414
|
-
if (destroyed) return;
|
|
18415
|
-
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18421
|
-
|
|
18422
|
-
|
|
18423
|
-
|
|
18578
|
+
if (destroyed || renderFailed) return;
|
|
18579
|
+
try {
|
|
18580
|
+
const current = options.getDetail?.() ?? detail;
|
|
18581
|
+
const copiedArguments = copyAppJson(current.arguments ?? {});
|
|
18582
|
+
const copiedResult = copyAppJson(current.result);
|
|
18583
|
+
const argumentsRecord = typeof copiedArguments === "object" && copiedArguments !== null && !Array.isArray(copiedArguments) ? copiedArguments : {};
|
|
18584
|
+
initialized = true;
|
|
18585
|
+
clearTimeout(renderTimer);
|
|
18586
|
+
bridge.setHostContext(hostContext);
|
|
18587
|
+
void Promise.all([
|
|
18588
|
+
bridge.sendToolInput({ arguments: argumentsRecord }),
|
|
18589
|
+
bridge.sendToolResult({
|
|
18590
|
+
content: [{ type: "text", text: JSON.stringify(copiedResult) }],
|
|
18591
|
+
...typeof copiedResult === "object" && copiedResult !== null && !Array.isArray(copiedResult) ? { structuredContent: copiedResult } : {}
|
|
18592
|
+
})
|
|
18593
|
+
]).catch(() => showRenderFailure("view_bridge_error"));
|
|
18594
|
+
} catch {
|
|
18595
|
+
showRenderFailure("view_bridge_error");
|
|
18596
|
+
}
|
|
18424
18597
|
};
|
|
18425
18598
|
const transport = new E(target, target);
|
|
18426
18599
|
mountedTransport = transport;
|
|
18427
18600
|
void bridge.connect(transport).catch(() => {
|
|
18428
|
-
|
|
18601
|
+
showRenderFailure("view_bridge_error");
|
|
18429
18602
|
});
|
|
18430
18603
|
},
|
|
18431
18604
|
{ once: true }
|
|
@@ -18451,8 +18624,7 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
18451
18624
|
}
|
|
18452
18625
|
} catch {
|
|
18453
18626
|
}
|
|
18454
|
-
await
|
|
18455
|
-
});
|
|
18627
|
+
await closeTransport();
|
|
18456
18628
|
card.remove();
|
|
18457
18629
|
mountedApps.delete(card);
|
|
18458
18630
|
})();
|