@xapp/chat-widget 1.86.0 → 1.87.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/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import require$$1, { useCallback, useState, useRef, useEffect, createContext, useContext, useMemo, memo, useLayoutEffect } from 'react';
1
+ import require$$1, { useCallback, useState, useEffect, useRef, createContext, useMemo, useContext, memo, useLayoutEffect } from 'react';
2
2
  export { default as React } from 'react';
3
3
  import require$$0, { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useSelector, useDispatch, Provider } from 'react-redux';
@@ -13,7 +13,6 @@ var lib = {};
13
13
 
14
14
  var ActionBar$1 = {};
15
15
 
16
- /*! Copyright (c) 2021, XAPPmedia */
17
16
  Object.defineProperty(ActionBar$1, "__esModule", { value: true });
18
17
  ActionBar$1.isActionBarChatButton = isActionBarChatButton;
19
18
  ActionBar$1.isActionBarFormButton = isActionBarFormButton;
@@ -304,12 +303,103 @@ var ActionBarButton = function (_a) {
304
303
  return (jsxs("button", { className: buttonClassName, onClick: handleClick, "aria-label": ariaLabel, "aria-pressed": isActive, tabIndex: tabIndex, children: [jsx(ActionBarIcon, { type: button.type, icon: button.icon, iconUrl: button.iconUrl, className: "xapp-action-bar__icon" }), button.label && (jsx("span", { className: "xapp-action-bar__label", children: button.label }))] }));
305
304
  };
306
305
 
