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