@webless/agent 0.4.1 → 0.5.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/react.cjs CHANGED
@@ -34,7 +34,7 @@ __export(react_exports, {
34
34
  module.exports = __toCommonJS(react_exports);
35
35
 
36
36
  // src/react/components/AgentWidget/AgentWidget.tsx
37
- var import_react6 = require("react");
37
+ var import_react7 = require("react");
38
38
 
39
39
  // src/react/page-shift.ts
40
40
  var import_react = require("react");
@@ -303,6 +303,9 @@ function runtimeSessionIdKey(visitorSessionId, prefix) {
303
303
  function runtimeStreamIndexKey(visitorSessionId, prefix) {
304
304
  return `${prefix}:eve:${visitorSessionId}:streamIndex`;
305
305
  }
306
+ function runtimeLastMessageKey(visitorSessionId, prefix) {
307
+ return `${prefix}:eve:${visitorSessionId}:lastMessage`;
308
+ }
306
309
  function loadPersistedAgentSession(visitorSessionId, options) {
307
310
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
308
311
  const prefix = resolvePrefix(options);
@@ -310,9 +313,11 @@ function loadPersistedAgentSession(visitorSessionId, options) {
310
313
  if (!sessionId) return null;
311
314
  const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
312
315
  const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
316
+ const lastMessage = sessionStorage.getItem(runtimeLastMessageKey(visitorSessionId, prefix))?.trim();
313
317
  return {
314
318
  sessionId,
315
- streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0
319
+ streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0,
320
+ ...lastMessage ? { lastMessage } : {}
316
321
  };
317
322
  }
318
323
  function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
@@ -326,11 +331,19 @@ function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, opt
326
331
  String(Math.max(0, streamIndex))
327
332
  );
328
333
  }
334
+ function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
335
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
336
+ return;
337
+ }
338
+ const prefix = resolvePrefix(options);
339
+ sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
340
+ }
329
341
  function clearPersistedAgentSession(visitorSessionId, options) {
330
342
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
331
343
  const prefix = resolvePrefix(options);
332
344
  sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
333
345
  sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
346
+ sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
334
347
  }
335
348
 
336
349
  // src/runtime/client.ts
@@ -345,6 +358,12 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
345
358
  if (event.type === "message.completed") {
346
359
  handlers.onComplete?.();
347
360
  }
361
+ if (event.type === "action.result") {
362
+ const result = event.data.result;
363
+ if (result && typeof result === "object" && "output" in result) {
364
+ handlers.onActionResult?.(result.output);
365
+ }
366
+ }
348
367
  if (event.type !== "message.appended") return rendered;
349
368
  const { messageDelta, messageSoFar } = event.data;
350
369
  let delta = messageDelta;
@@ -358,6 +377,12 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
358
377
  if (delta) handlers.onDelta(delta);
359
378
  return next;
360
379
  }
