@syntrologie/adapt-chatbot 2.41.3 → 2.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/AdaptiveChatBar.js +1 -1
  2. package/dist/AdaptiveChatTrail.d.ts +19 -0
  3. package/dist/AdaptiveChatTrail.d.ts.map +1 -1
  4. package/dist/AdaptiveChatTrail.js +1 -1
  5. package/dist/AdaptiveChipsStrip.d.ts +12 -12
  6. package/dist/AdaptiveChipsStrip.d.ts.map +1 -1
  7. package/dist/AdaptiveChipsStrip.js +2 -2
  8. package/dist/ChatAssistantLit.js +2 -2
  9. package/dist/ChatTransport.d.ts +26 -0
  10. package/dist/ChatTransport.d.ts.map +1 -1
  11. package/dist/LeaveAMessage.leadconnector.integration.test.d.ts +2 -0
  12. package/dist/LeaveAMessage.leadconnector.integration.test.d.ts.map +1 -0
  13. package/dist/{chunk-KBU4BUOE.js → chunk-3JCJDTHS.js} +2 -2
  14. package/dist/{chunk-3Z52KSYZ.js → chunk-EKGGGUI2.js} +82 -33
  15. package/dist/chunk-EKGGGUI2.js.map +7 -0
  16. package/dist/{chunk-X7N6ZRAX.js → chunk-NWVTKK75.js} +2 -2
  17. package/dist/{chunk-5TFBPUYA.js → chunk-Q6K4TUQO.js} +141 -5
  18. package/dist/chunk-Q6K4TUQO.js.map +7 -0
  19. package/dist/observer/__tests__/detector-observation-contract.test.d.ts +25 -0
  20. package/dist/observer/__tests__/detector-observation-contract.test.d.ts.map +1 -0
  21. package/dist/observer/allowlist.d.ts +2 -0
  22. package/dist/observer/allowlist.d.ts.map +1 -1
  23. package/dist/runtime.js +411 -159
  24. package/dist/runtime.js.map +4 -4
  25. package/dist/schema.d.ts +66 -40
  26. package/dist/schema.d.ts.map +1 -1
  27. package/dist/schema.js +106 -6
  28. package/dist/schema.js.map +3 -3
  29. package/dist/support-chat/__tests__/lc-fetch-stub.d.ts +27 -0
  30. package/dist/support-chat/__tests__/lc-fetch-stub.d.ts.map +1 -0
  31. package/dist/support-chat/boot.d.ts +3 -0
  32. package/dist/support-chat/boot.d.ts.map +1 -1
  33. package/dist/support-chat/leadconnector.d.ts +13 -0
  34. package/dist/support-chat/leadconnector.d.ts.map +1 -0
  35. package/dist/support-chat/leadconnector.test.d.ts +2 -0
  36. package/dist/support-chat/leadconnector.test.d.ts.map +1 -0
  37. package/dist/support-chat/schema-widgetid.test.d.ts +2 -0
  38. package/dist/support-chat/schema-widgetid.test.d.ts.map +1 -0
  39. package/package.json +1 -1
  40. package/dist/chunk-3Z52KSYZ.js.map +0 -7
  41. package/dist/chunk-5TFBPUYA.js.map +0 -7
  42. /package/dist/{chunk-KBU4BUOE.js.map → chunk-3JCJDTHS.js.map} +0 -0
  43. /package/dist/{chunk-X7N6ZRAX.js.map → chunk-NWVTKK75.js.map} +0 -0
package/dist/runtime.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  decodeMutationEnvelope,
10
10
  fetchMountedElements,
11
11
  renderFallbackHtml
12
- } from "./chunk-KBU4BUOE.js";
12
+ } from "./chunk-3JCJDTHS.js";
13
13
  import {
14
14
  pickPersona
15
15
  } from "./chunk-FXHYE6FY.js";
@@ -17,15 +17,16 @@ import {
17
17
  getElementInstanceStore,
18
18
  setElementInstanceStore
19
19
  } from "./chunk-EWPPVPJ4.js";
20
- import "./chunk-3Z52KSYZ.js";
20
+ import "./chunk-EKGGGUI2.js";
21
21
  import "./chunk-VZIUXXAM.js";
22
- import "./chunk-X7N6ZRAX.js";
22
+ import "./chunk-NWVTKK75.js";
23
23
  import {
24
24
  DIVE_DEEPER_EVENT,
25
25
  TILE_MOUNTED_EVENT,
26
26
  TILE_UNMOUNTED_EVENT,
27
+ isCanonicalBusEvent,
27
28
  stripMountPlumbing
28
- } from "./chunk-5TFBPUYA.js";
29
+ } from "./chunk-Q6K4TUQO.js";
29
30
  import "./chunk-UVKRO5ER.js";
30
31
 
31
32
  // src/ChatSession.ts
