@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.
- package/dist/AdaptiveChatBar.composer.test.d.ts +2 -0
- package/dist/AdaptiveChatBar.composer.test.d.ts.map +1 -0
- package/dist/AdaptiveChatBar.d.ts +140 -4
- package/dist/AdaptiveChatBar.d.ts.map +1 -1
- package/dist/AdaptiveChatBar.js +4 -3
- package/dist/AdaptiveChatBarMountable.d.ts +47 -0
- package/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
- package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts +2 -0
- package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts.map +1 -0
- package/dist/AdaptiveChatTrail.d.ts +397 -5
- package/dist/AdaptiveChatTrail.d.ts.map +1 -1
- package/dist/AdaptiveChatTrail.js +6 -4
- package/dist/AdaptiveChatTrail.test.d.ts +2 -0
- package/dist/AdaptiveChatTrail.test.d.ts.map +1 -0
- package/dist/AdaptiveChipsStrip.d.ts +17 -11
- package/dist/AdaptiveChipsStrip.d.ts.map +1 -1
- package/dist/AdaptiveChipsStrip.js +2 -2
- package/dist/ChatAssistantLit.d.ts.map +1 -1
- package/dist/ChatAssistantLit.js +4 -3
- package/dist/ChatSession.d.ts +66 -5
- package/dist/ChatSession.d.ts.map +1 -1
- package/dist/ChatTransport.d.ts +107 -0
- package/dist/ChatTransport.d.ts.map +1 -1
- package/dist/InlineRegistryWidgetMount.test.d.ts +2 -0
- package/dist/InlineRegistryWidgetMount.test.d.ts.map +1 -0
- package/dist/InlineWidgetLiveMount.test.d.ts +2 -0
- package/dist/InlineWidgetLiveMount.test.d.ts.map +1 -0
- package/dist/NavLinkMountable.d.ts.map +1 -1
- package/dist/TextAnswerMountable.test.d.ts +2 -0
- package/dist/TextAnswerMountable.test.d.ts.map +1 -0
- package/dist/Turnstile.d.ts.map +1 -1
- package/dist/chunk-A7RBZLIX.js +1678 -0
- package/dist/chunk-A7RBZLIX.js.map +7 -0
- package/dist/{chunk-435KJD27.js → chunk-EACGDSJI.js} +46 -1
- package/dist/chunk-EACGDSJI.js.map +7 -0
- package/dist/chunk-EWPPVPJ4.js +14 -0
- package/dist/chunk-EWPPVPJ4.js.map +7 -0
- package/dist/chunk-FXHYE6FY.js +632 -0
- package/dist/chunk-FXHYE6FY.js.map +7 -0
- package/dist/{chunk-AUER7ZCK.js → chunk-OIGVZGSX.js} +11 -4
- package/dist/chunk-OIGVZGSX.js.map +7 -0
- package/dist/{chunk-UC4XU6GH.js → chunk-VZIUXXAM.js} +761 -52
- package/dist/chunk-VZIUXXAM.js.map +7 -0
- package/dist/{chunk-KUO67E2W.js → chunk-ZXV4GXMO.js} +715 -227
- package/dist/chunk-ZXV4GXMO.js.map +7 -0
- package/dist/elements/ActionHandler.d.ts +1 -1
- package/dist/elements/ActionHandler.d.ts.map +1 -1
- package/dist/elements/ElementInstanceStore.d.ts +62 -0
- package/dist/elements/ElementInstanceStore.d.ts.map +1 -1
- package/dist/elements/TileHandler.d.ts.map +1 -1
- package/dist/elements/TileHandler.test.d.ts +2 -0
- package/dist/elements/TileHandler.test.d.ts.map +1 -0
- package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts +2 -0
- package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts.map +1 -0
- package/dist/elements/fetcher.d.ts +3 -3
- package/dist/elements/fetcher.d.ts.map +1 -1
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.d.ts.map +1 -1
- package/dist/elements/store-singleton.d.ts +4 -0
- package/dist/elements/store-singleton.d.ts.map +1 -0
- package/dist/elements/types.d.ts +4 -0
- package/dist/elements/types.d.ts.map +1 -1
- package/dist/observer/__tests__/bus-translate.test.d.ts +16 -0
- package/dist/observer/__tests__/bus-translate.test.d.ts.map +1 -0
- package/dist/observer/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/observer/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/observer/__tests__/observer-enrich.test.d.ts +10 -0
- package/dist/observer/__tests__/observer-enrich.test.d.ts.map +1 -0
- package/dist/observer/allowlist.d.ts +5 -7
- package/dist/observer/allowlist.d.ts.map +1 -1
- package/dist/observer/bus-translate.d.ts +33 -0
- package/dist/observer/bus-translate.d.ts.map +1 -0
- package/dist/observer/index.d.ts +10 -1
- package/dist/observer/index.d.ts.map +1 -1
- package/dist/observer/queue.d.ts +6 -0
- package/dist/observer/queue.d.ts.map +1 -1
- package/dist/observer/transport.d.ts +1 -0
- package/dist/observer/transport.d.ts.map +1 -1
- package/dist/personas.d.ts +30 -0
- package/dist/personas.d.ts.map +1 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +856 -129
- package/dist/runtime.js.map +4 -4
- package/dist/schema.d.ts +363 -67
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +176 -3
- package/dist/schema.js.map +2 -2
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-435KJD27.js.map +0 -7
- package/dist/chunk-AUER7ZCK.js.map +0 -7
- package/dist/chunk-DOMEUJR7.js +0 -382
- package/dist/chunk-DOMEUJR7.js.map +0 -7
- package/dist/chunk-KUO67E2W.js.map +0 -7
- package/dist/chunk-QELVKBQV.js +0 -214
- package/dist/chunk-QELVKBQV.js.map +0 -7
- 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-
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
|
|
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
|
|
22
|
-
function
|
|
23
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
944
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1641
|
+
function _attachPageHide(transport, queue) {
|
|
1128
1642
|
const handler = () => {
|
|
1129
1643
|
const stats = queue.stats();
|
|
1130
1644
|
if (stats.queued > 0) {
|
|
1131
|
-
|
|
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
|
|
1151
|
-
if (
|
|
1152
|
-
queue.push(
|
|
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
|
-
|
|
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
|
-
|
|
1684
|
+
_active = active;
|
|
1685
|
+
return _makeHandle(active);
|
|
1167
1686
|
}
|
|
1168
|
-
function _makeHandle() {
|
|
1687
|
+
function _makeHandle(owner) {
|
|
1169
1688
|
return {
|
|
1170
1689
|
stop() {
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
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
|
|
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 (
|
|
1189
|
-
const
|
|
1190
|
-
observableEvents:
|
|
1707
|
+
if (_active !== owner) return;
|
|
1708
|
+
const matched = matchEvent(raw, {
|
|
1709
|
+
observableEvents: owner.options.observableEvents
|
|
1191
1710
|
});
|
|
1192
|
-
if (
|
|
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
|
|
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 (
|
|
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,
|
|
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
|
|
1281
|
-
if (!
|
|
1282
|
-
|
|
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
|
|
1285
|
-
const ph = window.posthog;
|
|
1984
|
+
const runtimeId = (method) => {
|
|
1286
1985
|
try {
|
|
1287
|
-
const v =
|
|
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
|
|
1998
|
+
const enrich = buildObserverEnrich(runtimeRef);
|
|
1999
|
+
const handle = startObserver({ url, token, getDistinctId, getSessionId, enrich });
|
|
1298
2000
|
window.__syntroObserverStats = () => handle.stats();
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
2368
|
+
getElementInstanceStore,
|
|
2369
|
+
runtime,
|
|
2370
|
+
setElementInstanceStore
|
|
1644
2371
|
};
|
|
1645
2372
|
//# sourceMappingURL=runtime.js.map
|