380
+ function isResumeTurnMessage(received, candidate) {
381
+ if (received === candidate) return true;
382
+ return Boolean(candidate) && received.endsWith(`
383
+
384
+ ${candidate}`);
385
+ }
361
386
  function latestTurnEvents(events) {
362
387
  let startIndex = -1;
363
388
  for (let index = events.length - 1; index >= 0; index -= 1) {
@@ -589,6 +614,11 @@ var AgentSession = class {
589
614
  this.session = session;
590
615
  try {
591
616
  const activeSession = session;
617
+ savePersistedAgentTurnMessage(
618
+ this.visitorSessionId,
619
+ message,
620
+ this.storeOptions
621
+ );
592
622
  response = await withCapabilityRefresh(
593
623
  this.capability,
594
624
  () => activeSession.send(message, { signal })
@@ -604,6 +634,11 @@ var AgentSession = class {
604
634
  }
605
635
  }
606
636
  if (!response) {
637
+ savePersistedAgentTurnMessage(
638
+ this.visitorSessionId,
639
+ message,
640
+ this.storeOptions
641
+ );
607
642
  const created = await withCapabilityRefresh(
608
643
  this.capability,
609
644
  () => client.sessions.create({ message, signal })
@@ -658,7 +693,9 @@ var AgentSession = class {
658
693
  );
659
694
  const turnEvents = latestTurnEvents(snapshot.events);
660
695
  const received = turnEvents[0];
661
- if (received?.type !== "message.received" || received.data.message !== message) {
696
+ const lastSent = persisted.lastMessage;
697
+ const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
698
+ if (received?.type !== "message.received" || !(isResumeTurnMessage(received.data.message, message) || inFlight && lastSent !== void 0 && received.data.message === lastSent)) {
662
699
  return null;
663
700
  }
664
701
  let rendered = renderTurn(turnEvents);
@@ -822,6 +859,246 @@ function formatAgentError(error) {
822
859
  return TRANSIENT_AGENT_ERROR_MESSAGE;
823
860
  }
824
861
 
862
+ // src/react/lib/tool-card.ts
863
+ function bookingOfferIdentityKey(offer) {
864
+ const eventTypes = offer.eventTypes.map(
865
+ (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
866
+ ).join("|");
867
+ const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
868
+ return `${eventTypes}::${slots}` || "offer";
869
+ }
870
+ var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
871
+ function asRecord(value) {
872
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
873
+ }
874
+ function asString(value) {
875
+ return typeof value === "string" ? value.trim() : "";
876
+ }
877
+ function isEventUri(value) {
878
+ return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
879
+ }
880
+ function isEventTypeUri(value) {
881
+ return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
882
+ }
883
+ function parseToolCard(value) {
884
+ const record = asRecord(value);
885
+ if (!record) return null;
886
+ if (record.booking_offer && asString(record.type) !== "booking_offer") {
887
+ const nested = parseToolCard(record.booking_offer);
888
+ if (nested) return nested;
889
+ }
890
+ const type = asString(record.type);
891
+ if (type === "booking_offer") {
892
+ const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
893
+ const entry = asRecord(item);
894
+ const uri = asString(entry?.uri);
895
+ if (!entry || !isEventTypeUri(uri)) return [];
896
+ const duration = entry.duration;
897
+ const locationKind = asString(entry.locationKind);
898
+ const location = asString(entry.location);
899
+ return [
900
+ {
901
+ name: asString(entry.name) || "Meeting",
902
+ uri,
903
+ ...typeof duration === "number" ? { duration } : {},
904
+ ...locationKind ? { locationKind } : {},
905
+ ...location ? { location } : {}
906
+ }
907
+ ];
908
+ }) : [];
909
+ const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
910
+ const entry = asRecord(item);
911
+ const startTime = asString(entry?.startTime);
912
+ if (!entry || !startTime) return [];
913
+ const eventTypeUri = asString(entry.eventTypeUri);
914
+ return [
915
+ {
916
+ startTime,
917
+ ...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
918
+ }
919
+ ];
920
+ }) : [];
921
+ if (slots.length === 0) return null;
922
+ return { type: "booking_offer", eventTypes, slots };
923
+ }
924
+ if (type === "booking_confirmed") {
925
+ const eventUri = asString(record.eventUri);
926
+ if (!isEventUri(eventUri)) return null;
927
+ const inviteeUri = asString(record.inviteeUri);
928
+ const inviteeEmail = asString(record.inviteeEmail);
929
+ const startTime = asString(record.startTime);
930
+ return {
931
+ type: "booking_confirmed",
932
+ eventUri,
933
+ ...inviteeUri ? { inviteeUri } : {},
934
+ ...inviteeEmail ? { inviteeEmail } : {},
935
+ ...startTime ? { startTime } : {}
936
+ };
937
+ }
938
+ if (type === "booking_canceled") {
939
+ const eventUri = asString(record.eventUri);
940
+ if (!isEventUri(eventUri)) return null;
941
+ return { type: "booking_canceled", eventUri };
942
+ }
943
+ return null;
944
+ }
945
+ function formatBookingOfferFence(offer) {
946
+ return [
947
+ "```webless-tool-card",
948
+ JSON.stringify({
949
+ type: "booking_offer",
950
+ eventTypes: offer.eventTypes,
951
+ slots: offer.slots
952
+ }),
953
+ "```"
954
+ ].join("\n");
955
+ }
956
+ function bookingOfferFromActionOutput(output) {
957
+ const record = asRecord(output);
958
+ const data = asRecord(record?.data) ?? record;
959
+ const card = parseToolCard(data);
960
+ return card?.type === "booking_offer" ? card : null;
961
+ }
962
+ function ensureBookingOfferText(text, offer) {
963
+ if (!offer) return text;
964
+ if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
965
+ return text;
966
+ }
967
+ const visible = stripToolCards(text).trim() || text.trim();
968
+ return `${visible}
969
+
970
+ ${formatBookingOfferFence(offer)}`;
971
+ }
972
+ function hideToolCardFences(text) {
973
+ return text.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
974
+ }
975
+ function visitorTimeZone() {
976
+ try {
977
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
978
+ } catch {
979
+ return "UTC";
980
+ }
981
+ }
982
+ function extractToolCards(text) {
983
+ const cards = [];
984
+ for (const match of text.matchAll(FENCE_PATTERN)) {
985
+ try {
986
+ const card = parseToolCard(JSON.parse(match[1] ?? ""));
987
+ if (card) cards.push(card);
988
+ } catch {
989
+ }
990
+ }
991
+ return cards;
992
+ }
993
+ function stripToolCards(text) {
994
+ return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
995
+ }
996
+ function localDateKey(date) {
997
+ if (Number.isNaN(date.getTime())) return "";
998
+ return [
999
+ date.getFullYear(),
1000
+ String(date.getMonth() + 1).padStart(2, "0"),
1001
+ String(date.getDate()).padStart(2, "0")
1002
+ ].join("-");
1003
+ }
1004
+ function slotDateKey(startTime) {
1005
+ return localDateKey(new Date(startTime)) || startTime;
1006
+ }
1007
+ function bookingSlotsForEventType(slots, eventTypeUri) {
1008
+ return slots.filter(
1009
+ (slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
1010
+ );
1011
+ }
1012
+ function firstAvailableBookingMonth(slots) {
1013
+ let earliest;
1014
+ for (const slot of slots) {
1015
+ const key = slotDateKey(slot.startTime);
1016
+ if (!earliest || key < earliest) earliest = key;
1017
+ }
1018
+ const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
1019
+ if (!year || !month) {
1020
+ const now = /* @__PURE__ */ new Date();
1021
+ return { year: now.getFullYear(), month: now.getMonth() };
1022
+ }
1023
+ return { year, month: month - 1 };
1024
+ }
1025
+ function formatMonthTitle(year, month) {
1026
+ return new Intl.DateTimeFormat(void 0, {
1027
+ month: "long",
1028
+ year: "numeric"
1029
+ }).format(new Date(year, month, 1));
1030
+ }
1031
+ function formatLongDate(startTime) {
1032
+ const date = new Date(startTime);
1033
+ if (Number.isNaN(date.getTime())) return startTime;
1034
+ return new Intl.DateTimeFormat(void 0, {
1035
+ weekday: "long",
1036
+ month: "long",
1037
+ day: "numeric"
1038
+ }).format(date);
1039
+ }
1040
+ function weekdayLabels() {
1041
+ return Array.from(
1042
+ { length: 7 },
1043
+ (_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
1044
+ new Date(2026, 7, 3 + index)
1045
+ )
1046
+ );
1047
+ }
1048
+ function formatTimeChip(startTime) {
1049
+ const date = new Date(startTime);
1050
+ if (Number.isNaN(date.getTime())) return startTime;
1051
+ return new Intl.DateTimeFormat(void 0, {
1052
+ hour: "numeric",
1053
+ minute: "2-digit"
1054
+ }).format(date);
1055
+ }
1056
+ function formatSlotTimeZone(startTime) {
1057
+ const date = new Date(startTime);
1058
+ if (Number.isNaN(date.getTime())) return "";
1059
+ return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
1060
+ }
1061
+ function formatSlotLabel(startTime) {
1062
+ const date = new Date(startTime);
1063
+ if (Number.isNaN(date.getTime())) return startTime;
1064
+ return new Intl.DateTimeFormat(void 0, {
1065
+ weekday: "short",
1066
+ month: "short",
1067
+ day: "numeric",
1068
+ hour: "numeric",
1069
+ minute: "2-digit",
1070
+ timeZoneName: "short"
1071
+ }).format(date);
1072
+ }
1073
+ function formatBookingRequest(input) {
1074
+ return [
1075
+ "Book this meeting now with CALENDLY_POST_INVITEE.",
1076
+ "Do not open a Calendly URL and do not list other scheduled events.",
1077
+ "Do not invent a location kind. Use only the location fields below.",
1078
+ `event_type: ${input.eventTypeUri}`,
1079
+ `start_time: ${input.startTime}`,
1080
+ `invitee.name: ${input.inviteeName}`,
1081
+ `invitee.email: ${input.inviteeEmail}`,
1082
+ `invitee.timezone: ${input.timezone}`,
1083
+ ...input.locationKind ? [
1084
+ `location.kind: ${input.locationKind}`,
1085
+ ...input.location ? [`location.location: ${input.location}`] : []
1086
+ ] : ["Do not send a location field."],
1087
+ "After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
1088
+ ].join("\n");
1089
+ }
1090
+ function visitorBookingPrefix(booking) {
1091
+ return [
1092
+ "This visitor already booked a meeting. Use only this meeting:",
1093
+ `- scheduled event URI: ${booking.eventUri}`,
1094
+ ...booking.inviteeUri ? [`- invitee URI: ${booking.inviteeUri}`] : [],
1095
+ ...booking.inviteeEmail ? [`- invitee email: ${booking.inviteeEmail}`] : [],
1096
+ "For details call CALENDLY_GET_EVENT or CALENDLY_GET_EVENT_INVITEE with those URIs.",
1097
+ "If you must list events, pass this invitee_email. Never describe any other scheduled event.",
1098
+ "start_time values from Calendly are UTC."
1099
+ ].join("\n");
1100
+ }
1101
+
825
1102
  // src/react/persisted-conversation.ts
826
1103
  var CONVERSATION_VERSION = 1;
827
1104
  function conversationKey(storageKeyPrefix, visitorSessionId) {
@@ -833,13 +1110,25 @@ function parseMessage(value) {
833
1110
  if (typeof record.id !== "string" || record.role !== "agent" && record.role !== "visitor" || typeof record.text !== "string" || typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt)) {
834
1111
  return null;
835
1112
  }
1113
+ if (record.role === "visitor") {
1114
+ return {
1115
+ id: record.id,
1116
+ role: "visitor",
1117
+ text: record.text,
1118
+ createdAt: record.createdAt,
1119
+ ...typeof record.runtimeText === "string" && record.runtimeText ? { runtimeText: record.runtimeText } : {}
1120
+ };
1121
+ }
836
1122
  return {
837
1123
  id: record.id,
838
- role: record.role,
1124
+ role: "agent",
839
1125
  text: record.text,
840
1126
  createdAt: record.createdAt
841
1127
  };
842
1128
  }
1129
+ function visitorTurnText(message) {
1130
+ return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
1131
+ }
843
1132
  function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
844
1133
  if (typeof sessionStorage === "undefined") return null;
845
1134
  const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
@@ -872,6 +1161,42 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
872
1161
  function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
873
1162
  if (typeof sessionStorage === "undefined") return;
874
1163
  sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
1164
+ clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
1165
+ }
1166
+ function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
1167
+ return `${storageKeyPrefix}:pending-booking:${visitorSessionId}`;
1168
+ }
1169
+ function loadPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1170
+ if (typeof sessionStorage === "undefined") return null;
1171
+ const raw = sessionStorage.getItem(
1172
+ pendingBookingKey(storageKeyPrefix, visitorSessionId)
1173
+ );
1174
+ if (!raw) return null;
1175
+ try {
1176
+ const value = JSON.parse(raw);
1177
+ if (typeof value !== "object" || value === null) return null;
1178
+ const record = value;
1179
+ if (typeof record.eventUri !== "string" || !record.eventUri) return null;
1180
+ return {
1181
+ eventUri: record.eventUri,
1182
+ ...typeof record.inviteeUri === "string" && record.inviteeUri ? { inviteeUri: record.inviteeUri } : {},
1183
+ ...typeof record.inviteeEmail === "string" && record.inviteeEmail ? { inviteeEmail: record.inviteeEmail } : {},
1184
+ ...typeof record.startTime === "string" && record.startTime ? { startTime: record.startTime } : {}
1185
+ };
1186
+ } catch {
1187
+ return null;
1188
+ }
1189
+ }
1190
+ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
1191
+ if (typeof sessionStorage === "undefined") return;
1192
+ sessionStorage.setItem(
1193
+ pendingBookingKey(storageKeyPrefix, visitorSessionId),
1194
+ JSON.stringify(booking)
1195
+ );
1196
+ }
1197
+ function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1198
+ if (typeof sessionStorage === "undefined") return;
1199
+ sessionStorage.removeItem(pendingBookingKey(storageKeyPrefix, visitorSessionId));
875
1200
  }
