@xapp/chat-widget 1.86.0 → 1.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,7 +14,6 @@ var lib = {};
14
14
 
15
15
  var ActionBar$1 = {};
16
16
 
17
- /*! Copyright (c) 2021, XAPPmedia */
18
17
  Object.defineProperty(ActionBar$1, "__esModule", { value: true });
19
18
  ActionBar$1.isActionBarChatButton = isActionBarChatButton;
20
19
  ActionBar$1.isActionBarFormButton = isActionBarFormButton;
@@ -305,12 +304,99 @@ var ActionBarButton = function (_a) {
305
304
  return (require$$0.jsxs("button", { className: buttonClassName, onClick: handleClick, "aria-label": ariaLabel, "aria-pressed": isActive, tabIndex: tabIndex, children: [require$$0.jsx(ActionBarIcon, { type: button.type, icon: button.icon, iconUrl: button.iconUrl, className: "xapp-action-bar__icon" }), button.label && (require$$0.jsx("span", { className: "xapp-action-bar__label", children: button.label }))] }));
306
305
  };
307
306
 
307
+ /**
308
+ * CTA banner for ActionBar - a full-width rectangular banner that appears
309
+ * above the action bar to prompt users to engage with chat.
310
+ */
311
+ var ActionBarCta = function (_a) {
312
+ var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
313
+ var config = _a.config, isMobile = _a.isMobile, onClick = _a.onClick, onDismiss = _a.onDismiss, hasUserInteracted = _a.hasUserInteracted;
314
+ var _m = require$$1.useState(false), isVisible = _m[0], setIsVisible = _m[1];
315
+ var _o = require$$1.useState(false), isAnimating = _o[0], setIsAnimating = _o[1];
316
+ var _p = require$$1.useState(false), isDismissed = _p[0], setIsDismissed = _p[1];
317
+ var message = isMobile && ((_b = config.mobile) === null || _b === void 0 ? void 0 : _b.message) ? config.mobile.message : config.message;
318
+ 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);
319
+ var timeout = isMobile && ((_e = config.mobile) === null || _e === void 0 ? void 0 : _e.timeout) !== undefined ? config.mobile.timeout : config.timeout;
320
+ var animation = isMobile && ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.animation) ? config.mobile.animation : config.animation;
321
+ var animationDelay = isMobile && ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.animationDelay) !== undefined
322
+ ? config.mobile.animationDelay
323
+ : ((_h = config.animationDelay) !== null && _h !== void 0 ? _h : delay);
324
+ var animationTimeout = isMobile && ((_j = config.mobile) === null || _j === void 0 ? void 0 : _j.animationTimeout) !== undefined
325
+ ? config.mobile.animationTimeout
326
+ : config.animationTimeout;
327
+ var dismissible = isMobile && ((_k = config.mobile) === null || _k === void 0 ? void 0 : _k.dismissible) !== undefined
328
+ ? config.mobile.dismissible
329
+ : ((_l = config.dismissible) !== null && _l !== void 0 ? _l : true);
330
+ // Show CTA after delay
331
+ require$$1.useEffect(function () {
332
+ if (!message || hasUserInteracted || isDismissed) {
333
+ setIsVisible(false);
334
+ return undefined;
335
+ }
336
+ var showTimer = setTimeout(function () {
337
+ setIsVisible(true);
338
+ }, delay);
339
+ return function () { return clearTimeout(showTimer); };
340
+ }, [message, delay, hasUserInteracted, isDismissed]);
341
+ // Hide CTA after timeout
342
+ require$$1.useEffect(function () {
343
+ if (!isVisible || !timeout) {
344
+ return undefined;
345
+ }
346
+ var hideTimer = setTimeout(function () {
347
+ setIsVisible(false);
348
+ }, timeout);
349
+ return function () { return clearTimeout(hideTimer); };
350
+ }, [isVisible, timeout]);
351
+ // Start animation after animationDelay
352
+ require$$1.useEffect(function () {
353
+ if (!animation || hasUserInteracted || isDismissed) {
354
+ setIsAnimating(false);
355
+ return undefined;
356
+ }
357
+ var animateTimer = setTimeout(function () {
358
+ setIsAnimating(true);
359
+ }, animationDelay);
360
+ return function () { return clearTimeout(animateTimer); };
361
+ }, [animation, animationDelay, hasUserInteracted, isDismissed]);
362
+ // Stop animation after animationTimeout
363
+ require$$1.useEffect(function () {
364
+ if (!isAnimating || !animationTimeout) {
365
+ return undefined;
366
+ }
367
+ var stopTimer = setTimeout(function () {
368
+ setIsAnimating(false);
369
+ }, animationTimeout);
370
+ return function () { return clearTimeout(stopTimer); };
371
+ }, [isAnimating, animationTimeout]);
372
+ var handleClick = function () {
373
+ onClick();
374
+ setIsVisible(false);
375
+ };
376
+ var handleDismiss = function (e) {
377
+ e.stopPropagation();
378
+ setIsDismissed(true);
379
+ setIsVisible(false);
380
+ onDismiss();
381
+ };
382
+ if (!isVisible || !message) {
383
+ return null;
384
+ }
385
+ var animationClass = isAnimating && animation ? "xapp-action-bar-cta--".concat(animation) : "";
386
+ return (require$$0.jsxs("div", { className: "xapp-action-bar-cta ".concat(animationClass), onClick: handleClick, role: "button", tabIndex: 0, "aria-label": message, onKeyDown: function (e) {
387
+ if (e.key === "Enter" || e.key === " ") {
388
+ e.preventDefault();
389
+ handleClick();
390
+ }
391
+ }, children: [require$$0.jsx("span", { className: "xapp-action-bar-cta__message", children: message }), dismissible && (require$$0.jsx("button", { className: "xapp-action-bar-cta__dismiss", onClick: handleDismiss, "aria-label": "Dismiss", type: "button", children: require$$0.jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: require$$0.jsx("path", { d: "M13 1L1 13M1 1L13 13", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }));
392
+ };
393
+
308
394
  var DEFAULT_MOBILE_BREAKPOINT = 768;
309
395
  var RESIZE_DEBOUNCE_MS = 150;
310
396
  var ActionBar = function (_a) {
311
- var _b, _c;
312
- var config = _a.config, visible = _a.visible, chatDisabled = _a.chatDisabled, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick;
313
- var _d = require$$1.useState(false), isMobile = _d[0], setIsMobile = _d[1];
397
+ var _b, _c, _d;
398
+ 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;
399
+ var _f = require$$1.useState(false), isMobile = _f[0], setIsMobile = _f[1];
314
400
  var mobileBreakpoint = (_b = config.mobileBreakpoint) !== null && _b !== void 0 ? _b : DEFAULT_MOBILE_BREAKPOINT;
315
401
  var position = (_c = config.position) !== null && _c !== void 0 ? _c : "right";
316
402
  var resizeTimeoutRef = require$$1.useRef(null);
@@ -346,7 +432,19 @@ var ActionBar = function (_a) {
346
432
  ]
347
433
  .filter(Boolean)
348
434
  .join(" ");
349
- return (require$$0.jsx("div", { className: classNames, role: "toolbar", "aria-label": "Chat actions", children: filteredButtons.map(function (button) { return (require$$0.jsx(ActionBarButton, { button: button, chatActive: visible, onChatClick: onChatClick, onChatMinimize: onChatMinimize, onFormClick: onFormClick, isMobile: isMobile }, button.id)); }) }));
435
+ // Determine if CTA should be shown:
436
+ // - Must have CTA config with a message
437
+ // - Chat must not be visible (user hasn't opened it)
438
+ // - Chat must not be disabled
439
+ var hasChatButton = config.buttons.some(lib.isActionBarChatButton);
440
+ var showCta = ((_d = config.cta) === null || _d === void 0 ? void 0 : _d.message) && !visible && !chatDisabled && hasChatButton;
441
+ var handleCtaClick = function () {
442
+ onChatClick();
443
+ };
444
+ var handleCtaDismiss = function () {
445
+ onCtaDismiss === null || onCtaDismiss === void 0 ? void 0 : onCtaDismiss();
446
+ };
447
+ return (require$$0.jsxs("div", { className: classNames, role: "toolbar", "aria-label": "Chat actions", children: [showCta && (require$$0.jsx("div", { className: "xapp-action-bar-cta-wrapper", children: require$$0.jsx(ActionBarCta, { config: config.cta, isMobile: isMobile, onClick: handleCtaClick, onDismiss: handleCtaDismiss, hasUserInteracted: hasUserInteracted }) })), require$$0.jsx("div", { className: "xapp-action-bar__buttons", children: filteredButtons.map(function (button) { return (require$$0.jsx(ActionBarButton, { button: button, chatActive: visible, onChatClick: onChatClick, onChatMinimize: onChatMinimize, onFormClick: onFormClick, isMobile: isMobile }, button.id)); }) })] }));
350
448
  };
351
449
 
352
450
  /**
@@ -979,14 +1077,16 @@ function getServerConfig(env) {
979
1077
  }
980
1078
  function useServerConfig(env, nonce) {
981
1079
  var connection = getServerConfig(env);
982
- var accountKey = connection.accountKey, serverUrl = connection.serverUrl, type = connection.type, timeout = connection.timeout;
1080
+ var accountKey = connection.accountKey, serverUrl = connection.serverUrl, type = connection.type, timeout = connection.timeout, backgroundSleepDelay = connection.backgroundSleepDelay, mcp = connection.mcp;
983
1081
  return require$$1.useMemo(function () { return ({
984
1082
  accountKey: accountKey,
985
1083
  serverUrl: serverUrl,
986
1084
  timeout: timeout,
987
1085
  type: type,
988
1086
  nonce: nonce,
989
- }); }, [accountKey, serverUrl, timeout, type, nonce]);
1087
+ backgroundSleepDelay: backgroundSleepDelay,
1088
+ mcp: mcp,
1089
+ }); }, [accountKey, serverUrl, timeout, type, nonce, backgroundSleepDelay, mcp]);
990
1090
  }
991
1091
  function useConnectionInfo(env) {
992
1092
  var nonce = reactRedux.useSelector(function (state) { return state.connection.nonce; });
@@ -995,7 +1095,7 @@ function useConnectionInfo(env) {
995
1095
  }
996
1096
 
997
1097
  var Avatar = function (props) {
998
- var _a, _b;
1098
+ var _a, _b, _c, _d, _e;
999
1099
  var style = {};
1000
1100
  var child;
1001
1101
  var entity = props.entity;
@@ -1018,7 +1118,9 @@ var Avatar = function (props) {
1018
1118
  child = getVisitorSvg();
1019
1119
  }
1020
1120
  var hasImage = !!style.backgroundImage || !!child;
1021
- return (require$$0.jsx("div", { className: "avatar ".concat(agentAva ? "avatar--agent" : "avatar--visitor", " ").concat(!hasImage ? "avatar--empty" : ""), style: style, title: entity ? entity.display_name : "Agent", children: child }));
1121
+ // Show AI badge if configured and the entity is an AI
1122
+ 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);
1123
+ return (require$$0.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 && require$$0.jsx("span", { className: "avatar__ai-badge", children: "AI" })] }));
1022
1124
  };
1023
1125
  /**
1024
1126
  * Generates an SVG based on the
@@ -2603,236 +2705,231 @@ var parseSuggestionsResponse_1 = parseSuggestionsResponse;
2603
2705
  var useSuggestionsFetch_1 = useSuggestionsFetch$1;
2604
2706
  var uuid_1 = uuid;
2605
2707
 
2606
- /**
2607
- * Sends a POST to your STENTOR based server.
2608
- *
2609
- * @param data
2610
- * @param url
2611
- * @param key
2612
- * @param signal
2613
- * @returns
2614
- */
2615
- function postMessageToStentor(data, url, key, signal) {
2616
- return __awaiter$1(this, void 0, void 0, function () {
2617
- var body, response;
2618
- return __generator$1(this, function (_a) {
2619
- switch (_a.label) {
2620
- case 0:
2621
- body = JSON.stringify(data);
2622
- log("URL: ".concat(url));
2623
- log("BODY: ".concat(body));
2624
- return [4 /*yield*/, fetch(url, {
2625
- method: "POST",
2626
- headers: {
2627
- "Content-Type": "application/json",
2628
- "Authorization": "Bearer ".concat(key),
2629
- },
2630
- body: body,
2631
- mode: "cors",
2632
- signal: signal
2633
- })];
2634
- case 1:
2635
- response = _a.sent();
2636
- if (!response.ok) {
2637
- throw new Error("Status ".concat(response.status, ", Text: ").concat(response.statusText));
2638
- }
2639
- return [2 /*return*/, response.json()];
2640
- }
2641
- });
2642
- });
2643
- }
2708
+ var cjs = {};
2644
2709
 
2645
- function convertFromListDisplay(list) {
2646
- return {
2647
- type: list.type,
2648
- title: list.title,
2649
- items: list.items.map(function (item) {
2650
- var _a, _b;
2651
- var responseItem = {
2652
- title: item.title,
2653
- subTitle: item.description,
2654
- token: item.token,
2655
- url: item.url,
2656
- hideUrl: item.hideUrl,
2657
- imageUrl: (_a = item.image) === null || _a === void 0 ? void 0 : _a.url,
2658
- imageActionUrl: (_b = item.image) === null || _b === void 0 ? void 0 : _b.imageActionUrl
2659
- };
2660
- // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
2661
- var itemButtons = item.buttons;
2662
- if (itemButtons && itemButtons.length > 0) {
2663
- responseItem.buttons = itemButtons.map(function (button) { return ({
2664
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2665
- actionUrl: button.openUrlAction,
2666
- label: button.title
2667
- }); });
2668
- }
2669
- return responseItem;
2670
- })
2671
- };
2672
- }
2673
- function convertToListDisplay(list) {
2674
- return {
2675
- type: list.type,
2676
- title: list.title,
2677
- items: (list.items || []).map(function (item) {
2678
- var responseItem = {
2679
- title: item.title,
2680
- description: item.subTitle,
2681
- token: item.token,
2682
- url: item.url,
2683
- hideUrl: item.hideUrl,
2684
- image: {
2685
- url: item.imageUrl,
2686
- imageActionUrl: item.imageActionUrl,
2687
- accessibilityText: ""
2688
- }
2689
- };
2690
- // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
2691
- var itemButtons = item.buttons;
2692
- if (itemButtons && itemButtons.length > 0) {
2693
- responseItem.buttons = itemButtons.map(function (button) { return ({
2694
- openUrlAction: button.actionUrl,
2695
- title: button.label
2696
- }); });
2697
- }
2698
- return responseItem;
2699
- })
2700
- };
2701
- }
2702
- function convertFromCardDisplay(card) {
2703
- return {
2704
- content: card.content,
2705
- imageUrl: card.smallImageUrl,
2706
- title: card.title,
2707
- imageActionUrl: card === null || card === void 0 ? void 0 : card.imageActionUrl,
2708
- buttons: card.buttons ? card.buttons.map(function (button) { return ({
2709
- actionUrl: button.openUrlAction,
2710
- label: button.title
2711
- }); }) : undefined
2712
- };
2713
- }
2714
- function getOutput(value) {
2715
- if (value) {
2716
- if (typeof value === "string") {
2717
- return {
2718
- displayText: value
2719
- };
2720
- }
2721
- else {
2722
- return value;
2723
- }
2724
- }
2725
- return undefined;
2726
- }
2727
- /**
2728
- * Converts a Stentor Response to a ChatMessageRequest
2729
- *
2730
- * @param botResponse
2731
- * @param now
2732
- * @returns
2733
- */
2734
- function responseToMessage(botResponse, now) {
2735
- var _a, _b, _c, _d, _e;
2736
- if (now === void 0) { now = new Date().getTime(); }
2737
- var responseMessage;
2738
- if (!botResponse) {
2739
- return responseMessage;
2740
- }
2741
- var text;
2742
- var html;
2743
- var endSession;
2744
- var outputSpeech = getOutput(botResponse.outputSpeech);
2745
- var reprompt = getOutput(botResponse.reprompt);
2746
- if (outputSpeech) {
2747
- text = outputSpeech.displayText;
2748
- html = outputSpeech.html;
2749
- }
2750
- if (botResponse.system === "TRANSFER_CALL") {
2751
- responseMessage = {
2752
- type: "handOff",
2753
- timestamp: now,
2754
- handoffMessage: text,
2755
- handoffTarget: (_a = botResponse === null || botResponse === void 0 ? void 0 : botResponse.data) === null || _a === void 0 ? void 0 : _a.transferPhoneNumber,
2756
- endSession: false,
2757
- user: undefined
2758
- };
2759
- }
2760
- else if ((_b = botResponse.system) === null || _b === void 0 ? void 0 : _b.startsWith("PERMISSION_")) {
2761
- responseMessage = getPermissionResponse(botResponse, now);
2762
- }
2763
- else {
2764
- endSession = !reprompt || !(reprompt.displayText || reprompt.ssml);
2765
- responseMessage = {
2766
- type: "msg",
2767
- timestamp: now,
2768
- msg: {
2769
- displays: botResponse.displays,
2770
- 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; })
2771
- },
2772
- endSession: endSession,
2773
- user: undefined
2774
- };
2775
- if (text && !html) {
2776
- responseMessage.msg = __assign(__assign({}, responseMessage.msg), { text: text });
2777
- }
2778
- if (html) {
2779
- responseMessage.msg = __assign(__assign({}, responseMessage.msg), { html: html });
2780
- }
2781
- if ((_e = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.suggestions) === null || _e === void 0 ? void 0 : _e.length) {
2782
- responseMessage.msg.options = outputSpeech.suggestions.map(function (suggestion) {
2783
- if (typeof suggestion === "string") {
2784
- // Simple chips (strings)
2785
- return suggestion;
2786
- }
2787
- else {
2788
- // "call out" chips
2789
- return {
2790
- label: suggestion.title,
2791
- actionUrl: suggestion.url
2792
- };
2793
- }
2794
- });
2795
- }
2796
- }
2797
- return responseMessage;
2798
- }
2799
- function getPermissionRequestType(type) {
2800
- switch (type) {
2801
- case "PERMISSION_EMAIL":
2802
- return "EMAIL";
2803
- case "PERMISSION_LOCATION_PRECISE":
2804
- return "LOCATION_PRECISE";
2805
- default:
2806
- throw new Error("Unsupported permission: ".concat(type));
2807
- }
2808
- }
2809
- function getPermissionResponse(botResponse, now) {
2810
- var _a;
2811
- var type = getPermissionRequestType(botResponse.system);
2812
- var outputSpeech = getOutput(botResponse.outputSpeech);
2813
- var permissionPrimerAccepted = botResponse.data.permissionPrimerAccepted;
2814
- var permissionDenied = botResponse.data.permissionDenied;
2815
- return {
2816
- type: "permissionRequest",
2817
- timestamp: now,
2818
- msg: {
2819
- text: (_a = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.displayText) !== null && _a !== void 0 ? _a : "".concat(botResponse.data.permissionRequestTTSContext),
2820
- permissionRequest: {
2821
- time: now,
2822
- type: type,
2823
- approve: typeof permissionPrimerAccepted === "object" ? responseToMessage({
2824
- outputSpeech: permissionPrimerAccepted
2825
- }).msg : undefined,
2826
- deny: typeof permissionDenied === "object" ? responseToMessage({
2827
- outputSpeech: permissionDenied
2828
- }).msg : undefined
2829
- }
2830
- },
2831
- endSession: false,
2832
- user: undefined //todo: set
2833
- };
2710
+ var fetch$1 = {};
2711
+
2712
+ var parse$3 = {};
2713
+
2714
+ Object.defineProperty(parse$3, "__esModule", { value: true });
2715
+ parse$3.getMessages = parse$3.getLines = parse$3.getBytes = void 0;
2716
+ async function getBytes(stream, onChunk) {
2717
+ const reader = stream.getReader();
2718
+ let result;
2719
+ while (!(result = await reader.read()).done) {
2720
+ onChunk(result.value);
2721
+ }
2722
+ }
2723
+ parse$3.getBytes = getBytes;
2724
+ function getLines(onLine) {
2725
+ let buffer;
2726
+ let position;
2727
+ let fieldLength;
2728
+ let discardTrailingNewline = false;
2729
+ return function onChunk(arr) {
2730
+ if (buffer === undefined) {
2731
+ buffer = arr;
2732
+ position = 0;
2733
+ fieldLength = -1;
2734
+ }
2735
+ else {
2736
+ buffer = concat(buffer, arr);
2737
+ }
2738
+ const bufLength = buffer.length;
2739
+ let lineStart = 0;
2740
+ while (position < bufLength) {
2741
+ if (discardTrailingNewline) {
2742
+ if (buffer[position] === 10) {
2743
+ lineStart = ++position;
2744
+ }
2745
+ discardTrailingNewline = false;
2746
+ }
2747
+ let lineEnd = -1;
2748
+ for (; position < bufLength && lineEnd === -1; ++position) {
2749
+ switch (buffer[position]) {
2750
+ case 58:
2751
+ if (fieldLength === -1) {
2752
+ fieldLength = position - lineStart;
2753
+ }
2754
+ break;
2755
+ case 13:
2756
+ discardTrailingNewline = true;
2757
+ case 10:
2758
+ lineEnd = position;
2759
+ break;
2760
+ }
2761
+ }
2762
+ if (lineEnd === -1) {
2763
+ break;
2764
+ }
2765
+ onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
2766
+ lineStart = position;
2767
+ fieldLength = -1;
2768
+ }
2769
+ if (lineStart === bufLength) {
2770
+ buffer = undefined;
2771
+ }
2772
+ else if (lineStart !== 0) {
2773
+ buffer = buffer.subarray(lineStart);
2774
+ position -= lineStart;
2775
+ }
2776
+ };
2777
+ }
2778
+ parse$3.getLines = getLines;
2779
+ function getMessages(onId, onRetry, onMessage) {
2780
+ let message = newMessage();
2781
+ const decoder = new TextDecoder();
2782
+ return function onLine(line, fieldLength) {
2783
+ if (line.length === 0) {
2784
+ onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
2785
+ message = newMessage();
2786
+ }
2787
+ else if (fieldLength > 0) {
2788
+ const field = decoder.decode(line.subarray(0, fieldLength));
2789
+ const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
2790
+ const value = decoder.decode(line.subarray(valueOffset));
2791
+ switch (field) {
2792
+ case 'data':
2793
+ message.data = message.data
2794
+ ? message.data + '\n' + value
2795
+ : value;
2796
+ break;
2797
+ case 'event':
2798
+ message.event = value;
2799
+ break;
2800
+ case 'id':
2801
+ onId(message.id = value);
2802
+ break;
2803
+ case 'retry':
2804
+ const retry = parseInt(value, 10);
2805
+ if (!isNaN(retry)) {
2806
+ onRetry(message.retry = retry);
2807
+ }
2808
+ break;
2809
+ }
2810
+ }
2811
+ };
2812
+ }
2813
+ parse$3.getMessages = getMessages;
2814
+ function concat(a, b) {
2815
+ const res = new Uint8Array(a.length + b.length);
2816
+ res.set(a);
2817
+ res.set(b, a.length);
2818
+ return res;
2819
+ }
2820
+ function newMessage() {
2821
+ return {
2822
+ data: '',
2823
+ event: '',
2824
+ id: '',
2825
+ retry: undefined,
2826
+ };
2834
2827
  }
2835
2828
 
2829
+ (function (exports$1) {
2830
+ var __rest = (commonjsGlobal && commonjsGlobal.__rest) || function (s, e) {
2831
+ var t = {};
2832
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
2833
+ t[p] = s[p];
2834
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
2835
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
2836
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
2837
+ t[p[i]] = s[p[i]];
2838
+ }
2839
+ return t;
2840
+ };
2841
+ Object.defineProperty(exports$1, "__esModule", { value: true });
2842
+ exports$1.fetchEventSource = exports$1.EventStreamContentType = void 0;
2843
+ const parse_1 = parse$3;
2844
+ exports$1.EventStreamContentType = 'text/event-stream';
2845
+ const DefaultRetryInterval = 1000;
2846
+ const LastEventId = 'last-event-id';
2847
+ function fetchEventSource(input, _a) {
2848
+ 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"]);
2849
+ return new Promise((resolve, reject) => {
2850
+ const headers = Object.assign({}, inputHeaders);
2851
+ if (!headers.accept) {
2852
+ headers.accept = exports$1.EventStreamContentType;
2853
+ }
2854
+ let curRequestController;
2855
+ function onVisibilityChange() {
2856
+ curRequestController.abort();
2857
+ if (!document.hidden) {
2858
+ create();
2859
+ }
2860
+ }
2861
+ if (!openWhenHidden) {
2862
+ document.addEventListener('visibilitychange', onVisibilityChange);
2863
+ }
2864
+ let retryInterval = DefaultRetryInterval;
2865
+ let retryTimer = 0;
2866
+ function dispose() {
2867
+ document.removeEventListener('visibilitychange', onVisibilityChange);
2868
+ window.clearTimeout(retryTimer);
2869
+ curRequestController.abort();
2870
+ }
2871
+ inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener('abort', () => {
2872
+ dispose();
2873
+ resolve();
2874
+ });
2875
+ const fetch = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
2876
+ const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
2877
+ async function create() {
2878
+ var _a;
2879
+ curRequestController = new AbortController();
2880
+ try {
2881
+ const response = await fetch(input, Object.assign(Object.assign({}, rest), { headers, signal: curRequestController.signal }));
2882
+ await onopen(response);
2883
+ await parse_1.getBytes(response.body, parse_1.getLines(parse_1.getMessages(id => {
2884
+ if (id) {
2885
+ headers[LastEventId] = id;
2886
+ }
2887
+ else {
2888
+ delete headers[LastEventId];
2889
+ }
2890
+ }, retry => {
2891
+ retryInterval = retry;
2892
+ }, onmessage)));
2893
+ onclose === null || onclose === void 0 ? void 0 : onclose();
2894
+ dispose();
2895
+ resolve();
2896
+ }
2897
+ catch (err) {
2898
+ if (!curRequestController.signal.aborted) {
2899
+ try {
2900
+ const interval = (_a = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a !== void 0 ? _a : retryInterval;
2901
+ window.clearTimeout(retryTimer);
2902
+ retryTimer = window.setTimeout(create, interval);
2903
+ }
2904
+ catch (innerErr) {
2905
+ dispose();
2906
+ reject(innerErr);
2907
+ }
2908
+ }
2909
+ }
2910
+ }
2911
+ create();
2912
+ });
2913
+ }
2914
+ exports$1.fetchEventSource = fetchEventSource;
2915
+ function defaultOnOpen(response) {
2916
+ const contentType = response.headers.get('content-type');
2917
+ if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(exports$1.EventStreamContentType))) {
2918
+ throw new Error(`Expected content-type to be ${exports$1.EventStreamContentType}, Actual: ${contentType}`);
2919
+ }
2920
+ }
2921
+
2922
+ } (fetch$1));
2923
+
2924
+ (function (exports$1) {
2925
+ Object.defineProperty(exports$1, "__esModule", { value: true });
2926
+ exports$1.EventStreamContentType = exports$1.fetchEventSource = void 0;
2927
+ var fetch_1 = fetch$1;
2928
+ Object.defineProperty(exports$1, "fetchEventSource", { enumerable: true, get: function () { return fetch_1.fetchEventSource; } });
2929
+ Object.defineProperty(exports$1, "EventStreamContentType", { enumerable: true, get: function () { return fetch_1.EventStreamContentType; } });
2930
+
2931
+ } (cjs));
2932
+
2836
2933
  /**
2837
2934
  * Default configuration messages
2838
2935
  * @returns
@@ -2879,6 +2976,1416 @@ function setSessionId(sessionId) {
2879
2976
  };
2880
2977
  }
2881
2978
 
2979
+ // Endpoint path constants
2980
+ var HOMEOWNER_ENDPOINT_PATH = "/chat/stream";
2981
+ var BUSINESS_OWNER_ENDPOINT_PATH = "/api/chat/stream";
2982
+ // Default retry configuration for MCP connections
2983
+ var DEFAULT_MCP_RETRY_CONFIG = [
2984
+ { retry: 0, delay: 0, text: "" }, // Initial attempt (silent)
2985
+ { retry: 1, delay: 3, text: "Having trouble connecting. Retrying in ${sec} seconds..." },
2986
+ { retry: 2, delay: 5, text: "Still having trouble. Retrying in ${sec} seconds..." },
2987
+ { retry: 0, delay: 0, text: "Unable to connect to the assistant. Please try again later." }, // Final failure
2988
+ ];
2989
+ // Streaming throttle configuration
2990
+ // Average reading speed is ~250 words/min (~20 chars/sec). We go slightly faster.
2991
+ var STREAM_CHARS_PER_TICK = 3; // Characters to display per tick
2992
+ var STREAM_TICK_INTERVAL_MS = 30; // Milliseconds between ticks (~100 chars/sec)
2993
+ /**
2994
+ * Storage keys for MCP session persistence.
2995
+ * Uses localStorage directly (not Redux) for MCP-specific data.
2996
+ */
2997
+ var MCP_STORAGE_KEYS = {
2998
+ USER_ID: "xapp_mcp_userId",
2999
+ SESSION_ID: "xapp_mcp_sessionId",
3000
+ };
3001
+ /**
3002
+ * Gets the platform string, preferring the modern userAgentData API.
3003
+ * Falls back to the deprecated navigator.platform for older browsers.
3004
+ */
3005
+ function getPlatform() {
3006
+ // Modern API (Chrome 90+, Edge 90+, Opera 76+)
3007
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3008
+ var userAgentData = navigator.userAgentData;
3009
+ if (userAgentData === null || userAgentData === void 0 ? void 0 : userAgentData.platform) {
3010
+ return userAgentData.platform;
3011
+ }
3012
+ // Fallback for older browsers (deprecated but widely supported)
3013
+ return navigator.platform || undefined;
3014
+ }
3015
+ /**
3016
+ * Collects non-invasive device/browser fingerprint data.
3017
+ * This helps the server identify returning users without requiring login.
3018
+ *
3019
+ * PRIVACY NOTE: Only basic, non-identifying browser characteristics are collected.
3020
+ * This data cannot be used to personally identify individual users.
3021
+ */
3022
+ function getFingerprint() {
3023
+ if (typeof window === "undefined") {
3024
+ return {};
3025
+ }
3026
+ return {
3027
+ userAgent: navigator.userAgent,
3028
+ language: navigator.language,
3029
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
3030
+ screenResolution: "".concat(window.screen.width, "x").concat(window.screen.height),
3031
+ platform: getPlatform(),
3032
+ colorDepth: window.screen.colorDepth,
3033
+ touchSupport: "ontouchstart" in window || navigator.maxTouchPoints > 0,
3034
+ };
3035
+ }
3036
+ /**
3037
+ * Retrieves stored MCP identity from localStorage.
3038
+ */
3039
+ function getStoredIdentity() {
3040
+ if (typeof window === "undefined" || !window.localStorage) {
3041
+ return {};
3042
+ }
3043
+ try {
3044
+ return {
3045
+ userId: localStorage.getItem(MCP_STORAGE_KEYS.USER_ID) || undefined,
3046
+ sessionId: localStorage.getItem(MCP_STORAGE_KEYS.SESSION_ID) || undefined,
3047
+ };
3048
+ }
3049
+ catch (_a) {
3050
+ // localStorage may be unavailable (private browsing, etc.)
3051
+ return {};
3052
+ }
3053
+ }
3054
+ /**
3055
+ * Stores MCP identity to localStorage.
3056
+ */
3057
+ function storeIdentity(userId, sessionId) {
3058
+ if (typeof window === "undefined" || !window.localStorage) {
3059
+ return;
3060
+ }
3061
+ try {
3062
+ if (userId) {
3063
+ localStorage.setItem(MCP_STORAGE_KEYS.USER_ID, userId);
3064
+ }
3065
+ if (sessionId) {
3066
+ localStorage.setItem(MCP_STORAGE_KEYS.SESSION_ID, sessionId);
3067
+ }
3068
+ }
3069
+ catch (_a) {
3070
+ // localStorage may be unavailable
3071
+ }
3072
+ }
3073
+ /**
3074
+ * Clears stored MCP session (but preserves userId for returning user identification).
3075
+ */
3076
+ function clearStoredSession() {
3077
+ if (typeof window === "undefined" || !window.localStorage) {
3078
+ return;
3079
+ }
3080
+ try {
3081
+ localStorage.removeItem(MCP_STORAGE_KEYS.SESSION_ID);
3082
+ }
3083
+ catch (_a) {
3084
+ // localStorage may be unavailable
3085
+ }
3086
+ }
3087
+ /**
3088
+ * Gets the current page context from browser globals.
3089
+ * If an override is provided, uses that instead (useful for testing).
3090
+ *
3091
+ * PRIVACY NOTE: Query strings are stripped from URLs by default as they
3092
+ * may contain sensitive data (tokens, PII). Only origin + pathname are sent.
3093
+ */
3094
+ function getPageContext(override) {
3095
+ var localTime = new Date().toISOString();
3096
+ // If override provided, merge with defaults (override wins)
3097
+ // Overrides are typically for testing and may include full URLs
3098
+ if (override && Object.keys(override).length > 0) {
3099
+ log("[getPageContext] Using override: ".concat(JSON.stringify(override)));
3100
+ return {
3101
+ url: override.url,
3102
+ path: override.path,
3103
+ title: override.title,
3104
+ referrer: override.referrer,
3105
+ localTime: localTime,
3106
+ };
3107
+ }
3108
+ if (typeof window === "undefined") {
3109
+ return { localTime: localTime };
3110
+ }
3111
+ // Strip query strings from URL for privacy (may contain tokens/PII)
3112
+ var urlWithoutQuery = window.location.origin + window.location.pathname;
3113
+ // Strip query strings from referrer as well
3114
+ var referrerWithoutQuery;
3115
+ if (document.referrer) {
3116
+ try {
3117
+ var refUrl = new URL(document.referrer);
3118
+ referrerWithoutQuery = refUrl.origin + refUrl.pathname;
3119
+ }
3120
+ catch (_a) {
3121
+ // Invalid referrer URL, skip it
3122
+ referrerWithoutQuery = undefined;
3123
+ }
3124
+ }
3125
+ return {
3126
+ url: urlWithoutQuery,
3127
+ path: window.location.pathname,
3128
+ title: document.title || undefined,
3129
+ referrer: referrerWithoutQuery,
3130
+ localTime: localTime,
3131
+ };
3132
+ }
3133
+ var MCPChat = /** @class */ (function () {
3134
+ function MCPChat(config, options) {
3135
+ this._userId = "";
3136
+ this._sessionId = "";
3137
+ this._accessToken = "";
3138
+ this._attributes = {};
3139
+ // Streaming throttle state
3140
+ this.streamBuffer = "";
3141
+ // New session initialization state
3142
+ this.isInitializingSession = false;
3143
+ // Session validation state (silent handshake - no stream dispatch)
3144
+ this.isValidatingSession = false;
3145
+ // Tool call tracking for debug mode
3146
+ this.currentToolCalls = [];
3147
+ this.config = config;
3148
+ this.options = options;
3149
+ this.configurableMessages = options === null || options === void 0 ? void 0 : options.configurableMessages;
3150
+ this.isNewSession = false;
3151
+ this.hooks = options === null || options === void 0 ? void 0 : options.hooks;
3152
+ // Validate business_owner mode requires authToken
3153
+ var mode = config.mode || "homeowner";
3154
+ if (mode === "business_owner" && !config.authToken) {
3155
+ throw new Error("authToken is required for business_owner mode");
3156
+ }
3157
+ // Load stored identity from localStorage
3158
+ var storedIdentity = getStoredIdentity();
3159
+ if (storedIdentity.userId) {
3160
+ this._userId = storedIdentity.userId;
3161
+ log("[MCPChat] Loaded stored userId: ".concat(this._userId));
3162
+ }
3163
+ if (storedIdentity.sessionId) {
3164
+ this._sessionId = storedIdentity.sessionId;
3165
+ log("[MCPChat] Loaded stored sessionId: ".concat(this._sessionId));
3166
+ }
3167
+ // Config-provided sessionId takes precedence
3168
+ if (config.sessionId) {
3169
+ this._sessionId = config.sessionId;
3170
+ }
3171
+ }
3172
+ MCPChat.prototype.init = function (dispatch) {
3173
+ this.dispatch = dispatch;
3174
+ var mode = this.config.mode || "homeowner";
3175
+ log("MCPChat initialized with mode: ".concat(mode));
3176
+ // Warn if not using HTTPS with authentication
3177
+ if (mode === "business_owner" && !this.config.serverUrl.startsWith("https://")) {
3178
+ err("WARNING: Using non-HTTPS URL with authentication tokens. This is insecure in production.");
3179
+ }
3180
+ this.setConnectionStatus("online");
3181
+ this.setAccountStatus("online");
3182
+ // Returning user with existing session - validate session silently
3183
+ // New user without session - initialize new session to get greeting
3184
+ if (this._sessionId) {
3185
+ this.validateSession();
3186
+ }
3187
+ else {
3188
+ this.initializeNewSession();
3189
+ }
3190
+ };
3191
+ /**
3192
+ * Validates an existing session with the server ("Silent Handshake").
3193
+ * Called when widget reopens with a stored sessionId.
3194
+ *
3195
+ * The server will respond with:
3196
+ * - "connected" event if session is valid
3197
+ * - "session_expired" event if session is gone (triggers initializeNewSession)
3198
+ *
3199
+ * This is lightweight - no LLM call, just a quick DB check to:
3200
+ * - Validate session before user tries to send a message
3201
+ * - Update lastActivityAt in persistent session
3202
+ * - Confirm userId is still valid
3203
+ */
3204
+ MCPChat.prototype.validateSession = function () {
3205
+ return __awaiter$1(this, void 0, void 0, function () {
3206
+ var endpoint, error_1;
3207
+ return __generator$1(this, function (_a) {
3208
+ switch (_a.label) {
3209
+ case 0:
3210
+ endpoint = this.getEndpoint();
3211
+ log("Validating existing session at ".concat(endpoint, ", sessionId: ").concat(this._sessionId));
3212
+ this.isValidatingSession = true;
3213
+ _a.label = 1;
3214
+ case 1:
3215
+ _a.trys.push([1, 3, 4, 5]);
3216
+ return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload({ resumeSession: true }))];
3217
+ case 2:
3218
+ _a.sent();
3219
+ log("Session validation complete");
3220
+ return [3 /*break*/, 5];
3221
+ case 3:
3222
+ error_1 = _a.sent();
3223
+ err("Failed to validate session: ".concat(error_1));
3224
+ // If validation fails (network error, etc.), clear session and start fresh
3225
+ this._sessionId = "";
3226
+ clearStoredSession();
3227
+ this.initializeNewSession();
3228
+ return [3 /*break*/, 5];
3229
+ case 4:
3230
+ this.isValidatingSession = false;
3231
+ return [7 /*endfinally*/];
3232
+ case 5: return [2 /*return*/];
3233
+ }
3234
+ });
3235
+ });
3236
+ };
3237
+ /**
3238
+ * Builds the common request payload with identity and context.
3239
+ */
3240
+ MCPChat.prototype.buildRequestPayload = function (additionalData) {
3241
+ if (additionalData === void 0) { additionalData = {}; }
3242
+ return __assign({
3243
+ // Identity - server generates these, we just send back what we have
3244
+ userId: this._userId || undefined, sessionId: this._sessionId || undefined,
3245
+ // Fingerprint for user identification (can be disabled for privacy)
3246
+ fingerprint: this.config.disableFingerprinting ? undefined : getFingerprint(),
3247
+ // Page context
3248
+ pageContext: getPageContext(this.config.pageContextOverride) }, additionalData);
3249
+ };
3250
+ /**
3251
+ * Initializes a new session by calling the endpoint with { newSession: true }.
3252
+ * This returns a greeting message and session ID without making an LLM call.
3253
+ */
3254
+ MCPChat.prototype.initializeNewSession = function () {
3255
+ return __awaiter$1(this, void 0, void 0, function () {
3256
+ var endpoint, error_2;
3257
+ var _this = this;
3258
+ return __generator$1(this, function (_a) {
3259
+ switch (_a.label) {
3260
+ case 0:
3261
+ // Prevent multiple concurrent initialization requests
3262
+ if (this.isInitializingSession) {
3263
+ log("New session initialization already in progress, skipping");
3264
+ return [2 /*return*/];
3265
+ }
3266
+ endpoint = this.getEndpoint();
3267
+ log("Initializing new session at ".concat(endpoint));
3268
+ this.isInitializingSession = true;
3269
+ // Create a promise that resolves when initialization completes
3270
+ this.sessionInitPromise = new Promise(function (resolve) {
3271
+ _this.resolveSessionInit = resolve;
3272
+ });
3273
+ this.typing();
3274
+ _a.label = 1;
3275
+ case 1:
3276
+ _a.trys.push([1, 3, 4, 5]);
3277
+ return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload({ newSession: true, generateGreeting: true }))];
3278
+ case 2:
3279
+ _a.sent();
3280
+ return [3 /*break*/, 5];
3281
+ case 3:
3282
+ error_2 = _a.sent();
3283
+ err("Failed to initialize new session: ".concat(error_2));
3284
+ this.stopTyping();
3285
+ this.setConnectionStatus("offline");
3286
+ // Show a failure message but don't block - user can still try sending messages
3287
+ this.sendFailureMessage(0, 0, this.getErrorMessage(error_2));
3288
+ return [3 /*break*/, 5];
3289
+ case 4:
3290
+ this.isInitializingSession = false;
3291
+ // Resolve the promise so any waiting postMessage calls can proceed
3292
+ if (this.resolveSessionInit) {
3293
+ this.resolveSessionInit();
3294
+ this.resolveSessionInit = undefined;
3295
+ }
3296
+ this.sessionInitPromise = undefined;
3297
+ return [7 /*endfinally*/];
3298
+ case 5: return [2 /*return*/];
3299
+ }
3300
+ });
3301
+ });
3302
+ };
3303
+ MCPChat.prototype.setConnectionStatus = function (status) {
3304
+ log("SERVER: connection_update: ".concat(JSON.stringify(status)));
3305
+ this.dispatch(setConnectionStatus(status));
3306
+ };
3307
+ MCPChat.prototype.setAccountStatus = function (status) {
3308
+ log("SERVER: account_status: ".concat(JSON.stringify(status)));
3309
+ this.dispatch(setAccountStatus(status));
3310
+ };
3311
+ MCPChat.prototype.getEndpoint = function () {
3312
+ // Normalize serverUrl to remove trailing slashes
3313
+ var serverUrl = this.config.serverUrl.replace(/\/+$/, "");
3314
+ var mode = this.config.mode || "homeowner";
3315
+ if (mode === "business_owner") {
3316
+ return "".concat(serverUrl).concat(BUSINESS_OWNER_ENDPOINT_PATH);
3317
+ }
3318
+ // homeowner mode - serverUrl should already include appId
3319
+ return "".concat(serverUrl).concat(HOMEOWNER_ENDPOINT_PATH);
3320
+ };
3321
+ MCPChat.prototype.sendNewMessage = function (msg, user) {
3322
+ log("SERVER: new message: ".concat(JSON.stringify(msg)));
3323
+ this.dispatch({
3324
+ type: "chat",
3325
+ detail: {
3326
+ type: "chat.msg",
3327
+ user: user || this.getBot(undefined),
3328
+ msg: msg,
3329
+ timestamp: +new Date(),
3330
+ },
3331
+ });
3332
+ };
3333
+ MCPChat.prototype.userJoined = function (user) {
3334
+ log("SERVER: user joined: ".concat(JSON.stringify(user)));
3335
+ this.dispatch({
3336
+ type: "chat",
3337
+ detail: {
3338
+ type: "chat.memberjoin",
3339
+ user: user,
3340
+ timestamp: +new Date(),
3341
+ },
3342
+ });
3343
+ };
3344
+ MCPChat.prototype.typing = function () {
3345
+ this.dispatch({
3346
+ type: "chat",
3347
+ detail: {
3348
+ type: "chat.typing",
3349
+ user: this.getBot(undefined),
3350
+ typing: true,
3351
+ timestamp: +new Date(),
3352
+ },
3353
+ });
3354
+ };
3355
+ MCPChat.prototype.stopTyping = function () {
3356
+ this.dispatch({
3357
+ type: "chat",
3358
+ detail: {
3359
+ type: "chat.typing",
3360
+ user: this.getBot(undefined),
3361
+ typing: false,
3362
+ timestamp: +new Date(),
3363
+ },
3364
+ });
3365
+ };
3366
+ MCPChat.prototype.sendFailureMessage = function (retry, delay, text) {
3367
+ this.stopTyping();
3368
+ this.dispatch({
3369
+ type: "chat",
3370
+ detail: {
3371
+ type: "chat.failureMsg",
3372
+ user: this.getBot(undefined),
3373
+ failureMsg: {
3374
+ retry: retry,
3375
+ delay: delay,
3376
+ text: text,
3377
+ },
3378
+ timestamp: +new Date(),
3379
+ },
3380
+ });
3381
+ };
3382
+ /**
3383
+ * Gets the retry configuration, using custom config if provided or defaults.
3384
+ */
3385
+ MCPChat.prototype.getRetryConfig = function () {
3386
+ var _a, _b;
3387
+ if (((_b = (_a = this.configurableMessages) === null || _a === void 0 ? void 0 : _a.items) === null || _b === void 0 ? void 0 : _b.length) > 0) {
3388
+ return getConfigurableMessagesConfig(this.configurableMessages);
3389
+ }
3390
+ return DEFAULT_MCP_RETRY_CONFIG;
3391
+ };
3392
+ /**
3393
+ * Delays execution for the specified number of seconds.
3394
+ */
3395
+ MCPChat.prototype.delay = function (seconds) {
3396
+ return new Promise(function (resolve) { return setTimeout(resolve, seconds * 1000); });
3397
+ };
3398
+ /**
3399
+ * Determines a user-friendly error message based on the error type.
3400
+ */
3401
+ MCPChat.prototype.getErrorMessage = function (error, statusCode) {
3402
+ // Auth errors
3403
+ if (statusCode === 401) {
3404
+ return "Authentication failed. Please check your credentials.";
3405
+ }
3406
+ if (statusCode === 403) {
3407
+ return "Access denied. You don't have permission to access this resource.";
3408
+ }
3409
+ // Server errors
3410
+ if (statusCode && statusCode >= 500) {
3411
+ return "The server is experiencing issues. Please try again later.";
3412
+ }
3413
+ // Not found (likely wrong appId or endpoint)
3414
+ if (statusCode === 404) {
3415
+ return "Service not found. Please check your configuration.";
3416
+ }
3417
+ // Network errors
3418
+ if (error instanceof TypeError && (error.message.includes("fetch") || error.message.includes("network"))) {
3419
+ return "Unable to connect to the server. Please check your internet connection.";
3420
+ }
3421
+ // Connection refused / server down
3422
+ if (error instanceof Error) {
3423
+ var msg = error.message.toLowerCase();
3424
+ if (msg.includes("failed to fetch") || msg.includes("network") || msg.includes("econnrefused")) {
3425
+ return "Unable to reach the server. Please try again later.";
3426
+ }
3427
+ }
3428
+ // Generic fallback
3429
+ return "Something went wrong. Please try again.";
3430
+ };
3431
+ MCPChat.prototype.dispatchStreamStart = function (messageId) {
3432
+ var bot = this.getBot(undefined);
3433
+ log("Stream started: ".concat(messageId, ", bot: ").concat(JSON.stringify(bot)));
3434
+ this.dispatch({
3435
+ type: "chat",
3436
+ detail: {
3437
+ type: "chat.stream.start",
3438
+ user: bot,
3439
+ messageId: messageId,
3440
+ timestamp: +new Date(),
3441
+ },
3442
+ });
3443
+ };
3444
+ MCPChat.prototype.dispatchStreamChunk = function (messageId, text) {
3445
+ this.dispatch({
3446
+ type: "chat",
3447
+ detail: {
3448
+ type: "chat.stream.chunk",
3449
+ user: this.getBot(undefined),
3450
+ messageId: messageId,
3451
+ text: text,
3452
+ timestamp: +new Date(),
3453
+ },
3454
+ });
3455
+ };
3456
+ MCPChat.prototype.dispatchStreamEnd = function (messageId, fullMessage, toolCalls) {
3457
+ log("Stream ended: ".concat(messageId));
3458
+ this.dispatch({
3459
+ type: "chat",
3460
+ detail: {
3461
+ type: "chat.stream.end",
3462
+ user: this.getBot(undefined),
3463
+ messageId: messageId,
3464
+ msg: fullMessage,
3465
+ toolCalls: toolCalls,
3466
+ timestamp: +new Date(),
3467
+ },
3468
+ });
3469
+ };
3470
+ MCPChat.prototype.dispatchToolStart = function (params) {
3471
+ this.dispatch({
3472
+ type: "chat",
3473
+ detail: {
3474
+ type: "chat.stream.tool_start",
3475
+ user: this.getBot(undefined),
3476
+ messageId: params.messageId,
3477
+ toolName: params.toolName,
3478
+ toolCallId: params.toolCallId,
3479
+ displayName: params.displayName,
3480
+ label: params.label,
3481
+ hidden: params.hidden,
3482
+ toolInput: params.toolInput,
3483
+ timestamp: +new Date(),
3484
+ },
3485
+ });
3486
+ };
3487
+ MCPChat.prototype.dispatchToolEnd = function (params) {
3488
+ this.dispatch({
3489
+ type: "chat",
3490
+ detail: {
3491
+ type: "chat.stream.tool_end",
3492
+ user: this.getBot(undefined),
3493
+ messageId: params.messageId,
3494
+ toolName: params.toolName,
3495
+ toolCallId: params.toolCallId,
3496
+ displayName: params.displayName,
3497
+ label: params.label,
3498
+ hidden: params.hidden,
3499
+ toolResult: params.toolResult,
3500
+ toolError: params.toolError,
3501
+ timestamp: +new Date(),
3502
+ },
3503
+ });
3504
+ };
3505
+ /**
3506
+ * Starts the throttled streaming for a message.
3507
+ * Text will be dispatched at a readable pace.
3508
+ */
3509
+ MCPChat.prototype.startStreamThrottle = function (messageId) {
3510
+ var _this = this;
3511
+ this.streamBuffer = "";
3512
+ this.streamingMessageIdForThrottle = messageId;
3513
+ // Clear any existing timer and visibility handler
3514
+ if (this.streamThrottleTimer) {
3515
+ clearInterval(this.streamThrottleTimer);
3516
+ }
3517
+ if (this.visibilityHandler) {
3518
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3519
+ }
3520
+ // Start interval to dispatch buffered text
3521
+ this.streamThrottleTimer = setInterval(function () {
3522
+ if (_this.streamBuffer.length > 0 && _this.streamingMessageIdForThrottle) {
3523
+ var charsToSend = Math.min(STREAM_CHARS_PER_TICK, _this.streamBuffer.length);
3524
+ var textToDispatch = _this.streamBuffer.slice(0, charsToSend);
3525
+ _this.streamBuffer = _this.streamBuffer.slice(charsToSend);
3526
+ _this.dispatchStreamChunk(_this.streamingMessageIdForThrottle, textToDispatch);
3527
+ }
3528
+ }, STREAM_TICK_INTERVAL_MS);
3529
+ // Handle tab visibility changes - flush buffer when tab becomes visible
3530
+ this.visibilityHandler = function () {
3531
+ if (document.visibilityState === "visible") {
3532
+ log("[Visibility] Tab became visible, buffer length: ".concat(_this.streamBuffer.length, ", messageId: ").concat(_this.streamingMessageIdForThrottle));
3533
+ if (_this.streamBuffer.length > 0 && _this.streamingMessageIdForThrottle) {
3534
+ // Dispatch all buffered text immediately when tab becomes visible
3535
+ log("[Visibility] Flushing ".concat(_this.streamBuffer.length, " chars to UI"));
3536
+ _this.dispatchStreamChunk(_this.streamingMessageIdForThrottle, _this.streamBuffer);
3537
+ _this.streamBuffer = "";
3538
+ }
3539
+ }
3540
+ else {
3541
+ log("[Visibility] Tab hidden");
3542
+ }
3543
+ };
3544
+ document.addEventListener("visibilitychange", this.visibilityHandler);
3545
+ };
3546
+ /**
3547
+ * Adds text to the stream buffer for throttled dispatch.
3548
+ */
3549
+ MCPChat.prototype.bufferStreamText = function (text) {
3550
+ this.streamBuffer += text;
3551
+ };
3552
+ /**
3553
+ * Flushes any remaining buffered text and stops the throttle timer.
3554
+ * Returns a promise that resolves when all buffered text has been dispatched.
3555
+ */
3556
+ MCPChat.prototype.flushStreamBuffer = function () {
3557
+ return __awaiter$1(this, void 0, void 0, function () {
3558
+ var waitCount, maxWait;
3559
+ return __generator$1(this, function (_a) {
3560
+ switch (_a.label) {
3561
+ case 0:
3562
+ if (!(this.streamBuffer.length > 0 && this.streamingMessageIdForThrottle)) return [3 /*break*/, 5];
3563
+ if (!(document.visibilityState === "hidden")) return [3 /*break*/, 1];
3564
+ log("[flushStreamBuffer] Tab hidden, dispatching remaining ".concat(this.streamBuffer.length, " chars immediately"));
3565
+ this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
3566
+ this.streamBuffer = "";
3567
+ return [3 /*break*/, 5];
3568
+ case 1:
3569
+ waitCount = 0;
3570
+ maxWait = 100;
3571
+ _a.label = 2;
3572
+ case 2:
3573
+ if (!(this.streamBuffer.length > 0 && waitCount < maxWait)) return [3 /*break*/, 4];
3574
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, STREAM_TICK_INTERVAL_MS); })];
3575
+ case 3:
3576
+ _a.sent();
3577
+ waitCount++;
3578
+ return [3 /*break*/, 2];
3579
+ case 4:
3580
+ // If still has content after timeout, flush it
3581
+ if (this.streamBuffer.length > 0 && this.streamingMessageIdForThrottle) {
3582
+ log("[flushStreamBuffer] Timeout waiting for buffer, dispatching remaining ".concat(this.streamBuffer.length, " chars"));
3583
+ this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
3584
+ this.streamBuffer = "";
3585
+ }
3586
+ _a.label = 5;
3587
+ case 5:
3588
+ // Stop the throttle timer
3589
+ if (this.streamThrottleTimer) {
3590
+ clearInterval(this.streamThrottleTimer);
3591
+ this.streamThrottleTimer = undefined;
3592
+ }
3593
+ if (this.visibilityHandler) {
3594
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3595
+ this.visibilityHandler = undefined;
3596
+ }
3597
+ this.streamingMessageIdForThrottle = undefined;
3598
+ return [2 /*return*/];
3599
+ }
3600
+ });
3601
+ });
3602
+ };
3603
+ /**
3604
+ * Immediately stops throttling and clears the buffer (used on abort/error).
3605
+ */
3606
+ MCPChat.prototype.stopStreamThrottle = function () {
3607
+ if (this.streamThrottleTimer) {
3608
+ clearInterval(this.streamThrottleTimer);
3609
+ this.streamThrottleTimer = undefined;
3610
+ }
3611
+ if (this.visibilityHandler) {
3612
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3613
+ this.visibilityHandler = undefined;
3614
+ }
3615
+ this.streamBuffer = "";
3616
+ this.streamingMessageIdForThrottle = undefined;
3617
+ };
3618
+ MCPChat.prototype.sendOfflineMsg = function (_, cb) {
3619
+ cb();
3620
+ };
3621
+ MCPChat.prototype.sendChatMsg = function (message, cb) {
3622
+ return __awaiter$1(this, void 0, void 0, function () {
3623
+ return __generator$1(this, function (_a) {
3624
+ return [2 /*return*/, this.sendChatMsgRequest({
3625
+ msg: message,
3626
+ timestamp: new Date().getTime(),
3627
+ agent: false,
3628
+ user: undefined,
3629
+ type: "msg",
3630
+ }, cb)];
3631
+ });
3632
+ });
3633
+ };
3634
+ MCPChat.prototype.bargeOut = function (_) {
3635
+ return __awaiter$1(this, void 0, void 0, function () {
3636
+ return __generator$1(this, function (_a) {
3637
+ throw new Error("bargeOut is not supported in MCP mode");
3638
+ });
3639
+ });
3640
+ };
3641
+ MCPChat.prototype.bargeIn = function (_agentName, __) {
3642
+ return __awaiter$1(this, void 0, void 0, function () {
3643
+ return __generator$1(this, function (_a) {
3644
+ throw new Error("bargeIn is not supported in MCP mode");
3645
+ });
3646
+ });
3647
+ };
3648
+ /**
3649
+ * Ensures a nick has the "agent:" prefix required for agent message detection.
3650
+ */
3651
+ MCPChat.prototype.ensureAgentNick = function (nick) {
3652
+ if (!nick)
3653
+ return "agent:robot";
3654
+ if (nick.startsWith("agent:"))
3655
+ return nick;
3656
+ return "agent:".concat(nick);
3657
+ };
3658
+ MCPChat.prototype.getBot = function (user) {
3659
+ var _a;
3660
+ var bot = (_a = this.options) === null || _a === void 0 ? void 0 : _a.bot;
3661
+ if (!user) {
3662
+ // Ensure bot nick has agent: prefix and mark as AI
3663
+ var defaultBot = bot || { nick: "agent:robot", displayName: "Assistant" };
3664
+ return __assign(__assign({}, defaultBot), { nick: this.ensureAgentNick(defaultBot.nick), isAI: true });
3665
+ }
3666
+ if (user.displayName && user.nick && user.avatarPath) {
3667
+ return __assign(__assign({}, user), { nick: this.ensureAgentNick(user.nick), isAI: true });
3668
+ }
3669
+ 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 });
3670
+ };
3671
+ MCPChat.prototype.sendChatMsgRequest = function (serviceRequest, cb) {
3672
+ return __awaiter$1(this, void 0, void 0, function () {
3673
+ var error_3;
3674
+ return __generator$1(this, function (_a) {
3675
+ switch (_a.label) {
3676
+ case 0:
3677
+ _a.trys.push([0, 2, , 3]);
3678
+ return [4 /*yield*/, this.postMessage(serviceRequest)];
3679
+ case 1:
3680
+ _a.sent();
3681
+ cb();
3682
+ return [3 /*break*/, 3];
3683
+ case 2:
3684
+ error_3 = _a.sent();
3685
+ cb(error_3);
3686
+ return [3 /*break*/, 3];
3687
+ case 3: return [2 /*return*/];
3688
+ }
3689
+ });
3690
+ });
3691
+ };
3692
+ MCPChat.prototype.postMessage = function (message) {
3693
+ return __awaiter$1(this, void 0, void 0, function () {
3694
+ var trimmedText, timeout, result, retryConfig, messageText, success, attempt, isLastAttempt, config, error_4, statusCode, errorMessage, statusCode_1, errorMessage, nextConfig, nextConfig;
3695
+ var _a, _b, _c;
3696
+ return __generator$1(this, function (_d) {
3697
+ switch (_d.label) {
3698
+ case 0:
3699
+ trimmedText = ((_b = (_a = message.msg) === null || _a === void 0 ? void 0 : _a.text) === null || _b === void 0 ? void 0 : _b.trim()) || "";
3700
+ if (!trimmedText) {
3701
+ log("Ignoring empty message");
3702
+ return [2 /*return*/];
3703
+ }
3704
+ if (!(this.isInitializingSession && this.sessionInitPromise)) return [3 /*break*/, 2];
3705
+ log("Waiting for session initialization to complete before sending message...");
3706
+ timeout = new Promise(function (resolve) {
3707
+ return setTimeout(function () { return resolve("timeout"); }, 10000);
3708
+ });
3709
+ return [4 /*yield*/, Promise.race([this.sessionInitPromise, timeout])];
3710
+ case 1:
3711
+ result = _d.sent();
3712
+ if (result === "timeout" || this.isInitializingSession) {
3713
+ err("Session initialization timed out");
3714
+ this.sendFailureMessage(0, 0, "Chat is not ready. Please try again.");
3715
+ return [2 /*return*/];
3716
+ }
3717
+ _d.label = 2;
3718
+ case 2:
3719
+ retryConfig = this.getRetryConfig();
3720
+ messageText = ((_c = message.msg) === null || _c === void 0 ? void 0 : _c.text) || "";
3721
+ success = false;
3722
+ attempt = 0;
3723
+ _d.label = 3;
3724
+ case 3:
3725
+ if (!(attempt < retryConfig.length)) return [3 /*break*/, 14];
3726
+ isLastAttempt = attempt === retryConfig.length - 1;
3727
+ config = retryConfig[attempt];
3728
+ log("MCP attempt ".concat(attempt + 1, "/").concat(retryConfig.length, " for message: ").concat(messageText));
3729
+ // Start typing indicator
3730
+ this.typing();
3731
+ _d.label = 4;
3732
+ case 4:
3733
+ _d.trys.push([4, 6, , 13]);
3734
+ return [4 /*yield*/, this.attemptSSERequest(message)];
3735
+ case 5:
3736
+ _d.sent();
3737
+ success = true;
3738
+ return [3 /*break*/, 14]; // Success - exit retry loop
3739
+ case 6:
3740
+ error_4 = _d.sent();
3741
+ this.stopTyping();
3742
+ statusCode = error_4 === null || error_4 === void 0 ? void 0 : error_4.statusCode;
3743
+ err("MCP attempt ".concat(attempt + 1, " failed: ").concat(error_4, ", statusCode: ").concat(statusCode));
3744
+ log("[postMessage] Error details:", { error: error_4, statusCode: statusCode, messageText: messageText, sessionId: this._sessionId });
3745
+ // Don't retry on 4xx client errors - these won't succeed on retry
3746
+ if (statusCode && statusCode >= 400 && statusCode < 500) {
3747
+ log("Client error ".concat(statusCode, ", not retrying"));
3748
+ errorMessage = this.getErrorMessage(error_4, statusCode);
3749
+ this.sendFailureMessage(0, 0, errorMessage);
3750
+ return [2 /*return*/];
3751
+ }
3752
+ // Set offline status
3753
+ this.setConnectionStatus("offline");
3754
+ if (!isLastAttempt) return [3 /*break*/, 7];
3755
+ statusCode_1 = error_4 === null || error_4 === void 0 ? void 0 : error_4.statusCode;
3756
+ errorMessage = config.text || this.getErrorMessage(error_4, statusCode_1);
3757
+ this.sendFailureMessage(0, 0, errorMessage);
3758
+ return [3 /*break*/, 12];
3759
+ case 7:
3760
+ if (!(attempt > 0 || config.text)) return [3 /*break*/, 10];
3761
+ nextConfig = retryConfig[attempt + 1];
3762
+ if (!(nextConfig && nextConfig.delay > 0)) return [3 /*break*/, 9];
3763
+ this.sendFailureMessage(nextConfig.retry, nextConfig.delay, nextConfig.text);
3764
+ // Wait for the delay before retrying
3765
+ return [4 /*yield*/, this.delay(nextConfig.delay)];
3766
+ case 8:
3767
+ // Wait for the delay before retrying
3768
+ _d.sent();
3769
+ _d.label = 9;
3770
+ case 9: return [3 /*break*/, 12];
3771
+ case 10:
3772
+ nextConfig = retryConfig[attempt + 1];
3773
+ if (!(nextConfig && nextConfig.delay > 0)) return [3 /*break*/, 12];
3774
+ return [4 /*yield*/, this.delay(nextConfig.delay)];
3775
+ case 11:
3776
+ _d.sent();
3777
+ _d.label = 12;
3778
+ case 12: return [3 /*break*/, 13];
3779
+ case 13:
3780
+ attempt++;
3781
+ return [3 /*break*/, 3];
3782
+ case 14:
3783
+ if (!success) {
3784
+ log("All MCP retry attempts exhausted");
3785
+ }
3786
+ return [2 /*return*/];
3787
+ }
3788
+ });
3789
+ });
3790
+ };
3791
+ /**
3792
+ * Attempts a single SSE request. Throws on failure.
3793
+ */
3794
+ MCPChat.prototype.attemptSSERequest = function (message) {
3795
+ return __awaiter$1(this, void 0, void 0, function () {
3796
+ var messageText;
3797
+ var _a;
3798
+ return __generator$1(this, function (_b) {
3799
+ messageText = ((_a = message.msg) === null || _a === void 0 ? void 0 : _a.text) || "";
3800
+ log("[attemptSSERequest] Sending message: \"".concat(messageText, "\", userId: ").concat(this._userId, ", sessionId: ").concat(this._sessionId));
3801
+ return [2 /*return*/, this.performSSERequest(this.buildRequestPayload({ message: messageText }))];
3802
+ });
3803
+ });
3804
+ };
3805
+ /**
3806
+ * Performs an SSE request with the given body.
3807
+ * Used for both new session initialization and regular messages.
3808
+ */
3809
+ MCPChat.prototype.performSSERequest = function (body) {
3810
+ return __awaiter$1(this, void 0, void 0, function () {
3811
+ var endpoint, messageId, textChunks, hasError, httpStatusCode, sseError;
3812
+ var _this = this;
3813
+ return __generator$1(this, function (_a) {
3814
+ endpoint = this.getEndpoint();
3815
+ log("SSE request to ".concat(endpoint, ": ").concat(JSON.stringify(body)));
3816
+ // Cancel any ongoing stream
3817
+ if (this.abortController) {
3818
+ this.abortController.abort();
3819
+ this.abortController = undefined;
3820
+ }
3821
+ this.abortController = new AbortController();
3822
+ messageId = uuid_1();
3823
+ this.currentMessageId = messageId;
3824
+ textChunks = [];
3825
+ hasError = false;
3826
+ return [2 /*return*/, new Promise(function (resolve, reject) {
3827
+ // WARNING: Do not log headers object - it contains sensitive auth tokens
3828
+ cjs.fetchEventSource(endpoint, {
3829
+ method: "POST",
3830
+ headers: __assign({ "Content-Type": "application/json" }, ((_this.config.mode || "homeowner") === "business_owner" && _this.config.authToken
3831
+ ? { Authorization: "Bearer ".concat(_this.config.authToken) }
3832
+ : {})),
3833
+ body: JSON.stringify(body),
3834
+ signal: _this.abortController.signal,
3835
+ onopen: function (response) { return __awaiter$1(_this, void 0, void 0, function () {
3836
+ var error;
3837
+ return __generator$1(this, function (_a) {
3838
+ httpStatusCode = response.status;
3839
+ if (!response.ok) {
3840
+ error = new Error("HTTP error! status: ".concat(response.status));
3841
+ error.statusCode = response.status;
3842
+ throw error;
3843
+ }
3844
+ log("SSE connection opened");
3845
+ // Connection successful - ensure we're marked as online
3846
+ this.setConnectionStatus("online");
3847
+ return [2 /*return*/];
3848
+ });
3849
+ }); },
3850
+ onmessage: function (event) {
3851
+ log("SSE event: ".concat(event.event, ", data: ").concat(event.data));
3852
+ var eventType = event.event || "message";
3853
+ var data = {};
3854
+ try {
3855
+ data = JSON.parse(event.data);
3856
+ }
3857
+ catch (e) {
3858
+ log("Failed to parse SSE data: ".concat(e));
3859
+ return;
3860
+ }
3861
+ switch (eventType) {
3862
+ case "connected":
3863
+ // Connection established
3864
+ log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser));
3865
+ // Store identity from server response
3866
+ if (data.userId || data.sessionId) {
3867
+ if (data.userId) {
3868
+ _this._userId = data.userId;
3869
+ }
3870
+ if (data.sessionId) {
3871
+ _this._sessionId = data.sessionId;
3872
+ _this.dispatch(setSessionId(_this._sessionId));
3873
+ }
3874
+ // Persist to localStorage for session continuity
3875
+ storeIdentity(data.userId, data.sessionId);
3876
+ log("[SSE] Identity from connected - userId: ".concat(data.userId, ", sessionId: ").concat(data.sessionId));
3877
+ }
3878
+ // Always start streaming - server sends greeting for both new and returning users
3879
+ // (resumeSession requests always get a personalized greeting)
3880
+ _this.currentToolCalls = [];
3881
+ _this.stopTyping();
3882
+ _this.dispatchStreamStart(messageId);
3883
+ _this.startStreamThrottle(messageId);
3884
+ break;
3885
+ case "session_expired":
3886
+ // Session no longer exists on server - need to start fresh
3887
+ log("[SSE] Session expired: ".concat(data.reason || "Session not found"));
3888
+ _this._sessionId = "";
3889
+ // Clear stored session but keep userId for returning user identification
3890
+ clearStoredSession();
3891
+ _this.stopTyping();
3892
+ _this.stopStreamThrottle();
3893
+ // Reset validation flag before starting new session
3894
+ // (ensures connected handler will dispatch stream for greeting)
3895
+ _this.isValidatingSession = false;
3896
+ // Start a new session automatically
3897
+ _this.initializeNewSession();
3898
+ resolve();
3899
+ break;
3900
+ case "text":
3901
+ if (data.text) {
3902
+ log("[SSE] Text chunk received (".concat(data.text.length, " chars), total chunks: ").concat(textChunks.length + 1));
3903
+ textChunks.push(data.text);
3904
+ // Buffer text for throttled dispatch
3905
+ _this.bufferStreamText(data.text);
3906
+ }
3907
+ break;
3908
+ case "tool_start":
3909
+ log("Tool execution started: ".concat(data.toolName, ", toolCallId: ").concat(data.toolCallId, ", displayName: ").concat(data.displayName, ", label: ").concat(data.label, ", hidden: ").concat(data.hidden));
3910
+ if (data.toolName) {
3911
+ var toolCall = {
3912
+ name: data.toolName,
3913
+ toolCallId: data.toolCallId,
3914
+ displayName: data.displayName,
3915
+ label: data.label,
3916
+ hidden: data.hidden,
3917
+ input: data.toolInput,
3918
+ startTime: Date.now(),
3919
+ isExecuting: true,
3920
+ };
3921
+ _this.currentToolCalls.push(toolCall);
3922
+ // Always dispatch tool start for user-friendly progress display
3923
+ _this.dispatchToolStart({
3924
+ messageId: messageId,
3925
+ toolName: data.toolName,
3926
+ toolCallId: data.toolCallId,
3927
+ displayName: data.displayName,
3928
+ label: data.label,
3929
+ hidden: data.hidden,
3930
+ toolInput: data.toolInput,
3931
+ });
3932
+ }
3933
+ break;
3934
+ case "tool_end":
3935
+ log("Tool execution ended: ".concat(data.toolName, ", toolCallId: ").concat(data.toolCallId, ", displayName: ").concat(data.displayName, ", label: ").concat(data.label, ", hidden: ").concat(data.hidden));
3936
+ if (data.toolName) {
3937
+ // Find the matching tool call and update it
3938
+ // Match by toolCallId if available, otherwise fall back to name + isExecuting
3939
+ var toolCall = _this.currentToolCalls.find(function (tc) {
3940
+ return data.toolCallId
3941
+ ? tc.toolCallId === data.toolCallId
3942
+ : tc.name === data.toolName && tc.isExecuting;
3943
+ });
3944
+ if (toolCall) {
3945
+ toolCall.result = data.toolResult;
3946
+ toolCall.error = data.toolError;
3947
+ toolCall.label = data.label; // Update with completion label
3948
+ toolCall.hidden = data.hidden; // Update hidden status
3949
+ toolCall.endTime = Date.now();
3950
+ toolCall.isExecuting = false;
3951
+ }
3952
+ // Always dispatch tool end for user-friendly progress display
3953
+ _this.dispatchToolEnd({
3954
+ messageId: messageId,
3955
+ toolName: data.toolName,
3956
+ toolCallId: data.toolCallId,
3957
+ displayName: data.displayName,
3958
+ label: data.label,
3959
+ hidden: data.hidden,
3960
+ toolResult: data.toolResult,
3961
+ toolError: data.toolError,
3962
+ });
3963
+ }
3964
+ break;
3965
+ case "done":
3966
+ _this.stopTyping();
3967
+ if (data.response) {
3968
+ // Update session ID if provided and persist to localStorage
3969
+ if (data.response.sessionId) {
3970
+ _this._sessionId = data.response.sessionId;
3971
+ _this.dispatch(setSessionId(_this._sessionId));
3972
+ storeIdentity(undefined, data.response.sessionId);
3973
+ }
3974
+ // Wait for buffer to flush, then finalize
3975
+ var accumulatedText = textChunks.join("");
3976
+ log("[SSE] Done event received. Total chunks: ".concat(textChunks.length, ", accumulated text length: ").concat(accumulatedText.length));
3977
+ log("[SSE] First 100 chars of accumulated: ".concat(accumulatedText.substring(0, 100)));
3978
+ // Map suggestions to options (chips) format
3979
+ var suggestions = data.response.suggestions;
3980
+ var options = suggestions === null || suggestions === void 0 ? void 0 : suggestions.map(function (s) { return ({
3981
+ label: s.title,
3982
+ actionUrl: s.url || "",
3983
+ }); });
3984
+ if (options === null || options === void 0 ? void 0 : options.length) {
3985
+ log("[SSE] Suggestions: ".concat(options.length, " items"));
3986
+ }
3987
+ // Map sources for reference links
3988
+ var sources = data.response.sources;
3989
+ if (sources === null || sources === void 0 ? void 0 : sources.length) {
3990
+ log("[SSE] Sources: ".concat(sources.length, " items"));
3991
+ }
3992
+ var finalMessage_1 = {
3993
+ text: accumulatedText || data.response.response || "",
3994
+ options: options,
3995
+ sources: sources,
3996
+ };
3997
+ // Capture tool calls before flushing (debug mode)
3998
+ var toolCalls_1 = _this.config.debugMode ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
3999
+ _this.flushStreamBuffer().then(function () {
4000
+ _this.dispatchStreamEnd(messageId, finalMessage_1, toolCalls_1);
4001
+ _this.currentToolCalls = []; // Clear after dispatch
4002
+ resolve();
4003
+ });
4004
+ }
4005
+ else {
4006
+ _this.stopStreamThrottle();
4007
+ resolve();
4008
+ }
4009
+ break;
4010
+ case "error":
4011
+ hasError = true;
4012
+ _this.stopTyping();
4013
+ _this.stopStreamThrottle();
4014
+ err("SSE error: ".concat(data.error));
4015
+ // Server-side errors should not retry - reject immediately with the error
4016
+ reject(new Error(data.error || "Server error during processing"));
4017
+ break;
4018
+ }
4019
+ },
4020
+ onerror: function (error) {
4021
+ // Only handle error if this is still the current message
4022
+ if (_this.currentMessageId === messageId && !hasError) {
4023
+ hasError = true;
4024
+ _this.stopStreamThrottle();
4025
+ sseError = error instanceof Error ? error : new Error(String(error));
4026
+ // Get status code if available
4027
+ var statusCode = (error === null || error === void 0 ? void 0 : error.statusCode) || httpStatusCode;
4028
+ if (statusCode) {
4029
+ sseError.statusCode = statusCode;
4030
+ }
4031
+ err("SSE connection error: ".concat(error));
4032
+ }
4033
+ // Throw to stop fetchEventSource from retrying
4034
+ throw error;
4035
+ },
4036
+ }).catch(function (error) {
4037
+ // Handle errors that occur before/outside the SSE connection
4038
+ _this.stopStreamThrottle();
4039
+ if (!hasError && _this.currentMessageId === messageId) {
4040
+ var statusCode = (error === null || error === void 0 ? void 0 : error.statusCode) || httpStatusCode;
4041
+ var wrappedError = error instanceof Error ? error : new Error(String(error));
4042
+ if (statusCode) {
4043
+ wrappedError.statusCode = statusCode;
4044
+ }
4045
+ reject(wrappedError);
4046
+ }
4047
+ else if (sseError) {
4048
+ reject(sseError);
4049
+ }
4050
+ else {
4051
+ reject(error);
4052
+ }
4053
+ });
4054
+ })];
4055
+ });
4056
+ });
4057
+ };
4058
+ MCPChat.prototype.sendTyping = function () { };
4059
+ MCPChat.prototype.setVisitorInfo = function (visitorInfo, sessionId, cb) {
4060
+ this.visitorInfo = visitorInfo;
4061
+ this._attributes = __assign({}, this.visitorInfo.attributes);
4062
+ // do not set currentUrl if localhost
4063
+ var href = new URL(window.location.href);
4064
+ if (href === null || href === void 0 ? void 0 : href.host.toLowerCase().startsWith("localhost")) {
4065
+ this._attributes.isLocal = true;
4066
+ }
4067
+ else {
4068
+ this._attributes.currentUrl = window.location.href;
4069
+ }
4070
+ this._accessToken = this.visitorInfo.accessToken;
4071
+ // This is for the bot
4072
+ this.userJoined(this.getBot(undefined));
4073
+ // Show typing indicator after bot joins if we're still waiting for initial greeting
4074
+ if (this.isInitializingSession) {
4075
+ this.typing();
4076
+ }
4077
+ this.startSession(sessionId);
4078
+ cb();
4079
+ };
4080
+ MCPChat.prototype.sendChatRating = function () { };
4081
+ MCPChat.prototype.sendFile = function (_, cb) {
4082
+ cb(new Error("File upload not supported in MCP mode"));
4083
+ };
4084
+ MCPChat.prototype.markAsRead = function () { };
4085
+ MCPChat.prototype.flush = function () { };
4086
+ MCPChat.prototype.dispose = function () {
4087
+ this.stopStreamThrottle();
4088
+ if (this.abortController) {
4089
+ this.abortController.abort();
4090
+ this.abortController = undefined;
4091
+ }
4092
+ };
4093
+ MCPChat.prototype.sleep = function () {
4094
+ this.stopStreamThrottle();
4095
+ if (this.abortController) {
4096
+ this.abortController.abort();
4097
+ this.abortController = undefined;
4098
+ }
4099
+ };
4100
+ MCPChat.prototype.wakeup = function () { };
4101
+ MCPChat.prototype.startSession = function (sessionId) {
4102
+ if (this.visitorInfo.visitorId) {
4103
+ this._userId = "".concat(this.visitorInfo.visitorId);
4104
+ }
4105
+ else if (this.visitorInfo.email) {
4106
+ this._userId = "mcp-widget-user-".concat(this.visitorInfo.email);
4107
+ }
4108
+ else {
4109
+ this._userId = "mcp-widget-user-".concat(uuid_1());
4110
+ }
4111
+ // Session ID priority:
4112
+ // 1. If explicitly provided via setVisitorInfo, use it
4113
+ // 2. If empty string provided, generate a new session ID
4114
+ // 3. If not provided (undefined) and we have an existing session, keep it
4115
+ if (sessionId) {
4116
+ // Explicit session ID provided - use it
4117
+ this._sessionId = sessionId;
4118
+ log("Using provided session id: ".concat(this._sessionId));
4119
+ }
4120
+ else if (sessionId === "") {
4121
+ // Empty string explicitly passed - generate new session
4122
+ this._sessionId = "mcp-widget-session-".concat(uuid_1());
4123
+ this.dispatch(setSessionId(this._sessionId));
4124
+ log("Using generated session id: ".concat(this._sessionId));
4125
+ }
4126
+ else if (!this._sessionId && this.config.sessionId) {
4127
+ // No existing session and config has one - use config
4128
+ this._sessionId = this.config.sessionId;
4129
+ log("Using config session id: ".concat(this._sessionId));
4130
+ }
4131
+ else if (!this._sessionId) {
4132
+ // No session ID at all - generate one
4133
+ this._sessionId = "mcp-widget-session-".concat(uuid_1());
4134
+ this.dispatch(setSessionId(this._sessionId));
4135
+ log("Using generated session id: ".concat(this._sessionId));
4136
+ }
4137
+ else {
4138
+ log("Keeping existing session id: ".concat(this._sessionId));
4139
+ }
4140
+ this.isNewSession = true;
4141
+ };
4142
+ Object.defineProperty(MCPChat.prototype, "userId", {
4143
+ get: function () {
4144
+ return this._userId;
4145
+ },
4146
+ enumerable: false,
4147
+ configurable: true
4148
+ });
4149
+ Object.defineProperty(MCPChat.prototype, "sessionId", {
4150
+ get: function () {
4151
+ return this._sessionId;
4152
+ },
4153
+ enumerable: false,
4154
+ configurable: true
4155
+ });
4156
+ return MCPChat;
4157
+ }());
4158
+
4159
+ /**
4160
+ * Sends a POST to your STENTOR based server.
4161
+ *
4162
+ * @param data
4163
+ * @param url
4164
+ * @param key
4165
+ * @param signal
4166
+ * @returns
4167
+ */
4168
+ function postMessageToStentor(data, url, key, signal) {
4169
+ return __awaiter$1(this, void 0, void 0, function () {
4170
+ var body, response;
4171
+ return __generator$1(this, function (_a) {
4172
+ switch (_a.label) {
4173
+ case 0:
4174
+ body = JSON.stringify(data);
4175
+ log("URL: ".concat(url));
4176
+ log("BODY: ".concat(body));
4177
+ return [4 /*yield*/, fetch(url, {
4178
+ method: "POST",
4179
+ headers: {
4180
+ "Content-Type": "application/json",
4181
+ "Authorization": "Bearer ".concat(key),
4182
+ },
4183
+ body: body,
4184
+ mode: "cors",
4185
+ signal: signal
4186
+ })];
4187
+ case 1:
4188
+ response = _a.sent();
4189
+ if (!response.ok) {
4190
+ throw new Error("Status ".concat(response.status, ", Text: ").concat(response.statusText));
4191
+ }
4192
+ return [2 /*return*/, response.json()];
4193
+ }
4194
+ });
4195
+ });
4196
+ }
4197
+
4198
+ function convertFromListDisplay(list) {
4199
+ return {
4200
+ type: list.type,
4201
+ title: list.title,
4202
+ items: list.items.map(function (item) {
4203
+ var _a, _b;
4204
+ var responseItem = {
4205
+ title: item.title,
4206
+ subTitle: item.description,
4207
+ token: item.token,
4208
+ url: item.url,
4209
+ hideUrl: item.hideUrl,
4210
+ imageUrl: (_a = item.image) === null || _a === void 0 ? void 0 : _a.url,
4211
+ imageActionUrl: (_b = item.image) === null || _b === void 0 ? void 0 : _b.imageActionUrl
4212
+ };
4213
+ // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
4214
+ var itemButtons = item.buttons;
4215
+ if (itemButtons && itemButtons.length > 0) {
4216
+ responseItem.buttons = itemButtons.map(function (button) { return ({
4217
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4218
+ actionUrl: button.openUrlAction,
4219
+ label: button.title
4220
+ }); });
4221
+ }
4222
+ return responseItem;
4223
+ })
4224
+ };
4225
+ }
4226
+ function convertToListDisplay(list) {
4227
+ return {
4228
+ type: list.type,
4229
+ title: list.title,
4230
+ items: (list.items || []).map(function (item) {
4231
+ var responseItem = {
4232
+ title: item.title,
4233
+ description: item.subTitle,
4234
+ token: item.token,
4235
+ url: item.url,
4236
+ hideUrl: item.hideUrl,
4237
+ image: {
4238
+ url: item.imageUrl,
4239
+ imageActionUrl: item.imageActionUrl,
4240
+ accessibilityText: ""
4241
+ }
4242
+ };
4243
+ // TODO: Remove any when the buttons are defined on the list (ListButton - title + openUrlAction)
4244
+ var itemButtons = item.buttons;
4245
+ if (itemButtons && itemButtons.length > 0) {
4246
+ responseItem.buttons = itemButtons.map(function (button) { return ({
4247
+ openUrlAction: button.actionUrl,
4248
+ title: button.label
4249
+ }); });
4250
+ }
4251
+ return responseItem;
4252
+ })
4253
+ };
4254
+ }
4255
+ function convertFromCardDisplay(card) {
4256
+ return {
4257
+ content: card.content,
4258
+ imageUrl: card.smallImageUrl,
4259
+ title: card.title,
4260
+ imageActionUrl: card === null || card === void 0 ? void 0 : card.imageActionUrl,
4261
+ buttons: card.buttons ? card.buttons.map(function (button) { return ({
4262
+ actionUrl: button.openUrlAction,
4263
+ label: button.title
4264
+ }); }) : undefined
4265
+ };
4266
+ }
4267
+ function getOutput(value) {
4268
+ if (value) {
4269
+ if (typeof value === "string") {
4270
+ return {
4271
+ displayText: value
4272
+ };
4273
+ }
4274
+ else {
4275
+ return value;
4276
+ }
4277
+ }
4278
+ return undefined;
4279
+ }
4280
+ /**
4281
+ * Converts a Stentor Response to a ChatMessageRequest
4282
+ *
4283
+ * @param botResponse
4284
+ * @param now
4285
+ * @returns
4286
+ */
4287
+ function responseToMessage(botResponse, now) {
4288
+ var _a, _b, _c, _d, _e;
4289
+ if (now === void 0) { now = new Date().getTime(); }
4290
+ var responseMessage;
4291
+ if (!botResponse) {
4292
+ return responseMessage;
4293
+ }
4294
+ var text;
4295
+ var html;
4296
+ var endSession;
4297
+ var outputSpeech = getOutput(botResponse.outputSpeech);
4298
+ var reprompt = getOutput(botResponse.reprompt);
4299
+ if (outputSpeech) {
4300
+ text = outputSpeech.displayText;
4301
+ html = outputSpeech.html;
4302
+ }
4303
+ if (botResponse.system === "TRANSFER_CALL") {
4304
+ responseMessage = {
4305
+ type: "handOff",
4306
+ timestamp: now,
4307
+ handoffMessage: text,
4308
+ handoffTarget: (_a = botResponse === null || botResponse === void 0 ? void 0 : botResponse.data) === null || _a === void 0 ? void 0 : _a.transferPhoneNumber,
4309
+ endSession: false,
4310
+ user: undefined
4311
+ };
4312
+ }
4313
+ else if ((_b = botResponse.system) === null || _b === void 0 ? void 0 : _b.startsWith("PERMISSION_")) {
4314
+ responseMessage = getPermissionResponse(botResponse, now);
4315
+ }
4316
+ else {
4317
+ endSession = !reprompt || !(reprompt.displayText || reprompt.ssml);
4318
+ responseMessage = {
4319
+ type: "msg",
4320
+ timestamp: now,
4321
+ msg: {
4322
+ displays: botResponse.displays,
4323
+ 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; })
4324
+ },
4325
+ endSession: endSession,
4326
+ user: undefined
4327
+ };
4328
+ if (text && !html) {
4329
+ responseMessage.msg = __assign(__assign({}, responseMessage.msg), { text: text });
4330
+ }
4331
+ if (html) {
4332
+ responseMessage.msg = __assign(__assign({}, responseMessage.msg), { html: html });
4333
+ }
4334
+ if ((_e = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.suggestions) === null || _e === void 0 ? void 0 : _e.length) {
4335
+ responseMessage.msg.options = outputSpeech.suggestions.map(function (suggestion) {
4336
+ if (typeof suggestion === "string") {
4337
+ // Simple chips (strings)
4338
+ return suggestion;
4339
+ }
4340
+ else {
4341
+ // "call out" chips
4342
+ return {
4343
+ label: suggestion.title,
4344
+ actionUrl: suggestion.url
4345
+ };
4346
+ }
4347
+ });
4348
+ }
4349
+ }
4350
+ return responseMessage;
4351
+ }
4352
+ function getPermissionRequestType(type) {
4353
+ switch (type) {
4354
+ case "PERMISSION_EMAIL":
4355
+ return "EMAIL";
4356
+ case "PERMISSION_LOCATION_PRECISE":
4357
+ return "LOCATION_PRECISE";
4358
+ default:
4359
+ throw new Error("Unsupported permission: ".concat(type));
4360
+ }
4361
+ }
4362
+ function getPermissionResponse(botResponse, now) {
4363
+ var _a;
4364
+ var type = getPermissionRequestType(botResponse.system);
4365
+ var outputSpeech = getOutput(botResponse.outputSpeech);
4366
+ var permissionPrimerAccepted = botResponse.data.permissionPrimerAccepted;
4367
+ var permissionDenied = botResponse.data.permissionDenied;
4368
+ return {
4369
+ type: "permissionRequest",
4370
+ timestamp: now,
4371
+ msg: {
4372
+ text: (_a = outputSpeech === null || outputSpeech === void 0 ? void 0 : outputSpeech.displayText) !== null && _a !== void 0 ? _a : "".concat(botResponse.data.permissionRequestTTSContext),
4373
+ permissionRequest: {
4374
+ time: now,
4375
+ type: type,
4376
+ approve: typeof permissionPrimerAccepted === "object" ? responseToMessage({
4377
+ outputSpeech: permissionPrimerAccepted
4378
+ }).msg : undefined,
4379
+ deny: typeof permissionDenied === "object" ? responseToMessage({
4380
+ outputSpeech: permissionDenied
4381
+ }).msg : undefined
4382
+ }
4383
+ },
4384
+ endSession: false,
4385
+ user: undefined //todo: set
4386
+ };
4387
+ }
4388
+
2882
4389
  var PERMISSION_QUESTION_EXPIRATION_MS$1 = 300000; // 5 minutes
