@syntrologie/adapt-chatbot 2.28.0 → 2.29.1

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 (102) hide show
  1. package/dist/AdaptiveChatBar.composer.test.d.ts +2 -0
  2. package/dist/AdaptiveChatBar.composer.test.d.ts.map +1 -0
  3. package/dist/AdaptiveChatBar.d.ts +140 -4
  4. package/dist/AdaptiveChatBar.d.ts.map +1 -1
  5. package/dist/AdaptiveChatBar.js +4 -3
  6. package/dist/AdaptiveChatBarMountable.d.ts +47 -0
  7. package/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
  8. package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts +2 -0
  9. package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts.map +1 -0
  10. package/dist/AdaptiveChatTrail.d.ts +397 -5
  11. package/dist/AdaptiveChatTrail.d.ts.map +1 -1
  12. package/dist/AdaptiveChatTrail.js +6 -4
  13. package/dist/AdaptiveChatTrail.test.d.ts +2 -0
  14. package/dist/AdaptiveChatTrail.test.d.ts.map +1 -0
  15. package/dist/AdaptiveChipsStrip.d.ts +17 -11
  16. package/dist/AdaptiveChipsStrip.d.ts.map +1 -1
  17. package/dist/AdaptiveChipsStrip.js +2 -2
  18. package/dist/ChatAssistantLit.d.ts +5 -3
  19. package/dist/ChatAssistantLit.d.ts.map +1 -1
  20. package/dist/ChatAssistantLit.js +4 -3
  21. package/dist/ChatSession.d.ts +66 -5
  22. package/dist/ChatSession.d.ts.map +1 -1
  23. package/dist/ChatTransport.d.ts +107 -0
  24. package/dist/ChatTransport.d.ts.map +1 -1
  25. package/dist/InlineRegistryWidgetMount.test.d.ts +2 -0
  26. package/dist/InlineRegistryWidgetMount.test.d.ts.map +1 -0
  27. package/dist/InlineWidgetLiveMount.test.d.ts +2 -0
  28. package/dist/InlineWidgetLiveMount.test.d.ts.map +1 -0
  29. package/dist/NavLinkMountable.d.ts.map +1 -1
  30. package/dist/TextAnswerMountable.test.d.ts +2 -0
  31. package/dist/TextAnswerMountable.test.d.ts.map +1 -0
  32. package/dist/Turnstile.d.ts.map +1 -1
  33. package/dist/{chunk-KUO67E2W.js → chunk-A3TIGA4G.js} +718 -237
  34. package/dist/chunk-A3TIGA4G.js.map +7 -0
  35. package/dist/chunk-A7RBZLIX.js +1678 -0
  36. package/dist/chunk-A7RBZLIX.js.map +7 -0
  37. package/dist/{chunk-435KJD27.js → chunk-EACGDSJI.js} +46 -1
  38. package/dist/chunk-EACGDSJI.js.map +7 -0
  39. package/dist/chunk-EWPPVPJ4.js +14 -0
  40. package/dist/chunk-EWPPVPJ4.js.map +7 -0
  41. package/dist/chunk-FXHYE6FY.js +632 -0
  42. package/dist/chunk-FXHYE6FY.js.map +7 -0
  43. package/dist/{chunk-AUER7ZCK.js → chunk-OIGVZGSX.js} +11 -4
  44. package/dist/chunk-OIGVZGSX.js.map +7 -0
  45. package/dist/{chunk-UC4XU6GH.js → chunk-VZIUXXAM.js} +761 -52
  46. package/dist/chunk-VZIUXXAM.js.map +7 -0
  47. package/dist/elements/ActionHandler.d.ts +1 -1
  48. package/dist/elements/ActionHandler.d.ts.map +1 -1
  49. package/dist/elements/ElementInstanceStore.d.ts +62 -0
  50. package/dist/elements/ElementInstanceStore.d.ts.map +1 -1
  51. package/dist/elements/TileHandler.d.ts.map +1 -1
  52. package/dist/elements/TileHandler.test.d.ts +2 -0
  53. package/dist/elements/TileHandler.test.d.ts.map +1 -0
  54. package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts +2 -0
  55. package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts.map +1 -0
  56. package/dist/elements/fetcher.d.ts +8 -5
  57. package/dist/elements/fetcher.d.ts.map +1 -1
  58. package/dist/elements/index.d.ts +1 -0
  59. package/dist/elements/index.d.ts.map +1 -1
  60. package/dist/elements/store-singleton.d.ts +4 -0
  61. package/dist/elements/store-singleton.d.ts.map +1 -0
  62. package/dist/elements/types.d.ts +4 -0
  63. package/dist/elements/types.d.ts.map +1 -1
  64. package/dist/observer/__tests__/bus-translate.test.d.ts +16 -0
  65. package/dist/observer/__tests__/bus-translate.test.d.ts.map +1 -0
  66. package/dist/observer/__tests__/lifecycle.test.d.ts +2 -0
  67. package/dist/observer/__tests__/lifecycle.test.d.ts.map +1 -0
  68. package/dist/observer/__tests__/observer-enrich.test.d.ts +10 -0
  69. package/dist/observer/__tests__/observer-enrich.test.d.ts.map +1 -0
  70. package/dist/observer/allowlist.d.ts +5 -7
  71. package/dist/observer/allowlist.d.ts.map +1 -1
  72. package/dist/observer/bus-translate.d.ts +33 -0
  73. package/dist/observer/bus-translate.d.ts.map +1 -0
  74. package/dist/observer/index.d.ts +10 -1
  75. package/dist/observer/index.d.ts.map +1 -1
  76. package/dist/observer/queue.d.ts +6 -0
  77. package/dist/observer/queue.d.ts.map +1 -1
  78. package/dist/observer/transport.d.ts +1 -0
  79. package/dist/observer/transport.d.ts.map +1 -1
  80. package/dist/personas.d.ts +30 -0
  81. package/dist/personas.d.ts.map +1 -0
  82. package/dist/runtime.d.ts +2 -0
  83. package/dist/runtime.d.ts.map +1 -1
  84. package/dist/runtime.js +860 -137
  85. package/dist/runtime.js.map +4 -4
  86. package/dist/schema.d.ts +363 -67
  87. package/dist/schema.d.ts.map +1 -1
  88. package/dist/schema.js +176 -3
  89. package/dist/schema.js.map +2 -2
  90. package/dist/test/setup.d.ts +1 -0
  91. package/dist/test/setup.d.ts.map +1 -0
  92. package/dist/types.d.ts +25 -0
  93. package/dist/types.d.ts.map +1 -1
  94. package/package.json +5 -1
  95. package/dist/chunk-435KJD27.js.map +0 -7
  96. package/dist/chunk-AUER7ZCK.js.map +0 -7
  97. package/dist/chunk-DOMEUJR7.js +0 -382
  98. package/dist/chunk-DOMEUJR7.js.map +0 -7
  99. package/dist/chunk-KUO67E2W.js.map +0 -7
  100. package/dist/chunk-QELVKBQV.js +0 -214
  101. package/dist/chunk-QELVKBQV.js.map +0 -7
  102. package/dist/chunk-UC4XU6GH.js.map +0 -7
package/dist/runtime.js CHANGED
@@ -9,52 +9,87 @@ import {
9
9
  decodeMutationEnvelope,
10
10
  fetchMountedElements,
11
11
  renderFallbackHtml
12
- } from "./chunk-KUO67E2W.js";
13
- import "./chunk-QELVKBQV.js";
14
- import "./chunk-DOMEUJR7.js";
15
- import "./chunk-UC4XU6GH.js";
16
- import "./chunk-AUER7ZCK.js";
17
- import "./chunk-435KJD27.js";
12
+ } from "./chunk-A3TIGA4G.js";
13
+ import {
14
+ pickPersona
15
+ } from "./chunk-FXHYE6FY.js";
16
+ import {
17
+ getElementInstanceStore,
18
+ setElementInstanceStore
19
+ } from "./chunk-EWPPVPJ4.js";
20
+ import "./chunk-A7RBZLIX.js";
21
+ import "./chunk-VZIUXXAM.js";
22
+ import "./chunk-OIGVZGSX.js";
23
+ import {
24
+ DIVE_DEEPER_EVENT,
25
+ TILE_MOUNTED_EVENT,
26
+ TILE_UNMOUNTED_EVENT
27
+ } from "./chunk-EACGDSJI.js";
18
28
  import "./chunk-UVKRO5ER.js";
19
29
 
20
30
  // src/ChatSession.ts