876
1201
 
877
1202
  // src/react/hooks/useAgentChat.ts
@@ -891,6 +1216,7 @@ function createInitialState(greeting = DEFAULT_GREETING) {
891
1216
  journey: null,
892
1217
  followUps: [],
893
1218
  streamingText: "",
1219
+ pendingOffer: null,
894
1220
  error: null
895
1221
  };
896
1222
  }
@@ -973,6 +1299,9 @@ function useAgentChat({
973
1299
  initialState
974
1300
  )
975
1301
  );
1302
+ const pendingBookingRef = (0, import_react2.useRef)(
1303
+ loadPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId)
1304
+ );
976
1305
  const runRef = (0, import_react2.useRef)(null);
977
1306
  const clientRef = (0, import_react2.useRef)(
978
1307
  createAgentClient({
@@ -1005,6 +1334,10 @@ function useAgentChat({
1005
1334
  visitorSessionId: visitorId,
1006
1335
  storageKeyPrefix: resolvedStorageKeyPrefix
1007
1336
  });
1337
+ pendingBookingRef.current = loadPendingWidgetBooking(
1338
+ resolvedStorageKeyPrefix,
1339
+ visitorId
1340
+ );
1008
1341
  setState(
1009
1342
  stateFromConversation(
1010
1343
  loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId),
@@ -1040,6 +1373,7 @@ function useAgentChat({
1040
1373
  runRef.current?.abort();
1041
1374
  runRef.current = null;
1042
1375
  clientRef.current.reset();
1376
+ pendingBookingRef.current = null;
1043
1377
  clearPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId);
1044
1378
  setState(initialState);
1045
1379
  }, [initialState, resolvedStorageKeyPrefix, visitorId]);
@@ -1051,6 +1385,7 @@ function useAgentChat({
1051
1385
  try {
1052
1386
  let streamStarted = Boolean(initialText);
1053
1387
  let streamed = initialText;
1388
+ const capturedOffers = [];
1054
1389
  const handlers = {
1055
1390
  onWork: (item) => {
1056
1391
  if (!isActiveRun()) return;
@@ -1060,6 +1395,12 @@ function useAgentChat({
1060
1395
  toolSteps: upsertToolStep(prev.toolSteps, item)
1061
1396
  }));
1062
1397
  },
1398
+ onActionResult: (output) => {
1399
+ const offer = bookingOfferFromActionOutput(output);
1400
+ if (!offer) return;
1401
+ capturedOffers.push(offer);
1402
+ setState((prev) => ({ ...prev, pendingOffer: offer }));
1403
+ },
1063
1404
  onDelta: (delta) => {
1064
1405
  if (!isActiveRun()) return;
1065
1406
  if (!streamStarted) {
@@ -1075,7 +1416,8 @@ function useAgentChat({
1075
1416
  setState((prev) => ({
1076
1417
  ...prev,
1077
1418
  phase: "streaming",
1078
- streamingText: streamed
1419
+ streamingText: hideToolCardFences(streamed),
1420
+ pendingOffer: prev.pendingOffer ?? capturedOffers.at(-1) ?? null
1079
1421
  }));
1080
1422
  },
1081
1423
  onComplete: () => {
@@ -1096,18 +1438,34 @@ function useAgentChat({
1096
1438
  });
1097
1439
  }
1098
1440
  if (!isActiveRun() || finalText === null) return;
1441
+ const displayText = ensureBookingOfferText(
1442
+ finalText,
1443
+ capturedOffers.at(-1) ?? null
1444
+ );
1099
1445
  const agentMessage = {
1100
1446
  id: `agent-${Date.now()}`,
1101
1447
  role: "agent",
1102
- text: finalText,
1448
+ text: displayText,
1103
1449
  createdAt: Date.now()
1104
1450
  };
1451
+ const parsedCards = extractToolCards(displayText);
1452
+ for (const card of parsedCards) {
1453
+ if (card.type === "booking_confirmed") {
1454
+ pendingBookingRef.current = card;
1455
+ savePendingWidgetBooking(resolvedStorageKeyPrefix, visitorId, card);
1456
+ }
1457
+ if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
1458
+ pendingBookingRef.current = null;
1459
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
1460
+ }
1461
+ }
1105
1462
  setState((prev) => ({
1106
1463
  ...prev,
1107
1464
  phase: "complete",
1108
1465
  messages: [...prev.messages, agentMessage],
1109
1466
  toolSteps: completeActivePlanning(prev.toolSteps),
1110
1467
  streamingText: "",
1468
+ pendingOffer: null,
1111
1469
  followUps: [],
1112
1470
  journey: null
1113
1471
  }));
@@ -1129,26 +1487,54 @@ function useAgentChat({
1129
1487
  } : step
1130
1488
  ),
1131
1489
  streamingText: "",
1490
+ pendingOffer: null,
1132
1491
  error: message
1133
1492
  }));
1134
1493
  runRef.current = null;
1135
1494
  }
1136
1495
  },
1137
- []
1496
+ [resolvedStorageKeyPrefix, visitorId]
1497
+ );
1498
+ const rememberBooking = (0, import_react2.useCallback)(
1499
+ (booking) => {
1500
+ const current = pendingBookingRef.current;
1501
+ if (current?.eventUri === booking.eventUri && current.inviteeEmail === booking.inviteeEmail && current.inviteeUri === booking.inviteeUri) {
1502
+ return;
1503
+ }
1504
+ pendingBookingRef.current = booking;
1505
+ savePendingWidgetBooking(resolvedStorageKeyPrefix, visitorId, booking);
1506
+ },
1507
+ [resolvedStorageKeyPrefix, visitorId]
1508
+ );
1509
+ const forgetBooking = (0, import_react2.useCallback)(
1510
+ (eventUri) => {
1511
+ const current = pendingBookingRef.current;
1512
+ if (!current) return;
1513
+ if (eventUri && current.eventUri !== eventUri) return;
1514
+ pendingBookingRef.current = null;
1515
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
1516
+ },
1517
+ [resolvedStorageKeyPrefix, visitorId]
1138
1518
  );
1139
1519
  const submit = (0, import_react2.useCallback)(
1140
- async (visitorText) => {
1520
+ async (visitorText, options) => {
1141
1521
  if (runRef.current) {
1142
1522
  runRef.current.abort();
1143
1523
  clientRef.current.cancelActive();
1144
1524
  }
1145
1525
  const controller = new AbortController();
1146
1526
  runRef.current = controller;
1527
+ const booking = pendingBookingRef.current;
1528
+ const outgoing = options?.runtimeText ?? visitorText;
1529
+ const runtimeText = booking ? `${visitorBookingPrefix(booking)}
1530
+
1531
+ ${outgoing}` : outgoing;
1147
1532
  const visitorMessage = {
1148
1533
  id: `visitor-${Date.now()}`,
1149
1534
  role: "visitor",
1150
1535
  text: visitorText,
1151
- createdAt: Date.now()
1536
+ createdAt: Date.now(),
1537
+ ...runtimeText !== visitorText ? { runtimeText } : {}
1152
1538
  };
1153
1539
  setState((prev) => ({
1154
1540
  ...prev,
@@ -1165,9 +1551,10 @@ function useAgentChat({
1165
1551
  journey: null,
1166
1552
  followUps: [],
1167
1553
  streamingText: "",
1554
+ pendingOffer: null,
1168
1555
  error: null
1169
1556
  }));
1170
- await runTurn({ controller, resume: false, visitorText });
1557
+ await runTurn({ controller, resume: false, visitorText: runtimeText });
1171
1558
  },
1172
1559
  [runTurn]
1173
1560
  );
@@ -1194,12 +1581,13 @@ function useAgentChat({
1194
1581
  journey: null,
1195
1582
  followUps: [],
1196
1583
  streamingText: "",
1584
+ pendingOffer: null,
1197
1585
  error: null
1198
1586
  }));
1199
1587
  await runTurn({
1200
1588
  controller,
1201
1589
  resume: false,
1202
- visitorText: visitorMessage.text
1590
+ visitorText: visitorTurnText(visitorMessage)
1203
1591
  });
1204
1592
  }, [runTurn, state.messages]);
1205
1593
  (0, import_react2.useEffect)(() => {
@@ -1216,7 +1604,7 @@ function useAgentChat({
1216
1604
  controller,
1217
1605
  initialText: conversation.streamingText,
1218
1606
  resume: true,
1219
- visitorText: visitorMessage.text
1607
+ visitorText: visitorTurnText(visitorMessage)
1220
1608
  });
1221
1609
  return () => {
1222
1610
  if (runRef.current === controller) {
@@ -1236,6 +1624,8 @@ function useAgentChat({
1236
1624
  reset,
1237
1625
  retry,
1238
1626
  submit,
1627
+ rememberBooking,
1628
+ forgetBooking,
1239
1629
  visitorSessionId: visitorId,
1240
1630
  sessionId: clientRef.current.getActiveSessionId()
1241
1631
  };
@@ -1297,7 +1687,7 @@ function unregisterAgentPanelController(customerId) {
1297
1687
  }
1298
1688
 
1299
1689
  // src/react/components/AgentRail/AgentRail.tsx
1300
- var import_react5 = require("react");
1690
+ var import_react6 = require("react");
1301
1691
 
1302
1692
  // src/react/types/conversation.ts
1303
1693
  var defaultAgentRailTheme = {
@@ -1337,10 +1727,9 @@ function workSummary(steps, failed, brandLabel) {
1337
1727
  );
1338
1728
  if (hasError) return "Answered with available information";
1339
1729
  if (specialists.length > 1)
1340
- return `Answer prepared with ${specialists.length} specialists`;
1341
- if (specialists.length === 1)
1342
- return `Answer prepared with ${specialists[0]?.label}`;
1343
- if (searched) return "Answer prepared from this site";
1730
+ return `Consulted ${specialists.length} specialists`;
1731
+ if (specialists.length === 1) return `Consulted ${specialists[0]?.label}`;
1732
+ if (searched) return "Searched this site";
1344
1733
  return "Answer ready";
1345
1734
  }
1346
1735
  function stepLabel(step, brandLabel) {
@@ -1390,7 +1779,6 @@ function AgentActivityBubble({
1390
1779
  steps
1391
1780
  }) {
1392
1781
  const active = steps.some((step) => step.state === "active");
1393
- const delegated = steps.some((step) => step.kind === "specialist");
1394
1782
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "agent-activity-bubble", children: [
1395
1783
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "agent-activity-bubble__status", "aria-live": "polite", children: [
1396
1784
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1402,57 +1790,50 @@ function AgentActivityBubble({
1402
1790
  ),
1403
1791
  workSummary(steps, failed, brandLabel)
1404
1792
  ] }),
1405
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1406
- "details",
1407
- {
1408
- className: "agent-activity-bubble__details",
1409
- open: active || delegated,
1410
- children: [
1411
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: failed ? "What happened" : active ? "Working" : "How this answer was prepared" }),
1412
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: steps.map((step) => {
1413
- const detail = stepDetail(step, steps);
1414
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1415
- "li",
1416
- {
1417
- className: "agent-activity-bubble__step",
1418
- "data-kind": step.kind,
1419
- "data-state": step.state,
1420
- children: [
1421
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1422
- "span",
1423
- {
1424
- className: "agent-activity-bubble__step-icon",
1425
- "aria-hidden": "true",
1426
- children: step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1427
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanningIcon, {}),
1428
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1429
- "img",
1430
- {
1431
- src: brandLogoUrl,
1432
- alt: "",
1433
- onError: (event) => {
1434
- event.currentTarget.hidden = true;
1435
- }
1436
- }
1437
- ) : null
1438
- ] }) : step.kind === "search" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
1439
- }
1440
- ),
1441
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1442
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-heading", children: [
1443
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: stepLabel(step, brandLabel) }),
1444
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: step.kind === "specialist" ? "Specialist" : step.kind === "search" ? "Built-in capability" : "Supervisor" })
1445
- ] }),
1446
- detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
1447
- ] })
1448
- ]
1449
- },
1450
- step.id
1451
- );
1452
- }) })
1453
- ]
1454
- }
1455
- )
1793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "agent-activity-bubble__details", open: active, children: [
1794
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "Work details" }),
1795
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: steps.map((step) => {
1796
+ const detail = stepDetail(step, steps);
1797
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1798
+ "li",
1799
+ {
1800
+ className: "agent-activity-bubble__step",
1801
+ "data-kind": step.kind,
1802
+ "data-state": step.state,
1803
+ children: [
1804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1805
+ "span",
1806
+ {
1807
+ className: "agent-activity-bubble__step-icon",
1808
+ "aria-hidden": "true",
1809
+ children: step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1810
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanningIcon, {}),
1811
+ brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1812
+ "img",
1813
+ {
1814
+ src: brandLogoUrl,
1815
+ alt: "",
1816
+ onError: (event) => {
1817
+ event.currentTarget.hidden = true;
1818
+ }
1819
+ }
1820
+ ) : null
1821
+ ] }) : step.kind === "search" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
1822
+ }
1823
+ ),
1824
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-heading", children: [
1826
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: stepLabel(step, brandLabel) }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: step.kind === "specialist" ? "Specialist" : step.kind === "search" ? "Built-in capability" : "Supervisor" })
1828
+ ] }),
1829
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
1830
+ ] })
1831
+ ]
1832
+ },
1833
+ step.id
1834
+ );
1835
+ }) })
1836
+ ] })
1456
1837
  ] });
