@noodleseed/assistant 1.23.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.
Files changed (50) hide show
  1. package/README.md +4 -9
  2. package/dist/{app-view-element-7ZQZLYbL.d.ts → app-view-element-9ojWWYov.d.cts} +3 -2
  3. package/dist/{app-view-element-1i_w6FDf.d.cts → app-view-element-BM4lral-.d.ts} +3 -2
  4. package/dist/app-view.cjs +213 -41
  5. package/dist/app-view.cjs.map +1 -1
  6. package/dist/app-view.d.cts +3 -3
  7. package/dist/app-view.d.ts +3 -3
  8. package/dist/app-view.js +2 -2
  9. package/dist/{appearance-Cc-eqSbp.d.cts → appearance-B6vSg8Ub.d.cts} +5 -0
  10. package/dist/{appearance-Cc-eqSbp.d.ts → appearance-B6vSg8Ub.d.ts} +5 -0
  11. package/dist/{chunk-IBDP6PXQ.js → chunk-7BQHW37I.js} +71 -3
  12. package/dist/chunk-7BQHW37I.js.map +1 -0
  13. package/dist/{chunk-5NBHHNI3.js → chunk-LNBUYHL7.js} +2 -2
  14. package/dist/chunk-LNBUYHL7.js.map +1 -0
  15. package/dist/{chunk-XKTUXFGX.js → chunk-P32MAR4K.js} +214 -42
  16. package/dist/chunk-P32MAR4K.js.map +1 -0
  17. package/dist/{chunk-X7UQDIGS.js → chunk-SGMECNNU.js} +94 -43
  18. package/dist/chunk-SGMECNNU.js.map +1 -0
  19. package/dist/{client-Bp_VKpMg.d.ts → client-BkogON2A.d.ts} +11 -1
  20. package/dist/{client-uo57Swlm.d.cts → client-C07ppTU_.d.cts} +11 -1
  21. package/dist/client.cjs +70 -2
  22. package/dist/client.cjs.map +1 -1
  23. package/dist/client.d.cts +2 -2
  24. package/dist/client.d.ts +2 -2
  25. package/dist/client.js +1 -1
  26. package/dist/embed.global.js +149 -114
  27. package/dist/index.cjs +396 -105
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +3 -3
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.js +3 -3
  32. package/dist/react/client.cjs +70 -2
  33. package/dist/react/client.cjs.map +1 -1
  34. package/dist/react/client.d.cts +2 -2
  35. package/dist/react/client.d.ts +2 -2
  36. package/dist/react/client.js +1 -1
  37. package/dist/react.cjs +396 -105
  38. package/dist/react.cjs.map +1 -1
  39. package/dist/react.d.cts +3 -3
  40. package/dist/react.d.ts +3 -3
  41. package/dist/react.js +4 -4
  42. package/dist/server.cjs.map +1 -1
  43. package/dist/server.d.cts +3 -1
  44. package/dist/server.d.ts +3 -1
  45. package/dist/server.js.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/chunk-5NBHHNI3.js.map +0 -1
  48. package/dist/chunk-IBDP6PXQ.js.map +0 -1
  49. package/dist/chunk-X7UQDIGS.js.map +0 -1
  50. package/dist/chunk-XKTUXFGX.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -15197,7 +15197,7 @@ __export(src_exports, {
15197
15197
  });