306
+ /**
307
+ * CTA banner for ActionBar - a full-width rectangular banner that appears
308
+ * above the action bar to prompt users to engage with chat.
309
+ */
310
+ var ActionBarCta = function (_a) {
311
+ var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
312
+ var config = _a.config, isMobile = _a.isMobile, onClick = _a.onClick, onDismiss = _a.onDismiss, hasUserInteracted = _a.hasUserInteracted;
313
+ var _m = useState(false), isVisible = _m[0], setIsVisible = _m[1];
314
+ var _o = useState(false), isAnimating = _o[0], setIsAnimating = _o[1];
315
+ var _p = useState(false), isDismissed = _p[0], setIsDismissed = _p[1];
316
+ // For mobile overrides: null means "explicitly disabled", undefined means "use desktop default"
317
+ var mobileMessage = isMobile ? (_b = config.mobile) === null || _b === void 0 ? void 0 : _b.message : undefined;
318
+ var message = mobileMessage !== undefined ? mobileMessage : config.message;
319
+ var delay = isMobile && ((_c = config.mobile) === null || _c === void 0 ? void 0 : _c.delay) !== undefined ? config.mobile.delay : ((_d = config.delay) !== null && _d !== void 0 ? _d : 0);
320
+ var mobileTimeout = isMobile ? (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.timeout : undefined;
321
+ var timeout = mobileTimeout !== undefined ? mobileTimeout : config.timeout;
322
+ var mobileAnimation = isMobile ? (_f = config.mobile) === null || _f === void 0 ? void 0 : _f.animation : undefined;
323
+ var animation = mobileAnimation !== undefined ? mobileAnimation : config.animation;
324
+ var animationDelay = isMobile && ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.animationDelay) !== undefined
325
+ ? config.mobile.animationDelay
326
+ : ((_h = config.animationDelay) !== null && _h !== void 0 ? _h : delay);
327
+ var animationTimeout = isMobile && ((_j = config.mobile) === null || _j === void 0 ? void 0 : _j.animationTimeout) !== undefined
328
+ ? config.mobile.animationTimeout
329
+ : config.animationTimeout;
330
+ var dismissible = isMobile && ((_k = config.mobile) === null || _k === void 0 ? void 0 : _k.dismissible) !== undefined
331
+ ? config.mobile.dismissible
332
+ : ((_l = config.dismissible) !== null && _l !== void 0 ? _l : true);
333
+ // Show CTA after delay
334
+ useEffect(function () {
335
+ if (!message || hasUserInteracted || isDismissed) {
336
+ setIsVisible(false);
337
+ return undefined;
338
+ }
339
+ var showTimer = setTimeout(function () {
340
+ setIsVisible(true);
341
+ }, delay);
342
+ return function () { return clearTimeout(showTimer); };
343
+ }, [message, delay, hasUserInteracted, isDismissed]);
344
+ // Hide CTA after timeout
345
+ useEffect(function () {
346
+ if (!isVisible || !timeout) {
347
+ return undefined;
348
+ }
349
+ var hideTimer = setTimeout(function () {
350
+ setIsVisible(false);
351
+ }, timeout);
352
+ return function () { return clearTimeout(hideTimer); };
353
+ }, [isVisible, timeout]);
354
+ // Start animation after animationDelay
355
+ useEffect(function () {
356
+ if (!animation || hasUserInteracted || isDismissed) {
357
+ setIsAnimating(false);
358
+ return undefined;
359
+ }
360
+ var animateTimer = setTimeout(function () {
361
+ setIsAnimating(true);
362
+ }, animationDelay);
363
+ return function () { return clearTimeout(animateTimer); };
364
+ }, [animation, animationDelay, hasUserInteracted, isDismissed]);
365
+ // Stop animation after animationTimeout
366
+ useEffect(function () {
367
+ if (!isAnimating || !animationTimeout) {
368
+ return undefined;
369
+ }
370
+ var stopTimer = setTimeout(function () {
371
+ setIsAnimating(false);
372
+ }, animationTimeout);
373
+ return function () { return clearTimeout(stopTimer); };
374
+ }, [isAnimating, animationTimeout]);
375
+ var handleClick = function () {
376
+ onClick();
377
+ setIsVisible(false);
378
+ };
379
+ var handleDismiss = function (e) {
380
+ e.stopPropagation();
381
+ setIsDismissed(true);
382
+ setIsVisible(false);
383
+ onDismiss();
384
+ };
385
+ if (!isVisible || !message) {
386
+ return null;
387
+ }
388
+ var animationClass = isAnimating && animation ? "xapp-action-bar-cta--".concat(animation) : "";
389
+ return (jsxs("div", { className: "xapp-action-bar-cta ".concat(animationClass), onClick: handleClick, role: "button", tabIndex: 0, "aria-label": message, onKeyDown: function (e) {
390
+ if (e.key === "Enter" || e.key === " ") {
391
+ e.preventDefault();
392
+ handleClick();
393
+ }
394
+ }, children: [jsx("span", { className: "xapp-action-bar-cta__message", children: message }), dismissible && (jsx("button", { className: "xapp-action-bar-cta__dismiss", onClick: handleDismiss, "aria-label": "Dismiss", type: "button", children: jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M13 1L1 13M1 1L13 13", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }));
395
+ };
396
+
307
397
  var DEFAULT_MOBILE_BREAKPOINT = 768;
308
398
  var RESIZE_DEBOUNCE_MS = 150;
309
399
  var ActionBar = function (_a) {
310
- var _b, _c;
311
- var config = _a.config, visible = _a.visible, chatDisabled = _a.chatDisabled, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick;
312
- var _d = useState(false), isMobile = _d[0], setIsMobile = _d[1];
400
+ var _b, _c, _d;
401
+ var config = _a.config, visible = _a.visible, chatDisabled = _a.chatDisabled, _e = _a.hasUserInteracted, hasUserInteracted = _e === void 0 ? false : _e, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick, onCtaDismiss = _a.onCtaDismiss;
402
+ var _f = useState(false), isMobile = _f[0], setIsMobile = _f[1];
313
403
  var mobileBreakpoint = (_b = config.mobileBreakpoint) !== null && _b !== void 0 ? _b : DEFAULT_MOBILE_BREAKPOINT;
314
404
  var position = (_c = config.position) !== null && _c !== void 0 ? _c : "right";
315
405
  var resizeTimeoutRef = useRef(null);
@@ -345,7 +435,19 @@ var ActionBar = function (_a) {
345
435
  ]
346
436
  .filter(Boolean)
347
437
  .join(" ");
348
- return (jsx("div", { className: classNames, role: "toolbar", "aria-label": "Chat actions", children: filteredButtons.map(function (button) { return (jsx(ActionBarButton, { button: button, chatActive: visible, onChatClick: onChatClick, onChatMinimize: onChatMinimize, onFormClick: onFormClick, isMobile: isMobile }, button.id)); }) }));
438
+ // Determine if CTA should be shown:
439
+ // - Must have CTA config with a message
440
+ // - Chat must not be visible (user hasn't opened it)
441
+ // - Chat must not be disabled
442
+ var hasChatButton = config.buttons.some(lib.isActionBarChatButton);
443
+ var showCta = ((_d = config.cta) === null || _d === void 0 ? void 0 : _d.message) && !visible && !chatDisabled && hasChatButton;
444
+ var handleCtaClick = function () {
445
+ onChatClick();
446
+ };
447
+ var handleCtaDismiss = function () {
448
+ onCtaDismiss === null || onCtaDismiss === void 0 ? void 0 : onCtaDismiss();
449
+ };
450
+ return (jsxs("div", { className: classNames, role: "toolbar", "aria-label": "Chat actions", children: [showCta && (jsx("div", { className: "xapp-action-bar-cta-wrapper", children: jsx(ActionBarCta, { config: config.cta, isMobile: isMobile, onClick: handleCtaClick, onDismiss: handleCtaDismiss, hasUserInteracted: hasUserInteracted }) })), jsx("div", { className: "xapp-action-bar__buttons", children: filteredButtons.map(function (button) { return (jsx(ActionBarButton, { button: button, chatActive: visible, onChatClick: onChatClick, onChatMinimize: onChatMinimize, onFormClick: onFormClick, isMobile: isMobile }, button.id)); }) })] }));
349
451
  };
350
452
 
351
453
  /**
@@ -978,14 +1080,16 @@ function getServerConfig(env) {
978
1080
  }
979
1081
  function useServerConfig(env, nonce) {
980
1082
  var connection = getServerConfig(env);
981
- var accountKey = connection.accountKey, serverUrl = connection.serverUrl, type = connection.type, timeout = connection.timeout;
1083
+ var accountKey = connection.accountKey, serverUrl = connection.serverUrl, type = connection.type, timeout = connection.timeout, backgroundSleepDelay = connection.backgroundSleepDelay, mcp = connection.mcp;
982
1084
  return useMemo(function () { return ({
983
1085
  accountKey: accountKey,
984
1086
  serverUrl: serverUrl,
985
1087
  timeout: timeout,
986
1088
  type: type,
987
1089
  nonce: nonce,
988
- }); }, [accountKey, serverUrl, timeout, type, nonce]);
1090
+ backgroundSleepDelay: backgroundSleepDelay,
1091
+ mcp: mcp,
1092
+ }); }, [accountKey, serverUrl, timeout, type, nonce, backgroundSleepDelay, mcp]);
989
1093
  }
990
1094
  function useConnectionInfo(env) {
991
1095
  var nonce = useSelector(function (state) { return state.connection.nonce; });
@@ -994,7 +1098,7 @@ function useConnectionInfo(env) {
994
1098
  }
995
1099
 
996
1100
  var Avatar = function (props) {
997
- var _a, _b;
1101
+ var _a, _b, _c, _d, _e;
998
1102
  var style = {};
999
1103
  var child;
1000
1104
  var entity = props.entity;
@@ -1017,7 +1121,9 @@ var Avatar = function (props) {
1017
1121
  child = getVisitorSvg();
1018
1122
  }
1019
1123
  var hasImage = !!style.backgroundImage || !!child;
1020
- return (jsx("div", { className: "avatar ".concat(agentAva ? "avatar--agent" : "avatar--visitor", " ").concat(!hasImage ? "avatar--empty" : ""), style: style, title: entity ? entity.display_name : "Agent", children: child }));
1124
+ // Show AI badge if configured and the entity is an AI
1125
+ var showAiBadge = ((_e = (_d = (_c = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _c === void 0 ? void 0 : _c.connection) === null || _d === void 0 ? void 0 : _d.mcp) === null || _e === void 0 ? void 0 : _e.showAiBadge) && (entity === null || entity === void 0 ? void 0 : entity.isAI);
1126
+ return (jsxs("div", { className: "avatar ".concat(agentAva ? "avatar--agent" : "avatar--visitor", " ").concat(!hasImage ? "avatar--empty" : ""), style: style, title: entity ? entity.display_name : "Agent", children: [child, showAiBadge && jsx("span", { className: "avatar__ai-badge", children: "AI" })] }));
1021
1127
  };
1022
1128
  /**
1023
1129
  * Generates an SVG based on the
@@ -1677,14 +1783,14 @@ function useOpenUrlCallback() {
1677
1783
  }, [env]);
1678
1784
  }
1679
1785
 
1680
- function writeMessage(msg, user) {
1786
+ function writeMessage(msg, user, timestamp) {
1681
1787
  return {
1682
1788
  type: "synthetic",
1683
1789
  detail: {
1684
1790
  type: "write_msg",
1685
1791
  user: user,
1686
1792
  msg: msg,
1687
- timestamp: +new Date()
1793
+ timestamp: timestamp !== undefined ? timestamp : +new Date()
1688
1794
  }
1689
1795
  };
1690
1796
  }
@@ -1696,16 +1802,19 @@ function executeAction(text, meta) {
1696
1802
  attributes = __assign(__assign({}, attributes), { currentUrl: window.location.href });
1697
1803
  var _a = meta || {}, token = _a.token, rest = __rest(_a, ["token"]);
1698
1804
  attributes = __assign(__assign({}, attributes), rest);
1805
+ // Add user message to Redux BEFORE sending to server
1806
+ // to ensure it appears above the bot's streaming response
1807
+ var visitor = getState().visitor;
1808
+ var userMsgTimestamp = +new Date();
1809
+ dispatch(writeMessage({
1810
+ text: !token ? text : "".concat(text, " (from the list)"),
1811
+ }, visitor, userMsgTimestamp));
1699
1812
  chatServer.sendChatMsg({ text: text, token: token, attributes: attributes }, function (err) {
1700
1813
  if (err) {
1701
1814
  log("Error sending message", err);
1702
1815
  return;
1703
1816
  }
1704
1817
  });
1705
- var visitor = getState().visitor;
1706
- dispatch(writeMessage({
1707
- text: !token ? text : "".concat(text, " (from the list)"),
1708
- }, visitor));
1709
1818
  }; };
1710
1819
  }
1711
1820
 
@@ -2602,236 +2711,231 @@ var parseSuggestionsResponse_1 = parseSuggestionsResponse;
2602
2711
  var useSuggestionsFetch_1 = useSuggestionsFetch$1;
2603
2712
  var uuid_1 = uuid;
2604
2713
 
2605
- /**
2606
- * Sends a POST to your STENTOR based server.
2607
- *
2608
- * @param data
2609
- * @param url
2610
- * @param key
2611
- * @param signal
2612
- * @returns
2613
- */
2614
- function postMessageToStentor(data, url, key, signal) {
2615
- return __awaiter$1(this, void 0, void 0, function () {
2616
- var body, response;
2617
- return __generator$1(this, function (_a) {
2618
- switch (_a.label) {
2619
- case 0:
2620
- body = JSON.stringify(data);
2621
- log("URL: ".concat(url));
2622
- log("BODY: ".concat(body));
2623
- return [4 /*yield*/, fetch(url, {
2624
- method: "POST",
2625
- headers: {
2626
- "Content-Type": "application/json",
2627
- "Authorization": "Bearer ".concat(key),
2628
- },
2629
- body: body,
2630
- mode: "cors",
2631
- signal: signal
2632
- })];
2633
- case 1:
2634
- response = _a.sent();
2635
- if (!response.ok) {
2636
- throw new Error("Status ".concat(response.status, ", Text: ").concat(response.statusText));
2637
- }
2638
- return [2 /*return*/, response.json()];
2639
- }
2640
- });
2641
- });
2642
- }
2714
+ var cjs = {};
2643
2715
 
2644
- function convertFromListDisplay(list) {
2645
- return {
2646
- type: list.type,
2647
- title: list.title,
2648
- items: list.items.map(function (item) {
2649
- var _a, _b;
2650
- var responseItem = {
2651
- title: item.title,
2652
- subTitle: item.description,
2653
- token: item.token,
2654
- url: item.url,
2655
- hideUrl: item.hideUrl,
2656
- imageUrl: (_a = item.image) === null || _a === void 0 ? void 0 : _a.url,
2657
- imageActionUrl: (_b = item.image) === null || _b === void 0 ? void 0 : _b.imageActionUrl
2658
- };
2659
- // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
2660
- var itemButtons = item.buttons;
2661
- if (itemButtons && itemButtons.length > 0) {
2662
- responseItem.buttons = itemButtons.map(function (button) { return ({
2663
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2664
- actionUrl: button.openUrlAction,
2665
- label: button.title
2666
- }); });
2667
- }
2668
- return responseItem;
2669
- })
2670
- };
2671
- }
2672
- function convertToListDisplay(list) {
2673
- return {
2674
- type: list.type,
2675
- title: list.title,
2676
- items: (list.items || []).map(function (item) {
2677
- var responseItem = {
2678
- title: item.title,
2679
- description: item.subTitle,
2680
- token: item.token,
2681
- url: item.url,
2682
- hideUrl: item.hideUrl,
2683
- image: {
2684
- url: item.imageUrl,
2685
- imageActionUrl: item.imageActionUrl,
2686
- accessibilityText: ""
2687
- }
2688
- };
2689
- // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
2690
- var itemButtons = item.buttons;
2691
- if (itemButtons && itemButtons.length > 0) {
2692
- responseItem.buttons = itemButtons.map(function (button) { return ({
2693
- openUrlAction: button.actionUrl,
2694
- title: button.label
2695
- }); });
2696
- }
2697
- return responseItem;
2698
- })
2699
- };
2700
- }
2701
- function convertFromCardDisplay(card) {
2702
- return {
2703
- content: card.content,
2704
- imageUrl: card.smallImageUrl,
2705
- title: card.title,
2706
- imageActionUrl: card === null || card === void 0 ? void 0 : card.imageActionUrl,
2707
- buttons: card.buttons ? card.buttons.map(function (button) { return ({
2708
- actionUrl: button.openUrlAction,
2709
- label: button.title
2710
- }); }) : undefined
2711
- };
2712
- }
2713
- function getOutput(value) {
2714
- if (value) {
2715
- if (typeof value === "string") {
2716
- return {
2717
- displayText: value
2718
- };
2719
- }
2720
- else {
2721
- return value;
2722
- }
2723
- }
2724
- return undefined;
2725
- }
2726
- /**
2727
- * Converts a Stentor Response to a ChatMessageRequest
2728
- *
2729
- * @param botResponse
2730
- * @param now
2731
- * @returns
2732
- */
2733
- function responseToMessage(botResponse, now) {
2734
- var _a, _b, _c, _d, _e;
2735
- if (now === void 0) { now = new Date().getTime(); }
2736
- var responseMessage;
2737
- if (!botResponse) {
2738
- return responseMessage;
2739
- }
2740
- var text;
2741
- var html;
2742
- var endSession;
2743
- var outputSpeech = getOutput(botResponse.outputSpeech);
2744
- var reprompt = getOutput(botResponse.reprompt);
2745
- if (outputSpeech) {
2746
- text = outputSpeech.displayText;
2747
- html = outputSpeech.html;
2748
- }
2749
- if (botResponse.system === "TRANSFER_CALL") {
2750
- responseMessage = {
2751
- type: "handOff",
2752
- timestamp: now,
2753
- handoffMessage: text,
2754
- handoffTarget: (_a = botResponse === null || botResponse === void 0 ? void 0 : botResponse.data) === null || _a === void 0 ? void 0 : _a.transferPhoneNumber,
2755
- endSession: false,
2756
- user: undefined
2757
- };
2758
- }
2759
- else if ((_b = botResponse.system) === null || _b === void 0 ? void 0 : _b.startsWith("PERMISSION_")) {
2760
- responseMessage = getPermissionResponse(botResponse, now);
2761
- }
2762
- else {
2763
- endSession = !reprompt || !(reprompt.displayText || reprompt.ssml);
2764
- responseMessage = {
2765
- type: "msg",
2766
- timestamp: now,
2767
- msg: {
2768
- displays: botResponse.displays,
2769
- context: (_d = (_c = botResponse.context) === null || _c === void 0 ? void 0 : _c.active) === null || _d === void 0 ? void 0 : _d.map(function (ctx) { return ctx.name; })
2770
- },
2771
- endSession: endSession,
2772
- user: undefined
2773
- };
2774
- if (text && !html) {
2775
- responseMessage.msg = __assign(__assign({}, responseMessage.msg), { text: text });
2776
- }
2777
- if (html) {
2778
- responseMessage.msg = __assign(__assign({}, responseMessage.msg), { html: html });
2779
- }
2780
- if ((_e = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.suggestions) === null || _e === void 0 ? void 0 : _e.length) {
2781
- responseMessage.msg.options = outputSpeech.suggestions.map(function (suggestion) {
2782
- if (typeof suggestion === "string") {
2783
- // Simple chips (strings)
2784
- return suggestion;
2785
- }
2786
- else {
2787
- // "call out" chips
2788
- return {
2789
- label: suggestion.title,
2790
- actionUrl: suggestion.url
2791
- };
2792
- }
2793
- });
2794
- }
2795
- }
2796
- return responseMessage;
2797
- }
2798
- function getPermissionRequestType(type) {
2799
- switch (type) {
2800
- case "PERMISSION_EMAIL":
2801
- return "EMAIL";
2802
- case "PERMISSION_LOCATION_PRECISE":
2803
- return "LOCATION_PRECISE";
2804
- default:
2805
- throw new Error("Unsupported permission: ".concat(type));
2806
- }
2807
- }
2808
- function getPermissionResponse(botResponse, now) {
2809
- var _a;
2810
- var type = getPermissionRequestType(botResponse.system);
2811
- var outputSpeech = getOutput(botResponse.outputSpeech);
2812
- var permissionPrimerAccepted = botResponse.data.permissionPrimerAccepted;
2813
- var permissionDenied = botResponse.data.permissionDenied;
2814
- return {
2815
- type: "permissionRequest",
2816
- timestamp: now,
2817
- msg: {
2818
- text: (_a = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.displayText) !== null && _a !== void 0 ? _a : "".concat(botResponse.data.permissionRequestTTSContext),
2819
- permissionRequest: {
2820
- time: now,
2821
- type: type,
2822
- approve: typeof permissionPrimerAccepted === "object" ? responseToMessage({
2823
- outputSpeech: permissionPrimerAccepted
2824
- }).msg : undefined,
2825
- deny: typeof permissionDenied === "object" ? responseToMessage({
2826
- outputSpeech: permissionDenied
2827
- }).msg : undefined
2828
- }
2829
- },
2830
- endSession: false,
2831
- user: undefined //todo: set
2832
- };
2716
+ var fetch$1 = {};
2717
+
2718
+ var parse$3 = {};
2719
+
2720
+ Object.defineProperty(parse$3, "__esModule", { value: true });
2721
+ parse$3.getMessages = parse$3.getLines = parse$3.getBytes = void 0;
2722
+ async function getBytes(stream, onChunk) {
2723
+ const reader = stream.getReader();
2724
+ let result;
2725
+ while (!(result = await reader.read()).done) {
2726
+ onChunk(result.value);
2727
+ }
2728
+ }
2729
+ parse$3.getBytes = getBytes;
2730
+ function getLines(onLine) {
2731
+ let buffer;
2732
+ let position;
2733
+ let fieldLength;
2734
+ let discardTrailingNewline = false;
2735
+ return function onChunk(arr) {
2736
+ if (buffer === undefined) {
2737
+ buffer = arr;
2738
+ position = 0;
2739
+ fieldLength = -1;
2740
+ }
2741
+ else {
2742
+ buffer = concat(buffer, arr);
2743
+ }
2744
+ const bufLength = buffer.length;
2745
+ let lineStart = 0;
2746
+ while (position < bufLength) {
2747
+ if (discardTrailingNewline) {
2748
+ if (buffer[position] === 10) {
2749
+ lineStart = ++position;
2750
+ }
2751
+ discardTrailingNewline = false;
2752
+ }
2753
+ let lineEnd = -1;
2754
+ for (; position < bufLength && lineEnd === -1; ++position) {
2755
+ switch (buffer[position]) {
2756
+ case 58:
2757
+ if (fieldLength === -1) {
2758
+ fieldLength = position - lineStart;
2759
+ }
2760
+ break;
2761
+ case 13:
2762
+ discardTrailingNewline = true;
2763
+ case 10:
2764
+ lineEnd = position;
2765
+ break;
2766
+ }
2767
+ }
2768
+ if (lineEnd === -1) {
2769
+ break;
2770
+ }
2771
+ onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
2772
+ lineStart = position;
2773
+ fieldLength = -1;
2774
+ }
2775
+ if (lineStart === bufLength) {
2776
+ buffer = undefined;
2777
+ }
2778
+ else if (lineStart !== 0) {
2779
+ buffer = buffer.subarray(lineStart);
2780
+ position -= lineStart;
2781
+ }
2782
+ };
2783
+ }
2784
+ parse$3.getLines = getLines;
2785
+ function getMessages(onId, onRetry, onMessage) {
2786
+ let message = newMessage();
2787
+ const decoder = new TextDecoder();
2788
+ return function onLine(line, fieldLength) {
2789
+ if (line.length === 0) {
2790
+ onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
2791
+ message = newMessage();
2792
+ }
2793
+ else if (fieldLength > 0) {
2794
+ const field = decoder.decode(line.subarray(0, fieldLength));
2795
+ const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
2796
+ const value = decoder.decode(line.subarray(valueOffset));
2797
+ switch (field) {
2798
+ case 'data':
2799
+ message.data = message.data
2800
+ ? message.data + '\n' + value
2801
+ : value;
2802
+ break;
2803
+ case 'event':
2804
+ message.event = value;
2805
+ break;
2806
+ case 'id':
2807
+ onId(message.id = value);
2808
+ break;
2809
+ case 'retry':
2810
+ const retry = parseInt(value, 10);
2811
+ if (!isNaN(retry)) {
2812
+ onRetry(message.retry = retry);
2813
+ }
2814
+ break;
2815
+ }
2816
+ }
2817
+ };
2818
+ }
2819
+ parse$3.getMessages = getMessages;
2820
+ function concat(a, b) {
2821
+ const res = new Uint8Array(a.length + b.length);
2822
+ res.set(a);
2823
+ res.set(b, a.length);
2824
+ return res;
2825
+ }
2826
+ function newMessage() {
2827
+ return {
2828
+ data: '',
2829
+ event: '',
2830
+ id: '',
2831
+ retry: undefined,
2832
+ };
2833
2833
  }
2834
2834
 
2835
+ (function (exports$1) {
2836
+ var __rest = (commonjsGlobal && commonjsGlobal.__rest) || function (s, e) {
2837
+ var t = {};
2838
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
2839
+ t[p] = s[p];
2840
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
2841
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
2842
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
2843
+ t[p[i]] = s[p[i]];
2844
+ }
2845
+ return t;
2846
+ };
2847
+ Object.defineProperty(exports$1, "__esModule", { value: true });
2848
+ exports$1.fetchEventSource = exports$1.EventStreamContentType = void 0;
2849
+ const parse_1 = parse$3;
2850
+ exports$1.EventStreamContentType = 'text/event-stream';
2851
+ const DefaultRetryInterval = 1000;
2852
+ const LastEventId = 'last-event-id';
2853
+ function fetchEventSource(input, _a) {
2854
+ var { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch } = _a, rest = __rest(_a, ["signal", "headers", "onopen", "onmessage", "onclose", "onerror", "openWhenHidden", "fetch"]);
2855
+ return new Promise((resolve, reject) => {
2856
+ const headers = Object.assign({}, inputHeaders);
2857
+ if (!headers.accept) {
2858
+ headers.accept = exports$1.EventStreamContentType;
2859
+ }
2860
+ let curRequestController;
2861
+ function onVisibilityChange() {
2862
+ curRequestController.abort();
2863
+ if (!document.hidden) {
2864
+ create();
2865
+ }
2866
+ }
2867
+ if (!openWhenHidden) {
2868
+ document.addEventListener('visibilitychange', onVisibilityChange);
2869
+ }
2870
+ let retryInterval = DefaultRetryInterval;
2871
+ let retryTimer = 0;
2872
+ function dispose() {
2873
+ document.removeEventListener('visibilitychange', onVisibilityChange);
2874
+ window.clearTimeout(retryTimer);
2875
+ curRequestController.abort();
2876
+ }
2877
+ inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener('abort', () => {
2878
+ dispose();
2879
+ resolve();
2880
+ });
2881
+ const fetch = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
2882
+ const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
2883
+ async function create() {
2884
+ var _a;
2885
+ curRequestController = new AbortController();
2886
+ try {
2887
+ const response = await fetch(input, Object.assign(Object.assign({}, rest), { headers, signal: curRequestController.signal }));
2888
+ await onopen(response);
2889
+ await parse_1.getBytes(response.body, parse_1.getLines(parse_1.getMessages(id => {
2890
+ if (id) {
2891
+ headers[LastEventId] = id;
2892
+ }
2893
+ else {
2894
+ delete headers[LastEventId];
2895
+ }
2896
+ }, retry => {
2897
+ retryInterval = retry;
2898
+ }, onmessage)));
2899
+ onclose === null || onclose === void 0 ? void 0 : onclose();
2900
+ dispose();
2901
+ resolve();
2902
+ }
2903
+ catch (err) {
2904
+ if (!curRequestController.signal.aborted) {
2905
+ try {
2906
+ const interval = (_a = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a !== void 0 ? _a : retryInterval;
2907
+ window.clearTimeout(retryTimer);
2908
+ retryTimer = window.setTimeout(create, interval);
2909
+ }
2910
+ catch (innerErr) {
2911
+ dispose();
2912
+ reject(innerErr);
2913
+ }
2914
+ }
2915
+ }
2916
+ }
2917
+ create();
2918
+ });
2919
+ }
2920
+ exports$1.fetchEventSource = fetchEventSource;
2921
+ function defaultOnOpen(response) {
2922
+ const contentType = response.headers.get('content-type');
2923
+ if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(exports$1.EventStreamContentType))) {
2924
+ throw new Error(`Expected content-type to be ${exports$1.EventStreamContentType}, Actual: ${contentType}`);
2925
+ }
2926
+ }
2927
+
2928
+ } (fetch$1));
2929
+
2930
+ (function (exports$1) {
2931
+ Object.defineProperty(exports$1, "__esModule", { value: true });
2932
+ exports$1.EventStreamContentType = exports$1.fetchEventSource = void 0;
2933
+ var fetch_1 = fetch$1;
2934
+ Object.defineProperty(exports$1, "fetchEventSource", { enumerable: true, get: function () { return fetch_1.fetchEventSource; } });
2935
+ Object.defineProperty(exports$1, "EventStreamContentType", { enumerable: true, get: function () { return fetch_1.EventStreamContentType; } });
2936
+
2937
+ } (cjs));
2938
+
2835
2939
  /**
2836
2940
  * Default configuration messages
2837
2941
  * @returns
@@ -2878,6 +2982,1533 @@ function setSessionId(sessionId) {
2878
2982
  };
2879
2983
  }
2880
2984
 
2985
+ // Endpoint path constants
2986
+ var HOMEOWNER_ENDPOINT_PATH = "/chat/stream";
2987
+ var BUSINESS_OWNER_ENDPOINT_PATH = "/api/chat/stream";
2988
+ // Default retry configuration for MCP connections
2989
+ var DEFAULT_MCP_RETRY_CONFIG = [
2990
+ { retry: 0, delay: 0, text: "" }, // Initial attempt (silent)
2991
+ { retry: 1, delay: 3, text: "Having trouble connecting. Retrying in ${sec} seconds..." },
2992
+ { retry: 2, delay: 5, text: "Still having trouble. Retrying in ${sec} seconds..." },
2993
+ { retry: 0, delay: 0, text: "Unable to connect to the assistant. Please try again later." }, // Final failure
2994
+ ];
2995
+ // Streaming throttle configuration
2996
+ // Average reading speed is ~250 words/min (~20 chars/sec). We go slightly faster.
2997
+ var STREAM_CHARS_PER_TICK = 3; // Characters to display per tick
2998
+ var STREAM_TICK_INTERVAL_MS = 30; // Milliseconds between ticks (~100 chars/sec)
2999
+ /**
3000
+ * Storage keys for MCP session persistence.
3001
+ * Uses localStorage directly (not Redux) for MCP-specific data.
3002
+ */
3003
+ var MCP_STORAGE_KEYS = {
3004
+ USER_ID: "xapp_mcp_userId",
3005
+ SESSION_ID: "xapp_mcp_sessionId",
3006
+ };
3007
+ /**
3008
+ * Gets the platform string, preferring the modern userAgentData API.
3009
+ * Falls back to the deprecated navigator.platform for older browsers.
3010
+ */
3011
+ function getPlatform() {
3012
+ // Modern API (Chrome 90+, Edge 90+, Opera 76+)
3013
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3014
+ var userAgentData = navigator.userAgentData;
3015
+ if (userAgentData === null || userAgentData === void 0 ? void 0 : userAgentData.platform) {
3016
+ return userAgentData.platform;
3017
+ }
3018
+ // Fallback for older browsers (deprecated but widely supported)
3019
+ return navigator.platform || undefined;
3020
+ }
3021
+ /**
3022
+ * Collects non-invasive device/browser fingerprint data.
3023
+ * This helps the server identify returning users without requiring login.
3024
+ *
3025
+ * PRIVACY NOTE: Only basic, non-identifying browser characteristics are collected.
3026
+ * This data cannot be used to personally identify individual users.
3027
+ */
3028
+ function getFingerprint() {
3029
+ if (typeof window === "undefined") {
3030
+ return {};
3031
+ }
3032
+ return {
3033
+ userAgent: navigator.userAgent,
3034
+ language: navigator.language,
3035
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
3036
+ screenResolution: "".concat(window.screen.width, "x").concat(window.screen.height),
3037
+ platform: getPlatform(),
3038
+ colorDepth: window.screen.colorDepth,
3039
+ touchSupport: "ontouchstart" in window || navigator.maxTouchPoints > 0,
3040
+ };
3041
+ }
3042
+ /**
3043
+ * Retrieves stored MCP identity from localStorage.
3044
+ */
3045
+ function getStoredIdentity() {
3046
+ if (typeof window === "undefined" || !window.localStorage) {
3047
+ return {};
3048
+ }
3049
+ try {
3050
+ return {
3051
+ userId: localStorage.getItem(MCP_STORAGE_KEYS.USER_ID) || undefined,
3052
+ sessionId: localStorage.getItem(MCP_STORAGE_KEYS.SESSION_ID) || undefined,
3053
+ };
3054
+ }
3055
+ catch (_a) {
3056
+ // localStorage may be unavailable (private browsing, etc.)
3057
+ return {};
3058
+ }
3059
+ }
3060
+ /**
3061
+ * Stores MCP identity to localStorage.
3062
+ */
3063
+ function storeIdentity(userId, sessionId) {
3064
+ if (typeof window === "undefined" || !window.localStorage) {
3065
+ return;
3066
+ }
3067
+ try {
3068
+ if (userId) {
3069
+ localStorage.setItem(MCP_STORAGE_KEYS.USER_ID, userId);
3070
+ }
3071
+ if (sessionId) {
3072
+ localStorage.setItem(MCP_STORAGE_KEYS.SESSION_ID, sessionId);
3073
+ }
3074
+ }
3075
+ catch (_a) {
3076
+ // localStorage may be unavailable
3077
+ }
3078
+ }
3079
+ /**
3080
+ * Clears stored MCP session (but preserves userId for returning user identification).
3081
+ * Optionally clears the stored lastSeq for the given sessionId.
3082
+ */
3083
+ function clearStoredSession(sessionId) {
3084
+ if (typeof window === "undefined" || !window.localStorage) {
3085
+ return;
3086
+ }
3087
+ try {
3088
+ localStorage.removeItem(MCP_STORAGE_KEYS.SESSION_ID);
3089
+ if (sessionId) {
3090
+ clearStoredLastSeq(sessionId);
3091
+ }
3092
+ }
3093
+ catch (_a) {
3094
+ // localStorage may be unavailable
3095
+ }
3096
+ }
3097
+ /**
3098
+ * Retrieves stored lastSeq from sessionStorage for replay on reconnect.
3099
+ * Uses sessionStorage (not localStorage) because seq is ephemeral to the browser tab.
3100
+ */
3101
+ function getStoredLastSeq(sessionId) {
3102
+ if (typeof window === "undefined" || !window.sessionStorage) {
3103
+ return undefined;
3104
+ }
3105
+ try {
3106
+ var stored = sessionStorage.getItem("xapp_mcp_lastSeq_".concat(sessionId));
3107
+ return stored ? parseInt(stored, 10) : undefined;
3108
+ }
3109
+ catch (_a) {
3110
+ return undefined;
3111
+ }
3112
+ }
3113
+ /**
3114
+ * Persists lastSeq to sessionStorage for replay on reconnect.
3115
+ */
3116
+ function storeLastSeq(sessionId, seq) {
3117
+ if (typeof window === "undefined" || !window.sessionStorage) {
3118
+ return;
3119
+ }
3120
+ try {
3121
+ sessionStorage.setItem("xapp_mcp_lastSeq_".concat(sessionId), String(seq));
3122
+ }
3123
+ catch (_a) {
3124
+ // sessionStorage may be unavailable
3125
+ }
3126
+ }
3127
+ /**
3128
+ * Clears stored lastSeq from sessionStorage.
3129
+ */
3130
+ function clearStoredLastSeq(sessionId) {
3131
+ if (typeof window === "undefined" || !window.sessionStorage) {
3132
+ return;
3133
+ }
3134
+ try {
3135
+ sessionStorage.removeItem("xapp_mcp_lastSeq_".concat(sessionId));
3136
+ }
3137
+ catch (_a) {
3138
+ // sessionStorage may be unavailable
3139
+ }
3140
+ }
3141
+ /**
3142
+ * Gets the current page context from browser globals.
3143
+ * If an override is provided, uses that instead (useful for testing).
3144
+ *
3145
+ * PRIVACY NOTE: Query strings are stripped from URLs by default as they
3146
+ * may contain sensitive data (tokens, PII). Only origin + pathname are sent.
3147
+ */
3148
+ function getPageContext(override) {
3149
+ var localTime = new Date().toISOString();
3150
+ // If override provided, merge with defaults (override wins)
3151
+ // Overrides are typically for testing and may include full URLs
3152
+ if (override && Object.keys(override).length > 0) {
3153
+ log("[getPageContext] Using override: ".concat(JSON.stringify(override)));
3154
+ return {
3155
+ url: override.url,
3156
+ path: override.path,
3157
+ title: override.title,
3158
+ referrer: override.referrer,
3159
+ localTime: localTime,
3160
+ };
3161
+ }
3162
+ if (typeof window === "undefined") {
3163
+ return { localTime: localTime };
3164
+ }
3165
+ // Strip query strings from URL for privacy (may contain tokens/PII)
3166
+ var urlWithoutQuery = window.location.origin + window.location.pathname;
3167
+ // Strip query strings from referrer as well
3168
+ var referrerWithoutQuery;
3169
+ if (document.referrer) {
3170
+ try {
3171
+ var refUrl = new URL(document.referrer);
3172
+ referrerWithoutQuery = refUrl.origin + refUrl.pathname;
3173
+ }
3174
+ catch (_a) {
3175
+ // Invalid referrer URL, skip it
3176
+ referrerWithoutQuery = undefined;
3177
+ }
3178
+ }
3179
+ return {
3180
+ url: urlWithoutQuery,
3181
+ path: window.location.pathname,
3182
+ title: document.title || undefined,
3183
+ referrer: referrerWithoutQuery,
3184
+ localTime: localTime,
3185
+ };
3186
+ }
3187
+ var MCPChat = /** @class */ (function () {
3188
+ function MCPChat(config, options) {
3189
+ this._userId = "";
3190
+ this._sessionId = "";
3191
+ this._accessToken = "";
3192
+ this._attributes = {};
3193
+ // Streaming throttle state
3194
+ this.streamBuffer = "";
3195
+ // New session initialization state
3196
+ this.isInitializingSession = false;
3197
+ // Session validation state (silent handshake - no stream dispatch)
3198
+ this.isValidatingSession = false;
3199
+ // Tool call tracking for debug mode
3200
+ this.currentToolCalls = [];
3201
+ // TODO: isReplaying and lastSeq are tracked for future replay support.
3202
+ // Wire up replay requests in wakeup() once the server endpoint supports it.
3203
+ this.isReplaying = false;
3204
+ // True when userId was assigned by the server (via connected SSE event),
3205
+ // meaning it should not be overwritten by the default client fingerprint.
3206
+ this.userIdIsServerAssigned = false;
3207
+ this.config = config;
3208
+ this.options = options;
3209
+ this.configurableMessages = options === null || options === void 0 ? void 0 : options.configurableMessages;
3210
+ this.isNewSession = false;
3211
+ this.hooks = options === null || options === void 0 ? void 0 : options.hooks;
3212
+ // Validate business_owner mode requires authToken
3213
+ var mode = config.mode || "homeowner";
3214
+ if (mode === "business_owner" && !config.authToken) {
3215
+ throw new Error("authToken is required for business_owner mode");
3216
+ }
3217
+ // Load stored identity from localStorage
3218
+ var storedIdentity = getStoredIdentity();
3219
+ if (storedIdentity.userId) {
3220
+ this._userId = storedIdentity.userId;
3221
+ this.userIdIsServerAssigned = true;
3222
+ log("[MCPChat] Loaded stored userId: ".concat(this._userId));
3223
+ }
3224
+ if (storedIdentity.sessionId) {
3225
+ this._sessionId = storedIdentity.sessionId;
3226
+ log("[MCPChat] Loaded stored sessionId: ".concat(this._sessionId));
3227
+ }
3228
+ // Config-provided sessionId takes precedence
3229
+ if (config.sessionId) {
3230
+ this._sessionId = config.sessionId;
3231
+ }
3232
+ // Load lastSeq from sessionStorage for replay on reconnect
3233
+ if (this._sessionId) {
3234
+ this.lastSeq = getStoredLastSeq(this._sessionId);
3235
+ if (this.lastSeq !== undefined) {
3236
+ log("[MCPChat] Loaded stored lastSeq: ".concat(this.lastSeq));
3237
+ }
3238
+ }
3239
+ }
3240
+ MCPChat.prototype.init = function (dispatch) {
3241
+ this.dispatch = dispatch;
3242
+ var mode = this.config.mode || "homeowner";
3243
+ log("MCPChat initialized with mode: ".concat(mode));
3244
+ // Warn if not using HTTPS with authentication
3245
+ if (mode === "business_owner" && !this.config.serverUrl.startsWith("https://")) {
3246
+ err("WARNING: Using non-HTTPS URL with authentication tokens. This is insecure in production.");
3247
+ }
3248
+ this.setConnectionStatus("online");
3249
+ this.setAccountStatus("online");
3250
+ // Dispatch bot joined early so it appears before the greeting.
3251
+ // getBot() is config-derived (uses options.bot), so it's safe to call before setVisitorInfo.
3252
+ this.userJoined(this.getBot(undefined));
3253
+ // Returning user with existing session - validate session silently
3254
+ // New user without session - initialize new session to get greeting
3255
+ if (this._sessionId) {
3256
+ this.validateSession();
3257
+ }
3258
+ else {
3259
+ this.initializeNewSession();
3260
+ }
3261
+ };
3262
+ /**
3263
+ * Validates an existing session with the server ("Silent Handshake").
3264
+ * Called when widget reopens with a stored sessionId.
3265
+ *
3266
+ * The server will respond with:
3267
+ * - "connected" event if session is valid
3268
+ * - "session_expired" event if session is gone (triggers initializeNewSession)
3269
+ *
3270
+ * This is lightweight - no LLM call, just a quick DB check to:
3271
+ * - Validate session before user tries to send a message
3272
+ * - Update lastActivityAt in persistent session
3273
+ * - Confirm userId is still valid
3274
+ */
3275
+ MCPChat.prototype.validateSession = function () {
3276
+ return __awaiter$1(this, void 0, void 0, function () {
3277
+ var endpoint, error_1;
3278
+ return __generator$1(this, function (_a) {
3279
+ switch (_a.label) {
3280
+ case 0:
3281
+ endpoint = this.getEndpoint();
3282
+ log("Validating existing session at ".concat(endpoint, ", sessionId: ").concat(this._sessionId));
3283
+ this.isValidatingSession = true;
3284
+ _a.label = 1;
3285
+ case 1:
3286
+ _a.trys.push([1, 3, 4, 5]);
3287
+ return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload({ resumeSession: true }))];
3288
+ case 2:
3289
+ _a.sent();
3290
+ log("Session validation complete");
3291
+ return [3 /*break*/, 5];
3292
+ case 3:
3293
+ error_1 = _a.sent();
3294
+ err("Failed to validate session: ".concat(error_1));
3295
+ // If validation fails (network error, etc.), clear session and start fresh
3296
+ clearStoredSession(this._sessionId);
3297
+ this._sessionId = "";
3298
+ this.lastSeq = undefined;
3299
+ this.initializeNewSession();
3300
+ return [3 /*break*/, 5];
3301
+ case 4:
3302
+ this.isValidatingSession = false;
3303
+ return [7 /*endfinally*/];
3304
+ case 5: return [2 /*return*/];
3305
+ }
3306
+ });
3307
+ });
3308
+ };
3309
+ /**
3310
+ * Builds the common request payload with identity and context.
3311
+ */
3312
+ MCPChat.prototype.buildRequestPayload = function (additionalData) {
3313
+ if (additionalData === void 0) { additionalData = {}; }
3314
+ return __assign({
3315
+ // Identity - server generates these, we just send back what we have
3316
+ userId: this._userId || undefined, sessionId: this._sessionId || undefined,
3317
+ // Fingerprint for user identification (can be disabled for privacy)
3318
+ fingerprint: this.config.disableFingerprinting ? undefined : getFingerprint(),
3319
+ // Page context
3320
+ pageContext: getPageContext(this.config.pageContextOverride),
3321
+ // Client timestamp for latency measurement and clock drift detection
3322
+ clientTime: new Date().toISOString() }, additionalData);
3323
+ };
3324
+ /**
3325
+ * Initializes a new session by calling the endpoint with { newSession: true }.
3326
+ * This returns a greeting message and session ID without making an LLM call.
3327
+ */
3328
+ MCPChat.prototype.initializeNewSession = function () {
3329
+ return __awaiter$1(this, void 0, void 0, function () {
3330
+ var endpoint, error_2;
3331
+ var _this = this;
3332
+ return __generator$1(this, function (_a) {
3333
+ switch (_a.label) {
3334
+ case 0:
3335
+ // Prevent multiple concurrent initialization requests
3336
+ if (this.isInitializingSession) {
3337
+ log("New session initialization already in progress, skipping");
3338
+ return [2 /*return*/];
3339
+ }
3340
+ endpoint = this.getEndpoint();
3341
+ log("Initializing new session at ".concat(endpoint));
3342
+ this.isInitializingSession = true;
3343
+ // Create a promise that resolves when initialization completes
3344
+ this.sessionInitPromise = new Promise(function (resolve) {
3345
+ _this.resolveSessionInit = resolve;
3346
+ });
3347
+ this.typing();
3348
+ _a.label = 1;
3349
+ case 1:
3350
+ _a.trys.push([1, 3, 4, 5]);
3351
+ return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload({ newSession: true, generateGreeting: true }))];
3352
+ case 2:
3353
+ _a.sent();
3354
+ return [3 /*break*/, 5];
3355
+ case 3:
3356
+ error_2 = _a.sent();
3357
+ err("Failed to initialize new session: ".concat(error_2));
3358
+ this.stopTyping();
3359
+ this.setConnectionStatus("offline");
3360
+ // Show a failure message but don't block - user can still try sending messages
3361
+ this.sendFailureMessage(0, 0, this.getErrorMessage(error_2));
3362
+ return [3 /*break*/, 5];
3363
+ case 4:
3364
+ this.isInitializingSession = false;
3365
+ // Resolve the promise so any waiting postMessage calls can proceed
3366
+ if (this.resolveSessionInit) {
3367
+ this.resolveSessionInit();
3368
+ this.resolveSessionInit = undefined;
3369
+ }
3370
+ this.sessionInitPromise = undefined;
3371
+ return [7 /*endfinally*/];
3372
+ case 5: return [2 /*return*/];
3373
+ }
3374
+ });
3375
+ });
3376
+ };
3377
+ MCPChat.prototype.setConnectionStatus = function (status) {
3378
+ log("SERVER: connection_update: ".concat(JSON.stringify(status)));
3379
+ this.dispatch(setConnectionStatus(status));
3380
+ };
3381
+ MCPChat.prototype.setAccountStatus = function (status) {
3382
+ log("SERVER: account_status: ".concat(JSON.stringify(status)));
3383
+ this.dispatch(setAccountStatus(status));
3384
+ };
3385
+ MCPChat.prototype.getEndpoint = function () {
3386
+ // Normalize serverUrl to remove trailing slashes
3387
+ var serverUrl = this.config.serverUrl.replace(/\/+$/, "");
3388
+ var mode = this.config.mode || "homeowner";
3389
+ if (mode === "business_owner") {
3390
+ return "".concat(serverUrl).concat(BUSINESS_OWNER_ENDPOINT_PATH);
3391
+ }
3392
+ // homeowner mode - serverUrl should already include appId
3393
+ return "".concat(serverUrl).concat(HOMEOWNER_ENDPOINT_PATH);
3394
+ };
3395
+ MCPChat.prototype.sendNewMessage = function (msg, user) {
3396
+ log("SERVER: new message: ".concat(JSON.stringify(msg)));
3397
+ this.dispatch({
3398
+ type: "chat",
3399
+ detail: {
3400
+ type: "chat.msg",
3401
+ user: user || this.getBot(undefined),
3402
+ msg: msg,
3403
+ timestamp: +new Date(),
3404
+ },
3405
+ });
3406
+ };
3407
+ MCPChat.prototype.userJoined = function (user) {
3408
+ log("SERVER: user joined: ".concat(JSON.stringify(user)));
3409
+ this.dispatch({
3410
+ type: "chat",
3411
+ detail: {
3412
+ type: "chat.memberjoin",
3413
+ user: user,
3414
+ timestamp: +new Date(),
3415
+ },
3416
+ });
3417
+ };
3418
+ MCPChat.prototype.typing = function () {
3419
+ this.dispatch({
3420
+ type: "chat",
3421
+ detail: {
3422
+ type: "chat.typing",
3423
+ user: this.getBot(undefined),
3424
+ typing: true,
3425
+ timestamp: +new Date(),
3426
+ },
3427
+ });
3428
+ };
3429
+ MCPChat.prototype.stopTyping = function () {
3430
+ this.dispatch({
3431
+ type: "chat",
3432
+ detail: {
3433
+ type: "chat.typing",
3434
+ user: this.getBot(undefined),
3435
+ typing: false,
3436
+ timestamp: +new Date(),
3437
+ },
3438
+ });
3439
+ };
3440
+ MCPChat.prototype.sendFailureMessage = function (retry, delay, text) {
3441
+ this.stopTyping();
3442
+ this.dispatch({
3443
+ type: "chat",
3444
+ detail: {
3445
+ type: "chat.failureMsg",
3446
+ user: this.getBot(undefined),
3447
+ failureMsg: {
3448
+ retry: retry,
3449
+ delay: delay,
3450
+ text: text,
3451
+ },
3452
+ timestamp: +new Date(),
3453
+ },
3454
+ });
3455
+ };
3456
+ /**
3457
+ * Gets the retry configuration, using custom config if provided or defaults.
3458
+ */
3459
+ MCPChat.prototype.getRetryConfig = function () {
3460
+ var _a, _b;
3461
+ if (((_b = (_a = this.configurableMessages) === null || _a === void 0 ? void 0 : _a.items) === null || _b === void 0 ? void 0 : _b.length) > 0) {
3462
+ return getConfigurableMessagesConfig(this.configurableMessages);
3463
+ }
3464
+ return DEFAULT_MCP_RETRY_CONFIG;
3465
+ };
3466
+ /**
3467
+ * Delays execution for the specified number of seconds.
3468
+ */
3469
+ MCPChat.prototype.delay = function (seconds) {
3470
+ return new Promise(function (resolve) { return setTimeout(resolve, seconds * 1000); });
3471
+ };
3472
+ /**
3473
+ * Determines a user-friendly error message based on the error type.
3474
+ */
3475
+ MCPChat.prototype.getErrorMessage = function (error, statusCode) {
3476
+ // Auth errors
3477
+ if (statusCode === 401) {
3478
+ return "Authentication failed. Please check your credentials.";
3479
+ }
3480
+ if (statusCode === 403) {
3481
+ return "Access denied. You don't have permission to access this resource.";
3482
+ }
3483
+ // Server errors
3484
+ if (statusCode && statusCode >= 500) {
3485
+ return "The server is experiencing issues. Please try again later.";
3486
+ }
3487
+ // Not found (likely wrong appId or endpoint)
3488
+ if (statusCode === 404) {
3489
+ return "Service not found. Please check your configuration.";
3490
+ }
3491
+ // Network errors
3492
+ if (error instanceof TypeError && (error.message.includes("fetch") || error.message.includes("network"))) {
3493
+ return "Unable to connect to the server. Please check your internet connection.";
3494
+ }
3495
+ // Connection refused / server down
3496
+ if (error instanceof Error) {
3497
+ var msg = error.message.toLowerCase();
3498
+ if (msg.includes("failed to fetch") || msg.includes("network") || msg.includes("econnrefused")) {
3499
+ return "Unable to reach the server. Please try again later.";
3500
+ }
3501
+ }
3502
+ // Generic fallback
3503
+ return "Something went wrong. Please try again.";
3504
+ };
3505
+ MCPChat.prototype.dispatchStreamStart = function (messageId) {
3506
+ var bot = this.getBot(undefined);
3507
+ var streamTimestamp = +new Date();
3508
+ log("[dispatchStreamStart] Stream started: ".concat(messageId, ", timestamp: ").concat(streamTimestamp));
3509
+ this.dispatch({
3510
+ type: "chat",
3511
+ detail: {
3512
+ type: "chat.stream.start",
3513
+ user: bot,
3514
+ messageId: messageId,
3515
+ timestamp: streamTimestamp,
3516
+ },
3517
+ });
3518
+ };
3519
+ MCPChat.prototype.dispatchStreamChunk = function (messageId, text) {
3520
+ this.dispatch({
3521
+ type: "chat",
3522
+ detail: {
3523
+ type: "chat.stream.chunk",
3524
+ user: this.getBot(undefined),
3525
+ messageId: messageId,
3526
+ text: text,
3527
+ timestamp: +new Date(),
3528
+ },
3529
+ });
3530
+ };
3531
+ MCPChat.prototype.dispatchStreamEnd = function (messageId, fullMessage, toolCalls) {
3532
+ log("Stream ended: ".concat(messageId));
3533
+ this.dispatch({
3534
+ type: "chat",
3535
+ detail: {
3536
+ type: "chat.stream.end",
3537
+ user: this.getBot(undefined),
3538
+ messageId: messageId,
3539
+ msg: fullMessage,
3540
+ toolCalls: toolCalls,
3541
+ timestamp: +new Date(),
3542
+ },
3543
+ });
3544
+ };
3545
+ MCPChat.prototype.dispatchToolStart = function (params) {
3546
+ this.dispatch({
3547
+ type: "chat",
3548
+ detail: {
3549
+ type: "chat.stream.tool_start",
3550
+ user: this.getBot(undefined),
3551
+ messageId: params.messageId,
3552
+ toolName: params.toolName,
3553
+ toolCallId: params.toolCallId,
3554
+ displayName: params.displayName,
3555
+ label: params.label,
3556
+ hidden: params.hidden,
3557
+ toolInput: params.toolInput,
3558
+ timestamp: +new Date(),
3559
+ },
3560
+ });
3561
+ };
3562
+ MCPChat.prototype.dispatchToolEnd = function (params) {
3563
+ this.dispatch({
3564
+ type: "chat",
3565
+ detail: {
3566
+ type: "chat.stream.tool_end",
3567
+ user: this.getBot(undefined),
3568
+ messageId: params.messageId,
3569
+ toolName: params.toolName,
3570
+ toolCallId: params.toolCallId,
3571
+ displayName: params.displayName,
3572
+ label: params.label,
3573
+ hidden: params.hidden,
3574
+ toolResult: params.toolResult,
3575
+ toolError: params.toolError,
3576
+ timestamp: +new Date(),
3577
+ },
3578
+ });
3579
+ };
3580
+ /**
3581
+ * Starts the throttled streaming for a message.
3582
+ * Text will be dispatched at a readable pace.
3583
+ */
3584
+ MCPChat.prototype.startStreamThrottle = function (messageId) {
3585
+ var _this = this;
3586
+ this.streamBuffer = "";
3587
+ this.streamingMessageIdForThrottle = messageId;
3588
+ // Clear any existing timer and visibility handler
3589
+ if (this.streamThrottleTimer) {
3590
+ clearInterval(this.streamThrottleTimer);
3591
+ }
3592
+ if (this.visibilityHandler) {
3593
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3594
+ }
3595
+ // Start interval to dispatch buffered text
3596
+ this.streamThrottleTimer = setInterval(function () {
3597
+ if (_this.streamBuffer.length > 0 && _this.streamingMessageIdForThrottle) {
3598
+ var charsToSend = Math.min(STREAM_CHARS_PER_TICK, _this.streamBuffer.length);
3599
+ var textToDispatch = _this.streamBuffer.slice(0, charsToSend);
3600
+ _this.streamBuffer = _this.streamBuffer.slice(charsToSend);
3601
+ _this.dispatchStreamChunk(_this.streamingMessageIdForThrottle, textToDispatch);
3602
+ }
3603
+ }, STREAM_TICK_INTERVAL_MS);
3604
+ // Handle tab visibility changes - flush buffer when tab becomes visible
3605
+ this.visibilityHandler = function () {
3606
+ if (document.visibilityState === "visible") {
3607
+ log("[Visibility] Tab became visible, buffer length: ".concat(_this.streamBuffer.length, ", messageId: ").concat(_this.streamingMessageIdForThrottle));
3608
+ if (_this.streamBuffer.length > 0 && _this.streamingMessageIdForThrottle) {
3609
+ // Dispatch all buffered text immediately when tab becomes visible
3610
+ log("[Visibility] Flushing ".concat(_this.streamBuffer.length, " chars to UI"));
3611
+ _this.dispatchStreamChunk(_this.streamingMessageIdForThrottle, _this.streamBuffer);
3612
+ _this.streamBuffer = "";
3613
+ }
3614
+ }
3615
+ else {
3616
+ log("[Visibility] Tab hidden");
3617
+ }
3618
+ };
3619
+ document.addEventListener("visibilitychange", this.visibilityHandler);
3620
+ };
3621
+ /**
3622
+ * Adds text to the stream buffer for throttled dispatch.
3623
+ */
3624
+ MCPChat.prototype.bufferStreamText = function (text) {
3625
+ this.streamBuffer += text;
3626
+ };
3627
+ /**
3628
+ * Flushes any remaining buffered text and stops the throttle timer.
3629
+ * Returns a promise that resolves when all buffered text has been dispatched.
3630
+ */
3631
+ MCPChat.prototype.flushStreamBuffer = function () {
3632
+ return __awaiter$1(this, void 0, void 0, function () {
3633
+ var waitCount, maxWait;
3634
+ return __generator$1(this, function (_a) {
3635
+ switch (_a.label) {
3636
+ case 0:
3637
+ if (!(this.streamBuffer.length > 0 && this.streamingMessageIdForThrottle)) return [3 /*break*/, 5];
3638
+ if (!(document.visibilityState === "hidden")) return [3 /*break*/, 1];
3639
+ log("[flushStreamBuffer] Tab hidden, dispatching remaining ".concat(this.streamBuffer.length, " chars immediately"));
3640
+ this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
3641
+ this.streamBuffer = "";
3642
+ return [3 /*break*/, 5];
3643
+ case 1:
3644
+ waitCount = 0;
3645
+ maxWait = 100;
3646
+ _a.label = 2;
3647
+ case 2:
3648
+ if (!(this.streamBuffer.length > 0 && waitCount < maxWait)) return [3 /*break*/, 4];
3649
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, STREAM_TICK_INTERVAL_MS); })];
3650
+ case 3:
3651
+ _a.sent();
3652
+ waitCount++;
3653
+ return [3 /*break*/, 2];
3654
+ case 4:
3655
+ // If still has content after timeout, flush it
3656
+ if (this.streamBuffer.length > 0 && this.streamingMessageIdForThrottle) {
3657
+ log("[flushStreamBuffer] Timeout waiting for buffer, dispatching remaining ".concat(this.streamBuffer.length, " chars"));
3658
+ this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
3659
+ this.streamBuffer = "";
3660
+ }
3661
+ _a.label = 5;
3662
+ case 5:
3663
+ // Stop the throttle timer
3664
+ if (this.streamThrottleTimer) {
3665
+ clearInterval(this.streamThrottleTimer);
3666
+ this.streamThrottleTimer = undefined;
3667
+ }
3668
+ if (this.visibilityHandler) {
3669
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3670
+ this.visibilityHandler = undefined;
3671
+ }
3672
+ this.streamingMessageIdForThrottle = undefined;
3673
+ return [2 /*return*/];
3674
+ }
3675
+ });
3676
+ });
3677
+ };
3678
+ /**
3679
+ * Immediately stops throttling and clears the buffer (used on abort/error).
3680
+ */
3681
+ MCPChat.prototype.stopStreamThrottle = function () {
3682
+ if (this.streamThrottleTimer) {
3683
+ clearInterval(this.streamThrottleTimer);
3684
+ this.streamThrottleTimer = undefined;
3685
+ }
3686
+ if (this.visibilityHandler) {
3687
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3688
+ this.visibilityHandler = undefined;
3689
+ }
3690
+ this.streamBuffer = "";
3691
+ this.streamingMessageIdForThrottle = undefined;
3692
+ };
3693
+ MCPChat.prototype.sendOfflineMsg = function (_, cb) {
3694
+ cb();
3695
+ };
3696
+ MCPChat.prototype.sendChatMsg = function (message, cb) {
3697
+ return __awaiter$1(this, void 0, void 0, function () {
3698
+ return __generator$1(this, function (_a) {
3699
+ return [2 /*return*/, this.sendChatMsgRequest({
3700
+ msg: message,
3701
+ timestamp: new Date().getTime(),
3702
+ agent: false,
3703
+ user: undefined,
3704
+ type: "msg",
3705
+ }, cb)];
3706
+ });
3707
+ });
3708
+ };
3709
+ MCPChat.prototype.bargeOut = function (_) {
3710
+ return __awaiter$1(this, void 0, void 0, function () {
3711
+ return __generator$1(this, function (_a) {
3712
+ throw new Error("bargeOut is not supported in MCP mode");
3713
+ });
3714
+ });
3715
+ };
3716
+ MCPChat.prototype.bargeIn = function (_agentName, __) {
3717
+ return __awaiter$1(this, void 0, void 0, function () {
3718
+ return __generator$1(this, function (_a) {
3719
+ throw new Error("bargeIn is not supported in MCP mode");
3720
+ });
3721
+ });
3722
+ };
3723
+ /**
3724
+ * Ensures a nick has the "agent:" prefix required for agent message detection.
3725
+ */
3726
+ MCPChat.prototype.ensureAgentNick = function (nick) {
3727
+ if (!nick)
3728
+ return "agent:robot";
3729
+ if (nick.startsWith("agent:"))
3730
+ return nick;
3731
+ return "agent:".concat(nick);
3732
+ };
3733
+ MCPChat.prototype.getBot = function (user) {
3734
+ var _a;
3735
+ var bot = (_a = this.options) === null || _a === void 0 ? void 0 : _a.bot;
3736
+ if (!user) {
3737
+ // Ensure bot nick has agent: prefix and mark as AI
3738
+ var defaultBot = bot || { nick: "agent:robot", displayName: "Assistant" };
3739
+ return __assign(__assign({}, defaultBot), { nick: this.ensureAgentNick(defaultBot.nick), isAI: true });
3740
+ }
3741
+ if (user.displayName && user.nick && user.avatarPath) {
3742
+ return __assign(__assign({}, user), { nick: this.ensureAgentNick(user.nick), isAI: true });
3743
+ }
3744
+ return __assign(__assign({}, user), { nick: this.ensureAgentNick(user.nick || (bot === null || bot === void 0 ? void 0 : bot.nick)), displayName: user.displayName || (bot === null || bot === void 0 ? void 0 : bot.displayName) || "Assistant", avatarPath: user.avatarPath || (bot === null || bot === void 0 ? void 0 : bot.avatarPath), isAI: true });
3745
+ };
3746
+ MCPChat.prototype.sendChatMsgRequest = function (serviceRequest, cb) {
3747
+ return __awaiter$1(this, void 0, void 0, function () {
3748
+ var error_3;
3749
+ return __generator$1(this, function (_a) {
3750
+ switch (_a.label) {
3751
+ case 0:
3752
+ _a.trys.push([0, 2, , 3]);
3753
+ return [4 /*yield*/, this.postMessage(serviceRequest)];
3754
+ case 1:
3755
+ _a.sent();
3756
+ cb();
3757
+ return [3 /*break*/, 3];
3758
+ case 2:
3759
+ error_3 = _a.sent();
3760
+ cb(error_3);
3761
+ return [3 /*break*/, 3];
3762
+ case 3: return [2 /*return*/];
3763
+ }
3764
+ });
3765
+ });
3766
+ };
3767
+ MCPChat.prototype.postMessage = function (message) {
3768
+ return __awaiter$1(this, void 0, void 0, function () {
3769
+ var trimmedText, timeout, result, retryConfig, messageText, success, attempt, isLastAttempt, config, error_4, statusCode, errorMessage, statusCode_1, errorMessage, nextConfig, nextConfig;
3770
+ var _a, _b, _c;
3771
+ return __generator$1(this, function (_d) {
3772
+ switch (_d.label) {
3773
+ case 0:
3774
+ trimmedText = ((_b = (_a = message.msg) === null || _a === void 0 ? void 0 : _a.text) === null || _b === void 0 ? void 0 : _b.trim()) || "";
3775
+ if (!trimmedText) {
3776
+ log("Ignoring empty message");
3777
+ return [2 /*return*/];
3778
+ }
3779
+ if (!(this.isInitializingSession && this.sessionInitPromise)) return [3 /*break*/, 2];
3780
+ log("Waiting for session initialization to complete before sending message...");
3781
+ timeout = new Promise(function (resolve) {
3782
+ return setTimeout(function () { return resolve("timeout"); }, 10000);
3783
+ });
3784
+ return [4 /*yield*/, Promise.race([this.sessionInitPromise, timeout])];
3785
+ case 1:
3786
+ result = _d.sent();
3787
+ if (result === "timeout" || this.isInitializingSession) {
3788
+ err("Session initialization timed out");
3789
+ this.sendFailureMessage(0, 0, "Chat is not ready. Please try again.");
3790
+ return [2 /*return*/];
3791
+ }
3792
+ _d.label = 2;
3793
+ case 2:
3794
+ retryConfig = this.getRetryConfig();
3795
+ messageText = ((_c = message.msg) === null || _c === void 0 ? void 0 : _c.text) || "";
3796
+ success = false;
3797
+ attempt = 0;
3798
+ _d.label = 3;
3799
+ case 3:
3800
+ if (!(attempt < retryConfig.length)) return [3 /*break*/, 14];
3801
+ isLastAttempt = attempt === retryConfig.length - 1;
3802
+ config = retryConfig[attempt];
3803
+ log("MCP attempt ".concat(attempt + 1, "/").concat(retryConfig.length, " for message: ").concat(messageText));
3804
+ // Start typing indicator
3805
+ this.typing();
3806
+ _d.label = 4;
3807
+ case 4:
3808
+ _d.trys.push([4, 6, , 13]);
3809
+ return [4 /*yield*/, this.attemptSSERequest(message)];
3810
+ case 5:
3811
+ _d.sent();
3812
+ success = true;
3813
+ return [3 /*break*/, 14]; // Success - exit retry loop
3814
+ case 6:
3815
+ error_4 = _d.sent();
3816
+ this.stopTyping();
3817
+ statusCode = error_4 === null || error_4 === void 0 ? void 0 : error_4.statusCode;
3818
+ err("MCP attempt ".concat(attempt + 1, " failed: ").concat(error_4, ", statusCode: ").concat(statusCode));
3819
+ log("[postMessage] Error details:", { error: error_4, statusCode: statusCode, messageText: messageText, sessionId: this._sessionId });
3820
+ // Don't retry on 4xx client errors - these won't succeed on retry
3821
+ if (statusCode && statusCode >= 400 && statusCode < 500) {
3822
+ log("Client error ".concat(statusCode, ", not retrying"));
3823
+ errorMessage = this.getErrorMessage(error_4, statusCode);
3824
+ this.sendFailureMessage(0, 0, errorMessage);
3825
+ return [2 /*return*/];
3826
+ }
3827
+ // Set offline status
3828
+ this.setConnectionStatus("offline");
3829
+ if (!isLastAttempt) return [3 /*break*/, 7];
3830
+ statusCode_1 = error_4 === null || error_4 === void 0 ? void 0 : error_4.statusCode;
3831
+ errorMessage = config.text || this.getErrorMessage(error_4, statusCode_1);
3832
+ this.sendFailureMessage(0, 0, errorMessage);
3833
+ return [3 /*break*/, 12];
3834
+ case 7:
3835
+ if (!(attempt > 0 || config.text)) return [3 /*break*/, 10];
3836
+ nextConfig = retryConfig[attempt + 1];
3837
+ if (!(nextConfig && nextConfig.delay > 0)) return [3 /*break*/, 9];
3838
+ this.sendFailureMessage(nextConfig.retry, nextConfig.delay, nextConfig.text);
3839
+ // Wait for the delay before retrying
3840
+ return [4 /*yield*/, this.delay(nextConfig.delay)];
3841
+ case 8:
3842
+ // Wait for the delay before retrying
3843
+ _d.sent();
3844
+ _d.label = 9;
3845
+ case 9: return [3 /*break*/, 12];
3846
+ case 10:
3847
+ nextConfig = retryConfig[attempt + 1];
3848
+ if (!(nextConfig && nextConfig.delay > 0)) return [3 /*break*/, 12];
3849
+ return [4 /*yield*/, this.delay(nextConfig.delay)];
3850
+ case 11:
3851
+ _d.sent();
3852
+ _d.label = 12;
3853
+ case 12: return [3 /*break*/, 13];
3854
+ case 13:
3855
+ attempt++;
3856
+ return [3 /*break*/, 3];
3857
+ case 14:
3858
+ if (!success) {
3859
+ log("All MCP retry attempts exhausted");
3860
+ }
3861
+ return [2 /*return*/];
3862
+ }
3863
+ });
3864
+ });
3865
+ };
3866
+ /**
3867
+ * Attempts a single SSE request. Throws on failure.
3868
+ */
3869
+ MCPChat.prototype.attemptSSERequest = function (message) {
3870
+ return __awaiter$1(this, void 0, void 0, function () {
3871
+ var messageText;
3872
+ var _a;
3873
+ return __generator$1(this, function (_b) {
3874
+ messageText = ((_a = message.msg) === null || _a === void 0 ? void 0 : _a.text) || "";
3875
+ log("[attemptSSERequest] Sending message: \"".concat(messageText, "\", userId: ").concat(this._userId, ", sessionId: ").concat(this._sessionId));
3876
+ return [2 /*return*/, this.performSSERequest(this.buildRequestPayload({ message: messageText }))];
3877
+ });
3878
+ });
3879
+ };
3880
+ /**
3881
+ * Performs an SSE request with the given body.
3882
+ * Used for both new session initialization and regular messages.
3883
+ */
3884
+ MCPChat.prototype.performSSERequest = function (body) {
3885
+ return __awaiter$1(this, void 0, void 0, function () {
3886
+ var endpoint, messageId, textChunks, hasError, httpStatusCode, sseError;
3887
+ var _this = this;
3888
+ return __generator$1(this, function (_a) {
3889
+ endpoint = this.getEndpoint();
3890
+ log("SSE request to ".concat(endpoint, ": ").concat(JSON.stringify(body)));
3891
+ // Cancel any ongoing stream
3892
+ if (this.abortController) {
3893
+ this.abortController.abort();
3894
+ this.abortController = undefined;
3895
+ }
3896
+ this.abortController = new AbortController();
3897
+ messageId = uuid_1();
3898
+ this.currentMessageId = messageId;
3899
+ textChunks = [];
3900
+ hasError = false;
3901
+ return [2 /*return*/, new Promise(function (resolve, reject) {
3902
+ // WARNING: Do not log headers object - it contains sensitive auth tokens
3903
+ cjs.fetchEventSource(endpoint, {
3904
+ method: "POST",
3905
+ headers: __assign({ "Content-Type": "application/json" }, ((_this.config.mode || "homeowner") === "business_owner" && _this.config.authToken
3906
+ ? { Authorization: "Bearer ".concat(_this.config.authToken) }
3907
+ : {})),
3908
+ body: JSON.stringify(body),
3909
+ signal: _this.abortController.signal,
3910
+ onopen: function (response) { return __awaiter$1(_this, void 0, void 0, function () {
3911
+ var error;
3912
+ return __generator$1(this, function (_a) {
3913
+ httpStatusCode = response.status;
3914
+ if (!response.ok) {
3915
+ error = new Error("HTTP error! status: ".concat(response.status));
3916
+ error.statusCode = response.status;
3917
+ throw error;
3918
+ }
3919
+ log("SSE connection opened");
3920
+ // Connection successful - ensure we're marked as online
3921
+ this.setConnectionStatus("online");
3922
+ return [2 /*return*/];
3923
+ });
3924
+ }); },
3925
+ onmessage: function (event) {
3926
+ log("SSE event: ".concat(event.event, ", data: ").concat(event.data));
3927
+ var eventType = event.event || "message";
3928
+ var data = {};
3929
+ try {
3930
+ data = JSON.parse(event.data);
3931
+ }
3932
+ catch (e) {
3933
+ log("Failed to parse SSE data: ".concat(e));
3934
+ return;
3935
+ }
3936
+ // Track sequence number for replay on reconnect
3937
+ if (data.seq !== undefined && data.seq !== null) {
3938
+ _this.lastSeq = data.seq;
3939
+ if (_this._sessionId) {
3940
+ storeLastSeq(_this._sessionId, data.seq);
3941
+ }
3942
+ }
3943
+ switch (eventType) {
3944
+ case "connected":
3945
+ // Connection established
3946
+ log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser, ", isReplay: ").concat(data.isReplay));
3947
+ // Track if this is a replay connection
3948
+ if (data.isReplay) {
3949
+ _this.isReplaying = true;
3950
+ }
3951
+ // Store identity from server response
3952
+ if (data.userId || data.sessionId) {
3953
+ if (data.userId) {
3954
+ _this._userId = data.userId;
3955
+ _this.userIdIsServerAssigned = true;
3956
+ }
3957
+ if (data.sessionId) {
3958
+ _this._sessionId = data.sessionId;
3959
+ _this.dispatch(setSessionId(_this._sessionId));
3960
+ }
3961
+ // Persist to localStorage for session continuity
3962
+ storeIdentity(data.userId, data.sessionId);
3963
+ log("[SSE] Identity from connected - userId: ".concat(data.userId, ", sessionId: ").concat(data.sessionId));
3964
+ }
3965
+ // Always start streaming - server sends greeting for both new and returning users
3966
+ // (resumeSession requests always get a personalized greeting)
3967
+ _this.currentToolCalls = [];
3968
+ _this.stopTyping();
3969
+ _this.dispatchStreamStart(messageId);
3970
+ _this.startStreamThrottle(messageId);
3971
+ break;
3972
+ case "session_expired":
3973
+ // Session no longer exists on server - need to start fresh
3974
+ log("[SSE] Session expired: ".concat(data.reason || "Session not found"));
3975
+ // Clear stored session (and lastSeq) but keep userId for returning user identification
3976
+ clearStoredSession(_this._sessionId);
3977
+ _this._sessionId = "";
3978
+ _this.lastSeq = undefined;
3979
+ _this.stopTyping();
3980
+ _this.stopStreamThrottle();
3981
+ // Reset validation flag before starting new session
3982
+ // (ensures connected handler will dispatch stream for greeting)
3983
+ _this.isValidatingSession = false;
3984
+ // Start a new session automatically
3985
+ _this.initializeNewSession();
3986
+ resolve();
3987
+ break;
3988
+ case "text":
3989
+ if (data.text) {
3990
+ log("[SSE] Text chunk received (".concat(data.text.length, " chars), total chunks: ").concat(textChunks.length + 1, ", isReplay: ").concat(data.isReplay));
3991
+ textChunks.push(data.text);
3992
+ if (data.isReplay) {
3993
+ // Replayed text — dispatch immediately (no throttle)
3994
+ // so the user sees caught-up content instantly
3995
+ _this.dispatchStreamChunk(messageId, data.text);
3996
+ }
3997
+ else {
3998
+ // Live text — buffer for throttled dispatch
3999
+ _this.bufferStreamText(data.text);
4000
+ }
4001
+ }
4002
+ break;
4003
+ case "tool_start":
4004
+ log("Tool execution started: ".concat(data.toolName, ", toolCallId: ").concat(data.toolCallId, ", displayName: ").concat(data.displayName, ", label: ").concat(data.label, ", hidden: ").concat(data.hidden));
4005
+ if (data.toolName) {
4006
+ var toolCall = {
4007
+ name: data.toolName,
4008
+ toolCallId: data.toolCallId,
4009
+ displayName: data.displayName,
4010
+ label: data.label,
4011
+ hidden: data.hidden,
4012
+ input: data.toolInput,
4013
+ startTime: Date.now(),
4014
+ isExecuting: true,
4015
+ };
4016
+ _this.currentToolCalls.push(toolCall);
4017
+ // Always dispatch tool start for user-friendly progress display
4018
+ _this.dispatchToolStart({
4019
+ messageId: messageId,
4020
+ toolName: data.toolName,
4021
+ toolCallId: data.toolCallId,
4022
+ displayName: data.displayName,
4023
+ label: data.label,
4024
+ hidden: data.hidden,
4025
+ toolInput: data.toolInput,
4026
+ });
4027
+ }
4028
+ break;
4029
+ case "tool_end":
4030
+ log("Tool execution ended: ".concat(data.toolName, ", toolCallId: ").concat(data.toolCallId, ", displayName: ").concat(data.displayName, ", label: ").concat(data.label, ", hidden: ").concat(data.hidden));
4031
+ if (data.toolName) {
4032
+ // Find the matching tool call and update it
4033
+ // Match by toolCallId if available, otherwise fall back to name + isExecuting
4034
+ var toolCall = _this.currentToolCalls.find(function (tc) {
4035
+ return data.toolCallId
4036
+ ? tc.toolCallId === data.toolCallId
4037
+ : tc.name === data.toolName && tc.isExecuting;
4038
+ });
4039
+ if (toolCall) {
4040
+ toolCall.result = data.toolResult;
4041
+ toolCall.error = data.toolError;
4042
+ toolCall.label = data.label; // Update with completion label
4043
+ toolCall.hidden = data.hidden; // Update hidden status
4044
+ toolCall.endTime = Date.now();
4045
+ toolCall.isExecuting = false;
4046
+ }
4047
+ // Always dispatch tool end for user-friendly progress display
4048
+ _this.dispatchToolEnd({
4049
+ messageId: messageId,
4050
+ toolName: data.toolName,
4051
+ toolCallId: data.toolCallId,
4052
+ displayName: data.displayName,
4053
+ label: data.label,
4054
+ hidden: data.hidden,
4055
+ toolResult: data.toolResult,
4056
+ toolError: data.toolError,
4057
+ });
4058
+ }
4059
+ break;
4060
+ case "done":
4061
+ _this.stopTyping();
4062
+ if (data.response) {
4063
+ // Update session ID if provided and persist to localStorage
4064
+ if (data.response.sessionId) {
4065
+ _this._sessionId = data.response.sessionId;
4066
+ _this.dispatch(setSessionId(_this._sessionId));
4067
+ storeIdentity(undefined, data.response.sessionId);
4068
+ }
4069
+ // Wait for buffer to flush, then finalize
4070
+ var accumulatedText = textChunks.join("");
4071
+ log("[SSE] Done event received. Total chunks: ".concat(textChunks.length, ", accumulated text length: ").concat(accumulatedText.length));
4072
+ log("[SSE] First 100 chars of accumulated: ".concat(accumulatedText.substring(0, 100)));
4073
+ // Map suggestions to options (chips) format
4074
+ var suggestions = data.response.suggestions;
4075
+ var options = suggestions === null || suggestions === void 0 ? void 0 : suggestions.map(function (s) { return ({
4076
+ label: s.title,
4077
+ actionUrl: s.url || "",
4078
+ }); });
4079
+ if (options === null || options === void 0 ? void 0 : options.length) {
4080
+ log("[SSE] Suggestions: ".concat(options.length, " items"));
4081
+ }
4082
+ // Map sources for reference links
4083
+ var sources = data.response.sources;
4084
+ if (sources === null || sources === void 0 ? void 0 : sources.length) {
4085
+ log("[SSE] Sources: ".concat(sources.length, " items"));
4086
+ }
4087
+ var finalMessage_1 = {
4088
+ text: accumulatedText || data.response.response || "",
4089
+ options: options,
4090
+ sources: sources,
4091
+ };
4092
+ // Capture tool calls before flushing (debug mode)
4093
+ var toolCalls_1 = _this.config.debugMode ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
4094
+ _this.flushStreamBuffer().then(function () {
4095
+ _this.dispatchStreamEnd(messageId, finalMessage_1, toolCalls_1);
4096
+ _this.currentToolCalls = []; // Clear after dispatch
4097
+ resolve();
4098
+ });
4099
+ }
4100
+ else {
4101
+ _this.stopStreamThrottle();
4102
+ resolve();
4103
+ }
4104
+ break;
4105
+ case "replay_complete":
4106
+ _this.isReplaying = false;
4107
+ log("[SSE] Replay complete: replayed ".concat(data.replayedCount, " events, currentSeq: ").concat(data.currentSeq));
4108
+ break;
4109
+ case "replay_partial":
4110
+ _this.isReplaying = false;
4111
+ log("[SSE] Replay partial: some events unavailable, oldestAvailableSeq: ".concat(data.oldestAvailableSeq));
4112
+ break;
4113
+ case "error":
4114
+ hasError = true;
4115
+ _this.stopTyping();
4116
+ _this.stopStreamThrottle();
4117
+ err("SSE error: ".concat(data.error));
4118
+ // Server-side errors should not retry - reject immediately with the error
4119
+ reject(new Error(data.error || "Server error during processing"));
4120
+ break;
4121
+ }
4122
+ },
4123
+ onerror: function (error) {
4124
+ // Only handle error if this is still the current message
4125
+ if (_this.currentMessageId === messageId && !hasError) {
4126
+ hasError = true;
4127
+ _this.isReplaying = false;
4128
+ _this.stopStreamThrottle();
4129
+ sseError = error instanceof Error ? error : new Error(String(error));
4130
+ // Get status code if available
4131
+ var statusCode = (error === null || error === void 0 ? void 0 : error.statusCode) || httpStatusCode;
4132
+ if (statusCode) {
4133
+ sseError.statusCode = statusCode;
4134
+ }
4135
+ err("SSE connection error: ".concat(error));
4136
+ }
4137
+ // Throw to stop fetchEventSource from retrying
4138
+ throw error;
4139
+ },
4140
+ }).catch(function (error) {
4141
+ // Handle errors that occur before/outside the SSE connection
4142
+ _this.stopStreamThrottle();
4143
+ if (!hasError && _this.currentMessageId === messageId) {
4144
+ var statusCode = (error === null || error === void 0 ? void 0 : error.statusCode) || httpStatusCode;
4145
+ var wrappedError = error instanceof Error ? error : new Error(String(error));
4146
+ if (statusCode) {
4147
+ wrappedError.statusCode = statusCode;
4148
+ }
4149
+ reject(wrappedError);
4150
+ }
4151
+ else if (sseError) {
4152
+ reject(sseError);
4153
+ }
4154
+ else {
4155
+ reject(error);
4156
+ }
4157
+ });
4158
+ })];
4159
+ });
4160
+ });
4161
+ };
4162
+ MCPChat.prototype.sendTyping = function () { };
4163
+ MCPChat.prototype.setVisitorInfo = function (visitorInfo, sessionId, cb) {
4164
+ this.visitorInfo = visitorInfo;
4165
+ this._attributes = __assign({}, this.visitorInfo.attributes);
4166
+ // do not set currentUrl if localhost
4167
+ var href = new URL(window.location.href);
4168
+ if (href === null || href === void 0 ? void 0 : href.host.toLowerCase().startsWith("localhost")) {
4169
+ this._attributes.isLocal = true;
4170
+ }
4171
+ else {
4172
+ this._attributes.currentUrl = window.location.href;
4173
+ }
4174
+ this._accessToken = this.visitorInfo.accessToken;
4175
+ // Bot joined is dispatched in init() to ensure correct ordering
4176
+ // Show typing indicator if we're still waiting for initial greeting
4177
+ if (this.isInitializingSession) {
4178
+ this.typing();
4179
+ }
4180
+ this.startSession(sessionId);
4181
+ cb();
4182
+ };
4183
+ MCPChat.prototype.sendChatRating = function () { };
4184
+ MCPChat.prototype.sendFile = function (_, cb) {
4185
+ cb(new Error("File upload not supported in MCP mode"));
4186
+ };
4187
+ MCPChat.prototype.markAsRead = function () { };
4188
+ MCPChat.prototype.flush = function () { };
4189
+ MCPChat.prototype.dispose = function () {
4190
+ this.stopStreamThrottle();
4191
+ if (this.abortController) {
4192
+ this.abortController.abort();
4193
+ this.abortController = undefined;
4194
+ }
4195
+ };
4196
+ MCPChat.prototype.sleep = function () {
4197
+ this.stopStreamThrottle();
4198
+ this.isReplaying = false;
4199
+ if (this.abortController) {
4200
+ this.abortController.abort();
4201
+ this.abortController = undefined;
4202
+ }
4203
+ };
4204
+ MCPChat.prototype.wakeup = function () {
4205
+ // When tab is foregrounded, just restore connection status.
4206
+ // Don't validate/replay yet — the session will be validated
4207
+ // on the next user message. This avoids creating duplicate
4208
+ // greetings if validation fails transiently.
4209
+ if (this._sessionId) {
4210
+ log("[MCPChat] Wakeup - session exists, lastSeq: ".concat(this.lastSeq));
4211
+ this.setConnectionStatus("online");
4212
+ }
4213
+ };
4214
+ MCPChat.prototype.startSession = function (sessionId) {
4215
+ // userId priority:
4216
+ // 1. Existing server-assigned userId (from localStorage or previous connected event)
4217
+ // 2. Explicitly provided visitorId or email from host app
4218
+ // 3. Generate a new one as last resort
4219
+ if (this.userIdIsServerAssigned) {
4220
+ // Keep server-assigned userId — don't overwrite with default fingerprint
4221
+ log("[startSession] Preserving server-assigned userId: ".concat(this._userId));
4222
+ }
4223
+ else if (this.visitorInfo.visitorId) {
4224
+ this._userId = "".concat(this.visitorInfo.visitorId);
4225
+ }
4226
+ else if (this.visitorInfo.email) {
4227
+ this._userId = "mcp-widget-user-".concat(this.visitorInfo.email);
4228
+ }
4229
+ else if (!this._userId) {
4230
+ // Only generate a new userId if we don't already have one
4231
+ // (preserves userId loaded from localStorage or received from server)
4232
+ this._userId = "mcp-widget-user-".concat(uuid_1());
4233
+ }
4234
+ // Session ID priority:
4235
+ // 1. If explicitly provided via setVisitorInfo, use it
4236
+ // 2. If empty string provided, generate a new session ID
4237
+ // 3. If not provided (undefined) and we have an existing session, keep it
4238
+ if (sessionId) {
4239
+ // Explicit session ID provided - use it
4240
+ this._sessionId = sessionId;
4241
+ log("Using provided session id: ".concat(this._sessionId));
4242
+ }
4243
+ else if (sessionId === "") {
4244
+ // Empty string explicitly passed - generate new session
4245
+ this._sessionId = "mcp-widget-session-".concat(uuid_1());
4246
+ this.dispatch(setSessionId(this._sessionId));
4247
+ log("Using generated session id: ".concat(this._sessionId));
4248
+ }
4249
+ else if (!this._sessionId && this.config.sessionId) {
4250
+ // No existing session and config has one - use config
4251
+ this._sessionId = this.config.sessionId;
4252
+ log("Using config session id: ".concat(this._sessionId));
4253
+ }
4254
+ else if (!this._sessionId) {
4255
+ // No session ID at all - generate one
4256
+ this._sessionId = "mcp-widget-session-".concat(uuid_1());
4257
+ this.dispatch(setSessionId(this._sessionId));
4258
+ log("Using generated session id: ".concat(this._sessionId));
4259
+ }
4260
+ else {
4261
+ log("Keeping existing session id: ".concat(this._sessionId));
4262
+ }
4263
+ this.isNewSession = true;
4264
+ };
4265
+ Object.defineProperty(MCPChat.prototype, "userId", {
4266
+ get: function () {
4267
+ return this._userId;
4268
+ },
4269
+ enumerable: false,
4270
+ configurable: true
4271
+ });
4272
+ Object.defineProperty(MCPChat.prototype, "sessionId", {
4273
+ get: function () {
4274
+ return this._sessionId;
4275
+ },
4276
+ enumerable: false,
4277
+ configurable: true
4278
+ });
4279
+ return MCPChat;
4280
+ }());
4281
+
4282
+ /**
4283
+ * Sends a POST to your STENTOR based server.
4284
+ *
4285
+ * @param data
4286
+ * @param url
4287
+ * @param key
4288
+ * @param signal
4289
+ * @returns
4290
+ */
4291
+ function postMessageToStentor(data, url, key, signal) {
4292
+ return __awaiter$1(this, void 0, void 0, function () {
4293
+ var body, response;
4294
+ return __generator$1(this, function (_a) {
4295
+ switch (_a.label) {
4296
+ case 0:
4297
+ body = JSON.stringify(data);
4298
+ log("URL: ".concat(url));
4299
+ log("BODY: ".concat(body));
4300
+ return [4 /*yield*/, fetch(url, {
4301
+ method: "POST",
4302
+ headers: {
4303
+ "Content-Type": "application/json",
4304
+ "Authorization": "Bearer ".concat(key),
4305
+ },
4306
+ body: body,
4307
+ mode: "cors",
4308
+ signal: signal
4309
+ })];
4310
+ case 1:
4311
+ response = _a.sent();
4312
+ if (!response.ok) {
4313
+ throw new Error("Status ".concat(response.status, ", Text: ").concat(response.statusText));
4314
+ }
4315
+ return [2 /*return*/, response.json()];
4316
+ }
4317
+ });
4318
+ });
4319
+ }
4320
+
4321
+ function convertFromListDisplay(list) {
4322
+ return {
4323
+ type: list.type,
4324
+ title: list.title,
4325
+ items: list.items.map(function (item) {
4326
+ var _a, _b;
4327
+ var responseItem = {
4328
+ title: item.title,
4329
+ subTitle: item.description,
4330
+ token: item.token,
4331
+ url: item.url,
4332
+ hideUrl: item.hideUrl,
4333
+ imageUrl: (_a = item.image) === null || _a === void 0 ? void 0 : _a.url,
4334
+ imageActionUrl: (_b = item.image) === null || _b === void 0 ? void 0 : _b.imageActionUrl
4335
+ };
4336
+ // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
4337
+ var itemButtons = item.buttons;
4338
+ if (itemButtons && itemButtons.length > 0) {
4339
+ responseItem.buttons = itemButtons.map(function (button) { return ({
4340
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4341
+ actionUrl: button.openUrlAction,
4342
+ label: button.title
4343
+ }); });
4344
+ }
4345
+ return responseItem;
4346
+ })
4347
+ };
4348
+ }
4349
+ function convertToListDisplay(list) {
4350
+ return {
4351
+ type: list.type,
4352
+ title: list.title,
4353
+ items: (list.items || []).map(function (item) {
4354
+ var responseItem = {
4355
+ title: item.title,
4356
+ description: item.subTitle,
4357
+ token: item.token,
4358
+ url: item.url,
4359
+ hideUrl: item.hideUrl,
4360
+ image: {
4361
+ url: item.imageUrl,
4362
+ imageActionUrl: item.imageActionUrl,
4363
+ accessibilityText: ""
4364
+ }
4365
+ };
4366
+ // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
4367
+ var itemButtons = item.buttons;
4368
+ if (itemButtons && itemButtons.length > 0) {
4369
+ responseItem.buttons = itemButtons.map(function (button) { return ({
4370
+ openUrlAction: button.actionUrl,
4371
+ title: button.label
4372
+ }); });
4373
+ }
4374
+ return responseItem;
4375
+ })
4376
+ };
4377
+ }
4378
+ function convertFromCardDisplay(card) {
4379
+ return {
4380
+ content: card.content,
4381
+ imageUrl: card.smallImageUrl,
4382
+ title: card.title,
4383
+ imageActionUrl: card === null || card === void 0 ? void 0 : card.imageActionUrl,
4384
+ buttons: card.buttons ? card.buttons.map(function (button) { return ({
4385
+ actionUrl: button.openUrlAction,
4386
+ label: button.title
4387
+ }); }) : undefined
4388
+ };
4389
+ }
4390
+ function getOutput(value) {
4391
+ if (value) {
4392
+ if (typeof value === "string") {
4393
+ return {
4394
+ displayText: value
4395
+ };
4396
+ }
4397
+ else {
4398
+ return value;
4399
+ }
4400
+ }
4401
+ return undefined;
4402
+ }
4403
+ /**
4404
+ * Converts a Stentor Response to a ChatMessageRequest
4405
+ *
4406
+ * @param botResponse
4407
+ * @param now
4408
+ * @returns
4409
+ */
4410
+ function responseToMessage(botResponse, now) {
4411
+ var _a, _b, _c, _d, _e;
4412
+ if (now === void 0) { now = new Date().getTime(); }
4413
+ var responseMessage;
4414
+ if (!botResponse) {
4415
+ return responseMessage;
4416
+ }
4417
+ var text;
4418
+ var html;
4419
+ var endSession;
4420
+ var outputSpeech = getOutput(botResponse.outputSpeech);
4421
+ var reprompt = getOutput(botResponse.reprompt);
4422
+ if (outputSpeech) {
4423
+ text = outputSpeech.displayText;
4424
+ html = outputSpeech.html;
4425
+ }
4426
+ if (botResponse.system === "TRANSFER_CALL") {
4427
+ responseMessage = {
4428
+ type: "handOff",
4429
+ timestamp: now,
4430
+ handoffMessage: text,
4431
+ handoffTarget: (_a = botResponse === null || botResponse === void 0 ? void 0 : botResponse.data) === null || _a === void 0 ? void 0 : _a.transferPhoneNumber,
4432
+ endSession: false,
4433
+ user: undefined
4434
+ };
4435
+ }
4436
+ else if ((_b = botResponse.system) === null || _b === void 0 ? void 0 : _b.startsWith("PERMISSION_")) {
4437
+ responseMessage = getPermissionResponse(botResponse, now);
4438
+ }
4439
+ else {
4440
+ endSession = !reprompt || !(reprompt.displayText || reprompt.ssml);
4441
+ responseMessage = {
4442
+ type: "msg",
4443
+ timestamp: now,
4444
+ msg: {
4445
+ displays: botResponse.displays,
4446
+ context: (_d = (_c = botResponse.context) === null || _c === void 0 ? void 0 : _c.active) === null || _d === void 0 ? void 0 : _d.map(function (ctx) { return ctx.name; })
4447
+ },
4448
+ endSession: endSession,
4449
+ user: undefined
4450
+ };
4451
+ if (text && !html) {
4452
+ responseMessage.msg = __assign(__assign({}, responseMessage.msg), { text: text });
4453
+ }
4454
+ if (html) {
4455
+ responseMessage.msg = __assign(__assign({}, responseMessage.msg), { html: html });
4456
+ }
4457
+ if ((_e = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.suggestions) === null || _e === void 0 ? void 0 : _e.length) {
4458
+ responseMessage.msg.options = outputSpeech.suggestions.map(function (suggestion) {
4459
+ if (typeof suggestion === "string") {
4460
+ // Simple chips (strings)
4461
+ return suggestion;
4462
+ }
4463
+ else {
4464
+ // "call out" chips
4465
+ return {
4466
+ label: suggestion.title,
4467
+ actionUrl: suggestion.url
4468
+ };
4469
+ }
4470
+ });
4471
+ }
4472
+ }
4473
+ return responseMessage;
4474
+ }
4475
+ function getPermissionRequestType(type) {
4476
+ switch (type) {
4477
+ case "PERMISSION_EMAIL":
4478
+ return "EMAIL";
4479
+ case "PERMISSION_LOCATION_PRECISE":
4480
+ return "LOCATION_PRECISE";
4481
+ default:
4482
+ throw new Error("Unsupported permission: ".concat(type));
4483
+ }
4484
+ }
4485
+ function getPermissionResponse(botResponse, now) {
4486
+ var _a;
4487
+ var type = getPermissionRequestType(botResponse.system);
4488
+ var outputSpeech = getOutput(botResponse.outputSpeech);
4489
+ var permissionPrimerAccepted = botResponse.data.permissionPrimerAccepted;
4490
+ var permissionDenied = botResponse.data.permissionDenied;
4491
+ return {
4492
+ type: "permissionRequest",
4493
+ timestamp: now,
4494
+ msg: {
4495
+ text: (_a = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.displayText) !== null && _a !== void 0 ? _a : "".concat(botResponse.data.permissionRequestTTSContext),
4496
+ permissionRequest: {
4497
+ time: now,
4498
+ type: type,
4499
+ approve: typeof permissionPrimerAccepted === "object" ? responseToMessage({
4500
+ outputSpeech: permissionPrimerAccepted
4501
+ }).msg : undefined,
4502
+ deny: typeof permissionDenied === "object" ? responseToMessage({
4503
+ outputSpeech: permissionDenied
4504
+ }).msg : undefined
4505
+ }
4506
+ },
4507
+ endSession: false,
4508
+ user: undefined //todo: set
4509
+ };
4510
+ }
4511
+
2881
4512
  var PERMISSION_QUESTION_EXPIRATION_MS$1 = 300000; // 5 minutes