@@ -694,11 +695,11 @@ var ChatTransport = class {
694
695
  this._sessionStreamAbort = abort;
695
696
  const baseUrl = this._config.backendUrl.replace(/\/$/, "");
696
697
  const url = `${baseUrl}/api/adaptive/session`;
697
- const headers = {};
698
+ const headers2 = {};
698
699
  const token = readRuntimeToken(this._config?.runtime);
699
- if (token) headers.Authorization = `Bearer ${token}`;
700
+ if (token) headers2.Authorization = `Bearer ${token}`;
700
701
  const cft = await this._acquireTurnstileWithChallenge();
701
- if (cft) headers["CF-Turnstile-Token"] = cft;
702
+ if (cft) headers2["CF-Turnstile-Token"] = cft;
702
703
  if (abort.signal.aborted) return;
703
704
  const readIds = () => {
704
705
  try {
@@ -727,8 +728,8 @@ var ChatTransport = class {
727
728
  ids = readIds();
728
729
  }
729
730
  if (abort.signal.aborted) return;
730
- if (ids.did) headers["X-Distinct-Id"] = ids.did;
731
- if (ids.sid) headers["X-Syntro-Session"] = ids.sid;
731
+ if (ids.did) headers2["X-Distinct-Id"] = ids.did;
732
+ if (ids.sid) headers2["X-Syntro-Session"] = ids.sid;
732
733
  const runReader = async () => {
733
734
  const connAbort = new AbortController();
734
735
  const onOuterAbort = () => connAbort.abort();
@@ -744,7 +745,7 @@ var ChatTransport = class {
744
745
  });
745
746
  try {
746
747
  const response = await fetch(url, {
747
- headers,
748
+ headers: headers2,
748
749
  credentials: "include",
749
750
  signal: connAbort.signal
750
751
  });
@@ -800,8 +801,8 @@ var ChatTransport = class {
800
801
  await new Promise((r) => setTimeout(r, this._sessionStreamBackoffMs));
801
802
  this._sessionStreamBackoffMs = Math.min(this._sessionStreamBackoffMs * 2, 3e4);
802
803
  const fresh = readIds();
803
- if (fresh.did) headers["X-Distinct-Id"] = fresh.did;
804
- if (fresh.sid) headers["X-Syntro-Session"] = fresh.sid;
804
+ if (fresh.did) headers2["X-Distinct-Id"] = fresh.did;
805
+ if (fresh.sid) headers2["X-Syntro-Session"] = fresh.sid;
805
806
  }
806
807
  })();
807
808
  await Promise.resolve();
@@ -1374,6 +1375,120 @@ function renderCustomEvent(raw) {
1374
1375
  }
1375
1376
  return fields.length > 0 ? `${raw.event} (${fields.join(", ")})` : raw.event;
1376
1377
  }
1378
+ function seconds(ms) {
1379
+ return typeof ms === "number" && Number.isFinite(ms) ? Math.round(ms / 1e3) : null;
1380
+ }
1381
+ function describeBusElement(el) {
1382
+ if (!el) return null;
1383
+ const text = typeof el.text === "string" ? el.text.trim() : "";
1384
+ if (text) return text;
1385
+ const aria = el["attr__aria-label"];
1386
+ if (typeof aria === "string" && aria.trim()) return aria.trim();
1387
+ const id = el.attr__id;
1388
+ if (typeof id === "string" && id.trim()) return `#${id.trim()}`;
1389
+ return null;
1390
+ }
1391
+ var CANONICAL_SUMMARIES = {
1392
+ "ui.hover": {
1393
+ requires: ["element"],
1394
+ render: (p, ts) => {
1395
+ const label = describeBusElement(p.element);
1396
+ if (!label) return null;
1397
+ return { ts, kind: "hover", text: truncate(`hovered on '${label}'`) };
1398
+ }
1399
+ },
1400
+ "ui.idle": {
1401
+ requires: ["duration_ms"],
1402
+ render: (p, ts) => {
1403
+ const secs = seconds(p.duration_ms);
1404
+ if (secs === null) return null;
1405
+ return { ts, kind: "idle", text: `idle for ${secs}s` };
1406
+ }
1407
+ },
1408
+ "ui.hesitate": {
1409
+ requires: ["duration_ms"],
1410
+ render: (p, ts) => {
1411
+ const secs = seconds(p.duration_ms);
1412
+ if (secs === null) return null;
1413
+ return { ts, kind: "hesitation", text: `hesitated in place for ${secs}s` };
1414
+ }
1415
+ },
1416
+ "ui.rage_click": {
1417
+ // Previously had NO case at all: the rrweb rage-click detector emits
1418
+ // `ui.rage_click`, the summarizer only knew PostHog's `$rageclick`, so
1419
+ // every detector-sourced rage click was dropped on the floor.
1420
+ requires: ["click_count"],
1421
+ render: (p, ts) => {
1422
+ const count = p.click_count;
1423
+ if (typeof count !== "number") return null;
1424
+ return { ts, kind: "rage", text: `clicked the same spot ${count} times in a row` };
1425
+ }
1426
+ },
1427
+ "ui.scroll_thrash": {
1428
+ requires: ["reversals"],
1429
+ render: (p, ts) => {
1430
+ const reversals = p.reversals;
1431
+ if (typeof reversals !== "number") return null;
1432
+ return {
1433
+ ts,
1434
+ kind: "scroll",
1435
+ text: `changed scroll direction ${reversals} times (looking for something)`
1436
+ };
1437
+ }
1438
+ },
1439
+ "ui.focus_bounce": {
1440
+ // The rrweb detector has an rrweb node id and a duration — no element
1441
+ // chain, no text, and no way to get one. Say what we actually know
1442
+ // instead of rendering `focused '' but didn't fill it`.
1443
+ requires: ["duration_ms"],
1444
+ render: (p, ts) => {
1445
+ const secs = seconds(p.duration_ms);
1446
+ if (secs === null) return null;
1447
+ return {
1448
+ ts,
1449
+ kind: "form",
1450
+ text: secs >= 1 ? `focused a form field for ${secs}s without filling it` : "focused a form field without filling it"
1451
+ };
1452
+ }
1453
+ },
1454
+ "nav.section_viewed": {
1455
+ requires: ["section"],
1456
+ render: (p, ts) => {
1457
+ const section = typeof p.section === "string" ? p.section : "";
1458
+ if (!section) return null;
1459
+ return {
1460
+ ts,
1461
+ kind: "view",
1462
+ text: truncate(`viewed the '${section}' section`),
1463
+ ref: truncate(section)
1464
+ };
1465
+ }
1466
+ },
1467
+ "nav.scroll_depth": {
1468
+ requires: ["percent"],
1469
+ render: (p, ts) => {
1470
+ const pct = p.percent;
1471
+ if (typeof pct !== "number") return null;
1472
+ return { ts, kind: "scroll", text: truncate(`scrolled to ${pct}% of the page`) };
1473
+ }
1474
+ }
1475
+ };
1476
+ var warnedDrift = /* @__PURE__ */ new Set();
1477
+ function renderCanonicalBusEvent(name, props, ts) {
1478
+ const summary = CANONICAL_SUMMARIES[name];
1479
+ const missing = summary.requires.filter((key) => props[key] === void 0);
1480
+ if (missing.length > 0) {
1481
+ const dedupKey = `${name}|${missing.join(",")}`;
1482
+ if (!warnedDrift.has(dedupKey)) {
1483
+ warnedDrift.add(dedupKey);
1484
+ console.warn(
1485
+ `observer.canonical_props_drift: '${name}' arrived without [${missing.join(", ")}]; got keys [${Object.keys(props).join(", ")}]. Producer disagrees with @syntrologie/sdk-contracts CanonicalBusEventProps \u2014 observation dropped. (warned once per event/missing-key shape)`
1486
+ );
1487
+ }
1488
+ return null;
1489
+ }
1490
+ return summary.render(props, ts);
1491
+ }
1377
1492
  function matchEvent(raw, opts = {}) {
1378
1493
  const props = raw.properties;
1379
1494
  switch (raw.event) {
@@ -1422,77 +1537,10 @@ function matchEvent(raw, opts = {}) {
1422
1537
  text: truncate(`clicked '${text}' (no response)`)
1423
1538
  };
1424
1539
  }
1425
- // ── Canonical runtime-bus events (sources: `'canvas'` instrumentation
1426
- // or the event-processor's rrweb detectors). These don't go through
1427
- // PostHog; the chat-bar's bus translator forwards them as-is with
1428
- // the canonical event name, and matchEvent picks the right slot
1429
- // here. Keeps a single allowlist as the source of truth for what
1430
- // the agent's observation tail can carry.
1431
- case "nav.section_viewed": {
1432
- const section = typeof props.section === "string" ? props.section : "";
1433
- if (!section) return null;
1434
- return {
1435
- ts: raw.timestamp,
1436
- kind: "view",
1437
- text: truncate(`viewed the '${section}' section`),
1438
- ref: truncate(section)
1439
- };
1440
- }
1441
- case "nav.scroll_depth": {
1442
- const pct = typeof props.percent === "number" ? props.percent : null;
1443
- if (pct === null) return null;
1444
- return {
1445
- ts: raw.timestamp,
1446
- kind: "scroll",
1447
- text: truncate(`scrolled to ${pct}% of the page`)
1448
- };
1449
- }
1450
- case "ui.scroll_thrash": {
1451
- return {
1452
- ts: raw.timestamp,
1453
- kind: "scroll",
1454
- text: "scrolled up and down repeatedly (looking for something)"
1455
- };
1456
- }
1457
- case "ui.idle": {
1458
- const ms = typeof props.durationMs === "number" ? props.durationMs : null;
1459
- const secs = ms !== null ? Math.round(ms / 1e3) : null;
1460
- return {
1461
- ts: raw.timestamp,
1462
- kind: "idle",
1463
- text: secs !== null ? `idle for ${secs}s` : "idle"
1464
- };
1465
- }
1466
- case "ui.hover": {
1467
- const text = elementText(props) || elementTag(props);
1468
- return {
1469
- ts: raw.timestamp,
1470
- kind: "hover",
1471
- text: truncate(`hovered on '${text}'`)
1472
- };
1473
- }
1474
- // Canonical name is 'ui.hesitate' (StandardEvents.UI_HESITATE) — the rrweb
1475
- // hesitation detector emits coordinates + duration_ms, no element chain, so
1476
- // the summary is duration-based. (BUG-1784146789: this case was spelled
1477
- // 'ui.hesitation', a name nothing emits, so the signal never arrived.)
1478
- case "ui.hesitate": {
1479
- const ms = typeof props.duration_ms === "number" ? props.duration_ms : null;
1480
- const secs = ms !== null ? Math.round(ms / 1e3) : null;
1481
- return {
1482
- ts: raw.timestamp,
1483
- kind: "hesitation",
1484
- text: secs !== null ? `hesitated in place for ${secs}s` : "hesitated in place"
1485
- };
1486
- }
1487
- case "ui.focus_bounce": {
1488
- const text = elementText(props) || elementTag(props);
1489
- return {
1490
- ts: raw.timestamp,
1491
- kind: "form",
1492
- text: truncate(`focused '${text}' but didn't fill it`)
1493
- };
1494
- }
1495
1540
  default: {
1541
+ if (isCanonicalBusEvent(raw.event)) {
1542
+ return renderCanonicalBusEvent(raw.event, props, raw.timestamp);
1543
+ }
1496
1544
  const allow = opts.observableEvents ?? [];
1497
1545
  if (allow.includes(raw.event)) {
1498
1546
  return {
@@ -1546,7 +1594,7 @@ var ObserverQueue = class {
1546
1594
  * Returns false when the sender rejected delivery and restores the batch so
1547
1595
  * the caller can fall back to fetch({ keepalive: true }).
1548
1596
  */
1549
- flushOnPageHide(send) {
1597
+ flushOnPageHide(send2) {
1550
1598
  if (this._disabled || this._buffer.length === 0) return true;
1551
1599
  this._clearInterval();
1552
1600
  const overflow = Math.max(0, this._buffer.length - 50);
@@ -1555,7 +1603,7 @@ var ObserverQueue = class {
1555
1603
  this._dropped += overflow;
1556
1604
  }
1557
1605
  const batch = this._buffer.splice(0, 50);
1558
- if (!send(batch)) {
1606
+ if (!send2(batch)) {
1559
1607
  this._buffer.unshift(...batch);
1560
1608
  return false;
1561
1609
  }
@@ -1848,18 +1896,222 @@ function registerCrispBridge() {
1848
1896
  }
1849
1897
  registerCrispBridge();
1850
1898
 
1899
+ // src/support-chat/leadconnector.ts
1900
+ var LC_SERVICES = "https://services.leadconnectorhq.com";
1901
+ var LC_POLL_MS = 1e4;
1902
+ var LC_PAGE_LIMIT = 20;
1903
+ function lcStorageKey(widgetId) {
1904
+ return `syntro:supportchat:leadconnector:${widgetId}`;
1905
+ }
1906
+ var cfg;
1907
+ var locationIdCache;
1908
+ function warn(what, detail) {
1909
+ console.warn(`supportChat.bridge: leadconnector ${what}`, detail);
1910
+ }
1911
+ function load() {
1912
+ if (!cfg) return void 0;
1913
+ try {
1914
+ const raw = sessionStorage.getItem(lcStorageKey(cfg.widgetId));
1915
+ return raw ? JSON.parse(raw) : void 0;
1916
+ } catch (err) {
1917
+ warn("state read failed", err);
1918
+ return void 0;
1919
+ }
1920
+ }
1921
+ function save(id) {
1922
+ if (!cfg) return;
1923
+ try {
1924
+ sessionStorage.setItem(lcStorageKey(cfg.widgetId), JSON.stringify(id));
1925
+ } catch (err) {
1926
+ warn("state write failed", err);
1927
+ }
1928
+ }
1929
+ async function resolveLocationId() {
1930
+ const persisted = load();
1931
+ if (persisted?.locationId) return persisted.locationId;
1932
+ if (!cfg) return void 0;
1933
+ const widgetId = cfg.widgetId;
1934
+ locationIdCache ?? (locationIdCache = (async () => {
1935
+ try {
1936
+ const r = await fetch(`${LC_SERVICES}/chat-widget/public/config/${widgetId}`);
1937
+ if (!r.ok) {
1938
+ warn("public config fetch failed", r.status);
1939
+ return void 0;
1940
+ }
1941
+ const j = await r.json();
1942
+ const loc = j?.config?.["location-id"];
1943
+ return typeof loc === "string" && loc ? loc : void 0;
1944
+ } catch (err) {
1945
+ warn("public config fetch threw", err);
1946
+ return void 0;
1947
+ }
1948
+ })());
1949
+ return locationIdCache;
1950
+ }
1951
+ function headers(token) {
1952
+ const h = { "Content-Type": "application/json" };
1953
+ if (token) h.Authorization = `Bearer ${token}`;
1954
+ return h;
1955
+ }
1956
+ async function doSend(text) {
1957
+ if (!cfg) return;
1958
+ const locationId = await resolveLocationId();
1959
+ if (!locationId) {
1960
+ warn("no locationId \u2014 send dropped", cfg.widgetId);
1961
+ return;
1962
+ }
1963
+ const prior = load();
1964
+ const body = {
1965
+ locationId,
1966
+ widgetId: cfg.widgetId,
1967
+ defaultWidget: true,
1968
+ message: text,
1969
+ subType: "liveChat"
1970
+ };
1971
+ if (prior?.conversationId) body.conversationId = prior.conversationId;
1972
+ try {
1973
+ const r = await fetch(`${LC_SERVICES}/conversations/providers/live-chat/messages`, {
1974
+ method: "POST",
1975
+ headers: headers(prior?.token),
1976
+ body: JSON.stringify(body)
1977
+ });
1978
+ if (!r.ok) {
1979
+ warn("send rejected", r.status);
1980
+ return;
1981
+ }
1982
+ const j = await r.json();
1983
+ const fresh = load();
1984
+ save({
1985
+ ...fresh,
1986
+ locationId,
1987
+ visitorId: j.visitorId ?? fresh?.visitorId ?? prior?.visitorId,
1988
+ conversationId: j.conversationId ?? fresh?.conversationId ?? prior?.conversationId,
1989
+ contactId: j.contactId ?? fresh?.contactId ?? prior?.contactId,
1990
+ token: j.token ?? fresh?.token ?? prior?.token
1991
+ });
1992
+ } catch (err) {
1993
+ warn("send threw", err);
1994
+ }
1995
+ }
1996
+ var sendChain = Promise.resolve();
1997
+ function send(text) {
1998
+ const result = sendChain.then(() => doSend(text));
1999
+ sendChain = result;
2000
+ return result;
2001
+ }
2002
+ var pollWarned = false;
2003
+ var pollInFlight = false;
2004
+ async function pollOnce(cb, isActive) {
2005
+ const id = load();
2006
+ if (!id?.conversationId || !id.token) return;
2007
+ if (pollInFlight) return;
2008
+ pollInFlight = true;
2009
+ try {
2010
+ const qs = new URLSearchParams({
2011
+ conversationId: id.conversationId,
2012
+ locationId: id.locationId,
2013
+ lastMessageId: id.lastMessageId ?? "",
2014
+ pageLimit: String(LC_PAGE_LIMIT)
2015
+ });
2016
+ try {
2017
+ const r = await fetch(
2018
+ `${LC_SERVICES}/conversations/providers/live-chat/messages/search?${qs}`,
2019
+ {
2020
+ headers: headers(id.token)
2021
+ }
2022
+ );
2023
+ if (!r.ok) {
2024
+ if (!pollWarned) {
2025
+ warn("poll rejected", r.status);
2026
+ pollWarned = true;
2027
+ }
2028
+ return;
2029
+ }
2030
+ const j = await r.json();
2031
+ if (!isActive()) return;
2032
+ const messages = j.messages ?? [];
2033
+ for (const m of messages) {
2034
+ if (m.direction === "outbound" && typeof m.body === "string") cb(m.body);
2035
+ }
2036
+ const lastMessage = messages[messages.length - 1];
2037
+ const nextLastMessageId = typeof j.lastMessageId === "string" && j.lastMessageId ? j.lastMessageId : typeof lastMessage?.id === "string" && lastMessage.id ? lastMessage.id : void 0;
2038
+ if (nextLastMessageId) {
2039
+ const current = load();
2040
+ save({ ...id, ...current, lastMessageId: nextLastMessageId });
2041
+ }
2042
+ pollWarned = false;
2043
+ } catch (err) {
2044
+ if (!pollWarned) {
2045
+ warn("poll threw", err);
2046
+ pollWarned = true;
2047
+ }
2048
+ }
2049
+ } finally {
2050
+ pollInFlight = false;
2051
+ }
2052
+ }
2053
+ var leadConnectorBridge = {
2054
+ vendor: "leadconnector",
2055
+ configure(c) {
2056
+ cfg = c;
2057
+ locationIdCache = void 0;
2058
+ },
2059
+ detect: () => Boolean(cfg?.widgetId),
2060
+ hideNative: () => {
2061
+ try {
2062
+ window.leadConnector?.chatWidget?.closeWidget?.();
2063
+ } catch (err) {
2064
+ warn("closeWidget failed", err);
2065
+ }
2066
+ },
2067
+ setVisitorEmail: (email) => {
2068
+ const trimmed = email.trim();
2069
+ if (!trimmed) return;
2070
+ void send(`[Visitor email: ${trimmed}]`);
2071
+ },
2072
+ sendMessage: (text) => send(text),
2073
+ onOperatorReply: (cb) => {
2074
+ let active = true;
2075
+ pollWarned = false;
2076
+ const isActive = () => active;
2077
+ const tick = async () => {
2078
+ if (!active) return;
2079
+ await pollOnce(cb, isActive);
2080
+ };
2081
+ const timer = setInterval(() => void tick(), LC_POLL_MS);
2082
+ return () => {
2083
+ active = false;
2084
+ clearInterval(timer);
2085
+ };
2086
+ }
2087
+ };
2088
+ function registerLeadConnectorBridge() {
2089
+ registerSupportChatBridge(leadConnectorBridge);
2090
+ }
2091
+ registerLeadConnectorBridge();
2092
+
1851
2093
  // src/support-chat/boot.ts
1852
- function bootSupportChat(cfg) {
1853
- if (!cfg?.vendor) return void 0;
2094
+ function bootSupportChat(cfg2) {
2095
+ if (!cfg2?.vendor) return void 0;
1854
2096
  registerCrispBridge();
1855
- const bridge = getSupportChatBridge(cfg.vendor);
2097
+ registerLeadConnectorBridge();
2098
+ if (cfg2.vendor === "leadconnector") {
2099
+ if (!cfg2.widgetId) {
2100
+ console.warn(
2101
+ "supportChat.bridge: leadconnector needs supportChat.widgetId \u2014 handoff disabled"
2102
+ );
2103
+ return void 0;
2104
+ }
2105
+ leadConnectorBridge.configure({ widgetId: cfg2.widgetId });
2106
+ }
2107
+ const bridge = getSupportChatBridge(cfg2.vendor);
1856
2108
  if (!bridge || !bridge.detect()) {
1857
2109
  console.warn(
1858
- `supportChat.bridge: vendor '${cfg.vendor}' not detected on this page \u2014 handoff disabled`
2110
+ `supportChat.bridge: vendor '${cfg2.vendor}' not detected on this page \u2014 handoff disabled`
1859
2111
  );
1860
2112
  return void 0;
1861
2113
  }
1862
- if (cfg.suppressNative) bridge.hideNative();
2114
+ if (cfg2.suppressNative) bridge.hideNative();
1863
2115
  return bridge;
1864
2116
  }
1865
2117
 
@@ -1871,16 +2123,16 @@ function getState(container) {
1871
2123
  function setState(container, state) {
1872
2124
  container[STATE_KEY] = state;
1873
2125
  }
1874
- function applyPlaceholder(bar, cfg) {
1875
- if (cfg.placeholder !== void 0) bar.placeholder = cfg.placeholder;
1876
- if (cfg.assistantName !== void 0) bar.assistantName = cfg.assistantName;
1877
- if (cfg.personas !== void 0) bar.personas = cfg.personas;
1878
- if (cfg.greeting !== void 0) bar.greeting = cfg.greeting;
1879
- if (cfg.voiceInput !== void 0) bar.voiceInput = cfg.voiceInput;
1880
- bar.introSuggestion = cfg.introSuggestion;
1881
- bar.forceExpanded = cfg.forceExpanded === true;
1882
- if (typeof cfg.maximized === "boolean") {
1883
- bar.maximized = cfg.maximized;
2126
+ function applyPlaceholder(bar, cfg2) {
2127
+ if (cfg2.placeholder !== void 0) bar.placeholder = cfg2.placeholder;
2128
+ if (cfg2.assistantName !== void 0) bar.assistantName = cfg2.assistantName;
2129
+ if (cfg2.personas !== void 0) bar.personas = cfg2.personas;
2130
+ if (cfg2.greeting !== void 0) bar.greeting = cfg2.greeting;
2131
+ if (cfg2.voiceInput !== void 0) bar.voiceInput = cfg2.voiceInput;
2132
+ bar.introSuggestion = cfg2.introSuggestion;
2133
+ bar.forceExpanded = cfg2.forceExpanded === true;
2134
+ if (typeof cfg2.maximized === "boolean") {
2135
+ bar.maximized = cfg2.maximized;
1884
2136
  }
1885
2137
  }
1886
2138
  var _elementStore = null;
@@ -2011,49 +2263,49 @@ function wireNudgeSurface(runtime2, backendUrl) {
2011
2263
  };
2012
2264
  document.addEventListener("syntro:nudge", _nudgeListener);
2013
2265
  }
2014
- function configureTransportIfPossible(cfg) {
2015
- if (!cfg.runtime) return;
2016
- const backendUrl = cfg.runtime.backendUrl;
2266
+ function configureTransportIfPossible(cfg2) {
2267
+ if (!cfg2.runtime) return;
2268
+ const backendUrl = cfg2.runtime.backendUrl;
2017
2269
  if (!backendUrl) return;
2018
- const elementsActive = cfg.elementsEnabled === true && cfg.uiTemplates != null;
2019
- refreshTemplateWidgetMap(elementsActive ? cfg.uiTemplates : void 0);
2020
- const forwardedProps = elementsActive ? { elementsEnabled: true, uiTemplates: cfg.uiTemplates } : void 0;
2021
- if (elementsActive) publishTemplatesIfPossible(backendUrl, cfg.uiTemplates, cfg.runtime?.token);
2022
- const runtime2 = cfg.runtime;
2270
+ const elementsActive = cfg2.elementsEnabled === true && cfg2.uiTemplates != null;
2271
+ refreshTemplateWidgetMap(elementsActive ? cfg2.uiTemplates : void 0);
2272
+ const forwardedProps = elementsActive ? { elementsEnabled: true, uiTemplates: cfg2.uiTemplates } : void 0;
2273
+ if (elementsActive) publishTemplatesIfPossible(backendUrl, cfg2.uiTemplates, cfg2.runtime?.token);
2274
+ const runtime2 = cfg2.runtime;
2023
2275
  const onElementMutation = elementsActive ? (mutations) => {
2024
2276
  void getOrCreateElementStore(runtime2).apply(mutations);
2025
2277
  } : void 0;
2026
2278
  if (elementsActive) {
2027
2279
  hydrateOnce(runtime2, backendUrl);
2028
2280
  }
2029
- const persona = pickPersona(cfg.personas ?? null);
2281
+ const persona = pickPersona(cfg2.personas ?? null);
2030
2282
  chatTransport.configure({
2031
- ...cfg,
2283
+ ...cfg2,
2032
2284
  backendUrl,
2033
2285
  forwardedProps,
2034
2286
  onElementMutation,
2035
- activeLidSlot: cfg._syntroSlotName,
2287
+ activeLidSlot: cfg2._syntroSlotName,
2036
2288
  personaId: persona?.id,
2037
2289
  personaName: persona?.name,
2038
2290
  personaRole: persona?.role_title
2039
2291
  });
2040
2292
  void chatTransport.startSessionStream();
2041
2293
  wireNudgeSurface(runtime2, backendUrl);
2042
- startObserverIfPossible(cfg);
2294
+ startObserverIfPossible(cfg2);
2043
2295
  }
2044
2296
  var _templatesPublished = false;
2045
2297
  function publishTemplatesIfPossible(backendUrl, uiTemplates, token) {
2046
2298
  if (_templatesPublished || typeof window === "undefined") return;
2047
2299
  if (!uiTemplates || typeof uiTemplates !== "object") return;
2048
2300
  _templatesPublished = true;
2049
- const headers = { "Content-Type": "application/json" };
2050
- if (token) headers.Authorization = `Bearer ${token}`;
2301
+ const headers2 = { "Content-Type": "application/json" };
2302
+ if (token) headers2.Authorization = `Bearer ${token}`;
2051
2303
  const editorToken = readEditorToken();
2052
- if (editorToken) headers["X-Syntro-Editor-Token"] = editorToken;
2304
+ if (editorToken) headers2["X-Syntro-Editor-Token"] = editorToken;
2053
2305
  const trimmed = backendUrl.replace(/\/$/, "");
2054
2306
  void fetch(`${trimmed}/api/adaptive/templates`, {
2055
2307
  method: "POST",
2056
- headers,
2308
+ headers: headers2,
2057
2309
  credentials: "include",
2058
2310
  body: JSON.stringify({ ui_templates: uiTemplates })
2059
2311
  }).catch(() => {
@@ -2080,9 +2332,9 @@ var _observerStarted = false;
2080
2332
  var _observerRuntime = null;
2081
2333
  var _observerHandle = null;
2082
2334
  var _observerBusUnsubscribe = null;
2083
- function startObserverIfPossible(cfg) {
2335
+ function startObserverIfPossible(cfg2) {
2084
2336
  if (typeof window === "undefined") return;
2085
- const runtimeRef = cfg.runtime;
2337
+ const runtimeRef = cfg2.runtime;
2086
2338
  if (!runtimeRef?.events?.subscribe) return;
2087
2339
  if (_observerStarted && _observerRuntime === runtimeRef) return;
2088
2340
  if (_observerStarted) {
@@ -2151,13 +2403,13 @@ function wireListeners(bar, state) {
2151
2403
  }
2152
2404
  var AdaptiveChatBarMountable = {
2153
2405
  mount(container, mountConfig) {
2154
- const cfg = mountConfig ?? {};
2155
- configureTransportIfPossible(cfg);
2156
- bootSupportChat(cfg.supportChat);
2406
+ const cfg2 = mountConfig ?? {};
2407
+ configureTransportIfPossible(cfg2);
2408
+ bootSupportChat(cfg2.supportChat);
2157
2409
  wireDiveDeeperListener();
2158
2410
  const bar = document.createElement("adaptive-chat-bar");
2159
- applyPlaceholder(bar, cfg);
2160
- const widgets = cfg.runtime?.widgets;
2411
+ applyPlaceholder(bar, cfg2);
2412
+ const widgets = cfg2.runtime?.widgets;
2161
2413
  if (widgets) {
2162
2414
  bar.mountInlineWidget = (widgetId, container2, props) => {
2163
2415
  if (!widgets.has(widgetId)) return null;
@@ -2175,7 +2427,7 @@ var AdaptiveChatBarMountable = {
2175
2427
  bar.inFlight = s.inFlight;
2176
2428
  bar.thinkingText = s.thinkingText;
2177
2429
  });
2178
- const runtimeEvents = cfg.runtime ? cfg.runtime.events : void 0;
2430
+ const runtimeEvents = cfg2.runtime ? cfg2.runtime.events : void 0;
2179
2431
  const refreshReceipts = () => {
2180
2432
  const store = _elementStore;
2181
2433
  if (store) {
@@ -2192,7 +2444,7 @@ var AdaptiveChatBarMountable = {
2192
2444
  }
2193
2445
  refreshReceipts();
2194
2446
  const state = { bar, cleanup: () => {
2195
- }, cfg };
2447
+ }, cfg: cfg2 };
2196
2448
  const unwireListeners = wireListeners(bar, state);
2197
2449
  container.appendChild(bar);
2198
2450
  const unsubFallback = chatTransport.onFallback(() => {
@@ -2213,10 +2465,10 @@ var AdaptiveChatBarMountable = {
2213
2465
  update(container, mountConfig) {
2214
2466
  const state = getState(container);
2215
2467
  if (!state) return;
2216
- const cfg = mountConfig ?? {};
2217
- configureTransportIfPossible(cfg);
2218
- applyPlaceholder(state.bar, cfg);
2219
- state.cfg = cfg;
2468
+ const cfg2 = mountConfig ?? {};
2469
+ configureTransportIfPossible(cfg2);
2470
+ applyPlaceholder(state.bar, cfg2);
2471
+ state.cfg = cfg2;
2220
2472
  }
2221
2473
  };
2222
2474
 
@@ -2228,23 +2480,23 @@ function getState2(container) {
2228
2480
  function setState2(container, state) {
2229
2481
  container[STATE_KEY2] = state;
2230
2482
  }
2231
- function applyProps(strip, cfg) {
2232
- if (cfg.chips !== void 0) strip.chips = cfg.chips;
2233
- if (cfg.runtime !== void 0) {
2234
- strip.runtimeRef = cfg.runtime;
2483
+ function applyProps(strip, cfg2) {
2484
+ if (cfg2.chips !== void 0) strip.chips = cfg2.chips;
2485
+ if (cfg2.runtime !== void 0) {
2486
+ strip.runtimeRef = cfg2.runtime;
2235
2487
  }
2236
- if (cfg.chromeless !== void 0) strip.chromeless = cfg.chromeless;
2488
+ if (cfg2.chromeless !== void 0) strip.chromeless = cfg2.chromeless;
2237
2489
  }
2238
2490
  var AdaptiveChipsStripMountable = {
2239
2491
  mount(container, mountConfig) {
2240
- const cfg = mountConfig ?? {};
2492
+ const cfg2 = mountConfig ?? {};
2241
2493
  const strip = document.createElement("adaptive-chips-strip");
2242
- applyProps(strip, cfg);
2494
+ applyProps(strip, cfg2);
2243
2495
  const state = {
2244
2496
  strip,
2245
2497
  listeners: {
2246
2498
  revealed: (e) => {
2247
- const cb = cfg.onChipRevealed;
2499
+ const cb = cfg2.onChipRevealed;
2248
2500
  if (cb) {
2249
2501
  cb(
2250
2502
  e.detail
@@ -2252,7 +2504,7 @@ var AdaptiveChipsStripMountable = {
2252
2504
  }
2253
2505
  },
2254
2506
  dismissed: (e) => {
2255
- const cb = cfg.onChipDismissed;
2507
+ const cb = cfg2.onChipDismissed;
2256
2508
  if (cb) cb(e.detail);
2257
2509
  }
2258
2510
  }
@@ -2271,18 +2523,18 @@ var AdaptiveChipsStripMountable = {
2271
2523
  update(container, mountConfig) {
2272
2524
  const state = getState2(container);
2273
2525
  if (!state) return;
2274
- const cfg = mountConfig ?? {};
2275
- applyProps(state.strip, cfg);
2526
+ const cfg2 = mountConfig ?? {};
2527
+ applyProps(state.strip, cfg2);
2276
2528
  state.strip.removeEventListener("chip-revealed", state.listeners.revealed);
2277
2529
  state.strip.removeEventListener("chip-dismissed", state.listeners.dismissed);
2278
2530
  state.listeners.revealed = (e) => {
2279
- const cb = cfg.onChipRevealed;
2531
+ const cb = cfg2.onChipRevealed;
2280
2532
  if (cb) {
2281
2533
  cb(e.detail);
2282
2534
  }
2283
2535
  };
2284
2536
  state.listeners.dismissed = (e) => {
2285
- const cb = cfg.onChipDismissed;
2537
+ const cb = cfg2.onChipDismissed;
2286
2538
  if (cb) cb(e.detail);
2287
2539
  };
2288
2540
  state.strip.addEventListener("chip-revealed", state.listeners.revealed);
@@ -2811,7 +3063,7 @@ function navigateTo(rawUrl) {
2811
3063
  window.history.pushState(null, "", resolved.toString());
2812
3064
  window.dispatchEvent(new PopStateEvent("popstate"));
2813
3065
  }
2814
- function buildButton(cfg) {
3066
+ function buildButton(cfg2) {
2815
3067
  const button = document.createElement("button");
2816
3068
  button.type = "button";
2817
3069
  button.dataset.syntroNavLink = "";
@@ -2827,26 +3079,26 @@ function buildButton(cfg) {
2827
3079
  button.style.color = "var(--sc-accent-foreground, currentColor)";
2828
3080
  button.style.background = "hsl(var(--sc-accent-color) / 0.85)";
2829
3081
  button.style.border = "1px solid hsl(var(--sc-accent-color) / 0.30)";
2830
- applyLabel(button, cfg);
3082
+ applyLabel(button, cfg2);
2831
3083
  return button;
2832
3084
  }
2833
- function applyLabel(button, cfg) {
2834
- const url = String(cfg.url ?? "");
2835
- const label = cfg.label ?? (url ? `Go to ${url}` : "Go");
3085
+ function applyLabel(button, cfg2) {
3086
+ const url = String(cfg2.url ?? "");
3087
+ const label = cfg2.label ?? (url ? `Go to ${url}` : "Go");
2836
3088
  button.textContent = label;
2837
3089
  }
2838
3090
  var NavLinkMountable = {
2839
3091
  mount(container, mountConfig) {
2840
- const cfg = mountConfig ?? {};
2841
- const button = buildButton(cfg);
3092
+ const cfg2 = mountConfig ?? {};
3093
+ const button = buildButton(cfg2);
2842
3094
  const onClick = (_e) => {
2843
3095
  const current = getState3(container);
2844
- const url = current?.cfg.url ?? cfg.url;
3096
+ const url = current?.cfg.url ?? cfg2.url;
2845
3097
  navigateTo(String(url ?? ""));
2846
3098
  };
2847
3099
  button.addEventListener("click", onClick);
2848
3100
  container.appendChild(button);
2849
- setState3(container, { button, cfg, onClick });
3101
+ setState3(container, { button, cfg: cfg2, onClick });
2850
3102
  return () => {
2851
3103
  button.removeEventListener("click", onClick);
2852
3104
  button.remove();
@@ -2856,9 +3108,9 @@ var NavLinkMountable = {
2856
3108
  update(container, mountConfig) {
2857
3109
  const state = getState3(container);
2858
3110
  if (!state) return;
2859
- const cfg = mountConfig ?? {};
2860
- applyLabel(state.button, cfg);
2861
- state.cfg = cfg;
3111
+ const cfg2 = mountConfig ?? {};
3112
+ applyLabel(state.button, cfg2);
3113
+ state.cfg = cfg2;
2862
3114
  }
2863
3115
  };
2864
3116
 
@@ -2875,16 +3127,16 @@ function applyText(p, text) {
2875
3127
  }
2876
3128
  var TextAnswerMountable = {
2877
3129
  mount(container, mountConfig) {
2878
- const cfg = mountConfig ?? {};
3130
+ const cfg2 = mountConfig ?? {};
2879
3131
  const paragraph = document.createElement("p");
2880
3132
  paragraph.dataset.syntroTextAnswer = "";
2881
3133
  paragraph.style.margin = "0";
2882
3134
  paragraph.style.fontSize = "12px";
2883
3135
  paragraph.style.lineHeight = "1.55";
2884
3136
  paragraph.style.color = "var(--sc-tile-text-color, currentColor)";
2885
- applyText(paragraph, String(cfg.text ?? ""));
3137
+ applyText(paragraph, String(cfg2.text ?? ""));
2886
3138
  container.appendChild(paragraph);
2887
- setState4(container, { paragraph, cfg });
3139
+ setState4(container, { paragraph, cfg: cfg2 });
2888
3140
  return () => {
2889
3141
  paragraph.remove();
2890
3142
  setState4(container, null);
@@ -2893,9 +3145,9 @@ var TextAnswerMountable = {
2893
3145
  update(container, mountConfig) {
2894
3146
  const state = getState4(container);
2895
3147
  if (!state) return;
2896
- const cfg = mountConfig ?? {};
2897
- applyText(state.paragraph, String(cfg.text ?? ""));
2898
- state.cfg = cfg;
3148
+ const cfg2 = mountConfig ?? {};
3149
+ applyText(state.paragraph, String(cfg2.text ?? ""));
3150
+ state.cfg = cfg2;
2899
3151
  }
2900
3152
  };
2901
3153