@webless/agent 0.4.0 → 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.
@@ -188,6 +188,9 @@ function runtimeSessionIdKey(visitorSessionId, prefix) {
188
188
  function runtimeStreamIndexKey(visitorSessionId, prefix) {
189
189
  return `${prefix}:eve:${visitorSessionId}:streamIndex`;
190
190
  }
191
+ function runtimeLastMessageKey(visitorSessionId, prefix) {
192
+ return `${prefix}:eve:${visitorSessionId}:lastMessage`;
193
+ }
191
194
  function loadPersistedAgentSession(visitorSessionId, options) {
192
195
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
193
196
  const prefix = resolvePrefix(options);
@@ -195,9 +198,11 @@ function loadPersistedAgentSession(visitorSessionId, options) {
195
198
  if (!sessionId) return null;
196
199
  const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
197
200
  const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
201
+ const lastMessage = sessionStorage.getItem(runtimeLastMessageKey(visitorSessionId, prefix))?.trim();
198
202
  return {
199
203
  sessionId,
200
- streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0
204
+ streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0,
205
+ ...lastMessage ? { lastMessage } : {}
201
206
  };
202
207
  }
203
208
  function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
@@ -211,11 +216,19 @@ function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, opt
211
216
  String(Math.max(0, streamIndex))
212
217
  );
213
218
  }
219
+ function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
220
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
221
+ return;
222
+ }
223
+ const prefix = resolvePrefix(options);
224
+ sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
225
+ }
214
226
  function clearPersistedAgentSession(visitorSessionId, options) {
215
227
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
216
228
  const prefix = resolvePrefix(options);
217
229
  sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
218
230
  sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
231
+ sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
219
232
  }
220
233
 
221
234
  // src/runtime/client.ts
@@ -230,6 +243,12 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
230
243
  if (event.type === "message.completed") {
231
244
  handlers.onComplete?.();
232
245
  }
246
+ if (event.type === "action.result") {
247
+ const result = event.data.result;
248
+ if (result && typeof result === "object" && "output" in result) {
249
+ handlers.onActionResult?.(result.output);
250
+ }
251
+ }
233
252
  if (event.type !== "message.appended") return rendered;
234
253
  const { messageDelta, messageSoFar } = event.data;
235
254
  let delta = messageDelta;
@@ -243,6 +262,12 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
243
262
  if (delta) handlers.onDelta(delta);
244
263
  return next;
245
264
  }