2883
4390
  // interface UserLeaveMessage {
2884
4391
  // readonly user: ChatUserInfo;
@@ -8665,6 +10172,7 @@ var StentorServerChat = /** @class */ (function () {
8665
10172
  }());
8666
10173
 
8667
10174
  function createChatServerCore(config, options) {
10175
+ var _a, _b, _c, _d, _e;
8668
10176
  switch (config.type) {
8669
10177
  case "direct":
8670
10178
  return new StentorDirectChat({
@@ -8680,6 +10188,15 @@ function createChatServerCore(config, options) {
8680
10188
  return new StentorRouterChat({
8681
10189
  url: config.serverUrl,
8682
10190
  }, options);
10191
+ case "mcp":
10192
+ return new MCPChat({
10193
+ serverUrl: config.serverUrl,
10194
+ mode: (_a = config.mcp) === null || _a === void 0 ? void 0 : _a.mode,
10195
+ authToken: (_b = config.mcp) === null || _b === void 0 ? void 0 : _b.authToken,
10196
+ debugMode: (_c = config.mcp) === null || _c === void 0 ? void 0 : _c.debugMode,
10197
+ pageContextOverride: (_d = config.mcp) === null || _d === void 0 ? void 0 : _d.pageContextOverride,
10198
+ showAiBadge: (_e = config.mcp) === null || _e === void 0 ? void 0 : _e.showAiBadge,
10199
+ }, options);
8683
10200
  case "local":
8684
10201
  return new StentorLocalChat();
8685
10202
  default:
@@ -8833,6 +10350,79 @@ var DrawerBars = function (props) {
8833
10350
  return (require$$0.jsx("button", { className: "drawer-bars", tabIndex: props.tabIndex ? Number(props.tabIndex) : 0, "aria-label": "open menu", "aria-hidden": false, onClick: props.onToggle, children: getBars() }));
8834
10351
  };
8835
10352
 
10353
+ /**
10354
+ * Type guard for ChatMsgDetail.
10355
+ * Use this to safely narrow ChatDetail to ChatMsgDetail.
10356
+ */
10357
+ function isChatMsgDetail(chat) {
10358
+ return chat.type === "chat.msg";
10359
+ }
10360
+
10361
+ /**
10362
+ * Formats a suggestion chip for export
10363
+ */
10364
+ function formatSuggestion(option) {
10365
+ if (isChatServerActionLink(option)) {
10366
+ return option.actionUrl ? "[".concat(option.label, "](").concat(option.actionUrl, ")") : option.label;
10367
+ }
10368
+ return option;
10369
+ }
10370
+ /**
10371
+ * Formats chat messages for export
10372
+ */
10373
+ function formatConversation(chats) {
10374
+ return chats
10375
+ .filter(isChatMsgDetail)
10376
+ .map(function (chat) {
10377
+ var _a, _b, _c;
10378
+ var isVisitor = (_a = chat.user.nick) === null || _a === void 0 ? void 0 : _a.startsWith("visitor:");
10379
+ var role = isVisitor ? "User" : "Assistant";
10380
+ var text = ((_b = chat.msg) === null || _b === void 0 ? void 0 : _b.text) || "";
10381
+ var result = "".concat(role, ": ").concat(text);
10382
+ // Add suggestion chips if present
10383
+ var options = (_c = chat.msg) === null || _c === void 0 ? void 0 : _c.options;
10384
+ if (options && options.length > 0) {
10385
+ var suggestions = options.map(formatSuggestion).join(", ");
10386
+ result += "\nSuggestions: ".concat(suggestions);
10387
+ }
10388
+ return result;
10389
+ })
10390
+ .join("\n");
10391
+ }
10392
+ var ExportButton = function (props) {
10393
+ var chats = reactRedux.useSelector(function (state) { return state.chats; });
10394
+ var handleClick = function () { return __awaiter$1(void 0, void 0, void 0, function () {
10395
+ var conversation, textarea;
10396
+ return __generator$1(this, function (_b) {
10397
+ switch (_b.label) {
10398
+ case 0:
10399
+ conversation = formatConversation(chats);
10400
+ _b.label = 1;
10401
+ case 1:
10402
+ _b.trys.push([1, 3, , 4]);
10403
+ return [4 /*yield*/, navigator.clipboard.writeText(conversation)];
10404
+ case 2:
10405
+ _b.sent();
10406
+ return [3 /*break*/, 4];
10407
+ case 3:
10408
+ _b.sent();
10409
+ // Fallback for older browsers using deprecated execCommand
10410
+ // Note: execCommand is deprecated and may be removed in future browsers
10411
+ console.warn("[ExportButton] Using deprecated execCommand fallback for clipboard copy");
10412
+ textarea = document.createElement("textarea");
10413
+ textarea.value = conversation;
10414
+ document.body.appendChild(textarea);
10415
+ textarea.select();
10416
+ document.execCommand("copy");
10417
+ document.body.removeChild(textarea);
10418
+ return [3 /*break*/, 4];
10419
+ case 4: return [2 /*return*/];
10420
+ }
10421
+ });
10422
+ }); };
10423
+ return (require$$0.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 }));
10424
+ };
10425
+
8836
10426
  var MenuButton = function (props) {
8837
10427
  return (require$$0.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 }));
8838
10428
  };
@@ -8976,7 +10566,7 @@ var closeButtonAriaLabel = "To close widget click on close icon in top right sid
8976
10566
  var ChatHeader = function (props) {
8977
10567
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
8978
10568
  var innerDispatch = useChatDispatch();
8979
- var menuConfig = props.menuConfig, onSubmit = props.onSubmit;
10569
+ var menuConfig = props.menuConfig, onSubmit = props.onSubmit, debugMode = props.debugMode;
8980
10570
  var _r = require$$1.useState(false), drawerOpen = _r[0], setDrawerState = _r[1]; // false initially
8981
10571
  var menuPosition = (menuConfig === null || menuConfig === void 0 ? void 0 : menuConfig.menuButtonLocation) || "FOOTER";
8982
10572
  var showMenuLeft = menuPosition === "HEADER_LEFT";
@@ -9015,7 +10605,7 @@ var ChatHeader = function (props) {
9015
10605
  ? "status-text-positionLeftNoAvatar"
9016
10606
  : "status-text-positionLeft", "\n ").concat(((_d = props.config) === null || _d === void 0 ? void 0 : _d.alignTextCenter)
9017
10607
  ? "status-text-positionCenter"
9018
- : "", " \n "), children: [require$$0.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) && (require$$0.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) }))] }), require$$0.jsxs(ButtonGroup, { children: [hasRightMenu && (require$$0.jsx(MenuButton, { onClick: toggleDrawer, tabIndex: menuButtonTabIndex })), props.canRefresh && (require$$0.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 && (require$$0.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 && (require$$0.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 ? (require$$0.jsx("div", { className: "xa-chat-menu-container", children: require$$0.jsx(ChatMenu, { openFrom: showMenuRight ? "right" : "left", opened: drawerOpen, tabIndex: menuItemsTabIndex, onItemClick: handleMenuItem, items: menuItems }) })) : (require$$0.jsx(require$$0.Fragment, {}))] }));
10608
+ : "", " \n "), children: [require$$0.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) && (require$$0.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) }))] }), require$$0.jsxs(ButtonGroup, { children: [hasRightMenu && (require$$0.jsx(MenuButton, { onClick: toggleDrawer, tabIndex: menuButtonTabIndex })), debugMode && require$$0.jsx(ExportButton, {}), props.canRefresh && (require$$0.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 && (require$$0.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 && (require$$0.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 ? (require$$0.jsx("div", { className: "xa-chat-menu-container", children: require$$0.jsx(ChatMenu, { openFrom: showMenuRight ? "right" : "left", opened: drawerOpen, tabIndex: menuItemsTabIndex, onItemClick: handleMenuItem, items: menuItems }) })) : (require$$0.jsx(require$$0.Fragment, {}))] }));
9019
10609
  };
9020
10610
 
9021
10611
  var UnknownMessage = function () { return require$$0.jsx(require$$0.Fragment, {}); };
@@ -9029,7 +10619,8 @@ var ChatMessagePart = function (props) {
9029
10619
  var containerClass = "xappw-chat-msg-part" +
9030
10620
  (position === "below" ? " xappw-chat-msg-part--avatar-below" : "") +
9031
10621
  (position === "bottom" ? " xappw-chat-msg-part--avatar-bottom" : "") +
9032
- (props.fullWidth ? " xappw-chat-msg-part--full-width" : "");
10622
+ (props.fullWidth ? " xappw-chat-msg-part--full-width" : "") +
10623
+ (props.isStreaming ? " xappw-chat-msg-part--streaming" : "");
9033
10624
  var user = props.user, hideUserInfo = props.hideUserInfo;
9034
10625
  // Hide user info if hideUserInfo is true and position is "bottom"
9035
10626
  var shouldHideUserInfo = hideUserInfo && position === "bottom";
@@ -9527,12 +11118,169 @@ var ChatPermissionMessage = function (props) {
9527
11118
  return (require$$0.jsxs("div", { className: "chat-msg", children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + props.time + (agentMessage ? " the bot said" : " the user said") }), require$$0.jsxs("div", { className: "buttons-container", children: [require$$0.jsx(ActionButton, { label: allowLabel, addClass: "button", onClick: handleAllow }), require$$0.jsx(ActionButton, { label: denyLabel, addClass: "button", onClick: handleDeny })] })] }));