15198
15198
  module.exports = __toCommonJS(src_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";
@@ -20594,7 +20594,7 @@ var pipelineType = ZodPipeline.create;
20594
20594
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/parse.js
20595
20595
  init_core2();
20596
20596
 
20597
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
20597
+ // ../../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
20598
20598
  function isZ4Schema(s2) {
20599
20599
  const schema = s2;
20600
20600
  return !!schema._zod;
@@ -20657,7 +20657,7 @@ function getLiteralValue(schema) {
20657
20657
  return void 0;
20658
20658
  }
20659
20659
 
20660
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
20660
+ // ../../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
20661
20661
  function isTerminal(status) {
20662
20662
  return status === "completed" || status === "failed" || status === "cancelled";
20663
20663
  }
@@ -20665,7 +20665,7 @@ function isTerminal(status) {
20665
20665
  // ../../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
20666
20666
  var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
20667
20667
 
20668
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
20668
+ // ../../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
20669
20669
  function getMethodLiteral(schema) {
20670
20670
  const shape = getObjectShape(schema);
20671
20671
  const methodSchema = shape?.method;
@@ -20686,7 +20686,7 @@ function parseWithCompat(schema, data) {
20686
20686
  return result.data;
20687
20687
  }
20688
20688
 
20689
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
20689
+ // ../../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
20690
20690
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
20691
20691
  var Protocol = class {
20692
20692
  constructor(_options) {
@@ -21798,6 +21798,15 @@ var E = class {
21798
21798
  sessionId;
21799
21799
  setProtocolVersion;
21800
21800
  };
21801
+ function AZ(X2) {
21802
+ if (!X2) return "";
21803
+ let Y = [];
21804
+ if (X2.camera) Y.push("camera");
21805
+ if (X2.microphone) Y.push("microphone");
21806
+ if (X2.geolocation) Y.push("geolocation");
21807
+ if (X2.clipboardWrite) Y.push("clipboard-write");
21808
+ return Y.join("; ");
21809
+ }
21801
21810
  var KX = [j];
21802
21811
  var DX = class extends _ {
21803
21812
  _client;
@@ -22068,34 +22077,131 @@ function GX(X2, Y) {
22068
22077
  return JSON.stringify(X2) === JSON.stringify(Y);
22069
22078
  }
22070
22079
 
22080
+ // src/app-sandbox-policy.ts
22081
+ var MAX_DOMAINS_PER_DIRECTIVE = 32;
22082
+ function isRecord(value) {
22083
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22084
+ }
22085
+ function isLoopback(hostname3) {
22086
+ return hostname3 === "localhost" || hostname3 === "127.0.0.1" || hostname3 === "[::1]";
22087
+ }
22088
+ function normalizeDomain(value, connect) {
22089
+ if (value.length > 512 || /[;\r\n'"\s]/u.test(value)) return void 0;
22090
+ try {
22091
+ const parsed = new URL(value);
22092
+ const secure = parsed.protocol === "https:" || connect && parsed.protocol === "wss:";
22093
+ const local = isLoopback(parsed.hostname) && (parsed.protocol === "http:" || connect && parsed.protocol === "ws:");
22094
+ if (!secure && !local) return void 0;
22095
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
22096
+ return void 0;
22097
+ }
22098
+ return parsed.origin;
22099
+ } catch {
22100
+ return void 0;
22101
+ }
22102
+ }
22103
+ function normalizeDomains(values, connect) {
22104
+ const normalized = [
22105
+ ...new Set(
22106
+ values.slice(0, MAX_DOMAINS_PER_DIRECTIVE).map((value) => typeof value === "string" ? normalizeDomain(value, connect) : void 0).filter((value) => value !== void 0)
22107
+ )
22108
+ ];
22109
+ return normalized;
22110
+ }
22111
+ function resolveAssistantAppSandboxPolicy(resourceMeta) {
22112
+ if (!isRecord(resourceMeta)) return {};
22113
+ const rawCsp = resourceMeta.csp;
22114
+ const csp = isRecord(rawCsp) ? {
22115
+ ...Array.isArray(rawCsp.connectDomains) ? { connectDomains: normalizeDomains(rawCsp.connectDomains, true) } : {},
22116
+ ...Array.isArray(rawCsp.resourceDomains) ? { resourceDomains: normalizeDomains(rawCsp.resourceDomains, false) } : {},
22117
+ ...Array.isArray(rawCsp.frameDomains) ? { frameDomains: normalizeDomains(rawCsp.frameDomains, false) } : {},
22118
+ ...Array.isArray(rawCsp.baseUriDomains) ? { baseUriDomains: normalizeDomains(rawCsp.baseUriDomains, false) } : {}
22119
+ } : void 0;
22120
+ const rawPermissions = resourceMeta.permissions;
22121
+ const permissions = isRecord(rawPermissions) ? {
22122
+ ...isRecord(rawPermissions.camera) ? { camera: {} } : {},
22123
+ ...isRecord(rawPermissions.microphone) ? { microphone: {} } : {},
22124
+ ...isRecord(rawPermissions.geolocation) ? { geolocation: {} } : {},
22125
+ ...isRecord(rawPermissions.clipboardWrite) ? { clipboardWrite: {} } : {}
22126
+ } : void 0;
22127
+ const allowAttribute = AZ(permissions);
22128
+ return {
22129
+ ...csp === void 0 ? {} : { csp },
22130
+ ...permissions === void 0 ? {} : { permissions },
22131
+ ...allowAttribute ? { allowAttribute } : {}
22132
+ };
22133
+ }
22134
+ function assistantAppSandboxUrl(sandboxUrl, policy) {
22135
+ const parsed = new URL(sandboxUrl);
22136
+ if (policy.csp) parsed.searchParams.set("csp", JSON.stringify(policy.csp));
22137
+ const parentOrigin = globalThis.location?.origin;
22138
+ if (parentOrigin && parentOrigin !== "null") {
22139
+ parsed.hash = new URLSearchParams({ parentOrigin }).toString();
22140
+ }
22141
+ return parsed.href;
22142
+ }
22143
+
22071
22144
  // src/app-host.ts
22072
- 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>
22073
- let inner;
22145
+ 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>
22146
+ let inner,loaded=false;
22147
+ if(self===top)throw new Error('The assistant sandbox relay must be embedded');
22148
+ const standardSandbox='allow-scripts allow-same-origin allow-forms';
22149
+ const rawParentOrigin=new URLSearchParams(location.hash.slice(1)).get('parentOrigin');
22150
+ let expectedParentOrigin;
22151
+ try{const parsed=new URL(rawParentOrigin||'');if(parsed.origin===rawParentOrigin&&/^https?:$/.test(parsed.protocol))expectedParentOrigin=parsed.origin}catch{}
22152
+ const parentTarget=expectedParentOrigin||'*';
22153
+ const ownOrigin=self.origin;
22074
22154
  const ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};
22075
22155
  // Re-announce until the host acknowledges with the resource: when this document is hosted
22076
22156
  // cross-origin, the first announcement can arrive before the host's load handler is listening.
22077
- const announce=setInterval(()=>parent.postMessage(ready,'*'),120);
22157
+ const announce=setInterval(()=>parent.postMessage(ready,parentTarget),120);
22078
22158
  setTimeout(()=>clearInterval(announce),15000);
22079
22159
  addEventListener('message',(event)=>{
22080
22160
  if(event.source===parent){
22161
+ if(expectedParentOrigin&&event.origin!==expectedParentOrigin)return;
22081
22162
  const message=event.data;
22082
22163
  if(message?.method==='ui/notifications/sandbox-resource-ready'){
22083
22164
  clearInterval(announce);
22084
- if(inner)return;
22165
+ if(loaded)return;
22166
+ loaded=true;
22167
+ const requestedSandbox=message.params?.sandbox;
22085
22168
  inner=document.createElement('iframe');
22086
- inner.setAttribute('sandbox',message.params?.sandbox||'allow-scripts');
22169
+ inner.setAttribute('sandbox',requestedSandbox===standardSandbox?standardSandbox:'allow-scripts');
22087
22170
  inner.setAttribute('referrerpolicy','no-referrer');
22088
- inner.srcdoc=String(message.params?.html||'');
22089
- document.body.replaceChildren(inner);
22171
+ const permissions=message.params?.permissions;
22172
+ const allow=[];
22173
+ if(permissions?.camera)allow.push('camera');
22174
+ if(permissions?.microphone)allow.push('microphone');
22175
+ if(permissions?.geolocation)allow.push('geolocation');
22176
+ if(permissions?.clipboardWrite)allow.push('clipboard-write');
22177
+ if(allow.length)inner.setAttribute('allow',allow.join('; '));
22178
+ document.body.append(inner);
22179
+ let html=String(message.params?.html||'');
22180
+ const csp=message.params?.csp;
22181
+ if(csp&&typeof csp==='object'){
22182
+ const list=(value,fallback)=>Array.isArray(value)&&value.length?value.join(' '):fallback;
22183
+ const resources=Array.isArray(csp.resourceDomains)?csp.resourceDomains:[];
22184
+ const suffix=resources.length?' '+resources.join(' '):'';
22185
+ 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('; ');
22186
+ const meta='<meta http-equiv="Content-Security-Policy" content="'+policy.replace(/&/g,'&amp;').replace(/"/g,'&quot;')+'">';
22187
+ const doctype=html.match(/^\\s*<!doctype[^>]*>/i);
22188
+ html=doctype?html.slice(0,doctype[0].length)+meta+html.slice(doctype[0].length):meta+html;
22189
+ }
22190
+ try{
22191
+ const doc=inner.contentDocument||inner.contentWindow?.document;
22192
+ if(!doc)throw new Error('document unavailable');
22193
+ doc.open();doc.write(html);doc.close();
22194
+ }catch{inner.srcdoc=html}
22090
22195
  return;
22091
22196
  }
22092
22197
  inner?.contentWindow?.postMessage(message,'*');
22093
22198
  } else if(inner && event.source===inner.contentWindow) {
22094
- parent.postMessage(event.data,'*');
22199
+ if(event.origin!==ownOrigin)return;
22200
+ parent.postMessage(event.data,parentTarget);
22095
22201
  }
22096
22202
  });
22097
- parent.postMessage(ready,'*');
22098
- </script>`;
22203
+ parent.postMessage(ready,parentTarget);
22204
+ </script></body>`;
22099
22205
  function isAllowedAppLink(value, allowedDomains, baseUrl) {
22100
22206
  try {
22101
22207
  const parsed = new URL(value, baseUrl);
@@ -22122,16 +22228,42 @@ function createAssistantAppHostContext(theme, allowFullscreen = false, displayMo
22122
22228
  }
22123
22229
  var APP_RENDER_TIMEOUT_MS = 1e4;
22124
22230
  var APP_TEARDOWN_TIMEOUT_MS = 1e3;
22231
+ var STANDARD_SANDBOX = "allow-scripts allow-same-origin allow-forms";
22125
22232
  var mountedApps = /* @__PURE__ */ new WeakMap();
22126
22233
  function resolveSandboxUrl(value) {
22127
22234
  if (!value) return void 0;
22128
22235
  try {
22129
22236
  const parsed = new URL(value);
22130
- return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed.href : void 0;
22237
+ const loopback = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
22238
+ const permittedProtocol = parsed.protocol === "https:" || parsed.protocol === "http:" && loopback;
22239
+ const embedderOrigin = globalThis.location?.origin;
22240
+ if (!permittedProtocol || embedderOrigin && embedderOrigin !== "null" && parsed.origin === embedderOrigin) {
22241
+ return void 0;
22242
+ }
22243
+ return parsed.href;
22131
22244
  } catch {
22132
22245
  return void 0;
22133
22246
  }
22134
22247
  }
22248
+ function copyAppJson(value, depth = 0) {
22249
+ if (depth > 64) throw new Error("App payload nesting exceeds the host limit");
22250
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
22251
+ if (typeof value === "number") {
22252
+ if (!Number.isFinite(value)) throw new Error("App payload numbers must be finite");
22253
+ return value;
22254
+ }
22255
+ if (Array.isArray(value)) return value.map((entry) => copyAppJson(entry, depth + 1));
22256
+ const copied = {};
22257
+ for (const [key, entry] of Object.entries(value)) {
22258
+ Object.defineProperty(copied, key, {
22259
+ value: copyAppJson(entry, depth + 1),
22260
+ enumerable: true,
22261
+ configurable: true,
22262
+ writable: true
22263
+ });
22264
+ }
22265
+ return copied;
22266
+ }
22135
22267
  function mountAssistantApp(detail, actions, options = {}) {
22136
22268
  if (!detail.html) return void 0;
22137
22269
  const card = document.createElement("section");
@@ -22152,16 +22284,25 @@ function mountAssistantApp(detail, actions, options = {}) {
22152
22284
  const frame = document.createElement("iframe");
22153
22285
  frame.className = "noodle-app-frame";
22154
22286
  frame.title = detail.title ?? detail.tool;
22155
- frame.setAttribute("sandbox", "allow-scripts");
22287
+ const policy = resolveAssistantAppSandboxPolicy(detail.resourceMeta);
22156
22288
  frame.setAttribute("referrerpolicy", "no-referrer");
22157
22289
  const sandboxUrl = resolveSandboxUrl(options.sandboxUrl);
22158
- if (sandboxUrl) frame.src = sandboxUrl;
22159
- else frame.srcdoc = PROXY_DOCUMENT;
22290
+ const appliedPolicy = sandboxUrl ? policy : { csp: policy.csp ?? {} };
22291
+ if (sandboxUrl) {
22292
+ frame.setAttribute("sandbox", STANDARD_SANDBOX);
22293
+ if (policy.allowAttribute) frame.setAttribute("allow", policy.allowAttribute);
22294
+ frame.src = assistantAppSandboxUrl(sandboxUrl, policy);
22295
+ } else {
22296
+ frame.setAttribute("sandbox", "allow-scripts");
22297
+ frame.srcdoc = PROXY_DOCUMENT;
22298
+ }
22160
22299
  card.append(frame);
22161
22300
  let mountedBridge;
22162
22301
  let mountedTransport;
22163
22302
  let destroyed = false;
22164
22303
  let initialized = false;
22304
+ let renderFailed = false;
22305
+ let renderStage = "sandbox_load";
22165
22306
  let currentTheme = actions.theme;
22166
22307
  let displayMode = "inline";
22167
22308
  let hostContext = createAssistantAppHostContext(
@@ -22170,6 +22311,15 @@ function mountAssistantApp(detail, actions, options = {}) {
22170
22311
  displayMode
22171
22312
  );
22172
22313
  let destroyPromise;
22314
+ let transportClosePromise;
22315
+ let renderTimer;
22316
+ const closeTransport = () => {
22317
+ if (transportClosePromise) return transportClosePromise;
22318
+ const closing = mountedTransport?.close().catch(() => {
22319
+ }) ?? Promise.resolve();
22320
+ transportClosePromise = closing;
22321
+ return closing;
22322
+ };
22173
22323
  const applyDisplayMode = (requestedMode) => {
22174
22324
  displayMode = requestedMode === "fullscreen" && options.allowFullscreen === true ? "fullscreen" : "inline";
22175
22325
  card.toggleAttribute("data-fullscreen", displayMode === "fullscreen");
@@ -22187,18 +22337,26 @@ function mountAssistantApp(detail, actions, options = {}) {
22187
22337
  applyDisplayMode("inline");
22188
22338
  frame.focus();
22189
22339
  });
22190
- const renderTimer = setTimeout(() => {
22191
- if (initialized || !frame.isConnected) return;
22340
+ const showRenderFailure = (code) => {
22341
+ if (code === "view_render_timeout" && initialized || destroyed || renderFailed || !frame.isConnected)
22342
+ return;
22343
+ renderFailed = true;
22344
+ clearTimeout(renderTimer);
22192
22345
  const note = document.createElement("div");
22193
22346
  note.className = "noodle-app-fallback";
22194
22347
  note.textContent = "This app view could not be displayed.";
22195
22348
  frame.replaceWith(note);
22196
- options.onRenderFailure?.({ code: "view_render_timeout", retryable: false });
22349
+ options.onRenderFailure?.({ code, retryable: false, stage: renderStage });
22350
+ void closeTransport();
22351
+ };
22352
+ renderTimer = setTimeout(() => {
22353
+ showRenderFailure("view_render_timeout");
22197
22354
  }, APP_RENDER_TIMEOUT_MS);
22198
22355
  frame.addEventListener(
22199
22356
  "load",
22200
22357
  () => {
22201
- if (destroyed) return;
22358
+ if (destroyed || renderFailed) return;
22359
+ renderStage = "sandbox_proxy";
22202
22360
  const target = frame.contentWindow;
22203
22361
  if (!target) return;
22204
22362
  const bridge = new DX(
@@ -22209,7 +22367,11 @@ function mountAssistantApp(detail, actions, options = {}) {
22209
22367
  serverTools: {},
22210
22368
  serverResources: {},
22211
22369
  updateModelContext: { text: {}, structuredContent: {} },
22212
- message: { text: {} }
22370
+ message: { text: {} },
22371
+ sandbox: {
22372
+ ...appliedPolicy.csp === void 0 ? {} : { csp: appliedPolicy.csp },
22373
+ ...appliedPolicy.permissions === void 0 ? {} : { permissions: appliedPolicy.permissions }
22374
+ }
22213
22375
  },
22214
22376
  {
22215
22377
  hostContext
@@ -22217,13 +22379,15 @@ function mountAssistantApp(detail, actions, options = {}) {
22217
22379
  );
22218
22380
  mountedBridge = bridge;
22219
22381
  bridge.onsandboxready = () => {
22220
- if (destroyed) return;
22382
+ if (destroyed || renderFailed) return;
22383
+ renderStage = "app_initialize";
22221
22384
  const current = options.getDetail?.() ?? detail;
22222
22385
  void bridge.sendSandboxResourceReady({
22223
22386
  html: current.html,
22224
- sandbox: "allow-scripts",
22225
- ...current.resourceMeta ?? {}
22226
- });
22387
+ sandbox: sandboxUrl ? STANDARD_SANDBOX : "allow-scripts",
22388
+ ...appliedPolicy.csp === void 0 ? {} : { csp: appliedPolicy.csp },
22389
+ ...appliedPolicy.permissions === void 0 ? {} : { permissions: appliedPolicy.permissions }
22390
+ }).catch(() => showRenderFailure("view_bridge_error"));
22227
22391
  };
22228
22392
  bridge.onsizechange = ({ height }) => {
22229
22393
  if (destroyed) return;
@@ -22287,21 +22451,30 @@ function mountAssistantApp(detail, actions, options = {}) {
22287
22451
  return await actions.client.requestApp("resources/read", params);
22288
22452
  };
22289
22453
  bridge.oninitialized = () => {
22290
- if (destroyed) return;
22291
- initialized = true;
22292
- clearTimeout(renderTimer);
22293
- bridge.setHostContext(hostContext);
22294
- const current = options.getDetail?.() ?? detail;
22295
- void bridge.sendToolInput({ arguments: current.arguments ?? {} });
22296
- void bridge.sendToolResult({
22297
- content: [{ type: "text", text: JSON.stringify(current.result) }],
22298
- ...typeof current.result === "object" && current.result !== null && !Array.isArray(current.result) ? { structuredContent: current.result } : {}
22299
- });
22454
+ if (destroyed || renderFailed) return;
22455
+ try {
22456
+ const current = options.getDetail?.() ?? detail;
22457
+ const copiedArguments = copyAppJson(current.arguments ?? {});
22458
+ const copiedResult = copyAppJson(current.result);
22459
+ const argumentsRecord = typeof copiedArguments === "object" && copiedArguments !== null && !Array.isArray(copiedArguments) ? copiedArguments : {};
22460
+ initialized = true;
22461
+ clearTimeout(renderTimer);
22462
+ bridge.setHostContext(hostContext);
22463
+ void Promise.all([
22464
+ bridge.sendToolInput({ arguments: argumentsRecord }),
22465
+ bridge.sendToolResult({
22466
+ content: [{ type: "text", text: JSON.stringify(copiedResult) }],
22467
+ ...typeof copiedResult === "object" && copiedResult !== null && !Array.isArray(copiedResult) ? { structuredContent: copiedResult } : {}
22468
+ })
22469
+ ]).catch(() => showRenderFailure("view_bridge_error"));
22470
+ } catch {
22471
+ showRenderFailure("view_bridge_error");
22472
+ }
22300
22473
  };
22301
22474
  const transport = new E(target, target);
22302
22475
  mountedTransport = transport;
22303
22476
  void bridge.connect(transport).catch(() => {
22304
- void destroy();
22477
+ showRenderFailure("view_bridge_error");
22305
22478
  });
22306
22479
  },
22307
22480
  { once: true }
@@ -22327,8 +22500,7 @@ function mountAssistantApp(detail, actions, options = {}) {
22327
22500
  }
22328
22501
  } catch {
22329
22502
  }
22330
- await mountedTransport?.close().catch(() => {
22331
- });
22503
+ await closeTransport();
22332
22504
  card.remove();
22333
22505
  mountedApps.delete(card);
22334
22506
  })();
@@ -22448,13 +22620,19 @@ var DEFAULT_APPEARANCE = {
22448
22620
  copy: "Copy",
22449
22621
  newMessages: "New messages",
22450
22622
  reconnect: "Reconnect",
22623
+ newConversation: "New conversation",
22451
22624
  retry: "Try again",
22452
22625
  unavailable: "The assistant is temporarily unavailable.",
22453
22626
  sessionExpired: "Your assistant session expired. Reopen it to continue.",
22454
22627
  sessionIdle: "Assistant session idle",
22455
22628
  sessionLoading: "Assistant session loading",
22456
22629
  sessionReady: "Assistant session ready",
22457
- sessionError: "Assistant session unavailable"
22630
+ sessionError: "Assistant session unavailable",
22631
+ signInHeading: "Sign in to continue",
22632
+ signInBody: "",
22633
+ signInAction: "Sign in",
22634
+ // Empty means no sign-up button: authoring the label is the opt-in (ADR 0201, 2026-08-22).
22635
+ signUpAction: ""
22458
22636
  },
22459
22637
  presentation: {
22460
22638
  panel: {
@@ -27094,6 +27272,9 @@ var AssistantChatStateStore = class {
27094
27272
  })
27095
27273
  );
27096
27274
  return;
27275
+ case "auth_requested":
27276
+ this.#writeStandaloneData(() => this.#writeData("sign-in", event.data.id, event.data));
27277
+ return;
27097
27278
  case "tool_completed":
27098
27279
  this.#writeData("tool-result", event.data.id, event.data);
27099
27280
  return;
@@ -27318,14 +27499,14 @@ function toChatError(error51) {
27318
27499
  }
27319
27500
  function readErrorDetail(error51) {
27320
27501
  const detail = Reflect.get(error51, "detail");
27321
- if (!isRecord(detail) || typeof detail.code !== "string") return void 0;
27502
+ if (!isRecord2(detail) || typeof detail.code !== "string") return void 0;
27322
27503
  return {
27323
27504
  code: detail.code,
27324
27505
  ...typeof detail.status === "number" ? { status: detail.status } : {},
27325
27506
  retryable: detail.retryable === true
27326
27507
  };
27327
27508
  }
27328
- function isRecord(value) {
27509
+ function isRecord2(value) {
27329
27510
  return typeof value === "object" && value !== null && !Array.isArray(value);
27330
27511
  }
27331
27512
 
@@ -27352,13 +27533,14 @@ var AssistantClientError = class extends Error {
27352
27533
  }
27353
27534
  };
27354
27535
  function parseSession(value) {
27355
- if (!isRecord2(value) || !isRecord2(value.endpoints)) {
27536
+ if (!isRecord3(value) || !isRecord3(value.endpoints)) {
27356
27537
  throw clientError("session_failed", "Assistant session response is invalid", true);
27357
27538
  }
27358
27539
  const interactions = value.endpoints.interactions;
27359
27540
  const apps = value.endpoints.apps;
27360
27541
  const sandbox = value.endpoints.sandbox;
27361
- if (typeof value.token !== "string" || typeof value.expiresAt !== "string" || typeof value.endpoints.turns !== "string" || typeof value.endpoints.toolConfirmations !== "string" || interactions !== void 0 && typeof interactions !== "string" || apps !== void 0 && typeof apps !== "string" || sandbox !== void 0 && typeof sandbox !== "string") {
27542
+ const transcript = value.endpoints.transcript;
27543
+ if (typeof value.token !== "string" || typeof value.expiresAt !== "string" || typeof value.endpoints.turns !== "string" || typeof value.endpoints.toolConfirmations !== "string" || interactions !== void 0 && typeof interactions !== "string" || apps !== void 0 && typeof apps !== "string" || sandbox !== void 0 && typeof sandbox !== "string" || transcript !== void 0 && typeof transcript !== "string") {
27362
27544
  throw clientError("session_failed", "Assistant session response is invalid", true);
27363
27545
  }
27364
27546
  return {
@@ -27369,13 +27551,14 @@ function parseSession(value) {
27369
27551
  toolConfirmations: value.endpoints.toolConfirmations,
27370
27552
  ...typeof interactions === "string" ? { interactions } : {},
27371
27553
  ...typeof apps === "string" ? { apps } : {},
27372
- ...typeof sandbox === "string" ? { sandbox } : {}
27554
+ ...typeof sandbox === "string" ? { sandbox } : {},
27555
+ ...typeof transcript === "string" ? { transcript } : {}
27373
27556
  },
27374
- ...isRecord2(value.configuration) ? { configuration: value.configuration } : {},
27375
- ...isRecord2(value.resume) && typeof value.resume.tool === "string" ? { resume: { tool: value.resume.tool } } : {}
27557
+ ...isRecord3(value.configuration) ? { configuration: value.configuration } : {},
27558
+ ...isRecord3(value.resume) && typeof value.resume.tool === "string" ? { resume: { tool: value.resume.tool } } : {}
27376
27559
  };
27377
27560
  }
27378
- function isRecord2(value) {
27561
+ function isRecord3(value) {
27379
27562
  return typeof value === "object" && value !== null && !Array.isArray(value);
27380
27563
  }
27381
27564
  async function readStableErrorCode(response) {
@@ -27385,7 +27568,7 @@ async function readStableErrorCode(response) {
27385
27568
  } catch {
27386
27569
  return void 0;
27387
27570
  }
27388
- if (!isRecord2(value) || typeof value.code !== "string") return void 0;
27571
+ if (!isRecord3(value) || typeof value.code !== "string") return void 0;
27389
27572
  return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
27390
27573
  }
27391
27574
  function clientError(code, message, retryable, status, options, serviceCode) {
@@ -27416,12 +27599,12 @@ function toAssistantClientEvent(event) {
27416
27599
  }
27417
27600
  break;
27418
27601
  case "tool_proposed":
27419
- if (hasString(value, "id") && hasString(value, "tool") && hasOptionalString(value.title) && hasOptionalString(value.description) && hasOptionalJson(value.arguments) && (value.reviewSchema === void 0 || isRecord3(value.reviewSchema)) && hasOptionalString(value.expiresAt) && (value.requiresConfirmation === void 0 || value.requiresConfirmation === true) && hasOptionalTurnId(value)) {
27602
+ if (hasString(value, "id") && hasString(value, "tool") && hasOptionalString(value.title) && hasOptionalString(value.description) && hasOptionalJson(value.arguments) && (value.reviewSchema === void 0 || isRecord4(value.reviewSchema)) && hasOptionalString(value.expiresAt) && (value.requiresConfirmation === void 0 || value.requiresConfirmation === true) && hasOptionalTurnId(value)) {
27420
27603
  return event;
27421
27604
  }
27422
27605
  break;
27423
27606
  case "input_requested":
27424
- if (hasString(value, "id") && hasString(value, "message") && isRecord3(value.requestedSchema) && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27607
+ if (hasString(value, "id") && hasString(value, "message") && isRecord4(value.requestedSchema) && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
27425
27608
  return event;
27426
27609
  }
27427
27610
  break;
@@ -27465,7 +27648,7 @@ function toAssistantClientEvent(event) {
27465
27648
  return { event: "unrecognized", data: { name: event.event, payload: value } };
27466
27649
  }
27467
27650
  function isViewAvailableDetail(value) {
27468
- return hasString(value, "id") && hasString(value, "tool") && typeof value.resourceUri === "string" && value.resourceUri.startsWith("ui://") && hasOptionalString(value.title) && (value.replayed === void 0 || value.replayed === true) && isJsonValue(value.result, 0) && hasOptionalJson(value.arguments) && hasOptionalString(value.html) && (value.resourceMeta === void 0 || isRecord3(value.resourceMeta)) && (value.allowedOpenDomains === void 0 || Array.isArray(value.allowedOpenDomains) && value.allowedOpenDomains.every(isHttpsUrl)) && hasOptionalTurnId(value);
27651
+ return hasString(value, "id") && hasString(value, "tool") && typeof value.resourceUri === "string" && value.resourceUri.startsWith("ui://") && hasOptionalString(value.title) && (value.replayed === void 0 || value.replayed === true) && isJsonValue(value.result, 0) && hasOptionalJson(value.arguments) && hasOptionalString(value.html) && (value.resourceMeta === void 0 || isRecord4(value.resourceMeta)) && (value.allowedOpenDomains === void 0 || Array.isArray(value.allowedOpenDomains) && value.allowedOpenDomains.every(isHttpsUrl)) && hasOptionalTurnId(value);
27469
27652
  }
27470
27653
  function isHttpsUrl(value) {
27471
27654
  if (typeof value !== "string") return false;
@@ -27498,11 +27681,11 @@ function isJsonValue(value, depth) {
27498
27681
  if (Array.isArray(value)) {
27499
27682
  return value.length <= 256 && value.every((entry) => isJsonValue(entry, depth + 1));
27500
27683
  }
27501
- if (!isRecord3(value)) return false;
27684
+ if (!isRecord4(value)) return false;
27502
27685
  const entries2 = Object.entries(value);
27503
27686
  return entries2.length <= 256 && entries2.every(([, entry]) => isJsonValue(entry, depth + 1));
27504
27687
  }
27505
- function isRecord3(value) {
27688
+ function isRecord4(value) {
27506
27689
  return typeof value === "object" && value !== null && !Array.isArray(value);
27507
27690
  }
27508
27691
 
@@ -27524,6 +27707,43 @@ function sessionSourceKey(options) {
27524
27707
  return `${options.embedId ?? ""}|${options.serviceUrl ?? ""}|${options.sessionEndpoint ?? ""}`;
27525
27708
  }
27526
27709
 
27710
+ // src/transcript-replay.ts
27711
+ function parseTranscriptEntries(value) {
27712
+ if (typeof value !== "object" || value === null) return [];
27713
+ const entries2 = value.entries;
27714
+ if (!Array.isArray(entries2)) return [];
27715
+ const parsed = [];
27716
+ for (const entry of entries2) {
27717
+ const { role, text: text3 } = entry ?? {};
27718
+ if (role !== "user" && role !== "assistant" || typeof text3 !== "string") return [];
27719
+ parsed.push({ role, text: text3 });
27720
+ }
27721
+ return parsed;
27722
+ }
27723
+ function transcriptReplayEvents(entries2) {
27724
+ const events = [];
27725
+ let open = false;
27726
+ let awaitingAssistant = false;
27727
+ for (const entry of entries2) {
27728
+ if (entry.role === "user") {
27729
+ if (open) events.push({ event: "message_completed", data: {} });
27730
+ events.push({ event: "message_started", data: { message: entry.text } });
27731
+ open = true;
27732
+ awaitingAssistant = true;
27733
+ continue;
27734
+ }
27735
+ if (!open || !awaitingAssistant) {
27736
+ if (open) events.push({ event: "message_completed", data: {} });
27737
+ events.push({ event: "resume_started", data: { tool: "" } });
27738
+ open = true;
27739
+ }
27740
+ events.push({ event: "content", data: { delta: entry.text } });
27741
+ awaitingAssistant = false;
27742
+ }
27743
+ if (open) events.push({ event: "message_completed", data: {} });
27744
+ return events;
27745
+ }
27746
+
27527
27747
  // src/transport.ts
27528
27748
  var AssistantTransportError = class extends Error {
27529
27749
  code = "invalid_response";
@@ -27632,15 +27852,15 @@ function parseFrame(frame) {
27632
27852
  if (event === "done" && !isValidDonePayload(parsed)) {
27633
27853
  throw invalidStream("assistant event stream contained an invalid done event");
27634
27854
  }
27635
- return { event, data: isRecord4(parsed) ? parsed : { value: parsed } };
27855
+ return { event, data: isRecord5(parsed) ? parsed : { value: parsed } };
27636
27856
  }
27637
27857
  function normalizeLineEndings(input) {
27638
27858
  return input.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
27639
27859
  }
27640
27860
  function isValidDonePayload(value) {
27641
- return isRecord4(value) && (value.turnId === void 0 || typeof value.turnId === "string" && value.turnId.length > 0);
27861
+ return isRecord5(value) && (value.turnId === void 0 || typeof value.turnId === "string" && value.turnId.length > 0);
27642
27862
  }
27643
- function isRecord4(value) {
27863
+ function isRecord5(value) {
27644
27864
  return typeof value === "object" && value !== null && !Array.isArray(value);
27645
27865
  }
27646
27866
  function invalidStream(message, cause) {
@@ -27694,6 +27914,12 @@ var DefaultAssistantClient = class {
27694
27914
  await this.#singleFlight(async (signal) => {
27695
27915
  if (this.#session) return;
27696
27916
  const session = await this.#createSession(signal);
27917
+ if (this.#source.kind !== "public" && session.endpoints.transcript) {
27918
+ try {
27919
+ await this.#replayTranscript(session, signal);
27920
+ } catch {
27921
+ }
27922
+ }
27697
27923
  if (session.resume) {
27698
27924
  try {
27699
27925
  await this.#runChatOperation(signal, () => this.#resumeTurn(session, signal));
@@ -27702,6 +27928,26 @@ var DefaultAssistantClient = class {
27702
27928
  }
27703
27929
  });
27704
27930
  }
27931
+ /** Fetch and re-emit the bounded visible transcript through the standard event path. */
27932
+ async #replayTranscript(session, signal) {
27933
+ const endpoint = session.endpoints.transcript;
27934
+ if (!endpoint) return;
27935
+ const response = await this.#request(
27936
+ endpoint,
27937
+ {
27938
+ method: "POST",
27939
+ headers: authorizedHeaders(session.token, "application/json"),
27940
+ body: "{}",
27941
+ signal
27942
+ },
27943
+ "turn_failed"
27944
+ );
27945
+ if (!response.ok) return;
27946
+ for (const event of transcriptReplayEvents(parseTranscriptEntries(await response.json()))) {
27947
+ this.#emit(event);
27948
+ if (event.event === "message_completed") await this.#chat.flush();
27949
+ }
27950
+ }
27705
27951
  /** POST the one-shot resume trigger; a 409 means nothing was pending, which renders as silence. */
27706
27952
  async #resumeTurn(session, signal) {
27707
27953
  const response = await this.#request(
@@ -28133,10 +28379,10 @@ function authorizedHeaders(token, accept) {
28133
28379
  };
28134
28380
  }
28135
28381
  function parseAppInteraction(value) {
28136
- if (!isRecord2(value) || !isRecord2(value.interaction)) return void 0;
28382
+ if (!isRecord3(value) || !isRecord3(value.interaction)) return void 0;
28137
28383
  const event = value.interaction.event;
28138
28384
  const data = value.interaction.data;
28139
- if (event !== "tool_proposed" && event !== "input_requested" || !isRecord2(data)) {
28385
+ if (event !== "tool_proposed" && event !== "input_requested" || !isRecord3(data)) {
28140
28386
  return void 0;
28141
28387
  }
28142
28388
  const parsed = toAssistantClientEvent({ event, data });
@@ -28157,6 +28403,27 @@ function appInteractionStopped(action) {
28157
28403
  };
28158
28404
  }
28159
28405
 
28406
+ // src/conversation-recovery.ts
28407
+ function appendConversationRecovery(input) {
28408
+ if (!input.messages) return;
28409
+ input.messages.querySelector(".conversation-error")?.remove();
28410
+ const error51 = document.createElement("section");
28411
+ error51.className = "conversation-error";
28412
+ error51.setAttribute("role", input.action ? "alert" : "status");
28413
+ const text3 = document.createElement("p");
28414
+ text3.textContent = input.message;
28415
+ error51.append(text3);
28416
+ if (input.action) {
28417
+ const button2 = document.createElement("button");
28418
+ button2.type = "button";
28419
+ button2.textContent = input.action.label;
28420
+ button2.addEventListener("click", input.action.run);
28421
+ error51.append(button2);
28422
+ }
28423
+ input.messages.append(error51);
28424
+ input.revealLatest();
28425
+ }
28426
+
28160
28427
  // src/input-request-card.ts
28161
28428
  function createInputRequestCard(options) {
28162
28429
  const card = document.createElement("section");
@@ -31247,22 +31514,46 @@ function renderMarkdown(text3) {
31247
31514
  // src/sign-in-card.ts
31248
31515
  function createSignInCard(options) {
31249
31516
  const card = document.createElement("div");
31250
- card.className = "noodle-assistant-card noodle-assistant-sign-in";
31517
+ card.className = "tool-proposal sign-in";
31251
31518
  card.dataset.tool = options.tool;
31252
31519
  card.setAttribute("role", "status");
31253
- const heading = document.createElement("p");
31254
- heading.className = "noodle-assistant-card-heading";
31520
+ const badge = document.createElement("span");
31521
+ badge.className = "sign-in-badge";
31522
+ badge.setAttribute("aria-hidden", "true");
31523
+ card.append(badge);
31524
+ const heading = document.createElement("h3");
31255
31525
  heading.textContent = options.labels.heading;
31256
31526
  card.append(heading);
31257
- const action = document.createElement("button");
31258
- action.type = "button";
31259
- action.className = "noodle-assistant-card-action";
31260
- action.textContent = options.labels.action;
31261
- action.addEventListener("click", () => {
31262
- action.disabled = true;
31263
- options.onSignIn();
31264
- });
31265
- card.append(action);
31527
+ if (options.labels.body) {
31528
+ const body = document.createElement("p");
31529
+ body.className = "proposal-description";
31530
+ body.textContent = options.labels.body;
31531
+ card.append(body);
31532
+ }
31533
+ const actions = document.createElement("div");
31534
+ actions.className = "proposal-actions";
31535
+ const buttons = [];
31536
+ const act = (intent) => {
31537
+ for (const button2 of buttons) button2.disabled = true;
31538
+ options.onAction(intent);
31539
+ };
31540
+ const signIn = document.createElement("button");
31541
+ signIn.type = "button";
31542
+ signIn.className = "sign-in-action";
31543
+ signIn.textContent = options.labels.action;
31544
+ signIn.addEventListener("click", () => act("sign-in"));
31545
+ buttons.push(signIn);
31546
+ actions.append(signIn);
31547
+ if (options.labels.signUpAction) {
31548
+ const signUp = document.createElement("button");
31549
+ signUp.type = "button";
31550
+ signUp.className = "sign-up-action";
31551
+ signUp.textContent = options.labels.signUpAction;
31552
+ signUp.addEventListener("click", () => act("sign-up"));
31553
+ buttons.push(signUp);
31554
+ actions.append(signUp);
31555
+ }
31556
+ card.append(actions);
31266
31557
  return card;
31267
31558
  }
31268
31559
 
@@ -31290,7 +31581,7 @@ var AssistantElementEventController = class {
31290
31581
  if (event.event === "unrecognized") return;
31291
31582
  const { event: name21, data } = event;
31292
31583
  if (name21 === "session_started") {
31293
- if (isRecord5(data.configuration)) {
31584
+ if (isRecord6(data.configuration)) {
31294
31585
  this.#host.applyConfiguration(data.configuration);
31295
31586
  }
31296
31587
  this.#host.element.dispatchEvent(
@@ -31358,10 +31649,10 @@ var AssistantElementEventController = class {
31358
31649
  this.#appendToolProposal(
31359
31650
  String(data.id ?? ""),
31360
31651
  String(data.tool ?? "action"),
31361
- isRecord5(data.arguments) ? data.arguments : void 0,
31652
+ isRecord6(data.arguments) ? data.arguments : void 0,
31362
31653
  typeof data.title === "string" ? data.title : void 0,
31363
31654
  "description" in data && typeof data.description === "string" ? data.description : void 0,
31364
- "reviewSchema" in data && isRecord5(data.reviewSchema) ? data.reviewSchema : void 0
31655
+ "reviewSchema" in data && isRecord6(data.reviewSchema) ? data.reviewSchema : void 0
31365
31656
  );
31366
31657
  return;
31367
31658
  }
@@ -31374,7 +31665,7 @@ var AssistantElementEventController = class {
31374
31665
  );
31375
31666
  return;
31376
31667
  }
31377
- if (name21 === "input_requested" && isRecord5(data.requestedSchema)) {
31668
+ if (name21 === "input_requested" && isRecord6(data.requestedSchema)) {
31378
31669
  this.#appendInputRequest(
31379
31670
  String(data.id ?? ""),
31380
31671
  String(data.message ?? "More information is required"),
@@ -31468,17 +31759,20 @@ var AssistantElementEventController = class {
31468
31759
  const messages = this.#host.messages();
31469
31760
  if (!messages) return;
31470
31761
  this.#proposalCards.get(id)?.remove();
31762
+ const labels = this.#host.labels();
31471
31763
  const card = createSignInCard({
31472
31764
  tool,
31473
31765
  signInTicket,
31474
31766
  expiresAt,
31475
31767
  labels: {
31476
- heading: "Sign in to continue",
31477
- action: "Sign in"
31768
+ heading: labels.signInHeading,
31769
+ body: labels.signInBody,
31770
+ action: labels.signInAction,
31771
+ signUpAction: labels.signUpAction
31478
31772
  },
31479
- onSignIn: () => this.#host.element.dispatchEvent(
31773
+ onAction: (intent) => this.#host.element.dispatchEvent(
31480
31774
  new CustomEvent("assistant-sign-in-requested", {
31481
- detail: { id, tool, signInTicket, expiresAt },
31775
+ detail: { id, tool, signInTicket, expiresAt, intent },
31482
31776
  bubbles: true,
31483
31777
  composed: true
31484
31778
  })
@@ -31542,7 +31836,7 @@ var AssistantElementEventController = class {
31542
31836
  this.#host.revealLatest();
31543
31837
  }
31544
31838
  };
31545
- function isRecord5(value) {
31839
+ function isRecord6(value) {
31546
31840
  return typeof value === "object" && value !== null && !Array.isArray(value);
31547
31841
  }
31548
31842
 
@@ -31874,8 +32168,10 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
31874
32168
  .proposal-details .proposal-arguments { margin-top: 4px; }
31875
32169
  .proposal-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: end; }
31876
32170
  .tool-proposal button { min-height: 44px; padding: 9px 16px; border: 1px solid var(--ns-assistant-secondary-button-border, transparent); border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); color: var(--ns-assistant-secondary-button-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); cursor: pointer; font-weight: 600; }
31877
- .tool-proposal .proposal-accept { color: var(--ns-assistant-primary-button-text, var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text))); background: var(--ns-assistant-primary-button, var(--ns-assistant-accent, var(--ns-assistant-default-accent))); border-color: var(--ns-assistant-primary-button-border, transparent); }
32171
+ .tool-proposal .proposal-accept, .tool-proposal .sign-in-action { color: var(--ns-assistant-primary-button-text, var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text))); background: var(--ns-assistant-primary-button, var(--ns-assistant-accent, var(--ns-assistant-default-accent))); border-color: var(--ns-assistant-primary-button-border, transparent); }
31878
32172
  .tool-proposal button:disabled { opacity: .55; cursor: not-allowed; }
32173
+ .sign-in .sign-in-badge { display: grid; place-items: center; width: 38px; height: 38px; border-radius: 999px; color: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); background: color-mix(in srgb, var(--ns-assistant-accent, var(--ns-assistant-default-accent)) 12%, transparent); }
32174
+ .sign-in .sign-in-badge::before { content: ''; width: 19px; height: 19px; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9Zm0 2.2c-4.3 0-7.8 2.6-7.8 5.8v1h15.6v-1c0-3.2-3.5-5.8-7.8-5.8Z'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9Zm0 2.2c-4.3 0-7.8 2.6-7.8 5.8v1h15.6v-1c0-3.2-3.5-5.8-7.8-5.8Z'/%3E%3C/svg%3E") center / contain no-repeat; }
31879
32175
  .tool-proposal button { min-height: 45px; }
31880
32176
  .tool-result { display: grid; gap: 10px; margin: 8px 0 16px; padding: 16px; border: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); border-radius: var(--ns-assistant-card-radius, 14px); background: var(--ns-assistant-assistant-message, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); }
31881
32177
  .tool-result > strong { color: var(--ns-assistant-success, var(--ns-assistant-default-success)); }
@@ -32843,33 +33139,28 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32843
33139
  this.#setSessionState("error");
32844
33140
  }
32845
33141
  const detail = error51 instanceof AssistantClientError ? error51.detail : { code: fallbackCode, retryable: false };
32846
- this.#appendRecoveryError(
32847
- detail.code === "session_expired" ? this.#appearance.labels.sessionExpired : this.#appearance.labels.unavailable,
33142
+ const startNewConversation = detail.serviceCode === "session_turn_budget_exhausted";
33143
+ const reconnect = detail.retryable !== false || detail.serviceCode === void 0;
33144
+ const recoveryAction = startNewConversation ? {
33145
+ label: this.#appearance.labels.newConversation,
33146
+ run: () => this.#startNewConversation()
33147
+ } : reconnect ? { label: this.#appearance.labels.reconnect, run: () => this.reconnect() } : void 0;
33148
+ appendConversationRecovery({
33149
+ messages: this.#messages,
33150
+ message: detail.code === "session_expired" ? this.#appearance.labels.sessionExpired : this.#appearance.labels.unavailable,
32848
33151
  // A spent or switched-off daily budget offers no retry. Reconnecting cannot succeed, and every
32849
33152
  // open tab trying is exactly the load the cap was set to refuse — so the button goes, not just
32850
33153
  // the alarming words. A spent *session* keeps it: reconnect mints a fresh one, which is the fix.
32851
- detail.retryable !== false || detail.serviceCode === void 0
32852
- );
33154
+ ...recoveryAction === void 0 ? {} : { action: recoveryAction },
33155
+ revealLatest: () => this.#revealLatest()
33156
+ });
32853
33157
  this.#dispatchError(detail);
32854
33158
  }
32855
- #appendRecoveryError(message, offerRetry = true) {
32856
- if (!this.#messages) return;
32857
- this.#messages.querySelector(".conversation-error")?.remove();
32858
- const error51 = document.createElement("section");
32859
- error51.className = "conversation-error";
32860
- error51.setAttribute("role", offerRetry ? "alert" : "status");
32861
- const text3 = document.createElement("p");
32862
- text3.textContent = message;
32863
- error51.append(text3);
32864
- if (offerRetry) {
32865
- const retry = document.createElement("button");
32866
- retry.type = "button";
32867
- retry.textContent = this.#appearance.labels.reconnect;
32868
- retry.addEventListener("click", () => this.reconnect());
32869
- error51.append(retry);
32870
- }
32871
- this.#messages.append(error51);
32872
- this.#revealLatest();
33159
+ #startNewConversation() {
33160
+ this.resetSession();
33161
+ this.#setSessionState("loading");
33162
+ this.#primeSession();
33163
+ this.dispatchEvent(new CustomEvent("assistant-new-conversation"));
32873
33164
  }
32874
33165
  #resolvedMode() {
32875
33166
  const configured = this.getAttribute("theme") ?? this.#appearance.theme.defaultMode;