@noodleseed/assistant 1.6.0 → 1.7.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 CHANGED
@@ -32,6 +32,11 @@ The full, always-current integration guide (access modes, session response contr
32
32
  troubleshooting) lives at <https://docs.noodleseed.dev/guides/embedded-assistant>. Authoring and deploying
33
33
  the server itself is `@noodleseed/one` (`npm install -g @noodleseed/one`).
34
34
 
35
+ If your page sends a `Content-Security-Policy`, allow the Noodle service origin in `connect-src` (turns and
36
+ event streams) **and** `frame-src` (app widgets render inside a hosted sandbox document served from the
37
+ service origin, `endpoints.sandbox`, so your `script-src` can stay strict). Details are in the guide's
38
+ "Content-Security-Policy on your page" section.
39
+
35
40
  ## Quick start
36
41
 
37
42
  Install the package in the customer web application with that application's existing package manager. For
@@ -2,7 +2,7 @@ import {
2
2
  AssistantClientError,
3
3
  copyAssistantModelContext,
4
4
  createAssistantClient
5
- } from "./chunk-GTYYQNGT.js";
5
+ } from "./chunk-7E5HINN4.js";
6
6
  import {
7
7
  _enum,
8
8
  _null,
@@ -3022,10 +3022,17 @@ function GX(X2, Y) {
3022
3022
  // src/app-host.ts
3023
3023
  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>
3024
3024
  let inner;
3025
+ const ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};
3026
+ // Re-announce until the host acknowledges with the resource: when this document is hosted
3027
+ // cross-origin, the first announcement can arrive before the host's load handler is listening.
3028
+ const announce=setInterval(()=>parent.postMessage(ready,'*'),120);
3029
+ setTimeout(()=>clearInterval(announce),15000);
3025
3030
  addEventListener('message',(event)=>{
3026
3031
  if(event.source===parent){
3027
3032
  const message=event.data;
3028
3033
  if(message?.method==='ui/notifications/sandbox-resource-ready'){
3034
+ clearInterval(announce);
3035
+ if(inner)return;
3029
3036
  inner=document.createElement('iframe');
3030
3037
  inner.setAttribute('sandbox',message.params?.sandbox||'allow-scripts');
3031
3038
  inner.setAttribute('referrerpolicy','no-referrer');
@@ -3038,7 +3045,7 @@ addEventListener('message',(event)=>{
3038
3045
  parent.postMessage(event.data,'*');
3039
3046
  }
3040
3047
  });
3041
- parent.postMessage({jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}},'*');
3048
+ parent.postMessage(ready,'*');
3042
3049
  </script>`;
3043
3050
  function isAllowedAppLink(value, allowedDomains, baseUrl) {
3044
3051
  try {
@@ -3056,7 +3063,17 @@ function isAllowedAppLink(value, allowedDomains, baseUrl) {
3056
3063
  return void 0;
3057
3064
  }
3058
3065
  }
3059
- function mountAssistantApp(detail, actions) {
3066
+ var APP_RENDER_TIMEOUT_MS = 1e4;
3067
+ function resolveSandboxUrl(value) {
3068
+ if (!value) return void 0;
3069
+ try {
3070
+ const parsed = new URL(value);
3071
+ return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed.href : void 0;
3072
+ } catch {
3073
+ return void 0;
3074
+ }
3075
+ }
3076
+ function mountAssistantApp(detail, actions, options = {}) {
3060
3077
  if (!detail.html) return void 0;
3061
3078
  const card = document.createElement("section");
3062
3079
  card.className = "noodle-app-card";
@@ -3071,8 +3088,19 @@ function mountAssistantApp(detail, actions) {
3071
3088
  frame.title = detail.title ?? detail.tool;
3072
3089
  frame.setAttribute("sandbox", "allow-scripts");
3073
3090
  frame.setAttribute("referrerpolicy", "no-referrer");
3074
- frame.srcdoc = PROXY_DOCUMENT;
3091
+ const sandboxUrl = resolveSandboxUrl(options.sandboxUrl);
3092
+ if (sandboxUrl) frame.src = sandboxUrl;
3093
+ else frame.srcdoc = PROXY_DOCUMENT;
3075
3094
  card.append(frame);
3095
+ let initialized = false;
3096
+ const renderTimer = setTimeout(() => {
3097
+ if (initialized || !frame.isConnected) return;
3098
+ const note = document.createElement("div");
3099
+ note.className = "noodle-app-fallback";
3100
+ note.textContent = "This app view could not be displayed.";
3101
+ frame.replaceWith(note);
3102
+ options.onRenderFailure?.({ code: "view_render_timeout", retryable: false });
3103
+ }, APP_RENDER_TIMEOUT_MS);
3076
3104
  frame.addEventListener(
3077
3105
  "load",
3078
3106
  () => {
@@ -3138,13 +3166,18 @@ function mountAssistantApp(detail, actions) {
3138
3166
  bridge.onlistresources = async (params) => await actions.client.requestApp("resources/list", params ?? {});
3139
3167
  bridge.onreadresource = async (params) => await actions.client.requestApp("resources/read", params);
3140
3168
  bridge.oninitialized = () => {
3169
+ initialized = true;
3170
+ clearTimeout(renderTimer);
3141
3171
  void bridge.sendToolInput({ arguments: detail.arguments ?? {} });
3142
3172
  void bridge.sendToolResult({
3143
3173
  content: [{ type: "text", text: JSON.stringify(detail.result) }],
3144
3174
  ...typeof detail.result === "object" && detail.result !== null && !Array.isArray(detail.result) ? { structuredContent: detail.result } : {}
3145
3175
  });
3146
3176
  };
3147
- void bridge.connect(new E(target, target)).catch(() => card.remove());
3177
+ void bridge.connect(new E(target, target)).catch(() => {
3178
+ clearTimeout(renderTimer);
3179
+ card.remove();
3180
+ });
3148
3181
  },
3149
3182
  { once: true }
3150
3183
  );
@@ -6871,6 +6904,7 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
6871
6904
  .noodle-app-card { position: relative; display: grid; gap: 8px; width: 100%; margin: 4px 0 16px; overflow: hidden; color: var(--ns-assistant-app-text, inherit); border: 1px solid var(--ns-assistant-app-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: var(--ns-assistant-app, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); }
6872
6905
  .noodle-app-title { padding: 10px 12px 0; font-size: .86em; font-weight: 650; }
6873
6906
  .noodle-app-frame { display: block; width: 100%; min-height: 120px; border: 0; background: transparent; }
6907
+ .noodle-app-fallback { padding: 14px 16px; font-size: 13px; color: var(--ns-assistant-muted, var(--ns-assistant-default-muted, #6b7280)); }
6874
6908
  .noodle-app-card[data-fullscreen] { position: fixed; inset: 0; z-index: 2; margin: 0; border-radius: 0; background: var(--ns-assistant-panel, var(--ns-assistant-default-panel)); }
6875
6909
  .noodle-app-card[data-fullscreen] .noodle-app-frame { height: 100% !important; }
6876
6910
  form { display: flex; flex: 0 0 auto; gap: 8px; align-items: center; min-height: 56px; margin: 8px 16px 16px; padding: 6px 6px 6px 16px; color: var(--ns-assistant-composer-text, inherit); border: 1px solid var(--ns-assistant-composer-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-input-radius, 14px); background: var(--ns-assistant-composer, var(--ns-assistant-input, var(--ns-assistant-default-input))); box-shadow: 0 1px 2px rgba(0,0,0,.04); transition: border-color .15s ease, box-shadow .15s ease; }
@@ -7436,11 +7470,19 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7436
7470
  const messages = this.#messages;
7437
7471
  const client = this.#client;
7438
7472
  if (!messages || !client) return;
7439
- const view = mountAssistantApp(detail, {
7440
- client,
7441
- sendMessage: (message) => this.sendMessage(message),
7442
- updateModelContext: (update) => this.updateModelContext(update)
7443
- });
7473
+ const sandboxUrl = client.appSandboxUrl?.();
7474
+ const view = mountAssistantApp(
7475
+ detail,
7476
+ {
7477
+ client,
7478
+ sendMessage: (message) => this.sendMessage(message),
7479
+ updateModelContext: (update) => this.updateModelContext(update)
7480
+ },
7481
+ {
7482
+ ...sandboxUrl === void 0 ? {} : { sandboxUrl },
7483
+ onRenderFailure: (failure) => this.#dispatchError(failure)
7484
+ }
7485
+ );
7444
7486
  if (!view) return;
7445
7487
  messages.append(view);
7446
7488
  this.setAttribute("has-messages", "");
@@ -7999,4 +8041,4 @@ export {
7999
8041
  dompurify/dist/purify.es.mjs:
8000
8042
  (*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE *)
8001
8043
  */
8002
- //# sourceMappingURL=chunk-ZKXOX67J.js.map
8044
+ //# sourceMappingURL=chunk-35A7REQX.js.map