21
- var CHAT_SESSION_STORAGE_KEY = "syntro:chat:v1";
22
- function isValidMessage(value) {
23
- if (typeof value !== "object" || value === null) return false;
24
- const m = value;
25
- return (typeof m.id === "number" || typeof m.id === "string") && (m.role === "user" || m.role === "assistant" || m.role === "system") && typeof m.text === "string";
26
- }
27
- function loadFromStorage() {
28
- try {
29
- const raw = globalThis.localStorage?.getItem(CHAT_SESSION_STORAGE_KEY);
30
- if (!raw) return null;
31
- const parsed = JSON.parse(raw);
32
- if (!parsed || !Array.isArray(parsed.messages)) return null;
33
- const messages = parsed.messages.filter(isValidMessage);
34
- const nextId = typeof parsed.nextId === "number" ? parsed.nextId : messages.length + 1;
35
- return { messages, nextId };
36
- } catch {
37
- return null;
38
- }
31
+ var _userIdCounter = 0;
32
+ function generateUserMessageId() {
33
+ return `user-${Date.now()}-${++_userIdCounter}`;
39
34
  }
40
35
  var ChatSession = class {
41
36
  constructor() {
42
37
  this._messages = [];
43
38
  this._inFlight = false;
39
+ this._runActive = false;
40
+ this._thinkingText = "";
44
41
  this._nextId = 1;
45
42
  this.subscribers = /* @__PURE__ */ new Set();
46
43
  this.sendListeners = /* @__PURE__ */ new Set();
47
44
  this.interruptListeners = /* @__PURE__ */ new Set();
48
45
  this.toolResultListeners = /* @__PURE__ */ new Set();
49
- const restored = loadFromStorage();
50
- if (restored) {
51
- this._messages = restored.messages;
52
- this._nextId = restored.nextId;
53
- }
54
46
  }
55
47
  /** Snapshot the current state. Always returns a fresh immutable view. */
56
48
  getState() {
57
- return { messages: [...this._messages], inFlight: this._inFlight };
49
+ return {
50
+ messages: [...this._messages],
51
+ inFlight: this._inFlight || this._runActive,
52
+ thinkingText: this._thinkingText
53
+ };
54
+ }
55
+ /**
56
+ * Append a delta to the active thinking turn's narration buffer.
57
+ * Forwarded from the AG-UI THINKING_TEXT_MESSAGE_CONTENT event so
58
+ * the chat trail can render the model's own reasoning instead of
59
+ * silent loading dots between turns.
60
+ */
61
+ appendThinkingDelta(delta) {
62
+ if (!delta) return;
63
+ this._thinkingText += delta;
64
+ this.notify();
65
+ }
66
+ /**
67
+ * Replace the active thinking narration outright (vs. appendThinkingDelta's
68
+ * streaming accumulation). Used to surface tool-call progress — e.g.
69
+ * "Searching…" while the agent works a tool — so a long tool phase shows
70
+ * what's happening instead of silent dots. No-op if unchanged.
71
+ */
72
+ setThinkingText(text) {
73
+ if (this._thinkingText === text) return;
74
+ this._thinkingText = text;
75
+ this.notify();
76
+ }
77
+ /**
78
+ * End the active thinking turn — drop any narration so the trail
79
+ * stops showing it. Fires on real text-message-start and on
80
+ * tool-call-start, the moment the model switches from reasoning
81
+ * to visible output.
82
+ */
83
+ clearThinking() {
84
+ if (!this._thinkingText) return;
85
+ this._thinkingText = "";
86
+ this.notify();
87
+ }
88
+ setRunActive(active) {
89
+ if (this._runActive === active) return;
90
+ this._runActive = active;
91
+ if (!active) this._thinkingText = "";
92
+ this.notify();
58
93
  }
59
94
  /**
60
95
  * Subscribe to state changes. Called immediately with the current
@@ -76,11 +111,13 @@ var ChatSession = class {
76
111
  send(text, opts) {
77
112
  const trimmed = text.trim();
78
113
  if (!trimmed) return;
79
- this._messages.push({ id: this._nextId++, role: "user", text: trimmed });
114
+ const id = generateUserMessageId();
115
+ this._messages.push({ id, role: "user", text: trimmed });
80
116
  this._inFlight = true;
81
117
  this.notify();
82
- const event = { text: trimmed };
118
+ const event = { text: trimmed, id };
83
119
  if (opts?.activeLidSlot) event.activeLidSlot = opts.activeLidSlot;
120
+ if (opts?.forwardedProps) event.forwardedProps = opts.forwardedProps;
84
121
  for (const listener of this.sendListeners) listener(event);
85
122
  }
86
123
  /**
@@ -133,7 +170,9 @@ var ChatSession = class {
133
170
  * message as 'error' (so the trail can render a styled error chip
134
171
  * instead of pretending the partial text was a complete answer),
135
172
  * appends a system-role message with the error text for visibility,
136
- * and clears inFlight.
173
+ * and clears inFlight. Also drops any pending thinking narration
174
+ * — leaving stale "Let me check…" alongside an error chip reads
175
+ * as if the assistant is still working when it isn't.
137
176
  */
138
177
  error(message) {
139
178
  for (const m of this._messages) {
@@ -146,16 +185,23 @@ var ChatSession = class {
146
185
  status: "error"
147
186
  });
148
187
  this._inFlight = false;
188
+ this._runActive = false;
189
+ this._thinkingText = "";
149
190
  this.notify();
150
191
  }
151
192
  /**
152
193
  * User clicked the in-flight stop button. Clears inFlight and
153
194
  * fires an "interrupt" event so transports can cancel their
154
- * in-flight request. No-op if not in-flight (idempotent).
195
+ * in-flight request. No-op if not in-flight (idempotent). Drops
196
+ * any pending thinking narration for the same reason as error():
197
+ * the visitor explicitly asked the assistant to stop, so it
198
+ * shouldn't keep narrating in the trail.
155
199
  */
156
200
  interrupt() {
157
- if (!this._inFlight) return;
201
+ if (!this._inFlight && !this._runActive) return;
158
202
  this._inFlight = false;
203
+ this._runActive = false;
204
+ this._thinkingText = "";
159
205
  this.notify();
160
206
  for (const listener of this.interruptListeners) listener();
161
207
  }
@@ -166,6 +212,8 @@ var ChatSession = class {
166
212
  reset() {
167
213
  this._messages = [];
168
214
  this._inFlight = false;
215
+ this._runActive = false;
216
+ this._thinkingText = "";
169
217
  this._nextId = 1;
170
218
  this.notify();
171
219
  }
@@ -260,31 +308,64 @@ var ChatSession = class {
260
308
  listener({ toolCallId, result, approved });
261
309
  }
262
310
  }
311
+ /**
312
+ * Hydrate from the server transcript (the /session SSE's
313
+ * messages-snapshot) — the server is the transcript of record
314
+ * (FEAT-1783701440). Snapshot order is truth: shared ids keep the
315
+ * LOCAL object (it may carry richer live tool-call state) but take the
316
+ * snapshot's position; snapshot-only messages (history on reload, a
317
+ * nudge missed during an SSE gap) are inserted; local-only messages
318
+ * (an in-flight turn, the greeting) float after in local order.
319
+ *
320
+ * Deploy-boundary guard: a user message persisted under a pre-Phase-2
321
+ * transport id (before ids were threaded) would arrive as
322
+ * "snapshot-only" next to its local twin — skip snapshot USER messages
323
+ * whose exact text already exists locally under a different id.
324
+ */
325
+ hydrate(snapshotMessages) {
326
+ if (!snapshotMessages.length) return;
327
+ const localById = new Map(this._messages.map((m) => [String(m.id), m]));
328
+ const twinBudget = /* @__PURE__ */ new Map();
329
+ for (const m of this._messages) {
330
+ if (m.role === "user") twinBudget.set(m.text, (twinBudget.get(m.text) ?? 0) + 1);
331
+ }
332
+ const seen = /* @__PURE__ */ new Set();
333
+ const merged = [];
334
+ for (const sm of snapshotMessages) {
335
+ const key = String(sm.id);
336
+ const local = localById.get(key);
337
+ if (local) {
338
+ seen.add(key);
339
+ merged.push(local);
340
+ continue;
341
+ }
342
+ if (sm.role === "user") {
343
+ const budget = twinBudget.get(sm.text) ?? 0;
344
+ if (budget > 0) {
345
+ twinBudget.set(sm.text, budget - 1);
346
+ continue;
347
+ }
348
+ }
349
+ merged.push(sm);
350
+ }
351
+ for (const m of this._messages) {
352
+ if (!seen.has(String(m.id))) merged.push(m);
353
+ }
354
+ this._messages = merged;
355
+ this.notify();
356
+ }
263
357
  notify() {
264
358
  const state = this.getState();
265
- this.persist();
266
359
  for (const sub of this.subscribers) sub(state);
267
360
  }
268
- persist() {
269
- try {
270
- if (this._messages.length === 0) {
271
- globalThis.localStorage?.removeItem(CHAT_SESSION_STORAGE_KEY);
272
- return;
273
- }
274
- const payload = { messages: this._messages, nextId: this._nextId };
275
- globalThis.localStorage?.setItem(CHAT_SESSION_STORAGE_KEY, JSON.stringify(payload));
276
- } catch {
277
- }
278
- }
279
361
  };
280
362
  var chatSession = new ChatSession();
281
363
 
282
364
  // src/ChatTransport.ts
365
+ var SESSION_READ_DEADLINE_MS = 6e4;
283
366
  var FALLBACK_DEBOUNCE_MS = 1500;
284
- function readSyntroToken() {
285
- if (typeof window === "undefined") return void 0;
286
- const cfg = window.__SYNTRO_CONFIG__;
287
- const token = cfg?.token;
367
+ function readRuntimeToken(runtime2) {
368
+ const token = runtime2?.token;
288
369
  return typeof token === "string" && token.length > 0 ? token : void 0;
289
370
  }
290
371
  function debug(...args) {
@@ -293,6 +374,38 @@ function debug(...args) {
293
374
  console.debug("[chat-transport]", ...args);
294
375
  }
295
376
  }
377
+ function parseToolCallArgs(toolCall) {
378
+ if (toolCall.args && typeof toolCall.args === "object") return toolCall.args;
379
+ const buf = toolCall.argsBuffer;
380
+ if (!buf) return null;
381
+ try {
382
+ const parsed = JSON.parse(buf);
383
+ return parsed && typeof parsed === "object" ? parsed : null;
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+ function toolNarrationVariants(name) {
389
+ const n = (name || "").toLowerCase();
390
+ if (n.includes("search") || n.includes("retrieve"))
391
+ return ["Assembling relevant information", "Pulling together what matters for you"];
392
+ if (n.includes("route"))
393
+ return ["Diving deeper on specific products for you", "Exploring the best options for you"];
394
+ if (n.includes("read") || n.includes("section") || n.includes("overview"))
395
+ return ["Buttoning up details to point the right way", "Lining up the details for you"];
396
+ if (n.includes("selector"))
397
+ return ["Finalizing where to guide you", "Pinpointing the right spot for you"];
398
+ if (n.includes("wiki"))
399
+ return ["Looking that up for you", "Checking the knowledge base for you"];
400
+ if (n.includes("mount") || n.includes("unmount") || n.includes("element") || n.includes("response") || n.includes("primary") || n.includes("action"))
401
+ return ["Materializing the information for you", "Bringing it onto your screen"];
402
+ return ["Putting your answer together for you", "Almost there"];
403
+ }
404
+ function mergeForwardedProps(prev, next) {
405
+ if (next.forwardedProps !== void 0) return next;
406
+ if (!prev?.forwardedProps) return next;
407
+ return { ...next, forwardedProps: prev.forwardedProps };
408
+ }
296
409
  var ChatTransport = class {
297
410
  constructor() {
298
411
  this._config = null;
@@ -305,6 +418,12 @@ var ChatTransport = class {
305
418
  this._connectInFlight = null;
306
419
  /** Bounded streaming-message id for the currently-being-typed assistant turn. */
307
420
  this._currentAssistantMessageId = null;
421
+ /** Intent messageIds this transport actually dispatched (BUG-1784021805).
422
+ * The server's `suppressToast` hint assumes the intent frame reached us —
423
+ * but that frame is fire-and-forget, and a fresh visitor's connection gap
424
+ * loses it. Only the transport knows what it dispatched, so suppression is
425
+ * honored ONLY when the sibling intent (`{id}-intent`) went through here. */
426
+ this._dispatchedNudgeIntentIds = /* @__PURE__ */ new Set();
308
427
  /** Outcome bookkeeping for telemetry. */
309
428
  this._hadTurnstileToken = null;
310
429
  /** Count of successful round-trips for telemetry payloads. */
@@ -324,8 +443,28 @@ var ChatTransport = class {
324
443
  * pins the header to the bar that actually sent the message.
325
444
  */
326
445
  this._pendingLidSlot = null;
446
+ /**
447
+ * Per-send forwardedProps override. Set in the ``chatSession.onSend``
448
+ * listener before each ``_forwardUserMessage`` and spread OVER the
449
+ * config-level forwardedProps for that one request (see
450
+ * ``_resolveForwardedProps``). Overwritten on EVERY send — ``null`` when
451
+ * the send carried no envelope — so turn provenance can never leak onto a
452
+ * later typed message and make it look synthetic. Same lifecycle as
453
+ * ``_pendingLidSlot``.
454
+ */
455
+ this._pendingForwardedProps = null;
327
456
  /** Active debounce timer; null when no debounce pending. */
328
457
  this._errorDebounceTimer = null;
458
+ /** Rotates thinking-chip phrasings during a long/looping tool phase (C9). */
459
+ this._thinkingCycleTimer = null;
460
+ /**
461
+ * Per-class rotation counter, keyed by the narration set's first variant.
462
+ * A NEW tool class shows its primary (variant A) so it's recognizable; a
463
+ * REPEATED same-class call advances to the next variant, so a research loop
464
+ * of (e.g.) several searches reads as different thinking, not one repeated
465
+ * label. Bounded to ~6 keys (one per tool class).
466
+ */
467
+ this._thinkingClassSeq = /* @__PURE__ */ new Map();
329
468
  /** Most recent error payload, captured so debounced fallback can attach it. */
330
469
  this._lastErrorPayload = null;
331
470
  this._fallbackListeners = /* @__PURE__ */ new Set();
@@ -337,6 +476,14 @@ var ChatTransport = class {
337
476
  */
338
477
  this._transportId = `tx_${Math.random().toString(36).slice(2, 8)}`;
339
478
  this._configuredAt = 0;
479
+ // -------------------------------------------------------------------------
480
+ // Session stream — server-initiated events (proactive nudges)
481
+ // -------------------------------------------------------------------------
482
+ /** AbortController for the live /session SSE reader; null = not running. */
483
+ this._sessionStreamAbort = null;
484
+ // (read-deadline constant lives at module scope: SESSION_READ_DEADLINE_MS)
485
+ /** Capped reconnect backoff for the session stream. */
486
+ this._sessionStreamBackoffMs = 2e3;
340
487
  }
341
488
  /**
342
489
  * Configure the transport. Idempotent — calling again with the same
@@ -346,7 +493,7 @@ var ChatTransport = class {
346
493
  configure(config) {
347
494
  const same = this._config && this._config.backendUrl === config.backendUrl && this._config.threadId === config.threadId;
348
495
  if (same) {
349
- this._config = { ...this._config, ...config };
496
+ this._config = { ...this._config, ...mergeForwardedProps(this._config, config) };
350
497
  return;
351
498
  }
352
499
  this._disconnect();
@@ -394,6 +541,22 @@ var ChatTransport = class {
394
541
  simulateError(payload) {
395
542
  this._handleErrorEvent(payload);
396
543
  }
544
+ /**
545
+ * Test seam — drive the FULL server-error path (fallback kernel + the
546
+ * visitor-facing chip), exactly what the `case 'error'` transport-event
547
+ * branch does. `midAnswer` simulates an assistant message having been
548
+ * mid-stream when the connection died.
549
+ */
550
+ simulateServerError(payload) {
551
+ if (payload.midAnswer) this._currentAssistantMessageId = "test-mid-answer";
552
+ this._onTransportEvent({
553
+ type: "error",
554
+ message: payload.message ?? "boom",
555
+ status: payload.status ?? null,
556
+ body: payload.body ?? null,
557
+ errorName: payload.errorName ?? null
558
+ });
559
+ }
397
560
  /**
398
561
  * Test seam — register a synthetic successful message-complete so
399
562
  * the hasSucceeded gate flips without a real AgUi round-trip.
@@ -440,6 +603,8 @@ var ChatTransport = class {
440
603
  this._agui = null;
441
604
  }
442
605
  this._clearDebounceTimer();
606
+ this._stopThinkingCycle();
607
+ this._thinkingClassSeq.clear();
443
608
  this._config = null;
444
609
  this._status = "idle";
445
610
  this._connectInFlight = null;
@@ -450,6 +615,7 @@ var ChatTransport = class {
450
615
  this._fallbackRendered = false;
451
616
  this._lastErrorPayload = null;
452
617
  this._pendingLidSlot = null;
618
+ this._pendingForwardedProps = null;
453
619
  }
454
620
  /**
455
621
  * Tear everything down — connection state PLUS host listeners.
@@ -460,6 +626,142 @@ var ChatTransport = class {
460
626
  reset() {
461
627
  this._disconnect();
462
628
  this._fallbackListeners.clear();
629
+ this._dispatchedNudgeIntentIds.clear();
630
+ this.stopSessionStream();
631
+ }
632
+ /**
633
+ * Open the persistent `/api/adaptive/session` SSE and feed its frames
634
+ * through the SAME `_onTransportEvent` switch the AG-UI path uses
635
+ * (FEAT-1783640628 — proactive nudges).
636
+ *
637
+ * The AG-UI cutover left this endpoint with no client consumer, so
638
+ * server-INITIATED events had nowhere to land. A fetch-based reader —
639
+ * unlike EventSource — can send the SDK token, and the visitor ids that
640
+ * write the server's nudge identity bridge. Idempotent; reconnects with
641
+ * capped backoff; `reset()`/`stopSessionStream()` tears it down.
642
+ */
643
+ async startSessionStream() {
644
+ if (this._sessionStreamAbort || !this._config) return;
645
+ const abort = new AbortController();
646
+ this._sessionStreamAbort = abort;
647
+ const baseUrl = this._config.backendUrl.replace(/\/$/, "");
648
+ const url = `${baseUrl}/api/adaptive/session`;
649
+ const headers = {};
650
+ const token = readRuntimeToken(this._config?.runtime);
651
+ if (token) headers.Authorization = `Bearer ${token}`;
652
+ const cft = await this._acquireTurnstileWithChallenge();
653
+ if (cft) headers["CF-Turnstile-Token"] = cft;
654
+ if (abort.signal.aborted) return;
655
+ const readIds = () => {
656
+ try {
657
+ const rt = this._config?.runtime;
658
+ const did = rt?.telemetry?.getDistinctId?.();
659
+ const sid = rt?.telemetry?.getSessionId?.();
660
+ return {
661
+ did: typeof did === "string" && did.length > 0 ? did : void 0,
662
+ sid: typeof sid === "string" && sid.length > 0 ? sid : void 0
663
+ };
664
+ } catch {
665
+ return {};
666
+ }
667
+ };
668
+ const hasTelemetryAccessor = () => {
669
+ try {
670
+ const rt = this._config?.runtime;
671
+ return typeof rt?.telemetry?.getDistinctId === "function";
672
+ } catch {
673
+ return false;
674
+ }
675
+ };
676
+ let ids = readIds();
677
+ for (let i = 0; i < 80 && hasTelemetryAccessor() && !ids.did && !abort.signal.aborted; i++) {
678
+ await new Promise((r) => setTimeout(r, 250));
679
+ ids = readIds();
680
+ }
681
+ if (abort.signal.aborted) return;
682
+ if (ids.did) headers["X-Distinct-Id"] = ids.did;
683
+ if (ids.sid) headers["X-Syntro-Session"] = ids.sid;
684
+ const runReader = async () => {
685
+ const connAbort = new AbortController();
686
+ const onOuterAbort = () => connAbort.abort();
687
+ abort.signal.addEventListener("abort", onOuterAbort, { once: true });
688
+ const abortRejection = new Promise((_, reject) => {
689
+ connAbort.signal.addEventListener(
690
+ "abort",
691
+ () => reject(new Error("session stream read deadline exceeded")),
692
+ { once: true }
693
+ );
694
+ });
695
+ abortRejection.catch(() => {
696
+ });
697
+ try {
698
+ const response = await fetch(url, {
699
+ headers,
700
+ credentials: "include",
701
+ signal: connAbort.signal
702
+ });
703
+ if (!response.ok || !response.body) {
704
+ throw new Error(`session stream connect failed: ${response.status}`);
705
+ }
706
+ this._sessionStreamBackoffMs = 2e3;
707
+ const reader = response.body.getReader();
708
+ const decoder = new TextDecoder();
709
+ let buffer = "";
710
+ for (; ; ) {
711
+ const deadline = setTimeout(() => connAbort.abort(), SESSION_READ_DEADLINE_MS);
712
+ let result;
713
+ try {
714
+ result = await Promise.race([reader.read(), abortRejection]);
715
+ } catch (err) {
716
+ void reader.cancel().catch(() => {
717
+ });
718
+ throw err;
719
+ } finally {
720
+ clearTimeout(deadline);
721
+ }
722
+ const { done, value } = result;
723
+ if (done) return;
724
+ buffer += decoder.decode(value, { stream: true });
725
+ let sep = buffer.indexOf("\n\n");
726
+ while (sep >= 0) {
727
+ const frame = buffer.slice(0, sep);
728
+ buffer = buffer.slice(sep + 2);
729
+ sep = buffer.indexOf("\n\n");
730
+ const data = frame.split("\n").filter((l) => l.startsWith("data: ")).map((l) => l.slice(6)).join("");
731
+ if (!data) continue;
732
+ try {
733
+ this._onTransportEvent(JSON.parse(data));
734
+ } catch (err) {
735
+ console.warn("[adaptive-chatbot] session-stream frame parse failed", err);
736
+ }
737
+ }
738
+ }
739
+ } finally {
740
+ abort.signal.removeEventListener("abort", onOuterAbort);
741
+ }
742
+ };
743
+ void (async () => {
744
+ while (!abort.signal.aborted) {
745
+ try {
746
+ await runReader();
747
+ } catch (err) {
748
+ if (abort.signal.aborted) return;
749
+ console.warn("[adaptive-chatbot] session stream dropped; reconnecting", err);
750
+ }
751
+ if (abort.signal.aborted) return;
752
+ await new Promise((r) => setTimeout(r, this._sessionStreamBackoffMs));
753
+ this._sessionStreamBackoffMs = Math.min(this._sessionStreamBackoffMs * 2, 3e4);
754
+ const fresh = readIds();
755
+ if (fresh.did) headers["X-Distinct-Id"] = fresh.did;
756
+ if (fresh.sid) headers["X-Syntro-Session"] = fresh.sid;
757
+ }
758
+ })();
759
+ await Promise.resolve();
760
+ }
761
+ stopSessionStream() {
762
+ this._sessionStreamAbort?.abort();
763
+ this._sessionStreamAbort = null;
764
+ this._sessionStreamBackoffMs = 2e3;
463
765
  }
464
766
  _clearDebounceTimer() {
465
767
  if (this._errorDebounceTimer) {
@@ -467,6 +769,36 @@ var ChatTransport = class {
467
769
  this._errorDebounceTimer = null;
468
770
  }
469
771
  }
772
+ /**
773
+ * Show a per-call rotating variant in the thinking chip immediately (so
774
+ * consecutive tool calls differ), then rotate through the remaining phrasings
775
+ * every 4s while the same tool phase runs (C9). A
776
+ * single tool call can sit for tens of seconds (the model reasoning about the
777
+ * next step, or a slow retrieval); a static chip reads as frozen and hides a
778
+ * loop. Cycling makes a long/looping phase read as live progress. Restarting
779
+ * (a new tool), any output (message/thinking), or teardown clears the cycle.
780
+ */
781
+ _startThinkingCycle(variants) {
782
+ this._stopThinkingCycle();
783
+ const key = variants[0] ?? "";
784
+ const seq = this._thinkingClassSeq.get(key) ?? 0;
785
+ this._thinkingClassSeq.set(key, seq + 1);
786
+ let i = variants.length > 0 ? seq % variants.length : 0;
787
+ chatSession.setThinkingText(variants[i] ?? "Working on it\u2026");
788
+ if (variants.length < 2) return;
789
+ const id = setInterval(() => {
790
+ if (this._thinkingCycleTimer !== id) return;
791
+ i = (i + 1) % variants.length;
792
+ chatSession.setThinkingText(variants[i]);
793
+ }, 4e3);
794
+ this._thinkingCycleTimer = id;
795
+ }
796
+ _stopThinkingCycle() {
797
+ if (this._thinkingCycleTimer) {
798
+ clearInterval(this._thinkingCycleTimer);
799
+ this._thinkingCycleTimer = null;
800
+ }
801
+ }
470
802
  /**
471
803
  * Shared error-handling kernel — called by the AG-UI subscriber and
472
804
  * by the simulateError test seam. Publishes transport_error
@@ -484,6 +816,7 @@ var ChatTransport = class {
484
816
  body,
485
817
  errorName: payload.errorName ?? null
486
818
  };
819
+ this._stopThinkingCycle();
487
820
  this._config?.runtime.events.publish("chatbot.transport_error", {
488
821
  source: "chat-transport",
489
822
  transportId: this._transportId,
@@ -534,10 +867,25 @@ var ChatTransport = class {
534
867
  // -------------------------------------------------------------------------
535
868
  // Internal: chatSession wiring
536
869
  // -------------------------------------------------------------------------
870
+ /**
871
+ * forwardedProps for the NEXT outbound AG-UI request: the config-level
872
+ * value (late-bound — `configure()` can update uiTemplates /
873
+ * elementsEnabled mid-session without a reconnect, so this must be
874
+ * resolved per request, not captured at construction) with the per-send
875
+ * envelope spread over it. Shallow merge; per-send wins per top-level key.
876
+ */
877
+ _resolveForwardedProps() {
878
+ const raw = this._config?.forwardedProps;
879
+ const base = typeof raw === "function" ? raw() : raw;
880
+ const pending = this._pendingForwardedProps;
881
+ if (!pending) return base;
882
+ return { ...base ?? {}, ...pending };
883
+ }
537
884
  _wireSession() {
538
- this._sessionUnsubSend = chatSession.onSend(({ text, activeLidSlot }) => {
885
+ this._sessionUnsubSend = chatSession.onSend(({ text, id, activeLidSlot, forwardedProps }) => {
539
886
  this._pendingLidSlot = typeof activeLidSlot === "string" && activeLidSlot.length > 0 ? activeLidSlot : null;
540
- void this._forwardUserMessage(text);
887
+ this._pendingForwardedProps = forwardedProps ?? null;
888
+ void this._forwardUserMessage(text, id);
541
889
  });
542
890
  this._sessionUnsubInterrupt = chatSession.onInterrupt(() => {
543
891
  this._agui?.send({ type: "stop-generation" });
@@ -546,13 +894,22 @@ var ChatTransport = class {
546
894
  this._agui?.send({ type: "tool-result", toolCallId, result, approved });
547
895
  });
548
896
  }
549
- async _forwardUserMessage(text) {
550
- const ok = await this._ensureConnected();
897
+ async _forwardUserMessage(text, id) {
898
+ let ok = false;
899
+ try {
900
+ ok = await this._ensureConnected();
901
+ } catch (err) {
902
+ this._status = "error";
903
+ this._handleErrorEvent({
904
+ message: err instanceof Error ? err.message : String(err),
905
+ errorName: err instanceof Error ? err.name : null
906
+ });
907
+ }
551
908
  if (!ok || !this._agui) {
552
909
  chatSession.error("Couldn't connect to chat. Please try again.");
553
910
  return;
554
911
  }
555
- this._agui.send({ type: "user-message", text });
912
+ this._agui.send({ type: "user-message", text, ...id ? { id } : {} });
556
913
  }
557
914
  // -------------------------------------------------------------------------
558
915
  // Internal: connect (lazy, Turnstile-gated)
@@ -573,7 +930,7 @@ var ChatTransport = class {
573
930
  if (this._config === null) return false;
574
931
  const baseUrl = this._config.backendUrl.replace(/\/$/, "");
575
932
  const streamUrl = `${baseUrl}/api/adaptive/stream`;
576
- const token = readSyntroToken();
933
+ const token = readRuntimeToken(this._config?.runtime);
577
934
  const buildHeaders = () => {
578
935
  const h = {};
579
936
  if (token) h.Authorization = `Bearer ${token}`;
@@ -582,23 +939,30 @@ var ChatTransport = class {
582
939
  if (typeof lidSlot === "string" && lidSlot.length > 0) {
583
940
  h["X-Active-Lid-Slot"] = lidSlot;
584
941
  }
942
+ const asciiSafe = (v, max) => (v ?? "").replace(/[^\x20-\x7e]/g, "").slice(0, max).trim();
943
+ const pid = asciiSafe(this._config?.personaId, 32);
944
+ const pname = asciiSafe(this._config?.personaName, 24);
945
+ if (pid && pname) {
946
+ h["X-Syntro-Persona-Id"] = pid;
947
+ h["X-Syntro-Persona-Name"] = pname;
948
+ const prole = asciiSafe(this._config?.personaRole, 48);
949
+ if (prole) h["X-Syntro-Persona-Role"] = prole;
950
+ }
585
951
  try {
586
952
  const rt = this._config?.runtime;
587
- const fromRuntime = rt?.telemetry?.getDistinctId?.();
588
- const fromWindow = window.posthog?.get_distinct_id?.();
589
- const did = fromRuntime || fromWindow;
953
+ const did = rt?.telemetry?.getDistinctId?.();
590
954
  if (typeof did === "string" && did.length > 0) {
591
955
  h["X-Distinct-Id"] = did;
592
956
  }
957
+ const sid = rt?.telemetry?.getSessionId?.();
958
+ if (typeof sid === "string" && sid.length > 0) {
959
+ h["X-Syntro-Session"] = sid;
960
+ }
593
961
  } catch {
594
962
  }
595
963
  return h;
596
964
  };
597
- const runtime2 = this._config.runtime;
598
- const resolveForwardedProps = () => {
599
- const raw = this._config?.forwardedProps;
600
- return typeof raw === "function" ? raw() : raw;
601
- };
965
+ const resolveForwardedProps = () => this._resolveForwardedProps();
602
966
  this._agui = new AgUiTransport({
603
967
  url: streamUrl,
604
968
  headers: buildHeaders,
@@ -612,6 +976,8 @@ var ChatTransport = class {
612
976
  credentials: "include",
613
977
  forwardedProps: resolveForwardedProps,
614
978
  onA2UIEvent: (payload) => {
979
+ const runtime2 = this._config?.runtime;
980
+ if (!runtime2) return;
615
981
  const mutations = decodeMutationEnvelope(payload);
616
982
  if (mutations !== null) {
617
983
  const handler = this._config?.onElementMutation;
@@ -676,6 +1042,50 @@ var ChatTransport = class {
676
1042
  }
677
1043
  return null;
678
1044
  }
1045
+ /**
1046
+ * Convert one server-transcript message (the session store shape:
1047
+ * ``{id, role, content, timestamp, status, toolCalls?}`` with toolCalls
1048
+ * in AG-UI wire shape) into a TrailMessage. Returns null for entries the
1049
+ * trail can't render (missing id, system/tool roles, empty content with
1050
+ * no tool receipts) — the transcript may legitimately contain them.
1051
+ */
1052
+ _snapshotToTrailMessage(raw) {
1053
+ if (typeof raw !== "object" || raw === null) return null;
1054
+ const m = raw;
1055
+ const id = m.id;
1056
+ const role = m.role;
1057
+ if (typeof id !== "string" || !id) return null;
1058
+ if (role !== "user" && role !== "assistant") return null;
1059
+ const text = String(m.content ?? m.text ?? "");
1060
+ const toolCalls = this._snapshotToolCalls(m.toolCalls);
1061
+ if (!text && toolCalls.length === 0) return null;
1062
+ const message = { id, role, text, status: "complete" };
1063
+ if (toolCalls.length) message.toolCalls = toolCalls;
1064
+ return message;
1065
+ }
1066
+ /** AG-UI wire-shape toolCalls → settled TrailToolCall receipts. */
1067
+ _snapshotToolCalls(raw) {
1068
+ if (!Array.isArray(raw)) return [];
1069
+ const out = [];
1070
+ for (const entry of raw) {
1071
+ if (typeof entry !== "object" || entry === null) continue;
1072
+ const tc = entry;
1073
+ const fn = tc.function ?? {};
1074
+ if (typeof tc.id !== "string" || typeof fn.name !== "string" || !fn.name) continue;
1075
+ let args;
1076
+ if (typeof fn.arguments === "string" && fn.arguments) {
1077
+ try {
1078
+ const parsed = JSON.parse(fn.arguments);
1079
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1080
+ args = parsed;
1081
+ }
1082
+ } catch {
1083
+ }
1084
+ }
1085
+ out.push({ id: tc.id, name: fn.name, status: "done", ...args ? { args } : {} });
1086
+ }
1087
+ return out;
1088
+ }
679
1089
  /** Map AG-UI ToolCallStatus to TrailToolCall status. */
680
1090
  _mapToolCallStatus(status) {
681
1091
  switch (status) {
@@ -694,18 +1104,29 @@ var ChatTransport = class {
694
1104
  }
695
1105
  }
696
1106
  _onTransportEvent(event) {
697
- const runtime2 = this._config?.runtime;
698
1107
  switch (event.type) {
699
1108
  case "session-ready":
700
- case "messages-snapshot":
1109
+ return;
1110
+ case "messages-snapshot": {
1111
+ const converted = (event.messages ?? []).map((m) => this._snapshotToTrailMessage(m)).filter((m) => m !== null);
1112
+ if (converted.length) chatSession.hydrate(converted);
1113
+ return;
1114
+ }
701
1115
  case "typing":
1116
+ if (!event.isTyping) this._stopThinkingCycle();
1117
+ chatSession.setRunActive(event.isTyping);
702
1118
  return;
703
1119
  case "message-append": {
1120
+ this._stopThinkingCycle();
1121
+ chatSession.clearThinking();
704
1122
  this._currentAssistantMessageId = event.message.id;
705
1123
  chatSession.receiveStart(event.message.id);
706
1124
  if (event.message.content && event.message.content.length > 0) {
707
1125
  chatSession.receiveDelta(event.message.id, event.message.content);
708
1126
  }
1127
+ if (event.message.status === "complete") {
1128
+ chatSession.receiveEnd(event.message.id);
1129
+ }
709
1130
  return;
710
1131
  }
711
1132
  case "message-delta": {
@@ -714,9 +1135,54 @@ var ChatTransport = class {
714
1135
  chatSession.receiveDelta(id, event.delta);
715
1136
  return;
716
1137
  }
1138
+ case "nudge-intent": {
1139
+ try {
1140
+ if (event.messageId) this._dispatchedNudgeIntentIds.add(event.messageId);
1141
+ document.dispatchEvent(
1142
+ new CustomEvent("syntro:nudge", {
1143
+ detail: {
1144
+ pattern: event.pattern,
1145
+ confidence: event.confidence,
1146
+ messageId: event.messageId,
1147
+ phase: "intent"
1148
+ }
1149
+ })
1150
+ );
1151
+ } catch {
1152
+ }
1153
+ return;
1154
+ }
1155
+ case "nudge": {
1156
+ const id = event.messageId ?? this._currentAssistantMessageId;
1157
+ if (id) chatSession.receiveEnd(id);
1158
+ try {
1159
+ const intentDispatched = id != null && this._dispatchedNudgeIntentIds.has(`${id}-intent`);
1160
+ document.dispatchEvent(
1161
+ new CustomEvent("syntro:nudge", {
1162
+ detail: {
1163
+ pattern: event.pattern,
1164
+ confidence: event.confidence,
1165
+ messageId: id,
1166
+ suppressToast: event.suppressToast === true && intentDispatched
1167
+ }
1168
+ })
1169
+ );
1170
+ } catch {
1171
+ }
1172
+ return;
1173
+ }
1174
+ case "thinking-delta": {
1175
+ return;
1176
+ }
1177
+ case "thinking-clear": {
1178
+ this._stopThinkingCycle();
1179
+ chatSession.clearThinking();
1180
+ return;
1181
+ }
717
1182
  case "message-complete": {
718
1183
  const id = event.messageId ?? this._currentAssistantMessageId;
719
1184
  if (!id) return;
1185
+ this._stopThinkingCycle();
720
1186
  chatSession.receiveEnd(id);
721
1187
  this._currentAssistantMessageId = null;
722
1188
  this._messagesSucceeded += 1;
@@ -725,19 +1191,25 @@ var ChatTransport = class {
725
1191
  return;
726
1192
  }
727
1193
  case "tool-call": {
1194
+ const existing = this._findToolCallStatus(event.toolCall.id);
1195
+ if (!existing) {
1196
+ this._startThinkingCycle(toolNarrationVariants(event.toolCall.name));
1197
+ }
728
1198
  const targetMessageId = event.messageId ?? this._currentAssistantMessageId;
729
1199
  if (!targetMessageId) return;
730
- const existing = this._findToolCallStatus(event.toolCall.id);
1200
+ const parsedArgs = parseToolCallArgs(event.toolCall);
731
1201
  if (!existing) {
732
1202
  chatSession.addToolCall(targetMessageId, {
733
1203
  id: event.toolCall.id,
734
1204
  name: event.toolCall.name,
735
- status: this._mapToolCallStatus(event.toolCall.status)
1205
+ status: this._mapToolCallStatus(event.toolCall.status),
1206
+ ...parsedArgs ? { args: parsedArgs } : {}
736
1207
  });
737
1208
  } else {
738
1209
  chatSession.updateToolCall(event.toolCall.id, {
739
1210
  name: event.toolCall.name,
740
- status: this._mapToolCallStatus(event.toolCall.status)
1211
+ status: this._mapToolCallStatus(event.toolCall.status),
1212
+ ...parsedArgs ? { args: parsedArgs } : {}
741
1213
  });
742
1214
  }
743
1215
  return;
@@ -745,9 +1217,14 @@ var ChatTransport = class {
745
1217
  case "tool-call-args-delta":
746
1218
  debug("tool-call-args-delta", event);
747
1219
  return;
748
- case "tool-call-done":
749
- chatSession.updateToolCall(event.toolCallId, { status: "done" });
1220
+ case "tool-call-done": {
1221
+ const doneArgs = parseToolCallArgs(event);
1222
+ chatSession.updateToolCall(event.toolCallId, {
1223
+ status: "done",
1224
+ ...doneArgs ? { args: doneArgs } : {}
1225
+ });
750
1226
  return;
1227
+ }
751
1228
  case "a2ui":
752
1229
  return;
753
1230
  case "error": {
@@ -763,7 +1240,10 @@ var ChatTransport = class {
763
1240
  body,
764
1241
  errorName: event.errorName ?? null
765
1242
  });
766
- chatSession.error(event.message ?? "Chat connection failed");
1243
+ const wasMidAnswer = this._currentAssistantMessageId != null;
1244
+ chatSession.error(
1245
+ wasMidAnswer ? "The connection dropped partway through \u2014 ask again and I'll pick it back up." : "Hmm, that didn't go through. Please try again."
1246
+ );
767
1247
  this._currentAssistantMessageId = null;
768
1248
  if (this._agui) {
769
1249
  this._agui.disconnect();
@@ -852,7 +1332,7 @@ function matchEvent(raw, opts = {}) {
852
1332
  ts: raw.timestamp,
853
1333
  kind: "nav",
854
1334
  text: truncate(`navigated to ${path}`),
855
- ref: path
1335
+ ref: truncate(path)
856
1336
  };
857
1337
  }
858
1338
  case "$autocapture": {
@@ -904,7 +1384,7 @@ function matchEvent(raw, opts = {}) {
904
1384
  ts: raw.timestamp,
905
1385
  kind: "view",
906
1386
  text: truncate(`viewed the '${section}' section`),
907
- ref: section
1387
+ ref: truncate(section)
908
1388
  };
909
1389
  }
910
1390
  case "nav.scroll_depth": {
@@ -940,12 +1420,17 @@ function matchEvent(raw, opts = {}) {
940
1420
  text: truncate(`hovered on '${text}'`)
941
1421
  };
942
1422
  }
943
- case "ui.hesitation": {
944
- const text = elementText(props) || elementTag(props);
1423
+ // Canonical name is 'ui.hesitate' (StandardEvents.UI_HESITATE) — the rrweb
1424
+ // hesitation detector emits coordinates + duration_ms, no element chain, so
1425
+ // the summary is duration-based. (BUG-1784146789: this case was spelled
1426
+ // 'ui.hesitation', a name nothing emits, so the signal never arrived.)
1427
+ case "ui.hesitate": {
1428
+ const ms = typeof props.duration_ms === "number" ? props.duration_ms : null;
1429
+ const secs = ms !== null ? Math.round(ms / 1e3) : null;
945
1430
  return {
946
1431
  ts: raw.timestamp,
947
1432
  kind: "hesitation",
948
- text: truncate(`hesitated near '${text}'`)
1433
+ text: secs !== null ? `hesitated in place for ${secs}s` : "hesitated in place"
949
1434
  };
950
1435
  }
951
1436
  case "ui.focus_bounce": {
@@ -1005,6 +1490,28 @@ var ObserverQueue = class {
1005
1490
  this._clearInterval();
1006
1491
  await this._flush();
1007
1492
  }
1493
+ /**
1494
+ * Synchronously hand the newest backend-sized batch to an unload-safe sender.
1495
+ * Returns false when the sender rejected delivery and restores the batch so
1496
+ * the caller can fall back to fetch({ keepalive: true }).
1497
+ */
1498
+ flushOnPageHide(send) {
1499
+ if (this._disabled || this._buffer.length === 0) return true;
1500
+ this._clearInterval();
1501
+ const overflow = Math.max(0, this._buffer.length - 50);
1502
+ if (overflow > 0) {
1503
+ this._buffer.splice(0, overflow);
1504
+ this._dropped += overflow;
1505
+ }
1506
+ const batch = this._buffer.splice(0, 50);
1507
+ if (!send(batch)) {
1508
+ this._buffer.unshift(...batch);
1509
+ return false;
1510
+ }
1511
+ this._sent += batch.length;
1512
+ this._lastError = null;
1513
+ return true;
1514
+ }
1008
1515
  stats() {
1009
1516
  return {
1010
1517
  queued: this._buffer.length,
@@ -1067,14 +1574,16 @@ var ObserverQueue = class {
1067
1574
  };
1068
1575
 
1069
1576
  // src/observer/transport.ts
1070
- function buildBody(distinctId, batch) {
1071
- const body = { distinct_id: distinctId, batch };
1577
+ function buildBody(distinctId, sessionId, batch) {
1578
+ if (!distinctId || !sessionId) return null;
1579
+ const body = { distinct_id: distinctId, session_id: sessionId, batch };
1072
1580
  return JSON.stringify(body);
1073
1581
  }
1074
1582
  function createTransport(opts) {
1075
1583
  return {
1076
1584
  async send(batch) {
1077
- const body = buildBody(opts.getDistinctId(), batch);
1585
+ const body = buildBody(opts.getDistinctId(), opts.getSessionId(), batch);
1586
+ if (body === null) return;
1078
1587
  const resp = await fetch(opts.url, {
1079
1588
  method: "POST",
1080
1589
  headers: {
@@ -1094,11 +1603,14 @@ function createTransport(opts) {
1094
1603
  }
1095
1604
  },
1096
1605
  sendBeacon(batch) {
1097
- const body = buildBody(opts.getDistinctId(), batch);
1606
+ const body = buildBody(opts.getDistinctId(), opts.getSessionId(), batch);
1607
+ if (body === null) return false;
1098
1608
  const blob = new Blob([body], { type: "application/json" });
1099
1609
  const beacon = navigator?.sendBeacon;
1100
1610
  if (typeof beacon !== "function") return false;
1101
- return beacon.call(navigator, opts.url, blob);
1611
+ const separator = opts.url.includes("?") ? "&" : "?";
1612
+ const url = `${opts.url}${separator}syn=${encodeURIComponent(opts.token())}`;
1613
+ return beacon.call(navigator, url, blob);
1102
1614
  }
1103
1615
  };
1104
1616
  }
@@ -1124,11 +1636,15 @@ function _attachPostHog(ingest, _observableEvents) {
1124
1636
  return () => {
1125
1637
  };
1126
1638
  }
1127
- function _attachPageHide(_transport, queue) {
1639
+ function _attachPageHide(transport, queue) {
1128
1640
  const handler = () => {
1129
1641
  const stats = queue.stats();
1130
1642
  if (stats.queued > 0) {
1131
- void queue.flushNow();
1643
+ queue.flushOnPageHide((batch) => {
1644
+ if (transport.sendBeacon(batch)) return true;
1645
+ void transport.send(batch);
1646
+ return true;
1647
+ });
1132
1648
  }
1133
1649
  };
1134
1650
  window.addEventListener("pagehide", handler, { capture: true });
@@ -1137,7 +1653,7 @@ function _attachPageHide(_transport, queue) {
1137
1653
  function startObserver(opts) {
1138
1654
  if (_active) {
1139
1655
  _active.refCount += 1;
1140
- return _makeHandle();
1656
+ return _makeHandle(_active);
1141
1657
  }
1142
1658
  const transport = opts.transport ?? createTransport(opts);
1143
1659
  const queue = new ObserverQueue({
@@ -1147,14 +1663,14 @@ function startObserver(opts) {
1147
1663
  flush: (batch) => transport.send(batch)
1148
1664
  });
1149
1665
  const ingest = (raw) => {
1150
- const ev = matchEvent(raw, { observableEvents: opts.observableEvents });
1151
- if (ev !== null) {
1152
- queue.push(ev);
1666
+ const matched = matchEvent(raw, { observableEvents: opts.observableEvents });
1667
+ if (matched !== null) {
1668
+ queue.push(opts.enrich ? opts.enrich(matched, raw) : matched);
1153
1669
  }
1154
1670
  };
1155
1671
  const unsubscribePh = _attachPostHog(ingest, opts.observableEvents ?? []);
1156
1672
  const unsubscribePageHide = _attachPageHide(transport, queue);
1157
- _active = {
1673
+ const active = {
1158
1674
  refCount: 1,
1159
1675
  queue,
1160
1676
  unsubscribe: () => {
@@ -1163,20 +1679,21 @@ function startObserver(opts) {
1163
1679
  },
1164
1680
  options: opts
1165
1681
  };
1166
- return _makeHandle();
1682
+ _active = active;
1683
+ return _makeHandle(active);
1167
1684
  }
1168
- function _makeHandle() {
1685
+ function _makeHandle(owner) {
1169
1686
  return {
1170
1687
  stop() {
1171
- if (!_active) return;
1172
- _active.refCount -= 1;
1173
- if (_active.refCount <= 0) {
1174
- _active.unsubscribe();
1688
+ if (_active !== owner) return;
1689
+ owner.refCount -= 1;
1690
+ if (owner.refCount <= 0) {
1691
+ owner.unsubscribe();
1175
1692
  _active = null;
1176
1693
  }
1177
1694
  },
1178
1695
  stats() {
1179
- return _active?.queue.stats() ?? {
1696
+ return (_active === owner ? owner.queue.stats() : null) ?? {
1180
1697
  queued: 0,
1181
1698
  sent: 0,
1182
1699
  dropped: 0,
@@ -1185,18 +1702,52 @@ function _makeHandle() {
1185
1702
  };
1186
1703
  },
1187
1704
  ingest(raw) {
1188
- if (!_active) return;
1189
- const ev = matchEvent(raw, {
1190
- observableEvents: _active.options.observableEvents
1705
+ if (_active !== owner) return;
1706
+ const matched = matchEvent(raw, {
1707
+ observableEvents: owner.options.observableEvents
1191
1708
  });
1192
- if (ev !== null) _active.queue.push(ev);
1709
+ if (matched !== null) {
1710
+ owner.queue.push(owner.options.enrich ? owner.options.enrich(matched, raw) : matched);
1711
+ }
1193
1712
  },
1194
1713
  flushNow() {
1195
- return _active?.queue.flushNow() ?? Promise.resolve();
1714
+ return _active === owner ? owner.queue.flushNow() : Promise.resolve();
1196
1715
  }
1197
1716
  };
1198
1717
  }
1199
1718
 
1719
+ // src/observer/bus-translate.ts
1720
+ function eventTypeFromName(name) {
1721
+ switch (name) {
1722
+ case "ui.click":
1723
+ return "click";
1724
+ case "ui.submit":
1725
+ return "submit";
1726
+ case "ui.change":
1727
+ return "change";
1728
+ default:
1729
+ return void 0;
1730
+ }
1731
+ }
1732
+ function busEventToRawEvent(e) {
1733
+ if (!e?.name) return null;
1734
+ const ts = typeof e.ts === "number" ? e.ts : Date.now();
1735
+ if (e.source === "posthog") {
1736
+ const original = e.props?.originalEvent;
1737
+ if (typeof original !== "string" || original.length === 0) return null;
1738
+ const src = e.props ?? {};
1739
+ const properties = { ...src };
1740
+ if (Array.isArray(src.elements)) properties.$elements = src.elements;
1741
+ if (typeof src.tagName === "string") properties.$element_tag_name = src.tagName;
1742
+ if (typeof src.pathname === "string") properties.$pathname = src.pathname;
1743
+ if (typeof src.url === "string") properties.$current_url = src.url;
1744
+ const eventType = eventTypeFromName(e.name);
1745
+ if (eventType) properties.$event_type = eventType;
1746
+ return { event: original, timestamp: ts, properties };
1747
+ }
1748
+ return { event: e.name, timestamp: ts, properties: { ...e.props ?? {} } };
1749
+ }
1750
+
1200
1751
  // src/AdaptiveChatBarMountable.ts
1201
1752
  var STATE_KEY = "__syntroChatBarMount";
1202
1753
  function getState(container) {
@@ -1207,15 +1758,27 @@ function setState(container, state) {
1207
1758
  }
1208
1759
  function applyPlaceholder(bar, cfg) {
1209
1760
  if (cfg.placeholder !== void 0) bar.placeholder = cfg.placeholder;
1761
+ if (cfg.assistantName !== void 0) bar.assistantName = cfg.assistantName;
1762
+ if (cfg.personas !== void 0) bar.personas = cfg.personas;
1210
1763
  if (cfg.greeting !== void 0) bar.greeting = cfg.greeting;
1764
+ if (cfg.voiceInput !== void 0) bar.voiceInput = cfg.voiceInput;
1765
+ bar.introSuggestion = cfg.introSuggestion;
1766
+ bar.forceExpanded = cfg.forceExpanded === true;
1767
+ if (typeof cfg.maximized === "boolean") {
1768
+ bar.maximized = cfg.maximized;
1769
+ }
1211
1770
  }
1212
1771
  var _elementStore = null;
1213
1772
  var _templateWidgetMap = /* @__PURE__ */ new Map();
1773
+ var SERVER_MOUNTED_TILE_WIDGETS = ["syntro-product-reco-carousel"];
1214
1774
  function resolveTileWidget(templateId) {
1215
1775
  return _templateWidgetMap.get(templateId);
1216
1776
  }
1217
1777
  function refreshTemplateWidgetMap(uiTemplates) {
1218
1778
  _templateWidgetMap.clear();
1779
+ for (const tag of SERVER_MOUNTED_TILE_WIDGETS) {
1780
+ _templateWidgetMap.set(tag, tag);
1781
+ }
1219
1782
  if (!uiTemplates) return;
1220
1783
  const tiles = uiTemplates.tiles;
1221
1784
  if (!Array.isArray(tiles)) return;
@@ -1239,6 +1802,7 @@ function getOrCreateElementStore(runtime2) {
1239
1802
  handlers: [new TileHandler(resolveTileWidget), new ActionHandler(), new ItemHandler()],
1240
1803
  resolveTileWidget
1241
1804
  });
1805
+ setElementInstanceStore(_elementStore);
1242
1806
  return _elementStore;
1243
1807
  }
1244
1808
  var _hydrationStarted = false;
@@ -1248,73 +1812,195 @@ function hydrateOnce(runtime2, backendUrl) {
1248
1812
  const store = getOrCreateElementStore(runtime2);
1249
1813
  const trimmed = backendUrl.replace(/\/$/, "");
1250
1814
  const endpoint = trimmed ? `${trimmed}/api/adaptive/mounted_elements` : "/api/adaptive/mounted_elements";
1251
- fetchMountedElements({ endpoint }).then((response) => {
1815
+ fetchMountedElements({ endpoint, token: runtime2.token }).then((response) => {
1252
1816
  if (!response) return;
1253
1817
  void store.hydrate(response.mounted_elements);
1254
1818
  });
1255
1819
  }
1820
+ var PAGE_LEVEL_KINDS = /* @__PURE__ */ new Set(["view", "scroll", "idle", "hesitation", "hover", "nav"]);
1821
+ function buildObserverEnrich(runtimeRef) {
1822
+ return (ev, raw) => {
1823
+ const extract = runtimeRef.getProductIdentity;
1824
+ if (typeof extract !== "function") return ev;
1825
+ if (ev.kind === "click") {
1826
+ const chain = raw.properties.$elements;
1827
+ if (!Array.isArray(chain)) return ev;
1828
+ const id = extract(chain);
1829
+ if (!id) return ev;
1830
+ return {
1831
+ ...ev,
1832
+ product: {
1833
+ handle: id.productHandle,
1834
+ url: id.productUrl,
1835
+ ...id.productTitle ? { title: id.productTitle } : {}
1836
+ }
1837
+ };
1838
+ }
1839
+ if (!ev.product && PAGE_LEVEL_KINDS.has(ev.kind) && typeof location !== "undefined") {
1840
+ const id = extract([{ href: location.pathname }]);
1841
+ if (!id) return ev;
1842
+ return {
1843
+ ...ev,
1844
+ product: {
1845
+ handle: id.productHandle,
1846
+ url: id.productUrl,
1847
+ ...id.productTitle ? { title: id.productTitle } : {}
1848
+ }
1849
+ };
1850
+ }
1851
+ return ev;
1852
+ };
1853
+ }
1854
+ var _nudgeSurfaceWired = false;
1855
+ var _nudgeListener = null;
1856
+ function wireNudgeSurface(runtime2, backendUrl) {
1857
+ if (_nudgeSurfaceWired || typeof document === "undefined") return;
1858
+ _nudgeSurfaceWired = true;
1859
+ _nudgeListener = (e) => {
1860
+ try {
1861
+ const detail = e.detail;
1862
+ const isIntent = detail?.phase === "intent";
1863
+ if (!isIntent) {
1864
+ const store = getOrCreateElementStore(runtime2);
1865
+ const trimmed = backendUrl.replace(/\/$/, "");
1866
+ const endpoint = trimmed ? `${trimmed}/api/adaptive/mounted_elements` : "/api/adaptive/mounted_elements";
1867
+ void fetchMountedElements({ endpoint, token: runtime2.token }).then((response) => {
1868
+ if (!response) return;
1869
+ void store.hydrate(response.mounted_elements);
1870
+ });
1871
+ }
1872
+ if (detail?.suppressToast === true) return;
1873
+ const findCanvas = (root) => {
1874
+ const direct = root.querySelector("smart-canvas");
1875
+ if (direct) return direct;
1876
+ for (const el of root.querySelectorAll("*")) {
1877
+ const sr = el.shadowRoot;
1878
+ if (sr) {
1879
+ const found = findCanvas(sr);
1880
+ if (found) return found;
1881
+ }
1882
+ }
1883
+ return null;
1884
+ };
1885
+ const canvas = findCanvas(document);
1886
+ const messageId = e.detail?.messageId;
1887
+ const nudgeText = messageId ? (chatSession.getState().messages.find((m) => m.id === messageId)?.text ?? "").trim() : "";
1888
+ const title = nudgeText ? nudgeText.length > 140 ? `${nudgeText.slice(0, 137)}\u2026` : nudgeText : "I put something together for you";
1889
+ canvas?.previewNotification?.({
1890
+ title,
1891
+ body: "Tap to take a look"
1892
+ });
1893
+ } catch (err) {
1894
+ console.warn("[adaptive-chatbot] nudge surface reaction failed", err);
1895
+ }
1896
+ };
1897
+ document.addEventListener("syntro:nudge", _nudgeListener);
1898
+ }
1256
1899
  function configureTransportIfPossible(cfg) {
1257
- if (cfg.backendUrl === void 0 || !cfg.runtime) return;
1900
+ if (!cfg.runtime) return;
1901
+ const backendUrl = cfg.runtime.backendUrl;
1902
+ if (!backendUrl) return;
1258
1903
  const elementsActive = cfg.elementsEnabled === true && cfg.uiTemplates != null;
1259
1904
  refreshTemplateWidgetMap(elementsActive ? cfg.uiTemplates : void 0);
1260
1905
  const forwardedProps = elementsActive ? { elementsEnabled: true, uiTemplates: cfg.uiTemplates } : void 0;
1906
+ if (elementsActive) publishTemplatesIfPossible(backendUrl, cfg.uiTemplates, cfg.runtime?.token);
1261
1907
  const runtime2 = cfg.runtime;
1262
1908
  const onElementMutation = elementsActive ? (mutations) => {
1263
1909
  void getOrCreateElementStore(runtime2).apply(mutations);
1264
1910
  } : void 0;
1265
1911
  if (elementsActive) {
1266
- hydrateOnce(runtime2, cfg.backendUrl);
1912
+ hydrateOnce(runtime2, backendUrl);
1267
1913
  }
1914
+ const persona = pickPersona(cfg.personas ?? null);
1268
1915
  chatTransport.configure({
1269
1916
  ...cfg,
1917
+ backendUrl,
1270
1918
  forwardedProps,
1271
1919
  onElementMutation,
1272
- activeLidSlot: cfg._syntroSlotName
1920
+ activeLidSlot: cfg._syntroSlotName,
1921
+ personaId: persona?.id,
1922
+ personaName: persona?.name,
1923
+ personaRole: persona?.role_title
1273
1924
  });
1925
+ void chatTransport.startSessionStream();
1926
+ wireNudgeSurface(runtime2, backendUrl);
1274
1927
  startObserverIfPossible(cfg);
1275
1928
  }
1929
+ var _templatesPublished = false;
1930
+ function publishTemplatesIfPossible(backendUrl, uiTemplates, token) {
1931
+ if (_templatesPublished || typeof window === "undefined") return;
1932
+ if (!uiTemplates || typeof uiTemplates !== "object") return;
1933
+ _templatesPublished = true;
1934
+ const headers = { "Content-Type": "application/json" };
1935
+ if (token) headers.Authorization = `Bearer ${token}`;
1936
+ const trimmed = backendUrl.replace(/\/$/, "");
1937
+ void fetch(`${trimmed}/api/adaptive/templates`, {
1938
+ method: "POST",
1939
+ headers,
1940
+ credentials: "include",
1941
+ body: JSON.stringify({ ui_templates: uiTemplates })
1942
+ }).catch(() => {
1943
+ });
1944
+ }
1945
+ var _diveDeeperWired = false;
1946
+ function wireDiveDeeperListener() {
1947
+ if (_diveDeeperWired || typeof window === "undefined") return;
1948
+ _diveDeeperWired = true;
1949
+ window.addEventListener(DIVE_DEEPER_EVENT, (e) => {
1950
+ const detail = e.detail ?? {};
1951
+ const prompt = typeof detail.prompt === "string" && detail.prompt.trim() ? detail.prompt : detail.title ? "I'd like to dive deeper on this, I didn't see what I needed." : "";
1952
+ if (!prompt) return;
1953
+ const origin = { kind: "dive-deeper" };
1954
+ if (detail.title) origin.title = detail.title;
1955
+ if (detail.context) origin.context = detail.context;
1956
+ chatSession.send(prompt, { forwardedProps: { origin } });
1957
+ if (!chatSession.hasTransport()) {
1958
+ chatSession.error("Chat backend not configured \u2014 set backendUrl in the canvas config.");
1959
+ }
1960
+ });
1961
+ }
1276
1962
  var _observerStarted = false;
1963
+ var _observerRuntime = null;
1964
+ var _observerHandle = null;
1965
+ var _observerBusUnsubscribe = null;
1277
1966
  function startObserverIfPossible(cfg) {
1278
- if (_observerStarted) return;
1279
1967
  if (typeof window === "undefined") return;
1280
- const runtime2 = cfg.runtime;
1281
- if (!runtime2?.events?.subscribe) return;
1282
- const trimmed = (cfg.backendUrl ?? "").replace(/\/$/, "");
1968
+ const runtimeRef = cfg.runtime;
1969
+ if (!runtimeRef?.events?.subscribe) return;
1970
+ if (_observerStarted && _observerRuntime === runtimeRef) return;
1971
+ if (_observerStarted) {
1972
+ _observerBusUnsubscribe?.();
1973
+ _observerHandle?.stop();
1974
+ _observerBusUnsubscribe = null;
1975
+ _observerHandle = null;
1976
+ _observerStarted = false;
1977
+ }
1978
+ const trimmed = (runtimeRef.backendUrl ?? "").replace(/\/$/, "");
1283
1979
  const url = trimmed ? `${trimmed}/api/adaptive/observation` : "/api/adaptive/observation";
1284
- const getDistinctId = () => {
1285
- const ph = window.posthog;
1980
+ const runtimeId = (method) => {
1286
1981
  try {
1287
- const v = ph?.get_distinct_id?.();
1982
+ const v = runtimeRef.telemetry?.[method]?.();
1288
1983
  return typeof v === "string" && v.length > 0 ? v : null;
1289
1984
  } catch {
1290
1985
  return null;
1291
1986
  }
1292
1987
  };
1988
+ const getDistinctId = () => runtimeId("getDistinctId");
1989
+ const getSessionId = () => runtimeId("getSessionId");
1293
1990
  const token = () => {
1294
- const c = window.__SYNTRO_CONFIG__;
1295
- return typeof c?.token === "string" ? c.token : "";
1991
+ const t = runtimeRef.token;
1992
+ return typeof t === "string" ? t : "";
1296
1993
  };
1297
- const handle = startObserver({ url, token, getDistinctId });
1994
+ const enrich = buildObserverEnrich(runtimeRef);
1995
+ const handle = startObserver({ url, token, getDistinctId, getSessionId, enrich });
1298
1996
  window.__syntroObserverStats = () => handle.stats();
1299
- runtime2.events.subscribe({}, (evt) => {
1300
- const e = evt;
1301
- if (!e?.name) return;
1302
- const ts = typeof e.ts === "number" ? e.ts : Date.now();
1303
- if (e.source === "posthog") {
1304
- const original = e.props?.originalEvent;
1305
- if (typeof original !== "string" || original.length === 0) return;
1306
- const properties = { ...e.props ?? {} };
1307
- if (typeof e.props?.pathname === "string") properties.$pathname = e.props.pathname;
1308
- if (typeof e.props?.url === "string") properties.$current_url = e.props.url;
1309
- handle.ingest({ event: original, timestamp: ts, properties });
1310
- return;
1311
- }
1312
- handle.ingest({
1313
- event: e.name,
1314
- timestamp: ts,
1315
- properties: { ...e.props ?? {} }
1316
- });
1997
+ const unsubscribe = runtimeRef.events.subscribe({}, (evt) => {
1998
+ const raw = busEventToRawEvent(evt);
1999
+ if (raw) handle.ingest(raw);
1317
2000
  });
2001
+ _observerRuntime = runtimeRef;
2002
+ _observerHandle = handle;
2003
+ _observerBusUnsubscribe = unsubscribe;
1318
2004
  _observerStarted = true;
1319
2005
  }
1320
2006
  function wireListeners(bar, state) {
@@ -1350,12 +2036,43 @@ var AdaptiveChatBarMountable = {
1350
2036
  mount(container, mountConfig) {
1351
2037
  const cfg = mountConfig ?? {};
1352
2038
  configureTransportIfPossible(cfg);
2039
+ wireDiveDeeperListener();
1353
2040
  const bar = document.createElement("adaptive-chat-bar");
1354
2041
  applyPlaceholder(bar, cfg);
2042
+ const widgets = cfg.runtime?.widgets;
2043
+ if (widgets) {
2044
+ bar.mountInlineWidget = (widgetId, container2, props) => {
2045
+ if (!widgets.has(widgetId)) return null;
2046
+ const handle = widgets.mount(widgetId, container2, props);
2047
+ const element = container2.firstElementChild;
2048
+ if (!element) {
2049
+ handle.unmount?.();
2050
+ return null;
2051
+ }
2052
+ return { element, cleanup: () => handle.unmount?.() };
2053
+ };
2054
+ }
1355
2055
  const unsubSession = chatSession.subscribe((s) => {
1356
2056
  bar.messages = [...s.messages];
1357
2057
  bar.inFlight = s.inFlight;
2058
+ bar.thinkingText = s.thinkingText;
1358
2059
  });
2060
+ const runtimeEvents = cfg.runtime ? cfg.runtime.events : void 0;
2061
+ const refreshReceipts = () => {
2062
+ const store = _elementStore;
2063
+ if (store) {
2064
+ bar.tileReceipts = store.listTileReceipts();
2065
+ bar.inlineWidgets = store.listInlineWidgets();
2066
+ }
2067
+ };
2068
+ let unsubReceipts;
2069
+ if (runtimeEvents?.subscribe) {
2070
+ unsubReceipts = runtimeEvents.subscribe(
2071
+ { names: [TILE_MOUNTED_EVENT, TILE_UNMOUNTED_EVENT] },
2072
+ refreshReceipts
2073
+ );
2074
+ }
2075
+ refreshReceipts();
1359
2076
  const state = { bar, cleanup: () => {
1360
2077
  }, cfg };
1361
2078
  const unwireListeners = wireListeners(bar, state);
@@ -1367,6 +2084,7 @@ var AdaptiveChatBarMountable = {
1367
2084
  state.cleanup = () => {
1368
2085
  unsubSession();
1369
2086
  unsubFallback();
2087
+ unsubReceipts?.();
1370
2088
  unwireListeners();
1371
2089
  bar.remove();
1372
2090
  setState(container, null);
@@ -1456,6 +2174,7 @@ var AdaptiveChipsStripMountable = {
1456
2174
 
1457
2175
  // src/NavLinkMountable.ts
1458
2176
  var STATE_KEY3 = "__syntroNavLinkMount";
2177
+ var SAFE_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
1459
2178
  function getState3(container) {
1460
2179
  return container[STATE_KEY3] ?? null;
1461
2180
  }
@@ -1471,7 +2190,7 @@ function navigateTo(rawUrl) {
1471
2190
  return;
1472
2191
  }
1473
2192
  if (resolved.origin !== window.location.origin) return;
1474
- if (resolved.protocol !== "http:" && resolved.protocol !== "https:") return;
2193
+ if (!SAFE_NAVIGATION_PROTOCOLS.has(resolved.protocol)) return;
1475
2194
  window.history.pushState(null, "", resolved.toString());
1476
2195
  window.dispatchEvent(new PopStateEvent("popstate"));
1477
2196
  }
@@ -1639,7 +2358,11 @@ var runtime = {
1639
2358
  };
1640
2359
  var runtime_default = runtime;
1641
2360
  export {
2361
+ ElementInstanceStore,
2362
+ TileHandler,
1642
2363
  runtime_default as default,
1643
- runtime
2364
+ getElementInstanceStore,
2365
+ runtime,
2366
+ setElementInstanceStore
1644
2367
  };
1645
2368
  //# sourceMappingURL=runtime.js.map