265
+ function isResumeTurnMessage(received, candidate) {
266
+ if (received === candidate) return true;
267
+ return Boolean(candidate) && received.endsWith(`
268
+
269
+ ${candidate}`);
270
+ }
246
271
  function latestTurnEvents(events) {
247
272
  let startIndex = -1;
248
273
  for (let index = events.length - 1; index >= 0; index -= 1) {
@@ -474,6 +499,11 @@ var AgentSession = class {
474
499
  this.session = session;
475
500
  try {
476
501
  const activeSession = session;
502
+ savePersistedAgentTurnMessage(
503
+ this.visitorSessionId,
504
+ message,
505
+ this.storeOptions
506
+ );
477
507
  response = await withCapabilityRefresh(
478
508
  this.capability,
479
509
  () => activeSession.send(message, { signal })
@@ -489,6 +519,11 @@ var AgentSession = class {
489
519
  }
490
520
  }
491
521
  if (!response) {
522
+ savePersistedAgentTurnMessage(
523
+ this.visitorSessionId,
524
+ message,
525
+ this.storeOptions
526
+ );
492
527
  const created = await withCapabilityRefresh(
493
528
  this.capability,
494
529
  () => client.sessions.create({ message, signal })
@@ -543,7 +578,9 @@ var AgentSession = class {
543
578
  );
544
579
  const turnEvents = latestTurnEvents(snapshot.events);
545
580
  const received = turnEvents[0];
546
- if (received?.type !== "message.received" || received.data.message !== message) {
581
+ const lastSent = persisted.lastMessage;
582
+ const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
583
+ if (received?.type !== "message.received" || !(isResumeTurnMessage(received.data.message, message) || inFlight && lastSent !== void 0 && received.data.message === lastSent)) {
547
584
  return null;
548
585
  }
549
586
  let rendered = renderTurn(turnEvents);
@@ -707,6 +744,246 @@ function formatAgentError(error) {
707
744
  return TRANSIENT_AGENT_ERROR_MESSAGE;
708
745
  }
709
746
 
747
+ // src/react/lib/tool-card.ts
748
+ function bookingOfferIdentityKey(offer) {
749
+ const eventTypes = offer.eventTypes.map(
750
+ (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
751
+ ).join("|");
752
+ const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
753
+ return `${eventTypes}::${slots}` || "offer";
754
+ }
755
+ var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
756
+ function asRecord(value) {
757
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
758
+ }
759
+ function asString(value) {
760
+ return typeof value === "string" ? value.trim() : "";
761
+ }
762
+ function isEventUri(value) {
763
+ return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
764
+ }
765
+ function isEventTypeUri(value) {
766
+ return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
767
+ }
768
+ function parseToolCard(value) {
769
+ const record = asRecord(value);
770
+ if (!record) return null;
771
+ if (record.booking_offer && asString(record.type) !== "booking_offer") {
772
+ const nested = parseToolCard(record.booking_offer);
773
+ if (nested) return nested;
774
+ }
775
+ const type = asString(record.type);
776
+ if (type === "booking_offer") {
777
+ const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
778
+ const entry = asRecord(item);
779
+ const uri = asString(entry?.uri);
780
+ if (!entry || !isEventTypeUri(uri)) return [];
781
+ const duration = entry.duration;
782
+ const locationKind = asString(entry.locationKind);
783
+ const location = asString(entry.location);
784
+ return [
785
+ {
786
+ name: asString(entry.name) || "Meeting",
787
+ uri,
788
+ ...typeof duration === "number" ? { duration } : {},
789
+ ...locationKind ? { locationKind } : {},
790
+ ...location ? { location } : {}
791
+ }
792
+ ];
793
+ }) : [];
794
+ const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
795
+ const entry = asRecord(item);
796
+ const startTime = asString(entry?.startTime);
797
+ if (!entry || !startTime) return [];
798
+ const eventTypeUri = asString(entry.eventTypeUri);
799
+ return [
800
+ {
801
+ startTime,
802
+ ...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
803
+ }
804
+ ];
805
+ }) : [];
806
+ if (slots.length === 0) return null;
807
+ return { type: "booking_offer", eventTypes, slots };
808
+ }
809
+ if (type === "booking_confirmed") {
810
+ const eventUri = asString(record.eventUri);
811
+ if (!isEventUri(eventUri)) return null;
812
+ const inviteeUri = asString(record.inviteeUri);
813
+ const inviteeEmail = asString(record.inviteeEmail);
814
+ const startTime = asString(record.startTime);
815
+ return {
816
+ type: "booking_confirmed",
817
+ eventUri,
818
+ ...inviteeUri ? { inviteeUri } : {},
819
+ ...inviteeEmail ? { inviteeEmail } : {},
820
+ ...startTime ? { startTime } : {}
821
+ };
822
+ }
823
+ if (type === "booking_canceled") {
824
+ const eventUri = asString(record.eventUri);
825
+ if (!isEventUri(eventUri)) return null;
826
+ return { type: "booking_canceled", eventUri };
827
+ }
828
+ return null;
829
+ }
830
+ function formatBookingOfferFence(offer) {
831
+ return [
832
+ "```webless-tool-card",
833
+ JSON.stringify({
834
+ type: "booking_offer",
835
+ eventTypes: offer.eventTypes,
836
+ slots: offer.slots
837
+ }),
838
+ "```"
839
+ ].join("\n");
840
+ }
841
+ function bookingOfferFromActionOutput(output) {
842
+ const record = asRecord(output);
843
+ const data = asRecord(record?.data) ?? record;
844
+ const card = parseToolCard(data);
845
+ return card?.type === "booking_offer" ? card : null;
846
+ }
847
+ function ensureBookingOfferText(text, offer) {
848
+ if (!offer) return text;
849
+ if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
850
+ return text;
851
+ }
852
+ const visible = stripToolCards(text).trim() || text.trim();
853
+ return `${visible}
854
+
855
+ ${formatBookingOfferFence(offer)}`;
856
+ }
857
+ function hideToolCardFences(text) {
858
+ 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();
859
+ }
860
+ function visitorTimeZone() {
861
+ try {
862
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
863
+ } catch {
864
+ return "UTC";
865
+ }
866
+ }
867
+ function extractToolCards(text) {
868
+ const cards = [];
869
+ for (const match of text.matchAll(FENCE_PATTERN)) {
870
+ try {
871
+ const card = parseToolCard(JSON.parse(match[1] ?? ""));
872
+ if (card) cards.push(card);
873
+ } catch {
874
+ }
875
+ }
876
+ return cards;
877
+ }
878
+ function stripToolCards(text) {
879
+ return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
880
+ }
881
+ function localDateKey(date) {
882
+ if (Number.isNaN(date.getTime())) return "";
883
+ return [
884
+ date.getFullYear(),
885
+ String(date.getMonth() + 1).padStart(2, "0"),
886
+ String(date.getDate()).padStart(2, "0")
887
+ ].join("-");
888
+ }
889
+ function slotDateKey(startTime) {
890
+ return localDateKey(new Date(startTime)) || startTime;
891
+ }
892
+ function bookingSlotsForEventType(slots, eventTypeUri) {
893
+ return slots.filter(
894
+ (slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
895
+ );
896
+ }
897
+ function firstAvailableBookingMonth(slots) {
898
+ let earliest;
899
+ for (const slot of slots) {
900
+ const key = slotDateKey(slot.startTime);
901
+ if (!earliest || key < earliest) earliest = key;
902
+ }
903
+ const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
904
+ if (!year || !month) {
905
+ const now = /* @__PURE__ */ new Date();
906
+ return { year: now.getFullYear(), month: now.getMonth() };
907
+ }
908
+ return { year, month: month - 1 };
909
+ }
910
+ function formatMonthTitle(year, month) {
911
+ return new Intl.DateTimeFormat(void 0, {
912
+ month: "long",
913
+ year: "numeric"
914
+ }).format(new Date(year, month, 1));
915
+ }
916
+ function formatLongDate(startTime) {
917
+ const date = new Date(startTime);
918
+ if (Number.isNaN(date.getTime())) return startTime;
919
+ return new Intl.DateTimeFormat(void 0, {
920
+ weekday: "long",
921
+ month: "long",
922
+ day: "numeric"
923
+ }).format(date);
924
+ }
925
+ function weekdayLabels() {
926
+ return Array.from(
927
+ { length: 7 },
928
+ (_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
929
+ new Date(2026, 7, 3 + index)
930
+ )
931
+ );
932
+ }
933
+ function formatTimeChip(startTime) {
934
+ const date = new Date(startTime);
935
+ if (Number.isNaN(date.getTime())) return startTime;
936
+ return new Intl.DateTimeFormat(void 0, {
937
+ hour: "numeric",
938
+ minute: "2-digit"
939
+ }).format(date);
940
+ }
941
+ function formatSlotTimeZone(startTime) {
942
+ const date = new Date(startTime);
943
+ if (Number.isNaN(date.getTime())) return "";
944
+ return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
945
+ }
946
+ function formatSlotLabel(startTime) {
947
+ const date = new Date(startTime);
948
+ if (Number.isNaN(date.getTime())) return startTime;
949
+ return new Intl.DateTimeFormat(void 0, {
950
+ weekday: "short",
951
+ month: "short",
952
+ day: "numeric",
953
+ hour: "numeric",
954
+ minute: "2-digit",
955
+ timeZoneName: "short"
956
+ }).format(date);
957
+ }
958
+ function formatBookingRequest(input) {
959
+ return [
960
+ "Book this meeting now with CALENDLY_POST_INVITEE.",
961
+ "Do not open a Calendly URL and do not list other scheduled events.",
962
+ "Do not invent a location kind. Use only the location fields below.",
963
+ `event_type: ${input.eventTypeUri}`,
964
+ `start_time: ${input.startTime}`,
965
+ `invitee.name: ${input.inviteeName}`,
966
+ `invitee.email: ${input.inviteeEmail}`,
967
+ `invitee.timezone: ${input.timezone}`,
968
+ ...input.locationKind ? [
969
+ `location.kind: ${input.locationKind}`,
970
+ ...input.location ? [`location.location: ${input.location}`] : []
971
+ ] : ["Do not send a location field."],
972
+ "After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
973
+ ].join("\n");
974
+ }
975
+ function visitorBookingPrefix(booking) {
976
+ return [
977
+ "This visitor already booked a meeting. Use only this meeting:",
978
+ `- scheduled event URI: ${booking.eventUri}`,
979
+ ...booking.inviteeUri ? [`- invitee URI: ${booking.inviteeUri}`] : [],
980
+ ...booking.inviteeEmail ? [`- invitee email: ${booking.inviteeEmail}`] : [],
981
+ "For details call CALENDLY_GET_EVENT or CALENDLY_GET_EVENT_INVITEE with those URIs.",
982
+ "If you must list events, pass this invitee_email. Never describe any other scheduled event.",
983
+ "start_time values from Calendly are UTC."
984
+ ].join("\n");
985
+ }
986
+
710
987
  // src/react/persisted-conversation.ts
711
988
  var CONVERSATION_VERSION = 1;
712
989
  function conversationKey(storageKeyPrefix, visitorSessionId) {
@@ -718,13 +995,25 @@ function parseMessage(value) {
718
995
  if (typeof record.id !== "string" || record.role !== "agent" && record.role !== "visitor" || typeof record.text !== "string" || typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt)) {
719
996
  return null;
720
997
  }
998
+ if (record.role === "visitor") {
999
+ return {
1000
+ id: record.id,
1001
+ role: "visitor",
1002
+ text: record.text,
1003
+ createdAt: record.createdAt,
1004
+ ...typeof record.runtimeText === "string" && record.runtimeText ? { runtimeText: record.runtimeText } : {}
1005
+ };
1006
+ }
721
1007
  return {
722
1008
  id: record.id,
723
- role: record.role,
1009
+ role: "agent",
724
1010
  text: record.text,
725
1011
  createdAt: record.createdAt
726
1012
  };
727
1013
  }
1014
+ function visitorTurnText(message) {
1015
+ return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
1016
+ }
728
1017
  function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
729
1018
  if (typeof sessionStorage === "undefined") return null;
730
1019
  const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
@@ -757,6 +1046,42 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
757
1046
  function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
758
1047
  if (typeof sessionStorage === "undefined") return;
759
1048
  sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
1049
+ clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
1050
+ }
1051
+ function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
1052
+ return `${storageKeyPrefix}:pending-booking:${visitorSessionId}`;
1053
+ }
1054
+ function loadPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1055
+ if (typeof sessionStorage === "undefined") return null;
1056
+ const raw = sessionStorage.getItem(
1057
+ pendingBookingKey(storageKeyPrefix, visitorSessionId)
1058
+ );
1059
+ if (!raw) return null;
1060
+ try {
1061
+ const value = JSON.parse(raw);
1062
+ if (typeof value !== "object" || value === null) return null;
1063
+ const record = value;
1064
+ if (typeof record.eventUri !== "string" || !record.eventUri) return null;
1065
+ return {
1066
+ eventUri: record.eventUri,
1067
+ ...typeof record.inviteeUri === "string" && record.inviteeUri ? { inviteeUri: record.inviteeUri } : {},
1068
+ ...typeof record.inviteeEmail === "string" && record.inviteeEmail ? { inviteeEmail: record.inviteeEmail } : {},
1069
+ ...typeof record.startTime === "string" && record.startTime ? { startTime: record.startTime } : {}
1070
+ };
1071
+ } catch {
1072
+ return null;
1073
+ }
1074
+ }
1075
+ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
1076
+ if (typeof sessionStorage === "undefined") return;
1077
+ sessionStorage.setItem(
1078
+ pendingBookingKey(storageKeyPrefix, visitorSessionId),
1079
+ JSON.stringify(booking)
1080
+ );
1081
+ }
1082
+ function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1083
+ if (typeof sessionStorage === "undefined") return;
1084
+ sessionStorage.removeItem(pendingBookingKey(storageKeyPrefix, visitorSessionId));
760
1085
  }
761
1086
 
762
1087
  // src/react/hooks/useAgentChat.ts
@@ -776,6 +1101,7 @@ function createInitialState(greeting = DEFAULT_GREETING) {
776
1101
  journey: null,
777
1102
  followUps: [],
778
1103
  streamingText: "",
1104
+ pendingOffer: null,
779
1105
  error: null
780
1106
  };
781
1107
  }
@@ -858,6 +1184,9 @@ function useAgentChat({
858
1184
  initialState
859
1185
  )
860
1186
  );
1187
+ const pendingBookingRef = useRef(
1188
+ loadPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId)
1189
+ );
861
1190
  const runRef = useRef(null);