1457
1838
  }
1458
1839
 
@@ -1554,34 +1935,318 @@ function FollowUpChips({
1554
1935
  ] });
1555
1936
  }
1556
1937
 
1938
+ // src/react/components/BookingCard/BookingCard.tsx
1939
+ var import_react5 = require("react");
1940
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1941
+ function monthFromKey(key) {
1942
+ const [year, month] = key.split("-").map(Number);
1943
+ if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
1944
+ return { year, month: month - 1 };
1945
+ }
1946
+ function dateKeyFromParts(year, month, day) {
1947
+ return [
1948
+ year,
1949
+ String(month + 1).padStart(2, "0"),
1950
+ String(day).padStart(2, "0")
1951
+ ].join("-");
1952
+ }
1953
+ function calendarCells(year, month) {
1954
+ const firstWeekday = (new Date(year, month, 1).getDay() + 6) % 7;
1955
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
1956
+ const cells = [];
1957
+ for (let index = 0; index < firstWeekday; index += 1) cells.push(null);
1958
+ for (let day = 1; day <= daysInMonth; day += 1) {
1959
+ cells.push({ day, key: dateKeyFromParts(year, month, day) });
1960
+ }
1961
+ while (cells.length < 42) cells.push(null);
1962
+ return cells;
1963
+ }
1964
+ function BookingCard({
1965
+ offer,
1966
+ onBook
1967
+ }) {
1968
+ const fieldId = (0, import_react5.useId)();
1969
+ const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
1970
+ const [step, setStep] = (0, import_react5.useState)("date");
1971
+ const [eventTypeUri, setEventTypeUri] = (0, import_react5.useState)(defaultType);
1972
+ const [selectedDate, setSelectedDate] = (0, import_react5.useState)("");
1973
+ const [startTime, setStartTime] = (0, import_react5.useState)("");
1974
+ const [name, setName] = (0, import_react5.useState)("");
1975
+ const [email, setEmail] = (0, import_react5.useState)("");
1976
+ const slots = (0, import_react5.useMemo)(
1977
+ () => bookingSlotsForEventType(offer.slots, eventTypeUri),
1978
+ [eventTypeUri, offer.slots]
1979
+ );
1980
+ const availableByDate = (0, import_react5.useMemo)(() => {
1981
+ const next = /* @__PURE__ */ new Map();
1982
+ for (const slot of slots) {
1983
+ const key = slotDateKey(slot.startTime);
1984
+ if (!next.has(key)) next.set(key, slot.startTime);
1985
+ }
1986
+ return next;
1987
+ }, [slots]);
1988
+ const [visibleMonth, setVisibleMonth] = (0, import_react5.useState)(
1989
+ () => firstAvailableBookingMonth(slots)
1990
+ );
1991
+ function selectEventType(nextType) {
1992
+ setEventTypeUri(nextType);
1993
+ setSelectedDate("");
1994
+ setStartTime("");
1995
+ setVisibleMonth(
1996
+ firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
1997
+ );
1998
+ }
1999
+ const daySlots = (0, import_react5.useMemo)(
2000
+ () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
2001
+ [selectedDate, slots]
2002
+ );
2003
+ const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
2004
+ const selectedSample = availableByDate.get(selectedDate) ?? startTime;
2005
+ const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
2006
+ const weekdays = (0, import_react5.useMemo)(() => weekdayLabels(), []);
2007
+ const cells = calendarCells(visibleMonth.year, visibleMonth.month);
2008
+ const canPrevMonth = [...availableByDate.keys()].some((key) => {
2009
+ const month = monthFromKey(key);
2010
+ return month.year < visibleMonth.year || month.year === visibleMonth.year && month.month < visibleMonth.month;
2011
+ });
2012
+ const canNextMonth = [...availableByDate.keys()].some((key) => {
2013
+ const month = monthFromKey(key);
2014
+ return month.year > visibleMonth.year || month.year === visibleMonth.year && month.month > visibleMonth.month;
2015
+ });
2016
+ function goToMonth(offset) {
2017
+ setVisibleMonth((current) => {
2018
+ const next = new Date(current.year, current.month + offset, 1);
2019
+ return { year: next.getFullYear(), month: next.getMonth() };
2020
+ });
2021
+ }
2022
+ function selectDate(key) {
2023
+ if (!availableByDate.has(key)) return;
2024
+ setSelectedDate(key);
2025
+ setStartTime("");
2026
+ setStep("time");
2027
+ }
2028
+ function selectTime(value) {
2029
+ setStartTime(value);
2030
+ setStep("details");
2031
+ }
2032
+ function handleSubmit(event) {
2033
+ event.preventDefault();
2034
+ if (!eventTypeUri || !startTime || !name.trim() || !email.trim()) return;
2035
+ onBook?.({
2036
+ displayText: `Book the ${formatSlotLabel(startTime)} demo`,
2037
+ runtimeText: formatBookingRequest({
2038
+ eventTypeUri,
2039
+ inviteeEmail: email.trim(),
2040
+ inviteeName: name.trim(),
2041
+ startTime,
2042
+ timezone: visitorTimeZone(),
2043
+ locationKind: selectedType?.locationKind,
2044
+ location: selectedType?.location
2045
+ })
2046
+ });
2047
+ }
2048
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { className: "booking-card__form", onSubmit: handleSubmit, children: [
2049
+ step === "date" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2050
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2051
+ timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2052
+ "Times in ",
2053
+ timeZone
2054
+ ] }) : null,
2055
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-type`, children: [
2056
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
2057
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2058
+ "select",
2059
+ {
2060
+ id: `${fieldId}-type`,
2061
+ value: eventTypeUri,
2062
+ onChange: (event) => selectEventType(event.target.value),
2063
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
2064
+ }
2065
+ )
2066
+ ] }) : null,
2067
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__month", children: [
2068
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2069
+ "button",
2070
+ {
2071
+ type: "button",
2072
+ className: "booking-card__nav",
2073
+ "aria-label": "Previous month",
2074
+ disabled: !canPrevMonth,
2075
+ onClick: () => goToMonth(-1),
2076
+ children: "\u2039"
2077
+ }
2078
+ ),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__month-title", children: formatMonthTitle(visibleMonth.year, visibleMonth.month) }),
2080
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2081
+ "button",
2082
+ {
2083
+ type: "button",
2084
+ className: "booking-card__nav",
2085
+ "aria-label": "Next month",
2086
+ disabled: !canNextMonth,
2087
+ onClick: () => goToMonth(1),
2088
+ children: "\u203A"
2089
+ }
2090
+ )
2091
+ ] }),
2092
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__weekdays", children: weekdays.map((label) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: label }, label)) }),
2093
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__calendar", role: "grid", "aria-label": "Available dates", children: cells.map((cell, index) => {
2094
+ if (!cell) {
2095
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "booking-card__day" }, `empty-${index}`);
2096
+ }
2097
+ const available = availableByDate.has(cell.key);
2098
+ const selected = cell.key === selectedDate;
2099
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2100
+ "button",
2101
+ {
2102
+ type: "button",
2103
+ className: [
2104
+ "booking-card__day",
2105
+ available ? "booking-card__day--available" : "",
2106
+ selected ? "booking-card__day--selected" : ""
2107
+ ].filter(Boolean).join(" "),
2108
+ disabled: !available,
2109
+ "aria-pressed": selected,
2110
+ onClick: () => selectDate(cell.key),
2111
+ children: cell.day
2112
+ },
2113
+ cell.key
2114
+ );
2115
+ }) })
2116
+ ] }, "date") : null,
2117
+ step === "time" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2118
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
2119
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2120
+ "button",
2121
+ {
2122
+ type: "button",
2123
+ className: "booking-card__nav",
2124
+ "aria-label": "Back to dates",
2125
+ onClick: () => setStep("date"),
2126
+ children: "\u2039"
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedSample ? formatLongDate(selectedSample) : "Pick a time" }),
2131
+ timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2132
+ "Times in ",
2133
+ timeZone
2134
+ ] }) : null
2135
+ ] })
2136
+ ] }),
2137
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__times", children: daySlots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2138
+ "button",
2139
+ {
2140
+ type: "button",
2141
+ className: startTime === slot.startTime ? "booking-card__time booking-card__time--selected" : "booking-card__time",
2142
+ onClick: () => selectTime(slot.startTime),
2143
+ children: formatTimeChip(slot.startTime)
2144
+ },
2145
+ slot.startTime
2146
+ )) })
2147
+ ] }, "time") : null,
2148
+ step === "details" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2149
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
2150
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2151
+ "button",
2152
+ {
2153
+ type: "button",
2154
+ className: "booking-card__nav",
2155
+ "aria-label": "Back to times",
2156
+ onClick: () => setStep("time"),
2157
+ children: "\u2039"
2158
+ }
2159
+ ),
2160
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
2161
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: "Enter details" }),
2162
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__tz", children: formatSlotLabel(startTime) }),
2163
+ selectedType?.location ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__tz", children: selectedType.location }) : null
2164
+ ] })
2165
+ ] }),
2166
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__identity", children: [
2167
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-name`, children: [
2168
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
2169
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2170
+ "input",
2171
+ {
2172
+ id: `${fieldId}-name`,
2173
+ autoComplete: "name",
2174
+ value: name,
2175
+ onChange: (event) => setName(event.target.value),
2176
+ required: true
2177
+ }
2178
+ )
2179
+ ] }),
2180
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-email`, children: [
2181
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
2182
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2183
+ "input",
2184
+ {
2185
+ id: `${fieldId}-email`,
2186
+ type: "email",
2187
+ autoComplete: "email",
2188
+ value: email,
2189
+ onChange: (event) => setEmail(event.target.value),
2190
+ required: true
2191
+ }
2192
+ )
2193
+ ] })
2194
+ ] }),
2195
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
2196
+ ] }, "details") : null
2197
+ ] }) });
2198
+ }
2199
+
1557
2200
  // src/react/components/MessageBubble/MessageBubble.tsx
1558
2201
  var import_streamdown = require("streamdown");
1559
2202
  var import_styles = require("streamdown/styles.css");
1560
- var import_jsx_runtime4 = require("react/jsx-runtime");
1561
- function MessageBubble({ message }) {
2203
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2204
+ function MessageBubble({
2205
+ message,
2206
+ offer,
2207
+ onBook
2208
+ }) {
2209
+ const cards = message.role === "agent" ? extractToolCards(message.text) : [];
2210
+ const extractedOffers = cards.filter(
2211
+ (card) => card.type === "booking_offer"
2212
+ );
2213
+ const offers = offer ? [offer] : extractedOffers;
2214
+ const visibleText = hideToolCardFences(message.text);
2215
+ const isStreaming = message.role === "agent" && Boolean(message.streaming);
2216
+ const displayText = offers.length > 0 ? visibleText || "Pick a date and time that works for you." : visibleText || (isStreaming ? "" : message.text);
1562
2217
  if (message.role === "visitor") {
1563
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "message-bubble__text", children: message.text }) });
2218
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "message-bubble__text", children: message.text }) });
1564
2219
  }
1565
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("article", { className: "message-bubble message-bubble--agent", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "message-bubble__text", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1566
- import_streamdown.Streamdown,
1567
- {
1568
- animated: true,
1569
- caret: "circle",
1570
- className: "message-bubble__markdown",
1571
- controls: false,
1572
- isAnimating: message.streaming,
1573
- linkSafety: { enabled: false },
1574
- mode: message.streaming ? "streaming" : "static",
1575
- skipHtml: true,
1576
- children: message.text
1577
- }
1578
- ) }) });
2220
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
2221
+ displayText ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "message-bubble__text", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2222
+ import_streamdown.Streamdown,
2223
+ {
2224
+ animated: true,
2225
+ caret: "circle",
2226
+ className: "message-bubble__markdown",
2227
+ controls: false,
2228
+ isAnimating: isStreaming,
2229
+ linkSafety: { enabled: false },
2230
+ mode: isStreaming ? "streaming" : "static",
2231
+ skipHtml: true,
2232
+ children: displayText
2233
+ }
2234
+ ) }) : null,
2235
+ offers.map((nextOffer, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2236
+ BookingCard,
2237
+ {
2238
+ offer: nextOffer,
2239
+ onBook
2240
+ },
2241
+ `${bookingOfferIdentityKey(nextOffer)}-${index}`
2242
+ ))
2243
+ ] });
1579
2244
  }
1580
2245
 
1581
2246
  // src/react/components/AgentRail/AgentRail.tsx
1582
- var import_jsx_runtime5 = require("react/jsx-runtime");
2247
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1583
2248
  function MinimizeIcon() {
1584
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2249
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1585
2250
  "path",
1586
2251
  {
1587
2252
  d: "M3.5 8h9",
@@ -1592,7 +2257,7 @@ function MinimizeIcon() {
1592
2257
  ) });
1593
2258
  }
1594
2259
  function CloseIcon() {
1595
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2260
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1596
2261
  "path",
1597
2262
  {
1598
2263
  d: "M4 4l8 8M12 4l-8 8",
@@ -1603,7 +2268,7 @@ function CloseIcon() {
1603
2268
  ) });
1604
2269
  }
1605
2270
  function NewChatIcon() {
1606
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2271
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1607
2272
  "path",
1608
2273
  {
1609
2274
  d: "M9.5 3.5h3v3M12.25 3.75 8 8M7 4H4.5A1.5 1.5 0 0 0 3 5.5v6A1.5 1.5 0 0 0 4.5 13h6a1.5 1.5 0 0 0 1.5-1.5V9",
@@ -1615,7 +2280,7 @@ function NewChatIcon() {
1615
2280
  ) });
1616
2281
  }
1617
2282
  function ExpandIcon() {
1618
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2283
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1619
2284
  "path",
1620
2285
  {
1621
2286
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -1627,7 +2292,7 @@ function ExpandIcon() {
1627
2292
  ) });
1628
2293
  }
1629
2294
  function RestoreIcon() {
1630
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2295
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1631
2296
  "path",
1632
2297
  {
1633
2298
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -1653,10 +2318,10 @@ function AgentRail({
1653
2318
  onReset,
1654
2319
  onRetry,
1655
2320
  onSubmit,
1656
- onFollowUpSelect
2321
+ onFollowUpSelect,
2322
+ onBook
1657
2323
  }) {
1658
- const transcriptRef = (0, import_react5.useRef)(null);
1659
- const welcomeTitleId = (0, import_react5.useId)();
2324
+ const transcriptRef = (0, import_react6.useRef)(null);
1660
2325
  const resolvedTheme = { ...defaultAgentRailTheme, ...theme };
1661
2326
  const railStyle = {
1662
2327
  "--rail-width": resolvedTheme.railMaxWidth,
@@ -1696,8 +2361,14 @@ function AgentRail({
1696
2361
  role: "agent",
1697
2362
  streaming: true,
1698
2363
  text: state.streamingText
2364
+ } : state.pendingOffer ? {
2365
+ createdAt: 0,
2366
+ id: "pending-booking",
2367
+ role: "agent",
2368
+ streaming: false,
2369
+ text: "Pick a date and time that works for you."
1699
2370
  } : null;
1700
- (0, import_react5.useEffect)(() => {
2371
+ (0, import_react6.useEffect)(() => {
1701
2372
  const node = transcriptRef.current;
1702
2373
  if (!node) return;
1703
2374
  node.scrollTop = node.scrollHeight;
@@ -1708,7 +2379,7 @@ function AgentRail({
1708
2379
  state.followUps,
1709
2380
  state.journey
1710
2381
  ]);
1711
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2382
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1712
2383
  "aside",
1713
2384
  {
1714
2385
  className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
@@ -1719,30 +2390,30 @@ function AgentRail({
1719
2390
  role: mobileFullscreen || expanded ? "dialog" : void 0,
1720
2391
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
1721
2392
  children: [
1722
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "agent-rail__brand-row", children: [
1723
- onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2393
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__brand-row", children: [
2394
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1724
2395
  "button",
1725
2396
  {
1726
2397
  type: "button",
1727
2398
  className: "agent-rail__collapse",
1728
2399
  "aria-label": "Collapse assist",
1729
2400
  onClick: onCollapse,
1730
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MinimizeIcon, {})
2401
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MinimizeIcon, {})
1731
2402
  }
1732
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2403
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1733
2404
  "button",
1734
2405
  {
1735
2406
  type: "button",
1736
2407
  className: "agent-rail__close",
1737
2408
  "aria-label": "Close agent",
1738
2409
  onClick: onClose,
1739
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {})
2410
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {})
1740
2411
  }
1741
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
1742
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__identity", children: [
1743
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: [
2412
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
2413
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__identity", children: [
2414
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: [
1744
2415
  brandLabel.slice(0, 1).toUpperCase(),
1745
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2416
+ brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1746
2417
  "img",
1747
2418
  {
1748
2419
  className: "agent-rail__brand-logo",
@@ -1754,10 +2425,10 @@ function AgentRail({
1754
2425
  }
1755
2426
  ) : null
1756
2427
  ] }),
1757
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "agent-rail__brand-label", children: brandLabel })
2428
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-label", children: brandLabel })
1758
2429
  ] }),
1759
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__actions", children: [
1760
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2430
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__actions", children: [
2431
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1761
2432
  "button",
1762
2433
  {
1763
2434
  type: "button",
@@ -1765,60 +2436,37 @@ function AgentRail({
1765
2436
  "aria-label": "Start a new conversation",
1766
2437
  disabled: !hasVisitorMessages2,
1767
2438
  onClick: onReset,
1768
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {})
2439
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NewChatIcon, {})
1769
2440
  }
1770
2441
  ) : null,
1771
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2442
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1772
2443
  "button",
1773
2444
  {
1774
2445
  type: "button",
1775
2446
  className: "agent-rail__expand",
1776
2447
  "aria-label": expanded ? "Exit focus view" : "Open focus view",
1777
2448
  onClick: onExpandToggle,
1778
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExpandIcon, {})
2449
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpandIcon, {})
1779
2450
  }
1780
2451
  ) : null
1781
2452
  ] })
1782
2453
  ] }) }),
1783
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "agent-rail__thread", children: [
1784
- !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1785
- "section",
1786
- {
1787
- className: "agent-rail__welcome",
1788
- "aria-labelledby": welcomeTitleId,
1789
- children: [
1790
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__welcome-mark", "aria-hidden": "true", children: [
1791
- brandLabel.slice(0, 1).toUpperCase(),
1792
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1793
- "img",
1794
- {
1795
- className: "agent-rail__welcome-logo",
1796
- src: brandLogoUrl,
1797
- alt: "",
1798
- onError: (event) => {
1799
- event.currentTarget.hidden = true;
1800
- }
1801
- }
1802
- ) : null
1803
- ] }),
1804
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "agent-rail__welcome-copy", children: [
1805
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { id: welcomeTitleId, children: "What can I help you find?" }),
1806
- greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: greeting.text }) : null
1807
- ] }),
1808
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1809
- FollowUpChips,
1810
- {
1811
- suggestions: state.followUps,
1812
- disabled: isBusy,
1813
- label: "Start here",
1814
- onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
1815
- }
1816
- ) }) : null
1817
- ]
1818
- }
1819
- ) : null,
1820
- transcriptMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageBubble, { message }, message.id)),
1821
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2454
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__thread", children: [
2455
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
2456
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageBubble, { message: greeting, onBook }) : null,
2457
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2458
+ FollowUpChips,
2459
+ {
2460
+ suggestions: state.followUps,
2461
+ disabled: isBusy,
2462
+ label: "Start here",
2463
+ onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2464
+ }
2465
+ ) }) : null
2466
+ ] }) : null,
2467
+ transcriptMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageBubble, { message, onBook }, message.id)),
2468
+ completedAnswer ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageBubble, { message: completedAnswer, onBook }) : null,
2469
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1822
2470
  AgentActivityBubble,
1823
2471
  {
1824
2472
  brandLabel,
@@ -1827,18 +2475,24 @@ function AgentRail({
1827
2475
  steps: state.toolSteps
1828
2476
  }
1829
2477
  ) : null,
1830
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageBubble, { message: streamingMessage }) : null,
1831
- completedAnswer ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageBubble, { message: completedAnswer }) : null,
1832
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
1833
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1834
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "Something went wrong" }),
1835
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: state.error })
2478
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2479
+ MessageBubble,
2480
+ {
2481
+ message: streamingMessage,
2482
+ offer: state.pendingOffer,
2483
+ onBook
2484
+ }
2485
+ ) : null,
2486
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
2487
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2488
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Something went wrong" }),
2489
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: state.error })
1836
2490
  ] }),
1837
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2491
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
1838
2492
  ] }) : null
1839
2493
  ] }) }),
1840
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
1841
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2494
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
2495
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1842
2496
  Composer,
1843
2497
  {
1844
2498
  variant: expanded || mobileFullscreen ? "dock" : "default",
@@ -1847,10 +2501,9 @@ function AgentRail({
1847
2501
  onSubmit
1848
2502
  }
1849
2503
  ),
1850
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { children: [
1851
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "AI can make mistakes." }),
1852
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": "true", children: " \xB7 " }),
1853
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: poweredByLabel })
2504
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2505
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "AI can make mistakes. Check important info." }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: poweredByLabel })
1854
2507
  ] }) })
1855
2508
  ] })
1856
2509
  ]
@@ -1859,9 +2512,9 @@ function AgentRail({
1859
2512
  }
1860
2513
 
1861
2514
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
1862
- var import_jsx_runtime6 = require("react/jsx-runtime");
2515
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1863
2516
  function SparklesIcon() {
1864
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2517
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1865
2518
  "svg",
1866
2519
  {
1867
2520
  className: "assist-edge-tab__sparkles",
@@ -1869,21 +2522,21 @@ function SparklesIcon() {
1869
2522
  fill: "none",
1870
2523
  "aria-hidden": "true",
1871
2524
  children: [
1872
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2525
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1873
2526
  "path",
1874
2527
  {
1875
2528
  d: "M8 1.2l.95 2.7 2.85.05-2.25 1.75.8 2.75L8 6.7 5.65 8.45l.8-2.75L4.2 3.95l2.85-.05L8 1.2z",
1876
2529
  fill: "currentColor"
1877
2530
  }
1878
2531
  ),
1879
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2532
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1880
2533
  "path",
1881
2534
  {
1882
2535
  d: "M14.2 6.4l.55 1.55 1.65.03-1.3 1 .46 1.58-1.36-1-1.36 1 .46-1.58-1.3-1 1.65-.03.55-1.55z",
1883
2536
  fill: "currentColor"
1884
2537
  }
1885
2538
  ),
1886
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2539
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1887
2540
  "path",
1888
2541
  {
1889
2542
  d: "M3.1 9.1l.4 1.15 1.22.02-.96.74.34 1.17-1-.74-1 .74.34-1.17-.96-.74 1.22-.02.4-1.15z",
@@ -1894,8 +2547,23 @@ function SparklesIcon() {
1894
2547
  }
1895
2548
  );
1896
2549
  }
2550
+ function TabMarkIcon({ customIconUrl }) {
2551
+ const url = customIconUrl?.trim();
2552
+ if (url) {
2553
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2554
+ "img",
2555
+ {
2556
+ alt: "",
2557
+ "aria-hidden": true,
2558
+ className: "assist-edge-tab__custom-icon",
2559
+ src: url
2560
+ }
2561
+ );
2562
+ }
2563
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparklesIcon, {});
2564
+ }
1897
2565
  function ChevronLeftIcon() {
1898
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2566
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1899
2567
  "path",
1900
2568
  {
1901
2569
  d: "M10 4L6 8l4 4",
@@ -1907,7 +2575,7 @@ function ChevronLeftIcon() {
1907
2575
  ) });
1908
2576
  }
1909
2577
  function ChevronDownIcon() {
1910
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2578
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1911
2579
  "path",
1912
2580
  {
1913
2581
  d: "M4 6l4 4 4-4",
@@ -1919,7 +2587,7 @@ function ChevronDownIcon() {
1919
2587
  ) });
1920
2588
  }
1921
2589
  function DragDots() {
1922
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("i", {}, index)) });
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", {}, index)) });
1923
2591
  }
1924
2592
  var VARIANT_COPY = {
1925
2593
  outline: { label: "Assist", aria: "Open Assist" },
@@ -1933,6 +2601,7 @@ function AssistEdgeTab({
1933
2601
  inset,
1934
2602
  visible,
1935
2603
  label,
2604
+ customIconUrl,
1936
2605
  logoUrl,
1937
2606
  brandColor,
1938
2607
  brandForeground,
@@ -1945,6 +2614,7 @@ function AssistEdgeTab({
1945
2614
  }) {
1946
2615
  const copy = VARIANT_COPY[variant];
1947
2616
  const visibleLabel = label?.trim() || copy.label;
2617
+ const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
1948
2618
  const style = {
1949
2619
  "--tab-along": `${along}%`,
1950
2620
  "--tab-inset": `${inset}px`,
@@ -1955,7 +2625,7 @@ function AssistEdgeTab({
1955
2625
  ...surfaceColor ? { "--as-surface": surfaceColor } : {},
1956
2626
  ...textColor ? { "--as-text": textColor } : {}
1957
2627
  };
1958
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2628
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1959
2629
  "button",
1960
2630
  {
1961
2631
  type: "button",
@@ -1966,15 +2636,15 @@ function AssistEdgeTab({
1966
2636
  tabIndex: visible ? 0 : -1,
1967
2637
  onClick: onOpen,
1968
2638
  children: [
1969
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1970
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2639
+ mobile ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2640
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1971
2641
  "span",
1972
2642
  {
1973
2643
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
1974
2644
  "aria-hidden": "true",
1975
2645
  children: [
1976
- visibleLabel.slice(0, 1).toUpperCase(),
1977
- logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2646
+ customIconUrl?.trim() ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }) : visibleLabel.slice(0, 1).toUpperCase(),
2647
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1978
2648
  "img",
1979
2649
  {
1980
2650
  className: "assist-edge-tab__logo",
@@ -1988,14 +2658,14 @@ function AssistEdgeTab({
1988
2658
  ]
1989
2659
  }
1990
2660
  ),
1991
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "assist-edge-tab__label", children: [
2661
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__label", children: [
1992
2662
  "Ask ",
1993
2663
  visibleLabel
1994
2664
  ] })
1995
- ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1996
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
1997
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SparklesIcon, {}),
1998
- logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2665
+ ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2666
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2667
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2668
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1999
2669
  "img",
2000
2670
  {
2001
2671
  className: "assist-edge-tab__logo",
@@ -2007,18 +2677,18 @@ function AssistEdgeTab({
2007
2677
  }
2008
2678
  ) : null
2009
2679
  ] }),
2010
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2011
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronDownIcon, {})
2680
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2681
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
2012
2682
  ] }) : null,
2013
- variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2014
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronLeftIcon, {}),
2015
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2016
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DragDots, {})
2683
+ variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2684
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {}),
2685
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2686
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DragDots, {})
2017
2687
  ] }) : null,
2018
- variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2019
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2020
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SparklesIcon, {}),
2021
- logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2688
+ variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2689
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2690
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2691
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2022
2692
  "img",
2023
2693
  {
2024
2694
  className: "assist-edge-tab__logo",
@@ -2030,8 +2700,8 @@ function AssistEdgeTab({
2030
2700
  }
2031
2701
  ) : null
2032
2702
  ] }),
2033
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2034
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronLeftIcon, {})
2703
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2704
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {})
2035
2705
  ] }) : null
2036
2706
  ]
2037
2707
  }
@@ -2039,7 +2709,7 @@ function AssistEdgeTab({
2039
2709
  }
2040
2710
 
2041
2711
  // src/react/components/AgentWidget/AgentWidget.tsx
2042
- var import_jsx_runtime7 = require("react/jsx-runtime");
2712
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2043
2713
  function AgentWidget({
2044
2714
  indexId,
2045
2715
  customerId,
@@ -2055,9 +2725,9 @@ function AgentWidget({
2055
2725
  }) {
2056
2726
  const isMobile = useIsMobile();
2057
2727
  const placement = normalizeAgentPlacement(placementInput);
2058
- const railSlotRef = (0, import_react6.useRef)(null);
2059
- const [railCollapsed, setRailCollapsed] = (0, import_react6.useState)(defaultCollapsed);
2060
- const [railExpanded, setRailExpanded] = (0, import_react6.useState)(false);
2728
+ const railSlotRef = (0, import_react7.useRef)(null);
2729
+ const [railCollapsed, setRailCollapsed] = (0, import_react7.useState)(defaultCollapsed);
2730
+ const [railExpanded, setRailExpanded] = (0, import_react7.useState)(false);
2061
2731
  const pageShiftActive = shouldApplyPageShift({
2062
2732
  pageShift,
2063
2733
  isMobile,
@@ -2078,6 +2748,7 @@ function AgentWidget({
2078
2748
  greeting: branding?.greeting
2079
2749
  });
2080
2750
  const agentName = branding?.agentName ?? "Webless Guide";
2751
+ const tabLabel = branding?.tabLabel ?? agentName;
2081
2752
  const theme = {
2082
2753
  ...branding?.fontFamily ? { fontBody: branding.fontFamily, fontDisplay: branding.fontFamily } : {},
2083
2754
  ...branding?.colors?.primary ? {
@@ -2095,8 +2766,7 @@ function AgentWidget({
2095
2766
  } : {},
2096
2767
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2097
2768
  };
2098
- const idle = state.phase === "idle" && !hasVisitorMessages(state.messages);
2099
- (0, import_react6.useEffect)(() => {
2769
+ (0, import_react7.useEffect)(() => {
2100
2770
  if (!registerPanelController) return;
2101
2771
  registerAgentPanelController(customerId, {
2102
2772
  open: () => setRailCollapsed(false),
@@ -2111,7 +2781,7 @@ function AgentWidget({
2111
2781
  if (isMobile) setRailCollapsed(false);
2112
2782
  await submit(message);
2113
2783
  }
2114
- (0, import_react6.useEffect)(() => {
2784
+ (0, import_react7.useEffect)(() => {
2115
2785
  if (railCollapsed) return;
2116
2786
  const handleKeyDown = (event) => {
2117
2787
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2145,31 +2815,28 @@ function AgentWidget({
2145
2815
  window.addEventListener("keydown", handleKeyDown);
2146
2816
  return () => window.removeEventListener("keydown", handleKeyDown);
2147
2817
  }, [isMobile, railCollapsed, railExpanded]);
2148
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "webless-agent-root", children: [
2149
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2818
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "webless-agent-root", children: [
2819
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2150
2820
  "div",
2151
2821
  {
2152
2822
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2153
2823
  children: [
2154
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2824
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2155
2825
  "div",
2156
2826
  {
2157
2827
  ref: railSlotRef,
2158
2828
  className: "webless-agent-root__rail-slot",
2159
2829
  inert: railCollapsed || void 0,
2160
2830
  "aria-hidden": railCollapsed,
2161
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2831
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2162
2832
  AgentRail,
2163
2833
  {
2164
2834
  theme,
2165
2835
  brandLabel: agentName,
2166
2836
  brandLogoUrl: branding?.logoUrl,
2167
- composerPlaceholder: branding?.composerPlaceholder ?? `Ask ${agentName}\u2026`,
2837
+ composerPlaceholder: branding?.composerPlaceholder ?? "Ask as a visitor\u2026",
2168
2838
  poweredByLabel: branding?.poweredByLabel ?? "Powered by Webless",
2169
- state: idle ? {
2170
- ...state,
2171
- followUps: createIdleSuggestions()
2172
- } : state,
2839
+ state,
2173
2840
  mobileFullscreen: isMobile && !railCollapsed,
2174
2841
  expanded: railExpanded,
2175
2842
  onCollapse: !isMobile ? () => setRailCollapsed(true) : void 0,
@@ -2178,12 +2845,13 @@ function AgentWidget({
2178
2845
  onSubmit: handleSubmit,
2179
2846
  onReset: reset,
2180
2847
  onRetry: () => void retry(),
2181
- onFollowUpSelect: (label) => void handleSubmit(label)
2848
+ onFollowUpSelect: (label) => void handleSubmit(label),
2849
+ onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
2182
2850
  }
2183
2851
  )
2184
2852
  }
2185
2853
  ),
2186
- !railCollapsed && isMobile || !isMobile && railExpanded && !railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2854
+ !railCollapsed && isMobile || !isMobile && railExpanded && !railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2187
2855
  "button",
2188
2856
  {
2189
2857
  type: "button",
@@ -2202,7 +2870,7 @@ function AgentWidget({
2202
2870
  ]
2203
2871
  }
2204
2872
  ),
2205
- railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2873
+ railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2206
2874
  AssistEdgeTab,
2207
2875
  {
2208
2876
  variant: placement.variant,
@@ -2210,7 +2878,8 @@ function AgentWidget({
2210
2878
  along: placement.along,
2211
2879
  inset: placement.inset,
2212
2880
  visible: true,
2213
- label: agentName,
2881
+ label: tabLabel,
2882
+ customIconUrl: branding?.tabIconUrl,
2214
2883
  logoUrl: branding?.logoUrl,
2215
2884
  brandColor: branding?.colors?.primary,
2216
2885
  brandForeground: branding?.colors?.primaryForeground,