2882
4513
  // interface UserLeaveMessage {
2883
4514
  // readonly user: ChatUserInfo;
@@ -4760,7 +6391,7 @@ function createPacketDecoderStream(maxPayload, binaryType) {
4760
6391
  },
4761
6392
  });
4762
6393
  }
4763
- const protocol$1 = 4;
6394
+ const protocol = 4;
4764
6395
 
4765
6396
  /**
4766
6397
  * Expose `Emitter`.
@@ -6036,7 +7667,7 @@ class SocketWithoutUpgrade extends Emitter_1 {
6036
7667
  createTransport(name) {
6037
7668
  const query = Object.assign({}, this.opts.query);
6038
7669
  // append engine.io protocol identifier
6039
- query.EIO = protocol$1;
7670
+ query.EIO = protocol;
6040
7671
  // transport name
6041
7672
  query.transport = name;
6042
7673
  // session id if we already have one
@@ -6411,7 +8042,7 @@ class SocketWithoutUpgrade extends Emitter_1 {
6411
8042
  }
6412
8043
  }
6413
8044
  }
6414
- SocketWithoutUpgrade.protocol = protocol$1;
8045
+ SocketWithoutUpgrade.protocol = protocol;
6415
8046
  /**
6416
8047
  * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
6417
8048
  * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
@@ -6807,12 +8438,6 @@ const RESERVED_EVENTS$1 = [
6807
8438
  "newListener",
6808
8439
  "removeListener", // used by the Node.js EventEmitter
6809
8440
  ];
6810
- /**
6811
- * Protocol version.
6812
- *
6813
- * @public
6814
- */
6815
- const protocol = 5;
6816
8441
  var PacketType;