9528
11119
  };
9529
11120
 
11121
+ var CopyButton = function (_a) {
11122
+ var text = _a.text;
11123
+ var _b = require$$1.useState(false), copied = _b[0], setCopied = _b[1];
11124
+ var handleCopy = function (e) { return __awaiter$1(void 0, void 0, void 0, function () {
11125
+ var textarea;
11126
+ return __generator$1(this, function (_b) {
11127
+ switch (_b.label) {
11128
+ case 0:
11129
+ e.stopPropagation();
11130
+ _b.label = 1;
11131
+ case 1:
11132
+ _b.trys.push([1, 3, , 4]);
11133
+ return [4 /*yield*/, navigator.clipboard.writeText(text)];
11134
+ case 2:
11135
+ _b.sent();
11136
+ setCopied(true);
11137
+ setTimeout(function () { return setCopied(false); }, 1500);
11138
+ return [3 /*break*/, 4];
11139
+ case 3:
11140
+ _b.sent();
11141
+ textarea = document.createElement("textarea");
11142
+ textarea.value = text;
11143
+ document.body.appendChild(textarea);
11144
+ textarea.select();
11145
+ document.execCommand("copy");
11146
+ document.body.removeChild(textarea);
11147
+ setCopied(true);
11148
+ setTimeout(function () { return setCopied(false); }, 1500);
11149
+ return [3 /*break*/, 4];
11150
+ case 4: return [2 /*return*/];
11151
+ }
11152
+ });
11153
+ }); };
11154
+ return (require$$0.jsx("button", { className: "tool-call-item__copy-btn", onClick: handleCopy, title: "Copy to clipboard", children: copied ? "✓" : "⧉" }));
11155
+ };
11156
+ /**
11157
+ * Gets the display text for a tool call, using fallback chain:
11158
+ * label -> displayName -> toolName -> "Working..."
11159
+ */
11160
+ var getDisplayLabel = function (toolCall) {
11161
+ var _a, _b, _c;
11162
+ 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...";
11163
+ };
11164
+ /**
11165
+ * User-friendly tool progress item (non-debug mode)
11166
+ * Shows just the label with a spinner or checkmark
11167
+ */
11168
+ var ToolProgressItem = function (_a) {
11169
+ var toolCall = _a.toolCall;
11170
+ var label = getDisplayLabel(toolCall);
11171
+ return (require$$0.jsxs("div", { className: "tool-progress-item ".concat(toolCall.isExecuting ? "tool-progress-item--executing" : "", " ").concat(toolCall.error ? "tool-progress-item--error" : ""), children: [require$$0.jsx("span", { className: "tool-progress-item__icon", children: toolCall.isExecuting ? (require$$0.jsx("span", { className: "tool-progress-item__spinner", "aria-label": "executing" })) : toolCall.error ? (require$$0.jsx("span", { className: "tool-progress-item__status tool-progress-item__status--error", children: "!" })) : (require$$0.jsx("span", { className: "tool-progress-item__status tool-progress-item__status--success", children: "\u2713" })) }), require$$0.jsx("span", { className: "tool-progress-item__label", children: label })] }));
11172
+ };
11173
+ /**
11174
+ * Debug mode tool call item with expandable details
11175
+ * Shows label, tool name, and expandable input/output
11176
+ */
11177
+ var ToolCallItem = function (_a) {
11178
+ var toolCall = _a.toolCall;
11179
+ var _b = require$$1.useState(false), isExpanded = _b[0], setIsExpanded = _b[1];
11180
+ var toggleExpanded = function () { return setIsExpanded(!isExpanded); };
11181
+ var formatJson = function (data) {
11182
+ if (data === undefined || data === null)
11183
+ return "";
11184
+ try {
11185
+ // If it's a string, try to parse it as JSON first (server may send stringified JSON)
11186
+ if (typeof data === "string") {
11187
+ try {
11188
+ var parsed = JSON.parse(data);
11189
+ return JSON.stringify(parsed, null, 2);
11190
+ }
11191
+ catch (_a) {
11192
+ // Not valid JSON, return as-is
11193
+ return data;
11194
+ }
11195
+ }
11196
+ return JSON.stringify(data, null, 2);
11197
+ }
11198
+ catch (_b) {
11199
+ return String(data);
11200
+ }
11201
+ };
11202
+ var label = getDisplayLabel(toolCall);
11203
+ var duration = toolCall.endTime
11204
+ ? "".concat(((toolCall.endTime - toolCall.startTime) / 1000).toFixed(2), "s")
11205
+ : null;
11206
+ var hasInput = toolCall.input !== undefined && toolCall.input !== null;
11207
+ var hasResult = toolCall.result !== undefined && toolCall.result !== null;
11208
+ var hasError = !!toolCall.error;
11209
+ return (require$$0.jsxs("div", { className: "tool-call-item ".concat(toolCall.isExecuting ? "tool-call-item--executing" : ""), children: [require$$0.jsxs("button", { className: "tool-call-item__header", onClick: toggleExpanded, "aria-expanded": isExpanded, children: [require$$0.jsx("span", { className: "tool-call-item__icon", children: toolCall.isExecuting ? (require$$0.jsx("span", { className: "tool-call-item__spinner", "aria-label": "executing" })) : toolCall.error ? (require$$0.jsx("span", { className: "tool-call-item__status tool-call-item__status--error", children: "!" })) : (require$$0.jsx("span", { className: "tool-call-item__status tool-call-item__status--success", children: "\u2713" })) }), require$$0.jsxs("span", { className: "tool-call-item__info", children: [require$$0.jsx("span", { className: "tool-call-item__label", children: label }), require$$0.jsx("span", { className: "tool-call-item__name", children: toolCall.name })] }), duration && require$$0.jsx("span", { className: "tool-call-item__duration", children: duration }), require$$0.jsx("span", { className: "tool-call-item__chevron ".concat(isExpanded ? "tool-call-item__chevron--expanded" : ""), children: "\u25BE" })] }), isExpanded && (require$$0.jsxs("div", { className: "tool-call-item__content", children: [hasInput && (require$$0.jsxs("div", { className: "tool-call-item__section", children: [require$$0.jsxs("div", { className: "tool-call-item__section-header", children: [require$$0.jsx("span", { className: "tool-call-item__section-label", children: "Input" }), require$$0.jsx(CopyButton, { text: formatJson(toolCall.input) })] }), require$$0.jsx("pre", { className: "tool-call-item__code", children: formatJson(toolCall.input) })] })), hasResult && (require$$0.jsxs("div", { className: "tool-call-item__section", children: [require$$0.jsxs("div", { className: "tool-call-item__section-header", children: [require$$0.jsx("span", { className: "tool-call-item__section-label", children: "Output" }), require$$0.jsx(CopyButton, { text: formatJson(toolCall.result) })] }), require$$0.jsx("pre", { className: "tool-call-item__code", children: formatJson(toolCall.result) })] })), hasError && (require$$0.jsxs("div", { className: "tool-call-item__section", children: [require$$0.jsxs("div", { className: "tool-call-item__section-header", children: [require$$0.jsx("span", { className: "tool-call-item__section-label tool-call-item__section-label--error", children: "Error" }), require$$0.jsx(CopyButton, { text: toolCall.error || "" })] }), require$$0.jsx("pre", { className: "tool-call-item__code tool-call-item__code--error", children: toolCall.error })] })), !hasInput && !hasResult && !hasError && (require$$0.jsx("div", { className: "tool-call-item__empty", children: "No data available" }))] }))] }));
11210
+ };
11211
+ var ToolCallDisplay = function (_a) {
11212
+ var toolCalls = _a.toolCalls, _b = _a.debugMode, debugMode = _b === void 0 ? false : _b;
11213
+ if (!toolCalls || toolCalls.length === 0) {
11214
+ return null;
11215
+ }
11216
+ // Debug mode: show all tools with full expandable details
11217
+ if (debugMode) {
11218
+ return (require$$0.jsxs("div", { className: "tool-call-display", children: [require$$0.jsxs("div", { className: "tool-call-display__header", children: [require$$0.jsx("span", { className: "tool-call-display__icon", children: "\u2699" }), require$$0.jsxs("span", { className: "tool-call-display__title", children: ["Tool Calls (", toolCalls.length, ")"] })] }), require$$0.jsx("div", { className: "tool-call-display__list", children: toolCalls.map(function (toolCall, index) { return (require$$0.jsx(ToolCallItem, { toolCall: toolCall }, "".concat(toolCall.name, "-").concat(toolCall.startTime, "-").concat(index))); }) })] }));
11219
+ }
11220
+ // Normal mode: filter out hidden tools and show user-friendly progress
11221
+ var visibleToolCalls = toolCalls.filter(function (tc) { return !tc.hidden; });
11222
+ if (visibleToolCalls.length === 0) {
11223
+ return null;
11224
+ }
11225
+ return (require$$0.jsx("div", { className: "tool-progress-display", children: visibleToolCalls.map(function (toolCall, index) { return (require$$0.jsx(ToolProgressItem, { toolCall: toolCall }, "".concat(toolCall.name, "-").concat(toolCall.startTime, "-").concat(index))); }) }));
11226
+ };
11227
+
11228
+ /**
11229
+ * Parses basic markdown to HTML.
11230
+ * Supports: bold, italic, links, headers, and unordered lists.
11231
+ */
11232
+ function parseMarkdown(text) {
11233
+ if (!text)
11234
+ return "";
11235
+ var html = text
11236
+ // Escape HTML entities first
11237
+ .replace(/&/g, "&amp;")
11238
+ .replace(/</g, "&lt;")
11239
+ .replace(/>/g, "&gt;")
11240
+ // Headers (must be at start of line)
11241
+ .replace(/^### (.+)$/gm, "<h4>$1</h4>")
11242
+ .replace(/^## (.+)$/gm, "<h3>$1</h3>")
11243
+ .replace(/^# (.+)$/gm, "<h2>$1</h2>")
11244
+ // Bold (** or __)
11245
+ .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
11246
+ .replace(/__(.+?)__/g, "<strong>$1</strong>")
11247
+ // Italic (* or _) - must come after bold
11248
+ .replace(/\*([^*]+?)\*/g, "<em>$1</em>")
11249
+ .replace(/_([^_]+?)_/g, "<em>$1</em>")
11250
+ // Links [text](url)
11251
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
11252
+ // Unordered lists (- or *)
11253
+ .replace(/^[-*] (.+)$/gm, "<li>$1</li>")
11254
+ // Line breaks
11255
+ .replace(/\n/g, "<br>");
11256
+ // Wrap consecutive <li> elements in <ul>
11257
+ html = html.replace(/(<li>.*?<\/li>)(<br>)?/g, "$1");
11258
+ html = html.replace(/(<li>.*?<\/li>)+/g, "<ul>$&</ul>");
11259
+ // Clean up <br> inside <ul>
11260
+ html = html.replace(/<ul>(.*?)<\/ul>/g, function (match) {
11261
+ return match.replace(/<br>/g, "");
11262
+ });
11263
+ return html;
11264
+ }
9530
11265
  var ChatTextMessage = function (props) {
11266
+ var _a;
9531
11267
  var message = props.message;
9532
11268
  var date = new Date(message.timestamp);
9533
11269
  var time = date.getHours() + ":" + date.getMinutes();
9534
- var agentMessage = isAgent(props.message.user.nick);
9535
- return (require$$0.jsx("div", { className: "chat-msg", children: require$$0.jsx("div", { className: "chat-text-container", children: require$$0.jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: agentMessage && !props.sibling, children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? " the bot said" : " the user said") }), require$$0.jsx("span", { children: message.msg.text })] }) }) }));
11270
+ var agentMessage = isAgent((_a = props.message.user) === null || _a === void 0 ? void 0 : _a.nick);
11271
+ var isStreaming = message.isStreaming;
11272
+ // Parse markdown for agent messages
11273
+ var parsedHtml = require$$1.useMemo(function () {
11274
+ if (agentMessage && message.msg.text) {
11275
+ return parseMarkdown(message.msg.text);
11276
+ }
11277
+ return null;
11278
+ }, [agentMessage, message.msg.text]);
11279
+ // No tail for streaming messages (or messages that were streamed)
11280
+ var wasStreamed = message.wasStreamed;
11281
+ var showTail = agentMessage && !props.sibling && !isStreaming && !wasStreamed;
11282
+ var hasToolCalls = props.toolCalls && props.toolCalls.length > 0;
11283
+ return (require$$0.jsxs("div", { className: "chat-msg".concat(isStreaming ? " chat-msg--streaming" : ""), children: [hasToolCalls && (require$$0.jsx(ToolCallDisplay, { toolCalls: props.toolCalls, debugMode: props.debugMode })), require$$0.jsx("div", { className: "chat-text-container", children: require$$0.jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: showTail, children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? (isStreaming ? " the bot is typing" : " the bot said") : " the user said") }), parsedHtml ? (require$$0.jsx("span", { className: "chat-msg__content", dangerouslySetInnerHTML: { __html: parsedHtml } })) : (require$$0.jsx("span", { className: "chat-msg__content", children: message.msg.text })), isStreaming && require$$0.jsx("span", { className: "streaming-cursor", "aria-hidden": "true" })] }) })] }));
9536
11284
  };
