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