862
1191
  const clientRef = useRef(
863
1192
  createAgentClient({
@@ -890,6 +1219,10 @@ function useAgentChat({
890
1219
  visitorSessionId: visitorId,
891
1220
  storageKeyPrefix: resolvedStorageKeyPrefix
892
1221
  });
1222
+ pendingBookingRef.current = loadPendingWidgetBooking(
1223
+ resolvedStorageKeyPrefix,
1224
+ visitorId
1225
+ );
893
1226
  setState(
894
1227
  stateFromConversation(
895
1228
  loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId),
@@ -925,6 +1258,7 @@ function useAgentChat({
925
1258
  runRef.current?.abort();
926
1259
  runRef.current = null;
927
1260
  clientRef.current.reset();
1261
+ pendingBookingRef.current = null;
928
1262
  clearPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId);
929
1263
  setState(initialState);
930
1264
  }, [initialState, resolvedStorageKeyPrefix, visitorId]);
@@ -936,6 +1270,7 @@ function useAgentChat({
936
1270
  try {
937
1271
  let streamStarted = Boolean(initialText);
938
1272
  let streamed = initialText;
1273
+ const capturedOffers = [];
939
1274
  const handlers = {
940
1275
  onWork: (item) => {
941
1276
  if (!isActiveRun()) return;
@@ -945,6 +1280,12 @@ function useAgentChat({
945
1280
  toolSteps: upsertToolStep(prev.toolSteps, item)
946
1281
  }));
947
1282
  },
1283
+ onActionResult: (output) => {
1284
+ const offer = bookingOfferFromActionOutput(output);
1285
+ if (!offer) return;
1286
+ capturedOffers.push(offer);
1287
+ setState((prev) => ({ ...prev, pendingOffer: offer }));
1288
+ },
948
1289
  onDelta: (delta) => {
949
1290
  if (!isActiveRun()) return;
950
1291
  if (!streamStarted) {
@@ -960,7 +1301,8 @@ function useAgentChat({
960
1301
  setState((prev) => ({
961
1302
  ...prev,
962
1303
  phase: "streaming",
963
- streamingText: streamed
1304
+ streamingText: hideToolCardFences(streamed),
1305
+ pendingOffer: prev.pendingOffer ?? capturedOffers.at(-1) ?? null
964
1306
  }));
965
1307
  },
966
1308
  onComplete: () => {
@@ -981,18 +1323,34 @@ function useAgentChat({
981
1323
  });
982
1324
  }
983
1325
  if (!isActiveRun() || finalText === null) return;
1326
+ const displayText = ensureBookingOfferText(
1327
+ finalText,
1328
+ capturedOffers.at(-1) ?? null
1329
+ );
984
1330
  const agentMessage = {
985
1331
  id: `agent-${Date.now()}`,
986
1332
  role: "agent",
987
- text: finalText,
1333
+ text: displayText,
988
1334
  createdAt: Date.now()
989
1335
  };
1336
+ const parsedCards = extractToolCards(displayText);
1337
+ for (const card of parsedCards) {
1338
+ if (card.type === "booking_confirmed") {
1339
+ pendingBookingRef.current = card;
1340
+ savePendingWidgetBooking(resolvedStorageKeyPrefix, visitorId, card);
1341
+ }
1342
+ if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
1343
+ pendingBookingRef.current = null;
1344
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
1345
+ }
1346
+ }
990
1347
  setState((prev) => ({
991
1348
  ...prev,
992
1349
  phase: "complete",
993
1350
  messages: [...prev.messages, agentMessage],
994
1351
  toolSteps: completeActivePlanning(prev.toolSteps),
995
1352
  streamingText: "",
1353
+ pendingOffer: null,
996
1354
  followUps: [],
997
1355
  journey: null
998
1356
  }));
@@ -1014,26 +1372,54 @@ function useAgentChat({
1014
1372
  } : step
1015
1373
  ),
1016
1374
  streamingText: "",
1375
+ pendingOffer: null,
1017
1376
  error: message
1018
1377
  }));
1019
1378
  runRef.current = null;
1020
1379
  }
1021
1380
  },
1022
- []
1381
+ [resolvedStorageKeyPrefix, visitorId]
1382
+ );
1383
+ const rememberBooking = useCallback(
1384
+ (booking) => {
1385
+ const current = pendingBookingRef.current;
1386
+ if (current?.eventUri === booking.eventUri && current.inviteeEmail === booking.inviteeEmail && current.inviteeUri === booking.inviteeUri) {
1387
+ return;
1388
+ }
1389
+ pendingBookingRef.current = booking;
1390
+ savePendingWidgetBooking(resolvedStorageKeyPrefix, visitorId, booking);
1391
+ },
1392
+ [resolvedStorageKeyPrefix, visitorId]
1393
+ );
1394
+ const forgetBooking = useCallback(
1395
+ (eventUri) => {
1396
+ const current = pendingBookingRef.current;
1397
+ if (!current) return;
1398
+ if (eventUri && current.eventUri !== eventUri) return;
1399
+ pendingBookingRef.current = null;
1400
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
1401
+ },
1402
+ [resolvedStorageKeyPrefix, visitorId]
1023
1403
  );
1024
1404
  const submit = useCallback(
1025
- async (visitorText) => {
1405
+ async (visitorText, options) => {
1026
1406
  if (runRef.current) {
1027
1407
  runRef.current.abort();
1028
1408
  clientRef.current.cancelActive();
1029
1409
  }
1030
1410
  const controller = new AbortController();
1031
1411
  runRef.current = controller;
1412
+ const booking = pendingBookingRef.current;
1413
+ const outgoing = options?.runtimeText ?? visitorText;
1414
+ const runtimeText = booking ? `${visitorBookingPrefix(booking)}
1415
+
1416
+ ${outgoing}` : outgoing;
1032
1417
  const visitorMessage = {
1033
1418
  id: `visitor-${Date.now()}`,
1034
1419
  role: "visitor",
1035
1420
  text: visitorText,
1036
- createdAt: Date.now()
1421
+ createdAt: Date.now(),
1422
+ ...runtimeText !== visitorText ? { runtimeText } : {}
1037
1423
  };
1038
1424
  setState((prev) => ({
1039
1425
  ...prev,
@@ -1050,9 +1436,10 @@ function useAgentChat({
1050
1436
  journey: null,
1051
1437
  followUps: [],
1052
1438
  streamingText: "",
1439
+ pendingOffer: null,
1053
1440
  error: null
1054
1441
  }));
1055
- await runTurn({ controller, resume: false, visitorText });
1442
+ await runTurn({ controller, resume: false, visitorText: runtimeText });
1056
1443
  },
1057
1444
  [runTurn]
1058
1445
  );
@@ -1079,12 +1466,13 @@ function useAgentChat({
1079
1466
  journey: null,
1080
1467
  followUps: [],
1081
1468
  streamingText: "",
1469
+ pendingOffer: null,
1082
1470
  error: null
1083
1471
  }));
1084
1472
  await runTurn({
1085
1473
  controller,
1086
1474
  resume: false,
1087
- visitorText: visitorMessage.text
1475
+ visitorText: visitorTurnText(visitorMessage)
1088
1476
  });
1089
1477
  }, [runTurn, state.messages]);
1090
1478
  useEffect(() => {
@@ -1101,7 +1489,7 @@ function useAgentChat({
1101
1489
  controller,
1102
1490
  initialText: conversation.streamingText,
1103
1491
  resume: true,
1104
- visitorText: visitorMessage.text
1492
+ visitorText: visitorTurnText(visitorMessage)
1105
1493
  });
1106
1494
  return () => {
1107
1495
  if (runRef.current === controller) {
@@ -1121,6 +1509,8 @@ function useAgentChat({
1121
1509
  reset,
1122
1510
  retry,
1123
1511
  submit,
1512
+ rememberBooking,
1513
+ forgetBooking,
1124
1514
  visitorSessionId: visitorId,
1125
1515
  sessionId: clientRef.current.getActiveSessionId()
1126
1516
  };
@@ -1177,7 +1567,7 @@ var defaultAgentRailTheme = {
1177
1567
  };
1178
1568
 
1179
1569
  // src/react/components/AgentRail/AgentRail.tsx
1180
- import { useEffect as useEffect2, useId, useRef as useRef3 } from "react";
1570
+ import { useEffect as useEffect2, useRef as useRef3 } from "react";
1181
1571
 
1182
1572
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
1183
1573
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1197,10 +1587,9 @@ function workSummary(steps, failed, brandLabel) {
1197
1587
  );
1198
1588
  if (hasError) return "Answered with available information";
1199
1589
  if (specialists.length > 1)
1200
- return `Answer prepared with ${specialists.length} specialists`;
1201
- if (specialists.length === 1)
1202
- return `Answer prepared with ${specialists[0]?.label}`;
1203
- if (searched) return "Answer prepared from this site";
1590
+ return `Consulted ${specialists.length} specialists`;
1591
+ if (specialists.length === 1) return `Consulted ${specialists[0]?.label}`;
1592
+ if (searched) return "Searched this site";
1204
1593
  return "Answer ready";
1205
1594
  }
1206
1595
  function stepLabel(step, brandLabel) {
@@ -1250,7 +1639,6 @@ function AgentActivityBubble({
1250
1639
  steps
1251
1640
  }) {
1252
1641
  const active = steps.some((step) => step.state === "active");
1253
- const delegated = steps.some((step) => step.kind === "specialist");
1254
1642
  return /* @__PURE__ */ jsxs("article", { className: "agent-activity-bubble", children: [
1255
1643
  /* @__PURE__ */ jsxs("p", { className: "agent-activity-bubble__status", "aria-live": "polite", children: [
1256
1644
  /* @__PURE__ */ jsx(
@@ -1262,57 +1650,50 @@ function AgentActivityBubble({
1262
1650
  ),
1263
1651
  workSummary(steps, failed, brandLabel)
1264
1652
  ] }),
1265
- /* @__PURE__ */ jsxs(
1266
- "details",
1267
- {
1268
- className: "agent-activity-bubble__details",
1269
- open: active || delegated,
1270
- children: [
1271
- /* @__PURE__ */ jsx("summary", { children: failed ? "What happened" : active ? "Working" : "How this answer was prepared" }),
1272
- /* @__PURE__ */ jsx("ol", { className: "agent-activity-bubble__steps", children: steps.map((step) => {
1273
- const detail = stepDetail(step, steps);
1274
- return /* @__PURE__ */ jsxs(
1275
- "li",
1276
- {
1277
- className: "agent-activity-bubble__step",
1278
- "data-kind": step.kind,
1279
- "data-state": step.state,
1280
- children: [
1281
- /* @__PURE__ */ jsx(
1282
- "span",
1283
- {
1284
- className: "agent-activity-bubble__step-icon",
1285
- "aria-hidden": "true",
1286
- children: step.kind === "planning" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1287
- /* @__PURE__ */ jsx(PlanningIcon, {}),
1288
- brandLogoUrl ? /* @__PURE__ */ jsx(
1289
- "img",
1290
- {
1291
- src: brandLogoUrl,
1292
- alt: "",
1293
- onError: (event) => {
1294
- event.currentTarget.hidden = true;
1295
- }
1296
- }
1297
- ) : null
1298
- ] }) : step.kind === "search" ? /* @__PURE__ */ jsx(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
1299
- }
1300
- ),
1301
- /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-copy", children: [
1302
- /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-heading", children: [
1303
- /* @__PURE__ */ jsx("strong", { children: stepLabel(step, brandLabel) }),
1304
- /* @__PURE__ */ jsx("small", { children: step.kind === "specialist" ? "Specialist" : step.kind === "search" ? "Built-in capability" : "Supervisor" })
1305
- ] }),
1306
- detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
1307
- ] })
1308
- ]
1309
- },
1310
- step.id
1311
- );
1312
- }) })
1313
- ]
1314
- }
1315
- )
1653
+ /* @__PURE__ */ jsxs("details", { className: "agent-activity-bubble__details", open: active, children: [
1654
+ /* @__PURE__ */ jsx("summary", { children: "Work details" }),
1655
+ /* @__PURE__ */ jsx("ol", { className: "agent-activity-bubble__steps", children: steps.map((step) => {
1656
+ const detail = stepDetail(step, steps);
1657
+ return /* @__PURE__ */ jsxs(
1658
+ "li",
1659
+ {
1660
+ className: "agent-activity-bubble__step",
1661
+ "data-kind": step.kind,
1662
+ "data-state": step.state,
1663
+ children: [
1664
+ /* @__PURE__ */ jsx(
1665
+ "span",
1666
+ {
1667
+ className: "agent-activity-bubble__step-icon",
1668
+ "aria-hidden": "true",
1669
+ children: step.kind === "planning" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1670
+ /* @__PURE__ */ jsx(PlanningIcon, {}),
1671
+ brandLogoUrl ? /* @__PURE__ */ jsx(
1672
+ "img",
1673
+ {
1674
+ src: brandLogoUrl,
1675
+ alt: "",
1676
+ onError: (event) => {
1677
+ event.currentTarget.hidden = true;
1678
+ }
1679
+ }
1680
+ ) : null
1681
+ ] }) : step.kind === "search" ? /* @__PURE__ */ jsx(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
1682
+ }
1683
+ ),
1684
+ /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-copy", children: [
1685
+ /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-heading", children: [
1686
+ /* @__PURE__ */ jsx("strong", { children: stepLabel(step, brandLabel) }),
1687
+ /* @__PURE__ */ jsx("small", { children: step.kind === "specialist" ? "Specialist" : step.kind === "search" ? "Built-in capability" : "Supervisor" })
1688
+ ] }),
1689
+ detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
1690
+ ] })
1691
+ ]
1692
+ },
1693
+ step.id
1694
+ );
1695
+ }) })
1696
+ ] })
1316
1697
  ] });