9537
11285
 
9538
11286
  var ChatScheduleWidget = function (props) {
@@ -9546,6 +11294,24 @@ var ChatScheduleWidget = function (props) {
9546
11294
  return (require$$0.jsx("div", { className: "chat-schedule-button-container", children: require$$0.jsxs("button", { className: "chat-schedule-button", onClick: handleClick, children: [require$$0.jsx("i", { className: "fa fa-lg fa-calendar" }), require$$0.jsx("span", { children: display.label || "Schedule Now!" })] }) }));
9547
11295
  };
9548
11296
 
11297
+ /**
11298
+ * Displays source links below a chat message.
11299
+ * Designed to be compact and unobtrusive, unlike suggestion chips.
11300
+ */
11301
+ var SourceLinks = function (props) {
11302
+ var sources = props.sources, _a = props.prefix, prefix = _a === void 0 ? "Sources:" : _a, onSourceClick = props.onSourceClick;
11303
+ if (!sources || sources.length === 0) {
11304
+ return null;
11305
+ }
11306
+ var handleClick = function (source, e) {
11307
+ if (onSourceClick) {
11308
+ e.preventDefault();
11309
+ onSourceClick(source);
11310
+ }
11311
+ };
11312
+ return (require$$0.jsxs("div", { className: "source-links", children: [prefix && require$$0.jsx("span", { className: "source-links__prefix", children: prefix }), require$$0.jsx("span", { className: "source-links__list", children: sources.map(function (source, index) { return (require$$0.jsxs("span", { className: "source-links__item", children: [require$$0.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 && (require$$0.jsx("span", { className: "source-links__separator", children: "\u00B7" }))] }, index)); }) })] }));
11313
+ };
11314
+
9549
11315
  function getClassName(msg) {
9550
11316
  return isAgent(msg.user.nick) ? "agent" : "visitor";
9551
11317
  }
@@ -9557,7 +11323,7 @@ var avaKeys = ["text", "html", "card", "list"];
9557
11323
  * @returns
9558
11324
  */
9559
11325
  var ChatMessage = function (props) {
9560
- var _a;
11326
+ var _a, _b, _c;
9561
11327
  var middleware = props.messageMiddleware || StandardMiddlewares;
9562
11328
  var chatConfig = require$$1.useContext(ChatConfigContext);
9563
11329
  // console.log(`########### chatConfig: ${JSON.stringify(chatConfig, null, 2)}`);
@@ -9567,7 +11333,7 @@ var ChatMessage = function (props) {
9567
11333
  var agentInfo = (_a = props.agents) === null || _a === void 0 ? void 0 : _a[props.message.user.nick];
9568
11334
  var hideUserInfo = (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.hideUserInfo) || false;
9569
11335
  function renderByType() {
9570
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
11336
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
9571
11337
  var msg = props.message.msg;
9572
11338
  switch (props.message.type) {
9573
11339
  // TODO: props actually requires it to be "chat.msg". Fix prop typing?
@@ -9575,27 +11341,42 @@ var ChatMessage = function (props) {
9575
11341
  // Here is the deal. If we have text (output speech), then text - card - list - options
9576
11342
  // OR card OR list only. Avatar with text bubble.
9577
11343
  var avaKey = avaKeys.find(function (key) { return !!msg[key]; });
9578
- return (require$$0.jsxs(require$$0.Fragment, { children: [msg.text &&
9579
- require$$0.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: require$$0.jsx(ChatTextMessage, { message: props.message, sibling: props.sibling }) }), msg.html &&
9580
- require$$0.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: require$$0.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) {
11344
+ var isStreaming_1 = props.message.isStreaming;
11345
+ var wasStreamed = props.message.wasStreamed;
11346
+ // Tool calls - show progress while tools execute (before text arrives)
11347
+ var toolCalls = props.message.toolCalls;
11348
+ var hasToolCalls = toolCalls && toolCalls.length > 0;
11349
+ 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;
11350
+ // Show tool progress with avatar when no text yet, or above text when text exists
11351
+ var showToolProgressWithAvatar = hasToolCalls && !msg.text;
11352
+ return (require$$0.jsxs(require$$0.Fragment, { children: [showToolProgressWithAvatar && (require$$0.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: require$$0.jsx("div", { className: "chat-msg", children: require$$0.jsx(ToolCallDisplay, { toolCalls: toolCalls, debugMode: debugMode }) }) })), msg.text && (require$$0.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: require$$0.jsx(ChatTextMessage, { message: props.message, sibling: props.sibling, user: user, toolCalls: hasToolCalls ? toolCalls : undefined, debugMode: debugMode }) })), msg.html &&
11353
+ require$$0.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: require$$0.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) {
9581
11354
  if (display.type === "ScheduleButton") {
9582
11355
  return (require$$0.jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }));
9583
11356
  }
9584
11357
  var Middleware = middleware;
9585
11358
  return (require$$0.jsx(Middleware, { msg: display, ctx: props.middlewareContext }, index));
9586
11359
  }), msg.permissionRequest && ctx &&
9587
- require$$0.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: require$$0.jsx(ChatPermissionMessage, { message: props.message, sibling: props.sibling, ctx: ctx }) })] }));
11360
+ require$$0.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: require$$0.jsx(ChatPermissionMessage, { message: props.message, sibling: props.sibling, ctx: ctx }) })] }));
9588
11361
  }