6817
8442
  (function (PacketType) {
6818
8443
  PacketType[PacketType["CONNECT"] = 0] = "CONNECT";
@@ -7109,8 +8734,7 @@ var parser = /*#__PURE__*/Object.freeze({
7109
8734
  __proto__: null,
7110
8735
  Decoder: Decoder,
7111
8736
  Encoder: Encoder,
7112
- get PacketType () { return PacketType; },
7113
- protocol: protocol
8737
+ get PacketType () { return PacketType; }
7114
8738
  });
7115
8739
 
7116
8740
  function on(obj, ev, fn) {
@@ -8664,6 +10288,7 @@ var StentorServerChat = /** @class */ (function () {
8664
10288
  }());
8665
10289
 
8666
10290
  function createChatServerCore(config, options) {
10291
+ var _a, _b, _c, _d, _e;
8667
10292
  switch (config.type) {
8668
10293
  case "direct":
8669
10294
  return new StentorDirectChat({
@@ -8679,6 +10304,15 @@ function createChatServerCore(config, options) {
8679
10304
  return new StentorRouterChat({
8680
10305
  url: config.serverUrl,
8681
10306
  }, options);
10307
+ case "mcp":
10308
+ return new MCPChat({
10309
+ serverUrl: config.serverUrl,
10310
+ mode: (_a = config.mcp) === null || _a === void 0 ? void 0 : _a.mode,
10311
+ authToken: (_b = config.mcp) === null || _b === void 0 ? void 0 : _b.authToken,
10312
+ debugMode: (_c = config.mcp) === null || _c === void 0 ? void 0 : _c.debugMode,
10313
+ pageContextOverride: (_d = config.mcp) === null || _d === void 0 ? void 0 : _d.pageContextOverride,
10314
+ showAiBadge: (_e = config.mcp) === null || _e === void 0 ? void 0 : _e.showAiBadge,
10315
+ }, options);
8682
10316
  case "local":
8683
10317
  return new StentorLocalChat();
8684
10318
  default:
@@ -8832,6 +10466,79 @@ var DrawerBars = function (props) {
8832
10466
  return (jsx("button", { className: "drawer-bars", tabIndex: props.tabIndex ? Number(props.tabIndex) : 0, "aria-label": "open menu", "aria-hidden": false, onClick: props.onToggle, children: getBars() }));
8833
10467
  };
8834
10468
 
10469
+ /**
10470
+ * Type guard for ChatMsgDetail.
10471
+ * Use this to safely narrow ChatDetail to ChatMsgDetail.
10472
+ */
10473
+ function isChatMsgDetail(chat) {
10474
+ return chat.type === "chat.msg";
10475
+ }
10476
+
10477
+ /**
10478
+ * Formats a suggestion chip for export
10479
+ */
10480
+ function formatSuggestion(option) {
10481
+ if (isChatServerActionLink(option)) {
10482
+ return option.actionUrl ? "[".concat(option.label, "](").concat(option.actionUrl, ")") : option.label;
10483
+ }
10484
+ return option;
10485
+ }
10486
+ /**
10487
+ * Formats chat messages for export
10488
+ */
10489
+ function formatConversation(chats) {
10490
+ return chats
10491
+ .filter(isChatMsgDetail)
10492
+ .map(function (chat) {
10493
+ var _a, _b, _c;
10494
+ var isVisitor = (_a = chat.user.nick) === null || _a === void 0 ? void 0 : _a.startsWith("visitor:");
10495
+ var role = isVisitor ? "User" : "Assistant";
10496
+ var text = ((_b = chat.msg) === null || _b === void 0 ? void 0 : _b.text) || "";
10497
+ var result = "".concat(role, ": ").concat(text);
10498
+ // Add suggestion chips if present
10499
+ var options = (_c = chat.msg) === null || _c === void 0 ? void 0 : _c.options;
10500
+ if (options && options.length > 0) {
10501
+ var suggestions = options.map(formatSuggestion).join(", ");
10502
+ result += "\nSuggestions: ".concat(suggestions);
10503
+ }
10504
+ return result;
10505
+ })
10506
+ .join("\n");
10507
+ }
10508
+ var ExportButton = function (props) {
10509
+ var chats = useSelector(function (state) { return state.chats; });
10510
+ var handleClick = function () { return __awaiter$1(void 0, void 0, void 0, function () {
10511
+ var conversation, textarea;
10512
+ return __generator$1(this, function (_b) {
10513
+ switch (_b.label) {
10514
+ case 0:
10515
+ conversation = formatConversation(chats);
10516
+ _b.label = 1;
10517
+ case 1:
10518
+ _b.trys.push([1, 3, , 4]);
10519
+ return [4 /*yield*/, navigator.clipboard.writeText(conversation)];
10520
+ case 2:
10521
+ _b.sent();
10522
+ return [3 /*break*/, 4];
10523
+ case 3:
10524
+ _b.sent();
10525
+ // Fallback for older browsers using deprecated execCommand
10526
+ // Note: execCommand is deprecated and may be removed in future browsers
10527
+ console.warn("[ExportButton] Using deprecated execCommand fallback for clipboard copy");
10528
+ textarea = document.createElement("textarea");
10529
+ textarea.value = conversation;
10530
+ document.body.appendChild(textarea);
10531
+ textarea.select();
10532
+ document.execCommand("copy");
10533
+ document.body.removeChild(textarea);
10534
+ return [3 /*break*/, 4];
10535
+ case 4: return [2 /*return*/];
10536
+ }
10537
+ });
10538
+ }); };
10539
+ return (jsx("div", { id: "xapp-widget-export", "aria-label": "export conversation", "aria-hidden": false, tabIndex: props.tabIndex ? Number(props.tabIndex) : 0, className: "export-button", onClick: handleClick }));
10540
+ };
10541
+
8835
10542
  var MenuButton = function (props) {
8836
10543
  return (jsx("div", { id: "xapp-widget-menu", "aria-label": "open menu", "aria-hidden": false, tabIndex: props.tabIndex ? Number(props.tabIndex) : 0, className: "menu-button", onClick: props.onClick }));
8837
10544
  };
@@ -8975,7 +10682,7 @@ var closeButtonAriaLabel = "To close widget click on close icon in top right sid
8975
10682
  var ChatHeader = function (props) {
8976
10683
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
8977
10684
  var innerDispatch = useChatDispatch();
8978
- var menuConfig = props.menuConfig, onSubmit = props.onSubmit;
10685
+ var menuConfig = props.menuConfig, onSubmit = props.onSubmit, debugMode = props.debugMode;
8979
10686
  var _r = useState(false), drawerOpen = _r[0], setDrawerState = _r[1]; // false initially
8980
10687
  var menuPosition = (menuConfig === null || menuConfig === void 0 ? void 0 : menuConfig.menuButtonLocation) || "FOOTER";
8981
10688
  var showMenuLeft = menuPosition === "HEADER_LEFT";
@@ -9014,7 +10721,7 @@ var ChatHeader = function (props) {
9014
10721
  ? "status-text-positionLeftNoAvatar"
9015
10722
  : "status-text-positionLeft", "\n ").concat(((_d = props.config) === null || _d === void 0 ? void 0 : _d.alignTextCenter)
9016
10723
  ? "status-text-positionCenter"
9017
- : "", " \n "), children: [jsx("span", { className: "status-text-title", children: getStatusText(props.accountStatus, (_e = props.config) === null || _e === void 0 ? void 0 : _e.status) }), ((_g = (_f = props.config) === null || _f === void 0 ? void 0 : _f.subtitle) === null || _g === void 0 ? void 0 : _g.enabled) && (jsx("span", { className: "status-text-subtitle", children: renderSubtitleText((_j = (_h = props.config) === null || _h === void 0 ? void 0 : _h.subtitle) === null || _j === void 0 ? void 0 : _j.text) }))] }), jsxs(ButtonGroup, { children: [hasRightMenu && (jsx(MenuButton, { onClick: toggleDrawer, tabIndex: menuButtonTabIndex })), props.canRefresh && (jsx(RefreshButton, { onClick: props.refreshOnClick, tabIndex: (_l = (_k = props.config) === null || _k === void 0 ? void 0 : _k.actions) === null || _l === void 0 ? void 0 : _l.refreshTabIndex, showInLeft: false, showInRight: false })), props.canMinimize && (jsx(MinimizeButton, { onClick: props.minimizeOnClick, tabIndex: (_o = (_m = props.config) === null || _m === void 0 ? void 0 : _m.actions) === null || _o === void 0 ? void 0 : _o.minimizeTabIndex, showInRight: false })), props.canCancel && (jsx(CancelButton, { onClick: props.cancelOnClick, tabIndex: (_q = (_p = props.config) === null || _p === void 0 ? void 0 : _p.actions) === null || _q === void 0 ? void 0 : _q.cancelTabIndex }))] })] }), drawerOpen ? (jsx("div", { className: "xa-chat-menu-container", children: jsx(ChatMenu, { openFrom: showMenuRight ? "right" : "left", opened: drawerOpen, tabIndex: menuItemsTabIndex, onItemClick: handleMenuItem, items: menuItems }) })) : (jsx(Fragment, {}))] }));
10724
+ : "", " \n "), children: [jsx("span", { className: "status-text-title", children: getStatusText(props.accountStatus, (_e = props.config) === null || _e === void 0 ? void 0 : _e.status) }), ((_g = (_f = props.config) === null || _f === void 0 ? void 0 : _f.subtitle) === null || _g === void 0 ? void 0 : _g.enabled) && (jsx("span", { className: "status-text-subtitle", children: renderSubtitleText((_j = (_h = props.config) === null || _h === void 0 ? void 0 : _h.subtitle) === null || _j === void 0 ? void 0 : _j.text) }))] }), jsxs(ButtonGroup, { children: [hasRightMenu && (jsx(MenuButton, { onClick: toggleDrawer, tabIndex: menuButtonTabIndex })), debugMode && jsx(ExportButton, {}), props.canRefresh && (jsx(RefreshButton, { onClick: props.refreshOnClick, tabIndex: (_l = (_k = props.config) === null || _k === void 0 ? void 0 : _k.actions) === null || _l === void 0 ? void 0 : _l.refreshTabIndex, showInLeft: false, showInRight: false })), props.canMinimize && (jsx(MinimizeButton, { onClick: props.minimizeOnClick, tabIndex: (_o = (_m = props.config) === null || _m === void 0 ? void 0 : _m.actions) === null || _o === void 0 ? void 0 : _o.minimizeTabIndex, showInRight: false })), props.canCancel && (jsx(CancelButton, { onClick: props.cancelOnClick, tabIndex: (_q = (_p = props.config) === null || _p === void 0 ? void 0 : _p.actions) === null || _q === void 0 ? void 0 : _q.cancelTabIndex }))] })] }), drawerOpen ? (jsx("div", { className: "xa-chat-menu-container", children: jsx(ChatMenu, { openFrom: showMenuRight ? "right" : "left", opened: drawerOpen, tabIndex: menuItemsTabIndex, onItemClick: handleMenuItem, items: menuItems }) })) : (jsx(Fragment, {}))] }));
9018
10725
  };