1317
1698
  }
1318
1699
 
@@ -1414,34 +1795,318 @@ function FollowUpChips({
1414
1795
  ] });
1415
1796
  }
1416
1797
 
1798
+ // src/react/components/BookingCard/BookingCard.tsx
1799
+ import { useId, useMemo as useMemo2, useState as useState3 } from "react";
1800
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1801
+ function monthFromKey(key) {
1802
+ const [year, month] = key.split("-").map(Number);
1803
+ if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
1804
+ return { year, month: month - 1 };
1805
+ }
1806
+ function dateKeyFromParts(year, month, day) {
1807
+ return [
1808
+ year,
1809
+ String(month + 1).padStart(2, "0"),
1810
+ String(day).padStart(2, "0")
1811
+ ].join("-");
1812
+ }
1813
+ function calendarCells(year, month) {
1814
+ const firstWeekday = (new Date(year, month, 1).getDay() + 6) % 7;
1815
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
1816
+ const cells = [];
1817
+ for (let index = 0; index < firstWeekday; index += 1) cells.push(null);
1818
+ for (let day = 1; day <= daysInMonth; day += 1) {
1819
+ cells.push({ day, key: dateKeyFromParts(year, month, day) });
1820
+ }
1821
+ while (cells.length < 42) cells.push(null);
1822
+ return cells;
1823
+ }
1824
+ function BookingCard({
1825
+ offer,
1826
+ onBook
1827
+ }) {
1828
+ const fieldId = useId();
1829
+ const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
1830
+ const [step, setStep] = useState3("date");
1831
+ const [eventTypeUri, setEventTypeUri] = useState3(defaultType);
1832
+ const [selectedDate, setSelectedDate] = useState3("");
1833
+ const [startTime, setStartTime] = useState3("");
1834
+ const [name, setName] = useState3("");
1835
+ const [email, setEmail] = useState3("");
1836
+ const slots = useMemo2(
1837
+ () => bookingSlotsForEventType(offer.slots, eventTypeUri),
1838
+ [eventTypeUri, offer.slots]
1839
+ );
1840
+ const availableByDate = useMemo2(() => {
1841
+ const next = /* @__PURE__ */ new Map();
1842
+ for (const slot of slots) {
1843
+ const key = slotDateKey(slot.startTime);
1844
+ if (!next.has(key)) next.set(key, slot.startTime);
1845
+ }
1846
+ return next;
1847
+ }, [slots]);
1848
+ const [visibleMonth, setVisibleMonth] = useState3(
1849
+ () => firstAvailableBookingMonth(slots)
1850
+ );
1851
+ function selectEventType(nextType) {
1852
+ setEventTypeUri(nextType);
1853
+ setSelectedDate("");
1854
+ setStartTime("");
1855
+ setVisibleMonth(
1856
+ firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
1857
+ );
1858
+ }
1859
+ const daySlots = useMemo2(
1860
+ () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
1861
+ [selectedDate, slots]
1862
+ );
1863
+ const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
1864
+ const selectedSample = availableByDate.get(selectedDate) ?? startTime;
1865
+ const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
1866
+ const weekdays = useMemo2(() => weekdayLabels(), []);
1867
+ const cells = calendarCells(visibleMonth.year, visibleMonth.month);
1868
+ const canPrevMonth = [...availableByDate.keys()].some((key) => {
1869
+ const month = monthFromKey(key);
1870
+ return month.year < visibleMonth.year || month.year === visibleMonth.year && month.month < visibleMonth.month;
1871
+ });
1872
+ const canNextMonth = [...availableByDate.keys()].some((key) => {
1873
+ const month = monthFromKey(key);
1874
+ return month.year > visibleMonth.year || month.year === visibleMonth.year && month.month > visibleMonth.month;
1875
+ });
1876
+ function goToMonth(offset) {
1877
+ setVisibleMonth((current) => {
1878
+ const next = new Date(current.year, current.month + offset, 1);
1879
+ return { year: next.getFullYear(), month: next.getMonth() };
1880
+ });
1881
+ }
1882
+ function selectDate(key) {
1883
+ if (!availableByDate.has(key)) return;
1884
+ setSelectedDate(key);
1885
+ setStartTime("");
1886
+ setStep("time");
1887
+ }
1888
+ function selectTime(value) {
1889
+ setStartTime(value);
1890
+ setStep("details");
1891
+ }
1892
+ function handleSubmit(event) {
1893
+ event.preventDefault();
1894
+ if (!eventTypeUri || !startTime || !name.trim() || !email.trim()) return;
1895
+ onBook?.({
1896
+ displayText: `Book the ${formatSlotLabel(startTime)} demo`,
1897
+ runtimeText: formatBookingRequest({
1898
+ eventTypeUri,
1899
+ inviteeEmail: email.trim(),
1900
+ inviteeName: name.trim(),
1901
+ startTime,
1902
+ timezone: visitorTimeZone(),
1903
+ locationKind: selectedType?.locationKind,
1904
+ location: selectedType?.location
1905
+ })
1906
+ });
1907
+ }
1908
+ return /* @__PURE__ */ jsx4("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ jsxs4("form", { className: "booking-card__form", onSubmit: handleSubmit, children: [
1909
+ step === "date" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
1910
+ /* @__PURE__ */ jsx4("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
1911
+ timeZone ? /* @__PURE__ */ jsxs4("p", { className: "booking-card__tz", children: [
1912
+ "Times in ",
1913
+ timeZone
1914
+ ] }) : null,
1915
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ jsxs4("label", { className: "booking-card__field", htmlFor: `${fieldId}-type`, children: [
1916
+ /* @__PURE__ */ jsx4("span", { children: "Meeting" }),
1917
+ /* @__PURE__ */ jsx4(
1918
+ "select",
1919
+ {
1920
+ id: `${fieldId}-type`,
1921
+ value: eventTypeUri,
1922
+ onChange: (event) => selectEventType(event.target.value),
1923
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ jsx4("option", { value: item.uri, children: item.name }, item.uri))
1924
+ }
1925
+ )
1926
+ ] }) : null,
1927
+ /* @__PURE__ */ jsxs4("div", { className: "booking-card__month", children: [
1928
+ /* @__PURE__ */ jsx4(
1929
+ "button",
1930
+ {
1931
+ type: "button",
1932
+ className: "booking-card__nav",
1933
+ "aria-label": "Previous month",
1934
+ disabled: !canPrevMonth,
1935
+ onClick: () => goToMonth(-1),
1936
+ children: "\u2039"
1937
+ }
1938
+ ),
1939
+ /* @__PURE__ */ jsx4("p", { className: "booking-card__month-title", children: formatMonthTitle(visibleMonth.year, visibleMonth.month) }),
1940
+ /* @__PURE__ */ jsx4(
1941
+ "button",
1942
+ {
1943
+ type: "button",
1944
+ className: "booking-card__nav",
1945
+ "aria-label": "Next month",
1946
+ disabled: !canNextMonth,
1947
+ onClick: () => goToMonth(1),
1948
+ children: "\u203A"
1949
+ }
1950
+ )
1951
+ ] }),
1952
+ /* @__PURE__ */ jsx4("div", { className: "booking-card__weekdays", children: weekdays.map((label) => /* @__PURE__ */ jsx4("span", { children: label }, label)) }),
1953
+ /* @__PURE__ */ jsx4("div", { className: "booking-card__calendar", role: "grid", "aria-label": "Available dates", children: cells.map((cell, index) => {
1954
+ if (!cell) {
1955
+ return /* @__PURE__ */ jsx4("span", { className: "booking-card__day" }, `empty-${index}`);
1956
+ }
1957
+ const available = availableByDate.has(cell.key);
1958
+ const selected = cell.key === selectedDate;
1959
+ return /* @__PURE__ */ jsx4(
1960
+ "button",
1961
+ {
1962
+ type: "button",
1963
+ className: [
1964
+ "booking-card__day",
1965
+ available ? "booking-card__day--available" : "",
1966
+ selected ? "booking-card__day--selected" : ""
1967
+ ].filter(Boolean).join(" "),
1968
+ disabled: !available,
1969
+ "aria-pressed": selected,
1970
+ onClick: () => selectDate(cell.key),
1971
+ children: cell.day
1972
+ },
1973
+ cell.key
1974
+ );
1975
+ }) })
1976
+ ] }, "date") : null,
1977
+ step === "time" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
1978
+ /* @__PURE__ */ jsxs4("div", { className: "booking-card__step-bar", children: [
1979
+ /* @__PURE__ */ jsx4(
1980
+ "button",
1981
+ {
1982
+ type: "button",
1983
+ className: "booking-card__nav",
1984
+ "aria-label": "Back to dates",
1985
+ onClick: () => setStep("date"),
1986
+ children: "\u2039"
1987
+ }
1988
+ ),
1989
+ /* @__PURE__ */ jsxs4("div", { children: [
1990
+ /* @__PURE__ */ jsx4("p", { className: "booking-card__title", children: selectedSample ? formatLongDate(selectedSample) : "Pick a time" }),
1991
+ timeZone ? /* @__PURE__ */ jsxs4("p", { className: "booking-card__tz", children: [
1992
+ "Times in ",
1993
+ timeZone
1994
+ ] }) : null
1995
+ ] })
1996
+ ] }),
1997
+ /* @__PURE__ */ jsx4("div", { className: "booking-card__times", children: daySlots.map((slot) => /* @__PURE__ */ jsx4(
1998
+ "button",
1999
+ {
2000
+ type: "button",
2001
+ className: startTime === slot.startTime ? "booking-card__time booking-card__time--selected" : "booking-card__time",
2002
+ onClick: () => selectTime(slot.startTime),
2003
+ children: formatTimeChip(slot.startTime)
2004
+ },
2005
+ slot.startTime
2006
+ )) })
2007
+ ] }, "time") : null,
2008
+ step === "details" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
2009
+ /* @__PURE__ */ jsxs4("div", { className: "booking-card__step-bar", children: [
2010
+ /* @__PURE__ */ jsx4(
2011
+ "button",
2012
+ {
2013
+ type: "button",
2014
+ className: "booking-card__nav",
2015
+ "aria-label": "Back to times",
2016
+ onClick: () => setStep("time"),
2017
+ children: "\u2039"
2018
+ }
2019
+ ),
2020
+ /* @__PURE__ */ jsxs4("div", { children: [
2021
+ /* @__PURE__ */ jsx4("p", { className: "booking-card__title", children: "Enter details" }),
2022
+ /* @__PURE__ */ jsx4("p", { className: "booking-card__tz", children: formatSlotLabel(startTime) }),
2023
+ selectedType?.location ? /* @__PURE__ */ jsx4("p", { className: "booking-card__tz", children: selectedType.location }) : null
2024
+ ] })
2025
+ ] }),
2026
+ /* @__PURE__ */ jsxs4("div", { className: "booking-card__identity", children: [
2027
+ /* @__PURE__ */ jsxs4("label", { className: "booking-card__field", htmlFor: `${fieldId}-name`, children: [
2028
+ /* @__PURE__ */ jsx4("span", { children: "Name" }),
2029
+ /* @__PURE__ */ jsx4(
2030
+ "input",
2031
+ {
2032
+ id: `${fieldId}-name`,
2033
+ autoComplete: "name",
2034
+ value: name,
2035
+ onChange: (event) => setName(event.target.value),
2036
+ required: true
2037
+ }
2038
+ )
2039
+ ] }),
2040
+ /* @__PURE__ */ jsxs4("label", { className: "booking-card__field", htmlFor: `${fieldId}-email`, children: [
2041
+ /* @__PURE__ */ jsx4("span", { children: "Email" }),
2042
+ /* @__PURE__ */ jsx4(
2043
+ "input",
2044
+ {
2045
+ id: `${fieldId}-email`,
2046
+ type: "email",
2047
+ autoComplete: "email",
2048
+ value: email,
2049
+ onChange: (event) => setEmail(event.target.value),
2050
+ required: true
2051
+ }
2052
+ )
2053
+ ] })
2054
+ ] }),
2055
+ /* @__PURE__ */ jsx4("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
2056
+ ] }, "details") : null
2057
+ ] }) });
2058
+ }
2059
+
1417
2060
  // src/react/components/MessageBubble/MessageBubble.tsx