9589
11362
  return (require$$0.jsx(require$$0.Fragment, {}));
9590
11363
  }
9591
- function renderTimestamp() {
11364
+ function renderTimestampAndSources() {
11365
+ var _a;
9592
11366
  var timestamp = props.message.timestamp;
9593
11367
  var ts = new Date(timestamp);
9594
11368
  var timeAgo = getTimeAgo(ts);
9595
- return (require$$0.jsx("div", { className: "chat-msg-timestamp ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor"), children: require$$0.jsx("span", { children: timeAgo }) }));
9596
- }
11369
+ var sources = (_a = props.message.msg) === null || _a === void 0 ? void 0 : _a.sources;
11370
+ var hasSources = sources && sources.length > 0;
11371
+ var isAgentMessage = isAgent(props.message.user.nick);
11372
+ return (require$$0.jsxs("div", { className: "chat-msg-footer ".concat(isAgentMessage ? "agent" : "visitor"), children: [require$$0.jsx("span", { className: "chat-msg-timestamp", children: timeAgo }), hasSources && (require$$0.jsx(SourceLinks, { sources: sources.map(function (s) { return ({ title: s.title, url: s.url, newTab: s.newTab }); }) }))] }));
11373
+ }
11374
+ // Don't show timestamp/sources while streaming or if message has no content yet
11375
+ var isStreaming = props.message.isStreaming;
11376
+ 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));
11377
+ var showFooter = !isStreaming && hasContent;
9597
11378
  // empty