9019
10726
 
9020
10727
  var UnknownMessage = function () { return jsx(Fragment, {}); };
@@ -9028,7 +10735,8 @@ var ChatMessagePart = function (props) {
9028
10735
  var containerClass = "xappw-chat-msg-part" +
9029
10736
  (position === "below" ? " xappw-chat-msg-part--avatar-below" : "") +
9030
10737
  (position === "bottom" ? " xappw-chat-msg-part--avatar-bottom" : "") +
9031
- (props.fullWidth ? " xappw-chat-msg-part--full-width" : "");
10738
+ (props.fullWidth ? " xappw-chat-msg-part--full-width" : "") +
10739
+ (props.isStreaming ? " xappw-chat-msg-part--streaming" : "");
9032
10740
  var user = props.user, hideUserInfo = props.hideUserInfo;
9033
10741
  // Hide user info if hideUserInfo is true and position is "bottom"
9034
10742
  var shouldHideUserInfo = hideUserInfo && position === "bottom";
@@ -9526,12 +11234,169 @@ var ChatPermissionMessage = function (props) {
9526
11234
  return (jsxs("div", { className: "chat-msg", children: [jsx("span", { className: "message-sr-only", children: "at " + props.time + (agentMessage ? " the bot said" : " the user said") }), jsxs("div", { className: "buttons-container", children: [jsx(ActionButton, { label: allowLabel, addClass: "button", onClick: handleAllow }), jsx(ActionButton, { label: denyLabel, addClass: "button", onClick: handleDeny })] })] }));
9527
11235
  };
9528
11236
 
11237
+ var CopyButton = function (_a) {
11238
+ var text = _a.text;
11239
+ var _b = useState(false), copied = _b[0], setCopied = _b[1];
11240
+ var handleCopy = function (e) { return __awaiter$1(void 0, void 0, void 0, function () {
11241
+ var textarea;
11242
+ return __generator$1(this, function (_b) {
11243
+ switch (_b.label) {
11244
+ case 0:
11245
+ e.stopPropagation();
11246
+ _b.label = 1;
11247
+ case 1:
11248
+ _b.trys.push([1, 3, , 4]);
11249
+ return [4 /*yield*/, navigator.clipboard.writeText(text)];
11250
+ case 2:
11251
+ _b.sent();
11252
+ setCopied(true);
11253
+ setTimeout(function () { return setCopied(false); }, 1500);
11254
+ return [3 /*break*/, 4];
11255
+ case 3:
11256
+ _b.sent();
11257
+ textarea = document.createElement("textarea");
11258
+ textarea.value = text;
11259
+ document.body.appendChild(textarea);
11260
+ textarea.select();
11261
+ document.execCommand("copy");
11262
+ document.body.removeChild(textarea);
11263
+ setCopied(true);
11264
+ setTimeout(function () { return setCopied(false); }, 1500);
11265
+ return [3 /*break*/, 4];
11266
+ case 4: return [2 /*return*/];
11267
+ }
11268
+ });
11269
+ }); };
11270
+ return (jsx("button", { className: "tool-call-item__copy-btn", onClick: handleCopy, title: "Copy to clipboard", children: copied ? "✓" : "⧉" }));
11271
+ };
11272
+ /**
11273
+ * Gets the display text for a tool call, using fallback chain:
11274
+ * label -> displayName -> toolName -> "Working..."
11275
+ */
11276
+ var getDisplayLabel = function (toolCall) {
11277
+ var _a, _b, _c;
11278
+ return (_c = (_b = (_a = toolCall.label) !== null && _a !== void 0 ? _a : toolCall.displayName) !== null && _b !== void 0 ? _b : toolCall.name) !== null && _c !== void 0 ? _c : "Working...";
11279
+ };
11280
+ /**
11281
+ * User-friendly tool progress item (non-debug mode)
11282
+ * Shows just the label with a spinner or checkmark
11283
+ */
11284
+ var ToolProgressItem = function (_a) {
11285
+ var toolCall = _a.toolCall;
11286
+ var label = getDisplayLabel(toolCall);
11287
+ return (jsxs("div", { className: "tool-progress-item ".concat(toolCall.isExecuting ? "tool-progress-item--executing" : "", " ").concat(toolCall.error ? "tool-progress-item--error" : ""), children: [jsx("span", { className: "tool-progress-item__icon", children: toolCall.isExecuting ? (jsx("span", { className: "tool-progress-item__spinner", "aria-label": "executing" })) : toolCall.error ? (jsx("span", { className: "tool-progress-item__status tool-progress-item__status--error", children: "!" })) : (jsx("span", { className: "tool-progress-item__status tool-progress-item__status--success", children: "\u2713" })) }), jsx("span", { className: "tool-progress-item__label", children: label })] }));
11288
+ };
11289
+ /**
11290
+ * Debug mode tool call item with expandable details
11291
+ * Shows label, tool name, and expandable input/output
11292
+ */
11293
+ var ToolCallItem = function (_a) {
11294
+ var toolCall = _a.toolCall;
11295
+ var _b = useState(false), isExpanded = _b[0], setIsExpanded = _b[1];
11296
+ var toggleExpanded = function () { return setIsExpanded(!isExpanded); };
11297
+ var formatJson = function (data) {
11298
+ if (data === undefined || data === null)
11299
+ return "";
11300
+ try {
11301
+ // If it's a string, try to parse it as JSON first (server may send stringified JSON)
11302
+ if (typeof data === "string") {
11303
+ try {
11304
+ var parsed = JSON.parse(data);
11305
+ return JSON.stringify(parsed, null, 2);
11306
+ }
11307
+ catch (_a) {
11308
+ // Not valid JSON, return as-is
11309
+ return data;
11310
+ }
11311
+ }
11312
+ return JSON.stringify(data, null, 2);
11313
+ }
11314
+ catch (_b) {
11315
+ return String(data);
11316
+ }
11317
+ };
11318
+ var label = getDisplayLabel(toolCall);
11319
+ var duration = toolCall.endTime
11320
+ ? "".concat(((toolCall.endTime - toolCall.startTime) / 1000).toFixed(2), "s")
11321
+ : null;
11322
+ var hasInput = toolCall.input !== undefined && toolCall.input !== null;
11323
+ var hasResult = toolCall.result !== undefined && toolCall.result !== null;
11324
+ var hasError = !!toolCall.error;
11325
+ return (jsxs("div", { className: "tool-call-item ".concat(toolCall.isExecuting ? "tool-call-item--executing" : ""), children: [jsxs("button", { className: "tool-call-item__header", onClick: toggleExpanded, "aria-expanded": isExpanded, children: [jsx("span", { className: "tool-call-item__icon", children: toolCall.isExecuting ? (jsx("span", { className: "tool-call-item__spinner", "aria-label": "executing" })) : toolCall.error ? (jsx("span", { className: "tool-call-item__status tool-call-item__status--error", children: "!" })) : (jsx("span", { className: "tool-call-item__status tool-call-item__status--success", children: "\u2713" })) }), jsxs("span", { className: "tool-call-item__info", children: [jsx("span", { className: "tool-call-item__label", children: label }), jsx("span", { className: "tool-call-item__name", children: toolCall.name })] }), duration && jsx("span", { className: "tool-call-item__duration", children: duration }), jsx("span", { className: "tool-call-item__chevron ".concat(isExpanded ? "tool-call-item__chevron--expanded" : ""), children: "\u25BE" })] }), isExpanded && (jsxs("div", { className: "tool-call-item__content", children: [hasInput && (jsxs("div", { className: "tool-call-item__section", children: [jsxs("div", { className: "tool-call-item__section-header", children: [jsx("span", { className: "tool-call-item__section-label", children: "Input" }), jsx(CopyButton, { text: formatJson(toolCall.input) })] }), jsx("pre", { className: "tool-call-item__code", children: formatJson(toolCall.input) })] })), hasResult && (jsxs("div", { className: "tool-call-item__section", children: [jsxs("div", { className: "tool-call-item__section-header", children: [jsx("span", { className: "tool-call-item__section-label", children: "Output" }), jsx(CopyButton, { text: formatJson(toolCall.result) })] }), jsx("pre", { className: "tool-call-item__code", children: formatJson(toolCall.result) })] })), hasError && (jsxs("div", { className: "tool-call-item__section", children: [jsxs("div", { className: "tool-call-item__section-header", children: [jsx("span", { className: "tool-call-item__section-label tool-call-item__section-label--error", children: "Error" }), jsx(CopyButton, { text: toolCall.error || "" })] }), jsx("pre", { className: "tool-call-item__code tool-call-item__code--error", children: toolCall.error })] })), !hasInput && !hasResult && !hasError && (jsx("div", { className: "tool-call-item__empty", children: "No data available" }))] }))] }));
11326
+ };
11327
+ var ToolCallDisplay = function (_a) {
11328
+ var toolCalls = _a.toolCalls, _b = _a.debugMode, debugMode = _b === void 0 ? false : _b;
11329
+ if (!toolCalls || toolCalls.length === 0) {
11330
+ return null;
11331
+ }
11332
+ // Debug mode: show all tools with full expandable details
11333
+ if (debugMode) {
11334
+ return (jsxs("div", { className: "tool-call-display", children: [jsxs("div", { className: "tool-call-display__header", children: [jsx("span", { className: "tool-call-display__icon", children: "\u2699" }), jsxs("span", { className: "tool-call-display__title", children: ["Tool Calls (", toolCalls.length, ")"] })] }), jsx("div", { className: "tool-call-display__list", children: toolCalls.map(function (toolCall, index) { return (jsx(ToolCallItem, { toolCall: toolCall }, "".concat(toolCall.name, "-").concat(toolCall.startTime, "-").concat(index))); }) })] }));
11335
+ }
11336
+ // Normal mode: filter out hidden tools and show user-friendly progress
11337
+ var visibleToolCalls = toolCalls.filter(function (tc) { return !tc.hidden; });
11338
+ if (visibleToolCalls.length === 0) {
11339
+ return null;
11340
+ }
11341
+ return (jsx("div", { className: "tool-progress-display", children: visibleToolCalls.map(function (toolCall, index) { return (jsx(ToolProgressItem, { toolCall: toolCall }, "".concat(toolCall.name, "-").concat(toolCall.startTime, "-").concat(index))); }) }));
11342
+ };
11343
+
11344
+ /**
11345
+ * Parses basic markdown to HTML.
11346
+ * Supports: bold, italic, links, headers, and unordered lists.
11347
+ */
11348
+ function parseMarkdown(text) {
11349
+ if (!text)
11350
+ return "";
11351
+ var html = text
11352
+ // Escape HTML entities first
11353
+ .replace(/&/g, "&amp;")
11354
+ .replace(/</g, "&lt;")
11355
+ .replace(/>/g, "&gt;")
11356
+ // Headers (must be at start of line)
11357
+ .replace(/^### (.+)$/gm, "<h4>$1</h4>")
11358
+ .replace(/^## (.+)$/gm, "<h3>$1</h3>")
11359
+ .replace(/^# (.+)$/gm, "<h2>$1</h2>")
11360
+ // Bold (** or __)
11361
+ .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
11362
+ .replace(/__(.+?)__/g, "<strong>$1</strong>")
11363
+ // Italic (* or _) - must come after bold
11364
+ .replace(/\*([^*]+?)\*/g, "<em>$1</em>")
11365
+ .replace(/_([^_]+?)_/g, "<em>$1</em>")
11366
+ // Links [text](url)
11367
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
11368
+ // Unordered lists (- or *)
11369
+ .replace(/^[-*] (.+)$/gm, "<li>$1</li>")
11370
+ // Line breaks
11371
+ .replace(/\n/g, "<br>");
11372
+ // Wrap consecutive <li> elements in <ul>
11373
+ html = html.replace(/(<li>.*?<\/li>)(<br>)?/g, "$1");
11374
+ html = html.replace(/(<li>.*?<\/li>)+/g, "<ul>$&</ul>");
11375
+ // Clean up <br> inside <ul>
11376
+ html = html.replace(/<ul>(.*?)<\/ul>/g, function (match) {
11377
+ return match.replace(/<br>/g, "");
11378
+ });
11379
+ return html;
11380
+ }
9529
11381
  var ChatTextMessage = function (props) {
11382
+ var _a;
9530
11383
  var message = props.message;
9531
11384
  var date = new Date(message.timestamp);
9532
11385
  var time = date.getHours() + ":" + date.getMinutes();
9533
- var agentMessage = isAgent(props.message.user.nick);
9534
- return (jsx("div", { className: "chat-msg", children: jsx("div", { className: "chat-text-container", children: jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: agentMessage && !props.sibling, children: [jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? " the bot said" : " the user said") }), jsx("span", { children: message.msg.text })] }) }) }));
11386
+ var agentMessage = isAgent((_a = props.message.user) === null || _a === void 0 ? void 0 : _a.nick);
11387
+ var isStreaming = message.isStreaming;
11388
+ // Parse markdown for agent messages
11389
+ var parsedHtml = useMemo(function () {
11390
+ if (agentMessage && message.msg.text) {
11391
+ return parseMarkdown(message.msg.text);
11392
+ }
11393
+ return null;
11394
+ }, [agentMessage, message.msg.text]);
11395
+ // No tail for streaming messages (or messages that were streamed)
11396
+ var wasStreamed = message.wasStreamed;
11397
+ var showTail = agentMessage && !props.sibling && !isStreaming && !wasStreamed;
11398
+ var hasToolCalls = props.toolCalls && props.toolCalls.length > 0;
11399
+ return (jsxs("div", { className: "chat-msg".concat(isStreaming ? " chat-msg--streaming" : ""), children: [hasToolCalls && (jsx(ToolCallDisplay, { toolCalls: props.toolCalls, debugMode: props.debugMode })), jsx("div", { className: "chat-text-container", children: jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: showTail, children: [jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? (isStreaming ? " the bot is typing" : " the bot said") : " the user said") }), parsedHtml ? (jsx("span", { className: "chat-msg__content", dangerouslySetInnerHTML: { __html: parsedHtml } })) : (jsx("span", { className: "chat-msg__content", children: message.msg.text })), isStreaming && jsx("span", { className: "streaming-cursor", "aria-hidden": "true" })] }) })] }));
9535
11400
  };