1418
2061
  import { Streamdown } from "streamdown";
1419
2062
  import "streamdown/styles.css";
1420
- import { jsx as jsx4 } from "react/jsx-runtime";
1421
- function MessageBubble({ message }) {
2063
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2064
+ function MessageBubble({
2065
+ message,
2066
+ offer,
2067
+ onBook
2068
+ }) {
2069
+ const cards = message.role === "agent" ? extractToolCards(message.text) : [];
2070
+ const extractedOffers = cards.filter(
2071
+ (card) => card.type === "booking_offer"
2072
+ );
2073
+ const offers = offer ? [offer] : extractedOffers;
2074
+ const visibleText = hideToolCardFences(message.text);
2075
+ const isStreaming = message.role === "agent" && Boolean(message.streaming);
2076
+ const displayText = offers.length > 0 ? visibleText || "Pick a date and time that works for you." : visibleText || (isStreaming ? "" : message.text);
1422
2077
  if (message.role === "visitor") {
1423
- return /* @__PURE__ */ jsx4("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx4("p", { className: "message-bubble__text", children: message.text }) });
2078
+ return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx5("p", { className: "message-bubble__text", children: message.text }) });
1424
2079
  }
1425
- return /* @__PURE__ */ jsx4("article", { className: "message-bubble message-bubble--agent", children: /* @__PURE__ */ jsx4("div", { className: "message-bubble__text", children: /* @__PURE__ */ jsx4(
1426
- Streamdown,
1427
- {
1428
- animated: true,
1429
- caret: "circle",
1430
- className: "message-bubble__markdown",
1431
- controls: false,
1432
- isAnimating: message.streaming,
1433
- linkSafety: { enabled: false },
1434
- mode: message.streaming ? "streaming" : "static",
1435
- skipHtml: true,
1436
- children: message.text
1437
- }
1438
- ) }) });
2080
+ return /* @__PURE__ */ jsxs5("article", { className: "message-bubble message-bubble--agent", children: [
2081
+ displayText ? /* @__PURE__ */ jsx5("div", { className: "message-bubble__text", children: /* @__PURE__ */ jsx5(
2082
+ Streamdown,
2083
+ {
2084
+ animated: true,
2085
+ caret: "circle",
2086
+ className: "message-bubble__markdown",
2087
+ controls: false,
2088
+ isAnimating: isStreaming,
2089
+ linkSafety: { enabled: false },
2090
+ mode: isStreaming ? "streaming" : "static",
2091
+ skipHtml: true,
2092
+ children: displayText
2093
+ }
2094
+ ) }) : null,
2095
+ offers.map((nextOffer, index) => /* @__PURE__ */ jsx5(
2096
+ BookingCard,
2097
+ {
2098
+ offer: nextOffer,
2099
+ onBook
2100
+ },
2101
+ `${bookingOfferIdentityKey(nextOffer)}-${index}`
2102
+ ))
2103
+ ] });
1439
2104
  }
1440
2105
 
1441
2106
  // src/react/components/AgentRail/AgentRail.tsx
1442
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2107
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1443
2108
  function MinimizeIcon() {
1444
- return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
2109
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1445
2110
  "path",
1446
2111
  {
1447
2112
  d: "M3.5 8h9",
@@ -1452,7 +2117,7 @@ function MinimizeIcon() {
1452
2117
  ) });