9598
- return (require$$0.jsx("div", { className: "chat-msg-container-wrapper ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor", " ").concat(props.sibling ? "sibling" : ""), children: require$$0.jsxs("div", { className: "chat-msg-container ".concat(getClassName(props.message)), children: [renderByType(), renderTimestamp()] }) }));
11379
+ return (require$$0.jsx("div", { className: "chat-msg-container-wrapper ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor", " ").concat(props.sibling ? "sibling" : ""), children: require$$0.jsxs("div", { className: "chat-msg-container ".concat(getClassName(props.message)), children: [renderByType(), showFooter && renderTimestampAndSources()] }) }));
9599
11380
  };
9600
11381
 
9601
11382
  /**
@@ -33547,7 +35328,7 @@ var ChatWidgetConnected = function (props) {
33547
35328
  * Exported for use by StaticChatWidgetContainer to avoid infinite loops.
33548
35329
  */
33549
35330
  var ChatWidgetUI = function (props) {
33550
- 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;
35331
+ 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;
33551
35332
  var innerDispatch = useChatDispatch();
33552
35333
  var dispatch = useChatServerDispatch();
33553
35334
  // From Redux
@@ -33577,12 +35358,12 @@ var ChatWidgetUI = function (props) {
33577
35358
  chatState.visuals = {};
33578
35359
  }
33579
35360
  // Our state - pull from storage
33580
- var _10 = require$$1.useState((!canMinimize && !canCancel) ||
35361
+ var _15 = require$$1.useState((!canMinimize && !canCancel) ||
33581
35362
  // !!get("visible") ||
33582
35363
  chatState.visuals.visible ||
33583
35364
  (((_m = props.config) === null || _m === void 0 ? void 0 : _m.autoOpenOnWidth) &&
33584
- window.matchMedia("(min-width: ".concat((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth, ")")).matches)), visible = _10[0], setVisibleState = _10[1];
33585
- var _11 = require$$1.useState(false); _11[0]; var setTypingState = _11[1]; // false initially - state kept for potential external observers
35365
+ window.matchMedia("(min-width: ".concat((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth, ")")).matches)), visible = _15[0], setVisibleState = _15[1];
35366
+ var _16 = require$$1.useState(false); _16[0]; var setTypingState = _16[1]; // false initially - state kept for potential external observers
33586
35367
  // Ref to track typing state for use in timeout callbacks
33587
35368
  var typingRef = require$$1.useRef(false);
33588
35369
  // Timeout ref for debouncing "stop typing" events
@@ -33628,7 +35409,7 @@ var ChatWidgetUI = function (props) {
33628
35409
  };
33629
35410
  // eslint-disable-next-line react-hooks/exhaustive-deps
33630
35411
  }, []);