9536
11401
 
9537
11402
  var ChatScheduleWidget = function (props) {
@@ -9545,6 +11410,24 @@ var ChatScheduleWidget = function (props) {
9545
11410
  return (jsx("div", { className: "chat-schedule-button-container", children: jsxs("button", { className: "chat-schedule-button", onClick: handleClick, children: [jsx("i", { className: "fa fa-lg fa-calendar" }), jsx("span", { children: display.label || "Schedule Now!" })] }) }));
9546
11411
  };
9547
11412
 
11413
+ /**
11414
+ * Displays source links below a chat message.
11415
+ * Designed to be compact and unobtrusive, unlike suggestion chips.
11416
+ */
11417
+ var SourceLinks = function (props) {
11418
+ var sources = props.sources, _a = props.prefix, prefix = _a === void 0 ? "Sources:" : _a, onSourceClick = props.onSourceClick;
11419
+ if (!sources || sources.length === 0) {
11420
+ return null;
11421
+ }
11422
+ var handleClick = function (source, e) {
11423
+ if (onSourceClick) {
11424
+ e.preventDefault();
11425
+ onSourceClick(source);
11426
+ }
11427
+ };
11428
+ return (jsxs("div", { className: "source-links", children: [prefix && jsx("span", { className: "source-links__prefix", children: prefix }), jsx("span", { className: "source-links__list", children: sources.map(function (source, index) { return (jsxs("span", { className: "source-links__item", children: [jsx("a", { href: source.url, target: source.newTab !== false ? "_blank" : "_self", rel: source.newTab !== false ? "noopener noreferrer" : undefined, className: "source-links__link", onClick: function (e) { return handleClick(source, e); }, title: source.url, children: source.title }), index < sources.length - 1 && (jsx("span", { className: "source-links__separator", children: "\u00B7" }))] }, index)); }) })] }));
11429
+ };
11430
+
9548
11431
  function getClassName(msg) {
9549
11432
  return isAgent(msg.user.nick) ? "agent" : "visitor";
9550
11433
  }
@@ -9556,7 +11439,7 @@ var avaKeys = ["text", "html", "card", "list"];
9556
11439
  * @returns
9557
11440
  */
9558
11441
  var ChatMessage = function (props) {
9559
- var _a;
11442
+ var _a, _b, _c;
9560
11443
  var middleware = props.messageMiddleware || StandardMiddlewares;
9561
11444
  var chatConfig = useContext(ChatConfigContext);
9562
11445
  // console.log(`########### chatConfig: ${JSON.stringify(chatConfig, null, 2)}`);
@@ -9566,7 +11449,7 @@ var ChatMessage = function (props) {
9566
11449
  var agentInfo = (_a = props.agents) === null || _a === void 0 ? void 0 : _a[props.message.user.nick];
9567
11450
  var hideUserInfo = (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.hideUserInfo) || false;
9568
11451
  function renderByType() {
9569
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
11452
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
9570
11453
  var msg = props.message.msg;
9571
11454
  switch (props.message.type) {
9572
11455
  // TODO: props actually requires it to be "chat.msg". Fix prop typing?
@@ -9574,27 +11457,42 @@ var ChatMessage = function (props) {
9574
11457
  // Here is the deal. If we have text (output speech), then text - card - list - options
9575
11458
  // OR card OR list only. Avatar with text bubble.
9576
11459
  var avaKey = avaKeys.find(function (key) { return !!msg[key]; });
9577
- return (jsxs(Fragment, { children: [msg.text &&
9578
- jsx(ChatMessagePart, { showAvatar: avaKey === "text", user: user, avatarPosition: (_c = (_b = (_a = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _a === void 0 ? void 0 : _a.theme) === null || _b === void 0 ? void 0 : _b.messages) === null || _c === void 0 ? void 0 : _c.avatarPosition, hideUserInfo: hideUserInfo, children: jsx(ChatTextMessage, { message: props.message, sibling: props.sibling }) }), msg.html &&
9579
- jsx(ChatMessagePart, { showAvatar: avaKey === "html", user: user, avatarPosition: (_f = (_e = (_d = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _d === void 0 ? void 0 : _d.theme) === null || _e === void 0 ? void 0 : _e.messages) === null || _f === void 0 ? void 0 : _f.avatarPosition, hideUserInfo: hideUserInfo, children: jsx(ChatMarkdownMessage, { message: props.message, sibling: props.sibling, onOpenUrl: (_g = props.middlewareContext) === null || _g === void 0 ? void 0 : _g.openUrl }) }), msg.displays && middleware && msg.displays.map(function (display, index) {
11460
+ var isStreaming_1 = props.message.isStreaming;
11461
+ var wasStreamed = props.message.wasStreamed;
11462
+ // Tool calls - show progress while tools execute (before text arrives)
11463
+ var toolCalls = props.message.toolCalls;
11464
+ var hasToolCalls = toolCalls && toolCalls.length > 0;
11465
+ var debugMode = (_d = (_c = (_b = (_a = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.mcp) === null || _c === void 0 ? void 0 : _c.debugMode) !== null && _d !== void 0 ? _d : false;
11466
+ // Show tool progress with avatar when no text yet, or above text when text exists
11467
+ var showToolProgressWithAvatar = hasToolCalls && !msg.text;
11468
+ return (jsxs(Fragment, { children: [showToolProgressWithAvatar && (jsx(ChatMessagePart, { showAvatar: true, user: user, avatarPosition: (_g = (_f = (_e = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _e === void 0 ? void 0 : _e.theme) === null || _f === void 0 ? void 0 : _f.messages) === null || _g === void 0 ? void 0 : _g.avatarPosition, hideUserInfo: hideUserInfo, isStreaming: true, children: jsx("div", { className: "chat-msg", children: jsx(ToolCallDisplay, { toolCalls: toolCalls, debugMode: debugMode }) }) })), msg.text && (jsx(ChatMessagePart, { showAvatar: avaKey === "text", user: user, avatarPosition: (_k = (_j = (_h = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _h === void 0 ? void 0 : _h.theme) === null || _j === void 0 ? void 0 : _j.messages) === null || _k === void 0 ? void 0 : _k.avatarPosition, hideUserInfo: hideUserInfo, isStreaming: isStreaming_1 || wasStreamed, children: jsx(ChatTextMessage, { message: props.message, sibling: props.sibling, user: user, toolCalls: hasToolCalls ? toolCalls : undefined, debugMode: debugMode }) })), msg.html &&
11469
+ jsx(ChatMessagePart, { showAvatar: avaKey === "html", user: user, avatarPosition: (_o = (_m = (_l = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _l === void 0 ? void 0 : _l.theme) === null || _m === void 0 ? void 0 : _m.messages) === null || _o === void 0 ? void 0 : _o.avatarPosition, hideUserInfo: hideUserInfo, children: jsx(ChatMarkdownMessage, { message: props.message, sibling: props.sibling, onOpenUrl: (_p = props.middlewareContext) === null || _p === void 0 ? void 0 : _p.openUrl }) }), msg.displays && middleware && msg.displays.map(function (display, index) {
9580
11470
  if (display.type === "ScheduleButton") {
9581
11471
  return (jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }));
9582
11472
  }
9583
11473
  var Middleware = middleware;
9584
11474
  return (jsx(Middleware, { msg: display, ctx: props.middlewareContext }, index));
9585
11475
  }), msg.permissionRequest && ctx &&
9586
- jsx(ChatMessagePart, { showAvatar: avaKey === "permissionRequest", user: user, avatarPosition: (_k = (_j = (_h = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _h === void 0 ? void 0 : _h.theme) === null || _j === void 0 ? void 0 : _j.messages) === null || _k === void 0 ? void 0 : _k.avatarPosition, hideUserInfo: hideUserInfo, children: jsx(ChatPermissionMessage, { message: props.message, sibling: props.sibling, ctx: ctx }) })] }));
11476
+ jsx(ChatMessagePart, { showAvatar: avaKey === "permissionRequest", user: user, avatarPosition: (_s = (_r = (_q = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _q === void 0 ? void 0 : _q.theme) === null || _r === void 0 ? void 0 : _r.messages) === null || _s === void 0 ? void 0 : _s.avatarPosition, hideUserInfo: hideUserInfo, children: jsx(ChatPermissionMessage, { message: props.message, sibling: props.sibling, ctx: ctx }) })] }));
9587
11477
  }
9588
11478
  return (jsx(Fragment, {}));
9589
11479
  }
9590
- function renderTimestamp() {
11480
+ function renderTimestampAndSources() {
11481
+ var _a;
9591
11482
  var timestamp = props.message.timestamp;
9592
11483
  var ts = new Date(timestamp);
9593
11484
  var timeAgo = getTimeAgo(ts);
9594
- return (jsx("div", { className: "chat-msg-timestamp ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor"), children: jsx("span", { children: timeAgo }) }));
9595
- }
11485
+ var sources = (_a = props.message.msg) === null || _a === void 0 ? void 0 : _a.sources;
11486
+ var hasSources = sources && sources.length > 0;
11487
+ var isAgentMessage = isAgent(props.message.user.nick);
11488
+ return (jsxs("div", { className: "chat-msg-footer ".concat(isAgentMessage ? "agent" : "visitor"), children: [jsx("span", { className: "chat-msg-timestamp", children: timeAgo }), hasSources && (jsx(SourceLinks, { sources: sources.map(function (s) { return ({ title: s.title, url: s.url, newTab: s.newTab }); }) }))] }));
11489
+ }
11490
+ // Don't show timestamp/sources while streaming or if message has no content yet
11491
+ var isStreaming = props.message.isStreaming;
11492
+ var hasContent = !!(((_b = props.message.msg) === null || _b === void 0 ? void 0 : _b.text) || ((_c = props.message.msg) === null || _c === void 0 ? void 0 : _c.html));
11493
+ var showFooter = !isStreaming && hasContent;
9596
11494
  // empty
9597
- return (jsx("div", { className: "chat-msg-container-wrapper ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor", " ").concat(props.sibling ? "sibling" : ""), children: jsxs("div", { className: "chat-msg-container ".concat(getClassName(props.message)), children: [renderByType(), renderTimestamp()] }) }));
11495
+ return (jsx("div", { className: "chat-msg-container-wrapper ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor", " ").concat(props.sibling ? "sibling" : ""), children: jsxs("div", { className: "chat-msg-container ".concat(getClassName(props.message)), children: [renderByType(), showFooter && renderTimestampAndSources()] }) }));
9598
11496
  };
9599
11497
 
9600
11498
  /**
@@ -33546,7 +35444,7 @@ var ChatWidgetConnected = function (props) {
33546
35444
  * Exported for use by StaticChatWidgetContainer to avoid infinite loops.
33547
35445
  */
33548
35446
  var ChatWidgetUI = function (props) {
33549
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9;
35447
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14;
33550
35448
  var innerDispatch = useChatDispatch();
33551
35449
  var dispatch = useChatServerDispatch();
33552
35450
  // From Redux
@@ -33576,12 +35474,12 @@ var ChatWidgetUI = function (props) {
33576
35474
  chatState.visuals = {};
33577
35475
  }
33578
35476
  // Our state - pull from storage
33579
- var _10 = useState((!canMinimize && !canCancel) ||
35477
+ var _15 = useState((!canMinimize && !canCancel) ||
33580
35478
  // !!get("visible") ||
33581
35479
  chatState.visuals.visible ||
33582
35480
  (((_m = props.config) === null || _m === void 0 ? void 0 : _m.autoOpenOnWidth) &&
33583
- window.matchMedia("(min-width: ".concat((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth, ")")).matches)), visible = _10[0], setVisibleState = _10[1];
33584
- var _11 = useState(false); _11[0]; var setTypingState = _11[1]; // false initially - state kept for potential external observers
35481
+ window.matchMedia("(min-width: ".concat((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth, ")")).matches)), visible = _15[0], setVisibleState = _15[1];
35482
+ var _16 = useState(false); _16[0]; var setTypingState = _16[1]; // false initially - state kept for potential external observers
33585
35483
  // Ref to track typing state for use in timeout callbacks
33586
35484
  var typingRef = useRef(false);
33587
35485
  // Timeout ref for debouncing "stop typing" events
@@ -33627,7 +35525,7 @@ var ChatWidgetUI = function (props) {
33627
35525
  };
33628
35526
  // eslint-disable-next-line react-hooks/exhaustive-deps
33629
35527
  }, []);
33630
- var _12 = useState(!document.hidden), isTabVisible = _12[0], setIsTabVisible = _12[1];
35528
+ var _17 = useState(!document.hidden), isTabVisible = _17[0], setIsTabVisible = _17[1];
33631
35529
  useEffect(function () {
33632
35530
  var handleVisibilityChange = function () {
33633
35531
  setIsTabVisible(!document.hidden);
@@ -33839,8 +35737,8 @@ var ChatWidgetUI = function (props) {
33839
35737
  }));
33840
35738
  }
33841
35739
  // Action Bar state and handlers
33842
- var _13 = useState(false), formModalOpen = _13[0], setFormModalOpen = _13[1];
33843
- var _14 = useState(null), activeFormConfig = _14[0], setActiveFormConfig = _14[1];
35740
+ var _18 = useState(false), formModalOpen = _18[0], setFormModalOpen = _18[1];
35741
+ var _19 = useState(null), activeFormConfig = _19[0], setActiveFormConfig = _19[1];
33844
35742
  var handleFormButtonClick = useCallback(function (formButton) {
33845
35743
  // If form-widget is configured, use its openForm API
33846
35744
  // The form-widget script should already be pre-loaded
@@ -33954,7 +35852,9 @@ var ChatWidgetUI = function (props) {
33954
35852
  avatarPath: config.avatarUrl,
33955
35853
  display_name: "Agent",
33956
35854
  };
33957
- return (jsxs(Fragment, { children: [jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarEnabled ? " widget-container--with-action-bar" : ""), children: [jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), jsx(ChatHeader, { accountStatus: chatState.accountStatus, refreshOnClick: handleRestartClick, minimizeOnClick: handleMinimizeClick, cancelOnClick: handleCancelClick, agent: widgetAgent, canRefresh: canRefresh, canMinimize: canMinimize, canCancel: canCancel, config: config === null || config === void 0 ? void 0 : config.header, menuConfig: config.menu, onSubmit: handleOnSubmit }), jsx(MessageList, { visible: visible, queuePosition: chatState.queuePosition, isChatting: chatState.isChatting, isOffline: isOffline, messages: messages, agents: chatState.agents, agent: config === null || config === void 0 ? void 0 : config.agent, lastRatingRequestTimestamp: chatState.lastRatingRequestTimestamp, hasRating: chatState.hasRating, visitorId: chatState.visitorId, hasWsButton: !!chatState.wsButton, messageMiddleware: props.messageMiddleware, textTypingStatusEnabled: (_x = (_w = props.config) === null || _w === void 0 ? void 0 : _w.typingStatus) === null || _x === void 0 ? void 0 : _x.textTypingStatusEnabled, disableAutoScroll: props.disableAutoScroll, onSend: handleSendMessage, onWrite: handleWriteMessage, onOpenUrl: handleOpenUrl, minimizeOnClick: handleMinimizeClick }), jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && jsx(ServerOffline, {}), chatState.wsButton && visible && (jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), jsx(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_y = config === null || config === void 0 ? void 0 : config.input) === null || _y === void 0 ? void 0 : _y.placeholder, sendButtonIcon: (_0 = (_z = config === null || config === void 0 ? void 0 : config.footer) === null || _z === void 0 ? void 0 : _z.sendButton) === null || _0 === void 0 ? void 0 : _0.icon, sendButtonIconHover: (_2 = (_1 = config === null || config === void 0 ? void 0 : config.footer) === null || _1 === void 0 ? void 0 : _1.sendButton) === null || _2 === void 0 ? void 0 : _2.iconHover, sendButtonIconDisabled: (_4 = (_3 = config === null || config === void 0 ? void 0 : config.footer) === null || _3 === void 0 ? void 0 : _3.sendButton) === null || _4 === void 0 ? void 0 : _4.iconDisabled, visible: visible, hasWsButton: !!chatState.wsButton, menuConfig: (_5 = props.config) === null || _5 === void 0 ? void 0 : _5.menu, footerConfig: (_6 = props.config) === null || _6 === void 0 ? void 0 : _6.footer, inputConfig: (_7 = props.config) === null || _7 === void 0 ? void 0 : _7.input, disabled: previewMode, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }), jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarEnabled && config.actionBar ? (jsxs(Fragment, { children: [jsx(ActionBar, { config: config.actionBar, visible: visible, chatDisabled: config.disabled, onChatClick: chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick }), formModalOpen && activeFormConfig && (jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (jsx(ChatButton, { addClass: visible ? "visible" : "", onClick: chatButtonOnClick, config: config === null || config === void 0 ? void 0 : config.cta, imageUrl: (_8 = config === null || config === void 0 ? void 0 : config.chatButton) === null || _8 === void 0 ? void 0 : _8.imageUrl, visible: visible, hasInteracted: (_9 = chatState.visuals) === null || _9 === void 0 ? void 0 : _9.hasInteracted, onCtaDismiss: handleCtaDismiss })), jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
35855
+ return (jsxs(Fragment, { children: [jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarEnabled ? " widget-container--with-action-bar" : ""), children: [jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), jsx(ChatHeader, { accountStatus: chatState.accountStatus, refreshOnClick: handleRestartClick, minimizeOnClick: handleMinimizeClick, cancelOnClick: handleCancelClick, agent: widgetAgent, canRefresh: canRefresh, canMinimize: canMinimize, canCancel: canCancel, config: config === null || config === void 0 ? void 0 : config.header, menuConfig: config.menu, debugMode: (_y = (_x = (_w = props.config) === null || _w === void 0 ? void 0 : _w.connection) === null || _x === void 0 ? void 0 : _x.mcp) === null || _y === void 0 ? void 0 : _y.debugMode, onSubmit: handleOnSubmit }), jsx(MessageList, { visible: visible, queuePosition: chatState.queuePosition, isChatting: chatState.isChatting, isOffline: isOffline, messages: messages, agents: chatState.agents, agent: config === null || config === void 0 ? void 0 : config.agent, lastRatingRequestTimestamp: chatState.lastRatingRequestTimestamp, hasRating: chatState.hasRating, visitorId: chatState.visitorId, hasWsButton: !!chatState.wsButton, messageMiddleware: props.messageMiddleware, textTypingStatusEnabled: (_0 = (_z = props.config) === null || _z === void 0 ? void 0 : _z.typingStatus) === null || _0 === void 0 ? void 0 : _0.textTypingStatusEnabled, disableAutoScroll: props.disableAutoScroll, onSend: handleSendMessage, onWrite: handleWriteMessage, onOpenUrl: handleOpenUrl, minimizeOnClick: handleMinimizeClick }), jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && jsx(ServerOffline, {}), chatState.wsButton && visible && (jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), jsx(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_1 = config === null || config === void 0 ? void 0 : config.input) === null || _1 === void 0 ? void 0 : _1.placeholder, sendButtonIcon: (_3 = (_2 = config === null || config === void 0 ? void 0 : config.footer) === null || _2 === void 0 ? void 0 : _2.sendButton) === null || _3 === void 0 ? void 0 : _3.icon, sendButtonIconHover: (_5 = (_4 = config === null || config === void 0 ? void 0 : config.footer) === null || _4 === void 0 ? void 0 : _4.sendButton) === null || _5 === void 0 ? void 0 : _5.iconHover, sendButtonIconDisabled: (_7 = (_6 = config === null || config === void 0 ? void 0 : config.footer) === null || _6 === void 0 ? void 0 : _6.sendButton) === null || _7 === void 0 ? void 0 : _7.iconDisabled, visible: visible, hasWsButton: !!chatState.wsButton, menuConfig: (_8 = props.config) === null || _8 === void 0 ? void 0 : _8.menu, footerConfig: (_9 = props.config) === null || _9 === void 0 ? void 0 : _9.footer, inputConfig: (_10 = props.config) === null || _10 === void 0 ? void 0 : _10.input, disabled: previewMode, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }), jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarEnabled && config.actionBar ? (jsxs(Fragment, { children: [jsx(ActionBar, { config: __assign(__assign({}, config.actionBar), {
35856
+ // Use actionBar.cta if set, otherwise fall back to widget-level cta
35857
+ cta: (_11 = config.actionBar.cta) !== null && _11 !== void 0 ? _11 : config.cta }), visible: visible, chatDisabled: config.disabled, hasUserInteracted: (_12 = chatState.visuals) === null || _12 === void 0 ? void 0 : _12.hasInteracted, onChatClick: chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick, onCtaDismiss: handleCtaDismiss }), formModalOpen && activeFormConfig && (jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (jsx(ChatButton, { addClass: visible ? "visible" : "", onClick: chatButtonOnClick, config: config === null || config === void 0 ? void 0 : config.cta, imageUrl: (_13 = config === null || config === void 0 ? void 0 : config.chatButton) === null || _13 === void 0 ? void 0 : _13.imageUrl, visible: visible, hasInteracted: (_14 = chatState.visuals) === null || _14 === void 0 ? void 0 : _14.hasInteracted, onCtaDismiss: handleCtaDismiss })), jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
33958
35858
  };
33959
35859
  /**
33960
35860
  * Top-level wrapper that dispatches between preview mode and connected mode.
@@ -34136,6 +36036,7 @@ function memberLeave(state, detail) {
34136
36036
  }
34137
36037
 
34138
36038
  function resetReducer(state) {
36039
+ var _a;
34139
36040
  if (state === void 0) { state = DEFAULT_STATE; }
34140
36041
  var defaultState = createDefaultState({
34141
36042
  accessToken: state.accessToken,
@@ -34145,6 +36046,8 @@ function resetReducer(state) {
34145
36046
  });
34146
36047
  // If we have an active connection, preserve status to avoid showing offline during reconnect
34147
36048
  var wasOnline = state.connection.connectionStatus === 'online';
36049
+ // Preserve opened state so widget doesn't close on refresh
36050
+ var wasOpened = (_a = state.visuals) === null || _a === void 0 ? void 0 : _a.opened;
34148
36051
  return __assign(__assign({}, defaultState), { connection: __assign(__assign({}, defaultState.connection), { greetingRequested: false,
34149
36052
  // Generate new nonce to trigger server recreation and start fresh session
34150
36053
  nonce: uuid_1(),
@@ -34152,8 +36055,11 @@ function resetReducer(state) {
34152
36055
  connectionStatus: wasOnline ? 'online' : defaultState.connection.connectionStatus }),
34153
36056
  // Preserve accountStatus if we were online to avoid showing offline message during reconnect
34154
36057
  accountStatus: wasOnline ? 'online' : defaultState.accountStatus, visitor: state.visitor, visitorId: state.visitorId,
34155
- // Explicitly reset visuals to clear hasInteracted flag for widget refresh
34156
- visuals: {} });
36058
+ // Reset visuals but preserve opened state so widget stays open during refresh
36059
+ visuals: {
36060
+ opened: wasOpened,
36061
+ hasInteracted: false
36062
+ } });
34157
36063
  }
34158
36064
 
34159
36065
  // Type guard for ChatSystemMessageDetail
@@ -34169,7 +36075,7 @@ function appendMessageToState(state, msg) {
34169
36075
  }
34170
36076
  function update(state, action) {
34171
36077
  var _a, _b, _c, _d;
34172
- var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
36078
+ var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
34173
36079
  if (state === void 0) { state = DEFAULT_STATE; }
34174
36080
  log("action", action);
34175
36081
  if (action.type === "reset") {
@@ -34277,6 +36183,121 @@ function update(state, action) {
34277
36183
  case "chat.typing":
34278
36184
  var agent = state.agents[action.detail.user.nick];
34279
36185
  return __assign(__assign({}, state), { lastTimestamp: (_v = action.detail) === null || _v === void 0 ? void 0 : _v.timestamp, agents: __assign(__assign({}, state.agents), (_d = {}, _d[action.detail.user.nick] = __assign(__assign({}, agent), { typing: action.detail.typing }), _d)) });
36186
+ case "chat.stream.start":
36187
+ // Add placeholder message with streaming state
36188
+ var streamingMsg = {
36189
+ type: "chat.msg",
36190
+ user: action.detail.user,
36191
+ timestamp: action.detail.timestamp,
36192
+ msg: {
36193
+ text: "",
36194
+ },
36195
+ isStreaming: true,
36196
+ };
36197
+ return __assign(__assign({}, newState), { chats: __spreadArray$1(__spreadArray$1([], newState.chats, true), [streamingMsg], false), streamingMessageId: action.detail.messageId });
36198
+ case "chat.stream.chunk": {
36199
+ // Find the streaming message by ID and append text
36200
+ var chunkDetail = action.detail;
36201
+ if (state.streamingMessageId === chunkDetail.messageId) {
36202
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36203
+ var streamingIndex_1 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36204
+ if (streamingIndex_1 >= 0) {
36205
+ var chunkText_1 = chunkDetail.text;
36206
+ return __assign(__assign({}, state), { lastTimestamp: chunkDetail === null || chunkDetail === void 0 ? void 0 : chunkDetail.timestamp, chats: state.chats.map(function (chat, index) {
36207
+ var _a;
36208
+ if (index === streamingIndex_1 && isChatMsgDetail(chat)) {
36209
+ return __assign(__assign({}, chat), { msg: __assign(__assign({}, chat.msg), { text: (((_a = chat.msg) === null || _a === void 0 ? void 0 : _a.text) || "") + chunkText_1 }) });
36210
+ }
36211
+ return chat;
36212
+ }) });
36213
+ }
36214
+ }
36215
+ return state;
36216
+ }
36217
+ case "chat.stream.tool_start": {
36218
+ // Add tool call to the streaming message (in-progress state)
36219
+ log("Tool execution started: ".concat(action.detail.toolName));
36220
+ var toolStartDetail_1 = action.detail;
36221
+ if (state.streamingMessageId === toolStartDetail_1.messageId) {
36222
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36223
+ var streamingIndex_2 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36224
+ if (streamingIndex_2 >= 0) {
36225
+ return __assign(__assign({}, state), { lastTimestamp: toolStartDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
36226
+ if (index === streamingIndex_2 && isChatMsgDetail(chat)) {
36227
+ var existingToolCalls = chat.toolCalls || [];
36228
+ return __assign(__assign({}, chat), { toolCalls: __spreadArray$1(__spreadArray$1([], existingToolCalls, true), [
36229
+ {
36230
+ name: toolStartDetail_1.toolName,
36231
+ toolCallId: toolStartDetail_1.toolCallId,
36232
+ displayName: toolStartDetail_1.displayName,
36233
+ label: toolStartDetail_1.label,
36234
+ hidden: toolStartDetail_1.hidden,
36235
+ input: toolStartDetail_1.toolInput,
36236
+ startTime: toolStartDetail_1.timestamp,
36237
+ isExecuting: true,
36238
+ },
36239
+ ], false) });
36240
+ }
36241
+ return chat;
36242
+ }) });
36243
+ }
36244
+ }
36245
+ return state;
36246
+ }
36247
+ case "chat.stream.tool_end": {
36248
+ // Update tool call with result in the streaming message
36249
+ log("Tool execution ended: ".concat(action.detail.toolName));
36250
+ var toolEndDetail_1 = action.detail;
36251
+ if (state.streamingMessageId === toolEndDetail_1.messageId) {
36252
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36253
+ var streamingIndex_3 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36254
+ if (streamingIndex_3 >= 0) {
36255
+ return __assign(__assign({}, state), { lastTimestamp: toolEndDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
36256
+ var _a;
36257
+ if (index === streamingIndex_3 && isChatMsgDetail(chat)) {
36258
+ var toolCalls = (_a = chat.toolCalls) === null || _a === void 0 ? void 0 : _a.map(function (tc) {
36259
+ var _a;
36260
+ // Match by toolCallId if available, otherwise fall back to name + isExecuting
36261
+ var isMatch = toolEndDetail_1.toolCallId
36262
+ ? tc.toolCallId === toolEndDetail_1.toolCallId
36263
+ : tc.name === toolEndDetail_1.toolName && tc.isExecuting;
36264
+ if (isMatch) {
36265
+ return __assign(__assign({}, tc), { displayName: toolEndDetail_1.displayName || tc.displayName, label: toolEndDetail_1.label, hidden: (_a = toolEndDetail_1.hidden) !== null && _a !== void 0 ? _a : tc.hidden, result: toolEndDetail_1.toolResult, error: toolEndDetail_1.toolError, endTime: toolEndDetail_1.timestamp, isExecuting: false });
36266
+ }
36267
+ return tc;
36268
+ });
36269
+ return __assign(__assign({}, chat), { toolCalls: toolCalls });
36270
+ }
36271
+ return chat;
36272
+ }) });
36273
+ }
36274
+ }
36275
+ return state;
36276
+ }
36277
+ case "chat.stream.end": {
36278
+ // Finalize the streaming message
36279
+ var endDetail = action.detail;
36280
+ if (state.streamingMessageId === endDetail.messageId) {
36281
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36282
+ var streamingIndex_4 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36283
+ var endStreamingChat = streamingIndex_4 >= 0 ? state.chats[streamingIndex_4] : undefined;
36284
+ // Verify it's the right message using type guard
36285
+ if (streamingIndex_4 >= 0 && endStreamingChat && isChatMsgDetail(endStreamingChat)) {
36286
+ var finalMsg_1 = endDetail.msg;
36287
+ // Preserve tool calls from either the action or the existing message
36288
+ var toolCalls_1 = endDetail.toolCalls || endStreamingChat.toolCalls;
36289
+ // Update chips from message options (same as chat.msg handler)
36290
+ var chips = ((_w = finalMsg_1 === null || finalMsg_1 === void 0 ? void 0 : finalMsg_1.options) === null || _w === void 0 ? void 0 : _w.length) ? finalMsg_1.options : [];
36291
+ return __assign(__assign({}, state), { lastTimestamp: endDetail === null || endDetail === void 0 ? void 0 : endDetail.timestamp, streamingMessageId: undefined, chips: chips, chats: state.chats.map(function (chat, index) {
36292
+ if (index === streamingIndex_4 && isChatMsgDetail(chat)) {
36293
+ return __assign(__assign({}, chat), { msg: finalMsg_1, isStreaming: false, wasStreamed: true, toolCalls: toolCalls_1 });
36294
+ }
36295
+ return chat;
36296
+ }) });
36297
+ }
36298
+ }
36299
+ return state;
36300
+ }
34280
36301
  default:
34281
36302
  return state;
34282
36303
  }