1453
2118
  }
1454
2119
  function CloseIcon() {
1455
- return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
2120
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1456
2121
  "path",
1457
2122
  {
1458
2123
  d: "M4 4l8 8M12 4l-8 8",
@@ -1463,7 +2128,7 @@ function CloseIcon() {
1463
2128
  ) });
1464
2129
  }
1465
2130
  function NewChatIcon() {
1466
- return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
2131
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1467
2132
  "path",
1468
2133
  {
1469
2134
  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",
@@ -1475,7 +2140,7 @@ function NewChatIcon() {
1475
2140
  ) });
1476
2141
  }
1477
2142
  function ExpandIcon() {
1478
- return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
2143
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1479
2144
  "path",
1480
2145
  {
1481
2146
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -1487,7 +2152,7 @@ function ExpandIcon() {
1487
2152
  ) });
1488
2153
  }
1489
2154
  function RestoreIcon() {
1490
- return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
2155
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1491
2156
  "path",
1492
2157
  {
1493
2158
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -1513,10 +2178,10 @@ function AgentRail({
1513
2178
  onReset,
1514
2179
  onRetry,
1515
2180
  onSubmit,
1516
- onFollowUpSelect
2181
+ onFollowUpSelect,
2182
+ onBook
1517
2183
  }) {
1518
2184
  const transcriptRef = useRef3(null);
1519
- const welcomeTitleId = useId();
1520
2185
  const resolvedTheme = { ...defaultAgentRailTheme, ...theme };
1521
2186
  const railStyle = {
1522
2187
  "--rail-width": resolvedTheme.railMaxWidth,
@@ -1556,6 +2221,12 @@ function AgentRail({
1556
2221
  role: "agent",
1557
2222
  streaming: true,
1558
2223
  text: state.streamingText
2224
+ } : state.pendingOffer ? {
2225
+ createdAt: 0,
2226
+ id: "pending-booking",
2227
+ role: "agent",
2228
+ streaming: false,
2229
+ text: "Pick a date and time that works for you."
1559
2230
  } : null;
1560
2231
  useEffect2(() => {
1561
2232
  const node = transcriptRef.current;
@@ -1568,7 +2239,7 @@ function AgentRail({
1568
2239
  state.followUps,
1569
2240
  state.journey
1570
2241
  ]);
1571
- return /* @__PURE__ */ jsxs4(
2242
+ return /* @__PURE__ */ jsxs6(
1572
2243
  "aside",
1573
2244
  {
1574
2245
  className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
@@ -1579,30 +2250,30 @@ function AgentRail({
1579
2250
  role: mobileFullscreen || expanded ? "dialog" : void 0,
1580
2251
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
1581
2252
  children: [
1582
- /* @__PURE__ */ jsx5("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs4("div", { className: "agent-rail__brand-row", children: [
1583
- onCollapse ? /* @__PURE__ */ jsx5(
2253
+ /* @__PURE__ */ jsx6("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs6("div", { className: "agent-rail__brand-row", children: [
2254
+ onCollapse ? /* @__PURE__ */ jsx6(
1584
2255
  "button",
1585
2256
  {
1586
2257
  type: "button",
1587
2258
  className: "agent-rail__collapse",
1588
2259
  "aria-label": "Collapse assist",
1589
2260
  onClick: onCollapse,
1590
- children: /* @__PURE__ */ jsx5(MinimizeIcon, {})
2261
+ children: /* @__PURE__ */ jsx6(MinimizeIcon, {})
1591
2262
  }
1592
- ) : onClose ? /* @__PURE__ */ jsx5(
2263
+ ) : onClose ? /* @__PURE__ */ jsx6(
1593
2264
  "button",
1594
2265
  {
1595
2266
  type: "button",
1596
2267
  className: "agent-rail__close",
1597
2268
  "aria-label": "Close agent",
1598
2269
  onClick: onClose,
1599
- children: /* @__PURE__ */ jsx5(CloseIcon, {})
2270
+ children: /* @__PURE__ */ jsx6(CloseIcon, {})
1600
2271
  }
1601
- ) : /* @__PURE__ */ jsx5("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
1602
- /* @__PURE__ */ jsxs4("span", { className: "agent-rail__identity", children: [
1603
- /* @__PURE__ */ jsxs4("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: [
2272
+ ) : /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
2273
+ /* @__PURE__ */ jsxs6("span", { className: "agent-rail__identity", children: [
2274
+ /* @__PURE__ */ jsxs6("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: [
1604
2275
  brandLabel.slice(0, 1).toUpperCase(),
1605
- brandLogoUrl ? /* @__PURE__ */ jsx5(
2276
+ brandLogoUrl ? /* @__PURE__ */ jsx6(
1606
2277
  "img",
1607
2278
  {
1608
2279
  className: "agent-rail__brand-logo",
@@ -1614,10 +2285,10 @@ function AgentRail({
1614
2285
  }
1615
2286
  ) : null
1616
2287
  ] }),
1617
- /* @__PURE__ */ jsx5("span", { className: "agent-rail__brand-label", children: brandLabel })
2288
+ /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-label", children: brandLabel })
1618
2289
  ] }),
1619
- /* @__PURE__ */ jsxs4("span", { className: "agent-rail__actions", children: [
1620
- onReset ? /* @__PURE__ */ jsx5(
2290
+ /* @__PURE__ */ jsxs6("span", { className: "agent-rail__actions", children: [
2291
+ onReset ? /* @__PURE__ */ jsx6(
1621
2292
  "button",
1622
2293
  {
1623
2294
  type: "button",
@@ -1625,60 +2296,37 @@ function AgentRail({
1625
2296
  "aria-label": "Start a new conversation",
1626
2297
  disabled: !hasVisitorMessages2,
1627
2298
  onClick: onReset,
1628
- children: /* @__PURE__ */ jsx5(NewChatIcon, {})
2299
+ children: /* @__PURE__ */ jsx6(NewChatIcon, {})
1629
2300
  }
1630
2301
  ) : null,
1631
- onExpandToggle ? /* @__PURE__ */ jsx5(
2302
+ onExpandToggle ? /* @__PURE__ */ jsx6(
1632
2303
  "button",
1633
2304
  {
1634
2305
  type: "button",
1635
2306
  className: "agent-rail__expand",
1636
2307
  "aria-label": expanded ? "Exit focus view" : "Open focus view",
1637
2308
  onClick: onExpandToggle,
1638
- children: expanded ? /* @__PURE__ */ jsx5(RestoreIcon, {}) : /* @__PURE__ */ jsx5(ExpandIcon, {})
2309
+ children: expanded ? /* @__PURE__ */ jsx6(RestoreIcon, {}) : /* @__PURE__ */ jsx6(ExpandIcon, {})
1639
2310
  }
1640
2311
  ) : null
1641
2312
  ] })
1642
2313
  ] }) }),
1643
- /* @__PURE__ */ jsx5("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ jsxs4("div", { className: "agent-rail__thread", children: [
1644
- !hasVisitorMessages2 ? /* @__PURE__ */ jsxs4(
1645
- "section",
1646
- {
1647
- className: "agent-rail__welcome",
1648
- "aria-labelledby": welcomeTitleId,
1649
- children: [
1650
- /* @__PURE__ */ jsxs4("span", { className: "agent-rail__welcome-mark", "aria-hidden": "true", children: [
1651
- brandLabel.slice(0, 1).toUpperCase(),
1652
- brandLogoUrl ? /* @__PURE__ */ jsx5(
1653
- "img",
1654
- {
1655
- className: "agent-rail__welcome-logo",
1656
- src: brandLogoUrl,
1657
- alt: "",
1658
- onError: (event) => {
1659
- event.currentTarget.hidden = true;
1660
- }
1661
- }
1662
- ) : null
1663
- ] }),
1664
- /* @__PURE__ */ jsxs4("div", { className: "agent-rail__welcome-copy", children: [
1665
- /* @__PURE__ */ jsx5("h2", { id: welcomeTitleId, children: "What can I help you find?" }),
1666
- greeting?.role === "agent" ? /* @__PURE__ */ jsx5("p", { children: greeting.text }) : null
1667
- ] }),
1668
- showIdleFollowUps ? /* @__PURE__ */ jsx5("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx5(
1669
- FollowUpChips,
1670
- {
1671
- suggestions: state.followUps,
1672
- disabled: isBusy,
1673
- label: "Start here",
1674
- onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
1675
- }
1676
- ) }) : null
1677
- ]
1678
- }
1679
- ) : null,
1680
- transcriptMessages.map((message) => /* @__PURE__ */ jsx5(MessageBubble, { message }, message.id)),
1681
- showActivity ? /* @__PURE__ */ jsx5(
2314
+ /* @__PURE__ */ jsx6("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ jsxs6("div", { className: "agent-rail__thread", children: [
2315
+ !hasVisitorMessages2 ? /* @__PURE__ */ jsxs6("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
2316
+ greeting?.role === "agent" ? /* @__PURE__ */ jsx6(MessageBubble, { message: greeting, onBook }) : null,
2317
+ showIdleFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
2318
+ FollowUpChips,
2319
+ {
2320
+ suggestions: state.followUps,
2321
+ disabled: isBusy,
2322
+ label: "Start here",
2323
+ onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2324
+ }
2325
+ ) }) : null
2326
+ ] }) : null,
2327
+ transcriptMessages.map((message) => /* @__PURE__ */ jsx6(MessageBubble, { message, onBook }, message.id)),
2328
+ completedAnswer ? /* @__PURE__ */ jsx6(MessageBubble, { message: completedAnswer, onBook }) : null,
2329
+ showActivity ? /* @__PURE__ */ jsx6(
1682
2330
  AgentActivityBubble,
1683
2331
  {
1684
2332
  brandLabel,
@@ -1687,18 +2335,24 @@ function AgentRail({
1687
2335
  steps: state.toolSteps
1688
2336
  }
1689
2337
  ) : null,
1690
- streamingMessage ? /* @__PURE__ */ jsx5(MessageBubble, { message: streamingMessage }) : null,
1691
- completedAnswer ? /* @__PURE__ */ jsx5(MessageBubble, { message: completedAnswer }) : null,
1692
- state.error ? /* @__PURE__ */ jsxs4("section", { className: "agent-rail__error", role: "alert", children: [
1693
- /* @__PURE__ */ jsxs4("div", { children: [
1694
- /* @__PURE__ */ jsx5("strong", { children: "Something went wrong" }),
1695
- /* @__PURE__ */ jsx5("p", { children: state.error })
2338
+ streamingMessage ? /* @__PURE__ */ jsx6(
2339
+ MessageBubble,
2340
+ {
2341
+ message: streamingMessage,
2342
+ offer: state.pendingOffer,
2343
+ onBook
2344
+ }
2345
+ ) : null,
2346
+ state.error ? /* @__PURE__ */ jsxs6("section", { className: "agent-rail__error", role: "alert", children: [
2347
+ /* @__PURE__ */ jsxs6("div", { children: [
2348
+ /* @__PURE__ */ jsx6("strong", { children: "Something went wrong" }),
2349
+ /* @__PURE__ */ jsx6("p", { children: state.error })
1696
2350
  ] }),
1697
- onRetry ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2351
+ onRetry ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
1698
2352
  ] }) : null
1699
2353
  ] }) }),
1700
- /* @__PURE__ */ jsxs4("div", { className: "agent-rail__composer-wrap", children: [
1701
- /* @__PURE__ */ jsx5(
2354
+ /* @__PURE__ */ jsxs6("div", { className: "agent-rail__composer-wrap", children: [
2355
+ /* @__PURE__ */ jsx6(
1702
2356
  Composer,
1703
2357
  {
1704
2358
  variant: expanded || mobileFullscreen ? "dock" : "default",
@@ -1707,10 +2361,9 @@ function AgentRail({
1707
2361
  onSubmit
1708
2362
  }
1709
2363
  ),
1710
- /* @__PURE__ */ jsx5("div", { className: "agent-rail__footer", children: /* @__PURE__ */ jsxs4("p", { children: [
1711
- /* @__PURE__ */ jsx5("span", { children: "AI can make mistakes." }),
1712
- /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", children: " \xB7 " }),
1713
- /* @__PURE__ */ jsx5("span", { children: poweredByLabel })
2364
+ /* @__PURE__ */ jsx6("div", { className: "agent-rail__footer", children: /* @__PURE__ */ jsxs6("p", { children: [
2365
+ /* @__PURE__ */ jsx6("span", { children: "AI can make mistakes. Check important info." }),
2366
+ /* @__PURE__ */ jsx6("span", { children: poweredByLabel })
1714
2367
  ] }) })
1715
2368
  ] })
1716
2369
  ]
@@ -1719,9 +2372,9 @@ function AgentRail({
1719
2372
  }
1720
2373
 
1721
2374
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
1722
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2375
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1723
2376
  function SparklesIcon() {
1724
- return /* @__PURE__ */ jsxs5(
2377
+ return /* @__PURE__ */ jsxs7(
1725
2378
  "svg",
1726
2379
  {
1727
2380
  className: "assist-edge-tab__sparkles",
@@ -1729,21 +2382,21 @@ function SparklesIcon() {
1729
2382
  fill: "none",
1730
2383
  "aria-hidden": "true",
1731
2384
  children: [
1732
- /* @__PURE__ */ jsx6(
2385
+ /* @__PURE__ */ jsx7(
1733
2386
  "path",
1734
2387
  {
1735
2388
  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",
1736
2389
  fill: "currentColor"
1737
2390
  }
1738
2391
  ),
1739
- /* @__PURE__ */ jsx6(
2392
+ /* @__PURE__ */ jsx7(
1740
2393
  "path",
1741
2394
  {
1742
2395
  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",
1743
2396
  fill: "currentColor"
1744
2397
  }
1745
2398
  ),
1746
- /* @__PURE__ */ jsx6(
2399
+ /* @__PURE__ */ jsx7(
1747
2400
  "path",
1748
2401
  {
1749
2402
  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",
@@ -1754,8 +2407,23 @@ function SparklesIcon() {
1754
2407
  }
1755
2408
  );
1756
2409
  }
2410
+ function TabMarkIcon({ customIconUrl }) {
2411
+ const url = customIconUrl?.trim();
2412
+ if (url) {
2413
+ return /* @__PURE__ */ jsx7(
2414
+ "img",
2415
+ {
2416
+ alt: "",
2417
+ "aria-hidden": true,
2418
+ className: "assist-edge-tab__custom-icon",
2419
+ src: url
2420
+ }
2421
+ );
2422
+ }
2423
+ return /* @__PURE__ */ jsx7(SparklesIcon, {});
2424
+ }
1757
2425
  function ChevronLeftIcon() {
1758
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
2426
+ return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
1759
2427
  "path",
1760
2428
  {
1761
2429
  d: "M10 4L6 8l4 4",
@@ -1767,7 +2435,7 @@ function ChevronLeftIcon() {
1767
2435
  ) });
1768
2436
  }
1769
2437
  function ChevronDownIcon() {
1770
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
2438
+ return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
1771
2439
  "path",
1772
2440
  {
1773
2441
  d: "M4 6l4 4 4-4",
@@ -1779,7 +2447,7 @@ function ChevronDownIcon() {
1779
2447
  ) });
1780
2448
  }
1781
2449
  function DragDots() {
1782
- return /* @__PURE__ */ jsx6("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx6("i", {}, index)) });
2450
+ return /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx7("i", {}, index)) });
1783
2451
  }
1784
2452
  var VARIANT_COPY = {
1785
2453
  outline: { label: "Assist", aria: "Open Assist" },
@@ -1793,6 +2461,7 @@ function AssistEdgeTab({
1793
2461
  inset,
1794
2462
  visible,
1795
2463
  label,
2464
+ customIconUrl,
1796
2465
  logoUrl,
1797
2466
  brandColor,
1798
2467
  brandForeground,
@@ -1805,6 +2474,7 @@ function AssistEdgeTab({
1805
2474
  }) {
1806
2475
  const copy = VARIANT_COPY[variant];
1807
2476
  const visibleLabel = label?.trim() || copy.label;
2477
+ const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
1808
2478
  const style = {
1809
2479
  "--tab-along": `${along}%`,
1810
2480
  "--tab-inset": `${inset}px`,
@@ -1815,7 +2485,7 @@ function AssistEdgeTab({
1815
2485
  ...surfaceColor ? { "--as-surface": surfaceColor } : {},
1816
2486
  ...textColor ? { "--as-text": textColor } : {}
1817
2487
  };
1818
- return /* @__PURE__ */ jsxs5(
2488
+ return /* @__PURE__ */ jsxs7(
1819
2489
  "button",
1820
2490
  {
1821
2491
  type: "button",
@@ -1826,15 +2496,15 @@ function AssistEdgeTab({
1826
2496
  tabIndex: visible ? 0 : -1,
1827
2497
  onClick: onOpen,
1828
2498
  children: [
1829
- mobile ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1830
- /* @__PURE__ */ jsxs5(
2499
+ mobile ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2500
+ /* @__PURE__ */ jsxs7(
1831
2501
  "span",
1832
2502
  {
1833
2503
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
1834
2504
  "aria-hidden": "true",
1835
2505
  children: [
1836
- visibleLabel.slice(0, 1).toUpperCase(),
1837
- logoUrl ? /* @__PURE__ */ jsx6(
2506
+ customIconUrl?.trim() ? /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }) : visibleLabel.slice(0, 1).toUpperCase(),
2507
+ showLogo ? /* @__PURE__ */ jsx7(
1838
2508
  "img",
1839
2509
  {
1840
2510
  className: "assist-edge-tab__logo",
@@ -1848,14 +2518,14 @@ function AssistEdgeTab({
1848
2518
  ]
1849
2519
  }
1850
2520
  ),
1851
- /* @__PURE__ */ jsxs5("span", { className: "assist-edge-tab__label", children: [
2521
+ /* @__PURE__ */ jsxs7("span", { className: "assist-edge-tab__label", children: [
1852
2522
  "Ask ",
1853
2523
  visibleLabel
1854
2524
  ] })
1855
- ] }) : variant === "outline" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1856
- /* @__PURE__ */ jsxs5("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
1857
- /* @__PURE__ */ jsx6(SparklesIcon, {}),
1858
- logoUrl ? /* @__PURE__ */ jsx6(
2525
+ ] }) : variant === "outline" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2526
+ /* @__PURE__ */ jsxs7("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2527
+ /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }),
2528
+ showLogo ? /* @__PURE__ */ jsx7(
1859
2529
  "img",
1860
2530
  {
1861
2531
  className: "assist-edge-tab__logo",
@@ -1867,18 +2537,18 @@ function AssistEdgeTab({
1867
2537
  }
1868
2538
  ) : null
1869
2539
  ] }),
1870
- /* @__PURE__ */ jsx6("span", { className: "assist-edge-tab__label", children: visibleLabel }),
1871
- /* @__PURE__ */ jsx6(ChevronDownIcon, {})
2540
+ /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2541
+ /* @__PURE__ */ jsx7(ChevronDownIcon, {})
1872
2542
  ] }) : null,
1873
- variant === "ask" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1874
- /* @__PURE__ */ jsx6(ChevronLeftIcon, {}),
1875
- /* @__PURE__ */ jsx6("span", { className: "assist-edge-tab__label", children: visibleLabel }),
1876
- /* @__PURE__ */ jsx6(DragDots, {})
2543
+ variant === "ask" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2544
+ /* @__PURE__ */ jsx7(ChevronLeftIcon, {}),
2545
+ /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2546
+ /* @__PURE__ */ jsx7(DragDots, {})
1877
2547
  ] }) : null,
1878
- variant === "fill" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1879
- /* @__PURE__ */ jsxs5("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
1880
- /* @__PURE__ */ jsx6(SparklesIcon, {}),
1881
- logoUrl ? /* @__PURE__ */ jsx6(
2548
+ variant === "fill" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2549
+ /* @__PURE__ */ jsxs7("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2550
+ /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }),
2551
+ showLogo ? /* @__PURE__ */ jsx7(
1882
2552
  "img",
1883
2553
  {
1884
2554
  className: "assist-edge-tab__logo",
@@ -1890,8 +2560,8 @@ function AssistEdgeTab({
1890
2560
  }
1891
2561
  ) : null
1892
2562
  ] }),
1893
- /* @__PURE__ */ jsx6("span", { className: "assist-edge-tab__label", children: visibleLabel }),
1894
- /* @__PURE__ */ jsx6(ChevronLeftIcon, {})
2563
+ /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2564
+ /* @__PURE__ */ jsx7(ChevronLeftIcon, {})
1895
2565
  ] }) : null
1896
2566
  ]
1897
2567
  }
@@ -1899,7 +2569,7 @@ function AssistEdgeTab({
1899
2569
  }
1900
2570
 
1901
2571
  // src/react/components/AgentWidget/AgentWidget.tsx
1902
- import { useEffect as useEffect5, useRef as useRef4, useState as useState4 } from "react";
2572
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
1903
2573
 
1904
2574
  // src/react/page-shift.ts
1905
2575
  import { useEffect as useEffect3 } from "react";
@@ -1982,9 +2652,9 @@ function usePageShift(input) {
1982
2652
  }
1983
2653
 
1984
2654
  // src/react/hooks/useIsMobile.ts
1985
- import { useEffect as useEffect4, useState as useState3 } from "react";
2655
+ import { useEffect as useEffect4, useState as useState4 } from "react";
1986
2656
  function useIsMobile(breakpoint = 767) {
1987
- const [isMobile, setIsMobile] = useState3(
2657
+ const [isMobile, setIsMobile] = useState4(
1988
2658
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
1989
2659
  );
1990
2660
  useEffect4(() => {
@@ -2013,7 +2683,7 @@ function closeAgentPanel(customerId) {
2013
2683
  }
2014
2684
 
2015
2685
  // src/react/components/AgentWidget/AgentWidget.tsx
2016
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2686
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2017
2687
  function AgentWidget({
2018
2688
  indexId,
2019
2689
  customerId,
@@ -2030,8 +2700,8 @@ function AgentWidget({
2030
2700
  const isMobile = useIsMobile();
2031
2701
  const placement = normalizeAgentPlacement(placementInput);
2032
2702
  const railSlotRef = useRef4(null);
2033
- const [railCollapsed, setRailCollapsed] = useState4(defaultCollapsed);
2034
- const [railExpanded, setRailExpanded] = useState4(false);
2703
+ const [railCollapsed, setRailCollapsed] = useState5(defaultCollapsed);
2704
+ const [railExpanded, setRailExpanded] = useState5(false);
2035
2705
  const pageShiftActive = shouldApplyPageShift({
2036
2706
  pageShift,
2037
2707
  isMobile,
@@ -2052,6 +2722,7 @@ function AgentWidget({
2052
2722
  greeting: branding?.greeting
2053
2723
  });
2054
2724
  const agentName = branding?.agentName ?? "Webless Guide";
2725
+ const tabLabel = branding?.tabLabel ?? agentName;
2055
2726
  const theme = {
2056
2727
  ...branding?.fontFamily ? { fontBody: branding.fontFamily, fontDisplay: branding.fontFamily } : {},
2057
2728
  ...branding?.colors?.primary ? {
@@ -2069,7 +2740,6 @@ function AgentWidget({
2069
2740
  } : {},
2070
2741
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2071
2742
  };
2072
- const idle = state.phase === "idle" && !hasVisitorMessages(state.messages);
2073
2743
  useEffect5(() => {
2074
2744
  if (!registerPanelController) return;
2075
2745
  registerAgentPanelController(customerId, {
@@ -2119,31 +2789,28 @@ function AgentWidget({
2119
2789
  window.addEventListener("keydown", handleKeyDown);
2120
2790
  return () => window.removeEventListener("keydown", handleKeyDown);
2121
2791
  }, [isMobile, railCollapsed, railExpanded]);
2122
- return /* @__PURE__ */ jsxs6("div", { className: "webless-agent-root", children: [
2123
- /* @__PURE__ */ jsxs6(
2792
+ return /* @__PURE__ */ jsxs8("div", { className: "webless-agent-root", children: [
2793
+ /* @__PURE__ */ jsxs8(
2124
2794
  "div",
2125
2795
  {
2126
2796
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2127
2797
  children: [
2128
- /* @__PURE__ */ jsx7(
2798
+ /* @__PURE__ */ jsx8(
2129
2799
  "div",
2130
2800
  {
2131
2801
  ref: railSlotRef,
2132
2802
  className: "webless-agent-root__rail-slot",
2133
2803
  inert: railCollapsed || void 0,
2134
2804
  "aria-hidden": railCollapsed,
2135
- children: /* @__PURE__ */ jsx7(
2805
+ children: /* @__PURE__ */ jsx8(
2136
2806
  AgentRail,
2137
2807
  {
2138
2808
  theme,
2139
2809
  brandLabel: agentName,
2140
2810
  brandLogoUrl: branding?.logoUrl,
2141
- composerPlaceholder: branding?.composerPlaceholder ?? `Ask ${agentName}\u2026`,
2811
+ composerPlaceholder: branding?.composerPlaceholder ?? "Ask as a visitor\u2026",
2142
2812
  poweredByLabel: branding?.poweredByLabel ?? "Powered by Webless",
2143
- state: idle ? {
2144
- ...state,
2145
- followUps: createIdleSuggestions()
2146
- } : state,
2813
+ state,
2147
2814
  mobileFullscreen: isMobile && !railCollapsed,
2148
2815
  expanded: railExpanded,
2149
2816
  onCollapse: !isMobile ? () => setRailCollapsed(true) : void 0,
@@ -2152,12 +2819,13 @@ function AgentWidget({
2152
2819
  onSubmit: handleSubmit,
2153
2820
  onReset: reset,
2154
2821
  onRetry: () => void retry(),
2155
- onFollowUpSelect: (label) => void handleSubmit(label)
2822
+ onFollowUpSelect: (label) => void handleSubmit(label),
2823
+ onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
2156
2824
  }
2157
2825
  )
2158
2826
  }
2159
2827
  ),
2160
- !railCollapsed && isMobile || !isMobile && railExpanded && !railCollapsed ? /* @__PURE__ */ jsx7(
2828
+ !railCollapsed && isMobile || !isMobile && railExpanded && !railCollapsed ? /* @__PURE__ */ jsx8(
2161
2829
  "button",
2162
2830
  {
2163
2831
  type: "button",
@@ -2176,7 +2844,7 @@ function AgentWidget({
2176
2844
  ]
2177
2845
  }
2178
2846
  ),
2179
- railCollapsed ? /* @__PURE__ */ jsx7(
2847
+ railCollapsed ? /* @__PURE__ */ jsx8(
2180
2848
  AssistEdgeTab,
2181
2849
  {
2182
2850
  variant: placement.variant,
@@ -2184,7 +2852,8 @@ function AgentWidget({
2184
2852
  along: placement.along,
2185
2853
  inset: placement.inset,
2186
2854
  visible: true,
2187
- label: agentName,
2855
+ label: tabLabel,
2856
+ customIconUrl: branding?.tabIconUrl,
2188
2857
  logoUrl: branding?.logoUrl,
2189
2858
  brandColor: branding?.colors?.primary,
2190
2859
  brandForeground: branding?.colors?.primaryForeground,
@@ -2215,4 +2884,4 @@ export {
2215
2884
  AssistEdgeTab,
2216
2885
  AgentWidget
2217
2886
  };
2218
- //# sourceMappingURL=chunk-SVWXFDV3.js.map
2887
+ //# sourceMappingURL=chunk-E4JSTYPX.js.map