33631
- var _12 = require$$1.useState(!document.hidden), isTabVisible = _12[0], setIsTabVisible = _12[1];
35412
+ var _17 = require$$1.useState(!document.hidden), isTabVisible = _17[0], setIsTabVisible = _17[1];
33632
35413
  require$$1.useEffect(function () {
33633
35414
  var handleVisibilityChange = function () {
33634
35415
  setIsTabVisible(!document.hidden);
@@ -33840,8 +35621,8 @@ var ChatWidgetUI = function (props) {
33840
35621
  }));
33841
35622
  }
33842
35623
  // Action Bar state and handlers
33843
- var _13 = require$$1.useState(false), formModalOpen = _13[0], setFormModalOpen = _13[1];
33844
- var _14 = require$$1.useState(null), activeFormConfig = _14[0], setActiveFormConfig = _14[1];
35624
+ var _18 = require$$1.useState(false), formModalOpen = _18[0], setFormModalOpen = _18[1];
35625
+ var _19 = require$$1.useState(null), activeFormConfig = _19[0], setActiveFormConfig = _19[1];
33845
35626
  var handleFormButtonClick = require$$1.useCallback(function (formButton) {
33846
35627
  // If form-widget is configured, use its openForm API
33847
35628
  // The form-widget script should already be pre-loaded
@@ -33955,7 +35736,9 @@ var ChatWidgetUI = function (props) {
33955
35736
  avatarPath: config.avatarUrl,
33956
35737
  display_name: "Agent",
33957
35738
  };
33958
- return (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarEnabled ? " widget-container--with-action-bar" : ""), children: [require$$0.jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), require$$0.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 }), require$$0.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 }), require$$0.jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: require$$0.jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && require$$0.jsx(ServerOffline, {}), chatState.wsButton && visible && (require$$0.jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), require$$0.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 }), require$$0.jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: require$$0.jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarEnabled && config.actionBar ? (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsx(ActionBar, { config: config.actionBar, visible: visible, chatDisabled: config.disabled, onChatClick: chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick }), formModalOpen && activeFormConfig && (require$$0.jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (require$$0.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 })), require$$0.jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
35739
+ return (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarEnabled ? " widget-container--with-action-bar" : ""), children: [require$$0.jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), require$$0.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 }), require$$0.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 }), require$$0.jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: require$$0.jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && require$$0.jsx(ServerOffline, {}), chatState.wsButton && visible && (require$$0.jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), require$$0.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 }), require$$0.jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: require$$0.jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarEnabled && config.actionBar ? (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsx(ActionBar, { config: __assign(__assign({}, config.actionBar), {
35740
+ // Use actionBar.cta if set, otherwise fall back to widget-level cta
35741
+ 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 && (require$$0.jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (require$$0.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 })), require$$0.jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
33959
35742
  };
33960
35743
  /**
33961
35744
  * Top-level wrapper that dispatches between preview mode and connected mode.
@@ -34137,6 +35920,7 @@ function memberLeave(state, detail) {
34137
35920
  }
34138
35921
 
34139
35922
  function resetReducer(state) {
35923
+ var _a;
34140
35924
  if (state === void 0) { state = DEFAULT_STATE; }
34141
35925
  var defaultState = createDefaultState({
34142
35926
  accessToken: state.accessToken,
@@ -34146,6 +35930,8 @@ function resetReducer(state) {
34146
35930
  });
34147
35931
  // If we have an active connection, preserve status to avoid showing offline during reconnect
34148
35932
  var wasOnline = state.connection.connectionStatus === 'online';
35933
+ // Preserve opened state so widget doesn't close on refresh
35934
+ var wasOpened = (_a = state.visuals) === null || _a === void 0 ? void 0 : _a.opened;
34149
35935
  return __assign(__assign({}, defaultState), { connection: __assign(__assign({}, defaultState.connection), { greetingRequested: false,
34150
35936
  // Generate new nonce to trigger server recreation and start fresh session
34151
35937
  nonce: uuid_1(),
@@ -34153,8 +35939,11 @@ function resetReducer(state) {
34153
35939
  connectionStatus: wasOnline ? 'online' : defaultState.connection.connectionStatus }),
34154
35940
  // Preserve accountStatus if we were online to avoid showing offline message during reconnect
34155
35941
  accountStatus: wasOnline ? 'online' : defaultState.accountStatus, visitor: state.visitor, visitorId: state.visitorId,
34156
- // Explicitly reset visuals to clear hasInteracted flag for widget refresh
34157
- visuals: {} });
35942
+ // Reset visuals but preserve opened state so widget stays open during refresh
35943
+ visuals: {
35944
+ opened: wasOpened,
35945
+ hasInteracted: false
35946
+ } });
34158
35947
  }
34159
35948
 
34160
35949
  // Type guard for ChatSystemMessageDetail
@@ -34170,7 +35959,7 @@ function appendMessageToState(state, msg) {
34170
35959
  }
34171
35960
  function update(state, action) {
34172
35961
  var _a, _b, _c, _d;
34173
- var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
35962
+ var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
34174
35963
  if (state === void 0) { state = DEFAULT_STATE; }
34175
35964
  log("action", action);
34176
35965
  if (action.type === "reset") {
@@ -34278,6 +36067,121 @@ function update(state, action) {
34278
36067
  case "chat.typing":
34279
36068
  var agent = state.agents[action.detail.user.nick];
34280
36069
  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)) });
36070
+ case "chat.stream.start":
36071
+ // Add placeholder message with streaming state
36072
+ var streamingMsg = {
36073
+ type: "chat.msg",
36074
+ user: action.detail.user,
36075
+ timestamp: action.detail.timestamp,
36076
+ msg: {
36077
+ text: "",
36078
+ },
36079
+ isStreaming: true,
36080
+ };
36081
+ return __assign(__assign({}, newState), { chats: __spreadArray$1(__spreadArray$1([], newState.chats, true), [streamingMsg], false), streamingMessageId: action.detail.messageId });
36082
+ case "chat.stream.chunk": {
36083
+ // Find the streaming message by ID and append text
36084
+ var chunkDetail = action.detail;
36085
+ if (state.streamingMessageId === chunkDetail.messageId) {
36086
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36087
+ var streamingIndex_1 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36088
+ if (streamingIndex_1 >= 0) {
36089
+ var chunkText_1 = chunkDetail.text;
36090
+ return __assign(__assign({}, state), { lastTimestamp: chunkDetail === null || chunkDetail === void 0 ? void 0 : chunkDetail.timestamp, chats: state.chats.map(function (chat, index) {
36091
+ var _a;
36092
+ if (index === streamingIndex_1 && isChatMsgDetail(chat)) {
36093
+ return __assign(__assign({}, chat), { msg: __assign(__assign({}, chat.msg), { text: (((_a = chat.msg) === null || _a === void 0 ? void 0 : _a.text) || "") + chunkText_1 }) });
36094
+ }
36095
+ return chat;
36096
+ }) });
36097
+ }
36098
+ }
36099
+ return state;
36100
+ }
36101
+ case "chat.stream.tool_start": {
36102
+ // Add tool call to the streaming message (in-progress state)
36103
+ log("Tool execution started: ".concat(action.detail.toolName));
36104
+ var toolStartDetail_1 = action.detail;
36105
+ if (state.streamingMessageId === toolStartDetail_1.messageId) {
36106
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36107
+ var streamingIndex_2 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36108
+ if (streamingIndex_2 >= 0) {
36109
+ return __assign(__assign({}, state), { lastTimestamp: toolStartDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
36110
+ if (index === streamingIndex_2 && isChatMsgDetail(chat)) {
36111
+ var existingToolCalls = chat.toolCalls || [];
36112
+ return __assign(__assign({}, chat), { toolCalls: __spreadArray$1(__spreadArray$1([], existingToolCalls, true), [
36113
+ {
36114
+ name: toolStartDetail_1.toolName,
36115
+ toolCallId: toolStartDetail_1.toolCallId,
36116
+ displayName: toolStartDetail_1.displayName,
36117
+ label: toolStartDetail_1.label,
36118
+ hidden: toolStartDetail_1.hidden,
36119
+ input: toolStartDetail_1.toolInput,
36120
+ startTime: toolStartDetail_1.timestamp,
36121
+ isExecuting: true,
36122
+ },
36123
+ ], false) });
36124
+ }
36125
+ return chat;
36126
+ }) });
36127
+ }
36128
+ }
36129
+ return state;
36130
+ }
36131
+ case "chat.stream.tool_end": {
36132
+ // Update tool call with result in the streaming message
36133
+ log("Tool execution ended: ".concat(action.detail.toolName));
36134
+ var toolEndDetail_1 = action.detail;
36135
+ if (state.streamingMessageId === toolEndDetail_1.messageId) {
36136
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36137
+ var streamingIndex_3 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36138
+ if (streamingIndex_3 >= 0) {
36139
+ return __assign(__assign({}, state), { lastTimestamp: toolEndDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
36140
+ var _a;
36141
+ if (index === streamingIndex_3 && isChatMsgDetail(chat)) {
36142
+ var toolCalls = (_a = chat.toolCalls) === null || _a === void 0 ? void 0 : _a.map(function (tc) {
36143
+ var _a;
36144
+ // Match by toolCallId if available, otherwise fall back to name + isExecuting
36145
+ var isMatch = toolEndDetail_1.toolCallId
36146
+ ? tc.toolCallId === toolEndDetail_1.toolCallId
36147
+ : tc.name === toolEndDetail_1.toolName && tc.isExecuting;
36148
+ if (isMatch) {
36149
+ 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 });
36150
+ }
36151
+ return tc;
36152
+ });
36153
+ return __assign(__assign({}, chat), { toolCalls: toolCalls });
36154
+ }
36155
+ return chat;
36156
+ }) });
36157
+ }
36158
+ }
36159
+ return state;
36160
+ }
36161
+ case "chat.stream.end": {
36162
+ // Finalize the streaming message
36163
+ var endDetail = action.detail;
36164
+ if (state.streamingMessageId === endDetail.messageId) {
36165
+ // Find the streaming message by ID (not by index - other messages could be inserted)
36166
+ var streamingIndex_4 = state.chats.findIndex(function (chat) { return isChatMsgDetail(chat) && chat.isStreaming; });
36167
+ var endStreamingChat = streamingIndex_4 >= 0 ? state.chats[streamingIndex_4] : undefined;
36168
+ // Verify it's the right message using type guard
36169
+ if (streamingIndex_4 >= 0 && endStreamingChat && isChatMsgDetail(endStreamingChat)) {
36170
+ var finalMsg_1 = endDetail.msg;
36171
+ // Preserve tool calls from either the action or the existing message
36172
+ var toolCalls_1 = endDetail.toolCalls || endStreamingChat.toolCalls;
36173
+ // Update chips from message options (same as chat.msg handler)
36174
+ 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 : [];
36175
+ 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) {
36176
+ if (index === streamingIndex_4 && isChatMsgDetail(chat)) {
36177
+ return __assign(__assign({}, chat), { msg: finalMsg_1, isStreaming: false, wasStreamed: true, toolCalls: toolCalls_1 });
36178
+ }
36179
+ return chat;
36180
+ }) });
36181
+ }
36182
+ }
36183
+ return state;
36184
+ }
34281
36185
  default:
34282
36186
  return state;
34283
36187
  }